From aa6df9eb555d3c5c3c8e22c577352e2996041fe5 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 26 Mar 2026 14:32:30 +0800 Subject: [PATCH] docs: add TTS interrupt and read-aloud design plan Made-with: Cursor --- ...6-03-26-tts-interrupt-read-aloud-design.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/plans/2026-03-26-tts-interrupt-read-aloud-design.md diff --git a/docs/plans/2026-03-26-tts-interrupt-read-aloud-design.md b/docs/plans/2026-03-26-tts-interrupt-read-aloud-design.md new file mode 100644 index 0000000..6fd001f --- /dev/null +++ b/docs/plans/2026-03-26-tts-interrupt-read-aloud-design.md @@ -0,0 +1,78 @@ +# TTS 打断、朗读态与重复朗读 — 设计定稿 + +**日期:** 2026-03-26 +**范围:** Expo 对话页;后端已具备 TTS 上传与 URL 落库,本设计侧重客户端与可选协议增强。 + +--- + +## 1. 目标 + +- **打断(B):** 开始录音时立即停止 TTS 并清空待播队列。 +- **打断(C):** 在「自动 TTS 正在播放」时,点击助手文本气泡区域停止朗读。 +- **区分纯文本:** 正在朗读时,助手气泡有明确视觉态(描边/底 + 行内「朗读中」或扬声器动效)。 +- **重复朗读:** 对已落库且带有 COS URL 列表的助手消息,可再次按序播放,**不重新合成**(见第 4 节)。 + +**手势分工:** 气泡空白区域在播放中 = 停止;**独立「朗读 / 再读」控件** = 开始或重播,避免与「点一下停」冲突。 + +--- + +## 2. 后端现状(方案 C 已具备) + +- `Segment` / `ConversationMessage` 均有 `tts_audio_urls`(JSON 数组)。 +- `pipeline._send_tts_audio` 上传 COS 后返回 URL;回合结束 `attach_ai_tts_audio_urls` 写库。 +- 历史/API 已透出 `ttsAudioUrls`(camelCase)。 + +**重复朗读策略:** 客户端从消息读取 `ttsAudioUrls`,按顺序 `enqueue`;无需新增「按文本重合成」接口作为默认路径。 + +--- + +## 3. 客户端缺口与数据模型 + +### 3.1 `MessageItem` + +- 增加可选字段:`ttsAudioUrls?: string[]`(与后端一致)。 +- 从 REST / React Query 缓存映射时填入;无数组或为空则隐藏「朗读」或置灰。 + +### 3.2 `PlaybackItem`(`use-player` / `features/voice/types`) + +- 扩展:`kind: 'tts_auto' | 'tts_repeat' | 'voice'`(或保留 `label` 并规范化)。 +- `messageRef?: { listKey: string }`(或 `messageId`),用于高亮对应气泡。 +- 入队时:自动 TTS 片段写入 `kind: 'tts_auto'` + 当前助手消息引用(若能从会话层解析);手动「再读」写入 `kind: 'tts_repeat'` + 该条 `listKey`。 + +### 3.3 `RealtimeSession` / WS + +- `TtsSegmentPayload` 携带 `index` / `total`(类型已存在,需从 `client` 映射到回调)。 +- 理想情况:后端在 `tts_audio` 中增加 `assistant_message_id`(或与 segment 对齐的 id),便于客户端稳定绑定「哪一条在播」。**未上字段前:** 流式阶段可仅用「当前 streaming 轮次」+ 全局条提示;落库后以 `messageId` + `ttsAudioUrls` 为准。 + +--- + +## 4. 打断与串播 + +- **`stop()`:** 已在 `usePlayer`;在开始录音成功后调用 `stop()`,保证队列清空(除 `audioFocus` 外,逻辑上立即静音)。 +- **服务端后续片段:** 若用户已 `stop()` 仍收到 `tts_audio`,可能再次入队。推荐客户端维护 **`ttsPlaybackGeneration`**(打断时自增),仅处理与当前 generation 匹配的片段;或后续增加 `tts_cancel` WS 与 pipeline 短路(可选)。 + +--- + +## 5. UI 要点 + +- 助手文本气泡:内联「朗读」按钮(图标 + a11y 文案);播放中可显示为停止或与气泡点击一致。 +- 播放中:气泡弱高亮 + 「朗读中…」;流式未结束时可对 streaming 区使用同一套样式。 +- 与用户语音条:沿用 `Play`/`Pause` 或扬声器语义,降低认知成本。 + +--- + +## 6. 错误与测试 + +- **无 `ttsAudioUrls`:** 提示「暂无法朗读」或隐藏按钮。 +- **单测:** `stop()` 后队列为空;generation 丢弃旧片段(若实现)。 +- **手测:** 录音打断、点气泡停止、自动播完后高亮消失、用历史 URL 重播同一条。 + +--- + +## 7. 实施顺序建议 + +1. 类型与 API 映射:`ttsAudioUrls` → `MessageItem`。 +2. `PlaybackItem` + `usePlayer` 入队参数;TTS 回调写入 `messageRef`(力所能及)。 +3. 录音与气泡 `Pressable` 调用 `stop()` / generation。 +4. 助手气泡 UI:朗读按钮 + 朗读中高亮。 +5. (可选)WS `tts_cancel` 与 `assistant_message_id`。