Skip to content

Commit 8ca1b9b

Browse files
authored
feat: viz-node (#187)
1 parent 4b551c8 commit 8ca1b9b

30 files changed

+840
-61
lines changed

.changeset/metal-facts-guess.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@llamaindex/workflow-viz-node": patch
3+
"@llamaindex/workflow-viz": patch
4+
---
5+
6+
Generate workflow visualization in Node.js

demo/visualization/README.md

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
### Workflow Visualization Demo
1+
# Workflow Visualization Demo
22

3-
A minimal Vite + TypeScript + React demo that visualizes an example `@llamaindex/workflow-core` graph using `@llamaindex/workflow-viz` and Sigma.js.
3+
This demo showcases two different ways to visualize `@llamaindex/workflow-core` graphs:
44

5-
### Prerequisites
5+
1. **Browser Visualization**: Interactive web-based graph using Sigma.js
6+
2. **Node.js Image Generation**: Generate static PNG images using node-canvas
7+
8+
## Prerequisites
69

710
- **Node.js**: v20 or newer
811
- **npm** (bundled with Node.js)
912

10-
### Install
13+
## Installation
1114

1215
From the repo root:
1316

@@ -16,6 +19,12 @@ cd demo/visualization
1619
npm install
1720
```
1821

22+
---
23+
24+
## 🌐 Browser Visualization
25+
26+
Interactive web-based workflow visualization using Sigma.js for real-time graph rendering.
27+
1928
### Run in development
2029

2130
```bash
@@ -38,13 +47,67 @@ npm run preview
3847

3948
Open the preview server at [http://localhost:4173](http://localhost:4173) (or the port shown in the terminal).
4049

41-
### What this demo does
50+
### What it does
4251

43-
- Defines a small workflow in `src/workflow.ts` using `createWorkflow` and wraps it with `withDrawing` to add drawing capabilities.
44-
- Use `draw` method of the workflow to render it in a HTML container in `src/main.ts` using force-directed layout.
52+
- Defines a workflow in `src/workflow.ts` using `createWorkflow` and wraps it with `withDrawing` to add drawing capabilities
53+
- Uses the `draw` method to render the workflow in an HTML container using force-directed layout
54+
- Provides interactive graph navigation, zoom, and pan capabilities
4555

4656
### Key files
4757

48-
- `src/workflow.ts`: Example workflow (events, handlers) - adding drawing capabilities
49-
- `src/main.ts`: Renders the workflow in a HTML container using force-directed layout
58+
- `src/workflow.ts`: Example workflow (events, handlers) with drawing capabilities
59+
- `src/viz-browser.ts`: Browser visualization entry point
60+
- `src/style.css`: Styling for the visualization
61+
- `index.html`: HTML container for the visualization
5062
- `vite.config.ts`: Vite config with React SWC plugin
63+
64+
---
65+
66+
## 🖼️ Node.js Image Generation
67+
68+
Generate static PNG images of workflow graphs using node-canvas for documentation, reports, or automated workflows.
69+
70+
### Generate Graph as PNG image
71+
72+
```bash
73+
npm run generate
74+
```
75+
76+
This will create `workflow.png` in the current directory.
77+
78+
> **Note:** This uses `node-canvas`, which requires native dependencies.
79+
>
80+
> **On macOS:**
81+
>
82+
> ```bash
83+
> brew install pkg-config cairo pango libpng jpeg giflib librsvg
84+
> ```
85+
>
86+
> **On Ubuntu/Debian:**
87+
>
88+
> ```bash
89+
> sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
90+
> ```
91+
>
92+
> **For pnpm v10+:** Run `pnpm approve-builds` or add `enable-pnpm-unsafe-build-scripts=true` to `.npmrc`.
93+
94+
### What it does
95+
96+
- Uses the same workflow definition from `src/workflow.ts`
97+
- Wraps the workflow with `withDrawingNode` for Node.js image generation
98+
- Generates a high-quality PNG image with configurable dimensions and layout
99+
- Perfect for automated documentation, CI/CD pipelines, or batch processing
100+
101+
### Key files
102+
103+
- `src/workflow.ts`: Shared workflow definition (events, handlers)
104+
- `src/viz-node.ts`: Node.js image generation entry point
105+
- `package.json`: Contains the `generate` script
106+
107+
---
108+
109+
## Shared Components
110+
111+
Both examples use the same underlying workflow definition:
112+
113+
- **`src/workflow.ts`**: Contains the example workflow with events and handlers that both browser and Node.js examples can use

demo/visualization/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99

1010
<body>
1111
<div id="app"></div>
12-
<script type="module" src="/src/main.ts"></script>
12+
<script type="module" src="/src/viz-browser.ts"></script>
1313
</body>
1414
</html>

demo/visualization/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
"scripts": {
77
"dev": "vite",
88
"build": "tsc -b && vite build",
9-
"preview": "vite preview"
9+
"preview": "vite preview",
10+
"generate": "npx tsx src/viz-node.ts"
1011
},
1112
"dependencies": {
1213
"@llamaindex/workflow-core": "^1.3.3",
1314
"@llamaindex/workflow-viz": "^1.0.4",
15+
"@llamaindex/workflow-viz-node": "^1.0.0",
1416
"react": "^19.1.0",
1517
"react-dom": "^19.1.0"
1618
},

demo/visualization/src/main.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { withDrawing } from "@llamaindex/workflow-viz";
2+
import "./style.css";
3+
import { setupWorkflowEvents } from "./workflow";
4+
import { createWorkflow } from "@llamaindex/workflow-core";
5+
6+
const container = document.getElementById("app") as HTMLElement;
7+
8+
const workflow = withDrawing(createWorkflow());
9+
10+
setupWorkflowEvents(workflow);
11+
12+
workflow.draw(container, {
13+
defaultEdgeColor: "#999",
14+
layout: "force",
15+
});

demo/visualization/src/viz-node.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { withDrawingNode } from "@llamaindex/workflow-viz-node";
2+
import { setupWorkflowEvents } from "./workflow";
3+
import { createWorkflow } from "@llamaindex/workflow-core";
4+
5+
async function main() {
6+
const workflow = withDrawingNode(createWorkflow());
7+
setupWorkflowEvents(workflow);
8+
9+
await workflow.drawToImage({
10+
layout: "force",
11+
width: 800,
12+
height: 600,
13+
output: "workflow.png",
14+
});
15+
}
16+
17+
main().catch(console.error);

demo/visualization/src/workflow.ts

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { createWorkflow, workflowEvent } from "@llamaindex/workflow-core";
2-
import { withDrawing } from "@llamaindex/workflow-viz";
1+
import { Workflow, workflowEvent } from "@llamaindex/workflow-core";
32

43
//#region define workflow events
54
const startEvent = workflowEvent<string>({
@@ -25,36 +24,35 @@ const stopEvent = workflowEvent<string>({
2524
});
2625
//#endregion
2726

28-
//#region defines workflow
29-
const workflow = withDrawing(createWorkflow());
27+
export function setupWorkflowEvents(workflow: Workflow) {
28+
workflow.handle([startEvent], async (ctx) => {
29+
const { sendEvent, stream } = ctx;
30+
// emit 3 different events, handled separately
3031

31-
workflow.handle([startEvent], async (ctx) => {
32-
const { sendEvent, stream } = ctx;
33-
// emit 3 different events, handled separately
32+
sendEvent(branchAEvent.with("Branch A"));
33+
sendEvent(branchBEvent.with("Branch B"));
34+
sendEvent(branchCEvent.with("Branch C"));
3435

35-
sendEvent(branchAEvent.with("Branch A"));
36-
sendEvent(branchBEvent.with("Branch B"));
37-
sendEvent(branchCEvent.with("Branch C"));
36+
const results = await stream.filter(branchCompleteEvent).take(3).toArray();
3837

39-
const results = await stream.filter(branchCompleteEvent).take(3).toArray();
38+
return allCompleteEvent.with(results.map((e) => e.data).join(", "));
39+
});
4040

41-
return allCompleteEvent.with(results.map((e) => e.data).join(", "));
42-
});
43-
44-
workflow.handle([branchAEvent], (_context1, branchA) => {
45-
return branchCompleteEvent.with(branchA.data);
46-
});
41+
workflow.handle([branchAEvent], (_context1, branchA) => {
42+
return branchCompleteEvent.with(branchA.data);
43+
});
4744

48-
workflow.handle([branchBEvent], (_context2, branchB) => {
49-
return branchCompleteEvent.with(branchB.data);
50-
});
45+
workflow.handle([branchBEvent], (_context2, branchB) => {
46+
return branchCompleteEvent.with(branchB.data);
47+
});
5148

52-
workflow.handle([branchCEvent], (_context3, branchC) => {
53-
return branchCompleteEvent.with(branchC.data);
54-
});
49+
workflow.handle([branchCEvent], (_context3, branchC) => {
50+
return branchCompleteEvent.with(branchC.data);
51+
});
5552

56-
workflow.handle([allCompleteEvent], (_context4, allComplete) => {
57-
return stopEvent.with(allComplete.data);
58-
});
53+
workflow.handle([allCompleteEvent], (_context4, allComplete) => {
54+
return stopEvent.with(allComplete.data);
55+
});
5956

60-
export { workflow };
57+
return workflow;
58+
}

packages/graph/biome.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"root": false,
3+
"extends": "//",
4+
"files": {
5+
"includes": ["src/**", "tests/**"]
6+
}
7+
}

packages/graph/package.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "@llamaindex/workflow-graph",
3+
"version": "1.0.0",
4+
"private": true,
5+
"description": "Shared graph utilities",
6+
"type": "module",
7+
"main": "dist/index.cjs",
8+
"types": "dist/index.d.ts",
9+
"module": "dist/index.js",
10+
"exports": {
11+
".": {
12+
"import": {
13+
"types": "./dist/index.d.ts",
14+
"default": "./dist/index.js"
15+
},
16+
"require": {
17+
"types": "./dist/index.d.cts",
18+
"default": "./dist/index.cjs"
19+
},
20+
"default": {
21+
"types": "./dist/index.d.ts",
22+
"default": "./dist/index.js"
23+
}
24+
}
25+
},
26+
"files": [
27+
"dist"
28+
],
29+
"scripts": {
30+
"build": "bunchee",
31+
"dev": "bunchee --watch",
32+
"test": "vitest run",
33+
"test:ui": "vitest --ui",
34+
"test:watch": "vitest"
35+
},
36+
"devDependencies": {
37+
"@babel/types": "^7.27.1",
38+
"@llamaindex/workflow-core": "workspace:*",
39+
"@types/node": "^22.15.19",
40+
"bunchee": "^6.5.1",
41+
"vitest": "^2.0.0",
42+
"@vitest/ui": "^2.0.0"
43+
},
44+
"peerDependencies": {
45+
"@llamaindex/workflow-core": "workspace:*"
46+
},
47+
"license": "MIT",
48+
"repository": {
49+
"type": "git",
50+
"url": "https://github.com/run-llama/workflow-ts.git",
51+
"directory": "packages/graph"
52+
},
53+
"dependencies": {
54+
"@babel/parser": "^7.27.2",
55+
"graphology": "^0.26.0",
56+
"graphology-layout-force": "^0.2.4"
57+
}
58+
}

0 commit comments

Comments
 (0)