diff --git a/packages/ui/src/components/prompt-input.tsx b/packages/ui/src/components/prompt-input.tsx index 1ae41690..30a7c38f 100644 --- a/packages/ui/src/components/prompt-input.tsx +++ b/packages/ui/src/components/prompt-input.tsx @@ -1,5 +1,5 @@ import { createSignal, Show, onMount, onCleanup, createEffect, on } from "solid-js" -import { ArrowBigUp, ArrowBigDown, Loader2, Mic, Square } from "lucide-solid" +import { ArrowBigUp, ArrowBigDown, Loader2, Mic } from "lucide-solid" import UnifiedPicker from "./unified-picker" import ExpandButton from "./expand-button" import { clearAttachments, removeAttachment } from "../stores/attachments" @@ -425,6 +425,32 @@ export default function PromptInput(props: PromptInputProps) { const instance = () => getActiveInstance() + let voiceButtonPressed = false + + const beginVoicePress = (event?: PointerEvent | KeyboardEvent) => { + if (voiceButtonPressed || props.disabled || voiceInput.isTranscribing() || !voiceInput.canUseVoiceInput()) return + voiceButtonPressed = true + + if (event instanceof PointerEvent) { + const target = event.currentTarget + if (target instanceof HTMLElement) { + try { + target.setPointerCapture(event.pointerId) + } catch { + // no-op + } + } + } + + void voiceInput.startRecording() + } + + const endVoicePress = () => { + if (!voiceButtonPressed) return + voiceButtonPressed = false + voiceInput.stopRecording() + } + return (
void voiceInput.toggleRecording()} + onPointerDown={(event) => { + event.preventDefault() + beginVoicePress(event) + }} + onPointerUp={(event) => { + event.preventDefault() + endVoicePress() + }} + onPointerCancel={() => endVoicePress()} + onLostPointerCapture={() => endVoicePress()} + onKeyDown={(event) => { + if (event.repeat) return + if (event.key !== " " && event.key !== "Enter") return + event.preventDefault() + beginVoicePress(event) + }} + onKeyUp={(event) => { + if (event.key !== " " && event.key !== "Enter") return + event.preventDefault() + endVoicePress() + }} + onBlur={() => endVoicePress()} disabled={!voiceInput.isRecording() && (props.disabled || voiceInput.isTranscribing() || !voiceInput.canUseVoiceInput())} aria-label={voiceInput.buttonTitle()} title={voiceInput.buttonTitle()} > - - {formatVoiceTimer(voiceInput.elapsedMs())} -