导读:有了 DSL 作为"设计图纸",接下来我们要建造"施工场地"了。本文带你深入了解如何实现一个流畅的可视化画布,让智能体编排像搭积木一样简单。


在这里插入图片描述

一、画布:智能体的"工作台"

1.1 画布的核心功能

想象一下 Photoshop 的画布、Figma 的设计板,或者思维导图工具的编辑区——我们的智能体画布也是类似的 concept。

┌─────────────────────────────────────────────────────┐
│                  画布核心功能                        │
├─────────────────────────────────────────────────────┤
│                                                      │
│  🎯 节点展示    →  显示所有节点及其状态             │
│  🖱️ 拖拽添加    →  从面板拖入新节点                 │
│  🔗 连线编辑    →  连接节点构建流程                 │
│  🔍 缩放平移    →  查看大型工作流                   │
│  ✏️ 节点操作    →  移动/复制/删除节点               │
│  💾 自动保存    →  实时同步到 DSL                   │
│                                                      │
└─────────────────────────────────────────────────────┘

1.2 画布界面布局

┌──────────────────────────────────────────────────────────┐
│                    顶部工具栏                            │
│  ← 返回  │  智能体名称  │  导入  导出  保存  运行       │
├──────────┬───────────────────────────────────────────────┤
│          │                                               │
│  节点    │                                               │
│  面板    │              画布区域                         │
│          │                                               │
│  [开始]  │         [节点] ──► [节点] ──► [节点]         │
│  [AI]    │             │                                 │
│  [检索]  │             ▼                                 │
│  [条件]  │           [节点]                             │
│  [工具]  │                                               │
│  ...     │                                               │
│          │                                               │
└──────────┴───────────────────────────────────────────────┘

二、技术选型:为什么选择 VueFlow?

2.1 流程图引擎对比

┌────────────────────────────────────────────────────┐
│              主流流程图引擎对比                     │
├────────────────────────────────────────────────────┤
│                                                     │
│  引擎          │  优点           │  缺点           │
│  ─────────────│────────────────│─────────────────│
│  VueFlow      │ Vue3 原生       │ 生态相对较小     │
│  React Flow   │ 功能强大        │ 仅支持 React     │
│  GoJS         │ 功能最全        │ 商业授权         │
│  JointJS     │ 开源免费        │ 学习曲线陡       │
│  自研         │ 完全可控        │ 成本高           │
│                                                     │
└────────────────────────────────────────────────────┘

2.2 VueFlow 的核心能力

┌─────────────────────────────────────────┐
│          VueFlow 能做什么                │
├─────────────────────────────────────────┤
│                                         │
│  ✅ 节点渲染    →  自定义节点组件       │
│  ✅ 边渲染      →  多种连线样式         │
│  ✅ 拖拽支持    →  从外部拖入节点       │
│  ✅ 连线交互    →  拖拽连线、自动吸附   │
│  ✅ 视图控制    →  缩放、平移、适应视图 │
│  ✅ 选择/多选   →  框选、Ctrl+ 点击     │
│  ✅ 快捷键      →  复制/粘贴/删除       │
│  ✅ 迷你地图    →  快速导航             │
│  ✅ 背景网格    →  对齐辅助             │
│                                         │
└─────────────────────────────────────────┘

2.3 VueFlow 的基本用法

<template>
  <VueFlow
    v-model:nodes="nodes"
    v-model:edges="edges"
    :node-types="nodeTypes"
    @connect="handleConnect"
    @node-drag-stop="handleNodeMove"
  >
    <!-- 自定义节点模板 -->
    <template #node-beginNode="slotProps">
      <BeginNode v-bind="slotProps" />
    </template>
  </VueFlow>
</template>

三、节点面板:智能体的"工具箱"

3.1 节点分类设计

好的分类让用户快速找到需要的节点:

┌─────────────────────────────────────────┐
│              节点面板                    │
├─────────────────────────────────────────┤
│                                         │
│  📌 基础                                │
│     ├─ 开始                             │
│     ├─ 结束                             │
│     └─ 消息输出                         │
│                                         │
│  🤖 AI 能力                             │
│     ├─ AI 对话                           │
│     ├─ 知识检索                         │
│     └─ 问题分类                         │
│                                         │
│  🔀 流程控制                            │
│     ├─ 条件分支                         │
│     ├─ 迭代循环                         │
│     └─ 并行处理                         │
│                                         │
│  🛠️ 工具扩展                            │
│     ├─ API 调用                          │
│     ├─ 代码执行                         │
│     └─ 数据处理                         │
│                                         │
└─────────────────────────────────────────┘

3.2 拖拽添加节点

用户操作流程

1. 鼠标悬停节点 → 显示预览
   ↓
2. 按下鼠标 → 开始拖拽
   ↓
3. 拖到画布上方 → 显示放置位置
   ↓
4. 松开鼠标 → 创建节点

实现要点

┌─────────────────────────────────────────┐
│         拖拽实现要点                     │
├─────────────────────────────────────────┤
│                                         │
│  1. 设置 draggable="true"              │
│  2. 监听 dragstart 事件                │
│  3. 携带节点类型信息                   │
│  4. 画布监听 dragover 和 drop          │
│  5. 计算放置位置                       │
│  6. 创建节点并添加到画布               │
│                                         │
└─────────────────────────────────────────┘

3.3 节点预览

拖拽时显示半透明预览,帮助用户定位:

┌─────────────────────────────────────────┐
│                                          │
│   [节点面板]                              │
│   ┌─────┐                               │
│   │ AI  │ ──► 拖拽                       │
│   └─────┘                               │
│          ╲                              │
│           ╲  半透明预览                  │
│            ╲  ┌─────┐                   │
│             ╲ │ AI  │                   │
│              ╲└─────┘                   │
│                ↓                        │
│           放置位置                       │
│                                          │
└─────────────────────────────────────────┘

四、连线:构建智能体的"神经网络"

4.1 连线交互流程

1. 鼠标悬停节点输出点 → 高亮显示
   ↓
2. 点击输出点 → 开始连线
   ↓
3. 拖动鼠标 → 显示跟随的连线
   ↓
4. 悬停目标节点输入点 → 吸附高亮
   ↓
5. 松开鼠标 → 创建连接

4.2 连线校验

不是所有节点都能随意连接,需要规则校验:

┌─────────────────────────────────────────┐
│              连线规则                    │
├─────────────────────────────────────────┤
│                                         │
│  ✅ 允许的连接                           │
│     • 开始 → 任意节点                   │
│     • AI → 消息输出                     │
│     • 条件 → 多个分支                   │
│                                         │
│  ❌ 禁止的连接                           │
│     • 循环依赖                          │
│     • 类型不匹配                        │
│     • 超过最大连接数                    │
│                                         │
└─────────────────────────────────────────┘

校验逻辑示意

function canConnect(source, target) {
  // 1. 不能自己连自己
  if (source === target) return false
  
  // 2. 不能产生循环
  if (wouldCreateCycle(source, target)) return false
  
  // 3. 检查节点类型兼容性
  if (!isCompatible(source.type, target.type)) return false
  
  // 4. 检查连接数限制
  if (exceedsMaxConnections(source)) return false
  
  return true
}

4.3 连线样式

不同状态的连线用不同样式表示:

┌─────────────────────────────────────────┐
│              连线状态样式                │
├─────────────────────────────────────────┤
│                                         │
│  正常状态   ─────  黑色实线             │
│  悬停状态   ─────  蓝色加粗             │
│  选中状态   ─────  蓝色虚线             │
│  无效状态   ─ ─ ─  灰色虚线            │
│  错误状态   ═════  红色双线            │
│                                         │
└─────────────────────────────────────────┘

4.4 自定义边组件

<template>
  <g class="custom-edge">
    <!-- 边路径 -->
    <path :d="path" class="edge-path" />
    
    <!-- 边上的文字 -->
    <textPath :href="`#${id}`" class="edge-label">
      {{ label }}
    </textPath>
    
    <!-- 删除按钮(悬停显示) -->
    <circle 
      :cx="midX" 
      :cy="midY" 
      class="delete-btn"
      @click="onDelete"
    />
  </g>
</template>

五、画布交互:让编辑更流畅

5.1 视图控制

缩放和平移

┌─────────────────────────────────────────┐
│              视图控制方式                │
├─────────────────────────────────────────┤
│                                         │
│  🔍 缩放                                 │
│     • 鼠标滚轮                          │
│     • Ctrl/Cmd + +/-                   │
│     • 工具栏按钮                        │
│     • 双指捏合(触控板)               │
│                                         │
│  ✋ 平移                                 │
│     • 拖拽空白区域                      │
│     • 方向键                            │
│     • 迷你地图拖拽                      │
│                                         │
│  📐 适应视图                             │
│     • 一键显示所有节点                  │
│     • 重置缩放级别                      │
│                                         │
└─────────────────────────────────────────┘

5.2 节点选择

单选:点击节点 → 选中(蓝色边框)

多选

  • Ctrl/Cmd + 点击 → 添加/取消选择
  • 框选 → 选择区域内的所有节点
┌─────────────────────────────────────────┐
│                                          │
│   ┌─────┐         ┌─────┐               │
│   │ A ✓ │         │ B ✓ │               │
│   └─────┘         └─────┘               │
│       ╲           ╱                     │
│        ╲  框选   ╱                      │
│         ╲     ╱                         │
│          └───┘                          │
│                                          │
│   ┌─────┐         ┌─────┐               │
│   │ C ✓ │         │ D   │               │
│   └─────┘         └─────┘               │
│                                          │
└─────────────────────────────────────────┘

5.3 节点操作

移动节点

拖拽节点 → 实时显示位置 → 松开 → 更新坐标

复制节点

1. 选中节点
   ↓
2. Ctrl/Cmd + C(或点击复制按钮)
   ↓
3. Ctrl/Cmd + V(或点击画布)
   ↓
4. 生成新节点(ID 不同,配置相同)

删除节点

1. 选中节点
   ↓
2. Delete/Backspace(或点击删除按钮)
   ↓
3. 确认对话框
   ↓
4. 删除节点及相关连线

5.4 快捷键设计

┌─────────────────────────────────────────┐
│              快捷键列表                  │
├─────────────────────────────────────────┤
│                                         │
│  Ctrl/Cmd + C    →  复制选中节点       │
│  Ctrl/Cmd + V    →  粘贴节点           │
│  Delete/Backspace → 删除选中节点       │
│  Ctrl/Cmd + Z    →  撤销               │
│  Ctrl/Cmd + Y    →  重做               │
│  Ctrl/Cmd + A    →  全选               │
│  Ctrl/Cmd + 0    →  适应视图           │
│  Ctrl/Cmd + +    →  放大               │
│  Ctrl/Cmd + -    →  缩小               │
│                                         │
└─────────────────────────────────────────┘

六、节点渲染:从数据到 UI

6.1 节点组件结构

每个节点都是一个独立的 Vue 组件:

┌─────────────────────────────────────────┐
│              节点组件结构                │
├─────────────────────────────────────────┤
│                                         │
│  ┌─────────────────────────┐            │
│  │       节点头部          │            │
│  │  [图标] 节点名称 [×]    │            │
│  ├─────────────────────────┤            │
│  │                         │            │
│  │       节点内容          │            │
│  │   (预览/摘要信息)     │            │
│  │                         │            │
│  ├─────────────────────────┤            │
│  │  ● 输入点    输出点 ●  │            │
│  └─────────────────────────┘            │
│                                         │
└─────────────────────────────────────────┘

6.2 节点状态

┌─────────────────────────────────────────┐
│              节点状态                    │
├─────────────────────────────────────────┤
│                                         │
│  默认状态   →  正常显示                  │
│  悬停状态   →  轻微高亮                  │
│  选中状态   →  蓝色边框 + 阴影           │
│  运行状态   →  显示执行进度              │
│  成功状态   →  绿色标记                  │
│  错误状态   →  红色标记 + 错误信息       │
│                                         │
└─────────────────────────────────────────┘

6.3 节点颜色系统

不同颜色的节点代表不同类型:

┌─────────────────────────────────────────┐
│              节点颜色规范                │
├─────────────────────────────────────────┤
│                                         │
│  🟢 绿色系   →  开始/结束节点           │
│  🔵 蓝色系   →  AI/工具节点             │
│  🟣 紫色系   →  检索/知识节点           │
│  🟠 橙色系   →  条件/分支节点           │
│  🟡 黄色系   →  消息/输出节点           │
│  🔴 红色系   →  错误/异常节点           │
│  🟤 棕色系   →  循环/迭代节点           │
│                                         │
└─────────────────────────────────────────┘

七、配置表单:节点的"控制面板"

7.1 表单触发方式

┌─────────────────────────────────────────┐
│              打开表单的方式              │
├─────────────────────────────────────────┤
│                                         │
│  1. 双击节点 → 打开配置抽屉            │
│  2. 点击节点 → 显示快捷菜单 → 配置    │
│  3. 右键菜单 → 编辑配置                │
│  4. 属性面板 → 始终显示在右侧          │
│                                         │
└─────────────────────────────────────────┘

7.2 表单布局

抽屉式(推荐):

┌──────────────────────────────────────────┐
│  画布区域                    │  配置抽屉  │
│                            │ ┌────────┐ │
│   [节点] ──► [节点]        │ │ 表单   │ │
│                            │ │ 内容   │ │
│                            │ │        │ │
│                            │ │        │ │
│                            │ │        │ │
│                            │ └────────┘ │
└──────────────────────────────────────────┘

弹窗式

┌──────────────────────────────────────────┐
│  画布区域(背景变暗)                     │
│                                          │
│     ┌────────────────────────────┐      │
│     │      节点配置              │      │
│     ├────────────────────────────┤      │
│     │                            │      │
│     │       表单内容             │      │
│     │                            │      │
│     │                            │      │
│     │    [取消]    [确定]        │      │
│     └────────────────────────────┘      │
│                                          │
└──────────────────────────────────────────┘

7.3 表单内容组织

┌─────────────────────────────────────────┐
│           AI 节点配置表单                │
├─────────────────────────────────────────┤
│                                         │
│  📋 基础设置                            │
│     ├─ 模型选择                         │
│     ├─ 温度参数                         │
│     └─ 最大 Token 数                     │
│                                         │
│  💬 提示词配置                           │
│     ├─ 系统提示词                       │
│     ├─ 用户提示词                       │
│     └─ 变量插入                         │
│                                         │
│  🛠️ 工具配置                            │
│     ├─ 可用工具列表                     │
│     └─ 工具参数                         │
│                                         │
│  ⚙️ 高级设置                            │
│     ├─ 错误处理                         │
│     ├─ 重试策略                         │
│     └─ 输出格式                         │
│                                         │
└─────────────────────────────────────────┘

八、实时同步:画布与 DSL 的双向绑定

8.1 数据流设计

┌─────────────────────────────────────────┐
│              数据流向                    │
├─────────────────────────────────────────┤
│                                         │
│  画布操作                                │
│     ↓                                   │
│  [事件触发]                             │
│     ↓                                   │
│  Store 更新                             │
│     ↓                                   │
│  DSL 自动同步                            │
│     ↓                                   │
│  [防抖处理]                             │
│     ↓                                   │
│  保存到后端                             │
│                                         │
└─────────────────────────────────────────┘

8.2 关键同步点

┌─────────────────────────────────────────┐
│           需要同步的操作                 │
├─────────────────────────────────────────┤
│                                         │
│  ✅ 添加节点    →  更新 nodes 和 components│
│  ✅ 删除节点    →  清理相关数据          │
│  ✅ 移动节点    →  更新 position        │
│  ✅ 连接节点    →  更新 edges           │
│  ✅ 断开连线    →  删除 edge            │
│  ✅ 配置节点    →  更新 component.params│
│                                         │
└─────────────────────────────────────────┘

8.3 防抖优化

频繁操作时,避免每次都保存:

用户操作 → 操作 1 → 操作 2 → 操作 3 → ... → 停止
                ↓        ↓        ↓           ↓
              (不保存) (不保存) (不保存)    保存
                          ↑
                      延迟 500ms

九、性能优化

9.1 大规模画布优化

问题:当节点超过 100 个时,渲染变慢。

解决方案

┌─────────────────────────────────────────┐
│           性能优化策略                   │
├─────────────────────────────────────────┤
│                                         │
│  1️⃣  虚拟渲染    →  只渲染可视区域      │
│  2️⃣  懒加载      →  节点内容按需渲染   │
│  3️⃣  缓存       →  复用已渲染节点      │
│  4️⃣  Web Worker →  复杂计算异步处理    │
│  5️⃣  降级显示   →  缩放时显示简化版    │
│                                         │
└─────────────────────────────────────────┘

9.2 渲染优化技巧

使用 memo 缓存组件

<template>
  <VueFlow>
    <template #node-LLMNode="slotProps">
      <LLMNode
        v-memo="[slotProps.id, slotProps.data]"
        v-bind="slotProps"
      />
    </template>
  </VueFlow>
</template>

避免不必要的响应式

// 使用 markRaw 避免深度响应式
const nodeTypes = {
  BeginNode: markRaw(BeginNode),
  LLMNode: markRaw(LLMNode)
}

十、总结

10.1 画布实现要点回顾

┌────────────────────────────────────────────────────┐
│              画布实现八大要点                        │
├────────────────────────────────────────────────────┤
│                                                     │
│  1️⃣  引擎选择    →  VueFlow 提供强大基础能力        │
│  2️⃣  节点分类    →  清晰分类帮助用户快速找到节点    │
│  3️⃣  拖拽体验    →  预览+吸附让放置更精准          │
│  4️⃣  连线校验    →  防止无效连接保证流程正确        │
│  5️⃣  视图控制    →  缩放平移适应不同规模工作流      │
│  6️⃣  快捷操作    →  复制粘贴删除提升编辑效率        │
│  7️⃣  双向同步    →  画布与 DSL 实时保持一致        │
│  8️⃣  性能优化    →  虚拟渲染支撑大规模节点          │
│                                                     │
└────────────────────────────────────────────────────┘

10.2 用户体验关键点

  • 所见即所得:拖拽即生效,无需额外确认
  • 即时反馈:悬停、选中、错误都有明确提示
  • 容错设计:误操作可以撤销,删除需要确认
  • 辅助功能:对齐网格、迷你地图、快捷键

系列文章导航

  1. 架构篇 - 整体设计和技术选型
  2. DSL 设计篇 - 数据结构与序列化
  3. 画布实现篇 - VueFlow 集成与交互(本文)
  4. 📝 节点系统篇 - 18 种节点的实现细节
  5. 📝 表单系统篇 - 动态表单与变量引用
  6. 📝 状态管理篇 - Pinia Store 设计
  7. 📝 高级特性篇 - 迭代/循环/嵌套
  8. 📝 实战篇 - 从零构建一个完整智能体

作者注:本文基于 agent-flow 项目的实际代码分析编写,力求还原真实的架构设计过程。欢迎在评论区讨论或提问!

下一篇从零开始设计一个智能体编排系统 - 节点系统篇(敬请期待)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐