diff --git a/docs/superpowers/specs/2026-07-19-scriptview-unified-chat-design.md b/docs/superpowers/specs/2026-07-19-scriptview-unified-chat-design.md new file mode 100644 index 0000000..974560f --- /dev/null +++ b/docs/superpowers/specs/2026-07-19-scriptview-unified-chat-design.md @@ -0,0 +1,249 @@ +--- +author: AI Assistant +created_at: 2026-07-19 +purpose: ScriptView 心愿实现页面统一对话流改造设计,将澄清/大纲/小说生成全部纳入单一对话页面 +--- + +# ScriptView 统一对话页改造设计 + +## 概述 + +将小程序心愿实现页面(ScriptView.vue)的生成流程从"多独立视图切换"改为"单一对话页面流"。用户从提交心愿开始,始终停留在同一个对话页面,澄清问答、大纲确认/修改、小说流式生成、后续对话修改都作为对话消息依次出现在聊天流里。 + +## 背景与问题 + +### 当前架构(AS-IS) + +ScriptView.vue 用 `viewState` 在 5 个独立视图间切换: + +| viewState | 内容 | 对话页? | +|---|---|---| +| `home` | 心愿输入首页(灵感推荐+输入框) | 否 | +| `generating` | 生成中加载页(用户气泡+loading orbit) | 否 | +| `clarifying` | 澄清卡片(只有 ClarificationCard) | 否 | +| `outlining` | 大纲确认(大纲+确认/修改按钮) | 否 | +| `result` | 结果对话页(MessageCard+用户气泡+输入栏) | 是 | + +问题:澄清、大纲、小说生成阶段用户"不在对话页面",体验割裂。用户要求"心愿实现时必须一直处于对话的页面,必须支持对话和选择的澄清判断"。 + +### 可复用资产 + +`result` 视图已有完整对话机制: +- `resultMessages` 数组 + `addResultMessage({id, role, kind, content, pending, time})` +- `MessageCard` 组件渲染 AI 消息 +- 用户气泡渲染用户消息 +- 底部输入栏 `sendChat`(当前接旧接口 `streamScriptChat`) +- 滚动控制(`keepResultAtBottom`、`resultScrollTop` 等) + +## 目标架构(TO-BE) + +### viewState 简化 + +从 5 个视图简化为 **2 个**: + +``` +home(首页:灵感推荐+输入框,保留不变) + ↓ 用户提交心愿 +chat(统一对话页:澄清→大纲→小说→后续修改 全在这里) +``` + +删除独立的 `generating`/`clarifying`/`outlining`/`novel-generating` 视图。 + +### 统一对话消息模型 + +复用 `resultMessages` + `addResultMessage`,扩展 `kind` 字段: + +```javascript +{ + id, // createMessageId() + role, // 'user' | 'assistant' + kind, // 'text' | 'card' | 'outline' | 'novel' + content, // 文本内容(text/novel) + pending, // 是否加载中(novel 流式时 true) + time, // formatMessageTime() + card?, // kind:'card' 时携带澄清卡片数据 + outline?, // kind:'outline' 时携带大纲数据 + submitted?, // kind:'card' 时标记是否已提交(禁用交互) + confirmed?, // kind:'outline' 时标记是否已确认/修改(禁用交互) + failed? // 是否失败(error 事件) +} +``` + +### 消息类型与渲染 + +`chat` 视图对话区用 `v-for` 遍历 `resultMessages`,根据 `kind` 渲染不同气泡内容: + +| kind | role | 渲染方式 | +|---|---|---| +| `text` | user | 用户气泡(`chat-bubble user`) | +| `text` | assistant | AI 文本气泡(`chat-bubble system`),含状态提示/错误 | +| `card` | assistant | AI 气泡内嵌 `ClarificationCard`,`submitted` 后禁用交互并显示已选答案 | +| `outline` | assistant | AI 气泡内嵌大纲卡片+确认/修改按钮,`confirmed` 后禁用按钮 | +| `novel` | assistant | AI 气泡内嵌小说正文,`pending` 时流式逐字追加,`pending=false` 后完成 | + +新消息追加到底部,`keepResultAtBottom()` 自动滚动跟随。 + +### SSE 事件 → 对话消息映射 + +`handleShortNovelEvent` 改为往统一对话流追加/更新消息: + +| SSE 事件 | 对话流行为 | +|---|---| +| `status` | 更新底部 loading 提示文案(不追加独立消息) | +| `clarification_card` | `addResultMessage({role:'assistant', kind:'card', card: payload.card, submitted:false})` | +| `outline_created` | `addResultMessage({role:'assistant', kind:'outline', outline: payload.outline, confirmed:false})` | +| `novel_start` | `addResultMessage({role:'assistant', kind:'novel', content:'', pending:true})` | +| `novel_delta` | 往最后一条 `kind:'novel'` 消息的 `content` 追加 `payload.delta` | +| `novel_done` | 标记最后一条 novel 消息 `pending=false`,保存 `scriptId`/`conversationId`/`currentVersionMessageId` | +| `error` | 若 `DAILY_GENERATION_IN_PROGRESS` 且有 `session_id`:保存 `resumeableSessionId`,更新底部 loading 为失败态+继续创作入口;其他错误:追加 `kind:'text', role:'assistant', failed:true` 消息 | + +### 用户交互 → 接口调用 + +每张卡片/大纲消息内部渲染交互按钮,用户操作时**先追加一条 user 消息到对话流,再调接口**: + +| 阶段 | 交互 | 追加 user 消息 | 接口调用 | +|---|---|---|---| +| 生成 | ClarificationCard 提交 | `{kind:'text', content: 答案}` | `followupStream(action:'answer_clarification', payload:{answer})` | +| 生成 | 大纲"确认" | `{kind:'text', content:'确认大纲'}` | `followupStream(action:'confirm_outline')` | +| 生成 | 大纲"修改" | `{kind:'text', content: 修改意见}` | `followupStream(action:'modify_outline', payload:{feedback})` | +| 生成 | 失败"继续创作" | `{kind:'text', content:'继续之前的创作'}` | `followupStream(action:'retry')` | +| 后续 | novel_done 后底部输入 | `{kind:'text', content: 修改建议}` | `streamScriptChat`(旧接口,支持改写/继续/版本) | + +### 接口切换边界 + +- **生成流程**(澄清→大纲→小说):用新接口 `startNovelStream` / `followupStream` +- **novel_done 后**:保存记录后,底部输入栏 `sendChat` 切换用现有旧接口 `streamScriptChat` + `createMessage`(已成熟,支持改写/继续/版本切换) + +切换时机:`novel_done` 事件处理时,设置 `generationPhase = 'done'`,底部输入栏根据该标志切换接口。两套接口的消息都进同一个 `resultMessages` 对话流,用户无感知。 + +## 交互态管理 + +### 卡片消息(kind:'card') + +- 渲染 `ClarificationCard`,传入 `:card="message.card"` +- `submitted=false` 时:正常交互,`@submit` 触发 `submitClarification` +- `submitClarification` 内:设置 `message.submitted=true`(禁用卡片),追加 user 消息,调 followupStream +- `submitted=true` 时:卡片显示"已回答:xxx",禁用交互 + +### 大纲消息(kind:'outline') + +- 渲染大纲内容(title/logline/beats/ending)+ 确认/修改按钮 +- `confirmed=false` 时:显示输入框+确认+修改按钮 +- 用户确认/修改后:设置 `message.confirmed=true`(隐藏按钮),追加 user 消息,调 followupStream +- `confirmed=true` 时:只展示大纲内容,不再有按钮 + +### 小说消息(kind:'novel') + +- `pending=true` 时:流式追加 `novel_delta` 文本,显示打字光标 +- `pending=false`(novel_done)时:完整小说正文,无光标,可复制/朗读 + +## chat 视图模板结构 + +```vue + + + ... + + + + + + + + {{ msg.content }} + {{ msg.time }} + + + + + + + 已回答:{{ msg.answer }} + + + + + + + {{ msg.outline.title }} + {{ msg.outline.logline }} + + + {{ beat.order || idx + 1 }} + + {{ beat.title }} + {{ beat.summary }} + + + + + 结局 + {{ msg.outline.ending }} + + + + + + + + + + + {{ msg.content }}| + + + + + {{ msg.content }} + + + + + + ... + 正在为你重写人生 + + + + + + + + + + + + +