重构: 移除 AI 消息重新生成入口及配套调整
- MessageList: 移除末条 AI 消息重新生成操作栏入口 - package.json: build:dev 改 tauri build --debug --no-bundle(只编译不打包) - 文档: workspace_root 弱化决策 + BUG-260625-01/02 工具修复记录
This commit is contained in:
11
docs/todo.md
11
docs/todo.md
@@ -983,6 +983,17 @@
|
||||
> - **P1**(agentic/mod.rs:985 压缩失败不发 AiError):前端 AiError 无条件停流,降级 warn + build_for_request 裁剪兜底,loop 继续 stream_llm。
|
||||
> - **纠正 workflow 方案 A 矛盾**:workflow 建议 is_active 排除 summarized,但会致 build_for_request 不送摘要进 LLM 丢历史;改用 has_compressible 排除 system(摘要仍 active 进 LLM)。
|
||||
>
|
||||
> **BUG-260625-01 grep 传单文件路径报「目录名称无效」根治**(用户「grep path 需要传目录不能传单文件·怎么总是失败」):
|
||||
> - 根因(DB 铁证 ai_tool_executions fb549a0b):grep handler 直接 `grep_recursive(root)` → 内部 `read_dir(root)`,root 是单文件时报 os error 267「目录名称无效」。grep 命令行原生支持单文件,LLM 常传文件路径(64 次 grep 调用多次传文件),工具只认目录 →「总是失败」。
|
||||
> - 修复(tool_registry.rs):提取 `grep_one_file`(从 grep_recursive 循环体搬移,continue→return Ok),grep_recursive 改调它(DRY 复用),handler 判 `root_meta.is_file()` → 单文件走 grep_one_file,is_dir → 递归。描述加「path 文件或目录均可」。
|
||||
> - 验证:cargo test grep **13 passed**(9 个 grep_recursive 测试全过 = 提取零回归)+ audit 3 grep 测试。
|
||||
>
|
||||
> **BUG-260625-02 read_file search 模式显示「0 行」根治**(用户「读取 MessageList.vue 0 行·65.2KB·怎么总是失败」):
|
||||
> - 根因(DB 铁证 ai_tool_executions cad747e9):read_file 带 `search` 参数返 `{matches:[{line,content}],total,has_more}` **无 content/lines/returned_lines**。ToolResultBody.vue read_file 分支只认 content/lines → 显示「0 行 · 65.2KB」+ 空 content 区。LLM 拿到 matches 能继续,但**用户看 UI 以为读取失败/空**。
|
||||
> - 修复(ToolResultBody.vue + useToolCard.ts):read_file 分支识别 search 模式(parsed.search),header 显「『search』 命中 N 处」(复用 grepHitsN)+ 命中行渲染(复用 grep content 样式,read_file matches 仅 {line,content} 无 file);ToolResult 加 `search?: string` 字段。
|
||||
> - 验证:vue-tsc EXIT 0。
|
||||
> - 附:state.rs/tool_registry.rs test 的 AllowedDirs once 字段初始化是工作区被改掉的预存状态,本次恢复到 HEAD(once 功能早已提交),**未引入新改动**。
|
||||
>
|
||||
> **剩余低优 [ ](非阻断)**:
|
||||
> - [x] ✅(2026-06-24·ebba973) BUG-260624-01 残留:① MessageList:503 scrollToFirstPending return 过早修复(遍历全实例调,各内部 querySelector 无 pending 则 no-op,治 pending 落后续实例滚不到)+ ② ToolCardList listener 加 props.toolCalls.some(pending) 过滤短路(降大列表 N 实例×M emit 的 DOM 查询)。**scrollToFirstPending 时间窗去抖评估后不做**:同 id 连发被上游守卫拦截(AiDirAuthRequired/AiApprovalRequired useAiEvents:359/472 some 去重 + AiToolCallStarted:427 findToolCall 守卫),不同 id 连发是多 pending 合理场景每次滚到首个合理,vue-tsc EXIT 0。
|
||||
> - [ ] BUG-260624-02 残留:审批超时倒计时 UI(5min 无倒计时,ToolCard pending 态显 mm:ss 到期前 60s 变红)+ 核 detached 窗口 emit 作用域(分离窗口是否独立 startListener,否则 ai-pending-arrived 收不到)
|
||||
|
||||
@@ -296,6 +296,8 @@
|
||||
|
||||
**关联**:F-260619-03 去固定根(已删 is_authorized 硬编码放行)/ projects.path 自动授权(reload_allowed_dirs)/ memory [[devflow-project-path-binding]]
|
||||
**状态**:🟡 待决策(workspace_root 运行期动态方案 a/b/c/d)。**分发前必须解决**(否则分发后 AI 文件工具全部失效)。
|
||||
**注**:授权弹窗侧三档(once 本次单次 / session 当前会话 / always 始终)已于 2026-06-24 落地(F-260619-03 Phase B 演进,AllowedDirs 加 once 层 + 弹窗加"当前会话"按钮,详见 todo + 待审查 CR-260624-01)——**授权机制侧就绪**。本节待决策的是**相对路径锚点**(固定编译期 root vs 活跃项目),非授权机制。
|
||||
**2026-06-25 弱化决策(用户)**:方向**去掉"活跃项目/根据地"概念抽象**——active_workspace 全局缓存动态锚曾试加(state.rs OnceLock+SyncRwLock + reload_active_workspace KV active_project_id)后撤回,用户明确不要此层。workspace_root 变量**暂留**(不删,resolve 相对锚/trust_key/run_command working_dir 仍引用),**弱化其默认依赖**。实质方案二选一分发前定:① 去默认白名单(default_with_root 不硬塞 workspace_root,工程根靠 projects.path 自动授权)② 去相对锚(AI 改绝对路径,相对路径不 join)。当前:逻辑不动,变量保留。
|
||||
|
||||
**2026-06-23 进展(BUG-260623-01)**:实测会话 6acb7f9b 暴露 write_file/append_file/rename_file 父目录校验**误用 workspace_root**(tool_registry.rs:1112/1418/1555 原用 `starts_with(workspace_root())`)→ 用户授权的其他项目目录(ai-news 等)parent 不 starts_with devflow 源码目录 → 误拒「禁止在项目目录之外创建目录」(用户授权了仍拒)。**已修**:三处改用 `allowed_dirs.is_authorized(parent)` 脱离 workspace_root。**仍写死残留**(待本决策 a/b/c/d 根治):delete_file:1482 trash 目录 / resolve_workspace_path_impl:380 相对路径 join / run_command:1791 working_dir 默认。
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"build:dev": "cd src-tauri && cargo build"
|
||||
"build:dev": "tauri build --debug --no-bundle"
|
||||
},
|
||||
"dependencies": {
|
||||
"@arco-design/web-vue": "^2.58.0",
|
||||
|
||||
@@ -722,20 +722,6 @@ async function copyMsgContent(msg: AiMessage): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
// ── UX-02: 重新生成最后一条 AI 回复 ──
|
||||
// 复用 store.regenerate(后端 ai_regenerate:弹末尾 AI 回复→重跑 loop)。
|
||||
// 失败 toast 提示,不影响其他状态(后端已复位 generating)。
|
||||
async function handleRegenerate(): Promise<void> {
|
||||
try {
|
||||
await store.regenerate()
|
||||
await nextTick()
|
||||
scrollToBottom()
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
emit('toast', { msg: t('aiChat.regenerateFailed', { msg }), type: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
// ── UX-03: 错误气泡操作入口 ──
|
||||
// errorType 经 useAiEvents push 错误消息时附带(types.ts AiMessage 未含此字段,
|
||||
// 经 cast 读写闭环在 AiChat.vue / useAiEvents 两端,不污染 types.ts 定义)。
|
||||
@@ -1059,33 +1045,8 @@ defineExpose({
|
||||
<span class="ai-cursor"></span>
|
||||
</div>
|
||||
|
||||
<!-- UX-02:AI 消息操作栏(hover 显示:重新生成)。
|
||||
复制走气泡内右下角图标(ai-copy-btn--ai),本栏仅留重新生成,故只在
|
||||
末条 AI 消息(重生成语义)、非错误、有内容、非流式中显示。 -->
|
||||
<div
|
||||
v-if="isLastAi(item.msg) && item.msg.content && !item.msg.isError && !store.state.streaming"
|
||||
class="ai-msg-actions"
|
||||
>
|
||||
<button
|
||||
class="ai-msg-act"
|
||||
:disabled="store.state.streaming"
|
||||
:title="$t('aiChat.regenerate')"
|
||||
@click.stop="handleRegenerate"
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
|
||||
<span>{{ $t('aiChat.regenerate') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- UX-03 / UX-260616-01:错误气泡操作入口(重试 / 去设置)。
|
||||
仅 isError 消息显示;「重试」仅可重试错误(network/timeout/unknown)显示,
|
||||
Fatal 类(auth/provider_config — 鉴权/4xx/参数错)重试必败故隐藏;
|
||||
「去设置」仅 error_type ∈ {auth, provider_config} 显示;
|
||||
Fatal 时附 notRetryable 提示,说明为何无重试按钮。 -->
|
||||
<div
|
||||
v-if="item.msg.isError && !store.state.streaming"
|
||||
class="ai-msg-actions ai-msg-actions--error"
|
||||
>
|
||||
<!-- UX-03 / UX-260616-01:错误气泡操作入口(重试 / 去设置) -->
|
||||
<div v-if="item.msg.isError" class="ai-msg-actions ai-msg-actions--error">
|
||||
<span v-if="!canRetry(item.msg)" class="ai-msg-not-retryable">{{ $t('ai.notRetryable') }}</span>
|
||||
<button
|
||||
v-if="canRetry(item.msg)"
|
||||
|
||||
Reference in New Issue
Block a user