**重构前**(旧编号 02-11): - docs/02-产品需求/ → 00 产品规划/PRD - docs/03-技术架构/ → 01-05 子目录散落 - docs/04-原型设计/ → 01-02 产品设计(HTML 原型) - docs/05-原型设计/ → screens/ - docs/06-测试素材/ → 02-E2E / 03-功能 / 04-版本测试 - docs/07-项目管理/ → 任务说明书/日报/计划 - docs/08-安全审计/ → 审计报告 - docs/09-堡垒运维/ → toolbox / deploy - docs/10-项目管理/ → 任务说明书(重复) - docs/11-历史归档/ → deploy-nas-archived **重构后**(新编号 00-07,语义化): - docs/00-产品开发流程与文档管理规范.md - docs/00-版本迭代总览.md - docs/01-产品文档/ (PRD/原型/认证/会话/AI 服务/坐席/集成) - docs/02-技术文档/ (技术方案/架构图/重构记录/前端改造/实现配置) - docs/03-测试文档/ (E2E/功能用例/版本报告/缺陷单) - docs/04-运维文档/ (部署运维/运维指南) - docs/05-运营文档/ (品牌推广/用户手册) - docs/06-安全审计/ (审计报告) - docs/07-项目管理/ (任务说明书/日报/计划/看板) **净收益**: - 目录编号与产品文档管理规范对齐(按文档阶段 01-07 编号) - 消除 02-产品需求 与 10-项目管理 的编号重叠 - 子目录按文档类型分组(如 01-产品文档/00-产品规划、01-产品文档/01-认证与登录) - 把运维/安全/项目管理从 0X 散落改为 04/06/07 合计 494 文件 + 78495 行 / - 14076 行
37 KiB
AI 对话链路全栈改造实施计划
版本: v1.2 日期: 2026-07-24 作者: 宋献 (Simon) + Duckula 状态: ✅ 已实施并部署(Phase 1-6 全部完成,2026-07-13 01:38 生产部署)
部署记录
2026-07-13 01:38 生产部署(v5)
| 组件 | 版本 | 部署内容 | 验证 |
|---|---|---|---|
| 后端 | v5 | 6 个 Python 文件部署到 /opt/wecom-it-desk/app/ |
docker compose restart backend → healthy ✅ |
| H5 前端 | v4 | dist 部署到 /opt/wecom-it-desk/frontend-h5/dist/ |
JS hash index-B6dzwk-X.js ✅ |
| Agent 前端 | v5 | dist 部署到 /opt/wecom-it-desk/frontend-agent/dist/ |
JS hash index-2BTn4SZz.js ✅ |
| Nginx | - | nginx -s reload |
healthy ✅ |
后端变更文件清单(旧文件备份在 /tmp/backend_bak_v5/):
| 文件 | 变更内容 |
|---|---|
app/tasks/h5_ai_task.py |
VisionService 接入 + 图片消息融合 + ai_thinking 双推 + diagnosis_stage 存储 |
app/api/h5.py |
process_h5_ai_reply() 调用新增 msg_type + media_url 参数 |
app/services/ai_service.py |
get_structured_reply() blocking 模式 + JSON 解析 + response_time_ms 计时 + 慢响应告警 |
app/services/closing_service.py |
新增 check_diagnosis_stage() + get_diagnosis_summary() 辅助方法 |
app/services/vision_service.py |
已有实现,本次接入主链路 |
app/api/websocket.py |
ai_thinking 同时推员工和坐席 + dynamic_recommend 推送 |
前端变更文件清单:
| 前端 | 文件 | 变更内容 |
|---|---|---|
| H5 | MessageBubble.vue |
新增 ai_structured 渲染分支(文字 + 选项按钮 + 脉冲动画) |
| H5 | DynamicRecommend.vue |
新建 — 右边栏动态推荐卡片(3 种类型 approval/action/info) |
| H5 | RightPanel.vue |
重写为 v2 手风琴布局(设备信息 / 自助诊断 / 底部标签页) |
| H5 | useH5WebSocket.ts |
新增模块级 sendWsMessage() 导出函数 + ai_thinking / dynamic_recommend case |
| H5 | conversation.ts |
sendOptionSelect() WS + HTTP 降级;删除 checkApprovalIntent |
| H5 | api/conversation.ts |
MsgContentType 新增 'ai_structured' |
| Agent | MessageBubble.vue |
新增 ai_structured 只读渲染 + byod_card 渲染分支 |
| Agent | ChatArea.vue |
新增 AI 思考指示器 UI(脉冲动画) |
| Agent | useWebSocket.ts |
新增 ai_thinking case |
| Agent | conversation.ts |
新增 aiThinkingConversations + handleAiThinking() + handleNewMessage 透传修复 |
部署方式:通过 v2_ops.py(jumpserver-V2 技能)JumpServer REST API + plink PTY 执行
验证结果:
- 5 个容器全部 healthy(backend / nginx / redis / neo4j / postgres)
- 后端
/health返回 200 - 后端日志正常(调度任务运行,无报错)
- H5 / Agent 前端新 JS hash 已就位
一、背景与目标
1.1 改造背景
当前 IT 智能服务台存在以下核心问题:
- Dify 工作流臃肿:85 个节点,推理延迟高,维护困难
- 审批意图识别质量差:回复太快、无关触发、精度低、准确率低
- 卡片与文字"两张皮":审批卡片由前端异步独立推送,与 AI 文字回复无关联、时间不同步
- 图片消息处理缺失:
VisionService完整实现但零调用,员工发图片 AI "失明" - 右边栏布局分散:自助诊断、软件下载、资源申请三个独立模块,无动态推荐区
1.2 改造目标
| 目标 | 衡量标准 |
|---|---|
| Dify 节点精简 | 85 → ~35 节点,推理延迟降低 40%+ |
| 审批意图准确率 | 误触发率从 ~60% 降至 ~15% |
| 卡片与文字融合 | 同一次推理、同一时刻到达、语义强关联 |
| 图片消息可用 | 员工发截图 → AI 能"看懂"并回复 |
| 右边栏统一 | 手风琴折叠 + 智能推荐默认页 |
二、当前架构分析
2.1 当前消息流:两条平行轨道
┌─ 通道 A(审批卡片)──────────────────────────────────┐
│ 前端 checkApprovalIntent() │
│ → POST /approval/detect-intent │
│ → Dify 意图识别 (blocking, 同一个 Dify 应用) │
│ → 前端 messages.value.push(approvalCardMessage) │
│ ※ 异步 fire-and-forget,与 AI 回复独立 │
└───────────────────────────────────────────────────────┘
┌─ 通道 B(AI 文字回复)────────────────────────────────┐
│ 后端 process_h5_ai_reply() │
│ → routing_keyword_prefilter → detect_routing_intent │
│ → ai_service.get_reply_stream() │
│ → Dify 主对话应用 (streaming, dify2openai 代理) │
│ → WS: ai_reply_chunk → ai_reply │
│ ※ 前端逐字渲染,与卡片无关联 │
└───────────────────────────────────────────────────────┘
问题:两条通道各自调用 Dify,结果可能矛盾;卡片随机插入聊天流,打断阅读节奏。
2.2 关键代码位置
| 模块 | 文件路径 | 关键行号 | 说明 |
|---|---|---|---|
| 后端消息处理 | backend/app/tasks/h5_ai_task.py |
368-474 | process_h5_ai_reply() 主函数 |
| 路由预过滤 | backend/app/services/routing_service.py |
46-57, 80-94 | ROUTING_PREFILTER_KEYWORDS + routing_keyword_prefilter() |
| 审批预过滤 | backend/app/api/approval.py |
213-242, 957-976 | APPROVAL_PREFILTER_KEYWORDS + _keyword_prefilter() |
| BYOD 预过滤 | backend/app/api/byod.py |
102-113 | BYOD_PREFILTER_KEYWORDS |
| AI 流式调用 | backend/app/services/ai_service.py |
197-292 | get_reply_stream() — OpenAI 兼容格式 |
| AI 非流式调用 | backend/app/services/ai_service.py |
83-192 | get_reply() — 降级用 |
| AI 处理器 | backend/app/services/ai_handler.py |
199-329 | handle_message() + AIReplyResult |
| 视觉服务 | backend/app/services/vision_service.py |
全文件 | 完整实现但零调用 |
| RAGFlow 客户端 | backend/app/integrations/ragflow/client.py |
全文件 | 完整实现但未接入主链路 |
| RAGFlow 旧客户端 | backend/app/core/clients/ragflow.py |
全文件 | 依赖未配置环境变量 |
| 前端审批检测 | frontend-h5/src/stores/conversation.ts |
794-820 | checkApprovalIntent() — 异步推送卡片 |
| 前端审批调用 | 同上 | 458-460 | sendNewMessage() 中 fire-and-forget |
| 前端 WS 处理 | frontend-h5/src/composables/useH5WebSocket.ts |
301-436 | handleMessage() — 消息类型路由 |
| 前端 AI 回复 | frontend-h5/src/stores/conversation.ts |
1029-1127 | handleAiReplyChunk() + handleAiReply() |
| 前端右边栏 | frontend-h5/src/components/assistant/RightPanel.vue |
15-77 | 模板结构 |
| Dify 意图 Prompt | docs/02-产品需求/dify_unified_intent_prompt_v3.md |
全文件 | v3 统一意图识别 Prompt |
| BYOD 意图 Prompt | docs/02-产品需求/dify_byod_intent_prompt.md |
全文件 | BYOD 意图扩展 |
2.3 当前三套预过滤关键词系统
| 系统 | 变量名 | 位置 | 词数 | 问题 |
|---|---|---|---|---|
| 审批预过滤 | APPROVAL_PREFILTER_KEYWORDS |
approval.py:213-242 |
~40 个 | 过于宽泛,"设备""电脑""邮箱""权限"等高频词几乎覆盖所有 IT 场景 |
| 路由预过滤 | ROUTING_PREFILTER_KEYWORDS |
routing_service.py:46-57 |
~22 个 | 合理,仅非 IT 业务词 |
| BYOD 预过滤 | BYOD_PREFILTER_KEYWORDS |
byod.py:102-113 |
~10 个 | 合理,仅 BYOD 专用词 |
2.4 两套 Dify API 调用模式
| 维度 | AI 回复 (ai_service.py) | 意图识别 (approval.py / routing_service.py) |
|---|---|---|
| API 端点 | dify_api_url (dify2openai 代理) |
approval_dify_base_url + /v1/chat-messages (Dify 原生) |
| 请求格式 | OpenAI 兼容 (messages, stream) |
Dify 原生 (inputs, query, response_mode) |
| 响应格式 | choices[0].delta.content (SSE) |
answer 字段含 JSON 字符串 |
| 流式 | 是 | 否 (blocking) |
| 用途 | IT 知识库问答 | 审批/路由意图分类 |
| 配置 | dify_api_url, dify_api_key |
approval_dify_base_url, approval_dify_api_key |
三、差距分析(11 项)
3.1 改造方案能解决的(3 项)
| # | 差距 | 解决程度 | 说明 |
|---|---|---|---|
| 1 | Dify 节点精简 | 完全解决 | 85→35 节点,删除冗余分支 |
| 5 | 保留 RAGFlow 节点 | 完全解决 | Dify 内部知识检索不丢失 |
| 6 | 保留 Vision 节点 | 完全解决 | Dify Vision Workflow 节点保留 |
3.2 方案部分覆盖但有风险的(3 项)
差距 ①:JSON 解析链路断裂 [P0]
问题:Dify Prompt 改为输出 JSON 后,后端和前端都没有解析能力。
ai_service.py:232-263—get_reply_stream()把choices[0].delta.content作为纯文本逐 chunk 透传ai_handler.py:278-282—handle_message()把ai_result["content"]直接放入AIReplyResult.contentconversation.ts:1029-1054—handleAiReplyChunk()直接将data.chunk追加到content字段MessageBubble.vue— 对msg_type === 'text'用white-space: pre-wrap纯文本渲染
需要补的:全链路结构化改造——Dify 输出 JSON → 后端解析提取 → WS 推送结构化消息 → 前端新增渲染组件。
差距 ②:Dify 不支持图片输入 [P1]
问题:ai_service.py:217-226 的 payload 用 OpenAI 兼容格式,content 只接受字符串,不能传图片。
正确路径:图片 → VisionService.analyze_screenshot() 预分析 → 生成文字描述 → 拼接到 Dify 文本输入中。
差距 ③:消息融合的边界场景 [P2]
问题:
- 超时风险:用户发图后思考 10 秒再打字,5 秒窗口已关闭
- 多图处理:连续发 3 张截图 + 一段文字,合并还是分别处理?
- 乱序到达:文字先到、图片后到
- 窗口内多条文字:多条短消息需要合并
3.3 方案完全未涉及的(5 项)
差距 ④:VisionService 完整实现但零调用 [P0]
问题:vision_service.py 有完整的 analyze_screenshot() + inject_to_conversation_context() 实现,但只通过独立 REST 端点暴露,process_h5_ai_reply 中没有调用。
员工发图片 → 不触发视觉分析 → Dify 只收到空文字 → AI 回复"请问您遇到了什么问题?"
需要补的:在 process_h5_ai_reply 中增加图片分支——检测到图片 → 调 VisionService.analyze_screenshot() → 描述拼接到用户文字中 → 传给 Dify。
差距 ⑤:选项回传链路 [P1]
问题:AI 回复包含选项按钮,用户点击后无链路回传。
- WS 消息类型缺失:没有
option_select类型 - 后端处理缺失:没有 API 端点接收用户选项选择
- Dify 对话续接缺失:选择后需要用
conversation_id继续对话
完整链路:用户点击选项 → 前端发 WS option_select → 后端接收 → 转化为 Dify user message → get_reply_stream() → 推送新 AI 回复 → 前端渲染下一张卡片。
差距 ⑥:坐席端可见性 [P2]
问题:坐席端 WS 消息处理只识别 ai_reply(纯文本),不识别卡片/选项类型消息,坐席端前端也没有渲染组件。
差距 ⑦:错误处理与降级 [P0]
| 失败场景 | 当前行为 | 期望降级 |
|---|---|---|
| Dify 返回非 JSON | 前端渲染原始文本(暴露 JSON 源码) | 后端检测 → 降级为纯文本回复 |
| VisionService 失败 | 未接入,不触发 | 降级为"我收到了您的截图,请描述一下问题" |
| 消息融合超时 | 未定义 | 超时后单独处理已有消息 |
| Dify 响应超时 | 无超时保护 | 15 秒超时 → "正在思考" → 30 秒 → 建议转人工 |
差距 ⑧:诊断闭环与 Dify 的协调 [P3]
问题:Queue/Quiz/Closing 系统与 Dify 对话流是两条独立轨道,改造后信息锁定条件如何判定需要明确。
四、审批意图识别问题分析(4 问题 + 3 根因)
4.1 四个问题的根因
问题 1:「回复太快了」
根因:审批卡片推送走前端异步 fire-and-forget 路径。
// conversation.ts:458-460
checkApprovalIntent(content).catch(...) // 异步触发,不等待
Dify 意图识别返回后(3-15 秒),卡片突然插入消息流中,与正在流式输出的 AI 回复交错出现。无任何确认步骤。
问题 2:「无关性」
根因:APPROVAL_PREFILTER_KEYWORDS(approval.py:213-242)有 ~40 个词,包含"设备""电脑""邮箱""权限""软件""报修"等高频 IT 词,几乎覆盖所有 IT 相关消息。
例如员工说"我的邮箱登不上"——命中"邮箱" → 触发 Dify 意图识别 → 可能误判为"公共邮箱账号申请" → 推送审批卡片。
问题 3:「精度差」
根因:一次性判断 12 种审批类型,无层级分类。LLM 容易混淆相似类型("会议室故障报修" vs "员工IT支持与故障报修")。且每次只看单条消息,不看对话历史。
问题 4:「准确率低」
根因:前后端双重调用同一 Dify 意图应用但不共享结果:
- 前端调
POST /approval/detect-intent→ Dify 判断审批意图 - 后端
routing_keyword_prefilter命中 →detect_routing_intent→ 同一个 Dify 判断路由意图
两者可能给出不一致的判断。
4.2 三个额外根因及解决方案
根因 A:关键词预过滤过于宽泛(代码层)
当前:APPROVAL_PREFILTER_KEYWORDS 有 ~40 词。
改造:收窄到仅强意图词——
# 改造后:只保留明确表达"申请/提交"意图的词
APPROVAL_PREFILTER_KEYWORDS = [
"申请", "审批", "提交", "表单", "走流程",
"帮我申请", "我要申请", "需要申请",
]
# 去掉:设备、电脑、邮箱、权限、软件、报修、变更等高频词
效果:预过滤命中率从 ~60% 降到 ~15%,减少 75% 的无效 Dify 调用。
根因 B:前后端双重调用未统一(架构层)
当前:前端 checkApprovalIntent() + 后端 _handle_routing() 各自调 Dify。
改造:统一为后端单一入口——前端删除 checkApprovalIntent(),所有意图判断由后端在 process_h5_ai_reply 中统一处理,结果通过 WebSocket 推送。
根因 C:12 种审批类型一次性分类(Prompt 层)
当前:Dify 意图识别 Prompt 要求 LLM 一次性从 12 种类型中选择。
改造:改为两级分类——
第一级(粗分,4类):
- 设备类(设备申请/资产变更/资产处置)
- 账号权限类(账号权限/VPN/公共邮箱)
- 软件应用类(软件服务/企业应用管理)
- 服务支持类(故障报修/会议室/活动支持)
第二级(细分,仅当第一级命中后触发):
- 在粗分结果范围内做精确匹配
- 配合 few-shot examples 强化边界
五、统一消息架构设计
5.1 核心设计:一次推理,双通道交付
用户消息 → 后端 process_h5_ai_reply()
│
├─ 1. 统一意图识别(一次 Dify 调用,blocking)
│ → 判定 intent_type: approval / it_consult / non_it_routing / chitchat
│
├─ 2. 根据意图分流:
│ ├─ approval → Dify 主对话应用(输出 JSON: text + action)
│ ├─ it_consult → Dify 主对话应用(输出 JSON: text + options)
│ ├─ non_it_routing → 推送名片 + 正常 AI 回复
│ └─ chitchat → 正常 AI 回复
│
└─ 3. 后端解析 Dify JSON,同时发两条 WS 消息:
├─ WS: ai_reply → 聊天气泡(text + options)
└─ WS: dynamic_recommend → 侧边栏推荐(action 卡片)
关键:两条 WS 消息由后端在同一时刻发出,文字和卡片零时间差到达,文字明确引用侧边栏内容(如"右侧已为您准备好入口")。
5.2 Dify JSON 输出格式
场景 1:审批意图(文字 + 卡片推荐)
{
"text": "您想申请 VPN 账号?点击右侧卡片快速提交,一般 1-2 个工作日审批完成。",
"action": {
"type": "approval_card",
"approval_type": "账号权限申请",
"title": "VPN 账号申请",
"template_id": "tpl_vpn_001",
"confidence": 0.92
},
"options": null
}
场景 2:交互式排查(文字 + 选项)
{
"text": "电脑蓝屏了?我来帮您排查。蓝屏时有错误代码吗?",
"action": null,
"options": [
{"label": "有错误代码", "value": "has_code"},
{"label": "没有", "value": "no_code"},
{"label": "不确定", "value": "unsure"}
]
}
场景 3:纯文字回复
{
"text": "好的,VPN 账号一般 1-2 个工作日审批完成,届时会通过企微通知您。",
"action": null,
"options": null
}
5.3 WS 消息格式
聊天气泡消息
{
"type": "ai_reply",
"data": {
"message_id": "msg_xxx",
"content": "您想申请 VPN 账号?点击右侧卡片快速提交...",
"msg_type": "ai_structured",
"extra_data": {
"options": [
{"label": "有错误代码", "value": "has_code"}
]
}
}
}
侧边栏推荐消息
{
"type": "dynamic_recommend",
"data": {
"recommend_id": "rec_xxx",
"card_type": "approval_card",
"title": "VPN 账号申请",
"description": "1-2 个工作日审批完成",
"action": {
"type": "approval_card",
"approval_type": "账号权限申请",
"template_id": "tpl_vpn_001"
},
"confidence": 0.92
}
}
5.4 流式 vs 阻塞决策
| 方案 | 用户体验 | 实现复杂度 | 延迟感知 |
|---|---|---|---|
| A. 全阻塞 | 等 3-8 秒 → 文字+卡片同时出现 | 最简单 | 有等待感 |
| B. 阻塞 + 思考指示(推荐) | 立即显示"正在思考..." → 3-8 秒后同时出现 | 简单 | 等待感降低 |
| C. 混合流式 | Dify 先返回 text → 流式推文字 → 再返回 action | 最复杂 | 体验最好但 Dify 不支持分段 JSON |
选择方案 B:阻塞模式 + "正在思考..."指示。理由:
- 改造后消息变短,3-8 秒等待可接受
- 文字和卡片同时出现,满足"发送时间一致"
- 实现最简单,不需要改 Dify 的流式机制
- 之前审批意图独立调 Dify 也是 blocking 模式,用户已习惯
5.5 图片消息处理路径
员工发图片 (msg_type=image)
│
├─ 1. 后端 process_h5_ai_reply 检测到 msg_type=image
│
├─ 2. 调用 VisionService.analyze_screenshot(media_url)
│ → Pillow 预处理 (resize + JPEG 压缩)
│ → base64 编码 POST 到 Dify Vision Workflow (Qwen3-VL-8B)
│ → 返回结构化描述 {description, confidence, metadata}
│
├─ 3. 将描述作为上下文前缀拼接到用户文字中:
│ "[图片分析] 用户发送了一张截图,内容为:{description}\n用户消息:{text}"
│
└─ 4. 传给 Dify 主对话应用 → 正常 JSON 输出流程
降级:VisionService 失败时,回复"我收到了您的截图,但暂时无法识别内容,请描述一下您遇到的问题"。
5.6 消息融合机制(5 秒窗口)
消息到达 → 加入待处理队列
│
├─ 队列为空 → 启动 5 秒计时器
├─ 队列有消息 → 重置计时器(最多 3 次重置 = 15 秒上限)
│
└─ 计时器到期 → 合并队列所有消息,一次性传给 Dify
├─ 图片 → VisionService 分析 → 描述文字
├─ 文字 → 直接拼接
└─ 多张图片 → 依次分析,描述拼接
边界处理:
- 超时(15 秒上限):强制提交已有消息
- 多条文字:用换行符拼接
- 图片 + 文字:图片描述作为前缀,用户文字在后
六、右边栏 v2 设计
6.1 布局结构
右边栏
├── ① 设备信息(默认折叠)
│ ├── 当前设备:设备名 + IP(始终可见,一行)
│ └── [展开] CPU / 内存 / 硬盘指标
│ └── 其他设备列表
│
├── ② 自助诊断(默认折叠,手风琴互斥)
│ ├── 标签页:网络联通 / 账号权限 / 设备硬件
│ └── 标签内容:检测结果 + 重新检测按钮
│
├── ③ 统一标签页区域(始终可见,不参与手风琴)
│ ├── 智能推荐(默认选中,带 Badge 红点)
│ │ └── AI 动态推荐卡片(审批/操作入口)
│ ├── 软件安装
│ │ └── 6 常用软件 + 安装状态
│ └── 资源权限
│ └── 6 个申请入口卡片
│
└── ④ 排队等待(独立,折叠态)
6.2 手风琴交互逻辑
| 区域 | 默认状态 | 交互行为 |
|---|---|---|
| 设备信息 | 折叠(CPU/内存/硬盘隐藏) | 点击展开 → 显示硬件指标;再点或点击自助诊断 → 自动收回 |
| 自助诊断 | 折叠 | 点击展开 → 显示标签页,默认选中"网络联通";再点或点击设备信息 → 自动收回 |
| 统一标签页 | 智能推荐默认选中 | 始终可见,标签切换,不参与手风琴 |
互斥规则:设备信息和自助诊断之间互斥——同一时间只有一个展开。
6.3 智能推荐组件行为
- 空状态:不显示(不占空间)
- 有推荐:淡入显示,最多 3 张卡片,每张带图标 + 标题 + 一句话描述 + 操作按钮
- 点击操作按钮:触发企微审批
wx.invoke('thirdPartyOpenPage', ...) - 自动过期:对话话题切换后,旧推荐淡出消失
- 手动关闭:每张卡片右上角有 × 按钮
- Badge 红点:有新推荐时显示数量
6.4 组件变更
| 原组件 | 变更 | 新组件 |
|---|---|---|
BasicInfoCard.vue |
增加折叠/展开逻辑 | BasicInfoCard.vue (改造) |
SelfDiagnosis.vue |
增加标签页 + 折叠逻辑 | SelfDiagnosis.vue (改造) |
SoftwareAndApply.vue |
拆分为标签页形式 | SoftwareInstall.vue + ResourcePermission.vue |
| 无 | 新增 | DynamicRecommend.vue (新建) |
RightPanel.vue |
整体布局重构 | RightPanel.vue (改造) |
七、实施计划
7.1 阶段划分
实施状态:Phase 1-6 全部完成,2026-07-13 01:38 生产部署验证通过。 v1.3 补充交付:2026-07-25,AI 回复打字机逐字显示效果(前端 useTypewriter composable),H5 + Agent 双端已编译验证通过。
Phase 1: 基础设施层(P0,前置条件)✅ 已完成
├─ 1A. Dify System Prompt 改造(JSON 输出)✅
├─ 1B. 后端统一消息处理(解析 JSON + 双 WS 推送)✅
├─ 1C. 后端错误降级机制(30s 超时 / 15s still_thinking)✅
└─ 1D. Dify 节点精简(85→35)✅
Phase 2: 意图识别优化(P0,并行于 Phase 1)✅ 已完成
├─ 2A. 收窄审批关键词预过滤(~40→~25 强意图词)✅
├─ 2B. 统一前后端意图调用(删除前端 checkApprovalIntent)✅
└─ 2C. 两级审批类型分类 Prompt v4.0 ✅
Phase 3: 前端改造(P0-P1,依赖 Phase 1)✅ 已完成
├─ 3A. WS 消息类型扩展(ai_thinking + dynamic_recommend + option_select)✅
├─ 3B. 聊天气泡渲染改造(文字 + 选项按钮)✅
├─ 3C. 右边栏 v2 重构(手风琴 + 智能推荐默认页)✅
└─ 3D. 选项回传链路 ✅
Phase 4: 图片处理(P0-P1,依赖 Phase 1)✅ 已完成
├─ 4A. VisionService 接入 process_h5_ai_reply ✅
└─ 4B. 消息融合机制(5 秒窗口)✅
Phase 5: 坐席端适配(P2,依赖 Phase 1+3)✅ 已完成
├─ 5A. 坐席端 WS 消息类型扩展 ✅
└─ 5B. 坐席端卡片渲染组件 ✅
Phase 6: 收尾(P3)✅ 已完成
├─ 6A. 诊断闭环与 Dify 协调 ✅
└─ 6B. 性能优化与监控 ✅
7.2 各阶段详细任务
Phase 1: 基础设施层
| 任务 | 文件 | 具体改动 |
|---|---|---|
| 1A. Dify Prompt 改造 | Dify 后台 + docs/02-产品需求/dify_unified_intent_prompt_v3.md |
将主对话应用 System Prompt 改为输出 {text, action, options} JSON 格式;意图识别应用 Prompt 改为两级分类 |
| 1B. 后端统一消息处理 | backend/app/services/ai_service.py |
get_reply_stream() 改为 blocking 模式调用 Dify;解析返回 JSON,提取 text/action/options;返回结构化结果而非纯文本 |
backend/app/services/ai_handler.py |
AIReplyResult 增加 action 和 options 字段;handle_message() 返回结构化结果 |
|
backend/app/tasks/h5_ai_task.py |
process_h5_ai_reply() 解析结构化结果;同时发送 ai_reply(文字+选项)和 dynamic_recommend(卡片)两条 WS 消息 |
|
backend/app/api/websocket.py(或对应 WS 管理文件) |
新增 dynamic_recommend WS 消息类型 |
|
| 1C. 错误降级 | backend/app/services/ai_service.py |
Dify 返回非 JSON → 降级为纯文本回复;15 秒超时 → "正在思考...";30 秒超时 → 建议转人工 |
| 1D. Dify 节点精简 | Dify 后台 | 85→~35 节点,保留 RAGFlow 和 Vision 节点 |
Phase 2: 意图识别优化
| 任务 | 文件 | 具体改动 |
|---|---|---|
| 2A. 收窄审批关键词 | backend/app/api/approval.py:213-242 |
APPROVAL_PREFILTER_KEYWORDS 从 ~40 词缩减到 ~7 个强意图词 |
| 2B. 统一前后端意图调用 | backend/app/tasks/h5_ai_task.py |
在 process_h5_ai_reply 中统一调用 Dify 意图识别,不再由前端独立调用 |
frontend-h5/src/stores/conversation.ts:458-460, 794-820 |
删除 checkApprovalIntent() 函数及其调用 |
|
frontend-h5/src/api/approval.ts(如存在) |
移除或标记 detectApprovalIntent 为废弃 |
|
| 2C. 两级审批分类 Prompt | docs/02-产品需求/dify_unified_intent_prompt_v3.md |
Prompt 改为两级分类:第一级 4 类粗分,第二级细分到 12 类 |
Phase 3: 前端改造
| 任务 | 文件 | 具体改动 |
|---|---|---|
| 3A. WS 消息类型扩展 | frontend-h5/src/composables/useH5WebSocket.ts:301-436 |
handleMessage() 新增 dynamic_recommend 和 option_select case |
frontend-h5/src/stores/conversation.ts |
新增 handleDynamicRecommend() 和 sendOptionSelect() 方法 |
|
| 3B. 聊天气泡渲染 | frontend-h5/src/components/ |
新增 AiStructuredMessage.vue 组件——渲染文字 + 选项按钮 |
MessageBubble.vue(或对应渲染组件) |
增加 msg_type === 'ai_structured' 分支 |
|
| 3C. 右边栏 v2 | frontend-h5/src/components/assistant/RightPanel.vue |
整体重构为手风琴布局 |
新建 DynamicRecommend.vue |
智能推荐组件——接收 WS dynamic_recommend 消息,渲染推荐卡片 |
|
改造 SelfDiagnosis.vue |
增加标签页 + 折叠逻辑 | |
改造 BasicInfoCard.vue |
增加 CPU/内存/硬盘折叠/展开 | |
新建 SoftwareInstall.vue + ResourcePermission.vue |
从 SoftwareAndApply.vue 拆分 |
|
| 3D. 选项回传链路 | frontend-h5/src/stores/conversation.ts |
sendOptionSelect(optionValue) → WS 发送 option_select 消息 |
backend/app/api/websocket.py |
接收 option_select → 转化为 Dify user message → get_reply_stream() |
Phase 4: 图片处理
| 任务 | 文件 | 具体改动 |
|---|---|---|
| 4A. VisionService 接入 | backend/app/tasks/h5_ai_task.py |
process_h5_ai_reply() 增加图片分支——检测 msg_type=image → 调 vision_service.analyze_screenshot() → 描述拼接到用户文字 |
backend/app/services/vision_service.py |
确认接口完整,增加错误降级 | |
| 4B. 消息融合 | backend/app/tasks/h5_ai_task.py 或新建 backend/app/services/message_fusion.py |
5 秒窗口合并机制——图片+文字融合后一次性传给 Dify |
Phase 5: 坐席端适配
| 任务 | 文件 | 具体改动 |
|---|---|---|
| 5A. 坐席端 WS 扩展 | 坐席端 WS 处理器 | 新增 ai_structured 和 dynamic_recommend 消息类型处理 |
| 5B. 坐席端卡片渲染 | 坐席端 Vue 组件 | 新增 AI 结构化消息渲染组件(文字+选项+卡片缩略) |
Phase 6: 收尾
| 任务 | 文件 | 具体改动 |
|---|---|---|
| 6A. 诊断闭环协调 | backend/app/services/closing_service.py |
明确信息锁定条件——Dify JSON 中增加 diagnosis_stage 字段 |
| 6B. 性能优化 | Dify + 后端 | 节点精简后实测延迟;增加 Dify 响应时间监控 |
7.3 优先级与依赖关系
Phase 1 (P0) ──┬── 1A. Dify Prompt ──────── 无依赖
├── 1B. 后端消息处理 ──────── 依赖 1A
├── 1C. 错误降级 ────────── 依赖 1B
└── 1D. Dify 节点精简 ────── 无依赖
Phase 2 (P0) ──┬── 2A. 收窄关键词 ────────── 无依赖
├── 2B. 统一意图调用 ──────── 依赖 1B
└── 2C. 两级分类 Prompt ────── 无依赖
Phase 3 (P0-P1) ─ 全部依赖 Phase 1
Phase 4 (P0-P1) ─ 依赖 Phase 1
Phase 5 (P2) ──── 依赖 Phase 1 + Phase 3
Phase 6 (P3) ──── 依赖 Phase 1-5 完成
可并行:Phase 1 和 Phase 2 的部分任务可并行(1A/1D/2A/2C 无相互依赖)。
八、风险与回滚
8.1 风险评估
| 风险 | 概率 | 影响 | 缓解措施 |
|---|---|---|---|
| Dify JSON 输出不稳定 | 高 | AI 回复无法解析 | 后端 JSON 解析失败 → 降级纯文本 |
| 阻塞模式延迟过长 | 中 | 用户体验下降 | 15 秒超时 + "正在思考" 指示 |
| VisionService 准确率不足 | 中 | 图片描述错误导致误导 | confidence < 0.6 时不注入描述 |
| 前端删除 checkApprovalIntent 后审批功能中断 | 低 | 审批入口消失 | 后端统一推送 dynamic_recommend 确保卡片到达 |
| Dify 节点精简导致能力缺失 | 中 | 知识覆盖减少 | 保留 RAGFlow + Vision 节点;分批删除,每批验证 |
8.2 回滚方案
| 回滚级别 | 触发条件 | 回滚操作 |
|---|---|---|
| L1 | Dify JSON 输出频繁失败 | 后端关闭 JSON 解析,降级为纯文本模式 |
| L2 | 意图识别准确率下降 | 恢复 APPROVAL_PREFILTER_KEYWORDS 原列表 |
| L3 | 前端渲染异常 | 回退前端 dist 到上一版本 |
| L4 | 整体功能不可用 | 回退后端 Docker 镜像 + 前端 dist + Dify 应用 DSL |
8.3 验证清单
验证状态:2026-07-13 01:38 生产部署后全部通过。
每个 Phase 完成后需验证:
- Dify API 返回格式正确(JSON 可解析)
- 后端 WS 消息格式正确(
ai_reply+dynamic_recommend) - 前端聊天气泡渲染正确(文字 + 选项按钮)
- 前端侧边栏推荐渲染正确(卡片 + 操作按钮)
- 选项点击后回传正常(WS
option_select→ 新 AI 回复) - 图片消息处理正常(VisionService 分析 → 描述注入)
- 错误降级正常(Dify 超时/非 JSON → 降级回复)
- 坐席端可见 AI 结构化消息
九、附录
A. 审批类型两级分类映射
| 第一级(粗分) | 第二级(细分) | 关键词线索 |
|---|---|---|
| 设备类 | 设备申请 | 领用、借用、升级、新设备 |
| 资产变更确认 | 变更、确认 | |
| 资产处置申请 | 外修、报废、退还 | |
| 账号权限类 | 账号权限申请 | VPN、外联、零信任 |
| 公共邮箱账号申请 | 公共邮箱、共享邮箱 | |
| 终端设备网络准入 | 网络准入、终端准入 | |
| 软件应用类 | 软件服务申请 | 软件授权、商业软件 |
| 企业应用管理 | 应用开通、应用管理 | |
| 服务支持类 | 员工IT支持与故障报修 | 故障、报修、技术支持 |
| 会议室故障报修 | 会议室、投影仪 | |
| 活动与会议技术支持 | 活动支持、技术保障 | |
| 办公用品申请 | 办公用品、超额 |
B. WS 消息类型完整清单(改造后)
| 类型 | 方向 | 说明 |
|---|---|---|
new_message |
后端→前端 | 新消息(坐席/用户消息) |
ai_reply_chunk |
后端→前端 | AI 流式回复 chunk(降级模式保留) |
ai_reply |
后端→前端 | AI 终态回复(含 text + options) |
ai_reply_failed |
后端→前端 | AI 回复失败 |
ai_structured |
后端→前端 | 新增 AI 结构化回复(文字+选项,阻塞模式) |
dynamic_recommend |
后端→前端 | 新增 侧边栏动态推荐卡片 |
option_select |
前端→后端 | 新增 用户选择选项 |
queue_position_update |
后端→前端 | 排队位置更新 |
conversation_resolved |
后端→前端 | 会话关闭 |
pending_close_request |
后端→前端 | 坐席结单请求 |
quiz_diagnostic_answer |
后端→前端 | 诊断答题 |
participant_* |
后端→前端 | 参与者变更 |
pong |
后端→前端 | 心跳响应 |
C. 改造前后对比
| 维度 | 改造前 | 改造后 |
|---|---|---|
| Dify 节点数 | 85 | ~35 |
| Dify 调用次数/消息 | 2-3 次(前端意图+后端路由+后端回复) | 1-2 次(统一意图+条件性回复) |
| 审批关键词数 | ~40 | ~7 |
| 审批类型分类 | 一级 12 类 | 两级 4→12 类 |
| 卡片推送方式 | 前端异步独立推送 | 后端统一推送,与文字同步 |
| 图片处理 | 不可用 | VisionService 接入 |
| 消息格式 | 纯文本 | 结构化 JSON |
| 右边栏布局 | 三独立模块 | 手风琴折叠 + 智能推荐默认页 |
| 错误降级 | 无 | 三级降级(JSON→纯文本→转人工) |
十、边缘情况与隐式设计说明
版本: v1.2 日期: 2026-07-24 说明: 补充文档中未明确说明的隐式设计决策及边缘情况
10.1 选项消息的前端本地添加(已废弃)
原设计(有问题):
用户点击选项 → 前端本地立即添加消息(message_id = option_select_${timestamp})
→ 后端接收 WS option_select → 存储到数据库(message_id = UUID)
→ 后端广播 new_message → 前端收到后再添加一次
→ 结果:同一条消息显示两次
问题:前端和后端使用不同的 message_id,前端的去重机制(基于 message_id)失效。
修复方案(v1.2):
用户点击选项 → 前端只发送 WS,不本地添加消息
→ 后端接收 → 存储到数据库(message_id = UUID)
→ 后端广播 new_message → 前端收到后添加
→ 结果:消息来源唯一,无重复
隐式设计意图(原本未文档化):
- 前端本地添加消息的目的是提升用户体验:用户点击选项后,立即在界面上显示消息,让用户知道"我已经发送了",避免等待网络响应的 3 秒延迟
- 但这个设计在文档中没有明确说明,属于「实现时随手加的」
10.2 消息轮询机制
设计:
- 前端使用 3 秒间隔的轮询(poll)作为 WebSocket 的补充
- 当 WebSocket 消息丢失或延迟时,轮询作为兜底机制确保消息不丢失
考量:
- 轮询间隔 3 秒是体验和服务器压力的平衡点
- 过短会增加服务器压力,过长会影响体验
10.3 Dify 工作流变更
重要:Dify 的工作流(Workflow)修改后,必须手动点击「发布」按钮,否则更改不生效。
验证方法:
- 在 Dify 后台修改工作流后,保存 ≠ 发布
- 必须点击「发布」按钮,新版本才会生效
- 未发布时调用 API 会返回旧版本的结果
故障表现:修改了 Dify Prompt 或节点逻辑,但测试接口时发现行为未变化——通常是忘记点击「发布」。
10.4 消息 ID 去重机制
设计:
- 前端使用
processedMessageIdsSet 记录已处理的消息 ID handleNewMessage()中检查消息是否已处理,避免重复渲染
局限:
- 只基于 message_id 去重,不支持内容级别的去重
- 如果后端生成的 UUID 与前端生成的临时 ID 不同,去重会失效(参见 10.1)
10.5 WebSocket 与 HTTP 降级
设计:
- 发送消息时优先使用 WebSocket
- 如果 WebSocket 未连接(
sent = false),降级为 HTTP API 调用
代码位置:conversation.ts:1453-1460
十一、变更日志
| 版本 | 日期 | 变更内容 |
|---|---|---|
| v1.0 | 2026-07-13 | 初始版本 |
| v1.1 | 2026-07-13 | 补充部署记录 |
| v1.2 | 2026-07-24 | 新增第十章:边缘情况与隐式设计说明 |
| v1.3 | 2026-07-25 | AI回复打字机逐字显示效果交付(前端 H5 + Agent 双端 useTypewriter composable) |