diff --git a/docs/superpowers/specs/2026-06-29-scriptview-chat-buttons-fix-design.md b/docs/superpowers/specs/2026-06-29-scriptview-chat-buttons-fix-design.md new file mode 100644 index 0000000..0f6feee --- /dev/null +++ b/docs/superpowers/specs/2026-06-29-scriptview-chat-buttons-fix-design.md @@ -0,0 +1,286 @@ +--- +author: Peanut +created_at: 2026-06-29 +purpose: 完整修复 ScriptView 对话模式的按钮缺失问题,补齐 spec 要求的版本/改写/续写/历史版本/删除功能 +--- + +# ScriptView 对话模式按钮完整修复设计 + +## 背景 + +`bf6f279 feat: ScriptView 改造为对话中心查看/修改模式` 重构后,对话模式的按钮出现多处丢失: + +1. **旧版按钮丢失**:`MessageCard` 在 `isShortMessage=true`(短消息)分支没有按钮组,而旧版 assistant 消息后面有「复制/换个方向/不像我/继续生成/播放」按钮组(commit `0db434c` 添加) +2. **Spec 功能未实现**:`2026-06-28-script-dialogue-design.md` 第 5.4 节要求 AI 剧本消息有「改写 / 续写 / 查看历史版本 / 删除版本」按钮,当前完全没实现 +3. **根版本被过滤**:`displayMessages` 用 `m.type !== 'script' || !m.parentMessageId` 过滤,把根版本 AI 剧本消息丢掉了 +4. **版本标签缺失**:AI 消息没有 `V1 / V2` 版本号标签和「(当前)」标记 + +## 设计 + +### 1. MessageCard.vue 重构 + +**新增 props**: +- `messageType`: `'script' | 'chat' | 'system'`,默认 `'script'` +- `versionLabel`: 版本号显示(如 `V1 (当前)`),script 类型必填,其他类型不传 +- `hasChildren`: 是否有子版本(布尔,用于控制「查看历史版本」按钮是否显示) +- `canDelete`: 是否可删除(布尔,当前生效版本不可删除) +- `canRewrite`: 是否可改写(布尔,默认 true) +- `canContinue`: 是否可续写(布尔,默认 true) + +**渲染规则**: + +| messageType | 渲染方式 | +|---|---| +| `'script'` | **永远**走长文本 story-card 分支(不论 `isShortMessage`),因为剧本消息总是需要按钮组 | +| `'chat'` 或 `'system'` | 保持原逻辑(短消息气泡 / 长消息 story-card) | + +**按钮组**(按 messageType 切换): + +``` +script 消息按钮组: + ┌─ 顶部操作(story-head-actions,不变) + │ · 收起/展开 + │ · 复制 + ├─ 版本标签(新增,仅在 versionLabel 有值时显示) + │ · V1 (当前) / V2 / ... + ├─ 功能按钮(新增,script 专属) + │ · 改写(emit 'rewrite') + │ · 续写(emit 'continue-script') + │ · 查看历史版本(emit 'view-versions',hasChildren=true 才显示) + │ · 删除版本(emit 'delete-version',canDelete=true 才显示) + └─ 通用操作(保留旧版) + · TTS 播放(emit 'play-tts') + +chat 消息按钮组(isShortMessage=false 时): + ┌─ 复制 + ├─ 收起/展开 + └─ TTS 播放(emit 'play-tts') + +system 消息(isShortMessage=true 默认走气泡): + └─ 不变 +``` + +**新增 emits**: +- `rewrite` — 改写按钮 +- `continue-script` — 续写按钮(避免和旧 `continue` 语义冲突,但保留旧 `continue` 兼容) +- `view-versions` — 查看历史版本 +- `delete-version` — 删除版本 + +### 2. ScriptView.vue 改动 + +**改动 2.1:显示根版本** + +```js +// 旧 +const displayMessages = computed(() => { + return messages.value.filter(m => m.type !== 'script' || !m.parentMessageId) +}) + +// 新:保留所有消息,根版本作为 chat 列表首条 +const displayMessages = computed(() => messages.value) +``` + +**改动 2.2:传 messageType 给 MessageCard** + +```vue + +``` + +**改动 2.3:版本标签生成函数** + +```js +const formatVersionLabel = (message) => { + if (message.type !== 'script') return '' + const versionNum = message.versionNumber || 1 + const isCurrent = message.id === currentVersionMessageId.value + return isCurrent ? `V${versionNum} (当前)` : `V${versionNum}` +} + +const messageHasChildren = (message) => { + // 通过 versions 数组长度判断(已加载的)或 metadata 推断 + return versions.value.some(v => v.parentMessageId === message.id) + || (message.versionNumber && message.versionNumber > 1) + || (message.childrenCount && message.childrenCount > 0) +} +``` + +**改动 2.4:查看历史版本** + +新增 `viewMessageVersions(msg)` 函数: +1. 调用 `listMessageVersions(msg.id)` +2. 用 `uni.showActionSheet` 列出所有版本(V1、V2...) +3. 用户选择后: + - 如果选的是当前版本:`uni.showToast` 提示「当前已生效」 + - 如果选的是其他版本:调用 `switchVersion({ scriptId, messageId: selectedId })` → 更新 `currentVersionMessageId` → `loadMessages()` 刷新 + +### 4. 历史剧本兼容层(高优先级) + +### 现状 + +DB 中绝大多数历史剧本的 `conversation_id` 是前端早期自造的临时 ID(`conv--` 格式),**`t_conversation` 表中没有对应记录**。 + +``` +329987360219471872 我的人生剧本 → YES (最新剧本,走新流程创建) +f82818b702d5ad4a... 高考 → NO +a3dadb7e85a6c75b... 我高考了 → NO +49ae0f432253aaff... 我中了100w → NO +... 其余 6 条 → NO +``` + +### 影响分析 + +| 场景 | 是否受影响 | 原因 | +|---|---|---| +| ScriptDetailView 阅读模式 | ✅ 正常 | 读 `t_epic_script.plotJson`,不依赖 conversation | +| ScriptView `viewMode='read'` 阅读模式 | ✅ 正常 | 通过 `currentScriptContent` 读 `currentVersionMessageId` 指向的 message;fallback 到 `plotJson` | +| ScriptView `viewMode='chat'` 聊天模式 | ❌ 异常 | `listByConversation` 返回空,对话历史丢失 | +| 改写 / 续写 / 版本切换 | ❌ 异常 | 需要有效的 conversation 和 message 才能操作 | + +### 兼容策略:前端 fallback + 后端自动建表 + +**改动 A:后端自动建 conversation** + +在 `EpicScriptServiceImpl.getScriptById`(或 `getListByCurrentUser` 返回前)检查: +- 若 `script.conversation_id` 指向的 conversation 不存在 → 自动创建一条 `t_conversation` 记录,写入 `user_id`、`script_id`、`type='script'`、`status='active'`,并回填 `t_epic_script.conversation_id` 为新 ID + +**改动 B:前端首次进入聊天模式时,基于 `plotJson` 初始化首条消息** + +在 ScriptView.vue 的 `loadMessages` 中: +- 若 API 返回空列表 && `script.plotJson?.fullContent` 存在 → 自动调用 `/message/create` 创建一条 `type='script'` 的 AI 剧本消息(content 来自 plotJson) +- 后续改写/续写/版本切换基于这条真实消息操作 + +**改动 C:ScriptView 阅读模式 fallback 强化** + +当前 `currentScriptContent` 已在 message 不存在时 fallback 到 `plotJson`。需确保: +- `viewMode='read'` 下,版本标签在没有 versions 数据时隐藏(不显示"V0"之类) +- "进入对话修改"按钮在没有 conversation 时仍可点击,触发改动 B 的初始化流程 + +### 验收标准(历史剧本) + +- [ ] 历史剧本在 ScriptDetailView 完整显示标题、summary、content +- [ ] 历史剧本在 ScriptView `viewMode='read'` 完整显示标题、章节内容(无版本标签时 UI 不异常) +- [ ] 历史剧本点击"进入对话修改" → 自动创建 conversation + 初始化首条 AI 消息 → 进入 chat 模式 +- [ ] 历史剧本进入 chat 模式后,能看到完整对话历史(首条为用户心愿 + AI 剧本) +- [ ] 历史剧本的"改写/续写/查看历史版本"功能可正常使用 +- [ ] 新创建的剧本所有功能不受影响(回归验证) + +## 5. 实施顺序 + +| 顺序 | 任务 | 类型 | +|---|---|---| +| 1 | 后端 `getScriptById` 自动建 conversation | 数据兼容 | +| 2 | 前端 MessageCard 重构 + 按钮组 | 功能补齐 | +| 3 | 前端 ScriptView displayMessages 去掉根版本过滤 | Bug 修复 | +| 4 | 前端 loadMessages 空结果时自动初始化首条消息 | 数据兼容 | +| 5 | 前端 chat 模式传 messageType/versionLabel 等 props | 功能补齐 | +| 6 | 端到端验收(新剧本 + 历史剧本) | 验收 | + +## 6. 影响范围 + +| 文件 | 改动量 | +|---|---| +| `server/.../EpicScriptServiceImpl.java` | 小(getScriptById 检查 + 自动建 conversation) | +| `mini-program/src/components/MessageCard.vue` | 中(+props、+emits、+按钮组条件渲染) | +| `mini-program/src/pages/main/ScriptView.vue` | 中(displayMessages filter、MessageCard props、formatVersionLabel、viewMessageVersions、loadMessages 初始化) | + +## 验收标准(代码层面) + +- [ ] chat 模式下,根版本 AI 剧本消息显示为 chat 列表首条 +- [ ] AI 剧本消息显示 V1 / V2 版本号 + (当前) 标记 +- [ ] AI 剧本消息按钮组包含:收起/展开、复制、TTS 播放、改写、续写、查看历史版本、删除版本 +- [ ] 用户消息(type=chat)保持气泡样式,有收起/展开按钮 +- [ ] 点击「改写」弹出 modal 输入改写意图,提交后生成新版本 +- [ ] 点击「续写」直接生成新版本 +- [ ] 点击「查看历史版本」弹 ActionSheet 列出版本,切换后 UI 更新 +- [ ] 当前生效版本不显示「删除版本」按钮 +- [ ] 历史剧本进入 ScriptView chat 模式 → 自动创建 conversation + 初始化首条 AI 消息 +- [ ] 浏览器 Console 无报错 +- [ ] 不影响 read 模式(阅读模式保持现状) + +## H5 端到端验收流程(强制) + +**后端验证码**:`AuthServiceImpl.java:80` 已硬编码 `DEFAULT_SMS_CODE = "123456"`,无需改动。 + +**测试账号**:手机号 `19928748688`,验证码 `123456`。 + +### 步骤 1:启动本地 H5 +```bash +python dev-services.py start mini-program +# 访问 http://localhost:5180 +``` + +### 步骤 2:登录 +1. 打开 `http://localhost:5180` +2. 输入手机号 `19928748688` +3. **必须点击"发送验证码"按钮**(触发 `/auth/sms-code` 接口,后端写入 Redis 并返回固定验证码) +4. 输入验证码 `123456` +5. 提交登录 + +### 步骤 3:新剧本完整流程 +1. 首页输入心愿 → 生成第一个剧本 +2. 查看剧本详情 → 标题、summary、content 完整显示 +3. 点击"继续" → 进入 ScriptView chat 模式 +4. **验证**: + - 用户心愿气泡显示 + - AI 剧本消息显示,带 `V1 (当前)` 版本标签 + - AI 消息按钮组包含:收起/展开、复制、TTS 播放、**改写**、**续写**、**查看历史版本**、**删除版本**(当前版本不显示删除) +5. 点"改写" → 输入改写意图 → 生成新版本 V2 +6. 点"查看历史版本" → ActionSheet 列出 V1/V2,点击切换 +7. 点"续写" → 生成新内容 + +### 步骤 4:历史剧本完整流程 +1. 点"历史"按钮 → 进入剧本库 +2. 点任一**历史剧本**(非本次新生成的)→ 进入 ScriptDetailView +3. **验证**: + - 标题、summary、content 完整显示(读 `plotJson.fullContent`) + - 风格、篇幅、字数正确 +4. 点"继续" → 进入 ScriptView +5. **验证**: + - 自动创建 `t_conversation`(DB 中新增记录) + - 自动初始化首条 AI 消息(基于 plotJson) + - chat 模式看到完整对话历史(心愿 + AI 剧本) + - AI 消息带版本标签 + - 改写/续写/查看历史版本 可正常操作 + +### 步骤 5:Console 与 Network 检查 +- DevTools Console 无报错 +- Network 中所有 API 调用返回 2xx(无 5xx、无 401、无 403) +- `listByConversation` 调用返回 200 且有数据 + +### 步骤 6:验收通过标准 +- ✅ 新剧本 + 历史剧本所有功能正常 +- ✅ 按钮组完整 +- ✅ 版本标签正确 +- ✅ Console 无报错 +- ✅ 历史剧本 conversation 自动创建成功 + +**未通过的处理**:任一验收项失败 → 停止 → 定位问题 → 修复 → 重新验收 → 直到全部通过才算完成 + +## 风险与回退 + +- **MessageCard 重构风险**:新增 props 都带默认值,不会破坏非对话模式(生成页、结果页)的现有用法 +- **向后兼容**:旧 `continue` emit 保留,新增 `continue-script` 作为 script 专属,避免语义冲突 +- **回退方案**:git revert 单个 commit 即可