From dc27e79fd20188d627097457ef66fa3be8df4b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Mon, 15 Jun 2026 05:54:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20AR-11=20=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=8F=98=E6=9B=B4=E8=81=94=E5=8A=A8(AI=20=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E6=89=A7=E8=A1=8C=E5=90=8E=20emit=20df-data-changed+s?= =?UTF-8?q?tore=20listen=20=E5=88=B7=E6=96=B0=E5=88=97=E8=A1=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit audit.rs data_change_for_tool 映射(create_project/idea/task→create,update_*→update,delete_*/purge→delete,restore/bind_directory→update)+emit_data_changed(process_tool_calls 已有 app_handle 无需透传);commands.rs ai_approve 成功路径 emit;types.ts DfDataChangedPayload;project.ts barrel listen df-data-changed + start/stopDataChangedListener 单例;App.vue onMounted 全局挂载+onUnmounted 清理。AI 工具(create/update/delete project/task/idea)执行后前端 Projects/Tasks/Ideas 列表自动刷新。批7,cargo+vue-tsc 0 --- src-tauri/src/commands/ai/audit.rs | 44 +++++++++++++++++++++++++++ src-tauri/src/commands/ai/commands.rs | 8 ++++- src/App.vue | 3 ++ src/api/types.ts | 15 +++++++++ src/stores/project.ts | 38 +++++++++++++++++++++++ 5 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/commands/ai/audit.rs b/src-tauri/src/commands/ai/audit.rs index 0f8b1c4..06bbc81 100644 --- a/src-tauri/src/commands/ai/audit.rs +++ b/src-tauri/src/commands/ai/audit.rs @@ -230,6 +230,47 @@ pub(crate) async fn audit_finalize(state: &AppState, tool_call_id: &str, status: let _ = state.ai_tool_executions.update_full(&rec).await; } +/// AR-11(方案A):工具名 → (entity, action) 映射,用于数据变更联动刷新。 +/// +/// 仅数据变更类工具返回 Some,其余工具返回 None(不 emit df-data-changed)。 +/// 映射规则: +/// - create_project/create_idea/create_task → (project/idea/task, create) +/// - update_project/update_task/update_idea → (..., update) +/// - delete_project/delete_task/delete_idea/purge_project → (..., delete) +/// - restore_project → (project, update)(恢复等同状态变更,前端走 update 刷新) +/// - bind_directory → (project, update)(绑定目录属项目字段更新) +fn data_change_for_tool(name: &str) -> Option<(&'static str, &'static str)> { + let (entity, action) = match name { + "create_project" => ("project", "create"), + "create_task" => ("task", "create"), + "create_idea" => ("idea", "create"), + "update_project" => ("project", "update"), + "update_task" => ("task", "update"), + "update_idea" => ("idea", "update"), + "delete_project" | "purge_project" => ("project", "delete"), + "delete_task" => ("task", "delete"), + "delete_idea" => ("idea", "delete"), + "restore_project" => ("project", "update"), + "bind_directory" => ("project", "update"), + _ => return None, + }; + Some((entity, action)) +} + +/// AR-11(方案A):工具执行成功后按映射 emit "df-data-changed" { entity, action }。 +/// +/// 前端 store listen 该事件,按 entity 调 loadProjects/loadTasks/loadIdeas 刷新列表, +/// 取代工具执行后需手动刷新。仅工具名命中 data_change_for_tool 映射才 emit, +/// 其余工具(run_workflow/list_tasks 等)不 emit。emit 失败不阻断(let _ =)。 +pub(crate) fn emit_data_changed(app_handle: &AppHandle, tool_name: &str) { + if let Some((entity, action)) = data_change_for_tool(tool_name) { + let _ = app_handle.emit( + "df-data-changed", + serde_json::json!({ "entity": entity, "action": action }), + ); + } +} + /// 处理流式接收的工具调用:Low 风险并行执行(join_all),Med/High 进审批门控 /// 返回待审批的工具数量(0 = 全部自动执行完成) pub(crate) async fn process_tool_calls( @@ -307,6 +348,9 @@ pub(crate) async fn process_tool_calls( result: val.clone(), conversation_id: Some(conv_clone), }); + // AR-11(方案A):数据变更工具执行成功后 emit df-data-changed, + // 前端 store listen 刷新列表(仅命中映射的工具 emit,见 emit_data_changed)。 + emit_data_changed(&app_clone, &draft.name); (draft, Ok(val.to_string())) } Err(e) => { diff --git a/src-tauri/src/commands/ai/commands.rs b/src-tauri/src/commands/ai/commands.rs index 0c84954..bbef506 100644 --- a/src-tauri/src/commands/ai/commands.rs +++ b/src-tauri/src/commands/ai/commands.rs @@ -13,7 +13,7 @@ use crate::state::AppState; use crate::commands::{err_str, now_millis}; use super::agentic::{run_agentic_loop, try_continue_agent_loop}; -use super::audit::audit_finalize; +use super::audit::{audit_finalize, emit_data_changed}; use super::conversation::save_conversation; use super::knowledge_inject::build_knowledge_context; use super::prompt::build_system_prompt; @@ -172,6 +172,12 @@ pub async fn ai_approve( // 审计:人工审批后无论成败回填(决策者=human) audit_finalize(&state, &tool_call_id, audit_status, Some(result_val.to_string())).await; + // AR-11(方案A):人工审批通过且工具执行成功(非 failed)后 emit df-data-changed, + // 前端 store listen 刷新列表(仅命中映射的工具 emit,见 emit_data_changed)。 + if exec_result.is_ok() { + emit_data_changed(&app, &approval.tool_name); + } + // 重新获取锁,替换占位 tool_result 为真实结果(失败时为错误信息,LLM 据此决定下一步) let mut session = state.ai_session.lock().await; session.messages.replace_tool_result_content(&id, &result_val.to_string()); diff --git a/src/App.vue b/src/App.vue index ea3b919..c32c7e4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -214,6 +214,8 @@ onMounted(async () => { window.addEventListener('keydown', handleKeydown) // 加载知识库候选数(供侧栏 knowledge badge 显示) knowledgeStore.loadCandidates() + // AR-11:数据变更联动监听(AI 工具 create/update/delete 后端 emit df-data-changed → store listen 刷新对应列表,全局挂载因用户可在任意页) + await projectStore.startDataChangedListener() }) /// 一次性迁移:遍历遗留的 localStorage key,若有值则导入 appSettings(SQLite), @@ -254,6 +256,7 @@ const candidateCount = computed(() => knowledgeStore.candidateCount) onUnmounted(() => { window.removeEventListener('keydown', handleKeydown) + projectStore.stopDataChangedListener?.() }) const primaryNav = [ diff --git a/src/api/types.ts b/src/api/types.ts index 32f4699..1d5202f 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -145,6 +145,21 @@ export type UpdateField = { value: string } +// ============================================================ +// 数据变更联动刷新(AR-11 方案A) +// ============================================================ + +/** + * 后端 AI 工具(create/update/delete/restore/purge/bind_directory 等)执行成功后 + * emit 的 "df-data-changed" 事件负载。前端 store listen 后按 entity 调对应 load 刷新列表。 + */ +export interface DfDataChangedPayload { + /** 变更实体:project / task / idea */ + entity: 'project' | 'task' | 'idea' + /** 变更动作:create / update / delete */ + action: 'create' | 'update' | 'delete' +} + // ============================================================ // AI 聊天 // ============================================================ diff --git a/src/stores/project.ts b/src/stores/project.ts index 1d5057a..68bed8c 100644 --- a/src/stores/project.ts +++ b/src/stores/project.ts @@ -1,9 +1,11 @@ import { reactive, computed } from 'vue' +import { listen } from '@tauri-apps/api/event' import { createProjectsStore } from './project/projects' import { createTasksStore } from './project/tasks' import { createIdeasStore } from './project/ideas' import { createWorkflowStore } from './project/workflow' import { state, clearError } from './project/state' +import type { DfDataChangedPayload } from '../api/types' // ── barrel 组合器(零行为变更,纯重构) ── // @@ -20,6 +22,39 @@ function createStore() { const ideasStore = createIdeasStore() const workflowStore = createWorkflowStore() + // ── AR-11(方案A):数据变更联动刷新 ── + // 后端 AI 工具(create/update/delete/restore/purge/bind_directory)执行成功后 emit + // "df-data-changed" { entity, action },前端按 entity 调对应 load 刷新列表, + // 取代工具执行后需手动刷新。 + let _dataChangedUnlisten: (() => void) | null = null + + async function startDataChangedListener() { + if (_dataChangedUnlisten) return _dataChangedUnlisten + _dataChangedUnlisten = await listen('df-data-changed', (event) => { + const { entity } = event.payload + // 按 entity 调对应 load 刷新(action 仅语义标识,刷新统一走 load 全量拉取) + if (entity === 'project') { + void projectsStore.loadProjects() + } else if (entity === 'task') { + void tasksStore.loadTasks() + } else if (entity === 'idea') { + void ideasStore.loadIdeas() + } + }) + return _dataChangedUnlisten + } + + function stopDataChangedListener() { + if (_dataChangedUnlisten) { + try { + _dataChangedUnlisten() + } catch (e) { + console.error('停止数据变更监听失败:', e) + } + _dataChangedUnlisten = null + } + } + // ── 统计(跨领域,留在组合器) ── const stats = computed(() => ({ ideas: state.ideas.length, @@ -70,6 +105,9 @@ function createStore() { clearLiveEvents: workflowStore.clearLiveEvents, approveHumanApproval: workflowStore.approveHumanApproval, cancelHumanApproval: workflowStore.cancelHumanApproval, + // AR-11(方案A):数据变更联动刷新监听(后端工具执行后 emit df-data-changed) + startDataChangedListener, + stopDataChangedListener, pendingApproval: computed(() => state.pendingApproval), // computed stats,