Fix Android voice bugs - STT restart and token retry
Critical Android fixes: BUG 1 - STT not restarting after TTS: - Problem: isSpeaking delay (300ms iOS visual) blocked Android STT - Android audio focus conflict: STT cannot start while isSpeaking=true - Fix: Platform-specific isSpeaking timing - iOS: 300ms delay (smooth visual indicator) - Android: immediate (allows STT to restart) BUG 2 - Session expired loop: - Problem: 401 error → token reset → no retry → user hears error - Fix: Automatic token refresh and retry on 401 - Flow: 401 → clear token → get new token → retry request - User never hears "Session expired" unless retry also fails contexts/VoiceContext.tsx:12-23,387-360
This commit is contained in:
parent
29fb3c1026
commit
8c0e36cae3
@ -17,6 +17,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
import * as Speech from 'expo-speech';
|
import * as Speech from 'expo-speech';
|
||||||
import { api } from '@/services/api';
|
import { api } from '@/services/api';
|
||||||
import { useVoiceTranscript } from './VoiceTranscriptContext';
|
import { useVoiceTranscript } from './VoiceTranscriptContext';
|
||||||
@ -322,10 +323,42 @@ export function VoiceProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
return responseText;
|
return responseText;
|
||||||
} else {
|
} else {
|
||||||
// Token might be expired
|
// Token might be expired - retry with new token
|
||||||
if (data.status === '401 Unauthorized') {
|
if (data.status === '401 Unauthorized') {
|
||||||
|
console.log('[VoiceContext] Token expired, retrying with new token...');
|
||||||
apiTokenRef.current = null;
|
apiTokenRef.current = null;
|
||||||
throw new Error('Session expired, please try again');
|
|
||||||
|
// Get new token and retry request
|
||||||
|
const newToken = await getWellNuoToken();
|
||||||
|
|
||||||
|
const retryRequestParams: Record<string, string> = {
|
||||||
|
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');
|
throw new Error(data.message || 'Could not get response');
|
||||||
}
|
}
|
||||||
@ -400,11 +433,15 @@ export function VoiceProvider({ children }: { children: ReactNode }) {
|
|||||||
},
|
},
|
||||||
onDone: () => {
|
onDone: () => {
|
||||||
console.log('[VoiceContext] TTS completed');
|
console.log('[VoiceContext] TTS completed');
|
||||||
// Delay turning off green indicator to match STT restart delay (300ms)
|
// On iOS: Delay turning off green indicator to match STT restart delay (300ms)
|
||||||
// This keeps the visual indicator on during the transition period
|
// On Android: Turn off immediately (audio focus conflict with STT)
|
||||||
setTimeout(() => {
|
if (Platform.OS === 'ios') {
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsSpeaking(false);
|
||||||
|
}, 300);
|
||||||
|
} else {
|
||||||
setIsSpeaking(false);
|
setIsSpeaking(false);
|
||||||
}, 300);
|
}
|
||||||
// Return to listening state after speaking (if session wasn't stopped)
|
// Return to listening state after speaking (if session wasn't stopped)
|
||||||
if (!sessionStoppedRef.current) {
|
if (!sessionStoppedRef.current) {
|
||||||
setStatus('listening');
|
setStatus('listening');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user