Rebrand fork to Hanzo Studio: licensing, branding, Engine nodes, fashion workflows
Licensing (GPL-3.0 preserved): - NOTICE attributes the ComfyUI upstream and the fork's modifications, GPL-3.0. - README credits upstream ComfyUI prominently and states GPL-3.0. Branding (frontend served from the comfyui-frontend-package static dir): - apply-branding.sh installs the Hanzo favicon (svg + multi-res ico), injects icon links, sets <title>Hanzo Studio</title>, and patches display strings. - make_favicon.py renders the committed favicon.ico from favicon.svg. Hanzo Engine nodes (custom_nodes/hanzo_engine/, OpenAI-compatible HTTP): - HanzoChat, HanzoImageGen, HanzoVisionCaption, HanzoSaveText. Graceful node errors on connection failure. Fashion/swimwear starter workflows (user/default/workflows/fashion/): - fashion_product_shot (FLUX.2 klein t2i), fashion_edit_garment (Qwen-Image-Edit i2i), fashion_caption_dataset (Hanzo vision caption loop). Each carries a Note node with usage and required files.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
Hanzo Studio
|
||||
============
|
||||
|
||||
Hanzo Studio is a fork of ComfyUI (https://github.com/comfyanonymous/ComfyUI),
|
||||
Copyright (c) ComfyUI contributors, licensed under the GNU General Public
|
||||
License v3.0 (GPL-3.0).
|
||||
|
||||
Modifications Copyright (c) 2026 Hanzo Industries Inc, also released under the
|
||||
GNU General Public License v3.0 (GPL-3.0). See the LICENSE file for the full
|
||||
license text.
|
||||
|
||||
As required by the GPL-3.0, this fork remains under the same license as the
|
||||
upstream project. The original ComfyUI copyright notices are retained.
|
||||
|
||||
Modifications made in this fork
|
||||
-------------------------------
|
||||
|
||||
- Branding: user-visible name changed from "ComfyUI" to "Hanzo Studio",
|
||||
including the served page <title>, favicon, in-app logos, and display
|
||||
strings. The prebuilt frontend package (comfyui-frontend-package) is patched
|
||||
at install time by branding/apply-branding.sh; source assets live in
|
||||
branding/. Backend Python packages are namespaced under studio_* and the
|
||||
server reports itself as "Hanzo Studio".
|
||||
|
||||
- Hanzo Engine integration nodes (custom_nodes/hanzo_engine/): custom nodes
|
||||
that call a local Hanzo Engine OpenAI-compatible server — HanzoChat
|
||||
(chat/completions), HanzoImageGen (images/generations), and
|
||||
HanzoVisionCaption (vision chat/completions). Hanzo Engine is the native-Rust,
|
||||
all-modality inference and training backend.
|
||||
|
||||
- Fashion/swimwear starter workflows (user/default/workflows/fashion/):
|
||||
product-shot, garment-edit, and caption-dataset workflow templates for a
|
||||
designer's day-to-day use.
|
||||
|
||||
- Deployment/integration middleware for Hanzo infrastructure (IAM auth via
|
||||
hanzo.id, commerce/billing, metrics), wired through environment variables and
|
||||
the Dockerfile.
|
||||
|
||||
Third-party components retain their own upstream copyrights and licenses.
|
||||
@@ -35,6 +35,12 @@
|
||||
|
||||
Hanzo Studio lets you design and execute advanced stable diffusion pipelines using a graph/nodes/flowchart based interface. Available on Windows, Linux, and macOS.
|
||||
|
||||
## Upstream & License
|
||||
|
||||
Hanzo Studio is a fork of [**ComfyUI**](https://github.com/comfyanonymous/ComfyUI) by comfyanonymous and the ComfyUI contributors — full credit to the upstream project for the engine this builds on. ComfyUI is licensed under the **GNU General Public License v3.0 (GPL-3.0)**, and this fork stays under the same license. All modifications in this fork are likewise GPL-3.0.
|
||||
|
||||
See [`LICENSE`](LICENSE) for the full license text and [`NOTICE`](NOTICE) for the fork's attribution and a summary of what it changes (branding, Hanzo Engine integration nodes, and fashion workflow templates).
|
||||
|
||||
## Get Started
|
||||
|
||||
#### [Manual Install](#manual-install-windows-linux)
|
||||
|
||||
+23
-41
@@ -1,16 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Apply Hanzo Studio branding to frontend package
|
||||
# This runs during Docker build after pip install to patch the pre-built frontend
|
||||
# Apply Hanzo Studio branding to the prebuilt frontend package (comfyui-frontend-package).
|
||||
# Runs during the Docker build after pip install, and can be run against a venv
|
||||
# for local dev. Idempotent: safe to run more than once.
|
||||
set -e
|
||||
|
||||
STATIC_DIR=$(python3 -c "import comfyui_frontend_package, importlib.resources; print(str(importlib.resources.files(comfyui_frontend_package) / 'static'))")
|
||||
BRANDING_DIR="$(dirname "$0")"
|
||||
BRANDING_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "=== Hanzo Studio Branding ==="
|
||||
echo "Target: $STATIC_DIR"
|
||||
|
||||
# --- 1. Replace logo SVG files ---
|
||||
echo "[1/6] Replacing logos..."
|
||||
# --- 1. Replace in-app logos ---
|
||||
echo "[1/5] Replacing logos..."
|
||||
for logo_file in comfy-logo-single.svg comfy-logo-mono.svg comfy-cloud-logo.svg; do
|
||||
target="$STATIC_DIR/assets/images/$logo_file"
|
||||
if [ -f "$target" ]; then
|
||||
@@ -21,54 +22,35 @@ for logo_file in comfy-logo-single.svg comfy-logo-mono.svg comfy-cloud-logo.svg;
|
||||
fi
|
||||
done
|
||||
|
||||
# --- 2. Replace favicon ---
|
||||
echo "[2/6] Replacing favicon..."
|
||||
python3 -c "
|
||||
import struct
|
||||
size = 16
|
||||
pixels = bytearray(size * size * 4)
|
||||
for y in range(size):
|
||||
for x in range(size):
|
||||
idx = (y * size + x) * 4
|
||||
pixels[idx] = 0; pixels[idx+1] = 0; pixels[idx+2] = 0; pixels[idx+3] = 0
|
||||
if 1 <= x <= 4 and 1 <= y <= 14:
|
||||
pixels[idx] = 255; pixels[idx+1] = 255; pixels[idx+2] = 255; pixels[idx+3] = 255
|
||||
if 11 <= x <= 14 and 1 <= y <= 14:
|
||||
pixels[idx] = 255; pixels[idx+1] = 255; pixels[idx+2] = 255; pixels[idx+3] = 255
|
||||
if 4 <= y <= 11:
|
||||
expected_x = 1 + (y - 4) * (13.0 / 7.0)
|
||||
if abs(x - expected_x) <= 2:
|
||||
pixels[idx] = 255; pixels[idx+1] = 255; pixels[idx+2] = 255; pixels[idx+3] = 255
|
||||
ico = bytearray()
|
||||
ico += struct.pack('<HHH', 0, 1, 1)
|
||||
ico += struct.pack('<BBBBHHII', size, size, 0, 0, 1, 32, len(pixels) + 40, 22)
|
||||
ico += struct.pack('<IiiHHIIiiII', 40, size, size*2, 1, 32, 0, len(pixels), 0, 0, 0, 0)
|
||||
for y in range(size-1, -1, -1):
|
||||
ico += pixels[y*size*4:(y+1)*size*4]
|
||||
with open('$STATIC_DIR/assets/favicon.ico', 'wb') as f:
|
||||
f.write(ico)
|
||||
print(' Generated favicon.ico')
|
||||
"
|
||||
# --- 2. Install favicon (Hanzo mark) ---
|
||||
# The frontend sets the runtime favicon to /assets/favicon.ico via useFavicon();
|
||||
# the web root copies cover the pre-boot page load and the default /favicon.ico
|
||||
# request. favicon.ico is a committed artifact rendered from favicon.svg by
|
||||
# make_favicon.py (the Docker image has no rasterizer).
|
||||
echo "[2/5] Installing favicon..."
|
||||
cp "$BRANDING_DIR/favicon.ico" "$STATIC_DIR/assets/favicon.ico"
|
||||
cp "$BRANDING_DIR/favicon.ico" "$STATIC_DIR/favicon.ico"
|
||||
cp "$BRANDING_DIR/favicon.svg" "$STATIC_DIR/favicon.svg"
|
||||
echo " Installed favicon.ico + favicon.svg"
|
||||
|
||||
# --- 3. Patch index.html ---
|
||||
echo "[3/6] Patching index.html..."
|
||||
# --- 3. Patch index.html (title + icon link + loading text) ---
|
||||
echo "[3/5] Patching index.html..."
|
||||
if [ -f "$STATIC_DIR/index.html" ]; then
|
||||
sed -i 's/<title>ComfyUI<\/title>/<title>Hanzo Studio<\/title>/g' "$STATIC_DIR/index.html"
|
||||
# Rename the title and inject icon links (the upstream head has none).
|
||||
sed -i 's#<title>ComfyUI</title>#<title>Hanzo Studio</title><link rel="icon" type="image/svg+xml" href="favicon.svg"/><link rel="alternate icon" href="favicon.ico"/>#g' "$STATIC_DIR/index.html"
|
||||
sed -i 's/Loading ComfyUI/Loading Hanzo Studio/g' "$STATIC_DIR/index.html"
|
||||
sed -i 's/content="ComfyUI/content="Hanzo Studio/g' "$STATIC_DIR/index.html"
|
||||
echo " Patched index.html"
|
||||
fi
|
||||
|
||||
# --- 4. Patch manifest JSON files ---
|
||||
echo "[4/6] Patching manifests..."
|
||||
echo "[4/5] Patching manifests..."
|
||||
find "$STATIC_DIR" -name "manifest*.json" -exec sed -i 's/"ComfyUI"/"Hanzo Studio"/g' {} \;
|
||||
find "$STATIC_DIR" -name "manifest*.json" -exec sed -i 's/studio\.org/hanzo.ai/g' {} \;
|
||||
echo " Done"
|
||||
|
||||
# --- 5. Smart JS/CSS patching via Python ---
|
||||
echo "[5/6] Smart JS patching..."
|
||||
# --- 5. Smart JS/CSS/JSON display-string patching ---
|
||||
echo "[5/5] Smart JS patching..."
|
||||
python3 "$BRANDING_DIR/patch_frontend.py" "$STATIC_DIR"
|
||||
|
||||
# --- 6. Final verification ---
|
||||
echo "[6/6] Verification..."
|
||||
echo "=== Hanzo Studio branding complete ==="
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render branding/favicon.svg to a multi-resolution favicon.ico.
|
||||
|
||||
Source of truth is favicon.svg (the Hanzo mark). The .ico is a committed build
|
||||
artifact so that apply-branding.sh can install it without needing a rasterizer
|
||||
in the Docker image. Re-run this whenever favicon.svg changes:
|
||||
|
||||
rsvg-convert must be on PATH (librsvg2-bin); Pillow provides ICO packing.
|
||||
python3 branding/make_favicon.py
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from PIL import Image
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SVG = os.path.join(HERE, "favicon.svg")
|
||||
ICO = os.path.join(HERE, "favicon.ico")
|
||||
SIZES = [16, 32, 48, 64, 128, 256]
|
||||
|
||||
|
||||
def render_png(size: int, path: str) -> None:
|
||||
subprocess.run(
|
||||
["rsvg-convert", "-w", str(size), "-h", str(size), SVG, "-o", path],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
frames = []
|
||||
for size in SIZES:
|
||||
png = os.path.join(tmp, f"favicon-{size}.png")
|
||||
render_png(size, png)
|
||||
frames.append(Image.open(png).convert("RGBA"))
|
||||
base = frames[-1]
|
||||
base.save(ICO, format="ICO", sizes=[(s, s) for s in SIZES])
|
||||
print(f"Wrote {ICO} ({os.path.getsize(ICO)} bytes) sizes={SIZES}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# Hanzo Engine nodes
|
||||
|
||||
Custom nodes that call a local **Hanzo Engine** from a Hanzo Studio graph.
|
||||
Hanzo Engine is the native-Rust, all-modality inference and training backend
|
||||
(the training loop lives engine-side); these nodes talk to its
|
||||
OpenAI-compatible HTTP API.
|
||||
|
||||
## Configuration
|
||||
|
||||
Set the engine base URL via environment variable (default shown):
|
||||
|
||||
```bash
|
||||
export HANZO_ENGINE_URL=http://127.0.0.1:1234/v1
|
||||
```
|
||||
|
||||
Connection or protocol failures surface as a clear error on the failing node —
|
||||
the server does not crash.
|
||||
|
||||
## Nodes (category: `Hanzo/Engine`)
|
||||
|
||||
| Node | Route | In → Out |
|
||||
|------|-------|----------|
|
||||
| **Hanzo Chat (Engine)** | `POST /v1/chat/completions` | model + prompt + system → `text` (STRING) |
|
||||
| **Hanzo Image Gen (Engine)** | `POST /v1/images/generations` | prompt + size + steps → `IMAGE` |
|
||||
| **Hanzo Vision Caption (Engine)** | `POST /v1/chat/completions` (vision) | `IMAGE` + prompt → `caption` (STRING) |
|
||||
| **Hanzo Save Text** | — | `text` (STRING) → writes `.txt` to `output/` |
|
||||
|
||||
- **Hanzo Chat** — prompt engineering and caption/text chains inside a graph.
|
||||
- **Hanzo Image Gen** — decodes the engine's `b64_json` (or `url`) response into a
|
||||
Hanzo Studio `IMAGE` tensor. Requests `n` images at the chosen `size`.
|
||||
- **Hanzo Vision Caption** — encodes the input image as an OpenAI `image_url`
|
||||
data URL and asks a vision model (e.g. Qwen3-VL) to caption it. Use for
|
||||
LoRA/dataset prep.
|
||||
- **Hanzo Save Text** — writes captions to `output/` as `.txt` (image/caption
|
||||
pairs) and previews them in the node. Completes the caption-dataset loop.
|
||||
|
||||
## Requirements
|
||||
|
||||
Uses only libraries already present in Hanzo Studio: `requests`, `Pillow`,
|
||||
`numpy`, `torch`. No extra install.
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Hanzo Engine node pack for Hanzo Studio."""
|
||||
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
@@ -0,0 +1,264 @@
|
||||
"""Hanzo Engine nodes for Hanzo Studio.
|
||||
|
||||
These nodes call a local Hanzo Engine — the native-Rust, all-modality inference
|
||||
and training backend — over its OpenAI-compatible HTTP API. The base URL comes
|
||||
from the HANZO_ENGINE_URL environment variable (default http://127.0.0.1:1234/v1).
|
||||
|
||||
Connection and protocol errors surface as clear node errors (raised
|
||||
RuntimeError), which Hanzo Studio reports on the failing node rather than
|
||||
crashing the server.
|
||||
"""
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import requests
|
||||
import torch
|
||||
from PIL import Image
|
||||
|
||||
import folder_paths
|
||||
|
||||
DEFAULT_BASE_URL = "http://127.0.0.1:1234/v1"
|
||||
|
||||
|
||||
def _base_url() -> str:
|
||||
return os.environ.get("HANZO_ENGINE_URL", DEFAULT_BASE_URL).rstrip("/")
|
||||
|
||||
|
||||
def _post(path: str, payload: dict, timeout: int = 120) -> dict:
|
||||
url = f"{_base_url()}{path}"
|
||||
try:
|
||||
resp = requests.post(url, json=payload, timeout=timeout)
|
||||
except requests.exceptions.RequestException as exc:
|
||||
raise RuntimeError(
|
||||
f"Hanzo Engine request to {url} failed: {exc}. Is the engine running? "
|
||||
f"Set HANZO_ENGINE_URL (default {DEFAULT_BASE_URL})."
|
||||
) from exc
|
||||
if resp.status_code != 200:
|
||||
raise RuntimeError(
|
||||
f"Hanzo Engine {url} returned HTTP {resp.status_code}: {resp.text[:500]}"
|
||||
)
|
||||
try:
|
||||
return resp.json()
|
||||
except ValueError as exc:
|
||||
raise RuntimeError(
|
||||
f"Hanzo Engine {url} returned a non-JSON response: {resp.text[:200]}"
|
||||
) from exc
|
||||
|
||||
|
||||
def _get_bytes(url: str, timeout: int = 120) -> bytes:
|
||||
try:
|
||||
resp = requests.get(url, timeout=timeout)
|
||||
except requests.exceptions.RequestException as exc:
|
||||
raise RuntimeError(f"Hanzo Engine image fetch from {url} failed: {exc}.") from exc
|
||||
if resp.status_code != 200:
|
||||
raise RuntimeError(f"Hanzo Engine image fetch {url} returned HTTP {resp.status_code}.")
|
||||
return resp.content
|
||||
|
||||
|
||||
def _extract_message_text(data: dict) -> str:
|
||||
try:
|
||||
content = (data["choices"][0].get("message") or {}).get("content")
|
||||
except (KeyError, IndexError, TypeError) as exc:
|
||||
raise RuntimeError(
|
||||
f"Unexpected Hanzo Engine response shape: {json.dumps(data)[:300]}"
|
||||
) from exc
|
||||
if isinstance(content, list): # OpenAI structured content parts
|
||||
parts = [p.get("text", "") for p in content if isinstance(p, dict) and p.get("type") == "text"]
|
||||
return "".join(parts).strip()
|
||||
return (content or "").strip()
|
||||
|
||||
|
||||
def _tensor_to_pil(image: torch.Tensor) -> Image.Image:
|
||||
if image.dim() == 4: # [B,H,W,C] -> first frame
|
||||
image = image[0]
|
||||
arr = (image.cpu().numpy() * 255.0).clip(0, 255).astype(np.uint8)
|
||||
return Image.fromarray(arr)
|
||||
|
||||
|
||||
def _pil_to_tensor(img: Image.Image) -> torch.Tensor:
|
||||
arr = np.array(img.convert("RGB")).astype(np.float32) / 255.0
|
||||
return torch.from_numpy(arr)[None,] # [1,H,W,C]
|
||||
|
||||
|
||||
def _pil_to_data_url(img: Image.Image) -> str:
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format="PNG")
|
||||
b64 = base64.b64encode(buf.getvalue()).decode("ascii")
|
||||
return f"data:image/png;base64,{b64}"
|
||||
|
||||
|
||||
def _decode_image_item(item: dict) -> Image.Image:
|
||||
b64 = item.get("b64_json")
|
||||
if b64:
|
||||
return Image.open(io.BytesIO(base64.b64decode(b64))).convert("RGB")
|
||||
url = item.get("url")
|
||||
if url:
|
||||
if url.startswith("data:"):
|
||||
raw = base64.b64decode(url.split(",", 1)[1])
|
||||
else:
|
||||
raw = _get_bytes(url)
|
||||
return Image.open(io.BytesIO(raw)).convert("RGB")
|
||||
raise RuntimeError("Hanzo Engine image item has neither 'b64_json' nor 'url'.")
|
||||
|
||||
|
||||
class HanzoChat:
|
||||
"""Text chat via Hanzo Engine /v1/chat/completions. Use for prompt
|
||||
engineering, caption cleanup, and text chains inside a graph."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"model": ("STRING", {"default": "default"}),
|
||||
"prompt": ("STRING", {"multiline": True, "default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"system": ("STRING", {"multiline": True, "default": ""}),
|
||||
"temperature": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 2.0, "step": 0.05}),
|
||||
"max_tokens": ("INT", {"default": 512, "min": 1, "max": 32768}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "Hanzo/Engine"
|
||||
|
||||
def generate(self, model, prompt, system="", temperature=0.7, max_tokens=512):
|
||||
messages = []
|
||||
if system.strip():
|
||||
messages.append({"role": "system", "content": system})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
data = _post(
|
||||
"/chat/completions",
|
||||
{"model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens},
|
||||
)
|
||||
return (_extract_message_text(data),)
|
||||
|
||||
|
||||
class HanzoImageGen:
|
||||
"""Text-to-image via Hanzo Engine /v1/images/generations. Returns a
|
||||
Hanzo Studio IMAGE tensor."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"multiline": True, "default": ""}),
|
||||
"size": (["1024x1024", "1024x1536", "1536x1024", "768x768", "512x512"], {"default": "1024x1024"}),
|
||||
},
|
||||
"optional": {
|
||||
"model": ("STRING", {"default": "default"}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 150}),
|
||||
"n": ("INT", {"default": 1, "min": 1, "max": 8}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "Hanzo/Engine"
|
||||
|
||||
def generate(self, prompt, size, model="default", steps=20, n=1):
|
||||
data = _post(
|
||||
"/images/generations",
|
||||
{"model": model, "prompt": prompt, "size": size, "n": n, "steps": steps, "response_format": "b64_json"},
|
||||
timeout=600,
|
||||
)
|
||||
items = data.get("data") or []
|
||||
if not items:
|
||||
raise RuntimeError("Hanzo Engine /images/generations returned no image data.")
|
||||
tensors = [_pil_to_tensor(_decode_image_item(it)) for it in items]
|
||||
if len(tensors) == 1:
|
||||
batch = tensors[0]
|
||||
else:
|
||||
same = len({t.shape for t in tensors}) == 1
|
||||
batch = torch.cat(tensors, dim=0) if same else tensors[0]
|
||||
return (batch,)
|
||||
|
||||
|
||||
class HanzoVisionCaption:
|
||||
"""Caption an IMAGE via Hanzo Engine vision chat (OpenAI image_url format).
|
||||
Feeds LoRA/dataset prep loops; pair with a vision model such as Qwen3-VL."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"prompt": ("STRING", {"multiline": True, "default": "Describe this image in detail for a training caption."}),
|
||||
},
|
||||
"optional": {
|
||||
"model": ("STRING", {"default": "default"}),
|
||||
"system": ("STRING", {"multiline": True, "default": ""}),
|
||||
"max_tokens": ("INT", {"default": 512, "min": 1, "max": 32768}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("caption",)
|
||||
FUNCTION = "caption"
|
||||
CATEGORY = "Hanzo/Engine"
|
||||
|
||||
def caption(self, image, prompt, model="default", system="", max_tokens=512):
|
||||
data_url = _pil_to_data_url(_tensor_to_pil(image))
|
||||
messages = []
|
||||
if system.strip():
|
||||
messages.append({"role": "system", "content": system})
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": prompt},
|
||||
{"type": "image_url", "image_url": {"url": data_url}},
|
||||
],
|
||||
})
|
||||
data = _post("/chat/completions", {"model": model, "messages": messages, "max_tokens": max_tokens}, timeout=300)
|
||||
return (_extract_message_text(data),)
|
||||
|
||||
|
||||
class HanzoSaveText:
|
||||
"""Write a STRING to a .txt file in the output directory (image/caption
|
||||
pairs for dataset prep) and preview it in the node."""
|
||||
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_output_directory()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}),
|
||||
"filename_prefix": ("STRING", {"default": "caption"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Hanzo/Engine"
|
||||
|
||||
def save(self, text, filename_prefix="caption"):
|
||||
full_output_folder, filename, counter, _subfolder, _prefix = folder_paths.get_save_image_path(
|
||||
filename_prefix, self.output_dir
|
||||
)
|
||||
path = os.path.join(full_output_folder, f"{filename}_{counter:05}_.txt")
|
||||
with open(path, "w", encoding="utf-8") as fh:
|
||||
fh.write(text)
|
||||
return {"ui": {"text": [text]}}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"HanzoChat": HanzoChat,
|
||||
"HanzoImageGen": HanzoImageGen,
|
||||
"HanzoVisionCaption": HanzoVisionCaption,
|
||||
"HanzoSaveText": HanzoSaveText,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"HanzoChat": "Hanzo Chat (Engine)",
|
||||
"HanzoImageGen": "Hanzo Image Gen (Engine)",
|
||||
"HanzoVisionCaption": "Hanzo Vision Caption (Engine)",
|
||||
"HanzoSaveText": "Hanzo Save Text",
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Fashion / swimwear starter workflows
|
||||
|
||||
Three starter templates for a designer's swimwear workflow. They live in the
|
||||
Hanzo Studio workflow directory, so they appear in the **Workflows** sidebar
|
||||
(Workflows → Browse). You can also **drag any `.json` here onto the canvas** to
|
||||
load it.
|
||||
|
||||
Each workflow contains a **Note** node describing usage and the model files it
|
||||
needs. Some required files (Flux.2 text encoder / VAE, Qwen-Image-Edit) may not
|
||||
be downloaded yet — the graph is prewired so you only fill in filenames.
|
||||
|
||||
| File | Pipeline | Purpose |
|
||||
|------|----------|---------|
|
||||
| `fashion_product_shot.json` | FLUX.2 klein, text-to-image | Editorial swimwear product photography from a prompt |
|
||||
| `fashion_edit_garment.json` | Qwen-Image-Edit, image-to-image | Re-scene / edit a garment shot while keeping the garment identical |
|
||||
| `fashion_caption_dataset.json` | Hanzo Vision Caption → Save Text | Caption images into `.txt` pairs for LoRA dataset prep |
|
||||
|
||||
## Notes
|
||||
|
||||
- `fashion_product_shot.json` — `flux-2-klein-4b.safetensors` is already in
|
||||
`models/diffusion_models/`. Add a Flux.2 text encoder (`type=flux2` on the
|
||||
CLIPLoader) and a Flux.2 VAE, then queue. Output lands in `output/swimwear/`.
|
||||
- `fashion_edit_garment.json` — uses the native `TextEncodeQwenImageEdit` path.
|
||||
Load a product shot, describe the edit, keep the garment unchanged.
|
||||
- `fashion_caption_dataset.json` — uses the Hanzo Engine nodes
|
||||
(`custom_nodes/hanzo_engine/`). Set `HANZO_ENGINE_URL` and run a vision model
|
||||
such as Qwen3-VL. Image/caption pairs land in `output/swimwear/`.
|
||||
@@ -0,0 +1,169 @@
|
||||
{
|
||||
"id": "f6bb36b5-86c3-459e-bc6d-1a4bed73d8e3",
|
||||
"revision": 0,
|
||||
"last_node_id": 4,
|
||||
"last_link_id": 2,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
40,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
300,
|
||||
320
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [],
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "HanzoVisionCaption",
|
||||
"pos": [
|
||||
400,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
220
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "caption",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HanzoVisionCaption"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Write a concise training caption for this swimwear fashion photo: garment type, cut, color, pattern, fabric, pose, setting and lighting.",
|
||||
"default",
|
||||
"",
|
||||
384
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "HanzoSaveText",
|
||||
"pos": [
|
||||
840,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
320,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "HanzoSaveText"
|
||||
},
|
||||
"widgets_values": [
|
||||
"swimwear/caption"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
40,
|
||||
400
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
240
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"FASHION CAPTION DATASET \u2014 LoRA dataset prep\n\nLoadImage -> Hanzo Vision Caption -> Hanzo Save Text.\n\nThe Hanzo Vision Caption node calls the local Hanzo Engine\n(HANZO_ENGINE_URL, default http://127.0.0.1:1234/v1) with a vision\nmodel such as Qwen3-VL. Point LoadImage at each garment photo, run,\nand image/caption .txt pairs land in output/swimwear/. Batch by\nswapping LoadImage for a folder loader when preparing a full set."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
{
|
||||
"id": "28aa3f84-589e-45af-963c-f1ee6de49359",
|
||||
"revision": 0,
|
||||
"last_node_id": 11,
|
||||
"last_link_id": 13,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
40,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
300,
|
||||
320
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
4,
|
||||
5
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [],
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "CLIPLoader",
|
||||
"pos": [
|
||||
40,
|
||||
400
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"qwen-image-text-encoder.safetensors",
|
||||
"qwen_image",
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "UNETLoader",
|
||||
"pos": [
|
||||
40,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
7
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "UNETLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"qwen-image-edit.safetensors",
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "VAELoader",
|
||||
"pos": [
|
||||
40,
|
||||
720
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
3,
|
||||
6,
|
||||
12
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAELoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"qwen-image-vae.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "TextEncodeQwenImageEdit",
|
||||
"pos": [
|
||||
400,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "TextEncodeQwenImageEdit"
|
||||
},
|
||||
"widgets_values": [
|
||||
"change the background to a sunlit tropical beach at golden hour; keep the swimwear garment, its color, pattern and fit exactly the same; editorial fashion lighting"
|
||||
],
|
||||
"title": "Edit (positive)"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "TextEncodeQwenImageEdit",
|
||||
"pos": [
|
||||
400,
|
||||
280
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
9
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "TextEncodeQwenImageEdit"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
],
|
||||
"title": "Negative"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "VAEEncode",
|
||||
"pos": [
|
||||
400,
|
||||
440
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pixels",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
10
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEEncode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
860,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 7
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 8
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 9
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
11
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"fixed",
|
||||
20,
|
||||
2.5,
|
||||
"euler",
|
||||
"simple",
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1220,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 11
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 12
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
13
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
1420,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
380,
|
||||
380
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 13
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "SaveImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"swimwear/edit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
40,
|
||||
900
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
280
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"FASHION GARMENT / SCENE EDIT \u2014 Qwen-Image-Edit (image-to-image)\n\nRequired model files (place under models/):\n * diffusion_models/<qwen-image-edit unet>.safetensors\n * text_encoders/<qwen_image text encoder>.safetensors\n * vae/<qwen_image vae>.safetensors\n\nThis wires the native Qwen-Image-Edit path: TextEncodeQwenImageEdit\ncarries the reference image + edit instruction; the source image is\nVAE-encoded to seed the sampler. Load a product shot in LoadImage,\ndescribe the edit in the positive prompt, keep the garment identical."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
3,
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
4,
|
||||
1,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
5,
|
||||
1,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
4,
|
||||
0,
|
||||
7,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
8,
|
||||
5,
|
||||
0,
|
||||
8,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
9,
|
||||
6,
|
||||
0,
|
||||
8,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
10,
|
||||
7,
|
||||
0,
|
||||
8,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
11,
|
||||
8,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
12,
|
||||
4,
|
||||
0,
|
||||
9,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
13,
|
||||
9,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
{
|
||||
"id": "ade0e429-67bf-4e6d-adf4-88747094a690",
|
||||
"revision": 0,
|
||||
"last_node_id": 11,
|
||||
"last_link_id": 10,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "UNETLoader",
|
||||
"pos": [
|
||||
40,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "UNETLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"flux-2-klein-4b.safetensors",
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "CLIPLoader",
|
||||
"pos": [
|
||||
40,
|
||||
200
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"flux2-text-encoder.safetensors",
|
||||
"flux2",
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "VAELoader",
|
||||
"pos": [
|
||||
40,
|
||||
360
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
9
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAELoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"flux2-vae.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
360,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"professional swimwear product photography, single-piece designer bikini on a model, beach at golden hour, editorial fashion lighting, crisp detail, high-end catalog, 85mm, shallow depth of field"
|
||||
],
|
||||
"title": "Positive"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "FluxGuidance",
|
||||
"pos": [
|
||||
360,
|
||||
280
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "conditioning",
|
||||
"type": "CONDITIONING",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
5
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FluxGuidance"
|
||||
},
|
||||
"widgets_values": [
|
||||
3.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
360,
|
||||
400
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
6
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
],
|
||||
"title": "Negative"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "EmptyFlux2LatentImage",
|
||||
"pos": [
|
||||
360,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
7
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyFlux2LatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
1024,
|
||||
1536,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
800,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 4
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 5
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"fixed",
|
||||
24,
|
||||
1.0,
|
||||
"euler",
|
||||
"simple",
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1160,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 8
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
10
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
1360,
|
||||
40
|
||||
],
|
||||
"size": [
|
||||
380,
|
||||
380
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "SaveImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"swimwear/product"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
40,
|
||||
520
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
300
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"FASHION PRODUCT SHOT \u2014 FLUX.2 klein (text-to-image)\n\nRequired model files (place under models/):\n * diffusion_models/flux-2-klein-4b.safetensors (present)\n * text_encoders/<flux2 text encoder>.safetensors (NOT downloaded)\n * vae/<flux2 vae>.safetensors (NOT downloaded)\n\nSet the CLIPLoader (type=flux2) and VAELoader filenames once the\nencoder + VAE are downloaded, then queue. Preset prompt is tuned for\neditorial swimwear product photography."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
3,
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
1,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
5,
|
||||
5,
|
||||
0,
|
||||
8,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
6,
|
||||
6,
|
||||
0,
|
||||
8,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
7,
|
||||
7,
|
||||
0,
|
||||
8,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
8,
|
||||
8,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
9,
|
||||
3,
|
||||
0,
|
||||
9,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
10,
|
||||
9,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
Reference in New Issue
Block a user