Files
wecom_it_smart_desk/docs/01-产品文档/00-产品规划/PRD-REQ-通用-005-选项选择持久化-v1.1-增量草案.md
T
Simon facc04aa65 chore: docs 结构整改 + compose 双目录对齐(合并重建提交)
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交
(5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。

一、docs 结构整改(整改 #14)
根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入,
随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录
树并存于 docs/,共 791 文件、双分类体系冲突。

修复动作:
- b2 同名异主题文件改名迁移保全 9 个
- C 类 39 个孤立文件按主题正确归类
- A/B1 类 222 个重复文件删除(新结构已有内容副本)
- 9 个旧独有空目录删除
- 270 处内部引用按 verified 映射改写
- 整改记录 #14 登记于 04-运维文档/部署运维

结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。
残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。

二、compose 双目录对齐(消除踩坑 A)
- docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist
  改为 src/frontend-*/dist(h5 / agent / admin / terminal)
- docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/
- 效果:本地 docker compose up 不再把根目录 stale dist 挂回,
  与线上一致,分叉隐患消除(已 docker compose config 校验通过)

防复发铁律:
- 重构须提交;仓库修复须 git stash -u 或先 commit
- 新结构须 git add 并提交,避免再次 untracked 复活
- H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
2026-08-07 22:31:32 +08:00

203 lines
13 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.
# PRD(增量草案) — 选项选择持久化 v1.1
> **REQ 编号**: REQ-通用-005
> **版本**: v1.1-**DRAFT**(草案,待用户确认范围,不替代 v1.0)
> **日期**: 2026-08-02
> **作者**: 许清楚(PM
> **状态**: 🟡 草案(v1.0 已批准,v1.1 仅增不删)
> **基线**: v1.0 PRD179 行,9 章节,已批准)+ v1.0 技术方案(523 行,11 章节)+ v1.0 测试用例(35 条)+ Dify v3 DRAFT
> **关联**:
> - 基线 PRD`docs/01-产品文档/00-产品规划/PRD-REQ-通用-005-选项选择持久化-v1.0.md`
> - 基线技术方案:`docs/02-技术文档/技术架构/技术方案-REQ-通用-005-选项选择持久化-v1.0.md`
> - 基线测试用例:`docs/03-测试文档/03-功能测试用例/TC-REQ-通用-005-选项选择持久化-v1.0.md`
> - Dify v3 DRAFT`docs/02-技术文档/实现配置/dify_dsl/itdesk_main_v3_feedback-vars_DRAFT.yml`
> - 原型:无(v1.0 暂无独立原型;UX 调整需重开)
---
## 1. 增量背景(Why v1.1
v1.0 已部署并通过 35 条测试用例验收,但 2026-08-02 用户实测暴露 3 个持续 Bug + 1 个新需求,**Dify v3 工作流(用户尚未导入)** 仍未落地,导致"AI 跟进选项"语义无法端到端验证。
| 触发事件 | 观察 | 业务影响 |
|---|---|---|
| 2026-08-02 17:45 用户反馈 | **Bug 4**:员工提问后看到 AI 回答,坐席端实时收单,但**员工端需刷新才显示** | 员工体感卡顿,怀疑"卡死" |
| 2026-08-02 17:45 用户反馈 | **Bug 5**:选选项后,**先看到 AI 思考占位 → 后同时出现"答案 + 上一个选择"** | 时序错位,破坏确定性信任 |
| 2026-08-02 17:45 用户问询 | **Bug 6**:曾发生"**页面刷新后选择消失**",询问是否真解决 | 会话连续性 + 审计可追溯 |
| 2026-08-02 17:45 用户强调 | **Req 7**:员工选选项后,**效果实时同步到坐席端(< 100ms)** | 紧急报修场景坐席分诊效率 |
**v1.1 目标**:在不动 v1.0 6 项产品决策(前缀:撤回/重选、幂等键、跨卡归属、敏感词、Dify 语义、转人工快照)的前提下,修复时序与持久化体感问题,并验证 v1.0 的"刷新保留"是否真达成。
---
## 2. 与 v1.0 的差异(What Changed
| 编号 | v1.0 内容 | v1.1 增量 | 优先级 |
|---|---|---|---|
| **Bug 4** | (v1.0 无) | 员工端 AI 回答延迟显示:消息已落库 + WS 已广播坐席端,但**员工端需刷新才显示** | **P0** |
| **Bug 5** | (v1.0 无) | 选选项时序错位:先 AI 思考占位 → 后同时出现"答案 + 上一个选择" | **P1** |
| **Bug 6** | v1.0 §4.1 / AC-01 已要求"落库 option_select + REST 仍可见" | 显式写入"刷新后必须保留选择历史"作为**P0 验收**;消除 v1.0 隐含歧义 | **P0** |
| **Req 7** | v1.0 §4.2 + §3.1 #2 仅要求"坐席可实时可见" | **新增显式时延指标**:坐席端 < 100ms 同步;坐席不在线时恢复后立即可见 | **P0**(用户强调) |
> v1.0 的 6 项产品决策(撤回/重选、5 秒 UUID 幂等、跨卡归属、敏感词 4 位中间 mask、Dify 5 字段 inputs、转人工 selected_options 快照)**全部不变**。
---
## 3. 增量 PRD:详细功能需求
### 3.1 Bug 4 — 员工端 AI 回答延迟显示(**P0**)
| 字段 | 内容 |
|---|---|
| **现象** | 员工提问 → AI 思考 → 答案已落库 + WS 已广播到坐席(坐席端实时可见)→ **员工端界面不更新**,刷新后才行 |
| **现状根因候选** | 员工端 `message-store``processedMessageIds` 已包含该 message_id(之前 WS 收到过 chunk 阶段 ID),导致 `new_message` 完整事件被 `if (processedMessageIds.has(data.message_id)) return` 静默丢弃(`src/frontend-h5/src/stores/conversation.ts:474-477` |
| **期望** | 员工端 AI 回答 < 1 秒内显示(不需要刷新) |
| **验收** | 员工端提问 → 2 秒内看到 AI 回答气泡(不强求同步打字机,但气泡必须出现) |
| **建议修复方向**(待架构师确认) | 区分"中间 chunk 的 message_id"与"最终 message_id";或把最终 `new_message` 事件直接接收,不去重 |
| **关联基线** | v1.0 §4.2 WS 事件;`conversation.ts:474-477``conversation.ts:1404-1407` |
### 3.2 Bug 5 — 选选项时序错位(**P1**)
| 字段 | 内容 |
|---|---|
| **现象** | 员工选选项 → UI 立即显示:AI 思考占位 + 上一个选择气泡 → 几秒后**同时出现"新答案 + 上一个选择"**(时序错位) |
| **现状根因候选** | H5 `sendOptionSelect` 触发 `process_h5_ai_reply` → 思考占位立刻 push 到 `messages.value``h5_ai_task.py:1574` WS 推送 `ai_thinking`)→ 但**上一个 option_select 消息尚未回流**到 store → 答案到达时**上一条 option_select 一起渲染** |
| **期望** | 选选项后 UI 顺序:① 已选气泡(✓)→ ② AI 思考占位 → ③ AI 答案气泡 |
| **验收** | 选选项后 3 个 UI 元素**按时间顺序独立出现**,不同时弹出 |
| **建议修复方向**(待架构师确认) | `sendOptionSelect` 同步把已选消息 push 到本地 store(不依赖 WS 回流),或后端先 ack 再触发 Dify |
| **关联基线** | v1.0 §4.4 H5 字段补全;`conversation.ts:1640-1710` |
### 3.3 Bug 6 — 刷新后选择消失(**P0**,需确认 v1.0 是否真解决)
| 字段 | 内容 |
|---|---|
| **现象** | 员工选选项后能看到 ✓ 气泡 → 刷新页面 → ✓ 气泡**消失** |
| **现状调研** | 见 §7 本节根因分析 |
| **调研结论** | H5 REST 端点 `h5.py:964-1029` **未实现 v1.0 §4.8 mask 要求**(存安全漏洞),但**未发现"消失"的代码缺陷**。理论上:DB 存原值 → REST 返回原值 → `mapMessage` 保留 `msg_type``MessageBubble.vue:174` 渲染 ✓ 模板 → 选项应可见 |
| **可能的"消失"根因** | ① 默认 `limit=50`,长会话(>50 条)历史选项被分页(P1 修复);② 浏览器缓存被清除(H5 应有 fallback);③ DB 该行因 WS 关闭或事务回滚未落库 |
| **v1.1 期望** | 显式写入"刷新后**所有历史选项气泡按时间顺序显示**"作为 P0 验收;不再依赖隐含理解 |
| **验收** | ① 员工选选项 → 刷新 → ✓ 气泡依然可见;② 选项按 server_timestamp 升序;③ 选项时间戳对应的 AI 题目卡片也应可见 |
| **建议修复方向** | ① 在 AC-01 加 P0 子项 AC-01-2"刷新后仍可见";② H5 端点在 `limit=50` 不够时支持 `before` 翻页验证;③ 紧急 P0 验证步骤必须包含实际操作 |
| **关联基线** | v1.0 §4.1 + AC-01`h5.py:964-1029` |
### 3.4 Req 7 — 实时同步到坐席端(**P0**,用户强调)
| 字段 | 内容 |
|---|---|
| **现象** | 员工选选项后,**坐席端是否立即看到**?用户担心存在 < 1s 延迟 |
| **现状** | v1.0 §4.2 已实现 `ws_manager.broadcast({"type": "new_message", "data": ...})`(坐席端有新事件后 MessageBubble 渲染 ✓),链路已稳定 |
| **v1.1 期望** | **显式时延指标**:员工点击选项 → 坐席端 < 100ms 内看到"已选:xxx ✓" |
| **验收** | ① 在线坐席端 < 1s 看到新消息事件;② 坐席不在线 → 重新加载时 REST 历史含同样 message_id;③ 弱网或 502 时降级为 3s 轮询可见 |
| **建议实施** | **无需新代码**——v1.0 §4.2 已实现。建议架构师出具 `E2E` 验收日志(WS 广播时间戳 + 坐席端 MessageBubble 渲染时间戳差值)证明 < 100ms |
| **关联基线** | v1.0 §4.2、AC-03、AC-10`ws.py:407-421` |
---
## 4. 与 v1.0 兼容性(What Stays
| 维度 | v1.0 决策 | v1.1 是否变动 |
|---|---|---|
| 撤回/重选语义 | 追加新行、不更新旧行(§4.1) | ❌ 不变 |
| 5 秒 UUID 幂等 | `(conversation_id, client_msg_id)` + 5s 窗口(§4.5 | ❌ 不变 |
| 跨卡归属 | `question_id + option_id` 联合,label 不作主键(§6) | ❌ 不变 |
| 敏感词 mask | 16 位数字中间 4 位 `****`(§4.8 | ❌ 不变 |
| Dify 5 字段 inputs | `feedback_type/question_id/option_id/option_value/option_label`(§4.6 | ❌ 不变 |
| 转人工快照 | `selected_options` 每题最新(§4.7) | ❌ 不变 |
| 5 重 UUID 守卫(V0-C 修复) | WS 在线 + 5 题内 + 5s 内 + 同题 + 未陈旧(`conversation.ts:1662-1675` | ❌ 不变,**作为 v1.1 Bug 5 修复的"前提"** |
---
## 5. 风险与依赖
| 风险 | 等级 | 缓解/验证 |
|---|---|---|
| **Dify v3 未导入** → 修复 Bug 4/5/6 后,**用户感知不到 Bug 真实修复**(因 AI 回答本身没变) | 高 | v1.1 落地**前提**:先确认 Dify v3 导入时间表;建议 2026-08-09 前完成 |
| 实时同步依赖后端 `ws_manager` 单进程 | 中 | v1.0 已固定 `--workers 1`v1.1 沿用 |
| 坐席端 before 翻页未断言 | 中 | v1.0 TC-004 已覆盖;v1.1 沿用 |
| 长会话 limit=50 可能漏显 | 中 | Bug 6 验证;若发现,v1.1 引入翻页 |
| H5 端点 mask 缺失(`h5.py:1025` | 高 | v1.0 §4.8 已要求,v1.1 修复(顺手) |
| 浏览器缓存丢失 | 低 | 现有 fallback `getMessages` API |
---
## 6. 待用户确认(5 个问题)
1. **Bug 4 / 5 / 6 的优先级排序**是否合理?(当前:Bug 4=Bug 6=P0 / Bug 5=P1
2. **Req 7 是否属于 v1.1 范围**?(用户强调"如果没有技术问题,希望实现"——倾向属于 v1.1)
3. 是否同意新增"**刷新后必须保留选择历史**"作为 v1.1 P0 验收(AC-01-2 增项)?
4. **Dify v3 何时导入**?(影响 AI 能否"基于选项的跟进",进而影响 Bug 4/5 是否真验收)
5. 是否需要拉原型?如需 UI 调整(坐席侧徽标位置、员工端选项视觉等),需 UX 重新设计
---
## 7. Bug 6 根因分析(基于代码 + Git 现状)
> 用户问"之前发生过页面刷新后选择消失问题,是否真解决"——下面给出**基于代码证据**的判断。
### 7.1 代码调研(5 个关键点)
| # | 调研点 | 代码位置 | 结论 |
|---|---|---|---|
| 1 | 后端持久化 | `ws.py:374-385` `Message(msg_type="option_select", content=option_label, ...)` + `db.add()` + `commit` | ✅ 落库 |
| 2 | H5 REST 端点是否返回 option_select | `h5.py:964-1029` `select(Message).where(conversation_id==...)`**无 msg_type 过滤** + `MessageResponse.model_validate(m).model_dump()` | ✅ 应返回 |
| 3 | H5 端点是否 mask option_select | `h5.py:1025` **直接 dump,无 mask**(违反 v1.0 §4.8 | ❌ **存安全漏洞**,但**与"消失"无关** |
| 4 | 前端字段映射 | `api/conversation.ts:234-249` `mapMessage``msg_type: raw.msg_type` 直接透传 | ✅ 保留 |
| 5 | UI 渲染分支 | `MessageBubble.vue:174-179` `<template v-else-if="msg.msg_type === 'option_select'">✓ {{ msg.content }}</template>` | ✅ 渲染分支存在 |
| 6 | 缓存 + 合并 | `conversation.ts:62-82` + `85-97` + `117-119` `mergeMessages``message_id` 去重 | ✅ 应保留 |
| 7 | 限分页 | `h5.py:999` `limit(limit)` 默认 50 | ⚠️ 长会话会被分页 |
### 7.2 根因判定(确定性)
**基于代码,`Bug 6 "刷新后选择消失" 在当前 v1.0 应不应发生**?——**不应发生**。但有以下 3 个潜在触发场景:
| 场景 | 当前是否根因 | 概率 |
|---|:---:|:---:|
| **A. H5 端点未 mask**`h5.py:1025`) | 否(是安全 bug,不是"消失" | 高 |
| **B. 默认 limit=50,长会话历史选项被分页** | **是**(超过 50 条之后,刷新只显示最新 50 条) | 中 |
| **C. 浏览器缓存被清除 + step 3 异步 fetch 失败** | **是**(无网络时,UI 不会显示选项气泡) | 中 |
| **D. 消息真正未落库**(DB 事务回滚) | 否(v1.0 §3.1 #1 已 P0 验收,PG advisory lock 已避免) | 极低 |
| **E. 字段映射丢失**`msg_type` 被过滤) | 否(`mapMessage` 直接透传) | 极低 |
### 7.3 结论
**Bug 6 在 v1.0 当前实现下大概率已被解决**——代码层面:
- 后端 ✅ 落库
- API ✅ 返回
- 字段映射 ✅ 保留
- UI ✅ 渲染分支存在
**但有 2 个**潜在根因未被 v1.0 显式覆盖:
1. **H5 端点 limit=50 未分页验证**(场景 B
2. **H5 端点 mask 缺失**(场景 A,与"消失"无关但是安全漏洞)
**v1.1 建议**
- 把"刷新后保留"显式写入 PR v1.1 验收(AC-01-2
- 顺手修复 H5 端点 mask 漏洞(§3.1 修复的同时)
- 增加 `E2E` 验收步骤:选选项 → 刷新 → 截图选项气泡
---
## 8. 附录:v1.1 增量范围 vs 完整 PRD
| 范围 | 是否在 v1.1 增量草案 | 备注 |
|---|:---:|---|
| Bug 4 / 5 / 6 修复 | ✅ 草案 | 待用户确认优先级 |
| Req 7 实时同步验收 | ✅ 草案 | 显式时延指标 |
| v1.0 6 项决策 | ❌ 不再重复 | 见 v1.0 原 PRD |
| Dify v3 变更 | ❌ 不再重复 | 见 Dify v3 CHANGELOG |
| 测试用例增量 | ⏳ 下一步 | 待 QA 在 v1.1 范围确认后增量 |
| 完整 PRD(含组件图、时序图、API 变更) | ❌ 不出 | 用户明确"先写草案" |
---
## 9. 变更记录
| 版本 | 日期 | 变更内容 | 变更人 | 变更原因 |
|---|---|---|---|---|
| v1.0 | 2026-07-29 | 创建 PRD,固化方案 A、6 项产品决策与 MVP 验收范围 | 许清楚、宋献 | 修复选项选择不落库 |
| **v1.1-DRAFT** | **2026-08-02** | **增量草案:3 Bug + 1 Req + Bug 6 根因分析 + 5 待确认问题** | **许清楚** | **用户实测反馈 + 询问刷新保留是否真解决** |
---
> **下一步**:等待用户对 §6 五个问题的回复 → 确认 v1.1 范围 → 由架构师出 v1.1 技术方案 → 由 QA 补 v1.1 测试用例增量。