优化: B-21重复渲染治标(Started幂等守卫)+排查文档+CR-43复审回填
This commit is contained in:
10
docs/todo.md
10
docs/todo.md
@@ -99,6 +99,16 @@
|
||||
- [ ] UX-260616-02 [P3] — **「全部收起」与搜索/技能区合并一行**。当前 ToolCardList.vue 有两个独立行:①batch-approve 栏(line 4-11,pendingCount>0 时显示)②global-toggle 栏(line 14-17,collapsibleGroupCount>0 时显示「▾ 全部收起」)。用户要求将「全部收起」与附近的操作元素(search files 搜索文件/技能触发等)放到同一行,减少垂直空间占用。**需确认**:「search files」具体指哪个 UI 元素(i18n `ai.searchFiles` 渲染位置需定位,可能在输入框上方 skill 栏或工具卡区域)。**改动方向**:global-toggle 从独占行改为 inline 元素,与相邻操作栏 flex 同行。—— ToolCardList.vue(:13-17 template + :278-295 CSS .ai-tool-global-toggle)
|
||||
- [ ] UX-260616-03 [P2] — **对话内容输出时自动收起旧工具卡片分组**。需求:当 AI 输出新内容(流式 delta / 新工具调用 / 新轮次)滚动到下方时,上方已完成的旧消息中的工具卡片分组自动收起,保持视野聚焦当前内容。**现状**:机制已存在——`ToolCardList.collapseInactive(activeIds)`(:191-199)供父组件调用,`AiChat.vue:1657-1661` 已有 watch 调用(refs 数组逐实例 collapseInactive)。**增强方向**:a)触发时机扩展——当前可能仅在特定时机调用,可扩展到:`AiTextDelta` 新消息开始时 + `AiAgentRound` 新轮次时 + 用户滚动接近底部时(跟随阅读位置自动收起已读内容)b)平滑过渡——收起加 CSS transition(高度动画 200ms)避免内容突然消失跳变c)可选:记忆用户手动展开的分组不自动收起(expandedCards Set 区分用户主动展开 vs 默认态)。—— ToolCardList.vue(collapseInactive + CSS transition) + AiChat.vue(watch 触发时机扩展)
|
||||
- [ ] UX-260616-04 [P1] — **审批通过后工具卡片仅显示裸 JSON**。用户实测:审批前 `pending_approval` 态渲染友好(参数键值对+风险提示+审批按钮),审批通过后 `completed` 态 body 仅显示一段 JSON 数据。**根因**:`ToolCard.vue` 模板渲染链对 `read_file`/`list_directory`/`write_file` 有专门 UI 分支,其余工具全部走通用兜底(L110 `v-else-if tc.result && tc.status === 'completed'`)→ `formatToolResult(tc)` → 兜底 `return formatJson(r)` 裸 JSON 美化输出。**代码核对**:①`formatToolResult`(L270-289)只特化 `write_file`,其余全 `formatJson` ②`toolResultSummary`(L476-498,header 摘要)覆盖 `list_tasks`/`list_projects`/`list_ideas`/`create_project`/`create_task`/`create_idea`/`update_project`/`delete_project`/`restore_project`/`purge_project`/`run_workflow` 共 11 工具,但 body 的 `formatToolResult` 不共享此逻辑 ③两函数覆盖范围严重不一致(header 有摘要但 body 展开仍裸 JSON)。**后端返回值核对**(tool_registry.rs):`update_task`→`{id,field,updated}` / `delete_task`→`{deleted,id}` / `delete_file`→`{path,deleted,...}` / `run_command`→`{command,exit_code,stdout,stderr,...}`(可能很大) / `patch_file`→`{path,changed,size_diff,...}` / `append_file`→`{path,bytes_written,new_size}` / `rename_file`→`{from,to,renamed,...}` / `search_files`→`{path,pattern,results,total,...}` / `file_info`→`{path,exists,size,lines,...}` / `bind_directory`→`{id,path,stack,bound}` / `advance_task`→`{...}` ——全部走裸 JSON。**修复方向**:统一 `formatToolResult` 的 switch 覆盖全部工具产出人类可读摘要(复用 `toolResultSummary` 已有 i18n key + 补缺失 key 如 `commandOk`/`commandFailed`/`patchedFile`/`appendedTo`/`renamed`/`foundN` 等),同时补 `toolResultSummary` 缺失 case(`update_task`/`delete_task`/`delete_file`/`run_command`/`patch_file`/`append_file`/`rename_file`/`search_files`/`file_info`/`bind_directory`/`advance_task`)。—— `src/components/ToolCard.vue`(`formatToolResult` L270 + `toolResultSummary` L476) + `src/i18n/{zh-CN,en}/aiTool.ts`(补 key)
|
||||
- [ ] UX-260616-05 [P1] — **打断按钮只停当前回复 + 队列保留续发(不清队列)**。用户需求:发送队列有消息时,打断按钮应只打断当前回复,并把队列中的多条消息一并发送给后续对话。现状:`stopChat`(useAiSend.ts:365-370) L368 `state.queue = []` **清空队列** → 用户生成中排队的消息随打断被**静默丢弃**。技术底座已就绪(已核后端):`stopChat`→`aiApi.stopChat`→后端 `stop_flag.load`(agentic.rs:178)→收尾 `save_conversation`(:185 已生成文本入库)+emit **`AiCompleted`**(agentic.rs:190,注释明确「保证前端收事件时后端已可接下一条,发送队列续发不被拒」)→前端 `ai-drain-queue`→`drainQueue`(useAiSend.ts:228) shift 队首续发。**最小修复(确定)**:删 stopChat L368 `state.queue=[]`,打断后 AiCompleted 自然触发 drainQueue 续发队首;当前未完成回复已入库(:185)保留为截断回复,无需额外处理。**决策点(待定)——「队列多条一并发送」语义**:**(a) 逐条续发**(删清队列即可,零改 drainQueue,AiCompleted 链式触发每条,保各条上下文独立)vs **(b) 合并成一条消息**(队列多条 text 拼接送,需改 drainQueue 为 bulkDrain 或合并 text 调一次 sendMessage)。**倾向 (a)**(逐条保上下文独立+零改链路;"一并"理解为「都送出不丢弃」非字面合并),**待用户确认**。— src/composables/ai/useAiSend.ts(stopChat:365 + drainQueue:228) + 后端 agentic.rs(:178/:190 stop 收尾 emit AiCompleted 已就绪)。
|
||||
- [ ] UX-260616-06 [P2] — **队列消息支持编辑**。用户需求:队列中的消息支持编辑。现状:队列 UI(AiChat.vue:511-516) 每项只有 × 删除(cancelQueued:353),无编辑入口;队列项结构 `{ text, skill?, enqueuedAt }`(stores/ai.ts:70)。**改动(纯前端低风险)**:队列项加「编辑」按钮 → 点击切 inline input(单行 textarea)→ 回车/失焦写回 `state.queue[idx].text`、ESC 取消;新增 `editQueued(index, newText)`(useAiSend.ts) splice 写回 + store 导出 + composable return。**决策点(待定)**:skill 是否可编辑(**倾向只编 text**——skill 来自发送时技能联想,编辑态不暴露 skill 改,简化交互)。i18n 补 edit/save/cancel key。— src/components/AiChat.vue(:511-516 队列项 UI + .ai-queue-item CSS:2856) + src/composables/ai/useAiSend.ts(editQueued 新增) + stores/ai.ts + i18n。
|
||||
- [ ] UX-260616-07 [P2/依赖 UX-05] — **队列消息支持立即发送**。用户需求:队列中的消息支持立即发送。现状:队列项只能 × 删除或等 drainQueue 串行轮到,无插队。**决策点(待定)——「立即发送」语义**:**(a) 插队=打断当前+立即发这条**(splice 出该项→stopChat→sendMessage 该条;复用 UX-05 stop→AiCompleted→drain 链路,当前未完成回复走 stop 截断保留)vs **(b) 移到队首等当前完成优先**(splice 出→unshift 队首,drainQueue 下一轮先发它,不打断当前)。**倾向 (a)**("立即"字面=马上发,且复用 UX-05 链路;但须注意 splice 后调 stopChat 已不清队列(UX-05),避免已发送项重复入队)。**待 UX-05 打断语义定后同批设计**(共享 stop+drain 链路)。— src/components/AiChat.vue(:511-516 队列项加「立即发送」按钮) + src/composables/ai/useAiSend.ts(sendQueuedNow 新增)。
|
||||
- [ ] F-260616-15 [P1] — **AI Chat 上下文管理增强:会话分段(清理上下文不删记录)+ 手动上下文压缩**。两个子需求:
|
||||
|
||||
**① 会话分段(清理上下文,保留记录)**:用户点「清理上下文」后,不清删 DB 消息,而是给当前消息打标记(`status="archived_segment"`),后续新消息作为新段继续同一对话。同一对话内产生 a'/a''/a''' 多段,DB 全量保留可回溯,LLM 上下文只看当前段。**现状**:`ai_chat_clear`(commands.rs:360) 直接 `clear_messages` 置 `messages='[]'`(全删)。`ChatMessage.status`(df-ai-core/provider.rs:59) 已有 `None/"active"/"truncated"` 三态,`is_active()` 过滤 truncated。`ContextManager.build_for_request`(context.rs:219) 发送前已调 `sanitize_messages` 过滤 `!is_active()`。**改动方向**:a) 新增 status 值 `"archived_segment"`(语义=被清理上下文标记,不进 LLM 上下文但前端可见可展开);b) `is_active()` 扩展过滤 archived_segment(不进 build_for_request);c) 新 IPC `ai_chat_clear_context`(区别于 `ai_chat_clear` 全删):给当前所有 active 消息标记 `status="archived_segment"` + 插入一条 system 消息「--- 上下文已清理 ---」做分隔线 + ContextManager 清内存只保留分隔线后的消息;d) 前端:清空按钮拆为「清空对话」(全删,现有)+「清理上下文」(分段标记,新);e) 前端消息列表渲染 archived_segment 段为折叠的灰色分隔区(点击展开看历史段),active 段正常展示。**关键约束**:工具调用三元组不能跨段拆散(整组标记)。
|
||||
|
||||
**② 手动上下文压缩(LLM 摘要)**:用户点「压缩上下文」后,把当前段内所有 active 消息发给 LLM 生成摘要,摘要替换原始消息作为新的上下文起点。**现状**:无压缩能力,`ContextManager` 超预算时直接丢弃旧消息(无摘要保留语义)。`LlmProvider::complete()`(df-ai-core/provider.rs:210) 可用于非流式摘要调用。**改动方向**:a) 新 IPC `ai_chat_compress_context`:取当前 active 消息 → 构建 summary prompt(「请将以下对话总结为关键上下文摘要,保留:用户意图/关键决策/已修改文件/重要约束」)→ `provider.complete()` 调用 → 摘要作为 system 消息替换原始消息(原始消息标记 `status="compressed"` 保留 DB);b) ContextManager 清内存只保留摘要 system + 后续新消息;c) 前端:压缩按钮 + loading 态 + 压缩后显示摘要卡片(可展开看被压缩的原始消息);d) 摘要 prompt 模板放 prompt.rs 可配。**关键约束**:压缩是单向的(不能"解压缩"恢复 LLM 上下文,但 DB 原始消息保留可查看);压缩后 token 计数重置。
|
||||
|
||||
**涉及文件**:`df-ai-core/src/provider.rs`(ChatMessage.status 扩展) + `df-ai/src/context.rs`(build_for_request 过滤) + `src-tauri/src/commands/ai/commands.rs`(2 新 IPC) + `src-tauri/src/commands/ai/prompt.rs`(压缩 prompt 模板) + `src/api/ai.ts` + `src/composables/ai/useAiPanel.ts` + `src/components/AiChat.vue`(按钮 UI + 分段渲染) + `src/i18n/{zh-CN,en}/aiChat.ts`
|
||||
|
||||
- [x] ✅(batch60·2026-06-16·workflow whae812z5+主代核查,cargo 0err) F-260616-13 [P2] — **build_for_request 持锁重活 + system_prompt token 每轮重估**。性能分析批次发现:每轮 `stream_llm` 前 `session_arc.lock()`(`agentic.rs:251`)持锁期间做 `TokenEstimator::estimate_text(system_prompt)`(L252)+ `build_for_request`(L253 history clone + 裁剪)。system_prompt loop 外固定传入,**每轮重估其 token 是浪费**(可缓存);build_for_request 持锁做 history clone 是重活,消息多时(200 cap)锁持有期长。**方向**:①system_prompt token loop 外算一次缓存 ②build_for_request 先 clone messages 释放锁再裁剪。低收益优化。— agentic.rs:251-253。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user