@@ -160,32 +160,17 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
160160 // Track if OpenAI API key is configured for voice input
161161 const [ openAIKeySet , setOpenAIKeySet ] = useState ( false ) ;
162162
163- // Voice input handling - appends transcribed text to input
164- const handleVoiceTranscript = useCallback (
165- ( text : string , _isFinal : boolean ) => {
166- // Whisper only returns final results, append to input with space separator if needed
163+ // Voice input - appends transcribed text to input
164+ const voiceInput = useVoiceInput ( {
165+ onTranscript : ( text ) => {
167166 setInput ( ( prev ) => {
168167 const separator = prev . length > 0 && ! prev . endsWith ( " " ) ? " " : "" ;
169168 return prev + separator + text ;
170169 } ) ;
171170 } ,
172- [ setInput ]
173- ) ;
174-
175- const handleVoiceError = useCallback (
176- ( error : string ) => {
177- setToast ( {
178- id : Date . now ( ) . toString ( ) ,
179- type : "error" ,
180- message : error ,
181- } ) ;
171+ onError : ( error ) => {
172+ setToast ( { id : Date . now ( ) . toString ( ) , type : "error" , message : error } ) ;
182173 } ,
183- [ setToast ]
184- ) ;
185-
186- const voiceInput = useVoiceInput ( {
187- onTranscript : handleVoiceTranscript ,
188- onError : handleVoiceError ,
189174 onSend : ( ) => void handleSend ( ) ,
190175 openAIKeySet,
191176 } ) ;
@@ -508,7 +493,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
508493 } ) ;
509494 return ;
510495 }
511- voiceInput . toggleListening ( ) ;
496+ voiceInput . toggle ( ) ;
512497 } ;
513498 window . addEventListener ( CUSTOM_EVENTS . TOGGLE_VOICE_INPUT , handler as EventListener ) ;
514499 return ( ) =>
@@ -857,7 +842,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
857842 } ) ;
858843 return ;
859844 }
860- voiceInput . toggleListening ( ) ;
845+ voiceInput . toggle ( ) ;
861846 return ;
862847 }
863848
@@ -990,43 +975,42 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
990975 />
991976
992977 < div className = "relative flex items-end" data-component = "ChatInputControls" >
993- { /* Recording/transcribing overlay - dramatically replaces textarea */ }
994- { voiceInput . isListening || voiceInput . isTranscribing ? (
978+ { /* Recording/transcribing overlay - replaces textarea when active */ }
979+ { voiceInput . state !== "idle" ? (
995980 < button
996981 type = "button"
997982 ref = { ( el ) => el ?. focus ( ) }
998- onClick = { voiceInput . isListening ? voiceInput . toggleListening : undefined }
983+ onClick = { voiceInput . state === "recording" ? voiceInput . toggle : undefined }
999984 onKeyDown = { ( e ) => {
1000- // Space stops recording and sends immediately after transcription
1001- if ( e . key === " " && voiceInput . isListening ) {
985+ if ( e . key === " " && voiceInput . state === "recording" ) {
1002986 e . preventDefault ( ) ;
1003- voiceInput . stopListeningAndSend ( ) ;
987+ voiceInput . stop ( { send : true } ) ;
1004988 }
1005989 } }
1006- disabled = { voiceInput . isTranscribing }
990+ disabled = { voiceInput . state === "transcribing" }
1007991 className = { cn (
1008992 "mb-1 flex min-h-[60px] w-full items-center justify-center gap-3 rounded-md border px-4 py-4 transition-all" ,
1009- voiceInput . isListening
993+ voiceInput . state === "recording"
1010994 ? "cursor-pointer border-blue-500 bg-blue-500/10"
1011995 : "cursor-wait border-amber-500 bg-amber-500/10"
1012996 ) }
1013- aria-label = { voiceInput . isListening ? "Stop recording" : "Transcribing..." }
997+ aria-label = { voiceInput . state === "recording" ? "Stop recording" : "Transcribing..." }
1014998 >
1015999 < WaveformBars
1016- colorClass = { voiceInput . isListening ? "bg-blue-500" : "bg-amber-500" }
1000+ colorClass = { voiceInput . state === "recording" ? "bg-blue-500" : "bg-amber-500" }
10171001 />
10181002 < span
10191003 className = { cn (
10201004 "text-sm font-medium" ,
1021- voiceInput . isListening ? "text-blue-500" : "text-amber-500"
1005+ voiceInput . state === "recording" ? "text-blue-500" : "text-amber-500"
10221006 ) }
10231007 >
1024- { voiceInput . isListening
1008+ { voiceInput . state === "recording"
10251009 ? `Recording... space to send, ${ formatKeybind ( KEYBINDS . TOGGLE_VOICE_INPUT ) } to stop`
10261010 : "Transcribing..." }
10271011 </ span >
10281012 < WaveformBars
1029- colorClass = { voiceInput . isListening ? "bg-blue-500" : "bg-amber-500" }
1013+ colorClass = { voiceInput . state === "recording" ? "bg-blue-500" : "bg-amber-500" }
10301014 mirrored
10311015 />
10321016 </ button >
@@ -1057,12 +1041,10 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
10571041 { /* Floating voice input button inside textarea */ }
10581042 < div className = "absolute right-2 bottom-2" >
10591043 < VoiceInputButton
1060- isListening = { voiceInput . isListening }
1061- isTranscribing = { voiceInput . isTranscribing }
1062- isSupported = { voiceInput . isSupported }
1044+ state = { voiceInput . state }
10631045 isApiKeySet = { voiceInput . isApiKeySet }
10641046 shouldShowUI = { voiceInput . shouldShowUI }
1065- onToggle = { voiceInput . toggleListening }
1047+ onToggle = { voiceInput . toggle }
10661048 disabled = { disabled || isSending }
10671049 />
10681050 </ div >
0 commit comments