diff --git a/CLAUDE.md b/CLAUDE.md index fc08ab0..597604d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,6 +37,8 @@ npm run build tsc + vite build + scripts/copy-uxp-assets.mjs -> dist/ npm run package build + zip + .ccx into packages/ (incl. openlayer-latest.*) npm run setup-pack GENERATED from presetRegistry; never hand-edit its output npm run audit-css measures CSS redeclarations +npm run object-info record tests/fixtures/comfyui-object-info.json from a live ComfyUI + (`-- --check` diffs a live ComfyUI against it; read-only) cd bridge && npm ci the MCP bridge is a separate package with its own lockfile ``` diff --git a/docs/beta-gate.md b/docs/beta-gate.md index 6f6b2b8..7f2e441 100644 --- a/docs/beta-gate.md +++ b/docs/beta-gate.md @@ -15,7 +15,7 @@ the same change. | # | Gate | How it is checked | Status | |---|---|---|---| -| G1 | **Runs on a current ComfyUI.** Every runnable preset's nodes and inputs match a recorded `/object_info` from the newest ComfyUI release, and a test fails when they drift. | Roadmap U2: a snapshot in the repo, a test of every preset's `requiredNodes` against it, and a script that diffs a live ComfyUI against the snapshot. Its output against a current ComfyUI is pasted in the PR. | **Open.** Every preset has been checked only against ComfyUI 0.37.0, the version on the test machine. ComfyUI 0.40 was out by 2026-10-13. | +| G1 | **Runs on a current ComfyUI.** Every runnable preset's nodes and inputs match a recorded `/object_info` from the newest ComfyUI release, and a test fails when they drift. | Roadmap U2: a snapshot in the repo, a test of every preset's `requiredNodes` against it, and a script that diffs a live ComfyUI against the snapshot. Its output against a current ComfyUI is pasted in the PR. | **Done in v0.41.** `tests/fixtures/comfyui-object-info.json` is recorded from ComfyUI 0.44.0 (80 node classes) by `npm run object-info`, and `tests/comfy/objectInfoSnapshot.test.ts` checks every node, input, nested input and fixed choice of all 39 presets against it. All match. Running Workflow Health against 0.44 found a real fault: it read only required inputs and marked 13 working presets "Missing ComfyUI node"; fixed, it now reports 28 ready, 10 experimental and 1 model in the wrong folder on the test machine. `npm run object-info -- --check` against a fresh Comfy Desktop 0.38.1 lists only the custom node packs it has not installed. | | G2 | **Finds Comfy Desktop.** The port scan includes Comfy Desktop's default port. | A test on `COMFY_PORT_CANDIDATES`, plus Settings › Find ComfyUI Active Port against a ComfyUI started on that port. | **Done in v0.41.** 8000 is scanned second, right after 8188, with a test. Not yet run against a real Comfy Desktop: on the test machine a ComfyUI on 8188 always answers first. A clean-install tester (G6) using Comfy Desktop checks it for real. | | G3 | **Says so when there is no GPU.** When ComfyUI reports only a CPU device, the panel says which tools will be too slow to use, rather than failing or hanging. | A unit test with a CPU-only `/system_stats` fixture, plus one run against ComfyUI started with `--cpu`. | **Done in v0.41.** Check ComfyUI says "ComfyUI is online, but on CPU only." with the reason underneath, and Detect Hardware reports "Processor only, no graphics card", VRAM none, tier No GPU. A CPU device no longer counts its system RAM as VRAM. Checked in Photoshop 2025 on 2026-11-04 against a real Comfy Desktop instance started with `--cpu` (ComfyUI 0.38.1, port 8189), and against a GPU ComfyUI 0.44.0 on 8188, which still reports normally. | | G4 | **Passes the full smoke checklist on the release build.** All blocks of [the UXP smoke checklist](uxp-smoke-checklist.md) on the build that will be tagged, with a row in its Results table. | The Results table. | **Open.** The only recorded full run is from v0.37.1. v0.38 to v0.40 were checked per feature, in their own PRs. | diff --git a/package.json b/package.json index dfe9648..9ea4ca5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "package": "npm run build && node scripts/package.mjs", "setup-pack": "node scripts/build-setup-pack.mjs", "audit-css": "node scripts/audit-css.mjs", + "object-info": "node scripts/object-info-snapshot.mjs", "test": "vitest run --config vitest.config.ts", "test:e2e": "vitest run --config vitest.e2e.config.ts" }, diff --git a/scripts/object-info-snapshot.mjs b/scripts/object-info-snapshot.mjs new file mode 100644 index 0000000..88844d4 --- /dev/null +++ b/scripts/object-info-snapshot.mjs @@ -0,0 +1,171 @@ +import { readFile, readdir, writeFile } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** + * Records, or checks against, the parts of ComfyUI's /object_info that OpenLayer + * depends on. Beta gate G1 (roadmap U2). + * + * node scripts/object-info-snapshot.mjs [serverUrl] record tests/fixtures/comfyui-object-info.json + * node scripts/object-info-snapshot.mjs --check [serverUrl] diff a live ComfyUI against the recording + * + * Only the node classes OpenLayer uses are kept: every class_type in the bundled + * API workflows, every classType the preset registry requires, and every + * class_type the TypeScript builds itself (Live Painting, the LoRA splice). For + * each input the snapshot keeps its type and, for a fixed list of choices such + * as a sampler or scheduler, the choices -- but never a list of model files, + * which differs on every machine. Nested inputs (ComfyUI's dynamic combo and + * autogrow inputs, written "parent.child" in a workflow) keep the names they + * allow. tests/comfy/objectInfoSnapshot.test.ts checks the presets against it. + * + * Read-only against ComfyUI: one GET of /object_info and one of /system_stats. + */ + +const root = fileURLToPath(new URL("..", import.meta.url)); +const snapshotPath = resolve(root, "tests/fixtures/comfyui-object-info.json"); +const args = process.argv.slice(2); +const checkMode = args.includes("--check"); +const serverUrl = (args.find((arg) => !arg.startsWith("--")) ?? "http://127.0.0.1:8188").replace(/\/+$/, ""); + +const FILE_LIKE = /\.(safetensors|pt|pth|gguf|ckpt|bin|onnx|png|jpe?g|webp|json|ya?ml)$/i; + +// "Class.input" -> the literal values the bundled workflows set on it, so a +// choice removed upstream only fails the check when a preset uses it. +const usedChoices = new Map(); + +async function usedClasses() { + const classes = new Set(); + const apiDir = resolve(root, "src/workflows/api"); + + for (const name of await readdir(apiDir)) { + if (!name.endsWith(".json")) continue; + const workflow = JSON.parse(await readFile(join(apiDir, name), "utf8")); + for (const node of Object.values(workflow)) { + if (!node || typeof node !== "object" || typeof node.class_type !== "string") continue; + classes.add(node.class_type); + for (const [input, value] of Object.entries(node.inputs ?? {})) { + if (typeof value !== "string" || value === "") continue; + const key = `${node.class_type}.${input}`; + if (!usedChoices.has(key)) usedChoices.set(key, new Set()); + usedChoices.get(key).add(value); + } + } + } + + const comfyDir = resolve(root, "src/comfy"); + for (const name of await readdir(comfyDir)) { + if (!name.endsWith(".ts")) continue; + const source = await readFile(join(comfyDir, name), "utf8"); + for (const match of source.matchAll(/class(?:_t|T)ype: "([^"]+)"/g)) classes.add(match[1]); + } + + return [...classes].sort(); +} + +function choicesOf(spec) { + if (Array.isArray(spec) && Array.isArray(spec[0])) return spec[0]; + if (Array.isArray(spec) && spec[0] === "COMBO" && Array.isArray(spec[1]?.options)) return spec[1].options; + return null; +} + +function summarizeInput(spec) { + const type = Array.isArray(spec) ? spec[0] : spec; + + if (type === "COMFY_DYNAMICCOMBO_V3") { + const nested = {}; + for (const option of spec[1]?.options ?? []) { + nested[option.key] = Object.keys({ ...(option.inputs?.required ?? {}), ...(option.inputs?.optional ?? {}) }).sort(); + } + return { type, nested }; + } + + if (type === "COMFY_AUTOGROW_V3") { + // ComfyUI 0.44 keeps the slot names ("image_1", ...) under "template". + return { type, names: spec[1]?.template?.names ?? spec[1]?.names ?? [] }; + } + + const choices = choicesOf(spec); + if (choices) { + const fixed = choices.length > 0 && choices.every((choice) => typeof choice === "string" && !FILE_LIKE.test(choice) && !choice.includes("/") && !choice.includes("\\")); + return fixed ? { type: "COMBO", choices } : { type: "COMBO" }; + } + + return { type: typeof type === "string" ? type : "UNKNOWN" }; +} + +function summarizeNode(info) { + const inputs = {}; + for (const [name, spec] of Object.entries({ ...(info.input?.required ?? {}), ...(info.input?.optional ?? {}) })) { + inputs[name] = summarizeInput(spec); + } + return { inputs }; +} + +async function fetchJson(path) { + const response = await fetch(`${serverUrl}${path}`); + if (!response.ok) throw new Error(`${serverUrl}${path} answered HTTP ${response.status}`); + return response.json(); +} + +const classes = await usedClasses(); +const [objectInfo, systemStats] = await Promise.all([fetchJson("/object_info"), fetchJson("/system_stats")]); +const live = { + comfyuiVersion: systemStats.system?.comfyui_version ?? "unknown", + recordedAt: new Date().toISOString().slice(0, 10), + missingClasses: classes.filter((name) => !objectInfo[name]), + nodes: Object.fromEntries(classes.filter((name) => objectInfo[name]).map((name) => [name, summarizeNode(objectInfo[name])])) +}; + +// process.exitCode rather than process.exit(): exiting while fetch's sockets +// are still closing trips a libuv assertion on Windows. +if (!checkMode) { + await writeFile(snapshotPath, `${JSON.stringify(live, null, 2)}\n`); + console.log(`Recorded ${Object.keys(live.nodes).length} node classes from ComfyUI ${live.comfyuiVersion} at ${serverUrl}.`); + if (live.missingClasses.length > 0) console.log(`Not installed there: ${live.missingClasses.join(", ")}.`); +} else { + process.exitCode = checkAgainstSnapshot(JSON.parse(await readFile(snapshotPath, "utf8"))) ? 0 : 1; +} + +function checkAgainstSnapshot(recorded) { + const problems = []; + const notes = []; + let unusedChoicesGone = 0; + + for (const missing of recorded.missingClasses ?? []) { + notes.push(`${missing}: was not installed when the snapshot was recorded`); + } + + for (const [name, node] of Object.entries(recorded.nodes)) { + const now = live.nodes[name]; + if (!now) { + problems.push(`${name}: class is not there (a core node renamed, or its node pack is not installed)`); + continue; + } + for (const [input, spec] of Object.entries(node.inputs)) { + const current = now.inputs[input]; + if (!current) { + problems.push(`${name}.${input}: input is gone`); + continue; + } + for (const choice of spec.choices ?? []) { + if (!current.choices || current.choices.includes(choice)) continue; + if (usedChoices.get(`${name}.${input}`)?.has(choice)) problems.push(`${name}.${input}: choice "${choice}" is gone, and a preset uses it`); + else unusedChoicesGone += 1; + } + for (const [key, names] of Object.entries(spec.nested ?? {})) { + const nowNames = current.nested?.[key]; + if (!nowNames) problems.push(`${name}.${input}: option "${key}" is gone`); + else for (const child of names) if (!nowNames.includes(child)) problems.push(`${name}.${input}.${child}: nested input is gone`); + } + } + for (const input of Object.keys(now.inputs)) { + if (!node.inputs[input]) notes.push(`${name}.${input}: new input`); + } + } + + console.log(`Snapshot: ComfyUI ${recorded.comfyuiVersion} (${recorded.recordedAt}). Live: ComfyUI ${live.comfyuiVersion} at ${serverUrl}.`); + console.log(problems.length === 0 ? "Nothing OpenLayer uses is missing." : `Missing, and OpenLayer uses it:\n ${problems.join("\n ")}`); + if (unusedChoicesGone > 0) console.log(`${unusedChoicesGone} choice(s) no preset uses are also missing there (harmless).`); + if (notes.length > 0) console.log(`Notes:\n ${notes.join("\n ")}`); + return problems.length === 0; +} diff --git a/src/comfy/comfyClient.ts b/src/comfy/comfyClient.ts index 3356d08..4cddc86 100644 --- a/src/comfy/comfyClient.ts +++ b/src/comfy/comfyClient.ts @@ -256,6 +256,15 @@ export class ComfyClient { return inventory; } + /** + * For Workflow Health and Setup: every input each preset node *declares*, + * required and optional, because the question is whether the input OpenLayer + * wires exists -- the same reading validatePresetSetup uses before Generate. + * Reading required inputs only marked 13 working presets "Missing ComfyUI + * node" on ComfyUI 0.44, among them every crop-and-stitch Inpaint preset + * (InpaintCropImproved's mask), both Remove object presets (LaMaInpaint's + * lama_model) and Select by Text (SAM3_Detect's conditioning). Beta gate G1. + */ async getWorkflowNodeAvailability( presets: readonly WorkflowPresetDefinition[] ): Promise { @@ -267,23 +276,29 @@ export class ComfyClient { } } - return this.getNodeAvailability([...nodeClasses]); + return this.getNodeAvailability([...nodeClasses], "declared"); } /** * The same question for an arbitrary list of class types, so a workflow * OpenLayer did not author can be checked against this server. * - * Records only the inputs ComfyUI marks *required*. A caller that treats the - * absence of an optional input as a fault repeats the mistake documented on - * the Klein edit preset, where it reported a working graph as broken. + * By default records only the inputs ComfyUI marks *required*: the custom + * workflow check asks whether a graph supplies every one of those, and an + * optional input left out is not a fault there. "declared" records required + * and optional together, for a caller asking whether an input exists at all + * (see getWorkflowNodeAvailability). Mixing the two up is how Workflow + * Health came to report working presets as broken. * * A class that does not exist is left out rather than recorded as empty. * That matters more than it looks: `GET /object_info/` answers 200 * with an empty body for an unknown class, so presence of a *response* proves * nothing and only the presence of the schema does. */ - async getNodeAvailability(classTypes: readonly string[]): Promise { + async getNodeAvailability( + classTypes: readonly string[], + inputs: "required" | "declared" = "required" + ): Promise { const availability: WorkflowNodeAvailability = {}; for (const classType of classTypes) { @@ -292,7 +307,11 @@ export class ComfyClient { const schema = objectInfo[classType]; if (schema) { - availability[classType] = Object.keys(schema.input?.required ?? {}); + availability[classType] = Object.keys( + inputs === "declared" + ? { ...(schema.input?.required ?? {}), ...(schema.input?.optional ?? {}) } + : schema.input?.required ?? {} + ); } } catch { // Missing node classes are reported by the caller, not here. diff --git a/src/comfy/workflowCompatibility.ts b/src/comfy/workflowCompatibility.ts index 5bdd3d9..adbd480 100644 --- a/src/comfy/workflowCompatibility.ts +++ b/src/comfy/workflowCompatibility.ts @@ -47,6 +47,11 @@ export type WorkflowCompatibilityIssue = { link?: WorkflowCompatibilityLink; }; +/** + * Node class -> input names. Which inputs depends on who asked: the preset + * checks (Workflow Health, Setup) get every declared input, the custom + * workflow check gets the required ones. See ComfyClient.getNodeAvailability. + */ export type WorkflowNodeAvailability = Record; export type WorkflowPhotoshopInputAvailability = Partial>; diff --git a/tests/comfy/nodeAvailability.test.ts b/tests/comfy/nodeAvailability.test.ts new file mode 100644 index 0000000..81737f4 --- /dev/null +++ b/tests/comfy/nodeAvailability.test.ts @@ -0,0 +1,55 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { ComfyClient } from "../../src/comfy/comfyClient"; +import { getWorkflowPreset } from "../../src/comfy/presetRegistry"; +import { createWorkflowHealthItem } from "../../src/comfy/workflowHealth"; + +/** + * Workflow Health asked ComfyUI which inputs each preset node has, but kept + * only the required ones. On ComfyUI 0.44, 13 working presets were marked + * "Missing ComfyUI node" because the input they wire is optional there: + * InpaintCropImproved's mask, LaMaInpaint's lama_model, SAM3_Detect's + * conditioning, ReferenceLatent's latent and others. Beta gate G1. + */ +describe("node availability", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + // InpaintCropImproved as ComfyUI 0.44 reports it: `mask` is optional. + function stubInpaintCrop() { + vi.stubGlobal("fetch", async (url: string) => { + const classType = decodeURIComponent(String(url).split("/object_info/")[1] ?? ""); + const body = + classType === "InpaintCropImproved" + ? { InpaintCropImproved: { input: { required: { image: ["IMAGE"] }, optional: { mask: ["MASK"] } } } } + : {}; + return new Response(JSON.stringify(body), { status: 200 }); + }); + } + + it("gives the preset checks every declared input, optional ones included", async () => { + stubInpaintCrop(); + const nodes = await new ComfyClient("http://127.0.0.1:8188").getWorkflowNodeAvailability([ + getWorkflowPreset("inpaint-flux2-klein") + ]); + + expect(nodes.InpaintCropImproved).toEqual(["image", "mask"]); + }); + + it("keeps the custom workflow check on required inputs only", async () => { + // A pasted graph that leaves out an optional input is not broken. + stubInpaintCrop(); + const nodes = await new ComfyClient("http://127.0.0.1:8188").getNodeAvailability(["InpaintCropImproved"]); + + expect(nodes.InpaintCropImproved).toEqual(["image"]); + }); + + it("no longer reports a preset as missing a node when its input is optional", () => { + const preset = getWorkflowPreset("inpaint-flux2-klein"); + const crop = preset.requiredNodes.find((node) => node.classType === "InpaintCropImproved"); + const availableNodes = Object.fromEntries(preset.requiredNodes.map((node) => [node.classType, node.requiredInputs])); + + expect(crop?.requiredInputs).toContain("mask"); + expect(createWorkflowHealthItem(preset, { availableNodes }).state).not.toBe("missing-node"); + }); +}); diff --git a/tests/comfy/objectInfoSnapshot.test.ts b/tests/comfy/objectInfoSnapshot.test.ts new file mode 100644 index 0000000..0d19d92 --- /dev/null +++ b/tests/comfy/objectInfoSnapshot.test.ts @@ -0,0 +1,119 @@ +import { readdirSync, readFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { listWorkflowPresets } from "../../src/comfy/presetRegistry"; + +/** + * Every preset against a recorded ComfyUI /object_info. Beta gate G1 (roadmap U2). + * + * The snapshot is written by `node scripts/object-info-snapshot.mjs` from a live + * ComfyUI and holds only the node classes OpenLayer uses. When ComfyUI renames + * a node or an input, re-recording the snapshot makes this test name every + * preset it breaks -- before an artist finds it in Workflow Health. When a + * preset starts using a new node, this test asks for the snapshot to be + * recorded again. + * + * Re-record after updating ComfyUI: node scripts/object-info-snapshot.mjs + * Check a ComfyUI without recording: node scripts/object-info-snapshot.mjs --check + */ + +type InputSummary = { + type: string; + choices?: string[]; + nested?: Record; + names?: string[]; +}; + +type Snapshot = { + comfyuiVersion: string; + recordedAt: string; + missingClasses: string[]; + nodes: Record }>; +}; + +const root = resolve(__dirname, "../.."); +const snapshot = JSON.parse(readFileSync(join(root, "tests/fixtures/comfyui-object-info.json"), "utf8")) as Snapshot; +const apiDir = join(root, "src/workflows/api"); +const workflows = readdirSync(apiDir) + .filter((name) => name.endsWith(".json")) + .map((name) => ({ name, graph: JSON.parse(readFileSync(join(apiDir, name), "utf8")) as Record }> })); + +/** Whether `input` (possibly "parent.child") exists on the recorded node. */ +function hasInput(inputs: Record, input: string): boolean { + if (inputs[input]) return true; + + const dot = input.indexOf("."); + if (dot < 0) return false; + + const parent = inputs[input.slice(0, dot)]; + const child = input.slice(dot + 1); + + if (parent?.nested) return Object.values(parent.nested).some((names) => names.includes(child)); + if (parent?.names) return parent.names.includes(child); + return false; +} + +describe("ComfyUI object_info snapshot", () => { + it("was recorded with every node class OpenLayer uses installed", () => { + expect(snapshot.missingClasses, `record it again from a ComfyUI with every node pack installed`).toEqual([]); + expect(Object.keys(snapshot.nodes).length).toBeGreaterThan(0); + }); + + it("knows every node class in every bundled workflow", () => { + const unknown = workflows.flatMap(({ name, graph }) => + Object.values(graph) + .filter((node) => typeof node?.class_type === "string" && !snapshot.nodes[node.class_type]) + .map((node) => `${name}: ${node.class_type}`) + ); + + expect(unknown, "classes the snapshot lacks: record it again with node scripts/object-info-snapshot.mjs").toEqual([]); + }); + + it("has every input every bundled workflow sets, nested inputs included", () => { + const missing = workflows.flatMap(({ name, graph }) => + Object.values(graph).flatMap((node) => { + const recorded = node?.class_type ? snapshot.nodes[node.class_type] : undefined; + if (!recorded) return []; + return Object.keys(node.inputs ?? {}) + .filter((input) => !hasInput(recorded.inputs, input)) + .map((input) => `${name}: ${node.class_type}.${input}`); + }) + ); + + expect(missing).toEqual([]); + }); + + it("offers every fixed choice a bundled workflow uses, such as a sampler or scheduler", () => { + // Model file names are left out of the snapshot on purpose (they differ per + // machine), so only inputs with a recorded fixed list are checked. An empty + // string is a placeholder the panel fills in at run time. + const missing = workflows.flatMap(({ name, graph }) => + Object.values(graph).flatMap((node) => { + const recorded = node?.class_type ? snapshot.nodes[node.class_type] : undefined; + if (!recorded) return []; + return Object.entries(node.inputs ?? {}) + .filter(([input, value]) => { + const choices = recorded.inputs[input]?.choices; + return choices && typeof value === "string" && value !== "" && !choices.includes(value); + }) + .map(([input, value]) => `${name}: ${node.class_type}.${input} = "${String(value)}"`); + }) + ); + + expect(missing).toEqual([]); + }); + + it("has every node and input the preset registry requires", () => { + const missing = listWorkflowPresets().flatMap((preset) => + preset.requiredNodes.flatMap((required) => { + const recorded = snapshot.nodes[required.classType]; + if (!recorded) return [`${preset.id}: ${required.classType}`]; + return required.requiredInputs + .filter((input) => !hasInput(recorded.inputs, input)) + .map((input) => `${preset.id}: ${required.classType}.${input}`); + }) + ); + + expect(missing).toEqual([]); + }); +}); diff --git a/tests/fixtures/comfyui-object-info.json b/tests/fixtures/comfyui-object-info.json new file mode 100644 index 0000000..1d85333 --- /dev/null +++ b/tests/fixtures/comfyui-object-info.json @@ -0,0 +1,1577 @@ +{ + "comfyuiVersion": "0.44.0", + "recordedAt": "2026-11-04", + "missingClasses": [], + "nodes": { + "AnyLineArtPreprocessor_aux": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "merge_with_lineart": { + "type": "COMBO", + "choices": [ + "lineart_standard", + "lineart_realisitic", + "lineart_anime", + "manga_line" + ] + }, + "resolution": { + "type": "INT" + }, + "lineart_lower_bound": { + "type": "FLOAT" + }, + "lineart_upper_bound": { + "type": "FLOAT" + }, + "object_min_size": { + "type": "INT" + }, + "object_connectivity": { + "type": "INT" + } + } + }, + "BAE-NormalMapPreprocessor": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "resolution": { + "type": "INT" + } + } + }, + "BasicGuider": { + "inputs": { + "model": { + "type": "MODEL" + }, + "conditioning": { + "type": "CONDITIONING" + } + } + }, + "CFGNorm": { + "inputs": { + "model": { + "type": "MODEL" + }, + "strength": { + "type": "FLOAT" + }, + "pre_cfg": { + "type": "BOOLEAN" + } + } + }, + "CLIPLoader": { + "inputs": { + "clip_name": { + "type": "COMBO" + }, + "type": { + "type": "COMBO", + "choices": [ + "stable_diffusion", + "stable_cascade", + "sd3", + "stable_audio", + "mochi", + "ltxv", + "pixart", + "cosmos", + "lumina2", + "wan", + "hidream", + "chroma", + "ace", + "omnigen2", + "qwen_image", + "hunyuan_image", + "flux2", + "ovis", + "longcat_image", + "cogvideox", + "lens", + "pixeldit", + "ideogram4", + "boogu", + "krea2", + "joyimage", + "mage", + "minimax", + "yue2", + "flux3", + "moss_image", + "fibo2" + ] + }, + "device": { + "type": "COMBO", + "choices": [ + "default", + "cpu" + ] + } + } + }, + "CLIPTextEncode": { + "inputs": { + "text": { + "type": "STRING" + }, + "clip": { + "type": "CLIP" + } + } + }, + "CLIPVisionLoader": { + "inputs": { + "clip_name": { + "type": "COMBO" + } + } + }, + "CheckpointLoaderSimple": { + "inputs": { + "ckpt_name": { + "type": "COMBO" + } + } + }, + "ConditioningZeroOut": { + "inputs": { + "conditioning": { + "type": "CONDITIONING" + } + } + }, + "ControlNetApplyAdvanced": { + "inputs": { + "positive": { + "type": "CONDITIONING" + }, + "negative": { + "type": "CONDITIONING" + }, + "control_net": { + "type": "CONTROL_NET" + }, + "image": { + "type": "IMAGE" + }, + "strength": { + "type": "FLOAT" + }, + "start_percent": { + "type": "FLOAT" + }, + "end_percent": { + "type": "FLOAT" + }, + "vae": { + "type": "VAE" + } + } + }, + "ControlNetLoader": { + "inputs": { + "control_net_name": { + "type": "COMBO" + } + } + }, + "DepthAnythingV2Preprocessor": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "ckpt_name": { + "type": "COMBO" + }, + "resolution": { + "type": "INT" + } + } + }, + "DifferentialDiffusion": { + "inputs": { + "model": { + "type": "MODEL" + }, + "strength": { + "type": "FLOAT" + } + } + }, + "DualCLIPLoader": { + "inputs": { + "clip_name1": { + "type": "COMBO" + }, + "clip_name2": { + "type": "COMBO" + }, + "type": { + "type": "COMBO", + "choices": [ + "sdxl", + "sd3", + "flux", + "hunyuan_video", + "hidream", + "hunyuan_image", + "hunyuan_video_15", + "kandinsky5", + "kandinsky5_image", + "ltxv", + "newbie", + "ace" + ] + }, + "device": { + "type": "COMBO", + "choices": [ + "default", + "cpu" + ] + } + } + }, + "EmptyFlux2LatentImage": { + "inputs": { + "width": { + "type": "INT" + }, + "height": { + "type": "INT" + }, + "batch_size": { + "type": "INT" + } + } + }, + "EmptyLatentImage": { + "inputs": { + "width": { + "type": "INT" + }, + "height": { + "type": "INT" + }, + "batch_size": { + "type": "INT" + } + } + }, + "EmptyQwenImageLayeredLatentImage": { + "inputs": { + "width": { + "type": "INT" + }, + "height": { + "type": "INT" + }, + "layers": { + "type": "INT" + }, + "batch_size": { + "type": "INT" + } + } + }, + "EmptySD3LatentImage": { + "inputs": { + "width": { + "type": "INT" + }, + "height": { + "type": "INT" + }, + "batch_size": { + "type": "INT" + } + } + }, + "Florence2ModelLoader": { + "inputs": { + "model": { + "type": "COMBO", + "choices": [ + "Florence-2-base", + "Florence-2-base-ft", + "Florence-2-base-PromptGen-v2.0" + ] + }, + "precision": { + "type": "COMBO", + "choices": [ + "fp16", + "bf16", + "fp32" + ] + }, + "lora": { + "type": "PEFTLORA" + }, + "convert_to_safetensors": { + "type": "BOOLEAN" + } + } + }, + "Florence2Run": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "florence2_model": { + "type": "FL2MODEL" + }, + "text_input": { + "type": "STRING" + }, + "task": { + "type": "COMBO", + "choices": [ + "region_caption", + "dense_region_caption", + "region_proposal", + "caption", + "detailed_caption", + "more_detailed_caption", + "caption_to_phrase_grounding", + "referring_expression_segmentation", + "ocr", + "ocr_with_region", + "docvqa", + "prompt_gen_tags", + "prompt_gen_mixed_caption", + "prompt_gen_analyze", + "prompt_gen_mixed_caption_plus" + ] + }, + "fill_mask": { + "type": "BOOLEAN" + }, + "keep_model_loaded": { + "type": "BOOLEAN" + }, + "max_new_tokens": { + "type": "INT" + }, + "num_beams": { + "type": "INT" + }, + "do_sample": { + "type": "BOOLEAN" + }, + "output_mask_select": { + "type": "STRING" + }, + "seed": { + "type": "INT" + } + } + }, + "Flux2Scheduler": { + "inputs": { + "steps": { + "type": "INT" + }, + "width": { + "type": "INT" + }, + "height": { + "type": "INT" + } + } + }, + "FluxGuidance": { + "inputs": { + "conditioning": { + "type": "CONDITIONING" + }, + "guidance": { + "type": "FLOAT" + } + } + }, + "FluxKontextImageScale": { + "inputs": { + "image": { + "type": "IMAGE" + } + } + }, + "FluxKontextMultiReferenceLatentMethod": { + "inputs": { + "conditioning": { + "type": "CONDITIONING" + }, + "reference_latents_method": { + "type": "COMBO" + } + } + }, + "GetImageSize": { + "inputs": { + "image": { + "type": "IMAGE" + } + } + }, + "IPAdapterAdvanced": { + "inputs": { + "model": { + "type": "MODEL" + }, + "ipadapter": { + "type": "IPADAPTER" + }, + "image": { + "type": "IMAGE" + }, + "weight": { + "type": "FLOAT" + }, + "weight_type": { + "type": "COMBO", + "choices": [ + "linear", + "ease in", + "ease out", + "ease in-out", + "reverse in-out", + "weak input", + "weak output", + "weak middle", + "strong middle", + "style transfer", + "composition", + "strong style transfer", + "style and composition", + "style transfer precise", + "composition precise" + ] + }, + "combine_embeds": { + "type": "COMBO", + "choices": [ + "concat", + "add", + "subtract", + "average", + "norm average" + ] + }, + "start_at": { + "type": "FLOAT" + }, + "end_at": { + "type": "FLOAT" + }, + "embeds_scaling": { + "type": "COMBO" + }, + "image_negative": { + "type": "IMAGE" + }, + "attn_mask": { + "type": "MASK" + }, + "clip_vision": { + "type": "CLIP_VISION" + } + } + }, + "IPAdapterModelLoader": { + "inputs": { + "ipadapter_file": { + "type": "COMBO" + } + } + }, + "ImageCompositeMasked": { + "inputs": { + "destination": { + "type": "IMAGE" + }, + "source": { + "type": "IMAGE" + }, + "x": { + "type": "INT" + }, + "y": { + "type": "INT" + }, + "resize_source": { + "type": "BOOLEAN" + }, + "mask": { + "type": "MASK" + } + } + }, + "ImageInvert": { + "inputs": { + "image": { + "type": "IMAGE" + } + } + }, + "ImagePadForOutpaint": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "left": { + "type": "INT" + }, + "top": { + "type": "INT" + }, + "right": { + "type": "INT" + }, + "bottom": { + "type": "INT" + }, + "feathering": { + "type": "INT" + } + } + }, + "ImageScale": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "upscale_method": { + "type": "COMBO", + "choices": [ + "nearest-exact", + "bilinear", + "area", + "bicubic", + "lanczos" + ] + }, + "width": { + "type": "INT" + }, + "height": { + "type": "INT" + }, + "crop": { + "type": "COMBO", + "choices": [ + "disabled", + "center" + ] + } + } + }, + "ImageScaleToMaxDimension": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "upscale_method": { + "type": "COMBO", + "choices": [ + "area", + "lanczos", + "bilinear", + "nearest-exact", + "bilinear", + "bicubic" + ] + }, + "largest_size": { + "type": "INT" + } + } + }, + "ImageScaleToTotalPixels": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "upscale_method": { + "type": "COMBO", + "choices": [ + "nearest-exact", + "bilinear", + "area", + "bicubic", + "lanczos" + ] + }, + "megapixels": { + "type": "FLOAT" + }, + "resolution_steps": { + "type": "INT" + } + } + }, + "ImageToMask": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "channel": { + "type": "COMBO", + "choices": [ + "red", + "green", + "blue", + "alpha" + ] + } + } + }, + "ImageUpscaleWithModel": { + "inputs": { + "upscale_model": { + "type": "UPSCALE_MODEL" + }, + "image": { + "type": "IMAGE" + } + } + }, + "InpaintCropImproved": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "downscale_algorithm": { + "type": "COMBO", + "choices": [ + "nearest", + "bilinear", + "bicubic", + "lanczos", + "box", + "hamming" + ] + }, + "upscale_algorithm": { + "type": "COMBO", + "choices": [ + "nearest", + "bilinear", + "bicubic", + "lanczos", + "box", + "hamming" + ] + }, + "preresize": { + "type": "BOOLEAN" + }, + "preresize_mode": { + "type": "COMBO", + "choices": [ + "ensure minimum resolution", + "ensure maximum resolution", + "ensure minimum and maximum resolution" + ] + }, + "preresize_min_width": { + "type": "INT" + }, + "preresize_min_height": { + "type": "INT" + }, + "preresize_max_width": { + "type": "INT" + }, + "preresize_max_height": { + "type": "INT" + }, + "mask_fill_holes": { + "type": "BOOLEAN" + }, + "mask_expand_pixels": { + "type": "INT" + }, + "mask_invert": { + "type": "BOOLEAN" + }, + "mask_blend_pixels": { + "type": "INT" + }, + "mask_hipass_filter": { + "type": "FLOAT" + }, + "extend_for_outpainting": { + "type": "BOOLEAN" + }, + "extend_up_factor": { + "type": "FLOAT" + }, + "extend_down_factor": { + "type": "FLOAT" + }, + "extend_left_factor": { + "type": "FLOAT" + }, + "extend_right_factor": { + "type": "FLOAT" + }, + "context_from_mask_extend_factor": { + "type": "FLOAT" + }, + "output_resize_to_target_size": { + "type": "BOOLEAN" + }, + "output_target_width": { + "type": "INT" + }, + "output_target_height": { + "type": "INT" + }, + "output_padding": { + "type": "COMBO", + "choices": [ + "0", + "8", + "16", + "32", + "64", + "128", + "256", + "512" + ] + }, + "device_mode": { + "type": "COMBO", + "choices": [ + "cpu (compatible)", + "gpu (much faster)" + ] + }, + "mask": { + "type": "MASK" + }, + "optional_context_mask": { + "type": "MASK" + } + } + }, + "InpaintModelConditioning": { + "inputs": { + "positive": { + "type": "CONDITIONING" + }, + "negative": { + "type": "CONDITIONING" + }, + "vae": { + "type": "VAE" + }, + "pixels": { + "type": "IMAGE" + }, + "mask": { + "type": "MASK" + }, + "noise_mask": { + "type": "BOOLEAN" + } + } + }, + "InpaintStitchImproved": { + "inputs": { + "stitcher": { + "type": "STITCHER" + }, + "inpainted_image": { + "type": "IMAGE" + } + } + }, + "InvertMask": { + "inputs": { + "mask": { + "type": "MASK" + } + } + }, + "JoinImageWithAlpha": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "alpha": { + "type": "MASK" + } + } + }, + "KSampler": { + "inputs": { + "model": { + "type": "MODEL" + }, + "seed": { + "type": "INT" + }, + "steps": { + "type": "INT" + }, + "cfg": { + "type": "FLOAT" + }, + "sampler_name": { + "type": "COMBO", + "choices": [ + "euler", + "euler_cfg_pp", + "euler_ancestral", + "euler_ancestral_cfg_pp", + "heun", + "heunpp2", + "exp_heun_2_x0", + "exp_heun_2_x0_sde", + "dpm_2", + "dpm_2_ancestral", + "lms", + "dpm_fast", + "dpm_adaptive", + "dpmpp_2s_ancestral", + "dpmpp_2s_ancestral_cfg_pp", + "dpmpp_sde", + "dpmpp_sde_gpu", + "dpmpp_2m", + "dpmpp_2m_cfg_pp", + "dpmpp_2m_sde", + "dpmpp_2m_sde_gpu", + "dpmpp_2m_sde_heun", + "dpmpp_2m_sde_heun_gpu", + "dpmpp_3m_sde", + "dpmpp_3m_sde_gpu", + "ddpm", + "lcm", + "ipndm", + "ipndm_v", + "deis", + "cfgpp_ud10_ab", + "res_multistep", + "res_multistep_cfg_pp", + "res_multistep_ancestral", + "res_multistep_ancestral_cfg_pp", + "gradient_estimation", + "gradient_estimation_cfg_pp", + "er_sde", + "seeds_2", + "seeds_3", + "sa_solver", + "sa_solver_pece", + "ddim", + "uni_pc", + "uni_pc_bh2", + "legacy_rk", + "rk", + "rk_beta", + "deis_3m_ode", + "deis_2m_ode", + "deis_3m", + "deis_2m", + "res_6s_ode", + "res_5s_ode", + "res_3s_ode", + "res_2s_ode", + "res_3m_ode", + "res_2m_ode", + "res_6s", + "res_5s", + "res_3s", + "res_2s", + "res_3m", + "res_2m" + ] + }, + "scheduler": { + "type": "COMBO", + "choices": [ + "simple", + "sgm_uniform", + "karras", + "exponential", + "ddim_uniform", + "beta", + "normal", + "linear_quadratic", + "kl_optimal", + "bong_tangent", + "beta57" + ] + }, + "positive": { + "type": "CONDITIONING" + }, + "negative": { + "type": "CONDITIONING" + }, + "latent_image": { + "type": "LATENT" + }, + "denoise": { + "type": "FLOAT" + } + } + }, + "KSamplerSelect": { + "inputs": { + "sampler_name": { + "type": "COMBO", + "choices": [ + "euler", + "euler_cfg_pp", + "euler_ancestral", + "euler_ancestral_cfg_pp", + "heun", + "heunpp2", + "exp_heun_2_x0", + "exp_heun_2_x0_sde", + "dpm_2", + "dpm_2_ancestral", + "lms", + "dpm_fast", + "dpm_adaptive", + "dpmpp_2s_ancestral", + "dpmpp_2s_ancestral_cfg_pp", + "dpmpp_sde", + "dpmpp_sde_gpu", + "dpmpp_2m", + "dpmpp_2m_cfg_pp", + "dpmpp_2m_sde", + "dpmpp_2m_sde_gpu", + "dpmpp_2m_sde_heun", + "dpmpp_2m_sde_heun_gpu", + "dpmpp_3m_sde", + "dpmpp_3m_sde_gpu", + "ddpm", + "lcm", + "ipndm", + "ipndm_v", + "deis", + "cfgpp_ud10_ab", + "res_multistep", + "res_multistep_cfg_pp", + "res_multistep_ancestral", + "res_multistep_ancestral_cfg_pp", + "gradient_estimation", + "gradient_estimation_cfg_pp", + "er_sde", + "seeds_2", + "seeds_3", + "sa_solver", + "sa_solver_pece", + "ddim", + "uni_pc", + "uni_pc_bh2", + "legacy_rk", + "rk", + "rk_beta", + "deis_3m_ode", + "deis_2m_ode", + "deis_3m", + "deis_2m", + "res_6s_ode", + "res_5s_ode", + "res_3s_ode", + "res_2s_ode", + "res_3m_ode", + "res_2m_ode", + "res_6s", + "res_5s", + "res_3s", + "res_2s", + "res_3m", + "res_2m" + ] + } + } + }, + "LaMaInpaint": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "mask": { + "type": "MASK" + }, + "device_mode": { + "type": "COMBO", + "choices": [ + "AUTO", + "Prefer GPU", + "CPU" + ] + }, + "lama_model": { + "type": "LAMA" + } + } + }, + "LatentCutToBatch": { + "inputs": { + "samples": { + "type": "LATENT" + }, + "dim": { + "type": "COMBO", + "choices": [ + "t", + "x", + "y" + ] + }, + "slice_size": { + "type": "INT" + } + } + }, + "LineArtPreprocessor": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "coarse": { + "type": "COMBO", + "choices": [ + "disable", + "enable" + ] + }, + "resolution": { + "type": "INT" + } + } + }, + "LoadBackgroundRemovalModel": { + "inputs": { + "bg_removal_name": { + "type": "COMBO" + } + } + }, + "LoadImage": { + "inputs": { + "image": { + "type": "COMBO" + } + } + }, + "LoadLaMaModel": { + "inputs": { + "model_name": { + "type": "COMBO" + } + } + }, + "LoraLoader": { + "inputs": { + "model": { + "type": "MODEL" + }, + "clip": { + "type": "CLIP" + }, + "lora_name": { + "type": "COMBO" + }, + "strength_model": { + "type": "FLOAT" + }, + "strength_clip": { + "type": "FLOAT" + } + } + }, + "LoraLoaderModelOnly": { + "inputs": { + "model": { + "type": "MODEL" + }, + "lora_name": { + "type": "COMBO" + }, + "strength_model": { + "type": "FLOAT" + } + } + }, + "MaskToImage": { + "inputs": { + "mask": { + "type": "MASK" + } + } + }, + "ModelPatchLoader": { + "inputs": { + "name": { + "type": "COMBO" + } + } + }, + "ModelSamplingAuraFlow": { + "inputs": { + "model": { + "type": "MODEL" + }, + "shift": { + "type": "FLOAT" + }, + "sampling": { + "type": "COMBO", + "choices": [ + "flow", + "img_to_img_velocity" + ] + } + } + }, + "PreviewAny": { + "inputs": { + "source": { + "type": "*" + } + } + }, + "RandomNoise": { + "inputs": { + "noise_seed": { + "type": "INT" + } + } + }, + "ReferenceLatent": { + "inputs": { + "conditioning": { + "type": "CONDITIONING" + }, + "latent": { + "type": "LATENT" + } + } + }, + "RemoveBackground": { + "inputs": { + "bg_removal_model": { + "type": "BACKGROUND_REMOVAL" + }, + "image": { + "type": "IMAGE" + } + } + }, + "ResizeImageMaskNode": { + "inputs": { + "input": { + "type": "COMFY_MATCHTYPE_V3" + }, + "resize_type": { + "type": "COMFY_DYNAMICCOMBO_V3", + "nested": { + "scale dimensions": [ + "crop", + "height", + "width" + ], + "scale by multiplier": [ + "multiplier" + ], + "scale longer dimension": [ + "longer_size" + ], + "scale shorter dimension": [ + "shorter_size" + ], + "scale width": [ + "width" + ], + "scale height": [ + "height" + ], + "scale total pixels": [ + "megapixels" + ], + "match size": [ + "crop", + "match" + ], + "scale to multiple": [ + "multiple" + ] + } + }, + "scale_method": { + "type": "COMBO", + "choices": [ + "nearest-exact", + "bilinear", + "area", + "bicubic", + "lanczos" + ] + } + } + }, + "SAM3_Detect": { + "inputs": { + "model": { + "type": "MODEL" + }, + "image": { + "type": "IMAGE" + }, + "threshold": { + "type": "FLOAT" + }, + "refine_iterations": { + "type": "INT" + }, + "individual_masks": { + "type": "BOOLEAN" + }, + "conditioning": { + "type": "CONDITIONING" + }, + "bboxes": { + "type": "BOUNDING_BOX" + }, + "positive_coords": { + "type": "STRING" + }, + "negative_coords": { + "type": "STRING" + } + } + }, + "SamplerCustomAdvanced": { + "inputs": { + "noise": { + "type": "NOISE" + }, + "guider": { + "type": "GUIDER" + }, + "sampler": { + "type": "SAMPLER" + }, + "sigmas": { + "type": "SIGMAS" + }, + "latent_image": { + "type": "LATENT" + } + } + }, + "SaveImage": { + "inputs": { + "images": { + "type": "IMAGE" + }, + "filename_prefix": { + "type": "STRING" + } + } + }, + "Scribble_PiDiNet_Preprocessor": { + "inputs": { + "image": { + "type": "IMAGE" + }, + "safe": { + "type": "COMBO", + "choices": [ + "enable", + "disable" + ] + }, + "resolution": { + "type": "INT" + } + } + }, + "SeedVR2Conditioning": { + "inputs": { + "model": { + "type": "MODEL" + }, + "vae_conditioning": { + "type": "LATENT" + } + } + }, + "SeedVR2PostProcessing": { + "inputs": { + "images": { + "type": "IMAGE" + }, + "original_resized_images": { + "type": "IMAGE" + }, + "color_correction_method": { + "type": "COMBO", + "choices": [ + "lab", + "wavelet", + "adain", + "none" + ] + } + } + }, + "SeedVR2Preprocess": { + "inputs": { + "resized_images": { + "type": "IMAGE" + } + } + }, + "SetLatentNoiseMask": { + "inputs": { + "samples": { + "type": "LATENT" + }, + "mask": { + "type": "MASK" + } + } + }, + "SplitImageWithAlpha": { + "inputs": { + "image": { + "type": "IMAGE" + } + } + }, + "StringConstantMultiline": { + "inputs": { + "string": { + "type": "STRING" + }, + "strip_newlines": { + "type": "BOOLEAN" + } + } + }, + "Superprompt": { + "inputs": { + "instruction_prompt": { + "type": "STRING" + }, + "prompt": { + "type": "STRING" + }, + "max_new_tokens": { + "type": "INT" + } + } + }, + "TextEncodeQwenImage21": { + "inputs": { + "clip": { + "type": "CLIP" + }, + "prompt": { + "type": "STRING" + }, + "negative_prompt": { + "type": "STRING" + }, + "resolution": { + "type": "INT" + }, + "images": { + "type": "COMFY_AUTOGROW_V3", + "names": [ + "image_1", + "image_2", + "image_3", + "image_4", + "image_5", + "image_6", + "image_7", + "image_8", + "image_9", + "image_10", + "image_11", + "image_12", + "image_13", + "image_14", + "image_15", + "image_16" + ] + }, + "vae": { + "type": "VAE" + } + } + }, + "TextEncodeQwenImageEditPlus": { + "inputs": { + "clip": { + "type": "CLIP" + }, + "prompt": { + "type": "STRING" + }, + "vae": { + "type": "VAE" + }, + "image1": { + "type": "IMAGE" + }, + "image2": { + "type": "IMAGE" + }, + "image3": { + "type": "IMAGE" + } + } + }, + "UNETLoader": { + "inputs": { + "unet_name": { + "type": "COMBO" + }, + "weight_dtype": { + "type": "COMBO", + "choices": [ + "default", + "fp8_e4m3fn", + "fp8_e4m3fn_fast", + "fp8_e5m2" + ] + } + } + }, + "UnetLoaderGGUF": { + "inputs": { + "unet_name": { + "type": "COMBO" + } + } + }, + "UpscaleModelLoader": { + "inputs": { + "model_name": { + "type": "COMBO" + } + } + }, + "VAEDecode": { + "inputs": { + "samples": { + "type": "LATENT" + }, + "vae": { + "type": "VAE" + } + } + }, + "VAEDecodeTiled": { + "inputs": { + "samples": { + "type": "LATENT" + }, + "vae": { + "type": "VAE" + }, + "tile_size": { + "type": "INT" + }, + "overlap": { + "type": "INT" + }, + "temporal_size": { + "type": "INT" + }, + "temporal_overlap": { + "type": "INT" + } + } + }, + "VAEEncode": { + "inputs": { + "pixels": { + "type": "IMAGE" + }, + "vae": { + "type": "VAE" + } + } + }, + "VAEEncodeTiled": { + "inputs": { + "pixels": { + "type": "IMAGE" + }, + "vae": { + "type": "VAE" + }, + "tile_size": { + "type": "INT" + }, + "overlap": { + "type": "INT" + }, + "temporal_size": { + "type": "INT" + }, + "temporal_overlap": { + "type": "INT" + } + } + }, + "VAELoader": { + "inputs": { + "vae_name": { + "type": "COMBO" + } + } + }, + "ZImageFunControlnet": { + "inputs": { + "model": { + "type": "MODEL" + }, + "model_patch": { + "type": "MODEL_PATCH" + }, + "vae": { + "type": "VAE" + }, + "strength": { + "type": "FLOAT" + }, + "image": { + "type": "IMAGE" + }, + "inpaint_image": { + "type": "IMAGE" + }, + "mask": { + "type": "MASK" + }, + "start_percent": { + "type": "FLOAT" + }, + "end_percent": { + "type": "FLOAT" + } + } + } + } +}