Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/metal-facts-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@llamaindex/workflow-viz-node": patch
"@llamaindex/workflow-viz": patch
---

Generate workflow visualization in Node.js
16 changes: 16 additions & 0 deletions demo/visualization/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ npm run dev

Then open the dev server at [http://localhost:5173](http://localhost:5173) (Vite will print the exact URL/port; it may choose a different port if 5173 is taken).

Here’s a shorter version for your README:

### Generate Graph as PNG image in Node.js

```bash
npm run generate
````

This will create `graph.png` in the `dist` directory.

> **Note:** This uses `node-canvas`, which requires native dependencies.
> On macOS: `brew install pkg-config cairo pango libpng jpeg giflib librsvg`
> On Ubuntu/Debian: `sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev`
> For pnpm v10+, run `pnpm approve-builds` or add `enable-pnpm-unsafe-build-scripts=true` to `.npmrc`.


### Build for production

```bash
Expand Down
4 changes: 3 additions & 1 deletion demo/visualization/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
"preview": "vite preview",
"generate": "npx tsx src/viz-node.ts"
},
"dependencies": {
"@llamaindex/workflow-core": "^1.3.3",
"@llamaindex/workflow-viz": "^1.0.4",
"@llamaindex/workflow-viz-node": "^1.0.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
Expand Down
69 changes: 69 additions & 0 deletions demo/visualization/src/viz-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createWorkflow, workflowEvent } from "@llamaindex/workflow-core";
import { withDrawingNode } from "@llamaindex/workflow-viz-node";

//#region define workflow events
const startEvent = workflowEvent<string>({
debugLabel: "start",
});
const branchAEvent = workflowEvent<string>({
debugLabel: "branchA",
});
const branchBEvent = workflowEvent<string>({
debugLabel: "branchB",
});
const branchCEvent = workflowEvent<string>({
debugLabel: "branchC",
});
const branchCompleteEvent = workflowEvent<string>({
debugLabel: "branchComplete",
});
const allCompleteEvent = workflowEvent<string>({
debugLabel: "allComplete",
});
const stopEvent = workflowEvent<string>({
debugLabel: "stop",
});
//#endregion

//#region defines workflow
const workflow = withDrawingNode(createWorkflow());

workflow.handle([startEvent], async (ctx) => {
const { sendEvent, stream } = ctx;
// emit 3 different events, handled separately

sendEvent(branchAEvent.with("Branch A"));
sendEvent(branchBEvent.with("Branch B"));
sendEvent(branchCEvent.with("Branch C"));

const results = await stream.filter(branchCompleteEvent).take(3).toArray();

return allCompleteEvent.with(results.map((e) => e.data).join(", "));
});

workflow.handle([branchAEvent], (_context1, branchA) => {
return branchCompleteEvent.with(branchA.data);
});

workflow.handle([branchBEvent], (_context2, branchB) => {
return branchCompleteEvent.with(branchB.data);
});

workflow.handle([branchCEvent], (_context3, branchC) => {
return branchCompleteEvent.with(branchC.data);
});

workflow.handle([allCompleteEvent], (_context4, allComplete) => {
return stopEvent.with(allComplete.data);
});

async function main() {
await workflow.drawToImage({
layout: "force",
width: 800,
height: 600,
output: "workflow.png",
});
}

main().catch(console.error);
7 changes: 7 additions & 0 deletions packages/graph/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"root": false,
"extends": "//",
"files": {
"includes": ["src/**", "tests/**"]
}
}
58 changes: 58 additions & 0 deletions packages/graph/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "@llamaindex/workflow-graph",
"version": "1.0.0",
"private": true,
"description": "Shared graph utilities",
"type": "module",
"main": "dist/index.cjs",
"types": "dist/index.d.ts",
"module": "dist/index.js",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
},
"default": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
},
"files": [
"dist"
],
"scripts": {
"build": "bunchee",
"dev": "bunchee --watch",
"test": "vitest run",
"test:ui": "vitest --ui",
"test:watch": "vitest"
},
"devDependencies": {
"@babel/types": "^7.27.1",
"@llamaindex/workflow-core": "workspace:*",
"@types/node": "^22.15.19",
"bunchee": "^6.5.1",
"vitest": "^2.0.0",
"@vitest/ui": "^2.0.0"
},
"peerDependencies": {
"@llamaindex/workflow-core": "workspace:*"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/run-llama/workflow-ts.git",
"directory": "packages/graph"
},
"dependencies": {
"@babel/parser": "^7.27.2",
"graphology": "^0.26.0",
"graphology-layout-force": "^0.2.4"
}
}
File renamed without changes.
1 change: 1 addition & 0 deletions packages/graph/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { withGraph, type WithGraphWorkflow } from "./graph";
File renamed without changes.
File renamed without changes.
11 changes: 11 additions & 0 deletions packages/graph/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./lib",
"tsBuildInfoFile": "./lib/.tsbuildinfo",
"paths": {
"@llamaindex/workflow-viz": ["./src/index.ts"]
}
},
"include": ["./src", "./examples"]
}
9 changes: 9 additions & 0 deletions packages/graph/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
globals: true,
environment: "node",
include: ["**/*.test.ts", "**/*.spec.ts"],
},
});
7 changes: 7 additions & 0 deletions packages/viz-node/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"root": false,
"extends": "//",
"files": {
"includes": ["src/**", "tests/**"]
}
}
63 changes: 63 additions & 0 deletions packages/viz-node/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"name": "@llamaindex/workflow-viz-node",
"version": "1.0.0",
"description": "Visualization components for drawing LlamaIndex Workflows in the node",
"type": "module",
"main": "dist/index.cjs",
"types": "dist/index.d.ts",
"module": "dist/index.js",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
},
"default": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
},
"files": [
"dist"
],
"scripts": {
"build": "bunchee",
"dev": "bunchee --watch",
"test": "vitest run",
"test:ui": "vitest --ui",
"test:watch": "vitest"
},
"devDependencies": {
"@babel/types": "^7.27.1",
"@llamaindex/workflow-core": "workspace:*",
"@types/node": "^22.15.19",
"bunchee": "^6.5.1",
"vitest": "^2.0.0",
"@vitest/ui": "^2.0.0",
"graphology": "^0.26.0"
},
"peerDependencies": {
"@llamaindex/workflow-core": "workspace:*"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/run-llama/workflow-ts.git",
"directory": "packages/viz-node"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@llamaindex/workflow-graph": "workspace:*",
"@babel/parser": "^7.27.2",
"graphology-layout-force": "^0.2.4",
"canvas": "^3.2.0",
"graphology-canvas": "^0.4.2"
}
}
22 changes: 22 additions & 0 deletions packages/viz-node/src/canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type Graph from "graphology";

export function toNodeCanvasGraph(graph: Graph): Graph {
const g = graph.copy();

g.forEachNode((node, attr) => {
if (attr.type === "handler") {
g.mergeNodeAttributes(node, { size: 20, color: "red" });
g.removeNodeAttribute(node, "type");
}
if (attr.type === "event") {
g.mergeNodeAttributes(node, { size: 10, color: "blue" });
g.removeNodeAttribute(node, "type");
}
});

g.forEachEdge((edge) => {
g.mergeEdgeAttributes(edge, { size: 3, color: "#999" });
});

return g;
}
Loading
Loading