Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Patchbay

A visual pipeline builder. Drag typed nodes onto a canvas, wire the ports together, and an LLM workflow takes shape — the way you'd patch signal through a studio rack, which is where the name comes from.

The interesting problem isn't the drag-and-drop. A real tool like this has dozens of node types, and if every one is a hand-written component it collapses under its own weight. So the core of Patchbay is the layer underneath: a way to describe a node as data and have it become a working node on its own.

React + ReactFlow + Zustand on the front end, Python + FastAPI on the back.


Running it

Two processes, two terminals.

cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn main:app --reload            # http://localhost:8000
cd frontend
npm install
npm start                            # http://localhost:3000 (or next free port)

The frontend talks to http://localhost:8000 by default; set REACT_APP_API_BASE to point it elsewhere.


The node abstraction

Every node type is one declarative object:

{
  type: 'filter',
  title: 'Filter',
  icon: Icons.filter,
  accent: '#fbbf24',
  fields: [
    { key: 'condition', label: 'Condition', type: 'select', options: [...] },
    { key: 'value', label: 'Value', type: 'text' },
  ],
  targets: [{ id: 'input' }],
  sources: [{ id: 'pass', label: 'pass' }, { id: 'fail', label: 'fail' }],
}

Three pieces make that work:

  • nodes/BaseNode.js — the card shell. Header, icon, accent, body, and the placement of every connection handle. Nodes never position a handle themselves; they declare what they connect and the shell lays them out.
  • nodes/fields.js — turns a field descriptor into a control (text, number, select, checkbox, auto-growing textarea). Add a control type once, every node can use it.
  • nodes/createNode.js — the factory. Takes a description, returns a React component wired to the store.

nodes/registry.js holds every definition and is the single source of truth for three consumers: the ReactFlow node-types map, the toolbar, and the styling. Adding a node type is one object — around eight lines — and it appears in all three at once.

The nine node types included (input, LLM, output, text, filter, math, API request, delay, note) exist to prove the range: one input / two outputs, two inputs / one output, a node with three fields, and a node with no ports at all — a zero-connection node is just another configuration, not a special case.

One design point worth calling out: targets and sources can be functions of the node's current values, not just static arrays. That's how the text node grows a port per template variable without the abstraction knowing anything about templates.

The text node

Two behaviours. It auto-resizes — width from the longest line, height from the content, and it shrinks back when you delete. And when you write a variable in double braces, a matching input port appears on the node, labelled with the name.

Variables are validated as real JavaScript identifiers: {{ 9bad }} (leading digit), {{ my-var }} (hyphen) and {{ class }} (reserved word) are all correctly ignored, and the same variable twice yields one port, not two.

Removing things

A node editor where the only way to undo a drag is to wipe the board is not a node editor. Every node carries a remove control in its header that appears on hover, and removing a node also removes any connection attached to it, so the graph is never left holding an edge pointing at something that is gone. Connections are removed by double-clicking them; Backspace and Delete work on a selection.

The backend

POST /pipelines/parse takes the pipeline as JSON and returns {num_nodes, num_edges, is_dag}.

is_dag uses Kahn's algorithm: repeatedly remove nodes with no remaining incoming edges; anything left over is part of a cycle. It's iterative (a deep pipeline can't exhaust the recursion limit) and survives the messy input a browser actually sends — empty graphs, self-loops, duplicate edges between the same pair, edges referencing nodes that no longer exist, and disconnected subgraphs.

The frontend shows the result as a dismissible panel rather than a browser alert, stating the outcome in words first — "Pipeline looks valid" or "This pipeline has a cycle" — with the raw counts underneath.

Design

Dark-first, because it's a canvas tool: on a dark field the node accents and the edges become the brightest things on screen. Everything visual is a design token in index.css, so recolouring the whole app is one file. No UI library — no Tailwind, no component kit; the entire surface is one hand-written CSS file.

Node icons are inline SVG on a single 24-unit grid with a consistent stroke, inheriting currentColor so each takes its node's accent automatically.

Stack

React · ReactFlow · Zustand · Python · FastAPI · one hand-built design system, no UI kit.

Licence

MIT — see LICENSE.

About

A visual pipeline builder — drag typed nodes onto a canvas, wire the ports, validate the graph. React · ReactFlow · Zustand · FastAPI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages