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,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