@@ -106,9 +106,32 @@ export function useVoiceChat() {
106106 } , [ ] ) ;
107107
108108 /**
109- * Callback per gestire i messaggi WebSocket
109+ * Ref stabile per le callback WebSocket.
110+ * Viene aggiornato ad ogni render in modo che VoiceBotWebSocket usi sempre
111+ * le callback correnti, anche dopo chiusura e riapertura del modal.
112+ * Questo risolve il problema "schermata statica ai numeri pari" causato da
113+ * closure stale catturate in initialize() al primo render.
110114 */
111- const websocketCallbacks : VoiceChatCallbacks = {
115+ const websocketCallbacksRef = useRef < VoiceChatCallbacks > ( { } ) ;
116+
117+ // Proxy stabile passato al costruttore VoiceBotWebSocket: delega sempre al ref corrente
118+ const stableCallbacks = useRef < VoiceChatCallbacks > ( {
119+ onConnectionOpen : ( ...args ) => websocketCallbacksRef . current . onConnectionOpen ?.( ...args ) ,
120+ onAuthenticationSuccess :( ...args ) => websocketCallbacksRef . current . onAuthenticationSuccess ?.( ...args ) ,
121+ onReady : ( ...args ) => websocketCallbacksRef . current . onReady ?.( ...args ) ,
122+ onAuthenticationFailed : ( ...args ) => websocketCallbacksRef . current . onAuthenticationFailed ?.( ...args ) ,
123+ onConnectionClose : ( ...args ) => websocketCallbacksRef . current . onConnectionClose ?.( ...args ) ,
124+ onStatus : ( ...args ) => websocketCallbacksRef . current . onStatus ?.( ...args ) ,
125+ onAudioChunk : ( ...args ) => websocketCallbacksRef . current . onAudioChunk ?.( ...args ) ,
126+ onTranscript : ( ...args ) => websocketCallbacksRef . current . onTranscript ?.( ...args ) ,
127+ onToolCall : ( ...args ) => websocketCallbacksRef . current . onToolCall ?.( ...args ) ,
128+ onToolOutput : ( ...args ) => websocketCallbacksRef . current . onToolOutput ?.( ...args ) ,
129+ onDone : ( ...args ) => websocketCallbacksRef . current . onDone ?.( ...args ) ,
130+ onError : ( ...args ) => websocketCallbacksRef . current . onError ?.( ...args ) ,
131+ } ) . current ;
132+
133+ // Aggiorna il ref ad ogni render con le callback che chiudono su stato/ref correnti
134+ websocketCallbacksRef . current = {
112135 onConnectionOpen : ( ) => {
113136 if ( ! isMountedRef . current ) return ;
114137 setState ( 'authenticating' ) ;
@@ -253,19 +276,26 @@ export function useVoiceChat() {
253276 isMutedRef . current = false ;
254277
255278 // Controlla se l'agente ha prodotto audio (risposta legittima) o era vuoto (ciclo spurio).
256- // I cicli spurii si verificano quando il server elabora audio residuo/rumore di avvio
257- // del microfono invece di vero parlato dell'utente (es. dopo chiamate MCP).
279+ // Un ciclo agente senza audio indica una fase di pianificazione tool call:
280+ // il modello ha deciso cosa fare ma non ha ancora parlato. Riavviare il mic
281+ // in questo momento crea una finestra (~374ms) in cui il mic invia audio
282+ // mentre il tool è in esecuzione → VAD trigge → risposta del secondo
283+ // ciclo interrotta prima ancora di partire.
284+ // Fix: al primo ciclo senza audio non riavviare il mic; il secondo ciclo
285+ // (con audio) gestirà correttamente il riavvio.
258286 if ( ! agentHadAudioRef . current ) {
259287 consecutiveEmptyAgentsRef . current ++ ;
260288 } else {
261289 consecutiveEmptyAgentsRef . current = 0 ;
262290 }
263291
264- // Se troppi cicli agente-vuoti consecutivi, non riavviare automaticamente.
265- // L'utente dovrà parlare manualmente (mic è già in stato 'ready').
266- if ( consecutiveEmptyAgentsRef . current >= 3 ) {
292+ // Non riavviare il mic se questo ciclo agente non ha prodotto audio.
293+ // Copre sia i cicli di pianificazione tool (1 ciclo vuoto) sia eventuali
294+ // cicli spurii multipli consecutivi.
295+ if ( consecutiveEmptyAgentsRef . current >= 1 ) {
267296 consecutiveEmptyAgentsRef . current = 0 ;
268- // Non riavviare il mic: l'utente deve parlare esplicitamente
297+ // Non riavviare il mic: l'agente sta ancora elaborando (tool call in corso).
298+ // Il ciclo successivo con audio gestirà il riavvio.
269299 break ;
270300 }
271301
@@ -456,7 +486,7 @@ export function useVoiceChat() {
456486
457487 audioRecorderRef . current = new AudioRecorder ( ) ;
458488 audioPlayerRef . current = new AudioPlayer ( ) ;
459- websocketRef . current = new VoiceBotWebSocket ( websocketCallbacks ) ;
489+ websocketRef . current = new VoiceBotWebSocket ( stableCallbacks ) ;
460490
461491 // Pre-inizializza TrackPlayer per evitare ritardi al primo chunk audio.
462492 // Questo elimina la race condition dove audio_end/agent_end arrivano
0 commit comments