项目组织与集成

把 rust-agent-flow 集成进自己的应用,本质上是在一个 gpui 窗口里放下一个 `FlowEditorView`,再按需注入扩展。本节给出最小骨架、初始化序列、扩展注入时序,以及 Demo 的实际项目结构作为参照。

crate 依赖关系

rust-agent-flow 分两个核心 crate:

rust_agent_flow        ← 纯逻辑:FlowGraph / Node / Edge / FlowDocument / dagre
    ▲
    │ depends
    │
rust_agent_flow_gpui   ← gpui 渲染层:FlowEditorView / PanelView / 扩展点

`rust_agent_flow` 不依赖 gpui,可单独用于后端/测试/序列化;`rust_agent_flow_gpui` 在前者基础上提供编辑器视图。你的应用依赖 `rust_agent_flow_gpui` 即可同时获得两者。

最小集成骨架

一个能跑起来的集成只需四步:初始化、建图、建视图、注入扩展。

use std::sync::Arc;
use gpui::AppContext;
use rust_agent_flow_gpui::{CombinedAssets, FlowEditorView, SharedToolbarProvider};

mod toolbar_provider;  // 你的 ToolbarProvider 实现

fn main() {
    gpui_platform::application()
        .with_assets(CombinedAssets)
        .run(move |cx: &mut gpui::App| {
            // 1. 框架初始化(注册内置节点、加载资源)
            rust_agent_flow_gpui::init(cx);

            cx.spawn(async move |cx| {
                cx.open_window(gpui::WindowOptions::default(), |window, cx| {
                    // 2. 准备图数据(来自你的 FlowDocument / JSON / 动态构建)
                    let graph = load_my_graph();

                    let view = cx.new(|cx| {
                        let mut editor = FlowEditorView::new(graph, cx);
                        // 3. 自动布局(不调则节点堆在原点)
                        editor.auto_layout(cx);
                        // 4. 注入扩展
                        editor.add_toolbar_provider(
                            Arc::new(toolbar_provider::MyToolbar::new()),
                            cx,
                        );
                        editor
                    });
                    cx.new(|cx| gpui_component::Root::new(view, window, cx))
                })
                .expect("Failed to open window");
            })
            .detach();
        });
}

四个动作缺一不可,下表说明省略各自的后果:

| 动作 | 省略后果 | |------|----------| | `init(cx)` | ListItem 选中边框异常等内置状态未注册 | | `auto_layout(cx)` | 节点全部堆在原点 (0,0) | | `add_toolbar_provider` | 工具栏只有内置项,无业务控件 | | `gpui_component::Root::new(view, ...)` | Button/DropdownMenu 等组件无主题上下文 |

初始化序列

正确顺序是「框架先就绪、再建视图」:

gpui_platform::application().with_assets(CombinedAssets).run(...)
   │
   ├─ rust_agent_flow_gpui::init(cx)      ← 注册内置节点类型、资源
   │
   └─ cx.open_window(...)
        │
        └─ cx.new(|cx| {
               let editor = FlowEditorView::new(graph, cx);  ← 视图构造
               editor.auto_layout(cx);                       ← 布局
               editor.add_toolbar_provider(..., cx);         ← 扩展
               editor.set_syntax_service(..., cx);           ← 扩展(可选)
               editor.set_data_type_provider(..., cx);       ← 扩展(可选)
               editor
           })

`init` 必须在 `FlowEditorView::new` 之前,因为它注册内置节点类型(Start/End/Action/Condition/Loop/Variable/Adapter/Agent);若顺序反了,`new` 时节点注册表为空,图里的节点找不到对应 `IFlowNode`,画布会是空的。

扩展注入时序

扩展注入的通用原则:**在首帧渲染前注入**。一旦 panel_view 已被 `ensure_panel_view` 创建,替换型注入(syntax/data-type/language)会销毁它重建——虽然结果正确,但多一次重建开销。在 `cx.new` 闭包内、`auto_layout` 前后注入都是安全的,因为闭包返回前没有渲染发生。

cx.new(|cx| {
    let mut editor = FlowEditorView::new(graph, cx);
    // 注入顺序无要求,但建议:替换型在前、累积型在后
    editor.set_syntax_service(Arc::new(MySyntax), cx);
    editor.set_data_type_provider(Arc::new(MyTypes), cx);
    editor.add_toolbar_provider(Arc::new(MyToolbar), cx);
    editor.auto_layout(cx);   // 布局最后做,避免与重建冲突
    editor
})

Demo 项目结构

Demo 是「最小完整集成」的范本,结构如下:

demo/
├── Cargo.toml
├── data/
│   ├── agent_flow.json       ← 预置流程(FlowDocument JSON)
│   ├── data_pipeline.json
│   └── simple_flow.json
└── src/
    ├── main.rs               ← 入口:加载图 + 建视图 + 注入扩展
    ├── data_sources.rs       ← DemoDataSource 枚举 + to_graph
    ├── toolbar_provider.rs   ← DataSourceToolbar / AppControlsToolbar
    └── data_type_provider.rs ← DemoDataTypeProvider(空,作扩展参考)

职责清晰分离:

| 文件 | 角色 | 是否必需 | |------|------|----------| | `main.rs` | 集成入口,组装框架 | 必需 | | `data_sources.rs` | 数据驱动:流程定义 → FlowGraph | 可替换为你的数据源 | | `toolbar_provider.rs` | 业务工具栏扩展 | 按需 | | `data_type_provider.rs` | 业务数据类型扩展 | 按需 | | `data/*.json` | 流程定义数据 | 可替换 |

这种「数据在 JSON、逻辑在代码、扩展在 provider」的分层,让新增流程只需加 JSON 文件、新增工具栏控件只需加 provider——核心集成代码(main.rs)几乎不动。

数据驱动加载

Demo 用 `include_str!` 把 JSON 编译期嵌入,运行时反序列化为 `FlowDocument` 再转 `FlowGraph`:

const AGENT_FLOW_JSON: &str = include_str!("../data/agent_flow.json");

impl DemoDataSource {
    pub fn to_graph(&self) -> FlowGraph {
        FlowGraph::from_document(&self.to_document())
    }
    pub fn to_document(&self) -> FlowDocument {
        let json = match self { Self::AgentFlow => AGENT_FLOW_JSON, ... };
        serde_json::from_str(json).expect("内置 JSON 解析失败")
    }
}

`include_str!` 适合内置示例;真实项目可改为运行时 `std::fs::read_to_string` 或网络加载,`FlowDocument` 协议不变。节点/边定义与渲染逻辑完全解耦——这是 rust-agent-flow 数据驱动设计的核心收益。

何时需要自定义节点

框架内置 8 种节点类型覆盖了控制流编排的绝大多数场景。需要自定义 `IFlowNode` 的典型信号:

- 节点数据 schema 内置 8 种 `FieldType` 表达不了(如需要文件上传控件) - 节点视图需要动态交互(如内联预览、实时计算结果) - 节点端口结构高度特殊(如多输入汇聚节点)

只是字段不同?优先用 `NodeSchema` 声明,享受 schema 驱动面板。需要特殊视图?实现 `IFlowNode::get_view` 自定义渲染,但仍可复用 schema 驱动面板(除非像 Start 那样走特例)。

小结

集成四步:`init` → `new` → `auto_layout` → 注入扩展,顺序不可乱。扩展在首帧前注入最省。Demo 的「JSON 数据 + provider 扩展 + main 装配」分层是可复用的项目骨架:新增流程加 JSON、新增控件加 provider,核心代码稳定。

下一节:[常见陷阱与排查](common-pitfalls.md)