新增: 初始化 DevFlow 项目仓库
Tauri 2 + Vue 3 + Vite 6 桌面应用,Rust workspace 含 13 个 crate (df-ai / df-storage / df-workflow / df-core / df-execute 等)。 核心能力:AI 聊天 agentic 循环(工具调用+人工审批)、工作流引擎、 任务/想法/项目/阶段管理、可追溯性,及配套前端组件。
This commit is contained in:
187
src-tauri/src/commands/workflow.rs
Normal file
187
src-tauri/src/commands/workflow.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
//! 工作流相关命令 — 触发执行、查询执行记录
|
||||
//!
|
||||
//! 事件转发:run_workflow 在执行前订阅事件总线,
|
||||
//! 将 WorkflowEvent 包装为 `{ execution_id, event }` 通过 `workflow-event` 事件发给前端。
|
||||
|
||||
use serde::Serialize;
|
||||
use tauri::{AppHandle, Emitter, State};
|
||||
use tokio::sync::broadcast::error::RecvError;
|
||||
|
||||
use df_core::events::{WorkflowEvent, HumanApprovalResponse};
|
||||
use df_core::types::new_id;
|
||||
use df_storage::crud::WorkflowRepo;
|
||||
use df_storage::models::WorkflowRecord;
|
||||
use df_workflow::dag_def::DagDef;
|
||||
use df_workflow::executor::DagExecutor;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::now_millis;
|
||||
|
||||
/// 转发到前端的事件载荷
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct WorkflowEventPayload {
|
||||
/// 工作流执行记录 ID
|
||||
execution_id: String,
|
||||
/// 原始工作流事件(serde tag = "type")
|
||||
event: WorkflowEvent,
|
||||
}
|
||||
|
||||
/// 触发工作流执行(核心命令)
|
||||
///
|
||||
/// 流程:build_dag 校验 → 写入执行记录(status=running) → 后台异步执行 →
|
||||
/// 事件经 EventBus 转发到前端 → 完成后更新执行记录状态。
|
||||
/// 立即返回执行记录 ID,前端凭此关联后续事件。
|
||||
#[tauri::command]
|
||||
pub async fn run_workflow(
|
||||
app: AppHandle,
|
||||
state: State<'_, AppState>,
|
||||
name: String,
|
||||
dag: DagDef,
|
||||
config: serde_json::Value,
|
||||
) -> Result<String, String> {
|
||||
// 1. 先构建运行时 DAG,校验失败直接返回,不落库
|
||||
let runtime_dag = state.registry.build_dag(&dag).map_err(|e| e.to_string())?;
|
||||
|
||||
// 2. 写入执行记录(status=running)
|
||||
let execution_id = new_id();
|
||||
let dag_json = serde_json::to_string(&dag).map_err(|e| e.to_string())?;
|
||||
let record = WorkflowRecord {
|
||||
id: execution_id.clone(),
|
||||
name,
|
||||
dag_json,
|
||||
status: "running".to_string(),
|
||||
triggered_by: Some("manual".to_string()),
|
||||
project_id: None,
|
||||
task_id: None,
|
||||
created_at: now_millis(),
|
||||
completed_at: None,
|
||||
};
|
||||
state
|
||||
.workflows
|
||||
.insert(record)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// 3. 订阅事件总线,把事件转发给前端(收到完成/失败事件后退出)
|
||||
let mut rx = state.event_bus.subscribe();
|
||||
let forward_app = app.clone();
|
||||
let forward_exec_id = execution_id.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
let finished = matches!(
|
||||
event,
|
||||
WorkflowEvent::WorkflowCompleted { .. }
|
||||
| WorkflowEvent::WorkflowFailed { .. }
|
||||
);
|
||||
let payload = WorkflowEventPayload {
|
||||
execution_id: forward_exec_id.clone(),
|
||||
event,
|
||||
};
|
||||
if let Err(e) = forward_app.emit("workflow-event", &payload) {
|
||||
tracing::warn!("工作流事件转发失败: {}", e);
|
||||
}
|
||||
if finished {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 消费过慢丢失部分事件时继续接收
|
||||
Err(RecvError::Lagged(n)) => {
|
||||
tracing::warn!("工作流事件消费滞后,丢失 {} 条", n);
|
||||
}
|
||||
Err(RecvError::Closed) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 后台异步执行 DAG,完成后更新执行记录状态
|
||||
let event_bus = state.event_bus.clone();
|
||||
let db = state.db.clone();
|
||||
let exec_id = execution_id.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mut executor = DagExecutor::new(event_bus.clone());
|
||||
let result = executor.run(&runtime_dag, config).await;
|
||||
|
||||
let (status, error) = match &result {
|
||||
Ok(_) => ("completed", None),
|
||||
Err(e) => ("failed", Some(format!("{:#}", e))),
|
||||
};
|
||||
|
||||
// 更新执行记录(Repo 内部仅持有 Arc 连接,重建开销可忽略)
|
||||
let workflows = WorkflowRepo::new(&db);
|
||||
if let Err(e) = workflows.update_field(&exec_id, "status", status).await {
|
||||
tracing::error!("更新工作流状态失败: {}", e);
|
||||
}
|
||||
if let Err(e) = workflows
|
||||
.update_field(&exec_id, "completed_at", &now_millis())
|
||||
.await
|
||||
{
|
||||
tracing::error!("更新工作流完成时间失败: {}", e);
|
||||
}
|
||||
|
||||
// 执行失败时补发 WorkflowFailed(执行器内部只发 NodeFailed)
|
||||
if let Some(error) = error {
|
||||
event_bus
|
||||
.send(WorkflowEvent::WorkflowFailed {
|
||||
error,
|
||||
failed_node: String::new(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(execution_id)
|
||||
}
|
||||
|
||||
/// 列出全部工作流执行记录
|
||||
#[tauri::command]
|
||||
pub async fn list_workflow_executions(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<WorkflowRecord>, String> {
|
||||
state.workflows.list_all().await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// 按 ID 查询工作流执行记录
|
||||
#[tauri::command]
|
||||
pub async fn get_workflow_execution(
|
||||
state: State<'_, AppState>,
|
||||
id: String,
|
||||
) -> Result<Option<WorkflowRecord>, String> {
|
||||
state
|
||||
.workflows
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// 发送人工审批响应
|
||||
#[tauri::command]
|
||||
pub async fn approve_human_approval(
|
||||
app: AppHandle,
|
||||
state: State<'_, AppState>,
|
||||
execution_id: String,
|
||||
node_id: String,
|
||||
decision: String,
|
||||
comment: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
// 发送审批响应到事件总线
|
||||
let response = HumanApprovalResponse {
|
||||
execution_id: execution_id.clone(),
|
||||
node_id: node_id.clone(),
|
||||
decision,
|
||||
comment,
|
||||
};
|
||||
|
||||
let event = WorkflowEvent::HumanApprovalResponse {
|
||||
execution_id: response.execution_id,
|
||||
node_id: response.node_id,
|
||||
decision: response.decision,
|
||||
comment: response.comment,
|
||||
};
|
||||
|
||||
// 直接发送到全局事件总线
|
||||
state.event_bus.send(event).await;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user