A visual, non-linear chat interface where conversations are nodes on an infinite canvas.
Try it online · GitHub · Docs
One command. No installation.
uvx canvas-chat launch
Your browser opens automatically to the local server.
Or try it instantly at ericmjl--canvas-chat-fastapi-app.modal.run — bring your own API keys.
Core Idea
Infinite canvas.
Directed acyclic graph.
Local-first storage.
Six key workflows. Each slide has an interactive mini-canvas — drag nodes like in the app.
Type a message, press Enter. Click any node and reply to branch the conversation.
Select text within a node, click Branch to create an excerpt node and continue from there.
Cmd/Ctrl+Click multiple nodes to combine their context into a single reply.
Click the layout button to arrange nodes in a clean left-to-right hierarchy.
Drag to pan, scroll to zoom. Semantic zoom: zoomed out shows summaries.
Save sessions as .canvaschat files. Share or restore anytime.
Spotlight
Type / to explore.
Powerful features, one keystroke away.
/research generates deep reports via Exa API/search finds relevant pages and summarizes them/committee consults multiple LLMs and synthesizes/matrix builds cross-product comparison tables/factcheck verifies claims with web evidence/code runs Python in-browser with PyodideBring your own keys. Your data stays in your browser.
Configure via the Settings panel. Supported providers:
Keys stored in localStorage — never sent to Canvas Chat servers.
All conversation data lives in your browser's IndexedDB. Nothing is stored server-side.
.canvaschat filesconfig.yamlDeliberately simple. No frameworks, no build step.
Frontend: SVG canvas + CSS. Backend: Python. Storage: IndexedDB. Deployment: Modal.
For contributors and local development.
git clone https://github.com/ericmjl/canvas-chat.git
cd canvas-chat
pixi install
pixi run dev
Opens at http://127.0.0.1:7865. Hot reload is enabled — edit files and the server restarts automatically.
pixi run test # Python
pixi run test-js # JavaScript
canvas-chat/
├── src/canvas_chat/
│ ├── app.py # FastAPI routes
│ ├── config.py # Configuration
│ ├── plugins/ # Python plugins
│ └── static/
│ ├── index.html # Main page
│ ├── css/ # Modular CSS (9 files)
│ └── js/ # ES modules
│ ├── app.js # Orchestrator
│ ├── canvas.js # SVG canvas
│ ├── crdt-graph.js # CRDT graph
│ ├── chat.js # LLM API
│ └── feature-*.js # Plugins
├── tests/ # Unit tests
├── cypress/ # E2E tests
├── docs/ # Diataxis docs
└── modal_app.py # Deployment
app.js — main orchestrator, slash commands, keyboard shortcutscanvas.js — SVG canvas, pan/zoom, node rendering, semantic zoomcrdt-graph.js — Yjs-backed graph data model and traversalchat.js — LLM API calls, streaming via SSEgraph-types.js — node/edge type definitions and factoriesfeature-plugin.js — plugin base class with AppContext DIExtensibility from simple custom nodes to complex features. Detail →
Extend Canvas Chat with a few lines of JavaScript.
import { FeaturePlugin } from './feature-plugin.js';
export class MyFeature extends FeaturePlugin {
constructor(context) {
super(context);
this.graph = context.graph;
this.canvas = context.canvas;
this.chat = context.chat;
}
getSlashCommands() {
return [{
command: '/mycommand',
description: 'Does something cool',
placeholder: 'Enter input...',
}];
}
async handleCommand(command, args, context) {
// Your logic here
}
}
Register in feature-registry.js or load via config.yaml plugins list. See the docs for the full guide.
Documentation follows the Diataxis framework:
docs/explanation/ — Design decisions, "why" docsdocs/how-to/ — Task-oriented guidesdocs/reference/ — API and config referencepixi run dev, pixi run test)Browse the repo: github.com/ericmjl/canvas-chat
Try it online · GitHub · Docs
uvx canvas-chat launch
Supplementary slides:
/search <query> — Web search via Exa/research <topic> — Deep research report via Exa/committee <question> — Multi-LLM consultation + synthesis/matrix <context> — Cross-product evaluation table/factcheck — Verify claims with web evidence/code — Run Python in-browser (Pyodide)/flashcards — Generate flashcards from content/git <url> — Fetch a Git repository/youtube <url> — Fetch YouTube video transcript/fetch <url> — Fetch URL content (HTML, PDF)/note — Create a note nodeType / in the input to see the autocomplete menu with all available commands.
node-registry.jsExample: poll node, chart node
FeaturePlugin base classgetSlashCommands()Example: /committee, /matrix, /research
Example: smart-fix plugin
Plugins can be JS-only, Python-only, or paired (JS + Python). Configured in config.yaml.
Cmd/Ctrl + F — Search nodesEscape — Deselect all nodesF — Fit all nodes in viewL — Auto-layoutCmd/Ctrl + Click — Multi-selectCmd/Ctrl + A — Select allE — Edit selected nodeC — Copy node contentDelete/Backspace — Delete selected nodesCmd/Ctrl + Z — UndoCmd/Ctrl + Shift + Z — Redo? — Open help modalN — New note nodeClick a slide to jump · Esc to close