akhaliq HF Staff commited on
Commit
ebb2166
·
1 Parent(s): 346a1d9

Add PE-T2I/PE-I2I prompt enhancement nodes and NCII guard for image-input requests

Browse files
Files changed (2) hide show
  1. app.py +134 -13
  2. workflow.json +118 -160
app.py CHANGED
@@ -1,30 +1,44 @@
1
  """Qwen-Image 2.1 — Gradio Workflow on ZeroGPU.
2
 
3
  A node-based canvas (gr.Workflow) exposing the diffusers QwenImage21Pipeline:
4
- - text_to_image: prompt -> image
5
- - edit_image: condition image + instruction -> edited image (chained after
6
- text_to_image, or fed from an uploaded image reference)
7
 
8
- The pipeline is placed on `cuda` at module level: outside @spaces.GPU functions
9
- PyTorch runs in CUDA emulation mode, and a real ZeroGPU is attached only while
10
- a decorated function executes.
 
 
 
 
 
 
 
 
 
 
11
  """
12
 
13
  import base64
 
 
14
  import os
15
  import urllib.parse
16
  import urllib.request
17
 
18
  import gradio as gr
19
- from gradio_client import utils as client_utils
20
- from gradio.utils import get_upload_folder
21
  import spaces
22
  import torch
 
 
 
23
  from PIL import Image
24
 
25
  from diffusers import QwenImage21Pipeline
26
 
27
  MODEL_ID = os.environ.get("QWEN_IMAGE_MODEL", "Qwen/Qwen-Image-2.1")
 
 
 
 
28
 
29
  # The model repo is private/gated. Per the Hub auth docs, the HF_TOKEN
30
  # environment variable is used implicitly for all Hub requests and takes
@@ -39,14 +53,46 @@ if not os.environ.get("HF_TOKEN"):
39
  pipe = QwenImage21Pipeline.from_pretrained(MODEL_ID, dtype=torch.bfloat16)
40
  pipe.to("cuda")
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  def _to_pil(image) -> Image.Image:
44
  """Accept whatever the canvas hands a bound function for an image port:
45
  a PIL image, a local path, a /gradio_api/file= reference, an http(s) or
46
  data: URL, or a file dict carrying any of those. Mirrors _file_ref in
47
  gradio.workflow — canvas file values carry only `url`, no `path`."""
48
- import io
49
-
50
  if isinstance(image, Image.Image):
51
  return image
52
  if isinstance(image, dict):
@@ -80,24 +126,99 @@ def _steps(value, default: int = 40) -> int:
80
  return default if value is None else int(value)
81
 
82
 
83
- @spaces.GPU(duration=120)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  def text_to_image(prompt: str, steps: int = 40) -> dict:
85
  """Generate an image from a text prompt with Qwen-Image 2.1."""
86
  image = pipe(prompt, num_inference_steps=_steps(steps)).images[0]
87
  return _save(image)
88
 
89
 
90
- @spaces.GPU(duration=120)
91
  def edit_image(image, instruction: str, steps: int = 40) -> dict:
92
  """Edit a condition image following an instruction (image-conditioned
93
  generation with Qwen-Image 2.1)."""
 
94
  edited = pipe(instruction, image=_to_pil(image), num_inference_steps=_steps(steps)).images[0]
95
  return _save(edited)
96
 
97
 
98
  demo = gr.Workflow(
99
  graph=os.path.join(os.path.dirname(__file__), "workflow.json"),
100
- bind={"text_to_image": text_to_image, "edit_image": edit_image},
 
 
 
 
 
101
  )
102
 
103
  if __name__ == "__main__":
 
1
  """Qwen-Image 2.1 — Gradio Workflow on ZeroGPU.
2
 
3
  A node-based canvas (gr.Workflow) exposing the diffusers QwenImage21Pipeline:
 
 
 
4
 
5
+ - enhance_prompt_t2i: prompt -> rewritten prompt (Qwen/Qwen-Image-2.1-PE-T2I)
6
+ - text_to_image: prompt -> image (Qwen/Qwen-Image-2.1)
7
+ - enhance_prompt_i2i: image + instruction -> rewritten instruction
8
+ (Qwen/Qwen-Image-2.1-PE-I2I)
9
+ - edit_image: condition image + instruction -> edited image
10
+
11
+ Image-input requests pass through an NCII prompt classifier
12
+ (hfmlsoc/ncii-guard-v02) before any rewriting or editing runs.
13
+
14
+ The diffusion pipeline stays resident on `cuda` (loaded at module level, as
15
+ ZeroGPU requires). The 9B prompt-rewriting models don't fit alongside it on a
16
+ 48GB card, so they live in CPU RAM and are moved onto the GPU only inside
17
+ their @spaces.GPU calls. The 270M guard classifier runs on CPU.
18
  """
19
 
20
  import base64
21
+ import io
22
+ import json
23
  import os
24
  import urllib.parse
25
  import urllib.request
26
 
27
  import gradio as gr
 
 
28
  import spaces
29
  import torch
30
+ from gradio_client import utils as client_utils
31
+ from gradio.utils import get_upload_folder
32
+ from huggingface_hub import hf_hub_download
33
  from PIL import Image
34
 
35
  from diffusers import QwenImage21Pipeline
36
 
37
  MODEL_ID = os.environ.get("QWEN_IMAGE_MODEL", "Qwen/Qwen-Image-2.1")
38
+ PE_T2I_ID = "Qwen/Qwen-Image-2.1-PE-T2I"
39
+ PE_I2I_ID = "Qwen/Qwen-Image-2.1-PE-I2I"
40
+ GUARD_ID = "hfmlsoc/ncii-guard-v02"
41
+ GUARD_THRESHOLD = 0.5
42
 
43
  # The model repo is private/gated. Per the Hub auth docs, the HF_TOKEN
44
  # environment variable is used implicitly for all Hub requests and takes
 
53
  pipe = QwenImage21Pipeline.from_pretrained(MODEL_ID, dtype=torch.bfloat16)
54
  pipe.to("cuda")
55
 
56
+ # --- Prompt rewriting models (resident in CPU RAM, moved to GPU per call) ---
57
+ from transformers import (
58
+ AutoModelForCausalLM,
59
+ AutoModelForImageTextToText,
60
+ AutoProcessor,
61
+ AutoTokenizer,
62
+ )
63
+
64
+ pe_t2i_tokenizer = AutoTokenizer.from_pretrained(PE_T2I_ID)
65
+ pe_t2i = AutoModelForCausalLM.from_pretrained(PE_T2I_ID, dtype=torch.bfloat16).eval()
66
+ pe_t2i_system = open(hf_hub_download(PE_T2I_ID, "system_prompt.txt")).read().strip()
67
+
68
+ pe_i2i_processor = AutoProcessor.from_pretrained(PE_I2I_ID)
69
+ pe_i2i = AutoModelForImageTextToText.from_pretrained(PE_I2I_ID, dtype=torch.bfloat16).eval()
70
+ pe_i2i_system = open(hf_hub_download(PE_I2I_ID, "system_prompt.txt")).read().strip()
71
+
72
+ # --- NCII guard for image-input requests (runs on CPU) ---
73
+ from transformers import AutoModelForSequenceClassification
74
+
75
+ guard_tokenizer = AutoTokenizer.from_pretrained(GUARD_ID) # carries the normalizer
76
+ guard = AutoModelForSequenceClassification.from_pretrained(GUARD_ID).eval()
77
+
78
+
79
+ def _check_prompt_guard(prompt: str) -> None:
80
+ """Reject image-editing prompts the NCII classifier flags. The error is
81
+ deliberately generic and does not say which classifier fired."""
82
+ batch = guard_tokenizer(
83
+ [prompt], truncation=True, max_length=256, padding=True, return_tensors="pt"
84
+ )
85
+ with torch.no_grad():
86
+ prob = torch.softmax(guard(**batch).logits.float(), dim=-1)[0, 1].item()
87
+ if prob >= GUARD_THRESHOLD:
88
+ raise gr.Error("prompt invalid based on our classifiers, try again")
89
+
90
 
91
  def _to_pil(image) -> Image.Image:
92
  """Accept whatever the canvas hands a bound function for an image port:
93
  a PIL image, a local path, a /gradio_api/file= reference, an http(s) or
94
  data: URL, or a file dict carrying any of those. Mirrors _file_ref in
95
  gradio.workflow — canvas file values carry only `url`, no `path`."""
 
 
96
  if isinstance(image, Image.Image):
97
  return image
98
  if isinstance(image, dict):
 
126
  return default if value is None else int(value)
127
 
128
 
129
+ def _parse_rewrite(gen: str, fallback: str) -> str:
130
+ """Split the PE model's <think> block from its JSON answer and return the
131
+ rewritten prompt. Fall back to the original prompt if parsing fails."""
132
+ _, _, answer = gen.partition("</think>")
133
+ try:
134
+ return json.loads(answer.strip()).get("rewritten_prompt") or fallback
135
+ except (json.JSONDecodeError, AttributeError):
136
+ return fallback
137
+
138
+
139
+ @spaces.GPU(duration=300)
140
+ def enhance_prompt_t2i(prompt: str) -> str:
141
+ """Rewrite a brief text-to-image request into a detailed English prompt
142
+ with Qwen-Image-2.1-PE-T2I."""
143
+ text = pe_t2i_tokenizer.apply_chat_template(
144
+ [{"role": "system", "content": pe_t2i_system},
145
+ {"role": "user", "content": prompt}],
146
+ tokenize=False, add_generation_prompt=True, enable_thinking=True,
147
+ )
148
+ pe_t2i.to("cuda")
149
+ try:
150
+ inputs = pe_t2i_tokenizer(text, return_tensors="pt").to("cuda")
151
+ with torch.no_grad():
152
+ out = pe_t2i.generate(
153
+ **inputs, max_new_tokens=4096,
154
+ do_sample=True, temperature=1.0, top_p=0.95, top_k=20,
155
+ )
156
+ gen = pe_t2i_tokenizer.decode(
157
+ out[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True
158
+ )
159
+ finally:
160
+ pe_t2i.to("cpu")
161
+ torch.cuda.empty_cache()
162
+ return _parse_rewrite(gen, prompt)
163
+
164
+
165
+ @spaces.GPU(duration=300)
166
+ def enhance_prompt_i2i(image, instruction: str) -> str:
167
+ """Rewrite an image-editing instruction against the condition image with
168
+ Qwen-Image-2.1-PE-I2I."""
169
+ _check_prompt_guard(instruction)
170
+ pil_image = _to_pil(image).convert("RGB")
171
+ messages = [
172
+ {"role": "system", "content": [{"type": "text", "text": pe_i2i_system}]},
173
+ {"role": "user", "content": [
174
+ {"type": "image", "image": pil_image},
175
+ {"type": "text", "text": instruction},
176
+ ]},
177
+ ]
178
+ pe_i2i.to("cuda")
179
+ try:
180
+ inputs = pe_i2i_processor.apply_chat_template(
181
+ messages, add_generation_prompt=True, tokenize=True,
182
+ return_dict=True, return_tensors="pt", enable_thinking=True,
183
+ ).to("cuda")
184
+ with torch.no_grad():
185
+ out = pe_i2i.generate(
186
+ **inputs, max_new_tokens=4096,
187
+ do_sample=True, temperature=1.0, top_p=0.95, top_k=20,
188
+ )
189
+ gen = pe_i2i_processor.tokenizer.decode(
190
+ out[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True
191
+ )
192
+ finally:
193
+ pe_i2i.to("cpu")
194
+ torch.cuda.empty_cache()
195
+ return _parse_rewrite(gen, instruction)
196
+
197
+
198
+ @spaces.GPU(duration=180)
199
  def text_to_image(prompt: str, steps: int = 40) -> dict:
200
  """Generate an image from a text prompt with Qwen-Image 2.1."""
201
  image = pipe(prompt, num_inference_steps=_steps(steps)).images[0]
202
  return _save(image)
203
 
204
 
205
+ @spaces.GPU(duration=180)
206
  def edit_image(image, instruction: str, steps: int = 40) -> dict:
207
  """Edit a condition image following an instruction (image-conditioned
208
  generation with Qwen-Image 2.1)."""
209
+ _check_prompt_guard(instruction)
210
  edited = pipe(instruction, image=_to_pil(image), num_inference_steps=_steps(steps)).images[0]
211
  return _save(edited)
212
 
213
 
214
  demo = gr.Workflow(
215
  graph=os.path.join(os.path.dirname(__file__), "workflow.json"),
216
+ bind={
217
+ "enhance_prompt_t2i": enhance_prompt_t2i,
218
+ "enhance_prompt_i2i": enhance_prompt_i2i,
219
+ "text_to_image": text_to_image,
220
+ "edit_image": edit_image,
221
+ },
222
  )
223
 
224
  if __name__ == "__main__":
workflow.json CHANGED
@@ -9,24 +9,9 @@
9
  "asset_type": "text",
10
  "x": 40,
11
  "y": 60,
12
- "inputs": [
13
- {
14
- "id": "in",
15
- "label": "Text",
16
- "type": "text"
17
- }
18
- ],
19
- "outputs": [
20
- {
21
- "id": "out",
22
- "label": "Text",
23
- "type": "text",
24
- "default_value": "A capybara wearing a wizard hat, oil painting"
25
- }
26
- ],
27
- "data": {
28
- "out": "A capybara wearing a wizard hat, oil painting"
29
- }
30
  },
31
  {
32
  "id": "ref_image",
@@ -35,21 +20,9 @@
35
  "asset_type": "image",
36
  "x": 40,
37
  "y": 380,
38
- "inputs": [
39
- {
40
- "id": "in",
41
- "label": "Image",
42
- "type": "image"
43
- }
44
- ],
45
- "outputs": [
46
- {
47
- "id": "out",
48
- "label": "Image",
49
- "type": "image"
50
- }
51
- ],
52
- "data": {}
53
  },
54
  {
55
  "id": "ref_instruction",
@@ -58,60 +31,53 @@
58
  "asset_type": "text",
59
  "x": 40,
60
  "y": 640,
61
- "inputs": [
62
- {
63
- "id": "in",
64
- "label": "Text",
65
- "type": "text"
66
- }
67
- ],
68
- "outputs": [
69
- {
70
- "id": "out",
71
- "label": "Text",
72
- "type": "text",
73
- "default_value": "Move it to a snowy mountain top"
74
- }
75
- ],
76
- "data": {
77
- "out": "Move it to a snowy mountain top"
78
- }
79
  }
80
  ],
81
  "operators": [
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  {
83
  "id": "op_t2i",
84
  "label": "Qwen-Image 2.1 Text-to-Image",
85
  "role": "operator",
86
  "kind": "fn",
87
  "fn": "text_to_image",
88
- "x": 420,
89
  "y": 60,
 
90
  "inputs": [
91
- {
92
- "id": "prompt",
93
- "label": "Prompt",
94
- "type": "text",
95
- "required": true
96
- },
97
- {
98
- "id": "steps",
99
- "label": "Steps",
100
- "type": "number",
101
- "default_value": 40
102
- }
103
  ],
104
- "outputs": [
105
- {
106
- "id": "out_0",
107
- "label": "Image",
108
- "type": "image",
109
- "output_index": 0
110
- }
 
 
 
 
 
 
111
  ],
112
- "data": {
113
- "steps": 40
114
- }
115
  },
116
  {
117
  "id": "op_edit",
@@ -119,127 +85,119 @@
119
  "role": "operator",
120
  "kind": "fn",
121
  "fn": "edit_image",
122
- "x": 820,
123
- "y": 320,
 
124
  "inputs": [
125
- {
126
- "id": "image",
127
- "label": "Image",
128
- "type": "image",
129
- "required": true
130
- },
131
- {
132
- "id": "instruction",
133
- "label": "Instruction",
134
- "type": "text",
135
- "required": true
136
- },
137
- {
138
- "id": "steps",
139
- "label": "Steps",
140
- "type": "number",
141
- "default_value": 40
142
- }
143
  ],
144
- "outputs": [
145
- {
146
- "id": "out_0",
147
- "label": "Image",
148
- "type": "image",
149
- "output_index": 0
150
- }
151
- ],
152
- "data": {
153
- "steps": 40
154
- }
155
  }
156
  ],
157
  "subjects": [
 
 
 
 
 
 
 
 
 
 
158
  {
159
  "id": "sub_generated",
160
  "label": "Generated Image",
161
  "role": "subject",
162
  "asset_type": "image",
163
- "x": 820,
164
  "y": 60,
165
- "inputs": [
166
- {
167
- "id": "in",
168
- "label": "Image",
169
- "type": "image"
170
- }
171
- ],
172
- "outputs": [
173
- {
174
- "id": "out",
175
- "label": "Image",
176
- "type": "image"
177
- }
178
- ]
179
  },
180
  {
181
  "id": "sub_edited",
182
  "label": "Edited Image",
183
  "role": "subject",
184
  "asset_type": "image",
185
- "x": 1220,
186
- "y": 320,
187
- "inputs": [
188
- {
189
- "id": "in",
190
- "label": "Image",
191
- "type": "image"
192
- }
193
- ],
194
- "outputs": [
195
- {
196
- "id": "out",
197
- "label": "Image",
198
- "type": "image"
199
- }
200
- ]
201
  }
202
  ],
203
  "edges": [
204
  {
205
  "id": "e1",
206
- "from_node_id": "ref_prompt",
207
- "from_port_id": "out",
208
- "to_node_id": "op_t2i",
209
- "to_port_id": "prompt",
210
  "type": "text"
211
  },
212
  {
213
  "id": "e2",
214
- "from_node_id": "op_t2i",
215
- "from_port_id": "out_0",
216
- "to_node_id": "sub_generated",
217
- "to_port_id": "in",
218
- "type": "image"
219
  },
220
  {
221
- "id": "e4",
222
- "from_node_id": "ref_instruction",
223
- "from_port_id": "out",
224
- "to_node_id": "op_edit",
225
- "to_port_id": "instruction",
226
  "type": "text"
227
  },
 
 
 
 
 
 
228
  {
229
  "id": "e5",
230
- "from_node_id": "op_edit",
231
- "from_port_id": "out_0",
232
- "to_node_id": "sub_edited",
233
- "to_port_id": "in",
234
  "type": "image"
235
  },
236
  {
237
- "id": "e3",
238
- "from_node_id": "ref_image",
239
- "from_port_id": "out",
240
- "to_node_id": "op_edit",
241
- "to_port_id": "image",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  "type": "image"
243
  }
244
  ]
245
- }
 
9
  "asset_type": "text",
10
  "x": 40,
11
  "y": 60,
12
+ "data": {"out": "A capybara wearing a wizard hat, oil painting"},
13
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
14
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  },
16
  {
17
  "id": "ref_image",
 
20
  "asset_type": "image",
21
  "x": 40,
22
  "y": 380,
23
+ "data": {},
24
+ "inputs": [{"id": "in", "label": "Image", "type": "image"}],
25
+ "outputs": [{"id": "out", "label": "Image", "type": "image"}]
 
 
 
 
 
 
 
 
 
 
 
 
26
  },
27
  {
28
  "id": "ref_instruction",
 
31
  "asset_type": "text",
32
  "x": 40,
33
  "y": 640,
34
+ "data": {"out": "Move it to a snowy mountain top"},
35
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
36
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
38
  ],
39
  "operators": [
40
+ {
41
+ "id": "op_enhance_t2i",
42
+ "label": "Enhance Prompt (PE-T2I)",
43
+ "role": "operator",
44
+ "kind": "fn",
45
+ "fn": "enhance_prompt_t2i",
46
+ "x": 420,
47
+ "y": 60,
48
+ "inputs": [
49
+ {"id": "prompt", "label": "Prompt", "type": "text", "required": true}
50
+ ],
51
+ "outputs": [{"id": "out_0", "label": "Rewritten Prompt", "type": "text", "output_index": 0}]
52
+ },
53
  {
54
  "id": "op_t2i",
55
  "label": "Qwen-Image 2.1 Text-to-Image",
56
  "role": "operator",
57
  "kind": "fn",
58
  "fn": "text_to_image",
59
+ "x": 800,
60
  "y": 60,
61
+ "data": {"steps": 40},
62
  "inputs": [
63
+ {"id": "prompt", "label": "Prompt", "type": "text", "required": true},
64
+ {"id": "steps", "label": "Steps", "type": "number"}
 
 
 
 
 
 
 
 
 
 
65
  ],
66
+ "outputs": [{"id": "out_0", "label": "Image", "type": "image", "output_index": 0}]
67
+ },
68
+ {
69
+ "id": "op_enhance_i2i",
70
+ "label": "Enhance Instruction (PE-I2I)",
71
+ "role": "operator",
72
+ "kind": "fn",
73
+ "fn": "enhance_prompt_i2i",
74
+ "x": 420,
75
+ "y": 480,
76
+ "inputs": [
77
+ {"id": "image", "label": "Image", "type": "image", "required": true},
78
+ {"id": "instruction", "label": "Instruction", "type": "text", "required": true}
79
  ],
80
+ "outputs": [{"id": "out_0", "label": "Rewritten Instruction", "type": "text", "output_index": 0}]
 
 
81
  },
82
  {
83
  "id": "op_edit",
 
85
  "role": "operator",
86
  "kind": "fn",
87
  "fn": "edit_image",
88
+ "x": 800,
89
+ "y": 480,
90
+ "data": {"steps": 40},
91
  "inputs": [
92
+ {"id": "image", "label": "Image", "type": "image", "required": true},
93
+ {"id": "instruction", "label": "Instruction", "type": "text", "required": true},
94
+ {"id": "steps", "label": "Steps", "type": "number"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  ],
96
+ "outputs": [{"id": "out_0", "label": "Image", "type": "image", "output_index": 0}]
 
 
 
 
 
 
 
 
 
 
97
  }
98
  ],
99
  "subjects": [
100
+ {
101
+ "id": "sub_prompt",
102
+ "label": "Rewritten Prompt",
103
+ "role": "subject",
104
+ "asset_type": "text",
105
+ "x": 800,
106
+ "y": 300,
107
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
108
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}]
109
+ },
110
  {
111
  "id": "sub_generated",
112
  "label": "Generated Image",
113
  "role": "subject",
114
  "asset_type": "image",
115
+ "x": 1180,
116
  "y": 60,
117
+ "inputs": [{"id": "in", "label": "Image", "type": "image"}],
118
+ "outputs": [{"id": "out", "label": "Image", "type": "image"}]
119
+ },
120
+ {
121
+ "id": "sub_edit_prompt",
122
+ "label": "Rewritten Instruction",
123
+ "role": "subject",
124
+ "asset_type": "text",
125
+ "x": 800,
126
+ "y": 720,
127
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
128
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}]
 
 
129
  },
130
  {
131
  "id": "sub_edited",
132
  "label": "Edited Image",
133
  "role": "subject",
134
  "asset_type": "image",
135
+ "x": 1180,
136
+ "y": 480,
137
+ "inputs": [{"id": "in", "label": "Image", "type": "image"}],
138
+ "outputs": [{"id": "out", "label": "Image", "type": "image"}]
 
 
 
 
 
 
 
 
 
 
 
 
139
  }
140
  ],
141
  "edges": [
142
  {
143
  "id": "e1",
144
+ "from_node_id": "ref_prompt", "from_port_id": "out",
145
+ "to_node_id": "op_enhance_t2i", "to_port_id": "prompt",
 
 
146
  "type": "text"
147
  },
148
  {
149
  "id": "e2",
150
+ "from_node_id": "op_enhance_t2i", "from_port_id": "out_0",
151
+ "to_node_id": "op_t2i", "to_port_id": "prompt",
152
+ "type": "text"
 
 
153
  },
154
  {
155
+ "id": "e3",
156
+ "from_node_id": "op_enhance_t2i", "from_port_id": "out_0",
157
+ "to_node_id": "sub_prompt", "to_port_id": "in",
 
 
158
  "type": "text"
159
  },
160
+ {
161
+ "id": "e4",
162
+ "from_node_id": "op_t2i", "from_port_id": "out_0",
163
+ "to_node_id": "sub_generated", "to_port_id": "in",
164
+ "type": "image"
165
+ },
166
  {
167
  "id": "e5",
168
+ "from_node_id": "ref_image", "from_port_id": "out",
169
+ "to_node_id": "op_enhance_i2i", "to_port_id": "image",
 
 
170
  "type": "image"
171
  },
172
  {
173
+ "id": "e6",
174
+ "from_node_id": "ref_instruction", "from_port_id": "out",
175
+ "to_node_id": "op_enhance_i2i", "to_port_id": "instruction",
176
+ "type": "text"
177
+ },
178
+ {
179
+ "id": "e7",
180
+ "from_node_id": "op_enhance_i2i", "from_port_id": "out_0",
181
+ "to_node_id": "op_edit", "to_port_id": "instruction",
182
+ "type": "text"
183
+ },
184
+ {
185
+ "id": "e8",
186
+ "from_node_id": "op_enhance_i2i", "from_port_id": "out_0",
187
+ "to_node_id": "sub_edit_prompt", "to_port_id": "in",
188
+ "type": "text"
189
+ },
190
+ {
191
+ "id": "e9",
192
+ "from_node_id": "ref_image", "from_port_id": "out",
193
+ "to_node_id": "op_edit", "to_port_id": "image",
194
+ "type": "image"
195
+ },
196
+ {
197
+ "id": "e10",
198
+ "from_node_id": "op_edit", "from_port_id": "out_0",
199
+ "to_node_id": "sub_edited", "to_port_id": "in",
200
  "type": "image"
201
  }
202
  ]
203
+ }