新增: AR-11 数据变更联动(AI 工具执行后 emit df-data-changed+store listen 刷新列表)
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
This commit is contained in:
@@ -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;
|
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 进审批门控
|
/// 处理流式接收的工具调用:Low 风险并行执行(join_all),Med/High 进审批门控
|
||||||
/// 返回待审批的工具数量(0 = 全部自动执行完成)
|
/// 返回待审批的工具数量(0 = 全部自动执行完成)
|
||||||
pub(crate) async fn process_tool_calls(
|
pub(crate) async fn process_tool_calls(
|
||||||
@@ -307,6 +348,9 @@ pub(crate) async fn process_tool_calls(
|
|||||||
result: val.clone(),
|
result: val.clone(),
|
||||||
conversation_id: Some(conv_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()))
|
(draft, Ok(val.to_string()))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::state::AppState;
|
|||||||
use crate::commands::{err_str, now_millis};
|
use crate::commands::{err_str, now_millis};
|
||||||
|
|
||||||
use super::agentic::{run_agentic_loop, try_continue_agent_loop};
|
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::conversation::save_conversation;
|
||||||
use super::knowledge_inject::build_knowledge_context;
|
use super::knowledge_inject::build_knowledge_context;
|
||||||
use super::prompt::build_system_prompt;
|
use super::prompt::build_system_prompt;
|
||||||
@@ -172,6 +172,12 @@ pub async fn ai_approve(
|
|||||||
// 审计:人工审批后无论成败回填(决策者=human)
|
// 审计:人工审批后无论成败回填(决策者=human)
|
||||||
audit_finalize(&state, &tool_call_id, audit_status, Some(result_val.to_string())).await;
|
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 据此决定下一步)
|
// 重新获取锁,替换占位 tool_result 为真实结果(失败时为错误信息,LLM 据此决定下一步)
|
||||||
let mut session = state.ai_session.lock().await;
|
let mut session = state.ai_session.lock().await;
|
||||||
session.messages.replace_tool_result_content(&id, &result_val.to_string());
|
session.messages.replace_tool_result_content(&id, &result_val.to_string());
|
||||||
|
|||||||
@@ -214,6 +214,8 @@ onMounted(async () => {
|
|||||||
window.addEventListener('keydown', handleKeydown)
|
window.addEventListener('keydown', handleKeydown)
|
||||||
// 加载知识库候选数(供侧栏 knowledge badge 显示)
|
// 加载知识库候选数(供侧栏 knowledge badge 显示)
|
||||||
knowledgeStore.loadCandidates()
|
knowledgeStore.loadCandidates()
|
||||||
|
// AR-11:数据变更联动监听(AI 工具 create/update/delete 后端 emit df-data-changed → store listen 刷新对应列表,全局挂载因用户可在任意页)
|
||||||
|
await projectStore.startDataChangedListener()
|
||||||
})
|
})
|
||||||
|
|
||||||
/// 一次性迁移:遍历遗留的 localStorage key,若有值则导入 appSettings(SQLite),
|
/// 一次性迁移:遍历遗留的 localStorage key,若有值则导入 appSettings(SQLite),
|
||||||
@@ -254,6 +256,7 @@ const candidateCount = computed(() => knowledgeStore.candidateCount)
|
|||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('keydown', handleKeydown)
|
window.removeEventListener('keydown', handleKeydown)
|
||||||
|
projectStore.stopDataChangedListener?.()
|
||||||
})
|
})
|
||||||
|
|
||||||
const primaryNav = [
|
const primaryNav = [
|
||||||
|
|||||||
@@ -145,6 +145,21 @@ export type UpdateField = {
|
|||||||
value: string
|
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 聊天
|
// AI 聊天
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { reactive, computed } from 'vue'
|
import { reactive, computed } from 'vue'
|
||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import { createProjectsStore } from './project/projects'
|
import { createProjectsStore } from './project/projects'
|
||||||
import { createTasksStore } from './project/tasks'
|
import { createTasksStore } from './project/tasks'
|
||||||
import { createIdeasStore } from './project/ideas'
|
import { createIdeasStore } from './project/ideas'
|
||||||
import { createWorkflowStore } from './project/workflow'
|
import { createWorkflowStore } from './project/workflow'
|
||||||
import { state, clearError } from './project/state'
|
import { state, clearError } from './project/state'
|
||||||
|
import type { DfDataChangedPayload } from '../api/types'
|
||||||
|
|
||||||
// ── barrel 组合器(零行为变更,纯重构) ──
|
// ── barrel 组合器(零行为变更,纯重构) ──
|
||||||
//
|
//
|
||||||
@@ -20,6 +22,39 @@ function createStore() {
|
|||||||
const ideasStore = createIdeasStore()
|
const ideasStore = createIdeasStore()
|
||||||
const workflowStore = createWorkflowStore()
|
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<DfDataChangedPayload>('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(() => ({
|
const stats = computed(() => ({
|
||||||
ideas: state.ideas.length,
|
ideas: state.ideas.length,
|
||||||
@@ -70,6 +105,9 @@ function createStore() {
|
|||||||
clearLiveEvents: workflowStore.clearLiveEvents,
|
clearLiveEvents: workflowStore.clearLiveEvents,
|
||||||
approveHumanApproval: workflowStore.approveHumanApproval,
|
approveHumanApproval: workflowStore.approveHumanApproval,
|
||||||
cancelHumanApproval: workflowStore.cancelHumanApproval,
|
cancelHumanApproval: workflowStore.cancelHumanApproval,
|
||||||
|
// AR-11(方案A):数据变更联动刷新监听(后端工具执行后 emit df-data-changed)
|
||||||
|
startDataChangedListener,
|
||||||
|
stopDataChangedListener,
|
||||||
pendingApproval: computed(() => state.pendingApproval),
|
pendingApproval: computed(() => state.pendingApproval),
|
||||||
// computed
|
// computed
|
||||||
stats,
|
stats,
|
||||||
|
|||||||
Reference in New Issue
Block a user