ComfyUI AI gen lesson changes
This commit is contained in:
+4
-2
@@ -21,8 +21,10 @@ SMTP_FROM="DrawIt <no-reply@example.com>"
|
|||||||
COMFYUI_URL=http://127.0.0.1:8188
|
COMFYUI_URL=http://127.0.0.1:8188
|
||||||
COMFYUI_API_KEY=
|
COMFYUI_API_KEY=
|
||||||
# Path to a ComfyUI workflow exported in API format ("Save (API format)").
|
# Path to a ComfyUI workflow exported in API format ("Save (API format)").
|
||||||
COMFYUI_WORKFLOW=./comfyui/coloring-book.workflow_api.json
|
COMFYUI_WORKFLOW=./comfyui/ColorBook01.json
|
||||||
# Node ids in that workflow to inject into: the positive prompt text, and the sampler seed.
|
# Node ids in that workflow: the positive-prompt node and the sampler (seed) node.
|
||||||
COMFYUI_PROMPT_NODE=6
|
COMFYUI_PROMPT_NODE=6
|
||||||
COMFYUI_SEED_NODE=3
|
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
|
COMFYUI_TIMEOUT_MS=120000
|
||||||
|
|||||||
+9
-3
@@ -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 →
|
3. Provide a workflow exported in **API format** (ComfyUI → Settings → enable dev mode →
|
||||||
"Save (API format)") and point `COMFYUI_WORKFLOW` at it.
|
"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
|
`ColorBook01.json` here is the active workflow (`COMFYUI_WORKFLOW`). Its positive-prompt node contains a
|
||||||
point. DrawIt injects its prompt into the positive-prompt node and a random seed into the sampler node:
|
**`[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_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)
|
## Tips for clean line art (matters — we vectorize it with potrace)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { requireCreator } from "@/lib/session";
|
import { requireCreator } from "@/lib/session";
|
||||||
import { isCreateEnabled, buildPrompt } from "@/lib/comfyui";
|
import { isCreateEnabled, resolvePrompt } from "@/lib/comfyui";
|
||||||
import { moderateSubject } from "@/lib/moderation";
|
import { moderateSubject } from "@/lib/moderation";
|
||||||
import { createCreatedLesson, processCreation, listCreatedForUser } from "@/lib/createdLessons";
|
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 });
|
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 status = mod.decision === "allowlist" ? "pending" : "needs_review";
|
||||||
const row = createCreatedLesson({ userId: user.id, level, subject: mod.subject, prompt, moderation: mod.decision, status });
|
const row = createCreatedLesson({ userId: user.id, level, subject: mod.subject, prompt, moderation: mod.decision, status });
|
||||||
|
|
||||||
|
|||||||
+27
-10
@@ -11,18 +11,30 @@ import path from "node:path";
|
|||||||
|
|
||||||
const URL_BASE = (process.env.COMFYUI_URL || "").replace(/\/$/, "");
|
const URL_BASE = (process.env.COMFYUI_URL || "").replace(/\/$/, "");
|
||||||
const API_KEY = process.env.COMFYUI_API_KEY || "";
|
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 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 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 default 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 {
|
export function isCreateEnabled(): boolean {
|
||||||
return URL_BASE.length > 0;
|
return URL_BASE.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The fixed coloring-book prompt with the subject interpolated. */
|
/**
|
||||||
export function buildPrompt(subject: string): string {
|
* The resolved positive prompt that will be sent: the workflow's prompt-node text with the
|
||||||
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.`;
|
* `[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<string, string> {
|
function authHeaders(): Record<string, string> {
|
||||||
@@ -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').
|
* Throws on timeout / ComfyUI errors (caller marks the lesson 'failed').
|
||||||
*/
|
*/
|
||||||
export async function generateLineArt(prompt: string): Promise<Buffer> {
|
export async function generateLineArt(subject: string): Promise<Buffer> {
|
||||||
if (!isCreateEnabled()) throw new Error("Create is disabled (COMFYUI_URL not set).");
|
if (!isCreateEnabled()) throw new Error("Create is disabled (COMFYUI_URL not set).");
|
||||||
|
|
||||||
const workflow = loadWorkflow();
|
const workflow = loadWorkflow();
|
||||||
// Inject our prompt + a fresh seed into the configured nodes.
|
// Replace the [DrawItSubject] placeholder in the positive prompt with the creator's subject.
|
||||||
if (workflow[PROMPT_NODE]?.inputs) workflow[PROMPT_NODE].inputs.text = prompt;
|
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);
|
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)}`;
|
const clientId = `drawit-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export async function processCreation(id: number): Promise<void> {
|
|||||||
updateCreatedStatus(id, "generating");
|
updateCreatedStatus(id, "generating");
|
||||||
const { generateLineArt } = await import("./comfyui");
|
const { generateLineArt } = await import("./comfyui");
|
||||||
const { pngToTemplateSvg } = await import("./vectorize");
|
const { pngToTemplateSvg } = await import("./vectorize");
|
||||||
const png = await generateLineArt(row.prompt);
|
const png = await generateLineArt(row.subject);
|
||||||
const templateSvg = await pngToTemplateSvg(png);
|
const templateSvg = await pngToTemplateSvg(png);
|
||||||
setReady(id, templateSvg, `data:image/png;base64,${png.toString("base64")}`);
|
setReady(id, templateSvg, `data:image/png;base64,${png.toString("base64")}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user