From 6f4d9ba0d86c0dee321bccfb35f4ca15f8f7daa3 Mon Sep 17 00:00:00 2001 From: Peanut Date: Sun, 26 Jul 2026 14:37:57 +0800 Subject: [PATCH] =?UTF-8?q?docs=EF=BC=9A=E5=B0=8F=E8=AF=B4=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E9=A1=B5/=E8=AF=A6=E6=83=85=E9=A1=B5=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E6=94=B9=E9=80=A0=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复详情页重复展示问题,列表页增加大纲预览,后端保存澄清问答消息。 详情页复刻生成时 ScriptView 的 chat-bubble 展示结构。 --- ...-07-26-novel-list-detail-display-design.md | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-26-novel-list-detail-display-design.md diff --git a/docs/superpowers/specs/2026-07-26-novel-list-detail-display-design.md b/docs/superpowers/specs/2026-07-26-novel-list-detail-display-design.md new file mode 100644 index 0000000..134002f --- /dev/null +++ b/docs/superpowers/specs/2026-07-26-novel-list-detail-display-design.md @@ -0,0 +1,298 @@ +--- +author: huazhongmin +created_at: 2026-07-26 +purpose: 小说列表页和详情页展示改造设计 — 修复详情页重复展示、列表页增加大纲预览、后端保存澄清问答消息 +--- + +# 小说列表页 / 详情页展示改造设计 + +## 1. 背景与问题 + +### 1.1 当前问题 + +**详情页重复展示**:`ScriptDetailView.vue` 当前结构存在内容重复: +- hero-card 显示 `script.summary`(正文前 90 字 — 小说正文开头) +- tabs 正文 tab 显示 `script.content`(完整小说正文) +- hero-card 显示 `userWish = script.theme`(用户心愿) +- 结果:用户看到小说正文展示了两次(summary + fullContent),加上用户心愿,看起来"重复展示了两遍" + +**列表页缺少大纲**:`ScriptLibraryView.vue` 当前只显示标题、标签、正文摘要,没有展示小说的大纲结构。 + +**澄清问答未保存**:后端 `saveNovelResult` 只创建 3 条 message(system 欢迎、user 心愿、assistant 小说正文)。生成过程中的澄清问答(3 轮 Q/A)没有保存为 message,导致详情页无法还原完整生成流程。 + +### 1.2 核心设计原则 + +> **"生成的时候是什么样展示的,后续进去详情页面就要怎么样展示"** + +详情页必须完全复刻生成时 `ScriptView.vue` 的 chat-bubble 展示结构,不发明新 UI。 + +--- + +## 2. 改造范围 + +| 模块 | 改造内容 | 复杂度 | +|---|---|---| +| 后端 `ShortNovelServiceImpl` | 保存 clarification_question / clarification_answer / outline 消息到 conversation.messages | 高 | +| 后端 新增 API | `/conversation/{conversationId}/messages` 返回消息列表 | 低 | +| 前端 `ScriptDetailView.vue` | 移除 hero-card + tabs,改为 chat-bubble 流程视图(复用 ScriptView 结构) | 高 | +| 前端 `ScriptLibraryView.vue` | 新增大纲预览行 | 低 | +| 前端 store/service | 新增 `fetchConversationMessages` 函数 | 低 | + +--- + +## 3. 数据层改造 + +### 3.1 后端 Message 保存(ShortNovelServiceImpl) + +**现状**:`saveNovelResult` 只在 `novel_done` 时创建 3 条 message。澄清问答没有保存。 + +**改造**:在 SSE 转发逻辑中增加 message 保存: + +| SSE 事件 | 触发的 message 保存 | +|---|---| +| `clarification_card` | 创建 assistant message(type=`clarification_question`, sender=`assistant`, content=问题 JSON) | +| followup `answer_clarification` | 创建 user message(type=`clarification_answer`, sender=`user`, content=用户答案) | +| `outline_created` | 创建 assistant message(type=`outline`, sender=`assistant`, content=大纲 JSON 摘要) | +| `novel_done` | 保持现有 3 条 message 不变(system 欢迎 + user 心愿 + assistant 小说正文) | + +**messageOrder 策略**:按事件到达顺序递增。每个 message 用 `snowflakeIdGenerator.nextIdAsString()` 生成 ID。 + +**message metadata**:clarification_question 消息的 metadata 字段存储完整的 card JSON(包含 question、options 等),用于详情页还原 ClarificationCard 组件。 + +### 3.2 前端 API 层 + +**新增**:`getMessagesByConversation(conversationId)` — 调用 `/conversation/{conversationId}/messages` 返回按 messageOrder 排序的 message 列表。 + +**后端新增接口**: +```java +@GetMapping("/conversation/{conversationId}/messages") +public Result> getMessages(@PathVariable String conversationId) +``` + +**注意**:需检查是否已有类似接口(如 `listMessagesByConversation` in scriptChat.js)。如已存在则复用。 + +### 3.3 前端 Store 层 + +**新增**:`fetchConversationMessages(conversationId)` — 调用 API 并返回消息列表。 + +--- + +## 4. 详情页改造(ScriptDetailView.vue) + +### 4.1 核心思路 + +**不发明新 UI,直接复用 ScriptView.vue 的 chat-bubble 结构**: + +| 生成时 ScriptView | 详情页 ScriptDetailView | +|---|---| +| `ClarificationCard` 组件(可交互) | 复用 `ClarificationCard` 组件(只读模式) | +| `kind: 'outline'` beats 渲染 | 复用相同 beats 渲染逻辑 | +| `kind: 'novel'` novel-text 渲染 | 升级为 Markdown 渲染(详情页不需要流式) | +| chat-bubble user/system 样式 | 完全复用相同样式 | +| chat-input-bar(用户输入) | 移除(详情页只读) | +| outline 修改/确认按钮 | 移除(详情页只读) | + +### 4.2 数据加载与消息重建 + +扩展 ScriptView.vue 已有的"从历史剧本重建 resultMessages"逻辑(line 1449-1500),让它完整还原生成时的消息流: + +```javascript +const rebuildResultMessages = async (script, messages) => { + const result = [] + + // 1. 用户心愿(首条 user 消息) + if (script.theme) { + result.push({ role: 'user', kind: 'text', content: script.theme }) + } + + // 2. 按 messageOrder 遍历 messages,转换为对应 kind + messages.forEach(m => { + if (m.type === 'clarification_question') { + result.push({ role: 'assistant', kind: 'card', card: parseCard(m) }) + } else if (m.type === 'clarification_answer') { + const lastCard = [...result].reverse().find(x => x.kind === 'card') + if (lastCard) { + lastCard.answer = m.content + lastCard.submitted = true + } + } else if (m.type === 'outline') { + result.push({ role: 'assistant', kind: 'outline', outline: parseOutline(m) }) + } else if (m.type === 'script') { + result.push({ role: 'assistant', kind: 'novel', content: m.content }) + } + }) + + return result +} +``` + +### 4.3 UI 布局 + +``` +┌─ 顶部栏 ───────────────────────────┐ +│ ‹ 人生剧本 ✦ 继续 │ +├──────────────────────────────────────┤ +│ │ +│ ┌─ chat-bubble user ────────────┐ │ +│ │ 我想开一家自己的咖啡馆... │ │ +│ └──────────────────────────────┘ │ +│ │ +│ ┌─ chat-bubble system (card) ───┐ │ +│ │ [ClarificationCard 只读模式] │ │ +│ │ Q: 你的咖啡馆有什么特色? │ │ +│ │ 已回答:安静的阅读空间 │ │ +│ └──────────────────────────────┘ │ +│ │ +│ ┌─ chat-bubble system (outline) ┐ │ +│ │ 咖啡与书页之间 │ │ +│ │ 简介:一个爱书人的咖啡馆故事 │ │ +│ │ ① 辞职的决定 │ │ +│ │ ② 街角邂逅 │ │ +│ │ 结局:新的开始 │ │ +│ └──────────────────────────────┘ │ +│ │ +│ ┌─ chat-bubble system (novel) ──┐ │ +│ │ [Markdown 渲染小说正文] │ │ +│ │ 你把那份商业计划书放在... │ │ +│ └──────────────────────────────┘ │ +│ │ +├──────────────────────────────────────┤ +│ 返回列表 ▶ 播放 继续生成 │ +└──────────────────────────────────────┘ +``` + +### 4.4 只读模式处理 + +- **ClarificationCard**:所有卡片都显示"已回答"状态,隐藏选项按钮 +- **Outline**:隐藏"修改大纲"/"确认大纲"按钮 +- **Novel**:用 Markdown 组件渲染(比生成时的纯文本更美观) +- **无 chat-input-bar**:详情页底部不显示输入框 + +### 4.5 降级策略 + +- **老剧本无 clarification 消息**:流程中跳过澄清步骤,只展示 wish → outline → novel +- **messages API 失败**:降级用 `plotJson.stages` 重建(只有最后 1 轮澄清) +- **messages 为空**:至少展示 wish(theme)+ novel(fullContent) + +--- + +## 5. 列表页增强(ScriptLibraryView.vue) + +### 5.1 现状卡片结构 + +``` +┌──────────────────────────────────────┐ +│ [封面] │ 标题 [中篇] │ +│ 字 │ [标签] [标签] │ +│ │ 正文前90字摘要... │ +│ │ 5章 | 3.2k字 | 07-26 │ +└──────────────────────────────────────┘ +``` + +### 5.2 增强后的卡片结构 + +``` +┌──────────────────────────────────────┐ +│ [封面] │ 标题 [中篇] │ +│ 字 │ [标签] [标签] │ +│ │ 📋 第1章:辞职 · 第2章:邂逅 │ ← 新增大纲预览行 +│ │ 正文前80字摘要... │ +│ │ 5章 | 3.2k字 | 07-26 │ +└──────────────────────────────────────┘ +``` + +### 5.3 大纲预览数据源 + +从 `script.plotJson.stages.outline.beats` 取前 2 章标题: + +```javascript +const getOutlinePreview = (script) => { + const beats = script.plotJson?.stages?.outline?.beats + if (!Array.isArray(beats) || beats.length === 0) return '' + const parts = beats.slice(0, 2).map((beat, i) => { + return `第${i + 1}章:${beat.title || ''}` + }) + return `📋 ${parts.join(' · ')}` +} +``` + +### 5.4 模板变更 + +在 tag-row 和 summary 之间新增大纲预览行: + +```vue + + {{ getOutlinePreview(script) }} + +{{ script.summary || '一段正在生成中的平行人生剧本。' }} +``` + +### 5.5 样式 + +```css +.outline-preview-row { + margin-top: 10rpx; + padding: 10rpx 14rpx; + border-radius: 14rpx; + background: rgba(168, 85, 247, 0.08); + border: 1rpx solid rgba(168, 85, 247, 0.16); +} + +.outline-preview-text { + font-size: 22rpx; + color: rgba(193, 134, 255, 0.88); + line-height: 1.4; +} +``` + +### 5.6 降级 + +- **无大纲数据**:不显示大纲预览行(v-if 控制) +- **beats 不足 2 章**:有几章显示几章 +- **beats 标题为空**:显示"第1章"(无冒号后缀) + +--- + +## 6. 错误处理与兼容性 + +### 6.1 老剧本兼容 + +老剧本(改造前生成的)conversation.messages 只有 3 条(system 欢迎、user 心愿、assistant 小说正文),没有 clarification 和 outline 消息。 + +**处理**:详情页重建消息流时,跳过缺失的 clarification/outline 步骤。对于大纲数据,fallback 到 `plotJson.stages.outline`(如果存在)。 + +### 6.2 API 失败降级 + +- messages API 失败 → 用 `plotJson.stages` 重建(只有最后 1 轮澄清) +- 整个 script API 失败 → 显示错误页面,不渲染流程 + +### 6.3 空数据降级 + +- 无 theme → 不显示心愿气泡 +- 无 outline → 不显示大纲气泡 +- 无 content/fullContent → 显示"暂无正文"占位 + +--- + +## 7. 验收标准 + +### 7.1 H5 端到端验收(强制) + +1. **生成新小说**:在 ScriptView 页面生成一篇新小说,观察生成时的展示(心愿 → 澄清 → 大纲 → 正文) +2. **返回列表**:进入 ScriptLibraryView,确认新小说卡片显示大纲预览行 +3. **进入详情**:点击新小说进入 ScriptDetailView,确认展示与生成时一致(chat-bubble 流程视图) +4. **老剧本兼容**:点击一个改造前生成的老小说,确认不报错、正常展示(可跳过澄清步骤) +5. **浏览器 Console**:确认无任何新增错误 + +### 7.2 mp-weixin 产物重建 + +代码改造完成后,执行 `npm run dev:mp-weixin` 重新构建 mp-weixin 产物,确保微信开发者工具中的真实用户也能看到改造效果。 + +--- + +## 8. 不在范围内 + +- 不改造 ScriptView.vue 的生成流程 UI(保持现状) +- 不改造后端 SSE 转发逻辑(只在关键事件点新增 message 保存) +- 不新增澄清问答的轮数限制(保持上游服务决定的轮数) +- 不改造列表页的搜索、排序、筛选功能