mrfakename commited on
Commit
2b422b2
·
1 Parent(s): ad4a177

gradio workflow (#89)

Browse files

- Convert app to gr.Workflow calling Z-Image-Turbo via HF Inference API (639e5b2674a6fe682cefac3e005abd5dcdb70404)
- Pin gradio 6.20.0 and scope OAuth to inference-api only (3953d49ac5e885a01cf75ed42b1d1b0f8927b74e)
- Use Spaces mode: workflow calls mrfakename/Z-Image-Turbo Space (2546607bb3b8fc5b737fa6495b34f3213c157adb)
- Single Space: workflow + @spaces.GPU fn bound via gr.Workflow (8859027f9fdd1583b29818a31734bf0b62cb6291)
- Drop inference-api OAuth scope (1fadc382061acddf691dab33e1171adaf8b51bb7)

Files changed (4) hide show
  1. README.md +73 -2
  2. app.py +56 -229
  3. requirements.txt +3 -2
  4. workflow.json +154 -0
README.md CHANGED
@@ -4,9 +4,80 @@ emoji: 🖼️
4
  colorFrom: yellow
5
  colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 6.0.1
8
  app_file: app.py
9
  pinned: true
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  colorFrom: yellow
5
  colorTo: yellow
6
  sdk: gradio
7
+ sdk_version: 6.20.0
8
  app_file: app.py
9
  pinned: true
10
+ hf_oauth: true
11
  ---
12
 
13
+ # Z-Image-Turbo (Gradio Workflow single Space, ZeroGPU)
14
+
15
+ A visual, node-based image-generation app built with `gr.Workflow`. The
16
+ workflow frontend and the ZeroGPU-powered generation function live in the
17
+ **same Space**: the canvas calls a bound `@spaces.GPU` Python function via a
18
+ `fn` operator node, so there is no cross-Space round-trip.
19
+
20
+ ## How it works
21
+
22
+ The workflow is defined in [`workflow.json`](./workflow.json):
23
+
24
+ | Node | Role | Type |
25
+ |---|---|---|
26
+ | Prompt · Height · Width · Inference Steps · Seed · Randomize Seed | references (inputs) | text / number / boolean |
27
+ | `generate_image` | operator — `kind: "fn"`, bound to `@spaces.GPU generate_image` in `app.py` | calls the local zero-GPU pipeline |
28
+ | Output Image · Seed Used | subjects (outputs) | image / number |
29
+
30
+ `app.py` loads the `Tongyi-MAI/Z-Image-Turbo` pipeline at startup and binds it:
31
+
32
+ ```python
33
+ @spaces.GPU
34
+ def generate_image(prompt, height, width, num_inference_steps, seed, randomize_seed):
35
+ ...
36
+ return image, seed_used
37
+
38
+ gr.Workflow(graph="workflow.json", bind={"generate_image": generate_image}).launch()
39
+ ```
40
+
41
+ When the canvas hits **Run**, the executor's `fn` branch routes the call to
42
+ the local `generate_image`, and `@spaces.GPU` allocates a ZeroGPU worker for
43
+ that invocation.
44
+
45
+ Edit the topology on the canvas (drag nodes, change the prompt, rewire) and
46
+ hit **Run**. Changes are saved back to `workflow.json`.
47
+
48
+ ## Running locally
49
+
50
+ ```bash
51
+ pip install -r requirements.txt
52
+ python app.py
53
+ ```
54
+
55
+ GPU access through `@spaces.GPU` only works on Hugging Face Spaces — locally
56
+ the decorated call will raise. Otherwise the workflow frontend, node wiring
57
+ and grading still work.
58
+
59
+ Open the **write-access link** printed at launch to edit the workflow; plain
60
+ local/share URLs open it read-only.
61
+
62
+ ## Deploying
63
+
64
+ ```bash
65
+ gradio deploy
66
+ ```
67
+
68
+ `hf_oauth: true` is set so that, on a Space, each visitor signs in with their
69
+ own HF account and ZeroGPU allocations run under their own token. The Space
70
+ owner can edit and save the workflow; visitors get a read-only view and can
71
+ run the pipeline.
72
+
73
+ ## API access
74
+
75
+ Every Workflow app is a Gradio app, so it exposes a REST endpoint per output
76
+ (subject) node — e.g. `/output_image` and `/seed_used`:
77
+
78
+ ```python
79
+ from gradio_client import Client
80
+
81
+ client = Client("your-username/your-space")
82
+ client.view_api() # list endpoints and their parameters
83
+ ```
app.py CHANGED
@@ -1,9 +1,13 @@
1
- import torch
 
 
2
  import spaces
 
3
  import gradio as gr
4
  from diffusers import DiffusionPipeline
5
 
6
- # Load the pipeline once at startup
 
7
  print("Loading Z-Image-Turbo pipeline...")
8
  pipe = DiffusionPipeline.from_pretrained(
9
  "Tongyi-MAI/Z-Image-Turbo",
@@ -11,19 +15,50 @@ pipe = DiffusionPipeline.from_pretrained(
11
  low_cpu_mem_usage=False,
12
  )
13
  pipe.to("cuda")
 
14
 
15
- # ======== AoTI compilation + FA3 ========
16
- # pipe.transformer.layers._repeated_blocks = ["ZImageTransformerBlock"]
17
- # spaces.aoti_blocks_load(pipe.transformer.layers, "zerogpu-aoti/Z-Image", variant="fa3")
18
 
19
- print("Pipeline loaded!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  @spaces.GPU
22
- def generate_image(prompt, height, width, num_inference_steps, seed, randomize_seed, progress=gr.Progress(track_tqdm=True)):
23
- """Generate an image from the given prompt."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  if randomize_seed:
25
  seed = torch.randint(0, 2**32 - 1, (1,)).item()
26
-
27
  generator = torch.Generator("cuda").manual_seed(int(seed))
28
  image = pipe(
29
  prompt=prompt,
@@ -33,228 +68,20 @@ def generate_image(prompt, height, width, num_inference_steps, seed, randomize_s
33
  guidance_scale=0.0,
34
  generator=generator,
35
  ).images[0]
36
-
37
- return image, seed
38
 
39
- # Example prompts
40
- examples = [
41
- ["Young Chinese woman in red Hanfu, intricate embroidery. Impeccable makeup, red floral forehead pattern. Elaborate high bun, golden phoenix headdress, red flowers, beads. Holds round folding fan with lady, trees, bird. Neon lightning-bolt lamp, bright yellow glow, above extended left palm. Soft-lit outdoor night background, silhouetted tiered pagoda, blurred colorful distant lights."],
42
- ["A majestic dragon soaring through clouds at sunset, scales shimmering with iridescent colors, detailed fantasy art style"],
43
- ["Cozy coffee shop interior, warm lighting, rain on windows, plants on shelves, vintage aesthetic, photorealistic"],
44
- ["Astronaut riding a horse on Mars, cinematic lighting, sci-fi concept art, highly detailed"],
45
- ["Portrait of a wise old wizard with a long white beard, holding a glowing crystal staff, magical forest background"],
46
- ]
47
 
48
- # Custom theme with modern aesthetics (Gradio 6)
49
- custom_theme = gr.themes.Soft(
50
- primary_hue="yellow",
51
- secondary_hue="amber",
52
- neutral_hue="slate",
53
- font=gr.themes.GoogleFont("Inter"),
54
- text_size="lg",
55
- spacing_size="md",
56
- radius_size="lg"
57
- ).set(
58
- button_primary_background_fill="*primary_500",
59
- button_primary_background_fill_hover="*primary_600",
60
- block_title_text_weight="600",
61
- )
62
 
63
- # Build the Gradio interface
64
- with gr.Blocks(fill_height=True) as demo:
65
- # Header
66
- gr.Markdown(
67
- """
68
- # 🎨 Z-Image-Turbo
69
- **Ultra-fast AI image generation** • Generate stunning images in just 8 steps
70
- """,
71
- elem_classes="header-text"
72
- )
73
-
74
- with gr.Row(equal_height=False):
75
- # Left column - Input controls
76
- with gr.Column(scale=1, min_width=320):
77
- prompt = gr.Textbox(
78
- label="✨ Your Prompt",
79
- placeholder="Describe the image you want to create...",
80
- lines=5,
81
- max_lines=10,
82
- autofocus=True,
83
- )
84
-
85
- with gr.Accordion("⚙️ Advanced Settings", open=False):
86
- with gr.Row():
87
- height = gr.Slider(
88
- minimum=512,
89
- maximum=2048,
90
- value=1024,
91
- step=64,
92
- label="Height",
93
- info="Image height in pixels"
94
- )
95
- width = gr.Slider(
96
- minimum=512,
97
- maximum=2048,
98
- value=1024,
99
- step=64,
100
- label="Width",
101
- info="Image width in pixels"
102
- )
103
-
104
- num_inference_steps = gr.Slider(
105
- minimum=1,
106
- maximum=20,
107
- value=9,
108
- step=1,
109
- label="Inference Steps",
110
- info="9 steps = 8 DiT forwards (recommended)"
111
- )
112
-
113
- with gr.Row():
114
- randomize_seed = gr.Checkbox(
115
- label="🎲 Random Seed",
116
- value=True,
117
- )
118
- seed = gr.Number(
119
- label="Seed",
120
- value=42,
121
- precision=0,
122
- visible=False,
123
- )
124
-
125
- def toggle_seed(randomize):
126
- return gr.Number(visible=not randomize)
127
-
128
- randomize_seed.change(
129
- toggle_seed,
130
- inputs=[randomize_seed],
131
- outputs=[seed]
132
- )
133
-
134
- generate_btn = gr.Button(
135
- "🚀 Generate Image",
136
- variant="primary",
137
- size="lg",
138
- scale=1
139
- )
140
-
141
- # Example prompts
142
- gr.Examples(
143
- examples=examples,
144
- inputs=[prompt],
145
- label="💡 Try these prompts",
146
- examples_per_page=5,
147
- )
148
-
149
- # Right column - Output
150
- with gr.Column(scale=1, min_width=320):
151
- output_image = gr.Image(
152
- label="Generated Image",
153
- type="pil",
154
- format="png",
155
- show_label=False,
156
- height=600,
157
- buttons=["download", "share"],
158
- )
159
-
160
- used_seed = gr.Number(
161
- label="🎲 Seed Used",
162
- interactive=False,
163
- container=True,
164
- )
165
-
166
- # Footer credits
167
- gr.Markdown(
168
- """
169
- ---
170
- <div style="text-align: center; opacity: 0.7; font-size: 0.9em; margin-top: 1rem;">
171
- <strong>Model:</strong> <a href="https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" target="_blank">Tongyi-MAI/Z-Image-Turbo</a> (Apache 2.0 License) •
172
- <strong>Demo by:</strong> <a href="https://x.com/realmrfakename" target="_blank">@mrfakename</a> •
173
- <strong>Redesign by:</strong> AnyCoder •
174
- <strong>Optimizations:</strong> <a href="https://huggingface.co/multimodalart" target="_blank">@multimodalart</a> (FA3 + AoTI)
175
- </div>
176
- """,
177
- elem_classes="footer-text"
178
- )
179
-
180
- # Connect the generate button
181
- generate_btn.click(
182
- fn=generate_image,
183
- inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
184
- outputs=[output_image, used_seed],
185
- )
186
-
187
- # Also allow generating by pressing Enter in the prompt box
188
- prompt.submit(
189
- fn=generate_image,
190
- inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
191
- outputs=[output_image, used_seed],
192
- )
193
 
194
  if __name__ == "__main__":
195
- demo.launch(
196
- theme=custom_theme,
197
- css="""
198
- .header-text h1 {
199
- font-size: 2.5rem !important;
200
- font-weight: 700 !important;
201
- margin-bottom: 0.5rem !important;
202
- background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
203
- -webkit-background-clip: text;
204
- -webkit-text-fill-color: transparent;
205
- background-clip: text;
206
- }
207
-
208
- .header-text p {
209
- font-size: 1.1rem !important;
210
- color: #64748b !important;
211
- margin-top: 0 !important;
212
- }
213
-
214
- .footer-text {
215
- padding: 1rem 0;
216
- }
217
-
218
- .footer-text a {
219
- color: #f59e0b !important;
220
- text-decoration: none !important;
221
- font-weight: 500;
222
- }
223
-
224
- .footer-text a:hover {
225
- text-decoration: underline !important;
226
- }
227
-
228
- /* Mobile optimizations */
229
- @media (max-width: 768px) {
230
- .header-text h1 {
231
- font-size: 1.8rem !important;
232
- }
233
-
234
- .header-text p {
235
- font-size: 1rem !important;
236
- }
237
- }
238
-
239
- /* Smooth transitions */
240
- button, .gr-button {
241
- transition: all 0.2s ease !important;
242
- }
243
-
244
- button:hover, .gr-button:hover {
245
- transform: translateY(-1px);
246
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
247
- }
248
-
249
- /* Better spacing */
250
- .gradio-container {
251
- max-width: 1400px !important;
252
- margin: 0 auto !important;
253
- }
254
- """,
255
- footer_links=[
256
- "api",
257
- "gradio"
258
- ],
259
- mcp_server=True
260
- )
 
1
+ import os
2
+ import tempfile
3
+
4
  import spaces
5
+ import torch
6
  import gradio as gr
7
  from diffusers import DiffusionPipeline
8
 
9
+ # Load the pipeline once at startup. The Space is a ZeroGPU space, so the
10
+ # model weights stay resident and `@spaces.GPU` allocates a worker per call.
11
  print("Loading Z-Image-Turbo pipeline...")
12
  pipe = DiffusionPipeline.from_pretrained(
13
  "Tongyi-MAI/Z-Image-Turbo",
 
15
  low_cpu_mem_usage=False,
16
  )
17
  pipe.to("cuda")
18
+ print("Pipeline loaded!")
19
 
 
 
 
20
 
21
+ def _save_image(image) -> dict:
22
+ """Mirror of `gradio.workflow._save_tmp`: serialize a PIL.Image as a JSON
23
+ pointer the canvas can render. `Workflow.launch()` already adds the
24
+ tempdir to `allowed_paths`, so the /gradio_api/file=… URL resolves."""
25
+ path = os.path.join(
26
+ tempfile.gettempdir(), f"zimage_{os.urandom(8).hex()}.png"
27
+ )
28
+ image.save(path)
29
+ return {
30
+ "path": path,
31
+ "url": f"/gradio_api/file={path}",
32
+ "orig_name": "zimage.png",
33
+ "mime_type": "image/png",
34
+ }
35
+
36
 
37
  @spaces.GPU
38
+ def generate_image(
39
+ prompt: str,
40
+ height: int,
41
+ width: int,
42
+ num_inference_steps: int,
43
+ seed: int,
44
+ randomize_seed: bool,
45
+ ):
46
+ """Generate an image from a prompt using Z-Image-Turbo on ZeroGPU.
47
+
48
+ Bound to the workflow canvas as a `fn` operator node — the workflow
49
+ calls this Python function directly server-side, so the entire pipeline
50
+ (frontend + ZeroGPU) lives in a single Space.
51
+
52
+ Returns (image_dict, seed_used). The image is serialized to a /gradio_api
53
+ file URL so JSON serialization across the fn bridge succeeds; the executor's
54
+ `fromGradioOutput` turns the dict back into an image port value.
55
+ """
56
+ if not prompt or not prompt.strip():
57
+ raise gr.Error("Please enter a prompt.")
58
+
59
  if randomize_seed:
60
  seed = torch.randint(0, 2**32 - 1, (1,)).item()
61
+
62
  generator = torch.Generator("cuda").manual_seed(int(seed))
63
  image = pipe(
64
  prompt=prompt,
 
68
  guidance_scale=0.0,
69
  generator=generator,
70
  ).images[0]
 
 
71
 
72
+ return _save_image(image), int(seed)
 
 
 
 
 
 
 
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ # The workflow (workflow.json) wires this function as a `fn` operator:
76
+ # Prompt, Height, Width, Inference Steps, Seed, Randomize Seed ─▶
77
+ # generate_image (fn operator, kind="fn") ─▶ Output Image, Seed Used
78
+ #
79
+ # On a Space with `hf_oauth: true`, visiting the canvas runs this function
80
+ # under a ZeroGPU worker using each visitor's own HF token.
81
+ demo = gr.Workflow(
82
+ graph="workflow.json",
83
+ bind={"generate_image": generate_image},
84
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
  if __name__ == "__main__":
87
+ demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,5 +1,6 @@
1
- gradio
2
  git+https://github.com/huggingface/diffusers
3
  transformers
4
  kernels
5
- gradio[mcp]
 
 
1
+ gradio>=6.20.0
2
  git+https://github.com/huggingface/diffusers
3
  transformers
4
  kernels
5
+ spaces
6
+ gradio[mcp]
workflow.json ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": "2",
3
+ "name": "Z-Image-Turbo",
4
+ "description": "Ultra-fast AI image generation with Z-Image-Turbo on ZeroGPU, driven by a gr.Workflow fn-bound @spaces.GPU function. Single Space: the workflow frontend and the GPU worker share one process.",
5
+ "runtime": { "default": "client" },
6
+ "view": { "default": "canvas" },
7
+ "references": [
8
+ {
9
+ "id": "ref_prompt",
10
+ "label": "Prompt",
11
+ "role": "reference",
12
+ "asset_type": "text",
13
+ "inputs": [{ "id": "in", "label": "Prompt", "type": "text" }],
14
+ "outputs": [{ "id": "out", "label": "Prompt", "type": "text" }],
15
+ "x": 60,
16
+ "y": 160,
17
+ "width": 240,
18
+ "height": 120,
19
+ "data": {
20
+ "out": "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k"
21
+ }
22
+ },
23
+ {
24
+ "id": "ref_height",
25
+ "label": "Height",
26
+ "role": "reference",
27
+ "asset_type": "number",
28
+ "inputs": [{ "id": "in", "label": "Height", "type": "number" }],
29
+ "outputs": [{ "id": "out", "label": "Height", "type": "number" }],
30
+ "x": 60,
31
+ "y": 300,
32
+ "width": 200,
33
+ "height": 90,
34
+ "data": { "out": 1024 }
35
+ },
36
+ {
37
+ "id": "ref_width",
38
+ "label": "Width",
39
+ "role": "reference",
40
+ "asset_type": "number",
41
+ "inputs": [{ "id": "in", "label": "Width", "type": "number" }],
42
+ "outputs": [{ "id": "out", "label": "Width", "type": "number" }],
43
+ "x": 60,
44
+ "y": 410,
45
+ "width": 200,
46
+ "height": 90,
47
+ "data": { "out": 1024 }
48
+ },
49
+ {
50
+ "id": "ref_steps",
51
+ "label": "Inference Steps",
52
+ "role": "reference",
53
+ "asset_type": "number",
54
+ "inputs": [{ "id": "in", "label": "Steps", "type": "number" }],
55
+ "outputs": [{ "id": "out", "label": "Steps", "type": "number" }],
56
+ "x": 60,
57
+ "y": 520,
58
+ "width": 200,
59
+ "height": 90,
60
+ "data": { "out": 9 }
61
+ },
62
+ {
63
+ "id": "ref_seed",
64
+ "label": "Seed",
65
+ "role": "reference",
66
+ "asset_type": "number",
67
+ "inputs": [{ "id": "in", "label": "Seed", "type": "number" }],
68
+ "outputs": [{ "id": "out", "label": "Seed", "type": "number" }],
69
+ "x": 60,
70
+ "y": 630,
71
+ "width": 200,
72
+ "height": 90,
73
+ "data": { "out": 42 }
74
+ },
75
+ {
76
+ "id": "ref_randomize",
77
+ "label": "Randomize Seed",
78
+ "role": "reference",
79
+ "asset_type": "boolean",
80
+ "inputs": [{ "id": "in", "label": "Randomize", "type": "boolean" }],
81
+ "outputs": [{ "id": "out", "label": "Randomize", "type": "boolean" }],
82
+ "x": 60,
83
+ "y": 740,
84
+ "width": 200,
85
+ "height": 90,
86
+ "data": { "out": true }
87
+ }
88
+ ],
89
+ "operators": [
90
+ {
91
+ "id": "op_generate",
92
+ "label": "generate_image",
93
+ "role": "operator",
94
+ "kind": "fn",
95
+ "source": "fn",
96
+ "fn": "generate_image",
97
+ "inputs": [
98
+ { "id": "in_0", "label": "prompt", "type": "text", "required": true },
99
+ { "id": "in_1", "label": "height", "type": "number" },
100
+ { "id": "in_2", "label": "width", "type": "number" },
101
+ { "id": "in_3", "label": "num_inference_steps", "type": "number" },
102
+ { "id": "in_4", "label": "seed", "type": "number" },
103
+ { "id": "in_5", "label": "randomize_seed", "type": "boolean" }
104
+ ],
105
+ "outputs": [
106
+ { "id": "out_0", "label": "image", "type": "image", "output_index": 0 },
107
+ { "id": "out_1", "label": "seed_used", "type": "number", "output_index": 1 }
108
+ ],
109
+ "x": 420,
110
+ "y": 320,
111
+ "width": 280,
112
+ "height": 220,
113
+ "data": {}
114
+ }
115
+ ],
116
+ "subjects": [
117
+ {
118
+ "id": "sub_image",
119
+ "label": "Output Image",
120
+ "role": "subject",
121
+ "asset_type": "image",
122
+ "inputs": [{ "id": "in", "label": "Image", "type": "image" }],
123
+ "outputs": [{ "id": "out", "label": "Image", "type": "image" }],
124
+ "x": 820,
125
+ "y": 280,
126
+ "width": 240,
127
+ "height": 130,
128
+ "data": {}
129
+ },
130
+ {
131
+ "id": "sub_seed",
132
+ "label": "Seed Used",
133
+ "role": "subject",
134
+ "asset_type": "number",
135
+ "inputs": [{ "id": "in", "label": "Seed", "type": "number" }],
136
+ "outputs": [{ "id": "out", "label": "Seed", "type": "number" }],
137
+ "x": 820,
138
+ "y": 460,
139
+ "width": 240,
140
+ "height": 100,
141
+ "data": {}
142
+ }
143
+ ],
144
+ "edges": [
145
+ { "id": "e_prompt", "from_node_id": "ref_prompt", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "in_0", "type": "text" },
146
+ { "id": "e_height", "from_node_id": "ref_height", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "in_1", "type": "number" },
147
+ { "id": "e_width", "from_node_id": "ref_width", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "in_2", "type": "number" },
148
+ { "id": "e_steps", "from_node_id": "ref_steps", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "in_3", "type": "number" },
149
+ { "id": "e_seed", "from_node_id": "ref_seed", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "in_4", "type": "number" },
150
+ { "id": "e_randomize", "from_node_id": "ref_randomize", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "in_5", "type": "boolean" },
151
+ { "id": "e_image_out", "from_node_id": "op_generate", "from_port_id": "out_0", "to_node_id": "sub_image", "to_port_id": "in", "type": "image" },
152
+ { "id": "e_seed_out", "from_node_id": "op_generate", "from_port_id": "out_1", "to_node_id": "sub_seed", "to_port_id": "in", "type": "number" }
153
+ ]
154
+ }