Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Load `dist/` in Photoshop with UXP Developer Tool (Add Plugin → `dist/manifest
Don't grow it: new tools go in `src/ui/tools/<name>.ts` (see `livePainting.ts`, `layerTools.ts`).
- `src/ui/generationController.ts`: the only way to run a generation (submit → WS progress →
poll `/history` → fetch `/view` → commit). Exactly one active run; stale runs cannot commit.
- `src/comfy/presetRegistry.ts`: source of truth for every preset (39): API workflow file, node
- `src/comfy/presetRegistry.ts`: source of truth for every preset (40): API workflow file, node
ids + `class_type` + required inputs, injection targets, model loader, required models with
download URL/size/licence gate. Setup screen, health checks, setup pack and hardware advisor
derive from it (Live Painting graphs are the exception).
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,13 +351,14 @@ Almost every preset is one of these. Install a stack once and every tool that us
costs 12.3 GB, not 20.7 GB. `ae.safetensors` is shared by Z_image_Turbo and Flux Fill.

<details>
<summary><b>Text to Image</b> — Klein, Z-Image, Krea-2, or any checkpoint you own</summary>
<summary><b>Text to Image</b> — Klein, Z-Image, NitroFusion2, Krea-2, or any checkpoint you own</summary>

| Preset | Needs | Extra |
| :--- | :--- | :--- |
| Standard checkpoint | any SD 1.x / SDXL checkpoint you already have | — |
| FLUX.2 Klein | **Klein stack** | — |
| Z_image_Turbo | **Z_image_Turbo stack** | — |
| NitroFusion2 | `nf2-bf16.safetensors` → `models/diffusion_models/` + the **Z_image_Turbo stack**'s `qwen_3_4b` and `ae` | +12.3 GB · Apache-2.0 · one step, a Z_image_Turbo fine-tune |
| Krea-2 Turbo | **Krea-2 Turbo stack** | — |
| Qwen-Image 2.1 | **Qwen-Image 2.1 stack** | research licence · the one preset with **Transparent Background** · native 2K |
| Flux1-dev fp8 | `flux1-dev-fp8.safetensors` → `models/checkpoints/` (17.3 GB) | non-commercial licence |
Expand Down
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ <h2 id="features-title">What works now</h2>
<article class="tool-card">
<span class="tool-icon"><img src="assets/tools/text-to-image.png" alt=""></span>
<div><h3>Text to Image</h3><span class="chip chip-ready">Stable</span></div>
<p>Prompt, negative prompt, size, steps, CFG, and seed into your checkpoint of choice — SD 1.x, SDXL, FLUX.2 Klein, Z-Image-Turbo, Krea-2, and Qwen-Image 2.1, which can hand back a transparent cut-out layer and letters spelled exactly.</p>
<p>Prompt, negative prompt, size, steps, CFG, and seed into your checkpoint of choice — SD 1.x, SDXL, FLUX.2 Klein, Z-Image-Turbo, one-step NitroFusion2, Krea-2, and Qwen-Image 2.1, which can hand back a transparent cut-out layer and letters spelled exactly.</p>
</article>
<article class="tool-card">
<span class="tool-icon"><img src="assets/tools/image-to-image.png" alt=""></span>
Expand Down
2 changes: 1 addition & 1 deletion docs/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ First run: start with FLUX.2 Klein 4B (Apache-2.0, three files, 12.5 GB, no sign

## Supported models

FLUX.2 Klein 4B (Apache-2.0), SeedVR2 3B for generative upscale (Apache-2.0), Qwen-Image 2.1, Qwen-Image-Layered, Krea-2 Turbo, Z-Image Turbo, Flux Fill, and Stable Diffusion 1.5 checkpoints with your own LoRAs. Qwen-Image 2.1 weights are licensed for research and evaluation only, and Flux Fill is non-commercial, so those presets are opt-in and labelled. Check each model's licence before commercial use.
FLUX.2 Klein 4B (Apache-2.0), SeedVR2 3B for generative upscale (Apache-2.0), Qwen-Image 2.1, Qwen-Image-Layered, Krea-2 Turbo, Z-Image Turbo, NitroFusion2 (Apache-2.0, a one-step Z-Image Turbo fine-tune), Flux Fill, and Stable Diffusion 1.5 checkpoints with your own LoRAs. Qwen-Image 2.1 weights are licensed for research and evaluation only, and Flux Fill is non-commercial, so those presets are opt-in and labelled. Check each model's licence before commercial use.

## Links

Expand Down
1 change: 1 addition & 0 deletions docs/model-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ These are practical starter guidelines, not strict rules.
| Inpaint / Flux Fill | Flux Fill Dev | Experimental `inpaint-flux-fill-basic`; not production-ready. `inpaint-flux-fill-cropstitch` uses the same files and adds the `comfyui-inpaint-cropandstitch` node pack. |
| Outpaint / Canvas Expansion | Flux Fill Dev | Experimental `outpaint-flux-fill-basic` using `ImagePadForOutpaint`. |
| Z_image_Turbo Text/Image | Z_image_Turbo | Experimental dedicated presets in v0.4.3-alpha. |
| NitroFusion2 Text to Image | NitroFusion2 (a Z_image_Turbo fine-tune) | `txt2img-nitrofusion2`: 1 step, CFG 1, 1024 × 1024. Needs `nf2-bf16.safetensors` (12.3 GB, Apache-2.0, ungated) plus Z_image_Turbo's `qwen_3_4b` and `ae`. Keep steps at 1 or 2; at 4 it speckles. |
| Prompt from Layer | Florence-2 PromptGen | Available for alpha testing through `prompt-from-layer-florence2` for describing captured Photoshop sources. |
| Future Realtime Preview | To be tested | Likely needs a very fast dedicated workflow. |

Expand Down
9 changes: 9 additions & 0 deletions src/comfy/modelCompatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ export function detectCheckpointFamily(checkpointName: string): ModelFamily {
return "zImage";
}

// NitroFusion2 is a Z_image_Turbo fine-tune whose files are named nf2-*.
// Matched as a prefix rather than a substring: "nf2" is short enough to turn
// up inside unrelated names. ComfyUI lists files in subfolders as
// "folder/name", so the prefix is checked on the file name alone.
const fileName = normalized.slice(Math.max(normalized.lastIndexOf("/"), normalized.lastIndexOf("\\")) + 1);
if (fileName.startsWith("nf2-") || fileName.startsWith("nf2_") || normalized.includes("nitrofusion2")) {
return "zImage";
}

// Must precede the plain "flux" test, which would otherwise swallow every
// Flux.2 filename by substring. The two are not interchangeable: Flux.1 runs
// through a plain KSampler, while Flux.2 needs the advanced sampler chain and
Expand Down
127 changes: 127 additions & 0 deletions src/comfy/presetRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ const COMFY_ORG_FLUX1_DEV_REPO = "https://huggingface.co/Comfy-Org/flux1-dev";
const COMFY_ORG_FLUX2_DEV_REPO = "https://huggingface.co/Comfy-Org/flux2-dev";
const CITY96_FLUX2_DEV_GGUF_REPO = "https://huggingface.co/city96/FLUX.2-dev-gguf";
const COMFY_ORG_Z_IMAGE_TURBO_REPO = "https://huggingface.co/Comfy-Org/z_image_turbo";
// NitroFusion2 is an Apache-2.0, ungated fine-tune of Z_image_Turbo, published by
// its authors rather than repackaged by Comfy-Org, so the model URL points at
// their repo. No licenseGate, for the same reason Klein has none.
const CHENDY_NITROFUSION2_REPO = "https://huggingface.co/ChenDY/NitroFusion2";
// Klein ships Apache-2.0 and ungated, unlike FLUX.1-dev and FLUX.2-dev, so
// there is deliberately no licenseGate on the stack below -- the setup pack can
// point straight at the file with no click-through.
Expand Down Expand Up @@ -386,6 +390,32 @@ const Z_IMAGE_TURBO_STACK = [
}
] as const;

/**
* NitroFusion2 swaps only the diffusion model: it is Z_image_Turbo's own
* architecture (the bf16 file is byte-for-byte the same size) with
* adversarially distilled weights, so it reads the same qwen_3_4b encoder
* through CLIPLoader type lumina2 and decodes through the same ae VAE. The two
* shared entries are taken from Z_IMAGE_TURBO_STACK itself rather than copied,
* so the setup pack de-duplicates them and a user who already runs Z_image_Turbo
* downloads only the 12.3 GB model.
*/
const NITROFUSION2_STACK = [
{
kind: "diffusion-model-stack",
objectInfoNode: "UNETLoader",
inputName: "unet_name",
label: "NitroFusion2 diffusion model",
modelName: "nf2-bf16.safetensors",
setupHint: "Install nf2-bf16.safetensors where ComfyUI's UNETLoader can find it.",
downloadUrl: `${CHENDY_NITROFUSION2_REPO}/resolve/main/nf2-bf16.safetensors`,
sourcePageUrl: CHENDY_NITROFUSION2_REPO,
downloadSizeBytes: 12309866400,
sha256: "a1e11422d84c1986ba106f892eda86b955b752627ec902aefff1fb939c7ac19c"
},
Z_IMAGE_TURBO_STACK[1],
Z_IMAGE_TURBO_STACK[2]
] as const;

/**
* ControlNet-as-model-patch weights for the Z_image_Turbo stack, applied
* through the core `ModelPatchLoader` / `ZImageFunControlnet` pair rather than
Expand Down Expand Up @@ -2589,6 +2619,30 @@ const Z_IMAGE_TURBO_TXT2IMG_CAPABILITY: WorkflowCapability = {
}
};

const NITROFUSION2_TXT2IMG_CAPABILITY: WorkflowCapability = {
toolType: "txt2img",
loaderType: "diffusion-model-stack",
artistLabel: "Text to Image",
technicalLabel: "txt2img-nitrofusion2",
requiredPhotoshopInputs: [],
controls: ["prompt", "negativePrompt", "width", "height", "steps", "guidance", "seed"],
output: {
kind: "full-image",
size: "preset",
importBehavior: "new-layer"
},
uiHints: {
showModelSelector: true,
modelSelectorLabel: "NitroFusion2 model",
primaryActionLabel: "Generate",
// CFG 1 means the sampler never reads the negative prompt, so a field for
// it would only look like it does something.
hiddenControls: ["negativePrompt"],
experimentalNote:
"NitroFusion2 is a one-step version of Z_image_Turbo: 1 step at CFG 1, at 1024 x 1024. Keep steps at 1 or 2. More steps do not improve it; at 4 it starts to speckle. It is fastest when you re-roll the same prompt with a new seed, because ComfyUI keeps the prompt encoded."
}
};

const Z_IMAGE_TURBO_IMG2IMG_CAPABILITY: WorkflowCapability = {
toolType: "img2img",
loaderType: "diffusion-model-stack",
Expand Down Expand Up @@ -5197,6 +5251,79 @@ export const WORKFLOW_PRESETS: WorkflowPresetDefinition[] = [
compatibilityNote:
"Z_image_Turbo is a diffusion model stack, not a checkpoint. OpenLayer loads it through UNETLoader, CLIPLoader, and VAELoader."
},
{
id: "txt2img-nitrofusion2",
label: "txt2img-nitrofusion2",
displayName: "NitroFusion2",
mode: "txt2img",
description: "One-step text-to-image preset for NitroFusion2, a distilled fine-tune of Z_image_Turbo.",
workflowFile: "workflows/api/txt2img-nitrofusion2.json",
status: "stable",
recommendedSettings: { steps: 1, cfg: 1, width: 1024, height: 1024 },
supportedModelFamilies: ["zImage"],
experimentalModelFamilies: ["unknown"],
modelSource: DIFFUSION_MODEL_SOURCE,
capability: NITROFUSION2_TXT2IMG_CAPABILITY,
modelStack: [...NITROFUSION2_STACK],
requiredModels: [...NITROFUSION2_STACK],
// The graph keeps the Z_image_Turbo graph's node ids, so its injection map
// is the same one; only the model file and sampler settings differ.
injections: Z_IMAGE_TURBO_TXT2IMG_INJECTIONS,
requiredNodes: [
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.diffusionModelLoader,
classType: "UNETLoader",
requiredInputs: ["unet_name", "weight_dtype"]
},
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.clipLoader,
classType: "CLIPLoader",
requiredInputs: ["clip_name", "type"]
},
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.vaeLoader,
classType: "VAELoader",
requiredInputs: ["vae_name"]
},
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.modelSampling,
classType: "ModelSamplingAuraFlow",
requiredInputs: ["model", "shift"]
},
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.positivePrompt,
classType: "CLIPTextEncode",
requiredInputs: ["text", "clip"]
},
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.negativePrompt,
classType: "CLIPTextEncode",
requiredInputs: ["text", "clip"]
},
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.latentImage,
classType: "EmptySD3LatentImage",
requiredInputs: ["width", "height", "batch_size"]
},
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.sampler,
classType: "KSampler",
requiredInputs: ["model", "seed", "steps", "cfg", "sampler_name", "scheduler", "positive", "negative", "latent_image", "denoise"]
},
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.decode,
classType: "VAEDecode",
requiredInputs: ["samples", "vae"]
},
{
id: Z_IMAGE_TURBO_TXT2IMG_NODES.saveImage,
classType: "SaveImage",
requiredInputs: ["images"]
}
],
compatibilityNote:
"txt2img-nitrofusion2 is the txt2img-z-image-turbo graph with three values changed, matching the authors' own one-step ComfyUI workflow: nf2-bf16.safetensors in UNETLoader, 1 step instead of 4, and euler instead of res_multistep, with the same ModelSamplingAuraFlow shift 3, CFG 1 and simple scheduler. Those values are the point of the preset rather than a tuning choice. On 2026-11-09, running NitroFusion2 through the Z_image_Turbo preset's own settings (4 steps, res_multistep) on six prompts added white splatter and speckle that the one-step run did not have, which is why the model is its own preset and not just a file picked in Z_image_Turbo. Measured on the same day on a 12 GB RTX 4070 Ti at 1024 x 1024: about 2.5 s against Z_image_Turbo's 5 s when only the seed changes, but 14 s against 17.5 s for a new prompt, because the 8 GB encoder and the 12.3 GB model do not fit on the card together and ComfyUI swaps them for every new prompt. The authors' two-step graph is not used: it needs their de-distillation LoRA switched on for the first step only, across two KSamplerAdvanced nodes, which OpenLayer's LoRA insertion cannot express. It declares no loraInsertion either: no Z_image_Turbo LoRA has been tried on the one-step weights, and the Z_image_Turbo preset stays the place for LoRAs until one has. GGUF quantisations exist but need the ComfyUI-GGUF loader and are not wired up here."
},
{
id: "img2img-z-image-turbo",
label: "img2img-z-image-turbo",
Expand Down
1 change: 1 addition & 0 deletions src/comfy/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type WorkflowPreset =
| "img2img-flux2-klein"
| "edit-flux2-klein"
| "txt2img-z-image-turbo"
| "txt2img-nitrofusion2"
| "img2img-z-image-turbo"
| "txt2img-krea2-turbo"
| "img2img-krea2-turbo"
Expand Down
2 changes: 2 additions & 0 deletions src/comfy/workflowBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import img2imgBasicWorkflow from "../workflows/api/img2img-basic.json";
import txt2imgFlux1DevFp8Workflow from "../workflows/api/txt2img-flux1-dev-fp8.json";
import txt2imgFlux2DevGgufWorkflow from "../workflows/api/txt2img-flux2-dev-gguf.json";
import txt2imgZImageTurboWorkflow from "../workflows/api/txt2img-z-image-turbo.json";
import txt2imgNitroFusion2Workflow from "../workflows/api/txt2img-nitrofusion2.json";
import img2imgZImageTurboWorkflow from "../workflows/api/img2img-z-image-turbo.json";
import txt2imgKrea2TurboWorkflow from "../workflows/api/txt2img-krea2-turbo.json";
import img2imgKrea2TurboWorkflow from "../workflows/api/img2img-krea2-turbo.json";
Expand Down Expand Up @@ -73,6 +74,7 @@ const WORKFLOW_TEMPLATES: Partial<Record<WorkflowPreset, ComfyWorkflow>> = {
"txt2img-flux1-dev-fp8": txt2imgFlux1DevFp8Workflow as ComfyWorkflow,
"txt2img-flux2-dev-gguf": txt2imgFlux2DevGgufWorkflow as ComfyWorkflow,
"txt2img-z-image-turbo": txt2imgZImageTurboWorkflow as ComfyWorkflow,
"txt2img-nitrofusion2": txt2imgNitroFusion2Workflow as ComfyWorkflow,
"img2img-z-image-turbo": img2imgZImageTurboWorkflow as ComfyWorkflow,
"txt2img-krea2-turbo": txt2imgKrea2TurboWorkflow as ComfyWorkflow,
"img2img-krea2-turbo": img2imgKrea2TurboWorkflow as ComfyWorkflow,
Expand Down
141 changes: 141 additions & 0 deletions src/workflows/api/txt2img-nitrofusion2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
{
"20": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "nf2-bf16.safetensors",
"weight_dtype": "default"
},
"_meta": {
"title": "Load NitroFusion2 Diffusion Model"
}
},
"21": {
"class_type": "CLIPLoader",
"inputs": {
"clip_name": "qwen_3_4b.safetensors",
"type": "lumina2",
"device": "default"
},
"_meta": {
"title": "Load Qwen CLIP"
}
},
"22": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "ae.safetensors"
},
"_meta": {
"title": "Load AE VAE"
}
},
"23": {
"class_type": "ModelSamplingAuraFlow",
"inputs": {
"model": [
"20",
0
],
"shift": 3
},
"_meta": {
"title": "NitroFusion2 Sampling"
}
},
"5": {
"class_type": "EmptySD3LatentImage",
"inputs": {
"width": 1024,
"height": 1024,
"batch_size": 1
},
"_meta": {
"title": "Image Size"
}
},
"6": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "",
"clip": [
"21",
0
]
},
"_meta": {
"title": "Positive Prompt"
}
},
"7": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "",
"clip": [
"21",
0
]
},
"_meta": {
"title": "Negative Prompt"
}
},
"3": {
"class_type": "KSampler",
"inputs": {
"seed": 1,
"steps": 1,
"cfg": 1,
"sampler_name": "euler",
"scheduler": "simple",
"denoise": 1,
"model": [
"23",
0
],
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"latent_image": [
"5",
0
]
},
"_meta": {
"title": "Sampler"
}
},
"8": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"3",
0
],
"vae": [
"22",
0
]
},
"_meta": {
"title": "Decode"
}
},
"9": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "OpenLayer_NitroFusion2",
"images": [
"8",
0
]
},
"_meta": {
"title": "Save Image"
}
}
}
Loading
Loading