diff --git a/docs/superpowers/plans/2026-07-26-detail-clarification-card-count-fix.md b/docs/superpowers/plans/2026-07-26-detail-clarification-card-count-fix.md new file mode 100644 index 0000000..043b94d --- /dev/null +++ b/docs/superpowers/plans/2026-07-26-detail-clarification-card-count-fix.md @@ -0,0 +1,135 @@ +# 详情页澄清选项卡显示不全修复 实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 修复后端 `forwardSse` 首次 stream 中第一个 clarification_card 因 sessionId 为 null 不累积的 bug,使详情页澄清选项卡数量与生成页完全一致。 + +**Architecture:** 在 `ShortNovelServiceImpl.forwardSse` 的事件循环中,每次解析 SSE 事件后,从事件**顶层**取 `session_id` 同步 `currentSessionId[0]`(与前端 `ScriptView.vue:1805` 取 sessionId 的方式对齐)。这样无论 status 事件是否在 payload 内携带 session_id,只要事件顶层有 session_id,首次 stream 的 `currentSessionId[0]` 就能被正确设置,首次 stream 里的 `clarification_card` 就能被累积。 + +**Tech Stack:** Java 17 / Spring Boot 2.7.18 / fastjson2 / OkHttp / MySQL / Maven / Python 部署脚本 / H5 端到端验收 + +## Global Constraints + +- 编译命令:`mvn clean install -DskipTests`(禁止 `mvn clean compile`) +- 部署命令:`python deploy.py backend`(根目录脚本) +- 部署前必须本地编译通过 +- 部署后必须通过 H5 端到端验收(Console 0 新增错误 + Network 接口正常) +- 注释必须使用中文 +- 禁止任何形式的 mock、兜底、默认值掩盖错误 +- 只改后端 1 个方法(`forwardSse`),零前端改动,零数据库变更 + +--- + +## Task 1: 后端修复 forwardSse 顶层取 session_id + +**Files:** +- Modify: `server/src/main/java/com/emotion/service/impl/ShortNovelServiceImpl.java:304-308`(`forwardSse` 方法的事件循环内,status 处理之前) + +**Interfaces:** +- Consumes: 上游 SSE 事件 JSON 结构(事件顶层 `session_id` 字段与 `type` 同级) +- Produces: `currentSessionId[0]` 在首次 stream 里也能被正确设置(不再是 null) + +### 背景 + +- **根因**:上游 SSE 事件的 `session_id` 在事件**顶层**(与 `type` 同级),但后端 `forwardSse` 行 312-314 从 `payload.getString("session_id")` 取,取不到。导致首次 stream 的 `currentSessionId[0]` 保持 null(`knownSessionId=null`)。 +- **数据证据**:DB 中 3 个有 clarification 的 conversation 全部是 `clarification_question=2 + clarification_answer=3`。第一个 `clarification_question` 因 `currentSessionId[0]==null` 不累积。 +- **修复思路**:在 status 处理之前,从事件顶层取 `session_id` 同步 `currentSessionId[0]`(与前端 `ScriptView.vue:1805-1806` 逻辑对齐)。 + +### 步骤 + +- [ ] **Step 1: 在 forwardSse 事件循环中插入从顶层取 session_id 的代码** + +打开 `server/src/main/java/com/emotion/service/impl/ShortNovelServiceImpl.java`,定位到 `forwardSse` 方法内的事件循环。当前代码(行 304-311): + +```java +try { + JSONObject event = JSON.parseObject(dataStr); + String type = event.getString("type"); + long eventTimestamp = System.currentTimeMillis(); + log.info("[ShortNovel SSE] 处理事件: type={}, timestamp={}", type, eventTimestamp); + + // 拦截 status 事件,缓存 sessionId → originalQuery 映射(供 followup 兜底) + if ("status".equals(type)) { +``` + +在 `log.info(...)` 之后、`// 拦截 status 事件` 注释之前,**插入以下 5 行代码**: + +```java + // 从事件顶层取 session_id 同步 sessionId + // 上游事件结构与前端对齐:session_id 与 type 同级,不在 payload 内 + // 修复:首次 stream 里 status 处理从 payload 取不到 session_id,导致 currentSessionId 保持 null, + // 第一个 clarification_card 因 sessionId 为 null 不累积,详情页少显示第一个选项卡 + String topSessionId = event.getString("session_id"); + if (topSessionId != null && !topSessionId.isEmpty()) { + currentSessionId[0] = topSessionId; + } +``` + +插入后整体结构(行 304-320): + +```java +try { + JSONObject event = JSON.parseObject(dataStr); + String type = event.getString("type"); + long eventTimestamp = System.currentTimeMillis(); + log.info("[ShortNovel SSE] 处理事件: type={}, timestamp={}", type, eventTimestamp); + + // 从事件顶层取 session_id 同步 sessionId + // 上游事件结构与前端对齐:session_id 与 type 同级,不在 payload 内 + // 修复:首次 stream 里 status 处理从 payload 取不到 session_id,导致 currentSessionId 保持 null, + // 第一个 clarification_card 因 sessionId 为 null 不累积,详情页少显示第一个选项卡 + String topSessionId = event.getString("session_id"); + if (topSessionId != null && !topSessionId.isEmpty()) { + currentSessionId[0] = topSessionId; + } + + // 拦截 status 事件,缓存 sessionId → originalQuery 映射(供 followup 兜底) + if ("status".equals(type)) { +``` + +- [ ] **Step 2: 本地编译验证** + +Run: `cd server && mvn clean install -DskipTests` +Expected: `BUILD SUCCESS`,零错误。 + +- [ ] **Step 3: 部署到远程服务器** + +Run: `python deploy.py backend` +Expected: 部署脚本输出"部署成功",服务重启完成。 +如果部署脚本在 Windows 报 PATH 问题,手动执行: +```bash +scp server/target/emotion-single-1.0.0.jar root@101.200.208.45:/data/programs/emotion-museum/ +ssh root@101.200.208.45 "cd /data/programs/emotion-museum && ./restart.sh emotion-museum-single" +``` + +- [ ] **Step 4: H5 端到端验收** + +1. 启动 mini-program H5:`python dev-services.py start mini-program`(端口 5180) +2. 浏览器访问 `http://localhost:5180/#/pages/main/index?tab=script` +3. 手动完成一次新的小说生成(包含至少 2 轮澄清) +4. 生成过程中**记录生成页显示的选项卡数量 N** +5. 生成完成后,进入该剧本的详情页(通过列表页点"历史"进入 ScriptLibraryView,再点击对应剧本) +6. 验证详情页显示 **N 个澄清选项卡**(与生成页完全一致) +7. 浏览器 Console 检查:0 新增错误(既有的 `uni.getRecorderManager` 错误除外) +8. Network 面板:`listByConversation` 接口响应中 `clarification_question` 类型消息数量 = 生成页澄清轮数 + +- [ ] **Step 5: 提交代码** + +```bash +cd G:\IdeaProjects\emotion-museun +git add server/src/main/java/com/emotion/service/impl/ShortNovelServiceImpl.java +git commit -m "fix(server): 修复 forwardSse 首次 stream 第一个 clarification_card 不累积的 bug + +上游 SSE 事件的 session_id 在事件顶层(与 type 同级),后端原从 payload.session_id 取导致首次 stream 取不到, +currentSessionId 保持 null,第一个 clarification_card 不累积。 +改为从事件顶层取 session_id 同步 currentSessionId(与前端 ScriptView.vue:1805 逻辑对齐)。 +修复后新剧本详情页澄清选项卡数量与生成页完全一致。" +``` + +### 验收标准 + +- ✅ 本地 `mvn clean install` 编译通过 +- ✅ 部署到远程服务器成功 +- ✅ 新剧本详情页澄清选项卡数量 = 生成页澄清选项卡数量 +- ✅ Console 0 新增错误 +- ✅ 老剧本(3 个已有 conversation)保持现状,仍显示 2 个 card(数据已永久丢失,符合预期)