A model writes SFL, the page renders it, and your next message edits the same
diagram. It is built on <stickyflow-diagram> rather than the lower-level
viewer API, to show the least setup that gets stickyflow.js working in Vue.
prompt → /api/chat → reply with an SFL block → <stickyflow-diagram :source>
cp .env.example .env # an OpenAI key, an Anthropic key, or both
npm install
npm run devThe keys stay in the Vite dev server and never reach the browser. With only
ANTHROPIC_API_KEY set, it runs on Claude.
The API is dev-server middleware, so this only works under npm run dev;
npm run build gives you static files and no backend. There is no auth and
no rate limiting, so keep it on localhost. vite --host hands your key's
spend to anyone on the network.
Register the element once, before mounting (src/main.ts):
import { defineStickyflowDiagram } from 'stickyflow/web-component'
defineStickyflowDiagram()Tell Vue's compiler it's a custom element, not a missing component
(vite.config.ts):
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag === 'stickyflow-diagram',
},
},
})Bind it (src/components/DiagramCard.vue). interactive is a bare attribute
and turns on selection, pan and zoom:
<stickyflow-diagram interactive :source="source" />That's the whole integration. Everything else in src/ is ordinary chat UI.
Most of these follow from what the web component exposes.
- No canvas editing. The element turns on selection, pan and zoom, but
doesn't wire in
stickyflow/interaction, so there is no drag, resize or label editing. For that, mount the viewer yourself and use the editing API. - One chat, in memory. It's gone on reload.
- Model and provider come from
.envonly. There is no settings UI. - Only identical replies are skipped. A reply that repeats the current document byte for byte doesn't replace it; anything else does.
.env |
Default | |
|---|---|---|
OPENAI_API_KEY, ANTHROPIC_API_KEY |
Either one is enough. | |
LLM_PROVIDER |
from the keys | openai or anthropic. Unset, it's Claude when only the Anthropic key is set, OpenAI otherwise. |
OPENAI_MODEL |
gpt-5.6-sol |
|
ANTHROPIC_MODEL |
claude-opus-5 |
|
OPENAI_BASE_URL, ANTHROPIC_BASE_URL |
SDK default | Any compatible endpoint. |
OPENAI_REASONING_EFFORT, ANTHROPIC_EFFORT |
off | See .env.example before turning it on. |
Restart the dev server after changing .env.
| Path | |
|---|---|
server/chatApi.ts |
The Vite plugin: /api/chat, /api/config, /api/prompt. |
server/request.ts |
Reads and validates the request body. |
server/providers/ |
One module per SDK behind a shared Complete type. |
server/sflPrimer.ts |
The system prompt that teaches the model SFL. |
src/composables/ |
useChat, useServerConfig, useClipboard. |
src/components/ |
Message, diagram card, composer, banner. |
src/lib/sfl.ts |
Finds the SFL block in a reply. |
src/markdown.ts |
Markdown to HTML through a strict allowlist. Model output is untrusted. |
npm run typecheck # vue-tsc, app and server projects
npm run lint # ESLint
npm run format # Prettier
npm run build # typecheck, then vite build- No streaming. The page waits for the whole reply.
- The model rewrites the whole document every turn.
- Invalid SFL shows the element's own inline error. The previous diagram isn't kept on screen.
- The element rebuilds its viewer whenever
sourcechanges, so pan and zoom reset with every new version of the diagram.