/ src / hooks / useClipboardImageHint.ts
useClipboardImageHint.ts
 1  import { useEffect, useRef } from 'react'
 2  import { useNotifications } from '../context/notifications.js'
 3  import { getShortcutDisplay } from '../keybindings/shortcutFormat.js'
 4  import { hasImageInClipboard } from '../utils/imagePaste.js'
 5  
 6  const NOTIFICATION_KEY = 'clipboard-image-hint'
 7  // Small debounce to batch rapid focus changes
 8  const FOCUS_CHECK_DEBOUNCE_MS = 1000
 9  // Don't show the hint more than once per this interval
10  const HINT_COOLDOWN_MS = 30000
11  
12  /**
13   * Hook that shows a notification when the terminal regains focus
14   * and the clipboard contains an image.
15   *
16   * @param isFocused - Whether the terminal is currently focused
17   * @param enabled - Whether image paste is enabled (onImagePaste is defined)
18   */
19  export function useClipboardImageHint(
20    isFocused: boolean,
21    enabled: boolean,
22  ): void {
23    const { addNotification } = useNotifications()
24    const lastFocusedRef = useRef(isFocused)
25    const lastHintTimeRef = useRef(0)
26    const checkTimeoutRef = useRef<NodeJS.Timeout | null>(null)
27  
28    useEffect(() => {
29      // Only trigger on focus regain (was unfocused, now focused)
30      const wasFocused = lastFocusedRef.current
31      lastFocusedRef.current = isFocused
32  
33      if (!enabled || !isFocused || wasFocused) {
34        return
35      }
36  
37      // Clear any pending check
38      if (checkTimeoutRef.current) {
39        clearTimeout(checkTimeoutRef.current)
40      }
41  
42      // Small debounce to batch rapid focus changes
43      checkTimeoutRef.current = setTimeout(
44        async (checkTimeoutRef, lastHintTimeRef, addNotification) => {
45          checkTimeoutRef.current = null
46  
47          // Check cooldown to avoid spamming the user
48          const now = Date.now()
49          if (now - lastHintTimeRef.current < HINT_COOLDOWN_MS) {
50            return
51          }
52  
53          // Check if clipboard has an image (async osascript call)
54          if (await hasImageInClipboard()) {
55            lastHintTimeRef.current = now
56            addNotification({
57              key: NOTIFICATION_KEY,
58              text: `Image in clipboard ยท ${getShortcutDisplay('chat:imagePaste', 'Chat', 'ctrl+v')} to paste`,
59              priority: 'immediate',
60              timeoutMs: 8000,
61            })
62          }
63        },
64        FOCUS_CHECK_DEBOUNCE_MS,
65        checkTimeoutRef,
66        lastHintTimeRef,
67        addNotification,
68      )
69  
70      return () => {
71        if (checkTimeoutRef.current) {
72          clearTimeout(checkTimeoutRef.current)
73          checkTimeoutRef.current = null
74        }
75      }
76    }, [isFocused, enabled, addNotification])
77  }