203 lines
13 KiB
Markdown
203 lines
13 KiB
Markdown
|
|
# PRD(增量草案) — 选项选择持久化 v1.1
|
|||
|
|
|
|||
|
|
> **REQ 编号**: REQ-通用-005
|
|||
|
|
> **版本**: v1.1-**DRAFT**(草案,待用户确认范围,不替代 v1.0)
|
|||
|
|
> **日期**: 2026-08-02
|
|||
|
|
> **作者**: 许清楚(PM)
|
|||
|
|
> **状态**: 🟡 草案(v1.0 已批准,v1.1 仅增不删)
|
|||
|
|
> **基线**: v1.0 PRD(179 行,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 测试用例增量。
|