- Merge staging workflow parent and resolve conflicts with English/i18n and WS pool - Re-greeting: language-aware fallbacks and prompts; router passes user_language - RealtimeSession: topic suggestion callbacks + TTS sync path preserved Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { AppState, type AppStateStatus } from 'react-native';
|
||||
|
||||
import i18n from '@/i18n';
|
||||
import type { WsConnectionState } from '@/core/ws/types';
|
||||
import type { TopicSuggestion, WsConnectionState } from '@/core/ws/types';
|
||||
|
||||
import { conversationApi } from './api';
|
||||
import {
|
||||
@@ -196,12 +196,11 @@ interface RealtimeSessionState {
|
||||
/** 已发出用户消息,尚未收到助手首段流式文本(用于「正在回复」气泡) */
|
||||
awaitingAssistantReply: boolean;
|
||||
error: string | null;
|
||||
sendText: (text: string, options?: { ttsThisTurn?: boolean }) => void;
|
||||
sendVoiceMessage: (
|
||||
uri: string,
|
||||
durationMs: number,
|
||||
options?: { ttsThisTurn?: boolean },
|
||||
) => Promise<boolean>;
|
||||
/** 服务端下发的 quick-start 话题 chips;用户首次发文本/语音后清空 */
|
||||
topicSuggestions: TopicSuggestion[];
|
||||
dismissTopicSuggestions: () => void;
|
||||
sendText: (text: string) => void;
|
||||
sendVoiceMessage: (uri: string, durationMs: number) => Promise<boolean>;
|
||||
sendEndConversation: () => void;
|
||||
sendTtsCancel: () => void;
|
||||
requestAssistantSegmentTts: (body: {
|
||||
@@ -237,6 +236,9 @@ export function useRealtimeSession({
|
||||
useState<StreamingAgentMessage | null>(null);
|
||||
const [awaitingAssistantReply, setAwaitingAssistantReply] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [topicSuggestions, setTopicSuggestions] = useState<TopicSuggestion[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const [foregroundResumeGeneration, setForegroundResumeGeneration] =
|
||||
useState(0);
|
||||
@@ -270,29 +272,16 @@ export function useRealtimeSession({
|
||||
setError(message);
|
||||
}, []);
|
||||
|
||||
uiRef.current.handleStreamingText = handleStreamingText;
|
||||
uiRef.current.handleError = handleError;
|
||||
uiRef.current.onTtsSegment = onTtsSegment;
|
||||
const handleTopicSuggestions = useCallback(
|
||||
(payload: { suggestions: TopicSuggestion[] }) => {
|
||||
setTopicSuggestions(payload.suggestions);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !conversationId) return;
|
||||
|
||||
const sub = AppState.addEventListener('change', (next: AppStateStatus) => {
|
||||
if (next === 'background') {
|
||||
needsResumeAfterBackgroundRef.current = true;
|
||||
disposeAllBackgroundConversationWs();
|
||||
sessionRef.current = null;
|
||||
setConnectionState('disconnected');
|
||||
setStreamingMessage(null);
|
||||
setAwaitingAssistantReply(false);
|
||||
} else if (next === 'active' && needsResumeAfterBackgroundRef.current) {
|
||||
needsResumeAfterBackgroundRef.current = false;
|
||||
setForegroundResumeGeneration((g) => g + 1);
|
||||
}
|
||||
});
|
||||
|
||||
return () => sub.remove();
|
||||
}, [enabled, conversationId]);
|
||||
const dismissTopicSuggestions = useCallback(() => {
|
||||
setTopicSuggestions([]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !conversationId) return;
|
||||
@@ -310,6 +299,8 @@ export function useRealtimeSession({
|
||||
uiRef.current.handleStreamingText(text, isComplete);
|
||||
},
|
||||
onTtsSegment: (payload) => uiRef.current.onTtsSegment?.(payload),
|
||||
onTopicSuggestions: (payload) =>
|
||||
handleTopicSuggestions({ suggestions: payload.suggestions }),
|
||||
onError: (message, code) => uiRef.current.handleError(message, code),
|
||||
onStateChange: setConnectionState,
|
||||
},
|
||||
@@ -325,8 +316,17 @@ export function useRealtimeSession({
|
||||
setConnectionState('disconnected');
|
||||
setStreamingMessage(null);
|
||||
setAwaitingAssistantReply(false);
|
||||
setTopicSuggestions([]);
|
||||
};
|
||||
}, [conversationId, enabled, queryClient, foregroundResumeGeneration]);
|
||||
}, [
|
||||
conversationId,
|
||||
enabled,
|
||||
queryClient,
|
||||
handleStreamingText,
|
||||
handleError,
|
||||
handleTopicSuggestions,
|
||||
onTtsSegment,
|
||||
]);
|
||||
|
||||
const sendText = useCallback(
|
||||
(text: string, options?: { ttsThisTurn?: boolean }) => {
|
||||
@@ -341,6 +341,7 @@ export function useRealtimeSession({
|
||||
onUserSendTtsPreference?.(options?.ttsThisTurn === true);
|
||||
|
||||
setAwaitingAssistantReply(true);
|
||||
setTopicSuggestions([]);
|
||||
onTtsPlaybackResume?.();
|
||||
|
||||
const localId = `pending_${Date.now()}`;
|
||||
@@ -407,6 +408,7 @@ export function useRealtimeSession({
|
||||
onUserSendTtsPreference?.(options?.ttsThisTurn === true);
|
||||
|
||||
setAwaitingAssistantReply(true);
|
||||
setTopicSuggestions([]);
|
||||
const localId = `pending_voice_${Date.now()}`;
|
||||
await voiceSegmentStore.recordSentSegment({
|
||||
voiceSessionId,
|
||||
@@ -475,6 +477,8 @@ export function useRealtimeSession({
|
||||
streamingMessage,
|
||||
awaitingAssistantReply,
|
||||
error,
|
||||
topicSuggestions,
|
||||
dismissTopicSuggestions,
|
||||
sendText,
|
||||
sendVoiceMessage,
|
||||
sendEndConversation,
|
||||
|
||||
Reference in New Issue
Block a user