""" Workflow1111 · a Diffusion Studio built entirely from `gr.Workflow` ================================================================== An Automatic1111-shaped image studio expressed as **one canvas graph** instead of a tabbed UI — 73 nodes across 11 pipelines, every one of them runnable on its own. python apps/05_workflow1111/app.py What's on the canvas -------------------- 1 txt2img prompt/style builders → FLUX.1-schnell → post-processing with the real control surface: negative prompt, steps, CFG, seed, and size presets 2 Hires fix the txt2img result re-rendered through FLUX.1-Kontext 3 img2img upload an image, edit it by instruction 4 Prompt magic an LLM writes a prompt from a rough idea 5 Interrogate recover a prompt from an image (VLM) + classify it (ViT) 6 Detect & mask DETR boxes → annotated preview → inpainting mask 7 Prompt matrix four variants rendered in parallel into an X/Y grid 8 Extras local upscale, AuraSR ×4, background removal 9 Annotators Canny / line art / sketch / luma-depth previews 10 PNG Info read generation parameters back out of a file 11 img2video that same PNG animated into a ~3s clip by Wan 2.2 I2V A14B on Inference Providers, with a motion/camera prompt builder and real length/steps/guidance/seed/resolution Why it is shaped like this -------------------------- `gr.Workflow` raises if constructed inside a `gr.Blocks` context, so an app like this genuinely cannot be tabs — the graph *is* the UI. Each output ("subject") is independently runnable and is also published as an API endpoint, which is the closest analogue to A1111's tabs. Five design rules came out of probing gradio 6.22 / huggingface_hub 1.26 directly, and the app depends on all five (details in `nodes.py` and README): • A `model` node's ports get rewritten to the endpoint's canonical schema the moment a browser loads the graph — and the file is saved back that way. So anything needing a richer control surface than the schema (`txt2img`, `chat_llm`, `interrogate`, `img2video`) is an `fn` node calling `InferenceClient` itself. `build_workflow.py` refuses to build if a `model` node's ports ever diverge from its schema again. • `fn` nodes emit images as ``{"path": , "url": }``. The REST endpoint's `gr.Image` component needs a real file (a bare ``data:`` URI is read as a *filename*); the canvas and a chained `model` node need the URI. Both keys, one value. • Video is the exception to that: `_emit_video` returns ``{"path", "url": "/gradio_api/file=…", "is_file": True}`` — gradio's own `_save_tmp` shape — because an mp4 is megabytes and base64-inlining one into the graph value would bloat every canvas update carrying it. • A `model` image output is never wired straight into another `model`'s image port; `prep_image` sits between them (gradio would otherwise hand the provider an unresolvable ``/gradio_api/file=`` path). • `space` nodes only ever take an *uploaded* image. 22 of the 32 `fn` nodes are pure local Pillow/numpy — no token, no quota, no network — so most of the app keeps working even when a provider is having a bad day. 14 nodes in total leave the machine. Setup ----- hf auth login # or: set HF_TOKEN=hf_xxx pip install -r apps/05_workflow1111/requirements.txt Tests (no network, ~2s): python apps/05_workflow1111/test_nodes.py Live pipelines (hits HF): python apps/05_workflow1111/test_pipelines.py REST endpoints: python apps/05_workflow1111/test_api.py Regenerate the graph: python apps/05_workflow1111/build_workflow.py Deploy to a Space: python apps/05_workflow1111/deploy_space.py --push """ import os import sys import gradio as gr sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import nodes # noqa: E402 # API/MCP callers authenticate per request with an `X-HF-Token` header (see # `nodes._hf_token`). The four `model`/`space` nodes are run by gradio itself, # whose `gradio.workflow._resolve_token` only knows OAuth sessions and the local # write-token — so extend it here to fall back to the caller's header token. import gradio.workflow as _gw # noqa: E402 _orig_resolve_token = _gw._resolve_token def _resolve_token_with_header(data, idx, token, request=None): return _orig_resolve_token(data, idx, token, request) or nodes._caller_hf_token(request) _gw._resolve_token = _resolve_token_with_header HERE = os.path.dirname(os.path.abspath(__file__)) WORKFLOW = os.path.join(HERE, "workflow.json") if not os.path.exists(WORKFLOW): raise SystemExit( f"{WORKFLOW} is missing.\n" "Generate it with: python apps/05_workflow1111/build_workflow.py" ) demo = gr.Workflow(WORKFLOW, bind=nodes.BIND) if __name__ == "__main__": from huggingface_hub import get_token if not (get_token() or os.environ.get("HF_TOKEN")): print( "\n ⚠ No Hugging Face token found.\n" " The 19 local `fn` nodes (post-processing, annotators, masks,\n" " contact sheet, PNG info) work regardless, but every `model`\n" " and `space` node will fail until you run `hf auth login`,\n" " set HF_TOKEN, or sign in from inside the app.\n" ) demo.launch(mcp_server=True)