feat: 2026-07-11 全量更新 - 代办集成+会议室预定+知识迭代修复+UI统一+Bug修复
== 已部署上线 (9项) == - 代办事项真实数据源集成 (企微审批API 8bug修复链) - H5/坐席端 Logo样式统一+绿色背景 - 视频引导页修复 (localStorage key v2) - 坐席端 v9 Vue版本修复 (ElMessage._context) - 截图按钮 v10 修复 (getDisplayMedia user gesture) - 扫码样式恢复+H5扫码登录跳转修复 - H5截图快捷键提示 == 代码完成待部署 (3项) == - 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查) - 会议室预定-小鱼易联终端 (40文件, 40/40测试通过) - IT资产升级审批推送 (asset_service.py) == 需求文档 (2项) == - 坐席端AI辅助消息框-PRD (4项新功能确认) - 坐席端布局优化建议 v2.0 (7天计划) == 新增文档 == - 日报-2026-07-11.md - 知识迭代Bug修复报告-20260711.md - 会议室预定-部署指南.md - CHANGELOG.md 更新 == 测试 == - test_todo_integration.py: 40/40 - test_meetingroom.py: 40/40 - test_bugfix_ki_suggestions.py: 21/21
This commit is contained in:
@@ -1,106 +1,203 @@
|
||||
%% 坐席端截图+拍照功能 — 时序图
|
||||
%% 文件: docs/03-技术架构/sequence-diagram.mermaid
|
||||
%% 包含 4 个关键流程:截图、拍照、图文发送、初始化与资源清理
|
||||
|
||||
%% ============================================
|
||||
%% 流程 1:截图完整流程
|
||||
%% ============================================
|
||||
sequenceDiagram
|
||||
actor 员工 as 👤 员工
|
||||
actor 坐席 as 👤 坐席
|
||||
actor 训练师 as 👤 AI训练师
|
||||
participant H5 as H5前端
|
||||
participant AgentFE as 坐席控制台
|
||||
participant API as FastAPI
|
||||
participant KIS as KnowledgeIterationService
|
||||
participant WS as WingmanService
|
||||
participant Dify as Dify AI Platform
|
||||
participant DB as PostgreSQL
|
||||
participant NEO as Neo4jClient
|
||||
participant N4J as Neo4j 图数据库
|
||||
participant User as 用户
|
||||
participant RB as ReplyBox.vue
|
||||
participant USC as useScreenCapture
|
||||
participant SC as ScreenCapture.vue
|
||||
participant UPI as usePendingImages
|
||||
participant PIP as PendingImagePreview.vue
|
||||
|
||||
Note over 员工,N4J: === 通道 A: 会话→Dify→建议 ===
|
||||
User->>RB: 点击截图按钮
|
||||
RB->>RB: handleScreenshot()
|
||||
RB->>USC: captureScreenAsCanvas()
|
||||
|
||||
Note over USC: 浏览器弹出 getDisplayMedia 选择器
|
||||
User->>USC: 选择屏幕/窗口/标签页
|
||||
|
||||
USC->>USC: getDisplayMedia() 获取视频流
|
||||
USC->>USC: video.play() → canvas.drawImage() 捕获一帧
|
||||
USC->>USC: stream.getTracks().stop() 停止共享
|
||||
USC-->>RB: 返回 HTMLCanvasElement
|
||||
|
||||
RB->>RB: screenshotCanvas = canvas
|
||||
RB->>RB: showScreenCapture = true
|
||||
|
||||
Note over SC: 组件挂载,显示全屏遮罩
|
||||
SC->>SC: 初始化:底图 Canvas + 遮罩 Canvas
|
||||
SC->>SC: drawMask() 绘制半透明遮罩
|
||||
|
||||
User->>SC: 鼠标拖拽框选区域
|
||||
SC->>SC: 更新 box {x, y, w, h}
|
||||
SC->>SC: drawMask() 清除选区内遮罩
|
||||
|
||||
User->>SC: 选择马赛克工具
|
||||
User->>SC: 在选区内拖拽涂抹
|
||||
SC->>SC: saveHistory() 保存当前状态
|
||||
SC->>SC: drawMosaic() 像素化处理
|
||||
|
||||
User->>SC: 选择箭头工具
|
||||
User->>SC: 拖拽绘制箭头
|
||||
SC->>SC: saveHistory() 保存当前状态
|
||||
SC->>SC: drawArrow() 绘制箭头线段
|
||||
|
||||
User->>SC: 点击撤销
|
||||
SC->>SC: historyIndex--
|
||||
SC->>SC: ctx.putImageData() 恢复上一状态
|
||||
|
||||
User->>SC: 点击确认 ✓
|
||||
SC->>SC: 从编辑 Canvas 裁剪选区
|
||||
SC->>SC: canvas.toBlob() 转 Blob
|
||||
SC-->>RB: emit('confirm', blob)
|
||||
|
||||
RB->>RB: showScreenCapture = false
|
||||
RB->>UPI: addImage(blob, 'screenshot')
|
||||
|
||||
UPI->>UPI: URL.createObjectURL(blob) 生成预览URL
|
||||
UPI->>UPI: images.push({ id, blob, previewUrl, source })
|
||||
UPI-->>RB: 队列已更新
|
||||
|
||||
RB->>PIP: 响应式传递 images
|
||||
PIP->>PIP: 渲染缩略图 + × 删除按钮
|
||||
PIP-->>User: 显示待发送图片预览
|
||||
|
||||
员工->>H5: 发送 IT 问题(如"VPN 连不上")
|
||||
H5->>API: POST /api/h5/conversations/current/messages
|
||||
API->>Dify: 调用 Dify Agent(员工端 AI)
|
||||
Dify-->>API: AI 回复 + confidence: 0.62
|
||||
API-->>H5: 返回消息(含 confidence)
|
||||
%% ============================================
|
||||
%% 流程 2:拍照完整流程
|
||||
%% ============================================
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant RB as ReplyBox.vue
|
||||
participant CC as CameraCapture.vue
|
||||
participant UC as useCamera
|
||||
participant UPI as usePendingImages
|
||||
|
||||
alt confidence < 0.7(门控触发)
|
||||
H5->>H5: 渲染「转人工」卡片 + 已收集上下文
|
||||
员工->>H5: 点击「转人工」
|
||||
H5->>API: POST 转人工请求(附上下文快照)
|
||||
User->>RB: 点击拍照按钮
|
||||
RB->>RB: handleCamera()
|
||||
RB->>RB: showCameraDialog = true
|
||||
RB->>CC: 挂载组件(v-model=true)
|
||||
|
||||
CC->>CC: ElMessage.info('即将请求摄像头权限...')
|
||||
CC->>UC: startCamera()
|
||||
|
||||
UC->>UC: getUserMedia({ video: { facingMode: 'user' } })
|
||||
Note over UC: 浏览器弹出权限请求
|
||||
User->>UC: 允许摄像头访问
|
||||
|
||||
UC->>UC: stream 赋值到 video.srcObject
|
||||
UC->>UC: video.play()
|
||||
UC-->>CC: 摄像头预览已启动
|
||||
CC-->>User: 显示实时摄像头预览
|
||||
|
||||
User->>CC: 点击拍照按钮
|
||||
CC->>UC: captureFrame()
|
||||
UC->>UC: canvas.drawImage(video, 0, 0)
|
||||
UC->>UC: canvas.toDataURL('image/png')
|
||||
UC-->>CC: 返回图片 dataURL
|
||||
CC->>CC: capturedImage = dataURL
|
||||
CC->>CC: 隐藏 video,显示 img 静态画面
|
||||
CC-->>User: 显示拍照结果 + 重拍/确认按钮
|
||||
|
||||
alt 用户点击重拍
|
||||
User->>CC: 点击重拍
|
||||
CC->>CC: capturedImage = null
|
||||
CC->>CC: 恢复 video 实时预览
|
||||
CC-->>User: 回到实时预览状态
|
||||
else 用户点击确认
|
||||
User->>CC: 点击确认 ✓
|
||||
CC->>UC: captureFrame()
|
||||
CC->>CC: dataURL → Blob 转换
|
||||
CC->>UC: stopCamera() 停止摄像头
|
||||
CC-->>RB: emit('confirm', blob)
|
||||
CC-->>RB: emit('update:modelValue', false)
|
||||
end
|
||||
|
||||
RB->>RB: showCameraDialog = false
|
||||
RB->>UPI: addImage(blob, 'camera')
|
||||
UPI->>UPI: 生成 previewUrl,加入队列
|
||||
UPI-->>RB: 队列已更新
|
||||
RB-->>User: 输入区显示拍照缩略图
|
||||
|
||||
Note over API,N4J: === 会话结束后触发分析 ===
|
||||
%% ============================================
|
||||
%% 流程 3:图文组合发送流程
|
||||
%% ============================================
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant RB as ReplyBox.vue
|
||||
participant UPI as usePendingImages
|
||||
participant API_U as uploadFile()
|
||||
participant API_M as sendMessage()
|
||||
participant Store as conversationStore
|
||||
|
||||
API->>KIS: analyze_and_generate_suggestions(db, days=7)
|
||||
KIS->>DB: 查询过去N天转人工/标注为无用 的会话
|
||||
DB-->>KIS: 返回候选数据
|
||||
|
||||
loop 每个候选会话
|
||||
KIS->>WS: generate_knowledge_suggestion(context_messages)
|
||||
WS->>WS: _build_context_messages(messages, KN_SUGGEST_PROMPT)
|
||||
WS->>Dify: _call_wingman_api(context_messages)
|
||||
Dify-->>WS: 结构化 JSON(title/content/category/tags/confidence/issue/action/relation)
|
||||
WS->>WS: _parse_json_response(content, default)
|
||||
WS-->>KIS: dict {title, content, category, confidence, issue, action, ...}
|
||||
|
||||
KIS->>KIS: _auto_tag_audience(source_type, source_data)
|
||||
Note over KIS: source_session_type=="employee" → employee_quick_reply<br/>source_session_type=="engineer" → engineer_workguide
|
||||
|
||||
KIS->>DB: INSERT KnowledgeSuggestion(status=pending, 含图字段)
|
||||
User->>RB: 输入文字 + 有待发送图片
|
||||
User->>RB: 点击发送 / 按 Enter
|
||||
|
||||
RB->>RB: handleSend()
|
||||
RB->>RB: content = inputText.trim()
|
||||
RB->>RB: hasImages = pendingImages.count > 0
|
||||
RB->>RB: hasText = content.length > 0
|
||||
|
||||
alt 无图片且无文字
|
||||
RB->>RB: return(不发送)
|
||||
else 有图片
|
||||
RB->>RB: ElMessage.info('发送中...')
|
||||
RB->>UPI: 获取 images 数组
|
||||
|
||||
loop 遍历每张图片
|
||||
RB->>API_U: uploadFile(image.blob)
|
||||
API_U-->>RB: { url, filename, file_size }
|
||||
|
||||
RB->>API_M: sendMessage(convId, '[图片]', 'image', { media_url, file_name, file_size })
|
||||
API_M-->>RB: 返回新消息 Message
|
||||
RB->>Store: messages.push(newMsg)
|
||||
end
|
||||
|
||||
RB->>UPI: clearAll() 清空图片队列
|
||||
RB->>RB: URL.revokeObjectURL() 释放预览URL
|
||||
end
|
||||
|
||||
KIS->>API: 返回分析结果统计
|
||||
|
||||
Note over 坐席,N4J: === D7 内联审批 ===
|
||||
|
||||
坐席->>AgentFE: 浏览会话中的提案卡片
|
||||
AgentFE->>API: GET /api/admin/knowledge-iteration/suggestions?status=pending
|
||||
API-->>AgentFE: 提案列表(含拓扑预览、confidence、audience)
|
||||
|
||||
alt 坐席选择「内联审批」
|
||||
坐席->>AgentFE: 在会话内联卡片点击「采纳」
|
||||
AgentFE->>API: POST /api/admin/knowledge-iteration/suggestions/{id}/approve
|
||||
Note over API: 鉴权: require_admin / require_trainer
|
||||
|
||||
API->>KIS: approve_suggestion(db, suggestion_id, reviewer_id)
|
||||
KIS->>DB: UPDATE status=approved, reviewed_at=now()
|
||||
KIS->>DB: INSERT KnowledgeBase (含 graph_sync_status=pending)
|
||||
KIS->>DB: UPDATE suggestion status=applied
|
||||
|
||||
Note over KIS,N4J: D1 解读2·直接写图
|
||||
|
||||
KIS->>NEO: merge_issue(issue_name, category, props)
|
||||
NEO->>N4J: MERGE (i:Issue {name: $name}) ON CREATE SET i+=$props
|
||||
N4J-->>NEO: IssueNode(uuid=...)
|
||||
|
||||
KIS->>NEO: merge_action(action_name, props)
|
||||
NEO->>N4J: MERGE (a:Action {name: $name}) ON CREATE SET a+=$props
|
||||
N4J-->>NEO: ActionNode(uuid=...)
|
||||
|
||||
KIS->>NEO: create_relation(issue_uuid, action_uuid, rel)
|
||||
NEO->>N4J: MATCH (i),(a) WHERE i.uuid=$i AND a.uuid=$a CREATE (i)-[:LEADS_TO {order:$o,weight:$w}]->(a)
|
||||
|
||||
KIS->>DB: UPDATE suggestion graph_sync_status=synced
|
||||
KIS->>DB: UPDATE KnowledgeBase graph_sync_status=synced, graph_node_uuid=...
|
||||
|
||||
API-->>AgentFE: 200 OK(含更新后提案)
|
||||
else 坐席选择「驳回」
|
||||
AgentFE->>API: POST /api/admin/knowledge-iteration/suggestions/{id}/reject {reject_reason}
|
||||
API->>KIS: reject_suggestion(db, id, reviewer_id, reason)
|
||||
KIS->>DB: UPDATE status=rejected
|
||||
API-->>AgentFE: 200 OK
|
||||
else 坐席选择「改写」
|
||||
AgentFE->>API: POST /api/admin/knowledge-iteration/suggestions/{id}/rewrite {title, content, ...}
|
||||
API->>KIS: rewrite_suggestion(db, id, reviewer_id, data)
|
||||
KIS->>DB: UPDATE title/content/category/tags/confidence...
|
||||
KIS->>DB: UPDATE status=pending(重新审批)
|
||||
API-->>AgentFE: 200 OK
|
||||
|
||||
opt 有文字
|
||||
RB->>RB: emit('send', content)
|
||||
RB->>RB: inputText = ''
|
||||
end
|
||||
|
||||
RB->>RB: textareaHeight = 60
|
||||
RB-->>User: 发送完成,输入区清空
|
||||
|
||||
Note over 训练师,N4J: === D7 独立队列(未处理提案) ===
|
||||
%% ============================================
|
||||
%% 流程 4:组件初始化与资源清理
|
||||
%% ============================================
|
||||
sequenceDiagram
|
||||
participant RB as ReplyBox
|
||||
participant SC as ScreenCapture
|
||||
participant CC as CameraCapture
|
||||
participant UPI as usePendingImages
|
||||
|
||||
训练师->>AgentFE: 打开独立队列页
|
||||
AgentFE->>API: GET /api/admin/approval-queue/queued?status=pending
|
||||
Note over API: 未处理的=SESSION_CLOSED 后仍 pending 的提案<br/>或手动标记 queued
|
||||
API-->>AgentFE: 队列列表
|
||||
|
||||
训练师->>AgentFE: 审核队列中的提案
|
||||
AgentFE->>API: POST /api/admin/approval-queue/{id}/dequeue-approve
|
||||
Note over API,N4J: 同 approve_suggestion 流程→写图
|
||||
Note over RB: 组件挂载 onMounted
|
||||
RB->>RB: 注册全局快捷键
|
||||
|
||||
Note over RB: 用户触发截图
|
||||
RB->>SC: v-if="showScreenCapture" 挂载
|
||||
SC->>SC: onMounted → 初始化 Canvas、事件监听
|
||||
SC->>SC: drawMask() 绘制初始遮罩
|
||||
|
||||
Note over RB: 截图取消/确认
|
||||
RB->>SC: v-if=false 卸载
|
||||
SC->>SC: onUnmounted → 移除事件监听、清空 history
|
||||
|
||||
Note over RB: 用户触发拍照
|
||||
RB->>CC: v-model=true 挂载
|
||||
CC->>CC: onMounted → startCamera()
|
||||
|
||||
Note over RB: 拍照取消/确认
|
||||
RB->>CC: v-model=false 卸载
|
||||
CC->>CC: onUnmounted → stopCamera() 停止流
|
||||
|
||||
Note over RB: ReplyBox 卸载
|
||||
RB->>RB: onUnmounted
|
||||
RB->>UPI: clearAll() → revokeObjectURL 释放内存
|
||||
RB->>RB: 移除快捷键监听
|
||||
|
||||
Reference in New Issue
Block a user