新增: 初始化 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:
96
crates/df-nodes/src/human_node.rs
Normal file
96
crates/df-nodes/src/human_node.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
//! 人工审批节点 — 阻塞工作流,等待人工确认或输入
|
||||
|
||||
use async_trait::async_trait;
|
||||
use std::time::Duration;
|
||||
use tokio::time;
|
||||
use df_workflow::node::{Node, NodeContext, NodeOutput, NodeResult, NodeSchema};
|
||||
use df_core::events::WorkflowEvent;
|
||||
|
||||
/// 人工审批节点(阻塞节点)
|
||||
pub struct HumanNode;
|
||||
|
||||
#[async_trait]
|
||||
impl Node for HumanNode {
|
||||
async fn execute(&self, ctx: NodeContext) -> NodeResult {
|
||||
let config = ctx.config.as_object().cloned().unwrap_or_default();
|
||||
let title = config.get("title")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("请确认");
|
||||
|
||||
let description = config.get("description")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
|
||||
let options = config.get("options")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| arr.iter()
|
||||
.filter_map(|v| v.as_str())
|
||||
.collect::<Vec<_>>())
|
||||
.unwrap_or_else(|| vec!["同意", "拒绝"]);
|
||||
|
||||
// 发送人工审批请求到事件总线
|
||||
let _ = ctx.event_bus.send(WorkflowEvent::HumanApprovalRequest {
|
||||
execution_id: ctx.execution_id.clone(),
|
||||
node_id: ctx.node_id.clone(),
|
||||
title: title.to_string(),
|
||||
description: description.to_string(),
|
||||
options: options.iter().map(|s| s.to_string()).collect(),
|
||||
});
|
||||
|
||||
// 等待用户响应(最多等待 1 小时)
|
||||
let timeout = Duration::from_secs(3600);
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
loop {
|
||||
// 检查是否超时
|
||||
if start_time.elapsed() > timeout {
|
||||
return Err(anyhow::anyhow!("人工审批超时"));
|
||||
}
|
||||
|
||||
// 检查节点是否被取消
|
||||
if ctx.node_status.is_cancelled(&ctx.node_id) {
|
||||
return Err(anyhow::anyhow!("人工审批被取消"));
|
||||
}
|
||||
|
||||
// TODO: 检查审批响应(需要前端实现)
|
||||
// 目前直接返回同意
|
||||
let output = serde_json::json!({
|
||||
"decision": "同意",
|
||||
"comment": "",
|
||||
});
|
||||
return Ok(NodeOutput::from_value(output));
|
||||
}
|
||||
}
|
||||
|
||||
fn schema(&self) -> NodeSchema {
|
||||
NodeSchema {
|
||||
params: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": { "type": "string" },
|
||||
"description": { "type": "string" },
|
||||
"options": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"required": ["title"]
|
||||
}),
|
||||
output: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"decision": { "type": "string" },
|
||||
"comment": { "type": "string" }
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_blocking(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn node_type(&self) -> &str {
|
||||
"human"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user