RioShiina commited on
Commit
5e8b132
·
1 Parent(s): 402d29f

Add FLUX.1, FLUX.2-KV, SD3.5 & additional base model architecture recipes

Browse files
Files changed (42) hide show
  1. app.py +14 -137
  2. comfy_integration/nodes.py +5 -0
  3. comfy_integration/setup.py +36 -13
  4. core/generation_logic.py +0 -15
  5. core/model_manager.py +6 -19
  6. core/pipelines/controlnet_preprocessor.py +0 -143
  7. core/pipelines/sd_image_pipeline.py +224 -59
  8. core/pipelines/workflow_recipes/_partials/{_base_sampler.yaml → _base_sampler_sd.yaml} +15 -2
  9. core/pipelines/workflow_recipes/_partials/conditioning/flux1.yaml +64 -0
  10. core/pipelines/workflow_recipes/_partials/conditioning/flux2-kv.yaml +104 -0
  11. core/pipelines/workflow_recipes/_partials/conditioning/flux2.yaml +33 -6
  12. core/pipelines/workflow_recipes/_partials/conditioning/sd35.yaml +58 -0
  13. core/pipelines/workflow_recipes/_partials/input/hires_fix.yaml +4 -3
  14. core/pipelines/workflow_recipes/_partials/input/img2img.yaml +3 -2
  15. core/pipelines/workflow_recipes/_partials/input/inpaint.yaml +6 -8
  16. core/pipelines/workflow_recipes/_partials/input/outpaint.yaml +14 -11
  17. core/pipelines/workflow_recipes/_partials/input/txt2img.yaml +2 -8
  18. core/pipelines/workflow_recipes/_partials/input/txt2img_chroma_radiance_latent.yaml +11 -0
  19. core/pipelines/workflow_recipes/_partials/input/txt2img_flux2_latent.yaml +11 -0
  20. core/pipelines/workflow_recipes/_partials/input/txt2img_hunyuan_latent.yaml +11 -0
  21. core/pipelines/workflow_recipes/_partials/input/txt2img_latent.yaml +11 -0
  22. core/pipelines/workflow_recipes/_partials/input/txt2img_sd3_latent.yaml +11 -0
  23. core/pipelines/workflow_recipes/sd_unified_recipe.yaml +2 -2
  24. core/settings.py +111 -31
  25. requirements.txt +10 -9
  26. ui/events.py +1044 -286
  27. ui/layout.py +16 -62
  28. ui/shared/hires_fix_ui.py +27 -17
  29. ui/shared/img2img_ui.py +27 -18
  30. ui/shared/inpaint_ui.py +38 -20
  31. ui/shared/outpaint_ui.py +35 -22
  32. ui/shared/txt2img_ui.py +26 -11
  33. ui/shared/ui_components.py +368 -57
  34. utils/app_utils.py +203 -99
  35. yaml/constants.yaml +138 -1
  36. yaml/file_list.yaml +626 -6
  37. yaml/image_gen_features.yaml +117 -0
  38. yaml/injectors.yaml +26 -2
  39. yaml/model_architectures.yaml +65 -1
  40. yaml/model_defaults.yaml +206 -22
  41. yaml/model_list.yaml +5 -16
  42. yaml/private_file_list.yaml +0 -12
app.py CHANGED
@@ -1,7 +1,6 @@
1
  import spaces
2
  import os
3
  import sys
4
- import requests
5
  import site
6
 
7
  APP_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -45,106 +44,14 @@ def dummy_gpu_for_startup():
45
  print("--- [GPU Startup] Startup check passed. ---")
46
  return "Startup check passed."
47
 
48
- def handle_private_downloads():
49
- """
50
- Checks for a private_file_list.yaml, downloads required models using HF_TOKEN,
51
- and then clears the token from the environment.
52
- """
53
- import yaml
54
- from huggingface_hub import hf_hub_download
55
- from core.settings import (
56
- DIFFUSION_MODELS_DIR, TEXT_ENCODERS_DIR, VAE_DIR, CHECKPOINT_DIR,
57
- LORA_DIR, CONTROLNET_DIR, MODEL_PATCHES_DIR, EMBEDDING_DIR
58
- )
59
-
60
- print("--- [Startup] Checking for private models to download... ---")
61
- private_list_path = os.path.join(APP_DIR, 'yaml', 'private_file_list.yaml')
62
-
63
- if not os.path.exists(private_list_path):
64
- print("--- [Startup] No private model list found. Skipping. ---")
65
- if 'HF_TOKEN' in os.environ:
66
- del os.environ['HF_TOKEN']
67
- print("--- [Startup] Cleared HF_TOKEN environment variable as it is no longer needed. ---")
68
- print(f"--- [Startup] Verifying HF_TOKEN after clearing: {os.environ.get('HF_TOKEN')}")
69
- return
70
-
71
- try:
72
- with open(private_list_path, 'r', encoding='utf-8') as f:
73
- private_files_config = yaml.safe_load(f)
74
-
75
- if not private_files_config or 'file' not in private_files_config:
76
- print("--- [Startup] Private model list is empty or malformed. Skipping. ---")
77
- return
78
-
79
- category_to_dir_map = {
80
- "diffusion_models": DIFFUSION_MODELS_DIR,
81
- "text_encoders": TEXT_ENCODERS_DIR,
82
- "vae": VAE_DIR,
83
- "checkpoints": CHECKPOINT_DIR,
84
- "loras": LORA_DIR,
85
- "controlnet": CONTROLNET_DIR,
86
- "model_patches": MODEL_PATCHES_DIR,
87
- "embeddings": EMBEDDING_DIR,
88
- }
89
-
90
- files_to_download = []
91
- for category, files in private_files_config.get('file', {}).items():
92
- dest_dir = category_to_dir_map.get(category)
93
- if not dest_dir:
94
- print(f"--- [Startup] ⚠️ Unknown category '{category}' in private_file_list.yaml. Skipping. ---")
95
- continue
96
-
97
- if isinstance(files, list):
98
- for file_info in files:
99
- files_to_download.append((file_info, dest_dir))
100
-
101
- if not files_to_download:
102
- print("--- [Startup] No private models configured for download. ---")
103
- return
104
-
105
- print(f"--- [Startup] Found {len(files_to_download)} private model(s) to download. Using HF_TOKEN if available. ---")
106
-
107
- for file_info, dest_dir in files_to_download:
108
- filename = file_info.get("filename")
109
- repo_id = file_info.get("repo_id")
110
- repo_path = file_info.get("repository_file_path", filename)
111
-
112
- if not all([filename, repo_id]):
113
- print(f"--- [Startup] ⚠️ Skipping malformed entry in private_file_list.yaml: {file_info} ---")
114
- continue
115
-
116
- dest_path = os.path.join(dest_dir, filename)
117
- if os.path.lexists(dest_path):
118
- print(f"--- [Startup] ✅ Model '{filename}' already exists. Skipping download. ---")
119
- continue
120
-
121
- print(f"--- [Startup] ⏳ Downloading '{filename}' from repo '{repo_id}'... ---")
122
- try:
123
- cached_path = hf_hub_download(repo_id=repo_id, filename=repo_path)
124
- os.makedirs(dest_dir, exist_ok=True)
125
- os.symlink(cached_path, dest_path)
126
- print(f"--- [Startup] ✅ Successfully downloaded and linked '{filename}'. ---")
127
- except Exception as e:
128
- print(f"--- [Startup] ❌ ERROR: Failed to download '{filename}': {e}")
129
- print("--- [Startup] ❌ Please ensure your HF_TOKEN is set correctly and has access to the repository. ---")
130
-
131
- finally:
132
- if 'HF_TOKEN' in os.environ:
133
- del os.environ['HF_TOKEN']
134
- print("--- [Startup] ✅ Cleared HF_TOKEN environment variable. ---")
135
- print(f"--- [Startup] Verifying HF_TOKEN after clearing: {os.environ.get('HF_TOKEN')}")
136
- else:
137
- print("--- [Startup] Note: HF_TOKEN environment variable was not set. Private downloads may fail without it. ---")
138
 
139
  def main():
140
  from utils.app_utils import print_welcome_message
141
  from scripts import build_sage_attention
 
142
 
143
  print_welcome_message()
144
 
145
- # Handle downloads that require authentication first.
146
- handle_private_downloads()
147
-
148
  print("--- [Setup] Attempting to build and install SageAttention... ---")
149
  try:
150
  build_sage_attention.install_sage_attention()
@@ -152,7 +59,9 @@ def main():
152
  except Exception as e:
153
  print(f"--- [Setup] ❌ SageAttention installation failed: {e}. Continuing with default attention. ---")
154
 
155
-
 
 
156
  print("--- [Setup] Reloading site-packages to detect newly installed packages... ---")
157
  try:
158
  site.main()
@@ -160,52 +69,20 @@ def main():
160
  except Exception as e:
161
  print(f"--- [Setup] ⚠️ Warning: Could not fully reload site-packages: {e} ---")
162
 
163
- from comfy_integration import setup as setup_comfyui
164
- from utils.app_utils import (
165
- build_preprocessor_model_map,
166
- build_preprocessor_parameter_map
167
- )
168
- from core import shared_state
169
- from core.settings import ALL_MODEL_MAP, ALL_FILE_DOWNLOAD_MAP
170
-
171
- def check_all_model_urls_on_startup():
172
- print("--- [Setup] Checking all model URL validity (one-time check) ---")
173
- for display_name, model_info in ALL_MODEL_MAP.items():
174
- _, components, _, _ = model_info
175
- if not components: continue
176
-
177
- for filename in components.values():
178
- download_info = ALL_FILE_DOWNLOAD_MAP.get(filename, {})
179
- repo_id = download_info.get('repo_id')
180
- if not repo_id: continue
181
-
182
- repo_file_path = download_info.get('repository_file_path', filename)
183
- url = f"https://huggingface.co/{repo_id}/resolve/main/{repo_file_path}"
184
-
185
- try:
186
- response = requests.head(url, timeout=5, allow_redirects=True)
187
- if response.status_code >= 400:
188
- print(f"❌ Invalid URL for '{display_name}' component '{filename}': {url} (Status: {response.status_code})")
189
- shared_state.INVALID_MODEL_URLS[display_name] = True
190
- break
191
- except requests.RequestException as e:
192
- print(f"❌ URL check failed for '{display_name}' component '{filename}': {e}")
193
- shared_state.INVALID_MODEL_URLS[display_name] = True
194
- break
195
- print("--- [Setup] ✅ Finished checking model URLs. ---")
196
 
197
  print("--- Starting Application Setup ---")
198
 
199
- setup_comfyui.initialize_comfyui()
 
 
200
 
201
- check_all_model_urls_on_startup()
202
-
203
- print("--- Building ControlNet preprocessor maps ---")
204
- from core.generation_logic import build_reverse_map
205
- build_reverse_map()
206
- build_preprocessor_model_map()
207
- build_preprocessor_parameter_map()
208
- print("--- ✅ ControlNet preprocessor setup complete. ---")
209
 
210
  print("--- Environment configured. Proceeding with module imports. ---")
211
  from ui.layout import build_ui
 
1
  import spaces
2
  import os
3
  import sys
 
4
  import site
5
 
6
  APP_DIR = os.path.dirname(os.path.abspath(__file__))
 
44
  print("--- [GPU Startup] Startup check passed. ---")
45
  return "Startup check passed."
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  def main():
49
  from utils.app_utils import print_welcome_message
50
  from scripts import build_sage_attention
51
+ from comfy_integration import setup as setup_comfyui
52
 
53
  print_welcome_message()
54
 
 
 
 
55
  print("--- [Setup] Attempting to build and install SageAttention... ---")
56
  try:
57
  build_sage_attention.install_sage_attention()
 
59
  except Exception as e:
60
  print(f"--- [Setup] ❌ SageAttention installation failed: {e}. Continuing with default attention. ---")
61
 
62
+ print("--- [Setup] Starting ComfyUI initialization ---")
63
+ setup_comfyui.initialize_comfyui()
64
+
65
  print("--- [Setup] Reloading site-packages to detect newly installed packages... ---")
66
  try:
67
  site.main()
 
69
  except Exception as e:
70
  print(f"--- [Setup] ⚠️ Warning: Could not fully reload site-packages: {e} ---")
71
 
72
+ print("--- Initiating GPU Startup Check & SageAttention Patch ---")
73
+ try:
74
+ dummy_gpu_for_startup()
75
+ except Exception as e:
76
+ print(f"--- [GPU Startup] ⚠️ Warning: Startup check failed: {e} ---")
77
+
78
+ from utils.app_utils import load_ipadapter_presets
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  print("--- Starting Application Setup ---")
81
 
82
+ print("--- Loading IPAdapter presets ---")
83
+ load_ipadapter_presets()
84
+ print("--- ✅ IPAdapter setup complete. ---")
85
 
 
 
 
 
 
 
 
 
86
 
87
  print("--- Environment configured. Proceeding with module imports. ---")
88
  from ui.layout import build_ui
comfy_integration/nodes.py CHANGED
@@ -23,6 +23,11 @@ CLIPTextEncodeSDXL = NODE_CLASS_MAPPINGS['CLIPTextEncodeSDXL']
23
  LoraLoader = NODE_CLASS_MAPPINGS['LoraLoader']
24
  CLIPSetLastLayer = NODE_CLASS_MAPPINGS['CLIPSetLastLayer']
25
 
 
 
 
 
 
26
  try:
27
  KSamplerNode = NODE_CLASS_MAPPINGS['KSampler']
28
  SAMPLER_CHOICES = KSamplerNode.INPUT_TYPES()["required"]["sampler_name"][0]
 
23
  LoraLoader = NODE_CLASS_MAPPINGS['LoraLoader']
24
  CLIPSetLastLayer = NODE_CLASS_MAPPINGS['CLIPSetLastLayer']
25
 
26
+ if 'EmptyHunyuanImageLatent' in NODE_CLASS_MAPPINGS:
27
+ EmptyHunyuanImageLatent = NODE_CLASS_MAPPINGS['EmptyHunyuanImageLatent']
28
+ else:
29
+ print("⚠️ Warning: 'EmptyHunyuanImageLatent' not found in NODE_CLASS_MAPPINGS. HunyuanImage txt2img may fail if this node is required.")
30
+
31
  try:
32
  KSamplerNode = NODE_CLASS_MAPPINGS['KSampler']
33
  SAMPLER_CHOICES = KSamplerNode.INPUT_TYPES()["required"]["sampler_name"][0]
comfy_integration/setup.py CHANGED
@@ -39,14 +39,40 @@ def initialize_comfyui():
39
  except OSError as e:
40
  print(f"⚠️ Could not remove temporary directory '{COMFYUI_TEMP_DIR}': {e}")
41
 
 
42
  print("--- Cloning third-party extensions for ComfyUI ---")
43
- controlnet_aux_path = os.path.join(APP_DIR, "custom_nodes", "comfyui_controlnet_aux")
44
- if not os.path.exists(controlnet_aux_path):
45
- os.system(f"git clone https://github.com/Fannovel16/comfyui_controlnet_aux.git {controlnet_aux_path}")
46
- print("✅ comfyui_controlnet_aux extension cloned.")
 
 
47
  else:
48
- print("✅ comfyui_controlnet_aux extension already exists.")
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  print(f"✅ Current working directory is: {os.getcwd()}")
52
 
@@ -55,13 +81,10 @@ def initialize_comfyui():
55
 
56
  print("✅ ComfyUI initialized with default attention mechanism.")
57
 
58
- os.makedirs(os.path.join(APP_DIR, CHECKPOINT_DIR), exist_ok=True)
59
- os.makedirs(os.path.join(APP_DIR, LORA_DIR), exist_ok=True)
60
- os.makedirs(os.path.join(APP_DIR, EMBEDDING_DIR), exist_ok=True)
61
- os.makedirs(os.path.join(APP_DIR, CONTROLNET_DIR), exist_ok=True)
62
- os.makedirs(os.path.join(APP_DIR, MODEL_PATCHES_DIR), exist_ok=True)
63
- os.makedirs(os.path.join(APP_DIR, DIFFUSION_MODELS_DIR), exist_ok=True)
64
- os.makedirs(os.path.join(APP_DIR, VAE_DIR), exist_ok=True)
65
- os.makedirs(os.path.join(APP_DIR, TEXT_ENCODERS_DIR), exist_ok=True)
66
  os.makedirs(os.path.join(APP_DIR, INPUT_DIR), exist_ok=True)
 
 
67
  print("✅ All required model directories are present.")
 
39
  except OSError as e:
40
  print(f"⚠️ Could not remove temporary directory '{COMFYUI_TEMP_DIR}': {e}")
41
 
42
+
43
  print("--- Cloning third-party extensions for ComfyUI ---")
44
+
45
+ # 1. ComfyUI_IPAdapter_plus
46
+ ipadapter_plus_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI_IPAdapter_plus")
47
+ if not os.path.exists(ipadapter_plus_path):
48
+ os.system(f"git clone https://github.com/cubiq/ComfyUI_IPAdapter_plus.git {ipadapter_plus_path}")
49
+ print("✅ ComfyUI_IPAdapter_plus extension cloned.")
50
  else:
51
+ print("✅ ComfyUI_IPAdapter_plus extension already exists.")
52
 
53
+ # 2. ComfyUI-InstantX-IPAdapter-SD3
54
+ ipadapter_plus_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-InstantX-IPAdapter-SD3")
55
+ if not os.path.exists(ipadapter_plus_path):
56
+ os.system(f"git clone https://github.com/Slickytail/ComfyUI-InstantX-IPAdapter-SD3.git {ipadapter_plus_path}")
57
+ print("✅ ComfyUI-InstantX-IPAdapter-SD3 extension cloned.")
58
+ else:
59
+ print("✅ ComfyUI-InstantX-IPAdapter-SD3 extension already exists.")
60
+
61
+ # 3. ComfyUI-IPAdapter-Flux
62
+ ipadapter_flux_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-IPAdapter-Flux")
63
+ if not os.path.exists(ipadapter_flux_path):
64
+ os.system(f"git clone https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux.git {ipadapter_flux_path}")
65
+ print("✅ ComfyUI-IPAdapter-Flux extension cloned.")
66
+ else:
67
+ print("✅ ComfyUI-IPAdapter-Flux extension already exists.")
68
+
69
+ # 4. ComfyUI-Newbie-Nodes
70
+ newbie_nodes_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-Newbie-Nodes")
71
+ if not os.path.exists(newbie_nodes_path):
72
+ os.system(f"git clone https://github.com/NewBieAI-Lab/ComfyUI-Newbie-Nodes.git {newbie_nodes_path}")
73
+ print("✅ ComfyUI-Newbie-Nodes extension cloned.")
74
+ else:
75
+ print("✅ ComfyUI-Newbie-Nodes extension already exists.")
76
 
77
  print(f"✅ Current working directory is: {os.getcwd()}")
78
 
 
81
 
82
  print("✅ ComfyUI initialized with default attention mechanism.")
83
 
84
+ for dir_path in CATEGORY_TO_DIR_MAP.values():
85
+ os.makedirs(os.path.join(APP_DIR, dir_path), exist_ok=True)
86
+
 
 
 
 
 
87
  os.makedirs(os.path.join(APP_DIR, INPUT_DIR), exist_ok=True)
88
+ os.makedirs(os.path.join(APP_DIR, OUTPUT_DIR), exist_ok=True)
89
+
90
  print("✅ All required model directories are present.")
core/generation_logic.py CHANGED
@@ -1,25 +1,10 @@
1
  from typing import Any, Dict
2
  import gradio as gr
3
 
4
- from core.pipelines.controlnet_preprocessor import ControlNetPreprocessorPipeline
5
  from core.pipelines.sd_image_pipeline import SdImagePipeline
6
 
7
- controlnet_preprocessor_pipeline = ControlNetPreprocessorPipeline()
8
  sd_image_pipeline = SdImagePipeline()
9
 
10
 
11
- def build_reverse_map():
12
- from nodes import NODE_DISPLAY_NAME_MAPPINGS
13
- import core.pipelines.controlnet_preprocessor as cn_module
14
-
15
- if cn_module.REVERSE_DISPLAY_NAME_MAP is None:
16
- cn_module.REVERSE_DISPLAY_NAME_MAP = {v: k for k, v in NODE_DISPLAY_NAME_MAPPINGS.items()}
17
- if "Semantic Segmentor (legacy, alias for UniFormer)" not in cn_module.REVERSE_DISPLAY_NAME_MAP:
18
- cn_module.REVERSE_DISPLAY_NAME_MAP["Semantic Segmentor (legacy, alias for UniFormer)"] = "SemSegPreprocessor"
19
-
20
-
21
- def run_cn_preprocessor_entry(*args, **kwargs):
22
- return controlnet_preprocessor_pipeline.run(*args, **kwargs)
23
-
24
  def generate_image_wrapper(ui_inputs: dict, progress=gr.Progress(track_tqdm=True)):
25
  return sd_image_pipeline.run(ui_inputs=ui_inputs, progress=progress)
 
1
  from typing import Any, Dict
2
  import gradio as gr
3
 
 
4
  from core.pipelines.sd_image_pipeline import SdImagePipeline
5
 
 
6
  sd_image_pipeline = SdImagePipeline()
7
 
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  def generate_image_wrapper(ui_inputs: dict, progress=gr.Progress(track_tqdm=True)):
10
  return sd_image_pipeline.run(ui_inputs=ui_inputs, progress=progress)
core/model_manager.py CHANGED
@@ -1,9 +1,8 @@
1
  import gc
2
  from typing import List
3
  import gradio as gr
4
-
5
- from core.settings import ALL_MODEL_MAP
6
  from utils.app_utils import _ensure_model_downloaded
 
7
 
8
  class ModelManager:
9
  _instance = None
@@ -21,25 +20,13 @@ class ModelManager:
21
 
22
  def ensure_models_downloaded(self, required_models: List[str], progress):
23
  print(f"--- [ModelManager] Ensuring models are downloaded: {required_models} ---")
24
-
25
- files_to_download = set()
26
- for display_name in required_models:
27
- if display_name in ALL_MODEL_MAP:
28
- _, components, _, _ = ALL_MODEL_MAP[display_name]
29
- for component_file in components.values():
30
- files_to_download.add(component_file)
31
-
32
- files_to_download = list(files_to_download)
33
- total_files = len(files_to_download)
34
-
35
- for i, filename in enumerate(files_to_download):
36
  if progress and hasattr(progress, '__call__'):
37
- progress(i / total_files if total_files > 0 else 0, desc=f"Checking file: {filename}")
38
  try:
39
- _ensure_model_downloaded(filename, progress)
40
  except Exception as e:
41
- raise gr.Error(f"Failed to download model component '{filename}'. Reason: {e}")
42
-
43
  print(f"--- [ModelManager] ✅ All required models are present on disk. ---")
44
-
45
  model_manager = ModelManager()
 
1
  import gc
2
  from typing import List
3
  import gradio as gr
 
 
4
  from utils.app_utils import _ensure_model_downloaded
5
+ from core.settings import ALL_MODEL_MAP
6
 
7
  class ModelManager:
8
  _instance = None
 
20
 
21
  def ensure_models_downloaded(self, required_models: List[str], progress):
22
  print(f"--- [ModelManager] Ensuring models are downloaded: {required_models} ---")
23
+ for i, display_name in enumerate(required_models):
 
 
 
 
 
 
 
 
 
 
 
24
  if progress and hasattr(progress, '__call__'):
25
+ progress(i / max(len(required_models), 1), desc=f"Checking file: {display_name}")
26
  try:
27
+ _ensure_model_downloaded(display_name, progress)
28
  except Exception as e:
29
+ raise gr.Error(f"Failed to download model '{display_name}'. Reason: {e}")
 
30
  print(f"--- [ModelManager] ✅ All required models are present on disk. ---")
31
+
32
  model_manager = ModelManager()
core/pipelines/controlnet_preprocessor.py DELETED
@@ -1,143 +0,0 @@
1
- from typing import Dict, Any, List
2
- import imageio
3
- import tempfile
4
- import numpy as np
5
- import torch
6
- import gradio as gr
7
- from PIL import Image
8
- import spaces
9
-
10
- from .base_pipeline import BasePipeline
11
- from comfy_integration.nodes import NODE_CLASS_MAPPINGS
12
- from nodes import NODE_DISPLAY_NAME_MAPPINGS
13
- from utils.app_utils import get_value_at_index
14
-
15
- REVERSE_DISPLAY_NAME_MAP = None
16
- CPU_ONLY_PREPROCESSORS = {
17
- "Binary Lines", "Canny Edge", "Color Pallete", "Fake Scribble Lines (aka scribble_hed)",
18
- "Image Intensity", "Image Luminance", "Inpaint Preprocessor", "PyraCanny", "Scribble Lines",
19
- "Scribble XDoG Lines", "Standard Lineart", "Content Shuffle", "Tile"
20
- }
21
-
22
- def run_node_by_function_name(node_instance: Any, **kwargs) -> Any:
23
- node_class = type(node_instance)
24
- function_name = getattr(node_class, 'FUNCTION', None)
25
- if not function_name:
26
- raise AttributeError(f"Node class '{node_class.__name__}' is missing the required 'FUNCTION' attribute.")
27
- execution_method = getattr(node_instance, function_name, None)
28
- if not callable(execution_method):
29
- raise AttributeError(f"Method '{function_name}' not found or not callable on node '{node_class.__name__}'.")
30
- return execution_method(**kwargs)
31
-
32
- class ControlNetPreprocessorPipeline(BasePipeline):
33
- def get_required_models(self, **kwargs) -> List[str]:
34
- return []
35
-
36
- def _gpu_logic(
37
- self, pil_images: List[Image.Image], preprocessor_name: str, model_name: str,
38
- params: Dict[str, Any], progress=gr.Progress(track_tqdm=True)
39
- ) -> List[Image.Image]:
40
- global REVERSE_DISPLAY_NAME_MAP
41
- if REVERSE_DISPLAY_NAME_MAP is None:
42
- raise RuntimeError("REVERSE_DISPLAY_NAME_MAP has not been initialized. `build_reverse_map` must be called on startup.")
43
-
44
- class_name = REVERSE_DISPLAY_NAME_MAP.get(preprocessor_name)
45
- if not class_name or class_name not in NODE_CLASS_MAPPINGS:
46
- raise ValueError(f"Preprocessor '{preprocessor_name}' not found.")
47
-
48
- preprocessor_instance = NODE_CLASS_MAPPINGS[class_name]()
49
- call_args = {**params, 'ckpt_name': model_name}
50
-
51
- processed_pil_images = []
52
- total_frames = len(pil_images)
53
-
54
- for i, frame_pil in enumerate(pil_images):
55
- progress(i / total_frames, desc=f"Processing frame {i+1}/{total_frames} with {preprocessor_name}...")
56
-
57
- frame_tensor = torch.from_numpy(np.array(frame_pil).astype(np.float32) / 255.0).unsqueeze(0)
58
-
59
- resolution_arg = {'resolution': max(frame_tensor.shape[2], frame_tensor.shape[3])}
60
-
61
- result_tuple = run_node_by_function_name(
62
- preprocessor_instance,
63
- image=frame_tensor,
64
- **resolution_arg,
65
- **call_args
66
- )
67
-
68
- processed_tensor = get_value_at_index(result_tuple, 0)
69
- processed_np = (processed_tensor.squeeze(0).cpu().numpy().clip(0, 1) * 255.0).astype(np.uint8)
70
- processed_pil_images.append(Image.fromarray(processed_np))
71
-
72
- return processed_pil_images
73
-
74
- def run(self, input_type, image_input, video_input, preprocessor_name, model_name, zero_gpu_duration, *args, progress=gr.Progress(track_tqdm=True)):
75
- from utils import app_utils
76
- pil_images, is_video, fps = [], False, 30
77
-
78
- progress(0, desc="Reading input file...")
79
- if input_type == "Image":
80
- if image_input is None: raise gr.Error("Please provide an input image.")
81
- pil_images = [image_input]
82
- elif input_type == "Video":
83
- if video_input is None: raise gr.Error("Please provide an input video.")
84
- try:
85
- video_reader = imageio.get_reader(video_input)
86
- meta = video_reader.get_meta_data()
87
- fps = meta.get('fps', 30)
88
- pil_images = [Image.fromarray(frame) for frame in video_reader]
89
- is_video = True
90
- video_reader.close()
91
- except Exception as e: raise gr.Error(f"Failed to read video file: {e}")
92
- else:
93
- raise gr.Error("Invalid input type selected.")
94
-
95
- if not pil_images: raise gr.Error("Could not extract any frames from the input.")
96
-
97
- if app_utils.PREPROCESSOR_PARAMETER_MAP is None:
98
- raise RuntimeError("Preprocessor parameter map is not built. Check startup logs.")
99
-
100
- params_config = app_utils.PREPROCESSOR_PARAMETER_MAP.get(preprocessor_name, [])
101
- sliders_params = [p for p in params_config if p['type'] in ["INT", "FLOAT"]]
102
- dropdown_params = [p for p in params_config if isinstance(p['type'], list)]
103
- checkbox_params = [p for p in params_config if p['type'] == "BOOLEAN"]
104
- ordered_params_config = sliders_params + dropdown_params + checkbox_params
105
- param_names = [p['name'] for p in ordered_params_config]
106
- provided_params = {param_names[i]: args[i] for i in range(len(param_names))}
107
-
108
- if preprocessor_name not in CPU_ONLY_PREPROCESSORS:
109
- print(f"--- '{preprocessor_name}' requires GPU, requesting ZeroGPU. ---")
110
- try:
111
- processed_pil_images = self._execute_gpu_logic(
112
- self._gpu_logic,
113
- duration=zero_gpu_duration,
114
- default_duration=60,
115
- task_name=f"Preprocessor '{preprocessor_name}'",
116
- pil_images=pil_images,
117
- preprocessor_name=preprocessor_name,
118
- model_name=model_name,
119
- params=provided_params,
120
- progress=progress
121
- )
122
- except Exception as e:
123
- import traceback; traceback.print_exc()
124
- raise gr.Error(f"Failed to run preprocessor '{preprocessor_name}' on GPU: {e}")
125
- else:
126
- print(f"--- Running '{preprocessor_name}' on CPU, no ZeroGPU requested. ---")
127
- try:
128
- processed_pil_images = self._gpu_logic(pil_images, preprocessor_name, model_name, provided_params, progress=progress)
129
- except Exception as e:
130
- import traceback; traceback.print_exc()
131
- raise gr.Error(f"Failed to run preprocessor '{preprocessor_name}' on CPU: {e}")
132
-
133
- if not processed_pil_images: raise gr.Error("Processing returned no frames.")
134
-
135
- progress(0.9, desc="Finalizing output...")
136
- if is_video:
137
- frames_np = [np.array(img) for img in processed_pil_images]
138
- frames_tensor = torch.from_numpy(np.stack(frames_np)).to(torch.float32) / 255.0
139
- video_path = self._encode_video_from_frames(frames_tensor, fps, progress)
140
- return [video_path]
141
- else:
142
- progress(1.0, desc="Done!")
143
- return processed_pil_images
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/pipelines/sd_image_pipeline.py CHANGED
@@ -11,12 +11,20 @@ import numpy as np
11
  from .base_pipeline import BasePipeline
12
  from core.settings import *
13
  from comfy_integration.nodes import *
14
- from utils.app_utils import get_value_at_index, sanitize_prompt, get_lora_path, get_embedding_path, ensure_controlnet_model_downloaded, sanitize_filename
15
  from core.workflow_assembler import WorkflowAssembler
16
 
17
  class SdImagePipeline(BasePipeline):
18
  def get_required_models(self, model_display_name: str, **kwargs) -> List[str]:
19
- return [model_display_name]
 
 
 
 
 
 
 
 
20
 
21
  def _topological_sort(self, workflow: Dict[str, Any]) -> List[str]:
22
  graph = defaultdict(list)
@@ -47,7 +55,6 @@ class SdImagePipeline(BasePipeline):
47
 
48
  return sorted_nodes
49
 
50
-
51
  def _execute_workflow(self, workflow: Dict[str, Any], initial_objects: Dict[str, Any]):
52
  with torch.no_grad():
53
  computed_outputs = initial_objects
@@ -119,7 +126,7 @@ class SdImagePipeline(BasePipeline):
119
  progress(0.4, desc="Executing workflow...")
120
 
121
  initial_objects = {}
122
-
123
  decoded_images_tensor = self._execute_workflow(workflow, initial_objects=initial_objects)
124
 
125
  output_images = []
@@ -135,6 +142,7 @@ class SdImagePipeline(BasePipeline):
135
  params_string = f"{ui_inputs['positive_prompt']}\nNegative prompt: {ui_inputs['negative_prompt']}\n"
136
  params_string += f"Steps: {ui_inputs['num_inference_steps']}, Sampler: {ui_inputs['sampler']}, Scheduler: {ui_inputs['scheduler']}, CFG scale: {ui_inputs['guidance_scale']}, Seed: {current_seed}, Size: {width_for_meta}x{height_for_meta}, Base Model: {model_display_name}"
137
  if ui_inputs['task_type'] != 'txt2img': params_string += f", Denoise: {ui_inputs['denoise']}"
 
138
  if loras_string: params_string += f", {loras_string}"
139
 
140
  pil_image.info = {'parameters': params_string.strip()}
@@ -146,39 +154,46 @@ class SdImagePipeline(BasePipeline):
146
  progress(0, desc="Preparing models...")
147
 
148
  task_type = ui_inputs['task_type']
 
 
 
 
 
149
 
150
  ui_inputs['positive_prompt'] = sanitize_prompt(ui_inputs.get('positive_prompt', ''))
151
  ui_inputs['negative_prompt'] = sanitize_prompt(ui_inputs.get('negative_prompt', ''))
152
 
153
- required_models = self.get_required_models(model_display_name=ui_inputs['model_display_name'])
154
-
 
 
 
 
155
  self.model_manager.ensure_models_downloaded(required_models, progress=progress)
156
 
157
  lora_data = ui_inputs.get('lora_data', [])
158
  active_loras_for_gpu, active_loras_for_meta = [], []
159
  if lora_data:
160
  sources, ids, scales, files = lora_data[0::4], lora_data[1::4], lora_data[2::4], lora_data[3::4]
161
-
162
  for i, (source, lora_id, scale, _) in enumerate(zip(sources, ids, scales, files)):
163
  if scale > 0 and lora_id and lora_id.strip():
164
  lora_filename = None
165
  if source == "File":
166
  lora_filename = sanitize_filename(lora_id)
167
  elif source == "Civitai":
168
- local_path, status = get_lora_path(source, lora_id, ui_inputs['civitai_api_key'], progress)
169
  if local_path: lora_filename = os.path.basename(local_path)
170
  else: raise gr.Error(f"Failed to prepare LoRA {lora_id}: {status}")
171
 
172
  if lora_filename:
173
  active_loras_for_gpu.append({"lora_name": lora_filename, "strength_model": scale, "strength_clip": scale})
174
  active_loras_for_meta.append(f"{source} {lora_id}:{scale}")
175
-
176
  ui_inputs['denoise'] = 1.0
177
  if task_type == 'img2img': ui_inputs['denoise'] = ui_inputs.get('img2img_denoise', 0.7)
178
  elif task_type == 'hires_fix': ui_inputs['denoise'] = ui_inputs.get('hires_denoise', 0.55)
179
 
180
  temp_files_to_clean = []
181
-
182
  if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
183
 
184
  if task_type == 'img2img':
@@ -197,7 +212,6 @@ class SdImagePipeline(BasePipeline):
197
  raise gr.Error("Inpainting requires an input image and a drawn mask.")
198
 
199
  background_img = inpaint_dict['background'].convert("RGBA")
200
-
201
  composite_mask_pil = Image.new('L', background_img.size, 0)
202
  for layer in inpaint_dict['layers']:
203
  if layer:
@@ -211,7 +225,7 @@ class SdImagePipeline(BasePipeline):
211
  temp_file_path = os.path.join(INPUT_DIR, f"temp_inpaint_composite_{random.randint(1000, 9999)}.png")
212
  composite_image_with_mask.save(temp_file_path, "PNG")
213
 
214
- ui_inputs['inpaint_image'] = os.path.basename(temp_file_path)
215
  temp_files_to_clean.append(temp_file_path)
216
  ui_inputs.pop('inpaint_mask', None)
217
 
@@ -222,6 +236,9 @@ class SdImagePipeline(BasePipeline):
222
  input_image_pil.save(temp_file_path, "PNG")
223
  ui_inputs['input_image'] = os.path.basename(temp_file_path)
224
  temp_files_to_clean.append(temp_file_path)
 
 
 
225
 
226
  elif task_type == 'hires_fix':
227
  input_image_pil = ui_inputs.get('hires_image')
@@ -241,7 +258,7 @@ class SdImagePipeline(BasePipeline):
241
  if source == "File":
242
  emb_filename = sanitize_filename(emb_id)
243
  elif source == "Civitai":
244
- local_path, status = get_embedding_path(source, emb_id, ui_inputs['civitai_api_key'], progress)
245
  if local_path: emb_filename = os.path.basename(local_path)
246
  else: raise gr.Error(f"Failed to prepare Embedding {emb_id}: {status}")
247
 
@@ -255,20 +272,162 @@ class SdImagePipeline(BasePipeline):
255
  else:
256
  ui_inputs['positive_prompt'] = embedding_prompt_text
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  from utils.app_utils import get_vae_path
259
  vae_source = ui_inputs.get('vae_source')
260
  vae_id = ui_inputs.get('vae_id')
261
- vae_file = ui_inputs.get('vae_file')
262
  vae_name_override = None
263
-
264
  if vae_source and vae_source != "None":
265
  if vae_source == "File":
266
  vae_name_override = sanitize_filename(vae_id)
267
  elif vae_source == "Civitai" and vae_id and vae_id.strip():
268
- local_path, status = get_vae_path(vae_source, vae_id, ui_inputs.get('civitai_api_key'), progress)
269
  if local_path: vae_name_override = os.path.basename(local_path)
270
  else: raise gr.Error(f"Failed to prepare VAE {vae_id}: {status}")
271
-
272
  if vae_name_override:
273
  ui_inputs['vae_name'] = vae_name_override
274
 
@@ -276,78 +435,84 @@ class SdImagePipeline(BasePipeline):
276
  active_conditioning = []
277
  if conditioning_data:
278
  num_units = len(conditioning_data) // 6
279
- prompts = conditioning_data[0*num_units : 1*num_units]
280
- widths = conditioning_data[1*num_units : 2*num_units]
281
- heights = conditioning_data[2*num_units : 3*num_units]
282
- xs = conditioning_data[3*num_units : 4*num_units]
283
- ys = conditioning_data[4*num_units : 5*num_units]
284
- strengths = conditioning_data[5*num_units : 6*num_units]
285
-
286
  for i in range(num_units):
287
  if prompts[i] and prompts[i].strip():
288
  active_conditioning.append({
289
- "prompt": prompts[i],
290
- "width": int(widths[i]),
291
- "height": int(heights[i]),
292
- "x": int(xs[i]),
293
- "y": int(ys[i]),
294
- "strength": float(strengths[i])
295
  })
296
 
297
- reference_latent_data = ui_inputs.get('reference_latent_data', [])
298
- active_reference_latents = []
299
- if reference_latent_data:
300
- for img_pil in reference_latent_data:
301
- if img_pil is not None:
302
- temp_file_path = os.path.join(INPUT_DIR, f"temp_ref_{random.randint(1000, 9999)}.png")
303
- img_pil.save(temp_file_path, "PNG")
304
- active_reference_latents.append(os.path.basename(temp_file_path))
305
- temp_files_to_clean.append(temp_file_path)
306
-
307
  loras_string = f"LoRAs: [{', '.join(active_loras_for_meta)}]" if active_loras_for_meta else ""
308
 
309
  progress(0.8, desc="Assembling workflow...")
310
 
311
  if ui_inputs.get('seed') == -1:
312
  ui_inputs['seed'] = random.randint(0, 2**32 - 1)
 
 
 
 
 
 
 
 
 
 
 
313
 
314
- dynamic_values = {'task_type': ui_inputs['task_type']}
 
 
 
 
 
315
 
316
  recipe_path = os.path.join(os.path.dirname(__file__), "workflow_recipes", "sd_unified_recipe.yaml")
317
  assembler = WorkflowAssembler(recipe_path, dynamic_values=dynamic_values)
318
 
319
- model_display_name = ui_inputs['model_display_name']
320
- if model_display_name not in ALL_MODEL_MAP:
321
- raise gr.Error(f"Model '{model_display_name}' is not configured in model_list.yaml.")
322
-
323
- _, components, _, _ = ALL_MODEL_MAP[model_display_name]
324
-
325
  workflow_inputs = {
 
326
  "positive_prompt": ui_inputs['positive_prompt'], "negative_prompt": ui_inputs['negative_prompt'],
327
  "seed": ui_inputs['seed'], "steps": ui_inputs['num_inference_steps'], "cfg": ui_inputs['guidance_scale'],
328
  "sampler_name": ui_inputs['sampler'], "scheduler": ui_inputs['scheduler'],
329
  "batch_size": ui_inputs['batch_size'],
330
- "denoise": ui_inputs['denoise'],
331
- "input_image": ui_inputs.get('input_image'),
332
- "inpaint_image": ui_inputs.get('inpaint_image'),
333
- "inpaint_mask": ui_inputs.get('inpaint_mask'),
334
- "left": ui_inputs.get('outpaint_left'), "top": ui_inputs.get('outpaint_top'),
335
- "right": ui_inputs.get('outpaint_right'), "bottom": ui_inputs.get('outpaint_bottom'),
336
- "hires_upscaler": ui_inputs.get('hires_upscaler'), "hires_scale_by": ui_inputs.get('hires_scale_by'),
337
- "unet_name": components['unet'],
338
- "clip_name": components['clip'],
339
- "vae_name": ui_inputs.get('vae_name', components['vae']),
340
  "lora_chain": active_loras_for_gpu,
 
 
 
 
 
 
341
  "conditioning_chain": active_conditioning,
342
  "reference_latent_chain": active_reference_latents,
 
343
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
  if task_type == 'txt2img':
346
  workflow_inputs['width'] = ui_inputs['width']
347
  workflow_inputs['height'] = ui_inputs['height']
348
 
349
  workflow = assembler.assemble(workflow_inputs)
350
-
351
  progress(1.0, desc="All models ready. Requesting GPU for generation...")
352
 
353
  try:
@@ -362,7 +527,7 @@ class SdImagePipeline(BasePipeline):
362
  assembler=assembler,
363
  progress=progress
364
  )
365
-
366
  import json
367
  import glob
368
  from PIL import PngImagePlugin
 
11
  from .base_pipeline import BasePipeline
12
  from core.settings import *
13
  from comfy_integration.nodes import *
14
+ from utils.app_utils import get_value_at_index, sanitize_prompt, get_lora_path, get_embedding_path, ensure_controlnet_model_downloaded, ensure_ipadapter_models_downloaded, sanitize_filename
15
  from core.workflow_assembler import WorkflowAssembler
16
 
17
  class SdImagePipeline(BasePipeline):
18
  def get_required_models(self, model_display_name: str, **kwargs) -> List[str]:
19
+ model_info = ALL_MODEL_MAP.get(model_display_name)
20
+ if not model_info:
21
+ return [model_display_name]
22
+
23
+ path_or_components = model_info[1]
24
+ if isinstance(path_or_components, dict):
25
+ return [v for v in path_or_components.values() if v and v != "pixel_space"]
26
+ else:
27
+ return [model_display_name]
28
 
29
  def _topological_sort(self, workflow: Dict[str, Any]) -> List[str]:
30
  graph = defaultdict(list)
 
55
 
56
  return sorted_nodes
57
 
 
58
  def _execute_workflow(self, workflow: Dict[str, Any], initial_objects: Dict[str, Any]):
59
  with torch.no_grad():
60
  computed_outputs = initial_objects
 
126
  progress(0.4, desc="Executing workflow...")
127
 
128
  initial_objects = {}
129
+
130
  decoded_images_tensor = self._execute_workflow(workflow, initial_objects=initial_objects)
131
 
132
  output_images = []
 
142
  params_string = f"{ui_inputs['positive_prompt']}\nNegative prompt: {ui_inputs['negative_prompt']}\n"
143
  params_string += f"Steps: {ui_inputs['num_inference_steps']}, Sampler: {ui_inputs['sampler']}, Scheduler: {ui_inputs['scheduler']}, CFG scale: {ui_inputs['guidance_scale']}, Seed: {current_seed}, Size: {width_for_meta}x{height_for_meta}, Base Model: {model_display_name}"
144
  if ui_inputs['task_type'] != 'txt2img': params_string += f", Denoise: {ui_inputs['denoise']}"
145
+ if ui_inputs.get('clip_skip') and ui_inputs['clip_skip'] != 1: params_string += f", Clip skip: {abs(ui_inputs['clip_skip'])}"
146
  if loras_string: params_string += f", {loras_string}"
147
 
148
  pil_image.info = {'parameters': params_string.strip()}
 
154
  progress(0, desc="Preparing models...")
155
 
156
  task_type = ui_inputs['task_type']
157
+ model_display_name = ui_inputs['model_display_name']
158
+ model_type = MODEL_TYPE_MAP.get(model_display_name, 'sdxl')
159
+
160
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
161
+ workflow_model_type = architectures_dict.get(model_type, {}).get("model_type", "sdxl")
162
 
163
  ui_inputs['positive_prompt'] = sanitize_prompt(ui_inputs.get('positive_prompt', ''))
164
  ui_inputs['negative_prompt'] = sanitize_prompt(ui_inputs.get('negative_prompt', ''))
165
 
166
+ if 'clip_skip' in ui_inputs and ui_inputs['clip_skip'] is not None:
167
+ ui_inputs['clip_skip'] = -int(ui_inputs['clip_skip'])
168
+ else:
169
+ ui_inputs['clip_skip'] = -1
170
+
171
+ required_models = self.get_required_models(model_display_name=model_display_name)
172
  self.model_manager.ensure_models_downloaded(required_models, progress=progress)
173
 
174
  lora_data = ui_inputs.get('lora_data', [])
175
  active_loras_for_gpu, active_loras_for_meta = [], []
176
  if lora_data:
177
  sources, ids, scales, files = lora_data[0::4], lora_data[1::4], lora_data[2::4], lora_data[3::4]
 
178
  for i, (source, lora_id, scale, _) in enumerate(zip(sources, ids, scales, files)):
179
  if scale > 0 and lora_id and lora_id.strip():
180
  lora_filename = None
181
  if source == "File":
182
  lora_filename = sanitize_filename(lora_id)
183
  elif source == "Civitai":
184
+ local_path, status = get_lora_path(source, lora_id, os.environ.get("CIVITAI_API_KEY", ""), progress)
185
  if local_path: lora_filename = os.path.basename(local_path)
186
  else: raise gr.Error(f"Failed to prepare LoRA {lora_id}: {status}")
187
 
188
  if lora_filename:
189
  active_loras_for_gpu.append({"lora_name": lora_filename, "strength_model": scale, "strength_clip": scale})
190
  active_loras_for_meta.append(f"{source} {lora_id}:{scale}")
191
+
192
  ui_inputs['denoise'] = 1.0
193
  if task_type == 'img2img': ui_inputs['denoise'] = ui_inputs.get('img2img_denoise', 0.7)
194
  elif task_type == 'hires_fix': ui_inputs['denoise'] = ui_inputs.get('hires_denoise', 0.55)
195
 
196
  temp_files_to_clean = []
 
197
  if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
198
 
199
  if task_type == 'img2img':
 
212
  raise gr.Error("Inpainting requires an input image and a drawn mask.")
213
 
214
  background_img = inpaint_dict['background'].convert("RGBA")
 
215
  composite_mask_pil = Image.new('L', background_img.size, 0)
216
  for layer in inpaint_dict['layers']:
217
  if layer:
 
225
  temp_file_path = os.path.join(INPUT_DIR, f"temp_inpaint_composite_{random.randint(1000, 9999)}.png")
226
  composite_image_with_mask.save(temp_file_path, "PNG")
227
 
228
+ ui_inputs['input_image'] = os.path.basename(temp_file_path)
229
  temp_files_to_clean.append(temp_file_path)
230
  ui_inputs.pop('inpaint_mask', None)
231
 
 
236
  input_image_pil.save(temp_file_path, "PNG")
237
  ui_inputs['input_image'] = os.path.basename(temp_file_path)
238
  temp_files_to_clean.append(temp_file_path)
239
+
240
+ ui_inputs['megapixels'] = 0.25
241
+ ui_inputs['grow_mask_by'] = ui_inputs.get('feathering', 10)
242
 
243
  elif task_type == 'hires_fix':
244
  input_image_pil = ui_inputs.get('hires_image')
 
258
  if source == "File":
259
  emb_filename = sanitize_filename(emb_id)
260
  elif source == "Civitai":
261
+ local_path, status = get_embedding_path(source, emb_id, os.environ.get("CIVITAI_API_KEY", ""), progress)
262
  if local_path: emb_filename = os.path.basename(local_path)
263
  else: raise gr.Error(f"Failed to prepare Embedding {emb_id}: {status}")
264
 
 
272
  else:
273
  ui_inputs['positive_prompt'] = embedding_prompt_text
274
 
275
+ controlnet_data = ui_inputs.get('controlnet_data', [])
276
+ active_controlnets = []
277
+ if controlnet_data:
278
+ (cn_images, _, _, cn_strengths, cn_filepaths) = [controlnet_data[i::5] for i in range(5)]
279
+ for i in range(len(cn_images)):
280
+ if cn_images[i] and cn_strengths[i] > 0 and cn_filepaths[i] and cn_filepaths[i] != "None":
281
+ ensure_controlnet_model_downloaded(cn_filepaths[i], progress)
282
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
283
+ cn_temp_path = os.path.join(INPUT_DIR, f"temp_cn_{i}_{random.randint(1000, 9999)}.png")
284
+ cn_images[i].save(cn_temp_path, "PNG")
285
+ temp_files_to_clean.append(cn_temp_path)
286
+ active_controlnets.append({
287
+ "image": os.path.basename(cn_temp_path), "strength": cn_strengths[i],
288
+ "start_percent": 0.0, "end_percent": 1.0, "control_net_name": cn_filepaths[i]
289
+ })
290
+
291
+ diffsynth_controlnet_data = ui_inputs.get('diffsynth_controlnet_data', [])
292
+ active_diffsynth_controlnets = []
293
+ if diffsynth_controlnet_data:
294
+ (cn_images, _, _, cn_strengths, cn_filepaths) = [diffsynth_controlnet_data[i::5] for i in range(5)]
295
+ for i in range(len(cn_images)):
296
+ if cn_images[i] and cn_strengths[i] > 0 and cn_filepaths[i] and cn_filepaths[i] != "None":
297
+ ensure_controlnet_model_downloaded(cn_filepaths[i], progress)
298
+
299
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
300
+ cn_temp_path = os.path.join(INPUT_DIR, f"temp_diffsynth_cn_{i}_{random.randint(1000, 9999)}.png")
301
+ cn_images[i].save(cn_temp_path, "PNG")
302
+ temp_files_to_clean.append(cn_temp_path)
303
+ active_diffsynth_controlnets.append({
304
+ "image": os.path.basename(cn_temp_path), "strength": cn_strengths[i],
305
+ "control_net_name": cn_filepaths[i]
306
+ })
307
+
308
+ ipadapter_data = ui_inputs.get('ipadapter_data', [])
309
+ active_ipadapters = []
310
+ if ipadapter_data:
311
+ num_ipa_units = (len(ipadapter_data) - 5) // 3
312
+ final_preset, final_weight, final_lora_strength, final_embeds_scaling, final_combine_method = ipadapter_data[-5:]
313
+ ipa_images, ipa_weights, ipa_lora_strengths = [ipadapter_data[i*num_ipa_units:(i+1)*num_ipa_units] for i in range(3)]
314
+ all_presets_to_download = set()
315
+ for i in range(num_ipa_units):
316
+ if ipa_images[i] and ipa_weights[i] > 0 and final_preset:
317
+ all_presets_to_download.add(final_preset)
318
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
319
+ ipa_temp_path = os.path.join(INPUT_DIR, f"temp_ipa_{i}_{random.randint(1000, 9999)}.png")
320
+ ipa_images[i].save(ipa_temp_path, "PNG")
321
+ temp_files_to_clean.append(ipa_temp_path)
322
+ active_ipadapters.append({
323
+ "image": os.path.basename(ipa_temp_path), "preset": final_preset,
324
+ "weight": ipa_weights[i], "lora_strength": ipa_lora_strengths[i]
325
+ })
326
+ if active_ipadapters and final_preset:
327
+ all_presets_to_download.add(final_preset)
328
+ for preset in all_presets_to_download:
329
+ ensure_ipadapter_models_downloaded(preset, progress)
330
+
331
+ model_type_key = 'sd15' if workflow_model_type == 'sd15' else 'sdxl'
332
+ if active_ipadapters:
333
+ active_ipadapters.append({
334
+ 'is_final_settings': True, 'model_type': model_type_key, 'final_preset': final_preset,
335
+ 'final_weight': final_weight, 'final_lora_strength': final_lora_strength,
336
+ 'final_embeds_scaling': final_embeds_scaling, 'final_combine_method': final_combine_method
337
+ })
338
+
339
+ flux1_ipadapter_data = ui_inputs.get('flux1_ipadapter_data', [])
340
+ active_flux1_ipadapters = []
341
+ if flux1_ipadapter_data:
342
+ num_units = len(flux1_ipadapter_data) // 4
343
+ f_images = flux1_ipadapter_data[0*num_units : 1*num_units]
344
+ f_weights = flux1_ipadapter_data[1*num_units : 2*num_units]
345
+ f_starts = flux1_ipadapter_data[2*num_units : 3*num_units]
346
+ f_ends = flux1_ipadapter_data[3*num_units : 4*num_units]
347
+ for i in range(len(f_images)):
348
+ if f_images[i] and f_weights[i] > 0:
349
+ from utils.app_utils import _ensure_model_downloaded
350
+ for filename in ["ip-adapter.bin"]:
351
+ _ensure_model_downloaded(filename, progress)
352
+
353
+ from huggingface_hub import snapshot_download
354
+ progress(0.5, desc="Caching HF SigLIP model...")
355
+ snapshot_download(
356
+ repo_id="google/siglip-so400m-patch14-384",
357
+ allow_patterns=["*.json", "*.safetensors", "*.txt"],
358
+ ignore_patterns=["*.msgpack", "*.h5", "*.bin"]
359
+ )
360
+
361
+ temp_path = os.path.join(INPUT_DIR, f"temp_fipa_{i}_{random.randint(1000, 9999)}.png")
362
+ f_images[i].save(temp_path, "PNG")
363
+ temp_files_to_clean.append(temp_path)
364
+ active_flux1_ipadapters.append({
365
+ "image": os.path.basename(temp_path),
366
+ "weight": f_weights[i], "start_percent": f_starts[i], "end_percent": f_ends[i]
367
+ })
368
+
369
+ sd3_ipadapter_data = ui_inputs.get('sd3_ipadapter_chain', [])
370
+ active_sd3_ipadapters = []
371
+ if sd3_ipadapter_data:
372
+ num_units = len(sd3_ipadapter_data) // 4
373
+ s_images = sd3_ipadapter_data[0*num_units : 1*num_units]
374
+ s_weights = sd3_ipadapter_data[1*num_units : 2*num_units]
375
+ s_starts = sd3_ipadapter_data[2*num_units : 3*num_units]
376
+ s_ends = sd3_ipadapter_data[3*num_units : 4*num_units]
377
+ sd3_ipa_downloaded = False
378
+ for i in range(len(s_images)):
379
+ if s_images[i] and s_weights[i] > 0:
380
+ if not sd3_ipa_downloaded:
381
+ from utils.app_utils import ensure_sd3_ipadapter_models_downloaded
382
+ ensure_sd3_ipadapter_models_downloaded(progress)
383
+ sd3_ipa_downloaded = True
384
+ temp_path = os.path.join(INPUT_DIR, f"temp_s3ipa_{i}_{random.randint(1000, 9999)}.png")
385
+ s_images[i].save(temp_path, "PNG")
386
+ temp_files_to_clean.append(temp_path)
387
+ active_sd3_ipadapters.append({
388
+ "image": os.path.basename(temp_path),
389
+ "weight": s_weights[i], "start_percent": s_starts[i], "end_percent": s_ends[i]
390
+ })
391
+
392
+ style_data = ui_inputs.get('style_data', [])
393
+ active_styles = []
394
+ if style_data:
395
+ num_units = len(style_data) // 2
396
+ st_images = style_data[0*num_units : 1*num_units]
397
+ st_strengths = style_data[1*num_units : 2*num_units]
398
+ for i in range(len(st_images)):
399
+ if st_images[i] and st_strengths[i] > 0:
400
+ from utils.app_utils import _ensure_model_downloaded
401
+ _ensure_model_downloaded("sigclip_vision_patch14_384.safetensors", progress)
402
+ temp_path = os.path.join(INPUT_DIR, f"temp_style_{i}_{random.randint(1000, 9999)}.png")
403
+ st_images[i].save(temp_path, "PNG")
404
+ temp_files_to_clean.append(temp_path)
405
+ active_styles.append({
406
+ "image": os.path.basename(temp_path), "strength": st_strengths[i]
407
+ })
408
+
409
+ reference_latent_data = ui_inputs.get('reference_latent_data', [])
410
+ active_reference_latents = []
411
+ if reference_latent_data:
412
+ for img in reference_latent_data:
413
+ if img:
414
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
415
+ temp_path = os.path.join(INPUT_DIR, f"temp_ref_{random.randint(1000, 9999)}.png")
416
+ img.save(temp_path, "PNG")
417
+ temp_files_to_clean.append(temp_path)
418
+ active_reference_latents.append(os.path.basename(temp_path))
419
+
420
  from utils.app_utils import get_vae_path
421
  vae_source = ui_inputs.get('vae_source')
422
  vae_id = ui_inputs.get('vae_id')
 
423
  vae_name_override = None
 
424
  if vae_source and vae_source != "None":
425
  if vae_source == "File":
426
  vae_name_override = sanitize_filename(vae_id)
427
  elif vae_source == "Civitai" and vae_id and vae_id.strip():
428
+ local_path, status = get_vae_path(vae_source, vae_id, os.environ.get("CIVITAI_API_KEY", ""), progress)
429
  if local_path: vae_name_override = os.path.basename(local_path)
430
  else: raise gr.Error(f"Failed to prepare VAE {vae_id}: {status}")
 
431
  if vae_name_override:
432
  ui_inputs['vae_name'] = vae_name_override
433
 
 
435
  active_conditioning = []
436
  if conditioning_data:
437
  num_units = len(conditioning_data) // 6
438
+ prompts, widths, heights, xs, ys, strengths = [conditioning_data[i*num_units : (i+1)*num_units] for i in range(6)]
 
 
 
 
 
 
439
  for i in range(num_units):
440
  if prompts[i] and prompts[i].strip():
441
  active_conditioning.append({
442
+ "prompt": prompts[i], "width": int(widths[i]), "height": int(heights[i]),
443
+ "x": int(xs[i]), "y": int(ys[i]), "strength": float(strengths[i])
 
 
 
 
444
  })
445
 
 
 
 
 
 
 
 
 
 
 
446
  loras_string = f"LoRAs: [{', '.join(active_loras_for_meta)}]" if active_loras_for_meta else ""
447
 
448
  progress(0.8, desc="Assembling workflow...")
449
 
450
  if ui_inputs.get('seed') == -1:
451
  ui_inputs['seed'] = random.randint(0, 2**32 - 1)
452
+
453
+ model_info = ALL_MODEL_MAP[model_display_name]
454
+ path_or_components = model_info[1]
455
+ latent_type = model_info[3] if len(model_info) > 3 and model_info[3] else 'latent'
456
+ latent_generator_template = "EmptyLatentImage"
457
+ if latent_type == 'sd3_latent':
458
+ latent_generator_template = "EmptySD3LatentImage"
459
+ elif latent_type == 'chroma_radiance_latent':
460
+ latent_generator_template = "EmptyChromaRadianceLatentImage"
461
+ elif latent_type == 'hunyuan_latent':
462
+ latent_generator_template = "EmptyHunyuanImageLatent"
463
 
464
+ dynamic_values = {
465
+ 'task_type': ui_inputs['task_type'],
466
+ 'model_type': workflow_model_type,
467
+ 'latent_type': latent_type,
468
+ 'latent_generator_template': latent_generator_template
469
+ }
470
 
471
  recipe_path = os.path.join(os.path.dirname(__file__), "workflow_recipes", "sd_unified_recipe.yaml")
472
  assembler = WorkflowAssembler(recipe_path, dynamic_values=dynamic_values)
473
 
 
 
 
 
 
 
474
  workflow_inputs = {
475
+ **ui_inputs,
476
  "positive_prompt": ui_inputs['positive_prompt'], "negative_prompt": ui_inputs['negative_prompt'],
477
  "seed": ui_inputs['seed'], "steps": ui_inputs['num_inference_steps'], "cfg": ui_inputs['guidance_scale'],
478
  "sampler_name": ui_inputs['sampler'], "scheduler": ui_inputs['scheduler'],
479
  "batch_size": ui_inputs['batch_size'],
480
+ "clip_skip": ui_inputs['clip_skip'],
481
+ "denoise": ui_inputs['denoise'],
482
+ "vae_name": ui_inputs.get('vae_name'),
483
+ "guidance": ui_inputs.get('guidance', 3.5),
 
 
 
 
 
 
484
  "lora_chain": active_loras_for_gpu,
485
+ "controlnet_chain": active_controlnets,
486
+ "diffsynth_controlnet_chain": active_diffsynth_controlnets,
487
+ "ipadapter_chain": active_ipadapters,
488
+ "flux1_ipadapter_chain": active_flux1_ipadapters,
489
+ "sd3_ipadapter_chain": active_sd3_ipadapters,
490
+ "style_chain": active_styles,
491
  "conditioning_chain": active_conditioning,
492
  "reference_latent_chain": active_reference_latents,
493
+ "vae_chain": [ui_inputs.get('vae_name')] if ui_inputs.get('vae_name') else [],
494
  }
495
+
496
+ if isinstance(path_or_components, dict):
497
+ workflow_inputs.update({
498
+ 'unet_name': path_or_components.get('unet'),
499
+ 'vae_name': ui_inputs.get('vae_name') or path_or_components.get('vae'),
500
+ 'clip_name': path_or_components.get('clip'),
501
+ 'clip1_name': path_or_components.get('clip1'),
502
+ 'clip2_name': path_or_components.get('clip2'),
503
+ 'clip3_name': path_or_components.get('clip3'),
504
+ 'clip4_name': path_or_components.get('clip4'),
505
+ 'lora_name': path_or_components.get('lora'),
506
+ })
507
+ else:
508
+ workflow_inputs['model_name'] = path_or_components
509
 
510
  if task_type == 'txt2img':
511
  workflow_inputs['width'] = ui_inputs['width']
512
  workflow_inputs['height'] = ui_inputs['height']
513
 
514
  workflow = assembler.assemble(workflow_inputs)
515
+
516
  progress(1.0, desc="All models ready. Requesting GPU for generation...")
517
 
518
  try:
 
527
  assembler=assembler,
528
  progress=progress
529
  )
530
+
531
  import json
532
  import glob
533
  from PIL import PngImagePlugin
core/pipelines/workflow_recipes/_partials/{_base_sampler.yaml → _base_sampler_sd.yaml} RENAMED
@@ -1,11 +1,21 @@
1
  nodes:
 
 
 
 
 
 
2
  ksampler:
3
  class_type: KSampler
4
-
 
 
5
  vae_decode:
6
  class_type: VAEDecode
 
7
  save_image:
8
  class_type: SaveImage
 
9
  params: {}
10
 
11
  connections:
@@ -15,9 +25,12 @@ connections:
15
  to: "save_image:images"
16
 
17
  ui_map:
 
 
18
  seed: "ksampler:seed"
19
  steps: "ksampler:steps"
20
  cfg: "ksampler:cfg"
21
  sampler_name: "ksampler:sampler_name"
22
  scheduler: "ksampler:scheduler"
23
- denoise: "ksampler:denoise"
 
 
1
  nodes:
2
+ pos_prompt:
3
+ class_type: CLIPTextEncode
4
+ title: "CLIP Text Encode (Positive)"
5
+ neg_prompt:
6
+ class_type: CLIPTextEncode
7
+ title: "CLIP Text Encode (Negative)"
8
  ksampler:
9
  class_type: KSampler
10
+ title: "KSampler"
11
+ params:
12
+ denoise: 1.0
13
  vae_decode:
14
  class_type: VAEDecode
15
+ title: "VAE Decode"
16
  save_image:
17
  class_type: SaveImage
18
+ title: "Save Image"
19
  params: {}
20
 
21
  connections:
 
25
  to: "save_image:images"
26
 
27
  ui_map:
28
+ positive_prompt: "pos_prompt:text"
29
+ negative_prompt: "neg_prompt:text"
30
  seed: "ksampler:seed"
31
  steps: "ksampler:steps"
32
  cfg: "ksampler:cfg"
33
  sampler_name: "ksampler:sampler_name"
34
  scheduler: "ksampler:scheduler"
35
+ denoise: "ksampler:denoise"
36
+ filename_prefix: "save_image:filename_prefix"
core/pipelines/workflow_recipes/_partials/conditioning/flux1.yaml ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ unet_loader:
3
+ class_type: UNETLoader
4
+ title: "Load FLUX UNET"
5
+ params:
6
+ weight_dtype: "default"
7
+ vae_loader:
8
+ class_type: VAELoader
9
+ title: "Load FLUX VAE"
10
+ clip_loader:
11
+ class_type: DualCLIPLoader
12
+ title: "Load FLUX Dual CLIP"
13
+ params:
14
+ type: "flux"
15
+ device: "default"
16
+ flux_guidance:
17
+ class_type: FluxGuidance
18
+ title: "FluxGuidance"
19
+
20
+ connections:
21
+ - from: "unet_loader:0"
22
+ to: "ksampler:model"
23
+ - from: "clip_loader:0"
24
+ to: "pos_prompt:clip"
25
+ - from: "clip_loader:0"
26
+ to: "neg_prompt:clip"
27
+ - from: "vae_loader:0"
28
+ to: "vae_decode:vae"
29
+ - from: "vae_loader:0"
30
+ to: "vae_encode:vae"
31
+ - from: "pos_prompt:0"
32
+ to: "flux_guidance:conditioning"
33
+ - from: "flux_guidance:0"
34
+ to: "ksampler:positive"
35
+ - from: "neg_prompt:0"
36
+ to: "ksampler:negative"
37
+
38
+ dynamic_controlnet_chains:
39
+ controlnet_chain:
40
+ template: "ControlNetApplyAdvanced"
41
+ ksampler_node: "ksampler"
42
+ vae_source: "vae_loader:0"
43
+
44
+ dynamic_flux1_ipadapter_chains:
45
+ flux1_ipadapter_chain:
46
+ ksampler_node: "ksampler"
47
+
48
+ dynamic_style_chains:
49
+ style_chain:
50
+ flux_guidance_node: "flux_guidance"
51
+ ksampler_node: "ksampler"
52
+
53
+ dynamic_conditioning_chains:
54
+ conditioning_chain:
55
+ flux_guidance_node: "flux_guidance"
56
+ ksampler_node: "ksampler"
57
+ clip_source: "clip_loader:0"
58
+
59
+ ui_map:
60
+ unet_name: "unet_loader:unet_name"
61
+ vae_name: "vae_loader:vae_name"
62
+ clip1_name: "clip_loader:clip_name1"
63
+ clip2_name: "clip_loader:clip_name2"
64
+ guidance: "flux_guidance:guidance"
core/pipelines/workflow_recipes/_partials/conditioning/flux2-kv.yaml ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ unet_loader:
3
+ class_type: UNETLoader
4
+ title: "Load Diffusion Model"
5
+ params:
6
+ weight_dtype: "default"
7
+ clip_loader:
8
+ class_type: CLIPLoader
9
+ title: "Load CLIP"
10
+ params:
11
+ type: "flux2"
12
+ device: "default"
13
+ vae_loader:
14
+ class_type: VAELoader
15
+ title: "Load VAE"
16
+
17
+ flux_kv_cache:
18
+ class_type: FluxKVCache
19
+ title: "Flux KV Cache"
20
+
21
+ pos_prompt:
22
+ class_type: CLIPTextEncode
23
+ title: "CLIP Text Encode (Positive)"
24
+ neg_prompt:
25
+ class_type: CLIPTextEncode
26
+ title: "CLIP Text Encode (Negative)"
27
+
28
+ ksampler:
29
+ class_type: KSampler
30
+ title: "KSampler"
31
+ params:
32
+ denoise: 1.0
33
+
34
+ vae_decode:
35
+ class_type: VAEDecode
36
+ title: "VAE Decode"
37
+
38
+ save_image:
39
+ class_type: SaveImage
40
+ title: "Save Image"
41
+
42
+ connections:
43
+ - from: "unet_loader:0"
44
+ to: "flux_kv_cache:model"
45
+ - from: "flux_kv_cache:0"
46
+ to: "ksampler:model"
47
+
48
+ - from: "clip_loader:0"
49
+ to: "pos_prompt:clip"
50
+ - from: "clip_loader:0"
51
+ to: "neg_prompt:clip"
52
+
53
+ - from: "vae_loader:0"
54
+ to: "vae_decode:vae"
55
+ - from: "vae_loader:0"
56
+ to: "vae_encode:vae"
57
+
58
+ - from: "pos_prompt:0"
59
+ to: "ksampler:positive"
60
+ - from: "neg_prompt:0"
61
+ to: "ksampler:negative"
62
+
63
+ - from: "latent_source:0"
64
+ to: "ksampler:latent_image"
65
+
66
+ - from: "ksampler:0"
67
+ to: "vae_decode:samples"
68
+ - from: "vae_decode:0"
69
+ to: "save_image:images"
70
+
71
+ dynamic_lora_chains:
72
+ lora_chain:
73
+ template: "LoraLoader"
74
+ output_map:
75
+ "unet_loader:0": "model"
76
+ "clip_loader:0": "clip"
77
+ input_map:
78
+ "model": "model"
79
+ "clip": "clip"
80
+ end_input_map:
81
+ "model": ["flux_kv_cache:model"]
82
+ "clip": ["pos_prompt:clip", "neg_prompt:clip"]
83
+
84
+ dynamic_reference_latent_chains:
85
+ reference_latent_chain:
86
+ ksampler_node: "ksampler"
87
+ vae_node: "vae_loader"
88
+
89
+ ui_map:
90
+ unet_name: "unet_loader:unet_name"
91
+ clip_name: "clip_loader:clip_name"
92
+ vae_name: "vae_loader:vae_name"
93
+
94
+ positive_prompt: "pos_prompt:text"
95
+ negative_prompt: "neg_prompt:text"
96
+
97
+ seed: "ksampler:seed"
98
+ steps: "ksampler:steps"
99
+ cfg: "ksampler:cfg"
100
+ sampler_name: "ksampler:sampler_name"
101
+ scheduler: "ksampler:scheduler"
102
+ denoise: "ksampler:denoise"
103
+
104
+ filename_prefix: "save_image:filename_prefix"
core/pipelines/workflow_recipes/_partials/conditioning/flux2.yaml CHANGED
@@ -20,6 +20,20 @@ nodes:
20
  neg_prompt:
21
  class_type: CLIPTextEncode
22
  title: "CLIP Text Encode (Negative)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  connections:
25
  - from: "unet_loader:0"
@@ -37,6 +51,14 @@ connections:
37
  to: "ksampler:positive"
38
  - from: "neg_prompt:0"
39
  to: "ksampler:negative"
 
 
 
 
 
 
 
 
40
 
41
  dynamic_lora_chains:
42
  lora_chain:
@@ -51,11 +73,6 @@ dynamic_lora_chains:
51
  "model": ["ksampler:model"]
52
  "clip": ["pos_prompt:clip", "neg_prompt:clip"]
53
 
54
- dynamic_conditioning_chains:
55
- conditioning_chain:
56
- ksampler_node: "ksampler"
57
- clip_source: "clip_loader:0"
58
-
59
  dynamic_reference_latent_chains:
60
  reference_latent_chain:
61
  ksampler_node: "ksampler"
@@ -65,5 +82,15 @@ ui_map:
65
  unet_name: "unet_loader:unet_name"
66
  clip_name: "clip_loader:clip_name"
67
  vae_name: "vae_loader:vae_name"
 
68
  positive_prompt: "pos_prompt:text"
69
- negative_prompt: "neg_prompt:text"
 
 
 
 
 
 
 
 
 
 
20
  neg_prompt:
21
  class_type: CLIPTextEncode
22
  title: "CLIP Text Encode (Negative)"
23
+
24
+ ksampler:
25
+ class_type: KSampler
26
+ title: "KSampler"
27
+ params:
28
+ denoise: 1.0
29
+
30
+ vae_decode:
31
+ class_type: VAEDecode
32
+ title: "VAE Decode"
33
+
34
+ save_image:
35
+ class_type: SaveImage
36
+ title: "Save Image"
37
 
38
  connections:
39
  - from: "unet_loader:0"
 
51
  to: "ksampler:positive"
52
  - from: "neg_prompt:0"
53
  to: "ksampler:negative"
54
+
55
+ - from: "latent_source:0"
56
+ to: "ksampler:latent_image"
57
+
58
+ - from: "ksampler:0"
59
+ to: "vae_decode:samples"
60
+ - from: "vae_decode:0"
61
+ to: "save_image:images"
62
 
63
  dynamic_lora_chains:
64
  lora_chain:
 
73
  "model": ["ksampler:model"]
74
  "clip": ["pos_prompt:clip", "neg_prompt:clip"]
75
 
 
 
 
 
 
76
  dynamic_reference_latent_chains:
77
  reference_latent_chain:
78
  ksampler_node: "ksampler"
 
82
  unet_name: "unet_loader:unet_name"
83
  clip_name: "clip_loader:clip_name"
84
  vae_name: "vae_loader:vae_name"
85
+
86
  positive_prompt: "pos_prompt:text"
87
+ negative_prompt: "neg_prompt:text"
88
+
89
+ seed: "ksampler:seed"
90
+ steps: "ksampler:steps"
91
+ cfg: "ksampler:cfg"
92
+ sampler_name: "ksampler:sampler_name"
93
+ scheduler: "ksampler:scheduler"
94
+ denoise: "ksampler:denoise"
95
+
96
+ filename_prefix: "save_image:filename_prefix"
core/pipelines/workflow_recipes/_partials/conditioning/sd35.yaml ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ ckpt_loader:
3
+ class_type: CheckpointLoaderSimple
4
+ title: "Load Checkpoint"
5
+
6
+ connections:
7
+ - from: "ckpt_loader:0"
8
+ to: "ksampler:model"
9
+ - from: "ckpt_loader:1"
10
+ to: "pos_prompt:clip"
11
+ - from: "ckpt_loader:1"
12
+ to: "neg_prompt:clip"
13
+ - from: "pos_prompt:0"
14
+ to: "ksampler:positive"
15
+ - from: "neg_prompt:0"
16
+ to: "ksampler:negative"
17
+ - from: "ckpt_loader:2"
18
+ to: "vae_decode:vae"
19
+ - from: "ckpt_loader:2"
20
+ to: "vae_encode:vae"
21
+
22
+ dynamic_vae_chains:
23
+ vae_chain:
24
+ targets:
25
+ - "vae_decode:vae"
26
+ - "vae_encode:vae"
27
+
28
+ dynamic_lora_chains:
29
+ lora_chain:
30
+ template: "LoraLoader"
31
+ start: "ckpt_loader"
32
+ output_map:
33
+ "0": "model"
34
+ "1": "clip"
35
+ input_map:
36
+ "model": "model"
37
+ "clip": "clip"
38
+ end_input_map:
39
+ "model": ["ksampler:model"]
40
+ "clip": ["pos_prompt:clip", "neg_prompt:clip"]
41
+
42
+ dynamic_controlnet_chains:
43
+ controlnet_chain:
44
+ template: "ControlNetApplyAdvanced"
45
+ ksampler_node: "ksampler"
46
+ vae_source: "ckpt_loader:2"
47
+
48
+ dynamic_sd3_ipadapter_chains:
49
+ sd3_ipadapter_chain:
50
+ ksampler_node: "ksampler"
51
+
52
+ dynamic_conditioning_chains:
53
+ conditioning_chain:
54
+ ksampler_node: "ksampler"
55
+ clip_source: "ckpt_loader:1"
56
+
57
+ ui_map:
58
+ model_name: "ckpt_loader:ckpt_name"
core/pipelines/workflow_recipes/_partials/input/hires_fix.yaml CHANGED
@@ -1,15 +1,16 @@
1
  nodes:
2
  input_image_loader:
3
  class_type: LoadImage
4
-
5
  vae_encode:
6
  class_type: VAEEncode
7
-
8
  latent_upscaler:
9
  class_type: LatentUpscaleBy
10
-
11
  latent_source:
12
  class_type: RepeatLatentBatch
 
13
 
14
  connections:
15
  - from: "input_image_loader:0"
 
1
  nodes:
2
  input_image_loader:
3
  class_type: LoadImage
4
+ title: "Load Input Image"
5
  vae_encode:
6
  class_type: VAEEncode
7
+ title: "VAE Encode (Hires Pre-step)"
8
  latent_upscaler:
9
  class_type: LatentUpscaleBy
10
+ title: "Upscale Latent By"
11
  latent_source:
12
  class_type: RepeatLatentBatch
13
+ title: "Repeat Latent Batch for Hires"
14
 
15
  connections:
16
  - from: "input_image_loader:0"
core/pipelines/workflow_recipes/_partials/input/img2img.yaml CHANGED
@@ -1,12 +1,13 @@
1
  nodes:
2
  input_image_loader:
3
  class_type: LoadImage
4
-
5
  vae_encode:
6
  class_type: VAEEncode
7
-
8
  latent_source:
9
  class_type: RepeatLatentBatch
 
10
 
11
  connections:
12
  - from: "input_image_loader:0"
 
1
  nodes:
2
  input_image_loader:
3
  class_type: LoadImage
4
+ title: "Load Input Image"
5
  vae_encode:
6
  class_type: VAEEncode
7
+ title: "VAE Encode (Img2Img)"
8
  latent_source:
9
  class_type: RepeatLatentBatch
10
+ title: "Repeat Latent Batch"
11
 
12
  connections:
13
  - from: "input_image_loader:0"
core/pipelines/workflow_recipes/_partials/input/inpaint.yaml CHANGED
@@ -2,24 +2,22 @@ nodes:
2
  inpaint_loader:
3
  class_type: LoadImage
4
  title: "Load Inpaint Image+Mask"
5
-
6
  vae_encode:
7
  class_type: VAEEncodeForInpaint
8
- params:
9
- grow_mask_by: 6
10
-
11
  latent_source:
12
  class_type: RepeatLatentBatch
13
-
 
14
  connections:
15
  - from: "inpaint_loader:0"
16
  to: "vae_encode:pixels"
17
  - from: "inpaint_loader:1"
18
  to: "vae_encode:mask"
19
-
20
  - from: "vae_encode:0"
21
  to: "latent_source:samples"
22
 
23
  ui_map:
24
- inpaint_image: "inpaint_loader:image"
25
- batch_size: "latent_source:amount"
 
 
2
  inpaint_loader:
3
  class_type: LoadImage
4
  title: "Load Inpaint Image+Mask"
 
5
  vae_encode:
6
  class_type: VAEEncodeForInpaint
7
+ title: "VAE Encode (for Inpainting)"
 
 
8
  latent_source:
9
  class_type: RepeatLatentBatch
10
+ title: "Repeat Latent Batch"
11
+
12
  connections:
13
  - from: "inpaint_loader:0"
14
  to: "vae_encode:pixels"
15
  - from: "inpaint_loader:1"
16
  to: "vae_encode:mask"
 
17
  - from: "vae_encode:0"
18
  to: "latent_source:samples"
19
 
20
  ui_map:
21
+ input_image: "inpaint_loader:image"
22
+ batch_size: "latent_source:amount"
23
+ grow_mask_by: "vae_encode:grow_mask_by"
core/pipelines/workflow_recipes/_partials/input/outpaint.yaml CHANGED
@@ -1,38 +1,41 @@
1
  nodes:
2
  input_image_loader:
3
  class_type: LoadImage
4
-
 
 
 
 
 
5
  pad_image:
6
  class_type: ImagePadForOutpaint
7
- params:
8
- feathering: 10
9
-
10
  vae_encode:
11
  class_type: VAEEncodeForInpaint
12
- params:
13
- grow_mask_by: 6
14
-
15
  latent_source:
16
  class_type: RepeatLatentBatch
 
17
 
18
  connections:
19
  - from: "input_image_loader:0"
 
 
20
  to: "pad_image:image"
21
-
22
  - from: "pad_image:0"
23
  to: "vae_encode:pixels"
24
  - from: "pad_image:1"
25
  to: "vae_encode:mask"
26
-
27
  - from: "vae_encode:0"
28
  to: "latent_source:samples"
29
 
30
  ui_map:
31
  input_image: "input_image_loader:image"
32
-
33
  left: "pad_image:left"
34
  top: "pad_image:top"
35
  right: "pad_image:right"
36
  bottom: "pad_image:bottom"
37
-
 
38
  batch_size: "latent_source:amount"
 
1
  nodes:
2
  input_image_loader:
3
  class_type: LoadImage
4
+ title: "Load Image for Outpaint"
5
+ scale_image:
6
+ class_type: ImageScaleToTotalPixels
7
+ title: "Scale Image to Total Pixels"
8
+ params:
9
+ upscale_method: "nearest-exact"
10
  pad_image:
11
  class_type: ImagePadForOutpaint
12
+ title: "Pad Image for Outpainting"
 
 
13
  vae_encode:
14
  class_type: VAEEncodeForInpaint
15
+ title: "VAE Encode (for Inpainting)"
 
 
16
  latent_source:
17
  class_type: RepeatLatentBatch
18
+ title: "Repeat Latent Batch"
19
 
20
  connections:
21
  - from: "input_image_loader:0"
22
+ to: "scale_image:image"
23
+ - from: "scale_image:0"
24
  to: "pad_image:image"
 
25
  - from: "pad_image:0"
26
  to: "vae_encode:pixels"
27
  - from: "pad_image:1"
28
  to: "vae_encode:mask"
 
29
  - from: "vae_encode:0"
30
  to: "latent_source:samples"
31
 
32
  ui_map:
33
  input_image: "input_image_loader:image"
34
+ megapixels: "scale_image:megapixels"
35
  left: "pad_image:left"
36
  top: "pad_image:top"
37
  right: "pad_image:right"
38
  bottom: "pad_image:bottom"
39
+ feathering: "pad_image:feathering"
40
+ grow_mask_by: "vae_encode:grow_mask_by"
41
  batch_size: "latent_source:amount"
core/pipelines/workflow_recipes/_partials/input/txt2img.yaml CHANGED
@@ -1,8 +1,2 @@
1
- nodes:
2
- latent_source:
3
- class_type: EmptyFlux2LatentImage
4
-
5
- ui_map:
6
- width: "latent_source:width"
7
- height: "latent_source:height"
8
- batch_size: "latent_source:batch_size"
 
1
+ imports:
2
+ - "txt2img_{{ latent_type }}.yaml"
 
 
 
 
 
 
core/pipelines/workflow_recipes/_partials/input/txt2img_chroma_radiance_latent.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ latent_source:
3
+ class_type: "EmptyChromaRadianceLatentImage"
4
+ title: "EmptyChromaRadianceLatentImage"
5
+
6
+ connections: []
7
+
8
+ ui_map:
9
+ width: "latent_source:width"
10
+ height: "latent_source:height"
11
+ batch_size: "latent_source:batch_size"
core/pipelines/workflow_recipes/_partials/input/txt2img_flux2_latent.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ latent_source:
3
+ class_type: "EmptyFlux2LatentImage"
4
+ title: "Empty Flux 2 Latent"
5
+
6
+ connections: []
7
+
8
+ ui_map:
9
+ width: "latent_source:width"
10
+ height: "latent_source:height"
11
+ batch_size: "latent_source:batch_size"
core/pipelines/workflow_recipes/_partials/input/txt2img_hunyuan_latent.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ latent_source:
3
+ class_type: "EmptyHunyuanImageLatent"
4
+ title: "EmptyHunyuanImageLatent"
5
+
6
+ connections: []
7
+
8
+ ui_map:
9
+ width: "latent_source:width"
10
+ height: "latent_source:height"
11
+ batch_size: "latent_source:batch_size"
core/pipelines/workflow_recipes/_partials/input/txt2img_latent.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ latent_source:
3
+ class_type: "{{ latent_generator_template }}"
4
+ title: "Empty Latent Image"
5
+
6
+ connections: []
7
+
8
+ ui_map:
9
+ width: "latent_source:width"
10
+ height: "latent_source:height"
11
+ batch_size: "latent_source:batch_size"
core/pipelines/workflow_recipes/_partials/input/txt2img_sd3_latent.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ latent_source:
3
+ class_type: "EmptySD3LatentImage"
4
+ title: "EmptySD3LatentImage"
5
+
6
+ connections: []
7
+
8
+ ui_map:
9
+ width: "latent_source:width"
10
+ height: "latent_source:height"
11
+ batch_size: "latent_source:batch_size"
core/pipelines/workflow_recipes/sd_unified_recipe.yaml CHANGED
@@ -1,7 +1,7 @@
1
  imports:
2
- - "_partials/_base_sampler.yaml"
3
  - "_partials/input/{{ task_type }}.yaml"
4
- - "_partials/conditioning/flux2.yaml"
5
 
6
  connections:
7
  - from: "latent_source:0"
 
1
  imports:
2
+ - "_partials/_base_sampler_sd.yaml"
3
  - "_partials/input/{{ task_type }}.yaml"
4
+ - "_partials/conditioning/{{ model_type }}.yaml"
5
 
6
  connections:
7
  - from: "latent_source:0"
core/settings.py CHANGED
@@ -10,16 +10,37 @@ MODEL_PATCHES_DIR = "models/model_patches"
10
  DIFFUSION_MODELS_DIR = "models/diffusion_models"
11
  VAE_DIR = "models/vae"
12
  TEXT_ENCODERS_DIR = "models/text_encoders"
 
 
 
 
13
  INPUT_DIR = "input"
14
  OUTPUT_DIR = "output"
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
  _MODEL_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_list.yaml')
18
  _FILE_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'file_list.yaml')
 
19
  _CONSTANTS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'constants.yaml')
 
 
20
  _MODEL_DEFAULTS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_defaults.yaml')
21
 
22
-
23
  def load_constants_from_yaml(filepath=_CONSTANTS_PATH):
24
  if not os.path.exists(filepath):
25
  print(f"Warning: Constants file not found at {filepath}. Using fallback values.")
@@ -27,6 +48,27 @@ def load_constants_from_yaml(filepath=_CONSTANTS_PATH):
27
  with open(filepath, 'r', encoding='utf-8') as f:
28
  return yaml.safe_load(f)
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def load_file_download_map(filepath=_FILE_LIST_PATH):
31
  if not os.path.exists(filepath):
32
  raise FileNotFoundError(f"The file list (for downloads) was not found at: {filepath}")
@@ -59,50 +101,86 @@ def load_models_from_yaml(model_list_filepath=_MODEL_LIST_PATH, download_map=Non
59
  }
60
  category_map_names = {
61
  "Checkpoint": "MODEL_MAP_CHECKPOINT",
 
62
  }
63
 
64
- for category, models in model_data.items():
65
  if category in category_map_names:
66
  map_name = category_map_names[category]
67
- if not isinstance(models, list): continue
68
- for model in models:
69
- display_name = model['display_name']
70
- components = model.get('components', {})
71
-
72
- model_tuple = (
73
- None,
74
- components,
75
- "SDXL",
76
- None
77
- )
78
- model_maps[map_name][display_name] = model_tuple
79
- model_maps["ALL_MODEL_MAP"][display_name] = model_tuple
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  return model_maps
82
 
83
- def load_model_defaults(filepath=_MODEL_DEFAULTS_PATH):
84
- if not os.path.exists(filepath):
85
- print(f"Warning: Model defaults file not found at {filepath}. Using empty defaults.")
86
- return {}
87
- with open(filepath, 'r', encoding='utf-8') as f:
88
- return yaml.safe_load(f)
89
-
90
  try:
91
  ALL_FILE_DOWNLOAD_MAP = load_file_download_map()
92
  loaded_maps = load_models_from_yaml(download_map=ALL_FILE_DOWNLOAD_MAP)
93
  MODEL_MAP_CHECKPOINT = loaded_maps["MODEL_MAP_CHECKPOINT"]
94
  ALL_MODEL_MAP = loaded_maps["ALL_MODEL_MAP"]
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  MODEL_TYPE_MAP = {k: v[2] for k, v in ALL_MODEL_MAP.items()}
97
-
98
- ALL_MODEL_DEFAULTS = load_model_defaults()
 
 
 
 
 
 
 
99
 
100
  except Exception as e:
101
  print(f"FATAL: Could not load model configuration from YAML. Error: {e}")
102
  ALL_FILE_DOWNLOAD_MAP = {}
103
  MODEL_MAP_CHECKPOINT, ALL_MODEL_MAP = {}, {}
104
  MODEL_TYPE_MAP = {}
105
- ALL_MODEL_DEFAULTS = {}
106
 
107
 
108
  try:
@@ -111,15 +189,17 @@ try:
111
  MAX_EMBEDDINGS = _constants.get('MAX_EMBEDDINGS', 5)
112
  MAX_CONDITIONINGS = _constants.get('MAX_CONDITIONINGS', 10)
113
  MAX_CONTROLNETS = _constants.get('MAX_CONTROLNETS', 5)
114
- MAX_REFERENCE_LATENTS = _constants.get('MAX_REFERENCE_LATENTS', 10)
115
  LORA_SOURCE_CHOICES = _constants.get('LORA_SOURCE_CHOICES', ["Civitai", "File"])
116
  RESOLUTION_MAP = _constants.get('RESOLUTION_MAP', {})
 
 
 
117
  except Exception as e:
118
  print(f"FATAL: Could not load constants from YAML. Error: {e}")
119
- MAX_LORAS, MAX_EMBEDDINGS, MAX_CONDITIONINGS, MAX_CONTROLNETS = 5, 5, 10, 5
120
- MAX_REFERENCE_LATENTS = 10
121
  LORA_SOURCE_CHOICES = ["Civitai", "File"]
122
  RESOLUTION_MAP = {}
123
-
124
-
125
- DEFAULT_NEGATIVE_PROMPT = ""
 
10
  DIFFUSION_MODELS_DIR = "models/diffusion_models"
11
  VAE_DIR = "models/vae"
12
  TEXT_ENCODERS_DIR = "models/text_encoders"
13
+ STYLE_MODELS_DIR = "models/style_models"
14
+ CLIP_VISION_DIR = "models/clip_vision"
15
+ IPADAPTER_DIR = "models/ipadapter"
16
+ IPADAPTER_FLUX_DIR = "models/ipadapter-flux"
17
  INPUT_DIR = "input"
18
  OUTPUT_DIR = "output"
19
 
20
+ CATEGORY_TO_DIR_MAP = {
21
+ "diffusion_models": DIFFUSION_MODELS_DIR,
22
+ "text_encoders": TEXT_ENCODERS_DIR,
23
+ "vae": VAE_DIR,
24
+ "checkpoints": CHECKPOINT_DIR,
25
+ "loras": LORA_DIR,
26
+ "controlnet": CONTROLNET_DIR,
27
+ "model_patches": MODEL_PATCHES_DIR,
28
+ "embeddings": EMBEDDING_DIR,
29
+ "style_models": STYLE_MODELS_DIR,
30
+ "clip_vision": CLIP_VISION_DIR,
31
+ "ipadapter": IPADAPTER_DIR,
32
+ "ipadapter-flux": IPADAPTER_FLUX_DIR
33
+ }
34
+
35
  _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
36
  _MODEL_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_list.yaml')
37
  _FILE_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'file_list.yaml')
38
+ _IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
39
  _CONSTANTS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'constants.yaml')
40
+ _MODEL_ARCHITECTURES_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_architectures.yaml')
41
+ _IMAGE_GEN_FEATURES_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'image_gen_features.yaml')
42
  _MODEL_DEFAULTS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_defaults.yaml')
43
 
 
44
  def load_constants_from_yaml(filepath=_CONSTANTS_PATH):
45
  if not os.path.exists(filepath):
46
  print(f"Warning: Constants file not found at {filepath}. Using fallback values.")
 
48
  with open(filepath, 'r', encoding='utf-8') as f:
49
  return yaml.safe_load(f)
50
 
51
+ def load_architectures_config(filepath=_MODEL_ARCHITECTURES_PATH):
52
+ if not os.path.exists(filepath):
53
+ print(f"Warning: Architectures file not found at {filepath}.")
54
+ return {}
55
+ with open(filepath, 'r', encoding='utf-8') as f:
56
+ return yaml.safe_load(f)
57
+
58
+ def load_features_config(filepath=_IMAGE_GEN_FEATURES_PATH):
59
+ if not os.path.exists(filepath):
60
+ print(f"Warning: Features file not found at {filepath}.")
61
+ return {}
62
+ with open(filepath, 'r', encoding='utf-8') as f:
63
+ return yaml.safe_load(f)
64
+
65
+ def load_model_defaults(filepath=_MODEL_DEFAULTS_PATH):
66
+ if not os.path.exists(filepath):
67
+ print(f"Warning: Model defaults file not found at {filepath}.")
68
+ return {}
69
+ with open(filepath, 'r', encoding='utf-8') as f:
70
+ return yaml.safe_load(f)
71
+
72
  def load_file_download_map(filepath=_FILE_LIST_PATH):
73
  if not os.path.exists(filepath):
74
  raise FileNotFoundError(f"The file list (for downloads) was not found at: {filepath}")
 
101
  }
102
  category_map_names = {
103
  "Checkpoint": "MODEL_MAP_CHECKPOINT",
104
+ "Checkpoints": "MODEL_MAP_CHECKPOINT"
105
  }
106
 
107
+ for category, architectures in model_data.items():
108
  if category in category_map_names:
109
  map_name = category_map_names[category]
110
+ if not isinstance(architectures, dict): continue
111
+
112
+ for arch, arch_data in architectures.items():
113
+ if not isinstance(arch_data, dict): continue
114
+
115
+ latent_type = arch_data.get('latent_type', 'latent')
116
+ models = arch_data.get('models', [])
117
+ if not isinstance(models, list): continue
118
+
119
+ for model in models:
120
+ display_name = model['display_name']
121
+ path_or_components = model.get('path') or model.get('components')
122
+ mod_category = model.get('category', None)
123
+
124
+ repo_id = ''
125
+ if isinstance(path_or_components, str):
126
+ download_info = download_map.get(path_or_components, {})
127
+ repo_id = download_info.get('repo_id', '')
128
+
129
+ model_tuple = (
130
+ repo_id,
131
+ path_or_components,
132
+ arch,
133
+ latent_type,
134
+ mod_category
135
+ )
136
+ model_maps[map_name][display_name] = model_tuple
137
+ model_maps["ALL_MODEL_MAP"][display_name] = model_tuple
138
 
139
  return model_maps
140
 
 
 
 
 
 
 
 
141
  try:
142
  ALL_FILE_DOWNLOAD_MAP = load_file_download_map()
143
  loaded_maps = load_models_from_yaml(download_map=ALL_FILE_DOWNLOAD_MAP)
144
  MODEL_MAP_CHECKPOINT = loaded_maps["MODEL_MAP_CHECKPOINT"]
145
  ALL_MODEL_MAP = loaded_maps["ALL_MODEL_MAP"]
146
 
147
+ category_to_model_type = {
148
+ "diffusion_models": "UNET",
149
+ "text_encoders": "TEXT_ENCODER",
150
+ "vae": "VAE",
151
+ "checkpoints": "SDXL",
152
+ "loras": "LORA",
153
+ "controlnet": "CONTROLNET",
154
+ "model_patches": "MODEL_PATCH",
155
+ "style_models": "STYLE",
156
+ "clip_vision": "CLIP_VISION",
157
+ "ipadapter": "IPADAPTER",
158
+ "ipadapter-flux": "IPADAPTER_FLUX"
159
+ }
160
+ for filename, file_info in ALL_FILE_DOWNLOAD_MAP.items():
161
+ if filename not in ALL_MODEL_MAP:
162
+ category = file_info.get('category')
163
+ model_type = category_to_model_type.get(category, 'UNKNOWN')
164
+ repo_id = file_info.get('repo_id', '')
165
+ ALL_MODEL_MAP[filename] = (repo_id, filename, model_type, None, None)
166
+
167
  MODEL_TYPE_MAP = {k: v[2] for k, v in ALL_MODEL_MAP.items()}
168
+
169
+ ARCH_CATEGORIES_MAP = {}
170
+ for display_name, info in MODEL_MAP_CHECKPOINT.items():
171
+ arch = info[2]
172
+ cat = info[4] if len(info) > 4 else None
173
+ if arch not in ARCH_CATEGORIES_MAP:
174
+ ARCH_CATEGORIES_MAP[arch] = []
175
+ if cat and cat not in ARCH_CATEGORIES_MAP[arch]:
176
+ ARCH_CATEGORIES_MAP[arch].append(cat)
177
 
178
  except Exception as e:
179
  print(f"FATAL: Could not load model configuration from YAML. Error: {e}")
180
  ALL_FILE_DOWNLOAD_MAP = {}
181
  MODEL_MAP_CHECKPOINT, ALL_MODEL_MAP = {}, {}
182
  MODEL_TYPE_MAP = {}
183
+ ARCH_CATEGORIES_MAP = {}
184
 
185
 
186
  try:
 
189
  MAX_EMBEDDINGS = _constants.get('MAX_EMBEDDINGS', 5)
190
  MAX_CONDITIONINGS = _constants.get('MAX_CONDITIONINGS', 10)
191
  MAX_CONTROLNETS = _constants.get('MAX_CONTROLNETS', 5)
192
+ MAX_IPADAPTERS = _constants.get('MAX_IPADAPTERS', 5)
193
  LORA_SOURCE_CHOICES = _constants.get('LORA_SOURCE_CHOICES', ["Civitai", "File"])
194
  RESOLUTION_MAP = _constants.get('RESOLUTION_MAP', {})
195
+ ARCHITECTURES_CONFIG = load_architectures_config()
196
+ FEATURES_CONFIG = load_features_config()
197
+ MODEL_DEFAULTS_CONFIG = load_model_defaults()
198
  except Exception as e:
199
  print(f"FATAL: Could not load constants from YAML. Error: {e}")
200
+ MAX_LORAS, MAX_EMBEDDINGS, MAX_CONDITIONINGS, MAX_CONTROLNETS, MAX_IPADAPTERS = 5, 5, 10, 5, 5
 
201
  LORA_SOURCE_CHOICES = ["Civitai", "File"]
202
  RESOLUTION_MAP = {}
203
+ ARCHITECTURES_CONFIG = {}
204
+ FEATURES_CONFIG = {}
205
+ MODEL_DEFAULTS_CONFIG = {}
requirements.txt CHANGED
@@ -1,10 +1,10 @@
1
- comfyui-frontend-package==1.42.10
2
- comfyui-workflow-templates==0.9.47
3
- comfyui-embedded-docs==0.4.3
4
- torch
5
  torchsde
6
- torchvision
7
- torchaudio
8
  numpy>=1.25.0
9
  einops
10
  transformers>=4.50.3
@@ -19,11 +19,11 @@ scipy
19
  tqdm
20
  psutil
21
  alembic
22
- SQLAlchemy
23
  filelock
24
  av>=14.2.0
25
  comfy-kitchen>=0.2.8
26
- comfy-aimdo>=0.2.12
27
  requests
28
  simpleeval>=1.0.0
29
  blake3
@@ -58,4 +58,5 @@ svglib
58
  trimesh[easy]
59
  yacs
60
  yapf
61
- onnxruntime-gpu
 
 
1
+ comfyui-frontend-package==1.42.15
2
+ comfyui-workflow-templates==0.9.66
3
+ comfyui-embedded-docs==0.4.4
4
+ torch==2.10.0
5
  torchsde
6
+ torchvision==0.25.0
7
+ torchaudio==2.10.0
8
  numpy>=1.25.0
9
  einops
10
  transformers>=4.50.3
 
19
  tqdm
20
  psutil
21
  alembic
22
+ SQLAlchemy>=2.0.0
23
  filelock
24
  av>=14.2.0
25
  comfy-kitchen>=0.2.8
26
+ comfy-aimdo==0.3.0
27
  requests
28
  simpleeval>=1.0.0
29
  blake3
 
58
  trimesh[easy]
59
  yacs
60
  yapf
61
+ onnxruntime-gpu
62
+ diffusers
ui/events.py CHANGED
@@ -8,150 +8,193 @@ from utils.app_utils import *
8
  from core.generation_logic import *
9
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
10
 
11
- from core.pipelines.controlnet_preprocessor import CPU_ONLY_PREPROCESSORS
12
- from utils.app_utils import PREPROCESSOR_MODEL_MAP, PREPROCESSOR_PARAMETER_MAP, save_uploaded_file_with_hash
13
- from ui.shared.ui_components import RESOLUTION_MAP, MAX_CONTROLNETS, MAX_EMBEDDINGS, MAX_CONDITIONINGS, MAX_LORAS, MAX_REFERENCE_LATENTS
14
 
15
 
16
- def on_model_change(model_display_name):
17
- """
18
- Callback function to update UI elements when the base model changes.
19
- It loads default values for steps and cfg from model_defaults.yaml.
20
- """
21
- defaults = ALL_MODEL_DEFAULTS.get('Default', {}).copy()
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- model_found = False
24
- for category, models_in_category in ALL_MODEL_DEFAULTS.items():
25
- if category == 'Default' or not isinstance(models_in_category, dict):
26
- continue
27
-
28
- if model_display_name in models_in_category:
29
- if '_defaults' in models_in_category:
30
- defaults.update(models_in_category['_defaults'])
31
- defaults.update(models_in_category[model_display_name])
32
- model_found = True
33
- break
34
 
35
- if not model_found:
36
- print(f"No specific defaults found for '{model_display_name}'. Using category or global defaults.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- steps_update = gr.update(value=defaults.get('steps'))
39
- cfg_update = gr.update(value=defaults.get('cfg'))
 
40
 
41
- return steps_update, cfg_update
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- def attach_event_handlers(ui_components, demo):
44
- def update_cn_input_visibility(choice):
45
- return {
46
- ui_components["cn_image_input"]: gr.update(visible=choice == "Image"),
47
- ui_components["cn_video_input"]: gr.update(visible=choice == "Video")
48
- }
49
- ui_components["cn_input_type"].change(
50
- fn=update_cn_input_visibility,
51
- inputs=[ui_components["cn_input_type"]],
52
- outputs=[ui_components["cn_image_input"], ui_components["cn_video_input"]]
53
- )
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- def update_preprocessor_models_dropdown(preprocessor_name):
56
- models = PREPROCESSOR_MODEL_MAP.get(preprocessor_name)
57
- if models:
58
- model_filenames = [m[1] for m in models]
59
- return gr.update(choices=model_filenames, value=model_filenames[0], visible=True)
60
- else:
61
- return gr.update(choices=[], value=None, visible=False)
62
-
63
- def update_preprocessor_settings_ui(preprocessor_name):
64
- from ui.layout import MAX_DYNAMIC_CONTROLS
65
- params = PREPROCESSOR_PARAMETER_MAP.get(preprocessor_name, [])
66
-
67
- slider_updates, dropdown_updates, checkbox_updates = [], [], []
68
-
69
- s_idx, d_idx, c_idx = 0, 0, 0
70
-
71
- for param in params:
72
- if s_idx + d_idx + c_idx >= MAX_DYNAMIC_CONTROLS: break
73
-
74
- name = param["name"]
75
- ptype = param["type"]
76
- config = param["config"]
77
- label = name.replace('_', ' ').title()
78
-
79
- if ptype == "INT" or ptype == "FLOAT":
80
- if s_idx < MAX_DYNAMIC_CONTROLS:
81
- slider_updates.append(gr.update(
82
- label=label,
83
- minimum=config.get('min', 0),
84
- maximum=config.get('max', 255),
85
- step=config.get('step', 0.1 if ptype == "FLOAT" else 1),
86
- value=config.get('default', 0),
87
- visible=True
88
- ))
89
- s_idx += 1
90
- elif isinstance(ptype, list):
91
- if d_idx < MAX_DYNAMIC_CONTROLS:
92
- dropdown_updates.append(gr.update(
93
- label=label,
94
- choices=ptype,
95
- value=config.get('default', ptype[0] if ptype else None),
96
- visible=True
97
- ))
98
- d_idx += 1
99
- elif ptype == "BOOLEAN":
100
- if c_idx < MAX_DYNAMIC_CONTROLS:
101
- checkbox_updates.append(gr.update(
102
- label=label,
103
- value=config.get('default', False),
104
- visible=True
105
- ))
106
- c_idx += 1
107
-
108
- for _ in range(s_idx, MAX_DYNAMIC_CONTROLS): slider_updates.append(gr.update(visible=False))
109
- for _ in range(d_idx, MAX_DYNAMIC_CONTROLS): dropdown_updates.append(gr.update(visible=False))
110
- for _ in range(c_idx, MAX_DYNAMIC_CONTROLS): checkbox_updates.append(gr.update(visible=False))
111
-
112
- return slider_updates + dropdown_updates + checkbox_updates
113
-
114
- def update_run_button_for_cpu(preprocessor_name):
115
- if preprocessor_name in CPU_ONLY_PREPROCESSORS:
116
- return gr.update(value="Run Preprocessor CPU Only", variant="primary"), gr.update(visible=False)
117
  else:
118
- return gr.update(value="Run Preprocessor", variant="primary"), gr.update(visible=True)
119
-
120
- ui_components["preprocessor_cn"].change(
121
- fn=update_preprocessor_models_dropdown,
122
- inputs=[ui_components["preprocessor_cn"]],
123
- outputs=[ui_components["preprocessor_model_cn"]]
124
- ).then(
125
- fn=update_preprocessor_settings_ui,
126
- inputs=[ui_components["preprocessor_cn"]],
127
- outputs=ui_components["cn_sliders"] + ui_components["cn_dropdowns"] + ui_components["cn_checkboxes"]
128
- ).then(
129
- fn=update_run_button_for_cpu,
130
- inputs=[ui_components["preprocessor_cn"]],
131
- outputs=[ui_components["run_cn"], ui_components["zero_gpu_cn"]]
132
- )
133
-
134
- all_dynamic_inputs = (
135
- ui_components["cn_sliders"] +
136
- ui_components["cn_dropdowns"] +
137
- ui_components["cn_checkboxes"]
138
- )
139
-
140
- ui_components["run_cn"].click(
141
- fn=run_cn_preprocessor_entry,
142
- inputs=[
143
- ui_components["cn_input_type"],
144
- ui_components["cn_image_input"],
145
- ui_components["cn_video_input"],
146
- ui_components["preprocessor_cn"],
147
- ui_components["preprocessor_model_cn"],
148
- ui_components["zero_gpu_cn"],
149
- ] + all_dynamic_inputs,
150
- outputs=[ui_components["output_gallery_cn"]]
151
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  def create_lora_event_handlers(prefix):
154
- lora_rows = ui_components[f'lora_rows_{prefix}']
 
155
  lora_ids = ui_components[f'lora_ids_{prefix}']
156
  lora_scales = ui_components[f'lora_scales_{prefix}']
157
  lora_uploads = ui_components[f'lora_uploads_{prefix}']
@@ -190,8 +233,362 @@ def attach_event_handlers(ui_components, demo):
190
  add_button.click(add_lora_row, [count_state], add_outputs, show_progress=False)
191
  del_button.click(del_lora_row, [count_state], del_outputs, show_progress=False)
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  def create_embedding_event_handlers(prefix):
194
- rows = ui_components[f'embedding_rows_{prefix}']
 
195
  ids = ui_components[f'embeddings_ids_{prefix}']
196
  files = ui_components[f'embeddings_files_{prefix}']
197
  count_state = ui_components[f'embedding_count_state_{prefix}']
@@ -224,7 +621,8 @@ def attach_event_handlers(ui_components, demo):
224
  del_button.click(fn=del_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
225
 
226
  def create_conditioning_event_handlers(prefix):
227
- rows = ui_components[f'conditioning_rows_{prefix}']
 
228
  prompts = ui_components[f'conditioning_prompts_{prefix}']
229
  count_state = ui_components[f'conditioning_count_state_{prefix}']
230
  add_button = ui_components[f'add_conditioning_button_{prefix}']
@@ -253,37 +651,6 @@ def attach_event_handlers(ui_components, demo):
253
  del_outputs = [count_state, add_button, del_button] + rows + prompts
254
  add_button.click(fn=add_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
255
  del_button.click(fn=del_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
256
-
257
- def create_reference_latent_event_handlers(prefix):
258
- rows = ui_components[f'reference_latent_rows_{prefix}']
259
- images = ui_components[f'reference_latent_images_{prefix}']
260
- count_state = ui_components[f'reference_latent_count_state_{prefix}']
261
- add_button = ui_components[f'add_reference_latent_button_{prefix}']
262
- del_button = ui_components[f'delete_reference_latent_button_{prefix}']
263
-
264
- def add_row(c):
265
- c += 1
266
- return {
267
- count_state: c,
268
- rows[c - 1]: gr.update(visible=True),
269
- add_button: gr.update(visible=c < MAX_REFERENCE_LATENTS),
270
- del_button: gr.update(visible=True),
271
- }
272
-
273
- def del_row(c):
274
- c -= 1
275
- return {
276
- count_state: c,
277
- rows[c]: gr.update(visible=False),
278
- images[c]: None,
279
- add_button: gr.update(visible=True),
280
- del_button: gr.update(visible=c > 0),
281
- }
282
-
283
- add_outputs = [count_state, add_button, del_button] + rows
284
- del_outputs = [count_state, add_button, del_button] + rows + images
285
- add_button.click(fn=add_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
286
- del_button.click(fn=del_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
287
 
288
  def on_vae_upload(file_obj):
289
  if not file_obj:
@@ -310,37 +677,48 @@ def attach_event_handlers(ui_components, demo):
310
  def create_run_event(prefix: str, task_type: str):
311
  run_inputs_map = {
312
  'model_display_name': ui_components[f'base_model_{prefix}'],
313
- 'positive_prompt': ui_components[f'prompt_{prefix}'],
314
- 'negative_prompt': ui_components[f'neg_prompt_{prefix}'],
315
- 'seed': ui_components[f'seed_{prefix}'],
316
- 'batch_size': ui_components[f'batch_size_{prefix}'],
317
- 'guidance_scale': ui_components[f'cfg_{prefix}'],
318
- 'num_inference_steps': ui_components[f'steps_{prefix}'],
319
- 'sampler': ui_components[f'sampler_{prefix}'],
320
- 'scheduler': ui_components[f'scheduler_{prefix}'],
321
- 'zero_gpu_duration': ui_components[f'zero_gpu_{prefix}'],
322
- 'civitai_api_key': ui_components.get(f'civitai_api_key_{prefix}'),
323
- 'clip_skip': ui_components[f'clip_skip_{prefix}'],
 
324
  'task_type': gr.State(task_type)
325
  }
326
 
327
  if task_type not in ['img2img', 'inpaint']:
328
- run_inputs_map.update({'width': ui_components[f'width_{prefix}'], 'height': ui_components[f'height_{prefix}']})
 
 
 
329
 
330
  task_specific_map = {
331
  'img2img': {'img2img_image': f'input_image_{prefix}', 'img2img_denoise': f'denoise_{prefix}'},
332
- 'inpaint': {'inpaint_image_dict': f'input_image_dict_{prefix}'},
333
- 'outpaint': {'outpaint_image': f'input_image_{prefix}', 'outpaint_left': f'outpaint_left_{prefix}', 'outpaint_top': f'outpaint_top_{prefix}', 'outpaint_right': f'outpaint_right_{prefix}', 'outpaint_bottom': f'outpaint_bottom_{prefix}'},
334
  'hires_fix': {'hires_image': f'input_image_{prefix}', 'hires_upscaler': f'hires_upscaler_{prefix}', 'hires_scale_by': f'hires_scale_by_{prefix}', 'hires_denoise': f'denoise_{prefix}'}
335
  }
336
  if task_type in task_specific_map:
337
  for key, comp_name in task_specific_map[task_type].items():
338
- run_inputs_map[key] = ui_components[comp_name]
 
339
 
340
  lora_data_components = ui_components.get(f'all_lora_components_flat_{prefix}', [])
 
 
 
 
 
 
341
  embedding_data_components = ui_components.get(f'all_embedding_components_flat_{prefix}', [])
342
  conditioning_data_components = ui_components.get(f'all_conditioning_components_flat_{prefix}', [])
343
- reference_latent_components = ui_components.get(f'all_reference_latent_components_flat_{prefix}', [])
344
 
345
  run_inputs_map['vae_source'] = ui_components.get(f'vae_source_{prefix}')
346
  run_inputs_map['vae_id'] = ui_components.get(f'vae_id_{prefix}')
@@ -348,153 +726,533 @@ def attach_event_handlers(ui_components, demo):
348
 
349
  input_keys = list(run_inputs_map.keys())
350
  input_list_flat = [v for v in run_inputs_map.values() if v is not None]
351
- input_list_flat += lora_data_components + embedding_data_components + conditioning_data_components + reference_latent_components
 
 
 
 
 
 
 
352
 
353
  def create_ui_inputs_dict(*args):
354
  valid_keys = [k for k in input_keys if run_inputs_map[k] is not None]
355
  ui_dict = dict(zip(valid_keys, args[:len(valid_keys)]))
356
  arg_idx = len(valid_keys)
357
-
358
- ui_dict['lora_data'] = list(args[arg_idx : arg_idx + len(lora_data_components)])
359
- arg_idx += len(lora_data_components)
360
- ui_dict['embedding_data'] = list(args[arg_idx : arg_idx + len(embedding_data_components)])
361
- arg_idx += len(embedding_data_components)
362
- ui_dict['conditioning_data'] = list(args[arg_idx : arg_idx + len(conditioning_data_components)])
363
- arg_idx += len(conditioning_data_components)
364
- ui_dict['reference_latent_data'] = list(args[arg_idx : arg_idx + len(reference_latent_components)])
365
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
  return ui_dict
368
 
369
- ui_components[f'run_{prefix}'].click(
370
- fn=lambda *args, progress=gr.Progress(track_tqdm=True): generate_image_wrapper(create_ui_inputs_dict(*args), progress),
371
- inputs=input_list_flat,
372
- outputs=[ui_components[f'result_{prefix}']]
373
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
 
376
  for prefix, task_type in [
377
  ("txt2img", "txt2img"), ("img2img", "img2img"), ("inpaint", "inpaint"),
378
  ("outpaint", "outpaint"), ("hires_fix", "hires_fix"),
379
  ]:
380
- model_dropdown = ui_components.get(f'base_model_{prefix}')
381
- steps_slider = ui_components.get(f'steps_{prefix}')
382
- cfg_slider = ui_components.get(f'cfg_{prefix}')
383
- if all([model_dropdown, steps_slider, cfg_slider]):
384
- model_dropdown.change(
385
- fn=on_model_change,
386
- inputs=[model_dropdown],
387
- outputs=[steps_slider, cfg_slider],
388
- show_progress=False
389
- )
390
 
391
- if f'add_lora_button_{prefix}' in ui_components:
392
- create_lora_event_handlers(prefix)
393
- lora_uploads = ui_components[f'lora_uploads_{prefix}']
394
- lora_ids = ui_components[f'lora_ids_{prefix}']
395
- lora_sources = ui_components[f'lora_sources_{prefix}']
396
- for i in range(MAX_LORAS):
397
- lora_uploads[i].upload(
398
- fn=on_lora_upload,
399
- inputs=[lora_uploads[i]],
400
- outputs=[lora_ids[i], lora_sources[i]],
401
- show_progress=False
402
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
 
404
- if f'add_embedding_button_{prefix}' in ui_components:
405
- create_embedding_event_handlers(prefix)
406
- if f'embeddings_uploads_{prefix}' in ui_components:
407
- emb_uploads = ui_components[f'embeddings_uploads_{prefix}']
408
- emb_ids = ui_components[f'embeddings_ids_{prefix}']
409
- emb_sources = ui_components[f'embeddings_sources_{prefix}']
410
- emb_files = ui_components[f'embeddings_files_{prefix}']
411
- for i in range(MAX_EMBEDDINGS):
412
- emb_uploads[i].upload(
413
- fn=on_embedding_upload,
414
- inputs=[emb_uploads[i]],
415
- outputs=[emb_ids[i], emb_sources[i], emb_files[i]],
416
- show_progress=False
417
- )
418
- if f'add_conditioning_button_{prefix}' in ui_components: create_conditioning_event_handlers(prefix)
419
- if f'add_reference_latent_button_{prefix}' in ui_components: create_reference_latent_event_handlers(prefix)
420
- if f'vae_source_{prefix}' in ui_components:
421
- upload_button = ui_components.get(f'vae_upload_button_{prefix}')
422
- if upload_button:
423
- upload_button.upload(
424
- fn=on_vae_upload,
425
- inputs=[upload_button],
426
- outputs=[
427
- ui_components[f'vae_id_{prefix}'],
428
- ui_components[f'vae_source_{prefix}'],
429
- ui_components[f'vae_file_{prefix}']
430
- ]
 
 
 
 
 
 
 
431
  )
 
432
 
 
 
 
 
 
 
 
 
 
433
  create_run_event(prefix, task_type)
434
 
435
- def on_aspect_ratio_change(ratio_key, model_display_name):
436
- model_type = MODEL_TYPE_MAP.get(model_display_name, 'sdxl').lower()
437
- res_map = RESOLUTION_MAP.get(model_type, RESOLUTION_MAP.get("sdxl", {}))
438
- w, h = res_map.get(ratio_key, (1024, 1024))
439
- return w, h
440
 
441
- for prefix in ["txt2img", "img2img", "inpaint", "outpaint", "hires_fix"]:
442
- if f'aspect_ratio_{prefix}' in ui_components:
443
- aspect_ratio_dropdown = ui_components[f'aspect_ratio_{prefix}']
444
- width_component = ui_components[f'width_{prefix}']
445
- height_component = ui_components[f'height_{prefix}']
446
- model_dropdown = ui_components[f'base_model_{prefix}']
447
- aspect_ratio_dropdown.change(fn=on_aspect_ratio_change, inputs=[aspect_ratio_dropdown, model_dropdown], outputs=[width_component, height_component], show_progress=False)
448
-
449
  if 'view_mode_inpaint' in ui_components:
450
  def toggle_inpaint_fullscreen_view(view_mode):
451
  is_fullscreen = (view_mode == "Fullscreen View")
452
  other_elements_visible = not is_fullscreen
453
  editor_height = 800 if is_fullscreen else 272
454
- return {
455
- ui_components['model_and_run_row_inpaint']: gr.update(visible=other_elements_visible),
456
  ui_components['prompts_column_inpaint']: gr.update(visible=other_elements_visible),
457
  ui_components['params_and_gallery_row_inpaint']: gr.update(visible=other_elements_visible),
458
  ui_components['accordion_wrapper_inpaint']: gr.update(visible=other_elements_visible),
459
  ui_components['input_image_dict_inpaint']: gr.update(height=editor_height),
460
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
461
 
462
- output_components = [
463
- ui_components['model_and_run_row_inpaint'], ui_components['prompts_column_inpaint'],
464
- ui_components['params_and_gallery_row_inpaint'], ui_components['accordion_wrapper_inpaint'],
 
465
  ui_components['input_image_dict_inpaint']
466
- ]
467
- ui_components['view_mode_inpaint'].change(fn=toggle_inpaint_fullscreen_view, inputs=[ui_components['view_mode_inpaint']], outputs=output_components, show_progress=False)
 
 
 
 
 
 
468
 
469
- def run_on_load():
470
- all_updates = {}
 
 
 
 
471
 
472
- default_preprocessor = "Canny Edge"
473
- model_update = update_preprocessor_models_dropdown(default_preprocessor)
474
- all_updates[ui_components["preprocessor_model_cn"]] = model_update
475
 
476
- settings_outputs = update_preprocessor_settings_ui(default_preprocessor)
477
- dynamic_outputs = ui_components["cn_sliders"] + ui_components["cn_dropdowns"] + ui_components["cn_checkboxes"]
478
- for i, comp in enumerate(dynamic_outputs):
479
- all_updates[comp] = settings_outputs[i]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
 
481
- run_button_update, zero_gpu_update = update_run_button_for_cpu(default_preprocessor)
482
- all_updates[ui_components["run_cn"]] = run_button_update
483
- all_updates[ui_components["zero_gpu_cn"]] = zero_gpu_update
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
 
485
  return all_updates
486
 
487
- all_load_outputs = [
488
- ui_components["preprocessor_model_cn"],
489
- *ui_components["cn_sliders"],
490
- *ui_components["cn_dropdowns"],
491
- *ui_components["cn_checkboxes"],
492
- ui_components["run_cn"],
493
- ui_components["zero_gpu_cn"]
494
- ]
 
 
 
 
 
 
495
 
496
  if all_load_outputs:
497
  demo.load(
498
  fn=run_on_load,
499
  outputs=all_load_outputs
500
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  from core.generation_logic import *
9
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
10
 
11
+ from utils.app_utils import save_uploaded_file_with_hash
12
+ from ui.shared.ui_components import RESOLUTION_MAP, MAX_CONTROLNETS, MAX_IPADAPTERS, MAX_EMBEDDINGS, MAX_CONDITIONINGS, MAX_LORAS
 
13
 
14
 
15
+ @lru_cache(maxsize=1)
16
+ def load_controlnet_config():
17
+ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18
+ _CN_MODEL_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'controlnet_models.yaml')
19
+ try:
20
+ print("--- Loading controlnet_models.yaml ---")
21
+ with open(_CN_MODEL_LIST_PATH, 'r', encoding='utf-8') as f:
22
+ config = yaml.safe_load(f)
23
+ print("--- ✅ controlnet_models.yaml loaded successfully ---")
24
+ return config.get("ControlNet", {})
25
+ except Exception as e:
26
+ print(f"Error loading controlnet_models.yaml: {e}")
27
+ return {}
28
+
29
+
30
+ def get_cn_defaults(arch_val):
31
+ cn_full_config = load_controlnet_config()
32
+ cn_config = cn_full_config.get(arch_val, [])
33
 
34
+ if not cn_config:
35
+ return [], None, [], None, "None"
36
+
37
+ all_types = sorted(list(set(t for model in cn_config for t in model.get("Type", []))))
38
+ default_type = all_types[0] if all_types else None
 
 
 
 
 
 
39
 
40
+ series_choices = []
41
+ if default_type:
42
+ series_choices = sorted(list(set(model.get("Series", "Default") for model in cn_config if default_type in model.get("Type", []))))
43
+ default_series = series_choices[0] if series_choices else None
44
+
45
+ filepath = "None"
46
+ if default_series and default_type:
47
+ for model in cn_config:
48
+ if model.get("Series") == default_series and default_type in model.get("Type", []):
49
+ filepath = model.get("Filepath")
50
+ break
51
+
52
+ return all_types, default_type, series_choices, default_series, filepath
53
+
54
+ @lru_cache(maxsize=1)
55
+ def load_diffsynth_controlnet_config():
56
+ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
57
+ _CN_MODEL_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'diffsynth_controlnet_models.yaml')
58
+ try:
59
+ print("--- Loading diffsynth_controlnet_models.yaml ---")
60
+ with open(_CN_MODEL_LIST_PATH, 'r', encoding='utf-8') as f:
61
+ config = yaml.safe_load(f)
62
+ print("--- ✅ diffsynth_controlnet_models.yaml loaded successfully ---")
63
+ return config.get("DiffSynth_ControlNet", {})
64
+ except Exception as e:
65
+ print(f"Error loading diffsynth_controlnet_models.yaml: {e}")
66
+ return {}
67
 
68
+ def get_diffsynth_cn_defaults(arch_val):
69
+ cn_full_config = load_diffsynth_controlnet_config()
70
+ cn_config = cn_full_config.get(arch_val, [])
71
 
72
+ if not cn_config:
73
+ return [], None, [], None, "None"
74
+
75
+ all_types = sorted(list(set(t for model in cn_config for t in model.get("Type", []))))
76
+ default_type = all_types[0] if all_types else None
77
+
78
+ series_choices = []
79
+ if default_type:
80
+ series_choices = sorted(list(set(model.get("Series", "Default") for model in cn_config if default_type in model.get("Type", []))))
81
+ default_series = series_choices[0] if series_choices else None
82
+
83
+ filepath = "None"
84
+ if default_series and default_type:
85
+ for model in cn_config:
86
+ if model.get("Series") == default_series and default_type in model.get("Type", []):
87
+ filepath = model.get("Filepath")
88
+ break
89
+
90
+ return all_types, default_type, series_choices, default_series, filepath
91
 
92
+
93
+ @lru_cache(maxsize=1)
94
+ def load_ipadapter_config():
95
+ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
96
+ _IPA_MODEL_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
97
+ try:
98
+ print("--- Loading ipadapter.yaml ---")
99
+ with open(_IPA_MODEL_LIST_PATH, 'r', encoding='utf-8') as f:
100
+ config = yaml.safe_load(f)
101
+ print("--- ✅ ipadapter.yaml loaded successfully ---")
102
+ return config
103
+ except Exception as e:
104
+ print(f"Error loading ipadapter.yaml: {e}")
105
+ return {}
106
+
107
+
108
+ def apply_data_to_ui(data, prefix, ui_components):
109
+ final_sampler = data.get('sampler') if data.get('sampler') in SAMPLER_CHOICES else SAMPLER_CHOICES[0]
110
+ default_scheduler = 'normal' if 'normal' in SCHEDULER_CHOICES else SCHEDULER_CHOICES[0]
111
+ final_scheduler = data.get('scheduler') if data.get('scheduler') in SCHEDULER_CHOICES else default_scheduler
112
+
113
+ updates = {}
114
+ base_model_name = data.get('base_model')
115
 
116
+ model_map = MODEL_MAP_CHECKPOINT
117
+
118
+ if f'base_model_{prefix}' in ui_components:
119
+ model_dropdown_component = ui_components[f'base_model_{prefix}']
120
+ if base_model_name and base_model_name in model_map:
121
+ updates[model_dropdown_component] = base_model_name
122
+ if f'model_arch_{prefix}' in ui_components:
123
+ m_type = MODEL_TYPE_MAP.get(base_model_name, "SDXL")
124
+ updates[ui_components[f'model_arch_{prefix}']] = m_type
125
+ if f'model_cat_{prefix}' in ui_components:
126
+ m_info = model_map.get(base_model_name)
127
+ m_cat = m_info[4] if m_info and len(m_info) > 4 else None
128
+ updates[ui_components[f'model_cat_{prefix}']] = m_cat if m_cat else "ALL"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  else:
130
+ updates[model_dropdown_component] = gr.update()
131
+
132
+ common_params = {
133
+ f'prompt_{prefix}': data.get('prompt', ''),
134
+ f'neg_prompt_{prefix}': data.get('negative_prompt', ''),
135
+ f'seed_{prefix}': data.get('seed', -1),
136
+ f'cfg_{prefix}': data.get('cfg_scale', 7.5),
137
+ f'steps_{prefix}': data.get('steps', 28),
138
+ f'sampler_{prefix}': final_sampler,
139
+ f'scheduler_{prefix}': final_scheduler,
140
+ }
141
+
142
+ for comp_name, value in common_params.items():
143
+ if comp_name in ui_components:
144
+ updates[ui_components[comp_name]] = value
145
+
146
+ if prefix == 'txt2img':
147
+ if f'width_{prefix}' in ui_components:
148
+ updates[ui_components[f'width_{prefix}']] = data.get('width', 1024)
149
+ if f'height_{prefix}' in ui_components:
150
+ updates[ui_components[f'height_{prefix}']] = data.get('height', 1024)
151
+
152
+ tab_indices = {"txt2img": 0, "img2img": 1, "inpaint": 2, "outpaint": 3, "hires_fix": 4}
153
+ tab_index = tab_indices.get(prefix, 0)
154
+
155
+ updates[ui_components['tabs']] = gr.Tabs(selected=tab_index)
156
+
157
+ return updates
158
+
159
+
160
+ def send_info_to_tab(image, prefix, ui_components):
161
+ if not image or not image.info.get('parameters', ''):
162
+ all_comps = [comp for comp_or_list in ui_components.values() for comp in (comp_or_list if isinstance(comp_or_list, list) else [comp_or_list])]
163
+ return {comp: gr.update() for comp in all_comps}
164
+
165
+ data = parse_parameters(image.info['parameters'])
166
+
167
+ image_input_map = {
168
+ "img2img": 'input_image_img2img',
169
+ "inpaint": 'input_image_dict_inpaint',
170
+ "outpaint": 'input_image_outpaint',
171
+ "hires_fix": 'input_image_hires_fix'
172
+ }
173
+
174
+ updates = apply_data_to_ui(data, prefix, ui_components)
175
+
176
+ if prefix in image_input_map and image_input_map[prefix] in ui_components:
177
+ component_key = image_input_map[prefix]
178
+ updates[ui_components[component_key]] = gr.update(value=image)
179
+
180
+ return updates
181
+
182
 
183
+ def send_info_by_hash(image, ui_components):
184
+ if not image or not image.info.get('parameters', ''):
185
+ all_comps = [comp for comp_or_list in ui_components.values() for comp in (comp_or_list if isinstance(comp_or_list, list) else [comp_or_list])]
186
+ return {comp: gr.update() for comp in all_comps}
187
+
188
+ data = parse_parameters(image.info['parameters'])
189
+
190
+ return apply_data_to_ui(data, "txt2img", ui_components)
191
+
192
+
193
+ def attach_event_handlers(ui_components, demo):
194
+
195
  def create_lora_event_handlers(prefix):
196
+ lora_rows = ui_components.get(f'lora_rows_{prefix}')
197
+ if not lora_rows: return
198
  lora_ids = ui_components[f'lora_ids_{prefix}']
199
  lora_scales = ui_components[f'lora_scales_{prefix}']
200
  lora_uploads = ui_components[f'lora_uploads_{prefix}']
 
233
  add_button.click(add_lora_row, [count_state], add_outputs, show_progress=False)
234
  del_button.click(del_lora_row, [count_state], del_outputs, show_progress=False)
235
 
236
+ def create_controlnet_event_handlers(prefix):
237
+ cn_rows = ui_components.get(f'controlnet_rows_{prefix}')
238
+ if not cn_rows: return
239
+ cn_types = ui_components[f'controlnet_types_{prefix}']
240
+ cn_series = ui_components[f'controlnet_series_{prefix}']
241
+ cn_filepaths = ui_components[f'controlnet_filepaths_{prefix}']
242
+ cn_images = ui_components[f'controlnet_images_{prefix}']
243
+ cn_strengths = ui_components[f'controlnet_strengths_{prefix}']
244
+
245
+ count_state = ui_components[f'controlnet_count_state_{prefix}']
246
+ add_button = ui_components[f'add_controlnet_button_{prefix}']
247
+ del_button = ui_components[f'delete_controlnet_button_{prefix}']
248
+ accordion = ui_components[f'controlnet_accordion_{prefix}']
249
+
250
+ arch_comp = ui_components.get(f'model_arch_{prefix}')
251
+ actual_arch_comp = arch_comp if arch_comp else gr.State("SDXL")
252
+
253
+ def add_cn_row(c):
254
+ c += 1
255
+ updates = {
256
+ count_state: c,
257
+ cn_rows[c-1]: gr.update(visible=True),
258
+ add_button: gr.update(visible=c < MAX_CONTROLNETS),
259
+ del_button: gr.update(visible=True)
260
+ }
261
+ return updates
262
+
263
+ def del_cn_row(c):
264
+ c -= 1
265
+ updates = {
266
+ count_state: c,
267
+ cn_rows[c]: gr.update(visible=False),
268
+ cn_images[c]: None,
269
+ cn_strengths[c]: 1.0,
270
+ add_button: gr.update(visible=True),
271
+ del_button: gr.update(visible=c > 0)
272
+ }
273
+ return updates
274
+
275
+ add_outputs = [count_state, add_button, del_button] + cn_rows
276
+ del_outputs = [count_state, add_button, del_button] + cn_rows + cn_images + cn_strengths
277
+ add_button.click(fn=add_cn_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
278
+ del_button.click(fn=del_cn_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
279
+
280
+ def on_cn_type_change(selected_type, arch_val):
281
+ cn_full_config = load_controlnet_config()
282
+
283
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
284
+ controlnet_key = architectures_dict.get(arch_val, {}).get("controlnet_key", arch_val)
285
+
286
+ cn_config = cn_full_config.get(controlnet_key, [])
287
+ series_choices = []
288
+ if selected_type:
289
+ series_choices = sorted(list(set(
290
+ model.get("Series", "Default") for model in cn_config
291
+ if selected_type in model.get("Type", [])
292
+ )))
293
+ default_series = series_choices[0] if series_choices else None
294
+ filepath = "None"
295
+ if default_series:
296
+ for model in cn_config:
297
+ if model.get("Series") == default_series and selected_type in model.get("Type", []):
298
+ filepath = model.get("Filepath")
299
+ break
300
+ return gr.update(choices=series_choices, value=default_series), filepath
301
+
302
+ def on_cn_series_change(selected_series, selected_type, arch_val):
303
+ cn_full_config = load_controlnet_config()
304
+
305
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
306
+ controlnet_key = architectures_dict.get(arch_val, {}).get("controlnet_key", arch_val)
307
+
308
+ cn_config = cn_full_config.get(controlnet_key, [])
309
+ filepath = "None"
310
+ if selected_series and selected_type:
311
+ for model in cn_config:
312
+ if model.get("Series") == selected_series and selected_type in model.get("Type", []):
313
+ filepath = model.get("Filepath")
314
+ break
315
+ return filepath
316
+
317
+ for i in range(MAX_CONTROLNETS):
318
+ cn_types[i].change(
319
+ fn=on_cn_type_change,
320
+ inputs=[cn_types[i], actual_arch_comp],
321
+ outputs=[cn_series[i], cn_filepaths[i]],
322
+ show_progress=False
323
+ )
324
+ cn_series[i].change(
325
+ fn=on_cn_series_change,
326
+ inputs=[cn_series[i], cn_types[i], actual_arch_comp],
327
+ outputs=[cn_filepaths[i]],
328
+ show_progress=False
329
+ )
330
+
331
+ def on_accordion_expand(*images):
332
+ return [gr.update() for _ in images]
333
+
334
+ accordion.expand(
335
+ fn=on_accordion_expand,
336
+ inputs=cn_images,
337
+ outputs=cn_images,
338
+ show_progress=False
339
+ )
340
+
341
+ def create_diffsynth_controlnet_event_handlers(prefix):
342
+ cn_rows = ui_components.get(f'diffsynth_controlnet_rows_{prefix}')
343
+ if not cn_rows: return
344
+ cn_types = ui_components[f'diffsynth_controlnet_types_{prefix}']
345
+ cn_series = ui_components[f'diffsynth_controlnet_series_{prefix}']
346
+ cn_filepaths = ui_components[f'diffsynth_controlnet_filepaths_{prefix}']
347
+ cn_images = ui_components[f'diffsynth_controlnet_images_{prefix}']
348
+ cn_strengths = ui_components[f'diffsynth_controlnet_strengths_{prefix}']
349
+
350
+ count_state = ui_components[f'diffsynth_controlnet_count_state_{prefix}']
351
+ add_button = ui_components[f'add_diffsynth_controlnet_button_{prefix}']
352
+ del_button = ui_components[f'delete_diffsynth_controlnet_button_{prefix}']
353
+ accordion = ui_components[f'diffsynth_controlnet_accordion_{prefix}']
354
+
355
+ arch_comp = ui_components.get(f'model_arch_{prefix}')
356
+ actual_arch_comp = arch_comp if arch_comp else gr.State("Z-Image")
357
+
358
+ def add_cn_row(c):
359
+ c += 1
360
+ updates = {
361
+ count_state: c,
362
+ cn_rows[c-1]: gr.update(visible=True),
363
+ add_button: gr.update(visible=c < MAX_CONTROLNETS),
364
+ del_button: gr.update(visible=True)
365
+ }
366
+ return updates
367
+
368
+ def del_cn_row(c):
369
+ c -= 1
370
+ updates = {
371
+ count_state: c,
372
+ cn_rows[c]: gr.update(visible=False),
373
+ cn_images[c]: None,
374
+ cn_strengths[c]: 1.0,
375
+ add_button: gr.update(visible=True),
376
+ del_button: gr.update(visible=c > 0)
377
+ }
378
+ return updates
379
+
380
+ add_outputs = [count_state, add_button, del_button] + cn_rows
381
+ del_outputs = [count_state, add_button, del_button] + cn_rows + cn_images + cn_strengths
382
+ add_button.click(fn=add_cn_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
383
+ del_button.click(fn=del_cn_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
384
+
385
+ def on_cn_type_change(selected_type, arch_val):
386
+ cn_full_config = load_diffsynth_controlnet_config()
387
+
388
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
389
+ controlnet_key = architectures_dict.get(arch_val, {}).get("controlnet_key", arch_val)
390
+
391
+ cn_config = cn_full_config.get(controlnet_key, [])
392
+ series_choices = []
393
+ if selected_type:
394
+ series_choices = sorted(list(set(
395
+ model.get("Series", "Default") for model in cn_config
396
+ if selected_type in model.get("Type", [])
397
+ )))
398
+ default_series = series_choices[0] if series_choices else None
399
+ filepath = "None"
400
+ if default_series:
401
+ for model in cn_config:
402
+ if model.get("Series") == default_series and selected_type in model.get("Type", []):
403
+ filepath = model.get("Filepath")
404
+ break
405
+ return gr.update(choices=series_choices, value=default_series), filepath
406
+
407
+ def on_cn_series_change(selected_series, selected_type, arch_val):
408
+ cn_full_config = load_diffsynth_controlnet_config()
409
+
410
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
411
+ controlnet_key = architectures_dict.get(arch_val, {}).get("controlnet_key", arch_val)
412
+
413
+ cn_config = cn_full_config.get(controlnet_key, [])
414
+ filepath = "None"
415
+ if selected_series and selected_type:
416
+ for model in cn_config:
417
+ if model.get("Series") == selected_series and selected_type in model.get("Type", []):
418
+ filepath = model.get("Filepath")
419
+ break
420
+ return filepath
421
+
422
+ for i in range(MAX_CONTROLNETS):
423
+ cn_types[i].change(
424
+ fn=on_cn_type_change,
425
+ inputs=[cn_types[i], actual_arch_comp],
426
+ outputs=[cn_series[i], cn_filepaths[i]],
427
+ show_progress=False
428
+ )
429
+ cn_series[i].change(
430
+ fn=on_cn_series_change,
431
+ inputs=[cn_series[i], cn_types[i], actual_arch_comp],
432
+ outputs=[cn_filepaths[i]],
433
+ show_progress=False
434
+ )
435
+
436
+ def on_accordion_expand(*images):
437
+ return [gr.update() for _ in images]
438
+
439
+ accordion.expand(
440
+ fn=on_accordion_expand,
441
+ inputs=cn_images,
442
+ outputs=cn_images,
443
+ show_progress=False
444
+ )
445
+
446
+ def create_flux1_ipadapter_event_handlers(prefix):
447
+ fipa_rows = ui_components.get(f'flux1_ipadapter_rows_{prefix}')
448
+ if not fipa_rows: return
449
+ count_state = ui_components[f'flux1_ipadapter_count_state_{prefix}']
450
+ add_button = ui_components[f'add_flux1_ipadapter_button_{prefix}']
451
+ del_button = ui_components[f'delete_flux1_ipadapter_button_{prefix}']
452
+
453
+ def add_fipa_row(c):
454
+ c += 1
455
+ return {
456
+ count_state: c,
457
+ fipa_rows[c - 1]: gr.update(visible=True),
458
+ add_button: gr.update(visible=c < MAX_IPADAPTERS),
459
+ del_button: gr.update(visible=True),
460
+ }
461
+
462
+ def del_fipa_row(c):
463
+ c -= 1
464
+ return {
465
+ count_state: c,
466
+ fipa_rows[c]: gr.update(visible=False),
467
+ add_button: gr.update(visible=True),
468
+ del_button: gr.update(visible=c > 0),
469
+ }
470
+
471
+ add_outputs = [count_state, add_button, del_button] + fipa_rows
472
+ del_outputs = [count_state, add_button, del_button] + fipa_rows
473
+ add_button.click(fn=add_fipa_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
474
+ del_button.click(fn=del_fipa_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
475
+
476
+ def create_style_event_handlers(prefix):
477
+ style_rows = ui_components.get(f'style_rows_{prefix}')
478
+ if not style_rows: return
479
+ count_state = ui_components[f'style_count_state_{prefix}']
480
+ add_button = ui_components[f'add_style_button_{prefix}']
481
+ del_button = ui_components[f'delete_style_button_{prefix}']
482
+
483
+ def add_style_row(c):
484
+ c += 1
485
+ return {
486
+ count_state: c,
487
+ style_rows[c - 1]: gr.update(visible=True),
488
+ add_button: gr.update(visible=c < 5),
489
+ del_button: gr.update(visible=True),
490
+ }
491
+
492
+ def del_style_row(c):
493
+ c -= 1
494
+ return {
495
+ count_state: c,
496
+ style_rows[c]: gr.update(visible=False),
497
+ add_button: gr.update(visible=True),
498
+ del_button: gr.update(visible=c > 0),
499
+ }
500
+
501
+ add_outputs = [count_state, add_button, del_button] + style_rows
502
+ del_outputs = [count_state, add_button, del_button] + style_rows
503
+ add_button.click(fn=add_style_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
504
+ del_button.click(fn=del_style_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
505
+
506
+ def create_ipadapter_event_handlers(prefix):
507
+ ipa_rows = ui_components.get(f'ipadapter_rows_{prefix}')
508
+ if not ipa_rows: return
509
+ ipa_lora_strengths = ui_components[f'ipadapter_lora_strengths_{prefix}']
510
+ ipa_final_preset = ui_components[f'ipadapter_final_preset_{prefix}']
511
+ ipa_final_lora_strength = ui_components[f'ipadapter_final_lora_strength_{prefix}']
512
+ count_state = ui_components[f'ipadapter_count_state_{prefix}']
513
+ add_button = ui_components[f'add_ipadapter_button_{prefix}']
514
+ del_button = ui_components[f'delete_ipadapter_button_{prefix}']
515
+ accordion = ui_components[f'ipadapter_accordion_{prefix}']
516
+
517
+ def add_ipa_row(c):
518
+ c += 1
519
+ return {
520
+ count_state: c,
521
+ ipa_rows[c - 1]: gr.update(visible=True),
522
+ add_button: gr.update(visible=c < MAX_IPADAPTERS),
523
+ del_button: gr.update(visible=True),
524
+ }
525
+
526
+ def del_ipa_row(c):
527
+ c -= 1
528
+ return {
529
+ count_state: c,
530
+ ipa_rows[c]: gr.update(visible=False),
531
+ add_button: gr.update(visible=True),
532
+ del_button: gr.update(visible=c > 0),
533
+ }
534
+
535
+ add_outputs = [count_state, add_button, del_button] + ipa_rows
536
+ del_outputs = [count_state, add_button, del_button] + ipa_rows
537
+ add_button.click(fn=add_ipa_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
538
+ del_button.click(fn=del_ipa_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
539
+
540
+ def on_preset_change(preset_value):
541
+ config = load_ipadapter_config()
542
+ faceid_presets = []
543
+ if config:
544
+ faceid_presets.extend(config.get("IPAdapter_FaceID_presets", {}).get("SDXL", []))
545
+ faceid_presets.extend(config.get("IPAdapter_FaceID_presets", {}).get("SD1.5", []))
546
+
547
+ is_visible = preset_value in faceid_presets
548
+ updates = [gr.update(visible=is_visible)] * (MAX_IPADAPTERS + 1)
549
+ return updates
550
+
551
+ all_lora_strength_sliders = [ipa_final_lora_strength] + ipa_lora_strengths
552
+ ipa_final_preset.change(fn=on_preset_change, inputs=[ipa_final_preset], outputs=all_lora_strength_sliders, show_progress=False)
553
+
554
+ accordion.expand(fn=lambda *imgs: [gr.update() for _ in imgs], inputs=ui_components[f'ipadapter_images_{prefix}'], outputs=ui_components[f'ipadapter_images_{prefix}'], show_progress=False)
555
+
556
+ def create_reference_latent_event_handlers(prefix):
557
+ ref_rows = ui_components.get(f'reference_latent_rows_{prefix}')
558
+ if not ref_rows: return
559
+ count_state = ui_components[f'reference_latent_count_state_{prefix}']
560
+ add_button = ui_components[f'add_reference_latent_button_{prefix}']
561
+ del_button = ui_components[f'delete_reference_latent_button_{prefix}']
562
+ images = ui_components[f'reference_latent_images_{prefix}']
563
+
564
+ def add_ref_row(c):
565
+ c += 1
566
+ return {
567
+ count_state: c,
568
+ ref_rows[c - 1]: gr.update(visible=True),
569
+ add_button: gr.update(visible=c < 10),
570
+ del_button: gr.update(visible=True),
571
+ }
572
+
573
+ def del_ref_row(c):
574
+ c -= 1
575
+ return {
576
+ count_state: c,
577
+ ref_rows[c]: gr.update(visible=False),
578
+ images[c]: None,
579
+ add_button: gr.update(visible=True),
580
+ del_button: gr.update(visible=c > 0),
581
+ }
582
+
583
+ add_outputs = [count_state, add_button, del_button] + ref_rows
584
+ del_outputs = [count_state, add_button, del_button] + ref_rows + images
585
+ add_button.click(fn=add_ref_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
586
+ del_button.click(fn=del_ref_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
587
+
588
+
589
  def create_embedding_event_handlers(prefix):
590
+ rows = ui_components.get(f'embedding_rows_{prefix}')
591
+ if not rows: return
592
  ids = ui_components[f'embeddings_ids_{prefix}']
593
  files = ui_components[f'embeddings_files_{prefix}']
594
  count_state = ui_components[f'embedding_count_state_{prefix}']
 
621
  del_button.click(fn=del_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
622
 
623
  def create_conditioning_event_handlers(prefix):
624
+ rows = ui_components.get(f'conditioning_rows_{prefix}')
625
+ if not rows: return
626
  prompts = ui_components[f'conditioning_prompts_{prefix}']
627
  count_state = ui_components[f'conditioning_count_state_{prefix}']
628
  add_button = ui_components[f'add_conditioning_button_{prefix}']
 
651
  del_outputs = [count_state, add_button, del_button] + rows + prompts
652
  add_button.click(fn=add_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
653
  del_button.click(fn=del_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
 
655
  def on_vae_upload(file_obj):
656
  if not file_obj:
 
677
  def create_run_event(prefix: str, task_type: str):
678
  run_inputs_map = {
679
  'model_display_name': ui_components[f'base_model_{prefix}'],
680
+ 'positive_prompt': ui_components.get(f'prompt_{prefix}') or ui_components.get(f'{prefix}_positive_prompt'),
681
+ 'negative_prompt': ui_components.get(f'neg_prompt_{prefix}') or ui_components.get(f'{prefix}_negative_prompt'),
682
+ 'seed': ui_components.get(f'seed_{prefix}') or ui_components.get(f'{prefix}_seed'),
683
+ 'batch_size': ui_components.get(f'batch_size_{prefix}') or ui_components.get(f'{prefix}_batch_size'),
684
+ 'guidance_scale': ui_components.get(f'cfg_{prefix}') or ui_components.get(f'{prefix}_cfg'),
685
+ 'num_inference_steps': ui_components.get(f'steps_{prefix}') or ui_components.get(f'{prefix}_steps'),
686
+ 'sampler': ui_components.get(f'sampler_{prefix}') or ui_components.get(f'{prefix}_sampler_name'),
687
+ 'scheduler': ui_components.get(f'scheduler_{prefix}') or ui_components.get(f'{prefix}_scheduler'),
688
+ 'zero_gpu_duration': ui_components.get(f'zero_gpu_{prefix}'),
689
+
690
+ 'clip_skip': ui_components.get(f'clip_skip_{prefix}'),
691
+ 'guidance': ui_components.get(f'guidance_{prefix}'),
692
  'task_type': gr.State(task_type)
693
  }
694
 
695
  if task_type not in ['img2img', 'inpaint']:
696
+ run_inputs_map.update({
697
+ 'width': ui_components.get(f'width_{prefix}') or ui_components.get(f'{prefix}_width'),
698
+ 'height': ui_components.get(f'height_{prefix}') or ui_components.get(f'{prefix}_height')
699
+ })
700
 
701
  task_specific_map = {
702
  'img2img': {'img2img_image': f'input_image_{prefix}', 'img2img_denoise': f'denoise_{prefix}'},
703
+ 'inpaint': {'inpaint_image_dict': f'input_image_dict_{prefix}', 'grow_mask_by': f'grow_mask_by_{prefix}'},
704
+ 'outpaint': {'outpaint_image': f'input_image_{prefix}', 'left': f'left_{prefix}', 'top': f'top_{prefix}', 'right': f'right_{prefix}', 'bottom': f'bottom_{prefix}', 'feathering': f'feathering_{prefix}'},
705
  'hires_fix': {'hires_image': f'input_image_{prefix}', 'hires_upscaler': f'hires_upscaler_{prefix}', 'hires_scale_by': f'hires_scale_by_{prefix}', 'hires_denoise': f'denoise_{prefix}'}
706
  }
707
  if task_type in task_specific_map:
708
  for key, comp_name in task_specific_map[task_type].items():
709
+ if comp_name in ui_components:
710
+ run_inputs_map[key] = ui_components[comp_name]
711
 
712
  lora_data_components = ui_components.get(f'all_lora_components_flat_{prefix}', [])
713
+ controlnet_data_components = ui_components.get(f'all_controlnet_components_flat_{prefix}', [])
714
+ diffsynth_controlnet_data_components = ui_components.get(f'all_diffsynth_controlnet_components_flat_{prefix}', [])
715
+ ipadapter_data_components = ui_components.get(f'all_ipadapter_components_flat_{prefix}', [])
716
+ sd3_ipadapter_data_components = ui_components.get(f'all_sd3_ipadapter_components_flat_{prefix}', [])
717
+ flux1_ipadapter_data_components = ui_components.get(f'all_flux1_ipadapter_components_flat_{prefix}', [])
718
+ style_data_components = ui_components.get(f'all_style_components_flat_{prefix}', [])
719
  embedding_data_components = ui_components.get(f'all_embedding_components_flat_{prefix}', [])
720
  conditioning_data_components = ui_components.get(f'all_conditioning_components_flat_{prefix}', [])
721
+ reference_latent_data_components = ui_components.get(f'all_reference_latent_components_flat_{prefix}', [])
722
 
723
  run_inputs_map['vae_source'] = ui_components.get(f'vae_source_{prefix}')
724
  run_inputs_map['vae_id'] = ui_components.get(f'vae_id_{prefix}')
 
726
 
727
  input_keys = list(run_inputs_map.keys())
728
  input_list_flat = [v for v in run_inputs_map.values() if v is not None]
729
+ all_chains = [
730
+ lora_data_components, controlnet_data_components, diffsynth_controlnet_data_components, ipadapter_data_components,
731
+ sd3_ipadapter_data_components, flux1_ipadapter_data_components, style_data_components,
732
+ embedding_data_components, conditioning_data_components, reference_latent_data_components
733
+ ]
734
+ for chain in all_chains:
735
+ if chain:
736
+ input_list_flat.extend(chain)
737
 
738
  def create_ui_inputs_dict(*args):
739
  valid_keys = [k for k in input_keys if run_inputs_map[k] is not None]
740
  ui_dict = dict(zip(valid_keys, args[:len(valid_keys)]))
741
  arg_idx = len(valid_keys)
 
 
 
 
 
 
 
 
742
 
743
+ def assign_chain_data(chain_key, components_list):
744
+ nonlocal arg_idx
745
+ if components_list:
746
+ ui_dict[chain_key] = list(args[arg_idx : arg_idx + len(components_list)])
747
+ arg_idx += len(components_list)
748
+
749
+ assign_chain_data('lora_data', lora_data_components)
750
+ assign_chain_data('controlnet_data', controlnet_data_components)
751
+ assign_chain_data('diffsynth_controlnet_data', diffsynth_controlnet_data_components)
752
+ assign_chain_data('ipadapter_data', ipadapter_data_components)
753
+ assign_chain_data('sd3_ipadapter_chain', sd3_ipadapter_data_components)
754
+ assign_chain_data('flux1_ipadapter_data', flux1_ipadapter_data_components)
755
+ assign_chain_data('style_data', style_data_components)
756
+ assign_chain_data('embedding_data', embedding_data_components)
757
+ assign_chain_data('conditioning_data', conditioning_data_components)
758
+ assign_chain_data('reference_latent_data', reference_latent_data_components)
759
 
760
  return ui_dict
761
 
762
+ run_btn = ui_components.get(f'run_{prefix}') or ui_components.get(f'{prefix}_run_button')
763
+ res_gal = ui_components.get(f'result_{prefix}') or ui_components.get(f'{prefix}_output_gallery')
764
+ if run_btn and res_gal:
765
+ run_btn.click(
766
+ fn=lambda *args, progress=gr.Progress(track_tqdm=True): generate_image_wrapper(create_ui_inputs_dict(*args), progress),
767
+ inputs=input_list_flat,
768
+ outputs=[res_gal]
769
+ )
770
+
771
+ def make_update_fn(m_comp, cat_comp, cs_comp, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, diffsynth_cn_types, diffsynth_cn_series, diffsynth_cn_filepaths, ipa_preset, lora_acc, cn_acc, diffsynth_cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp):
772
+ def update_fn(*args):
773
+ arch = args[0]
774
+ category = args[1]
775
+ current_ar = args[2] if len(args) > 2 else None
776
+ from core.settings import MODEL_TYPE_MAP, MODEL_MAP_CHECKPOINT, FEATURES_CONFIG, ARCHITECTURES_CONFIG, MODEL_DEFAULTS_CONFIG, ARCH_CATEGORIES_MAP
777
+ from utils.app_utils import get_model_generation_defaults
778
+
779
+ if arch == "ALL":
780
+ valid_cats = list(set(cat for cats in ARCH_CATEGORIES_MAP.values() for cat in cats))
781
+ else:
782
+ valid_cats = ARCH_CATEGORIES_MAP.get(arch, [])
783
+
784
+ cat_choices = ["ALL"] + sorted(valid_cats)
785
+ new_category = category if category in cat_choices else "ALL"
786
+
787
+ choices = []
788
+ for name, info in MODEL_MAP_CHECKPOINT.items():
789
+ m_arch = info[2]
790
+ m_cat = info[4] if len(info) > 4 else None
791
+ arch_match = (arch == "ALL" or m_arch == arch)
792
+ cat_match = (new_category == "ALL" or m_cat == new_category)
793
+ if arch_match and cat_match:
794
+ choices.append(name)
795
+
796
+ val = choices[0] if choices else None
797
+
798
+ updates = {
799
+ m_comp: gr.update(choices=choices, value=val),
800
+ cat_comp: gr.update(choices=cat_choices, value=new_category)
801
+ }
802
+
803
+ m_type = MODEL_TYPE_MAP.get(val, "SDXL") if val else "SDXL"
804
+
805
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
806
+ arch_model_type = architectures_dict.get(m_type, {}).get("model_type", m_type.lower().replace(" ", "").replace(".", ""))
807
+
808
+ arch_features = FEATURES_CONFIG.get(arch_model_type, FEATURES_CONFIG.get('default', {}))
809
+ enabled_chains = arch_features.get('enabled_chains', [])
810
+
811
+ if lora_acc: updates[lora_acc] = gr.update(visible=('lora' in enabled_chains))
812
+ if cn_acc: updates[cn_acc] = gr.update(visible=('controlnet' in enabled_chains))
813
+ if diffsynth_cn_acc: updates[diffsynth_cn_acc] = gr.update(visible=('controlnet_model_patch' in enabled_chains))
814
+ if ipa_acc: updates[ipa_acc] = gr.update(visible=('ipadapter' in enabled_chains))
815
+ if flux1_ipa_acc: updates[flux1_ipa_acc] = gr.update(visible=('flux1_ipadapter' in enabled_chains))
816
+ if sd3_ipa_acc: updates[sd3_ipa_acc] = gr.update(visible=('sd3_ipadapter' in enabled_chains))
817
+ if style_acc: updates[style_acc] = gr.update(visible=('style' in enabled_chains))
818
+ if embed_acc: updates[embed_acc] = gr.update(visible=('embedding' in enabled_chains))
819
+ if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
820
+ if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
821
+
822
+ if cs_comp:
823
+ updates[cs_comp] = gr.update(visible=(arch_model_type == "sd15"))
824
+ if guidance_comp:
825
+ updates[guidance_comp] = gr.update(visible=(arch_model_type == "flux1"))
826
+
827
+ if ar_comp:
828
+ res_key = arch_model_type
829
+ if res_key not in RESOLUTION_MAP:
830
+ res_key = 'sdxl'
831
+ res_map = RESOLUTION_MAP.get(res_key, {})
832
+ target_ar = current_ar if current_ar in res_map else (list(res_map.keys())[0] if res_map else "1:1 (Square)")
833
+ updates[ar_comp] = gr.update(choices=list(res_map.keys()), value=target_ar)
834
+ if width_comp and height_comp and target_ar in res_map:
835
+ updates[width_comp] = gr.update(value=res_map[target_ar][0])
836
+ updates[height_comp] = gr.update(value=res_map[target_ar][1])
837
+
838
+ controlnet_key = architectures_dict.get(m_type, {}).get("controlnet_key", m_type)
839
+
840
+ all_types, default_type, series_choices, default_series, filepath = get_cn_defaults(controlnet_key)
841
+ for t_comp in cn_types:
842
+ updates[t_comp] = gr.update(choices=all_types, value=default_type)
843
+ for s_comp in cn_series:
844
+ updates[s_comp] = gr.update(choices=series_choices, value=default_series)
845
+ for f_comp in cn_filepaths:
846
+ updates[f_comp] = filepath
847
+
848
+ diffsynth_all_types, diffsynth_default_type, diffsynth_series_choices, diffsynth_default_series, diffsynth_filepath = get_diffsynth_cn_defaults(controlnet_key)
849
+ for t_comp in diffsynth_cn_types:
850
+ updates[t_comp] = gr.update(choices=diffsynth_all_types, value=diffsynth_default_type)
851
+ for s_comp in diffsynth_cn_series:
852
+ updates[s_comp] = gr.update(choices=diffsynth_series_choices, value=diffsynth_default_series)
853
+ for f_comp in diffsynth_cn_filepaths:
854
+ updates[f_comp] = diffsynth_filepath
855
+
856
+ if ipa_preset and (arch_model_type in ["sdxl", "sd15", "sd35"]):
857
+ config = load_ipadapter_config()
858
+ ipa_arch_key = "SDXL" if arch_model_type in ["sdxl", "sd35"] else "SD1.5"
859
+ std_presets = config.get("IPAdapter_presets", {}).get(ipa_arch_key, [])
860
+ face_presets = config.get("IPAdapter_FaceID_presets", {}).get(ipa_arch_key, [])
861
+ all_ipa_presets = std_presets + face_presets
862
+ default_ipa = all_ipa_presets[0] if all_ipa_presets else None
863
+ updates[ipa_preset] = gr.update(choices=all_ipa_presets, value=default_ipa)
864
+
865
+ defaults = get_model_generation_defaults(val, arch_model_type, MODEL_DEFAULTS_CONFIG)
866
+ if steps_comp: updates[steps_comp] = gr.update(value=defaults.get('steps'))
867
+ if cfg_comp: updates[cfg_comp] = gr.update(value=defaults.get('cfg'))
868
+ if sampler_comp: updates[sampler_comp] = gr.update(value=defaults.get('sampler_name'))
869
+ if scheduler_comp: updates[scheduler_comp] = gr.update(value=defaults.get('scheduler'))
870
+ if prompt_comp: updates[prompt_comp] = gr.update(value=defaults.get('positive_prompt'))
871
+ if neg_prompt_comp: updates[neg_prompt_comp] = gr.update(value=defaults.get('negative_prompt'))
872
+
873
+ return updates
874
+ return update_fn
875
+
876
+ def make_model_change_fn(cat_comp_ref, cs_comp, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, diffsynth_cn_types, diffsynth_cn_series, diffsynth_cn_filepaths, arch_comp_ref, ipa_preset, lora_acc, cn_acc, diffsynth_cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp):
877
+ def change_fn(*args):
878
+ model_name = args[0]
879
+ idx = 1
880
+ current_arch = args[idx] if arch_comp_ref and idx < len(args) else None
881
+ if arch_comp_ref: idx += 1
882
+ current_cat = args[idx] if cat_comp_ref and idx < len(args) else None
883
+ if cat_comp_ref: idx += 1
884
+ current_ar = args[idx] if idx < len(args) else None
885
+ from core.settings import MODEL_TYPE_MAP, FEATURES_CONFIG, ARCHITECTURES_CONFIG, MODEL_DEFAULTS_CONFIG, ARCH_CATEGORIES_MAP, MODEL_MAP_CHECKPOINT
886
+ from utils.app_utils import get_model_generation_defaults
887
+ m_type = MODEL_TYPE_MAP.get(model_name, "SDXL")
888
+
889
+ m_info = MODEL_MAP_CHECKPOINT.get(model_name)
890
+ m_cat = m_info[4] if m_info and len(m_info) > 4 else None
891
+ if not m_cat: m_cat = "ALL"
892
+
893
+ updates = {}
894
+ target_arch = m_type
895
+ if arch_comp_ref:
896
+ if current_arch == "ALL":
897
+ updates[arch_comp_ref] = gr.update()
898
+ target_arch = "ALL"
899
+ else:
900
+ updates[arch_comp_ref] = m_type
901
+
902
+ if cat_comp_ref:
903
+ if target_arch == "ALL":
904
+ valid_cats = list(set(cat for cats in ARCH_CATEGORIES_MAP.values() for cat in cats))
905
+ else:
906
+ valid_cats = ARCH_CATEGORIES_MAP.get(target_arch, [])
907
+ cat_choices = ["ALL"] + sorted(valid_cats)
908
+
909
+ if current_cat == "ALL":
910
+ updates[cat_comp_ref] = gr.update(choices=cat_choices)
911
+ else:
912
+ updates[cat_comp_ref] = gr.update(choices=cat_choices, value=m_cat)
913
+
914
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
915
+ arch_model_type = architectures_dict.get(m_type, {}).get("model_type", m_type.lower().replace(" ", "").replace(".", ""))
916
+
917
+ arch_features = FEATURES_CONFIG.get(arch_model_type, FEATURES_CONFIG.get('default', {}))
918
+ enabled_chains = arch_features.get('enabled_chains', [])
919
+
920
+ if lora_acc: updates[lora_acc] = gr.update(visible=('lora' in enabled_chains))
921
+ if cn_acc: updates[cn_acc] = gr.update(visible=('controlnet' in enabled_chains))
922
+ if diffsynth_cn_acc: updates[diffsynth_cn_acc] = gr.update(visible=('controlnet_model_patch' in enabled_chains))
923
+ if ipa_acc: updates[ipa_acc] = gr.update(visible=('ipadapter' in enabled_chains))
924
+ if flux1_ipa_acc: updates[flux1_ipa_acc] = gr.update(visible=('flux1_ipadapter' in enabled_chains))
925
+ if sd3_ipa_acc: updates[sd3_ipa_acc] = gr.update(visible=('sd3_ipadapter' in enabled_chains))
926
+ if style_acc: updates[style_acc] = gr.update(visible=('style' in enabled_chains))
927
+ if embed_acc: updates[embed_acc] = gr.update(visible=('embedding' in enabled_chains))
928
+ if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
929
+ if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
930
+
931
+ if cs_comp:
932
+ updates[cs_comp] = gr.update(visible=(arch_model_type == "sd15"))
933
+ if guidance_comp:
934
+ updates[guidance_comp] = gr.update(visible=(arch_model_type == "flux1"))
935
+
936
+ if ar_comp:
937
+ res_key = arch_model_type
938
+ if res_key not in RESOLUTION_MAP:
939
+ res_key = 'sdxl'
940
+ res_map = RESOLUTION_MAP.get(res_key, {})
941
+ target_ar = current_ar if current_ar in res_map else (list(res_map.keys())[0] if res_map else "1:1 (Square)")
942
+ updates[ar_comp] = gr.update(choices=list(res_map.keys()), value=target_ar)
943
+ if width_comp and height_comp and target_ar in res_map:
944
+ updates[width_comp] = gr.update(value=res_map[target_ar][0])
945
+ updates[height_comp] = gr.update(value=res_map[target_ar][1])
946
+
947
+ controlnet_key = architectures_dict.get(m_type, {}).get("controlnet_key", m_type)
948
+
949
+ all_types, default_type, series_choices, default_series, filepath = get_cn_defaults(controlnet_key)
950
+ for t_comp in cn_types:
951
+ updates[t_comp] = gr.update(choices=all_types, value=default_type)
952
+ for s_comp in cn_series:
953
+ updates[s_comp] = gr.update(choices=series_choices, value=default_series)
954
+ for f_comp in cn_filepaths:
955
+ updates[f_comp] = filepath
956
+
957
+ diffsynth_all_types, diffsynth_default_type, diffsynth_series_choices, diffsynth_default_series, diffsynth_filepath = get_diffsynth_cn_defaults(controlnet_key)
958
+ for t_comp in diffsynth_cn_types:
959
+ updates[t_comp] = gr.update(choices=diffsynth_all_types, value=diffsynth_default_type)
960
+ for s_comp in diffsynth_cn_series:
961
+ updates[s_comp] = gr.update(choices=diffsynth_series_choices, value=diffsynth_default_series)
962
+ for f_comp in diffsynth_cn_filepaths:
963
+ updates[f_comp] = diffsynth_filepath
964
+
965
+ if ipa_preset and (arch_model_type in ["sdxl", "sd15", "sd35"]):
966
+ config = load_ipadapter_config()
967
+ ipa_arch_key = "SDXL" if arch_model_type in ["sdxl", "sd35"] else "SD1.5"
968
+ std_presets = config.get("IPAdapter_presets", {}).get(ipa_arch_key, [])
969
+ face_presets = config.get("IPAdapter_FaceID_presets", {}).get(ipa_arch_key, [])
970
+ all_ipa_presets = std_presets + face_presets
971
+ default_ipa = all_ipa_presets[0] if all_ipa_presets else None
972
+ updates[ipa_preset] = gr.update(choices=all_ipa_presets, value=default_ipa)
973
+
974
+ defaults = get_model_generation_defaults(model_name, arch_model_type, MODEL_DEFAULTS_CONFIG)
975
+ if steps_comp: updates[steps_comp] = gr.update(value=defaults.get('steps'))
976
+ if cfg_comp: updates[cfg_comp] = gr.update(value=defaults.get('cfg'))
977
+ if sampler_comp: updates[sampler_comp] = gr.update(value=defaults.get('sampler_name'))
978
+ if scheduler_comp: updates[scheduler_comp] = gr.update(value=defaults.get('scheduler'))
979
+ if prompt_comp: updates[prompt_comp] = gr.update(value=defaults.get('positive_prompt'))
980
+ if neg_prompt_comp: updates[neg_prompt_comp] = gr.update(value=defaults.get('negative_prompt'))
981
+
982
+ return updates
983
+ return change_fn
984
 
985
 
986
  for prefix, task_type in [
987
  ("txt2img", "txt2img"), ("img2img", "img2img"), ("inpaint", "inpaint"),
988
  ("outpaint", "outpaint"), ("hires_fix", "hires_fix"),
989
  ]:
 
 
 
 
 
 
 
 
 
 
990
 
991
+ arch_comp = ui_components.get(f'model_arch_{prefix}')
992
+ cat_comp = ui_components.get(f'model_cat_{prefix}')
993
+ model_comp = ui_components.get(f'base_model_{prefix}')
994
+ clip_skip_comp = ui_components.get(f'clip_skip_{prefix}') or ui_components.get(f'{prefix}_clip_skip')
995
+ guidance_comp = ui_components.get(f'guidance_{prefix}') or ui_components.get(f'{prefix}_guidance')
996
+ aspect_ratio_comp = ui_components.get(f'aspect_ratio_{prefix}') or ui_components.get(f'{prefix}_aspect_ratio_dropdown')
997
+ width_comp = ui_components.get(f'width_{prefix}') or ui_components.get(f'{prefix}_width')
998
+ height_comp = ui_components.get(f'height_{prefix}') or ui_components.get(f'{prefix}_height')
999
+
1000
+ cn_types_list = ui_components.get(f'controlnet_types_{prefix}', [])
1001
+ cn_series_list = ui_components.get(f'controlnet_series_{prefix}', [])
1002
+ cn_filepaths_list = ui_components.get(f'controlnet_filepaths_{prefix}', [])
1003
+
1004
+ diffsynth_cn_types_list = ui_components.get(f'diffsynth_controlnet_types_{prefix}', [])
1005
+ diffsynth_cn_series_list = ui_components.get(f'diffsynth_controlnet_series_{prefix}', [])
1006
+ diffsynth_cn_filepaths_list = ui_components.get(f'diffsynth_controlnet_filepaths_{prefix}', [])
1007
+
1008
+ lora_accordion = ui_components.get(f'lora_accordion_{prefix}')
1009
+ cn_accordion = ui_components.get(f'controlnet_accordion_{prefix}')
1010
+ diffsynth_cn_accordion = ui_components.get(f'diffsynth_controlnet_accordion_{prefix}')
1011
+ ipa_accordion = ui_components.get(f'ipadapter_accordion_{prefix}')
1012
+ sd3_ipa_accordion = ui_components.get(f'sd3_ipadapter_accordion_{prefix}')
1013
+ flux1_ipa_accordion = ui_components.get(f'flux1_ipadapter_accordion_{prefix}')
1014
+ style_accordion = ui_components.get(f'style_accordion_{prefix}')
1015
+ embedding_accordion = ui_components.get(f'embedding_accordion_{prefix}')
1016
+ conditioning_accordion = ui_components.get(f'conditioning_accordion_{prefix}')
1017
+ ref_latent_accordion = ui_components.get(f'reference_latent_accordion_{prefix}')
1018
+
1019
+ ipa_preset_list = ui_components.get(f'ipadapter_final_preset_{prefix}')
1020
+
1021
+ prompt_comp = ui_components.get(f'prompt_{prefix}') or ui_components.get(f'{prefix}_positive_prompt')
1022
+ neg_prompt_comp = ui_components.get(f'neg_prompt_{prefix}') or ui_components.get(f'{prefix}_negative_prompt')
1023
+ steps_comp = ui_components.get(f'steps_{prefix}') or ui_components.get(f'{prefix}_steps')
1024
+ cfg_comp = ui_components.get(f'cfg_{prefix}') or ui_components.get(f'{prefix}_cfg')
1025
+ sampler_comp = ui_components.get(f'sampler_{prefix}') or ui_components.get(f'{prefix}_sampler_name')
1026
+ scheduler_comp = ui_components.get(f'scheduler_{prefix}') or ui_components.get(f'{prefix}_scheduler')
1027
+
1028
+ extra_comps = [prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp, width_comp, height_comp]
1029
+ valid_extra_comps = [c for c in extra_comps if c is not None]
1030
+
1031
+ if arch_comp and cat_comp and model_comp:
1032
+ outputs = [model_comp, cat_comp]
1033
+ if clip_skip_comp: outputs.append(clip_skip_comp)
1034
+ if guidance_comp: outputs.append(guidance_comp)
1035
+ if aspect_ratio_comp: outputs.append(aspect_ratio_comp)
1036
+ outputs.extend(cn_types_list + cn_series_list + cn_filepaths_list)
1037
+ outputs.extend(diffsynth_cn_types_list + diffsynth_cn_series_list + diffsynth_cn_filepaths_list)
1038
+ if lora_accordion: outputs.append(lora_accordion)
1039
+ if cn_accordion: outputs.append(cn_accordion)
1040
+ if diffsynth_cn_accordion: outputs.append(diffsynth_cn_accordion)
1041
+ if ipa_accordion: outputs.append(ipa_accordion)
1042
+ if sd3_ipa_accordion: outputs.append(sd3_ipa_accordion)
1043
+ if flux1_ipa_accordion: outputs.append(flux1_ipa_accordion)
1044
+ if style_accordion: outputs.append(style_accordion)
1045
+ if embedding_accordion: outputs.append(embedding_accordion)
1046
+ if conditioning_accordion: outputs.append(conditioning_accordion)
1047
+ if ref_latent_accordion: outputs.append(ref_latent_accordion)
1048
+ if ipa_preset_list: outputs.append(ipa_preset_list)
1049
+
1050
+ outputs.extend(valid_extra_comps)
1051
+
1052
+ update_fn = make_update_fn(
1053
+ model_comp, cat_comp, clip_skip_comp, aspect_ratio_comp, width_comp, height_comp,
1054
+ cn_types_list, cn_series_list, cn_filepaths_list,
1055
+ diffsynth_cn_types_list, diffsynth_cn_series_list, diffsynth_cn_filepaths_list,
1056
+ ipa_preset_list, lora_accordion, cn_accordion, diffsynth_cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
1057
+ ref_latent_accordion, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp
1058
+ )
1059
+ inputs = [arch_comp, cat_comp]
1060
+ if aspect_ratio_comp:
1061
+ inputs.append(aspect_ratio_comp)
1062
+ arch_comp.change(fn=update_fn, inputs=inputs, outputs=outputs)
1063
+ cat_comp.change(fn=update_fn, inputs=inputs, outputs=outputs)
1064
 
1065
+ if model_comp:
1066
+ outputs2 = []
1067
+ if arch_comp: outputs2.append(arch_comp)
1068
+ if cat_comp: outputs2.append(cat_comp)
1069
+ if clip_skip_comp: outputs2.append(clip_skip_comp)
1070
+ if guidance_comp: outputs2.append(guidance_comp)
1071
+ if aspect_ratio_comp: outputs2.append(aspect_ratio_comp)
1072
+ outputs2.extend(cn_types_list + cn_series_list + cn_filepaths_list)
1073
+ outputs2.extend(diffsynth_cn_types_list + diffsynth_cn_series_list + diffsynth_cn_filepaths_list)
1074
+ if lora_accordion: outputs2.append(lora_accordion)
1075
+ if cn_accordion: outputs2.append(cn_accordion)
1076
+ if diffsynth_cn_accordion: outputs2.append(diffsynth_cn_accordion)
1077
+ if ipa_accordion: outputs2.append(ipa_accordion)
1078
+ if sd3_ipa_accordion: outputs2.append(sd3_ipa_accordion)
1079
+ if flux1_ipa_accordion: outputs2.append(flux1_ipa_accordion)
1080
+ if style_accordion: outputs2.append(style_accordion)
1081
+ if embedding_accordion: outputs2.append(embedding_accordion)
1082
+ if conditioning_accordion: outputs2.append(conditioning_accordion)
1083
+ if ref_latent_accordion: outputs2.append(ref_latent_accordion)
1084
+ if ipa_preset_list: outputs2.append(ipa_preset_list)
1085
+
1086
+ outputs2.extend(valid_extra_comps)
1087
+
1088
+ if outputs2:
1089
+ inputs2 = [model_comp]
1090
+ if arch_comp: inputs2.append(arch_comp)
1091
+ if cat_comp: inputs2.append(cat_comp)
1092
+ if aspect_ratio_comp: inputs2.append(aspect_ratio_comp)
1093
+ change_fn = make_model_change_fn(
1094
+ cat_comp, clip_skip_comp, aspect_ratio_comp, width_comp, height_comp,
1095
+ cn_types_list, cn_series_list, cn_filepaths_list,
1096
+ diffsynth_cn_types_list, diffsynth_cn_series_list, diffsynth_cn_filepaths_list,
1097
+ arch_comp, ipa_preset_list, lora_accordion, cn_accordion, diffsynth_cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
1098
+ ref_latent_accordion, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp
1099
  )
1100
+ model_comp.change(fn=change_fn, inputs=inputs2, outputs=outputs2)
1101
 
1102
+ create_lora_event_handlers(prefix)
1103
+ create_controlnet_event_handlers(prefix)
1104
+ create_diffsynth_controlnet_event_handlers(prefix)
1105
+ create_ipadapter_event_handlers(prefix)
1106
+ create_embedding_event_handlers(prefix)
1107
+ create_conditioning_event_handlers(prefix)
1108
+ create_flux1_ipadapter_event_handlers(prefix)
1109
+ create_style_event_handlers(prefix)
1110
+ create_reference_latent_event_handlers(prefix)
1111
  create_run_event(prefix, task_type)
1112
 
 
 
 
 
 
1113
 
 
 
 
 
 
 
 
 
1114
  if 'view_mode_inpaint' in ui_components:
1115
  def toggle_inpaint_fullscreen_view(view_mode):
1116
  is_fullscreen = (view_mode == "Fullscreen View")
1117
  other_elements_visible = not is_fullscreen
1118
  editor_height = 800 if is_fullscreen else 272
1119
+
1120
+ updates = {
1121
  ui_components['prompts_column_inpaint']: gr.update(visible=other_elements_visible),
1122
  ui_components['params_and_gallery_row_inpaint']: gr.update(visible=other_elements_visible),
1123
  ui_components['accordion_wrapper_inpaint']: gr.update(visible=other_elements_visible),
1124
  ui_components['input_image_dict_inpaint']: gr.update(height=editor_height),
1125
  }
1126
+
1127
+ model_and_run_rows = ui_components.get('model_and_run_row_inpaint', [])
1128
+ for row in model_and_run_rows:
1129
+ updates[row] = gr.update(visible=other_elements_visible)
1130
+
1131
+ return updates
1132
+
1133
+ output_components = []
1134
+ model_and_run_rows = ui_components.get('model_and_run_row_inpaint', [])
1135
+ if isinstance(model_and_run_rows, list):
1136
+ output_components.extend(model_and_run_rows)
1137
+ else:
1138
+ output_components.append(model_and_run_rows)
1139
 
1140
+ output_components.extend([
1141
+ ui_components['prompts_column_inpaint'],
1142
+ ui_components['params_and_gallery_row_inpaint'],
1143
+ ui_components['accordion_wrapper_inpaint'],
1144
  ui_components['input_image_dict_inpaint']
1145
+ ])
1146
+
1147
+ ui_components['view_mode_inpaint'].change(
1148
+ fn=toggle_inpaint_fullscreen_view,
1149
+ inputs=[ui_components['view_mode_inpaint']],
1150
+ outputs=output_components,
1151
+ show_progress=False
1152
+ )
1153
 
1154
+ def initialize_all_cn_dropdowns():
1155
+ from core.settings import MODEL_TYPE_MAP, MODEL_MAP_CHECKPOINT, ARCHITECTURES_CONFIG
1156
+ default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOINT else None
1157
+ default_m_type = MODEL_TYPE_MAP.get(default_model_name, "SDXL") if default_model_name else "SDXL"
1158
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
1159
+ controlnet_key = architectures_dict.get(default_m_type, {}).get("controlnet_key", default_m_type)
1160
 
1161
+ all_types, default_type, series_choices, default_series, filepath = get_cn_defaults(controlnet_key)
1162
+ diffsynth_all_types, diffsynth_default_type, diffsynth_series_choices, diffsynth_default_series, diffsynth_filepath = get_diffsynth_cn_defaults(controlnet_key)
 
1163
 
1164
+ updates = {}
1165
+ for prefix in ["txt2img", "img2img", "inpaint", "outpaint", "hires_fix"]:
1166
+ if f'controlnet_types_{prefix}' in ui_components:
1167
+ for type_dd in ui_components[f'controlnet_types_{prefix}']:
1168
+ updates[type_dd] = gr.update(choices=all_types, value=default_type)
1169
+ for series_dd in ui_components[f'controlnet_series_{prefix}']:
1170
+ updates[series_dd] = gr.update(choices=series_choices, value=default_series)
1171
+ for filepath_state in ui_components[f'controlnet_filepaths_{prefix}']:
1172
+ updates[filepath_state] = filepath
1173
+
1174
+ if f'diffsynth_controlnet_types_{prefix}' in ui_components:
1175
+ for type_dd in ui_components[f'diffsynth_controlnet_types_{prefix}']:
1176
+ updates[type_dd] = gr.update(choices=diffsynth_all_types, value=diffsynth_default_type)
1177
+ for series_dd in ui_components[f'diffsynth_controlnet_series_{prefix}']:
1178
+ updates[series_dd] = gr.update(choices=diffsynth_series_choices, value=diffsynth_default_series)
1179
+ for filepath_state in ui_components[f'diffsynth_controlnet_filepaths_{prefix}']:
1180
+ updates[filepath_state] = diffsynth_filepath
1181
+
1182
+ return updates
1183
 
1184
+ def initialize_all_ipa_dropdowns():
1185
+ config = load_ipadapter_config()
1186
+ if not config: return {}
1187
+
1188
+ from core.settings import MODEL_TYPE_MAP, MODEL_MAP_CHECKPOINT, ARCHITECTURES_CONFIG
1189
+ default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOINT else None
1190
+ default_m_type = MODEL_TYPE_MAP.get(default_model_name, "SDXL") if default_model_name else "SDXL"
1191
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
1192
+ arch_model_type = architectures_dict.get(default_m_type, {}).get("model_type", default_m_type.lower().replace(" ", "").replace(".", ""))
1193
+ ipa_arch_key = "SDXL" if arch_model_type in ["sdxl", "sd35"] else "SD1.5"
1194
+
1195
+ unified_presets = config.get("IPAdapter_presets", {}).get(ipa_arch_key, [])
1196
+ faceid_presets = config.get("IPAdapter_FaceID_presets", {}).get(ipa_arch_key, [])
1197
+
1198
+ all_presets = unified_presets + faceid_presets
1199
+ default_preset = all_presets[0] if all_presets else None
1200
+ is_faceid_default = default_preset in faceid_presets
1201
+
1202
+ lora_strength_update = gr.update(visible=is_faceid_default)
1203
+
1204
+ updates = {}
1205
+ for prefix in ["txt2img", "img2img", "inpaint", "outpaint", "hires_fix"]:
1206
+ if f'ipadapter_final_preset_{prefix}' in ui_components:
1207
+ for lora_strength_slider in ui_components[f'ipadapter_lora_strengths_{prefix}']:
1208
+ updates[lora_strength_slider] = lora_strength_update
1209
+ updates[ui_components[f'ipadapter_final_preset_{prefix}']] = gr.update(choices=all_presets, value=default_preset)
1210
+ updates[ui_components[f'ipadapter_final_lora_strength_{prefix}']] = lora_strength_update
1211
+ return updates
1212
+
1213
+ def run_on_load():
1214
+ cn_updates = initialize_all_cn_dropdowns()
1215
+ ipa_updates = initialize_all_ipa_dropdowns()
1216
+
1217
+ all_updates = {**cn_updates, **ipa_updates}
1218
 
1219
  return all_updates
1220
 
1221
+ all_load_outputs = []
1222
+ for prefix in ["txt2img", "img2img", "inpaint", "outpaint", "hires_fix"]:
1223
+ if f'controlnet_types_{prefix}' in ui_components:
1224
+ all_load_outputs.extend(ui_components[f'controlnet_types_{prefix}'])
1225
+ all_load_outputs.extend(ui_components[f'controlnet_series_{prefix}'])
1226
+ all_load_outputs.extend(ui_components[f'controlnet_filepaths_{prefix}'])
1227
+ if f'diffsynth_controlnet_types_{prefix}' in ui_components:
1228
+ all_load_outputs.extend(ui_components[f'diffsynth_controlnet_types_{prefix}'])
1229
+ all_load_outputs.extend(ui_components[f'diffsynth_controlnet_series_{prefix}'])
1230
+ all_load_outputs.extend(ui_components[f'diffsynth_controlnet_filepaths_{prefix}'])
1231
+ if f'ipadapter_final_preset_{prefix}' in ui_components:
1232
+ all_load_outputs.extend(ui_components[f'ipadapter_lora_strengths_{prefix}'])
1233
+ all_load_outputs.append(ui_components[f'ipadapter_final_preset_{prefix}'])
1234
+ all_load_outputs.append(ui_components[f'ipadapter_final_lora_strength_{prefix}'])
1235
 
1236
  if all_load_outputs:
1237
  demo.load(
1238
  fn=run_on_load,
1239
  outputs=all_load_outputs
1240
+ )
1241
+
1242
+ def on_aspect_ratio_change(ratio_key, model_display_name):
1243
+ from core.settings import MODEL_TYPE_MAP, ARCHITECTURES_CONFIG
1244
+ m_type = MODEL_TYPE_MAP.get(model_display_name, 'SDXL')
1245
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
1246
+ arch_model_type = architectures_dict.get(m_type, {}).get("model_type", m_type.lower().replace(" ", "").replace(".", ""))
1247
+
1248
+ res_map = RESOLUTION_MAP.get(arch_model_type, RESOLUTION_MAP.get("sdxl", {}))
1249
+ w, h = res_map.get(ratio_key, (1024, 1024))
1250
+ return w, h
1251
+
1252
+ for prefix in ["txt2img", "img2img", "inpaint", "outpaint", "hires_fix"]:
1253
+ aspect_ratio_dropdown = ui_components.get(f'aspect_ratio_{prefix}') or ui_components.get(f'{prefix}_aspect_ratio_dropdown')
1254
+ width_component = ui_components.get(f'width_{prefix}') or ui_components.get(f'{prefix}_width')
1255
+ height_component = ui_components.get(f'height_{prefix}') or ui_components.get(f'{prefix}_height')
1256
+ model_dropdown = ui_components.get(f'base_model_{prefix}')
1257
+ if aspect_ratio_dropdown and width_component and height_component and model_dropdown:
1258
+ aspect_ratio_dropdown.change(fn=on_aspect_ratio_change, inputs=[aspect_ratio_dropdown, model_dropdown], outputs=[width_component, height_component], show_progress=False)
ui/layout.py CHANGED
@@ -6,83 +6,37 @@ from .shared import txt2img_ui, img2img_ui, inpaint_ui, outpaint_ui, hires_fix_u
6
 
7
  MAX_DYNAMIC_CONTROLS = 10
8
 
9
- def get_preprocessor_choices():
10
- from nodes import NODE_DISPLAY_NAME_MAPPINGS
11
-
12
- preprocessor_names = [
13
- display_name for class_name, display_name in NODE_DISPLAY_NAME_MAPPINGS.items()
14
- if "Preprocessor" in class_name or "Segmentor" in class_name or
15
- "Estimator" in class_name or "Detector" in class_name
16
- ]
17
- return sorted(list(set(preprocessor_names)))
18
-
19
-
20
  def build_ui(event_handler_function):
21
  ui_components = {}
22
 
23
  with gr.Blocks() as demo:
24
- gr.Markdown("# ImageGen - FLUX.2")
25
  gr.Markdown(
26
- "This demo is a streamlined version of the [Comfy web UI](https://github.com/RioShiina47/comfy-webui)'s ImgGen functionality. "
27
  "Other versions are also available: "
28
- "[Z-Image](https://huggingface.co/spaces/RioShiina/ImageGen-Z-Image), "
29
- "[Qwen-Image](https://huggingface.co/spaces/RioShiina/ImageGen-Qwen-Image), "
30
  "[Anima](https://huggingface.co/spaces/RioShiina/ImageGen-Anima), "
31
- "[Illstrious](https://huggingface.co/spaces/RioShiina/ImageGen-Illstrious), "
32
  "[NoobAI](https://huggingface.co/spaces/RioShiina/ImageGen-NoobAI), "
33
- "[Pony](https://huggingface.co/spaces/RioShiina/ImageGen-Pony1), "
34
- "[SDXL](https://huggingface.co/spaces/RioShiina/ImageGen-SDXL)"
35
  )
36
  with gr.Tabs(elem_id="tabs_container") as tabs:
37
- with gr.TabItem("FLUX.2", id=0):
38
- with gr.Tabs(elem_id="image_gen_tabs") as image_gen_tabs:
39
- with gr.TabItem("Txt2Img", id=0):
40
- ui_components.update(txt2img_ui.create_ui())
41
-
42
- with gr.TabItem("Img2Img", id=1):
43
- ui_components.update(img2img_ui.create_ui())
44
 
45
- with gr.TabItem("Inpaint", id=2):
46
- ui_components.update(inpaint_ui.create_ui())
47
 
48
- with gr.TabItem("Outpaint", id=3):
49
- ui_components.update(outpaint_ui.create_ui())
50
 
51
- with gr.TabItem("Hires. Fix", id=4):
52
- ui_components.update(hires_fix_ui.create_ui())
53
-
54
- ui_components['image_gen_tabs'] = image_gen_tabs
55
 
56
- with gr.TabItem("Controlnet Preprocessors", id=1):
57
- gr.Markdown("## ControlNet Auxiliary Preprocessors")
58
- gr.Markdown("Powered by [Fannovel16/comfyui_controlnet_aux](https://github.com/Fannovel16/comfyui_controlnet_aux).")
59
- gr.Markdown("Upload an image or video to process it with a ControlNet preprocessor.")
60
- with gr.Row():
61
- with gr.Column(scale=1):
62
- cn_input_type = gr.Radio(["Image", "Video"], label="Input Type", value="Image")
63
- cn_image_input = gr.Image(type="pil", label="Input Image", visible=True, height=384)
64
- cn_video_input = gr.Video(label="Input Video", visible=False)
65
- preprocessor_cn = gr.Dropdown(label="Preprocessor", choices=get_preprocessor_choices(), value="Canny Edge")
66
- preprocessor_model_cn = gr.Dropdown(label="Preprocessor Model", choices=[], value=None, visible=False)
67
- with gr.Column() as preprocessor_settings_ui:
68
- cn_sliders, cn_dropdowns, cn_checkboxes = [], [], []
69
- for i in range(MAX_DYNAMIC_CONTROLS):
70
- cn_sliders.append(gr.Slider(visible=False, label=f"dyn_slider_{i}"))
71
- cn_dropdowns.append(gr.Dropdown(visible=False, label=f"dyn_dropdown_{i}"))
72
- cn_checkboxes.append(gr.Checkbox(visible=False, label=f"dyn_checkbox_{i}"))
73
- run_cn = gr.Button("Run Preprocessor", variant="primary")
74
- with gr.Column(scale=1):
75
- output_gallery_cn = gr.Gallery(label="Output", show_label=False, object_fit="contain", height=512)
76
- zero_gpu_cn = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60, Max: 120", info="Optional")
77
- ui_components.update({
78
- "cn_input_type": cn_input_type, "cn_image_input": cn_image_input, "cn_video_input": cn_video_input,
79
- "preprocessor_cn": preprocessor_cn, "preprocessor_model_cn": preprocessor_model_cn, "run_cn": run_cn,
80
- "zero_gpu_cn": zero_gpu_cn, "output_gallery_cn": output_gallery_cn,
81
- "preprocessor_settings_ui": preprocessor_settings_ui, "cn_sliders": cn_sliders,
82
- "cn_dropdowns": cn_dropdowns, "cn_checkboxes": cn_checkboxes
83
- })
84
-
85
  ui_components["tabs"] = tabs
 
86
 
87
  gr.Markdown("<div style='text-align: center; margin-top: 20px;'>Made by RioShiina with ❤️<br><a href='https://github.com/RioShiina47' target='_blank'>GitHub</a> | <a href='https://huggingface.co/RioShiina' target='_blank'>Hugging Face</a> | <a href='https://civitai.com/user/RioShiina' target='_blank'>Civitai</a></div>")
88
 
 
6
 
7
  MAX_DYNAMIC_CONTROLS = 10
8
 
 
 
 
 
 
 
 
 
 
 
 
9
  def build_ui(event_handler_function):
10
  ui_components = {}
11
 
12
  with gr.Blocks() as demo:
13
+ gr.Markdown("# ImageGen - FLUX.2-KV")
14
  gr.Markdown(
15
+ "This demo is a streamlined version of the [Comfy web UI](https://github.com/RioShiina47/comfy-webui)'s [ImageGen](https://huggingface.co/spaces/RioShiina/ImageGen) functionality. "
16
  "Other versions are also available: "
 
 
17
  "[Anima](https://huggingface.co/spaces/RioShiina/ImageGen-Anima), "
18
+ "[Illustrious](https://huggingface.co/spaces/RioShiina/ImageGen-Illustrious), "
19
  "[NoobAI](https://huggingface.co/spaces/RioShiina/ImageGen-NoobAI), "
20
+ "[Pony](https://huggingface.co/spaces/RioShiina/ImageGen-Pony)"
 
21
  )
22
  with gr.Tabs(elem_id="tabs_container") as tabs:
23
+ with gr.TabItem("Txt2Img", id=0):
24
+ ui_components.update(txt2img_ui.create_ui())
25
+
26
+ with gr.TabItem("Img2Img", id=1):
27
+ ui_components.update(img2img_ui.create_ui())
 
 
28
 
29
+ with gr.TabItem("Inpaint", id=2):
30
+ ui_components.update(inpaint_ui.create_ui())
31
 
32
+ with gr.TabItem("Outpaint", id=3):
33
+ ui_components.update(outpaint_ui.create_ui())
34
 
35
+ with gr.TabItem("Hires. Fix", id=4):
36
+ ui_components.update(hires_fix_ui.create_ui())
 
 
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  ui_components["tabs"] = tabs
39
+ ui_components["image_gen_tabs"] = tabs
40
 
41
  gr.Markdown("<div style='text-align: center; margin-top: 20px;'>Made by RioShiina with ❤️<br><a href='https://github.com/RioShiina47' target='_blank'>GitHub</a> | <a href='https://huggingface.co/RioShiina' target='_blank'>Hugging Face</a> | <a href='https://civitai.com/user/RioShiina' target='_blank'>Civitai</a></div>")
42
 
ui/shared/hires_fix_ui.py CHANGED
@@ -3,8 +3,10 @@ from core.settings import MODEL_MAP_CHECKPOINT
3
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
4
  from .ui_components import (
5
  create_lora_settings_ui,
6
- create_embedding_ui,
7
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui,
 
 
8
  create_reference_latent_ui
9
  )
10
 
@@ -13,12 +15,16 @@ def create_ui():
13
  components = {}
14
 
15
  with gr.Column():
 
 
16
  with gr.Row():
 
17
  components[f'base_model_{prefix}'] = gr.Dropdown(
18
  label="Base Model",
19
  choices=list(MODEL_MAP_CHECKPOINT.keys()),
20
  value=list(MODEL_MAP_CHECKPOINT.keys())[0],
21
- scale=3
 
22
  )
23
  with gr.Column(scale=1):
24
  components[f'run_{prefix}'] = gr.Button("Run Hires. Fix", variant="primary")
@@ -27,8 +33,8 @@ def create_ui():
27
  with gr.Column(scale=1):
28
  components[f'input_image_{prefix}'] = gr.Image(type="pil", label="Input Image", height=255)
29
  with gr.Column(scale=2):
30
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3, placeholder="Describe the final image...")
31
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3, value="")
32
 
33
  with gr.Row():
34
  with gr.Column(scale=1):
@@ -46,31 +52,35 @@ def create_ui():
46
  components[f'denoise_{prefix}'] = gr.Slider(label="Denoise Strength", minimum=0.0, maximum=1.0, step=0.01, value=0.55)
47
 
48
  with gr.Row():
49
- components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value="euler")
50
- components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value="simple")
51
  with gr.Row():
52
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=4)
53
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=1.0)
54
  with gr.Row():
55
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
56
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
57
  with gr.Row():
58
- components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60, Max: 120", info="Optional: Set how long to reserve the GPU.")
 
 
59
 
60
- components[f'clip_skip_{prefix}'] = gr.State(value=1)
61
  components[f'width_{prefix}'] = gr.State(value=512)
62
  components[f'height_{prefix}'] = gr.State(value=512)
63
 
64
  with gr.Column(scale=1):
65
  components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=610)
66
 
67
- components.update(create_api_key_ui(prefix))
68
  components.update(create_lora_settings_ui(prefix))
69
- # components.update(create_diffsynth_controlnet_ui(prefix))
70
- # components.update(create_controlnet_ui(prefix))
71
- # components.update(create_embedding_ui(prefix))
72
- components.update(create_reference_latent_ui(prefix))
 
 
73
  components.update(create_conditioning_ui(prefix))
74
- # components.update(create_vae_override_ui(prefix))
 
75
 
76
  return components
 
3
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
4
  from .ui_components import (
5
  create_lora_settings_ui,
6
+ create_controlnet_ui, create_ipadapter_ui, create_embedding_ui,
7
+ create_conditioning_ui, create_vae_override_ui,
8
+ create_model_architecture_filter_ui, create_category_filter_ui,
9
+ create_sd3_ipadapter_ui, create_flux1_ipadapter_ui, create_style_ui,
10
  create_reference_latent_ui
11
  )
12
 
 
15
  components = {}
16
 
17
  with gr.Column():
18
+ components.update(create_model_architecture_filter_ui(prefix))
19
+
20
  with gr.Row():
21
+ components.update(create_category_filter_ui(prefix))
22
  components[f'base_model_{prefix}'] = gr.Dropdown(
23
  label="Base Model",
24
  choices=list(MODEL_MAP_CHECKPOINT.keys()),
25
  value=list(MODEL_MAP_CHECKPOINT.keys())[0],
26
+ scale=3,
27
+ allow_custom_value=True
28
  )
29
  with gr.Column(scale=1):
30
  components[f'run_{prefix}'] = gr.Button("Run Hires. Fix", variant="primary")
 
33
  with gr.Column(scale=1):
34
  components[f'input_image_{prefix}'] = gr.Image(type="pil", label="Input Image", height=255)
35
  with gr.Column(scale=2):
36
+ components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3)
37
+ components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3)
38
 
39
  with gr.Row():
40
  with gr.Column(scale=1):
 
52
  components[f'denoise_{prefix}'] = gr.Slider(label="Denoise Strength", minimum=0.0, maximum=1.0, step=0.01, value=0.55)
53
 
54
  with gr.Row():
55
+ components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value=SAMPLER_CHOICES[0])
56
+ components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value='normal' if 'normal' in SCHEDULER_CHOICES else SCHEDULER_CHOICES[0])
57
  with gr.Row():
58
+ components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=28)
59
+ components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=7.5)
60
  with gr.Row():
61
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
62
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
63
  with gr.Row():
64
+ components[f'clip_skip_{prefix}'] = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=1, visible=False, interactive=True)
65
+ 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)
66
+ 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.")
67
 
 
68
  components[f'width_{prefix}'] = gr.State(value=512)
69
  components[f'height_{prefix}'] = gr.State(value=512)
70
 
71
  with gr.Column(scale=1):
72
  components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=610)
73
 
74
+
75
  components.update(create_lora_settings_ui(prefix))
76
+ components.update(create_controlnet_ui(prefix))
77
+ components.update(create_ipadapter_ui(prefix))
78
+ components.update(create_flux1_ipadapter_ui(prefix))
79
+ components.update(create_sd3_ipadapter_ui(prefix))
80
+ components.update(create_style_ui(prefix))
81
+ components.update(create_embedding_ui(prefix))
82
  components.update(create_conditioning_ui(prefix))
83
+ components.update(create_reference_latent_ui(prefix))
84
+ components.update(create_vae_override_ui(prefix))
85
 
86
  return components
ui/shared/img2img_ui.py CHANGED
@@ -3,8 +3,10 @@ from core.settings import MODEL_MAP_CHECKPOINT
3
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
4
  from .ui_components import (
5
  create_lora_settings_ui,
6
- create_embedding_ui,
7
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui,
 
 
8
  create_reference_latent_ui
9
  )
10
 
@@ -13,8 +15,11 @@ def create_ui():
13
  components = {}
14
 
15
  with gr.Column():
 
 
16
  with gr.Row():
17
- components[f'base_model_{prefix}'] = gr.Dropdown(label="Base Model", choices=list(MODEL_MAP_CHECKPOINT.keys()), value=list(MODEL_MAP_CHECKPOINT.keys())[0], scale=3)
 
18
  with gr.Column(scale=1):
19
  components[f'run_{prefix}'] = gr.Button("Run", variant="primary")
20
 
@@ -23,37 +28,41 @@ def create_ui():
23
  components[f'input_image_{prefix}'] = gr.Image(type="pil", label="Input Image", height=255)
24
 
25
  with gr.Column(scale=2):
26
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3, placeholder="Enter your prompt")
27
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3, value="")
28
 
29
  with gr.Row():
30
  with gr.Column(scale=1):
31
  components[f'denoise_{prefix}'] = gr.Slider(label="Denoise Strength", minimum=0.0, maximum=1.0, step=0.01, value=0.7)
32
 
33
  with gr.Row():
34
- components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value="euler")
35
- components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value="simple")
36
  with gr.Row():
37
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=4)
38
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=1.0)
39
  with gr.Row():
40
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
41
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
42
  with gr.Row():
43
- components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60, Max: 120", info="Optional: Set how long to reserve the GPU. Longer jobs may need more time.")
44
-
45
- components[f'clip_skip_{prefix}'] = gr.State(value=1)
46
 
47
  with gr.Column(scale=1):
48
  components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=505)
49
 
50
- components.update(create_api_key_ui(prefix))
51
  components.update(create_lora_settings_ui(prefix))
52
- # components.update(create_diffsynth_controlnet_ui(prefix))
53
- # components.update(create_controlnet_ui(prefix))
54
- # components.update(create_embedding_ui(prefix))
55
- components.update(create_reference_latent_ui(prefix))
 
 
 
56
  components.update(create_conditioning_ui(prefix))
57
- # components.update(create_vae_override_ui(prefix))
 
58
 
59
  return components
 
3
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
4
  from .ui_components import (
5
  create_lora_settings_ui,
6
+ create_controlnet_ui, create_diffsynth_controlnet_ui, create_ipadapter_ui, create_embedding_ui,
7
+ create_conditioning_ui, create_vae_override_ui,
8
+ create_model_architecture_filter_ui, create_category_filter_ui,
9
+ create_sd3_ipadapter_ui, create_flux1_ipadapter_ui, create_style_ui,
10
  create_reference_latent_ui
11
  )
12
 
 
15
  components = {}
16
 
17
  with gr.Column():
18
+ components.update(create_model_architecture_filter_ui(prefix))
19
+
20
  with gr.Row():
21
+ components.update(create_category_filter_ui(prefix))
22
+ components[f'base_model_{prefix}'] = gr.Dropdown(label="Base Model", choices=list(MODEL_MAP_CHECKPOINT.keys()), value=list(MODEL_MAP_CHECKPOINT.keys())[0], scale=3, allow_custom_value=True)
23
  with gr.Column(scale=1):
24
  components[f'run_{prefix}'] = gr.Button("Run", variant="primary")
25
 
 
28
  components[f'input_image_{prefix}'] = gr.Image(type="pil", label="Input Image", height=255)
29
 
30
  with gr.Column(scale=2):
31
+ components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3)
32
+ components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3)
33
 
34
  with gr.Row():
35
  with gr.Column(scale=1):
36
  components[f'denoise_{prefix}'] = gr.Slider(label="Denoise Strength", minimum=0.0, maximum=1.0, step=0.01, value=0.7)
37
 
38
  with gr.Row():
39
+ components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value=SAMPLER_CHOICES[0])
40
+ components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value='normal' if 'normal' in SCHEDULER_CHOICES else SCHEDULER_CHOICES[0])
41
  with gr.Row():
42
+ components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=28)
43
+ components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=7.5)
44
  with gr.Row():
45
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
46
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
47
  with gr.Row():
48
+ components[f'clip_skip_{prefix}'] = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=1, visible=False, interactive=True)
49
+ 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)
50
+ 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. Longer jobs may need more time.")
51
 
52
  with gr.Column(scale=1):
53
  components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=505)
54
 
55
+
56
  components.update(create_lora_settings_ui(prefix))
57
+ components.update(create_controlnet_ui(prefix))
58
+ components.update(create_diffsynth_controlnet_ui(prefix))
59
+ components.update(create_ipadapter_ui(prefix))
60
+ components.update(create_flux1_ipadapter_ui(prefix))
61
+ components.update(create_sd3_ipadapter_ui(prefix))
62
+ components.update(create_embedding_ui(prefix))
63
+ components.update(create_style_ui(prefix))
64
  components.update(create_conditioning_ui(prefix))
65
+ components.update(create_reference_latent_ui(prefix))
66
+ components.update(create_vae_override_ui(prefix))
67
 
68
  return components
ui/shared/inpaint_ui.py CHANGED
@@ -2,8 +2,10 @@ import gradio as gr
2
  from core.settings import MODEL_MAP_CHECKPOINT
3
  from .ui_components import (
4
  create_base_parameter_ui, create_lora_settings_ui,
5
- create_embedding_ui,
6
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui,
 
 
7
  create_reference_latent_ui
8
  )
9
 
@@ -12,17 +14,22 @@ def create_ui():
12
  components = {}
13
 
14
  with gr.Column():
 
 
 
15
  with gr.Row() as model_and_run_row:
 
16
  components[f'base_model_{prefix}'] = gr.Dropdown(
17
  label="Base Model",
18
  choices=list(MODEL_MAP_CHECKPOINT.keys()),
19
  value=list(MODEL_MAP_CHECKPOINT.keys())[0],
20
- scale=3
 
21
  )
22
  with gr.Column(scale=1):
23
  components[f'run_{prefix}'] = gr.Button("Run Inpaint", variant="primary")
24
 
25
- components[f'model_and_run_row_{prefix}'] = model_and_run_row
26
 
27
  with gr.Row() as main_content_row:
28
  with gr.Column(scale=1) as editor_column:
@@ -40,44 +47,55 @@ def create_ui():
40
  components[f'editor_column_{prefix}'] = editor_column
41
 
42
  with gr.Column(scale=2) as prompts_column:
43
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=6, placeholder="Describe what to fill in the mask...")
44
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=6, value="")
45
  components[f'prompts_column_{prefix}'] = prompts_column
46
 
47
  with gr.Row() as params_and_gallery_row:
48
  with gr.Column(scale=1):
49
- param_defaults = {'w': 1024, 'h': 1024, 'cs_vis': False, 'cs_val': 1}
50
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
51
  with gr.Row():
52
- components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value="euler")
53
- components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value="simple")
 
 
 
 
 
 
 
54
  with gr.Row():
55
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=4)
56
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=1.0)
57
  with gr.Row():
58
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
59
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
60
  with gr.Row():
61
- components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60, Max: 120", info="Optional: Set how long to reserve the GPU.")
 
 
62
 
63
- components[f'clip_skip_{prefix}'] = gr.State(value=1)
64
  components[f'width_{prefix}'] = gr.State(value=512)
65
  components[f'height_{prefix}'] = gr.State(value=512)
66
 
67
  with gr.Column(scale=1):
68
- components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=414)
69
 
70
  components[f'params_and_gallery_row_{prefix}'] = params_and_gallery_row
71
 
72
  with gr.Column() as accordion_wrapper:
73
- components.update(create_api_key_ui(prefix))
74
  components.update(create_lora_settings_ui(prefix))
75
- # components.update(create_diffsynth_controlnet_ui(prefix))
76
- # components.update(create_controlnet_ui(prefix))
77
- # components.update(create_embedding_ui(prefix))
78
- components.update(create_reference_latent_ui(prefix))
 
 
 
79
  components.update(create_conditioning_ui(prefix))
80
- # components.update(create_vae_override_ui(prefix))
 
81
  components[f'accordion_wrapper_{prefix}'] = accordion_wrapper
82
 
83
  return components
 
2
  from core.settings import MODEL_MAP_CHECKPOINT
3
  from .ui_components import (
4
  create_base_parameter_ui, create_lora_settings_ui,
5
+ create_controlnet_ui, create_diffsynth_controlnet_ui, create_ipadapter_ui, create_embedding_ui,
6
+ create_conditioning_ui, create_vae_override_ui,
7
+ create_model_architecture_filter_ui, create_category_filter_ui,
8
+ create_sd3_ipadapter_ui, create_flux1_ipadapter_ui, create_style_ui,
9
  create_reference_latent_ui
10
  )
11
 
 
14
  components = {}
15
 
16
  with gr.Column():
17
+ with gr.Row() as arch_row:
18
+ components.update(create_model_architecture_filter_ui(prefix))
19
+
20
  with gr.Row() as model_and_run_row:
21
+ components.update(create_category_filter_ui(prefix))
22
  components[f'base_model_{prefix}'] = gr.Dropdown(
23
  label="Base Model",
24
  choices=list(MODEL_MAP_CHECKPOINT.keys()),
25
  value=list(MODEL_MAP_CHECKPOINT.keys())[0],
26
+ scale=3,
27
+ allow_custom_value=True
28
  )
29
  with gr.Column(scale=1):
30
  components[f'run_{prefix}'] = gr.Button("Run Inpaint", variant="primary")
31
 
32
+ components[f'model_and_run_row_{prefix}'] = [arch_row, model_and_run_row]
33
 
34
  with gr.Row() as main_content_row:
35
  with gr.Column(scale=1) as editor_column:
 
47
  components[f'editor_column_{prefix}'] = editor_column
48
 
49
  with gr.Column(scale=2) as prompts_column:
50
+ components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=6)
51
+ components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=6)
52
  components[f'prompts_column_{prefix}'] = prompts_column
53
 
54
  with gr.Row() as params_and_gallery_row:
55
  with gr.Column(scale=1):
 
56
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
57
  with gr.Row():
58
+ components[f'denoise_{prefix}'] = gr.Slider(
59
+ label="Denoise", minimum=0.0, maximum=1.0, step=0.05, value=1.0
60
+ )
61
+ components[f'grow_mask_by_{prefix}'] = gr.Slider(
62
+ label="Grow Mask By", minimum=0, maximum=64, step=1, value=6
63
+ )
64
+ with gr.Row():
65
+ components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value=SAMPLER_CHOICES[0])
66
+ components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value='normal' if 'normal' in SCHEDULER_CHOICES else SCHEDULER_CHOICES[0])
67
  with gr.Row():
68
+ components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=28)
69
+ components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=7.5)
70
  with gr.Row():
71
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
72
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
73
  with gr.Row():
74
+ components[f'clip_skip_{prefix}'] = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=1, visible=False, interactive=True)
75
+ 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)
76
+ 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.")
77
 
 
78
  components[f'width_{prefix}'] = gr.State(value=512)
79
  components[f'height_{prefix}'] = gr.State(value=512)
80
 
81
  with gr.Column(scale=1):
82
+ components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=510)
83
 
84
  components[f'params_and_gallery_row_{prefix}'] = params_and_gallery_row
85
 
86
  with gr.Column() as accordion_wrapper:
87
+
88
  components.update(create_lora_settings_ui(prefix))
89
+ components.update(create_controlnet_ui(prefix))
90
+ components.update(create_diffsynth_controlnet_ui(prefix))
91
+ components.update(create_ipadapter_ui(prefix))
92
+ components.update(create_flux1_ipadapter_ui(prefix))
93
+ components.update(create_sd3_ipadapter_ui(prefix))
94
+ components.update(create_style_ui(prefix))
95
+ components.update(create_embedding_ui(prefix))
96
  components.update(create_conditioning_ui(prefix))
97
+ components.update(create_reference_latent_ui(prefix))
98
+ components.update(create_vae_override_ui(prefix))
99
  components[f'accordion_wrapper_{prefix}'] = accordion_wrapper
100
 
101
  return components
ui/shared/outpaint_ui.py CHANGED
@@ -3,8 +3,10 @@ from core.settings import MODEL_MAP_CHECKPOINT
3
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
4
  from .ui_components import (
5
  create_lora_settings_ui,
6
- create_embedding_ui,
7
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui,
 
 
8
  create_reference_latent_ui
9
  )
10
 
@@ -13,12 +15,16 @@ def create_ui():
13
  components = {}
14
 
15
  with gr.Column():
 
 
16
  with gr.Row():
 
17
  components[f'base_model_{prefix}'] = gr.Dropdown(
18
  label="Base Model",
19
  choices=list(MODEL_MAP_CHECKPOINT.keys()),
20
  value=list(MODEL_MAP_CHECKPOINT.keys())[0],
21
- scale=3
 
22
  )
23
  with gr.Column(scale=1):
24
  components[f'run_{prefix}'] = gr.Button("Run Outpaint", variant="primary")
@@ -27,44 +33,51 @@ def create_ui():
27
  with gr.Column(scale=1):
28
  components[f'input_image_{prefix}'] = gr.Image(type="pil", label="Input Image", height=255)
29
  with gr.Column(scale=2):
30
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3, placeholder="Describe the content for the expanded areas...")
31
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3, value="")
32
 
33
  with gr.Row():
34
  with gr.Column(scale=1):
35
  with gr.Row():
36
- components[f'outpaint_left_{prefix}'] = gr.Slider(label="Pad Left", minimum=0, maximum=512, step=64, value=0)
37
- components[f'outpaint_right_{prefix}'] = gr.Slider(label="Pad Right", minimum=0, maximum=512, step=64, value=256)
38
  with gr.Row():
39
- components[f'outpaint_top_{prefix}'] = gr.Slider(label="Pad Top", minimum=0, maximum=512, step=64, value=0)
40
- components[f'outpaint_bottom_{prefix}'] = gr.Slider(label="Pad Bottom", minimum=0, maximum=512, step=64, value=0)
 
 
41
 
42
  with gr.Row():
43
- components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value="euler")
44
- components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value="simple")
45
  with gr.Row():
46
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=4)
47
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=1.0)
48
  with gr.Row():
49
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
50
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
51
  with gr.Row():
52
- components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60, Max: 120", info="Optional: Set how long to reserve the GPU.")
 
 
53
 
54
- components[f'clip_skip_{prefix}'] = gr.State(value=1)
55
  components[f'width_{prefix}'] = gr.State(value=512)
56
  components[f'height_{prefix}'] = gr.State(value=512)
57
 
58
  with gr.Column(scale=1):
59
- components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=595)
60
 
61
- components.update(create_api_key_ui(prefix))
62
  components.update(create_lora_settings_ui(prefix))
63
- # components.update(create_diffsynth_controlnet_ui(prefix))
64
- # components.update(create_controlnet_ui(prefix))
65
- # components.update(create_embedding_ui(prefix))
66
- components.update(create_reference_latent_ui(prefix))
 
 
 
67
  components.update(create_conditioning_ui(prefix))
68
- # components.update(create_vae_override_ui(prefix))
 
69
 
70
  return components
 
3
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
4
  from .ui_components import (
5
  create_lora_settings_ui,
6
+ create_controlnet_ui, create_diffsynth_controlnet_ui, create_ipadapter_ui, create_embedding_ui,
7
+ create_conditioning_ui, create_vae_override_ui,
8
+ create_model_architecture_filter_ui, create_category_filter_ui,
9
+ create_sd3_ipadapter_ui, create_flux1_ipadapter_ui, create_style_ui,
10
  create_reference_latent_ui
11
  )
12
 
 
15
  components = {}
16
 
17
  with gr.Column():
18
+ components.update(create_model_architecture_filter_ui(prefix))
19
+
20
  with gr.Row():
21
+ components.update(create_category_filter_ui(prefix))
22
  components[f'base_model_{prefix}'] = gr.Dropdown(
23
  label="Base Model",
24
  choices=list(MODEL_MAP_CHECKPOINT.keys()),
25
  value=list(MODEL_MAP_CHECKPOINT.keys())[0],
26
+ scale=3,
27
+ allow_custom_value=True
28
  )
29
  with gr.Column(scale=1):
30
  components[f'run_{prefix}'] = gr.Button("Run Outpaint", variant="primary")
 
33
  with gr.Column(scale=1):
34
  components[f'input_image_{prefix}'] = gr.Image(type="pil", label="Input Image", height=255)
35
  with gr.Column(scale=2):
36
+ components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3)
37
+ components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3)
38
 
39
  with gr.Row():
40
  with gr.Column(scale=1):
41
  with gr.Row():
42
+ components[f'left_{prefix}'] = gr.Slider(label="Pad Left", minimum=0, maximum=512, step=64, value=64)
43
+ components[f'right_{prefix}'] = gr.Slider(label="Pad Right", minimum=0, maximum=512, step=64, value=64)
44
  with gr.Row():
45
+ components[f'top_{prefix}'] = gr.Slider(label="Pad Top", minimum=0, maximum=512, step=64, value=64)
46
+ components[f'bottom_{prefix}'] = gr.Slider(label="Pad Bottom", minimum=0, maximum=512, step=64, value=64)
47
+
48
+ components[f'feathering_{prefix}'] = gr.Slider(label="Feathering / Grow Mask", minimum=0, maximum=100, step=1, value=10)
49
 
50
  with gr.Row():
51
+ components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value=SAMPLER_CHOICES[0])
52
+ components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value='normal' if 'normal' in SCHEDULER_CHOICES else SCHEDULER_CHOICES[0])
53
  with gr.Row():
54
+ components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=28)
55
+ components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=7.5)
56
  with gr.Row():
57
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
58
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
59
  with gr.Row():
60
+ components[f'clip_skip_{prefix}'] = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=1, visible=False, interactive=True)
61
+ 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)
62
+ 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.")
63
 
 
64
  components[f'width_{prefix}'] = gr.State(value=512)
65
  components[f'height_{prefix}'] = gr.State(value=512)
66
 
67
  with gr.Column(scale=1):
68
+ components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=685)
69
 
70
+
71
  components.update(create_lora_settings_ui(prefix))
72
+ components.update(create_controlnet_ui(prefix))
73
+ components.update(create_diffsynth_controlnet_ui(prefix))
74
+ components.update(create_ipadapter_ui(prefix))
75
+ components.update(create_flux1_ipadapter_ui(prefix))
76
+ components.update(create_sd3_ipadapter_ui(prefix))
77
+ components.update(create_style_ui(prefix))
78
+ components.update(create_embedding_ui(prefix))
79
  components.update(create_conditioning_ui(prefix))
80
+ components.update(create_reference_latent_ui(prefix))
81
+ components.update(create_vae_override_ui(prefix))
82
 
83
  return components
ui/shared/txt2img_ui.py CHANGED
@@ -2,8 +2,10 @@ import gradio as gr
2
  from core.settings import MODEL_MAP_CHECKPOINT
3
  from .ui_components import (
4
  create_base_parameter_ui, create_lora_settings_ui,
5
- create_embedding_ui,
6
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui,
 
 
7
  create_reference_latent_ui
8
  )
9
 
@@ -13,13 +15,22 @@ def create_ui():
13
  components = {}
14
 
15
  with gr.Column():
 
 
16
  with gr.Row():
17
- components[f'base_model_{prefix}'] = gr.Dropdown(label="Base Model", choices=list(MODEL_MAP_CHECKPOINT.keys()), value=list(MODEL_MAP_CHECKPOINT.keys())[0], scale=3)
 
 
 
 
 
 
 
18
  with gr.Column(scale=1):
19
  components[f'run_{prefix}'] = gr.Button("Run", variant="primary")
20
 
21
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3, placeholder="Enter your prompt")
22
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3, value="")
23
 
24
  with gr.Row():
25
  with gr.Column(scale=1):
@@ -28,13 +39,17 @@ def create_ui():
28
  with gr.Column(scale=1):
29
  components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=2, object_fit="contain", height=627)
30
 
31
- components.update(create_api_key_ui(prefix))
32
  components.update(create_lora_settings_ui(prefix))
33
- # components.update(create_diffsynth_controlnet_ui(prefix))
34
- # components.update(create_controlnet_ui(prefix))
35
- # components.update(create_embedding_ui(prefix))
36
- components.update(create_reference_latent_ui(prefix))
 
 
 
37
  components.update(create_conditioning_ui(prefix))
38
- # components.update(create_vae_override_ui(prefix))
 
39
 
40
  return components
 
2
  from core.settings import MODEL_MAP_CHECKPOINT
3
  from .ui_components import (
4
  create_base_parameter_ui, create_lora_settings_ui,
5
+ create_controlnet_ui, create_diffsynth_controlnet_ui, create_ipadapter_ui, create_embedding_ui,
6
+ create_conditioning_ui, create_vae_override_ui,
7
+ create_model_architecture_filter_ui, create_category_filter_ui,
8
+ create_sd3_ipadapter_ui, create_flux1_ipadapter_ui, create_style_ui,
9
  create_reference_latent_ui
10
  )
11
 
 
15
  components = {}
16
 
17
  with gr.Column():
18
+ components.update(create_model_architecture_filter_ui(prefix))
19
+
20
  with gr.Row():
21
+ components.update(create_category_filter_ui(prefix))
22
+ components[f'base_model_{prefix}'] = gr.Dropdown(
23
+ label="Base Model",
24
+ choices=list(MODEL_MAP_CHECKPOINT.keys()),
25
+ value=list(MODEL_MAP_CHECKPOINT.keys())[0],
26
+ scale=3,
27
+ allow_custom_value=True
28
+ )
29
  with gr.Column(scale=1):
30
  components[f'run_{prefix}'] = gr.Button("Run", variant="primary")
31
 
32
+ components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3)
33
+ components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3)
34
 
35
  with gr.Row():
36
  with gr.Column(scale=1):
 
39
  with gr.Column(scale=1):
40
  components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=2, object_fit="contain", height=627)
41
 
42
+
43
  components.update(create_lora_settings_ui(prefix))
44
+ components.update(create_controlnet_ui(prefix))
45
+ components.update(create_diffsynth_controlnet_ui(prefix))
46
+ components.update(create_ipadapter_ui(prefix))
47
+ components.update(create_flux1_ipadapter_ui(prefix))
48
+ components.update(create_sd3_ipadapter_ui(prefix))
49
+ components.update(create_embedding_ui(prefix))
50
+ components.update(create_style_ui(prefix))
51
  components.update(create_conditioning_ui(prefix))
52
+ components.update(create_reference_latent_ui(prefix))
53
+ components.update(create_vae_override_ui(prefix))
54
 
55
  return components
ui/shared/ui_components.py CHANGED
@@ -2,12 +2,74 @@ 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, RESOLUTION_MAP, MAX_REFERENCE_LATENTS
 
6
  )
7
  import yaml
8
  import os
9
  from functools import lru_cache
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  def create_base_parameter_ui(prefix, defaults=None):
12
  if defaults is None:
13
  defaults = {}
@@ -17,50 +79,37 @@ def create_base_parameter_ui(prefix, defaults=None):
17
  with gr.Row():
18
  components[f'aspect_ratio_{prefix}'] = gr.Dropdown(
19
  label="Aspect Ratio",
20
- choices=list(RESOLUTION_MAP['sdxl'].keys()),
21
  value="1:1 (Square)",
22
- interactive=True
 
23
  )
24
  with gr.Row():
25
  components[f'width_{prefix}'] = gr.Number(label="Width", value=defaults.get('w', 1024), interactive=True)
26
  components[f'height_{prefix}'] = gr.Number(label="Height", value=defaults.get('h', 1024), interactive=True)
27
  with gr.Row():
28
- components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value="euler")
29
- components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value="simple")
30
  with gr.Row():
31
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=4)
32
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=1.0)
33
  with gr.Row():
34
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
35
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
36
  with gr.Row():
37
- components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60, Max: 120", info="Optional: Set how long to reserve the GPU. Longer jobs may need more time.")
38
-
39
- components[f'clip_skip_{prefix}'] = gr.State(value=1)
40
 
41
  return components
42
 
43
 
44
- def create_api_key_ui(prefix: str):
45
- components = {}
46
- with gr.Accordion("API Key Settings", open=False) as api_key_accordion:
47
- components[f'api_key_accordion_{prefix}'] = api_key_accordion
48
- gr.Markdown("💡 **Tip:** Enter API key (optional). An API key is required for resources that need a login to download. The key will be used for all Civitai downloads on this tab. You can also manually upload the corresponding files to avoid API Key leakage caused by potential vulnerabilities.")
49
- with gr.Row():
50
- components[f'civitai_api_key_{prefix}'] = gr.Textbox(
51
- label="Civitai API Key",
52
- type="password",
53
- placeholder="Enter your Civitai API key here (optional)"
54
- )
55
- return components
56
-
57
-
58
  def create_lora_settings_ui(prefix: str):
59
  components = {}
60
 
61
  lora_rows, lora_sources, lora_ids, lora_scales, lora_uploads = [], [], [], [], []
62
 
63
- with gr.Accordion("LoRA Settings", open=False) as lora_accordion:
64
  components[f'lora_accordion_{prefix}'] = lora_accordion
65
  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.")
66
  components[f'lora_count_state_{prefix}'] = gr.State(1)
@@ -69,7 +118,7 @@ def create_lora_settings_ui(prefix: str):
69
  with gr.Row(visible=i==0) as row:
70
  source = gr.Dropdown(label=f"LoRA Source {i+1}", choices=LORA_SOURCE_CHOICES, value=LORA_SOURCE_CHOICES[0], scale=1)
71
  lora_id = gr.Textbox(label=f"Civitai Version ID / File", placeholder="Civitai Version ID or Filename", scale=2, type="text")
72
- scale = gr.Slider(label=f"Scale", minimum=-2.0, maximum=2.0, step=0.05, value=0.8, scale=1)
73
  upload = gr.UploadButton(label="Upload", file_types=[".safetensors"], scale=1)
74
 
75
  lora_rows.append(row)
@@ -95,11 +144,273 @@ def create_lora_settings_ui(prefix: str):
95
 
96
  return components
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  def create_embedding_ui(prefix: str):
99
  components = {}
100
  key = lambda name: f"{name}_{prefix}"
101
 
102
- with gr.Accordion("Embedding Settings", open=False, visible=True) as accordion:
103
  components[key('embedding_accordion')] = accordion
104
  gr.Markdown("💡 **Tip:** Embeddings are automatically added to your prompt using `embedding:filename` syntax. 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 instance, using the Version ID `456` from the example above would automatically append `embedding:civitai_456` to your positive prompt.")
105
 
@@ -137,7 +448,7 @@ def create_conditioning_ui(prefix: str):
137
  components = {}
138
  key = lambda name: f"{name}_{prefix}"
139
 
140
- with gr.Accordion("Conditioning Settings", open=False) as accordion:
141
  components[key('conditioning_accordion')] = accordion
142
  gr.Markdown("💡 **Tip:** Define rectangular areas and assign specific prompts to them. Coordinates (X, Y) start from the top-left corner.")
143
 
@@ -173,35 +484,6 @@ def create_conditioning_ui(prefix: str):
173
 
174
  return components
175
 
176
- def create_reference_latent_ui(prefix: str):
177
- components = {}
178
- key = lambda name: f"{name}_{prefix}"
179
-
180
- with gr.Accordion("Reference Edit", open=False) as accordion:
181
- components[key('reference_latent_accordion')] = accordion
182
- gr.Markdown("💡 **Tip:** For multimodal models (like FLUX.2), 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**.")
183
-
184
- ref_rows, ref_images = [], []
185
- components.update({
186
- key('reference_latent_rows'): ref_rows,
187
- key('reference_latent_images'): ref_images,
188
- })
189
-
190
- with gr.Row():
191
- for i in range(MAX_REFERENCE_LATENTS):
192
- with gr.Column(visible=(i < 1), min_width=160) as row_wrapper:
193
- ref_images.append(gr.Image(type="pil", label=f"Reference {i+1}", sources=["upload"], height=150))
194
- ref_rows.append(row_wrapper)
195
-
196
- with gr.Row():
197
- components[key('add_reference_latent_button')] = gr.Button("✚ Add Reference Image")
198
- components[key('delete_reference_latent_button')] = gr.Button("➖ Delete Reference Image", visible=False)
199
- components[key('reference_latent_count_state')] = gr.State(1)
200
-
201
- components[key('all_reference_latent_components_flat')] = ref_images
202
-
203
- return components
204
-
205
  def create_vae_override_ui(prefix: str):
206
  components = {}
207
  key = lambda name: f"{name}_{prefix}"
@@ -233,4 +515,33 @@ def create_vae_override_ui(prefix: str):
233
  components[key('vae_upload_button')] = upload_btn
234
  components[key('vae_file')] = gr.State(None)
235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  return components
 
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
  )
8
  import yaml
9
  import os
10
  from functools import lru_cache
11
 
12
+ default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOINT else None
13
+ default_m_type = MODEL_TYPE_MAP.get(default_model_name, "SDXL") if default_model_name else "SDXL"
14
+ default_architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
15
+ default_arch_model_type = default_architectures_dict.get(default_m_type, {}).get("model_type", default_m_type.lower().replace(" ", "").replace(".", ""))
16
+ default_arch_features = FEATURES_CONFIG.get(default_arch_model_type, FEATURES_CONFIG.get('default', {}))
17
+ default_enabled_chains = default_arch_features.get('enabled_chains', [])
18
+
19
+
20
+ @lru_cache(maxsize=1)
21
+ def get_ipadapter_config_from_yaml():
22
+ try:
23
+ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
24
+ _IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
25
+ with open(_IPADAPTER_LIST_PATH, 'r', encoding='utf-8') as f:
26
+ config = yaml.safe_load(f)
27
+ return config
28
+ except Exception as e:
29
+ print(f"Warning: Could not load ipadapter.yaml for UI components: {e}")
30
+ return {}
31
+
32
+ def get_ipadapter_presets(arch="SDXL"):
33
+ config = get_ipadapter_config_from_yaml()
34
+ presets = []
35
+ if config:
36
+ std_presets = config.get("IPAdapter_presets", {}).get(arch, [])
37
+ face_presets = config.get("IPAdapter_FaceID_presets", {}).get(arch, [])
38
+ if std_presets:
39
+ presets.extend(std_presets)
40
+ if face_presets:
41
+ presets.extend(face_presets)
42
+ return presets if presets else ["STANDARD (medium strength)"]
43
+
44
+ def create_model_architecture_filter_ui(prefix):
45
+ components = {}
46
+ ordered_architectures = ARCHITECTURES_CONFIG.get("architecture_order", [])
47
+ choices = ["ALL"] + ordered_architectures
48
+
49
+ components[f'model_arch_{prefix}'] = gr.Radio(
50
+ label="Model Architecture",
51
+ choices=choices,
52
+ value="ALL",
53
+ interactive=True,
54
+ visible=False
55
+ )
56
+ return components
57
+
58
+ def create_category_filter_ui(prefix):
59
+ valid_cats = list(set(cat for cats in ARCH_CATEGORIES_MAP.values() for cat in cats))
60
+ cat_choices = ["ALL"] + sorted(valid_cats)
61
+
62
+ components = {}
63
+ components[f'model_cat_{prefix}'] = gr.Dropdown(
64
+ label="Filter Models",
65
+ choices=cat_choices,
66
+ value="ALL",
67
+ interactive=True,
68
+ scale=1,
69
+ allow_custom_value=True
70
+ )
71
+ return components
72
+
73
  def create_base_parameter_ui(prefix, defaults=None):
74
  if defaults is None:
75
  defaults = {}
 
79
  with gr.Row():
80
  components[f'aspect_ratio_{prefix}'] = gr.Dropdown(
81
  label="Aspect Ratio",
82
+ choices=list(RESOLUTION_MAP.get('sdxl', {}).keys()),
83
  value="1:1 (Square)",
84
+ interactive=True,
85
+ allow_custom_value=True
86
  )
87
  with gr.Row():
88
  components[f'width_{prefix}'] = gr.Number(label="Width", value=defaults.get('w', 1024), interactive=True)
89
  components[f'height_{prefix}'] = gr.Number(label="Height", value=defaults.get('h', 1024), interactive=True)
90
  with gr.Row():
91
+ components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value=SAMPLER_CHOICES[0])
92
+ components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value='normal' if 'normal' in SCHEDULER_CHOICES else SCHEDULER_CHOICES[0])
93
  with gr.Row():
94
+ components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=28)
95
+ components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=7.5)
96
  with gr.Row():
97
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
98
  components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
99
  with gr.Row():
100
+ components[f'clip_skip_{prefix}'] = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=1, visible=False, interactive=True)
101
+ 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)
102
+ 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.")
103
 
104
  return components
105
 
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  def create_lora_settings_ui(prefix: str):
108
  components = {}
109
 
110
  lora_rows, lora_sources, lora_ids, lora_scales, lora_uploads = [], [], [], [], []
111
 
112
+ with gr.Accordion("LoRA Settings", open=False, visible=('lora' in default_enabled_chains)) as lora_accordion:
113
  components[f'lora_accordion_{prefix}'] = lora_accordion
114
  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.")
115
  components[f'lora_count_state_{prefix}'] = gr.State(1)
 
118
  with gr.Row(visible=i==0) as row:
119
  source = gr.Dropdown(label=f"LoRA Source {i+1}", choices=LORA_SOURCE_CHOICES, value=LORA_SOURCE_CHOICES[0], scale=1)
120
  lora_id = gr.Textbox(label=f"Civitai Version ID / File", placeholder="Civitai Version ID or Filename", scale=2, type="text")
121
+ scale = gr.Slider(label=f"Scale", minimum=0.0, maximum=2.0, step=0.05, value=0.8, scale=1)
122
  upload = gr.UploadButton(label="Upload", file_types=[".safetensors"], scale=1)
123
 
124
  lora_rows.append(row)
 
144
 
145
  return components
146
 
147
+ def create_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
148
+ components = {}
149
+ key = lambda name: f"{name}_{prefix}"
150
+
151
+ with gr.Accordion("ControlNet Settings", open=False, visible=('controlnet' in default_enabled_chains)) as accordion:
152
+ components[key('controlnet_accordion')] = accordion
153
+
154
+ cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
155
+ components.update({
156
+ key('controlnet_rows'): cn_rows,
157
+ key('controlnet_images'): images,
158
+ key('controlnet_series'): series,
159
+ key('controlnet_types'): types,
160
+ key('controlnet_strengths'): strengths,
161
+ key('controlnet_filepaths'): filepaths
162
+ })
163
+
164
+ for i in range(max_units):
165
+ with gr.Row(visible=(i < 1)) as row:
166
+ with gr.Column(scale=1):
167
+ images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
168
+ with gr.Column(scale=2):
169
+ types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
170
+ series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
171
+ strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
172
+ filepaths.append(gr.State(None))
173
+ cn_rows.append(row)
174
+
175
+ with gr.Row():
176
+ components[key('add_controlnet_button')] = gr.Button("✚ Add ControlNet")
177
+ components[key('delete_controlnet_button')] = gr.Button("➖ Delete ControlNet", visible=False)
178
+ components[key('controlnet_count_state')] = gr.State(1)
179
+
180
+ all_cn_components_flat = []
181
+ for i in range(max_units):
182
+ all_cn_components_flat.extend([
183
+ images[i], types[i], series[i], strengths[i], filepaths[i]
184
+ ])
185
+ components[key('all_controlnet_components_flat')] = all_cn_components_flat
186
+
187
+ return components
188
+
189
+ def create_diffsynth_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
190
+ components = {}
191
+ key = lambda name: f"{name}_{prefix}"
192
+
193
+ with gr.Accordion("DiffSynth ControlNet Settings", open=False, visible=('controlnet_model_patch' in default_enabled_chains)) as accordion:
194
+ components[key('diffsynth_controlnet_accordion')] = accordion
195
+
196
+ cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
197
+ components.update({
198
+ key('diffsynth_controlnet_rows'): cn_rows,
199
+ key('diffsynth_controlnet_images'): images,
200
+ key('diffsynth_controlnet_series'): series,
201
+ key('diffsynth_controlnet_types'): types,
202
+ key('diffsynth_controlnet_strengths'): strengths,
203
+ key('diffsynth_controlnet_filepaths'): filepaths
204
+ })
205
+
206
+ for i in range(max_units):
207
+ with gr.Row(visible=(i < 1)) as row:
208
+ with gr.Column(scale=1):
209
+ images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
210
+ with gr.Column(scale=2):
211
+ types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
212
+ series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
213
+ strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
214
+ filepaths.append(gr.State(None))
215
+ cn_rows.append(row)
216
+
217
+ with gr.Row():
218
+ components[key('add_diffsynth_controlnet_button')] = gr.Button("✚ Add DiffSynth ControlNet")
219
+ components[key('delete_diffsynth_controlnet_button')] = gr.Button("➖ Delete DiffSynth ControlNet", visible=False)
220
+ components[key('diffsynth_controlnet_count_state')] = gr.State(1)
221
+
222
+ all_cn_components_flat = []
223
+ for i in range(max_units):
224
+ all_cn_components_flat.extend([
225
+ images[i], types[i], series[i], strengths[i], filepaths[i]
226
+ ])
227
+ components[key('all_diffsynth_controlnet_components_flat')] = all_cn_components_flat
228
+
229
+ return components
230
+
231
+ def create_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
232
+ components = {}
233
+ key = lambda name: f"{name}_{prefix}"
234
+
235
+ sdxl_presets = get_ipadapter_presets("SDXL")
236
+ default_preset = sdxl_presets[0] if sdxl_presets else None
237
+
238
+ with gr.Accordion("IPAdapter Settings", open=False, visible=('ipadapter' in default_enabled_chains)) as accordion:
239
+ components[key('ipadapter_accordion')] = accordion
240
+ gr.Markdown("Powered by [cubiq/ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus).")
241
+
242
+ with gr.Row():
243
+ components[key('ipadapter_final_preset')] = gr.Dropdown(
244
+ label="Preset (for all images)",
245
+ choices=sdxl_presets,
246
+ value=default_preset,
247
+ interactive=True,
248
+ allow_custom_value=True
249
+ )
250
+ components[key('ipadapter_embeds_scaling')] = gr.Dropdown(
251
+ label="Embeds Scaling",
252
+ choices=['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],
253
+ value='V only',
254
+ interactive=True
255
+ )
256
+
257
+ with gr.Row():
258
+ components[key('ipadapter_combine_method')] = gr.Dropdown(
259
+ label="Combine Method",
260
+ choices=["concat", "add", "subtract", "average", "norm average", "max", "min"],
261
+ value="concat",
262
+ interactive=True
263
+ )
264
+ components[key('ipadapter_final_weight')] = gr.Slider(label="Final Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True)
265
+ 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)
266
+
267
+ gr.Markdown("---")
268
+
269
+ ipa_rows, images, weights, lora_strengths = [], [], [], []
270
+ components.update({
271
+ key('ipadapter_rows'): ipa_rows,
272
+ key('ipadapter_images'): images,
273
+ key('ipadapter_weights'): weights,
274
+ key('ipadapter_lora_strengths'): lora_strengths
275
+ })
276
+
277
+ for i in range(max_units):
278
+ with gr.Row(visible=(i < 1)) as row:
279
+ with gr.Column(scale=1):
280
+ images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
281
+ with gr.Column(scale=2):
282
+ weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
283
+ lora_strengths.append(gr.Slider(label="LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False))
284
+ ipa_rows.append(row)
285
+
286
+ with gr.Row():
287
+ components[key('add_ipadapter_button')] = gr.Button("✚ Add IPAdapter")
288
+ components[key('delete_ipadapter_button')] = gr.Button("➖ Delete IPAdapter", visible=False)
289
+ components[key('ipadapter_count_state')] = gr.State(1)
290
+
291
+ all_ipa_components_flat = images + weights + lora_strengths
292
+ all_ipa_components_flat += [
293
+ components[key('ipadapter_final_preset')],
294
+ components[key('ipadapter_final_weight')],
295
+ components[key('ipadapter_final_lora_strength')],
296
+ components[key('ipadapter_embeds_scaling')],
297
+ components[key('ipadapter_combine_method')],
298
+ ]
299
+ components[key('all_ipadapter_components_flat')] = all_ipa_components_flat
300
+
301
+ return components
302
+
303
+ def create_flux1_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
304
+ components = {}
305
+ key = lambda name: f"{name}_{prefix}"
306
+
307
+ with gr.Accordion("IPAdapter Settings (FLUX.1)", open=False, visible=('flux1_ipadapter' in default_enabled_chains)) as accordion:
308
+ components[key('flux1_ipadapter_accordion')] = accordion
309
+
310
+ ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
311
+ components.update({
312
+ key('flux1_ipadapter_rows'): ipa_rows,
313
+ key('flux1_ipadapter_images'): images,
314
+ key('flux1_ipadapter_weights'): weights,
315
+ key('flux1_ipadapter_start_percents'): start_percents,
316
+ key('flux1_ipadapter_end_percents'): end_percents,
317
+ })
318
+
319
+ for i in range(max_units):
320
+ with gr.Row(visible=(i < 1)) as row:
321
+ with gr.Column(scale=1):
322
+ images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
323
+ with gr.Column(scale=2):
324
+ weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True))
325
+ with gr.Row():
326
+ start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
327
+ end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=0.6, interactive=True))
328
+ ipa_rows.append(row)
329
+
330
+ with gr.Row():
331
+ components[key('add_flux1_ipadapter_button')] = gr.Button("✚ Add IPAdapter (FLUX)")
332
+ components[key('delete_flux1_ipadapter_button')] = gr.Button("➖ Delete IPAdapter (FLUX)", visible=False)
333
+ components[key('flux1_ipadapter_count_state')] = gr.State(1)
334
+
335
+ all_flux1_ipa_components_flat = images + weights + start_percents + end_percents
336
+ components[key('all_flux1_ipadapter_components_flat')] = all_flux1_ipa_components_flat
337
+
338
+ return components
339
+
340
+ def create_sd3_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
341
+ components = {}
342
+ key = lambda name: f"{name}_{prefix}"
343
+
344
+ with gr.Accordion("IPAdapter Settings (SD3)", open=False, visible=('sd3_ipadapter' in default_enabled_chains)) as accordion:
345
+ components[key('sd3_ipadapter_accordion')] = accordion
346
+
347
+ ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
348
+ components.update({
349
+ key('sd3_ipadapter_rows'): ipa_rows,
350
+ key('sd3_ipadapter_images'): images,
351
+ key('sd3_ipadapter_weights'): weights,
352
+ key('sd3_ipadapter_start_percents'): start_percents,
353
+ key('sd3_ipadapter_end_percents'): end_percents,
354
+ })
355
+
356
+ for i in range(max_units):
357
+ with gr.Row(visible=(i < 1)) as row:
358
+ with gr.Column(scale=1):
359
+ images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
360
+ with gr.Column(scale=2):
361
+ weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.5, interactive=True))
362
+ with gr.Row():
363
+ start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
364
+ end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=1.0, interactive=True))
365
+ ipa_rows.append(row)
366
+
367
+ with gr.Row():
368
+ components[key('add_sd3_ipadapter_button')] = gr.Button("✚ Add IPAdapter (SD3)")
369
+ components[key('delete_sd3_ipadapter_button')] = gr.Button("➖ Delete IPAdapter (SD3)", visible=False)
370
+ components[key('sd3_ipadapter_count_state')] = gr.State(1)
371
+
372
+ all_sd3_ipa_components_flat = images + weights + start_percents + end_percents
373
+ components[key('all_sd3_ipadapter_components_flat')] = all_sd3_ipa_components_flat
374
+
375
+ return components
376
+
377
+ def create_style_ui(prefix: str):
378
+ components = {}
379
+ key = lambda name: f"{name}_{prefix}"
380
+
381
+ with gr.Accordion("Style Settings (FLUX.1)", open=False, visible=('style' in default_enabled_chains)) as accordion:
382
+ components[key('style_accordion')] = accordion
383
+
384
+ style_rows, images, strengths = [], [], []
385
+ components.update({
386
+ key('style_rows'): style_rows,
387
+ key('style_images'): images,
388
+ key('style_strengths'): strengths
389
+ })
390
+
391
+ for i in range(5):
392
+ with gr.Row(visible=(i < 1)) as row:
393
+ with gr.Column(scale=1):
394
+ images.append(gr.Image(label=f"Style Image {i+1}", type="pil", sources=["upload"], height=256))
395
+ with gr.Column(scale=2):
396
+ strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
397
+ style_rows.append(row)
398
+
399
+ with gr.Row():
400
+ components[key('add_style_button')] = gr.Button("✚ Add Style (FLUX)")
401
+ components[key('delete_style_button')] = gr.Button("➖ Delete Style (FLUX)", visible=False)
402
+ components[key('style_count_state')] = gr.State(1)
403
+
404
+ all_style_components_flat = images + strengths
405
+ components[key('all_style_components_flat')] = all_style_components_flat
406
+
407
+ return components
408
+
409
  def create_embedding_ui(prefix: str):
410
  components = {}
411
  key = lambda name: f"{name}_{prefix}"
412
 
413
+ with gr.Accordion("Embedding Settings", open=False, visible=('embedding' in default_enabled_chains)) as accordion:
414
  components[key('embedding_accordion')] = accordion
415
  gr.Markdown("💡 **Tip:** Embeddings are automatically added to your prompt using `embedding:filename` syntax. 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 instance, using the Version ID `456` from the example above would automatically append `embedding:civitai_456` to your positive prompt.")
416
 
 
448
  components = {}
449
  key = lambda name: f"{name}_{prefix}"
450
 
451
+ with gr.Accordion("Conditioning Settings", open=False, visible=('conditioning' in default_enabled_chains)) as accordion:
452
  components[key('conditioning_accordion')] = accordion
453
  gr.Markdown("💡 **Tip:** Define rectangular areas and assign specific prompts to them. Coordinates (X, Y) start from the top-left corner.")
454
 
 
484
 
485
  return components
486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  def create_vae_override_ui(prefix: str):
488
  components = {}
489
  key = lambda name: f"{name}_{prefix}"
 
515
  components[key('vae_upload_button')] = upload_btn
516
  components[key('vae_file')] = gr.State(None)
517
 
518
+ return components
519
+
520
+ def create_reference_latent_ui(prefix: str, max_units=10):
521
+ components = {}
522
+ key = lambda name: f"{name}_{prefix}"
523
+
524
+ with gr.Accordion("Reference Edit Settings", open=False, visible=('reference_latent' in default_enabled_chains)) as ref_accordion:
525
+ components[key('reference_latent_accordion')] = ref_accordion
526
+ gr.Markdown("💡 **Tip:** For multimodal models (like FLUX.2 or OmniGen), 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**.")
527
+
528
+ ref_image_groups = []
529
+ ref_image_inputs = []
530
+ with gr.Row():
531
+ for i in range(max_units):
532
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
533
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
534
+ ref_image_groups.append(img_col)
535
+ ref_image_inputs.append(img_comp)
536
+
537
+ components[key('reference_latent_rows')] = ref_image_groups
538
+ components[key('reference_latent_images')] = ref_image_inputs
539
+
540
+ with gr.Row():
541
+ components[key('add_reference_latent_button')] = gr.Button("✚ Add Reference Image")
542
+ components[key('delete_reference_latent_button')] = gr.Button("➖ Delete Reference Image", visible=False)
543
+ components[key('reference_latent_count_state')] = gr.State(1)
544
+
545
+ components[key('all_reference_latent_components_flat')] = ref_image_inputs
546
+
547
  return components
utils/app_utils.py CHANGED
@@ -11,16 +11,38 @@ from huggingface_hub import hf_hub_download, constants as hf_constants
11
  import torch
12
  import numpy as np
13
  from PIL import Image, ImageChops
14
-
15
 
16
  from core.settings import *
17
 
18
  DISK_LIMIT_GB = 120
19
  MODELS_ROOT_DIR = "ComfyUI/models"
20
 
21
- PREPROCESSOR_MODEL_MAP = None
22
- PREPROCESSOR_PARAMETER_MAP = None
23
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
  def save_uploaded_file_with_hash(file_obj: gr.File, target_dir: str) -> str:
26
  if not file_obj:
@@ -48,7 +70,6 @@ def save_uploaded_file_with_hash(file_obj: gr.File, target_dir: str) -> str:
48
 
49
  return hashed_filename
50
 
51
-
52
  def bytes_to_gb(byte_size: int) -> float:
53
  if byte_size is None or byte_size == 0:
54
  return 0.0
@@ -116,7 +137,6 @@ def enforce_disk_limit():
116
  except Exception as e:
117
  print(f"--- [Storage Manager] An unexpected error occurred: {e} ---")
118
 
119
-
120
  def get_value_at_index(obj: Union[Sequence, Mapping], index: int) -> Any:
121
  try:
122
  return obj[index]
@@ -151,7 +171,6 @@ def sanitize_filename(filename: str) -> str:
151
  sanitized = re.sub(r'[^\w\.\-]', '_', sanitized)
152
  return sanitized.lstrip('/\\')
153
 
154
-
155
  def get_civitai_file_info(version_id: str) -> dict | None:
156
  api_url = f"https://civitai.com/api/v1/model-versions/{version_id}"
157
  try:
@@ -168,7 +187,6 @@ def get_civitai_file_info(version_id: str) -> dict | None:
168
  except Exception:
169
  return None
170
 
171
-
172
  def download_file(url: str, save_path: str, api_key: str = None, progress=None, desc: str = "") -> str:
173
  enforce_disk_limit()
174
 
@@ -197,7 +215,6 @@ def download_file(url: str, save_path: str, api_key: str = None, progress=None,
197
  os.remove(save_path)
198
  return f"Download failed for {os.path.basename(save_path)}: {e}"
199
 
200
-
201
  def get_lora_path(source: str, id_or_url: str, civitai_key: str, progress) -> tuple[str | None, str]:
202
  if not id_or_url or not id_or_url.strip():
203
  return None, "No ID/URL provided."
@@ -301,47 +318,44 @@ def get_vae_path(source: str, id_or_url: str, civitai_key: str, progress) -> tup
301
  return (local_path, status) if "Successfully" in status else (None, status)
302
 
303
 
304
- def _ensure_model_downloaded(filename: str, progress=gr.Progress()):
305
- download_info = ALL_FILE_DOWNLOAD_MAP.get(filename)
 
 
 
 
 
 
 
306
  if not download_info:
307
- raise gr.Error(f"Model component '{filename}' not found in file_list.yaml. Cannot download.")
308
-
309
- category_to_dir_map = {
310
- "diffusion_models": DIFFUSION_MODELS_DIR,
311
- "text_encoders": TEXT_ENCODERS_DIR,
312
- "vae": VAE_DIR,
313
- "checkpoints": CHECKPOINT_DIR,
314
- "loras": LORA_DIR,
315
- "controlnet": CONTROLNET_DIR,
316
- "model_patches": MODEL_PATCHES_DIR,
317
- "clip_vision": os.path.join(os.path.dirname(LORA_DIR), "clip_vision")
318
- }
319
 
320
- category = download_info.get('category')
321
- dest_dir = category_to_dir_map.get(category)
322
  if not dest_dir:
323
- raise ValueError(f"Unknown model category '{category}' for file '{filename}'.")
324
 
325
- dest_path = os.path.join(dest_dir, filename)
326
 
327
  if os.path.lexists(dest_path):
328
  if not os.path.exists(dest_path):
329
  print(f"⚠️ Found and removed broken symlink: {dest_path}")
330
  os.remove(dest_path)
331
  else:
332
- return filename
333
 
334
  source = download_info.get("source")
335
  try:
336
- progress(0, desc=f"Downloading: {filename}")
337
 
338
  if source == "hf":
339
  repo_id = download_info.get("repo_id")
340
- hf_filename = download_info.get("repository_file_path", filename)
341
  if not repo_id:
342
- raise ValueError(f"repo_id is missing for HF model '{filename}'")
343
 
344
- cached_path = hf_hub_download(repo_id=repo_id, filename=hf_filename)
345
  os.makedirs(dest_dir, exist_ok=True)
346
  os.symlink(cached_path, dest_path)
347
  print(f"✅ Symlinked '{cached_path}' to '{dest_path}'")
@@ -349,98 +363,168 @@ def _ensure_model_downloaded(filename: str, progress=gr.Progress()):
349
  elif source == "civitai":
350
  model_version_id = download_info.get("model_version_id")
351
  if not model_version_id:
352
- raise ValueError(f"model_version_id is missing for Civitai model '{filename}'")
353
 
354
  file_info = get_civitai_file_info(model_version_id)
355
  if not file_info or not file_info.get('downloadUrl'):
356
  raise ConnectionError(f"Could not get download URL for Civitai model version ID {model_version_id}")
357
 
358
  status = download_file(
359
- file_info['downloadUrl'], dest_path, progress=progress, desc=f"Downloading: {filename}"
360
  )
361
  if "Failed" in status:
362
  raise ConnectionError(status)
363
  else:
364
- raise NotImplementedError(f"Download source '{source}' is not implemented for '{filename}'")
365
 
366
- progress(1.0, desc=f"Downloaded: {filename}")
367
 
368
  except Exception as e:
369
  if os.path.lexists(dest_path):
370
  try:
371
  os.remove(dest_path)
372
  except OSError: pass
373
- raise gr.Error(f"Failed to download and link '{filename}': {e}")
374
 
375
- return filename
376
 
377
  def ensure_controlnet_model_downloaded(filename: str, progress):
378
  if not filename or filename == "None":
379
  return
380
- _ensure_model_downloaded(filename, progress)
381
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
- def build_preprocessor_model_map():
384
- global PREPROCESSOR_MODEL_MAP
385
- if PREPROCESSOR_MODEL_MAP is not None: return PREPROCESSOR_MODEL_MAP
386
- print("--- Building ControlNet Preprocessor model map ---")
387
- manual_map = {
388
- "dwpose": [("yzd-v/DWPose", "yolox_l.onnx"), ("yzd-v/DWPose", "dw-ll_ucoco_384.onnx"), ("hr16/UnJIT-DWPose", "dw-ll_ucoco.onnx"), ("hr16/DWPose-TorchScript-BatchSize5", "dw-ll_ucoco_384_bs5.torchscript.pt"), ("hr16/DWPose-TorchScript-BatchSize5", "rtmpose-m_ap10k_256_bs5.torchscript.pt"), ("hr16/yolo-nas-fp16", "yolo_nas_l_fp16.onnx"), ("hr16/yolo-nas-fp16", "yolo_nas_m_fp16.onnx"), ("hr16/yolo-nas-fp16", "yolo_nas_s_fp16.onnx")],
389
- "densepose": [("LayerNorm/DensePose-TorchScript-with-hint-image", "densepose_r50_fpn_dl.torchscript"), ("LayerNorm/DensePose-TorchScript-with-hint-image", "densepose_r101_fpn_dl.torchscript")]
390
- }
391
- temp_map = {}
392
- from nodes import NODE_DISPLAY_NAME_MAPPINGS
393
- wrappers_dir = Path("./custom_nodes/comfyui_controlnet_aux/node_wrappers/")
394
- if not wrappers_dir.exists():
395
- print("⚠️ ControlNet AUX wrappers directory not found. Cannot build model map.")
396
- PREPROCESSOR_MODEL_MAP = {}; return PREPROCESSOR_MODEL_MAP
397
- for wrapper_file in wrappers_dir.glob("*.py"):
398
- if wrapper_file.name == "__init__.py": continue
399
- with open(wrapper_file, 'r', encoding='utf-8') as f:
400
- content = f.read()
401
- display_name_matches = re.findall(r'NODE_DISPLAY_NAME_MAPPINGS\s*=\s*{(?:.|\n)*?["\'](.*?)["\']\s*:\s*["\'](.*?)["\']', content)
402
- for _, display_name in display_name_matches:
403
- if display_name not in temp_map: temp_map[display_name] = []
404
- manual_key = wrapper_file.stem
405
- if manual_key in manual_map: temp_map[display_name].extend(manual_map[manual_key])
406
- matches = re.findall(r"from_pretrained\s*\(\s*(?:filename=)?\s*f?[\"']([^\"']+)[\"']", content)
407
- for model_filename in matches:
408
- repo_id = "lllyasviel/Annotators"
409
- if "depth_anything" in model_filename and "v2" in model_filename: repo_id = "LiheYoung/Depth-Anything-V2"
410
- elif "depth_anything" in model_filename: repo_id = "LiheYoung/Depth-Anything"
411
- elif "diffusion_edge" in model_filename: repo_id = "hr16/Diffusion-Edge"
412
- temp_map[display_name].append((repo_id, model_filename))
413
- final_map = {name: sorted(list(set(models))) for name, models in temp_map.items() if models}
414
- PREPROCESSOR_MODEL_MAP = final_map
415
- print("✅ ControlNet Preprocessor model map built."); return PREPROCESSOR_MODEL_MAP
416
-
417
- def build_preprocessor_parameter_map():
418
- global PREPROCESSOR_PARAMETER_MAP
419
- if PREPROCESSOR_PARAMETER_MAP is not None: return
420
- print("--- Building ControlNet Preprocessor parameter map ---")
421
- param_map = {}
422
- from nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
423
- for class_name, node_class in NODE_CLASS_MAPPINGS.items():
424
- if not hasattr(node_class, "INPUT_TYPES"): continue
425
- if hasattr(node_class, '__module__') and 'comfyui_controlnet_aux.node_wrappers' not in node_class.__module__: continue
426
- display_name = NODE_DISPLAY_NAME_MAPPINGS.get(class_name)
427
- if not display_name: continue
428
  try:
429
- input_types = node_class.INPUT_TYPES()
430
- all_inputs = {**input_types.get('required', {}), **input_types.get('optional', {})}
431
- params = []
432
- for name, details in all_inputs.items():
433
- if name in ['image', 'resolution', 'pose_kps']: continue
434
- if not isinstance(details, (list, tuple)) or not details: continue
435
- param_type = details[0]
436
- param_config = details[1] if len(details) > 1 and isinstance(details[1], dict) else {}
437
- param_info = {"name": name, "type": param_type, "config": param_config}
438
- params.append(param_info)
439
- if params: param_map[display_name] = params
440
  except Exception as e:
441
- print(f"⚠️ Could not parse parameters for {display_name}: {e}")
442
- PREPROCESSOR_PARAMETER_MAP = param_map
443
- print("✅ ControlNet Preprocessor parameter map built.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
  def print_welcome_message():
446
  author_name = "RioShiina"
@@ -459,4 +543,24 @@ def print_welcome_message():
459
  f"{border}\n"
460
  )
461
 
462
- print(message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  import torch
12
  import numpy as np
13
  from PIL import Image, ImageChops
14
+ import yaml
15
 
16
  from core.settings import *
17
 
18
  DISK_LIMIT_GB = 120
19
  MODELS_ROOT_DIR = "ComfyUI/models"
20
 
21
+ IPADAPTER_PRESETS = None
22
+
23
+ class UniqueKeyLoader(yaml.SafeLoader):
24
+ """
25
+ A custom YAML loader that handles duplicate keys by grouping their values into a list.
26
+ """
27
+ def construct_mapping(self, node, deep=False):
28
+ mapping = []
29
+ for key_node, value_node in node.value:
30
+ key = self.construct_object(key_node, deep=deep)
31
+ value = self.construct_object(value_node, deep=deep)
32
+ mapping.append((key, value))
33
+
34
+ result = {}
35
+ for k, v in mapping:
36
+ if k in result:
37
+ if isinstance(result[k], list):
38
+ result[k].append(v)
39
+ else:
40
+ result[k] = [result[k], v]
41
+ else:
42
+ result[k] = v
43
+ return result
44
+
45
+ UniqueKeyLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, UniqueKeyLoader.construct_mapping)
46
 
47
  def save_uploaded_file_with_hash(file_obj: gr.File, target_dir: str) -> str:
48
  if not file_obj:
 
70
 
71
  return hashed_filename
72
 
 
73
  def bytes_to_gb(byte_size: int) -> float:
74
  if byte_size is None or byte_size == 0:
75
  return 0.0
 
137
  except Exception as e:
138
  print(f"--- [Storage Manager] An unexpected error occurred: {e} ---")
139
 
 
140
  def get_value_at_index(obj: Union[Sequence, Mapping], index: int) -> Any:
141
  try:
142
  return obj[index]
 
171
  sanitized = re.sub(r'[^\w\.\-]', '_', sanitized)
172
  return sanitized.lstrip('/\\')
173
 
 
174
  def get_civitai_file_info(version_id: str) -> dict | None:
175
  api_url = f"https://civitai.com/api/v1/model-versions/{version_id}"
176
  try:
 
187
  except Exception:
188
  return None
189
 
 
190
  def download_file(url: str, save_path: str, api_key: str = None, progress=None, desc: str = "") -> str:
191
  enforce_disk_limit()
192
 
 
215
  os.remove(save_path)
216
  return f"Download failed for {os.path.basename(save_path)}: {e}"
217
 
 
218
  def get_lora_path(source: str, id_or_url: str, civitai_key: str, progress) -> tuple[str | None, str]:
219
  if not id_or_url or not id_or_url.strip():
220
  return None, "No ID/URL provided."
 
318
  return (local_path, status) if "Successfully" in status else (None, status)
319
 
320
 
321
+ def _ensure_model_downloaded(display_name: str, progress=gr.Progress()):
322
+ if display_name not in ALL_MODEL_MAP:
323
+ raise ValueError(f"Model '{display_name}' not found in configuration.")
324
+
325
+ model_info = ALL_MODEL_MAP[display_name]
326
+ repo_filename = model_info[1]
327
+ base_filename = os.path.basename(repo_filename)
328
+
329
+ download_info = ALL_FILE_DOWNLOAD_MAP.get(base_filename)
330
  if not download_info:
331
+ raise gr.Error(f"Model '{base_filename}' not found in file_list.yaml. Cannot download.")
332
+
333
+ category = download_info.get("category")
334
+ dest_dir = CATEGORY_TO_DIR_MAP.get(category)
 
 
 
 
 
 
 
 
335
 
 
 
336
  if not dest_dir:
337
+ raise ValueError(f"Unknown YAML category '{category}' for '{base_filename}'.")
338
 
339
+ dest_path = os.path.join(dest_dir, base_filename)
340
 
341
  if os.path.lexists(dest_path):
342
  if not os.path.exists(dest_path):
343
  print(f"⚠️ Found and removed broken symlink: {dest_path}")
344
  os.remove(dest_path)
345
  else:
346
+ return base_filename
347
 
348
  source = download_info.get("source")
349
  try:
350
+ progress(0, desc=f"Downloading: {base_filename}")
351
 
352
  if source == "hf":
353
  repo_id = download_info.get("repo_id")
354
+ hf_filename = download_info.get("repository_file_path", base_filename)
355
  if not repo_id:
356
+ raise ValueError(f"repo_id is missing for HF model '{base_filename}'")
357
 
358
+ cached_path = hf_hub_download(repo_id=repo_id, filename=hf_filename, token=os.environ.get("HF_TOKEN"))
359
  os.makedirs(dest_dir, exist_ok=True)
360
  os.symlink(cached_path, dest_path)
361
  print(f"✅ Symlinked '{cached_path}' to '{dest_path}'")
 
363
  elif source == "civitai":
364
  model_version_id = download_info.get("model_version_id")
365
  if not model_version_id:
366
+ raise ValueError(f"model_version_id is missing for Civitai model '{base_filename}'")
367
 
368
  file_info = get_civitai_file_info(model_version_id)
369
  if not file_info or not file_info.get('downloadUrl'):
370
  raise ConnectionError(f"Could not get download URL for Civitai model version ID {model_version_id}")
371
 
372
  status = download_file(
373
+ file_info['downloadUrl'], dest_path, api_key=os.environ.get("CIVITAI_API_KEY", ""), progress=progress, desc=f"Downloading: {base_filename}"
374
  )
375
  if "Failed" in status:
376
  raise ConnectionError(status)
377
  else:
378
+ raise NotImplementedError(f"Download source '{source}' is not implemented for '{base_filename}'")
379
 
380
+ progress(1.0, desc=f"Downloaded: {base_filename}")
381
 
382
  except Exception as e:
383
  if os.path.lexists(dest_path):
384
  try:
385
  os.remove(dest_path)
386
  except OSError: pass
387
+ raise gr.Error(f"Failed to download and link '{display_name}': {e}")
388
 
389
+ return base_filename
390
 
391
  def ensure_controlnet_model_downloaded(filename: str, progress):
392
  if not filename or filename == "None":
393
  return
 
394
 
395
+ download_info = ALL_FILE_DOWNLOAD_MAP.get(filename)
396
+ if not download_info:
397
+ raise gr.Error(f"ControlNet model '{filename}' not found in configuration (file_list.yaml). Cannot download.")
398
+
399
+ category = download_info.get("category", "controlnet")
400
+ dest_dir = CATEGORY_TO_DIR_MAP.get(category, CONTROLNET_DIR)
401
+ dest_path = os.path.join(dest_dir, filename)
402
+
403
+ if os.path.lexists(dest_path):
404
+ if not os.path.exists(dest_path):
405
+ print(f"⚠️ Found and removed broken symlink: {dest_path}")
406
+ os.remove(dest_path)
407
+ else:
408
+ return
409
+
410
+ source = download_info.get("source")
411
+
412
+ try:
413
+ if source == "hf":
414
+ repo_id = download_info.get("repo_id")
415
+ repo_filename = download_info.get("repository_file_path", filename)
416
+ if not repo_id:
417
+ raise ValueError("repo_id is missing for Hugging Face download.")
418
+
419
+ progress(0, desc=f"Downloading CN: {filename}")
420
+ cached_path = hf_hub_download(repo_id=repo_id, filename=repo_filename, token=os.environ.get("HF_TOKEN"))
421
+ os.makedirs(dest_dir, exist_ok=True)
422
+ os.symlink(cached_path, dest_path)
423
+ print(f"✅ Symlinked ControlNet '{cached_path}' to '{dest_path}'")
424
+ progress(1.0, desc=f"Downloaded CN: {filename}")
425
+
426
+ elif source == "civitai":
427
+ model_version_id = download_info.get("model_version_id")
428
+ if not model_version_id:
429
+ raise ValueError("model_version_id is missing for Civitai download.")
430
+
431
+ file_info = get_civitai_file_info(model_version_id)
432
+ if not file_info or not file_info.get('downloadUrl'):
433
+ raise ConnectionError(f"Could not get download URL for Civitai model version ID {model_version_id}")
434
+
435
+ status = download_file(
436
+ file_info['downloadUrl'],
437
+ dest_path,
438
+ api_key=os.environ.get("CIVITAI_API_KEY", ""),
439
+ progress=progress,
440
+ desc=f"Downloading CN: {filename}"
441
+ )
442
+ if "Failed" in status:
443
+ raise ConnectionError(status)
444
+ else:
445
+ raise NotImplementedError(f"Download source '{source}' is not implemented for ControlNets.")
446
+
447
+ except Exception as e:
448
+ if os.path.lexists(dest_path):
449
+ try:
450
+ os.remove(dest_path)
451
+ except OSError:
452
+ pass
453
+ raise gr.Error(f"Failed to download ControlNet model '{filename}': {e}")
454
+
455
+ def load_ipadapter_presets():
456
+ global IPADAPTER_PRESETS
457
+ if IPADAPTER_PRESETS is not None:
458
+ return
459
+
460
+ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
461
+ _IPADAPTER_MODELS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter_models.yaml')
462
+
463
+ try:
464
+ with open(_IPADAPTER_MODELS_PATH, 'r', encoding='utf-8') as f:
465
+ presets_list = yaml.load(f, Loader=UniqueKeyLoader)
466
+
467
+ IPADAPTER_PRESETS = {item['preset_name']: item for item in presets_list}
468
+ print("✅ IPAdapter presets loaded successfully.")
469
+ except Exception as e:
470
+ print(f"❌ FATAL: Could not load or parse ipadapter_models.yaml. IPAdapter will not work. Error: {e}")
471
+ IPADAPTER_PRESETS = {}
472
+
473
+ def ensure_ipadapter_models_downloaded(preset_name: str, progress):
474
+ if not preset_name:
475
+ return
476
+
477
+ if IPADAPTER_PRESETS is None:
478
+ raise RuntimeError("IPAdapter presets have not been loaded. `load_ipadapter_presets` must be called on startup.")
479
+
480
+ preset_info = IPADAPTER_PRESETS.get(preset_name)
481
+ if not preset_info:
482
+ print(f"⚠️ Warning: IPAdapter preset '{preset_name}' not found in configuration. Skipping download.")
483
+ return
484
+
485
+ model_files_to_check = []
486
+
487
+ def add_files(value, type_name):
488
+ if not value: return
489
+ if isinstance(value, list):
490
+ for v in value:
491
+ model_files_to_check.append((v, type_name))
492
+ else:
493
+ model_files_to_check.append((value, type_name))
494
+
495
+ add_files(preset_info.get('clip_vision'), 'CLIP_VISION')
496
+ add_files(preset_info.get('ipadapter'), 'IPADAPTER')
497
+ add_files(preset_info.get('loras'), 'LORA')
498
+
499
+ for filename, model_type in model_files_to_check:
500
+ if not filename:
501
+ continue
502
+
503
+ temp_display_name = f"ipadapter_asset_{filename}"
504
+
505
+ if temp_display_name not in ALL_MODEL_MAP:
506
+ ALL_MODEL_MAP[temp_display_name] = (None, filename, model_type, None, None)
507
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  try:
509
+ _ensure_model_downloaded(temp_display_name, progress)
 
 
 
 
 
 
 
 
 
 
510
  except Exception as e:
511
+ print(f" Error ensuring download for IPAdapter asset '{filename}': {e}")
512
+
513
+
514
+ def ensure_sd3_ipadapter_models_downloaded(progress):
515
+ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
516
+ yaml_path = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter_sd3_models.yaml')
517
+ try:
518
+ with open(yaml_path, 'r', encoding='utf-8') as f:
519
+ sd3_models = yaml.safe_load(f)
520
+ if sd3_models:
521
+ if 'ipadapter' in sd3_models:
522
+ _ensure_model_downloaded(sd3_models['ipadapter'], progress)
523
+ if 'clip_vision' in sd3_models:
524
+ _ensure_model_downloaded(sd3_models['clip_vision'], progress)
525
+ except Exception as e:
526
+ print(f"Warning: Failed to load or download sd3 ipadapter models: {e}")
527
+
528
 
529
  def print_welcome_message():
530
  author_name = "RioShiina"
 
543
  f"{border}\n"
544
  )
545
 
546
+ print(message)
547
+
548
+ def get_model_generation_defaults(model_display_name: str, model_type: str, defaults_config: dict):
549
+ final_defaults = {
550
+ 'steps': 25, 'cfg': 7.0, 'sampler_name': 'euler', 'scheduler': 'simple',
551
+ 'positive_prompt': '', 'negative_prompt': ''
552
+ }
553
+
554
+ if 'Default' in defaults_config:
555
+ final_defaults.update(defaults_config['Default'])
556
+
557
+ model_type_key = next((key for key in defaults_config if key.lower().replace(" ", "-").replace(".", "") == model_type.lower()), None)
558
+ if model_type_key:
559
+ model_type_config = defaults_config[model_type_key]
560
+ if '_defaults' in model_type_config:
561
+ final_defaults.update(model_type_config['_defaults'])
562
+
563
+ if model_display_name in model_type_config:
564
+ final_defaults.update(model_type_config[model_display_name])
565
+
566
+ return final_defaults
yaml/constants.yaml CHANGED
@@ -1,11 +1,116 @@
1
  MAX_LORAS: 5
2
  MAX_CONTROLNETS: 5
 
3
  MAX_EMBEDDINGS: 5
4
  MAX_CONDITIONINGS: 10
5
  MAX_REFERENCE_LATENTS: 10
6
  LORA_SOURCE_CHOICES: ["Civitai", "File"]
7
 
8
  RESOLUTION_MAP:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  sdxl:
10
  "1:1 (Square)": [1024, 1024]
11
  "16:9 (Landscape)": [1344, 768]
@@ -13,4 +118,36 @@ RESOLUTION_MAP:
13
  "4:3 (Classic)": [1152, 896]
14
  "3:4 (Classic Portrait)": [896, 1152]
15
  "3:2 (Photography)": [1216, 832]
16
- "2:3 (Photography Portrait)": [832, 1216]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  MAX_LORAS: 5
2
  MAX_CONTROLNETS: 5
3
+ MAX_IPADAPTERS: 5
4
  MAX_EMBEDDINGS: 5
5
  MAX_CONDITIONINGS: 10
6
  MAX_REFERENCE_LATENTS: 10
7
  LORA_SOURCE_CHOICES: ["Civitai", "File"]
8
 
9
  RESOLUTION_MAP:
10
+ ernie-image:
11
+ "1:1 (Square)": [1024, 1024]
12
+ "16:9 (Landscape)": [1344, 768]
13
+ "9:16 (Portrait)": [768, 1344]
14
+ "4:3 (Classic)": [1152, 896]
15
+ "3:4 (Classic Portrait)": [896, 1152]
16
+ "3:2 (Photography)": [1216, 832]
17
+ "2:3 (Photography Portrait)": [832, 1216]
18
+ flux2:
19
+ "1:1 (Square)": [1024, 1024]
20
+ "16:9 (Landscape)": [1344, 768]
21
+ "9:16 (Portrait)": [768, 1344]
22
+ "4:3 (Classic)": [1152, 896]
23
+ "3:4 (Classic Portrait)": [896, 1152]
24
+ "3:2 (Photography)": [1216, 832]
25
+ "2:3 (Photography Portrait)": [832, 1216]
26
+ flux2-kv:
27
+ "1:1 (Square)": [1024, 1024]
28
+ "16:9 (Landscape)": [1344, 768]
29
+ "9:16 (Portrait)": [768, 1344]
30
+ "4:3 (Classic)": [1152, 896]
31
+ "3:4 (Classic Portrait)": [896, 1152]
32
+ "3:2 (Photography)": [1216, 832]
33
+ "2:3 (Photography Portrait)": [832, 1216]
34
+ qwen-image:
35
+ "1:1 (Square)": [1328, 1328]
36
+ "16:9 (Landscape)": [1664, 928]
37
+ "9:16 (Portrait)": [928, 1664]
38
+ "4:3 (Classic)": [1472, 1104]
39
+ "3:4 (Classic Portrait)": [1104, 1472]
40
+ "3:2 (Photography)": [1536, 1024]
41
+ "2:3 (Photography Portrait)": [1024, 1536]
42
+ longcat-image:
43
+ "1:1 (Square)": [1024, 1024]
44
+ "16:9 (Landscape)": [1344, 768]
45
+ "9:16 (Portrait)": [768, 1344]
46
+ "4:3 (Classic)": [1152, 896]
47
+ "3:4 (Classic Portrait)": [896, 1152]
48
+ "3:2 (Photography)": [1216, 832]
49
+ "2:3 (Photography Portrait)": [832, 1216]
50
+ anima:
51
+ "1:1 (Square)": [1024, 1024]
52
+ "16:9 (Landscape)": [1344, 768]
53
+ "9:16 (Portrait)": [768, 1344]
54
+ "4:3 (Classic)": [1152, 896]
55
+ "3:4 (Classic Portrait)": [896, 1152]
56
+ "3:2 (Photography)": [1216, 832]
57
+ "2:3 (Photography Portrait)": [832, 1216]
58
+ newbie-image:
59
+ "1:1 (Square)": [1024, 1024]
60
+ "16:9 (Landscape)": [1344, 768]
61
+ "9:16 (Portrait)": [768, 1344]
62
+ "4:3 (Classic)": [1152, 896]
63
+ "3:4 (Classic Portrait)": [896, 1152]
64
+ "3:2 (Photography)": [1216, 832]
65
+ "2:3 (Photography Portrait)": [832, 1216]
66
+ omnigen2:
67
+ "1:1 (Square)": [1024, 1024]
68
+ "16:9 (Landscape)": [1344, 768]
69
+ "9:16 (Portrait)": [768, 1344]
70
+ "4:3 (Classic)": [1152, 896]
71
+ "3:4 (Classic Portrait)": [896, 1152]
72
+ "3:2 (Photography)": [1216, 832]
73
+ "2:3 (Photography Portrait)": [832, 1216]
74
+ lumina:
75
+ "1:1 (Square)": [1024, 1024]
76
+ "16:9 (Landscape)": [1344, 768]
77
+ "9:16 (Portrait)": [768, 1344]
78
+ "4:3 (Classic)": [1152, 896]
79
+ "3:4 (Classic Portrait)": [896, 1152]
80
+ "3:2 (Photography)": [1216, 832]
81
+ "2:3 (Photography Portrait)": [832, 1216]
82
+ ovis-image:
83
+ "1:1 (Square)": [1024, 1024]
84
+ "16:9 (Landscape)": [1344, 768]
85
+ "9:16 (Portrait)": [768, 1344]
86
+ "4:3 (Classic)": [1152, 896]
87
+ "3:4 (Classic Portrait)": [896, 1152]
88
+ "3:2 (Photography)": [1216, 832]
89
+ "2:3 (Photography Portrait)": [832, 1216]
90
+ flux1:
91
+ "1:1 (Square)": [1024, 1024]
92
+ "16:9 (Landscape)": [1344, 768]
93
+ "9:16 (Portrait)": [768, 1344]
94
+ "4:3 (Classic)": [1152, 896]
95
+ "3:4 (Classic Portrait)": [896, 1152]
96
+ "3:2 (Photography)": [1216, 832]
97
+ "2:3 (Photography Portrait)": [832, 1216]
98
+ hidream:
99
+ "1:1 (Square)": [1024, 1024]
100
+ "16:9 (Landscape)": [1344, 768]
101
+ "9:16 (Portrait)": [768, 1344]
102
+ "4:3 (Classic)": [1152, 896]
103
+ "3:4 (Classic Portrait)": [896, 1152]
104
+ "3:2 (Photography)": [1216, 832]
105
+ "2:3 (Photography Portrait)": [832, 1216]
106
+ sd35:
107
+ "1:1 (Square)": [1024, 1024]
108
+ "16:9 (Landscape)": [1344, 768]
109
+ "9:16 (Portrait)": [768, 1344]
110
+ "4:3 (Classic)": [1152, 896]
111
+ "3:4 (Classic Portrait)": [896, 1152]
112
+ "3:2 (Photography)": [1216, 832]
113
+ "2:3 (Photography Portrait)": [832, 1216]
114
  sdxl:
115
  "1:1 (Square)": [1024, 1024]
116
  "16:9 (Landscape)": [1344, 768]
 
118
  "4:3 (Classic)": [1152, 896]
119
  "3:4 (Classic Portrait)": [896, 1152]
120
  "3:2 (Photography)": [1216, 832]
121
+ "2:3 (Photography Portrait)": [832, 1216]
122
+ sd15:
123
+ "1:1 (Square)": [512, 512]
124
+ "16:9 (Landscape)": [896, 512]
125
+ "9:16 (Portrait)": [512, 896]
126
+ "4:3 (Classic Landscape)": [683, 512]
127
+ "3:4 (Classic Portrait)": [512, 683]
128
+ "3:2 (Landscape)": [768, 512]
129
+ "2:3 (Portrait)": [512, 768]
130
+ chroma1-radiance:
131
+ "1:1 (Square)": [1024, 1024]
132
+ "16:9 (Landscape)": [1344, 768]
133
+ "9:16 (Portrait)": [768, 1344]
134
+ "4:3 (Classic)": [1152, 896]
135
+ "3:4 (Classic Portrait)": [896, 1152]
136
+ "3:2 (Photography)": [1216, 832]
137
+ "2:3 (Photography Portrait)": [832, 1216]
138
+ chroma1:
139
+ "1:1 (Square)": [1024, 1024]
140
+ "16:9 (Landscape)": [1344, 768]
141
+ "9:16 (Portrait)": [768, 1344]
142
+ "4:3 (Classic)": [1152, 896]
143
+ "3:4 (Classic Portrait)": [896, 1152]
144
+ "3:2 (Photography)": [1216, 832]
145
+ "2:3 (Photography Portrait)": [832, 1216]
146
+ hunyuanimage:
147
+ "1:1 (Square)": [2048, 2048]
148
+ "16:9 (Landscape)": [2728, 1536]
149
+ "9:16 (Portrait)": [1536, 2728]
150
+ "4:3 (Classic)": [2368, 1776]
151
+ "3:4 (Classic Portrait)": [1776, 2368]
152
+ "3:2 (Photography)": [2504, 1672]
153
+ "2:3 (Photography Portrait)": [1672, 2504]
yaml/file_list.yaml CHANGED
@@ -1,25 +1,645 @@
1
- file:
2
- checkpoints:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  - filename: "sd_xl_base_1.0.safetensors"
4
  source: "hf"
5
  repo_id: "stabilityai/stable-diffusion-xl-base-1.0"
6
  repository_file_path: "sd_xl_base_1.0.safetensors"
 
7
  - filename: "v1-5-pruned-emaonly.safetensors"
8
  source: "hf"
9
  repo_id: "stable-diffusion-v1-5/stable-diffusion-v1-5"
10
  repository_file_path: "v1-5-pruned-emaonly.safetensors"
11
- diffusion_models:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  - filename: "flux2_dev_fp8mixed.safetensors"
13
  source: "hf"
14
  repo_id: "Comfy-Org/flux2-dev"
15
  repository_file_path: "split_files/diffusion_models/flux2_dev_fp8mixed.safetensors"
16
- text_encoders:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  - filename: "mistral_3_small_flux2_fp8.safetensors"
18
  source: "hf"
19
  repo_id: "Comfy-Org/flux2-dev"
20
  repository_file_path: "split_files/text_encoders/mistral_3_small_flux2_fp8.safetensors"
21
- vae:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  - filename: "flux2-vae.safetensors"
23
  source: "hf"
24
  repo_id: "Comfy-Org/flux2-dev"
25
- repository_file_path: "split_files/vae/flux2-vae.safetensors"
 
1
+ file:
2
+ checkpoints:
3
+ # Lumina
4
+ - filename: "lumina_2.safetensors"
5
+ source: "hf"
6
+ repo_id: "Comfy-Org/Lumina_Image_2.0_Repackaged"
7
+ repository_file_path: "all_in_one/lumina_2.safetensors"
8
+ # SD3.5
9
+ - filename: "sd3.5_large_fp8_scaled.safetensors"
10
+ source: "hf"
11
+ repo_id: "Comfy-Org/stable-diffusion-3.5-fp8"
12
+ repository_file_path: "sd3.5_large_fp8_scaled.safetensors"
13
+ - filename: "sd3.5_medium_incl_clips_t5xxlfp8scaled.safetensors"
14
+ source: "hf"
15
+ repo_id: "Comfy-Org/stable-diffusion-3.5-fp8"
16
+ repository_file_path: "sd3.5_medium_incl_clips_t5xxlfp8scaled.safetensors"
17
+ # SDXL-NoobAI
18
+ - filename: "NoobAI-XL-Vpred-v1.0.safetensors"
19
+ source: hf
20
+ repo_id: "Laxhar/noobai-XL-Vpred-1.0"
21
+ repository_file_path: "NoobAI-XL-Vpred-v1.0.safetensors"
22
+ - filename: "NoobAI-XL-v1.1.safetensors"
23
+ source: hf
24
+ repo_id: "Laxhar/noobai-XL-1.1"
25
+ repository_file_path: "NoobAI-XL-v1.1.safetensors"
26
+ - filename: "noob_v_pencil-XL-v3.0.0.safetensors"
27
+ source: hf
28
+ repo_id: "bluepen5805/noob_v_pencil-XL"
29
+ repository_file_path: "noob_v_pencil-XL-v3.0.0.safetensors"
30
+ - filename: "Hikari_Noob_v-pred_1.2.4.safetensors"
31
+ source: hf
32
+ repo_id: "RedRayz/hikari_noob_v-pred_1.2.4"
33
+ repository_file_path: "Hikari_Noob_v-pred_1.2.4.safetensors"
34
+ - filename: "ChenkinNoob-XL-V0.5.safetensors"
35
+ source: hf
36
+ repo_id: "ChenkinNoob/ChenkinNoob-XL-V0.5"
37
+ repository_file_path: "ChenkinNoob-XL-V0.5.safetensors"
38
+ # SDXL-Illustrious
39
+ - filename: "waiIllustriousSDXL_v170.safetensors"
40
+ source: hf
41
+ repo_id: "zhenshipo/waiIllustriousSDXL_v170"
42
+ repository_file_path: "waiIllustriousSDXL_v170.safetensors"
43
+ - filename: "mellow_pencil-XL-v1.0.0.safetensors"
44
+ source: hf
45
+ repo_id: "bluepen5805/mellow_pencil-XL"
46
+ repository_file_path: "mellow_pencil-XL-v1.0.0.safetensors"
47
+ - filename: "illustrious_pencil-XL-v5.0.0.safetensors"
48
+ source: hf
49
+ repo_id: "bluepen5805/illustrious_pencil-XL"
50
+ repository_file_path: "illustrious_pencil-XL-v5.0.0.safetensors"
51
+ - filename: "hassakuXLIllustrious_v34.safetensors"
52
+ source: hf
53
+ repo_id: "oldhag88/hassakuXLIllustrious_v34.safetensors"
54
+ repository_file_path: "hassakuXLIllustrious_v34.safetensors"
55
+ - filename: "novaAnimeXL_ilV160.safetensors"
56
+ source: hf
57
+ repo_id: "Yevrey921/novaAnimeXL_ilV160"
58
+ repository_file_path: "novaAnimeXL_ilV160.safetensors"
59
+ - filename: "Illustrious-XL-v2.0.safetensors"
60
+ source: hf
61
+ repo_id: "OnomaAIResearch/Illustrious-XL-v2.0"
62
+ - filename: "Illustrious-XL-v2.0.safetensors"
63
+ source: hf
64
+ repo_id: "OnomaAIResearch/Illustrious-XL-v2.0"
65
+ repository_file_path: "Illustrious-XL-v2.0.safetensors"
66
+ - filename: "Illustrious-XL-v1.1.safetensors"
67
+ source: hf
68
+ repo_id: "OnomaAIResearch/Illustrious-XL-v1.1"
69
+ repository_file_path: "Illustrious-XL-v1.1.safetensors"
70
+ - filename: "Illustrious-XL-v1.0.safetensors"
71
+ source: hf
72
+ repo_id: "OnomaAIResearch/Illustrious-XL-v1.0"
73
+ repository_file_path: "Illustrious-XL-v1.0.safetensors"
74
+ - filename: "illustriousXL_v01.safetensors"
75
+ source: hf
76
+ repo_id: "AiAF/Illustrious-XL-v0.1.safetensors"
77
+ repository_file_path: "illustriousXL_v01.safetensors"
78
+ # SDXL-Animate
79
+ - filename: "animagine-xl-4.0.safetensors"
80
+ source: hf
81
+ repo_id: "cagliostrolab/animagine-xl-4.0"
82
+ repository_file_path: "animagine-xl-4.0.safetensors"
83
+ - filename: "animagine-xl-3.1.safetensors"
84
+ source: hf
85
+ repo_id: "cagliostrolab/animagine-xl-3.1"
86
+ repository_file_path: "animagine-xl-3.1.safetensors"
87
+ - filename: "4nima_pencil-XL-v1.0.1.safetensors"
88
+ source: hf
89
+ repo_id: "bluepen5805/4nima_pencil-XL"
90
+ repository_file_path: "4nima_pencil-XL-v1.0.1.safetensors"
91
+ - filename: "anima_pencil-XL-v5.0.0.safetensors"
92
+ source: hf
93
+ repo_id: "bluepen5805/anima_pencil-XL"
94
+ repository_file_path: "anima_pencil-XL-v5.0.0.safetensors"
95
+ - filename: "blue_pencil-XL-v7.0.0.safetensors"
96
+ source: hf
97
+ repo_id: "bluepen5805/blue_pencil-XL"
98
+ repository_file_path: "blue_pencil-XL-v7.0.0.safetensors"
99
+ # SDXL-Pony
100
+ - filename: "ponyDiffusionV6XL_v6StartWithThisOne.safetensors"
101
+ source: hf
102
+ repo_id: "LyliaEngine/Pony_Diffusion_V6_XL"
103
+ repository_file_path: "ponyDiffusionV6XL_v6StartWithThisOne.safetensors"
104
+ - filename: "pony_pencil-XL-v2.0.0.safetensors"
105
+ source: hf
106
+ repo_id: "bluepen5805/pony_pencil-XL"
107
+ repository_file_path: "pony_pencil-XL-v2.0.0.safetensors"
108
+ - filename: "CyberRealisticPony_V14.0.safetensors"
109
+ source: hf
110
+ repo_id: "cyberdelia/CyberRealisticPony"
111
+ repository_file_path: "CyberRealisticPony_V14.0.safetensors"
112
+ # SDXL-Base
113
  - filename: "sd_xl_base_1.0.safetensors"
114
  source: "hf"
115
  repo_id: "stabilityai/stable-diffusion-xl-base-1.0"
116
  repository_file_path: "sd_xl_base_1.0.safetensors"
117
+ # SD1.5
118
  - filename: "v1-5-pruned-emaonly.safetensors"
119
  source: "hf"
120
  repo_id: "stable-diffusion-v1-5/stable-diffusion-v1-5"
121
  repository_file_path: "v1-5-pruned-emaonly.safetensors"
122
+ clip_vision:
123
+ # style_injector
124
+ - filename: "sigclip_vision_patch14_384.safetensors"
125
+ source: "hf"
126
+ repo_id: "Comfy-Org/sigclip_vision_384"
127
+ repository_file_path: "sigclip_vision_patch14_384.safetensors"
128
+ # IPAdapter
129
+ - filename: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
130
+ source: "hf"
131
+ repo_id: "h94/IP-Adapter"
132
+ repository_file_path: "models/image_encoder/model.safetensors"
133
+ - filename: "CLIP-ViT-bigG-14-laion2B-39B-b160k.safetensors"
134
+ source: "hf"
135
+ repo_id: "h94/IP-Adapter"
136
+ repository_file_path: "sdxl_models/image_encoder/model.safetensors"
137
+ # IPAdapter-SD3
138
+ - filename: "sigclip_vision_patch14_384.safetensors"
139
+ source: "hf"
140
+ repo_id: "Comfy-Org/sigclip_vision_384"
141
+ repository_file_path: "sigclip_vision_patch14_384.safetensors"
142
+ controlnet:
143
+ # SD3.5
144
+ - filename: "sd3.5_large_controlnet_blur.safetensors"
145
+ source: "hf"
146
+ repo_id: "stabilityai/stable-diffusion-3.5-controlnets"
147
+ repository_file_path: "sd3.5_large_controlnet_blur.safetensors"
148
+ - filename: "sd3.5_large_controlnet_canny.safetensors"
149
+ source: "hf"
150
+ repo_id: "stabilityai/stable-diffusion-3.5-controlnets"
151
+ repository_file_path: "sd3.5_large_controlnet_canny.safetensors"
152
+ - filename: "sd3.5_large_controlnet_depth.safetensors"
153
+ source: "hf"
154
+ repo_id: "stabilityai/stable-diffusion-3.5-controlnets"
155
+ repository_file_path: "sd3.5_large_controlnet_depth.safetensors"
156
+ # Qwen-Image
157
+ - filename: "Qwen-Image-InstantX-ControlNet-Union.safetensors"
158
+ source: "hf"
159
+ repo_id: "InstantX/Qwen-Image-ControlNet-Union"
160
+ repository_file_path: "diffusion_pytorch_model.safetensors"
161
+ - filename: "Qwen-Image-InstantX-ControlNet-Inpainting.safetensors"
162
+ source: "hf"
163
+ repo_id: "InstantX/Qwen-Image-ControlNet-Inpainting"
164
+ repository_file_path: "diffusion_pytorch_model.safetensors"
165
+ # FLUX.1
166
+ - filename: "FLUX.1-dev-ControlNet-Union-Pro-2.0.safetensors"
167
+ source: "hf"
168
+ repo_id: "Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro-2.0"
169
+ repository_file_path: "diffusion_pytorch_model.safetensors"
170
+ - filename: "flux-canny-controlnet-v3.safetensors"
171
+ source: "hf"
172
+ repo_id: "XLabs-AI/flux-controlnet-collections"
173
+ repository_file_path: "flux-canny-controlnet-v3.safetensors"
174
+ - filename: "flux-depth-controlnet-v3.safetensors"
175
+ source: "hf"
176
+ repo_id: "XLabs-AI/flux-controlnet-collections"
177
+ repository_file_path: "flux-depth-controlnet-v3.safetensors"
178
+ - filename: "flux-hed-controlnet-v3.safetensors"
179
+ source: "hf"
180
+ repo_id: "XLabs-AI/flux-controlnet-collections"
181
+ repository_file_path: "flux-hed-controlnet-v3.safetensors"
182
+ # SDXL
183
+ - filename: "controlnet-union-sdxl-1.0_promax.safetensors"
184
+ source: "hf"
185
+ repo_id: "xinsir/controlnet-union-sdxl-1.0"
186
+ repository_file_path: "diffusion_pytorch_model_promax.safetensors"
187
+ - filename: "controlnet-tile-sdxl-1.0.safetensors"
188
+ source: "hf"
189
+ repo_id: "xinsir/controlnet-tile-sdxl-1.0"
190
+ repository_file_path: "diffusion_pytorch_model.safetensors"
191
+ - filename: "controlnet-canny-sdxl-1.0_V2.safetensors"
192
+ source: "hf"
193
+ repo_id: "xinsir/controlnet-canny-sdxl-1.0"
194
+ repository_file_path: "diffusion_pytorch_model_V2.safetensors"
195
+ - filename: "controlnet-openpose-sdxl-1.0.safetensors"
196
+ source: "hf"
197
+ repo_id: "xinsir/controlnet-openpose-sdxl-1.0"
198
+ repository_file_path: "diffusion_pytorch_model.safetensors"
199
+ - filename: "controlnet-depth-sdxl-1.0.safetensors"
200
+ source: "hf"
201
+ repo_id: "xinsir/controlnet-depth-sdxl-1.0"
202
+ repository_file_path: "diffusion_pytorch_model.safetensors"
203
+ - filename: "controlnet-scribble-sdxl-1.0.safetensors"
204
+ source: "hf"
205
+ repo_id: "xinsir/controlnet-scribble-sdxl-1.0"
206
+ repository_file_path: "diffusion_pytorch_model.safetensors"
207
+ - filename: "anime-painter.safetensors"
208
+ source: "hf"
209
+ repo_id: "xinsir/anime-painter"
210
+ repository_file_path: "diffusion_pytorch_model.safetensors"
211
+ # SD1.5
212
+ - filename: "control_v11e_sd15_ip2p_fp16.safetensors"
213
+ source: "hf"
214
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
215
+ repository_file_path: "control_v11e_sd15_ip2p_fp16.safetensors"
216
+ - filename: "control_v11e_sd15_shuffle_fp16.safetensors"
217
+ source: "hf"
218
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
219
+ repository_file_path: "control_v11e_sd15_shuffle_fp16.safetensors"
220
+ - filename: "control_v11f1e_sd15_tile_fp16.safetensors"
221
+ source: "hf"
222
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
223
+ repository_file_path: "control_v11f1e_sd15_tile_fp16.safetensors"
224
+ - filename: "control_v11f1p_sd15_depth_fp16.safetensors"
225
+ source: "hf"
226
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
227
+ repository_file_path: "control_v11f1p_sd15_depth_fp16.safetensors"
228
+ - filename: "control_v11p_sd15_canny_fp16.safetensors"
229
+ source: "hf"
230
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
231
+ repository_file_path: "control_v11p_sd15_canny_fp16.safetensors"
232
+ - filename: "control_v11p_sd15_inpaint_fp16.safetensors"
233
+ source: "hf"
234
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
235
+ repository_file_path: "control_v11p_sd15_inpaint_fp16.safetensors"
236
+ - filename: "control_v11p_sd15_lineart_fp16.safetensors"
237
+ source: "hf"
238
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
239
+ repository_file_path: "control_v11p_sd15_lineart_fp16.safetensors"
240
+ - filename: "control_v11p_sd15_mlsd_fp16.safetensors"
241
+ source: "hf"
242
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
243
+ repository_file_path: "control_v11p_sd15_mlsd_fp16.safetensors"
244
+ - filename: "control_v11p_sd15_normalbae_fp16.safetensors"
245
+ source: "hf"
246
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
247
+ repository_file_path: "control_v11p_sd15_normalbae_fp16.safetensors"
248
+ - filename: "control_v11p_sd15_openpose_fp16.safetensors"
249
+ source: "hf"
250
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
251
+ repository_file_path: "control_v11p_sd15_openpose_fp16.safetensors"
252
+ - filename: "control_v11p_sd15_scribble_fp16.safetensors"
253
+ source: "hf"
254
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
255
+ repository_file_path: "control_v11p_sd15_scribble_fp16.safetensors"
256
+ - filename: "control_v11p_sd15_seg_fp16.safetensors"
257
+ source: "hf"
258
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
259
+ repository_file_path: "control_v11p_sd15_seg_fp16.safetensors"
260
+ - filename: "control_v11p_sd15_softedge_fp16.safetensors"
261
+ source: "hf"
262
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
263
+ repository_file_path: "control_v11p_sd15_softedge_fp16.safetensors"
264
+ - filename: "control_v11p_sd15s2_lineart_anime_fp16.safetensors"
265
+ source: "hf"
266
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
267
+ repository_file_path: "control_v11p_sd15s2_lineart_anime_fp16.safetensors"
268
+ - filename: "control_v11u_sd15_tile_fp16.safetensors"
269
+ source: "hf"
270
+ repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
271
+ repository_file_path: "control_v11u_sd15_tile_fp16.safetensors"
272
+ diffusion_models:
273
+ # FLUX.2-klein-9B
274
+ - filename: "flux-2-klein-9b-fp8.safetensors"
275
+ source: "hf"
276
+ repo_id: "black-forest-labs/FLUX.2-klein-9b-fp8"
277
+ repository_file_path: "flux-2-klein-9b-fp8.safetensors"
278
+ # FLUX.2-klein-base-9B
279
+ - filename: "flux-2-klein-base-9b-fp8.safetensors"
280
+ source: "hf"
281
+ repo_id: "black-forest-labs/FLUX.2-klein-base-9b-fp8"
282
+ repository_file_path: "flux-2-klein-base-9b-fp8.safetensors"
283
+ # Anima
284
+ - filename: "anima-preview3-base.safetensors"
285
+ source: "hf"
286
+ repo_id: "circlestone-labs/Anima"
287
+ repository_file_path: "split_files/diffusion_models/anima-preview3-base.safetensors"
288
+ - filename: "AnimaYume_tuned_v04.safetensors"
289
+ source: "hf"
290
+ repo_id: "duongve/AnimaYume"
291
+ repository_file_path: "split_files/diffusion_models/AnimaYume_tuned_v04.safetensors"
292
+ # NewBie-Image
293
+ - filename: "NewBie-Image-Exp0.1-bf16.safetensors"
294
+ source: "hf"
295
+ repo_id: "Comfy-Org/NewBie-image-Exp0.1_repackaged"
296
+ repository_file_path: "split_files/diffusion_models/NewBie-Image-Exp0.1-bf16.safetensors"
297
+ # ERNIE-Image
298
+ - filename: "ernie-image.safetensors"
299
+ source: "hf"
300
+ repo_id: "Comfy-Org/ERNIE-Image"
301
+ repository_file_path: "diffusion_models/ernie-image.safetensors"
302
+ - filename: "ernie-image-turbo.safetensors"
303
+ source: "hf"
304
+ repo_id: "Comfy-Org/ERNIE-Image"
305
+ repository_file_path: "diffusion_models/ernie-image-turbo.safetensors"
306
+ # FLUX.2-klein-9B-KV
307
+ - filename: "flux-2-klein-9b-kv-fp8.safetensors"
308
+ source: "hf"
309
+ repo_id: "black-forest-labs/FLUX.2-klein-9b-kv-fp8"
310
+ repository_file_path: "flux-2-klein-9b-kv-fp8.safetensors"
311
+ # FLUX.2-klein-4B
312
+ - filename: "flux-2-klein-4b-fp8.safetensors"
313
+ source: "hf"
314
+ repo_id: "black-forest-labs/FLUX.2-klein-4b-fp8"
315
+ repository_file_path: "flux-2-klein-4b-fp8.safetensors"
316
+ # FLUX.2-klein-base-4B
317
+ - filename: "flux-2-klein-base-4b-fp8.safetensors"
318
+ source: "hf"
319
+ repo_id: "black-forest-labs/FLUX.2-klein-base-4b-fp8"
320
+ repository_file_path: "flux-2-klein-base-4b-fp8.safetensors"
321
+ # FLUX.2-klein-9B
322
+ - filename: "flux-2-klein-9b-fp8.safetensors"
323
+ source: "hf"
324
+ repo_id: "black-forest-labs/FLUX.2-klein-9b-fp8"
325
+ repository_file_path: "flux-2-klein-9b-fp8.safetensors"
326
+ # FLUX.2-klein-base-9B
327
+ - filename: "flux-2-klein-base-9b-fp8.safetensors"
328
+ source: "hf"
329
+ repo_id: "black-forest-labs/FLUX.2-klein-base-9b-fp8"
330
+ repository_file_path: "flux-2-klein-base-9b-fp8.safetensors"
331
+ # FLUX.2-dev
332
  - filename: "flux2_dev_fp8mixed.safetensors"
333
  source: "hf"
334
  repo_id: "Comfy-Org/flux2-dev"
335
  repository_file_path: "split_files/diffusion_models/flux2_dev_fp8mixed.safetensors"
336
+ # LongCat-Image
337
+ - filename: "longcat_image_bf16.safetensors"
338
+ source: "hf"
339
+ repo_id: "Comfy-Org/LongCat-Image"
340
+ repository_file_path: "split_files/diffusion_models/longcat_image_bf16.safetensors"
341
+ # Ovis-Image
342
+ - filename: "ovis_image_bf16.safetensors"
343
+ source: "hf"
344
+ repo_id: "Comfy-Org/Ovis-Image"
345
+ repository_file_path: "split_files/diffusion_models/ovis_image_bf16.safetensors"
346
+ # Z-Image
347
+ - filename: "z_image_turbo_bf16.safetensors"
348
+ source: "hf"
349
+ repo_id: "Comfy-Org/z_image_turbo"
350
+ repository_file_path: "split_files/diffusion_models/z_image_turbo_bf16.safetensors"
351
+ - filename: "z_image_bf16.safetensors"
352
+ source: "hf"
353
+ repo_id: "Comfy-Org/z_image"
354
+ repository_file_path: "split_files/diffusion_models/z_image_bf16.safetensors"
355
+ # Qwen-Image
356
+ - filename: "qwen_image_2512_fp8_e4m3fn.safetensors"
357
+ source: "hf"
358
+ repo_id: "Comfy-Org/Qwen-Image_ComfyUI"
359
+ repository_file_path: "split_files/diffusion_models/qwen_image_2512_fp8_e4m3fn.safetensors"
360
+ - filename: "qwen_image_fp8_e4m3fn.safetensors"
361
+ source: "hf"
362
+ repo_id: "Comfy-Org/Qwen-Image_ComfyUI"
363
+ repository_file_path: "split_files/diffusion_models/qwen_image_fp8_e4m3fn.safetensors"
364
+ # Flux.1
365
+ - filename: "flux1-dev-fp8-e4m3fn.safetensors"
366
+ source: "hf"
367
+ repo_id: "Kijai/flux-fp8"
368
+ repository_file_path: "flux1-dev-fp8-e4m3fn.safetensors"
369
+ - filename: "flux1-schnell-fp8-e4m3fn.safetensors"
370
+ source: "hf"
371
+ repo_id: "Kijai/flux-fp8"
372
+ repository_file_path: "flux1-schnell-fp8-e4m3fn.safetensors"
373
+ - filename: "flux1-dev-kontext_fp8_scaled.safetensors"
374
+ source: "hf"
375
+ repo_id: "Comfy-Org/flux1-kontext-dev_ComfyUI"
376
+ repository_file_path: "split_files/diffusion_models/flux1-dev-kontext_fp8_scaled.safetensors"
377
+ - filename: "flux1-krea-dev_fp8_scaled.safetensors"
378
+ source: "hf"
379
+ repo_id: "Comfy-Org/FLUX.1-Krea-dev_ComfyUI"
380
+ repository_file_path: "split_files/diffusion_models/flux1-krea-dev_fp8_scaled.safetensors"
381
+ # HiDream
382
+ - filename: "hidream_i1_dev_fp8.safetensors"
383
+ source: "hf"
384
+ repo_id: "Comfy-Org/HiDream-I1_ComfyUI"
385
+ repository_file_path: "split_files/diffusion_models/hidream_i1_dev_fp8.safetensors"
386
+ - filename: "hidream_i1_fast_fp8.safetensors"
387
+ source: "hf"
388
+ repo_id: "Comfy-Org/HiDream-I1_ComfyUI"
389
+ repository_file_path: "split_files/diffusion_models/hidream_i1_fast_fp8.safetensors"
390
+ - filename: "hidream_i1_full_fp8.safetensors"
391
+ source: "hf"
392
+ repo_id: "Comfy-Org/HiDream-I1_ComfyUI"
393
+ repository_file_path: "split_files/diffusion_models/hidream_i1_full_fp8.safetensors"
394
+ - filename: "hunyuanimage2.1_fp8_e4m3fn.safetensors"
395
+ source: "hf"
396
+ repo_id: "Comfy-Org/HunyuanImage_2.1_ComfyUI"
397
+ repository_file_path: "split_files/diffusion_models/hunyuanimage2.1_fp8_e4m3fn.safetensors"
398
+ - filename: "hunyuanimage2.1_distilled_fp8_e4m3fn.safetensors"
399
+ source: "hf"
400
+ repo_id: "Comfy-Org/HunyuanImage_2.1_ComfyUI"
401
+ repository_file_path: "split_files/diffusion_models/hunyuanimage2.1_distilled_fp8_e4m3fn.safetensors"
402
+ - filename: "hunyuanimage2.1_refiner_fp8_e4m3fn.safetensors"
403
+ source: "hf"
404
+ repo_id: "Comfy-Org/HunyuanImage_2.1_ComfyUI"
405
+ repository_file_path: "split_files/diffusion_models/hunyuanimage2.1_refiner_fp8_e4m3fn.safetensors"
406
+ # Chroma1-Radiance
407
+ - filename: "Chroma1-Radiance-x0-fp8mixed_fullmm-20260104.safetensors"
408
+ source: "hf"
409
+ repo_id: "silveroxides/Chroma1-Radiance-fp8-scaled"
410
+ repository_file_path: "Chroma1-Radiance-x0-fp8mixed_fullmm-20260104.safetensors"
411
+ # Chroma1
412
+ - filename: "Chroma1-HD-Flash_float8_e4m3fn_scaled_learned_topk8_svd.safetensors"
413
+ source: "hf"
414
+ repo_id: "Clybius/Chroma-fp8-scaled"
415
+ repository_file_path: "Chroma1-HD/Chroma1-HD-Flash_float8_e4m3fn_scaled_learned_topk8_svd.safetensors"
416
+ - filename: "Chroma1-HD_float8_e4m3fn_scaled_learned_topk8_svd.safetensors"
417
+ source: "hf"
418
+ repo_id: "Clybius/Chroma-fp8-scaled"
419
+ repository_file_path: "Chroma1-HD/Chroma1-HD_float8_e4m3fn_scaled_learned_topk8_svd.safetensors"
420
+ - filename: "omnigen2_fp16.safetensors"
421
+ source: "hf"
422
+ repo_id: "Comfy-Org/Omnigen2_ComfyUI_repackaged"
423
+ repository_file_path: "split_files/diffusion_models/omnigen2_fp16.safetensors"
424
+ ipadapter:
425
+ # SD3.5
426
+ - filename: "ip-adapter_sd35l_instantx.bin"
427
+ source: "hf"
428
+ repo_id: "InstantX/SD3.5-Large-IP-Adapter"
429
+ repository_file_path: "ip-adapter.bin"
430
+ # SD1.5
431
+ - filename: "ip-adapter_sd15.safetensors"
432
+ source: "hf"
433
+ repo_id: "h94/IP-Adapter"
434
+ repository_file_path: "models/ip-adapter_sd15.safetensors"
435
+ - filename: "ip-adapter_sd15_light_v11.bin"
436
+ source: "hf"
437
+ repo_id: "h94/IP-Adapter"
438
+ repository_file_path: "models/ip-adapter_sd15_light_v11.bin"
439
+ - filename: "ip-adapter-plus_sd15.safetensors"
440
+ source: "hf"
441
+ repo_id: "h94/IP-Adapter"
442
+ repository_file_path: "models/ip-adapter-plus_sd15.safetensors"
443
+ - filename: "ip-adapter-plus-face_sd15.safetensors"
444
+ source: "hf"
445
+ repo_id: "h94/IP-Adapter"
446
+ repository_file_path: "models/ip-adapter-plus-face_sd15.safetensors"
447
+ - filename: "ip-adapter-full-face_sd15.safetensors"
448
+ source: "hf"
449
+ repo_id: "h94/IP-Adapter"
450
+ repository_file_path: "models/ip-adapter-full-face_sd15.safetensors"
451
+ - filename: "ip-adapter_sd15_vit-G.safetensors"
452
+ source: "hf"
453
+ repo_id: "h94/IP-Adapter"
454
+ repository_file_path: "models/ip-adapter_sd15_vit-G.safetensors"
455
+ # SDXL
456
+ - filename: "ip-adapter_sdxl_vit-h.safetensors"
457
+ source: "hf"
458
+ repo_id: "h94/IP-Adapter"
459
+ repository_file_path: "sdxl_models/ip-adapter_sdxl_vit-h.safetensors"
460
+ - filename: "ip-adapter-plus_sdxl_vit-h.safetensors"
461
+ source: "hf"
462
+ repo_id: "h94/IP-Adapter"
463
+ repository_file_path: "sdxl_models/ip-adapter-plus_sdxl_vit-h.safetensors"
464
+ - filename: "ip-adapter-plus-face_sdxl_vit-h.safetensors"
465
+ source: "hf"
466
+ repo_id: "h94/IP-Adapter"
467
+ repository_file_path: "sdxl_models/ip-adapter-plus-face_sdxl_vit-h.safetensors"
468
+ - filename: "ip-adapter_sdxl.safetensors"
469
+ source: "hf"
470
+ repo_id: "h94/IP-Adapter"
471
+ repository_file_path: "sdxl_models/ip-adapter_sdxl.safetensors"
472
+ - filename: "ip-adapter-faceid_sd15.bin"
473
+ source: "hf"
474
+ repo_id: "h94/IP-Adapter-FaceID"
475
+ repository_file_path: "ip-adapter-faceid_sd15.bin"
476
+ - filename: "ip-adapter-faceid-plusv2_sd15.bin"
477
+ source: "hf"
478
+ repo_id: "h94/IP-Adapter-FaceID"
479
+ repository_file_path: "ip-adapter-faceid-plusv2_sd15.bin"
480
+ - filename: "ip-adapter-faceid-portrait-v11_sd15.bin"
481
+ source: "hf"
482
+ repo_id: "h94/IP-Adapter-FaceID"
483
+ repository_file_path: "ip-adapter-faceid-portrait-v11_sd15.bin"
484
+ - filename: "ip-adapter-faceid_sdxl.bin"
485
+ source: "hf"
486
+ repo_id: "h94/IP-Adapter-FaceID"
487
+ repository_file_path: "ip-adapter-faceid_sdxl.bin"
488
+ - filename: "ip-adapter-faceid-plusv2_sdxl.bin"
489
+ source: "hf"
490
+ repo_id: "h94/IP-Adapter-FaceID"
491
+ repository_file_path: "ip-adapter-faceid-plusv2_sdxl.bin"
492
+ - filename: "ip-adapter-faceid-portrait_sdxl.bin"
493
+ source: "hf"
494
+ repo_id: "h94/IP-Adapter-FaceID"
495
+ repository_file_path: "ip-adapter-faceid-portrait_sdxl.bin"
496
+ - filename: "ip-adapter-faceid-portrait_sdxl_unnorm.bin"
497
+ source: "hf"
498
+ repo_id: "h94/IP-Adapter-FaceID"
499
+ repository_file_path: "ip-adapter-faceid-portrait_sdxl_unnorm.bin"
500
+ ipadapter-flux:
501
+ - filename: "ip-adapter.bin"
502
+ source: "hf"
503
+ repo_id: "InstantX/FLUX.1-dev-IP-Adapter"
504
+ repository_file_path: "ip-adapter.bin"
505
+ style_models:
506
+ # FLUX.1-Redux-dev
507
+ - filename: "flux1-redux-dev.safetensors"
508
+ source: "hf"
509
+ repo_id: "black-forest-labs/FLUX.1-Redux-dev"
510
+ repository_file_path: "flux1-redux-dev.safetensors"
511
+ loras:
512
+ # Qwen-Image
513
+ - filename: "Qwen-Image-2512-Lightning-4steps-V1.0-bf16.safetensors"
514
+ source: "hf"
515
+ repo_id: "lightx2v/Qwen-Image-2512-Lightning"
516
+ repository_file_path: "Qwen-Image-2512-Lightning-4steps-V1.0-bf16.safetensors"
517
+ - filename: "Qwen-Image-fp8-e4m3fn-Lightning-4steps-V1.0-bf16.safetensors"
518
+ source: "hf"
519
+ repo_id: "lightx2v/Qwen-Image-Lightning"
520
+ repository_file_path: "Qwen-Image-fp8-e4m3fn-Lightning-4steps-V1.0-bf16.safetensors"
521
+ # SD1.5 FaceID
522
+ - filename: "ip-adapter-faceid_sd15_lora.safetensors"
523
+ source: "hf"
524
+ repo_id: "h94/IP-Adapter-FaceID"
525
+ repository_file_path: "ip-adapter-faceid_sd15_lora.safetensors"
526
+ - filename: "ip-adapter-faceid-plusv2_sd15_lora.safetensors"
527
+ source: "hf"
528
+ repo_id: "h94/IP-Adapter-FaceID"
529
+ repository_file_path: "ip-adapter-faceid-plusv2_sd15_lora.safetensors"
530
+ - filename: "ip-adapter-faceid_sdxl_lora.safetensors"
531
+ source: "hf"
532
+ repo_id: "h94/IP-Adapter-FaceID"
533
+ repository_file_path: "ip-adapter-faceid_sdxl_lora.safetensors"
534
+ - filename: "ip-adapter-faceid-plusv2_sdxl_lora.safetensors"
535
+ source: "hf"
536
+ repo_id: "h94/IP-Adapter-FaceID"
537
+ repository_file_path: "ip-adapter-faceid-plusv2_sdxl_lora.safetensors"
538
+ model_patches:
539
+ # Z-Image
540
+ - filename: "Z-Image-Turbo-Fun-Controlnet-Union-2.1-8steps.safetensors"
541
+ source: "hf"
542
+ repo_id: "alibaba-pai/Z-Image-Turbo-Fun-Controlnet-Union-2.1"
543
+ repository_file_path: "Z-Image-Turbo-Fun-Controlnet-Union-2.1-8steps.safetensors"
544
+ - filename: "Z-Image-Turbo-Fun-Controlnet-Tile-2.1-8steps.safetensors"
545
+ source: "hf"
546
+ repo_id: "alibaba-pai/Z-Image-Turbo-Fun-Controlnet-Union-2.1"
547
+ repository_file_path: "Z-Image-Turbo-Fun-Controlnet-Tile-2.1-8steps.safetensors"
548
+ text_encoders:
549
+ # Anima
550
+ - filename: "qwen_3_06b_base.safetensors"
551
+ source: "hf"
552
+ repo_id: "circlestone-labs/Anima"
553
+ repository_file_path: "split_files/text_encoders/qwen_3_06b_base.safetensors"
554
+ # NewBie-Image
555
+ - filename: "gemma_3_4b_it_bf16.safetensors"
556
+ source: "hf"
557
+ repo_id: "Comfy-Org/NewBie-image-Exp0.1_repackaged"
558
+ repository_file_path: "split_files/text_encoders/gemma_3_4b_it_bf16.safetensors"
559
+ - filename: "jina_clip_v2_bf16.safetensors"
560
+ source: "hf"
561
+ repo_id: "Comfy-Org/NewBie-image-Exp0.1_repackaged"
562
+ repository_file_path: "split_files/text_encoders/jina_clip_v2_bf16.safetensors"
563
+ # ERNIE-Image
564
+ - filename: "ministral-3-3b.safetensors"
565
+ source: "hf"
566
+ repo_id: "Comfy-Org/ERNIE-Image"
567
+ repository_file_path: "text_encoders/ministral-3-3b.safetensors"
568
+ # FLUX.2-klein-4B & base
569
+ - filename: "qwen_3_4b.safetensors"
570
+ source: "hf"
571
+ repo_id: "Comfy-Org/vae-text-encorder-for-flux-klein-4b"
572
+ repository_file_path: "split_files/text_encoders/qwen_3_4b.safetensors"
573
+ # FLUX.2-klein-9B & base
574
+ - filename: "qwen_3_8b_fp8mixed.safetensors"
575
+ source: "hf"
576
+ repo_id: "Comfy-Org/vae-text-encorder-for-flux-klein-9b"
577
+ repository_file_path: "split_files/text_encoders/qwen_3_8b_fp8mixed.safetensors"
578
+ # FLUX.2-dev
579
  - filename: "mistral_3_small_flux2_fp8.safetensors"
580
  source: "hf"
581
  repo_id: "Comfy-Org/flux2-dev"
582
  repository_file_path: "split_files/text_encoders/mistral_3_small_flux2_fp8.safetensors"
583
+ # Ovis-Image
584
+ - filename: "ovis_2.5.safetensors"
585
+ source: "hf"
586
+ repo_id: "Comfy-Org/Ovis-Image"
587
+ repository_file_path: "split_files/text_encoders/ovis_2.5.safetensors"
588
+ # Z-Image
589
+ - filename: "qwen_3_4b_fp8_mixed.safetensors"
590
+ source: "hf"
591
+ repo_id: "Comfy-Org/z_image_turbo"
592
+ repository_file_path: "split_files/text_encoders/qwen_3_4b_fp8_mixed.safetensors"
593
+ - filename: "clip_l.safetensors"
594
+ source: "hf"
595
+ repo_id: "comfyanonymous/flux_text_encoders"
596
+ repository_file_path: "clip_l.safetensors"
597
+ - filename: "t5xxl_fp8_e4m3fn_scaled.safetensors"
598
+ source: "hf"
599
+ repo_id: "comfyanonymous/flux_text_encoders"
600
+ repository_file_path: "t5xxl_fp8_e4m3fn_scaled.safetensors"
601
+ - filename: "clip_l_hidream.safetensors"
602
+ source: "hf"
603
+ repo_id: "Comfy-Org/HiDream-I1_ComfyUI"
604
+ repository_file_path: "split_files/text_encoders/clip_l_hidream.safetensors"
605
+ - filename: "clip_g_hidream.safetensors"
606
+ source: "hf"
607
+ repo_id: "Comfy-Org/HiDream-I1_ComfyUI"
608
+ repository_file_path: "split_files/text_encoders/clip_g_hidream.safetensors"
609
+ - filename: "llama_3.1_8b_instruct_fp8_scaled.safetensors"
610
+ source: "hf"
611
+ repo_id: "Comfy-Org/HiDream-I1_ComfyUI"
612
+ repository_file_path: "split_files/text_encoders/llama_3.1_8b_instruct_fp8_scaled.safetensors"
613
+ - filename: "qwen_2.5_vl_7b_fp8_scaled.safetensors"
614
+ source: "hf"
615
+ repo_id: "Comfy-Org/Qwen-Image_ComfyUI"
616
+ repository_file_path: "split_files/text_encoders/qwen_2.5_vl_7b_fp8_scaled.safetensors"
617
+ - filename: "byt5_small_glyphxl_fp16.safetensors"
618
+ source: "hf"
619
+ repo_id: "Comfy-Org/HunyuanImage_2.1_ComfyUI"
620
+ repository_file_path: "split_files/text_encoders/byt5_small_glyphxl_fp16.safetensors"
621
+ - filename: "qwen_2.5_vl_fp16.safetensors"
622
+ source: "hf"
623
+ repo_id: "Comfy-Org/Omnigen2_ComfyUI_repackaged"
624
+ repository_file_path: "split_files/text_encoders/qwen_2.5_vl_fp16.safetensors"
625
+ vae:
626
+ - filename: "qwen_image_vae.safetensors"
627
+ source: "hf"
628
+ repo_id: "Comfy-Org/Qwen-Image_ComfyUI"
629
+ repository_file_path: "split_files/vae/qwen_image_vae.safetensors"
630
+ - filename: "hunyuan_image_2.1_vae_fp16.safetensors"
631
+ source: "hf"
632
+ repo_id: "Comfy-Org/HunyuanImage_2.1_ComfyUI"
633
+ repository_file_path: "split_files/vae/hunyuan_image_2.1_vae_fp16.safetensors"
634
+ - filename: "hunyuan_image_refiner_vae_fp16.safetensors"
635
+ source: "hf"
636
+ repo_id: "Comfy-Org/HunyuanImage_2.1_ComfyUI"
637
+ repository_file_path: "split_files/vae/hunyuan_image_refiner_vae_fp16.safetensors"
638
+ - filename: "ae.safetensors"
639
+ source: "hf"
640
+ repo_id: "Comfy-Org/Lumina_Image_2.0_Repackaged"
641
+ repository_file_path: "split_files/vae/ae.safetensors"
642
  - filename: "flux2-vae.safetensors"
643
  source: "hf"
644
  repo_id: "Comfy-Org/flux2-dev"
645
+ repository_file_path: "split_files/vae/flux2-vae.safetensors"
yaml/image_gen_features.yaml ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default:
2
+ enabled_chains:
3
+ - lora
4
+ - controlnet
5
+ - ipadapter
6
+ - embedding
7
+ - style
8
+ - conditioning
9
+
10
+ ernie-image:
11
+ enabled_chains:
12
+ - lora
13
+ - conditioning
14
+
15
+ flux2:
16
+ enabled_chains:
17
+ - lora
18
+ - conditioning
19
+ - reference_latent
20
+
21
+ flux2-kv:
22
+ enabled_chains:
23
+ - lora
24
+ - conditioning
25
+ - reference_latent
26
+
27
+ z-image:
28
+ enabled_chains:
29
+ - lora
30
+ - conditioning
31
+ - controlnet_model_patch
32
+
33
+ qwen-image:
34
+ enabled_chains:
35
+ - lora
36
+ - controlnet
37
+ - conditioning
38
+
39
+ longcat-image:
40
+ enabled_chains:
41
+ - lora
42
+ - conditioning
43
+
44
+ anima:
45
+ enabled_chains:
46
+ - lora
47
+ - conditioning
48
+
49
+ newbie-image:
50
+ enabled_chains:
51
+ - lora
52
+ - embedding
53
+ - conditioning
54
+
55
+ omnigen2:
56
+ enabled_chains:
57
+ - conditioning
58
+ - reference_latent
59
+
60
+ lumina:
61
+ enabled_chains:
62
+ - lora
63
+ - embedding
64
+ - conditioning
65
+
66
+ ovis-image:
67
+ enabled_chains:
68
+ - conditioning
69
+
70
+ sd35:
71
+ enabled_chains:
72
+ - lora
73
+ - controlnet
74
+ - embedding
75
+ - conditioning
76
+ - sd3_ipadapter
77
+
78
+ sdxl:
79
+ enabled_chains:
80
+ - lora
81
+ - controlnet
82
+ - ipadapter
83
+ - embedding
84
+ - conditioning
85
+
86
+ sd15:
87
+ enabled_chains:
88
+ - lora
89
+ - controlnet
90
+ - ipadapter
91
+ - embedding
92
+ - conditioning
93
+
94
+ flux1:
95
+ enabled_chains:
96
+ - lora
97
+ - controlnet
98
+ - style
99
+ - conditioning
100
+ - flux1_ipadapter
101
+
102
+ hidream:
103
+ enabled_chains:
104
+ - lora
105
+ - conditioning
106
+
107
+ chroma1:
108
+ enabled_chains:
109
+ - conditioning
110
+
111
+ chroma1-radiance:
112
+ enabled_chains:
113
+ - conditioning
114
+
115
+ hunyuanimage:
116
+ enabled_chains:
117
+ - conditioning
yaml/injectors.yaml CHANGED
@@ -1,12 +1,36 @@
1
  injector_definitions:
2
- dynamic_lora_chains:
 
 
3
  module: "chain_injectors.lora_injector"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  dynamic_conditioning_chains:
5
  module: "chain_injectors.conditioning_injector"
6
  dynamic_reference_latent_chains:
7
  module: "chain_injectors.reference_latent_injector"
8
 
9
  injector_order:
 
10
  - dynamic_lora_chains
 
 
 
 
 
 
 
11
  - dynamic_reference_latent_chains
12
- - dynamic_conditioning_chains
 
1
  injector_definitions:
2
+ dynamic_vae_chains:
3
+ module: "chain_injectors.vae_injector"
4
+ dynamic_lora_chains:
5
  module: "chain_injectors.lora_injector"
6
+ dynamic_newbie_lora_chains:
7
+ module: "chain_injectors.newbie_lora_injector"
8
+ dynamic_controlnet_chains:
9
+ module: "chain_injectors.controlnet_injector"
10
+ dynamic_diffsynth_controlnet_chains:
11
+ module: "chain_injectors.diffsynth_controlnet_injector"
12
+ dynamic_ipadapter_chains:
13
+ module: "chain_injectors.ipadapter_injector"
14
+ dynamic_flux1_ipadapter_chains:
15
+ module: "chain_injectors.flux1_ipadapter_injector"
16
+ dynamic_sd3_ipadapter_chains:
17
+ module: "chain_injectors.sd3_ipadapter_injector"
18
+ dynamic_style_chains:
19
+ module: "chain_injectors.style_injector"
20
  dynamic_conditioning_chains:
21
  module: "chain_injectors.conditioning_injector"
22
  dynamic_reference_latent_chains:
23
  module: "chain_injectors.reference_latent_injector"
24
 
25
  injector_order:
26
+ - dynamic_vae_chains
27
  - dynamic_lora_chains
28
+ - dynamic_newbie_lora_chains
29
+ - dynamic_diffsynth_controlnet_chains
30
+ - dynamic_ipadapter_chains
31
+ - dynamic_flux1_ipadapter_chains
32
+ - dynamic_sd3_ipadapter_chains
33
+ - dynamic_style_chains
34
+ - dynamic_conditioning_chains
35
  - dynamic_reference_latent_chains
36
+ - dynamic_controlnet_chains
yaml/model_architectures.yaml CHANGED
@@ -1,15 +1,79 @@
1
  architecture_order:
 
2
  - "FLUX.2"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  - "SDXL"
4
  - "SD1.5"
5
 
6
  architectures:
 
 
 
 
 
 
7
  "FLUX.2":
8
  model_type: "flux2"
9
  controlnet_key: "FLUX.2"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  "SDXL":
11
  model_type: "sdxl"
12
  controlnet_key: "SDXL"
 
 
 
 
 
 
13
  "SD1.5":
14
  model_type: "sd15"
15
- controlnet_key: "SD1.5"
 
1
  architecture_order:
2
+ - "FLUX.2-KV"
3
  - "FLUX.2"
4
+ - "ERNIE-Image"
5
+ - "Z-Image"
6
+ - "Qwen-Image"
7
+ - "LongCat-Image"
8
+ - "Anima"
9
+ - "NewBie-Image"
10
+ - "Ovis-Image"
11
+ - "HunyuanImage"
12
+ - "Chroma1-Radiance"
13
+ - "Chroma1"
14
+ - "OmniGen2"
15
+ - "Lumina"
16
+ - "HiDream"
17
+ - "FLUX.1"
18
+ - "SD3.5"
19
  - "SDXL"
20
  - "SD1.5"
21
 
22
  architectures:
23
+ "ERNIE-Image":
24
+ model_type: "ernie-image"
25
+ controlnet_key: "ERNIE-Image"
26
+ "FLUX.2-KV":
27
+ model_type: "flux2-kv"
28
+ controlnet_key: "FLUX.2"
29
  "FLUX.2":
30
  model_type: "flux2"
31
  controlnet_key: "FLUX.2"
32
+ "Z-Image":
33
+ model_type: "z-image"
34
+ controlnet_key: "Z-Image"
35
+ "Qwen-Image":
36
+ model_type: "qwen-image"
37
+ controlnet_key: "Qwen-Image"
38
+ "LongCat-Image":
39
+ model_type: "longcat-image"
40
+ controlnet_key: "LongCat-Image"
41
+ "Anima":
42
+ model_type: "anima"
43
+ controlnet_key: "Anima"
44
+ "Chroma1-Radiance":
45
+ model_type: "chroma1-radiance"
46
+ controlnet_key: "Chroma1-Radiance"
47
+ "Chroma1":
48
+ model_type: "chroma1"
49
+ controlnet_key: "Chroma1"
50
+ "OmniGen2":
51
+ model_type: "omnigen2"
52
+ controlnet_key: "OmniGen2"
53
+ "Lumina":
54
+ model_type: "lumina"
55
+ controlnet_key: "Lumina"
56
+ "Ovis-Image":
57
+ model_type: "ovis-image"
58
+ controlnet_key: "Ovis-Image"
59
+ "HunyuanImage":
60
+ model_type: "hunyuanimage"
61
+ controlnet_key: "HunyuanImage"
62
+ "NewBie-Image":
63
+ model_type: "newbie-image"
64
+ controlnet_key: "NewBie-Image"
65
+ "FLUX.1":
66
+ model_type: "flux1"
67
+ controlnet_key: "FLUX.1"
68
  "SDXL":
69
  model_type: "sdxl"
70
  controlnet_key: "SDXL"
71
+ "SD3.5":
72
+ model_type: "sd35"
73
+ controlnet_key: "SD3.5"
74
+ "HiDream":
75
+ model_type: "hidream"
76
+ controlnet_key: "HiDream"
77
  "SD1.5":
78
  model_type: "sd15"
79
+ controlnet_key: "SD1.5"
yaml/model_defaults.yaml CHANGED
@@ -1,22 +1,206 @@
1
- defaults:
2
- FLUX.2:
3
- width: 1024
4
- height: 1024
5
- num_inference_steps: 28
6
- guidance_scale: 3.5
7
- sampler: "Euler"
8
- schedule_type: "Default"
9
- SDXL:
10
- width: 1024
11
- height: 1024
12
- num_inference_steps: 28
13
- guidance_scale: 7.0
14
- sampler: "Euler a"
15
- schedule_type: "Default"
16
- SD1.5:
17
- width: 512
18
- height: 512
19
- num_inference_steps: 20
20
- guidance_scale: 7.0
21
- sampler: "Euler a"
22
- schedule_type: "Default"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Default:
2
+ steps: 25
3
+ cfg: 7.0
4
+ sampler_name: "euler"
5
+ scheduler: "simple"
6
+ total_pixels: 1048576
7
+ positive_prompt: ""
8
+ negative_prompt: ""
9
+
10
+ ERNIE-Image:
11
+ _defaults:
12
+ steps: 20
13
+ cfg: 4.0
14
+ sampler_name: "euler"
15
+ scheduler: "simple"
16
+ total_pixels: 1048576
17
+ "baidu/ERNIE-Image-Turbo":
18
+ steps: 8
19
+ cfg: 1.0
20
+ sampler_name: "euler"
21
+ scheduler: "simple"
22
+ total_pixels: 1048576
23
+
24
+ FLUX.2:
25
+ _defaults:
26
+ steps: 20
27
+ cfg: 4.0
28
+ sampler_name: "euler"
29
+ scheduler: "simple"
30
+ total_pixels: 1048576
31
+ "black-forest-labs/FLUX.2-klein-4B":
32
+ steps: 4
33
+ cfg: 1.0
34
+ "black-forest-labs/FLUX.2-klein-9B":
35
+ steps: 4
36
+ cfg: 1.0
37
+
38
+ FLUX.2-KV:
39
+ _defaults:
40
+ steps: 20
41
+ cfg: 4.0
42
+ sampler_name: "euler"
43
+ scheduler: "simple"
44
+ total_pixels: 1048576
45
+ "black-forest-labs/FLUX.2-klein-9B-KV":
46
+ steps: 4
47
+ cfg: 1.0
48
+
49
+ Z-Image:
50
+ _defaults:
51
+ steps: 25
52
+ cfg: 4.0
53
+ sampler_name: "euler"
54
+ scheduler: "simple"
55
+ total_pixels: 1048576
56
+ "Tongyi-MAI/Z Image Turbo":
57
+ steps: 9
58
+ cfg: 1.0
59
+ sampler_name: "euler"
60
+ scheduler: "simple"
61
+ total_pixels: 1048576
62
+
63
+ Qwen-Image:
64
+ _defaults:
65
+ steps: 4
66
+ cfg: 1.0
67
+ sampler_name: "euler"
68
+ scheduler: "simple"
69
+ total_pixels: 1763584
70
+
71
+ LongCat-Image:
72
+ _defaults:
73
+ steps: 20
74
+ cfg: 4.0
75
+ guidance: 4.0
76
+ sampler_name: "euler"
77
+ scheduler: "simple"
78
+ total_pixels: 1048576
79
+
80
+ Anima:
81
+ _defaults:
82
+ steps: 30
83
+ cfg: 4.0
84
+ sampler_name: "er_sde"
85
+ scheduler: "simple"
86
+ total_pixels: 1048576
87
+ positive_prompt: "masterpiece, best quality, score_7, safe. "
88
+ negative_prompt: "worst quality, low quality, score_1, score_2, score_3, blurry, jpeg artifacts, sepia"
89
+
90
+ NewBie-Image:
91
+ _defaults:
92
+ steps: 20
93
+ cfg: 5.5
94
+ sampler_name: "res_multistep"
95
+ scheduler: "simple"
96
+ positive_prompt: "You are an assistant designed to generate high-quality anime images with the highest degree of image-text alignment based on xml format textual prompts. <Prompt Start>"
97
+ negative_prompt: "You are an assistant designed to generate low-quality images based on textual prompts. <Prompt Start>"
98
+
99
+ Ovis-Image:
100
+ _defaults:
101
+ steps: 20
102
+ cfg: 5.0
103
+ sampler_name: "euler"
104
+ scheduler: "simple"
105
+ total_pixels: 1048576
106
+
107
+ OmniGen2:
108
+ _defaults:
109
+ steps: 20
110
+ cfg: 5.0
111
+ sampler_name: "euler"
112
+ scheduler: "simple"
113
+ total_pixels: 1048576
114
+ positive_prompt: ""
115
+ negative_prompt: ""
116
+
117
+ Chroma1:
118
+ _defaults:
119
+ steps: 30
120
+ cfg: 4.0
121
+ sampler_name: "euler"
122
+ scheduler: "simple"
123
+ total_pixels: 1048576
124
+ negative_prompt: "low quality, bad anatomy, extra digits, missing digits, extra limbs, missing limbs"
125
+ "lodestones/Chroma1-HD-Flash":
126
+ steps: 8
127
+ cfg: 1.0
128
+ scheduler: "beta"
129
+
130
+ Chroma1-Radiance:
131
+ _defaults:
132
+ steps: 30
133
+ cfg: 4.0
134
+ sampler_name: "euler"
135
+ scheduler: "simple"
136
+ total_pixels: 1048576
137
+ negative_prompt: "low quality, bad anatomy, extra digits, missing digits, extra limbs, missing limbs, hands, fingers"
138
+
139
+ SD3.5:
140
+ _defaults:
141
+ steps: 20
142
+ cfg: 4.0
143
+ sampler_name: "euler"
144
+ scheduler: "sgm_uniform"
145
+ total_pixels: 1048576
146
+
147
+ SDXL:
148
+ _defaults:
149
+ steps: 25
150
+ cfg: 7.0
151
+ sampler_name: "euler"
152
+ scheduler: "simple"
153
+ total_pixels: 1048576
154
+ positive_prompt: ""
155
+ negative_prompt: ""
156
+
157
+ SD1.5:
158
+ _defaults:
159
+ steps: 47
160
+ cfg: 7.0
161
+ sampler_name: "euler_ancestral"
162
+ scheduler: "simple"
163
+ total_pixels: 393216
164
+
165
+ FLUX.1:
166
+ _defaults:
167
+ steps: 20
168
+ cfg: 1.0
169
+ sampler_name: "euler"
170
+ scheduler: "simple"
171
+ total_pixels: 1048576
172
+ "flux1-schnell":
173
+ steps: 4
174
+ cfg: 1.0
175
+ sampler_name: "euler"
176
+ scheduler: "simple"
177
+
178
+ HiDream:
179
+ _defaults:
180
+ steps: 50
181
+ cfg: 3.0
182
+ sampler_name: "uni_pc"
183
+ scheduler: "simple"
184
+ total_pixels: 1048576
185
+ negative_prompt: "bad ugly jpeg artifacts"
186
+ "HiDream_i1_Dev":
187
+ steps: 28
188
+ cfg: 1.0
189
+ sampler_name: "lcm"
190
+ scheduler: "normal"
191
+ "HiDream_i1_Fast":
192
+ steps: 16
193
+ cfg: 1.0
194
+ sampler_name: "lcm"
195
+ scheduler: "normal"
196
+
197
+ HunyuanImage:
198
+ _defaults:
199
+ steps: 20
200
+ cfg: 3.5
201
+ sampler_name: "euler"
202
+ scheduler: "simple"
203
+ total_pixels: 4194304
204
+ "HunyuanImage-2.1-Distilled":
205
+ steps: 8
206
+ cfg: 1.0
yaml/model_list.yaml CHANGED
@@ -1,20 +1,9 @@
1
  Checkpoint:
2
- FLUX.2:
3
  latent_type: flux2_latent
4
  models:
5
- - display_name: "Comfy-Org/flux2-dev"
6
  components:
7
- unet: "flux2_dev_fp8mixed.safetensors"
8
- clip: "mistral_3_small_flux2_fp8.safetensors"
9
- vae: "flux2-vae.safetensors"
10
- SDXL:
11
- latent_type: latent
12
- models:
13
- - display_name: "stabilityai/SDXL-Base-1.0"
14
- path: "sd_xl_base_1.0.safetensors"
15
- category: "Base"
16
- SD1.5:
17
- latent_type: latent
18
- models:
19
- - display_name: "stable-diffusion-v1-5/stable-diffusion-v1-5"
20
- path: "v1-5-pruned-emaonly.safetensors"
 
1
  Checkpoint:
2
+ FLUX.2-KV:
3
  latent_type: flux2_latent
4
  models:
5
+ - display_name: "black-forest-labs/FLUX.2-klein-9B-KV"
6
  components:
7
+ unet: "flux-2-klein-9b-kv-fp8.safetensors"
8
+ clip: "qwen_3_8b_fp8mixed.safetensors"
9
+ vae: "flux2-vae.safetensors"
 
 
 
 
 
 
 
 
 
 
 
yaml/private_file_list.yaml DELETED
@@ -1,12 +0,0 @@
1
- file:
2
- diffusion_models:
3
- # FLUX.2-klein-9B
4
- - filename: "flux-2-klein-9b-fp8.safetensors"
5
- source: "hf"
6
- repo_id: "black-forest-labs/FLUX.2-klein-9b-fp8"
7
- repository_file_path: "flux-2-klein-9b-fp8.safetensors"
8
- # FLUX.2-klein-base-9B
9
- - filename: "flux-2-klein-base-9b-fp8.safetensors"
10
- source: "hf"
11
- repo_id: "black-forest-labs/FLUX.2-klein-base-9b-fp8"
12
- repository_file_path: "flux-2-klein-base-9b-fp8.safetensors"