A gallery website ("app store") hosting many tiny AI apps. Each gallery card opens a self-contained app (text-to-image, logo maker, …) that runs its own ComfyUI Cloud workflow on cloud GPUs. The whole point is modularity: adding a new app = drop 2 files + 1 registry line.
- Next.js 16 (App Router, Turbopack), React 19, TypeScript. Path alias
@/*→ repo root. - Hosting: Vercel project
comfy-cloud-app(teampablos-projects-6ecde209). Live URL: https://comfy-cloud-app.vercel.app - GitHub:
PabloWiedemann/comfy-labs(private). Git is connected to Vercel —git pushtomainauto-deploys. Prefer pushing oververcel deploy. - Local folder:
~/Dev/comfy-labs. - Env vars (set in Vercel Production + Development, and in local
.env.local, which is gitignored):COMFY_API_KEY— Comfy Cloud key (formatcomfyui-…, from platform.comfy.org/profile/api-keys)COMFY_API_BASE—https://cloud.comfy.org- Preview env vars are NOT set (CLI needed an interactive git-branch prompt); add via dashboard if PR previews are needed.
apps/<slug>/app.config.ts default-exports an AppDefinition (metadata + input→node mapping)
apps/<slug>/workflow.json ComfyUI workflow in API format
lib/types.ts AppDefinition, InputField, Workflow types
lib/registry.ts imports each app; getAllApps() / getApp(slug) ← grows per app
lib/comfy-client.ts verified Cloud API wrapper ← DO NOT edit per app
lib/build-workflow.ts buildWorkflow(app, userInputs) patches workflow ← DO NOT edit per app
hooks/useGenerate.ts generate(slug, inputs) → poll → result ← DO NOT edit per app
app/page.tsx gallery of cards (maps registry)
app/apps/[slug]/page.tsx server: lookup slug, strip workflow, 404 if missing
app/apps/[slug]/AppRunner.tsx client: renders form from config, runs useGenerate
app/api/generate/route.ts POST { slug, inputs } → registry → buildWorkflow → submit
app/api/status/[id]/route.ts GET → reads job, resolves signed output URL ← app-agnostic
app/api/cancel/[id]/route.ts POST → cancel job ← app-agnostic
Request flow: AppRunner → useGenerate(slug, inputs) → POST /api/generate {slug,inputs} →
getApp(slug) → buildWorkflow → submitWorkflow → poll /api/status/{jobId} → signed URL → render.
Probed live against https://cloud.comfy.org. All encoded in lib/comfy-client.ts:
- Submit:
POST /api/promptwith headerX-API-KeyAND body{ prompt: <workflow>, extra_data: { api_key_comfy_org: <KEY> } }.⚠️ Partner/API nodes (Gemini, Flux, Kling, …) FAIL with "Unauthorized: Please login first" unlessextra_data.api_key_comfy_orgis present. The header alone is not enough. - Status + outputs:
GET /api/jobs/{prompt_id}(PLURALjobs). Single source of truth — returnsstatus,outputs(with filenames),preview_output,error_message. The documented/api/job/{id}/status(singular) only returns a bare status string — don't use it. - Terminal status string is
"success", not"completed". Raw lifecycle:queued_limited → allocated → executing → success.normalizeStatus()maps these. - Output file:
GET /api/view?filename=&subfolder=&type=output→ 302 redirect to a signedstorage.googleapis.comURL (no auth needed). We follow it manually and hand the URL to the browser.
All three use the GeminiNanoBanana2 core node (model option
"Nano Banana 2 (Gemini 3.1 Flash Image)"), differing only by system_prompt + presets:
text-to-image— generallogo-maker— clean vector logossticker-maker— die-cut glossy stickers
GeminiNanoBanana2 required inputs: prompt (STRING), model (COMBO, the option above),
seed (INT), aspect_ratio (COMBO: auto,1:1,2:3,3:2,3:4,4:3,4:5,5:4,9:16,16:9,21:9),
resolution (COMBO: 1K,2K,4K), response_modalities (COMBO: IMAGE | IMAGE+TEXT),
thinking_level (COMBO: MINIMAL | HIGH). Output index 0 is IMAGE → feed into SaveImage.
Use the ComfyUI Cloud MCP tools (this repo's owner has the MCP installed):
search_nodes(setapi_node_only: truefor partner nodes) → exactclass_type+ input specssearch_templates→ ready-made workflows from comfy.org (try first)search_models→ checkpoints/loraspartner_generate/submit_workflow→ run workflows directly to test before wiring into an app Always verify a new workflow runs (submit →/api/jobs/{id}→ success) BEFORE building the app UI.
The UI is light mode only, built on shadcn/ui (Base UI variant) + Tailwind v4, with a soft light-purple theme (Figma Make feel). (It started as a Wise-green system; the structure is the same, the hue moved to purple.)
- Component library is the source of truth. Build UI ONLY from components in
components/ui/*(button,card,input,textarea,select,label,badge,skeleton, …). Do NOT hand-roll buttons/inputs/cards or add bespoke CSS for them. Need a component that doesn't exist yet? Add it withnpx shadcn@latest add <name>— never reinvent it. - Reuse before creating. Check
components/ui/first; compose existing primitives. - Theme tokens live in
app/globals.css(:root). Use semantic classes (bg-primary,text-brand,text-muted-foreground,border-border,bg-accent) — never hardcode hex colors in components. Palette: text is near-black "Midnight"#2c2b30(--foreground/--brand, headings included) or grey--muted-foreground#6e6e78; the accent is "Hydro Blue"#4d27f7(--primary/--ring, white label on it) used scarcely on CTAs/focus/links;--secondary#dfdcfc("Purple") and--accent#eeebfa("Light Purple") are the pale surfaces;--border/--input= "Light Grey"#dcdae1;--background#fafafboff-white. - Look: soft, "AI"-creative-tool depth (Figma Make-like). Buttons are
rounded-xl; cards use a large radius (rounded-2xl) with drop shadows (shadow-card/shadow-card-hoverutilities) for depth — NOT border strokes (avoid visible borders; let off-white/white contrast + shadow do the separation). Deep-indigo headings, muted body copy, generous whitespace. - Animated background: a full-viewport WebGL shader gradient (
app/components/ShaderBackground.tsx) mounted once globally inapp/layout.tsx. Mostly off-white with a small, deep "Lilac" purple glow- a lightened Hydro-Blue shimmer; drifts/animates subtly and reacts to mouse + scroll. Page roots
are transparent so the shader shows through. (The old
public/hero.jpgbackdrop was removed.)
- a lightened Hydro-Blue shimmer; drifts/animates subtly and reacts to mouse + scroll. Page roots
are transparent so the shader shows through. (The old
- Fonts: Satoshi (loaded from Fontshare in
globals.css, set as--font-sans). Don't add other fonts. app/page.tsx(gallery) andapp/apps/[slug]/AppRunner.tsxare the reference implementations — match their patterns when adding screens.AppRunneralready renders any app's form from its config, so new apps usually need ZERO UI work.
- Never commit secrets.
.env.localand.vercelare gitignored; only.env.exampleis tracked. - Commit message footer:
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>. - Verify before deploy:
npm run build(typecheck + routes) must pass. - To add an app, use the add-comfy-app skill in
.claude/skills/.
- Real thumbnail images (currently emoji).
- Auth / rate-limiting / per-user credits (API key is shared server-side — anyone with the URL spends the owner's credits).
- Image-input apps (img2img, sketch-to-3D):
lib/comfy-client.tsalready hasuploadImage(); needs animageinput type wired intoAppRunner+ a verified workflow.