From 1dc8b12ec4820adc3707032b2cbdaac9ed27e6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Fri, 19 Jun 2026 00:52:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20aichat=20advance=5Ftask?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E6=98=BE=E4=BB=BB=E5=8A=A1=E5=90=8D=20+=20?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E9=98=B2=E5=8F=8C=E5=87=BB=20+=20time.ts?= =?UTF-8?q?=E9=94=80=E8=B4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UX-260618-14: advance_task 工具结果/审批卡显任务名替代UUID 前端 ToolCard 取 r.title fallback r.id + TASK_ID_TOOL_ARG 审批卡 task_id→title 回显 + i18n 后端 audit resolve_task_label 查 tasks 表(原误查 projects恒报不存在) + 删 tool_display_hint 短路让模板生效 - UX-260618-16: time.ts formatDate .value 已修(CR-24/25闭环),漏销账补登 - UX-260618-17: ProjectDetail handleApprovalMulti 补 submitting try/finally + 模板联动防双击 --- docs/todo.md | 4 +-- docs/待审查.md | 10 ++++++ src-tauri/src/commands/ai/audit.rs | 24 ++++++++++++- src-tauri/src/commands/ai/tool_registry.rs | 3 +- src/components/ToolCard.vue | 39 +++++++++++++++++++--- src/i18n/en/aiTool.ts | 9 +++-- src/i18n/zh-CN/aiTool.ts | 9 +++-- src/views/ProjectDetail.vue | 15 ++++++--- 8 files changed, 94 insertions(+), 19 deletions(-) diff --git a/docs/todo.md b/docs/todo.md index a181ac4..6a3f7ca 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -341,11 +341,11 @@ > 用户实测报 2 bug。review 会话派 2 agent 并行核验源码,根因 + 修法定位完成,待主代实施。 -- [ ] **UX-260618-14 [P1]** — **advance_task 审批卡/结果渲染缺任务名 + 显 UUID/空白**。①审批卡 `ToolCard.vue:800` `PROJECT_ID_TOOL_ARG` 未登记 advance_task → id 裸显 UUID;②审批 reason `audit.rs:163` tool_display_hint 短路致 `tool_registry.rs:1546` 模板死代码 + `:172` 任务 id 误查 projects 表;③结果 `ToolCard.vue:477` 模板取 r.id(UUID)非 r.title(TaskRecord 有 title `models.rs:57`),"空白"=UUID 不可读非字段缺失。**修法 B(最快)**:ToolCard:477 取 r.title fallback r.id + i18n 改 `任务「{title}」已推进至 {status}`;**A1** 登记 TASK_ID_TOOL_ARG(advance_task/run_workflow)id→title 回显;**C** 删 `tool_registry.rs:1572` tool_display_hint 让 :1546 模板生效 + audit.rs 加 resolve_task_label。附带:`:1546/:1547` 模板均死代码 + audit.rs:172 任务 id 误查隐患。— `src/components/ToolCard.vue`(:474-478,800-825) + `src-tauri/src/commands/ai/{tool_registry.rs:1546/1572,audit.rs:163/172}` + `src/i18n/{zh-CN,en}/aiTool.ts` +- [x] ✅(workflow wwchro468 general-purpose agent 实施 6 文件 + 主代兜底核验·前端 ToolCard.vue advance_task 渲染取 r.title||r.id(:474/:891)+TASK_ID_TOOL_ARG 映射(:799)+displayArgValue task_id→title(:834)+i18n zh/en aiTool.ts;后端 audit.rs resolve_task_label(查 tasks 表 :151 三臂对齐 resolve_project_label)+build_approval_reason advance_task 分支(:173)+tool_registry.rs 删 tool_display_hint advance_task 项(:1575 让 :1549 模板生效)·主代 cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证) **UX-260618-14 [P1]** — **advance_task 审批卡/结果渲染缺任务名 + 显 UUID/空白**。①审批卡 `ToolCard.vue:800` `PROJECT_ID_TOOL_ARG` 未登记 advance_task → id 裸显 UUID;②审批 reason `audit.rs:163` tool_display_hint 短路致 `tool_registry.rs:1546` 模板死代码 + `:172` 任务 id 误查 projects 表;③结果 `ToolCard.vue:477` 模板取 r.id(UUID)非 r.title(TaskRecord 有 title `models.rs:57`),"空白"=UUID 不可读非字段缺失。**修法 B(最快)**:ToolCard:477 取 r.title fallback r.id + i18n 改 `任务「{title}」已推进至 {status}`;**A1** 登记 TASK_ID_TOOL_ARG(advance_task/run_workflow)id→title 回显;**C** 删 `tool_registry.rs:1572` tool_display_hint 让 :1546 模板生效 + audit.rs 加 resolve_task_label。附带:`:1546/:1547` 模板均死代码 + audit.rs:172 任务 id 误查隐患。— `src/components/ToolCard.vue`(:474-478,800-825) + `src-tauri/src/commands/ai/{tool_registry.rs:1546/1572,audit.rs:163/172}` + `src/i18n/{zh-CN,en}/aiTool.ts` - [ ] **UX-260618-15 [P1]** — **流式失败重试(1/4 2/4 3/4)每轮独立气泡**。后端每轮重试 emit AiError(`stream_recv.rs:200/258/298/358/398` 5 处 InitFailed 必 emit),前端 `useAiEvents.ts:326` AiError case 每次 push 新气泡无去重;`AiStreamRetry`(:177)本意更新末条合并但被新 AiError 破坏 → N+1 独立气泡。`agentic.rs:262` 注释误以为 stream_llm 只 emit 一次(实多次)。**用户诉求升级**:不只是修 bug,要**聚合显示设计**(重试 1→4 轮过程聚合单气泡 + 最终结果,勿一排错误吓人)。详见 review 会话重试聚合设计方案(3 角度论证:可行性/前后端一致性/架构整合)。**修法 A(推荐)**:`stream_recv.rs` 5 处 InitFailed 移除 AiError emit(改纯返 retryable,emit 权交调用方)+ `agentic.rs` 重试 loop 只 emit AiStreamRetry、耗尽/Fatal 才 emit 最终 AiError。影响仅流前失败重试路径(Fatal/MidStream 保文/正常错误/压缩失败不受影响)。— `src-tauri/src/commands/ai/{stream_recv.rs:200/258/298/358/398,agentic.rs:207-300}` + `src/composables/ai/useAiEvents.ts:177/326` -- [ ] **UX-260618-16 [P1🔴]** — **`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**(CR-260618-24 批2 审查发现·Agent C 铁证)。`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 下 formatDate 永走 zh-CN 分支,i18n 化失效(对齐 CR-260615-08 未完成回归)。铁证:`App.vue:227`/`GeneralPanel.vue:259` 均用 `i18n.global.locale.value`,唯 time.ts:44 漏。**修法**:补 `.value` → `(i18n as any).global.locale.value === 'en'`(locale 取值 'en' 非 'en-US',App.vue:227 印证)。— `src/utils/time.ts:44` +- [x] ✅(主代串行·doc-discovery 源码核验 `time.ts:44` 已 `(i18n as any).global.locale.value === 'en'`·CR-260618-24 修复闭环 + CR-260618-25 A-time 复审 ✅ 双印证·漏销账补登) **UX-260618-16 [P1🔴]** — **`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**(CR-260618-24 批2 审查发现·Agent C 铁证)。`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 下 formatDate 永走 zh-CN 分支,i18n 化失效(对齐 CR-260615-08 未完成回归)。铁证:`App.vue:227`/`GeneralPanel.vue:259` 均用 `i18n.global.locale.value`,唯 time.ts:44 漏。**修法**:补 `.value` → `(i18n as any).global.locale.value === 'en'`(locale 取值 'en' 非 'en-US',App.vue:227 印证)。— `src/utils/time.ts:44` - [ ] **UX-260618-17 [P1🟡]** — **ProjectDetail.handleApprovalMulti 漏 submitting 复位(防双击破口)**(CR-260618-25 前端审查发现·Agent B 对抗核验)。`ProjectDetail.vue:431-437` handleApprovalMulti 无 submitting set true/finally,模板 :196-202 只绑 `:disabled="multiDecisions.length === 0"` → 多选审批 approveHumanApproval IPC 进行中按钮不禁用,用户可重复点确认重复触发 IPC。同模板 handleApproval(:417-425)正确 try/finally,handleApprovalMulti 漏对齐。**修法**:顶 `submitting.value = true` + `try { ... } finally { submitting.value = false }`,对齐 handleApproval。— `src/views/ProjectDetail.vue`(:431-437 + :196-202) diff --git a/docs/待审查.md b/docs/待审查.md index 07c3b43..20a70cc 100644 --- a/docs/待审查.md +++ b/docs/待审查.md @@ -625,6 +625,16 @@ 1. `Knowledge.vue submitCreate`(:438)/ `ProjectDetail.vue handleApproval`(:417) 缺显式 `if(submitting) return` 入口 guard(其余 5 处有)。实际防并发由模板 `:disabled="submitting"` 兜住(按钮点不动·无重入),IPC 路径单调用,风险极低。可选补 guard 对齐范式一致性。 2. `ConnectionPanel.vue saveConn`(:152) 是同步函数(无 await),submitting try/finally 同步置位即复位,此处 submitting 实际无防双击语义(纯占位/视觉)。无功能影响,可选移除或改 async。 +### CR-260619-01 UX-14 advance_task渲染缺任务名 + UX-16 time.ts销账 + UX-17 handleApprovalMulti防双击(workflow wwchro468 UX-14实施 + 主代串行 UX-16/17·主代兜底核验) — 🟡 待审 + +- **范围**: + - **UX-260618-14 advance_task 渲染缺任务名**(workflow wwchro468 general-purpose agent 实施 6 文件):前端 `ToolCard.vue` advance_task 渲染取 `r.title||r.id`(:474 formatToolResult / :891 toolResultSummary)+ 新增 `TASK_ID_TOOL_ARG` 映射(:799 advance_task→id / run_workflow→task_id)+ `displayArgValue` 接入 task_id→title(:834)+ i18n zh/en `aiTool.ts`(advancedTask/advancedTaskWithRounds 改 {title} + taskLabel/taskIdNotFound);后端 `audit.rs` 新增 `resolve_task_label`(查 tasks 表 :151 三臂 Ok(Some)/Ok(None)/Err 对齐 resolve_project_label)+ `build_approval_reason` advance_task 分支(:173 id 走 task 解析)+ `tool_registry.rs` 删 `tool_display_hint` 的 advance_task 项(:1575 让 :1549 模板「推进任务状态:{} → {}」生效)。 + - **UX-260618-16 time.ts .value 销账**:主代核验已修(CR-24/25 闭环),漏销账补登。 + - **UX-260618-17 handleApprovalMulti 补 submitting**(主代串行):`ProjectDetail.vue` :431 try/finally + 模板 :198 `:disabled="submitting || multiDecisions.length===0"` 对齐单选 handleApproval。 +- **验证(主代兜底,不信 agent 自报)**:cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证 resolve_task_label 三臂 / tool_display_hint 删 advance_task 项 / ToolCard title 取值。 +- **审查要点(供审查 agent)**:① UX-14 行为变更——审批 reason 文案(advance_task 从静态串「推进任务状态」→ 含任务标题+target_status)+ audit resolve 查 tasks 表(原误查 projects 恒报「项目已不存在」);② resolve_task_label 三臂语义对齐 resolve_project_label(空 id/Ok(None)/Err 分流);③ tool_display_hint 删 advance_task 后 run_workflow 保留轻量命中(模板 task_id 走通用裸显,本批不动);④ 跨项目任务 taskNameById 查 projectStore.tasks 查不到降级「任务不在当前列表」(非硬错);⑤ UX-17 handleApprovalMulti submitting 对齐 handleApproval 防双击。 +- **关联**:todo UX-260618-14/16/17 销账。 + --- ## 已审归档 diff --git a/src-tauri/src/commands/ai/audit.rs b/src-tauri/src/commands/ai/audit.rs index 81f530b..0479751 100644 --- a/src-tauri/src/commands/ai/audit.rs +++ b/src-tauri/src/commands/ai/audit.rs @@ -8,7 +8,7 @@ use tauri::{AppHandle, Emitter, State}; use df_ai::ai_tools::{AiToolRegistry, RiskLevel}; use df_ai::provider::ChatMessage; -use df_storage::crud::{AiToolExecutionRepo, ProjectRepo}; +use df_storage::crud::{AiToolExecutionRepo, ProjectRepo, TaskRepo}; use df_storage::db::Database; use df_storage::models::AiToolExecutionRecord; @@ -143,6 +143,26 @@ async fn resolve_project_label(db: &Arc, id: &str) -> String { } } +/// 查任务可读标签:id → "「任务标题」(id=x)",对齐 resolve_project_label 三臂语义。 +/// +/// UX-260618-14:advance_task 审批卡的 id 是 task_id,原 build_approval_reason 把 "id" +/// 统一走 resolve_project_label(查 projects 表),误把任务 id 当项目 id 解析,永远落到 +/// "项目已不存在"。本方法改查 tasks 表,与 resolve_project_label 同 Ok(None)/Err 分流。 +async fn resolve_task_label(db: &Arc, id: &str) -> String { + if id.is_empty() { + return String::new(); + } + let repo = TaskRepo::new(db); + match repo.get_by_id(id).await { + Ok(Some(t)) => format!("「{}」(id={})", t.title, id), + Ok(None) => format!("(任务已不存在, id={})", id), + Err(e) => { + tracing::warn!("resolve_task_label: 查询任务 id={} 失败,回退裸 id: {}", id, e); + format!("(id={})", id) + } + } +} + /// 拼审批 reason:按工具名 + args 取可读字段,让用户知道"审批要做什么"。 /// /// 主要审批工具特化(带对象名/标识): @@ -170,6 +190,8 @@ async fn build_approval_reason( let val = s(key); if val.is_empty() { values.clear(); break; } match key { + // advance_task 的 id 是 task_id(非项目 id),改查 tasks 表,避免误报"项目已不存在" + "id" if tool_name == "advance_task" => values.push(resolve_task_label(db, &val).await), "id" | "project_id" => values.push(resolve_project_label(db, &val).await), _ => values.push(val.to_string()), } diff --git a/src-tauri/src/commands/ai/tool_registry.rs b/src-tauri/src/commands/ai/tool_registry.rs index c84d658..2b1512a 100644 --- a/src-tauri/src/commands/ai/tool_registry.rs +++ b/src-tauri/src/commands/ai/tool_registry.rs @@ -1572,7 +1572,8 @@ pub(crate) fn tool_display_hint(name: &str) -> Option<&'static str> { "bind_directory" => Some("绑定目录"), "create_idea" => Some("捕获灵感"), "update_task" => Some("修改任务"), - "advance_task" => Some("推进任务状态"), + // advance_task 不在此轻量映射:让 build_approval_reason 走 display_hint_for_tool + // 模板(含 target_status 详情),否则轻量命中"推进任务状态"丢失目标态信息(UX-260618-14) "run_workflow" => Some("触发工作流"), "delete_task" => Some("删除任务"), "update_idea" => Some("修改灵感"), diff --git a/src/components/ToolCard.vue b/src/components/ToolCard.vue index e761773..52f4cb1 100644 --- a/src/components/ToolCard.vue +++ b/src/components/ToolCard.vue @@ -473,9 +473,10 @@ function formatToolResult(tc: AiToolCallInfo): string { : t('aiTool.updated') case 'advance_task': // UX-260618-09: review_rounds>0 显退回累加轮数,=0 回退原文案(首推无噪音) + // UX-260618-14: 后端返 TaskRecord(含 title),优先显 title,无则回退 id(防裸 UUID) return typeof r.review_rounds === 'number' && r.review_rounds > 0 - ? t('aiTool.advancedTaskWithRounds', { id: r.id || '', status: r.status || '', n: r.review_rounds }) - : t('aiTool.advancedTask', { id: r.id || '', status: r.status || '' }) + ? t('aiTool.advancedTaskWithRounds', { title: r.title || r.id || '', status: r.status || '', n: r.review_rounds }) + : t('aiTool.advancedTask', { title: r.title || r.id || '', status: r.status || '' }) case 'delete_task': return r.deleted ? t('aiTool.deleted') : t('aiTool.deleteIneffective') case 'delete_file': @@ -796,6 +797,16 @@ function projectNameById(id: string): string | undefined { ?? projectStore.deletedProjects.find(p => p.id === id)?.name } +/** + * 按 id 查任务标题:审批 advance_task/run_workflow 的 task_id 时,把裸 UUID 转标题。 + * 复用 project store 已加载的 state.tasks(仅当前项目范围),不新增网络请求。 + * state.tasks 非「全量」(按当前项目/筛选加载),查不到时调用方走裸 id fallback—— + * 审批卡 task_id 多属当前活动项目,命中率足够,跨项目任务降级显 id 可接受。 + */ +function taskNameById(id: string): string | undefined { + return projectStore.tasks.find(t => t.id === id)?.title +} + /** 工具名 → 该工具中代表项目 id 的参数 key(仅项目类工具特化;delete_task 的 id 是任务 id,不在此列) */ const PROJECT_ID_TOOL_ARG: Record = { delete_project: 'id', @@ -806,6 +817,15 @@ const PROJECT_ID_TOOL_ARG: Record = { create_task: 'project_id', } +/** + * 工具名 → 该工具中代表任务 id 的参数 key(任务类工具特化)。 + * advance_task 的任务 id 在 "id" key,run_workflow 在 "task_id" key。 + */ +const TASK_ID_TOOL_ARG: Record = { + advance_task: 'id', + run_workflow: 'task_id', +} + /** * 审批参数值展示(AR-3):对项目类工具的 id/project_id 特化——优先回显项目名,查不到则显示 * 「项目已不存在」提示 + 原 id,避免用户只看到一串裸 id 不知道是什么数据。 @@ -821,6 +841,16 @@ function displayArgValue(arg: { key: string; val: unknown }): string { return t('aiTool.projectIdNotFound', { id }) } } + // UX-260618-14:advance_task/run_workflow 的 task_id → 任务标题(store.tasks 查,无则标 id) + const taskArgKey = TASK_ID_TOOL_ARG[props.tc.name] + if (taskArgKey && arg.key === taskArgKey) { + const id = typeof arg.val === 'string' ? arg.val : '' + if (id) { + const name = taskNameById(id) + if (name) return t('aiTool.taskLabel', { name }) + return t('aiTool.taskIdNotFound', { id }) + } + } return formatArgValue(arg.val) } @@ -889,8 +919,9 @@ function toolResultSummary(tc: AiToolCallInfo): string { ? t('aiTool.updatedTaskField', { id: r.id || '', field: r.field || '' }) : t('aiTool.updated') case 'advance_task': return typeof r.review_rounds === 'number' && r.review_rounds > 0 - ? t('aiTool.advancedTaskWithRounds', { id: r.id || '', status: r.status || '', n: r.review_rounds }) - : t('aiTool.advancedTask', { id: r.id || '', status: r.status || '' }) + // UX-260618-14: 优先显 title(后端返 TaskRecord),无则回退 id,防裸 UUID + ? t('aiTool.advancedTaskWithRounds', { title: r.title || r.id || '', status: r.status || '', n: r.review_rounds }) + : t('aiTool.advancedTask', { title: r.title || r.id || '', status: r.status || '' }) case 'delete_project': case 'delete_task': return r.deleted ? t('aiTool.deleted') : t('aiTool.deleteIneffective') case 'delete_file': return r.permanent diff --git a/src/i18n/en/aiTool.ts b/src/i18n/en/aiTool.ts index 3d5e4b4..0761685 100644 --- a/src/i18n/en/aiTool.ts +++ b/src/i18n/en/aiTool.ts @@ -64,9 +64,9 @@ export default { // UX-260616-04: formatToolResult covers all tools with human-readable summaries (missing keys) updatedTaskField: 'Task {id} updated field {field}', - advancedTask: 'Task {id} advanced to {status}', - // UX-260618-09: advance_task return-round accumulation (shown only when >0) - advancedTaskWithRounds: 'Task {id} advanced to {status} (returned {n} times)', + advancedTask: 'Task「{title}」 advanced to {status}', + // UX-260618-09: advance_task return-round accumulation (shown only when >0); UX-260618-14: {title} = task name (TaskRecord), fallback id + advancedTaskWithRounds: 'Task「{title}」 advanced to {status} (returned {n} times)', commandOk: 'Command succeeded ({ms} ms)', commandFailed: 'Command failed (exit code {code})', commandOutputLines: '{n} lines of output', @@ -97,6 +97,9 @@ export default { projectLabel: '"{name}"', projectIdNotFound: '(project not found, id={id})', + // UX-260618-14: advance_task/run_workflow task_id → task title (store.tasks lookup; fallback id) + taskLabel: '"{name}"', + taskIdNotFound: '(task not in current list, id={id})', // AE-2025-05: High-risk approval second confirm (ask once more before approve; misclick cancellable) confirmHighDelete: 'Permanently delete? This action cannot be undone', diff --git a/src/i18n/zh-CN/aiTool.ts b/src/i18n/zh-CN/aiTool.ts index 85b152c..c526206 100644 --- a/src/i18n/zh-CN/aiTool.ts +++ b/src/i18n/zh-CN/aiTool.ts @@ -65,9 +65,9 @@ export default { // UX-260616-04:formatToolResult 覆盖全部工具产出人类可读摘要(补缺失 key) updatedTaskField: '任务 {id} 已更新字段 {field}', - advancedTask: '任务 {id} 已推进至 {status}', - // UX-260618-09:advance_task 退回累加轮数(>0 才显) - advancedTaskWithRounds: '任务 {id} 已推进至 {status}(已退回 {n} 次)', + advancedTask: '任务「{title}」已推进至 {status}', + // UX-260618-09:advance_task 退回累加轮数(>0 才显);UX-260618-14:{title} 显任务名(后端返 TaskRecord),回退 id + advancedTaskWithRounds: '任务「{title}」已推进至 {status}(已退回 {n} 次)', commandOk: '命令成功({ms} 毫秒)', commandFailed: '命令失败(退出码 {code})', commandOutputLines: '{n} 行输出', @@ -99,6 +99,9 @@ export default { // 审批参数特化(id/project_id 回显项目名,查不到标注原 id) projectLabel: '「{name}」', projectIdNotFound: '(项目已不存在, id={id})', + // UX-260618-14:advance_task/run_workflow 的 task_id 回显任务标题(查 store.tasks,无则标 id) + taskLabel: '「{name}」', + taskIdNotFound: '(任务不在当前列表, id={id})', // AE-2025-05:High 风险审批二次确认文案(批准前再问一次,误点可取消) confirmHighDelete: '确定永久删除?此操作不可恢复', diff --git a/src/views/ProjectDetail.vue b/src/views/ProjectDetail.vue index 35d5775..bf113c6 100644 --- a/src/views/ProjectDetail.vue +++ b/src/views/ProjectDetail.vue @@ -195,7 +195,7 @@