Files
happy-life-star/docs/superpowers/specs/2026-06-29-scriptview-chat-buttons-fix-design.md
T

287 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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
<MessageCard
v-if="message.sender === 'assistant' || message.type === 'script'"
message-type="script"
:version-label="formatVersionLabel(message)"
:has-children="messageHasChildren(message)"
:can-delete="message.id !== currentVersionMessageId"
:can-rewrite="true"
:can-continue="true"
:content="message.content"
:collapsed="isMessageCollapsed(message)"
:content-length="message.content.length"
:is-short-message="false"
:tts-icon="..."
:tts-text="..."
@toggle-collapse="toggleMessageCollapse(message)"
@copy="copyMessageContent(message)"
@rewrite="rewriteMessage(message)"
@continue-script="continueMessage(message)"
@view-versions="viewMessageVersions(message)"
@delete-version="removeVersion(message)"
@play-tts="playMessageTts(message)"
@change-direction="changeDirection"
@not-like-me="notLikeMe"
/>
```
**改动 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-<ts>-<rand>` 格式),**`t_conversation` 表中没有对应记录**。
```
329987360219471872 我的人生剧本 → YES (最新剧本,走新流程创建)
f82818b702d5ad4a... 高考 → NO
a3dadb7e85a6c75b... 我高考了 → NO
49ae0f432253aaff... 我中了100w → NO
... 其余 6 条 → NO
```
### 影响分析
| 场景 | 是否受影响 | 原因 |
|---|---|---|
| ScriptDetailView 阅读模式 | ✅ 正常 | 读 `t_epic_script.plotJson`,不依赖 conversation |
| ScriptView `viewMode='read'` 阅读模式 | ✅ 正常 | 通过 `currentScriptContent``currentVersionMessageId` 指向的 messagefallback 到 `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
- 后续改写/续写/版本切换基于这条真实消息操作
**改动 CScriptView 阅读模式 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 消息带版本标签
- 改写/续写/查看历史版本 可正常操作
### 步骤 5Console 与 Network 检查
- DevTools Console 无报错
- Network 中所有 API 调用返回 2xx(无 5xx、无 401、无 403
- `listByConversation` 调用返回 200 且有数据
### 步骤 6:验收通过标准
- ✅ 新剧本 + 历史剧本所有功能正常
- ✅ 按钮组完整
- ✅ 版本标签正确
- ✅ Console 无报错
- ✅ 历史剧本 conversation 自动创建成功
**未通过的处理**:任一验收项失败 → 停止 → 定位问题 → 修复 → 重新验收 → 直到全部通过才算完成
## 风险与回退
- **MessageCard 重构风险**:新增 props 都带默认值,不会破坏非对话模式(生成页、结果页)的现有用法
- **向后兼容**:旧 `continue` emit 保留,新增 `continue-script` 作为 script 专属,避免语义冲突
- **回退方案**git revert 单个 commit 即可