# 结束视频后保留对话并支持继续文字聊天 > 创建日期:2026-06-20 > 状态:草案 ## 1. 背景与目标 ### 1.1 现状问题 当前点击"结束对话"按钮会执行完整的 teardown 流程: 1. 停止 VAD、麦克风、摄像头 2. 断开 WebSocket 连接 3. **清空所有聊天消息**(`setMessages([])`) 4. **清空对话历史**(`historyRef.current = []`) 5. 重置统计数据 6. UI 切回初始界面(显示"开始视频通话"按钮) **问题**:用户想结束视频通话后,保留聊天记录并继续通过文字输入对话,但当前实现会丢失所有对话内容。 ### 1.2 目标 | 目标 | 说明 | |------|------| | 结束视频后保留对话 | 点击"结束视频"后,聊天记录保持不变 | | 支持继续文字对话 | 视频结束后,用户可通过文字输入继续与 AI 对话 | | 可恢复视频 | 视频结束后,用户可随时重新开启视频 | | 完全结束可选 | 提供"结束会话"选项,彻底断开并清空 | ## 2. 状态设计 ### 2.1 三态模型 引入三个会话状态,替代当前的二态(初始/通话)模型: ``` ┌──────────┐ startSession() ┌──────────┐ │ initial │ ──────────────────→ │ video │ │ 初始态 │ │ 视频通话 │ └──────────┘ └──────────┘ ↑ │ │ stopVideo() │ │ │ ▼ │ ┌──────────┐ │ stopSession() │ textOnly │ └──────────────────────── │ 文字对话 │ └──────────┘ │ startSession() │ ▼ ┌──────────┐ │ video │ │ 视频通话 │ └──────────┘ ``` ### 2.2 各状态属性 | 状态 | WebSocket | 摄像头 | 麦克风 | VAD | 消息 | 文字输入 | |------|-----------|--------|--------|-----|------|---------| | `initial` | 断开 | 关闭 | 关闭 | 停止 | 空 | 可用(自动连接) | | `video` | 连接 | 开启 | 开启 | 运行 | 有 | 可用 | | `textOnly` | 连接 | 关闭 | 关闭 | 停止 | 保留 | 可用 | ### 2.3 派生状态 当前代码中 `isConnected` 是从 `connectionStatus === "connected"` 派生的布尔值。为支持三态,新增派生变量: ```ts // 是否在会话中(video 或 textOnly) const hasSession = isConnected || (connectionStatus === "disconnected" && messages.length > 0); ``` > **注意**:`textOnly` 状态下 WebSocket 保持连接(`isConnected === true`),所以 `hasSession` 实际上主要靠 `isConnected` 判断。只有在 textOnly 状态下 WebSocket 异常断开时,`messages.length > 0` 才作为兜底。 ## 3. 详细设计 ### 3.1 `useVisionSession.ts` 改动 #### 3.1.1 新增 `stopVideo` 回调 只停止媒体流,保持 WebSocket 连接和消息: ```ts /** 结束视频,保留聊天和连接 */ const stopVideo = useCallback(async () => { // 1. 停止观察模式 stopObserving(); setMode("dialogue"); // 2. 停止媒体流 await stopVAD(); stopMic(); stopCamera(); // 3. 停止 TTS 播放 ttsPlayerRef.current?.stop(); setIsAudioPlaying(false); // 4. 重置处理状态(但保留消息和历史) setCurrentReply(""); setIsProcessing(false); setIsCameraOn(false); setIsMicOn(false); // 注意:以下不执行 // - disconnect() → 保持 WebSocket 连接 // - setMessages([]) → 保留聊天记录 // - historyRef.current=[] → 保留对话历史 // - setStats(...) → 保留统计数据 }, [stopObserving, stopVAD, stopMic, stopCamera]); ``` #### 3.1.2 `stopSession` 保持不变 `stopSession` 仍然执行完全 teardown(断开 + 清空),作为"结束会话"使用。 #### 3.1.3 `return` 新增导出 ```ts return { // ...existing... stopVideo, // 新增 // ...existing... }; ``` ### 3.2 `App.tsx` 改动 #### 3.2.1 解构新增 ```ts const { // ...existing... stopVideo, // 新增 // ...existing... } = useVisionSession(...) ``` #### 3.2.2 视频下方控制区改为三态 当前代码(二态): ```tsx {!isConnected ? ( /* 初始态 */ ) : ( /* 通话态 */ )} ``` 改为三态: ```tsx {!isConnected ? ( /* 初始态:开始按钮 + 设备选择 + 模式切换(不变) */ ) : isCameraOn ? ( /* 视频通话态:摄像头/麦克风/识别/打断 + "结束视频" 按钮 + 模式切换 */ ) : ( /* 文字对话态: - "📹 视频已结束" 提示 - "📹 重新开始视频" 按钮 - "结束会话" 按钮 */ )} ``` #### 3.2.3 按钮变化 **视频通话态**(原"结束对话"改为"结束视频"): ```tsx ``` **文字对话态**(新增): ```tsx