MCPcopy
hub / github.com/jamiepine/voicebox / DictateWindow

Function DictateWindow

app/src/components/DictateWindow/DictateWindow.tsx:24–298  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

22 * ``dictate:hide`` so Rust tucks the window away.
23 */
24export function DictateWindow() {
25 // Force the host document chrome to be transparent so the Tauri window
26 // takes on the pill's own shape.
27 useEffect(() => {
28 const prevHtml = document.documentElement.style.background;
29 const prevBody = document.body.style.background;
30 document.documentElement.style.background = 'transparent';
31 document.body.style.background = 'transparent';
32 return () => {
33 document.documentElement.style.background = prevHtml;
34 document.body.style.background = prevBody;
35 };
36 }, []);
37
38 // Snapshot of the focused UI element at chord-start, shipped over from
39 // Rust on the ``dictate:start`` payload. Held in a ref so it survives
40 // the 1–2 s transcribe + refine window — the paste only fires once the
41 // final text comes back.
42 const focusRef = useRef<FocusSnapshot | null>(null);
43
44 const session = useCaptureRecordingSession({
45 onFinalText: async (text, _capture, allowAutoPaste) => {
46 const focus = focusRef.current;
47 // Consume-once: a second chord before this fires would overwrite
48 // focusRef, but nulling it here guards against the late-arriving
49 // refine-result firing a paste after the user has moved on.
50 focusRef.current = null;
51 if (!allowAutoPaste) return;
52 if (!focus || !text.trim()) return;
53 try {
54 await invoke('paste_final_text', { text, focus });
55 } catch (err) {
56 // Surface accessibility failures to the main window so it can prompt
57 // the user to grant permission. Other errors stay swallowed —
58 // the transcription still landed in the captures list.
59 const msg = err instanceof Error ? err.message : String(err);
60 if (/accessibility/i.test(msg)) {
61 emit('system:accessibility-missing').catch(() => {});
62 }
63 console.warn('[dictate] paste_final_text failed:', err);
64 }
65 },
66 });
67
68 // Route the chord events emitted from Rust into the session hook. Using a
69 // ref so the `listen` effect only subscribes once — rebinding every render
70 // would thrash the Tauri event bridge.
71 const sessionRef = useRef(session);
72 sessionRef.current = session;
73
74 useEffect(() => {
75 const unlistens: Promise<UnlistenFn>[] = [];
76 unlistens.push(
77 listen<{ focus: FocusSnapshot | null }>('dictate:start', (event) => {
78 focusRef.current = event.payload?.focus ?? null;
79 sessionRef.current.startRecording();
80 }),
81 );

Callers

nothing calls this directly

Calls 8

dismissSpeakFunction · 0.85
clearStatusTimeoutFunction · 0.85
startSpeakPlaybackFunction · 0.85
catchMethod · 0.80
thenMethod · 0.80
closeMethod · 0.45

Tested by

no test coverage detected