Hermes Bot commited on
Commit
704a7f8
Β·
1 Parent(s): 2ce9191

Simplify base parameter UI to avoid Row errors

Browse files
Files changed (1) hide show
  1. ui/shared/ui_components.py +670 -671
ui/shared/ui_components.py CHANGED
@@ -1,672 +1,671 @@
1
- import gradio as gr
2
- from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
3
- from core.settings import (
4
- MAX_LORAS, LORA_SOURCE_CHOICES, MAX_EMBEDDINGS, MAX_CONDITIONINGS,
5
- MAX_CONTROLNETS, MAX_IPADAPTERS, RESOLUTION_MAP, ARCHITECTURES_CONFIG,
6
- MODEL_MAP_CHECKPOINT, MODEL_TYPE_MAP, FEATURES_CONFIG, ARCH_CATEGORIES_MAP,
7
- VAE_DIR, MODEL_DEFAULTS_CONFIG
8
- )
9
- import yaml
10
- import os
11
- from functools import lru_cache
12
- from utils.app_utils import save_uploaded_file_with_hash
13
-
14
- default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOINT else None
15
- default_m_type = MODEL_TYPE_MAP.get(default_model_name, "SDXL") if default_model_name else "SDXL"
16
- default_architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
17
- default_arch_model_type = default_architectures_dict.get(default_m_type, {}).get("model_type", default_m_type.lower().replace(" ", "").replace(".", ""))
18
- default_arch_features = FEATURES_CONFIG.get(default_arch_model_type, FEATURES_CONFIG.get('default', {}))
19
- default_enabled_chains = default_arch_features.get('enabled_chains', [])
20
-
21
- default_vals = MODEL_DEFAULTS_CONFIG.get('Default', {})
22
- DEFAULT_STEPS = default_vals.get('steps', 20)
23
- DEFAULT_CFG = default_vals.get('cfg', 5.0)
24
- DEFAULT_SAMPLER = default_vals.get('sampler_name', 'euler')
25
- DEFAULT_SCHEDULER = default_vals.get('scheduler', 'simple')
26
- DEFAULT_POS_PROMPT = default_vals.get('positive_prompt', '')
27
- DEFAULT_NEG_PROMPT = default_vals.get('negative_prompt', '')
28
-
29
- @lru_cache(maxsize=1)
30
- def get_ipadapter_config_from_yaml():
31
- try:
32
- _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
33
- _IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
34
- with open(_IPADAPTER_LIST_PATH, 'r', encoding='utf-8') as f:
35
- config = yaml.safe_load(f)
36
- return config
37
- except Exception as e:
38
- print(f"Warning: Could not load ipadapter.yaml for UI components: {e}")
39
- return {}
40
-
41
- def get_ipadapter_presets(arch="SDXL"):
42
- config = get_ipadapter_config_from_yaml()
43
- presets = []
44
- if config:
45
- std_presets = config.get("IPAdapter_presets", {}).get(arch, [])
46
- face_presets = config.get("IPAdapter_FaceID_presets", {}).get(arch, [])
47
- if std_presets:
48
- presets.extend(std_presets)
49
- if face_presets:
50
- presets.extend(face_presets)
51
- return presets if presets else ["STANDARD (medium strength)"]
52
-
53
- def create_model_architecture_filter_ui(prefix):
54
- components = {}
55
- ordered_architectures = ARCHITECTURES_CONFIG.get("architecture_order", [])
56
- choices = ["ALL"] + ordered_architectures
57
-
58
- components[f'model_arch_{prefix}'] = gr.Radio(
59
- label="Model Architecture",
60
- choices=choices,
61
- value="ALL",
62
- interactive=True,
63
- visible=True
64
- )
65
- return components
66
-
67
- def create_category_filter_ui(prefix):
68
- valid_cats = list(set(cat for cats in ARCH_CATEGORIES_MAP.values() for cat in cats))
69
- cat_choices = ["ALL"] + sorted(valid_cats)
70
-
71
- components = {}
72
- components[f'model_cat_{prefix}'] = gr.Dropdown(
73
- label="Filter Models",
74
- choices=cat_choices,
75
- value="ALL",
76
- interactive=True,
77
- scale=1,
78
- allow_custom_value=True
79
- )
80
- return components
81
-
82
- def create_base_parameter_ui(prefix, defaults=None):
83
- if defaults is None:
84
- defaults = {}
85
-
86
- components = {}
87
-
88
- with gr.Row():
89
- components[f'aspect_ratio_{prefix}'] = gr.Dropdown(
90
- label="Aspect Ratio",
91
- choices=list(RESOLUTION_MAP.get('sdxl', {}).keys()),
92
- value="1:1 (Square)",
93
- interactive=True,
94
- allow_custom_value=True
95
- )
96
- with gr.Row():
97
- components[f'width_{prefix}'] = gr.Number(label="Width", value=defaults.get('w', 1024), interactive=True)
98
- components[f'height_{prefix}'] = gr.Number(label="Height", value=defaults.get('h', 1024), interactive=True)
99
- with gr.Row():
100
- components[f'sampler_{prefix}'] = gr.Dropdown(
101
- label="Sampler",
102
- choices=SAMPLER_CHOICES,
103
- value=DEFAULT_SAMPLER if DEFAULT_SAMPLER in SAMPLER_CHOICES else (SAMPLER_CHOICES[0] if SAMPLER_CHOICES else 'euler')
104
- )
105
- components[f'scheduler_{prefix}'] = gr.Dropdown(
106
- label="Scheduler",
107
- choices=SCHEDULER_CHOICES,
108
- value=DEFAULT_SCHEDULER if DEFAULT_SCHEDULER in SCHEDULER_CHOICES else (SCHEDULER_CHOICES[0] if SCHEDULER_CHOICES else 'simple')
109
- )
110
- with gr.Row():
111
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=DEFAULT_STEPS)
112
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=DEFAULT_CFG)
113
- with gr.Row():
114
- components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
115
- components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
116
- with gr.Row():
117
- components[f'clip_skip_{prefix}'] = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=1, visible=False, interactive=True)
118
- components[f'guidance_{prefix}'] = gr.Slider(label="Guidance (FLUX)", minimum=1.0, maximum=10.0, step=0.1, value=3.5, visible=False, interactive=True)
119
- components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60s, Max: 120s", info="Optional: Set how long to reserve the GPU.")
120
-
121
- return components
122
-
123
-
124
- def create_lora_settings_ui(prefix: str):
125
- components = {}
126
-
127
- lora_rows, lora_sources, lora_ids, lora_scales, lora_uploads = [], [], [], [], []
128
-
129
- with gr.Accordion("LoRA Settings", open=False, visible=('lora' in default_enabled_chains)) as lora_accordion:
130
- components[f'lora_accordion_{prefix}'] = lora_accordion
131
- gr.Markdown("πŸ’‘ **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `lightx2v/Qwen-Image-Lightning/Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors`).")
132
- components[f'lora_count_state_{prefix}'] = gr.State(1)
133
-
134
- for i in range(MAX_LORAS):
135
- with gr.Row(visible=i==0) as row:
136
- source = gr.Dropdown(label=f"LoRA Source {i+1}", choices=LORA_SOURCE_CHOICES, value=LORA_SOURCE_CHOICES[0], scale=1)
137
- lora_id = gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=2, type="text")
138
- scale = gr.Slider(label=f"Scale", minimum=0.0, maximum=2.0, step=0.05, value=1.0, scale=1)
139
- upload = gr.UploadButton(label="Upload", file_types=[".safetensors"], scale=1)
140
-
141
- lora_rows.append(row)
142
- lora_sources.append(source)
143
- lora_ids.append(lora_id)
144
- lora_scales.append(scale)
145
- lora_uploads.append(upload)
146
-
147
- with gr.Row():
148
- components[f'add_lora_button_{prefix}'] = gr.Button("Add LoRA", variant="secondary")
149
- components[f'delete_lora_button_{prefix}'] = gr.Button("Remove LoRA", variant="secondary", visible=False)
150
-
151
- components[f'lora_rows_{prefix}'] = lora_rows
152
- components[f'lora_sources_{prefix}'] = lora_sources
153
- components[f'lora_ids_{prefix}'] = lora_ids
154
- components[f'lora_scales_{prefix}'] = lora_scales
155
- components[f'lora_uploads_{prefix}'] = lora_uploads
156
-
157
- all_lora_components_flat = []
158
- for i in range(MAX_LORAS):
159
- all_lora_components_flat.extend([lora_sources[i], lora_ids[i], lora_scales[i], lora_uploads[i]])
160
- components[f'all_lora_components_flat_{prefix}'] = all_lora_components_flat
161
-
162
- return components
163
-
164
- def create_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
165
- components = {}
166
- key = lambda name: f"{name}_{prefix}"
167
-
168
- with gr.Accordion("ControlNet Settings", open=False, visible=('controlnet' in default_enabled_chains)) as accordion:
169
- components[key('controlnet_accordion')] = accordion
170
-
171
- cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
172
- components.update({
173
- key('controlnet_rows'): cn_rows,
174
- key('controlnet_images'): images,
175
- key('controlnet_series'): series,
176
- key('controlnet_types'): types,
177
- key('controlnet_strengths'): strengths,
178
- key('controlnet_filepaths'): filepaths
179
- })
180
-
181
- for i in range(max_units):
182
- with gr.Row(visible=(i < 1)) as row:
183
- with gr.Column(scale=1):
184
- images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
185
- with gr.Column(scale=2):
186
- types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
187
- series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
188
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
189
- filepaths.append(gr.State(None))
190
- cn_rows.append(row)
191
-
192
- with gr.Row():
193
- components[key('add_controlnet_button')] = gr.Button("✚ Add ControlNet")
194
- components[key('delete_controlnet_button')] = gr.Button("βž– Delete ControlNet", visible=False)
195
- components[key('controlnet_count_state')] = gr.State(1)
196
-
197
- all_cn_components_flat = []
198
- for i in range(max_units):
199
- all_cn_components_flat.extend([
200
- images[i], types[i], series[i], strengths[i], filepaths[i]
201
- ])
202
- components[key('all_controlnet_components_flat')] = all_cn_components_flat
203
-
204
- return components
205
-
206
- def create_anima_controlnet_lllite_ui(prefix: str, max_units=MAX_CONTROLNETS):
207
- components = {}
208
- key = lambda name: f"{name}_{prefix}"
209
-
210
- with gr.Accordion("Anima ControlNet Lllite Settings", open=False, visible=('anima_controlnet_lllite' in default_enabled_chains)) as accordion:
211
- components[key('anima_controlnet_lllite_accordion')] = accordion
212
- gr.Markdown("πŸ’‘ **Tip:** Processed using the [kohya-ss/ComfyUI-Anima-LLLite](https://github.com/kohya-ss/ComfyUI-Anima-LLLite) node.")
213
-
214
- cn_rows, images, series, types, strengths, filepaths, start_percents, end_percents = [], [], [], [], [], [], [], []
215
- components.update({
216
- key('anima_controlnet_lllite_rows'): cn_rows,
217
- key('anima_controlnet_lllite_images'): images,
218
- key('anima_controlnet_lllite_series'): series,
219
- key('anima_controlnet_lllite_types'): types,
220
- key('anima_controlnet_lllite_strengths'): strengths,
221
- key('anima_controlnet_lllite_filepaths'): filepaths,
222
- key('anima_controlnet_lllite_start_percents'): start_percents,
223
- key('anima_controlnet_lllite_end_percents'): end_percents
224
- })
225
-
226
- for i in range(max_units):
227
- with gr.Row(visible=(i < 1)) as row:
228
- with gr.Column(scale=1):
229
- images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
230
- with gr.Column(scale=2):
231
- types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
232
- series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
233
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
234
- with gr.Row(visible=False):
235
- start_percents.append(gr.State(0.0))
236
- end_percents.append(gr.State(1.0))
237
- filepaths.append(gr.State(None))
238
- cn_rows.append(row)
239
-
240
- with gr.Row():
241
- components[key('add_anima_controlnet_lllite_button')] = gr.Button("✚ Add Lllite")
242
- components[key('delete_anima_controlnet_lllite_button')] = gr.Button("βž– Delete Lllite", visible=False)
243
- components[key('anima_controlnet_lllite_count_state')] = gr.State(1)
244
-
245
- all_cn_components_flat = []
246
- for i in range(max_units):
247
- all_cn_components_flat.extend([
248
- images[i], types[i], series[i], strengths[i], filepaths[i], start_percents[i], end_percents[i]
249
- ])
250
- components[key('all_anima_controlnet_lllite_components_flat')] = all_cn_components_flat
251
-
252
- return components
253
-
254
- def create_diffsynth_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
255
- components = {}
256
- key = lambda name: f"{name}_{prefix}"
257
-
258
- with gr.Accordion("DiffSynth ControlNet Settings", open=False, visible=('controlnet_model_patch' in default_enabled_chains)) as accordion:
259
- components[key('diffsynth_controlnet_accordion')] = accordion
260
-
261
- cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
262
- components.update({
263
- key('diffsynth_controlnet_rows'): cn_rows,
264
- key('diffsynth_controlnet_images'): images,
265
- key('diffsynth_controlnet_series'): series,
266
- key('diffsynth_controlnet_types'): types,
267
- key('diffsynth_controlnet_strengths'): strengths,
268
- key('diffsynth_controlnet_filepaths'): filepaths
269
- })
270
-
271
- for i in range(max_units):
272
- with gr.Row(visible=(i < 1)) as row:
273
- with gr.Column(scale=1):
274
- images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
275
- with gr.Column(scale=2):
276
- types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
277
- series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
278
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
279
- filepaths.append(gr.State(None))
280
- cn_rows.append(row)
281
-
282
- with gr.Row():
283
- components[key('add_diffsynth_controlnet_button')] = gr.Button("✚ Add DiffSynth ControlNet")
284
- components[key('delete_diffsynth_controlnet_button')] = gr.Button("βž– Delete DiffSynth ControlNet", visible=False)
285
- components[key('diffsynth_controlnet_count_state')] = gr.State(1)
286
-
287
- all_cn_components_flat = []
288
- for i in range(max_units):
289
- all_cn_components_flat.extend([
290
- images[i], types[i], series[i], strengths[i], filepaths[i]
291
- ])
292
- components[key('all_diffsynth_controlnet_components_flat')] = all_cn_components_flat
293
-
294
- return components
295
-
296
- def create_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
297
- components = {}
298
- key = lambda name: f"{name}_{prefix}"
299
-
300
- sdxl_presets = get_ipadapter_presets("SDXL")
301
- default_preset = sdxl_presets[0] if sdxl_presets else None
302
-
303
- with gr.Accordion("IPAdapter Settings", open=False, visible=('ipadapter' in default_enabled_chains)) as accordion:
304
- components[key('ipadapter_accordion')] = accordion
305
- gr.Markdown("πŸ’‘ **Tip:** Processed using the [cubiq/ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) node.")
306
-
307
- with gr.Row():
308
- components[key('ipadapter_final_preset')] = gr.Dropdown(
309
- label="Preset (for all images)",
310
- choices=sdxl_presets,
311
- value=default_preset,
312
- interactive=True,
313
- allow_custom_value=True
314
- )
315
- components[key('ipadapter_embeds_scaling')] = gr.Dropdown(
316
- label="Embeds Scaling",
317
- choices=['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],
318
- value='V only',
319
- interactive=True
320
- )
321
-
322
- with gr.Row():
323
- components[key('ipadapter_combine_method')] = gr.Dropdown(
324
- label="Combine Method",
325
- choices=["concat", "add", "subtract", "average", "norm average", "max", "min"],
326
- value="concat",
327
- interactive=True
328
- )
329
- components[key('ipadapter_final_weight')] = gr.Slider(label="Final Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True)
330
- components[key('ipadapter_final_lora_strength')] = gr.Slider(label="Final LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False)
331
-
332
- gr.Markdown("---")
333
-
334
- ipa_rows, images, weights, lora_strengths = [], [], [], []
335
- components.update({
336
- key('ipadapter_rows'): ipa_rows,
337
- key('ipadapter_images'): images,
338
- key('ipadapter_weights'): weights,
339
- key('ipadapter_lora_strengths'): lora_strengths
340
- })
341
-
342
- for i in range(max_units):
343
- with gr.Row(visible=(i < 1)) as row:
344
- with gr.Column(scale=1):
345
- images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
346
- with gr.Column(scale=2):
347
- weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
348
- lora_strengths.append(gr.Slider(label="LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False))
349
- ipa_rows.append(row)
350
-
351
- with gr.Row():
352
- components[key('add_ipadapter_button')] = gr.Button("✚ Add IPAdapter")
353
- components[key('delete_ipadapter_button')] = gr.Button("βž– Delete IPAdapter", visible=False)
354
- components[key('ipadapter_count_state')] = gr.State(1)
355
-
356
- all_ipa_components_flat = images + weights + lora_strengths
357
- all_ipa_components_flat += [
358
- components[key('ipadapter_final_preset')],
359
- components[key('ipadapter_final_weight')],
360
- components[key('ipadapter_final_lora_strength')],
361
- components[key('ipadapter_embeds_scaling')],
362
- components[key('ipadapter_combine_method')],
363
- ]
364
- components[key('all_ipadapter_components_flat')] = all_ipa_components_flat
365
-
366
- return components
367
-
368
- def create_flux1_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
369
- components = {}
370
- key = lambda name: f"{name}_{prefix}"
371
-
372
- with gr.Accordion("IPAdapter Settings (FLUX.1)", open=False, visible=('flux1_ipadapter' in default_enabled_chains)) as accordion:
373
- components[key('flux1_ipadapter_accordion')] = accordion
374
- gr.Markdown("πŸ’‘ **Tip:** Processed using the [Shakker-Labs/ComfyUI-IPAdapter-Flux](https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux) node.")
375
-
376
- ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
377
- components.update({
378
- key('flux1_ipadapter_rows'): ipa_rows,
379
- key('flux1_ipadapter_images'): images,
380
- key('flux1_ipadapter_weights'): weights,
381
- key('flux1_ipadapter_start_percents'): start_percents,
382
- key('flux1_ipadapter_end_percents'): end_percents,
383
- })
384
-
385
- for i in range(max_units):
386
- with gr.Row(visible=(i < 1)) as row:
387
- with gr.Column(scale=1):
388
- images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
389
- with gr.Column(scale=2):
390
- weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True))
391
- with gr.Row():
392
- start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
393
- end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=0.6, interactive=True))
394
- ipa_rows.append(row)
395
-
396
- with gr.Row():
397
- components[key('add_flux1_ipadapter_button')] = gr.Button("✚ Add IPAdapter (FLUX)")
398
- components[key('delete_flux1_ipadapter_button')] = gr.Button("βž– Delete IPAdapter (FLUX)", visible=False)
399
- components[key('flux1_ipadapter_count_state')] = gr.State(1)
400
-
401
- all_flux1_ipa_components_flat = images + weights + start_percents + end_percents
402
- components[key('all_flux1_ipadapter_components_flat')] = all_flux1_ipa_components_flat
403
-
404
- return components
405
-
406
- def create_sd3_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
407
- components = {}
408
- key = lambda name: f"{name}_{prefix}"
409
-
410
- with gr.Accordion("IPAdapter Settings (SD3)", open=False, visible=('sd3_ipadapter' in default_enabled_chains)) as accordion:
411
- components[key('sd3_ipadapter_accordion')] = accordion
412
- gr.Markdown("πŸ’‘ **Tip:** Processed using the [Slickytail/ComfyUI-InstantX-IPAdapter-SD3](https://github.com/Slickytail/ComfyUI-InstantX-IPAdapter-SD3) node.")
413
-
414
- ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
415
- components.update({
416
- key('sd3_ipadapter_rows'): ipa_rows,
417
- key('sd3_ipadapter_images'): images,
418
- key('sd3_ipadapter_weights'): weights,
419
- key('sd3_ipadapter_start_percents'): start_percents,
420
- key('sd3_ipadapter_end_percents'): end_percents,
421
- })
422
-
423
- for i in range(max_units):
424
- with gr.Row(visible=(i < 1)) as row:
425
- with gr.Column(scale=1):
426
- images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
427
- with gr.Column(scale=2):
428
- weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.5, interactive=True))
429
- with gr.Row():
430
- start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
431
- end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=1.0, interactive=True))
432
- ipa_rows.append(row)
433
-
434
- with gr.Row():
435
- components[key('add_sd3_ipadapter_button')] = gr.Button("✚ Add IPAdapter (SD3)")
436
- components[key('delete_sd3_ipadapter_button')] = gr.Button("βž– Delete IPAdapter (SD3)", visible=False)
437
- components[key('sd3_ipadapter_count_state')] = gr.State(1)
438
-
439
- all_sd3_ipa_components_flat = images + weights + start_percents + end_percents
440
- components[key('all_sd3_ipadapter_components_flat')] = all_sd3_ipa_components_flat
441
-
442
- return components
443
-
444
- def create_style_ui(prefix: str):
445
- components = {}
446
- key = lambda name: f"{name}_{prefix}"
447
-
448
- with gr.Accordion("Style Settings (FLUX.1)", open=False, visible=('style' in default_enabled_chains)) as accordion:
449
- components[key('style_accordion')] = accordion
450
-
451
- style_rows, images, strengths = [], [], []
452
- components.update({
453
- key('style_rows'): style_rows,
454
- key('style_images'): images,
455
- key('style_strengths'): strengths
456
- })
457
-
458
- for i in range(5):
459
- with gr.Row(visible=(i < 1)) as row:
460
- with gr.Column(scale=1):
461
- images.append(gr.Image(label=f"Style Image {i+1}", type="pil", sources=["upload"], height=256))
462
- with gr.Column(scale=2):
463
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
464
- style_rows.append(row)
465
-
466
- with gr.Row():
467
- components[key('add_style_button')] = gr.Button("✚ Add Style (FLUX)")
468
- components[key('delete_style_button')] = gr.Button("βž– Delete Style (FLUX)", visible=False)
469
- components[key('style_count_state')] = gr.State(1)
470
-
471
- all_style_components_flat = images + strengths
472
- components[key('all_style_components_flat')] = all_style_components_flat
473
-
474
- return components
475
-
476
- def create_embedding_ui(prefix: str):
477
- components = {}
478
- key = lambda name: f"{name}_{prefix}"
479
-
480
- with gr.Accordion("Embedding Settings", open=False, visible=('embedding' in default_enabled_chains)) as accordion:
481
- components[key('embedding_accordion')] = accordion
482
- gr.Markdown("πŸ’‘ **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. For example, entering the Version ID 456 will automatically save the file as \"civitai_456.safetensors\", and you will need to manually enter `embedding:civitai_456` in either your prompt or negative prompt to activate it.When downloading from Hugging Face, please use the format: repo_id/filename.extension or repo_id/folder_path/filename.extension (e.g., ilikebigturtles/lazypos/lazypos.safetensors or ilikebigturtles/lazyneg/lazyneg.safetensors). For Hugging Face files, you will need to enter embedding:filename (e.g., entering embedding:lazypos in your positive prompt, or embedding:lazyneg in your negative prompt) to activate it.")
483
-
484
- embedding_rows, sources, ids, files, upload_buttons = [], [], [], [], []
485
- components.update({
486
- key('embedding_rows'): embedding_rows,
487
- key('embeddings_sources'): sources,
488
- key('embeddings_ids'): ids,
489
- key('embeddings_files'): files,
490
- key('embeddings_uploads'): upload_buttons
491
- })
492
-
493
- for i in range(MAX_EMBEDDINGS):
494
- with gr.Row(visible=(i < 1)) as row:
495
- sources.append(gr.Dropdown(label=f"Embedding Source {i+1}", choices=LORA_SOURCE_CHOICES, value="Civitai", scale=1, interactive=True))
496
- ids.append(gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=3, interactive=True, type="text"))
497
- upload_btn = gr.UploadButton("Upload", file_types=[".safetensors"], scale=1)
498
- files.append(gr.State(None))
499
- upload_buttons.append(upload_btn)
500
- embedding_rows.append(row)
501
-
502
- with gr.Row():
503
- components[key('add_embedding_button')] = gr.Button("✚ Add Embedding")
504
- components[key('delete_embedding_button')] = gr.Button("βž– Delete Embedding", visible=False)
505
- components[key('embedding_count_state')] = gr.State(1)
506
-
507
- all_embedding_components_flat = []
508
- for i in range(MAX_EMBEDDINGS):
509
- all_embedding_components_flat.extend([sources[i], ids[i], files[i]])
510
- components[key('all_embedding_components_flat')] = all_embedding_components_flat
511
-
512
- return components
513
-
514
- def create_conditioning_ui(prefix: str):
515
- components = {}
516
- key = lambda name: f"{name}_{prefix}"
517
-
518
- with gr.Accordion("Conditioning Settings", open=False, visible=('conditioning' in default_enabled_chains)) as accordion:
519
- components[key('conditioning_accordion')] = accordion
520
- gr.Markdown("πŸ’‘ **Tip:** Define rectangular areas and assign specific prompts to them. Coordinates (X, Y) start from the top-left corner.")
521
-
522
- cond_rows, prompts, widths, heights, xs, ys, strengths = [], [], [], [], [], [], []
523
- components.update({
524
- key('conditioning_rows'): cond_rows,
525
- key('conditioning_prompts'): prompts,
526
- key('conditioning_widths'): widths,
527
- key('conditioning_heights'): heights,
528
- key('conditioning_xs'): xs,
529
- key('conditioning_ys'): ys,
530
- key('conditioning_strengths'): strengths
531
- })
532
-
533
- for i in range(MAX_CONDITIONINGS):
534
- with gr.Column(visible=(i < 1)) as row_wrapper:
535
- prompts.append(gr.Textbox(label=f"Area Prompt {i+1}", lines=2, interactive=True))
536
- with gr.Row():
537
- xs.append(gr.Number(label="X", value=0, interactive=True, step=8, scale=1))
538
- ys.append(gr.Number(label="Y", value=0, interactive=True, step=8, scale=1))
539
- widths.append(gr.Number(label="Width", value=512, interactive=True, step=8, scale=1))
540
- heights.append(gr.Number(label="Height", value=512, interactive=True, step=8, scale=1))
541
- strengths.append(gr.Slider(label="Strength", minimum=0.1, maximum=2.0, step=0.05, value=1.0, interactive=True, scale=2))
542
- cond_rows.append(row_wrapper)
543
-
544
- with gr.Row():
545
- components[key('add_conditioning_button')] = gr.Button("✚ Add Area")
546
- components[key('delete_conditioning_button')] = gr.Button("βž– Delete Area", visible=False)
547
- components[key('conditioning_count_state')] = gr.State(1)
548
-
549
- all_cond_components_flat = prompts + widths + heights + xs + ys + strengths
550
- components[key('all_conditioning_components_flat')] = all_cond_components_flat
551
-
552
- return components
553
-
554
- def on_vae_upload(file_obj):
555
- if not file_obj:
556
- return gr.update(), gr.update(), None
557
-
558
- hashed_filename = save_uploaded_file_with_hash(file_obj, VAE_DIR)
559
- return hashed_filename, "File", file_obj
560
-
561
- def create_vae_override_ui(prefix: str):
562
- components = {}
563
- key = lambda name: f"{name}_{prefix}"
564
- source_choices = ["None"] + LORA_SOURCE_CHOICES
565
-
566
- with gr.Accordion("VAE Settings (Override)", open=False, visible=('vae' in default_enabled_chains)) as vae_accordion:
567
- components[key('vae_accordion')] = vae_accordion
568
- gr.Markdown("πŸ’‘ **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `madebyollin/sdxl-vae-fp16-fix/sdxl_vae.safetensors`).")
569
- with gr.Row():
570
- components[key('vae_source')] = gr.Dropdown(
571
- label="VAE Source",
572
- choices=source_choices,
573
- value="None",
574
- scale=1,
575
- interactive=True
576
- )
577
- components[key('vae_id')] = gr.Textbox(
578
- label="Civitai Version ID / HF file / Upload File",
579
- scale=3,
580
- interactive=True,
581
- type="text"
582
- )
583
- upload_btn = gr.UploadButton(
584
- "Upload",
585
- file_types=[".safetensors"],
586
- scale=1
587
- )
588
- components[key('vae_upload_button')] = upload_btn
589
- components[key('vae_file')] = gr.State(None)
590
-
591
- upload_btn.upload(
592
- fn=on_vae_upload,
593
- inputs=[upload_btn],
594
- outputs=[components[key('vae_id')], components[key('vae_source')], components[key('vae_file')]]
595
- )
596
-
597
- return components
598
-
599
- def create_reference_latent_ui(prefix: str, max_units=10):
600
- components = {}
601
- key = lambda name: f"{name}_{prefix}"
602
-
603
- with gr.Accordion("Reference Edit Settings", open=False, visible=('reference_latent' in default_enabled_chains)) as ref_accordion:
604
- components[key('reference_latent_accordion')] = ref_accordion
605
- gr.Markdown("πŸ’‘ **Tip:** For multimodal models, this feature enables powerful editing and combining capabilities. In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
606
-
607
- ref_image_groups = []
608
- ref_image_inputs = []
609
- with gr.Row():
610
- for i in range(max_units):
611
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
612
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
613
- ref_image_groups.append(img_col)
614
- ref_image_inputs.append(img_comp)
615
-
616
- components[key('reference_latent_rows')] = ref_image_groups
617
- components[key('reference_latent_images')] = ref_image_inputs
618
-
619
- with gr.Row():
620
- components[key('add_reference_latent_button')] = gr.Button("✚ Add Reference Image")
621
- components[key('delete_reference_latent_button')] = gr.Button("βž– Delete Reference Image", visible=False)
622
- components[key('reference_latent_count_state')] = gr.State(1)
623
-
624
- components[key('all_reference_latent_components_flat')] = ref_image_inputs
625
-
626
- return components
627
-
628
- def create_hidream_o1_reference_ui(prefix: str, max_units=10):
629
- components = {}
630
- key = lambda name: f"{name}_{prefix}"
631
-
632
- with gr.Accordion("HiDream-O1 Reference Edit Settings", open=False, visible=('hidream_o1_reference' in default_enabled_chains)) as ref_accordion:
633
- components[key('hidream_o1_reference_accordion')] = ref_accordion
634
- gr.Markdown("πŸ’‘ **Tip:** Please use **HiDream-O1-Image-Dev** (HiDream-O1-Image will time out), and set the resolution to **4.0MP** (e.g., 2048x2048). In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
635
-
636
- ref_image_groups = []
637
- ref_image_inputs = []
638
- with gr.Row():
639
- for i in range(max_units):
640
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
641
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
642
- ref_image_groups.append(img_col)
643
- ref_image_inputs.append(img_comp)
644
-
645
- components[key('hidream_o1_reference_rows')] = ref_image_groups
646
- components[key('hidream_o1_reference_images')] = ref_image_inputs
647
-
648
- with gr.Row():
649
- components[key('add_hidream_o1_reference_button')] = gr.Button("✚ Add Reference Image")
650
- components[key('delete_hidream_o1_reference_button')] = gr.Button("βž– Delete Reference Image", visible=False)
651
- components[key('hidream_o1_reference_count_state')] = gr.State(1)
652
-
653
- components[key('all_hidream_o1_reference_components_flat')] = ref_image_inputs
654
-
655
- return components
656
-
657
- def create_pid_ui(prefix: str):
658
- components = {}
659
- key = lambda name: f"{name}_{prefix}"
660
-
661
- with gr.Accordion("PiD Settings", open=False, visible=('pid' in default_enabled_chains)) as pid_accordion:
662
- components[key('pid_accordion')] = pid_accordion
663
- gr.Markdown("πŸ’‘ **Tip:** Use PiD (Pixel Diffusion Decoder) instead of the VAE Decoder for 4x decoding.")
664
- with gr.Row():
665
- components[key('pid_settings')] = gr.Dropdown(
666
- label="PiD Mode",
667
- choices=["OFF", "ON"],
668
- value="OFF",
669
- interactive=True
670
- )
671
-
672
  return components
 
1
+ import gradio as gr
2
+ from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
3
+ from core.settings import (
4
+ MAX_LORAS, LORA_SOURCE_CHOICES, MAX_EMBEDDINGS, MAX_CONDITIONINGS,
5
+ MAX_CONTROLNETS, MAX_IPADAPTERS, RESOLUTION_MAP, ARCHITECTURES_CONFIG,
6
+ MODEL_MAP_CHECKPOINT, MODEL_TYPE_MAP, FEATURES_CONFIG, ARCH_CATEGORIES_MAP,
7
+ VAE_DIR, MODEL_DEFAULTS_CONFIG
8
+ )
9
+ import yaml
10
+ import os
11
+ from functools import lru_cache
12
+ from utils.app_utils import save_uploaded_file_with_hash
13
+
14
+ default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOINT else None
15
+ default_m_type = MODEL_TYPE_MAP.get(default_model_name, "SDXL") if default_model_name else "SDXL"
16
+ default_architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
17
+ default_arch_model_type = default_architectures_dict.get(default_m_type, {}).get("model_type", default_m_type.lower().replace(" ", "").replace(".", ""))
18
+ default_arch_features = FEATURES_CONFIG.get(default_arch_model_type, FEATURES_CONFIG.get('default', {}))
19
+ default_enabled_chains = default_arch_features.get('enabled_chains', [])
20
+
21
+ default_vals = MODEL_DEFAULTS_CONFIG.get('Default', {})
22
+ DEFAULT_STEPS = default_vals.get('steps', 20)
23
+ DEFAULT_CFG = default_vals.get('cfg', 5.0)
24
+ DEFAULT_SAMPLER = default_vals.get('sampler_name', 'euler')
25
+ DEFAULT_SCHEDULER = default_vals.get('scheduler', 'simple')
26
+ DEFAULT_POS_PROMPT = default_vals.get('positive_prompt', '')
27
+ DEFAULT_NEG_PROMPT = default_vals.get('negative_prompt', '')
28
+
29
+ @lru_cache(maxsize=1)
30
+ def get_ipadapter_config_from_yaml():
31
+ try:
32
+ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
33
+ _IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
34
+ with open(_IPADAPTER_LIST_PATH, 'r', encoding='utf-8') as f:
35
+ config = yaml.safe_load(f)
36
+ return config
37
+ except Exception as e:
38
+ print(f"Warning: Could not load ipadapter.yaml for UI components: {e}")
39
+ return {}
40
+
41
+ def get_ipadapter_presets(arch="SDXL"):
42
+ config = get_ipadapter_config_from_yaml()
43
+ presets = []
44
+ if config:
45
+ std_presets = config.get("IPAdapter_presets", {}).get(arch, [])
46
+ face_presets = config.get("IPAdapter_FaceID_presets", {}).get(arch, [])
47
+ if std_presets:
48
+ presets.extend(std_presets)
49
+ if face_presets:
50
+ presets.extend(face_presets)
51
+ return presets if presets else ["STANDARD (medium strength)"]
52
+
53
+ def create_model_architecture_filter_ui(prefix):
54
+ components = {}
55
+ ordered_architectures = ARCHITECTURES_CONFIG.get("architecture_order", [])
56
+ choices = ["ALL"] + ordered_architectures
57
+
58
+ components[f'model_arch_{prefix}'] = gr.Radio(
59
+ label="Model Architecture",
60
+ choices=choices,
61
+ value="ALL",
62
+ interactive=True,
63
+ visible=True
64
+ )
65
+ return components
66
+
67
+ def create_category_filter_ui(prefix):
68
+ valid_cats = list(set(cat for cats in ARCH_CATEGORIES_MAP.values() for cat in cats))
69
+ cat_choices = ["ALL"] + sorted(valid_cats)
70
+
71
+ components = {}
72
+ components[f'model_cat_{prefix}'] = gr.Dropdown(
73
+ label="Filter Models",
74
+ choices=cat_choices,
75
+ value="ALL",
76
+ interactive=True,
77
+ scale=1,
78
+ allow_custom_value=True
79
+ )
80
+ return components
81
+
82
+ def create_base_parameter_ui(prefix, defaults=None):
83
+ if defaults is None:
84
+ defaults = {}
85
+
86
+ components = {}
87
+ # Aspect Ratio
88
+ components[f'aspect_ratio_{prefix}'] = gr.Dropdown(
89
+ label="Aspect Ratio",
90
+ choices=list(RESOLUTION_MAP.get('sdxl', {}).keys()),
91
+ value="1:1 (Square)",
92
+ interactive=True,
93
+ allow_custom_value=True
94
+ )
95
+ # Width & Height
96
+ components[f'width_{prefix}'] = gr.Number(label="Width", value=defaults.get('w', 1024), interactive=True)
97
+ components[f'height_{prefix}'] = gr.Number(label="Height", value=defaults.get('h', 1024), interactive=True)
98
+ # Sampler & Scheduler
99
+ components[f'sampler_{prefix}'] = gr.Dropdown(
100
+ label="Sampler",
101
+ choices=SAMPLER_CHOICES,
102
+ value=DEFAULT_SAMPLER if DEFAULT_SAMPLER in SAMPLER_CHOICES else (SAMPLER_CHOICES[0] if SAMPLER_CHOICES else 'euler')
103
+ )
104
+ components[f'scheduler_{prefix}'] = gr.Dropdown(
105
+ label="Scheduler",
106
+ choices=SCHEDULER_CHOICES,
107
+ value=DEFAULT_SCHEDULER if DEFAULT_SCHEDULER in SCHEDULER_CHOICES else (SCHEDULER_CHOICES[0] if SCHEDULER_CHOICES else 'simple')
108
+ )
109
+ # Steps & CFG
110
+ components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=DEFAULT_STEPS)
111
+ components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=DEFAULT_CFG)
112
+ # Seed & Batch Size
113
+ components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
114
+ components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
115
+ # Clip Skip & Guidance (FLUX) & ZeroGPU Duration
116
+ components[f'clip_skip_{prefix}'] = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=1, visible=False, interactive=True)
117
+ components[f'guidance_{prefix}'] = gr.Slider(label="Guidance (FLUX)", minimum=1.0, maximum=10.0, step=0.1, value=3.5, visible=False, interactive=True)
118
+ components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60s, Max: 120s", info="Optional: Set how long to reserve the GPU.")
119
+
120
+ return components
121
+
122
+
123
+ def create_lora_settings_ui(prefix: str):
124
+ components = {}
125
+
126
+ lora_rows, lora_sources, lora_ids, lora_scales, lora_uploads = [], [], [], [], []
127
+
128
+ with gr.Accordion("LoRA Settings", open=False, visible=('lora' in default_enabled_chains)) as lora_accordion:
129
+ components[f'lora_accordion_{prefix}'] = lora_accordion
130
+ gr.Markdown("πŸ’‘ **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `lightx2v/Qwen-Image-Lightning/Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors`).")
131
+ components[f'lora_count_state_{prefix}'] = gr.State(1)
132
+
133
+ for i in range(MAX_LORAS):
134
+ with gr.Row(visible=i==0) as row:
135
+ source = gr.Dropdown(label=f"LoRA Source {i+1}", choices=LORA_SOURCE_CHOICES, value=LORA_SOURCE_CHOICES[0], scale=1)
136
+ lora_id = gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=2, type="text")
137
+ scale = gr.Slider(label=f"Scale", minimum=0.0, maximum=2.0, step=0.05, value=1.0, scale=1)
138
+ upload = gr.UploadButton(label="Upload", file_types=[".safetensors"], scale=1)
139
+
140
+ lora_rows.append(row)
141
+ lora_sources.append(source)
142
+ lora_ids.append(lora_id)
143
+ lora_scales.append(scale)
144
+ lora_uploads.append(upload)
145
+
146
+ with gr.Row():
147
+ components[f'add_lora_button_{prefix}'] = gr.Button("Add LoRA", variant="secondary")
148
+ components[f'delete_lora_button_{prefix}'] = gr.Button("Remove LoRA", variant="secondary", visible=False)
149
+
150
+ components[f'lora_rows_{prefix}'] = lora_rows
151
+ components[f'lora_sources_{prefix}'] = lora_sources
152
+ components[f'lora_ids_{prefix}'] = lora_ids
153
+ components[f'lora_scales_{prefix}'] = lora_scales
154
+ components[f'lora_uploads_{prefix}'] = lora_uploads
155
+
156
+ all_lora_components_flat = []
157
+ for i in range(MAX_LORAS):
158
+ all_lora_components_flat.extend([lora_sources[i], lora_ids[i], lora_scales[i], lora_uploads[i]])
159
+ components[f'all_lora_components_flat_{prefix}'] = all_lora_components_flat
160
+
161
+ return components
162
+
163
+ def create_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
164
+ components = {}
165
+ key = lambda name: f"{name}_{prefix}"
166
+
167
+ with gr.Accordion("ControlNet Settings", open=False, visible=('controlnet' in default_enabled_chains)) as accordion:
168
+ components[key('controlnet_accordion')] = accordion
169
+
170
+ cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
171
+ components.update({
172
+ key('controlnet_rows'): cn_rows,
173
+ key('controlnet_images'): images,
174
+ key('controlnet_series'): series,
175
+ key('controlnet_types'): types,
176
+ key('controlnet_strengths'): strengths,
177
+ key('controlnet_filepaths'): filepaths
178
+ })
179
+
180
+ for i in range(max_units):
181
+ with gr.Row(visible=(i < 1)) as row:
182
+ with gr.Column(scale=1):
183
+ images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
184
+ with gr.Column(scale=2):
185
+ types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
186
+ series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
187
+ strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
188
+ filepaths.append(gr.State(None))
189
+ cn_rows.append(row)
190
+
191
+ with gr.Row():
192
+ components[key('add_controlnet_button')] = gr.Button("✚ Add ControlNet")
193
+ components[key('delete_controlnet_button')] = gr.Button("βž– Delete ControlNet", visible=False)
194
+ components[key('controlnet_count_state')] = gr.State(1)
195
+
196
+ all_cn_components_flat = []
197
+ for i in range(max_units):
198
+ all_cn_components_flat.extend([
199
+ images[i], types[i], series[i], strengths[i], filepaths[i]
200
+ ])
201
+ components[key('all_controlnet_components_flat')] = all_cn_components_flat
202
+
203
+ return components
204
+
205
+ def create_anima_controlnet_lllite_ui(prefix: str, max_units=MAX_CONTROLNETS):
206
+ components = {}
207
+ key = lambda name: f"{name}_{prefix}"
208
+
209
+ with gr.Accordion("Anima ControlNet Lllite Settings", open=False, visible=('anima_controlnet_lllite' in default_enabled_chains)) as accordion:
210
+ components[key('anima_controlnet_lllite_accordion')] = accordion
211
+ gr.Markdown("πŸ’‘ **Tip:** Processed using the [kohya-ss/ComfyUI-Anima-LLLite](https://github.com/kohya-ss/ComfyUI-Anima-LLLite) node.")
212
+
213
+ cn_rows, images, series, types, strengths, filepaths, start_percents, end_percents = [], [], [], [], [], [], [], []
214
+ components.update({
215
+ key('anima_controlnet_lllite_rows'): cn_rows,
216
+ key('anima_controlnet_lllite_images'): images,
217
+ key('anima_controlnet_lllite_series'): series,
218
+ key('anima_controlnet_lllite_types'): types,
219
+ key('anima_controlnet_lllite_strengths'): strengths,
220
+ key('anima_controlnet_lllite_filepaths'): filepaths,
221
+ key('anima_controlnet_lllite_start_percents'): start_percents,
222
+ key('anima_controlnet_lllite_end_percents'): end_percents
223
+ })
224
+
225
+ for i in range(max_units):
226
+ with gr.Row(visible=(i < 1)) as row:
227
+ with gr.Column(scale=1):
228
+ images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
229
+ with gr.Column(scale=2):
230
+ types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
231
+ series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
232
+ strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
233
+ with gr.Row(visible=False):
234
+ start_percents.append(gr.State(0.0))
235
+ end_percents.append(gr.State(1.0))
236
+ filepaths.append(gr.State(None))
237
+ cn_rows.append(row)
238
+
239
+ with gr.Row():
240
+ components[key('add_anima_controlnet_lllite_button')] = gr.Button("✚ Add Lllite")
241
+ components[key('delete_anima_controlnet_lllite_button')] = gr.Button("βž– Delete Lllite", visible=False)
242
+ components[key('anima_controlnet_lllite_count_state')] = gr.State(1)
243
+
244
+ all_cn_components_flat = []
245
+ for i in range(max_units):
246
+ all_cn_components_flat.extend([
247
+ images[i], types[i], series[i], strengths[i], filepaths[i], start_percents[i], end_percents[i]
248
+ ])
249
+ components[key('all_anima_controlnet_lllite_components_flat')] = all_cn_components_flat
250
+
251
+ return components
252
+
253
+ def create_diffsynth_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
254
+ components = {}
255
+ key = lambda name: f"{name}_{prefix}"
256
+
257
+ with gr.Accordion("DiffSynth ControlNet Settings", open=False, visible=('controlnet_model_patch' in default_enabled_chains)) as accordion:
258
+ components[key('diffsynth_controlnet_accordion')] = accordion
259
+
260
+ cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
261
+ components.update({
262
+ key('diffsynth_controlnet_rows'): cn_rows,
263
+ key('diffsynth_controlnet_images'): images,
264
+ key('diffsynth_controlnet_series'): series,
265
+ key('diffsynth_controlnet_types'): types,
266
+ key('diffsynth_controlnet_strengths'): strengths,
267
+ key('diffsynth_controlnet_filepaths'): filepaths
268
+ })
269
+
270
+ for i in range(max_units):
271
+ with gr.Row(visible=(i < 1)) as row:
272
+ with gr.Column(scale=1):
273
+ images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
274
+ with gr.Column(scale=2):
275
+ types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
276
+ series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
277
+ strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
278
+ filepaths.append(gr.State(None))
279
+ cn_rows.append(row)
280
+
281
+ with gr.Row():
282
+ components[key('add_diffsynth_controlnet_button')] = gr.Button("✚ Add DiffSynth ControlNet")
283
+ components[key('delete_diffsynth_controlnet_button')] = gr.Button("βž– Delete DiffSynth ControlNet", visible=False)
284
+ components[key('diffsynth_controlnet_count_state')] = gr.State(1)
285
+
286
+ all_cn_components_flat = []
287
+ for i in range(max_units):
288
+ all_cn_components_flat.extend([
289
+ images[i], types[i], series[i], strengths[i], filepaths[i]
290
+ ])
291
+ components[key('all_diffsynth_controlnet_components_flat')] = all_cn_components_flat
292
+
293
+ return components
294
+
295
+ def create_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
296
+ components = {}
297
+ key = lambda name: f"{name}_{prefix}"
298
+
299
+ sdxl_presets = get_ipadapter_presets("SDXL")
300
+ default_preset = sdxl_presets[0] if sdxl_presets else None
301
+
302
+ with gr.Accordion("IPAdapter Settings", open=False, visible=('ipadapter' in default_enabled_chains)) as accordion:
303
+ components[key('ipadapter_accordion')] = accordion
304
+ gr.Markdown("πŸ’‘ **Tip:** Processed using the [cubiq/ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) node.")
305
+
306
+ with gr.Row():
307
+ components[key('ipadapter_final_preset')] = gr.Dropdown(
308
+ label="Preset (for all images)",
309
+ choices=sdxl_presets,
310
+ value=default_preset,
311
+ interactive=True,
312
+ allow_custom_value=True
313
+ )
314
+ components[key('ipadapter_embeds_scaling')] = gr.Dropdown(
315
+ label="Embeds Scaling",
316
+ choices=['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],
317
+ value='V only',
318
+ interactive=True
319
+ )
320
+
321
+ with gr.Row():
322
+ components[key('ipadapter_combine_method')] = gr.Dropdown(
323
+ label="Combine Method",
324
+ choices=["concat", "add", "subtract", "average", "norm average", "max", "min"],
325
+ value="concat",
326
+ interactive=True
327
+ )
328
+ components[key('ipadapter_final_weight')] = gr.Slider(label="Final Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True)
329
+ components[key('ipadapter_final_lora_strength')] = gr.Slider(label="Final LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False)
330
+
331
+ gr.Markdown("---")
332
+
333
+ ipa_rows, images, weights, lora_strengths = [], [], [], []
334
+ components.update({
335
+ key('ipadapter_rows'): ipa_rows,
336
+ key('ipadapter_images'): images,
337
+ key('ipadapter_weights'): weights,
338
+ key('ipadapter_lora_strengths'): lora_strengths
339
+ })
340
+
341
+ for i in range(max_units):
342
+ with gr.Row(visible=(i < 1)) as row:
343
+ with gr.Column(scale=1):
344
+ images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
345
+ with gr.Column(scale=2):
346
+ weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
347
+ lora_strengths.append(gr.Slider(label="LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False))
348
+ ipa_rows.append(row)
349
+
350
+ with gr.Row():
351
+ components[key('add_ipadapter_button')] = gr.Button("✚ Add IPAdapter")
352
+ components[key('delete_ipadapter_button')] = gr.Button("βž– Delete IPAdapter", visible=False)
353
+ components[key('ipadapter_count_state')] = gr.State(1)
354
+
355
+ all_ipa_components_flat = images + weights + lora_strengths
356
+ all_ipa_components_flat += [
357
+ components[key('ipadapter_final_preset')],
358
+ components[key('ipadapter_final_weight')],
359
+ components[key('ipadapter_final_lora_strength')],
360
+ components[key('ipadapter_embeds_scaling')],
361
+ components[key('ipadapter_combine_method')],
362
+ ]
363
+ components[key('all_ipadapter_components_flat')] = all_ipa_components_flat
364
+
365
+ return components
366
+
367
+ def create_flux1_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
368
+ components = {}
369
+ key = lambda name: f"{name}_{prefix}"
370
+
371
+ with gr.Accordion("IPAdapter Settings (FLUX.1)", open=False, visible=('flux1_ipadapter' in default_enabled_chains)) as accordion:
372
+ components[key('flux1_ipadapter_accordion')] = accordion
373
+ gr.Markdown("πŸ’‘ **Tip:** Processed using the [Shakker-Labs/ComfyUI-IPAdapter-Flux](https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux) node.")
374
+
375
+ ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
376
+ components.update({
377
+ key('flux1_ipadapter_rows'): ipa_rows,
378
+ key('flux1_ipadapter_images'): images,
379
+ key('flux1_ipadapter_weights'): weights,
380
+ key('flux1_ipadapter_start_percents'): start_percents,
381
+ key('flux1_ipadapter_end_percents'): end_percents,
382
+ })
383
+
384
+ for i in range(max_units):
385
+ with gr.Row(visible=(i < 1)) as row:
386
+ with gr.Column(scale=1):
387
+ images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
388
+ with gr.Column(scale=2):
389
+ weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True))
390
+ with gr.Row():
391
+ start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
392
+ end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=0.6, interactive=True))
393
+ ipa_rows.append(row)
394
+
395
+ with gr.Row():
396
+ components[key('add_flux1_ipadapter_button')] = gr.Button("✚ Add IPAdapter (FLUX)")
397
+ components[key('delete_flux1_ipadapter_button')] = gr.Button("βž– Delete IPAdapter (FLUX)", visible=False)
398
+ components[key('flux1_ipadapter_count_state')] = gr.State(1)
399
+
400
+ all_flux1_ipa_components_flat = images + weights + start_percents + end_percents
401
+ components[key('all_flux1_ipadapter_components_flat')] = all_flux1_ipa_components_flat
402
+
403
+ return components
404
+
405
+ def create_sd3_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
406
+ components = {}
407
+ key = lambda name: f"{name}_{prefix}"
408
+
409
+ with gr.Accordion("IPAdapter Settings (SD3)", open=False, visible=('sd3_ipadapter' in default_enabled_chains)) as accordion:
410
+ components[key('sd3_ipadapter_accordion')] = accordion
411
+ gr.Markdown("πŸ’‘ **Tip:** Processed using the [Slickytail/ComfyUI-InstantX-IPAdapter-SD3](https://github.com/Slickytail/ComfyUI-InstantX-IPAdapter-SD3) node.")
412
+
413
+ ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
414
+ components.update({
415
+ key('sd3_ipadapter_rows'): ipa_rows,
416
+ key('sd3_ipadapter_images'): images,
417
+ key('sd3_ipadapter_weights'): weights,
418
+ key('sd3_ipadapter_start_percents'): start_percents,
419
+ key('sd3_ipadapter_end_percents'): end_percents,
420
+ })
421
+
422
+ for i in range(max_units):
423
+ with gr.Row(visible=(i < 1)) as row:
424
+ with gr.Column(scale=1):
425
+ images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
426
+ with gr.Column(scale=2):
427
+ weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.5, interactive=True))
428
+ with gr.Row():
429
+ start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
430
+ end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=1.0, interactive=True))
431
+ ipa_rows.append(row)
432
+
433
+ with gr.Row():
434
+ components[key('add_sd3_ipadapter_button')] = gr.Button("✚ Add IPAdapter (SD3)")
435
+ components[key('delete_sd3_ipadapter_button')] = gr.Button("βž– Delete IPAdapter (SD3)", visible=False)
436
+ components[key('sd3_ipadapter_count_state')] = gr.State(1)
437
+
438
+ all_sd3_ipa_components_flat = images + weights + start_percents + end_percents
439
+ components[key('all_sd3_ipadapter_components_flat')] = all_sd3_ipa_components_flat
440
+
441
+ return components
442
+
443
+ def create_style_ui(prefix: str):
444
+ components = {}
445
+ key = lambda name: f"{name}_{prefix}"
446
+
447
+ with gr.Accordion("Style Settings (FLUX.1)", open=False, visible=('style' in default_enabled_chains)) as accordion:
448
+ components[key('style_accordion')] = accordion
449
+
450
+ style_rows, images, strengths = [], [], []
451
+ components.update({
452
+ key('style_rows'): style_rows,
453
+ key('style_images'): images,
454
+ key('style_strengths'): strengths
455
+ })
456
+
457
+ for i in range(5):
458
+ with gr.Row(visible=(i < 1)) as row:
459
+ with gr.Column(scale=1):
460
+ images.append(gr.Image(label=f"Style Image {i+1}", type="pil", sources=["upload"], height=256))
461
+ with gr.Column(scale=2):
462
+ strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
463
+ style_rows.append(row)
464
+
465
+ with gr.Row():
466
+ components[key('add_style_button')] = gr.Button("✚ Add Style (FLUX)")
467
+ components[key('delete_style_button')] = gr.Button("βž– Delete Style (FLUX)", visible=False)
468
+ components[key('style_count_state')] = gr.State(1)
469
+
470
+ all_style_components_flat = images + strengths
471
+ components[key('all_style_components_flat')] = all_style_components_flat
472
+
473
+ return components
474
+
475
+ def create_embedding_ui(prefix: str):
476
+ components = {}
477
+ key = lambda name: f"{name}_{prefix}"
478
+
479
+ with gr.Accordion("Embedding Settings", open=False, visible=('embedding' in default_enabled_chains)) as accordion:
480
+ components[key('embedding_accordion')] = accordion
481
+ gr.Markdown("πŸ’‘ **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. For example, entering the Version ID 456 will automatically save the file as \"civitai_456.safetensors\", and you will need to manually enter `embedding:civitai_456` in either your prompt or negative prompt to activate it.When downloading from Hugging Face, please use the format: repo_id/filename.extension or repo_id/folder_path/filename.extension (e.g., ilikebigturtles/lazypos/lazypos.safetensors or ilikebigturtles/lazyneg/lazyneg.safetensors). For Hugging Face files, you will need to enter embedding:filename (e.g., entering embedding:lazypos in your positive prompt, or embedding:lazyneg in your negative prompt) to activate it.")
482
+
483
+ embedding_rows, sources, ids, files, upload_buttons = [], [], [], [], []
484
+ components.update({
485
+ key('embedding_rows'): embedding_rows,
486
+ key('embeddings_sources'): sources,
487
+ key('embeddings_ids'): ids,
488
+ key('embeddings_files'): files,
489
+ key('embeddings_uploads'): upload_buttons
490
+ })
491
+
492
+ for i in range(MAX_EMBEDDINGS):
493
+ with gr.Row(visible=(i < 1)) as row:
494
+ sources.append(gr.Dropdown(label=f"Embedding Source {i+1}", choices=LORA_SOURCE_CHOICES, value="Civitai", scale=1, interactive=True))
495
+ ids.append(gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=3, interactive=True, type="text"))
496
+ upload_btn = gr.UploadButton("Upload", file_types=[".safetensors"], scale=1)
497
+ files.append(gr.State(None))
498
+ upload_buttons.append(upload_btn)
499
+ embedding_rows.append(row)
500
+
501
+ with gr.Row():
502
+ components[key('add_embedding_button')] = gr.Button("✚ Add Embedding")
503
+ components[key('delete_embedding_button')] = gr.Button("βž– Delete Embedding", visible=False)
504
+ components[key('embedding_count_state')] = gr.State(1)
505
+
506
+ all_embedding_components_flat = []
507
+ for i in range(MAX_EMBEDDINGS):
508
+ all_embedding_components_flat.extend([sources[i], ids[i], files[i]])
509
+ components[key('all_embedding_components_flat')] = all_embedding_components_flat
510
+
511
+ return components
512
+
513
+ def create_conditioning_ui(prefix: str):
514
+ components = {}
515
+ key = lambda name: f"{name}_{prefix}"
516
+
517
+ with gr.Accordion("Conditioning Settings", open=False, visible=('conditioning' in default_enabled_chains)) as accordion:
518
+ components[key('conditioning_accordion')] = accordion
519
+ gr.Markdown("πŸ’‘ **Tip:** Define rectangular areas and assign specific prompts to them. Coordinates (X, Y) start from the top-left corner.")
520
+
521
+ cond_rows, prompts, widths, heights, xs, ys, strengths = [], [], [], [], [], [], []
522
+ components.update({
523
+ key('conditioning_rows'): cond_rows,
524
+ key('conditioning_prompts'): prompts,
525
+ key('conditioning_widths'): widths,
526
+ key('conditioning_heights'): heights,
527
+ key('conditioning_xs'): xs,
528
+ key('conditioning_ys'): ys,
529
+ key('conditioning_strengths'): strengths
530
+ })
531
+
532
+ for i in range(MAX_CONDITIONINGS):
533
+ with gr.Column(visible=(i < 1)) as row_wrapper:
534
+ prompts.append(gr.Textbox(label=f"Area Prompt {i+1}", lines=2, interactive=True))
535
+ with gr.Row():
536
+ xs.append(gr.Number(label="X", value=0, interactive=True, step=8, scale=1))
537
+ ys.append(gr.Number(label="Y", value=0, interactive=True, step=8, scale=1))
538
+ widths.append(gr.Number(label="Width", value=512, interactive=True, step=8, scale=1))
539
+ heights.append(gr.Number(label="Height", value=512, interactive=True, step=8, scale=1))
540
+ strengths.append(gr.Slider(label="Strength", minimum=0.1, maximum=2.0, step=0.05, value=1.0, interactive=True, scale=2))
541
+ cond_rows.append(row_wrapper)
542
+
543
+ with gr.Row():
544
+ components[key('add_conditioning_button')] = gr.Button("✚ Add Area")
545
+ components[key('delete_conditioning_button')] = gr.Button("βž– Delete Area", visible=False)
546
+ components[key('conditioning_count_state')] = gr.State(1)
547
+
548
+ all_cond_components_flat = prompts + widths + heights + xs + ys + strengths
549
+ components[key('all_conditioning_components_flat')] = all_cond_components_flat
550
+
551
+ return components
552
+
553
+ def on_vae_upload(file_obj):
554
+ if not file_obj:
555
+ return gr.update(), gr.update(), None
556
+
557
+ hashed_filename = save_uploaded_file_with_hash(file_obj, VAE_DIR)
558
+ return hashed_filename, "File", file_obj
559
+
560
+ def create_vae_override_ui(prefix: str):
561
+ components = {}
562
+ key = lambda name: f"{name}_{prefix}"
563
+ source_choices = ["None"] + LORA_SOURCE_CHOICES
564
+
565
+ with gr.Accordion("VAE Settings (Override)", open=False, visible=('vae' in default_enabled_chains)) as vae_accordion:
566
+ components[key('vae_accordion')] = vae_accordion
567
+ gr.Markdown("πŸ’‘ **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `madebyollin/sdxl-vae-fp16-fix/sdxl_vae.safetensors`).")
568
+ with gr.Row():
569
+ components[key('vae_source')] = gr.Dropdown(
570
+ label="VAE Source",
571
+ choices=source_choices,
572
+ value="None",
573
+ scale=1,
574
+ interactive=True
575
+ )
576
+ components[key('vae_id')] = gr.Textbox(
577
+ label="Civitai Version ID / HF file / Upload File",
578
+ scale=3,
579
+ interactive=True,
580
+ type="text"
581
+ )
582
+ upload_btn = gr.UploadButton(
583
+ "Upload",
584
+ file_types=[".safetensors"],
585
+ scale=1
586
+ )
587
+ components[key('vae_upload_button')] = upload_btn
588
+ components[key('vae_file')] = gr.State(None)
589
+
590
+ upload_btn.upload(
591
+ fn=on_vae_upload,
592
+ inputs=[upload_btn],
593
+ outputs=[components[key('vae_id')], components[key('vae_source')], components[key('vae_file')]]
594
+ )
595
+
596
+ return components
597
+
598
+ def create_reference_latent_ui(prefix: str, max_units=10):
599
+ components = {}
600
+ key = lambda name: f"{name}_{prefix}"
601
+
602
+ with gr.Accordion("Reference Edit Settings", open=False, visible=('reference_latent' in default_enabled_chains)) as ref_accordion:
603
+ components[key('reference_latent_accordion')] = ref_accordion
604
+ gr.Markdown("πŸ’‘ **Tip:** For multimodal models, this feature enables powerful editing and combining capabilities. In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
605
+
606
+ ref_image_groups = []
607
+ ref_image_inputs = []
608
+ with gr.Row():
609
+ for i in range(max_units):
610
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
611
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
612
+ ref_image_groups.append(img_col)
613
+ ref_image_inputs.append(img_comp)
614
+
615
+ components[key('reference_latent_rows')] = ref_image_groups
616
+ components[key('reference_latent_images')] = ref_image_inputs
617
+
618
+ with gr.Row():
619
+ components[key('add_reference_latent_button')] = gr.Button("✚ Add Reference Image")
620
+ components[key('delete_reference_latent_button')] = gr.Button("βž– Delete Reference Image", visible=False)
621
+ components[key('reference_latent_count_state')] = gr.State(1)
622
+
623
+ components[key('all_reference_latent_components_flat')] = ref_image_inputs
624
+
625
+ return components
626
+
627
+ def create_hidream_o1_reference_ui(prefix: str, max_units=10):
628
+ components = {}
629
+ key = lambda name: f"{name}_{prefix}"
630
+
631
+ with gr.Accordion("HiDream-O1 Reference Edit Settings", open=False, visible=('hidream_o1_reference' in default_enabled_chains)) as ref_accordion:
632
+ components[key('hidream_o1_reference_accordion')] = ref_accordion
633
+ gr.Markdown("πŸ’‘ **Tip:** Please use **HiDream-O1-Image-Dev** (HiDream-O1-Image will time out), and set the resolution to **4.0MP** (e.g., 2048x2048). In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
634
+
635
+ ref_image_groups = []
636
+ ref_image_inputs = []
637
+ with gr.Row():
638
+ for i in range(max_units):
639
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
640
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
641
+ ref_image_groups.append(img_col)
642
+ ref_image_inputs.append(img_comp)
643
+
644
+ components[key('hidream_o1_reference_rows')] = ref_image_groups
645
+ components[key('hidream_o1_reference_images')] = ref_image_inputs
646
+
647
+ with gr.Row():
648
+ components[key('add_hidream_o1_reference_button')] = gr.Button("✚ Add Reference Image")
649
+ components[key('delete_hidream_o1_reference_button')] = gr.Button("βž– Delete Reference Image", visible=False)
650
+ components[key('hidream_o1_reference_count_state')] = gr.State(1)
651
+
652
+ components[key('all_hidream_o1_reference_components_flat')] = ref_image_inputs
653
+
654
+ return components
655
+
656
+ def create_pid_ui(prefix: str):
657
+ components = {}
658
+ key = lambda name: f"{name}_{prefix}"
659
+
660
+ with gr.Accordion("PiD Settings", open=False, visible=('pid' in default_enabled_chains)) as pid_accordion:
661
+ components[key('pid_accordion')] = pid_accordion
662
+ gr.Markdown("πŸ’‘ **Tip:** Use PiD (Pixel Diffusion Decoder) instead of the VAE Decoder for 4x decoding.")
663
+ with gr.Row():
664
+ components[key('pid_settings')] = gr.Dropdown(
665
+ label="PiD Mode",
666
+ choices=["OFF", "ON"],
667
+ value="OFF",
668
+ interactive=True
669
+ )
670
+
 
671
  return components