fix(conversation): 离屏不丢回复、列表预热 WS 与非阻塞进入聊天

- 后端:文本/转写后 AI 生成改为独立任务,避免断连取消整轮;按需 TTS 等与 WS 改动
- 前端:RealtimeSession 重绑 UI 时恢复流式 buffer;列表 onPressIn/挂载预热、已有会话立即 push
- 同步会话相关类型、i18n、测试与 env/资源等累计改动

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin
2026-05-08 17:28:31 +08:00
parent 5dac3efd52
commit d0c26242db
44 changed files with 1209 additions and 212 deletions

View File

@@ -11,6 +11,13 @@ import type {
export type WsEventListener = (event: WsEvent) => void;
export type WsStateListener = (state: WsConnectionState) => void;
function buildWsUrl(conversationId: string, token: string): string {
const baseUrl = config.wsBaseUrl.replace(/\/+$/u, '');
const encodedConversationId = encodeURIComponent(conversationId);
const encodedToken = encodeURIComponent(token);
return `${baseUrl}/ws/conversation/${encodedConversationId}?token=${encodedToken}`;
}
function mapServerMessage(raw: RawServerMessage): WsEvent | null {
const cid = raw.conversation_id;
const d = raw.data;
@@ -51,6 +58,7 @@ function mapServerMessage(raw: RawServerMessage): WsEvent | null {
index: d.index as number | undefined,
total: d.total as number | undefined,
assistantMessageId: d.assistant_message_id as string | undefined,
manual: d.manual as boolean | undefined,
};
case 'end_conversation':
@@ -102,7 +110,7 @@ export class WsClient {
return;
}
const url = `${config.wsBaseUrl}/ws/conversation/${this.conversationId}?token=${token}`;
const url = buildWsUrl(this.conversationId, token);
try {
this.ws = new WebSocket(url);
@@ -164,14 +172,40 @@ export class WsClient {
return true;
}
sendText(text: string): boolean {
return this.send({ type: 'text', data: { text } });
sendText(
text: string,
opts?: { ttsThisTurn?: boolean },
): boolean {
return this.send({
type: 'text',
data: {
text,
...(opts?.ttsThisTurn === true ? { tts_this_turn: true } : {}),
},
});
}
sendTtsCancel(): boolean {
return this.send({ type: 'tts_cancel', data: {} });
}
sendTtsRequest(body: {
assistantMessageId: string;
segmentIndex: number;
segmentText?: string;
}): boolean {
return this.send({
type: 'tts_request',
data: {
assistant_message_id: body.assistantMessageId,
segment_index: body.segmentIndex,
...(body.segmentText != null && body.segmentText !== ''
? { segment_text: body.segmentText }
: {}),
},
});
}
sendEndConversation(): boolean {
return this.send({ type: 'end_conversation', data: {} });
}