# 坐席端 AI 辅助消息框与布局优化 — 系统架构设计 > **文档版本**: v1.0 > **日期**: 2026-07-11 > **架构师**: 宋献 (Simon) > **关联 PRD**: `docs/02-产品需求/坐席端AI辅助消息框-PRD.md` > **关联布局方案**: `docs/02-产品需求/坐席端布局优化建议.md` > **技术栈**: Vue 3 + TypeScript + Element Plus + FastAPI + Dify AI --- ## 目录 - [Part A: 系统设计](#part-a-系统设计) - [1. 架构概述](#1-架构概述) - [2. 后端架构设计](#2-后端架构设计) - [3. 前端架构设计](#3-前端架构设计) - [4. 布局重构架构设计](#4-布局重构架构设计) - [5. 数据流设计](#5-数据流设计) - [6. 关键设计决策](#6-关键设计决策) - [Part B: 接口与数据结构](#part-b-接口与数据结构) - [7. API 规范](#7-api-规范) - [8. Pydantic 模型](#8-pydantic-模型) - [9. Dify Prompt 模板](#9-dify-prompt-模板) - [10. TypeScript 类型定义](#10-typescript-类型定义) - [Part C: 架构图](#part-c-架构图) - [11. 类图](#11-类图) - [12. 时序图](#12-时序图) - [Part D: 任务分解](#part-d-任务分解) - [13. 文件清单](#13-文件清单) - [14. 开发计划](#14-开发计划) - [15. 风险与缓解](#15-风险与缓解) --- ## Part A: 系统设计 ### 1. 架构概述 #### 1.1 功能范围 本设计覆盖两大模块: | 模块 | 功能 | PRD 参考 | |------|------|---------| | AI 辅助消息框 | 实时自动补齐、语气调整、文字润色、智能改写 | `坐席端AI辅助消息框-PRD.md` §2 | | 布局优化 | 回复建议区、工具栏重构、右栏训练区、放大/缩小开关 | `坐席端布局优化建议.md` §3 | #### 1.2 系统架构图 ``` ┌──────────────────────────────────────────────────────────────────┐ │ 坐席工作台 (Vue3 + Element Plus) │ │ │ │ ┌──────────┐ ┌───────────────────┐ ┌───────────────────────┐ │ │ │ 左栏 │ │ 中栏 │ │ 右栏 │ │ │ │ 会话用户 │ │ ┌──────────────┐ │ │ ┌───────────────────┐ │ │ │ │ 列表 │ │ │ UserInfoBar │ │ │ │ PanelModeToggle │ │ │ │ │ +待办 │ │ ├──────────────┤ │ │ │ (正常/放大) │ │ │ │ │ │ │ │ Troubleshoot │ │ │ ├───────────────────┤ │ │ │ │ │ │ ├──────────────┤ │ │ │ AiTrainingPanel │ │ │ │ │ │ │ │ 消息列表 │ │ │ │ ├ SmartTagEditor │ │ │ │ │ │ │ ├──────────────┤ │ │ │ ├ QualityFeedback │ │ │ │ │ │ │ │ReplySuggestArea│ │ │ │ ├ KnowledgeContrib│ │ │ │ │ │ │ │ (AI推荐+快回) │ │ │ │ └ UsageStats │ │ │ │ │ │ │ ├──────────────┤ │ │ └───────────────────┘ │ │ │ │ │ │ │ ReplyBox │ │ │ │ │ │ │ │ │ │ ┌──────────┐ │ │ │ │ │ │ │ │ │ │ │工具栏 │ │ │ │ │ │ │ │ │ │ │ │常规|AI │ │ │ │ │ │ │ │ │ │ │ └──────────┘ │ │ │ │ │ │ │ │ │ │ textarea │ │ │ │ │ │ │ │ │ └──────────────┘ │ │ │ │ │ └──────────┘ └───────────────────┘ └───────────────────────┘ │ └──────────────────────────┬───────────────────────────────────────┘ │ HTTP / WebSocket ▼ ┌──────────────────────────────────────────────────────────────────┐ │ FastAPI 后端 │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ wingman.py (路由层) │ │ │ │ POST /wingman/autocomplete ← 自动补齐 │ │ │ │ POST /wingman/tone-adjust ← 语气调整 │ │ │ │ POST /wingman/polish ← 文字润色 │ │ │ │ POST /wingman/rewrite ← 智能改写 │ │ │ │ + 现有: draft / summary / tags │ │ │ └────────────────────────┬────────────────────────────────┘ │ │ │ │ │ ┌────────────────────────▼────────────────────────────────┐ │ │ │ WingmanService (服务层) │ │ │ │ generate_completion() ← 新增: 自动补齐 │ │ │ │ adjust_tone() ← 新增: 语气调整 │ │ │ │ polish_text() ← 新增: 文字润色 │ │ │ │ rewrite_versions() ← 新增: 智能改写 │ │ │ │ + 现有: generate_draft / generate_summary / suggest_tags│ │ │ │ │ │ │ │ _call_wingman_api() ← 改造: 支持 temperature 参数 │ │ │ │ _build_context_messages() ← 复用: 角色映射 │ │ │ └────────────────────────┬────────────────────────────────┘ │ │ │ httpx (OpenAI 兼容格式) │ └───────────────────────────┼──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ Dify AI (外部服务) │ │ Wingman Agent: /chat/completions (OpenAI 兼容) │ │ RAGFlow: 知识库检索 (智能改写时调用) │ └──────────────────────────────────────────────────────────────────┘ ``` #### 1.3 与现有系统的关系 | 现有组件 | 关系 | 说明 | |----------|------|------| | `WingmanService` | 扩展 | 新增 4 个方法,改造 `_call_wingman_api()` 支持 temperature | | `wingman.py` 路由 | 扩展 | 新增 4 个端点 + Pydantic 请求模型 | | `wingman.ts` API 层 | 扩展 | 新增 4 个前端 API 调用函数 | | `ReplyBox.vue` | 改造 | 工具栏重构 + AI 工具按钮 + 补齐逻辑 | | `AiAssistantPanel.vue` | 全面重构 | 移除回复前功能,改为训练区 | | `AiRecommendInline.vue` | 删除 | 功能被 `AiRecommendBar.vue` 替代 | | `AiSuggestReply.vue` | 删除 | 功能合并到 `AiRecommendBar.vue` | | `QuickReplyPanel.vue` | 改造 | 重命名为 `QuickReplyBar.vue`,横向布局 | --- ### 2. 后端架构设计 #### 2.1 WingmanService 扩展设计 现有 `WingmanService` 包含 4 个方法(generate_draft / generate_summary / suggest_tags / generate_knowledge_suggestion),全部通过 `_call_wingman_api()` 调用 Dify。本设计新增 4 个方法并改造底层调用。 **新增方法签名**: ```python class WingmanService: # === 现有方法(保持不变) === # generate_draft() # generate_summary() # suggest_tags() # generate_knowledge_suggestion() # === 新增方法 === async def generate_completion( self, conversation_id: str, current_text: str, messages: List[Dict[str, Any]], max_length: int = 80, ) -> Dict[str, Any]: """自动补齐:根据坐席当前输入内容补齐下一句""" async def adjust_tone( self, conversation_id: str, selected_text: str, full_text: str, tone: str, # professional / friendly / concise messages: List[Dict[str, Any]], ) -> Dict[str, Any]: """语气调整:将选中文字改写为指定风格""" async def polish_text( self, conversation_id: str, text: str, action: str, # expand / compress / correct messages: List[Dict[str, Any]], ) -> Dict[str, Any]: """文字润色:对输入框全部内容进行扩写/压缩/纠错""" async def rewrite_versions( self, conversation_id: str, current_text: str, messages: List[Dict[str, Any]], generate_count: int = 3, include_knowledge: bool = True, ) -> Dict[str, Any]: """智能改写:基于对话上下文+知识库生成多个备选版本""" ``` #### 2.2 _call_wingman_api 改造 **当前问题**:`_call_wingman_api()` 的 temperature 硬编码为 0.3,所有方法共用。新功能需要不同 temperature: | 方法 | temperature | 理由 | |------|-------------|------| | generate_completion | 0.2 | 补齐需要高确定性,避免创造性发散 | | adjust_tone | 0.3 | 改写需保持原意,适度创造性 | | polish_text | 0.3 | 润色需保持原意,适度创造性 | | rewrite_versions | 0.6 | 改写需要多样化输出,提高创造性 | | generate_draft (现有) | 0.3 | 保持不变 | | generate_summary (现有) | 0.3 | 保持不变 | **改造方案**: ```python # 改造前(当前代码) async def _call_wingman_api( self, context_messages: List[Dict[str, str]] ) -> Optional[str]: payload = { "model": "Chat", "messages": context_messages, "stream": False, "temperature": 0.3, # ← 硬编码 } # 改造后 async def _call_wingman_api( self, context_messages: List[Dict[str, str]], temperature: float = 0.3, # ← 新增参数,默认值保持兼容 ) -> Optional[str]: payload = { "model": "Chat", "messages": context_messages, "stream": False, "temperature": temperature, } ``` **兼容性**:现有 4 个方法调用时不传 temperature 参数,自动使用默认值 0.3,行为不变。 #### 2.3 上下文构建策略 现有 `_build_context_messages()` 将完整对话历史构建为 OpenAI Chat 格式。新功能需要差异化上下文: | 方法 | 上下文消息条数 | 附加内容 | 原因 | |------|--------------|---------|------| | generate_completion | 最近 5 条 | 当前输入文本拼入最后一条 user 消息 | 补齐需要最近上下文 + 当前输入 | | adjust_tone | 最近 5 条 | 选中文字 + 完整输入内容作为指令 | 改写需要理解选中文字的上下文 | | polish_text | 最近 5 条 | 全部输入内容作为指令 | 润色针对输入框全部内容 | | rewrite_versions | 最近 10 条 | 知识库检索结果(可选) | 改写需要更完整上下文 + 知识库引用 | **实现方式**:复用 `_build_context_messages()` 构建基础对话上下文,然后在方法内部追加功能特定的指令消息: ```python async def generate_completion(self, ...): # 1. 构建基础上下文(最近5条对话 → system prompt + 历史消息) context = self._build_context_messages(messages, self._COMPLETION_SYSTEM_PROMPT) # 2. 追加当前输入作为最后一条 user 消息 context.append({ "role": "user", "content": f"坐席正在输入的内容:{current_text}\n请补齐下一句话。" }) # 3. 调用 Dify(temperature=0.2) result = await self._call_wingman_api(context, temperature=0.2) ``` #### 2.4 智能改写的知识库集成 `rewrite_versions()` 在生成"带知识库引用"版本时需要调用 RAGFlow 检索: ``` 坐席点击"改写" → 后端从对话消息提取关键词 → 调用 RAGFlow API 检索相关知识 → 将检索结果注入 Dify prompt → Dify 生成 3 个版本(含知识库引用版本) ``` **RAGFlow 调用**:复用现有 `RAGFlowClient`(位于 `app/integrations/`),无需新建。若 RAGFlow 不可用,降级为仅基于对话上下文生成 2 个版本。 #### 2.5 降级策略 与现有方法保持一致:Dify 不可用时返回默认值而非抛异常。 ```python # 降级返回示例(generate_completion) { "completion": "", "confidence": 0.0, "error": "Wingman 服务暂不可用" } # 降级返回示例(rewrite_versions) { "versions": [], "error": "AI 服务暂不可用,请稍后重试" } ``` --- ### 3. 前端架构设计 #### 3.1 组件架构图 ``` Workspace.vue (主布局) ├── ConversationList.vue (左栏 - 会话用户列表) │ └── TodoPanel.vue (待办面板) │ ├── ChatArea.vue (中栏) │ ├── UserInfoBar.vue (用户信息栏 - 详情默认折叠) │ ├── TroubleshootBar.vue (排查步骤栏 - 默认折叠为图标条) │ ├── MessageList.vue (消息列表) │ ├── ReplySuggestArea.vue (回复建议区 - 新增) │ │ ├── AiRecommendBar.vue (AI推荐条 - 合并自 AiRecommendInline + AiSuggestReply) │ │ └── QuickReplyBar.vue (快速回复条 - 改造自 QuickReplyPanel) │ └── ReplyBox.vue (输入框 - 改造) │ ├── 工具栏 (单行左右分区) │ │ ├── 常规工具组: [截图] [拍照] [表情] [文件] [邀请] │ │ └── AI工具组: [补齐toggle] [语气] [润色] [改写] │ ├── GhostTextOverlay.vue (幽灵文字覆盖层 - 新增) │ ├── ToneAdjustPopover.vue (语气调整浮层 - 新增) │ ├── PolishPanel.vue (润色精修面板 - 新增) │ └── RewritePanel.vue (改写选择面板 - 新增) │ └── AiAssistantPanel.vue (右栏 - 全面重构) ├── PanelModeToggle.vue (模式切换: 正常/放大 - 新增) └── AiTrainingPanel.vue (训练区主体 - 新增) ├── SmartTagEditor.vue (智能标注) ├── QualityFeedback.vue (质量反馈) ├── KnowledgeContribute.vue (知识贡献) └── UsageStats.vue (使用统计) ``` #### 3.2 Composable 设计 新增 3 个 composable 封装 AI 辅助逻辑: | Composable | 职责 | 状态范围 | |------------|------|---------| | `useAutoComplete.ts` | 自动补齐的 debounce、请求、幽灵文字管理 | ReplyBox 局部 | | `useAiTextTools.ts` | 语气/润色/改写的统一调用和结果管理 | ReplyBox 局部 | | `usePanelMode.ts` | 右栏放大/缩小模式切换 | Workspace 全局 | **useAutoComplete.ts 核心逻辑**: ```typescript export function useAutoComplete( textareaRef: Ref, conversationId: Ref, enabled: Ref, ) { const ghostText = ref('') // 幽灵文字内容 const isLoading = ref(false) // 加载状态 const abortController = ref(null) // debounce 800ms,输入停顿后触发 const debouncedFetch = useDebounceFn(async (text: string) => { if (!enabled.value || text.length < 5) { ghostText.value = '' return } // 取消上一个请求 abortController.value?.abort() abortController.value = new AbortController() isLoading.value = true try { const result = await wingmanApi.autocomplete( conversationId.value, { current_text: text, cursor_position: text.length }, { signal: abortController.value.signal }, ) ghostText.value = result.completion || '' } catch (e) { if (!isAbortError(e)) ghostText.value = '' } finally { isLoading.value = false } }, 800) // Tab 接受补齐 function acceptCompletion() { // 将 ghostText 追加到输入框 // 清空 ghostText } // Esc 清除补齐 function dismissCompletion() { ghostText.value = '' } return { ghostText, isLoading, debouncedFetch, acceptCompletion, dismissCompletion } } ``` **usePanelMode.ts 核心逻辑**: ```typescript export function usePanelMode() { const mode = ref<'normal' | 'expanded'>('normal') const panelWidth = computed(() => mode.value === 'normal' ? '260px' : '560px' ) const centerColVisible = computed(() => mode.value === 'normal' ) function toggleMode() { mode.value = mode.value === 'normal' ? 'expanded' : 'normal' } return { mode, panelWidth, centerColVisible, toggleMode } } ``` #### 3.3 幽灵文字实现方案 PRD 确认为内联幽灵文字。实现方式为 textarea 上方覆盖透明文字层: ``` ┌──────────────────────────────┐ │ textarea (z-index: 1) │ ← 文字颜色: #303133 (正常) │ 透明背景,文字不可见的部分 │ 选中文字后的补齐文字不可见 ├──────────────────────────────┤ │ ghost overlay (z-index: 2) │ ← 文字颜色: #C0C4CC (灰色) │ pointer-events: none │ 仅在 textarea 文字末尾之后显示 │ 位置与 textarea 文字完全对齐 │ 通过 mirror div 技术计算光标坐标 └──────────────────────────────┘ ``` **技术要点**: 1. 创建一个与 textarea 样式完全一致的 `
`(mirror div),用于计算光标坐标 2. ghost overlay 定位在光标坐标处,显示灰色补齐文字 3. `pointer-events: none` 确保不拦截鼠标事件 4. Tab 键监听在 textarea 的 `keydown` 事件中,`e.key === 'Tab'` 时阻止默认行为并接受补齐 5. 继续输入时自动清除 ghost text(`input` 事件触发 debounce 重新请求) **Mirror div 技术**(用于精确计算光标位置): ```typescript function getCursorCoordinates(textarea: HTMLTextAreaElement, position: number) { const div = document.createElement('div') // 复制 textarea 的所有影响文字布局的样式 const styles = window.getComputedStyle(textarea) Object.assign(div.style, { position: 'absolute', visibility: 'hidden', whiteSpace: 'pre-wrap', wordWrap: 'break-word', // ... 复制 font-family, font-size, padding, border, width 等 }) // 截取光标位置之前的文字 div.textContent = textarea.value.substring(0, position) document.body.appendChild(div) // 创建一个 span 标记光标位置 const span = document.createElement('span') span.textContent = '|' div.appendChild(span) // 获取 span 的坐标(相对于 textarea) const coords = { x: span.offsetLeft - textarea.scrollLeft, y: span.offsetTop - textarea.scrollTop, } document.body.removeChild(div) return coords } ``` #### 3.4 前端 API 层扩展 在 `frontend-agent/src/api/wingman.ts` 中新增 4 个函数: ```typescript // 自动补齐 export async function autocomplete( conversationId: string, data: { current_text: string; cursor_position: number; max_length?: number }, config?: { signal?: AbortSignal }, ): Promise<{ completion: string; confidence: number }> // 语气调整 export async function toneAdjust( conversationId: string, data: { selected_text: string; full_text: string; tone: string }, ): Promise<{ rewritten_text: string; tone: string; changes_summary: string }> // 文字润色 export async function polish( conversationId: string, data: { text: string; action: string }, ): Promise<{ polished_text: string; action: string; changes_summary: string }> // 智能改写 export async function rewrite( conversationId: string, data: { current_text: string; generate_count?: number; include_knowledge?: boolean }, ): Promise<{ versions: Array<{ text: string; style: string; source: string }> }> ``` --- ### 4. 布局重构架构设计 #### 4.1 CSS 变量体系 ```css :root { /* === 布局尺寸 === */ --sidebar-width: 260px; /* 左栏: 280px → 260px */ --assistant-panel-width: 260px; /* 右栏正常: 320px → 260px */ --assistant-panel-expanded: 560px; /* 右栏放大: 新增 */ /* === 右栏模式控制 === */ --center-col-flex: 1; /* 正常: 中栏弹性 */ --center-col-width: auto; /* 正常: 中栏自动 */ --center-col-overflow: visible; /* 正常: 可见 */ /* === 回复建议区颜色 === */ --suggest-ai-bg: #FAECE7; /* AI推荐背景 - 珊瑚色 */ --suggest-ai-border: #F0997B; --suggest-ai-text: #993C1D; --suggest-quick-bg: #EAF3DE; /* 快速回复背景 - 绿色 */ --suggest-quick-border: #97C459; --suggest-quick-text: #27500A; /* === 工具栏分组颜色 === */ --toolbar-conv-bg: #E1F5EE; /* 常规工具 - 绿色系 */ --toolbar-ai-bg: #FAEEDA; /* AI工具 - 琥珀色系 */ /* === 回复建议区高度 === */ --suggest-area-height: auto; /* 动态: 选中后 0 */ --suggest-ai-height: 44px; /* AI推荐区固定高度 */ --suggest-quick-height: 56px; /* 快速回复区固定2层 */ } ``` #### 4.2 右栏放大/缩小模式实现 ```vue ``` #### 4.3 回复建议区动态高度 ```vue ``` #### 4.4 工具栏单行左右分区 ```vue ``` --- ### 5. 数据流设计 #### 5.1 自动补齐数据流 ``` 坐席输入文字 → textarea input 事件 → useAutoComplete.debouncedFetch(text) [debounce 800ms] → 取消上一个 AbortController → 调用 wingmanApi.autocomplete(conversationId, { current_text }) → POST /api/conversations/{id}/wingman/autocomplete → FastAPI 路由层 → _get_recent_messages(conversationId, db, limit=5) → WingmanService.generate_completion(conversationId, text, messages) → _build_context_messages(messages, _COMPLETION_SYSTEM_PROMPT) → 追加 current_text 到上下文末尾 → _call_wingman_api(context, temperature=0.2) → Dify API POST /chat/completions → 解析 choices[0].message.content → 返回 { completion, confidence } → 前端设置 ghostText.value = result.completion → ghost overlay 渲染灰色文字 坐席按 Tab → acceptCompletion() → inputText.value += ghostText.value → ghostText.value = '' ``` #### 5.2 语气调整数据流 ``` 坐席选中文字 → 点击"语气"按钮 → 弹出 ToneAdjustPopover → 选择"专业/友好/简洁" → 调用 wingmanApi.toneAdjust(conversationId, { selected_text, full_text, tone }) → POST /api/conversations/{id}/wingman/tone-adjust → WingmanService.adjust_tone(...) → _build_context_messages(messages, _TONE_SYSTEM_PROMPT) → 注入 selected_text + full_text + tone 指令 → _call_wingman_api(context, temperature=0.3) → 返回 { rewritten_text, tone, changes_summary } → 浮层显示"原文 → 改写文"对比 → 坐席点击"替换" → 用 rewritten_text 替换选中区域 → 坐席点击"取消" → 关闭浮层,不修改 ``` #### 5.3 右栏模式切换数据流 ``` 坐席点击 PanelModeToggle "放大"按钮 → usePanelMode.toggleMode() → mode.value = 'expanded' → CSS 变量更新: --assistant-panel-width → 560px --center-col-flex → 0 --center-col-width → 0 --center-col-overflow → hidden → CSS transition 0.3s 平滑动画 → 中栏压缩隐藏,右栏展开为 560px 坐席点击"正常"按钮 → toggleMode() → mode.value = 'normal' → CSS 变量恢复 → 中栏恢复显示,右栏缩回 260px ``` --- ### 6. 关键设计决策 #### 决策 1:自动补齐用 Mirror Div 而非 contenteditable **选择**:保持 `