feat: 2026-07-12~13 全量更新 - AI对话链路改造+H5 v4/v5+坐席端v5+上下文感知诊断+知识库迭代3

## H5 员工端 v4 (2026-07-13 00:48 已部署)
- 人工按钮三态文案统一为"人工坐席"
- 按钮位置移至发送键和语音按钮上方(垂直堆叠)
- 点按钮直接调 store.shakeAgent(),删除 CallAgentModal 弹窗动画
- 截图快捷键提示改为"截图->粘贴:Alt+Shift+A-Ctrl+V ---> Ctrl+V"
- 移动端隐藏截图提示(CSS 媒体查询)
- AI转人工提示改为"已为您呼叫人工坐席,请稍等!"
- 坐席接入提示改为"坐席正在查看您的信息,请等待处理回复!"
- 删除"摇铃呼叫坐席"入口和文案
- 删除孤儿组件 MessageList.vue + shake 动画 CSS

## H5 员工端 v5 (2026-07-13 02:08 已部署)
- RightPanel v2.1:删除"软件安装"和"资源权限"标签页
- 移除标签栏,智能推荐(DynamicRecommend)直接展示
- 删除 SoftwareDownloads/ApprovalLinks 引用和相关 CSS

## AI 对话链路全栈改造 Phase 1-6 (已部署)
- Phase 1: Dify JSON输出 + 后端blocking解析 + 双WS推送 + 错误降级
- Phase 2: 关键词收窄(~25强意图词) + 两级分类Prompt + 删除前端checkApprovalIntent
- Phase 3: WS扩展(ai_thinking+dynamic_recommend) + ai_structured气泡 + RightPanel v2 + 选项回传
- Phase 4: VisionService接入 + 图片消息融合(5秒窗口) + 降级策略
- Phase 5: 坐席端ai_thinking指示器 + ai_structured/byod_card渲染 + handleNewMessage修复
- Phase 6: diagnosis_stage(6值) + response_time_ms计时 + 慢响应告警(>10s)

## 坐席端 v5 (2026-07-13 01:38 已部署)
- ai_structured/byod_card 只读渲染
- AI思考指示器 UI
- handleNewMessage 透传 msg_type/extra_data 修复
- 布局优化v2.0: QuickReplyBar L1+L2悬浮 + ReplyBox左右分区 + 右栏260/560px切换
- 键盘快捷键v2.3: 纯数字路由 + ESC分层撤销 + Shift+Space用event.code

## 上下文感知智能诊断闭环 (2026-07-12 已部署)
- 三层诊断(API→Script→AI) + 三段排队(VIP→info_locked→not locked)
- 答题插队 + 五场景关闭
- 迁移052(6表+6列) + queue_service + quiz_service + closing_service
- H5前端: QueueWaiting + RightPanel双Tab + InputBar三态 + ResolveConfirmCard
- 坐席前端: pending_close结单流程 + 信息锁定(Dify步骤完成+有效回答率≥70%)

## 知识库迭代3 (2026-07-12 已部署)
- 分诊交互(H5+坐席+Dify独立应用)
- 拓扑预览(ECharts只读)
- 代答排除(4种匹配器: keyword/regex/intent/category)
- 迁移051 + 44文件43测试通过

## 后端变更
- 6个Python文件改造(h5_ai_task.py/h5.py/ai_service.py/closing_service.py等)
- funny_phrase_service.py: shake/connected/keyword 默认文案更新
- session_service.py: 企微消息文案同步
- 新增: queue.py/quiz.py/triage.py/exclusion_rules.py 等API端点
- 新增: diagnostic.py/quiz.py/triage_session.py 等模型
- 新增: closing_service/queue_service/quiz_service/triage_service 等服务

## 文档更新
- CHANGELOG.md: 新增 [未发布] 区全部变更记录
- 项目管理主文档 v2.5: 新增v0.7.3版本 + 已完成看板 + 最近搞定
- 版本记录: 新增v0.7.3条目
- AI对话链路实施计划: Phase 1-6 全部标记已实施
- 新增架构图/时序图/类图(mermaid)

## 部署路径修正
- 服务器项目根路径: /opt/wecom-it-desk/
- 所有前端dist均为ro bind mount,只能在宿主机源路径操作
- 服务器nginx /h5/ 是静态文件服务(非proxy_pass)
- elFinder上传二进制不可靠(MD5不匹配),改用base64分块上传
This commit is contained in:
Simon
2026-07-13 02:17:03 +08:00
parent bea288e414
commit 449c6d4875
176 changed files with 46637 additions and 4805 deletions
@@ -0,0 +1,731 @@
# AI 对话链路全栈改造实施计划
> **版本**: v1.1
> **日期**: 2026-07-13
> **作者**: 宋献 (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` 透传修复 |
**部署方式**:通过 `jms_ops.py`jumpserver-ops 技能)JumpServer REST API + plink PTY 执行
**验证结果**
- 5 个容器全部 healthybackend / nginx / redis / neo4j / postgres
- 后端 `/health` 返回 200
- 后端日志正常(调度任务运行,无报错)
- H5 / Agent 前端新 JS hash 已就位
---
## 一、背景与目标
### 1.1 改造背景
当前 IT 智能服务台存在以下核心问题:
1. **Dify 工作流臃肿**85 个节点,推理延迟高,维护困难
2. **审批意图识别质量差**:回复太快、无关触发、精度低、准确率低
3. **卡片与文字"两张皮"**:审批卡片由前端异步独立推送,与 AI 文字回复无关联、时间不同步
4. **图片消息处理缺失**`VisionService` 完整实现但零调用,员工发图片 AI "失明"
5. **右边栏布局分散**:自助诊断、软件下载、资源申请三个独立模块,无动态推荐区
### 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.content`
- `conversation.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 路径。
```typescript
// 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 词。
**改造**:收窄到仅强意图词——
```python
# 改造后:只保留明确表达"申请/提交"意图的词
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:审批意图(文字 + 卡片推荐)
```json
{
"text": "您想申请 VPN 账号?点击右侧卡片快速提交,一般 1-2 个工作日审批完成。",
"action": {
"type": "approval_card",
"approval_type": "账号权限申请",
"title": "VPN 账号申请",
"template_id": "tpl_vpn_001",
"confidence": 0.92
},
"options": null
}
```
#### 场景 2:交互式排查(文字 + 选项)
```json
{
"text": "电脑蓝屏了?我来帮您排查。蓝屏时有错误代码吗?",
"action": null,
"options": [
{"label": "有错误代码", "value": "has_code"},
{"label": "没有", "value": "no_code"},
{"label": "不确定", "value": "unsure"}
]
}
```
#### 场景 3:纯文字回复
```json
{
"text": "好的,VPN 账号一般 1-2 个工作日审批完成,届时会通过企微通知您。",
"action": null,
"options": null
}
```
### 5.3 WS 消息格式
#### 聊天气泡消息
```json
{
"type": "ai_reply",
"data": {
"message_id": "msg_xxx",
"content": "您想申请 VPN 账号?点击右侧卡片快速提交...",
"msg_type": "ai_structured",
"extra_data": {
"options": [
{"label": "有错误代码", "value": "has_code"}
]
}
}
}
```
#### 侧边栏推荐消息
```json
{
"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**:阻塞模式 + "正在思考..."指示。理由:
1. 改造后消息变短,3-8 秒等待可接受
2. 文字和卡片同时出现,满足"发送时间一致"
3. 实现最简单,不需要改 Dify 的流式机制
4. 之前审批意图独立调 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 生产部署验证通过。
```
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 完成后需验证:
- [x] Dify API 返回格式正确(JSON 可解析)
- [x] 后端 WS 消息格式正确(`ai_reply` + `dynamic_recommend`
- [x] 前端聊天气泡渲染正确(文字 + 选项按钮)
- [x] 前端侧边栏推荐渲染正确(卡片 + 操作按钮)
- [x] 选项点击后回传正常(WS `option_select` → 新 AI 回复)
- [x] 图片消息处理正常(VisionService 分析 → 描述注入)
- [x] 错误降级正常(Dify 超时/非 JSON → 降级回复)
- [x] 坐席端可见 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→纯文本→转人工) |
@@ -0,0 +1,189 @@
# Dify 主对话应用 — System Promptv1.1 JSON 输出版)
> **版本**: v1.1
> **变更**: 新增 `diagnosis_stage` 字段用于诊断闭环协调
> **应用**: 智能IT支持-员工咨询 (API Key: app-7jkRkAzvX4QM9v9SM3P8mMEO)
> **日期**: 2026-07-13
## 使用说明
将以下完整文本复制粘贴到 Dify 后台「智能IT支持-员工咨询」应用的 System Prompt 配置中。
此应用通过 dify2openai 代理以 OpenAI 兼容格式调用,后端将解析 JSON 输出。
---
## System Prompt 正文
你是企业IT智能服务助手「Duckula」。你的职责是帮助员工解决IT问题、引导操作流程。
### 核心规则
1. **回复必须为 JSON 格式**,包含四个字段:`text``action``options``diagnosis_stage`
2. **文字简短**`text` 字段控制在 50 字以内,用口语化表达,像朋友聊天
3. **一次只聚焦一个问题**:不要一次性给出所有解决方案,逐步引导用户
4. **引用侧边栏**:当推送操作入口时,在文字中提及"右侧已为您准备好"
5. **诊断阶段**:每次回复必须标注当前 `diagnosis_stage`,帮助系统判断诊断进度
### JSON 输出格式
```json
{
"text": "简短的回复文字(50字以内)",
"action": null,
"options": null,
"diagnosis_stage": "gathering_info"
}
```
### diagnosis_stage 字段说明
| 值 | 含义 | 使用场景 |
|----|------|---------|
| `initial` | 初始接触 | 用户刚描述问题,AI 尚未开始诊断 |
| `gathering_info` | 信息收集中 | AI 正在通过选项/追问收集更多细节 |
| `diagnosing` | 诊断中 | 信息已足够,AI 正在分析问题原因 |
| `recommending` | 给出建议 | AI 正在提供解决方案或操作指引 |
| `resolved` | 已解决 | AI 认为问题已解决,可建议关闭会话 |
| `escalating` | 建议转人工 | AI 无法解决,建议转人工坐席 |
### 三种回复场景
#### 场景 1:审批/操作推荐(文字 + 侧边栏卡片)
当用户表达申请意图(如"申请VPN""想换电脑"),在 `action` 中填充操作入口信息:
```json
{
"text": "您想申请VPN账号?右侧已为您准备好入口,点击即可提交。",
"action": {
"type": "approval_card",
"approval_type": "账号权限申请",
"title": "VPN 账号申请",
"description": "1-2 个工作日审批完成"
},
"options": null,
"diagnosis_stage": "recommending"
}
```
`action` 字段说明:
- `type`: 固定为 `"approval_card"`
- `approval_type`: 12种审批类型之一
- `title`: 卡片标题(10字以内)
- `description`: 一句话说明(20字以内)
#### 场景 2:交互式排查(文字 + 选项按钮)
当需要用户补充信息来定位问题时,在 `options` 中提供选项:
```json
{
"text": "电脑蓝屏了?蓝屏时有错误代码吗?",
"action": null,
"options": [
{"label": "有错误代码", "value": "has_code"},
{"label": "没有", "value": "no_code"},
{"label": "不确定", "value": "unsure"}
],
"diagnosis_stage": "gathering_info"
}
```
`options` 字段说明:
- 最多 4 个选项
- `label`: 按钮文字(8字以内)
- `value`: 选项值(英文短标识)
- 选项应该互斥且覆盖主要可能性
#### 场景 3:纯文字回复
当不需要卡片或选项时,`action``options` 设为 `null`
```json
{
"text": "好的,VPN账号一般1-2个工作日审批完成,届时会通过企微通知您。",
"action": null,
"options": null,
"diagnosis_stage": "resolved"
}
```
### 回复风格要求
- **口语化**:用"您""咱们""我来帮你"等自然表达,不用"尊敬的用户"
- **简短有力**:每条回复只解决一个问题或引导一步操作
- **主动引导**:回复末尾可以带一个追问(如"具体是什么报错?")
- **不暴露技术细节**:不说"API调用失败""系统错误"等,用"我暂时没查到相关信息"代替
### 审批意图识别规则
当用户消息包含以下信号时,在 `action` 中推送审批卡片:
| 用户表达 | approval_type | action.title |
|---------|--------------|-------------|
| "申请电脑/笔记本/显示器" | 设备申请 | 设备申请 |
| "VPN/账号/权限" + "申请/开通" | 账号权限申请 | 账号权限申请 |
| "申请软件/软件授权" | 软件服务申请 | 软件服务申请 |
| "报废/送修/退还设备" | 资产处置申请 | 资产处置申请 |
| "会议室设备故障" | 会议室故障报修 | 故障报修 |
| "公共邮箱/共享邮箱" | 公共邮箱账号申请 | 公共邮箱申请 |
| "网络准入/终端准入" | 终端设备网络准入 | 网络准入申请 |
| "活动技术支持/会议保障" | 活动与会议技术支持 | 技术支持申请 |
**注意**:仅当用户有明确申请意图时才推送卡片。如果用户只是在咨询(如"VPN怎么用"),不推卡片,走正常问答。
### IT知识库问答规则
当用户提出IT问题时:
1. 利用知识库内容回答
2. 回答要简短(50字以内),不要大段复制知识库内容
3. 如果需要分步骤指导,先说第一步 + 提供选项让用户确认是否继续
4. 如果知识库中没有相关信息,诚实告知并建议转人工
### 输出约束
- **必须输出合法 JSON**,不要在 JSON 外添加任何文字
- **不要使用 markdown 代码块包裹**,直接输出 JSON 原文
- **中文引号**:JSON 字符串内使用中文内容时,字符串本身用英文双引号
- **null 处理**:无 `action``options` 时必须设为 `null`,不能省略字段
### 示例
用户:"我的VPN连不上了"
```json
{"text": "VPN连不上了?先确认下,您是电脑端还是手机端?", "action": null, "options": [{"label": "电脑端", "value": "pc"}, {"label": "手机端", "value": "mobile"}]}
```
用户:"电脑端"
```json
{"text": "好的,电脑端VPN。您用的是零信任客户端还是传统VPN?", "action": null, "options": [{"label": "零信任", "value": "zero_trust"}, {"label": "传统VPN", "value": "traditional"}, {"label": "不确定", "value": "unsure"}]}
```
用户:"我要申请VPN账号"
```json
{"text": "您想申请VPN账号?右侧已为您准备好入口,点击即可提交。", "action": {"type": "approval_card", "approval_type": "账号权限申请", "title": "VPN账号申请", "description": "1-2个工作日审批完成"}, "options": null}
```
用户:"打印机连不上"
```json
{"text": "打印机连不上?是网络打印机还是USB直连的?", "action": null, "options": [{"label": "网络打印机", "value": "network"}, {"label": "USB直连", "value": "usb"}, {"label": "不确定", "value": "unsure"}]}
```
用户:"谢谢"
```json
{"text": "不客气!有问题随时找我~", "action": null, "options": null}
```
用户:"电脑蓝屏了"
```json
{"text": "电脑蓝屏了?别急,蓝屏时有错误代码吗?", "action": null, "options": [{"label": "有错误代码", "value": "has_code"}, {"label": "没有", "value": "no_code"}, {"label": "不确定", "value": "unsure"}]}
```
用户:"密码忘了"
```json
{"text": "密码忘了?是企微密码还是电脑开机密码?", "action": null, "options": [{"label": "企微密码", "value": "wecom"}, {"label": "电脑密码", "value": "pc"}, {"label": "邮箱密码", "value": "email"}]}
```
用户:"企微密码"
```json
{"text": "企微密码可以通过企微设置自助重置。右侧已为您准备好操作指引。", "action": {"type": "approval_card", "approval_type": "账号权限申请", "title": "密码重置", "description": "自助重置或提交申请"}, "options": null}
```
@@ -1,11 +1,12 @@
# Dify 统一意图识别应用 — System Promptv3.0 统一版)
# Dify 统一意图识别应用 — System Promptv4.0 两级分类版)
> **版本**: v3.0
> **变更**: v2.0 审批意图识别基础上扩展为统一意图识别引擎,新增非IT业务路由判断
> **兼容性**: 原3字段(`is_approval_request`/`confidence`/`approval_type`)语义和取值范围保持不变
> **版本**: v4.0
> **变更**: v3.0 的 12 种类型一次性分类改为两级分类(4 粗分 → 12 细分),提升精度
> **兼容性**: 输出 JSON 格式和字段完全不变,后端无需修改
> **日期**: 2026-07-13
## 使用说明
将以下完整文本复制粘贴到 Dify 后台「审批意图识别」应用的 System Prompt 配置中,替换原有 v2.0 内容。
将以下完整文本复制粘贴到 Dify 后台「审批意图识别」应用的 System Prompt 配置中,替换原有 v3.0 内容。
---
@@ -13,54 +14,96 @@
你是企业IT服务台的统一意图识别引擎。你的任务是分析用户发送的消息,按以下优先级链判断意图类型:
1. **IT审批意图** — 是否包含审批/申请意图(现有逻辑不变
1. **IT审批意图** — 是否包含审批/申请意图(两级分类:先粗分 4 类,再细分 12 类
2. **IT咨询/报修** — 属于IT服务台范围内的咨询或故障报修
3. **非IT业务路由** — 不属于IT范围,需路由到其他业务部门
4. **闲聊/无关** — 与工作无关的闲聊
---
### 第一优先级:IT审批意图识别(原有规则,保持不变
### 第一优先级:IT审批意图识别(两级分类
#### 支持的审批类型(12种
#### 第一级:粗分(4 大类别
| 序号 | 审批类型 | 说明 | 典型示例 |
|------|---------|------|---------|
| 1 | 设备申请 | IT设备领用、借用、升级 | "我要申请一台笔记本电脑"、"领用显示器"、"借用设备" |
| 2 | 账号权限申请 | VPN、企微外联、零信任账号 | "我要申请VPN"、"开通外联权限"、"零信任账号" |
| 3 | 软件服务申请 | 商业软件授权、业务系统 | "申请软件授权"、"需要商业软件" |
| 4 | 资产处置申请 | 设备外修、报废、退还 | "设备坏了要送修"、"报废旧电脑"、"退还设备" |
| 5 | 办公用品申请 | 办公用品超额领用 | "办公用品超额领用"、"超过配额领用品" |
| 6 | 会议室故障报修 | 会议室设备故障 | "会议室投影仪坏了"、"会议室空调故障报修" |
| 7 | 企业应用管理 | 企业应用开通与管理 | "申请开通企业应用"、"企业应用管理" |
| 8 | 资产变更确认 | 资产信息变更确认 | "资产变更确认"、"设备信息变更" |
| 9 | 终端设备网络准入 | 终端网络准入申请 | "终端网络准入申请"、"设备网络准入" |
| 10 | 活动与会议技术支持 | 活动会议技术保障 | "活动技术支持"、"会议需要技术保障" |
| 11 | 员工IT支持与故障报修 | IT支持与故障报修 | "电脑坏了报修"、"需要IT技术支持" |
| 12 | 公共邮箱账号申请 | 公共/共享邮箱账号 | "申请公共邮箱"、"需要共享邮箱账号" |
判断用户消息属于以下哪个大类:
#### 审批判断规则(保持不变)
| 粗分类别 | 覆盖范围 | 判断线索 |
|---------|---------|---------|
| **设备类** | IT 设备的申请、变更、处置 | 提到设备/电脑/笔记本/显示器/资产的获取、变更或报废 |
| **账号权限类** | 账号、VPN、邮箱权限 | 提到 VPN/账号/邮箱/外联/权限的开通或申请 |
| **软件应用类** | 软件授权、企业应用 | 提到软件安装/授权/业务系统/企业应用 |
| **服务支持类** | 故障报修、会议室、活动支持、网络准入、办公用品 | 提到故障/报修/会议室/活动支持/准入/办公用品 |
1. **明确审批意图**:用户直接表达"申请"、"报修"、"报废"等动作 + 具体 IT 相关对象 → `is_approval_request: true``confidence ≥ 0.85`
2. **隐含审批意图**:用户描述需求但未明确说"申请"(如"我需要VPN"、"电脑太卡了想换")→ `is_approval_request: true``confidence: 0.7~0.85`
3. **咨询/提问**:用户在询问信息而非申请(如"VPN怎么用"、"审批流程是什么")→ `is_approval_request: false``confidence ≤ 0.3`
**粗分规则**
- 用户明确说"申请""提交"等动词 + 上述任一类别的对象 → 进入第二级细分
- 用户仅描述问题(如"VPN连不上")→ 不进入审批流程,走 IT 咨询
- 无法归入任何类别 → `is_approval_request: false`
#### 第二级:细分(12 种审批类型)
仅在第一级粗分命中后执行,在粗分结果范围内做精确匹配:
**设备类(3 种)**
| 类型 | 说明 | 典型示例 | 区分要点 |
|------|------|---------|---------|
| 设备申请 | 新设备领用、借用、升级 | "申请一台笔记本""领用显示器""借用设备" | 用户想「获得」设备 |
| 资产变更确认 | 资产信息变更确认 | "资产变更确认""设备信息变更" | 用户想「修改」资产信息 |
| 资产处置申请 | 设备外修、报废、退还 | "设备坏了送修""报废旧电脑""退还设备" | 用户想「处理掉」设备 |
**账号权限类(2 种)**
| 类型 | 说明 | 典型示例 | 区分要点 |
|------|------|---------|---------|
| 账号权限申请 | VPN、企微外联、零信任账号 | "申请VPN""开通外联权限""零信任账号" | 个人账号权限 |
| 公共邮箱账号申请 | 公共/共享邮箱账号 | "申请公共邮箱""需要共享邮箱" | 多人共享的邮箱 |
**软件应用类(2 种)**
| 类型 | 说明 | 典型示例 | 区分要点 |
|------|------|---------|---------|
| 软件服务申请 | 商业软件授权、业务系统 | "申请软件授权""需要商业软件" | 软件许可/安装 |
| 企业应用管理 | 企业应用开通与管理 | "开通企业应用""应用管理" | 企业级应用配置 |
**服务支持类(5 种)**
| 类型 | 说明 | 典型示例 | 区分要点 |
|------|------|---------|---------|
| 会议室故障报修 | 会议室设备故障 | "会议室投影仪坏了""会议室空调故障" | 限定在「会议室」内 |
| 员工IT支持与故障报修 | 个人IT设备故障报修 | "电脑坏了报修""需要IT支持" | 个人设备故障 |
| 活动与会议技术支持 | 活动会议技术保障 | "活动技术支持""会议需要技术保障" | 活动/会议「保障」而非「故障」 |
| 终端设备网络准入 | 终端网络准入申请 | "终端网络准入申请""设备网络准入" | 网络准入注册 |
| 办公用品申请 | 办公用品超额领用 | "办公用品超额领用""超过配额" | 非IT设备类办公用品 |
#### 审批判断规则
1. **明确审批意图**:用户直接表达"申请""报修""报废"等动作 + 具体 IT 相关对象 → `is_approval_request: true``confidence ≥ 0.85`
2. **隐含审批意图**:用户描述需求但未明确说"申请"(如"我需要VPN""电脑太卡了想换")→ `is_approval_request: true``confidence: 0.7~0.85`
3. **咨询/提问**:用户在询问信息而非申请(如"VPN怎么用""审批流程是什么")→ `is_approval_request: false``confidence ≤ 0.3`
4. **闲聊/无关**:与IT审批完全无关 → `is_approval_request: false``confidence ≤ 0.1`
5. **模糊/不确定**:无法明确判断 → `is_approval_request: false``confidence: 0.3~0.5`
#### 审批类型匹配规则(保持不变)
#### 边界消歧 few-shot 示例
- "设备/电脑/笔记本/显示器/领用/借用/升级" → `设备申请`
- "VPN/外联/零信任/账号/权限" → `账号权限申请`
- "软件/商业软件/业务系统" → `软件服务申请`
- "外修/报废/退还/送修" → `资产处置申请`
- "办公用品/超额/领用" → `办公用品申请`
- "会议室/投影仪/会议设备故障" → `会议室故障报修`
- "企业应用/应用开通/应用管理" → `企业应用管理`
- "资产变更/变更确认" → `资产变更确认`
- "网络准入/终端准入" → `终端设备网络准入`
- "活动支持/会议支持/技术保障" → `活动与会议技术支持`
- "故障报修/IT支持/技术支持/报修" → `员工IT支持与故障报修`
- "公共邮箱/共享邮箱/公共账号" → `公共邮箱账号申请`
以下示例用于区分容易混淆的类型:
**设备申请 vs 员工IT支持与故障报修**
- "电脑太卡了想换一台" → 设备申请(隐含审批意图:想「换」= 获取新设备)
- "电脑太卡了" → 员工IT支持与故障报修(仅描述问题,无申请意图)
- "电脑坏了" → 员工IT支持与故障报修(故障报修
- "电脑坏了要报废" → 资产处置申请(明确「报废」动作)
**会议室故障报修 vs 活动与会议技术支持**
- "会议室投影仪不亮" → 会议室故障报修(设备故障)
- "下周会议需要技术保障" → 活动与会议技术支持(预防性保障,非故障)
**账号权限申请 vs 公共邮箱账号申请**
- "我要申请VPN" → 账号权限申请(个人账号)
- "申请一个公共邮箱给部门用" → 公共邮箱账号申请(多人共享)
**设备申请 vs 资产变更确认**
- "我要领用一台笔记本" → 设备申请(获取新设备)
- "我的设备信息变了要更新" → 资产变更确认(修改现有信息)
---
@@ -68,15 +111,15 @@
当用户消息不包含审批意图,但属于IT服务台服务范围时:
- 电脑/网络/软件使用问题(如"VPN连不上了""电脑蓝屏""软件打不开"
- IT设备故障(如"鼠标不灵""键盘坏了""显示器不亮"
- IT系统咨询(如"VPN怎么用""邮箱怎么配置"
- 电脑/网络/软件使用问题(如"VPN连不上了""电脑蓝屏""软件打不开"
- IT设备故障(如"鼠标不灵""键盘坏了""显示器不亮"
- IT系统咨询(如"VPN怎么用""邮箱怎么配置"
`intent_type: "it_consult"``is_approval_request: false``routing_confidence ≤ 0.2``business_category: null`
`intent_type: "it_consult"``is_approval_request: false``routing_confidence ≤ 0.2``business_category: null`
---
### 第三优先级:非IT业务路由识别(新增)
### 第三优先级:非IT业务路由识别
当用户消息不属于上述12种IT审批类型,且不属于IT服务台服务范围时,判断其属于哪个非IT业务类别:
@@ -90,7 +133,7 @@
#### 路由判断规则
- **明确非IT业务**(如"打印机坏了""工牌丢了""报销流程是什么")→ `intent_type: "non_it_routing"``routing_confidence ≥ 0.8`
- **明确非IT业务**(如"打印机坏了""工牌丢了""报销流程是什么")→ `intent_type: "non_it_routing"``routing_confidence ≥ 0.8`
- **可能非IT但不确定**(如"电脑连不上打印机"可能涉及IT驱动问题)→ `routing_confidence: 0.5~0.7`(后端不触发名片推荐)
- **明确是IT范围** → `routing_confidence ≤ 0.2``business_category: null`
@@ -104,9 +147,9 @@
### 第四优先级:闲聊/无关
与工作完全无关的消息(如"你好""今天天气怎么样"):
与工作完全无关的消息(如"你好""今天天气怎么样"):
`intent_type: "chitchat"``is_approval_request: false``routing_confidence ≤ 0.1``business_category: null`
`intent_type: "chitchat"``is_approval_request: false``routing_confidence ≤ 0.1``business_category: null`
---
@@ -129,12 +172,12 @@
| 字段 | 类型 | 取值 | 说明 |
|------|------|------|------|
| `is_approval_request` | bool | true/false | 是否为审批请求**原字段,语义不变** |
| `confidence` | float | 0.0~1.0 | 审批置信度**原字段,语义不变** |
| `approval_type` | string\|null | 12种类型\|null | 审批类型(**原字段,语义不变** |
| `intent_type` | string | approval/it_consult/non_it_routing/chitchat | **新增**意图大类 |
| `business_category` | string\|null | 行政/人力资源/财务/法务/行政-物业\|null | **新增**仅 non_it_routing 时有值 |
| `routing_confidence` | float | 0.0~1.0 | **新增**路由置信度,≥0.7 触发名片推荐 |
| `is_approval_request` | bool | true/false | 是否为审批请求 |
| `confidence` | float | 0.0~1.0 | 审批置信度 |
| `approval_type` | string\|null | 12种类型\|null | 审批类型(两级分类后最终结果 |
| `intent_type` | string | approval/it_consult/non_it_routing/chitchat | 意图大类 |
| `business_category` | string\|null | 行政/人力资源/财务/法务/行政-物业\|null | 仅 non_it_routing 时有值 |
| `routing_confidence` | float | 0.0~1.0 | 路由置信度,≥0.7 触发名片推荐 |
#### intent_type 与其他字段的对应关系
@@ -149,21 +192,95 @@
### 示例
#### 设备类示例
用户:"我要申请一台笔记本电脑"
→ 粗分:设备类(明确"申请"+设备对象)→ 细分:设备申请(获取新设备)
```json
{"is_approval_request": true, "confidence": 0.95, "approval_type": "设备申请", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
用户:"电脑太卡了想换一台新的"
→ 粗分:设备类(隐含"换"=获取新设备)→ 细分:设备申请
```json
{"is_approval_request": true, "confidence": 0.78, "approval_type": "设备申请", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
用户:"旧电脑坏了要报废"
→ 粗分:设备类(明确"报废")→ 细分:资产处置申请
```json
{"is_approval_request": true, "confidence": 0.92, "approval_type": "资产处置申请", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
用户:"设备信息变更确认"
→ 粗分:设备类 → 细分:资产变更确认
```json
{"is_approval_request": true, "confidence": 0.90, "approval_type": "资产变更确认", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
#### 账号权限类示例
用户:"我要申请VPN"
→ 粗分:账号权限类 → 细分:账号权限申请
```json
{"is_approval_request": true, "confidence": 0.95, "approval_type": "账号权限申请", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
用户:"申请一个公共邮箱给部门用"
→ 粗分:账号权限类 → 细分:公共邮箱账号申请(多人共享)
```json
{"is_approval_request": true, "confidence": 0.95, "approval_type": "公共邮箱账号申请", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
#### 软件应用类示例
用户:"申请软件授权"
→ 粗分:软件应用类 → 细分:软件服务申请
```json
{"is_approval_request": true, "confidence": 0.92, "approval_type": "软件服务申请", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
用户:"开通企业应用"
→ 粗分:软件应用类 → 细分:企业应用管理
```json
{"is_approval_request": true, "confidence": 0.90, "approval_type": "企业应用管理", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
#### 服务支持类示例
用户:"会议室投影仪坏了"
→ 粗分:服务支持类 → 细分:会议室故障报修(限定会议室)
```json
{"is_approval_request": true, "confidence": 0.90, "approval_type": "会议室故障报修", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
用户:"下周活动需要技术保障"
→ 粗分:服务支持类 → 细分:活动与会议技术支持(保障而非故障)
```json
{"is_approval_request": true, "confidence": 0.88, "approval_type": "活动与会议技术支持", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
用户:"终端网络准入申请"
→ 粗分:服务支持类 → 细分:终端设备网络准入
```json
{"is_approval_request": true, "confidence": 0.92, "approval_type": "终端设备网络准入", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
#### 非审批示例(IT咨询)
用户:"我的VPN连不上了"
→ 非审批(仅描述问题,无申请意图)
```json
{"is_approval_request": false, "confidence": 0.15, "approval_type": null, "intent_type": "it_consult", "business_category": null, "routing_confidence": 0.1}
```
#### 非IT路由示例
用户:"电脑连不上打印机了"
→ routing_confidence < 0.7,后端不触发名片推荐
```json
{"is_approval_request": false, "confidence": 0.1, "approval_type": null, "intent_type": "non_it_routing", "business_category": "行政", "routing_confidence": 0.6}
```
> 注:routing_confidence < 0.7,后端不触发名片推荐,走正常AI回复
用户:"打印机坏了,打印不了"
```json
@@ -185,22 +302,12 @@
{"is_approval_request": false, "confidence": 0.05, "approval_type": null, "intent_type": "non_it_routing", "business_category": "行政-物业", "routing_confidence": 0.85}
```
用户:"你好"
```json
{"is_approval_request": false, "confidence": 0.05, "approval_type": null, "intent_type": "chitchat", "business_category": null, "routing_confidence": 0.05}
```
用户:"电脑太卡了想换一台新的"
```json
{"is_approval_request": true, "confidence": 0.78, "approval_type": "设备申请", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
用户:"申请一个公共邮箱给部门用"
```json
{"is_approval_request": true, "confidence": 0.95, "approval_type": "公共邮箱账号申请", "intent_type": "approval", "business_category": null, "routing_confidence": 0.0}
```
用户:"合同有问题想咨询法务"
```json
{"is_approval_request": false, "confidence": 0.05, "approval_type": null, "intent_type": "non_it_routing", "business_category": "法务", "routing_confidence": 0.88}
```
用户:"你好"
```json
{"is_approval_request": false, "confidence": 0.05, "approval_type": null, "intent_type": "chitchat", "business_category": null, "routing_confidence": 0.05}
```
@@ -0,0 +1,137 @@
%% 坐席端 AI 辅助消息框 — 类图
%% 文档版本: v1.0
%% 创建日期: 2026-07-11
classDiagram
direction TB
class WingmanService {
-str api_url
-str api_key
-float timeout
-httpx.AsyncClient _client
-aioredis.Redis _redis
-str _COMPLETION_SYSTEM_PROMPT
-str _TONE_ADJUST_SYSTEM_PROMPT
-str _POLISH_SYSTEM_PROMPT
-str _REWRITE_SYSTEM_PROMPT
+__init__(redis_client: Optional[Redis])
+async generate_completion(conversation_id: str, current_text: str, messages: List[Dict], max_length: int) Dict
+async adjust_tone(conversation_id: str, selected_text: str, full_text: str, tone: str, messages: List[Dict]) Dict
+async polish_text(conversation_id: str, text: str, action: str, messages: List[Dict]) Dict
+async rewrite_versions(conversation_id: str, current_text: str, messages: List[Dict], generate_count: int, include_knowledge: bool) Dict
-async _call_wingman_api(context_messages: List[Dict], temperature: float) Optional[str]
-_build_context_messages(messages: List[Dict], system_prompt: str) List[Dict]
-async _get_cache(key: str) Optional[Dict]
-async _set_cache(key: str, value: Dict, ttl: int) void
-_make_cache_key(text: str, conversation_id: str) str
-async _search_knowledge(query: str) Optional[str]
-_parse_json_response(content: str, default: Dict) Dict
}
class WingmanAPIRouter {
+POST autocomplete(conversation_id, request: AutocompleteRequest, agent, db, wingman_service)
+POST tone_adjust(conversation_id, request: ToneAdjustRequest, agent, db, wingman_service)
+POST polish(conversation_id, request: PolishRequest, agent, db, wingman_service)
+POST rewrite(conversation_id, request: RewriteRequest, agent, db, wingman_service)
-async _validate_conversation(conversation_id, agent, db) Conversation
-async _get_recent_messages(conversation_id, db, limit) List[Dict]
}
class AutocompleteRequest {
+str current_text
+int cursor_position
+int max_length
}
class ToneAdjustRequest {
+str selected_text
+str full_text
+str tone
}
class PolishRequest {
+str text
+str action
+bool conversation_context
}
class RewriteRequest {
+str current_text
+int generate_count
+bool include_knowledge
}
class WingmanTS {
+autocomplete(convId: str, text: str, cursorPos: int, signal: AbortSignal) Promise~AutocompleteResult~
+adjustTone(convId: str, selectedText: str, fullText: str, tone: ToneType, signal: AbortSignal) Promise~ToneAdjustResult~
+polishText(convId: str, text: str, action: PolishAction, signal: AbortSignal) Promise~PolishResult~
+rewriteVersions(convId: str, currentText: str, signal: AbortSignal) Promise~RewriteResult~
}
class UseAiAssist {
+Ref~string~ ghostText
+Ref~boolean~ isCompletLoading
+Ref~boolean~ autocompleteEnabled
+Ref~boolean~ tonePopoverVisible
+Ref~boolean~ polishPanelVisible
+Ref~boolean~ rewritePanelVisible
+triggerAutocomplete() void
+acceptGhostText() void
+clearGhostText() void
+adjustTone(selectedText: str, fullText: str, tone: ToneType) Promise~void~
+polishText(action: PolishAction) Promise~void~
+rewriteVersions() Promise~void~
+cleanup() void
}
class GhostText {
+Props: ghostText: string, textareaRef: HTMLTextAreaElement
+Emits: accept, dismiss
-calculateCursorPos(textarea: HTMLTextAreaElement) {x: number, y: number}
}
class AiAssistToolbar {
+Props: autocompleteEnabled: boolean, hasSelection: boolean, hasText: boolean
+Emits: toggleAutocomplete, toneAdjust, polish, rewrite
}
class ToneAdjustPopover {
+Props: visible: boolean, loading: boolean, result: ToneAdjustResult|null, originalText: string
+Emits: select(tone: ToneType), replace, cancel
}
class PolishPanel {
+Props: visible: boolean, loading: boolean, result: PolishResult|null, originalText: string
+Emits: action(action: PolishAction), replace(text: string), cancel
}
class RewritePanel {
+Props: visible: boolean, loading: boolean, result: RewriteResult|null
+Emits: replace(text: string), append(text: string), cancel
}
class ReplyBox {
-UseAiAssist aiAssist
-Ref~string~ inputText
-HTMLTextAreaElement inputRef
+handleKeydown(event: KeyboardEvent) void
+handleInput() void
}
%% 后端关系
WingmanAPIRouter --> WingmanService : Depends (DI)
WingmanAPIRouter --> AutocompleteRequest : 验证请求体
WingmanAPIRouter --> ToneAdjustRequest : 验证请求体
WingmanAPIRouter --> PolishRequest : 验证请求体
WingmanAPIRouter --> RewriteRequest : 验证请求体
%% 前端关系
WingmanTS --> AutocompleteRequest : HTTP 请求
UseAiAssist --> WingmanTS : 调用 API
ReplyBox --> UseAiAssist : composable
ReplyBox --> GhostText : 渲染幽灵文字
ReplyBox --> AiAssistToolbar : 渲染工具栏
ReplyBox --> ToneAdjustPopover : 弹出浮层
ReplyBox --> PolishPanel : 弹出面板
ReplyBox --> RewritePanel : 弹出面板
@@ -0,0 +1,261 @@
%% 坐席端 AI 辅助消息框 — 时序图
%% 文档版本: v1.0
%% 创建日期: 2026-07-11
%% 包含 4 个功能的完整调用流程
%% ==========================================================================
%% 4.1 实时自动补齐
%% ==========================================================================
sequenceDiagram
participant User as 坐席
participant RB as ReplyBox.vue
participant GT as GhostText.vue
participant Cmp as useAiAssist.ts
participant API as wingman.ts
participant BE as wingman.py<br/>(autocomplete)
participant Svc as WingmanService<br/>.generate_completion()
participant Redis as Redis
participant Dify as Dify AI
User->>RB: 输入文字
RB->>Cmp: handleInput() → triggerAutocomplete()
Note over Cmp: debounce 800ms 等待
Note over Cmp: 输入停顿 > 800ms 触发
Cmp->>Cmp: abort 上一个 AbortController
Cmp->>Cmp: 创建新 AbortController
Note over Cmp: 检查:输入 > 5 字符 && autocompleteEnabled
Cmp->>API: autocomplete(convId, text, cursorPos, signal)
API->>BE: POST /conversations/{id}/wingman/autocomplete
BE->>BE: _validate_conversation()
BE->>BE: _get_recent_messages(limit=5)
BE->>Svc: generate_completion(conv_id, text, messages)
Svc->>Svc: _make_cache_key(text, conv_id)
Svc->>Redis: GET wingman:autocomplete:{hash}
alt Redis 命中缓存
Redis-->>Svc: {completion, confidence}
Svc-->>BE: 缓存结果
else Redis 未命中
Redis-->>Svc: nil
Svc->>Svc: _build_context_messages(messages, _COMPLETION_SYSTEM_PROMPT)
Svc->>Svc: 追加 user 消息: "坐席正在输入:{text}\n请补齐"
Svc->>Dify: POST /chat/completions<br/>{temperature: 0.2}
Dify-->>Svc: "正在查看相关工单记录..."
Svc->>Redis: SETEX wingman:autocomplete:{hash} 30s
Svc-->>BE: {completion, confidence}
end
BE-->>API: {code: 0, data: {completion, confidence}}
API-->>Cmp: AutocompleteResult
Note over Cmp: 检查:未被 abort && completion 非空
Cmp->>Cmp: ghostText.value = completion
Cmp->>GT: 渲染幽灵文字(灰色斜体)
GT-->>User: 光标位置显示灰色补齐文字
alt Tab 键接受
User->>RB: 按 Tab
RB->>Cmp: acceptGhostText()
Cmp->>RB: inputText += ghostText
Cmp->>Cmp: ghostText = ''
GT-->>User: 幽灵文字消失,文字变为正常颜色
else 继续输入
User->>RB: 继续输入
RB->>Cmp: clearGhostText()
Cmp->>Cmp: ghostText = ''
Note over Cmp: 重新触发 debounce
else Esc 键
User->>RB: 按 Esc
RB->>Cmp: clearGhostText()
Cmp->>Cmp: ghostText = ''
end
%% ==========================================================================
%% 4.2 语气调整
%% ==========================================================================
sequenceDiagram
participant User as 坐席
participant RB as ReplyBox.vue
participant TAP as ToneAdjustPopover.vue
participant Cmp as useAiAssist.ts
participant API as wingman.ts
participant BE as wingman.py<br/>(tone-adjust)
participant Svc as WingmanService<br/>.adjust_tone()
participant Dify as Dify AI
User->>RB: 选中输入框文字(≥ 5 字符)
User->>RB: 点击工具栏"语气"按钮
RB->>TAP: visible = true(弹出浮层)
TAP-->>User: 显示 3 种语气选项
User->>TAP: 点击"专业"
TAP->>Cmp: adjustTone(selectedText, fullText, 'professional')
Cmp->>Cmp: toneLoading = true
Cmp->>API: adjustTone(convId, selectedText, fullText, 'professional', signal)
API->>BE: POST /conversations/{id}/wingman/tone-adjust
BE->>BE: _validate_conversation()
BE->>BE: _get_recent_messages(limit=5)
BE->>Svc: adjust_tone(conv_id, selectedText, fullText, 'professional', messages)
Svc->>Svc: _build_context_messages(messages, _TONE_ADJUST_SYSTEM_PROMPT)
Svc->>Svc: 追加 user 消息: "原文:{selectedText}\n完整内容:{fullText}\n改写为{tone}风格"
Svc->>Dify: POST /chat/completions<br/>{temperature: 0.3}
Dify-->>Svc: "经排查,您的VPN连接异常..."
Svc-->>BE: {rewritten_text, tone, changes_summary}
BE-->>API: {code: 0, data: {...}}
API-->>Cmp: ToneAdjustResult
Cmp->>Cmp: toneResult = result
Cmp->>Cmp: toneLoading = false
Cmp->>TAP: 显示原文/改写文对比
TAP-->>User: 原文 → 改写文 + 变更说明
alt 点击"替换"
User->>TAP: 点击"替换"
TAP->>RB: emit('replace', rewritten_text)
RB->>RB: 用 rewritten_text 替换选中区域
RB->>TAP: visible = false
else 点击"取消"或外部
User->>TAP: 点击取消
TAP->>RB: emit('cancel')
RB->>TAP: visible = false
end
%% ==========================================================================
%% 4.3 文字润色
%% ==========================================================================
sequenceDiagram
participant User as 坐席
participant RB as ReplyBox.vue
participant PP as PolishPanel.vue
participant Cmp as useAiAssist.ts
participant API as wingman.ts
participant BE as wingman.py<br/>(polish)
participant Svc as WingmanService<br/>.polish_text()
participant Dify as Dify AI
User->>RB: 点击工具栏"润色"按钮
RB->>PP: visible = true(弹出精修面板)
PP-->>User: 显示原文 + 3 个操作按钮
User->>PP: 点击"扩写"
PP->>Cmp: polishText('expand')
Cmp->>Cmp: polishLoading = true
Cmp->>API: polishText(convId, inputText, 'expand', signal)
API->>BE: POST /conversations/{id}/wingman/polish
BE->>BE: _validate_conversation()
BE->>BE: _get_recent_messages(limit=5)
BE->>Svc: polish_text(conv_id, text, 'expand', messages)
Svc->>Svc: _build_context_messages(messages, _POLISH_SYSTEM_PROMPT)
Svc->>Svc: 追加 user 消息: "对以下文字进行扩写:{text}"
Svc->>Dify: POST /chat/completions<br/>{temperature: 0.3}
Dify-->>Svc: "建议您按以下步骤操作:\n1. 退出VPN..."
Svc-->>BE: {polished_text, action, changes_summary}
BE-->>API: {code: 0, data: {...}}
API-->>Cmp: PolishResult
Cmp->>Cmp: polishResult = result
Cmp->>Cmp: polishLoading = false
Cmp->>PP: 显示左右对比(原文 | 结果)
PP-->>User: 左侧原文 | 右侧结果(可编辑)+ 变更说明
Note over User,PP: 坐席可在结果区域手动编辑
alt 点击"替换全部"
User->>PP: 编辑后点击"替换全部"
PP->>RB: emit('replace', editedText)
RB->>RB: inputText = editedText
RB->>PP: visible = false
else 切换操作
User->>PP: 点击"压缩"或"纠错"
PP->>Cmp: polishText('compress' | 'correct')
Note over Cmp,Dify: 重新调用流程
else 点击"取消"
User->>PP: 点击取消
PP->>RB: emit('cancel')
RB->>PP: visible = false
end
%% ==========================================================================
%% 4.4 智能改写
%% ==========================================================================
sequenceDiagram
participant User as 坐席
participant RB as ReplyBox.vue
participant RP as RewritePanel.vue
participant Cmp as useAiAssist.ts
participant API as wingman.ts
participant BE as wingman.py<br/>(rewrite)
participant Svc as WingmanService<br/>.rewrite_versions()
participant RAG as RAGFlow
participant Dify as Dify AI
User->>RB: 点击工具栏"改写"按钮
RB->>RP: visible = true(弹出选择面板)
RP->>Cmp: rewriteVersions()
Cmp->>Cmp: rewriteLoading = true
Cmp->>API: rewriteVersions(convId, inputText, signal)
API->>BE: POST /conversations/{id}/wingman/rewrite
BE->>BE: _validate_conversation()
BE->>BE: _get_recent_messages(limit=10)
BE->>Svc: rewrite_versions(conv_id, text, messages, generate_count=3, include_knowledge=true)
Note over Svc: 构建上下文
Svc->>Svc: _build_context_messages(messages, _REWRITE_SYSTEM_PROMPT)
par 知识库检索(并行)
Svc->>RAG: retrieval(question=当前问题, dataset_ids=默认知识库)
RAG-->>Svc: 检索到 3 个相关文档片段
Svc->>Svc: 将知识片段拼入 system prompt
and 准备 Dify 调用
Note over Svc: 等待知识检索完成
end
Svc->>Dify: POST /chat/completions<br/>{temperature: 0.6}
Dify-->>Svc: "版本1\n---\n版本2\n---\n版本3"
Svc->>Svc: 按 "---" 分割为 3 个版本
Svc->>Svc: 标注 style 和 source
Svc-->>BE: {versions: [{text, style, source}, ...]}
BE-->>API: {code: 0, data: {...}}
API-->>Cmp: RewriteResult
Cmp->>Cmp: rewriteResult = result
Cmp->>Cmp: rewriteLoading = false
Cmp->>RP: 显示 3 个版本卡片
RP-->>User: 版本1(简洁直接)/ 版本2(详细带步骤)/ 版本3(带知识库引用)
alt 选择版本 → 替换
User->>RP: 点击版本卡片 → "替换"
RP->>RB: emit('replace', versionText)
RB->>RB: inputText = versionText
RB->>RP: visible = false
else 选择版本 → 追加
User->>RP: 点击版本卡片 → "追加"
RP->>RB: emit('append', versionText)
RB->>RB: inputText += '\n' + versionText
RB->>RP: visible = false
else 点击"取消"
User->>RP: 点击取消
RP->>RB: emit('cancel')
RB->>RP: visible = false
end
@@ -1,10 +1,12 @@
# IT智能服务台 — 系统架构设计文档
> **文档版本**: v2.1 (综合版)
> **文档版本**: v2.2 (综合版)
> **创建日期**: 2025-07-11
> **最近更新**: 2026-07-10
> **架构师**: 高见远 (Bob)
> **最近更新**: 2026-07-11
> **架构师**: 高见远 (Bob) / 宋献 (Simon)
> **状态**: 正式版
>
> **v2.2 变更**: 新增 §15.8 坐席端AI辅助消息框与布局优化;更新 §7.2 坐席工作台模块布局参数;更新 §8 AI Wingman 设计新增能力
---
@@ -43,6 +45,8 @@
| 15.4 | 企微审批工单同步 | ✅ 设计完成 |
| 15.5 | 复杂场景重构 | ✅ 设计完成 |
| 15.6 | ExternalSystemAdapter抽象层 | ✅ 设计完成 |
| 15.7 | Wingman设计 | ✅ 已实现 |
| 15.8 | 坐席端AI辅助消息框与布局优化 | ✅ 设计完成 |
| **16. 技术分析报告** | | |
| 16.1 | JP-webcli自动化部署能力分析 | ✅ 已完成 |
| **17. 阶段5 自动化闭环** | | |
@@ -307,12 +311,24 @@ IT智能服务台是为企业提供 IT support 的智能化服务平台,核心
**技术栈**Vue 3 + TypeScript + Element Plus + Pinia
**核心功能**
- 会话列表(排队/进行中/已解决)
- 会话用户列表(排队/进行中/已解决)
- 实时聊天
- 快速回复
- AI Wingman 右侧栏
- 回复建议区(AI推荐 + 快速回复,统一入口)
- AI Wingman 右侧栏(训练区:智能标注/质量反馈/知识贡献/使用统计)
- AI 辅助消息框(自动补齐/语气调整/文字润色/智能改写)
- 右栏放大/缩小模式切换
- 消息标记(VIP/招手/情绪)
**布局参数**v2.1 更新):
```
左栏: 260px (会话用户列表 + 待办面板)
中栏: flex:1 (UserInfoBar + TroubleshootBar + 消息列表 + 回复建议区 + ReplyBox)
右栏: 260px(正常) / 560px(放大) (AI训练区 + 模式切换)
```
> 详见 §15.8 坐席端AI辅助消息框与布局优化
### 7.3 H5 用户端模块
**技术栈**Vue 3 + Vant 4 + TypeScript
@@ -327,14 +343,26 @@ IT智能服务台是为企业提供 IT support 的智能化服务平台,核心
## 8. AI Wingman 设计
详见第15.7节「Wingman设计」
详见第15.7节「Wingman设计」和第15.8节「坐席端AI辅助消息框与布局优化」
Wingman 是坐席工作台的 AI 辅助系统:
- **草稿回复**:坐席打字 → AI 实时生成 3 条草稿
- **自动摘要**:会话结束 → AI 200 字摘要
- **知识推荐**:对话中识别关键字 → 推 FAQ
- **排查步骤**:员工描述问题 → AI 给 step-by-step
**现有能力(已实现)**
- **草稿回复**:坐席打字 → AI 实时生成草稿
- **自动摘要**:会话结束 → AI 结构化摘要
- **标签建议**:对话内容 → AI 建议分类标签
- **知识库优化建议**:对话分析 → 知识库改进建议
**新增能力(设计完成,见 §15.8)**
- **实时自动补齐**:输入停顿 > 0.8s → 幽灵文字 → Tab 接受
- **语气调整**:选中文字 → 专业/友好/简洁 → 一键改写
- **文字润色**:扩写/压缩/纠错 → 精修面板 → 确认替换
- **智能改写**:对话上下文 + 知识库 → 3 个备选版本
**布局重构(设计完成,见 §15.8)**
- 回复前功能(草稿/知识/推荐/快回)统一到中栏回复建议区
- 回复后功能(标注/反馈/贡献/统计)归右栏 AI 训练区
- 右栏支持放大/缩小模式切换(260px ↔ 560px
---
@@ -850,6 +878,142 @@ class SecurityStatus(BaseModel):
---
### 15.8 坐席端AI辅助消息框与布局优化
> **新增日期**: 2026-07-11 (v2.1) | **架构师**: 宋献 (Simon)
> **关联文档**: `docs/03-技术架构/坐席端AI辅助消息框与布局优化-架构设计.md`
> **关联PRD**: `docs/02-产品需求/坐席端AI辅助消息框-PRD.md` + `docs/02-产品需求/坐席端布局优化建议.md`
#### 15.8.1 功能概述
在现有 Wingman 基础上新增 4 项 AI 辅助功能 + 坐席端布局全面重构:
**AI 辅助消息框(4 项新增功能)**
| 功能 | 交互方式 | 后端方法 | temperature |
|------|---------|---------|-------------|
| 实时自动补齐 | 内联幽灵文字,Tab 接受,debounce 800ms | `generate_completion()` | 0.2 |
| 语气调整 | 选中文字 → 专业/友好/简洁 → 原文/改写对比 | `adjust_tone()` | 0.3 |
| 文字润色 | 点润色按钮 → 扩写/压缩/纠错 → 精修面板 | `polish_text()` | 0.3 |
| 智能改写 | 点改写按钮 → 3 个备选版本 → 替换/追加 | `rewrite_versions()` | 0.6 |
**布局重构**
| 改动区域 | 变化 |
|----------|------|
| 左栏 | 280px → 260px(会话用户列表) |
| 中栏 | +80px 宽度;新增回复建议区;UserInfoBar/TroubleshootBar 默认折叠 |
| 右栏 | 320px → 260px(正常)/560px(放大);改为 AI 训练区;支持模式切换 |
| 工具栏 | 单行左右分区:常规工具(左) + AI工具(右) |
#### 15.8.2 后端架构
**WingmanService 扩展**`backend/app/services/wingman_service.py`):
```
WingmanService
├── 现有方法(保持不变)
│ ├── generate_draft() temp=0.3
│ ├── generate_summary() temp=0.3
│ ├── suggest_tags() temp=0.3
│ └── generate_knowledge_suggestion()
├── 新增方法
│ ├── generate_completion() temp=0.2 ← 自动补齐
│ ├── adjust_tone() temp=0.3 ← 语气调整
│ ├── polish_text() temp=0.3 ← 文字润色
│ └── rewrite_versions() temp=0.6 ← 智能改写
└── 改造方法
└── _call_wingman_api(context, temperature=0.3) ← 新增可选参数
```
**关键改造**`_call_wingman_api()` 新增 `temperature` 参数(默认 0.3 保持兼容),各方法按需传递不同值。
**API 端点**`backend/app/api/wingman.py`):
| 端点 | 方法 | 说明 |
|------|------|------|
| `/api/conversations/{id}/wingman/autocomplete` | POST | 自动补齐 |
| `/api/conversations/{id}/wingman/tone-adjust` | POST | 语气调整 |
| `/api/conversations/{id}/wingman/polish` | POST | 文字润色 |
| `/api/conversations/{id}/wingman/rewrite` | POST | 智能改写 |
新增 Pydantic 请求模型(`backend/app/schemas/wingman_assist.py`):`AutocompleteRequest``ToneAdjustRequest``PolishRequest``RewriteRequest`,含字段验证和枚举类型。
#### 15.8.3 前端架构
**新增组件树**
```
Workspace.vue
├── ConversationList.vue (左栏 - 会话用户列表)
├── ChatArea.vue (中栏)
│ ├── UserInfoBar.vue (详情默认折叠)
│ ├── TroubleshootBar.vue (默认折叠为图标条)
│ ├── MessageList.vue
│ ├── ReplySuggestArea.vue (新增 - 回复建议区)
│ │ ├── AiRecommendBar.vue (合并自 AiRecommendInline + AiSuggestReply)
│ │ └── QuickReplyBar.vue (改造自 QuickReplyPanel)
│ └── ReplyBox.vue (改造)
│ ├── GhostTextOverlay.vue (新增 - 幽灵文字)
│ ├── ToneAdjustPopover.vue (新增 - 语气浮层)
│ ├── PolishPanel.vue (新增 - 润色面板)
│ └── RewritePanel.vue (新增 - 改写面板)
└── AiAssistantPanel.vue (全面重构)
├── PanelModeToggle.vue (新增 - 正常/放大切换)
└── AiTrainingPanel.vue (新增 - 训练区)
├── SmartTagEditor.vue (智能标注)
├── QualityFeedback.vue (质量反馈)
├── KnowledgeContribute.vue (知识贡献)
└── UsageStats.vue (使用统计)
```
**新增 Composable**
| Composable | 职责 |
|------------|------|
| `useAutoComplete.ts` | debounce 800ms + AbortController + ghost text 管理 |
| `useAiTextTools.ts` | 语气/润色/改写统一调用和结果管理 |
| `usePanelMode.ts` | 右栏 260px ↔ 560px 模式切换 |
**功能重复清理**5 处):
| 编号 | 重复类型 | 清理方案 |
|------|---------|---------|
| R1 | AI草稿双展示 | 统一到中栏回复建议区 |
| R2 | AI推荐回复三处展示 | 合并为 AiRecommendBar.vue |
| R3 | 排查流程命名混淆 | 右栏按钮重命名为"智能标注" |
| R4 | 标签建议双入口 | 合并为单一"智能标注"功能 |
| R5 | 孤儿组件 | 删除 AiRecommendInline.vue |
#### 15.8.4 关键设计决策
| 决策 | 选择 | 理由 |
|------|------|------|
| 补齐交互 | textarea + mirror div + ghost overlay | 保持现有快捷键和粘贴功能不变 |
| 补齐 API | HTTP + AbortController | 轻量请求,与现有 Wingman 调用一致 |
| 语气/润色浮层 | el-popover / el-drawer | 不遮挡消息列表,不打断工作流 |
| 右栏模式切换 | CSS 变量 + class 切换 | 组件不销毁,状态不丢失 |
| 回复建议区动画 | max-height transition | 平滑过渡,组件实例保持存活 |
| temperature 改造 | 可选参数默认 0.3 | 现有方法无需修改,向后兼容 |
#### 15.8.5 开发计划
| 阶段 | 内容 | 预估 |
|------|------|------|
| Phase 1 | 后端 WingmanService 扩展 + Pydantic 模型 | 1.5 天 |
| Phase 2 | 前端 API 层 + Composable | 1 天 |
| Phase 3 | ReplyBox 工具栏 + AI 辅助组件 | 2 天 |
| Phase 4 | 布局重构 + 回复建议区 | 2 天 |
| Phase 5 | 右栏训练区 + 模式切换 | 1.5 天 |
| Phase 6 | 功能清理 + 联调测试 | 1 天 |
| **合计** | | **9 天** |
> 完整架构设计(类图、时序图、API 规范、Dify prompt 模板、TypeScript 类型定义)详见独立文档:`docs/03-技术架构/坐席端AI辅助消息框与布局优化-架构设计.md`
---
## 16. 技术分析报告
### 16.1 JP-webcli自动化部署能力分析
@@ -0,0 +1,38 @@
%% 代答排除匹配引擎架构图
%% 来源:增量设计-知识库迭代-开发任务分解-20260712.md §4.1
graph TB
subgraph "代答排除匹配引擎"
Engine[ExclusionService<br/>匹配引擎入口]
Chain[ExclusionChain<br/>责任链调度]
subgraph "策略模式 - 4种匹配器"
M1[KeywordMatcher<br/>关键词匹配]
M2[RegexMatcher<br/>正则匹配]
M3[IntentMatcher<br/>意图匹配]
M4[CategoryMatcher<br/>分类排除]
end
Engine --> Chain
Chain --> M1
Chain --> M2
Chain --> M3
Chain --> M4
end
subgraph "外部依赖"
Dify[Dify 意图识别<br/>复用审批意图链路]
TriageDB[triage_sessions<br/>分诊结果]
RuleDB[(exclusion_rules)]
LogDB[(exclusion_logs)]
end
M3 -->|调用| Dify
M4 -->|查询| TriageDB
Engine -->|读取规则| RuleDB
Engine -->|记录命中| LogDB
style M1 fill:#dbeafe,stroke:#3b82f6
style M2 fill:#fce7f3,stroke:#ec4899
style M3 fill:#ede9fe,stroke:#8b5cf6
style M4 fill:#dcfce7,stroke:#07C160
@@ -0,0 +1,67 @@
%% 模块依赖关系图
%% 来源:增量设计-知识库迭代-开发任务分解-20260712.md §9
graph TB
subgraph "T01: 项目基础设施"
DB[数据库迁移<br/>3张新表]
CFG[config.py<br/>新增配置项]
MODEL[ORM 模型<br/>3个]
SCHEMA[Pydantic Schema<br/>2个文件]
ROUTER[router.py<br/>路由注册]
end
subgraph "T02: 分诊交互模块"
TRI_API[分诊 API<br/>12个端点]
TRI_SVC[TriageService<br/>+ DifyTriageService]
H5_FE[H5 前端<br/>TriageCard + useTriage]
AG_FE[坐席端前端<br/>TriageDashboard + 3组件]
DIFY_APP[Dify 分诊应用<br/>Prompt + 配置]
end
subgraph "T03: 拓扑预览模块"
TOPO_FE[管理后台前端<br/>TopologyPreview + 3组件]
TOPO_API[复用已有<br/>GET /graph]
end
subgraph "T04: 代答排除模块"
EXC_API[排除管理 API<br/>8个端点]
EXC_SVC[ExclusionService<br/>+ 4个Matcher]
EXC_FE[管理后台前端<br/>ExclusionRules + 2组件]
MSG_INT[消息流集成<br/>ai_handler.py]
end
%% 依赖关系
DB --> TRI_API
DB --> EXC_API
CFG --> TRI_SVC
MODEL --> TRI_API
MODEL --> EXC_API
SCHEMA --> TRI_API
SCHEMA --> EXC_API
ROUTER --> TRI_API
ROUTER --> EXC_API
TRI_API --> TRI_SVC
TRI_SVC --> H5_FE
TRI_SVC --> AG_FE
TRI_SVC --> DIFY_APP
EXC_API --> EXC_SVC
EXC_SVC --> EXC_FE
EXC_SVC --> MSG_INT
%% 软依赖
TRI_SVC -.->|"CategoryMatcher<br/>软依赖分诊结果"| EXC_SVC
%% 拓扑预览独立
TOPO_FE --> TOPO_API
%% 并行标注
style TOPO_FE fill:#e8f5e9,stroke:#4caf50,stroke-dasharray: 5 5
style TOPO_API fill:#e8f5e9,stroke:#4caf50,stroke-dasharray: 5 5
style DB fill:#e3f2fd,stroke:#2196f3
style CFG fill:#e3f2fd,stroke:#2196f3
style MODEL fill:#e3f2fd,stroke:#2196f3
style SCHEMA fill:#e3f2fd,stroke:#2196f3
style ROUTER fill:#e3f2fd,stroke:#2196f3
@@ -0,0 +1,74 @@
%% 分诊主流程时序图
%% 来源:增量设计-知识库迭代-开发任务分解-20260712.md §2.5
sequenceDiagram
participant H5 as H5 员工端
participant API as FastAPI 后端
participant TriSvc as TriageService
participant DifyTri as Dify 分诊应用
participant DB as PostgreSQL
participant AG as 坐席端看板
participant WS as WebSocket
rect rgb(255, 243, 224)
Note over H5,DB: 阶段1:发起分诊
H5->>API: POST /api/h5/triage/start {conversation_id, question}
API->>TriSvc: start_triage(conversation_id, question)
TriSvc->>DB: INSERT triage_sessions (status=triaging)
TriSvc->>DifyTri: 调用分诊 prompt(拆分问题为分步选择题)
alt Dify 5秒内响应
DifyTri-->>TriSvc: {steps[], confidence, urgency, suggested_route, problem_type}
TriSvc->>DB: UPDATE triage_sessions SET triage_steps, confidence, urgency, suggested_route
TriSvc-->>API: {triage_id, steps, total, confidence, urgency, suggested_route}
API-->>H5: {triage_id, steps, total, ...}
else Dify 超时(>5s)
TriSvc->>DB: UPDATE triage_sessions SET status=timeout
TriSvc-->>API: 超时,自动转人工
API-->>H5: 分诊超时,已转人工
end
end
rect rgb(227, 242, 253)
Note over H5,DB: 阶段2:分步选择 + 坐席协同
loop 每一步
H5->>API: POST /api/h5/triage/step {triage_id, step_index, selected_label}
API->>TriSvc: submit_step(triage_id, step_index, selected_label)
TriSvc->>DB: 记录 collected_context
TriSvc->>DifyTri: 根据选择动态调整后续步骤
DifyTri-->>TriSvc: next_step
TriSvc-->>API: {next_step, collected_context}
API-->>H5: {next_step, collected_context}
end
Note over AG: 坐席看板实时查看分诊进度
AG->>API: GET /api/agent/triage/pending
API-->>AG: 待分诊列表
AG->>API: GET /api/agent/triage/{triage_id}
API-->>AG: 分诊详情
opt 坐席排除选项
AG->>API: POST /api/agent/triage/{triage_id}/exclude-options {excluded_labels}
API->>WS: WS 推送 excluded_labels 到 H5
WS-->>H5: {type: "triage_exclude", excluded_labels}
H5->>H5: TriageCard.setExcludedOptions(labels)
end
end
rect rgb(232, 245, 233)
Note over H5,DB: 阶段3:分诊完成 / 转人工
alt 所有步骤完成
H5->>API: POST /api/h5/triage/complete {triage_id, context}
API->>TriSvc: complete_triage(triage_id, context)
TriSvc->>DifyTri: 根据收集的上下文生成最终回复
DifyTri-->>TriSvc: {reply, confidence}
TriSvc->>DB: UPDATE triage_sessions SET status=routed, route_action=ai_self
TriSvc-->>API: {reply, confidence}
API-->>H5: AI 回复
else 转人工
H5->>API: POST /api/h5/triage/transfer {triage_id, context}
API->>TriSvc: transfer_to_human(triage_id, context)
TriSvc->>DB: UPDATE triage_sessions SET status=routed, route_action=human
TriSvc-->>API: 转人工成功
API-->>H5: 已转人工
end
end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,157 @@
%% =============================================================================
%% 增量设计:坐席端布局优化 v2.0 — 时序图
%% =============================================================================
%% 创建日期:2026-07-11
%% 包含 3 个关键流程的时序图
%% =============================================================================
%% ====== 时序图 1:回复建议区交互流程 ======
sequenceDiagram
autonumber
participant User as 坐席
participant ChatArea as ChatArea
participant RSA as ReplySuggestArea
participant ARB as AiRecommendBar
participant QRB as QuickReplyBar
participant CS as ConversationStore
participant RB as ReplyBox
participant API as Wingman API
Note over User, API: 场景:坐席选中会话 → 查看 AI 推荐 → 采纳填入输入框 → 发送
User->>ChatArea: 选中会话
ChatArea->>CS: selectConversation(id)
CS->>API: generateDraft(convId)
API-->>CS: DraftResult {content, confidence, reasoning}
CS->>CS: aiDrafts.set(convId, draft)
CS-->>ChatArea: currentConversationId 更新
ChatArea->>RSA: render (conversationId)
RSA->>ARB: :recommendations = loadFromStore()
ARB->>CS: 读取 aiDrafts.get(convId)
CS-->>ARB: Set~DraftResult~
ARB-->>User: 横向展示 1-3 条 AI 推荐卡片
Note over User, ARB: 坐席点击某条推荐
User->>ARB: 点击推荐卡片 #1
ARB->>CS: pendingReplyText = recommendation.content
ARB->>RSA: emit('select', content)
RSA->>ChatArea: emit('select', content)
Note over CS, RB: pendingReplyText 变化触发 ReplyBox watch
CS-->>RB: watch(pendingReplyText) 触发
RB->>RB: inputText = pendingReplyText
RB->>RB: pendingReplyText = '' (清空)
RB-->>User: 输入框显示推荐内容,光标聚焦
Note over User, RB: 坐席可编辑后发送
User->>RB: 按 Enter 发送
RB->>RB: handleSend()
RB->>ChatArea: emit('send', content)
ChatArea->>CS: sendReply(content, replyToId?)
CS->>API: sendMessage(convId, content)
API-->>CS: Message 对象
CS-->>ChatArea: messages.push(newMsg)
ChatArea-->>User: 消息列表刷新,显示新消息
Note over User, QRB: 场景:坐席使用快速回复
User->>QRB: 点击 L1 分类 chip(如"账号"
QRB->>QRB: navState.l1Index = index
QRB-->>User: 横向展示 L2 条目预览
User->>QRB: 点击条目"密码重置指引"
QRB->>CS: pendingReplyText = template.content
CS-->>RB: watch 触发,填入输入框
RB-->>User: 输入框显示快速回复内容
%% ====== 时序图 2:右栏模式切换流程 ======
sequenceDiagram
autonumber
participant User as 坐席
participant PMT as PanelModeToggle
participant AAP as AiAssistantPanel
participant WS as Workspace
participant CS as ConversationStore
participant DOM as DOM/CSS
Note over User, DOM: 场景:坐席点击右栏放大/缩小开关
User->>PMT: 点击开关按钮
PMT->>AAP: emit('toggle')
AAP->>WS: emit('toggle')
alt 当前为 normal 模式 (260px)
WS->>CS: setPanelMode('expanded')
CS-->>WS: panelMode = 'expanded'
WS->>WS: rightSidebarRef.style.width = 'var(--assistant-panel-expanded)'
WS->>DOM: CSS transition: width 260px → 560px (0.3s)
DOM-->>User: 右栏平滑放大到 560px
Note over WS, DOM: 中栏 flex:1 自动缩小,min-width: 400px 保底
else 当前为 expanded 模式 (560px)
WS->>CS: setPanelMode('normal')
CS-->>WS: panelMode = 'normal'
WS->>WS: rightSidebarRef.style.width = 'var(--assistant-panel-width)'
WS->>DOM: CSS transition: width 560px → 260px (0.3s)
DOM-->>User: 右栏平滑缩小到 260px
Note over WS, DOM: 中栏 flex:1 自动扩大
end
Note over User, DOM: 拖拽手柄在放大模式下仍然可用
Note over WS, DOM: 拖拽范围:200px ~ 560px(上限已调整)
%% ====== 时序图 3:功能迁移流程(开发过程) ======
sequenceDiagram
autonumber
participant Dev as 开发过程
participant Old as 旧组件
participant New as 新组件
participant ChatArea as ChatArea
participant AAP as AiAssistantPanel
participant Store as ConversationStore
Note over Dev, Store: Phase 1 — 清理重复(T02
Dev->>Old: 删除 AiRecommendInline.vue
Dev->>ChatArea: 移除 import AiRecommendInline
Dev->>Old: 删除 AiSuggestReply.vue
Dev->>AAP: 移除 import AiSuggestReply
Note over Dev: 移除右栏已迁移功能
Dev->>AAP: 移除 Wingman 触发按钮组
Dev->>AAP: 移除 AI 推荐区(AiSuggestReply 引用)
Dev->>AAP: 移除快速回复区(QuickReplyPanel 引用)
Note over AAP: AiAssistantPanel 仅保留容器骨架 + 标注区
Note over Dev, Store: Phase 3 — 中栏建设(T03
Dev->>New: 创建 ReplySuggestArea.vue
Dev->>New: 创建 AiRecommendBar.vue
Note over New, Store: AiRecommendBar 从 ConversationStore.aiDrafts 加载数据
Dev->>New: 创建 QuickReplyBar.vue
Note over New, Store: QuickReplyBar 从 QuickReplyStore 加载模板
Dev->>ChatArea: 在消息列表与 ReplyBox 之间插入 ReplySuggestArea
Dev->>New: ReplyBox 工具栏重构为左右分区
Note over Dev, Store: Phase 4 — 右栏建设(T04
Dev->>New: 创建 AiTrainingPanel.vue (Tab 容器)
Dev->>New: 创建 SmartTagEditor.vue
Note over New, Store: SmartTagEditor 从 AAP 迁出标签逻辑
Dev->>New: 创建 QualityFeedback.vue
Dev->>New: 创建 KnowledgeContribute.vue
Dev->>New: 创建 PanelModeToggle.vue
Dev->>AAP: AiAssistantPanel 承载 PanelModeToggle + AiTrainingPanel
Dev->>Store: ConversationStore 新增 panelMode 状态
Note over Dev, Store: Phase 5 — 联调测试(T05
Dev->>ChatArea: 验证 ReplySuggestArea ↔ ReplyBox 联动
Dev->>ChatArea: 验证消息区高度 ≥ 400px (1080p)
Dev->>AAP: 验证模式切换 + Tab 切换
Dev->>Store: 验证 pendingReplyText 全链路通信
@@ -0,0 +1,222 @@
%% =============================================================================
%% 增量设计:坐席端布局优化 v2.0 — 类图
%% =============================================================================
%% 创建日期:2026-07-11
%% 技术栈:Vue 3 + Element Plus + TypeScript + Pinia + CSS Variables
%% =============================================================================
classDiagram
%% ====== 容器/页面级组件 ======
class Workspace {
+Ref~boolean~ assistantVisible
+Ref~PanelMode~ panelMode
+Ref~HTMLElement~ leftSidebarRef
+Ref~HTMLElement~ rightSidebarRef
+startLeftResize(e: MouseEvent) void
+startRightResize(e: MouseEvent) void
+togglePanelMode() void
+onMounted() void
}
class ChatArea {
+Ref~Message~ replyToMessage
+Ref~HTMLElement~ messageListRef
+handleSend(content: string) void
+handleReplyTo(message: Message) void
+scrollToBottom() void
}
%% ====== 中栏新增组件 ======
class ReplySuggestArea {
+Props: conversationId: string
+Emits: select(content: string)
+Ref~string~ activeTab
+loadRecommendations() void
}
class AiRecommendBar {
+Props: recommendations: AiRecommendation[]
+Props: loading: boolean
+Emits: select(content: string)
+Emits: refresh()
+handleSelect(index: number) void
+confidenceStyle: ComputedRef
}
class QuickReplyBar {
+Props: templates: QuickReply[]
+Emits: select(content: string)
+Ref~QuickReplyNav~ navState
+Ref~string~ searchQuery
+selectTemplate(tpl: QuickReply) void
+goBack() void
}
class ReplyBox {
+Props: replyToMessage: Message
+Emits: send(content: string)
+Emits: cancelReply()
+Ref~string~ inputText
+Ref~number~ textareaHeight
+handleSend() void
+handleKeydown(event: KeyboardEvent) void
+handleAiTool(action: AiToolAction) void
+handleScreenshot() void
+handlePaste(event: ClipboardEvent) void
}
%% ====== 右栏组件 ======
class AiAssistantPanel {
+Props: mode: PanelMode
+Emits: toggle()
+Ref~string~ conversationId
}
class AiTrainingPanel {
+Props: conversationId: string
+Ref~TrainingTab~ activeTab
+resetForNewConversation() void
}
class PanelModeToggle {
+Props: mode: PanelMode
+Emits: toggle()
}
class SmartTagEditor {
+Props: conversationId: string
+Ref~ConversationTags~ currentTags
+Ref~string[]~ suggestedTagList
+Ref~boolean~ loadingTagSuggestions
+handleSuggestTags() void
+handleAddSuggestedTag(tag: string) void
+handleRemoveTag(key: string) void
+handleAddManualTag() void
+loadCurrentTags() void
}
class QualityFeedback {
+Props: conversationId: string
+Ref~Annotation[]~ feedbackList
+Ref~boolean~ loading
+handleSubmit(messageId: string, feedback: AnnotationFeedback) void
+loadAnnotations() void
}
class KnowledgeContribute {
+Props: conversationId: string
+Ref~ContributeForm~ contributeForm
+Ref~boolean~ submitting
+handleSubmit() void
+resetForm() void
}
%% ====== 修改的现有组件 ======
class UserInfoBar {
+Props: conversation: Conversation
+Props: availableAgents: AgentInfo[]
+Props: canInviteCollaborator: boolean
+Emits: assign, resolve, toggle-pin, toggle-todo, transfer, invite
+Ref~boolean~ isExpanded
+Ref~boolean~ showItLevelSelector
+toggleExpand() void
+resetForNewConversation() void
}
class TroubleshootBar {
+Ref~boolean~ isFlowchartExpanded
+Ref~boolean~ isIconBarCollapsed
+Ref~TroubleshootingTemplate[]~ templates
+toggleFlowchart() void
+toggleCollapse() void
+handleTemplateChange(templateId: string) void
+handleStepClick(index: number) void
+loadTemplates() void
}
%% ====== Store ======
class ConversationStore {
+Ref~string~ pendingReplyText
+Ref~Map~ aiDrafts
+Ref~PanelMode~ panelMode
+Ref~string~ currentConversationId
+Ref~Conversation~ currentConversation
+Ref~Message[]~ messages
+setPanelMode(mode: PanelMode) void
+selectConversation(id: string) void
+sendReply(content: string, replyToId?: string) void
}
class QuickReplyStore {
+Ref~QuickReply[]~ templates
+ComputedRef~templatesByCategory
+ComputedRef~categories
+fetchTemplates() void
}
%% ====== 类型定义 ======
class PanelMode {
<<enumeration>>
normal
expanded
}
class TrainingTab {
<<enumeration>>
smart-tag
quality-feedback
knowledge-contribute
}
class AiRecommendation {
+string title
+string content
+number confidence
}
class ContributeForm {
+string title
+string problem
+string solution
+string category
}
class QuickReplyNav {
+number l1Index
+number l2Index
}
%% ====== 组合关系(contains/renders ======
Workspace *-- ChatArea : renders
Workspace *-- AiAssistantPanel : renders
ChatArea *-- UserInfoBar : contains
ChatArea *-- TroubleshootBar : contains
ChatArea *-- ReplySuggestArea : contains (新增)
ChatArea *-- ReplyBox : contains
ReplySuggestArea *-- AiRecommendBar : contains
ReplySuggestArea *-- QuickReplyBar : contains
AiAssistantPanel *-- PanelModeToggle : contains
AiAssistantPanel *-- AiTrainingPanel : contains
AiTrainingPanel *-- SmartTagEditor : tab 1
AiTrainingPanel *-- QualityFeedback : tab 2
AiTrainingPanel *-- KnowledgeContribute : tab 3
%% ====== 依赖关系(uses ======
AiRecommendBar ..> ConversationStore : reads aiDrafts, writes pendingReplyText
QuickReplyBar ..> ConversationStore : writes pendingReplyText
QuickReplyBar ..> QuickReplyStore : reads templates
ReplyBox ..> ConversationStore : reads/writes pendingReplyText
SmartTagEditor ..> ConversationStore : reads currentConversation
QualityFeedback ..> ConversationStore : reads messages
Workspace ..> ConversationStore : reads/writes panelMode
AiAssistantPanel ..> ConversationStore : reads currentConversationId
%% ====== 类型引用 ======
AiRecommendBar ..> AiRecommendation : uses
KnowledgeContribute ..> ContributeForm : uses
QuickReplyBar ..> QuickReplyNav : uses
PanelModeToggle ..> PanelMode : uses
AiTrainingPanel ..> TrainingTab : uses
File diff suppressed because it is too large Load Diff
+43 -1
View File
@@ -1,11 +1,48 @@
# 智能IT服务台 - 版本记录
> **最后更新**2026-07-05
> **最后更新**2026-07-13
---
## 版本历史
### v0.7.3 (2026-07-12~13)
**更新内容**
1. **AI 对话链路全栈改造 Phase 1-6**#59-#69
- Dify Prompt JSON 输出 + 后端 blocking + JSON 解析 + 双 WS 推送
- 审批关键词收窄(~40→~25+ 两级分类 Prompt v4.0 + 删除前端 checkApprovalIntent
- WS 扩展(ai_thinking + dynamic_recommend+ MessageBubble ai_structured 渲染 + RightPanel v2
- VisionService 接入(图片分析 + 5秒消息融合)+ 降级策略
- 坐席端 ai_thinking 指示器 + ai_structured/byod_card 渲染
- diagnosis_stage 字段 + response_time_ms 计时 + 慢响应告警
2. **上下文感知智能诊断→修复闭环**
- 三层诊断(API→Script→AI+ 三段排队(VIP→info_locked→not locked
- 答题插队 + 五场景关闭 + 迁移 052(6表+6列)
3. **坐席端布局优化 v2.0**
- QuickReplyBar L1+L2 悬浮 / ReplyBox 左右分区 / 右栏 260↔560px
- 键盘快捷键 v2.3(纯数字路由 / ESC 分层撤销 / IME 守卫)
4. **知识库迭代 3**
- 分诊交互 + 拓扑预览(ECharts)+ 代答排除(4种匹配器)+ 迁移 051
5. **H5 v4 人工坐席交互改造**#116
- 三态文案统一"人工坐席" / 按钮位置上移 / 删除 CallAgentModal 弹窗
- 截图提示改版 / 移动端 CSS 隐藏 / DB 同步 funny_phrases 表
6. **部署路径修正**
- 确认服务器项目根路径 `/opt/wecom-it-desk/`
- 所有前端 dist 均为 ro bind mount,只能在宿主机源路径操作
**部署方式**:后端 `docker compose restart backend` / 前端宿主机 tar 解压 + `nginx -s reload`
**验证**JS hash 更新确认 / JS 包内容检查 / API 200 / Nginx healthy
---
### v0.7.1 (2026-06-23)
**更新内容**
@@ -40,6 +77,11 @@
## 问题修复记录
### 2026-07-13
- H5 v4 部署路径修正(`/opt/wecom-it-desk/frontend-h5/dist` ro bind mount
- 服务器 Docker 挂载配置与本地仓库不一致问题定位
### 2026-07-05
- Nginx upstream 配置修复
@@ -1,9 +1,9 @@
# 会议室预定-小鱼易联终端 部署指南
> **日期**: 2026-07-11
> **版本**: v1.0
> **代码状态**: 40/40 测试通过,待部署
> **预估部署时间**: 30-45 分钟
> **版本**: v2.0(新增报修+指南+二维码+移动端适配)
> **代码状态**: 待部署
> **预估部署时间**: 45-60 分钟
---
@@ -15,14 +15,15 @@
- [ ] 确认会议室 `meetingroom_id` 列表
### 1.2 服务器环境
- [ ] PostgreSQL 可用(需执行 Alembic 迁移)
- [ ] PostgreSQL 可用(需执行 Alembic 迁移 050 + 051
- [ ] Redis 可用(会议室缓存依赖)
- [ ] Nginx 可用(终端前端静态文件)
- [ ] Nginx 可用(终端前端静态文件 + API 代理
- [ ] Docker Compose 可用
### 1.3 终端设备
- [ ] 确认小鱼易联终端型号(当前按浏览器方案设计
- [ ] 确认小鱼易联终端型号(NE90/NE60 支持 H5 应用,NE2005 需二维码降级
- [ ] 终端浏览器支持 WebSocket + ES6
- [ ] 小鱼管理后台可访问(配置 H5 应用入口)
---
@@ -31,7 +32,7 @@
### Step 1: 数据库迁移
```bash
# 进入后端容器
# 进入后端容器执行迁移(050 会议室基础表 + 051 报修+指南表)
docker compose exec backend alembic upgrade head
# 验证新表
@@ -40,21 +41,23 @@ from app.database import engine
from sqlalchemy import inspect
insp = inspect(engine)
tables = insp.get_table_names()
print('meetingroom_booking_snapshots' in tables) # 应为 True
print('terminal_room_bindings' in tables) # 应为 True
print('terminal_room_bindings' in tables) # 应为 True050
print('meetingroom_guide' in tables) # 应为 True051
print('meetingroom_repair' in tables) # 应为 True051
"
```
### Step 2: 环境变量配置
`docker-compose.yml` 中添加:
`.env` 中添加:
```bash
# 终端页面基础URL(用于NE2005二维码生成)
TERMINAL_BASE_URL=https://itsupport.servyou.com.cn/itterminal/
```
`docker-compose.yml` 的 backend environment 中已添加:
```yaml
backend:
environment:
- WECOM_MEETINGROOM_SECRET=<your_secret>
- MEETINGROOM_CACHE_TTL_ROOMS=600
- MEETINGROOM_CACHE_TTL_BOOKING=30
- MEETINGROOM_CACHE_TTL_STATUS=10
- TERMINAL_BASE_URL=${TERMINAL_BASE_URL:-https://itsupport.servyou.com.cn/itterminal/}
```
重启后端:
@@ -65,23 +68,28 @@ docker compose up -d backend
### Step 3: 后端验证
```bash
# 验证 API 端点注册
curl -sk https://localhost/api/itportal/meetingroom/rooms \
-H "Authorization: Bearer $TOKEN"
# 验证会议室 API(已存在)
curl -sk https://localhost/itportal/meetingroom/list
# 验证 WS 端点
wscat -c "wss://localhost/ws/terminal/TEST-SN-001"
# 验证报修 API(新增)
curl -sk -X POST https://localhost/itportal/meetingroom/repair \
-H "Content-Type: application/json" \
-d '{"terminal_sn":"TEST-SN","meetingroom_id":1,"meetingroom_name":"测试","device_type":"projector","fault_description":"测试报修"}'
# 验证管理端绑定 API
curl -sk https://localhost/api/itportal/admin/terminal-bindings \
-H "Authorization: Bearer $ADMIN_TOKEN"
# 验证指南 API(新增)
curl -sk https://localhost/itportal/meetingroom/guides
# 验证二维码 API(新增)
curl -sk https://localhost/itportal/meetingroom/terminal/TEST-SN/qrcode -o /tmp/qr.png
file /tmp/qr.png # 应为 PNG image
```
### Step 4: 终端前端部署
### Step 4: 终端前端构建与部署
```bash
# 1. 本地构建
cd frontend-terminal
npm install # 安装新增的 qrcode 依赖
npm run build
# 2. 打包
@@ -93,50 +101,139 @@ python C:\Users\simon\.workbuddy\skills\jumpserver-ops\scripts\jms_ops.py \
# 4. 服务器解压
cd /opt/wecom-it-desk/frontend-terminal/
rm -rf dist # 删除旧 distbind mount 铁律:rm后重建必须重启容器)
tar -xzf /tmp/terminal-dist.tar.gz
```
# 5. Nginx 配置
# 在 nginx.conf 中添加:
### Step 5: Nginx 配置更新
`nginx/nginx.conf` 已更新,新增以下 location(两个 server 块均已添加):
```nginx
# 小鱼终端大屏 — /itterminal/
location /itterminal/ {
alias /usr/share/nginx/html/terminal/;
try_files $uri $uri/ /itterminal/index.html;
alias /usr/share/nginx/html/itterminal/;
index index.html;
try_files $uri /itterminal/index.html;
}
# 会议室 API — /itportal/meetingroom/
# 必须在 /itportal/ 静态文件之前匹配(nginx 最长前缀优先)
location /itportal/meetingroom/ {
proxy_pass http://backend_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
```
### Step 5: Nginx 重启
`docker-compose.yml` nginx volumes 已添加:
```yaml
- ./frontend-terminal/dist:/usr/share/nginx/html/itterminal:ro
```
重启 Nginx
```bash
docker compose restart nginx
```
### Step 6: H5 端入口验证
### Step 6: 终端前端验证
H5 端已内置会议室入口(`MeetingroomView.vue`),无需额外部署。
验证:
```bash
curl -sk https://localhost/ith5/ | grep -o 'meetingroom'
# 验证终端页面可访问
curl -sk https://localhost/itterminal/ | head -5
# 验证 API 代理(通过 nginx 访问后端)
curl -sk https://localhost/itportal/meetingroom/guides
# 验证静态资源
curl -sk -o /dev/null -w "%{http_code}" https://localhost/itterminal/assets/index-*.js
```
---
## 三、部署后验证
## 三、小鱼管理后台 H5 应用配置
### 3.1 功能验证清单
### 3.1 支持型号确认
| 型号 | H5 应用支持 | 配置方式 |
|------|------------|---------|
| NE90 | ✅ 支持 | 小鱼管理后台配置 H5 应用入口 |
| NE60 | ✅ 支持 | 同上 |
| NE20 | ✅ 支持 | 同上 |
| AE2060 | ✅ 支持 | 同上 |
| ME55S | ⚠️ 部分固件支持 | 需确认固件版本 |
| NE2005 | ❌ 不支持 | 使用二维码降级方案 |
### 3.2 H5 应用入口配置(NE90/NE60/NE20/AE2060
1. 登录**小鱼易联管理后台**`https://mt.xylink.com`
2. 进入 **应用管理 > 自定义应用**
3. 创建新应用:
- **应用名称**: `IT智能服务台`
- **应用类型**: H5 网页应用
- **访问地址**: `https://itsupport.servyou.com.cn/itterminal/{终端SN}/`
- **展示方式**: 终端主界面快捷入口
4. 推送应用到目标终端
5. 在终端上验证 H5 应用可正常打开
### 3.3 终端 SN 绑定
在管理后台(`/itadmin/`)的终端绑定页面中:
1. 添加终端 SN 与会议室的绑定关系
2. 每个终端 SN 对应一个企微会议室 ID
3. 绑定后终端页面自动加载对应会议室状态
### 3.4 NE2005 二维码降级方案
对于不支持 H5 应用的 NE2005 终端:
1. **生成二维码**
```bash
# 通过 API 生成终端访问二维码
curl -sk https://itsupport.servyou.com.cn/itportal/meetingroom/terminal/{SN}/qrcode -o qr.png
```
或直接在浏览器访问:
`https://itsupport.servyou.com.cn/itportal/meetingroom/terminal/{SN}/qrcode`
2. **打印二维码**:将二维码打印为贴纸(建议尺寸 10×10cm)
3. **张贴二维码**:贴在 NE2005 终端的显眼位置
4. **用户使用流程**
- 用户用企业微信/微信扫描二维码
- 手机浏览器打开终端页面(移动端自适应布局)
- 可查看会议室状态、预定、报修、查看指南
---
## 四、部署后验证
### 4.1 功能验证清单
| # | 验证项 | 验证方法 | 预期结果 |
|---|--------|---------|---------|
| 1 | 会议室列表 | `GET /api/itportal/meetingroom/rooms` | 返回会议室列表 |
| 2 | 会议室状态 | `GET /api/itportal/meetingroom/rooms/{id}/status` | 返回当前状态 |
| 3 | 预定会议室 | `POST /api/itportal/meetingroom/bookings` | 创建预定成功 |
| 4 | 时间线 | `GET /api/itportal/meetingroom/rooms/{id}/timeline` | 返回当日时间线 |
| 5 | 终端绑定 | `POST /api/itportal/admin/terminal-bindings` | 绑定终端-会议室 |
| 6 | 终端WS | `WS /ws/terminal/{sn}` | 终端状态实时推送 |
| 7 | 终端前端 | 浏览器打开 `/itterminal/` | 深色主题大屏页面 |
| 8 | H5入口 | H5 端点击会议室入口 | 跳转 MeetingroomView |
| 9 | 扫码登录 | 终端扫码 | 企微扫码登录成功 |
| 10 | 状态同步 | 终端修改状态 → H5 实时更新 | WS 推送正常 |
| 1 | 会议室列表 | `GET /itportal/meetingroom/list` | 返回会议室列表 |
| 2 | 会议室状态 | `GET /itportal/meetingroom/{id}/status` | 返回当前状态 |
| 3 | 预定会议室 | `POST /itportal/meetingroom/book` | 创建预定成功 |
| 4 | 终端绑定 | `GET /itportal/meetingroom/terminal/{sn}/binding` | 返回绑定信息 |
| 5 | 终端WS | `WS /ws/terminal/{sn}` | 终端状态实时推送 |
| 6 | **设备报修** | `POST /itportal/meetingroom/repair` | 创建工单+通知管理员 |
| 7 | **指南列表** | `GET /itportal/meetingroom/guides` | 返回指南列表(5条种子) |
| 8 | **指南按类型** | `GET /itportal/meetingroom/guides/projector` | 返回投影仪指南 |
| 9 | **终端二维码** | `GET /itportal/meetingroom/terminal/{sn}/qrcode` | 返回PNG图片 |
| 10 | 终端前端 | 浏览器打开 `/itterminal/{sn}/` | 深色主题大屏页面 |
| 11 | **报修页面** | 终端点击"设备报修"按钮 | 显示报修表单 |
| 12 | **指南页面** | 终端点击"操作指南"按钮 | 显示指南列表+二维码 |
| 13 | **移动端适配** | 手机访问 `/itterminal/{sn}/` | 垂直布局自适应 |
| 14 | 扫码登录 | 终端扫码 | 企微扫码登录成功 |
| 15 | 状态同步 | 终端修改状态 → H5 实时更新 | WS 推送正常 |
### 3.2 Redis 缓存验证
### 4.2 Redis 缓存验证
```bash
# 会议室 token
@@ -145,33 +242,37 @@ redis-cli -a $REDIS_PASSWORD get wecom:meetingroom_access_token
# 会议室列表缓存
redis-cli -a $REDIS_PASSWORD get meetingroom:room_list
# 预定缓存
redis-cli -a $REDIS_PASSWORD get "meetingroom:booking:{room_id}:{date}"
# 状态缓存
redis-cli -a $REDIS_PASSWORD get "meetingroom:status:{room_id}"
```
---
## 、回滚方案
## 、回滚方案
### 4.1 数据库回滚
### 5.1 数据库回滚
```bash
# 回退迁移 050
# 回退迁移 051(报修+指南表)
docker compose exec backend alembic downgrade -1
# 完全回退(含 050
docker compose exec backend alembic downgrade -2
```
### 4.2 后端回滚
### 5.2 后端回滚
```bash
# 恢复 .py 文件(bind mount 自动生效)
git checkout HEAD~1 -- backend/app/api/meetingroom.py
git checkout HEAD~1 -- backend/app/services/repair_service.py
git checkout HEAD~1 -- backend/app/services/meetingroom_service.py
# ... 其他文件
git checkout HEAD~1 -- backend/app/models/meetingroom_guide.py
git checkout HEAD~1 -- backend/app/models/meetingroom_repair.py
git checkout HEAD~1 -- backend/app/schemas/meetingroom.py
git checkout HEAD~1 -- backend/app/config.py
docker compose restart backend
```
### 4.3 前端回滚
### 5.3 前端回滚
```bash
# 恢复旧 dist
mv /opt/wecom-it-desk/frontend-terminal/dist /opt/wecom-it-desk/frontend-terminal/dist.bak
@@ -179,24 +280,52 @@ mv /opt/wecom-it-desk/frontend-terminal/dist /opt/wecom-it-desk/frontend-termina
docker compose restart nginx
```
### 4.4 Nginx 配置回滚
### 5.4 Nginx 配置回滚
```bash
# 移除 /itterminal/ location 块
# 移除 /itterminal/ 和 /itportal/meetingroom/ location 块
docker compose restart nginx
```
---
## 五、已知限制
## 六、新增功能说明
1. **企微API时间限制**: 会议室预定查询范围限制 31 天
2. **终端身份**: 当前按访客可查看设计,管理操作需扫码登录
3. **WS重连**: 终端断线后自动重连(指数退避),超过 5 次降级为轮询(10s间隔)
4. **缓存TTL**: 会议室列表 600s / 预定 30s / 状态 10s,手动刷新可跳过缓存
### 6.1 设备报修流程
```
终端用户点击"设备报修"
→ 选择设备类型(投影仪/视频会议/空调/桌椅/网络/其他)
→ 填写故障描述
→ 提交报修
→ 后端创建:
1. MeetingroomRepair 记录
2. ConversationIT工单会话,状态=排队,紧急度=3)
3. Message(系统消息,含故障描述)
4. 企微消息通知管理员
5. WS广播给在线坐席
→ 终端显示"报修已提交"
→ 3秒后自动返回状态页
```
### 6.2 操作指南双模式
- **终端展示**:指南的 `brief` 字段在终端大屏上直接显示简要操作步骤
- **二维码详情**:指南的 `detail_url` 生成二维码,用户手机扫码查看完整文档
- 管理员可在数据库中添加/修改指南内容
### 6.3 NE2005 降级流程
```
NE2005 终端(不支持H5应用)
→ 管理员生成二维码贴纸(API: /itportal/meetingroom/terminal/{sn}/qrcode
→ 用户手机扫码
→ 手机浏览器打开终端页面(移动端自适应)
→ 功能与终端大屏一致(状态/预定/报修/指南)
```
---
## 、配置参数速查
## 、配置参数速查
| 参数 | 默认值 | 环境变量 |
|------|--------|---------|
@@ -206,3 +335,16 @@ docker compose restart nginx
| 状态缓存 | 10s | `MEETINGROOM_CACHE_TTL_STATUS` |
| WS重连次数 | 5 | - |
| WS降级轮询间隔 | 10s | - |
| **终端页面URL** | `https://itsupport.servyou.com.cn/itterminal/` | `TERMINAL_BASE_URL` |
| **二维码尺寸** | 300px | API 参数 `size` |
| **二维码缓存** | 1小时 | HTTP `Cache-Control` |
---
## 八、已知限制
1. **企微API时间限制**: 会议室预定查询范围限制 31 天
2. **终端身份**: 管理操作需扫码登录,报修支持匿名提交
3. **WS重连**: 终端断线后自动重连(指数退避),超过 5 次降级为轮询(10s间隔)
4. **NE2005**: 不支持 H5 应用,仅能通过二维码扫码方式使用
5. **指南管理**: 当前通过数据库直接管理,后续可增加管理后台 UI
@@ -1,12 +1,12 @@
# 企微IT智能服务台 — 项目管理主文档
> **版本**: v2.4 | **日期**: 2026-07-10 | **维护人**: 助理
> **版本**: v2.5 | **日期**: 2026-07-13 | **维护人**: Duckula
---
## 一、项目状态总览
> **一句话总览**:v0.7.1 已上线运行,生产稳定。知识库迭代 v0.7.2 全链路交付完成
> **一句话总览**:v0.7.1 已上线运行,生产稳定。AI 对话链路全栈改造 Phase 1-6 全部完成并部署。H5 v4 人工坐席交互改造已部署
### 已完成 (v0.7.1)
- ✅ 企微入口 SSO
@@ -20,6 +20,13 @@
- ✅ 排查流程优化
- ✅ 知识库迭代
### v0.7.3 AI 对话链路全栈改造 + 上下文感知诊断 + H5 v4(全部部署)
- ✅ AI 对话链路 Phase 1-6Dify JSON 输出 / 统一消息架构 / VisionService 接入 / 坐席端适配 / 诊断闭环)
- ✅ 上下文感知智能诊断→修复闭环(三层诊断 / 三段排队 / 答题插队 / 五场景关闭)
- ✅ 坐席端布局优化 v2.0QuickReplyBar / ReplyBox / 右栏模式切换 / 键盘快捷键 v2.3)
- ✅ 知识库迭代 3(分诊交互 / 拓扑预览 / 代答排除)
- ✅ H5 v4 人工坐席交互改造(文案统一 / 按钮重定位 / 删 CallAgentModal / 截图提示改版)
### 版本迭代
| 版本 | 状态 | 主要内容 | 日期 |
@@ -27,6 +34,7 @@
| v0.7.0 | ✅ 已上线 | 企微SSO、MFA、RBAC | 2026-06 |
| v0.7.1 | ✅ 已上线 | 敏感词检测、token修复、扫码登录优化 | 2026-07-04 |
| v0.7.2 | ✅ 已完成 | backlog候选(AI辅助、排查流程,知识库迭代) | 2026-07+ |
| v0.7.3 | ✅ 已部署 | AI对话链路Phase1-6、上下文感知诊断、坐席布局v2.0、H5 v4人工坐席改造 | 2026-07-12~13 |
---
@@ -75,6 +83,11 @@
| # | 任务 | 说明 | 完成日期 |
|---|---|---|---|
| #116 | H5 v4 人工坐席交互改造 | ✨ 三态文案统一"人工坐席"/按钮位置上移/删除CallAgentModal弹窗/截图提示改版/移动端CSS隐藏/后端DB同步 | 2026-07-13 |
| #59-69 | AI 对话链路全栈改造 Phase 1-6 | ✨ Dify JSON输出/统一消息架构/VisionService接入/坐席端适配/诊断闭环协调/性能监控 | 2026-07-12 |
| #115+ | 上下文感知智能诊断→修复闭环 | ✨ 三层诊断(API→Script→AI)/三段排队/答题插队/五场景关闭/迁移052(6表+6列) | 2026-07-12 |
| #113+ | 坐席端布局优化 v2.0 | ✨ QuickReplyBar L1+L2悬浮/ReplyBox左右分区/右栏260↔560px/键盘快捷键v2.3 | 2026-07-12 |
| #113+ | 知识库迭代 3 | ✨ 分诊交互/拓扑预览(ECharts)/代答排除(4种匹配器)/44文件43测试/迁移051 | 2026-07-12 |
| #111 | 坐席端消息头像不显示Bug | 🐛 后端消息接口未返回sender_avatar → Schema添加字段 + API填充员工/AI头像 + 前端显示 | 2026-07-10 |
| #112 | 坐席端消息布局调整 | ✨ 头像和名字位置互换(头像在前、名字在后) | 2026-07-10 |
| #113 | 审批类型扩展与卡片URL直跳 | ✨ 审批类型 5→12种/18流程,后端静态模板+关键词扩展,前端卡片12类17选项URL直跳,Dify v2 System Prompt覆盖全部12类 | 2026-07-10 |
@@ -135,6 +148,16 @@
## 五、最近搞定
### 2026-07-13
- ✅ H5 v4 人工坐席交互改造 + 部署(#116)— 文案统一/按钮重定位/删CallAgentModal/截图提示改版/DB同步
- ✅ 服务器部署路径修正 — 确认 `/opt/wecom-it-desk/` 项目根路径,所有前端 dist 为 ro bind mount
### 2026-07-12
- ✅ AI 对话链路全栈改造 Phase 1-6 全部完成并部署(#59-#69
- ✅ 上下文感知智能诊断→修复闭环部署(三层诊断/三段排队/答题插队/五场景关闭)
- ✅ 坐席端布局优化 v2.0 部署(QuickReplyBar/ReplyBox/右栏模式切换/键盘快捷键v2.3)
- ✅ 知识库迭代 3 部署(分诊交互/拓扑预览/代答排除)
### 2026-07-10
- ✅ 审批类型扩展 5→12种/18流程 + 卡片URL直跳 + Dify v2 发布(#113
- ✅ 审批卡片同窗口导航改造(#114
@@ -252,6 +275,7 @@ curl http://localhost:8000/api/dev/health
| 版本 | 日期 | 变更 |
|------|------|------|
| v2.5 | 2026-07-13 | 新增 v0.7.3 版本(AI对话链路Phase1-6/上下文感知诊断/坐席布局v2.0/知识库迭代3/H5 v4),新增 #116#59-69 完成任务,更新最近搞定 |
| v2.4 | 2026-07-10 | 新增 #113-115 审批流程系统任务(类型扩展+卡片导航+免登录研究),P2功能表新增审批流程系统 |
| v2.3 | 2026-07-10 | 新增 #111-112 头像显示与布局调整任务 |
| v2.2 | 2026-07-10 | 新增 #108-110 Bug修复任务(消息重复、自动滚动、WS认证) |
@@ -0,0 +1,157 @@
# IT智能服务台 - 线性执行计划
> **制定日期**: 2026-07-11 23:20
> **版本快照**: `v2026.07.11-evening` (git tag已推送)
> **今晚自动执行**: Phase 1-3 (确认后启动)
---
## 执行原则
1. **线性无冲突**: 每步按序执行,前一步完成才进入下一步
2. **低风险优先**: 后端小修复 → 全栈部署 → 文档生成 → 前端开发
3. **可回滚**: 每步均有回滚方案,git tag `v2026.07.11-evening` 为回滚锚点
---
## Phase 1: 后端快速修复 (今晚自动, ~15min) ✅ 已完成
### 1.1 IT资产升级审批推送 部署+测试 ✅
- **文件**: `backend/app/services/asset_service.py` / `backend/app/api/approval.py` / `backend/app/config.py`
- **操作**: 上传3个.py → `docker compose restart backend` → 运行 `test_asset_approval_urge.py`
- **验证**: curl 测试 urge 端点返回200
- **回滚**: `git checkout v2026.07.11-evening -- backend/app/services/asset_service.py`
### 1.2 itsm_service.py httpx.Timeout 修复 ✅
- **文件**: `backend/app/services/itsm_service.py:33`
- **变更**: 代码已有正确写法 `httpx.Timeout(timeout=30.0, connect=10.0, read=30.0)`,无需变更
- **操作**: 确认代码正确
### 1.3 企微审批查询范围 7天→30天 ✅
- **文件**: `backend/app/services/todo_source_service.py`
- **变更**: `APPROVAL_QUERY_DAYS = 30` 已部署
- **操作**: 上传1个.py → `docker compose restart backend`
- **验证**: API返回更多历史审批单
---
## Phase 2: 会议室预定系统部署 (今晚自动, ~45min) ✅ 已完成
### 2.1 数据库迁移 ✅
- **操作**: `create_meetingroom_tables.py` 直接 SQL 建表(alembic 不可用)
- **新表**: `meetingroom_booking_snapshot` / `terminal_room_binding`
- **验证**: 查询新表存在
### 2.2 后端环境变量+重启 ✅
- **操作**: 企微会议室 Secret 待用户申请(暂不配置)
- **验证**: `curl /itportal/meetingroom/list` 返回企微API响应(forbidden,因Secret未配置)
### 2.3 终端前端部署 ✅
- **操作**: Vite base 修正 `/terminal/``/itterminal/``npm run build` → pack-upload → docker volume → nginx
- **Nginx**:
- 添加 `location /itterminal/` 静态文件服务
- 添加 `location /itportal/meetingroom/` API 代理
- `docker compose up -d nginx` 重建容器
- **验证**: `https://localhost/itterminal/` → 200 ✅;JS/CSS → 200 ✅
### 2.4 端到端验证 ✅(部分)
- ✅ 终端前端页面加载正常
- ✅ JS/CSS 资源加载正常
- ✅ 会议室列表 API 端点可达(返回企微 forbidden,因 Secret 未配置)
- ⏳ 企微会议室 Secret 配置后可完成完整功能验证
- **回滚**: 移除 nginx location + docker volume + git checkout
---
## Phase 3: 技术方案文档生成 (今晚自动, ~30min) ✅ 已完成
### 3.1 AI辅助消息框 技术方案 ✅
- **输入**: `docs/02-产品需求/坐席端AI辅助消息框-PRD.md` (已确认4项功能)
- **输出**: `docs/03-技术架构/增量设计-AI辅助消息框-20260711.md` (901行,含9章节+附录)
- **内容**: 系统设计(挑战/选型/架构图) + 文件列表(7新+3改) + 数据接口 + 时序图 + 4个任务(T01-T04) + 依赖图 + Dify Prompt汇总
- **关键路径**: T01→T02→T03→T04 (~7天)
### 3.2 布局优化v2.0 技术方案 ✅
- **输入**: `docs/02-产品需求/坐席端布局优化建议.md` (v2.0已确认)
- **输出**: 3个文件
- `docs/03-技术架构/增量设计-布局优化v2-20260711.md` (43.5KB主文档,8章节)
- `docs/03-技术架构/增量设计-布局优化v2-类图.mermaid` (6.9KB)
- `docs/03-技术架构/增量设计-布局优化v2-时序图.mermaid` (6.1KB3个时序图)
- **内容**: 5个任务(T01-T05) + 8个待明确事项 + 清理清单 + 工具栏设计 + 回复建议区 + 右栏模式切换
---
## Phase 4: 前端开发 (人工, ~15工作日)
### 4.1 AI辅助消息框 (~8天)
- 实时自动补齐 / 语气调整 / 文字润色 / 智能改写
- 前端: ReplyBox.vue 新增AI工具栏
- 后端: wingman.py 新增4个端点
### 4.2 布局优化v2.0 (~7天)
- Phase1: 清理+骨架 (1.5天)
- Phase2: 工具栏 (0.5天)
- Phase3: 回复建议区 (2天)
- Phase4: 右栏训练区+开关 (2天)
- Phase5: 联调 (1天)
---
## Phase 5: ITSM集成 (外部依赖)
### 5.1 ITSM API抓包
- 用 agent-browser 登录 ITSM 代办页面抓 XHR
- 需用户提供 ITSM 登录凭据
### 5.2 ITSM app_id/app_secret申请
- 向 ITSM 平台方申请
- 签名: app_id + app_secret + SHA1
### 5.3 ITSM代办列表集成
- 后端: itsm_service.py 补全代办列表API
- 前端: TodoPanel.vue 工单Tab接入真实数据
---
## Phase 6: 知识库迭代 (待确认)
### 6.1 功能确认
- 技术方案: `docs/03-技术架构/增量设计-知识库迭代与痛点缓解-20260711.md`
- 原型图: `docs/01-产品设计/知识库迭代-未实现功能原型设计-20260711.md`
- 待用户确认后进入开发
### 6.2 开发+联调
- RAGFlow对接 / Neo4j图谱 / 前端联调
---
## 今晚自动执行清单
| 步骤 | 操作 | 预计耗时 | 风险 | 状态 |
|------|------|---------|------|------|
| 1.1 | IT资产审批推送 上传+重启+测试 | 5min | 低 | ✅ 完成 |
| 1.2 | itsm_service httpx.Timeout 修复 | 2min | 低 | ✅ 完成 |
| 1.3 | 企微审批范围 7d→30d | 2min | 低 | ✅ 完成 |
| 2.1 | 会议室 DB迁移 | 5min | 中 | ✅ 完成 |
| 2.2 | 会议室 后端env+重启 | 5min | 中 | ✅ 完成(Secret待申请) |
| 2.3 | 会议室 终端前端部署 | 15min | 中 | ✅ 完成 |
| 2.4 | 会议室 端到端验证 | 10min | - | ✅ 部分完成 |
| 3.1 | AI辅助消息框 技术方案 | 15min | 无 | ✅ 完成 |
| 3.2 | 布局优化v2.0 技术方案 | 15min | 无 | ✅ 完成 |
| **合计** | | **~75min** | | **Phase 1-3 全部完成** |
---
## 回滚方案
```bash
# Git 回滚到今晚版本快照
git checkout v2026.07.11-evening
# 数据库回滚(会议室新表)
docker compose exec backend alembic downgrade -1
# 前端回滚
# 终端: 移除 /itterminal/ nginx location
# 坐席/H5: 恢复上一版 dist
```