Files
united-workforce/docs/plans/2026-05-12-dashboard-workflow-graph.md
T

6.8 KiB

Dashboard Workflow Graph Visualization

Issue: #198 Status: In Progress Author: xingyue

Overview

在 Dashboard 的 ThreadDetail 页面中嵌入一个交互式流程图,将 workflow 的 ModeratorTable 可视化为有向图。用户可以一眼看到角色流转结构和当前执行进度。

数据层( 已完成 — PR #201)

WorkflowGraph 类型

WorkflowDefinition.moderator(函数)已替换为 WorkflowDefinition.table(声明式 ModeratorTable),buildDescriptor 自动从 table 提取 graph:

type WorkflowGraphEdge = {
  from: string;              // source role 或 "__start__"
  to: string;                // target role 或 "__end__"
  condition: string;         // condition.name 或 "FALLBACK"
  conditionDescription: string | null;
};

type WorkflowGraph = {
  edges: readonly WorkflowGraphEdge[];
};

type WorkflowDescriptor = {
  description: string;
  roles: Record<string, WorkflowRoleDescriptor>;
  graph: WorkflowGraph;      // 必填,新 bundle 自动生成
};

数据流

ModeratorTable (WorkflowDefinition.table)
  → buildDescriptor() 自动提取 graph
    → descriptor.yaml 持久化(hash.yaml)
      → CLI serve /workflows/:name API 返回 descriptor
        → Dashboard 前端拿到 graph

剩余数据层工作

serve API 需要返回 descriptor:当前 GET /workflows/:name 只返回 registry entry(hash + timestamp),不含 descriptor。需要从 bundles/{hash}.yaml 读取 descriptor 并返回给前端。

方案:在 routes-workflow.tsGET /workflows/:name 响应中附带 descriptor 字段。或者:thread-detail 发现 workflow name 后,请求 GET /workflows/:name/descriptor 拿到 graph。

前端渲染

库选型:React Flow + dagre

优势 劣势
React Flow React 原生、自定义节点/边、dagre 自动布局、~50KB gzip 需要学 API
Mermaid 声明式简单 无交互、无法高亮当前步骤
D3 完全控制 太底层,手撸成本高
Cytoscape 图论强 React 集成差

依赖新增

{
  "@xyflow/react": "^12",
  "@dagrejs/dagre": "^1"
}

图结构映射

WorkflowGraph.edges → React Flow nodes + edges

节点(自动从 edges 推导):
  - __start__  → 圆形小节点(入口)
  - role       → 圆角矩形,显示 role name + description
  - __end__    → 圆形小节点(终止)

边:
  - FALLBACK   → 虚线(dashed),无 label
  - condition  → 实线,label = condition
                  hover tooltip = conditionDescription

布局

使用 dagre 自动计算 TB(top-to-bottom)方向布局:

import Dagre from "@dagrejs/dagre";

function layoutGraph(nodes, edges) {
  const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
  g.setGraph({ rankdir: "TB", nodesep: 60, ranksep: 80 });

  for (const node of nodes) {
    g.setNode(node.id, { width: 180, height: 60 });
  }
  for (const edge of edges) {
    g.setEdge(edge.source, edge.target);
  }

  Dagre.layout(g);

  return nodes.map((node) => {
    const pos = g.node(node.id);
    return { ...node, position: { x: pos.x - 90, y: pos.y - 30 } };
  });
}

运行时高亮

ThreadDetail 已有 records: ThreadRecord[],其中 RoleRecord.role 就是当前/历史执行的 role。

高亮逻辑:

function getNodeStates(records: ThreadRecord[]): Map<string, "completed" | "active"> {
  const states = new Map<string, "completed" | "active">();
  const roleRecords = records.filter((r) => r.type === "role");

  for (let i = 0; i < roleRecords.length; i++) {
    const role = roleRecords[i].role;
    states.set(role, i === roleRecords.length - 1 ? "active" : "completed");
  }

  // 如果有 workflow-result,最后一个 role 也是 completed
  if (records.some((r) => r.type === "workflow-result")) {
    for (const [k] of states) {
      states.set(k, "completed");
    }
    states.set("__end__", "completed");
  }

  states.set("__start__", "completed");
  return states;
}

节点样式:

状态 样式
default border: var(--color-border), 暗色背景
completed border: var(--color-success), 绿色边框 + ✓ 图标
active border: var(--color-accent), 蓝色边框 + 脉冲动画

边高亮:当 source 和 target 都至少 completed 时,边变绿。

组件结构

workflow-dashboard/src/
  components/
    workflow-graph/
      types.ts           — NodeState 等前端类型
      index.ts           — export { WorkflowGraph }
      workflow-graph.tsx  — 主组件,React Flow canvas
      role-node.tsx       — 自定义 role 节点
      terminal-node.tsx   — START/END 圆形节点
      condition-edge.tsx  — 自定义边(虚线/实线 + label)
      use-layout.ts       — dagre 布局 hook

集成到 ThreadDetail

在 ThreadDetail 中,records 列表上方插入可折叠的图面板:

// thread-detail.tsx
{graph && (
  <div className="mb-4 border rounded-lg overflow-hidden" style={{ height: 300 }}>
    <WorkflowGraph graph={graph} nodeStates={getNodeStates(records)} />
  </div>
)}

图高度固定 300px,React Flow 支持 pan + zoom,不影响下方 records 滚动。

实施计划

Phase 0: 数据层 Done (PR #201)

  • WorkflowDefinition.moderatortable (ModeratorTable)
  • WorkflowDescriptor 新增 graph: WorkflowGraph
  • buildDescriptor 自动提取 graph
  • validateWorkflowDescriptor 校验 graph

Phase 1: API + 静态图渲染

  1. serve API:GET /workflows/:name 返回 descriptor(含 graph),或新增 GET /workflows/:name/descriptor
  2. Dashboard api.ts 新增 getWorkflowDescriptor(agent, name) 函数
  3. 安装 @xyflow/react + @dagrejs/dagre
  4. 实现 workflow-graph/ 组件集
  5. ThreadDetail 中集成:从 thread-start record 拿 workflow name → 请求 descriptor → 渲染图

产出:打开 ThreadDetail 看到 workflow 流程图,无高亮。

Phase 2: 运行时高亮

  1. ThreadDetail 根据 records 计算 nodeStates
  2. 节点/边样式响应状态变化
  3. SSE live 模式下实时更新高亮

产出:正在运行的 thread 能看到当前执行到哪个 role。

Phase 3: 交互增强

  1. 点击节点滚动到对应 role 的 RecordCard
  2. 边 hover 显示 conditionDescription tooltip
  3. 节点 hover 显示 role description + schema summary

产出:图和记录列表联动。

注意事项

  • 自循环边:如 coder → coder (FALLBACK),React Flow 支持自循环,dagre 需要特殊处理(self-edge 用 loop 路径)
  • 大图性能:dagre 在 <50 节点时性能无忧,workflow 通常 <10 个 role
  • 暗色主题:Dashboard 已使用 CSS variables,节点/边样式复用现有色板
  • 不提交 pnpm-lock.yaml