11import { useState , useEffect , useRef , useCallback } from 'react'
22import { useSettings } from '@/hooks/useSettings'
3- import { getWebSpeechRecognizer , isWebRecognitionSupported , type SpeechRecognitionOptions , type SpeechRecognitionResult , type RecognitionState } from '@/lib/webSpeechRecognizer'
3+ import { getWebSpeechRecognizer , isWebRecognitionSupported , appendTranscriptSegment , type SpeechRecognitionOptions , type SpeechRecognitionResult , type RecognitionState } from '@/lib/webSpeechRecognizer'
44import { AudioRecorder } from '@/lib/audioRecorder'
55import { sttApi } from '@/api/stt'
66import { FetchError } from '@/api/fetchWrapper'
@@ -42,6 +42,24 @@ export function useSTT(userId = 'default') {
4242 ? AudioRecorder . isSupported ( )
4343 : isWebRecognitionSupported ( )
4444
45+ const flashError = useCallback ( ( message : string ) => {
46+ setIsError ( true )
47+ setError ( message )
48+ if ( errorTimeoutRef . current ) clearTimeout ( errorTimeoutRef . current )
49+ errorTimeoutRef . current = setTimeout ( ( ) => {
50+ setIsError ( false )
51+ setError ( null )
52+ errorTimeoutRef . current = null
53+ } , 3000 )
54+ } , [ ] )
55+
56+ const resetToIdle = useCallback ( ( clearInterim = false ) => {
57+ setIsRecording ( false )
58+ setIsProcessing ( false )
59+ setState ( 'idle' )
60+ if ( clearInterim ) setInterimTranscript ( '' )
61+ } , [ ] )
62+
4563 useEffect ( ( ) => {
4664 if ( ! isEnabled || isExternalProvider ) {
4765 return
@@ -55,11 +73,7 @@ export function useSTT(userId = 'default') {
5573
5674 rec . onResult ( ( result : SpeechRecognitionResult ) => {
5775 setIsProcessing ( false )
58- setTranscript ( ( prev ) => {
59- const prevTrimmed = prev . trim ( )
60- const next = result . transcript . trim ( )
61- return prevTrimmed ? `${ prevTrimmed } ${ next } ` : next
62- } )
76+ setTranscript ( ( prev ) => appendTranscriptSegment ( prev , result . transcript ) )
6377 } )
6478
6579 rec . onInterimResult ( ( interim : string ) => {
@@ -74,21 +88,11 @@ export function useSTT(userId = 'default') {
7488 rec . onError ( ( errorMessage : string ) => {
7589 setIsProcessing ( false )
7690 setIsRecording ( false )
77- setIsError ( true )
78- setError ( errorMessage )
79-
80- if ( errorTimeoutRef . current ) clearTimeout ( errorTimeoutRef . current )
81- errorTimeoutRef . current = setTimeout ( ( ) => {
82- setIsError ( false )
83- setError ( null )
84- errorTimeoutRef . current = null
85- } , 3000 )
91+ flashError ( errorMessage )
8692 } )
8793
8894 rec . onEnd ( ( ) => {
89- setIsRecording ( false )
90- setIsProcessing ( false )
91- setState ( 'idle' )
95+ resetToIdle ( )
9296 } )
9397
9498 rec . onStart ( ( ) => {
@@ -105,7 +109,7 @@ export function useSTT(userId = 'default') {
105109 interimRafRef . current = null
106110 }
107111 }
108- } , [ isEnabled , isExternalProvider ] )
112+ } , [ isEnabled , isExternalProvider , flashError , resetToIdle ] )
109113
110114 const setupAudioRecorder = useCallback ( ( recorder : AudioRecorder ) => {
111115 recorder . setOnStateChange ( ( recState ) => {
@@ -117,9 +121,7 @@ export function useSTT(userId = 'default') {
117121 } else if ( recState === 'stopped' ) {
118122 setIsRecording ( false )
119123 } else if ( recState === 'error' ) {
120- setIsRecording ( false )
121- setIsProcessing ( false )
122- setState ( 'idle' )
124+ resetToIdle ( )
123125 } else if ( recState === 'idle' ) {
124126 setState ( 'idle' )
125127 }
@@ -128,22 +130,11 @@ export function useSTT(userId = 'default') {
128130 recorder . setOnError ( ( errorMessage ) => {
129131 setIsProcessing ( false )
130132 setIsRecording ( false )
131- setIsError ( true )
132- setError ( errorMessage )
133-
134- if ( errorTimeoutRef . current ) clearTimeout ( errorTimeoutRef . current )
135- errorTimeoutRef . current = setTimeout ( ( ) => {
136- setIsError ( false )
137- setError ( null )
138- errorTimeoutRef . current = null
139- } , 3000 )
133+ flashError ( errorMessage )
140134 } )
141135
142136 recorder . setOnNoSpeech ( ( ) => {
143- setIsProcessing ( false )
144- setIsRecording ( false )
145- setInterimTranscript ( '' )
146- setState ( 'idle' )
137+ resetToIdle ( true )
147138 } )
148139
149140 recorder . setOnDataAvailable ( async ( blob ) => {
@@ -163,11 +154,7 @@ export function useSTT(userId = 'default') {
163154 abortControllerRef . current . signal
164155 )
165156
166- setTranscript ( ( prev ) => {
167- const prevTrimmed = prev . trim ( )
168- const newText = result . text . trim ( )
169- return prevTrimmed ? `${ prevTrimmed } ${ newText } ` : newText
170- } )
157+ setTranscript ( ( prev ) => appendTranscriptSegment ( prev , result . text ) )
171158 setInterimTranscript ( '' )
172159 } catch ( err ) {
173160 if ( err instanceof Error && (
@@ -177,23 +164,14 @@ export function useSTT(userId = 'default') {
177164 return
178165 }
179166
180- setIsError ( true )
181- const errorMessage = err instanceof Error ? err . message : 'Transcription failed'
182- setError ( errorMessage )
183-
184- if ( errorTimeoutRef . current ) clearTimeout ( errorTimeoutRef . current )
185- errorTimeoutRef . current = setTimeout ( ( ) => {
186- setIsError ( false )
187- setError ( null )
188- errorTimeoutRef . current = null
189- } , 3000 )
167+ flashError ( err instanceof Error ? err . message : 'Transcription failed' )
190168 } finally {
191169 setIsProcessing ( false )
192170 setState ( 'idle' )
193171 abortControllerRef . current = null
194172 }
195173 } )
196- } , [ ] )
174+ } , [ flashError , resetToIdle ] )
197175
198176 const ensureAudioRecorder = useCallback ( ( ) : AudioRecorder => {
199177 if ( ! audioRecorder . current ) {
@@ -233,12 +211,10 @@ export function useSTT(userId = 'default') {
233211 } else {
234212 recognizer . current . abort ( )
235213 }
236- setIsRecording ( false )
237- setIsProcessing ( false )
238- setState ( 'idle' )
214+ resetToIdle ( )
239215 setIsError ( true )
240216 setError ( 'Microphone start timed out' )
241- } , [ isExternalProvider , disposeAudioRecorder ] )
217+ } , [ isExternalProvider , disposeAudioRecorder , resetToIdle ] )
242218
243219 const runStartupWithTimeout = useCallback (
244220 async ( startup : ( ) => Promise < void > , startOpId : number ) : Promise < boolean > => {
@@ -340,21 +316,15 @@ export function useSTT(userId = 'default') {
340316 }
341317
342318 setTranscript ( '' )
343- setInterimTranscript ( '' )
344- setIsRecording ( false )
345- setIsProcessing ( false )
346- setState ( 'idle' )
347- } , [ isExternalProvider ] )
319+ resetToIdle ( true )
320+ } , [ isExternalProvider , resetToIdle ] )
348321
349322 const reset = useCallback ( ( ) => {
350323 setTranscript ( '' )
351- setInterimTranscript ( '' )
352324 setIsError ( false )
353325 setError ( null )
354- setIsRecording ( false )
355- setIsProcessing ( false )
356- setState ( 'idle' )
357- } , [ ] )
326+ resetToIdle ( true )
327+ } , [ resetToIdle ] )
358328
359329 const clear = useCallback ( ( ) => {
360330 setTranscript ( '' )
0 commit comments