diff --git a/docs/01-产品文档/04-坐席工作台/PRD-REQ-坐席-009-会话状态Tab筛选-v1.0.md b/docs/01-产品文档/04-坐席工作台/PRD-REQ-坐席-009-会话状态Tab筛选-v1.0.md new file mode 100644 index 0000000..9b71eb2 --- /dev/null +++ b/docs/01-产品文档/04-坐席工作台/PRD-REQ-坐席-009-会话状态Tab筛选-v1.0.md @@ -0,0 +1,199 @@ +# 坐席端会话状态 Tab 筛选 — 产品需求文档 (PRD) + +> **版本**: v1.0 +> **日期**: 2026-08-03 +> **作者**: 宋献 +> **状态**: ✅ 已落地(生产版本 `index-BK_U7e10.js`) +> **子系统**: 04-坐席工作台 +> **模块**: 会话列表筛选 +> **关联任务**: #134(坐席端左栏会话状态 Tab 顺序调整 + 默认显示待处理) +> **关联原型**: `原型-REQ-坐席-000-坐席工作台-v1.2.html` + +--- + +## 1. 背景与目标 + +### 1.1 现状分析 + +坐席工作台左栏会话窗口已具备 Tab 筛选能力(v1.0 起即存在),但 v1.1 存在两个影响日常效率的问题: + +| 问题 | 表现 | 影响 | +|------|------|------| +| **Tab 顺序不合理** | 「全部」Tab 排在第一位(按字母 + 全集概念) | 高频 Tab(待处理)被埋在其他标签后面 | +| **默认激活错位** | 默认进入页面激活「全部」 | 坐席每天首次进入要先点 1 下切到「待处理」才看到工作队列 | +| **筛选粒度不足** | 「全部」Tab 混排 待处理 + 进行中 + 已完成 + 同事会话 + 历史会话 | 视觉噪音多,重点不突出 | + +### 1.2 用户痛点 + +**核心痛点**:坐席每日首次进入坐席工作台 → 应立即看到「我现在该处理的待办队列」。 + +但当前版本默认显示「全部」,坐席需要: +1. 看完整列表 → 识别哪些是新分派的 +2. 或点击「待处理」Tab → 才看到工作队列 + +**多点击 + 视觉噪音**:每天 8 小时 × 多次重入 = 累计损失明显。 + +### 1.3 目标 + +通过最小改动(**仅 Tab 顺序调整 + 默认激活态变更**),实现: + +1. **默认即工作队列** — 打开坐席工作台,第一眼就是待处理 +2. **顺序符合使用频次** — 高频 Tab 在前、低频 Tab 在后 +3. **「全部」沉底** — 仅作"全部查看"用,不作为默认入口 + +### 1.4 设计原则 + +| 原则 | 体现 | +|------|------| +| **最小改动** | 仅顺序 + 初值,不动 store / 不动样式 / 不动 key 名 | +| **后向兼容** | 保留 `activeFilter` 的 4 个 key(`'pending' \| 'active' \| 'done' \| 'all'`),switch 逻辑、API 调用、URL 参数都不变 | +| **数据准确** | status 映射规则保留:`pending→queued`、`active→serving/ai_handling`、`done→resolved` | +| **协同不干扰** | 同事会话(`colleagueConversations`)和历史会话(`historyConversations`)**仅在「全部」Tab 下可见**,其他 Tab 按 `applyFilters` 自然归零 | + +--- + +## 2. 需求说明 + +### 2.1 核心功能(FR) + +| FR ID | 功能描述 | 优先级 | +|-------|----------|--------| +| **FR-01** | 左栏会话窗口顶部展示 4 个 Tab,**顺序为「待处理 → 进行中 → 已完成 → 全部」** | P0 | +| **FR-02** | **默认进入页面激活「待处理」Tab**(无需点击) | P0 | +| **FR-03** | 切换 Tab 时列表实时过滤,按 `applyFilters` 规则(见 §2.3) | P0 | +| **FR-04** | 搜索框 + Tab 为 AND 关系(关键词 ∧ 状态) | P1 | +| **FR-05** | 「全部」Tab 下展示所有会话(含同事 + 历史) | P1 | + +### 2.2 详细说明 + +**4 个 Tab 排序逻辑**(高频在前,低频在后): +1. **待处理** — 每日最高频(队列分派 / 求助举手 / 新分派会话) +2. **进行中** — 高频(已回复待用户反馈的会话) +3. **已完成** — 中频(结单归档的会话) +4. **全部** — 低频(异常排查 / 数据回顾) + +**激活态与默认行为**: +- 默认 `activeFilter = 'pending'` +- 用户切换后,组件不持久化 Tab 选择(刷新即回到「待处理」)— 这是设计选择,避免历史 Tab 状态导致坐席看不到最新队列 + +### 2.3 状态映射规则(与 Store API 保持一致) + +| Tab 显示文案 | `activeFilter` 值 | 对应 `conv.status` | Store 归属 | +|---|---|---|---| +| **待处理** | `'pending'` | `queued`(待接单) | `myConversations` | +| **进行中** | `'active'` | `serving`(服务中)或 `ai_handling`(AI 处理中) | `myConversations` / `colleagueConversations` | +| **已完成** | `'done'` | `resolved`(90 天内) | `historyConversations` | +| **全部** | `'all'` | 不限定 | 三段全部 | + +> 注:`activeFilter` key 名 `'active'`(不是 `'in-progress'`)保持后向兼容;store status 命名(如 `serving` / `ai_handling`)不变。 + +### 2.4 跨段渲染规则 + +| Tab | 我的会话(mine) | 同事会话(colleague) | 历史会话(history) | +|---|---|---|---| +| **待处理** | ✅ 显示 pending | ❌ 自然为空(store 隔离) | ❌ 自然为空 | +| **进行中** | ✅ 显示 active | ❌ 自然为空 | ❌ 自然为空 | +| **已完成** | ✅ 显示 done(跨段渲染 from history) | ❌ 自然为空 | ✅ 显示 resolved | +| **全部** | ✅ 显示 all | ✅ 显示 all | ✅ 显示 all | + +> 「同事会话」在所有筛选 Tab 下都不出现(store 中 colleagueConversations 仅含 `serving+!is_mine+!is_collaborator` 和 `ai_handling`,不满足 pending/active/done 的任意状态)— 用户确认接受该行为。 + +--- + +## 3. 用户故事 & 验收标准 + +### 3.1 用户故事 + +| US ID | 角色 | 故事 | 优先级 | +|-------|------|------|--------| +| **US-01** | 坐席 | 作为坐席,我希望打开坐席工作台立即看到待处理会话列表,这样能马上开始工作 | P0 | +| **US-02** | 坐席 | 作为坐席,我希望 Tab 按使用频次排序(待处理在最左),这样切换更顺手 | P1 | +| **US-03** | 坐席 | 作为坐席,我希望「全部」沉到底部(不是默认入口),避免误点 | P1 | +| **US-04** | 资深坐席 | 作为坐席,我希望切换到「全部」Tab 还能看到同事会话和历史会话,便于排查 | P1 | + +### 3.2 验收标准(AC) + +| AC ID | 验证项 | 验收方法 | 预期结果 | +|-------|--------|---------|---------| +| **AC-01** | Tab 顺序 | 视觉确认 | 从左到右:`待处理 → 进行中 → 已完成 → 全部` | +| **AC-02** | 默认激活 | 刷新页面 | 「待处理」Tab 蓝色高亮 | +| **AC-03** | 默认列表 | 刷新页面 | 左侧仅展示 `status=queued` 的会话(来自 `myConversations` 自然过滤) | +| **AC-04** | Tab 切换「进行中」 | 点击 | 列表展示 `status in {serving, ai_handling}` | +| **AC-05** | Tab 切换「已完成」 | 点击 | 列表展示 `status=resolved`(来自 `historyConversations`) | +| **AC-06** | Tab 切换「全部」 | 点击 | 三段(my + colleague + history)全部展示 | +| **AC-07** | 搜索 + Tab 联动 | 输入关键词后切换 Tab | 关键词 ∧ 状态过滤均生效 | +| **AC-08** | 同事会话隐藏(非全 Tab) | 切到「待处理」 | 赵敏/周芳/吴明(同事区)不显示 | +| **AC-09** | 接手会话流转 | 同事会话点击「接手」 | 接手后会话归入 `myConversations`,「全部」Tab 下可见 | +| **AC-10** | 无数据容错 | DB 清空全部会话 | 「待处理」激活,左侧显示 `el-empty description="暂无会话"` 占位 | + +--- + +## 4. 交互示意 + +### 4.1 默认进入页面 + +``` +┌─────────────────────────────────┐ +│ [🔍 搜索用户、关键词...] │ +│ │ +│ [● 待处理 5] [进行中 1] ... [全部 10] │ ← Tab 行:默认 ● 待处理 +│ │ +│ 头像 张伟 10:25 待回复 张 │ ← 左侧会话语义化分区 +│ VPN 连接失败 │ +│ 头像 陈芳 09:42 待回复 陈 │ +│ 系统卡顿 │ +│ ... │ +└─────────────────────────────────┘ +``` + +### 4.2 切到「全部」Tab + +``` +┌─────────────────────────────────┐ +│ [● 全部 10] ... [● 待处理 5] │ ← 切到全部 Tab +│ │ +│ --- 我的会话 --- │ +│ 头像 张伟 10:25 待回复 张 │ +│ ... │ +│ --- 同事会话(刘明/王强/李静)--- │ +│ 头像 赵敏 OA 审批流程报错 [接手]│ +│ ... │ +│ --- 历史会话 --- │ +│ 头像 周杰 昨日 VPN 证书过期—已解决 │ +└─────────────────────────────────┘ +``` + +--- + +## 5. 不在本 PRD 范围(明确边界) + +为避免范围蔓延,以下问题由后续独立 PRD 处理: + +| 项 | 说明 | 后续 PRD 候选 | +|---|---|---| +| Tab 数量徽章(待处理 5 等) | v1.2 原型里有该设计,但需要 store 新增 4 个 computed 计算实时数量 | **#135 候选** | +| 空状态文案("暂无待处理会话") | 当前复用 `el-empty description="暂无会话"`,已能覆盖空场景 | 视真实使用反馈决定是否独立 PRD | +| 持久化 Tab 选择(localStorage) | 当前切换刷新即回默认;若坐席希望保留,可加 1 行 localStorage | 视需求反馈 | + +--- + +## 6. 关联文档 + +| 文档 | 路径 | 说明 | +|------|------|------| +| 原型 v1.2(最新版) | `docs/01-产品文档/04-坐席工作台/原型-REQ-坐席-000-坐席工作台-v1.2.html` | UI 设计基准 | +| 技术方案(会话列表) | `docs/02-技术文档/技术架构/技术方案-REQ-坐席-002-AI辅助消息框-v1.0.md` | 新增 §X.Y 段(commit 时同步) | +| 任务说明书 #134 | `docs/07-项目管理/任务说明书/任务说明书-134-坐席端左栏会话状态Tab顺序调整+默认显示待处理.md` | 落地过程记录 | +| 前端源码 | `src/frontend-agent/src/components/conversation/ConversationList.vue` | `filterTags` + `activeFilter` 2 处改动 | +| Pinia Store | `src/frontend-agent/src/stores/conversation.ts` | `myConversations` / `colleagueConversations` / `historyConversations` 数据分区(未改) | +| Git commit | `566bb46 feat(agent): 坐席端左栏会话状态 Tab 顺序调整 + 默认显示待处理 (#134)` | 单一原子提交 | + +--- + +## 7. 变更记录 + +| 日期 | 变更内容 | 变更人 | +|------|----------|--------| +| 2026-08-03 | 创建 PRD #009,承接 #134 落地需求 | 宋献 | +| 2026-08-03 | 同步技术方案 #002 加入交叉引用 | 宋献 | +| | | | diff --git a/docs/02-技术文档/技术架构/技术方案-REQ-坐席-002-AI辅助消息框-v1.0.md b/docs/02-技术文档/技术架构/技术方案-REQ-坐席-002-AI辅助消息框-v1.0.md new file mode 100644 index 0000000..d611821 --- /dev/null +++ b/docs/02-技术文档/技术架构/技术方案-REQ-坐席-002-AI辅助消息框-v1.0.md @@ -0,0 +1,1531 @@ +# 技术方案 - REQ-坐席-002 AI辅助消息框 + +> **版本**: v1.0 +> **日期**: 2026-07-11 +> **REQ编号**: REQ-坐席-002 +> **关联PRD**: `PRD-REQ-坐席-002-AI辅助消息框-v1.0.md` +> **状态**: 已完成 +> **架构师**: 宋献 (Simon) +> **技术栈**: 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 (左栏 - 会话用户列表) + │ │ ├── 筛选 Tab:待处理 → 进行中 → 已完成 → 全部(默认激活"待处理",见 PRD-REQ-坐席-009) + │ │ └── 数据源:Pinia conversationStore(myConversations / colleagueConversations / historyConversations) + │ └── 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 + +**选择**:保持 `