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:
Simon
2026-07-11 23:13:10 +08:00
parent 3d152fc8eb
commit bea288e414
928 changed files with 85169 additions and 54205 deletions
+192 -95
View File
@@ -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: 结构化 JSONtitle/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 KnowledgeSuggestionstatus=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: 移除快捷键监听