From cd36fae865da1fbdda95a97f37097db092e7d171 Mon Sep 17 00:00:00 2001 From: Behzad Date: Mon, 27 Jul 2026 13:20:07 +0200 Subject: [PATCH] Run the templates on FLUX, and name the loaded style The root templates ran on SDXL base, where every gallery style washed out. A full compiled style is ~480 tokens and SDXL's CLIP attends to ~77 per chunk, so the style was truncated before the sampler saw it. No checkpoint swap fixes that, Juggernaut included: it is an SDXL finetune with the same encoder. Root set now runs FLUX.1 dev (Comfy-Org fp8 all-in-one, since BFL's own file is UNET-only and CheckpointLoaderSimple cannot load it), compiled from the flux target, whose T5 encoder takes the whole prompt. FluxGuidance 3.5, CFG 1.0, and ConditioningZeroOut on the negative input: FLUX is guidance distilled and has no negative channel, exactly like Z-Image. Uses EmptySD3LatentImage because FLUX works in a 16-channel latent space. The two sets load different gallery styles on purpose. How well a style demos is a property of the style and the model together: Z-Image reproduces painterly technique and keeps the Renaissance style, FLUX is strongest at light and tonality and takes a noir one. Also: - Adds the reference-images template the root set was missing, and renumbers Facets to 05, so both sets are 01-05 with matching names. Both sets are now pinned by test against a shared story list. - Model download metadata moves to each node's properties.models, where ComfyUI reads it, so a missing checkpoint offers a download instead of being silently substituted. - The Load node names the style its slug resolves to, on a read-only row under style_ref, via a new /styleref/resolve route. Co-Authored-By: Claude Sonnet 5 --- README.md | 34 +- scripts/build_workflows.py | 293 ++++++-- server_routes.py | 35 +- tests/test_workflows.py | 71 +- web/styleref.js | 139 +++- workflows/01-quickstart.json | 174 +++-- workflows/02-consistency-grid.json | 548 ++++++++------ workflows/03-your-own-style.json | 174 +++-- workflows/04-reference-images.json | 745 +++++++++++++++++++ workflows/{04-facets.json => 05-facets.json} | 166 +++-- workflows/z-image/01-quickstart.json | 27 +- workflows/z-image/02-consistency-grid.json | 29 +- workflows/z-image/03-your-own-style.json | 27 +- workflows/z-image/04-reference-images.json | 27 +- workflows/z-image/05-facets.json | 27 +- 15 files changed, 1978 insertions(+), 538 deletions(-) create mode 100644 workflows/04-reference-images.json rename workflows/{04-facets.json => 05-facets.json} (91%) diff --git a/README.md b/README.md index 431fa0c..032f2a3 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ server console at startup. 1. Add **StyleRef Load** and paste a slug from the [gallery](https://styleref.io/gallery) into `style_ref` (try - `sbdlwwly-66ae1a89efdc`, the Renaissance Mythic Classicism style) — or - click **Search styles…** to pick one. + `72e1zdae-e2d54a18d090`, the Noir Low-Key Portrait style) — or click + **Search styles…** to pick one. 2. Add **StyleRef Apply**, connect `style`, type a subject, pick your target. 3. Wire `positive` / `negative` into your CLIP Text Encode nodes. @@ -43,7 +43,7 @@ canvas. | Node | Sign-in | What it does | | --- | --- | --- | -| **StyleRef Load** | No | Fetch a style by share slug or share URL — and, signed in, your own private styles by id or `/styles/{id}` URL. Includes gallery search with a picker dialog and a ↻ Refresh button. | +| **StyleRef Load** | No | Fetch a style by share slug or share URL — and, signed in, your own private styles by id or `/styles/{id}` URL. Names the loaded style on the node, plus gallery search with a picker dialog and a ↻ Refresh button. | | **StyleRef Apply** | No | Compose a subject with a style into `positive` / `negative` prompts for a chosen target, with a prompt preview in the node body. | | **StyleRef Facets** | No | Every schema section as its own output — exactly the style board's section list, plus the six custom style items. | | **StyleRef Reference Images** | No | The style's inspiration images as an IMAGE batch — for IPAdapter and reference conditioning. | @@ -66,22 +66,36 @@ and it lands in your library. Load it here by its share slug or id ## Workflow templates +The templates at the root run on **FLUX.1 dev** (the Comfy-Org fp8 all-in-one +checkpoint, downloaded on first queue). FLUX reads the prompt with T5, so a +full compiled style fits — an SDXL CLIP encoder truncates it at ~77 tokens, +which is why the style washes out there no matter which checkpoint you use. + | File | Sign-in | Story | | --- | --- | --- | | [`01-quickstart.json`](workflows/01-quickstart.json) | No | Load → Apply → generate | | [`02-consistency-grid.json`](workflows/02-consistency-grid.json) | No | One style, three subjects, side by side | | [`03-your-own-style.json`](workflows/03-your-own-style.json) | Yes | Extract on the web, load your own style here | -| [`04-facets.json`](workflows/04-facets.json) | No | Every section of the style as its own output, next to a normal render | +| [`04-reference-images.json`](workflows/04-reference-images.json) | No | The style's inspiration images as a batch, next to a render | +| [`05-facets.json`](workflows/05-facets.json) | No | Every section of the style as its own output, next to a normal render | A parallel set wired for **Z-Image Turbo** (an S3-DiT model — UNET + Qwen text encoder + VAE, compiled from the `diffusion` target) lives in -[`workflows/z-image/`](workflows/z-image): the same quickstart, consistency, -your-own-style and facets stories, plus a reference-images demo. The three -model files download on first run. +[`workflows/z-image/`](workflows/z-image): the same five stories on that +loader stack. The three model files download on first run. -**Z-Image takes no negative prompt.** It runs without classifier-free guidance, -so the Z-Image templates carry no negative encoder — the sampler's negative -input gets a zeroed conditioning, as in a FLUX graph. StyleRef still compiles +The two sets load **different gallery styles on purpose.** How well a style +demos is a property of the style *and* the model. FLUX is strongest at light +and tonality, so its templates load a noir style — hard key, crushed blacks, +monochrome. Z-Image reproduces painterly technique faithfully, so its +templates load a Renaissance tempera style. Swap either for any slug from the +[gallery](https://styleref.io/gallery); these are starting points, not +limits. + +**Neither FLUX nor Z-Image takes a negative prompt.** Both are guidance +distilled and run at CFG 1.0, where the negative branch is multiplied out +entirely — so neither set carries a negative encoder, and the sampler's +negative input gets a zeroed conditioning. StyleRef still compiles the negative and previews it on the canvas; fold anything you need from it into `subject` on Apply as a positive phrase. Raising CFG does not bring it back. diff --git a/scripts/build_workflows.py b/scripts/build_workflows.py index 49594a5..39ec2d8 100644 --- a/scripts/build_workflows.py +++ b/scripts/build_workflows.py @@ -54,6 +54,7 @@ OUTPUTS = { "StyleRefReferenceImages": [("images", "IMAGE"), ("count", "INT")], "StyleRefLogin": [("status", "STRING")], "CheckpointLoaderSimple": [("MODEL", "MODEL"), ("CLIP", "CLIP"), ("VAE", "VAE")], + "FluxGuidance": [("CONDITIONING", "CONDITIONING")], "UNETLoader": [("MODEL", "MODEL")], "CLIPLoader": [("CLIP", "CLIP")], "VAELoader": [("VAE", "VAE")], @@ -76,6 +77,7 @@ INPUTS = { "StyleRefReferenceImages": [("style", "STYLEREF_STYLE")], "CLIPTextEncode": [("clip", "CLIP"), ("text", "STRING")], "ConditioningZeroOut": [("conditioning", "CONDITIONING")], + "FluxGuidance": [("conditioning", "CONDITIONING")], "KSampler": [ ("model", "MODEL"), ("positive", "CONDITIONING"), @@ -109,15 +111,30 @@ MULTILINE_WIDGETS = { # Node titles longer than this truncate in both frontends. MAX_TITLE = 28 -# The gallery style every ready-to-run template loads. Picked for how legible -# its effect is on the first queue: it compiles to tempera-on-panel technique -# and a muted Renaissance palette, so the render is obviously not what the base -# checkpoint would have produced alone. Two styles that read well on the -# gallery make poor demos here — a naturalistic photography style lands close -# to what SDXL does unprompted (the node looks like it did nothing), and a -# typography-led graphic style asks the sampler for headline text it renders as -# gibberish. This one carries no type at all and constrains no subject. -GALLERY_STYLE_REF = "sbdlwwly-66ae1a89efdc" # Renaissance Mythic Classicism +# The gallery style each set loads. Deliberately different per model, because +# "which style demos well" is a property of the pair, not of the style. +# +# Z-Image renders a painterly style faithfully — panel texture, contour-led +# figures, a muted Renaissance palette — so it gets the painterly one. +# +# FLUX does not. It has a strong photoreal house look and reverts to it, +# and it is specifically weak at reproducing painterly technique; the usual +# remedy in the ecosystem is a style LoRA, which is far too much apparatus for +# a first-run demo. Rather than ship a demo that quietly under-delivers, the +# FLUX set uses a style whose identity is *light and tonality* — hard key, +# crushed blacks, monochrome. That is what FLUX is genuinely good at, and +# monochrome high-contrast output is unmistakably styled: nobody has to wonder +# whether the node did anything. +# +# Both are subject-agnostic (the consistency grid needs that) and both carry +# inspiration images (the reference-images template needs that). +# +# Two styles that read well on the gallery still demo badly anywhere here: a +# naturalistic photography style lands close to what a base model does +# unprompted, and a typography-led graphic style asks the sampler for headline +# text. Neither of these does either. +FLUX_STYLE_REF = "72e1zdae-e2d54a18d090" # Noir Low-Key Portrait +ZIMAGE_STYLE_REF = "sbdlwwly-66ae1a89efdc" # Renaissance Mythic Classicism # Placeholder ref for the "your own style" templates. Refs are opaque ids now # (no name lookup), so the templates ship an obvious placeholder the user swaps @@ -130,7 +147,8 @@ OWN_STYLE_REF = "" # words of their own, so anything stylistic in the render came from StyleRef. GRID_SUBJECTS = [ "two friends flipping through crates of vinyl in a record shop", - "a lone surfer walking back along the beach at dusk", + "A lone surfer strolled back along the beach at dusk, carrying her large " + "surfing board.", "a roadside diner at night seen from the parking lot", ] @@ -140,9 +158,24 @@ DEMO_SUBJECT = GRID_SUBJECTS[0] # Frontend model-download metadata: a missing checkpoint becomes a guided # download instead of a red error. -MODEL_SDXL = { - "name": "sd_xl_base_1.0.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors", +# +# The Comfy-Org all-in-one fp8 repack — UNET + CLIP/T5 + VAE in one file, so +# CheckpointLoaderSimple can load it. BFL's own flux1-dev.safetensors is +# UNET-only and cannot. +# +# Why FLUX rather than SDXL, which more people have installed: StyleRef +# compiles a long style spec (~480 tokens for a full style), and SDXL's CLIP +# attends to ~77 tokens per chunk — the style is mostly truncated before it +# reaches the sampler, whatever the checkpoint. FLUX reads the prompt with T5, +# which takes the whole thing. This is the first model where the compiled +# style and the text encoder actually fit each other. +# +# `flux1-schnell-fp8.safetensors` (Comfy-Org/flux1-schnell) is a drop-in swap: +# Apache-2.0 instead of dev's non-commercial licence, and ~4 steps instead of +# 20, at some cost in fidelity. +MODEL_FLUX = { + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", "directory": "checkpoints", } @@ -217,6 +250,7 @@ class Graph: widgets: list[Any] | None = None, title: str | None = None, height: int | None = None, + models: list[dict[str, str]] | None = None, ) -> int: if title is not None and len(title) > MAX_TITLE: raise ValueError(f"title longer than {MAX_TITLE} chars truncates: {title!r}") @@ -249,6 +283,12 @@ class Graph: "properties": {"Node name for S&R": node_type}, "widgets_values": widgets, } + if models: + # ComfyUI reads download metadata from the *node's* properties — + # this is what turns a missing file into a download offer instead + # of a silently-substituted combo value. The workflow-level + # "models" key alone does not drive that prompt. + node["properties"]["models"] = models if title: node["title"] = title self.nodes.append(node) @@ -303,28 +343,72 @@ def apply_widgets(subject: str, target: str) -> list[Any]: return [subject, target, "", False, "", True] -def sampler_widgets(cfg: float) -> list[Any]: - # Seeds randomize: a fixed seed makes the second queue a silent - # cache no-op — deadly in a demo. - return [0, "randomize", 25, cfg, "euler", "normal", 1.0] +def _flux_checkpoint(graph: Graph) -> int: + """ + Place the FLUX checkpoint loader with its download metadata attached. + + A helper rather than a bare `place()` call for the same reason + `_zimage_loaders` is one: the metadata has to travel with the node. When + declaring the download was a separate step, every template placed the + loader correctly and none of them offered the download. + """ + return graph.place( + "CheckpointLoaderSimple", 0, [MODEL_FLUX["name"]], models=[MODEL_FLUX] + ) -def _sdxl_tail(graph: Graph, apply_node: int, checkpoint: int, col: int) -> None: - """Apply → two CLIP encoders → KSampler → VAEDecode → SaveImage.""" +def flux_sampler_widgets() -> list[Any]: + """ + FLUX dev defaults: 20 steps, CFG 1.0, euler/simple. + + CFG stays at 1.0 for the same reason it does on Z-Image — FLUX is guidance + distilled, and prompt strength is set with FluxGuidance instead. Raising CFG + here does not sharpen the style, it just doubles the work and burns the + image. + """ + # Seeds randomize: a fixed seed makes the second queue a silent cache + # no-op — deadly in a demo. + return [0, "randomize", 20, 1.0, "euler", "simple", 1.0] + + +def _flux_tail(graph: Graph, apply_node: int, checkpoint: int, col: int) -> None: + """ + Apply → CLIP encoder → FluxGuidance → KSampler → VAEDecode → SaveImage. + + **No negative encoder**, the same story as `_zimage_tail`: at CFG 1.0 the + negative branch is multiplied out entirely, so a second encoder would be + dead weight. The sampler's negative input takes a zeroed conditioning. + Apply's `negative` output stays wired to its Preview Any so the compiled + exclusions remain readable; fold anything you need from them into `subject` + as a positive phrase. + + The latent is `EmptySD3LatentImage`, not `EmptyLatentImage`: FLUX works in a + 16-channel latent space and the plain node emits 4 channels. This graph + previously shipped with the 4-channel node — it is the kind of mistake that + surfaces as a shape error or noise at sample time, never at build time. + """ positive = graph.place("CLIPTextEncode", col, [""], title="Positive (from StyleRef)") - negative = graph.place("CLIPTextEncode", col, [""], title="Negative (from StyleRef)") - latent = graph.place("EmptyLatentImage", col, [1024, 1024, 1]) - sampler = graph.place("KSampler", col + 1, sampler_widgets(cfg=7.0)) + # FLUX's own default. It briefly shipped at 5.0 here, to force a painterly + # style that FLUX was never going to reproduce; with a style the model is + # actually good at, that compensation stops helping and starts costing the + # subject. This style asks for crushed blacks and no fill, and guidance is + # a blunt "obey harder" dial — over-driven, the frame goes to pure black and + # the subject dissolves into it. Style adherence is the pairing's job, not + # this number's. + guidance = graph.place("FluxGuidance", col, [3.5], title="Flux guidance") + zero_out = graph.place("ConditioningZeroOut", col, [], title="FLUX has no negative") + latent = graph.place("EmptySD3LatentImage", col, [1024, 1024, 1]) + sampler = graph.place("KSampler", col + 1, flux_sampler_widgets()) decode = graph.place("VAEDecode", col + 2) save = graph.place("SaveImage", col + 2, ["StyleRef"]) graph.link(apply_node, 0, positive, 1) - graph.link(apply_node, 1, negative, 1) graph.link(checkpoint, 1, positive, 0) - graph.link(checkpoint, 1, negative, 0) + graph.link(positive, 0, guidance, 0) + graph.link(positive, 0, zero_out, 0) graph.link(checkpoint, 0, sampler, 0) - graph.link(positive, 0, sampler, 1) - graph.link(negative, 0, sampler, 2) + graph.link(guidance, 0, sampler, 1) + graph.link(zero_out, 0, sampler, 2) graph.link(latent, 0, sampler, 3) graph.link(sampler, 0, decode, 0) graph.link(checkpoint, 2, decode, 1) @@ -373,18 +457,34 @@ def zimage_sampler_widgets() -> list[Any]: CFG stays at 1.0 because Z-Image has no negative channel to activate (see `_zimage_tail`) — raising it only oversaturates and slows the sample down. """ + # Seeds randomize: a fixed seed makes the second queue a silent cache + # no-op — deadly in a demo. return [0, "randomize", 8, 1.0, "euler", "simple", 1.0] def _zimage_loaders(graph: Graph) -> tuple[int, int, int]: """Place the Z-Image UNET + CLIP + VAE loaders. Returns their node ids.""" unet = graph.place( - "UNETLoader", 0, [MODEL_ZIMAGE_UNET["name"], "default"], title="Z-Image UNET" + "UNETLoader", + 0, + [MODEL_ZIMAGE_UNET["name"], "default"], + title="Z-Image UNET", + models=[MODEL_ZIMAGE_UNET], ) clip = graph.place( - "CLIPLoader", 0, [MODEL_ZIMAGE_CLIP["name"], "lumina2", "default"], title="Qwen text encoder" + "CLIPLoader", + 0, + [MODEL_ZIMAGE_CLIP["name"], "lumina2", "default"], + title="Qwen text encoder", + models=[MODEL_ZIMAGE_CLIP], + ) + vae = graph.place( + "VAELoader", + 0, + [MODEL_ZIMAGE_VAE["name"]], + title="Z-Image VAE", + models=[MODEL_ZIMAGE_VAE], ) - vae = graph.place("VAELoader", 0, [MODEL_ZIMAGE_VAE["name"]], title="Z-Image VAE") return unet, clip, vae @@ -439,6 +539,44 @@ _ZIMAGE_SETUP_NOTE = ( ) +_FLUX_STYLE_TIP = ( + "Style not coming through strongly enough?\n" + "FLUX has a confident house look and will drift back to it. Two dials, in " + "the order worth trying:\n" + "1. `Flux guidance` (3.5 here, FLUX's default) is the blunt dial. 4–5 makes " + "the style bite harder; past that the subject starts losing to it, and a " + "dark style can go to pure black.\n" + "2. Narrow `sections` on StyleRef Apply to the parts that define this " + "style. For a period or painterly style that is usually " + "`artistic_mediums,references,surface_material,colors` — the sections " + "naming the medium, the era and the finish. Leaving it empty sends every " + "section, and the identity-carrying ones render last, so a long style can " + "read as generic.\n" + "Which sections matter depends on the style: for a geometric or layout-led " + "style keep `shape_language` and `spatial_hierarchy` instead. Queue the " + "Facets template to see what a style actually carries." +) + + +_FLUX_SETUP_NOTE = ( + "FLUX.1 dev (fp8 all-in-one). The checkpoint downloads on first use " + "into checkpoints/ — one ~16 GB file containing the UNET, the T5 text " + "encoder and the VAE.\n" + "Sampler defaults: 20 steps, CFG 1.0, euler/simple. Prompt strength is " + "the FluxGuidance node (3.5), not CFG.\n" + "FLUX takes no negative prompt. It is guidance distilled and runs at " + "CFG 1.0, so there is no negative encoder on this canvas — the sampler's " + "negative input gets a zeroed conditioning. StyleRef still compiles the " + "negative and previews it; put anything you need from it into `subject` " + "on Apply as a positive phrase (\"fully clothed\", \"no text or " + "watermark\"). Raising CFG does not bring the negative back.\n" + "Style prompts use the `flux` target — FLUX reads prose with T5, so the " + "whole style fits instead of being truncated at ~77 tokens the way an " + "SDXL CLIP encoder would.\n\n" + + _FLUX_STYLE_TIP +) + + def workflow_zimage_01_quickstart() -> dict[str, Any]: """Z-Image quickstart: Load a gallery style → Apply → generate.""" g = Graph() @@ -451,7 +589,7 @@ def workflow_zimage_01_quickstart() -> dict[str, Any]: title="Z-Image quickstart", ) load = g.place( - "StyleRefLoad", 0, load_widgets(GALLERY_STYLE_REF), title="Load — swap this slug" + "StyleRefLoad", 0, load_widgets(ZIMAGE_STYLE_REF), title="Load — swap this slug" ) unet, clip, vae = _zimage_loaders(g) apply_node = g.place( @@ -475,7 +613,7 @@ def workflow_zimage_02_consistency() -> dict[str, Any]: "many subjects, a coherent set within this model.\n\n" + _ZIMAGE_SETUP_NOTE, title="Z-Image consistency", ) - load = g.place("StyleRefLoad", 0, load_widgets(GALLERY_STYLE_REF)) + load = g.place("StyleRefLoad", 0, load_widgets(ZIMAGE_STYLE_REF)) unet, clip, vae = _zimage_loaders(g) subjects = GRID_SUBJECTS @@ -535,7 +673,7 @@ def workflow_zimage_04_reference_images() -> dict[str, Any]: title="Z-Image + references", ) load = g.place( - "StyleRefLoad", 0, load_widgets(GALLERY_STYLE_REF), title="Load — a gallery slug" + "StyleRefLoad", 0, load_widgets(ZIMAGE_STYLE_REF), title="Load — a gallery slug" ) unet, clip, vae = _zimage_loaders(g) @@ -560,28 +698,29 @@ def workflow_01_quickstart() -> dict[str, Any]: g.note( 0, "Quickstart — no account needed.\n" - "1. Set your SDXL checkpoint below (a missing model offers a download).\n" - "2. Queue. Seeds randomize, so every queue is a fresh image.\n" + "1. Queue — the FLUX checkpoint downloads on first run.\n" + "2. Seeds randomize, so every queue is a fresh image.\n" "3. Change `subject` on StyleRef Apply and queue again.\n" "Swap `style_ref` on StyleRef Load for any slug from " - "https://styleref.io/gallery — or use the Search styles… button.", + "https://styleref.io/gallery — or use the Search styles… button.\n\n" + + _FLUX_SETUP_NOTE, title="Quickstart", ) load = g.place( "StyleRefLoad", 0, - load_widgets(GALLERY_STYLE_REF), + load_widgets(FLUX_STYLE_REF), title="Load — swap this slug", ) - checkpoint = g.place("CheckpointLoaderSimple", 0, [MODEL_SDXL["name"]]) + checkpoint = _flux_checkpoint(g) apply_node = g.place( - "StyleRefApply", 1, apply_widgets(DEMO_SUBJECT, "diffusion") + "StyleRefApply", 1, apply_widgets(DEMO_SUBJECT, "flux") ) _preview(g, apply_node, 1) g.link(load, 0, apply_node, 0) - _sdxl_tail(g, apply_node, checkpoint, col=2) + _flux_tail(g, apply_node, checkpoint, col=2) - return g.to_json(models=[MODEL_SDXL]) + return g.to_json(models=[MODEL_FLUX]) def workflow_02_consistency() -> dict[str, Any]: @@ -602,19 +741,19 @@ def workflow_02_consistency() -> dict[str, Any]: "Change any subject, or swap the style on StyleRef Load, and queue again.", title="Consistency grid", ) - load = g.place("StyleRefLoad", 0, load_widgets(GALLERY_STYLE_REF)) - checkpoint = g.place("CheckpointLoaderSimple", 0, [MODEL_SDXL["name"]]) + load = g.place("StyleRefLoad", 0, load_widgets(FLUX_STYLE_REF)) + checkpoint = _flux_checkpoint(g) subjects = GRID_SUBJECTS for index, subject in enumerate(subjects): apply_node = g.place( - "StyleRefApply", 1, apply_widgets(subject, "diffusion"), title=f"Subject {index + 1}" + "StyleRefApply", 1, apply_widgets(subject, "flux"), title=f"Subject {index + 1}" ) g.link(load, 0, apply_node, 0) _preview(g, apply_node, 1, suffix=f" {index + 1}") - _sdxl_tail(g, apply_node, checkpoint, col=2) + _flux_tail(g, apply_node, checkpoint, col=2) - return g.to_json(models=[MODEL_SDXL]) + return g.to_json(models=[MODEL_FLUX]) def workflow_03_your_own_style() -> dict[str, Any]: @@ -630,7 +769,7 @@ def workflow_03_your_own_style() -> dict[str, Any]: "set action to `sign in` and queue again.\n" "2. Put your style's id in `style_ref` on StyleRef Load, or pick it\n" " with the Search styles… button.\n" - "3. Set your SDXL checkpoint and queue.", + "3. Queue.\n\n" + _FLUX_SETUP_NOTE, title="Your own style", ) login = g.place("StyleRefLogin", 0, [], title="Login — check status") @@ -641,15 +780,15 @@ def workflow_03_your_own_style() -> dict[str, Any]: load_widgets(OWN_STYLE_REF), title="Load — your own style", ) - checkpoint = g.place("CheckpointLoaderSimple", 0, [MODEL_SDXL["name"]]) + checkpoint = _flux_checkpoint(g) apply_node = g.place( - "StyleRefApply", 1, apply_widgets(DEMO_SUBJECT, "diffusion") + "StyleRefApply", 1, apply_widgets(DEMO_SUBJECT, "flux") ) g.link(load, 0, apply_node, 0) _preview(g, apply_node, 1) - _sdxl_tail(g, apply_node, checkpoint, col=2) + _flux_tail(g, apply_node, checkpoint, col=2) - return g.to_json(models=[MODEL_SDXL]) + return g.to_json(models=[MODEL_FLUX]) _FACETS_NOTE = ( @@ -668,28 +807,63 @@ _FACETS_NOTE = ( ) -def workflow_04_facets() -> dict[str, Any]: +def workflow_04_reference_images() -> dict[str, Any]: + """FLUX render + the style's inspiration images side by side.""" + g = Graph() + g.note( + 0, + "Style prompt and reference images together.\n" + "StyleRef Apply drives the sampler with the style's prompt; StyleRef " + "Reference Images pulls the style's inspiration images out as a batch, " + "previewed alongside. The prompt says how the style reads; the images " + "show how it looks — wire the images into IPAdapter or a " + "reference-only ControlNet to combine both.\n" + "Use a gallery style that has inspiration images.\n" + "\n" + _FLUX_SETUP_NOTE, + title="FLUX + references", + ) + load = g.place( + "StyleRefLoad", 0, load_widgets(FLUX_STYLE_REF), title="Load — a gallery slug" + ) + checkpoint = _flux_checkpoint(g) + + refs = g.place("StyleRefReferenceImages", 1, [12], title="Reference images") + ref_preview = g.place("PreviewImage", 1, [], title="Inspiration images") + g.link(load, 0, refs, 0) + g.link(refs, 0, ref_preview, 0) + + apply_node = g.place( + "StyleRefApply", 1, apply_widgets(DEMO_SUBJECT, "flux") + ) + g.link(load, 0, apply_node, 0) + _preview(g, apply_node, 1) + _flux_tail(g, apply_node, checkpoint, col=2) + + return g.to_json(models=[MODEL_FLUX]) + + +def workflow_05_facets() -> dict[str, Any]: """Every section of a style, exposed and readable, next to a normal render.""" g = Graph() g.note(0, _FACETS_NOTE, title="Facets") load = g.place( "StyleRefLoad", 0, - load_widgets(GALLERY_STYLE_REF), + load_widgets(FLUX_STYLE_REF), title="Load — swap this slug", ) - checkpoint = g.place("CheckpointLoaderSimple", 0, [MODEL_SDXL["name"]]) + checkpoint = _flux_checkpoint(g) _facet_board(g, load, col=1) apply_node = g.place( - "StyleRefApply", 3, apply_widgets(DEMO_SUBJECT, "diffusion") + "StyleRefApply", 3, apply_widgets(DEMO_SUBJECT, "flux") ) g.link(load, 0, apply_node, 0) _preview(g, apply_node, 3) - _sdxl_tail(g, apply_node, checkpoint, col=4) + _flux_tail(g, apply_node, checkpoint, col=4) - return g.to_json(models=[MODEL_SDXL]) + return g.to_json(models=[MODEL_FLUX]) def workflow_zimage_05_facets() -> dict[str, Any]: @@ -699,7 +873,7 @@ def workflow_zimage_05_facets() -> dict[str, Any]: load = g.place( "StyleRefLoad", 0, - load_widgets(GALLERY_STYLE_REF), + load_widgets(ZIMAGE_STYLE_REF), title="Load — swap this slug", ) unet, clip, vae = _zimage_loaders(g) @@ -716,13 +890,14 @@ def workflow_zimage_05_facets() -> dict[str, Any]: return g.to_json(models=MODELS_ZIMAGE) -# Keyed by path relative to workflows/. The SDXL set lives at the root; the +# Keyed by path relative to workflows/. The FLUX set lives at the root; the # Z-Image set lives in workflows/z-image/. WORKFLOWS = { "01-quickstart.json": workflow_01_quickstart, "02-consistency-grid.json": workflow_02_consistency, "03-your-own-style.json": workflow_03_your_own_style, - "04-facets.json": workflow_04_facets, + "04-reference-images.json": workflow_04_reference_images, + "05-facets.json": workflow_05_facets, "z-image/01-quickstart.json": workflow_zimage_01_quickstart, "z-image/02-consistency-grid.json": workflow_zimage_02_consistency, "z-image/03-your-own-style.json": workflow_zimage_03_your_own_style, diff --git a/server_routes.py b/server_routes.py index 36c7570..1cedb46 100644 --- a/server_routes.py +++ b/server_routes.py @@ -23,7 +23,7 @@ from server import PromptServer import styleref_api as api from styleref_api import StyleRefError from styleref_auth import auth_status, clear_credentials, credentials_path, run_login_flow -from styleref_nodes.load import bump_refresh +from styleref_nodes.load import bump_refresh, fetch_style def _card(entry: dict) -> dict: @@ -149,6 +149,39 @@ async def styleref_my_styles(request: web.Request) -> web.Response: ) +@PromptServer.instance.routes.get("/styleref/resolve") +async def styleref_resolve(request: web.Request) -> web.Response: + """ + GET /styleref/resolve?ref=… → {ok, name, url} + + Backs the name line under the Load node's `style_ref` field. A slug is + opaque — `72e1zdae-e2d54a18d090` tells you nothing about which style is + wired in, and until the graph is queued the node cannot say either. This + answers the question the moment the field changes. + + It goes through the same client (and therefore the same session cache) the + node uses, so a resolve for a style that is about to be loaded costs one + request and the queue after it costs none. + """ + ref = request.query.get("ref", "").strip() + if not ref: + return web.json_response({"ok": False}) + + try: + style = fetch_style(ref) + except StyleRefError as err: + # 200 with an error field, as with search: an unresolvable ref is + # ordinary (a half-typed slug, a private style while signed out) and + # belongs inline on the node, not in a red toast. + return web.json_response({"ok": False, "error": err.message}) + + # No author: the structured lane carries name/sections/referenceImages only, + # so the node body's attribution line is where a handle would come from. + return web.json_response( + {"ok": True, "name": style.get("name"), "url": style.get("url")} + ) + + @PromptServer.instance.routes.post("/styleref/refresh") async def styleref_refresh(request: web.Request) -> web.Response: """ diff --git a/tests/test_workflows.py b/tests/test_workflows.py index d5c9d87..cf9e47e 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -55,16 +55,30 @@ def test_extraction_template_is_gone(): assert "03-your-own-style.json" in {os.path.basename(p) for p in FILES} -def test_zimage_set_ships_its_five_workflows(): - """The Z-Image folder carries the root's stories plus its own two demos.""" +STORIES = [ + "01-quickstart.json", + "02-consistency-grid.json", + "03-your-own-style.json", + "04-reference-images.json", + "05-facets.json", +] + + +def test_sdxl_set_ships_every_story(): + """ + The root (SDXL) set must tell all five stories. It shipped without the + reference-images template once, which nothing caught: every other test + iterates whatever happens to be on disk, so a missing file is simply a + story never checked. Pinning the set is what makes an omission fail. + """ + root = {_relpath(p) for p in FILES if "/" not in _relpath(p)} + assert root == set(STORIES) + + +def test_zimage_set_ships_every_story(): + """The Z-Image folder tells the same five stories on its own loader stack.""" zimage = {_relpath(p) for p in FILES if _relpath(p).startswith("z-image/")} - assert zimage == { - "z-image/01-quickstart.json", - "z-image/02-consistency-grid.json", - "z-image/03-your-own-style.json", - "z-image/04-reference-images.json", - "z-image/05-facets.json", - } + assert zimage == {f"z-image/{name}" for name in STORIES} @pytest.mark.parametrize("path", FILES, ids=[_relpath(p) for p in FILES]) @@ -190,19 +204,30 @@ _MODEL_DIRECTORIES = {"checkpoints", "diffusion_models", "text_encoders", "vae"} @pytest.mark.parametrize("path", FILES, ids=[_relpath(p) for p in FILES]) def test_model_loaders_carry_download_metadata(path): - """A missing model should be a guided download, not a red error — - for every loader type (checkpoints and the Z-Image UNET/CLIP/VAE stack).""" + """ + A missing model should be a guided download, not a silently-substituted + combo value — for every loader type (checkpoints and the Z-Image + UNET/CLIP/VAE stack). + + The metadata must be on the *node*, under `properties.models`: that is + where ComfyUI looks when deciding whether to offer the download. This + assertion used to read the workflow-level `models` key instead, which is + why every template shipped that key and none of them prompted. + """ graph = _load(path) - loader_files = { - n["widgets_values"][0] for n in graph["nodes"] if n["type"] in _MODEL_LOADERS - } - if not loader_files: + loaders = [n for n in graph["nodes"] if n["type"] in _MODEL_LOADERS] + if not loaders: return - declared = {m["name"] for m in graph.get("models", [])} - assert loader_files <= declared, f"models metadata missing: {loader_files - declared}" - for model in graph.get("models", []): - assert model["url"].startswith("https://") - assert model["directory"] in _MODEL_DIRECTORIES + + for node in loaders: + declared = node["properties"].get("models") + assert declared, f"{node['type']} carries no properties.models" + # The name must match the widget, or the frontend cannot tell which + # missing file this download would satisfy. + assert {m["name"] for m in declared} == {node["widgets_values"][0]} + for model in declared: + assert model["url"].startswith("https://") + assert model["directory"] in _MODEL_DIRECTORIES def test_node_slot_tables_cover_every_linked_node_type(): @@ -216,7 +241,7 @@ def test_node_slot_tables_cover_every_linked_node_type(): assert node_type in INPUTS, f"{node_type} inputs undeclared" -FACETS_FILES = ["04-facets.json", "z-image/05-facets.json"] +FACETS_FILES = ["05-facets.json", "z-image/05-facets.json"] @pytest.mark.parametrize("name", FACETS_FILES) @@ -299,8 +324,8 @@ def test_consistency_template_shows_one_style_many_subjects(): def test_prompt_previews_exist_where_promised(): - """Templates 01 and 04 make the compiled prompts visible.""" - for name in ("01-quickstart.json", "04-facets.json"): + """Templates 01 and 05 make the compiled prompts visible.""" + for name in ("01-quickstart.json", "05-facets.json"): graph = _load(os.path.join(WORKFLOW_DIR, name)) assert any(n["type"] == "PreviewAny" for n in graph["nodes"]), name diff --git a/web/styleref.js b/web/styleref.js index 097968e..e7f3f59 100644 --- a/web/styleref.js +++ b/web/styleref.js @@ -9,6 +9,10 @@ * styles) — each 8 rows deep with its own ‹ Prev / Next ›. The query filters * both sections, so the search box narrows the library as well as the * gallery; + * - a read-only "loaded style" line under `style_ref` naming the style that + * slug resolves to, so a graph says which style it uses without being + * queued. It is marked non-serializing and sits mid-list, the one place + * this file touches widget order; * - a "↻ Refresh style" button that POSTs /styleref/refresh, which drops the * server-side cache and dirties the node so the next queue re-downloads; * - status + Sign in / Sign out buttons on StyleRef Login, backed by the @@ -18,9 +22,13 @@ * (ai_tools, style_md, midjourney, json) but the `positive` output feeds * a CLIP Text Encode — those are for copying out, not sampler text. * - * It creates no custom widget type, overrides no drawing beyond the badge, and - * touches no serialization — so if a ComfyUI frontend release changes the - * extension API and this stops loading, the nodes degrade to exactly what they + * It creates no custom widget type and overrides no drawing beyond the badge. + * The one widget it inserts rather than appends is flagged out of serialization + * both ways frontends read that flag, because `widgets_values` is positional + * and a serialising widget mid-list would shift every value after it. + * + * If a ComfyUI frontend release changes the extension API and this stops + * loading, the nodes degrade to exactly what they * are without it: text fields you type into. That is the plan's * "degraded-but-unbreakable fallback", and it is why the node's own `search` + * `category` inputs exist in Python too. @@ -71,10 +79,10 @@ async function probeRoutes() { async function fetchJson(url) { try { const res = await fetch(url); - if (!res.ok) return { styles: [], error: `Search failed (${res.status})` }; + if (!res.ok) return { styles: [], error: `Request failed (${res.status})` }; return await res.json(); } catch (err) { - return { styles: [], error: `Search failed: ${err?.message ?? err}` }; + return { styles: [], error: `Request failed: ${err?.message ?? err}` }; } } @@ -581,6 +589,116 @@ function setStatus(node, widget, text) { app.canvas?.setDirty?.(true, true); } +const NAME_LABEL = "loaded style"; +const NAME_IDLE = "—"; + +/** + * A read-only line naming the loaded style, placed immediately under + * `style_ref` — the field it describes. + * + * Two things make this safe to insert mid-list rather than append. It is marked + * non-serializing on both the widget and its options (frontends have read one + * or the other), and `computeSize` is left alone so layout stays the frontend's + * business. That matters because `widgets_values` is a positional array: a + * widget that serialises from the middle of the list shifts every value after + * it, and a saved workflow reloads with `search` holding the category, and so + * on. The existing buttons all append precisely to avoid that question. + * + * If a frontend ignores the flags, the failure is visible immediately — reload + * a saved graph and the fields are wrong — which is what the smoke test checks. + * Nothing here is load-bearing: drop this widget and the node is unchanged. + */ +function addStyleNameWidget(node) { + const widget = node.addWidget("text", NAME_LABEL, NAME_IDLE, () => {}, { + serialize: false, + }); + widget.serialize = false; + widget.serializeValue = () => undefined; + // Read-only: it reports, it is not an input. `disabled` covers the canvas + // widget; `inputEl` exists only once a DOM-backed frontend has built it. + widget.disabled = true; + if (widget.inputEl) widget.inputEl.readOnly = true; + + // Move it from the end (where addWidget put it) to just after `style_ref`. + const widgets = node.widgets ?? []; + const from = widgets.indexOf(widget); + const anchor = widgets.findIndex((w) => w.name === "style_ref"); + if (from > -1 && anchor > -1 && from !== anchor + 1) { + widgets.splice(from, 1); + widgets.splice(anchor + 1, 0, widget); + } + return widget; +} + +/** The widget's value. Truncated so a long name cannot widen the node. */ +function styleNameValue(res) { + if (!res || res.ok !== true) { + // A half-typed slug is the common case here, not a failure worth + // shouting about — the node still queues and reports properly. + return res?.error ? res.error : "not found"; + } + const name = String(res.name ?? "").trim() || "Untitled style"; + return name.length > 34 ? `${name.slice(0, 33)}…` : name; +} + +/** + * Sets the widget's *value*, not its name — unlike the Login status line, + * which is a button whose name is its label. Here the name is the static + * "loaded style" caption and the value is the answer. + */ +function setStyleName(node, widget, text) { + widget.value = text; + node.setDirtyCanvas(true, true); + app.canvas?.setDirty?.(true, true); +} + +async function refreshStyleName(node, nameWidget) { + if (!nameWidget) return; + const ref = (node.widgets?.find((w) => w.name === "style_ref")?.value ?? "") + .toString() + .trim(); + if (!ref) { + setStyleName(node, nameWidget, NAME_IDLE); + return; + } + setStyleName(node, nameWidget, "resolving…"); + const res = await fetchJson(`/styleref/resolve?ref=${encodeURIComponent(ref)}`); + // The field may have moved on while the request was in flight (typing is + // faster than a round trip); a stale answer must not overwrite a newer one. + const current = (node.widgets?.find((w) => w.name === "style_ref")?.value ?? "") + .toString() + .trim(); + if (current !== ref) return; + setStyleName(node, nameWidget, styleNameValue(res)); +} + +/** + * Resolve on a settled edit, not on every keystroke — a slug is pasted or typed + * in bursts, and one request per character would hammer the API for prefixes + * that cannot resolve anyway. + */ +function watchStyleRef(node, nameWidget) { + const refWidget = node.widgets?.find((w) => w.name === "style_ref"); + if (!refWidget) return; + + let timer = null; + const schedule = () => { + clearTimeout(timer); + timer = setTimeout(() => refreshStyleName(node, nameWidget), 400); + }; + + const previous = refWidget.callback; + refWidget.callback = function (...args) { + const out = previous?.apply(this, args); + schedule(); + return out; + }; + + // Templates and reloaded graphs arrive with the field already filled, so + // the name must appear without the user touching anything. + schedule(); +} + async function refreshLoginStatus(node, statusWidget) { try { const res = await fetchJson("/styleref/auth/status"); @@ -618,6 +736,16 @@ app.registerExtension({ nodeType.prototype.onNodeCreated = function () { const result = onCreated?.apply(this, arguments); + // The name line, directly under `style_ref`. A slug says + // nothing about which style is wired in, and before the first + // queue the node cannot say either — so resolve it as soon as + // the field settles. + let nameWidget = null; + if (routesAvailable !== false) { + nameWidget = addStyleNameWidget(this); + watchStyleRef(this, nameWidget); + } + // Only add the search button when the backend routes exist // — otherwise it could only 404. if (routesAvailable !== false) { @@ -632,6 +760,7 @@ app.registerExtension({ refWidget.value = chosen; refWidget.callback?.(chosen); this.setDirtyCanvas(true, true); + refreshStyleName(this, nameWidget); }); } diff --git a/workflows/01-quickstart.json b/workflows/01-quickstart.json index 35719f1..12da168 100644 --- a/workflows/01-quickstart.json +++ b/workflows/01-quickstart.json @@ -1,5 +1,5 @@ { - "last_node_id": 12, + "last_node_id": 13, "last_link_id": 14, "nodes": [ { @@ -11,7 +11,7 @@ ], "size": [ 320, - 270 + 1008 ], "flags": {}, "order": 0, @@ -22,7 +22,7 @@ "Node name for S&R": "Note" }, "widgets_values": [ - "Quickstart \u2014 no account needed.\n1. Set your SDXL checkpoint below (a missing model offers a download).\n2. Queue. Seeds randomize, so every queue is a fresh image.\n3. Change `subject` on StyleRef Apply and queue again.\nSwap `style_ref` on StyleRef Load for any slug from https://styleref.io/gallery \u2014 or use the Search styles\u2026 button." + "Quickstart \u2014 no account needed.\n1. Queue \u2014 the FLUX checkpoint downloads on first run.\n2. Seeds randomize, so every queue is a fresh image.\n3. Change `subject` on StyleRef Apply and queue again.\nSwap `style_ref` on StyleRef Load for any slug from https://styleref.io/gallery \u2014 or use the Search styles\u2026 button.\n\nFLUX.1 dev (fp8 all-in-one). The checkpoint downloads on first use into checkpoints/ \u2014 one ~16 GB file containing the UNET, the T5 text encoder and the VAE.\nSampler defaults: 20 steps, CFG 1.0, euler/simple. Prompt strength is the FluxGuidance node (3.5), not CFG.\nFLUX takes no negative prompt. It is guidance distilled and runs at CFG 1.0, so there is no negative encoder on this canvas \u2014 the sampler's negative input gets a zeroed conditioning. StyleRef still compiles the negative and previews it; put anything you need from it into `subject` on Apply as a positive phrase (\"fully clothed\", \"no text or watermark\"). Raising CFG does not bring the negative back.\nStyle prompts use the `flux` target \u2014 FLUX reads prose with T5, so the whole style fits instead of being truncated at ~77 tokens the way an SDXL CLIP encoder would.\n\nStyle not coming through strongly enough?\nFLUX has a confident house look and will drift back to it. Two dials, in the order worth trying:\n1. `Flux guidance` (3.5 here, FLUX's default) is the blunt dial. 4\u20135 makes the style bite harder; past that the subject starts losing to it, and a dark style can go to pure black.\n2. Narrow `sections` on StyleRef Apply to the parts that define this style. For a period or painterly style that is usually `artistic_mediums,references,surface_material,colors` \u2014 the sections naming the medium, the era and the finish. Leaving it empty sends every section, and the identity-carrying ones render last, so a long style can read as generic.\nWhich sections matter depends on the style: for a geometric or layout-led style keep `shape_language` and `spatial_hierarchy` instead. Queue the Facets template to see what a style actually carries." ], "title": "Quickstart" }, @@ -31,7 +31,7 @@ "type": "StyleRefLoad", "pos": [ 40, - 378 + 1116 ], "size": [ 320, @@ -61,7 +61,7 @@ "Node name for S&R": "StyleRefLoad" }, "widgets_values": [ - "sbdlwwly-66ae1a89efdc", + "72e1zdae-e2d54a18d090", "", "any", true, @@ -74,7 +74,7 @@ "type": "CheckpointLoaderSimple", "pos": [ 40, - 674 + 1412 ], "size": [ 320, @@ -97,8 +97,7 @@ "name": "CLIP", "type": "CLIP", "links": [ - 6, - 7 + 5 ], "slot_index": 1 }, @@ -112,10 +111,17 @@ } ], "properties": { - "Node name for S&R": "CheckpointLoaderSimple" + "Node name for S&R": "CheckpointLoaderSimple", + "models": [ + { + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", + "directory": "checkpoints" + } + ] }, "widgets_values": [ - "sd_xl_base_1.0.safetensors" + "flux1-dev-fp8.safetensors" ] }, { @@ -153,8 +159,7 @@ "name": "negative", "type": "STRING", "links": [ - 2, - 5 + 2 ], "slot_index": 1 }, @@ -170,7 +175,7 @@ }, "widgets_values": [ "two friends flipping through crates of vinyl in a record shop", - "diffusion", + "flux", "", false, "", @@ -251,7 +256,7 @@ { "name": "clip", "type": "CLIP", - "link": 6 + "link": 5 }, { "name": "text", @@ -264,7 +269,8 @@ "name": "CONDITIONING", "type": "CONDITIONING", "links": [ - 9 + 6, + 7 ], "slot_index": 0 } @@ -279,28 +285,62 @@ }, { "id": 8, - "type": "CLIPTextEncode", + "type": "FluxGuidance", "pos": [ 800, 306 ], "size": [ 320, - 198 + 112 ], "flags": {}, "order": 7, "mode": 0, "inputs": [ { - "name": "clip", - "type": "CLIP", - "link": 7 - }, + "name": "conditioning", + "type": "CONDITIONING", + "link": 6 + } + ], + "outputs": [ { - "name": "text", - "type": "STRING", - "link": 5 + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 9 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FluxGuidance" + }, + "widgets_values": [ + 3.5 + ], + "title": "Flux guidance" + }, + { + "id": 9, + "type": "ConditioningZeroOut", + "pos": [ + 800, + 466 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "conditioning", + "type": "CONDITIONING", + "link": 7 } ], "outputs": [ @@ -314,26 +354,24 @@ } ], "properties": { - "Node name for S&R": "CLIPTextEncode" + "Node name for S&R": "ConditioningZeroOut" }, - "widgets_values": [ - "" - ], - "title": "Negative (from StyleRef)" + "widgets_values": [], + "title": "FLUX has no negative" }, { - "id": 9, - "type": "EmptyLatentImage", + "id": 10, + "type": "EmptySD3LatentImage", "pos": [ 800, - 552 + 598 ], "size": [ 320, 168 ], "flags": {}, - "order": 8, + "order": 9, "mode": 0, "inputs": [], "outputs": [ @@ -347,7 +385,7 @@ } ], "properties": { - "Node name for S&R": "EmptyLatentImage" + "Node name for S&R": "EmptySD3LatentImage" }, "widgets_values": [ 1024, @@ -356,7 +394,7 @@ ] }, { - "id": 10, + "id": 11, "type": "KSampler", "pos": [ 1180, @@ -367,7 +405,7 @@ 352 ], "flags": {}, - "order": 9, + "order": 10, "mode": 0, "inputs": [ { @@ -407,15 +445,15 @@ "widgets_values": [ 0, "randomize", - 25, - 7.0, + 20, + 1.0, "euler", - "normal", + "simple", 1.0 ] }, { - "id": 11, + "id": 12, "type": "VAEDecode", "pos": [ 1560, @@ -426,7 +464,7 @@ 108 ], "flags": {}, - "order": 10, + "order": 11, "mode": 0, "inputs": [ { @@ -456,7 +494,7 @@ "widgets_values": [] }, { - "id": 12, + "id": 13, "type": "SaveImage", "pos": [ 1560, @@ -467,7 +505,7 @@ 112 ], "flags": {}, - "order": 11, + "order": 12, "mode": 0, "inputs": [ { @@ -520,82 +558,82 @@ ], [ 5, - 4, + 3, 1, - 8, - 1, - "STRING" + 7, + 0, + "CLIP" ], [ 6, - 3, - 1, 7, 0, - "CLIP" + 8, + 0, + "CONDITIONING" ], [ 7, - 3, - 1, - 8, + 7, 0, - "CLIP" + 9, + 0, + "CONDITIONING" ], [ 8, 3, 0, - 10, + 11, 0, "MODEL" ], [ 9, - 7, + 8, 0, - 10, + 11, 1, "CONDITIONING" ], [ 10, - 8, + 9, 0, - 10, + 11, 2, "CONDITIONING" ], [ 11, - 9, - 0, 10, + 0, + 11, 3, "LATENT" ], [ 12, - 10, - 0, 11, 0, + 12, + 0, "LATENT" ], [ 13, 3, 2, - 11, + 12, 1, "VAE" ], [ 14, - 11, - 0, 12, 0, + 13, + 0, "IMAGE" ] ], @@ -609,8 +647,8 @@ "version": 0.4, "models": [ { - "name": "sd_xl_base_1.0.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors", + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", "directory": "checkpoints" } ] diff --git a/workflows/02-consistency-grid.json b/workflows/02-consistency-grid.json index e7ae3a8..449698b 100644 --- a/workflows/02-consistency-grid.json +++ b/workflows/02-consistency-grid.json @@ -1,5 +1,5 @@ { - "last_node_id": 30, + "last_node_id": 33, "last_link_id": 42, "nodes": [ { @@ -63,7 +63,7 @@ "Node name for S&R": "StyleRefLoad" }, "widgets_values": [ - "sbdlwwly-66ae1a89efdc", + "72e1zdae-e2d54a18d090", "", "any", true, @@ -100,12 +100,9 @@ "name": "CLIP", "type": "CLIP", "links": [ - 6, - 7, - 20, - 21, - 34, - 35 + 5, + 19, + 33 ], "slot_index": 1 }, @@ -121,10 +118,17 @@ } ], "properties": { - "Node name for S&R": "CheckpointLoaderSimple" + "Node name for S&R": "CheckpointLoaderSimple", + "models": [ + { + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", + "directory": "checkpoints" + } + ] }, "widgets_values": [ - "sd_xl_base_1.0.safetensors" + "flux1-dev-fp8.safetensors" ] }, { @@ -162,8 +166,7 @@ "name": "negative", "type": "STRING", "links": [ - 3, - 5 + 3 ], "slot_index": 1 }, @@ -179,7 +182,7 @@ }, "widgets_values": [ "two friends flipping through crates of vinyl in a record shop", - "diffusion", + "flux", "", false, "", @@ -261,7 +264,7 @@ { "name": "clip", "type": "CLIP", - "link": 6 + "link": 5 }, { "name": "text", @@ -274,7 +277,8 @@ "name": "CONDITIONING", "type": "CONDITIONING", "links": [ - 9 + 6, + 7 ], "slot_index": 0 } @@ -289,28 +293,62 @@ }, { "id": 8, - "type": "CLIPTextEncode", + "type": "FluxGuidance", "pos": [ 800, 306 ], "size": [ 320, - 198 + 112 ], "flags": {}, "order": 7, "mode": 0, "inputs": [ { - "name": "clip", - "type": "CLIP", - "link": 7 - }, + "name": "conditioning", + "type": "CONDITIONING", + "link": 6 + } + ], + "outputs": [ { - "name": "text", - "type": "STRING", - "link": 5 + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 9 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FluxGuidance" + }, + "widgets_values": [ + 3.5 + ], + "title": "Flux guidance" + }, + { + "id": 9, + "type": "ConditioningZeroOut", + "pos": [ + 800, + 466 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "conditioning", + "type": "CONDITIONING", + "link": 7 } ], "outputs": [ @@ -324,26 +362,24 @@ } ], "properties": { - "Node name for S&R": "CLIPTextEncode" + "Node name for S&R": "ConditioningZeroOut" }, - "widgets_values": [ - "" - ], - "title": "Negative (from StyleRef)" + "widgets_values": [], + "title": "FLUX has no negative" }, { - "id": 9, - "type": "EmptyLatentImage", + "id": 10, + "type": "EmptySD3LatentImage", "pos": [ 800, - 552 + 598 ], "size": [ 320, 168 ], "flags": {}, - "order": 8, + "order": 9, "mode": 0, "inputs": [], "outputs": [ @@ -357,7 +393,7 @@ } ], "properties": { - "Node name for S&R": "EmptyLatentImage" + "Node name for S&R": "EmptySD3LatentImage" }, "widgets_values": [ 1024, @@ -366,7 +402,7 @@ ] }, { - "id": 10, + "id": 11, "type": "KSampler", "pos": [ 1180, @@ -377,7 +413,7 @@ 352 ], "flags": {}, - "order": 9, + "order": 10, "mode": 0, "inputs": [ { @@ -417,15 +453,15 @@ "widgets_values": [ 0, "randomize", - 25, - 7.0, + 20, + 1.0, "euler", - "normal", + "simple", 1.0 ] }, { - "id": 11, + "id": 12, "type": "VAEDecode", "pos": [ 1560, @@ -436,7 +472,7 @@ 108 ], "flags": {}, - "order": 10, + "order": 11, "mode": 0, "inputs": [ { @@ -466,7 +502,7 @@ "widgets_values": [] }, { - "id": 12, + "id": 13, "type": "SaveImage", "pos": [ 1560, @@ -477,7 +513,7 @@ 112 ], "flags": {}, - "order": 11, + "order": 12, "mode": 0, "inputs": [ { @@ -495,7 +531,7 @@ ] }, { - "id": 13, + "id": 14, "type": "StyleRefApply", "pos": [ 420, @@ -506,7 +542,7 @@ 424 ], "flags": {}, - "order": 12, + "order": 13, "mode": 0, "inputs": [ { @@ -529,8 +565,7 @@ "name": "negative", "type": "STRING", "links": [ - 17, - 19 + 17 ], "slot_index": 1 }, @@ -545,8 +580,8 @@ "Node name for S&R": "StyleRefApply" }, "widgets_values": [ - "a lone surfer walking back along the beach at dusk", - "diffusion", + "A lone surfer strolled back along the beach at dusk, carrying her large surfing board.", + "flux", "", false, "", @@ -555,7 +590,7 @@ "title": "Subject 2" }, { - "id": 14, + "id": 15, "type": "PreviewAny", "pos": [ 420, @@ -566,7 +601,7 @@ 84 ], "flags": {}, - "order": 13, + "order": 14, "mode": 0, "inputs": [ { @@ -583,7 +618,7 @@ "title": "Compiled positive 2" }, { - "id": 15, + "id": 16, "type": "PreviewAny", "pos": [ 420, @@ -594,7 +629,7 @@ 84 ], "flags": {}, - "order": 14, + "order": 15, "mode": 0, "inputs": [ { @@ -611,24 +646,24 @@ "title": "Compiled negative 2" }, { - "id": 16, + "id": 17, "type": "CLIPTextEncode", "pos": [ 800, - 768 + 814 ], "size": [ 320, 198 ], "flags": {}, - "order": 15, + "order": 16, "mode": 0, "inputs": [ { "name": "clip", "type": "CLIP", - "link": 20 + "link": 19 }, { "name": "text", @@ -641,7 +676,8 @@ "name": "CONDITIONING", "type": "CONDITIONING", "links": [ - 23 + 20, + 21 ], "slot_index": 0 } @@ -655,29 +691,63 @@ "title": "Positive (from StyleRef)" }, { - "id": 17, - "type": "CLIPTextEncode", + "id": 18, + "type": "FluxGuidance", "pos": [ 800, - 1014 + 1060 ], "size": [ 320, - 198 + 112 ], "flags": {}, - "order": 16, + "order": 17, "mode": 0, "inputs": [ { - "name": "clip", - "type": "CLIP", - "link": 21 - }, + "name": "conditioning", + "type": "CONDITIONING", + "link": 20 + } + ], + "outputs": [ { - "name": "text", - "type": "STRING", - "link": 19 + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 23 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FluxGuidance" + }, + "widgets_values": [ + 3.5 + ], + "title": "Flux guidance" + }, + { + "id": 19, + "type": "ConditioningZeroOut", + "pos": [ + 800, + 1220 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 18, + "mode": 0, + "inputs": [ + { + "name": "conditioning", + "type": "CONDITIONING", + "link": 21 } ], "outputs": [ @@ -691,26 +761,24 @@ } ], "properties": { - "Node name for S&R": "CLIPTextEncode" + "Node name for S&R": "ConditioningZeroOut" }, - "widgets_values": [ - "" - ], - "title": "Negative (from StyleRef)" + "widgets_values": [], + "title": "FLUX has no negative" }, { - "id": 18, - "type": "EmptyLatentImage", + "id": 20, + "type": "EmptySD3LatentImage", "pos": [ 800, - 1260 + 1352 ], "size": [ 320, 168 ], "flags": {}, - "order": 17, + "order": 19, "mode": 0, "inputs": [], "outputs": [ @@ -724,7 +792,7 @@ } ], "properties": { - "Node name for S&R": "EmptyLatentImage" + "Node name for S&R": "EmptySD3LatentImage" }, "widgets_values": [ 1024, @@ -733,7 +801,7 @@ ] }, { - "id": 19, + "id": 21, "type": "KSampler", "pos": [ 1180, @@ -744,7 +812,7 @@ 352 ], "flags": {}, - "order": 18, + "order": 20, "mode": 0, "inputs": [ { @@ -784,15 +852,15 @@ "widgets_values": [ 0, "randomize", - 25, - 7.0, + 20, + 1.0, "euler", - "normal", + "simple", 1.0 ] }, { - "id": 20, + "id": 22, "type": "VAEDecode", "pos": [ 1560, @@ -803,7 +871,7 @@ 108 ], "flags": {}, - "order": 19, + "order": 21, "mode": 0, "inputs": [ { @@ -833,7 +901,7 @@ "widgets_values": [] }, { - "id": 21, + "id": 23, "type": "SaveImage", "pos": [ 1560, @@ -844,7 +912,7 @@ 112 ], "flags": {}, - "order": 20, + "order": 22, "mode": 0, "inputs": [ { @@ -862,7 +930,7 @@ ] }, { - "id": 22, + "id": 24, "type": "StyleRefApply", "pos": [ 420, @@ -873,7 +941,7 @@ 424 ], "flags": {}, - "order": 21, + "order": 23, "mode": 0, "inputs": [ { @@ -896,8 +964,7 @@ "name": "negative", "type": "STRING", "links": [ - 31, - 33 + 31 ], "slot_index": 1 }, @@ -913,7 +980,7 @@ }, "widgets_values": [ "a roadside diner at night seen from the parking lot", - "diffusion", + "flux", "", false, "", @@ -922,7 +989,7 @@ "title": "Subject 3" }, { - "id": 23, + "id": 25, "type": "PreviewAny", "pos": [ 420, @@ -933,7 +1000,7 @@ 84 ], "flags": {}, - "order": 22, + "order": 24, "mode": 0, "inputs": [ { @@ -950,7 +1017,7 @@ "title": "Compiled positive 3" }, { - "id": 24, + "id": 26, "type": "PreviewAny", "pos": [ 420, @@ -961,7 +1028,7 @@ 84 ], "flags": {}, - "order": 23, + "order": 25, "mode": 0, "inputs": [ { @@ -978,24 +1045,24 @@ "title": "Compiled negative 3" }, { - "id": 25, + "id": 27, "type": "CLIPTextEncode", "pos": [ 800, - 1476 + 1568 ], "size": [ 320, 198 ], "flags": {}, - "order": 24, + "order": 26, "mode": 0, "inputs": [ { "name": "clip", "type": "CLIP", - "link": 34 + "link": 33 }, { "name": "text", @@ -1008,7 +1075,8 @@ "name": "CONDITIONING", "type": "CONDITIONING", "links": [ - 37 + 34, + 35 ], "slot_index": 0 } @@ -1022,29 +1090,63 @@ "title": "Positive (from StyleRef)" }, { - "id": 26, - "type": "CLIPTextEncode", + "id": 28, + "type": "FluxGuidance", "pos": [ 800, - 1722 + 1814 ], "size": [ 320, - 198 + 112 ], "flags": {}, - "order": 25, + "order": 27, "mode": 0, "inputs": [ { - "name": "clip", - "type": "CLIP", - "link": 35 - }, + "name": "conditioning", + "type": "CONDITIONING", + "link": 34 + } + ], + "outputs": [ { - "name": "text", - "type": "STRING", - "link": 33 + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 37 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FluxGuidance" + }, + "widgets_values": [ + 3.5 + ], + "title": "Flux guidance" + }, + { + "id": 29, + "type": "ConditioningZeroOut", + "pos": [ + 800, + 1974 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 28, + "mode": 0, + "inputs": [ + { + "name": "conditioning", + "type": "CONDITIONING", + "link": 35 } ], "outputs": [ @@ -1058,26 +1160,24 @@ } ], "properties": { - "Node name for S&R": "CLIPTextEncode" + "Node name for S&R": "ConditioningZeroOut" }, - "widgets_values": [ - "" - ], - "title": "Negative (from StyleRef)" + "widgets_values": [], + "title": "FLUX has no negative" }, { - "id": 27, - "type": "EmptyLatentImage", + "id": 30, + "type": "EmptySD3LatentImage", "pos": [ 800, - 1968 + 2106 ], "size": [ 320, 168 ], "flags": {}, - "order": 26, + "order": 29, "mode": 0, "inputs": [], "outputs": [ @@ -1091,7 +1191,7 @@ } ], "properties": { - "Node name for S&R": "EmptyLatentImage" + "Node name for S&R": "EmptySD3LatentImage" }, "widgets_values": [ 1024, @@ -1100,7 +1200,7 @@ ] }, { - "id": 28, + "id": 31, "type": "KSampler", "pos": [ 1180, @@ -1111,7 +1211,7 @@ 352 ], "flags": {}, - "order": 27, + "order": 30, "mode": 0, "inputs": [ { @@ -1151,15 +1251,15 @@ "widgets_values": [ 0, "randomize", - 25, - 7.0, + 20, + 1.0, "euler", - "normal", + "simple", 1.0 ] }, { - "id": 29, + "id": 32, "type": "VAEDecode", "pos": [ 1560, @@ -1170,7 +1270,7 @@ 108 ], "flags": {}, - "order": 28, + "order": 31, "mode": 0, "inputs": [ { @@ -1200,7 +1300,7 @@ "widgets_values": [] }, { - "id": 30, + "id": 33, "type": "SaveImage", "pos": [ 1560, @@ -1211,7 +1311,7 @@ 112 ], "flags": {}, - "order": 29, + "order": 32, "mode": 0, "inputs": [ { @@ -1264,177 +1364,177 @@ ], [ 5, - 4, + 3, 1, - 8, - 1, - "STRING" + 7, + 0, + "CLIP" ], [ 6, - 3, - 1, 7, 0, - "CLIP" + 8, + 0, + "CONDITIONING" ], [ 7, - 3, - 1, - 8, + 7, 0, - "CLIP" + 9, + 0, + "CONDITIONING" ], [ 8, 3, 0, - 10, + 11, 0, "MODEL" ], [ 9, - 7, + 8, 0, - 10, + 11, 1, "CONDITIONING" ], [ 10, - 8, + 9, 0, - 10, + 11, 2, "CONDITIONING" ], [ 11, - 9, - 0, 10, + 0, + 11, 3, "LATENT" ], [ 12, - 10, - 0, 11, 0, + 12, + 0, "LATENT" ], [ 13, 3, 2, - 11, + 12, 1, "VAE" ], [ 14, - 11, - 0, 12, 0, + 13, + 0, "IMAGE" ], [ 15, 2, 0, - 13, + 14, 0, "STYLEREF_STYLE" ], [ 16, - 13, - 0, 14, 0, - "STRING" - ], - [ - 17, - 13, - 1, 15, 0, "STRING" ], [ - 18, - 13, - 0, + 17, + 14, + 1, 16, + 0, + "STRING" + ], + [ + 18, + 14, + 0, + 17, 1, "STRING" ], [ 19, - 13, + 3, 1, 17, - 1, - "STRING" + 0, + "CLIP" ], [ 20, - 3, - 1, - 16, + 17, 0, - "CLIP" + 18, + 0, + "CONDITIONING" ], [ 21, - 3, - 1, 17, 0, - "CLIP" + 19, + 0, + "CONDITIONING" ], [ 22, 3, 0, - 19, + 21, 0, "MODEL" ], [ 23, - 16, + 18, 0, - 19, + 21, 1, "CONDITIONING" ], [ 24, - 17, - 0, 19, + 0, + 21, 2, "CONDITIONING" ], [ 25, - 18, + 20, 0, - 19, + 21, 3, "LATENT" ], [ 26, - 19, + 21, 0, - 20, + 22, 0, "LATENT" ], @@ -1442,15 +1542,15 @@ 27, 3, 2, - 20, + 22, 1, "VAE" ], [ 28, - 20, + 22, 0, - 21, + 23, 0, "IMAGE" ], @@ -1458,95 +1558,95 @@ 29, 2, 0, - 22, + 24, 0, "STYLEREF_STYLE" ], [ 30, - 22, + 24, 0, - 23, + 25, 0, "STRING" ], [ 31, - 22, - 1, 24, + 1, + 26, 0, "STRING" ], [ 32, - 22, + 24, 0, - 25, + 27, 1, "STRING" ], [ 33, - 22, + 3, 1, - 26, - 1, - "STRING" + 27, + 0, + "CLIP" ], [ 34, - 3, - 1, - 25, + 27, 0, - "CLIP" + 28, + 0, + "CONDITIONING" ], [ 35, - 3, - 1, - 26, + 27, 0, - "CLIP" + 29, + 0, + "CONDITIONING" ], [ 36, 3, 0, - 28, + 31, 0, "MODEL" ], [ 37, - 25, - 0, 28, + 0, + 31, 1, "CONDITIONING" ], [ 38, - 26, + 29, 0, - 28, + 31, 2, "CONDITIONING" ], [ 39, - 27, + 30, 0, - 28, + 31, 3, "LATENT" ], [ 40, - 28, + 31, 0, - 29, + 32, 0, "LATENT" ], @@ -1554,15 +1654,15 @@ 41, 3, 2, - 29, + 32, 1, "VAE" ], [ 42, - 29, + 32, 0, - 30, + 33, 0, "IMAGE" ] @@ -1577,8 +1677,8 @@ "version": 0.4, "models": [ { - "name": "sd_xl_base_1.0.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors", + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", "directory": "checkpoints" } ] diff --git a/workflows/03-your-own-style.json b/workflows/03-your-own-style.json index 26f0373..cc7a5e6 100644 --- a/workflows/03-your-own-style.json +++ b/workflows/03-your-own-style.json @@ -1,5 +1,5 @@ { - "last_node_id": 13, + "last_node_id": 14, "last_link_id": 14, "nodes": [ { @@ -11,7 +11,7 @@ ], "size": [ 320, - 324 + 1062 ], "flags": {}, "order": 0, @@ -22,7 +22,7 @@ "Node name for S&R": "Note" }, "widgets_values": [ - "Use your own style.\nExtract a style from any image at https://styleref.io \u2014 it appears in your library \u2014 then load it here by its share slug or id.\n1. Queue the StyleRef Login node alone first (Queue runs every node, so an empty workflow is the clean way). If status says signed out, set action to `sign in` and queue again.\n2. Put your style's id in `style_ref` on StyleRef Load, or pick it\n with the Search styles\u2026 button.\n3. Set your SDXL checkpoint and queue." + "Use your own style.\nExtract a style from any image at https://styleref.io \u2014 it appears in your library \u2014 then load it here by its share slug or id.\n1. Queue the StyleRef Login node alone first (Queue runs every node, so an empty workflow is the clean way). If status says signed out, set action to `sign in` and queue again.\n2. Put your style's id in `style_ref` on StyleRef Load, or pick it\n with the Search styles\u2026 button.\n3. Queue.\n\nFLUX.1 dev (fp8 all-in-one). The checkpoint downloads on first use into checkpoints/ \u2014 one ~16 GB file containing the UNET, the T5 text encoder and the VAE.\nSampler defaults: 20 steps, CFG 1.0, euler/simple. Prompt strength is the FluxGuidance node (3.5), not CFG.\nFLUX takes no negative prompt. It is guidance distilled and runs at CFG 1.0, so there is no negative encoder on this canvas \u2014 the sampler's negative input gets a zeroed conditioning. StyleRef still compiles the negative and previews it; put anything you need from it into `subject` on Apply as a positive phrase (\"fully clothed\", \"no text or watermark\"). Raising CFG does not bring the negative back.\nStyle prompts use the `flux` target \u2014 FLUX reads prose with T5, so the whole style fits instead of being truncated at ~77 tokens the way an SDXL CLIP encoder would.\n\nStyle not coming through strongly enough?\nFLUX has a confident house look and will drift back to it. Two dials, in the order worth trying:\n1. `Flux guidance` (3.5 here, FLUX's default) is the blunt dial. 4\u20135 makes the style bite harder; past that the subject starts losing to it, and a dark style can go to pure black.\n2. Narrow `sections` on StyleRef Apply to the parts that define this style. For a period or painterly style that is usually `artistic_mediums,references,surface_material,colors` \u2014 the sections naming the medium, the era and the finish. Leaving it empty sends every section, and the identity-carrying ones render last, so a long style can read as generic.\nWhich sections matter depends on the style: for a geometric or layout-led style keep `shape_language` and `spatial_hierarchy` instead. Queue the Facets template to see what a style actually carries." ], "title": "Your own style" }, @@ -31,7 +31,7 @@ "type": "StyleRefLogin", "pos": [ 40, - 432 + 1170 ], "size": [ 320, @@ -60,7 +60,7 @@ "type": "StyleRefLoad", "pos": [ 40, - 564 + 1302 ], "size": [ 320, @@ -103,7 +103,7 @@ "type": "CheckpointLoaderSimple", "pos": [ 40, - 860 + 1598 ], "size": [ 320, @@ -126,8 +126,7 @@ "name": "CLIP", "type": "CLIP", "links": [ - 6, - 7 + 5 ], "slot_index": 1 }, @@ -141,10 +140,17 @@ } ], "properties": { - "Node name for S&R": "CheckpointLoaderSimple" + "Node name for S&R": "CheckpointLoaderSimple", + "models": [ + { + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", + "directory": "checkpoints" + } + ] }, "widgets_values": [ - "sd_xl_base_1.0.safetensors" + "flux1-dev-fp8.safetensors" ] }, { @@ -182,8 +188,7 @@ "name": "negative", "type": "STRING", "links": [ - 3, - 5 + 3 ], "slot_index": 1 }, @@ -199,7 +204,7 @@ }, "widgets_values": [ "two friends flipping through crates of vinyl in a record shop", - "diffusion", + "flux", "", false, "", @@ -280,7 +285,7 @@ { "name": "clip", "type": "CLIP", - "link": 6 + "link": 5 }, { "name": "text", @@ -293,7 +298,8 @@ "name": "CONDITIONING", "type": "CONDITIONING", "links": [ - 9 + 6, + 7 ], "slot_index": 0 } @@ -308,28 +314,62 @@ }, { "id": 9, - "type": "CLIPTextEncode", + "type": "FluxGuidance", "pos": [ 800, 306 ], "size": [ 320, - 198 + 112 ], "flags": {}, "order": 8, "mode": 0, "inputs": [ { - "name": "clip", - "type": "CLIP", - "link": 7 - }, + "name": "conditioning", + "type": "CONDITIONING", + "link": 6 + } + ], + "outputs": [ { - "name": "text", - "type": "STRING", - "link": 5 + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 9 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FluxGuidance" + }, + "widgets_values": [ + 3.5 + ], + "title": "Flux guidance" + }, + { + "id": 10, + "type": "ConditioningZeroOut", + "pos": [ + 800, + 466 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "conditioning", + "type": "CONDITIONING", + "link": 7 } ], "outputs": [ @@ -343,26 +383,24 @@ } ], "properties": { - "Node name for S&R": "CLIPTextEncode" + "Node name for S&R": "ConditioningZeroOut" }, - "widgets_values": [ - "" - ], - "title": "Negative (from StyleRef)" + "widgets_values": [], + "title": "FLUX has no negative" }, { - "id": 10, - "type": "EmptyLatentImage", + "id": 11, + "type": "EmptySD3LatentImage", "pos": [ 800, - 552 + 598 ], "size": [ 320, 168 ], "flags": {}, - "order": 9, + "order": 10, "mode": 0, "inputs": [], "outputs": [ @@ -376,7 +414,7 @@ } ], "properties": { - "Node name for S&R": "EmptyLatentImage" + "Node name for S&R": "EmptySD3LatentImage" }, "widgets_values": [ 1024, @@ -385,7 +423,7 @@ ] }, { - "id": 11, + "id": 12, "type": "KSampler", "pos": [ 1180, @@ -396,7 +434,7 @@ 352 ], "flags": {}, - "order": 10, + "order": 11, "mode": 0, "inputs": [ { @@ -436,15 +474,15 @@ "widgets_values": [ 0, "randomize", - 25, - 7.0, + 20, + 1.0, "euler", - "normal", + "simple", 1.0 ] }, { - "id": 12, + "id": 13, "type": "VAEDecode", "pos": [ 1560, @@ -455,7 +493,7 @@ 108 ], "flags": {}, - "order": 11, + "order": 12, "mode": 0, "inputs": [ { @@ -485,7 +523,7 @@ "widgets_values": [] }, { - "id": 13, + "id": 14, "type": "SaveImage", "pos": [ 1560, @@ -496,7 +534,7 @@ 112 ], "flags": {}, - "order": 12, + "order": 13, "mode": 0, "inputs": [ { @@ -549,82 +587,82 @@ ], [ 5, - 5, + 4, 1, - 9, - 1, - "STRING" + 8, + 0, + "CLIP" ], [ 6, - 4, - 1, 8, 0, - "CLIP" + 9, + 0, + "CONDITIONING" ], [ 7, - 4, - 1, - 9, + 8, 0, - "CLIP" + 10, + 0, + "CONDITIONING" ], [ 8, 4, 0, - 11, + 12, 0, "MODEL" ], [ 9, - 8, + 9, 0, - 11, + 12, 1, "CONDITIONING" ], [ 10, - 9, + 10, 0, - 11, + 12, 2, "CONDITIONING" ], [ 11, - 10, - 0, 11, + 0, + 12, 3, "LATENT" ], [ 12, - 11, - 0, 12, 0, + 13, + 0, "LATENT" ], [ 13, 4, 2, - 12, + 13, 1, "VAE" ], [ 14, - 12, - 0, 13, 0, + 14, + 0, "IMAGE" ] ], @@ -638,8 +676,8 @@ "version": 0.4, "models": [ { - "name": "sd_xl_base_1.0.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors", + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", "directory": "checkpoints" } ] diff --git a/workflows/04-reference-images.json b/workflows/04-reference-images.json new file mode 100644 index 0000000..236e299 --- /dev/null +++ b/workflows/04-reference-images.json @@ -0,0 +1,745 @@ +{ + "last_node_id": 15, + "last_link_id": 16, + "nodes": [ + { + "id": 1, + "type": "Note", + "pos": [ + 40, + 60 + ], + "size": [ + 320, + 1008 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": { + "Node name for S&R": "Note" + }, + "widgets_values": [ + "Style prompt and reference images together.\nStyleRef Apply drives the sampler with the style's prompt; StyleRef Reference Images pulls the style's inspiration images out as a batch, previewed alongside. The prompt says how the style reads; the images show how it looks \u2014 wire the images into IPAdapter or a reference-only ControlNet to combine both.\nUse a gallery style that has inspiration images.\n\nFLUX.1 dev (fp8 all-in-one). The checkpoint downloads on first use into checkpoints/ \u2014 one ~16 GB file containing the UNET, the T5 text encoder and the VAE.\nSampler defaults: 20 steps, CFG 1.0, euler/simple. Prompt strength is the FluxGuidance node (3.5), not CFG.\nFLUX takes no negative prompt. It is guidance distilled and runs at CFG 1.0, so there is no negative encoder on this canvas \u2014 the sampler's negative input gets a zeroed conditioning. StyleRef still compiles the negative and previews it; put anything you need from it into `subject` on Apply as a positive phrase (\"fully clothed\", \"no text or watermark\"). Raising CFG does not bring the negative back.\nStyle prompts use the `flux` target \u2014 FLUX reads prose with T5, so the whole style fits instead of being truncated at ~77 tokens the way an SDXL CLIP encoder would.\n\nStyle not coming through strongly enough?\nFLUX has a confident house look and will drift back to it. Two dials, in the order worth trying:\n1. `Flux guidance` (3.5 here, FLUX's default) is the blunt dial. 4\u20135 makes the style bite harder; past that the subject starts losing to it, and a dark style can go to pure black.\n2. Narrow `sections` on StyleRef Apply to the parts that define this style. For a period or painterly style that is usually `artistic_mediums,references,surface_material,colors` \u2014 the sections naming the medium, the era and the finish. Leaving it empty sends every section, and the identity-carrying ones render last, so a long style can read as generic.\nWhich sections matter depends on the style: for a geometric or layout-led style keep `shape_language` and `spatial_hierarchy` instead. Queue the Facets template to see what a style actually carries." + ], + "title": "FLUX + references" + }, + { + "id": 2, + "type": "StyleRefLoad", + "pos": [ + 40, + 1116 + ], + "size": [ + 320, + 248 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "style", + "type": "STYLEREF_STYLE", + "links": [ + 1, + 3 + ], + "slot_index": 0 + }, + { + "name": "search_results", + "type": "STRING", + "links": [], + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "StyleRefLoad" + }, + "widgets_values": [ + "72e1zdae-e2d54a18d090", + "", + "any", + true, + false + ], + "title": "Load \u2014 a gallery slug" + }, + { + "id": 3, + "type": "CheckpointLoaderSimple", + "pos": [ + 40, + 1412 + ], + "size": [ + 320, + 160 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 10 + ], + "slot_index": 0 + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 7 + ], + "slot_index": 1 + }, + { + "name": "VAE", + "type": "VAE", + "links": [ + 15 + ], + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "CheckpointLoaderSimple", + "models": [ + { + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", + "directory": "checkpoints" + } + ] + }, + "widgets_values": [ + "flux1-dev-fp8.safetensors" + ] + }, + { + "id": 4, + "type": "StyleRefReferenceImages", + "pos": [ + 420, + 60 + ], + "size": [ + 320, + 136 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "style", + "type": "STYLEREF_STYLE", + "link": 1 + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "links": [ + 2 + ], + "slot_index": 0 + }, + { + "name": "count", + "type": "INT", + "links": [], + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "StyleRefReferenceImages" + }, + "widgets_values": [ + 12 + ], + "title": "Reference images" + }, + { + "id": 5, + "type": "PreviewImage", + "pos": [ + 420, + 244 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 2 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "PreviewImage" + }, + "widgets_values": [], + "title": "Inspiration images" + }, + { + "id": 6, + "type": "StyleRefApply", + "pos": [ + 420, + 376 + ], + "size": [ + 320, + 424 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "style", + "type": "STYLEREF_STYLE", + "link": 3 + } + ], + "outputs": [ + { + "name": "positive", + "type": "STRING", + "links": [ + 4, + 6 + ], + "slot_index": 0 + }, + { + "name": "negative", + "type": "STRING", + "links": [ + 5 + ], + "slot_index": 1 + }, + { + "name": "attribution", + "type": "STRING", + "links": [], + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "StyleRefApply" + }, + "widgets_values": [ + "two friends flipping through crates of vinyl in a record shop", + "flux", + "", + false, + "", + true + ] + }, + { + "id": 7, + "type": "PreviewAny", + "pos": [ + 420, + 848 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 4 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [], + "title": "Compiled positive" + }, + { + "id": 8, + "type": "PreviewAny", + "pos": [ + 420, + 980 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 5 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [], + "title": "Compiled negative" + }, + { + "id": 9, + "type": "CLIPTextEncode", + "pos": [ + 800, + 60 + ], + "size": [ + 320, + 198 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 7 + }, + { + "name": "text", + "type": "STRING", + "link": 6 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 8, + 9 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ], + "title": "Positive (from StyleRef)" + }, + { + "id": 10, + "type": "FluxGuidance", + "pos": [ + 800, + 306 + ], + "size": [ + 320, + 112 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "conditioning", + "type": "CONDITIONING", + "link": 8 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 11 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FluxGuidance" + }, + "widgets_values": [ + 3.5 + ], + "title": "Flux guidance" + }, + { + "id": 11, + "type": "ConditioningZeroOut", + "pos": [ + 800, + 466 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "conditioning", + "type": "CONDITIONING", + "link": 9 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 12 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ConditioningZeroOut" + }, + "widgets_values": [], + "title": "FLUX has no negative" + }, + { + "id": 12, + "type": "EmptySD3LatentImage", + "pos": [ + 800, + 598 + ], + "size": [ + 320, + 168 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 13 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "EmptySD3LatentImage" + }, + "widgets_values": [ + 1024, + 1024, + 1 + ] + }, + { + "id": 13, + "type": "KSampler", + "pos": [ + 1180, + 60 + ], + "size": [ + 320, + 352 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 10 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 11 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 12 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 13 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 14 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 0, + "randomize", + 20, + 1.0, + "euler", + "simple", + 1.0 + ] + }, + { + "id": 14, + "type": "VAEDecode", + "pos": [ + 1560, + 60 + ], + "size": [ + 320, + 108 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 14 + }, + { + "name": "vae", + "type": "VAE", + "link": 15 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 16 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + }, + { + "id": 15, + "type": "SaveImage", + "pos": [ + 1560, + 216 + ], + "size": [ + 320, + 112 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 16 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "SaveImage" + }, + "widgets_values": [ + "StyleRef" + ] + } + ], + "links": [ + [ + 1, + 2, + 0, + 4, + 0, + "STYLEREF_STYLE" + ], + [ + 2, + 4, + 0, + 5, + 0, + "IMAGE" + ], + [ + 3, + 2, + 0, + 6, + 0, + "STYLEREF_STYLE" + ], + [ + 4, + 6, + 0, + 7, + 0, + "STRING" + ], + [ + 5, + 6, + 1, + 8, + 0, + "STRING" + ], + [ + 6, + 6, + 0, + 9, + 1, + "STRING" + ], + [ + 7, + 3, + 1, + 9, + 0, + "CLIP" + ], + [ + 8, + 9, + 0, + 10, + 0, + "CONDITIONING" + ], + [ + 9, + 9, + 0, + 11, + 0, + "CONDITIONING" + ], + [ + 10, + 3, + 0, + 13, + 0, + "MODEL" + ], + [ + 11, + 10, + 0, + 13, + 1, + "CONDITIONING" + ], + [ + 12, + 11, + 0, + 13, + 2, + "CONDITIONING" + ], + [ + 13, + 12, + 0, + 13, + 3, + "LATENT" + ], + [ + 14, + 13, + 0, + 14, + 0, + "LATENT" + ], + [ + 15, + 3, + 2, + 14, + 1, + "VAE" + ], + [ + 16, + 14, + 0, + 15, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": { + "styleref": { + "notes": "Generated by scripts/build_workflows.py \u2014 edit the script, not this file." + } + }, + "version": 0.4, + "models": [ + { + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", + "directory": "checkpoints" + } + ] +} diff --git a/workflows/04-facets.json b/workflows/05-facets.json similarity index 91% rename from workflows/04-facets.json rename to workflows/05-facets.json index 7f3cd5d..8b789a1 100644 --- a/workflows/04-facets.json +++ b/workflows/05-facets.json @@ -1,5 +1,5 @@ { - "last_node_id": 19, + "last_node_id": 20, "last_link_id": 21, "nodes": [ { @@ -62,7 +62,7 @@ "Node name for S&R": "StyleRefLoad" }, "widgets_values": [ - "sbdlwwly-66ae1a89efdc", + "72e1zdae-e2d54a18d090", "", "any", true, @@ -98,8 +98,7 @@ "name": "CLIP", "type": "CLIP", "links": [ - 13, - 14 + 12 ], "slot_index": 1 }, @@ -113,10 +112,17 @@ } ], "properties": { - "Node name for S&R": "CheckpointLoaderSimple" + "Node name for S&R": "CheckpointLoaderSimple", + "models": [ + { + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", + "directory": "checkpoints" + } + ] }, "widgets_values": [ - "sd_xl_base_1.0.safetensors" + "flux1-dev-fp8.safetensors" ] }, { @@ -507,8 +513,7 @@ "name": "negative", "type": "STRING", "links": [ - 10, - 12 + 10 ], "slot_index": 1 }, @@ -524,7 +529,7 @@ }, "widgets_values": [ "two friends flipping through crates of vinyl in a record shop", - "diffusion", + "flux", "", false, "", @@ -605,7 +610,7 @@ { "name": "clip", "type": "CLIP", - "link": 13 + "link": 12 }, { "name": "text", @@ -618,7 +623,8 @@ "name": "CONDITIONING", "type": "CONDITIONING", "links": [ - 16 + 13, + 14 ], "slot_index": 0 } @@ -633,28 +639,62 @@ }, { "id": 15, - "type": "CLIPTextEncode", + "type": "FluxGuidance", "pos": [ 1560, 306 ], "size": [ 320, - 198 + 112 ], "flags": {}, "order": 14, "mode": 0, "inputs": [ { - "name": "clip", - "type": "CLIP", - "link": 14 - }, + "name": "conditioning", + "type": "CONDITIONING", + "link": 13 + } + ], + "outputs": [ { - "name": "text", - "type": "STRING", - "link": 12 + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 16 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FluxGuidance" + }, + "widgets_values": [ + 3.5 + ], + "title": "Flux guidance" + }, + { + "id": 16, + "type": "ConditioningZeroOut", + "pos": [ + 1560, + 466 + ], + "size": [ + 320, + 84 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "conditioning", + "type": "CONDITIONING", + "link": 14 } ], "outputs": [ @@ -668,26 +708,24 @@ } ], "properties": { - "Node name for S&R": "CLIPTextEncode" + "Node name for S&R": "ConditioningZeroOut" }, - "widgets_values": [ - "" - ], - "title": "Negative (from StyleRef)" + "widgets_values": [], + "title": "FLUX has no negative" }, { - "id": 16, - "type": "EmptyLatentImage", + "id": 17, + "type": "EmptySD3LatentImage", "pos": [ 1560, - 552 + 598 ], "size": [ 320, 168 ], "flags": {}, - "order": 15, + "order": 16, "mode": 0, "inputs": [], "outputs": [ @@ -701,7 +739,7 @@ } ], "properties": { - "Node name for S&R": "EmptyLatentImage" + "Node name for S&R": "EmptySD3LatentImage" }, "widgets_values": [ 1024, @@ -710,7 +748,7 @@ ] }, { - "id": 17, + "id": 18, "type": "KSampler", "pos": [ 1940, @@ -721,7 +759,7 @@ 352 ], "flags": {}, - "order": 16, + "order": 17, "mode": 0, "inputs": [ { @@ -761,15 +799,15 @@ "widgets_values": [ 0, "randomize", - 25, - 7.0, + 20, + 1.0, "euler", - "normal", + "simple", 1.0 ] }, { - "id": 18, + "id": 19, "type": "VAEDecode", "pos": [ 2320, @@ -780,7 +818,7 @@ 108 ], "flags": {}, - "order": 17, + "order": 18, "mode": 0, "inputs": [ { @@ -810,7 +848,7 @@ "widgets_values": [] }, { - "id": 19, + "id": 20, "type": "SaveImage", "pos": [ 2320, @@ -821,7 +859,7 @@ 112 ], "flags": {}, - "order": 18, + "order": 19, "mode": 0, "inputs": [ { @@ -930,82 +968,82 @@ ], [ 12, - 11, + 3, 1, - 15, - 1, - "STRING" + 14, + 0, + "CLIP" ], [ 13, - 3, - 1, 14, 0, - "CLIP" + 15, + 0, + "CONDITIONING" ], [ 14, - 3, - 1, - 15, + 14, 0, - "CLIP" + 16, + 0, + "CONDITIONING" ], [ 15, 3, 0, - 17, + 18, 0, "MODEL" ], [ 16, - 14, + 15, 0, - 17, + 18, 1, "CONDITIONING" ], [ 17, - 15, + 16, 0, - 17, + 18, 2, "CONDITIONING" ], [ 18, - 16, - 0, 17, + 0, + 18, 3, "LATENT" ], [ 19, - 17, - 0, 18, 0, + 19, + 0, "LATENT" ], [ 20, 3, 2, - 18, + 19, 1, "VAE" ], [ 21, - 18, - 0, 19, 0, + 20, + 0, "IMAGE" ] ], @@ -1019,8 +1057,8 @@ "version": 0.4, "models": [ { - "name": "sd_xl_base_1.0.safetensors", - "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors", + "name": "flux1-dev-fp8.safetensors", + "url": "https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors", "directory": "checkpoints" } ] diff --git a/workflows/z-image/01-quickstart.json b/workflows/z-image/01-quickstart.json index ae1ac4e..52c4354 100644 --- a/workflows/z-image/01-quickstart.json +++ b/workflows/z-image/01-quickstart.json @@ -95,7 +95,14 @@ } ], "properties": { - "Node name for S&R": "UNETLoader" + "Node name for S&R": "UNETLoader", + "models": [ + { + "name": "z_image_turbo_bf16.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/diffusion_models/z_image_turbo_bf16.safetensors", + "directory": "diffusion_models" + } + ] }, "widgets_values": [ "z_image_turbo_bf16.safetensors", @@ -129,7 +136,14 @@ } ], "properties": { - "Node name for S&R": "CLIPLoader" + "Node name for S&R": "CLIPLoader", + "models": [ + { + "name": "qwen_3_4b.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors", + "directory": "text_encoders" + } + ] }, "widgets_values": [ "qwen_3_4b.safetensors", @@ -164,7 +178,14 @@ } ], "properties": { - "Node name for S&R": "VAELoader" + "Node name for S&R": "VAELoader", + "models": [ + { + "name": "ae.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", + "directory": "vae" + } + ] }, "widgets_values": [ "ae.safetensors" diff --git a/workflows/z-image/02-consistency-grid.json b/workflows/z-image/02-consistency-grid.json index 8ca3f91..6a2ede4 100644 --- a/workflows/z-image/02-consistency-grid.json +++ b/workflows/z-image/02-consistency-grid.json @@ -98,7 +98,14 @@ } ], "properties": { - "Node name for S&R": "UNETLoader" + "Node name for S&R": "UNETLoader", + "models": [ + { + "name": "z_image_turbo_bf16.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/diffusion_models/z_image_turbo_bf16.safetensors", + "directory": "diffusion_models" + } + ] }, "widgets_values": [ "z_image_turbo_bf16.safetensors", @@ -134,7 +141,14 @@ } ], "properties": { - "Node name for S&R": "CLIPLoader" + "Node name for S&R": "CLIPLoader", + "models": [ + { + "name": "qwen_3_4b.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors", + "directory": "text_encoders" + } + ] }, "widgets_values": [ "qwen_3_4b.safetensors", @@ -171,7 +185,14 @@ } ], "properties": { - "Node name for S&R": "VAELoader" + "Node name for S&R": "VAELoader", + "models": [ + { + "name": "ae.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", + "directory": "vae" + } + ] }, "widgets_values": [ "ae.safetensors" @@ -588,7 +609,7 @@ "Node name for S&R": "StyleRefApply" }, "widgets_values": [ - "a lone surfer walking back along the beach at dusk", + "A lone surfer strolled back along the beach at dusk, carrying her large surfing board.", "diffusion", "", false, diff --git a/workflows/z-image/03-your-own-style.json b/workflows/z-image/03-your-own-style.json index 5a5b87f..de8aeb0 100644 --- a/workflows/z-image/03-your-own-style.json +++ b/workflows/z-image/03-your-own-style.json @@ -124,7 +124,14 @@ } ], "properties": { - "Node name for S&R": "UNETLoader" + "Node name for S&R": "UNETLoader", + "models": [ + { + "name": "z_image_turbo_bf16.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/diffusion_models/z_image_turbo_bf16.safetensors", + "directory": "diffusion_models" + } + ] }, "widgets_values": [ "z_image_turbo_bf16.safetensors", @@ -158,7 +165,14 @@ } ], "properties": { - "Node name for S&R": "CLIPLoader" + "Node name for S&R": "CLIPLoader", + "models": [ + { + "name": "qwen_3_4b.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors", + "directory": "text_encoders" + } + ] }, "widgets_values": [ "qwen_3_4b.safetensors", @@ -193,7 +207,14 @@ } ], "properties": { - "Node name for S&R": "VAELoader" + "Node name for S&R": "VAELoader", + "models": [ + { + "name": "ae.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", + "directory": "vae" + } + ] }, "widgets_values": [ "ae.safetensors" diff --git a/workflows/z-image/04-reference-images.json b/workflows/z-image/04-reference-images.json index 899c248..d3cf0f0 100644 --- a/workflows/z-image/04-reference-images.json +++ b/workflows/z-image/04-reference-images.json @@ -96,7 +96,14 @@ } ], "properties": { - "Node name for S&R": "UNETLoader" + "Node name for S&R": "UNETLoader", + "models": [ + { + "name": "z_image_turbo_bf16.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/diffusion_models/z_image_turbo_bf16.safetensors", + "directory": "diffusion_models" + } + ] }, "widgets_values": [ "z_image_turbo_bf16.safetensors", @@ -130,7 +137,14 @@ } ], "properties": { - "Node name for S&R": "CLIPLoader" + "Node name for S&R": "CLIPLoader", + "models": [ + { + "name": "qwen_3_4b.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors", + "directory": "text_encoders" + } + ] }, "widgets_values": [ "qwen_3_4b.safetensors", @@ -165,7 +179,14 @@ } ], "properties": { - "Node name for S&R": "VAELoader" + "Node name for S&R": "VAELoader", + "models": [ + { + "name": "ae.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", + "directory": "vae" + } + ] }, "widgets_values": [ "ae.safetensors" diff --git a/workflows/z-image/05-facets.json b/workflows/z-image/05-facets.json index bb905c1..0869cd5 100644 --- a/workflows/z-image/05-facets.json +++ b/workflows/z-image/05-facets.json @@ -96,7 +96,14 @@ } ], "properties": { - "Node name for S&R": "UNETLoader" + "Node name for S&R": "UNETLoader", + "models": [ + { + "name": "z_image_turbo_bf16.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/diffusion_models/z_image_turbo_bf16.safetensors", + "directory": "diffusion_models" + } + ] }, "widgets_values": [ "z_image_turbo_bf16.safetensors", @@ -130,7 +137,14 @@ } ], "properties": { - "Node name for S&R": "CLIPLoader" + "Node name for S&R": "CLIPLoader", + "models": [ + { + "name": "qwen_3_4b.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors", + "directory": "text_encoders" + } + ] }, "widgets_values": [ "qwen_3_4b.safetensors", @@ -165,7 +179,14 @@ } ], "properties": { - "Node name for S&R": "VAELoader" + "Node name for S&R": "VAELoader", + "models": [ + { + "name": "ae.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", + "directory": "vae" + } + ] }, "widgets_values": [ "ae.safetensors"