From 89b175faf0b2af3002c1cb2b10314408d15aa45c Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Mon, 29 Jun 2026 09:40:43 -0500 Subject: [PATCH] ComfyUI AI gen lesson changes --- .env.example | 6 ++++-- comfyui/README.md | 12 +++++++++--- src/app/api/create/route.ts | 4 ++-- src/lib/comfyui.ts | 37 +++++++++++++++++++++++++++---------- src/lib/createdLessons.ts | 2 +- 5 files changed, 43 insertions(+), 18 deletions(-) diff --git a/.env.example b/.env.example index 2b7be24..417f500 100644 --- a/.env.example +++ b/.env.example @@ -21,8 +21,10 @@ SMTP_FROM="DrawIt " COMFYUI_URL=http://127.0.0.1:8188 COMFYUI_API_KEY= # Path to a ComfyUI workflow exported in API format ("Save (API format)"). -COMFYUI_WORKFLOW=./comfyui/coloring-book.workflow_api.json -# Node ids in that workflow to inject into: the positive prompt text, and the sampler seed. +COMFYUI_WORKFLOW=./comfyui/ColorBook01.json +# Node ids in that workflow: the positive-prompt node and the sampler (seed) node. COMFYUI_PROMPT_NODE=6 COMFYUI_SEED_NODE=3 +# Placeholder inside the positive prompt that gets replaced with the creator's subject. +COMFYUI_SUBJECT_TOKEN=[DrawItSubject] COMFYUI_TIMEOUT_MS=120000 diff --git a/comfyui/README.md b/comfyui/README.md index f6ec7af..935eab8 100644 --- a/comfyui/README.md +++ b/comfyui/README.md @@ -10,11 +10,17 @@ server and turns the result into a lesson. Nothing leaves your machine. 3. Provide a workflow exported in **API format** (ComfyUI → Settings → enable dev mode → "Save (API format)") and point `COMFYUI_WORKFLOW` at it. -`coloring-book.workflow_api.json` here is the **stock SD1.5 text-to-image workflow** as a starting -point. DrawIt injects its prompt into the positive-prompt node and a random seed into the sampler node: +`ColorBook01.json` here is the active workflow (`COMFYUI_WORKFLOW`). Its positive-prompt node contains a +**`[DrawItSubject]` placeholder** — DrawIt replaces that token with the creator's one-word subject and +sets a fresh random seed; the rest of your prompt is kept exactly as written. (`coloring-book.workflow_api.json` +is the older stock SD1.5 template, kept for reference.) -- `COMFYUI_PROMPT_NODE` (default `6`) — the positive `CLIPTextEncode` node. +- `COMFYUI_PROMPT_NODE` (default `6`) — the positive `CLIPTextEncode` node containing `[DrawItSubject]`. - `COMFYUI_SEED_NODE` (default `3`) — the `KSampler` node. +- `COMFYUI_SUBJECT_TOKEN` (default `[DrawItSubject]`) — the placeholder to substitute. + +Example: node 6 text `"coloring book page, single cute [DrawItSubject], black and white line art, …"` +with subject `Zebra` becomes `"coloring book page, single cute Zebra, black and white line art, …"`. ## Tips for clean line art (matters — we vectorize it with potrace) diff --git a/src/app/api/create/route.ts b/src/app/api/create/route.ts index 5087e55..c366583 100644 --- a/src/app/api/create/route.ts +++ b/src/app/api/create/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { requireCreator } from "@/lib/session"; -import { isCreateEnabled, buildPrompt } from "@/lib/comfyui"; +import { isCreateEnabled, resolvePrompt } from "@/lib/comfyui"; import { moderateSubject } from "@/lib/moderation"; import { createCreatedLesson, processCreation, listCreatedForUser } from "@/lib/createdLessons"; @@ -31,7 +31,7 @@ export async function POST(req: Request) { return NextResponse.json({ error: "Let's pick a different, friendly thing to draw!" }, { status: 422 }); } - const prompt = buildPrompt(mod.subject); + const prompt = resolvePrompt(mod.subject); const status = mod.decision === "allowlist" ? "pending" : "needs_review"; const row = createCreatedLesson({ userId: user.id, level, subject: mod.subject, prompt, moderation: mod.decision, status }); diff --git a/src/lib/comfyui.ts b/src/lib/comfyui.ts index 2f0a5fe..2742c3a 100644 --- a/src/lib/comfyui.ts +++ b/src/lib/comfyui.ts @@ -11,18 +11,30 @@ import path from "node:path"; const URL_BASE = (process.env.COMFYUI_URL || "").replace(/\/$/, ""); const API_KEY = process.env.COMFYUI_API_KEY || ""; -const WORKFLOW_PATH = process.env.COMFYUI_WORKFLOW || "./comfyui/coloring-book.workflow_api.json"; +const WORKFLOW_PATH = process.env.COMFYUI_WORKFLOW || "./comfyui/ColorBook01.json"; const TIMEOUT_MS = Number(process.env.COMFYUI_TIMEOUT_MS || 120000); -const PROMPT_NODE = process.env.COMFYUI_PROMPT_NODE || "6"; // positive CLIPTextEncode in the default workflow -const SEED_NODE = process.env.COMFYUI_SEED_NODE || "3"; // KSampler in the default workflow +const PROMPT_NODE = process.env.COMFYUI_PROMPT_NODE || "6"; // positive CLIPTextEncode in the workflow +const SEED_NODE = process.env.COMFYUI_SEED_NODE || "3"; // KSampler in the workflow +// Placeholder inside the workflow's positive prompt that we replace with the creator's subject. +const SUBJECT_TOKEN = process.env.COMFYUI_SUBJECT_TOKEN || "[DrawItSubject]"; export function isCreateEnabled(): boolean { return URL_BASE.length > 0; } -/** The fixed coloring-book prompt with the subject interpolated. */ -export function buildPrompt(subject: string): string { - return `A coloring book page of a ${subject}, clean black-and-white line art, bold crisp outlines, simple composition, large open areas to color, white background, no shading, no grayscale, no color, no gradients, no heavy background detail, printable page.`; +/** + * The resolved positive prompt that will be sent: the workflow's prompt-node text with the + * `[DrawItSubject]` placeholder replaced by the subject. Stored for audit. Falls back to the bare + * subject if the workflow can't be read. + */ +export function resolvePrompt(subject: string): string { + try { + const text = loadWorkflow()[PROMPT_NODE]?.inputs?.text; + if (typeof text === "string") return text.split(SUBJECT_TOKEN).join(subject); + } catch { + /* fall through */ + } + return subject; } function authHeaders(): Record { @@ -40,15 +52,20 @@ async function sleep(ms: number) { } /** - * Generate coloring-book line art for the given prompt. Returns the PNG bytes. + * Generate coloring-book line art for the given subject. Returns the PNG bytes. + * Injects the subject into the workflow's prompt placeholder + a fresh seed. * Throws on timeout / ComfyUI errors (caller marks the lesson 'failed'). */ -export async function generateLineArt(prompt: string): Promise { +export async function generateLineArt(subject: string): Promise { if (!isCreateEnabled()) throw new Error("Create is disabled (COMFYUI_URL not set)."); const workflow = loadWorkflow(); - // Inject our prompt + a fresh seed into the configured nodes. - if (workflow[PROMPT_NODE]?.inputs) workflow[PROMPT_NODE].inputs.text = prompt; + // Replace the [DrawItSubject] placeholder in the positive prompt with the creator's subject. + const promptNode = workflow[PROMPT_NODE]; + if (promptNode?.inputs && typeof promptNode.inputs.text === "string") { + promptNode.inputs.text = promptNode.inputs.text.split(SUBJECT_TOKEN).join(subject); + } + // Fresh seed each run so repeats vary. if (workflow[SEED_NODE]?.inputs) workflow[SEED_NODE].inputs.seed = Math.floor(Math.random() * 1e15); const clientId = `drawit-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; diff --git a/src/lib/createdLessons.ts b/src/lib/createdLessons.ts index fc1ea59..a1bc7e4 100644 --- a/src/lib/createdLessons.ts +++ b/src/lib/createdLessons.ts @@ -119,7 +119,7 @@ export async function processCreation(id: number): Promise { updateCreatedStatus(id, "generating"); const { generateLineArt } = await import("./comfyui"); const { pngToTemplateSvg } = await import("./vectorize"); - const png = await generateLineArt(row.prompt); + const png = await generateLineArt(row.subject); const templateSvg = await pngToTemplateSvg(png); setReady(id, templateSvg, `data:image/png;base64,${png.toString("base64")}`); } catch (e) {