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:
@@ -43,10 +43,11 @@ import { mockConversationListData, mockMessageListData } from '@/mock/data'
|
||||
// 排序规则(来自PRD):紧急→招手→需介入→活跃→AI处理中→已结单
|
||||
// 权重越小排越前面
|
||||
const STATUS_WEIGHT: Record<string, number> = {
|
||||
queued: 1, // 排队等待(招手等待区)— 最优先
|
||||
serving: 2, // 人工处理中
|
||||
ai_handling: 3, // AI处理中
|
||||
resolved: 4, // 已结单 — 最后
|
||||
queued: 1, // 排队等待(招手等待区)— 最优先
|
||||
serving: 2, // 人工处理中
|
||||
pending_close: 3, // 待员工确认结单 — 仍在处理但等待关闭
|
||||
ai_handling: 4, // AI处理中
|
||||
resolved: 5, // 已结单 — 最后
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,6 +129,11 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
/** 工作区视图模式:'chat' 为对话模式,'task' 为任务模式 */
|
||||
const workspaceView = ref<'chat' | 'task'>('chat')
|
||||
|
||||
/** 右栏面板模式:normal(260px) | expanded(560px),持久化到 localStorage */
|
||||
const panelMode = ref<'normal' | 'expanded'>(
|
||||
(localStorage.getItem('panelMode') as 'normal' | 'expanded') || 'normal'
|
||||
)
|
||||
|
||||
// ==========================================================================
|
||||
// 输入指示器(Typing Indicator)相关状态
|
||||
// ==========================================================================
|
||||
@@ -139,6 +145,14 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
*/
|
||||
const typingUsers = ref<Map<string, { sender_id: string; sender_name: string; timestamp: number }>>(new Map())
|
||||
|
||||
/**
|
||||
* AI 正在思考的会话集合:conversationId → { timestamp, status }
|
||||
* 做什么:记录 AI 正在生成回复的会话,在聊天区域显示"AI 正在思考..."提示
|
||||
* 为什么:坐席需要知道 AI 正在处理员工消息,避免误以为消息被忽略
|
||||
* 自动过期:30 秒内没有更新则自动清除(与后端 30 秒超时一致)
|
||||
*/
|
||||
const aiThinkingConversations = ref<Map<string, { timestamp: number; status?: string }>>(new Map())
|
||||
|
||||
// ==========================================================================
|
||||
// 消息去重相关状态(WS-06 修复)
|
||||
// ==========================================================================
|
||||
@@ -264,8 +278,9 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
})
|
||||
|
||||
/**
|
||||
* 📌 我的会话 — 合并:待接单 + 我的会话 + 协作会话
|
||||
* 📌 我的会话 — 合并:待接单 + 我的会话 + 协作会话 + 待确认结单
|
||||
* 对应左栏三段折叠的第一段(默认展开)
|
||||
* pending_close 会话仍在我的列表中,坐席可查看员工确认状态
|
||||
*/
|
||||
const myConversations = computed(() => {
|
||||
return sortedConversations.value.filter(c => {
|
||||
@@ -273,6 +288,8 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
if (c.status === 'queued') return true
|
||||
// 我的会话(状态 serving 且 is_mine)
|
||||
if (c.status === 'serving' && c.is_mine) return true
|
||||
// 我的待确认结单(状态 pending_close 且 is_mine)
|
||||
if (c.status === 'pending_close' && c.is_mine) return true
|
||||
// 协作会话
|
||||
if (c.is_collaborator && c.status === 'serving') return true
|
||||
return false
|
||||
@@ -424,22 +441,25 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 结单
|
||||
* 将会话状态改为 resolved
|
||||
* 坐席发起结单(触发员工确认流程)
|
||||
*
|
||||
* 改造说明(决策 G1):
|
||||
* - 原逻辑:直接将会话改为 resolved,清空当前选中
|
||||
* - 新逻辑:状态改为 pending_close,保持当前选中(坐席可继续查看等待状态)
|
||||
* - 员工确认后通过 WS 事件 pending_close_confirmed → resolved
|
||||
* - 员工拒绝后通过 WS 事件 pending_close_rejected → 恢复 serving
|
||||
*
|
||||
* @param conversationId - 会话ID
|
||||
* @param resolveSummary - 结单摘要(问题类型+根因+解决方式)
|
||||
*/
|
||||
async function resolveConv(conversationId: string): Promise<void> {
|
||||
async function resolveConv(conversationId: string, resolveSummary: string): Promise<void> {
|
||||
try {
|
||||
await resolveConversation(conversationId)
|
||||
await resolveConversation(conversationId, resolveSummary)
|
||||
await fetchConversations()
|
||||
// 如果结单的是当前会话,清空选中
|
||||
if (currentConversationId.value === conversationId) {
|
||||
currentConversationId.value = null
|
||||
messages.value = []
|
||||
}
|
||||
// 不清空当前会话 — 坐席可查看 pending_close 状态
|
||||
} catch (error) {
|
||||
console.error('结单失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -909,6 +929,8 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
sender_type: string
|
||||
sender_id: string
|
||||
content: string
|
||||
msg_type?: string
|
||||
extra_data?: Record<string, any>
|
||||
urgency_score?: number
|
||||
tags?: any
|
||||
}): void {
|
||||
@@ -925,6 +947,11 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
// 记录此消息ID为"已处理"(更新到 Set 的最新位置)
|
||||
trackProcessedMessageId(data.message_id)
|
||||
|
||||
// 如果是 AI 回复消息,清除该会话的 AI 思考状态
|
||||
if (data.sender_type === 'ai') {
|
||||
clearAiThinking(data.conversation_id)
|
||||
}
|
||||
|
||||
// 如果当前正在查看这个会话,追加消息到消息列表
|
||||
if (currentConversationId.value === data.conversation_id) {
|
||||
// 检查消息是否已存在(避免轮询和 WS 推送重复)
|
||||
@@ -937,7 +964,8 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
sender_id: data.sender_id,
|
||||
sender_name: '', // WS 推送不含姓名,后续轮询会补充
|
||||
content: data.content,
|
||||
msg_type: 'text',
|
||||
msg_type: data.msg_type || 'text', // 使用 WS 推送的 msg_type,默认 text
|
||||
extra_data: data.extra_data, // 传递 extra_data(options/action 等)
|
||||
ai_suggestion: false,
|
||||
is_read: false,
|
||||
created_at: new Date().toISOString(),
|
||||
@@ -1003,6 +1031,66 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
fetchConversations()
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 结单确认流程 WS 事件处理(决策 G1:坐席发起→员工确认)
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 处理 WebSocket 推送的"员工确认结单"事件
|
||||
*
|
||||
* 做什么:
|
||||
* 1. 刷新会话列表(会话状态 pending_close → resolved)
|
||||
* 2. 如果是当前选中的会话,清空选中(已结单无需继续操作)
|
||||
* 3. 弹出通知提示坐席
|
||||
*
|
||||
* @param data - WS 事件数据
|
||||
* @param data.conversation_id - 会话ID
|
||||
* @param data.resolved_method - 解决方式(employee_confirm 等)
|
||||
*/
|
||||
function handlePendingCloseConfirmed(data: {
|
||||
conversation_id: string
|
||||
resolved_method?: string
|
||||
}): void {
|
||||
fetchConversations()
|
||||
// 如果是当前选中的会话,清空选中
|
||||
if (currentConversationId.value === data.conversation_id) {
|
||||
currentConversationId.value = null
|
||||
messages.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 WebSocket 推送的"员工拒绝结单"事件
|
||||
*
|
||||
* 做什么:
|
||||
* 1. 刷新会话列表(会话状态 pending_close → serving 恢复)
|
||||
* 2. 弹出通知提示坐席员工拒绝了结单
|
||||
*
|
||||
* @param data - WS 事件数据
|
||||
* @param data.conversation_id - 会话ID
|
||||
* @param data.reason - 员工拒绝原因(可选)
|
||||
*/
|
||||
function handlePendingCloseRejected(_data: {
|
||||
conversation_id: string
|
||||
reason?: string
|
||||
}): void {
|
||||
fetchConversations()
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 右栏面板模式(布局优化v2)
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* 设置右栏面板模式并持久化到 localStorage
|
||||
*
|
||||
* @param mode - 'normal'(260px) | 'expanded'(560px)
|
||||
*/
|
||||
function setPanelMode(mode: 'normal' | 'expanded'): void {
|
||||
panelMode.value = mode
|
||||
localStorage.setItem('panelMode', mode)
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 输入指示器(Typing Indicator)方法
|
||||
// ==========================================================================
|
||||
@@ -1072,6 +1160,51 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
return `${info.sender_name}正在输入...`
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 AI 思考状态事件(WS: ai_thinking)
|
||||
*
|
||||
* 做什么:记录 AI 正在生成回复的会话,在聊天区域显示状态提示
|
||||
* 为什么:坐席需要知道 AI 正在处理员工消息
|
||||
*
|
||||
* @param data - { conversation_id, employee_id?, status? }
|
||||
* status="still_thinking" 表示 AI 已思考超过 15 秒
|
||||
*/
|
||||
function handleAiThinking(data: {
|
||||
conversation_id: string
|
||||
employee_id?: string
|
||||
status?: string
|
||||
}): void {
|
||||
if (!data.conversation_id) return
|
||||
aiThinkingConversations.value.set(data.conversation_id, {
|
||||
timestamp: Date.now(),
|
||||
status: data.status,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定会话的 AI 思考状态文本
|
||||
*
|
||||
* @param conversationId - 会话ID
|
||||
* @returns 状态文本,如 "AI 正在思考..." 或 "AI 仍在思考中...",无则返回空字符串
|
||||
*/
|
||||
function getAiThinkingText(conversationId: string): string {
|
||||
const info = aiThinkingConversations.value.get(conversationId)
|
||||
if (!info) return ''
|
||||
// 30 秒过期检查(与后端超时一致)
|
||||
if (Date.now() - info.timestamp > 30000) {
|
||||
aiThinkingConversations.value.delete(conversationId)
|
||||
return ''
|
||||
}
|
||||
return info.status === 'still_thinking'
|
||||
? 'AI 仍在思考中...'
|
||||
: 'AI 正在思考...'
|
||||
}
|
||||
|
||||
/** 清除指定会话的 AI 思考状态(收到 AI 回复后调用) */
|
||||
function clearAiThinking(conversationId: string): void {
|
||||
aiThinkingConversations.value.delete(conversationId)
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 返回
|
||||
// ==========================================================================
|
||||
@@ -1084,6 +1217,7 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
loadingMessages,
|
||||
pendingReplyText,
|
||||
workspaceView,
|
||||
panelMode,
|
||||
|
||||
// AI Wingman 状态
|
||||
aiDrafts,
|
||||
@@ -1146,6 +1280,9 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
startAllPolling,
|
||||
stopAllPolling,
|
||||
|
||||
// 右栏面板模式
|
||||
setPanelMode,
|
||||
|
||||
// WebSocket 事件处理
|
||||
handleNewMessage,
|
||||
handleConversationUpdated,
|
||||
@@ -1153,9 +1290,17 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
handleCollaboratorChanged,
|
||||
handleParticipantInvited,
|
||||
handleParticipantChanged,
|
||||
handlePendingCloseConfirmed,
|
||||
handlePendingCloseRejected,
|
||||
handleTypingEvent,
|
||||
getTypingText,
|
||||
|
||||
// AI 思考状态
|
||||
handleAiThinking,
|
||||
getAiThinkingText,
|
||||
clearAiThinking,
|
||||
aiThinkingConversations,
|
||||
|
||||
// 输入指示器
|
||||
typingUsers,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user