Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 additions & 7 deletions src/browser/hooks/useVoiceInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,38 @@ const HAS_MEDIA_RECORDER = typeof window !== "undefined" && typeof MediaRecorder
const HAS_GET_USER_MEDIA =
typeof window !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function";

// =============================================================================
// Global Key State Tracking
// =============================================================================

/**
* Track whether space is currently pressed at the module level.
* This runs outside React's render cycle, so it captures key state
* accurately even during async operations like microphone access.
*/
let isSpaceCurrentlyHeld = false;

if (typeof window !== "undefined") {
window.addEventListener(
"keydown",
(e) => {
if (e.key === " ") isSpaceCurrentlyHeld = true;
},
true
);
window.addEventListener(
"keyup",
(e) => {
if (e.key === " ") isSpaceCurrentlyHeld = false;
},
true
);
// Also reset on blur (user switches window while holding space)
window.addEventListener("blur", () => {
isSpaceCurrentlyHeld = false;
});
}

// =============================================================================
// Hook
// =============================================================================
Expand Down Expand Up @@ -248,24 +280,24 @@ export function useVoiceInput(options: UseVoiceInputOptions): UseVoiceInputResul
// Recording keybinds (when useRecordingKeybinds is true)
// ---------------------------------------------------------------------------

// Track if space is held to prevent start→send when user holds space
const spaceHeldRef = useRef(false);
// Track if space was held when recording started to prevent immediate send
const spaceHeldAtStartRef = useRef(false);

useEffect(() => {
if (!options.useRecordingKeybinds || state !== "recording") {
spaceHeldRef.current = false;
spaceHeldAtStartRef.current = false;
return;
}

// Assume space is held when recording starts (conservative default)
spaceHeldRef.current = true;
// Use global key state instead of assuming - handles async mic access delay
spaceHeldAtStartRef.current = isSpaceCurrentlyHeld;

const handleKeyUp = (e: KeyboardEvent) => {
if (e.key === " ") spaceHeldRef.current = false;
if (e.key === " ") spaceHeldAtStartRef.current = false;
};

const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === " " && !spaceHeldRef.current) {
if (e.key === " " && !spaceHeldAtStartRef.current) {
e.preventDefault();
stop({ send: true });
} else if (e.key === "Escape") {
Expand Down