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

13 KiB
Raw Blame History

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 关联:

  • 基线 PRDdocs/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 DRAFTdocs/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-storeprocessedMessageIds 已包含该 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-477conversation.ts:1404-1407

3.2 Bug 5 — 选选项时序错位(P1

字段 内容
现象 员工选选项 → UI 立即显示:AI 思考占位 + 上一个选择气泡 → 几秒后同时出现"新答案 + 上一个选择"(时序错位)
现状根因候选 H5 sendOptionSelect 触发 process_h5_ai_reply → 思考占位立刻 push 到 messages.valueh5_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_typeMessageBubble.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-01h5.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-10ws.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 1v1.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 mapMessagemsg_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 mergeMessagesmessage_id 去重 应保留
7 限分页 h5.py:999 limit(limit) 默认 50 ⚠️ 长会话会被分页

7.2 根因判定(确定性)

基于代码,`Bug 6 "刷新后选择消失" 在当前 v1.0 应不应发生?——不应发生。但有以下 3 个潜在触发场景:

场景 当前是否根因 概率
A. H5 端点未 maskh5.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 测试用例增量。