Architecture¶
Contributor map of how Prompt Studio is wired. Product setup and feature lists live in the documentation hub and main README.
Shape¶
Next.js App Router under src/app/, shared UI in src/components/, domain logic in src/lib/, hooks in src/hooks/.
| Layer | Responsibility |
|---|---|
| Client | Tool UIs, Dexie settings/history/workflows/gallery, plugin install, Comfy job register + poll/WebSocket |
Server (src/app/api/**) |
LLM calls, ComfyUI /prompt proxy + inject/preflight, auth/session/ACL, optional SQLite under PROMPT_DATA_DIR |
| Edge gate | src/proxy.ts — auth, rate limit, usage log before route handlers |
Shell (src/app/layout.tsx) wraps pages with AuthProvider, AppNav, gallery background poller, and storage sync.
Persistence¶
Browser (primary)¶
IndexedDB via Dexie — database comfy-prompt-studio-v1 (src/lib/app-db.ts):
galleryEntries— Comfy jobs and outputskv— settings, prompt history, workflow library, plugins, projects, etc.
Access: src/lib/browser-storage.ts, src/lib/gallery-db-store.ts, init src/lib/app-db-init.ts. Legacy localStorage keys migrate into Dexie on first hydrate. Theme/density stay mirrored in localStorage for the FOUC script and are also included in studio-extras sync.
Durable browser writes listed in src/lib/durable-sync-keys.ts schedule a debounced server push.
Server (optional SQLite)¶
When PROMPT_DATA_DIR is set, Prompt Studio opens {PROMPT_DATA_DIR}/studio.sqlite (WAL) via src/lib/sqlite/studio-db.ts:
- Namespaces (
src/lib/storage-namespaces.ts) in akvtable, scopedglobaloruser:{userId} - Gallery rows in
gallery_entries(not one JSON blob); tombstones ingallery_deleted_ids - Auth, sessions, API keys, reset tokens, audit, LLM usage, analytics, collab rooms as tables
- SMTP / queue-export overlays in
kv
Leftover JSON files (*.json under the data dir and auth/) are imported once on first open and renamed to *.json.imported. Per-user export snapshots remain files under users/{userId}/exports/. ComfyUI view-cache images stay as files.
Without PROMPT_DATA_DIR, /api/storage is disabled. Auth and shared presets still persist to studio.sqlite under the default data root (.prompt-studio-data). SMTP overlay stays in memory until restart.
Studio backup (src/lib/studio-backup.ts) is a versioned JSON download (v5) of history, settings, gallery, and collectStudioExtras() (gallery ELO and other Dexie KV). It is not a dump of PROMPT_DATA_DIR.
Auto-synced namespaces (SYNC_STORAGE_NAMESPACES): settings-cache, prompt-history, comfy-gallery, gallery-deleted-ids, studio-extras.
studio-extras covers workflows, ComfyUI settings, recipes, projects, webhooks, avoided tokens, templates, campaigns, appearance prefs, onboarding, workspace mode, held-max jobs, notifications, and other durable browser state. Legacy namespaces (scheduled-batch, webhook-settings, avoided-tokens, prompt-projects) are folded into studio-extras on pull.
Sync helpers: src/lib/storage-sync.ts, src/lib/auto-storage-sync.ts, src/lib/studio-extras.ts, APIs under src/app/api/storage/**. Browser Dexie remains the working cache; the server database is the durable copy.
ComfyUI queue path¶
flowchart LR
UI[Tool / Queue UI] --> Pref[plugin queue preflight]
Pref --> API["POST /api/comfyui"]
API --> Inject[inject / patch / optimize]
Inject --> Comfy[ComfyUI /prompt]
Comfy --> Gal[gallery register + poll / WS]
| Step | Module |
|---|---|
Client POST + early WebSocket clientId |
src/lib/comfyui-queue-request.ts |
| Result-panel queue actions | src/hooks/usePromptResultActions.ts |
| API entry | src/app/api/comfyui/route.ts |
| URL/workflow resolve + queue | src/lib/comfyui-client.ts |
| Tokens, inject, loaders, images | src/lib/comfyui-config.ts |
| Graph optimize / direct patch | src/lib/workflow-queue-optimizer.ts, src/lib/workflow-direct-patch.ts |
| Workflow library (Dexie KV) | src/lib/comfyui-workflow-files.ts |
| Draft / Final / Max (+ per-tool) | src/lib/queue-quality-profile.ts, src/lib/tool-quality-profiles.ts |
| Plugin mutators | src/lib/plugin-queue-hooks.ts |
| Gallery + progress | src/lib/comfyui-gallery-client.ts, src/lib/comfyui-websocket.ts |
| Engine seam (queue / progress) | src/lib/engine → getEngineAdapter() |
Related routes: src/app/api/comfyui/{status,history,view,upload,interrupt,live,probe,…}/, src/app/api/diffusers/{,status,view,upload}/, src/app/api/fal/{,status,view,upload}/, src/app/api/replicate/{,status,view,upload}/, and src/app/api/{openai,gemini,grok}/{,status,view,upload}/.
Pool members: parseComfyUiPool() merges COMFYUI_POOL with Settings comfyPoolUrls after normalizeComfyPoolUrlList (allowlist fail-closed per URL). Probe (POST /api/comfyui/probe) does not fetch hosts missing from COMFYUI_ALLOWED_HOSTS.
Engine adapter¶
Thin browser seam for queue / status / view / upload / progress so backends can plug in without rewriting gallery or prompt tools.
| Piece | Path |
|---|---|
| Interface | src/lib/engine/types.ts (EngineAdapter) |
| Comfy implementation | src/lib/engine/comfy-adapter.ts |
| Diffusers implementation | src/lib/engine/diffusers-adapter.ts |
| Fal implementation | src/lib/engine/fal-adapter.ts |
| Replicate implementation | src/lib/engine/replicate-adapter.ts |
| Runway implementation | src/lib/engine/runway-adapter.ts + src/lib/runway-client.ts |
| ChatGPT / Gemini / Grok | src/lib/engine/cloud-adapter.ts + src/lib/llm-image-client.ts + src/lib/cloud-video-client.ts |
| Cloud registry | src/lib/engine/capabilities.ts (CLOUD_ENGINE_OPTIONS) |
| Selection | getEngineAdapter() / getEngineAdapterById() in src/lib/engine/index.ts |
| Settings | inferenceEngine + Diffusers URL + cloud key/model (Settings → Inference engine) |
| Python service | services/diffusers-engine/ (optional FastAPI txt2img) |
Methods: postPrompt, fetchJobStatus, buildViewPath, uploadInputImage, subscribeProgress, openProgressBeforeQueue.
Backends today:
comfyui(default) — primary generate path via/api/comfyui/*(Qwen Lightning bf16 + Dynamic VRAM, Final/Max enrich, ControlNet, FaceDetailer, edit, video, custom graphs).diffusers(optional) — local stills only (txt2img + limited native graph compile) via/api/diffusers/*→ local FastAPI (DIFFUSERS_API_URL, defaulthttp://127.0.0.1:8190). Opt in from Settings orPROMPT_ENGINE=diffusers. Not for Play film / ControlNet / Dynamic VRAM / specialty enrich — video and film stay on ComfyUI or cloud clip engines. On 24GB, Qwen Lightning quality/speed remains Comfy’s strength; Diffusers Dynamic VRAM / bf16 parity stays a non-goal (parked).fal(optional) — cloud stills + clips via/api/fal/*→ Fal queue. Prompt + optional Image 1; Video uses Kling / WAN / LTX / Grok Imagine / Veo presets. Local clips can upload to Fal CDN then call LTX extend-video when the upload succeeds; otherwise continue is last-frame I2V. Key:FAL_KEYor Settings.replicate(optional) — same cloud contract via/api/replicate/*→ Replicate predictions. Stills + Kling / WAN / LTX clips. No extend API (continue is last-frame I2V). Token:REPLICATE_API_TOKENor Settings.openai(optional) — ChatGPT Images via/api/openai/*→POST /v1/images/generations(defaultgpt-image-2). Stills only — Sora is deprecated. Key:OPENAI_API_KEYor Settings.gemini(optional) — Gemini native image via/api/gemini/*→generateContent(defaultgemini-3.1-flash-image). Video tool uses documented Veogenerate_videos/predictLongRunning(veo-3.1-generate-preview). Key:GEMINI_API_KEYor Settings.grok(optional) — xAI Imagine stills via/api/grok/*→POST /v1/images/generations(defaultgrok-imagine-image-2.0). Video tool usesPOST /v1/videos/generations(grok-imagine-video-1.5). Key:XAI_API_KEYor Settings.runway(optional) — Gen-4 stills + Gen-4.5 / Aleph clips via/api/runway/*→ Runway Dev API. Text/image →POST /v1/text_to_image; T2V/I2V →text_to_video/image_to_video; continue/extend →POST /v1/video_to_video(aleph2). Key:RUNWAY_API_KEYor Settings.
Add another provider by extending CLOUD_ENGINE_OPTIONS plus an adapter and /api/<id> proxy.
Diffusers progress is poll-backed (no live latent WebSocket). Gallery entries store comfyUrl as the engine host and optional engineId so poll/view use the correct adapter after the user switches engines.
In scope of the seam: engine I/O (queue a job, poll status, proxy pixels, upload inputs, live/poll progress).
Out of scope (stay studio-owned): prompt drafting, quality profiles, LoRA stacking, workflow injection/optimize, gallery IndexedDB, interrupt / free / object-info.
Consumers: gallery re-queue (src/lib/comfyui-requeue.ts), result-panel send/batch (src/hooks/usePromptResultActions.ts), gallery poll (src/lib/comfyui-gallery-client.ts).
Auth and ACL¶
Enabled when PROMPT_AUTH_ENABLED=true or leftover users.json / imported users exist (src/lib/auth/config.ts, src/lib/auth/store.ts).
Roles (src/lib/auth/types.ts): admin | user | viewer.
- admin — all features
- viewer —
dashboard,gallery,studioonly - user — all features minus personal + group
blockedFeatures
Feature IDs and page/API maps: src/lib/auth/features.ts (e.g. / → generate, /api/comfyui → comfyui-api, LLM routes → llm-api).
Gate path: src/proxy.ts → authorizeAppRequest (src/lib/auth/access.ts). Nav filters by allowedFeatures from /api/auth/session (src/hooks/useAuth.tsx, src/components/AppNav.tsx).
Session cookie prompt-studio-session; also Bearer / x-prompt-api-token / per-user API keys.
Invites: POST /api/auth/invite (admin) creates or re-sends a user and emails a 1-hour reset token via src/lib/email/notifications.ts. Mailer: src/lib/email/mailer.ts (transporter rebuilt when SMTP overlay changes).
Plugins¶
Client Dexie manifests (src/lib/plugin-manifest.ts) plus an optional server registry under {PROMPT_DATA_DIR}/plugins (src/lib/server-plugin-registry.ts, GET/POST/DELETE /api/plugins/server).
{
id, label, version, enabled?,
nav?: [{ href, label, description }],
queueHooks?: { url, events, privileges? }, // e.g. "queue-preflight", "queue-post"
tools?: [{ id, title, iframeUrl?, route? }]
}
- Nav merges into the sidebar catalog
- Browser queue hooks run via
runPluginQueuePreflightbefore Comfy queue - Server plugins with privileges run inside
queuePromptToComfyUi(queue-preflight/queue-post) with allowlisted prompt / params / workflow JSON rewrite - Custom tools render at
/plugins/[id](src/app/plugins/[id]/page.tsx) - Iframe host protocol (queue / apply-prompt / LoRA stack / workflow tokens / gallery tag / engine): plugin-iframe-host.md; example at
/plugin-examples/hello-iframe.html - Caps: 48 client runtime plugins, 32 server plugins, 24 manual queue hooks; bookmarks stay separate (32)
Bookmarks (non-manifest) are separate: src/lib/tool-plugin-registry.ts. Example hook: src/app/api/plugin-hooks/denoise-rewrite/route.ts. Optional install HMAC: PROMPT_PLUGIN_HMAC_SECRET.
LLM and vision¶
| Concern | Where |
|---|---|
| Chat / vision client | src/lib/llm-client.ts |
| Env helpers | src/lib/llm-env.ts |
| Vision downscale/recompress before model | src/lib/vision-image-prepare.ts |
| Generate / format / refine | /api/generate, /api/format, /api/refine (+ tool-specific routes) |
LLM routes are gated as llm-api when auth is on. Prompt cleanup / thinking-artifact stripping lives in src/lib/prompt-cleanup.ts.
Env categories¶
See .env.example for the full list. Groups that matter for architecture:
| Category | Examples |
|---|---|
| LLM | LLM_ENABLED, LLM_API_BASE_URL, LLM_MODEL, LLM_VISION_MODEL |
| ComfyUI | COMFYUI_API_URL, COMFYUI_POOL, COMFYUI_ALLOW_CLIENT_URL, COMFYUI_ALLOWED_HOSTS, COMFYUI_ROOT |
| Auth | PROMPT_AUTH_ENABLED, PROMPT_ADMIN_*, PROMPT_SESSION_SECRET, PROMPT_API_TOKEN, PROMPT_API_URL |
| Persistence | PROMPT_DATA_DIR, PROMPT_AUTH_DIR |
PROMPT_SMTP_*, PROMPT_EMAIL_FROM (overlay in SQLite kv) |
|
| Ops | API_RATE_LIMIT_*, scheduled batch / maintenance flags |
Where to look next¶
| Question | Start here |
|---|---|
| Why did queue change my graph? | comfyui-config.ts, workflow-queue-optimizer.ts, Settings → workflow takeover |
| Where did this setting go? | Dexie kv via browser-storage.ts / settings-cache |
| Why is a nav item missing? | auth/features.ts + user/group blockedFeatures |
| Why did invite/reset mail use the wrong URL? | PROMPT_API_URL — default is http://127.0.0.1:47832 |
| Why did probe fail with allowlist? | url-safety.ts / COMFYUI_ALLOWED_HOSTS; copy snippet from cluster panel |
| Why did a plugin alter queue? | plugin-queue-hooks.ts + installed manifests |
| Refine / vision blew up? | vision-image-prepare.ts, llm-client.ts, /api/refine |
| Where does queue / progress hit the engine? | src/lib/engine (getEngineAdapter) — ComfyUI or Diffusers |