# AIChat 交互体验改进方案 > 创建: 2026-06-14 | 状态: 待讨论 > 范围: 消息发送、流式渲染、对话管理、错误恢复、技能/Provider、窗口布局等非授权类交互 --- ## 一、消息输入与发送 ### 1.1 无法编辑已发送消息 **现象**:用户发出消息后发现措辞有误,只能重新打一条新消息。无法像 ChatGPT/Claude 那样编辑上一条用户消息并重新生成。 **根因**:`sendMessage`(useAiSend.ts)push 后的消息是只追加不可变的。前端没有编辑入口,后端 `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` 枚举(`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 空状态/标题 | 打磨细节 |