Files
DevFlow/docs/02-架构设计/aichat交互体验改进方案-2026-06-14.md

296 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AIChat 交互体验改进方案
> 创建: 2026-06-14 | 状态: 待讨论
> 范围: 消息发送、流式渲染、对话管理、错误恢复、技能/Provider、窗口布局等非授权类交互
---
## 一、消息输入与发送
### 1.1 无法编辑已发送消息
**现象**:用户发出消息后发现措辞有误,只能重新打一条新消息。无法像 ChatGPT/Claude 那样编辑上一条用户消息并重新生成。
**根因**`sendMessage`useAiSend.tspush 后的消息是只追加不可变的。前端没有编辑入口,后端 `ai_chat_send` 也没有"替换最后一条 user 消息并重跑"的语义。
**方案**
- 用户消息气泡 hover 显示"编辑"按钮
- 点击后消息内容回填到输入框,用户修改后发送时后端截断该消息之后的所有历史(含 AI 回复),重新跑 agentic loop
- 后端新增 `ai_chat_edit` 命令,接收 `message + 截断位置`,替换 messages 数组中对应 user 消息并清掉后续消息,然后走正常 `run_agentic_loop`
### 1.2 无法重新生成 AI 回复
**现象**AI 回答不满意时没有"重新生成"按钮,只能重新措辞追问。
**根因**`AiChat.vue` 的 AI 消息气泡上没有任何操作按钮。后端也没有 `ai_regenerate` 命令。
**方案**
- AI 消息气泡 hover 显示操作栏(复制 | 重新生成)
- "重新生成":后端删除最后一条 AI 消息,用倒数第二条 user 消息重新触发 `run_agentic_loop`
- 后端新增 `ai_regenerate` 命令
### 1.3 无法一键复制消息内容
**现象**:代码或文本只能手动选中复制。流式渲染中选中文字会被后续 delta 打断。
**根因**:消息气泡上没有复制按钮。
**方案**
- AI 消息气泡 hover 显示"复制"按钮
- 点击后 `navigator.clipboard.writeText(msg.content)`toast 提示"已复制"
- 代码块单独提供"复制代码"按钮hover 代码块右上角浮出)
### 1.4 缺少 `@` 实体引用
**现象**用户无法在输入时引用某个项目、任务或文件。描述需求时无法精确指定上下文AI 可能猜错对象。
**根因**:输入框只有 `/` 技能联想,没有 `@` 实体引用机制。
**方案**
- 输入框支持 `@` 触发实体联想浮层(复用技能联想的 popover 架构)
- 联想源:项目列表、任务列表、最近编辑的文件
- 选中后在消息中展开为 `[项目: u-desk]` 等标记文本,后端 system prompt 注入对应实体的上下文摘要
### 1.5 输入框高度过紧
**现象**textarea 最大高度 120px约 5-6 行),超过后内部滚动。用户写长提示词时看不到全貌。
**根因**`autoResize``Math.min(el.scrollHeight, 120)` 限制太紧。
**方案**
- 最大高度提升到 200px约 10 行),超过后再内部滚动
- 或改为可拖拽调整高度(底部 resize handle
---
## 二、流式渲染与消息展示
### 2.1 流式渲染中无法稳定选中文字
**现象**AI 正在流式输出时,用户尝试选中已渲染的文字,新的 delta 触发 DOM 更新导致选区丢失。
**根因**`renderContent` 在流式时返回 `streamingHtml.value`rAF 节流重 parse每次更新都 `v-html` 替换整个 DOM 子树,浏览器选区被清除。
**方案**
- 方案 A推荐检测到用户正在选择文字时`selectionchange` 事件 + 选区非空且在消息容器内),暂停 rAF 流式 parse选区结束后恢复
- 方案 B流式渲染时在已完成的块blockCache 命中的块)上使用独立 DOM 节点不参与 v-html 替换,仅末块动态更新
### 2.2 代码块无语法高亮、无复制按钮
**现象**Markdown 代码块只有纯文本渲染,没有语法高亮,也没有一键复制按钮。
**根因**`useMarkdown` composable 配置了 marked + DOMPurify但没有集成 highlight.js / Prism 等高亮器。
**方案**
- 集成 highlight.js体积小、语言全在 marked renderer 的 `code` 回调中调用 `hljs.highlightAuto` 或按 info string 指定语言
- 代码块右上角浮出"复制"按钮纯前端hover 显示)
- 高亮器按需加载(与 marked 一样后台预热,不阻塞首屏)
### 2.3 历史消息无分页懒加载
**现象**:切换对话时 `switchConversation` 一次性加载全部 messages 到 `state.messages`。超长对话可能一次加载几百条消息。
**根因**`switchConversation`useAiConversations.ts直接 `state.messages = rawMsgs.filter().map()`,无分页。
**方案**
- 首次加载最近 50 条,滚动到顶部时加载更多(前端 slice + 后端支持 offset/limit 查询 messages
- 或前端全量加载但虚拟滚动只渲染可视区域(见 7.2
### 2.4 消息不显示时间戳
**现象**:消息气泡上不显示发送时间。用户无法判断某条消息是多久前发的。
**根因**:模板中 AI/用户消息都没有渲染 `msg.timestamp`
**方案**
- 消息气泡下方或侧边以极小字号9px+ dim 颜色展示相对时间(`formatRelativeZh`
- hover 时 tooltip 展示完整时间
---
## 三、对话管理
### 3.1 对话搜索缺失
**现象**:侧栏对话列表只能滚动浏览,没有搜索框。对话多了之后找不到特定对话。
**根因**:侧栏 `.ai-conv-list` 直接渲染 `groupedActive`,上方只有"新建"按钮,没有搜索输入框。
**方案**
- 侧栏 header 下方增加搜索输入框(实时过滤 `state.conversations`,匹配 title
- 搜索时取消分组,按相关度/时间平铺展示
- 支持 `Ctrl+K` 快捷键聚焦搜索框
### 3.2 对话无法置顶
**现象**:没有置顶或收藏功能。重要对话会被新对话挤到下面。
**根因**:对话列表只按 `updated_at` 排序,无 `pinned` 字段。
**方案**
- `ai_conversations` 表增加 `pinned INTEGER DEFAULT 0`
- 排序逻辑改为 `pinned DESC, updated_at DESC`
- 侧栏对话项 hover 显示"置顶/取消置顶"按钮(图钉图标)
### 3.3 对话无法导出
**现象**:无法将对话导出为 Markdown / JSON / 文本文件。
**根因**:没有导出 IPC 命令和前端入口。
**方案**
- 后端新增 `ai_conversation_export(conv_id, format)` 命令,支持 `markdown` / `json` / `txt` 三种格式
- 前端侧栏对话项 hover 显示"导出"按钮,或对话操作菜单中提供
- Markdown 格式:`## 用户` / `## 助手` 交替,代码块保留围栏
### 3.4 新建对话强制中断已有生成
**现象**:用户在 AI 生成中点"新建对话",后端 `ai_conversation_create` 会强制复位 `generating=false` + 置 `stop_flag`,中断当前生成。
**根因**`commands.rs ai_conversation_create``if session.generating` 分支主动结束生成B-260615-10 设计)。
**方案**
- 生成中点"新建对话"时弹 ConfirmDialog"当前对话正在生成,确定要新建对话并中断吗?"
- 用户确认后才执行中断+新建;取消则不操作
- 或改为不中断:新对话仅切换视图,旧对话后台继续生成(需 AiSession 多实例化支持,属远期方案)
---
## 四、错误处理与恢复
### 4.1 错误气泡无操作入口
**现象**:错误气泡只显示一行文字(如 `[GLM-4] AI 调用失败(HTTP 401): Unauthorized`),没有"重试"按钮或"去设置"链接。
**根因**`AiError` 事件只携带 `error: String`,前端 `handleEvent` push 一条 `isError: true` 的消息气泡,无操作按钮。
**方案**
- 错误气泡底部增加操作按钮区:
- **重试**:取上一条 user 消息重新发送(调 `ai_chat_send`
- **去设置**(仅 401/403/Provider 未配置时显示):跳转到 Settings → AI Tab
- 错误消息结构扩展:后端 `AiError` 增加 `error_type: Option<ErrorType>` 枚举(`auth` / `network` / `timeout` / `provider_config` / `unknown`),前端据此决定显示哪些按钮
### 4.2 流式中断丢失已生成文本
**现象**网络波动导致流式中断idle timeout 或 mid-stream error已接收的文本被丢弃`stream_llm` return None → `agentic.rs` emit AiError 并退出),用户看到错误气泡,之前生成的几百字全部丢失。
**根因**`stream_llm` 遇到错误时 `return None`,不保留已接收的部分文本。`agentic.rs` 收到 None 后直接结束。
**方案**
- `stream_llm` mid-stream error 时改为返回 `Some((partial_text, tool_calls, usage))` + 一个 `incomplete: bool` 标志
- `agentic.rs` 收到 incomplete=true 时:
- 已有文本正常入库(标注 `truncated`
- emit `AiCompleted`(而非 AiError让前端正常展示已生成内容
- 在消息末尾追加系统提示:"⚠ 响应因网络中断不完整"
- 前端 AI 消息气泡底部显示"继续生成"按钮(用最后一条 user 消息重新触发,后端识别不完整消息做续写或重跑)
---
## 五、技能与 Provider
### 5.1 Provider 切换零反馈
**现象**:点击 provider bar 循环切换 provider`cycleProvider`),切换后只在 bar 上显示名字变化,没有 toast 或动画反馈。
**根因**`cycleProvider` 直接调 `store.setProvider`,无 UI 反馈。
**方案**
- 切换后 toast 提示"已切换到 {providerName}"
- provider bar 切换时加 0.15s 淡入动画
- bar 上增加 provider 状态指示model 名称小字展示)
### 5.2 技能联想不展示参数用法
**现象**:技能联想浮层显示 name + description + source但不显示技能的参数格式或示例。选中后 placeholder 里的 `argument_hint` 太短。
**根因**:浮层设计只展示概要信息,`argument_hint` 仅在选中后作为 placeholder 显示。
**方案**
- 联想浮层每项增加一行参数提示(`argument_hint` 以等宽字体小字展示)
- 选中技能后输入框上方 chip 展示完整的参数格式说明(而非仅 name + description
- 或选中技能后自动在输入框填入模板骨架(如 `/commit `),光标定位到参数位置
---
## 六、窗口与布局
### 6.1 侧栏宽度不可调
**现象**:侧栏固定 160px不可拖拽调整宽度。长对话标题被截断。
**根因**`.ai-conv-sidebar { width: 160px; min-width: 160px; }` 硬编码。
**方案**
- 侧栏右边缘增加 2px 拖拽条(`cursor: col-resize`
- 拖拽时实时更新 width范围 120~280px
- 宽度持久化到 `df-ai-ui` 设置(与 sidebarOpen/maximized 同存)
### 6.2 分离窗口关闭后生成态不同步
**现象**:分离窗口关闭时,如果正在生成,主窗口虽然 `panelOpen=true` 恢复,但 `state.streaming` / `state.currentText` 可能不同步——如果生成中的对话不是主窗口当前活跃对话,主窗口看不到生成态。
**根因**:分离窗口关闭走 `closeDetachedWindow`,清了 localStorage 快照,但主窗口的状态依赖事件路由自然恢复。
**方案**
- 分离窗口关闭时,主窗口检测 `state.generatingConvId` 非空,自动切换到正在生成的对话
- 或在主窗口 header 显示"对话 X 正在生成中"提示条,点击切换过去
---
## 七、其他交互细节
### 7.1 无键盘快捷键
**现象**:除了 Enter 发送 / Shift+Enter 换行,没有其他快捷键。
**方案**
| 快捷键 | 功能 |
|--------|------|
| `Ctrl+N` | 新建对话 |
| `Ctrl+K` | 搜索对话 |
| `Ctrl+L` | 清空当前对话 |
| `Ctrl+Shift+C` | 复制最后一条 AI 消息 |
| `Ctrl+R` | 重新生成最后一条 AI 回复 |
| `Esc` | 关闭面板(嵌入模式)/ 关闭窗口(分离模式) |
| `Ctrl+B` | 切换侧栏 |
### 7.2 消息列表无虚拟滚动
**现象**:消息列表直接 `v-for` 渲染所有消息,长对话几百条消息全量渲染 DOM滚动卡顿。
**根因**:没有使用虚拟滚动库。
**方案**
- 集成 `vue-virtual-scroller` 或自研 IntersectionObserver 懒渲染
- 仅渲染可视区域 ±缓冲区的消息节点
- 注意:流式渲染的最后一条消息需要始终保持挂载
### 7.3 空状态无引导
**现象**:首次打开 AI Chat有 provider 配置),空状态只显示标题+提示语,没有示例问题或快捷操作。
**方案**
- 空状态展示 3-4 个示例问题卡片(如"帮我创建一个新项目"、"查看当前任务列表"、"分析这段代码的问题"
- 点击卡片自动填入输入框并发送
- 无 provider 配置时展示"去配置 AI Provider"引导按钮
### 7.4 对话标题生成对用户不透明
**现象**:对话标题由后端 `ensure_conversation_title` 异步生成,侧栏对话从"新对话"突然变成某个标题,没有过渡。
**方案**
- 标题生成后加 0.3s 淡入动画
- 或在标题前加小图标标识"AI 自动生成"(可 hover 查看)
---
## 八、落地优先级
| 批次 | 痛点 | 理由 |
|------|------|------|
| **第一批** | 2.1 流式选中文字、1.2+1.3 消息操作栏(复制/重新生成、4.1 错误重试、4.2 断线保文 | 每次对话都会遇到,最高频痛点 |
| **第二批** | 2.2 代码高亮+复制、3.1 对话搜索、7.1 键盘快捷键、3.4 新建不中断生成 | 显著提升日常效率 |
| **第三批** | 1.1 编辑重发、1.4 `@` 引用、3.3 导出、7.2 虚拟滚动、6.1 侧栏可调 | 按需推进,锦上添花 |
| **第四批** | 2.3 分页加载、2.4 时间戳、3.2 置顶、5.1+5.2 技能/Provider 反馈、7.3+7.4 空状态/标题 | 打磨细节 |