diff --git a/desktop/src/app/AppShell.tsx b/desktop/src/app/AppShell.tsx index 5d06c7227..087897c6f 100644 --- a/desktop/src/app/AppShell.tsx +++ b/desktop/src/app/AppShell.tsx @@ -597,7 +597,7 @@ export function AppShell() { if (key === "d" && !event.shiftKey) { event.preventDefault(); - window.dispatchEvent(new CustomEvent("buzz:toggle-dictation")); + window.dispatchEvent(new CustomEvent("buzz:dictation-key-down")); return; } @@ -608,9 +608,17 @@ export function AppShell() { } } + function handleKeyUp(event: KeyboardEvent) { + if (event.key.toLowerCase() === "d") { + window.dispatchEvent(new CustomEvent("buzz:dictation-key-up")); + } + } + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); return () => { window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); }; }, [ handleOpenBrowseChannels, diff --git a/desktop/src/features/dictation/hooks/useComposerDictation.ts b/desktop/src/features/dictation/hooks/useComposerDictation.ts index 46e252d15..e230bcae2 100644 --- a/desktop/src/features/dictation/hooks/useComposerDictation.ts +++ b/desktop/src/features/dictation/hooks/useComposerDictation.ts @@ -71,16 +71,31 @@ export function useComposerDictation({ } }, [disabled, dictation.isRecording, dictation.cancelRecording]); - // ⌘D global shortcut — dispatched from AppShell's keydown handler. + // ⌘D push-to-talk — hold to record, release to stop. + // Dispatched from AppShell's keydown/keyup handlers. useEffect(() => { - function handleToggle() { - dictation.toggleRecording(); + function handleKeyDown() { + if (!dictation.isRecording && !dictation.isStarting) { + dictation.startRecording(); + } } - window.addEventListener("buzz:toggle-dictation", handleToggle); + function handleKeyUp() { + if (dictation.isRecording || dictation.isStarting) { + dictation.stopRecording(); + } + } + window.addEventListener("buzz:dictation-key-down", handleKeyDown); + window.addEventListener("buzz:dictation-key-up", handleKeyUp); return () => { - window.removeEventListener("buzz:toggle-dictation", handleToggle); + window.removeEventListener("buzz:dictation-key-down", handleKeyDown); + window.removeEventListener("buzz:dictation-key-up", handleKeyUp); }; - }, [dictation.toggleRecording]); + }, [ + dictation.isRecording, + dictation.isStarting, + dictation.startRecording, + dictation.stopRecording, + ]); return dictation; } diff --git a/desktop/src/features/dictation/ui/DictationButton.tsx b/desktop/src/features/dictation/ui/DictationButton.tsx index 8f23d904b..f284957e1 100644 --- a/desktop/src/features/dictation/ui/DictationButton.tsx +++ b/desktop/src/features/dictation/ui/DictationButton.tsx @@ -1,4 +1,4 @@ -import { Mic } from "lucide-react"; +import { Mic, Square } from "lucide-react"; import { Button } from "@/shared/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; import { cn } from "@/shared/lib/cn"; @@ -51,7 +51,11 @@ export function DictationButton({ type="button" variant={dictation.isRecording ? "default" : "ghost"} > - + {dictation.isRecording ? ( + + ) : ( + + )} diff --git a/desktop/src/shared/lib/keyboard-shortcuts.ts b/desktop/src/shared/lib/keyboard-shortcuts.ts index 8887ec4e1..fa9dd442f 100644 --- a/desktop/src/shared/lib/keyboard-shortcuts.ts +++ b/desktop/src/shared/lib/keyboard-shortcuts.ts @@ -184,7 +184,7 @@ export const KEYBOARD_SHORTCUTS: KeyboardShortcut[] = [ { id: "voice-dictation", label: "Voice Dictation", - description: "Start or stop voice dictation in the composer", + description: "Hold to dictate, release to stop (push-to-talk)", keys: "⌘D", keysWindows: "Ctrl+D", category: "Messages",