@@ -12,7 +12,8 @@ import {
1212 PLAY_INTENT ,
1313} from "../components/Listening/VoiceConfirmationIntents" ;
1414
15- const RESPONSE_TIMEOUT = 15000 ;
15+ const CAPTURE_TIMEOUT = 10000 ;
16+ const AI_TIMEOUT = 30000 ;
1617const TIMEOUT_BEFORE_CLOSING_LISTENING_MS = 7500 ;
1718const OVERLAY_TRANSITION_DURATION_MS = 300 ;
1819
@@ -37,7 +38,8 @@ class VoiceStore {
3738 isMicMuted = localStorage . getItem ( "mockingbird_mic_muted" ) === "true" ;
3839 microphoneLevelsSlidingWindow = [ ] ;
3940 _wsCleanup = null ;
40- _responseTimeoutId = null ;
41+ _captureTimeoutId = null ;
42+ _aiTimeoutId = null ;
4143 _closeTimeoutId = null ;
4244 _micLevelIntervalId = null ;
4345
@@ -46,7 +48,8 @@ class VoiceStore {
4648 makeAutoObservable ( this , {
4749 rootStore : false ,
4850 _wsCleanup : false ,
49- _responseTimeoutId : false ,
51+ _captureTimeoutId : false ,
52+ _aiTimeoutId : false ,
5053 _closeTimeoutId : false ,
5154 _micLevelIntervalId : false ,
5255 } ) ;
@@ -110,28 +113,36 @@ class VoiceStore {
110113 if ( this . isMicMuted ) return ;
111114 this . resetVoiceSessionState ( ) ;
112115 overlayController . showVoice ( ) ;
113- this . _startResponseTimeout ( ) ;
116+ this . _startCaptureTimeout ( ) ;
114117 } ) ;
115118
116119 onTranscription = action ( ( data ) => {
117120 this . state . asr . transcript = data . transcript || "" ;
118121 this . state . asr . isFinal = ! ! data . is_final ;
119122 if ( data . is_final ) {
120123 this . micLevelMovingAverage = 0 ;
124+ this . _clearCaptureTimeout ( ) ;
125+ this . _startAITimeout ( ) ;
126+ } else {
127+ this . _startCaptureTimeout ( ) ;
121128 }
122129 } ) ;
123130
124131 onAIState = action ( ( data ) => {
125132 const prevState = this . state . aiState ;
126133 this . state . aiState = data . state || "idle" ;
134+ if ( data . state === "thinking" || data . state === "executing_tool" ) {
135+ this . _clearCaptureTimeout ( ) ;
136+ this . _startAITimeout ( ) ;
137+ }
127138 if ( data . state === "idle" && prevState === "speaking" ) {
128139 this . _scheduleClose ( ) ;
129140 }
130141 } ) ;
131142
132143 onAIResponse = action ( ( data ) => {
133144 this . state . aiResponse = data . text || "" ;
134- this . _clearResponseTimeout ( ) ;
145+ this . _clearAITimeout ( ) ;
135146 this . micLevelMovingAverage = 0 ;
136147 } ) ;
137148
@@ -151,6 +162,7 @@ class VoiceStore {
151162 this . state . showingVoiceConfirmation = true ;
152163 this . state . aiResponse = "" ;
153164 }
165+ this . _clearAITimeout ( ) ;
154166 } ) ;
155167
156168 _onMicLevel = action ( ( data ) => {
@@ -162,16 +174,18 @@ class VoiceStore {
162174 this . state . friendlyError = "" ;
163175 this . state . asr . transcript = "" ;
164176 this . state . asr . isFinal = false ;
165- this . _clearResponseTimeout ( ) ;
177+ this . _clearCaptureTimeout ( ) ;
178+ this . _clearAITimeout ( ) ;
166179 this . _clearCloseTimeout ( ) ;
167- this . _startResponseTimeout ( ) ;
180+ this . _startCaptureTimeout ( ) ;
168181 sendNocturneWsRequest ( "audio.record.start" , { } ) ;
169182 } ) ;
170183
171184 cancel = action ( ( ) => {
172185 sendNocturneWsRequest ( "audio.record.stop" , { } ) ;
173186 this . rootStore . overlayController . hideVoice ( ) ;
174- this . _clearResponseTimeout ( ) ;
187+ this . _clearCaptureTimeout ( ) ;
188+ this . _clearAITimeout ( ) ;
175189 this . _clearCloseTimeout ( ) ;
176190 this . _stopSyntheticMicLevel ( ) ;
177191 setTimeout (
@@ -194,29 +208,50 @@ class VoiceStore {
194208
195209 resetVoiceSessionState = action ( ( ) => {
196210 this . state = getInitialVoiceSessionState ( ) ;
197- this . _clearResponseTimeout ( ) ;
211+ this . _clearCaptureTimeout ( ) ;
212+ this . _clearAITimeout ( ) ;
198213 this . _clearCloseTimeout ( ) ;
199214 this . _stopSyntheticMicLevel ( ) ;
200215 this . micLevelMovingAverage = 0 ;
201216 } ) ;
202217
203- _startResponseTimeout ( ) {
204- this . _clearResponseTimeout ( ) ;
205- this . _responseTimeoutId = setTimeout (
218+ _startCaptureTimeout ( ) {
219+ this . _clearCaptureTimeout ( ) ;
220+ this . _captureTimeoutId = setTimeout (
221+ action ( ( ) => {
222+ this . state . error = "error" ;
223+ this . state . friendlyError = "Something went wrong. Tap to try again." ;
224+ this . _stopSyntheticMicLevel ( ) ;
225+ this . _scheduleClose ( ) ;
226+ } ) ,
227+ CAPTURE_TIMEOUT ,
228+ ) ;
229+ }
230+
231+ _clearCaptureTimeout ( ) {
232+ if ( this . _captureTimeoutId ) {
233+ clearTimeout ( this . _captureTimeoutId ) ;
234+ this . _captureTimeoutId = null ;
235+ }
236+ }
237+
238+ _startAITimeout ( ) {
239+ this . _clearAITimeout ( ) ;
240+ this . _aiTimeoutId = setTimeout (
206241 action ( ( ) => {
207242 this . state . error = "error" ;
208243 this . state . friendlyError = "Something went wrong. Tap to try again." ;
209244 this . _stopSyntheticMicLevel ( ) ;
210245 this . _scheduleClose ( ) ;
211246 } ) ,
212- RESPONSE_TIMEOUT ,
247+ AI_TIMEOUT ,
213248 ) ;
214249 }
215250
216- _clearResponseTimeout ( ) {
217- if ( this . _responseTimeoutId ) {
218- clearTimeout ( this . _responseTimeoutId ) ;
219- this . _responseTimeoutId = null ;
251+ _clearAITimeout ( ) {
252+ if ( this . _aiTimeoutId ) {
253+ clearTimeout ( this . _aiTimeoutId ) ;
254+ this . _aiTimeoutId = null ;
220255 }
221256 }
222257
@@ -266,7 +301,8 @@ class VoiceStore {
266301 this . _wsCleanup ( ) ;
267302 this . _wsCleanup = null ;
268303 }
269- this . _clearResponseTimeout ( ) ;
304+ this . _clearCaptureTimeout ( ) ;
305+ this . _clearAITimeout ( ) ;
270306 this . _clearCloseTimeout ( ) ;
271307 this . _stopSyntheticMicLevel ( ) ;
272308 }
0 commit comments