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

@@ -9,7 +9,7 @@ jest.mock('@/core/auth/token-manager', () => ({
jest.mock('@/core/config', () => ({
config: {
wsBaseUrl: 'ws://localhost:8000',
wsBaseUrl: 'ws://localhost:8000/',
ws: {
reconnectMaxRetries: 3,
reconnectBaseDelayMs: 10,
@@ -23,6 +23,7 @@ jest.mock('@/core/config', () => ({
class MockWebSocket {
static OPEN = 1;
static CLOSED = 3;
static instances: MockWebSocket[] = [];
readyState = MockWebSocket.OPEN;
onopen: (() => void) | null = null;
@@ -32,6 +33,7 @@ class MockWebSocket {
sentMessages: string[] = [];
constructor(public url: string) {
MockWebSocket.instances.push(this);
setTimeout(() => this.onopen?.(), 0);
}
@@ -53,6 +55,7 @@ class MockWebSocket {
describe('WsClient', () => {
afterEach(() => {
jest.clearAllMocks();
MockWebSocket.instances = [];
});
test('connects with token and conversation id in URL', async () => {
@@ -66,6 +69,9 @@ describe('WsClient', () => {
expect(states).toContain('connecting');
expect(states).toContain('connected');
expect(MockWebSocket.instances[0]?.url).toBe(
'ws://localhost:8000/ws/conversation/conv-123?token=test-token',
);
client.dispose();
});
@@ -133,6 +139,27 @@ describe('WsClient', () => {
client.dispose();
});
test('sends text with tts_this_turn when requested', async () => {
const client = new WsClient('conv-123');
await client.connect();
await new Promise((r) => setTimeout(r, 10));
client.sendText('Hello', { ttsThisTurn: true });
const ws = (client as unknown as { ws: MockWebSocket }).ws;
expect(ws.sentMessages).toHaveLength(1);
const sent = JSON.parse(ws.sentMessages[0]);
expect(sent).toEqual({
type: 'text',
conversation_id: 'conv-123',
data: { text: 'Hello', tts_this_turn: true },
});
client.dispose();
});
test('ignores unknown message types without crashing', async () => {
const client = new WsClient('conv-123');
const events: WsEvent[] = [];