diff --git a/contexts/VoiceContext.tsx b/contexts/VoiceContext.tsx index ddbce9f..397b398 100644 --- a/contexts/VoiceContext.tsx +++ b/contexts/VoiceContext.tsx @@ -17,6 +17,7 @@ import React, { useRef, ReactNode, } from 'react'; +import { Platform } from 'react-native'; import * as Speech from 'expo-speech'; import { api } from '@/services/api'; import { useVoiceTranscript } from './VoiceTranscriptContext'; @@ -322,10 +323,42 @@ export function VoiceProvider({ children }: { children: ReactNode }) { return responseText; } else { - // Token might be expired + // Token might be expired - retry with new token if (data.status === '401 Unauthorized') { + console.log('[VoiceContext] Token expired, retrying with new token...'); apiTokenRef.current = null; - throw new Error('Session expired, please try again'); + + // Get new token and retry request + const newToken = await getWellNuoToken(); + + const retryRequestParams: Record = { + function: voiceApiType, + clientId: 'MA_001', + user_name: WELLNUO_USER, + token: newToken, + question: normalizedQuestion, + deployment_id: deploymentId, + }; + + const retryResponse = await fetch(API_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams(retryRequestParams).toString(), + signal: abortController.signal, + }); + + const retryData = await retryResponse.json(); + + if (retryData.ok && retryData.response?.body) { + const responseText = retryData.response.body; + console.log('[VoiceContext] Retry succeeded:', responseText.slice(0, 100) + '...'); + setLastResponse(responseText); + addTranscriptEntry('assistant', responseText); + await speak(responseText); + return responseText; + } else { + throw new Error(retryData.message || 'Could not get response after retry'); + } } throw new Error(data.message || 'Could not get response'); } @@ -400,11 +433,15 @@ export function VoiceProvider({ children }: { children: ReactNode }) { }, onDone: () => { console.log('[VoiceContext] TTS completed'); - // Delay turning off green indicator to match STT restart delay (300ms) - // This keeps the visual indicator on during the transition period - setTimeout(() => { + // On iOS: Delay turning off green indicator to match STT restart delay (300ms) + // On Android: Turn off immediately (audio focus conflict with STT) + if (Platform.OS === 'ios') { + setTimeout(() => { + setIsSpeaking(false); + }, 300); + } else { setIsSpeaking(false); - }, 300); + } // Return to listening state after speaking (if session wasn't stopped) if (!sessionStoppedRef.current) { setStatus('listening');