fix(conversation): 修复实时会话 TTS/回复被离屏 WS 抢占

- 列表预热仅预取消息缓存,避免后台 WebSocket 覆盖服务端连接
- RealtimeSession UI 回调按 owner 独占,防止 offscreen 覆盖聊天页
- 列表页聚焦时再 prewarm,会话页 TTS 入队优先 base64
- 管线下发 TTS 同时带 audio_base64 与 audio_url;协议说明同步
- 移除 TTS 排查用前后端调试日志,保留错误/告警
- 补充 WS / RealtimeSession / entry-warmup / 播放器相关单测

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin
2026-05-12 10:42:44 +08:00
parent 93be60f74c
commit 3d01085442
18 changed files with 643 additions and 261 deletions

View File

@@ -18,6 +18,7 @@ import { conversationKeys } from './query-keys';
import { takePreparedRealtimeSession } from './prepared-session-registry';
import {
type ErrorCallback,
type RealtimeSessionUiOwner,
type StreamingTextCallback,
type TtsSegmentPayload,
type RealtimeSession,
@@ -219,6 +220,9 @@ export function useRealtimeSession({
}: UseRealtimeSessionOptions): RealtimeSessionState {
const queryClient = useQueryClient();
const sessionRef = useRef<RealtimeSession | null>(null);
const uiOwnerRef = useRef<RealtimeSessionUiOwner>(
Symbol('conversation-screen-ui'),
);
const uiRef = useRef({
handleStreamingText: (() => {}) as StreamingTextCallback,
handleError: (() => {}) as ErrorCallback,
@@ -300,20 +304,23 @@ export function useRealtimeSession({
prepared,
);
session.attachUiCallbacks({
onStreamingText: (text, isComplete) => {
uiRef.current.handleStreamingText(text, isComplete);
session.attachUiCallbacks(
{
onStreamingText: (text, isComplete) => {
uiRef.current.handleStreamingText(text, isComplete);
},
onTtsSegment: (payload) => uiRef.current.onTtsSegment?.(payload),
onError: (message, code) => uiRef.current.handleError(message, code),
onStateChange: setConnectionState,
},
onTtsSegment: (payload) => uiRef.current.onTtsSegment?.(payload),
onError: (message, code) => uiRef.current.handleError(message, code),
onStateChange: setConnectionState,
});
uiOwnerRef.current,
);
sessionRef.current = session;
setConnectionState(session.getConnectionState());
return () => {
releaseConversationWsUi(session);
releaseConversationWsUi(session, uiOwnerRef.current);
sessionRef.current = null;
setConnectionState('disconnected');
setStreamingMessage(null);