RioShiina commited on
Commit
79e946f
·
verified ·
1 Parent(s): f261f7a

Upload folder using huggingface_hub

Browse files
Files changed (40) hide show
  1. .gitattributes +35 -35
  2. README.md +2 -2
  3. app.py +6 -32
  4. chain_injectors/__init__.py +0 -0
  5. chain_injectors/conditioning_injector.py +0 -81
  6. chain_injectors/controlnet_injector.py +0 -57
  7. chain_injectors/lora_injector.py +0 -67
  8. comfy_integration/nodes.py +1 -24
  9. comfy_integration/setup.py +1 -15
  10. core/generation_logic.py +1 -9
  11. core/model_manager.py +0 -46
  12. core/pipelines/base_pipeline.py +2 -12
  13. core/pipelines/controlnet_preprocessor.py +0 -4
  14. core/pipelines/sd_image_pipeline.py +0 -423
  15. core/pipelines/workflow_recipes/_partials/_base_sampler.yaml +0 -23
  16. core/pipelines/workflow_recipes/_partials/conditioning/qwen-image.yaml +0 -80
  17. core/pipelines/workflow_recipes/_partials/input/hires_fix.yaml +0 -26
  18. core/pipelines/workflow_recipes/_partials/input/img2img.yaml +0 -19
  19. core/pipelines/workflow_recipes/_partials/input/inpaint.yaml +0 -25
  20. core/pipelines/workflow_recipes/_partials/input/outpaint.yaml +0 -38
  21. core/pipelines/workflow_recipes/_partials/input/txt2img.yaml +0 -8
  22. core/pipelines/workflow_recipes/sd_unified_recipe.yaml +0 -8
  23. core/settings.py +1 -104
  24. core/shared_state.py +0 -1
  25. core/workflow_assembler.py +0 -179
  26. requirements.txt +7 -6
  27. ui/events.py +9 -437
  28. ui/layout.py +28 -64
  29. ui/shared/hires_fix_ui.py +0 -73
  30. ui/shared/img2img_ui.py +0 -56
  31. ui/shared/inpaint_ui.py +0 -80
  32. ui/shared/outpaint_ui.py +0 -67
  33. ui/shared/txt2img_ui.py +0 -37
  34. ui/shared/ui_components.py +0 -249
  35. utils/app_utils.py +3 -348
  36. yaml/constants.yaml +0 -15
  37. yaml/controlnet_models.yaml +0 -5
  38. yaml/file_list.yaml +0 -38
  39. yaml/injectors.yaml +0 -12
  40. yaml/model_list.yaml +0 -13
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,10 +1,10 @@
1
  ---
2
- title: ImageGen - Qwen-Image
3
  emoji: 🖼
4
  colorFrom: purple
5
  colorTo: red
6
  sdk: gradio
7
  sdk_version: "5.50.0"
8
  app_file: app.py
9
- short_description: Multi-task image generator with dynamic, chainable workflows
10
  ---
 
1
  ---
2
+ title: ControlNet Preprocessors
3
  emoji: 🖼
4
  colorFrom: purple
5
  colorTo: red
6
  sdk: gradio
7
  sdk_version: "5.50.0"
8
  app_file: app.py
9
+ short_description: ControlNet Auxiliary Preprocessors
10
  ---
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__))
@@ -58,7 +57,6 @@ def main():
58
  except Exception as e:
59
  print(f"--- [Setup] ❌ SageAttention installation failed: {e}. Continuing with default attention. ---")
60
 
61
-
62
  print("--- [Setup] Reloading site-packages to detect newly installed packages... ---")
63
  try:
64
  site.main()
@@ -71,41 +69,17 @@ def main():
71
  build_preprocessor_model_map,
72
  build_preprocessor_parameter_map
73
  )
74
- from core import shared_state
75
- from core.settings import ALL_MODEL_MAP, ALL_FILE_DOWNLOAD_MAP
76
-
77
- def check_all_model_urls_on_startup():
78
- print("--- [Setup] Checking all model URL validity (one-time check) ---")
79
- for display_name, model_info in ALL_MODEL_MAP.items():
80
- _, components, _, _ = model_info
81
- if not components: continue
82
-
83
- for filename in components.values():
84
- download_info = ALL_FILE_DOWNLOAD_MAP.get(filename, {})
85
- repo_id = download_info.get('repo_id')
86
- if not repo_id: continue
87
-
88
- repo_file_path = download_info.get('repository_file_path', filename)
89
- url = f"https://huggingface.co/{repo_id}/resolve/main/{repo_file_path}"
90
-
91
- try:
92
- response = requests.head(url, timeout=5, allow_redirects=True)
93
- if response.status_code >= 400:
94
- print(f"❌ Invalid URL for '{display_name}' component '{filename}': {url} (Status: {response.status_code})")
95
- shared_state.INVALID_MODEL_URLS[display_name] = True
96
- break
97
- except requests.RequestException as e:
98
- print(f"❌ URL check failed for '{display_name}' component '{filename}': {e}")
99
- shared_state.INVALID_MODEL_URLS[display_name] = True
100
- break
101
- print("--- [Setup] ✅ Finished checking model URLs. ---")
102
 
103
  print("--- Starting Application Setup ---")
104
 
105
  setup_comfyui.initialize_comfyui()
106
 
107
- check_all_model_urls_on_startup()
108
-
 
 
 
 
109
  print("--- Building ControlNet preprocessor maps ---")
110
  from core.generation_logic import build_reverse_map
111
  build_reverse_map()
 
1
  import spaces
2
  import os
3
  import sys
 
4
  import site
5
 
6
  APP_DIR = os.path.dirname(os.path.abspath(__file__))
 
57
  except Exception as e:
58
  print(f"--- [Setup] ❌ SageAttention installation failed: {e}. Continuing with default attention. ---")
59
 
 
60
  print("--- [Setup] Reloading site-packages to detect newly installed packages... ---")
61
  try:
62
  site.main()
 
69
  build_preprocessor_model_map,
70
  build_preprocessor_parameter_map
71
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  print("--- Starting Application Setup ---")
74
 
75
  setup_comfyui.initialize_comfyui()
76
 
77
+ print("--- Initiating GPU Startup Check & SageAttention Patch ---")
78
+ try:
79
+ dummy_gpu_for_startup()
80
+ except Exception as e:
81
+ print(f"--- [GPU Startup] ⚠️ Warning: Startup check failed: {e} ---")
82
+
83
  print("--- Building ControlNet preprocessor maps ---")
84
  from core.generation_logic import build_reverse_map
85
  build_reverse_map()
chain_injectors/__init__.py DELETED
File without changes
chain_injectors/conditioning_injector.py DELETED
@@ -1,81 +0,0 @@
1
- def inject(assembler, chain_definition, chain_items):
2
- if not chain_items:
3
- return
4
-
5
- ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
6
-
7
- target_node_id = None
8
- target_input_name = None
9
-
10
- if ksampler_name in assembler.node_map:
11
- ksampler_id = assembler.node_map[ksampler_name]
12
- if 'positive' in assembler.workflow[ksampler_id]['inputs']:
13
- target_node_id = ksampler_id
14
- target_input_name = 'positive'
15
- print(f"Conditioning injector targeting KSampler node '{ksampler_name}'.")
16
- else:
17
- print(f"Warning: KSampler node '{ksampler_name}' for Conditioning chain not found. Skipping.")
18
- return
19
-
20
- if not target_node_id:
21
- print("Warning: Conditioning chain could not find a valid injection point (KSampler may be missing 'positive' input). Skipping.")
22
- return
23
-
24
- clip_source_str = chain_definition.get('clip_source')
25
- if not clip_source_str:
26
- print("Warning: 'clip_source' definition missing in the recipe for the Conditioning chain. Skipping.")
27
- return
28
- clip_node_name, clip_idx_str = clip_source_str.split(':')
29
- if clip_node_name not in assembler.node_map:
30
- print(f"Warning: CLIP source node '{clip_node_name}' for Conditioning chain not found. Skipping.")
31
- return
32
- clip_connection = [assembler.node_map[clip_node_name], int(clip_idx_str)]
33
-
34
- original_positive_connection = assembler.workflow[target_node_id]['inputs'][target_input_name]
35
-
36
- area_conditioning_outputs = []
37
-
38
- for item_data in chain_items:
39
- prompt = item_data.get('prompt', '')
40
- if not prompt or not prompt.strip():
41
- continue
42
-
43
- text_encode_id = assembler._get_unique_id()
44
- text_encode_node = assembler._get_node_template("CLIPTextEncode")
45
- text_encode_node['inputs']['text'] = prompt
46
- text_encode_node['inputs']['clip'] = clip_connection
47
- assembler.workflow[text_encode_id] = text_encode_node
48
-
49
- set_area_id = assembler._get_unique_id()
50
- set_area_node = assembler._get_node_template("ConditioningSetArea")
51
- set_area_node['inputs']['width'] = item_data.get('width', 1024)
52
- set_area_node['inputs']['height'] = item_data.get('height', 1024)
53
- set_area_node['inputs']['x'] = item_data.get('x', 0)
54
- set_area_node['inputs']['y'] = item_data.get('y', 0)
55
- set_area_node['inputs']['strength'] = item_data.get('strength', 1.0)
56
- set_area_node['inputs']['conditioning'] = [text_encode_id, 0]
57
- assembler.workflow[set_area_id] = set_area_node
58
-
59
- area_conditioning_outputs.append([set_area_id, 0])
60
-
61
- if not area_conditioning_outputs:
62
- return
63
-
64
- current_combined_conditioning = area_conditioning_outputs[0]
65
- if len(area_conditioning_outputs) > 1:
66
- for i in range(1, len(area_conditioning_outputs)):
67
- combine_id = assembler._get_unique_id()
68
- combine_node = assembler._get_node_template("ConditioningCombine")
69
- combine_node['inputs']['conditioning_1'] = current_combined_conditioning
70
- combine_node['inputs']['conditioning_2'] = area_conditioning_outputs[i]
71
- assembler.workflow[combine_id] = combine_node
72
- current_combined_conditioning = [combine_id, 0]
73
-
74
- final_combine_id = assembler._get_unique_id()
75
- final_combine_node = assembler._get_node_template("ConditioningCombine")
76
- final_combine_node['inputs']['conditioning_1'] = original_positive_connection
77
- final_combine_node['inputs']['conditioning_2'] = current_combined_conditioning
78
- assembler.workflow[final_combine_id] = final_combine_node
79
-
80
- assembler.workflow[target_node_id]['inputs'][target_input_name] = [final_combine_id, 0]
81
- print(f"Conditioning injector applied. Redirected '{target_input_name}' input with {len(area_conditioning_outputs)} regional prompts.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chain_injectors/controlnet_injector.py DELETED
@@ -1,57 +0,0 @@
1
- def inject(assembler, chain_definition, chain_items):
2
- if not chain_items:
3
- return
4
-
5
- ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
6
- if ksampler_name not in assembler.node_map:
7
- print(f"Warning: Target node '{ksampler_name}' for ControlNet chain not found. Skipping chain injection.")
8
- return
9
-
10
- ksampler_id = assembler.node_map[ksampler_name]
11
-
12
- if 'positive' not in assembler.workflow[ksampler_id]['inputs'] or \
13
- 'negative' not in assembler.workflow[ksampler_id]['inputs']:
14
- print(f"Warning: KSampler node '{ksampler_name}' is missing 'positive' or 'negative' inputs. Skipping ControlNet chain.")
15
- return
16
-
17
- vae_source_name = chain_definition.get('vae_source_node')
18
- if not vae_source_name or vae_source_name not in assembler.node_map:
19
- print(f"Warning: VAE source node '{vae_source_name}' for ControlNet chain not found in recipe. Skipping chain injection.")
20
- return
21
- vae_connection = [assembler.node_map[vae_source_name], 0]
22
-
23
- current_positive_connection = assembler.workflow[ksampler_id]['inputs']['positive']
24
- current_negative_connection = assembler.workflow[ksampler_id]['inputs']['negative']
25
-
26
- for item_data in chain_items:
27
- cn_loader_id = assembler._get_unique_id()
28
- cn_loader_node = assembler._get_node_template("ControlNetLoader")
29
- cn_loader_node['inputs']['control_net_name'] = item_data['control_net_name']
30
- assembler.workflow[cn_loader_id] = cn_loader_node
31
-
32
- image_loader_id = assembler._get_unique_id()
33
- image_loader_node = assembler._get_node_template("LoadImage")
34
- image_loader_node['inputs']['image'] = item_data['image']
35
- assembler.workflow[image_loader_id] = image_loader_node
36
-
37
- apply_cn_id = assembler._get_unique_id()
38
- apply_cn_node = assembler._get_node_template(chain_definition['template'])
39
-
40
- apply_cn_node['inputs']['strength'] = item_data['strength']
41
-
42
- apply_cn_node['inputs']['positive'] = current_positive_connection
43
- apply_cn_node['inputs']['negative'] = current_negative_connection
44
- apply_cn_node['inputs']['control_net'] = [cn_loader_id, 0]
45
- apply_cn_node['inputs']['image'] = [image_loader_id, 0]
46
-
47
- apply_cn_node['inputs']['vae'] = vae_connection
48
-
49
- assembler.workflow[apply_cn_id] = apply_cn_node
50
-
51
- current_positive_connection = [apply_cn_id, 0]
52
- current_negative_connection = [apply_cn_id, 1]
53
-
54
- assembler.workflow[ksampler_id]['inputs']['positive'] = current_positive_connection
55
- assembler.workflow[ksampler_id]['inputs']['negative'] = current_negative_connection
56
-
57
- print(f"ControlNet injector applied. KSampler inputs redirected through {len(chain_items)} ControlNet nodes.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chain_injectors/lora_injector.py DELETED
@@ -1,67 +0,0 @@
1
- from copy import deepcopy
2
-
3
- def inject(assembler, chain_definition, chain_items):
4
- if not chain_items:
5
- return
6
-
7
- start_node_name = chain_definition.get('start')
8
- start_node_id = None
9
- if start_node_name:
10
- if start_node_name not in assembler.node_map:
11
- print(f"Warning: Start node '{start_node_name}' for dynamic LoRA chain not found. Skipping chain.")
12
- return
13
- start_node_id = assembler.node_map[start_node_name]
14
-
15
- output_map = chain_definition.get('output_map', {})
16
- current_connections = {}
17
- for key, type_name in output_map.items():
18
- if ':' in str(key):
19
- node_name, idx_str = key.split(':')
20
- if node_name not in assembler.node_map:
21
- print(f"Warning: Node '{node_name}' in chain's output_map not found. Skipping.")
22
- continue
23
- node_id = assembler.node_map[node_name]
24
- start_output_idx = int(idx_str)
25
- current_connections[type_name] = [node_id, start_output_idx]
26
- elif start_node_id:
27
- start_output_idx = int(key)
28
- current_connections[type_name] = [start_node_id, start_output_idx]
29
- else:
30
- print(f"Warning: LoRA chain has no 'start' node defined, and an output_map key '{key}' is not in 'node:index' format. Skipping this connection.")
31
-
32
-
33
- input_map = chain_definition.get('input_map', {})
34
- chain_output_map = chain_definition.get('template_output_map', { "0": "model", "1": "clip" })
35
-
36
- for item_data in chain_items:
37
- template_name = chain_definition['template']
38
- template = assembler._get_node_template(template_name)
39
- node_data = deepcopy(template)
40
-
41
- for param_name, value in item_data.items():
42
- if param_name in node_data['inputs']:
43
- node_data['inputs'][param_name] = value
44
-
45
- for type_name, input_name in input_map.items():
46
- if type_name in current_connections:
47
- node_data['inputs'][input_name] = current_connections[type_name]
48
-
49
- new_node_id = assembler._get_unique_id()
50
- assembler.workflow[new_node_id] = node_data
51
-
52
- for idx_str, type_name in chain_output_map.items():
53
- current_connections[type_name] = [new_node_id, int(idx_str)]
54
-
55
- end_input_map = chain_definition.get('end_input_map', {})
56
- for type_name, targets in end_input_map.items():
57
- if type_name in current_connections:
58
- if not isinstance(targets, list):
59
- targets = [targets]
60
-
61
- for target_str in targets:
62
- end_node_name, end_input_name = target_str.split(':')
63
- if end_node_name in assembler.node_map:
64
- end_node_id = assembler.node_map[end_node_name]
65
- assembler.workflow[end_node_id]['inputs'][end_input_name] = current_connections[type_name]
66
- else:
67
- print(f"Warning: End node '{end_node_name}' for dynamic chain not found. Skipping connection.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
comfy_integration/nodes.py CHANGED
@@ -1,12 +1,7 @@
1
  import asyncio
2
  import execution
3
  import server
4
- from nodes import (
5
- init_extra_nodes, CheckpointLoaderSimple, EmptyLatentImage, KSampler,
6
- VAEDecode, SaveImage, NODE_CLASS_MAPPINGS, LoadImage, VAEEncode,
7
- VAEEncodeForInpaint, ImagePadForOutpaint, LatentUpscaleBy, RepeatLatentBatch
8
- )
9
-
10
 
11
  def import_custom_nodes() -> None:
12
  loop = asyncio.new_event_loop()
@@ -18,22 +13,4 @@ def import_custom_nodes() -> None:
18
 
19
  import_custom_nodes()
20
 
21
- CLIPTextEncode = NODE_CLASS_MAPPINGS['CLIPTextEncode']
22
- 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]
29
- SCHEDULER_CHOICES = KSamplerNode.INPUT_TYPES()["required"]["scheduler"][0]
30
- except Exception:
31
- print("⚠️ Could not dynamically get sampler/scheduler choices, using fallback list.")
32
- SAMPLER_CHOICES = ['euler', 'dpmpp_2m_sde_gpu']
33
- SCHEDULER_CHOICES = ['normal', 'karras']
34
-
35
- checkpointloadersimple = CheckpointLoaderSimple()
36
- loraloader = LoraLoader()
37
-
38
-
39
  print("✅ ComfyUI custom nodes and class mappings are ready.")
 
1
  import asyncio
2
  import execution
3
  import server
4
+ from nodes import init_extra_nodes, NODE_CLASS_MAPPINGS
 
 
 
 
 
5
 
6
  def import_custom_nodes() -> None:
7
  loop = asyncio.new_event_loop()
 
13
 
14
  import_custom_nodes()
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  print("✅ ComfyUI custom nodes and class mappings are ready.")
comfy_integration/setup.py CHANGED
@@ -2,8 +2,6 @@ import os
2
  import sys
3
  import shutil
4
 
5
- from core.settings import *
6
-
7
  def move_and_overwrite(src, dst):
8
  if os.path.isdir(src):
9
  if os.path.exists(dst):
@@ -39,7 +37,6 @@ def initialize_comfyui():
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
  controlnet_aux_path = os.path.join(APP_DIR, "custom_nodes", "comfyui_controlnet_aux")
45
  if not os.path.exists(controlnet_aux_path):
@@ -52,15 +49,4 @@ def initialize_comfyui():
52
 
53
  import comfy.model_management
54
  print("--- Environment Ready ---")
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, DIFFUSION_MODELS_DIR), exist_ok=True)
63
- os.makedirs(os.path.join(APP_DIR, VAE_DIR), exist_ok=True)
64
- os.makedirs(os.path.join(APP_DIR, TEXT_ENCODERS_DIR), exist_ok=True)
65
- os.makedirs(os.path.join(APP_DIR, INPUT_DIR), exist_ok=True)
66
- print("✅ All required model directories are present.")
 
2
  import sys
3
  import shutil
4
 
 
 
5
  def move_and_overwrite(src, dst):
6
  if os.path.isdir(src):
7
  if os.path.exists(dst):
 
37
  except OSError as e:
38
  print(f"⚠️ Could not remove temporary directory '{COMFYUI_TEMP_DIR}': {e}")
39
 
 
40
  print("--- Cloning third-party extensions for ComfyUI ---")
41
  controlnet_aux_path = os.path.join(APP_DIR, "custom_nodes", "comfyui_controlnet_aux")
42
  if not os.path.exists(controlnet_aux_path):
 
49
 
50
  import comfy.model_management
51
  print("--- Environment Ready ---")
52
+ print("✅ ComfyUI initialized.")
 
 
 
 
 
 
 
 
 
 
 
core/generation_logic.py CHANGED
@@ -1,12 +1,8 @@
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
@@ -17,9 +13,5 @@ def build_reverse_map():
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
 
3
  from core.pipelines.controlnet_preprocessor import ControlNetPreprocessorPipeline
 
4
 
5
  controlnet_preprocessor_pipeline = ControlNetPreprocessorPipeline()
 
 
6
 
7
  def build_reverse_map():
8
  from nodes import NODE_DISPLAY_NAME_MAPPINGS
 
13
  if "Semantic Segmentor (legacy, alias for UniFormer)" not in cn_module.REVERSE_DISPLAY_NAME_MAP:
14
  cn_module.REVERSE_DISPLAY_NAME_MAP["Semantic Segmentor (legacy, alias for UniFormer)"] = "SemSegPreprocessor"
15
 
 
16
  def run_cn_preprocessor_entry(*args, **kwargs):
17
+ return controlnet_preprocessor_pipeline.run(*args, **kwargs)
 
 
 
core/model_manager.py DELETED
@@ -1,46 +0,0 @@
1
- import gc
2
- from typing import Dict, List, Any, Set
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
10
-
11
- def __new__(cls, *args, **kwargs):
12
- if not cls._instance:
13
- cls._instance = super(ModelManager, cls).__new__(cls, *args, **kwargs)
14
- return cls._instance
15
-
16
- def __init__(self):
17
- if hasattr(self, 'initialized'):
18
- return
19
- self.initialized = True
20
- print("✅ ModelManager initialized.")
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_key, component_file in components.items():
30
- if component_key in ['unet', 'clip', 'vae', 'lora']:
31
- files_to_download.add(component_file)
32
-
33
- files_to_download = list(files_to_download)
34
- total_files = len(files_to_download)
35
-
36
- for i, filename in enumerate(files_to_download):
37
- if progress and hasattr(progress, '__call__'):
38
- progress(i / total_files, desc=f"Checking file: {filename}")
39
- try:
40
- _ensure_model_downloaded(filename, progress)
41
- except Exception as e:
42
- raise gr.Error(f"Failed to download model component '{filename}'. Reason: {e}")
43
-
44
- print(f"--- [ModelManager] ✅ All required models are present on disk. ---")
45
-
46
- model_manager = ModelManager()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/pipelines/base_pipeline.py CHANGED
@@ -1,5 +1,5 @@
1
  from abc import ABC, abstractmethod
2
- from typing import List, Any, Dict
3
  import gradio as gr
4
  import spaces
5
  import tempfile
@@ -8,22 +8,12 @@ import numpy as np
8
 
9
  class BasePipeline(ABC):
10
  def __init__(self):
11
- from core.model_manager import model_manager
12
- self.model_manager = model_manager
13
-
14
- @abstractmethod
15
- def get_required_models(self, **kwargs) -> List[str]:
16
  pass
17
 
18
  @abstractmethod
19
  def run(self, *args, progress: gr.Progress, **kwargs) -> Any:
20
  pass
21
 
22
- def _ensure_models_downloaded(self, progress: gr.Progress, **kwargs):
23
- """Ensures model files are downloaded before requesting GPU."""
24
- required_models = self.get_required_models(**kwargs)
25
- self.model_manager.ensure_models_downloaded(required_models, progress=progress)
26
-
27
  def _execute_gpu_logic(self, gpu_function: callable, duration: int, default_duration: int, task_name: str, *args, **kwargs):
28
  final_duration = default_duration
29
  try:
@@ -38,7 +28,7 @@ class BasePipeline(ABC):
38
 
39
  return gpu_runner(*args, **kwargs)
40
 
41
- def _encode_video_from_frames(self, frames_tensor_cpu: 'torch.Tensor', fps: int, progress: gr.Progress) -> str:
42
  progress(0.9, desc="Encoding video on CPU...")
43
  frames_np = (frames_tensor_cpu.numpy() * 255.0).astype(np.uint8)
44
 
 
1
  from abc import ABC, abstractmethod
2
+ from typing import Any
3
  import gradio as gr
4
  import spaces
5
  import tempfile
 
8
 
9
  class BasePipeline(ABC):
10
  def __init__(self):
 
 
 
 
 
11
  pass
12
 
13
  @abstractmethod
14
  def run(self, *args, progress: gr.Progress, **kwargs) -> Any:
15
  pass
16
 
 
 
 
 
 
17
  def _execute_gpu_logic(self, gpu_function: callable, duration: int, default_duration: int, task_name: str, *args, **kwargs):
18
  final_duration = default_duration
19
  try:
 
28
 
29
  return gpu_runner(*args, **kwargs)
30
 
31
+ def _encode_video_from_frames(self, frames_tensor_cpu, fps: int, progress: gr.Progress) -> str:
32
  progress(0.9, desc="Encoding video on CPU...")
33
  frames_np = (frames_tensor_cpu.numpy() * 255.0).astype(np.uint8)
34
 
core/pipelines/controlnet_preprocessor.py CHANGED
@@ -5,7 +5,6 @@ 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
@@ -30,9 +29,6 @@ def run_node_by_function_name(node_instance: Any, **kwargs) -> Any:
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)
 
5
  import torch
6
  import gradio as gr
7
  from PIL import Image
 
8
 
9
  from .base_pipeline import BasePipeline
10
  from comfy_integration.nodes import NODE_CLASS_MAPPINGS
 
29
  return execution_method(**kwargs)
30
 
31
  class ControlNetPreprocessorPipeline(BasePipeline):
 
 
 
32
  def _gpu_logic(
33
  self, pil_images: List[Image.Image], preprocessor_name: str, model_name: str,
34
  params: Dict[str, Any], progress=gr.Progress(track_tqdm=True)
core/pipelines/sd_image_pipeline.py DELETED
@@ -1,423 +0,0 @@
1
- import os
2
- import random
3
- import shutil
4
- import torch
5
- import gradio as gr
6
- from PIL import Image, ImageChops
7
- from typing import List, Dict, Any
8
- from collections import defaultdict, deque
9
- import numpy as np
10
-
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)
23
- in_degree = {node_id: 0 for node_id in workflow}
24
-
25
- for node_id, node_info in workflow.items():
26
- for input_value in node_info.get('inputs', {}).values():
27
- if isinstance(input_value, list) and len(input_value) == 2 and isinstance(input_value[0], str):
28
- source_node_id = input_value[0]
29
- if source_node_id in workflow:
30
- graph[source_node_id].append(node_id)
31
- in_degree[node_id] += 1
32
-
33
- queue = deque([node_id for node_id, degree in in_degree.items() if degree == 0])
34
-
35
- sorted_nodes = []
36
- while queue:
37
- current_node_id = queue.popleft()
38
- sorted_nodes.append(current_node_id)
39
-
40
- for neighbor_node_id in graph[current_node_id]:
41
- in_degree[neighbor_node_id] -= 1
42
- if in_degree[neighbor_node_id] == 0:
43
- queue.append(neighbor_node_id)
44
-
45
- if len(sorted_nodes) != len(workflow):
46
- raise RuntimeError("Workflow contains a cycle and cannot be executed.")
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
54
-
55
- try:
56
- sorted_node_ids = self._topological_sort(workflow)
57
- print(f"--- [Workflow Executor] Execution order: {sorted_node_ids}")
58
- except RuntimeError as e:
59
- print("--- [Workflow Executor] ERROR: Failed to sort workflow. Dumping graph details. ---")
60
- for node_id, node_info in workflow.items():
61
- print(f" Node {node_id} ({node_info['class_type']}):")
62
- for input_name, input_value in node_info['inputs'].items():
63
- if isinstance(input_value, list) and len(input_value) == 2 and isinstance(input_value[0], str):
64
- print(f" - {input_name} <- [{input_value[0]}, {input_value[1]}]")
65
- raise e
66
-
67
- for node_id in sorted_node_ids:
68
- if node_id in computed_outputs:
69
- continue
70
-
71
- node_info = workflow[node_id]
72
- class_type = node_info['class_type']
73
-
74
- is_loader_with_filename = 'Loader' in class_type and any(key.endswith('_name') for key in node_info['inputs'])
75
- if node_id in initial_objects and is_loader_with_filename:
76
- continue
77
-
78
- node_class = NODE_CLASS_MAPPINGS.get(class_type)
79
- if node_class is None:
80
- raise RuntimeError(f"Could not find node class '{class_type}'. Is it imported in comfy_integration/nodes.py?")
81
-
82
- node_instance = node_class()
83
-
84
- kwargs = {}
85
- for param_name, param_value in node_info['inputs'].items():
86
- if isinstance(param_value, list) and len(param_value) == 2 and isinstance(param_value[0], str):
87
- source_node_id, output_index = param_value
88
- if source_node_id not in computed_outputs:
89
- raise RuntimeError(f"Workflow integrity error: Output of node {source_node_id} needed for {node_id} but not yet computed.")
90
-
91
- source_output_tuple = computed_outputs[source_node_id]
92
- kwargs[param_name] = get_value_at_index(source_output_tuple, output_index)
93
- else:
94
- kwargs[param_name] = param_value
95
-
96
- function_name = getattr(node_class, 'FUNCTION')
97
- execution_method = getattr(node_instance, function_name)
98
-
99
- result = execution_method(**kwargs)
100
- computed_outputs[node_id] = result
101
-
102
- final_node_id = None
103
- for node_id in reversed(sorted_node_ids):
104
- if workflow[node_id]['class_type'] == 'SaveImage':
105
- final_node_id = node_id
106
- break
107
-
108
- if not final_node_id:
109
- raise RuntimeError("Workflow does not contain a 'SaveImage' node as the output.")
110
-
111
- save_image_inputs = workflow[final_node_id]['inputs']
112
- image_source_node_id, image_source_index = save_image_inputs['images']
113
-
114
- return get_value_at_index(computed_outputs[image_source_node_id], image_source_index)
115
-
116
- def _gpu_logic(self, ui_inputs: Dict, loras_string: str, workflow: Dict[str, Any], assembler: WorkflowAssembler, progress=gr.Progress(track_tqdm=True)):
117
- model_display_name = ui_inputs['model_display_name']
118
-
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 = []
126
- start_seed = ui_inputs['seed'] if ui_inputs['seed'] != -1 else random.randint(0, 2**64 - 1)
127
- for i in range(decoded_images_tensor.shape[0]):
128
- img_tensor = decoded_images_tensor[i]
129
- pil_image = Image.fromarray((img_tensor.cpu().numpy() * 255.0).astype("uint8"))
130
- current_seed = start_seed + i
131
-
132
- width_for_meta = ui_inputs.get('width', 'N/A')
133
- height_for_meta = ui_inputs.get('height', 'N/A')
134
-
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()}
141
- output_images.append(pil_image)
142
-
143
- return output_images
144
-
145
- def run(self, ui_inputs: Dict, progress):
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
- model_display_name = ui_inputs['model_display_name']
158
- if model_display_name not in ALL_MODEL_MAP:
159
- raise gr.Error(f"Model '{model_display_name}' is not configured in model_list.yaml.")
160
-
161
- _, components, _, _ = ALL_MODEL_MAP[model_display_name]
162
- base_lora_name = components.get('lora')
163
-
164
- active_loras_for_gpu, active_loras_for_meta = [], []
165
-
166
- if base_lora_name:
167
- active_loras_for_gpu.append({
168
- "lora_name": base_lora_name,
169
- "strength_model": 1.0,
170
- "strength_clip": 1.0
171
- })
172
- active_loras_for_meta.append(f"Base LoRA {base_lora_name}:1.0")
173
-
174
- lora_data = ui_inputs.get('lora_data', [])
175
- sources, ids, scales, files = lora_data[0::4], lora_data[1::4], lora_data[2::4], lora_data[3::4]
176
-
177
- for i, (source, lora_id, scale, _) in enumerate(zip(sources, ids, scales, files)):
178
- if scale > 0 and lora_id and lora_id.strip():
179
- lora_filename = None
180
- if source == "File":
181
- lora_filename = sanitize_filename(lora_id)
182
- elif source == "Civitai":
183
- local_path, status = get_lora_path(source, lora_id, ui_inputs['civitai_api_key'], progress)
184
- if local_path: lora_filename = os.path.basename(local_path)
185
- else: raise gr.Error(f"Failed to prepare LoRA {lora_id}: {status}")
186
-
187
- if lora_filename:
188
- active_loras_for_gpu.append({"lora_name": lora_filename, "strength_model": scale, "strength_clip": scale})
189
- active_loras_for_meta.append(f"{source} {lora_id}:{scale}")
190
-
191
- ui_inputs['denoise'] = 1.0
192
- if task_type == 'img2img': ui_inputs['denoise'] = ui_inputs.get('img2img_denoise', 0.7)
193
- elif task_type == 'hires_fix': ui_inputs['denoise'] = ui_inputs.get('hires_denoise', 0.55)
194
-
195
- temp_files_to_clean = []
196
-
197
- if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
198
-
199
- if task_type == 'img2img':
200
- input_image_pil = ui_inputs.get('img2img_image')
201
- if input_image_pil:
202
- temp_file_path = os.path.join(INPUT_DIR, f"temp_input_{random.randint(1000, 9999)}.png")
203
- input_image_pil.save(temp_file_path, "PNG")
204
- ui_inputs['input_image'] = os.path.basename(temp_file_path)
205
- temp_files_to_clean.append(temp_file_path)
206
- ui_inputs['width'] = input_image_pil.width
207
- ui_inputs['height'] = input_image_pil.height
208
-
209
- elif task_type == 'inpaint':
210
- inpaint_dict = ui_inputs.get('inpaint_image_dict')
211
- if not inpaint_dict or not inpaint_dict.get('background') or not inpaint_dict.get('layers'):
212
- raise gr.Error("Inpainting requires an input image and a drawn mask.")
213
-
214
- background_img = inpaint_dict['background'].convert("RGBA")
215
-
216
- composite_mask_pil = Image.new('L', background_img.size, 0)
217
- for layer in inpaint_dict['layers']:
218
- if layer:
219
- layer_alpha = layer.split()[-1]
220
- composite_mask_pil = ImageChops.lighter(composite_mask_pil, layer_alpha)
221
-
222
- inverted_mask_alpha = Image.fromarray(255 - np.array(composite_mask_pil), mode='L')
223
- r, g, b, _ = background_img.split()
224
- composite_image_with_mask = Image.merge('RGBA', [r, g, b, inverted_mask_alpha])
225
-
226
- temp_file_path = os.path.join(INPUT_DIR, f"temp_inpaint_composite_{random.randint(1000, 9999)}.png")
227
- composite_image_with_mask.save(temp_file_path, "PNG")
228
-
229
- ui_inputs['inpaint_image'] = os.path.basename(temp_file_path)
230
- temp_files_to_clean.append(temp_file_path)
231
- ui_inputs.pop('inpaint_mask', None)
232
-
233
- elif task_type == 'outpaint':
234
- input_image_pil = ui_inputs.get('outpaint_image')
235
- if input_image_pil:
236
- temp_file_path = os.path.join(INPUT_DIR, f"temp_input_{random.randint(1000, 9999)}.png")
237
- input_image_pil.save(temp_file_path, "PNG")
238
- ui_inputs['input_image'] = os.path.basename(temp_file_path)
239
- temp_files_to_clean.append(temp_file_path)
240
-
241
- elif task_type == 'hires_fix':
242
- input_image_pil = ui_inputs.get('hires_image')
243
- if input_image_pil:
244
- temp_file_path = os.path.join(INPUT_DIR, f"temp_input_{random.randint(1000, 9999)}.png")
245
- input_image_pil.save(temp_file_path, "PNG")
246
- ui_inputs['input_image'] = os.path.basename(temp_file_path)
247
- temp_files_to_clean.append(temp_file_path)
248
-
249
- embedding_data = ui_inputs.get('embedding_data', [])
250
- embedding_filenames = []
251
- if embedding_data:
252
- emb_sources, emb_ids, emb_files = embedding_data[0::3], embedding_data[1::3], embedding_data[2::3]
253
- for i, (source, emb_id, _) in enumerate(zip(emb_sources, emb_ids, emb_files)):
254
- if emb_id and emb_id.strip():
255
- emb_filename = None
256
- if source == "File":
257
- emb_filename = sanitize_filename(emb_id)
258
- elif source == "Civitai":
259
- local_path, status = get_embedding_path(source, emb_id, ui_inputs['civitai_api_key'], progress)
260
- if local_path: emb_filename = os.path.basename(local_path)
261
- else: raise gr.Error(f"Failed to prepare Embedding {emb_id}: {status}")
262
-
263
- if emb_filename:
264
- embedding_filenames.append(emb_filename)
265
-
266
- if embedding_filenames:
267
- embedding_prompt_text = " ".join([f"embedding:{f}" for f in embedding_filenames])
268
- if ui_inputs['positive_prompt']:
269
- ui_inputs['positive_prompt'] = f"{ui_inputs['positive_prompt']}, {embedding_prompt_text}"
270
- else:
271
- ui_inputs['positive_prompt'] = embedding_prompt_text
272
-
273
- controlnet_data = ui_inputs.get('controlnet_data', [])
274
- active_controlnets = []
275
- (cn_images, _, _, cn_strengths, cn_filepaths) = [controlnet_data[i::5] for i in range(5)]
276
- for i in range(len(cn_images)):
277
- if cn_images[i] and cn_strengths[i] > 0 and cn_filepaths[i] and cn_filepaths[i] != "None":
278
- ensure_controlnet_model_downloaded(cn_filepaths[i], progress)
279
-
280
- if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
281
- cn_temp_path = os.path.join(INPUT_DIR, f"temp_cn_{i}_{random.randint(1000, 9999)}.png")
282
- cn_images[i].save(cn_temp_path, "PNG")
283
- temp_files_to_clean.append(cn_temp_path)
284
- active_controlnets.append({
285
- "image": os.path.basename(cn_temp_path), "strength": cn_strengths[i],
286
- "start_percent": 0.0, "end_percent": 1.0, "control_net_name": cn_filepaths[i]
287
- })
288
-
289
- from utils.app_utils import get_vae_path
290
- vae_source = ui_inputs.get('vae_source')
291
- vae_id = ui_inputs.get('vae_id')
292
- vae_file = ui_inputs.get('vae_file')
293
- vae_name_override = None
294
-
295
- if vae_source and vae_source != "None":
296
- if vae_source == "File":
297
- vae_name_override = sanitize_filename(vae_id)
298
- elif vae_source == "Civitai" and vae_id and vae_id.strip():
299
- local_path, status = get_vae_path(vae_source, vae_id, ui_inputs.get('civitai_api_key'), progress)
300
- if local_path: vae_name_override = os.path.basename(local_path)
301
- else: raise gr.Error(f"Failed to prepare VAE {vae_id}: {status}")
302
-
303
- if vae_name_override:
304
- ui_inputs['vae_name'] = vae_name_override
305
-
306
- conditioning_data = ui_inputs.get('conditioning_data', [])
307
- active_conditioning = []
308
- if conditioning_data:
309
- num_units = len(conditioning_data) // 6
310
- prompts = conditioning_data[0*num_units : 1*num_units]
311
- widths = conditioning_data[1*num_units : 2*num_units]
312
- heights = conditioning_data[2*num_units : 3*num_units]
313
- xs = conditioning_data[3*num_units : 4*num_units]
314
- ys = conditioning_data[4*num_units : 5*num_units]
315
- strengths = conditioning_data[5*num_units : 6*num_units]
316
-
317
- for i in range(num_units):
318
- if prompts[i] and prompts[i].strip():
319
- active_conditioning.append({
320
- "prompt": prompts[i],
321
- "width": int(widths[i]),
322
- "height": int(heights[i]),
323
- "x": int(xs[i]),
324
- "y": int(ys[i]),
325
- "strength": float(strengths[i])
326
- })
327
-
328
- loras_string = f"LoRAs: [{', '.join(active_loras_for_meta)}]" if active_loras_for_meta else ""
329
-
330
- progress(0.8, desc="Assembling workflow...")
331
-
332
- if ui_inputs.get('seed') == -1:
333
- ui_inputs['seed'] = random.randint(0, 2**32 - 1)
334
-
335
- dynamic_values = {'task_type': ui_inputs['task_type']}
336
-
337
- recipe_path = os.path.join(os.path.dirname(__file__), "workflow_recipes", "sd_unified_recipe.yaml")
338
- assembler = WorkflowAssembler(recipe_path, dynamic_values=dynamic_values)
339
-
340
- workflow_inputs = {
341
- "positive_prompt": ui_inputs['positive_prompt'], "negative_prompt": ui_inputs['negative_prompt'],
342
- "seed": ui_inputs['seed'], "steps": ui_inputs['num_inference_steps'], "cfg": ui_inputs['guidance_scale'],
343
- "sampler_name": ui_inputs['sampler'], "scheduler": ui_inputs['scheduler'],
344
- "batch_size": ui_inputs['batch_size'],
345
- "denoise": ui_inputs['denoise'],
346
- "input_image": ui_inputs.get('input_image'),
347
- "inpaint_image": ui_inputs.get('inpaint_image'),
348
- "inpaint_mask": ui_inputs.get('inpaint_mask'),
349
- "left": ui_inputs.get('outpaint_left'), "top": ui_inputs.get('outpaint_top'),
350
- "right": ui_inputs.get('outpaint_right'), "bottom": ui_inputs.get('outpaint_bottom'),
351
- "hires_upscaler": ui_inputs.get('hires_upscaler'), "hires_scale_by": ui_inputs.get('hires_scale_by'),
352
- "unet_name": components['unet'],
353
- "clip_name": components['clip'],
354
- "vae_name": ui_inputs.get('vae_name', components['vae']),
355
- "lora_chain": active_loras_for_gpu,
356
- "controlnet_chain": active_controlnets,
357
- "conditioning_chain": active_conditioning,
358
- }
359
-
360
- if task_type == 'txt2img':
361
- workflow_inputs['width'] = ui_inputs['width']
362
- workflow_inputs['height'] = ui_inputs['height']
363
-
364
- workflow = assembler.assemble(workflow_inputs)
365
-
366
- progress(1.0, desc="All models ready. Requesting GPU for generation...")
367
-
368
- try:
369
- results = self._execute_gpu_logic(
370
- self._gpu_logic,
371
- duration=ui_inputs['zero_gpu_duration'],
372
- default_duration=60,
373
- task_name=f"ImageGen ({task_type})",
374
- ui_inputs=ui_inputs,
375
- loras_string=loras_string,
376
- workflow=workflow,
377
- assembler=assembler,
378
- progress=progress
379
- )
380
-
381
- import json
382
- import glob
383
- from PIL import PngImagePlugin
384
-
385
- prompt_json = json.dumps(workflow)
386
-
387
- out_dir = os.path.abspath(OUTPUT_DIR)
388
- os.makedirs(out_dir, exist_ok=True)
389
-
390
- try:
391
- existing_files = glob.glob(os.path.join(out_dir, "gen_*.png"))
392
- existing_files.sort(key=os.path.getmtime)
393
- while len(existing_files) > 50:
394
- os.remove(existing_files.pop(0))
395
- except Exception as e:
396
- print(f"Warning: Failed to cleanup output dir: {e}")
397
-
398
- final_results = []
399
- for img in results:
400
- if not isinstance(img, Image.Image):
401
- final_results.append(img)
402
- continue
403
-
404
- metadata = PngImagePlugin.PngInfo()
405
- params_string = img.info.get("parameters", "")
406
- if params_string:
407
- metadata.add_text("parameters", params_string)
408
- metadata.add_text("prompt", prompt_json)
409
-
410
- filename = f"gen_{random.randint(1000000, 9999999)}.png"
411
- filepath = os.path.join(out_dir, filename)
412
- img.save(filepath, "PNG", pnginfo=metadata)
413
- final_results.append(filepath)
414
-
415
- results = final_results
416
-
417
- finally:
418
- for temp_file in temp_files_to_clean:
419
- if temp_file and os.path.exists(temp_file):
420
- os.remove(temp_file)
421
- print(f"✅ Cleaned up temp file: {temp_file}")
422
-
423
- return results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/pipelines/workflow_recipes/_partials/_base_sampler.yaml DELETED
@@ -1,23 +0,0 @@
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:
12
- - from: "ksampler:0"
13
- to: "vae_decode:samples"
14
- - from: "vae_decode:0"
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"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/pipelines/workflow_recipes/_partials/conditioning/qwen-image.yaml DELETED
@@ -1,80 +0,0 @@
1
- nodes:
2
- unet_loader:
3
- class_type: UNETLoader
4
- title: "Load Qwen UNET"
5
- params:
6
- weight_dtype: "default"
7
- vae_loader:
8
- class_type: VAELoader
9
- title: "Load Qwen VAE"
10
- clip_loader:
11
- class_type: CLIPLoader
12
- title: "Load Qwen CLIP"
13
- params:
14
- type: "qwen_image"
15
- device: "default"
16
-
17
- model_sampler:
18
- class_type: ModelSamplingAuraFlow
19
- title: "ModelSamplingAuraFlow"
20
- params:
21
- shift: 3.1
22
- pos_prompt:
23
- class_type: CLIPTextEncode
24
- title: "Positive Prompt Encoder"
25
- neg_prompt:
26
- class_type: CLIPTextEncode
27
- title: "Negative Prompt Encoder"
28
-
29
- connections:
30
- - from: "unet_loader:0"
31
- to: "model_sampler:model"
32
-
33
- - from: "model_sampler:0"
34
- to: "ksampler:model"
35
-
36
- - from: "clip_loader:0"
37
- to: "pos_prompt:clip"
38
- - from: "clip_loader:0"
39
- to: "neg_prompt:clip"
40
-
41
- - from: "vae_loader:0"
42
- to: "vae_decode:vae"
43
- - from: "vae_loader:0"
44
- to: "vae_encode:vae"
45
-
46
- - from: "pos_prompt:0"
47
- to: "ksampler:positive"
48
- - from: "neg_prompt:0"
49
- to: "ksampler:negative"
50
-
51
- dynamic_lora_chains:
52
- lora_chain:
53
- template: "LoraLoader"
54
- output_map:
55
- "unet_loader:0": "model"
56
- "clip_loader:0": "clip"
57
- input_map:
58
- "model": "model"
59
- "clip": "clip"
60
- end_input_map:
61
- "model": ["model_sampler:model"]
62
- "clip": ["pos_prompt:clip", "neg_prompt:clip"]
63
-
64
- dynamic_controlnet_chains:
65
- controlnet_chain:
66
- template: "ControlNetApplyAdvanced"
67
- ksampler_node: "ksampler"
68
- vae_source_node: "vae_loader"
69
-
70
- dynamic_conditioning_chains:
71
- conditioning_chain:
72
- ksampler_node: "ksampler"
73
- clip_source: "clip_loader:0"
74
-
75
- ui_map:
76
- unet_name: "unet_loader:unet_name"
77
- vae_name: "vae_loader:vae_name"
78
- clip_name: "clip_loader:clip_name"
79
- positive_prompt: "pos_prompt:text"
80
- negative_prompt: "neg_prompt:text"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/pipelines/workflow_recipes/_partials/input/hires_fix.yaml DELETED
@@ -1,26 +0,0 @@
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"
16
- to: "vae_encode:pixels"
17
- - from: "vae_encode:0"
18
- to: "latent_upscaler:samples"
19
- - from: "latent_upscaler:0"
20
- to: "latent_source:samples"
21
-
22
- ui_map:
23
- input_image: "input_image_loader:image"
24
- hires_upscaler: "latent_upscaler:upscale_method"
25
- hires_scale_by: "latent_upscaler:scale_by"
26
- batch_size: "latent_source:amount"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/pipelines/workflow_recipes/_partials/input/img2img.yaml DELETED
@@ -1,19 +0,0 @@
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"
13
- to: "vae_encode:pixels"
14
- - from: "vae_encode:0"
15
- to: "latent_source:samples"
16
-
17
- ui_map:
18
- input_image: "input_image_loader:image"
19
- batch_size: "latent_source:amount"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/pipelines/workflow_recipes/_partials/input/inpaint.yaml DELETED
@@ -1,25 +0,0 @@
1
- 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"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/pipelines/workflow_recipes/_partials/input/outpaint.yaml DELETED
@@ -1,38 +0,0 @@
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"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/pipelines/workflow_recipes/_partials/input/txt2img.yaml DELETED
@@ -1,8 +0,0 @@
1
- nodes:
2
- latent_source:
3
- class_type: EmptySD3LatentImage
4
-
5
- ui_map:
6
- width: "latent_source:width"
7
- height: "latent_source:height"
8
- batch_size: "latent_source:batch_size"
 
 
 
 
 
 
 
 
 
core/pipelines/workflow_recipes/sd_unified_recipe.yaml DELETED
@@ -1,8 +0,0 @@
1
- imports:
2
- - "_partials/_base_sampler.yaml"
3
- - "_partials/input/{{ task_type }}.yaml"
4
- - "_partials/conditioning/qwen-image.yaml"
5
-
6
- connections:
7
- - from: "latent_source:0"
8
- to: "ksampler:latent_image"
 
 
 
 
 
 
 
 
 
core/settings.py CHANGED
@@ -1,111 +1,8 @@
1
- import yaml
2
  import os
3
- from collections import OrderedDict
4
 
5
- CHECKPOINT_DIR = "models/checkpoints"
6
- LORA_DIR = "models/loras"
7
- EMBEDDING_DIR = "models/embeddings"
8
- CONTROLNET_DIR = "models/controlnet"
9
- DIFFUSION_MODELS_DIR = "models/diffusion_models"
10
- VAE_DIR = "models/vae"
11
- TEXT_ENCODERS_DIR = "models/text_encoders"
12
  INPUT_DIR = "input"
13
  OUTPUT_DIR = "output"
14
 
15
  _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16
- _MODEL_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_list.yaml')
17
- _FILE_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'file_list.yaml')
18
- _CONSTANTS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'constants.yaml')
19
 
20
-
21
- def load_constants_from_yaml(filepath=_CONSTANTS_PATH):
22
- if not os.path.exists(filepath):
23
- print(f"Warning: Constants file not found at {filepath}. Using fallback values.")
24
- return {}
25
- with open(filepath, 'r', encoding='utf-8') as f:
26
- return yaml.safe_load(f)
27
-
28
- def load_file_download_map(filepath=_FILE_LIST_PATH):
29
- if not os.path.exists(filepath):
30
- raise FileNotFoundError(f"The file list (for downloads) was not found at: {filepath}")
31
-
32
- with open(filepath, 'r', encoding='utf-8') as f:
33
- file_list_data = yaml.safe_load(f)
34
-
35
- download_info_map = {}
36
- for category, files in file_list_data.get('file', {}).items():
37
- if isinstance(files, list):
38
- for file_info in files:
39
- if 'filename' in file_info:
40
- file_info['category'] = category
41
- download_info_map[file_info['filename']] = file_info
42
- return download_info_map
43
-
44
-
45
- def load_models_from_yaml(model_list_filepath=_MODEL_LIST_PATH, download_map=None):
46
- if not os.path.exists(model_list_filepath):
47
- raise FileNotFoundError(f"The model list file was not found at: {model_list_filepath}")
48
- if download_map is None:
49
- raise ValueError("download_map must be provided to load_models_from_yaml")
50
-
51
- with open(model_list_filepath, 'r', encoding='utf-8') as f:
52
- model_data = yaml.safe_load(f)
53
-
54
- model_maps = {
55
- "MODEL_MAP_CHECKPOINT": OrderedDict(),
56
- "ALL_MODEL_MAP": OrderedDict(),
57
- }
58
- category_map_names = {
59
- "Checkpoint": "MODEL_MAP_CHECKPOINT",
60
- }
61
-
62
- for category, models in model_data.items():
63
- if category in category_map_names:
64
- map_name = category_map_names[category]
65
- if not isinstance(models, list): continue
66
- for model in models:
67
- display_name = model['display_name']
68
- components = model.get('components', {})
69
-
70
- model_tuple = (
71
- None,
72
- components,
73
- "SDXL",
74
- None
75
- )
76
- model_maps[map_name][display_name] = model_tuple
77
- model_maps["ALL_MODEL_MAP"][display_name] = model_tuple
78
-
79
- return model_maps
80
-
81
- try:
82
- ALL_FILE_DOWNLOAD_MAP = load_file_download_map()
83
- loaded_maps = load_models_from_yaml(download_map=ALL_FILE_DOWNLOAD_MAP)
84
- MODEL_MAP_CHECKPOINT = loaded_maps["MODEL_MAP_CHECKPOINT"]
85
- ALL_MODEL_MAP = loaded_maps["ALL_MODEL_MAP"]
86
-
87
- MODEL_TYPE_MAP = {k: v[2] for k, v in ALL_MODEL_MAP.items()}
88
-
89
- except Exception as e:
90
- print(f"FATAL: Could not load model configuration from YAML. Error: {e}")
91
- ALL_FILE_DOWNLOAD_MAP = {}
92
- MODEL_MAP_CHECKPOINT, ALL_MODEL_MAP = {}, {}
93
- MODEL_TYPE_MAP = {}
94
-
95
-
96
- try:
97
- _constants = load_constants_from_yaml()
98
- MAX_LORAS = _constants.get('MAX_LORAS', 5)
99
- MAX_EMBEDDINGS = _constants.get('MAX_EMBEDDINGS', 5)
100
- MAX_CONDITIONINGS = _constants.get('MAX_CONDITIONINGS', 10)
101
- MAX_CONTROLNETS = _constants.get('MAX_CONTROLNETS', 5)
102
- LORA_SOURCE_CHOICES = _constants.get('LORA_SOURCE_CHOICES', ["Civitai", "Custom URL", "File"])
103
- RESOLUTION_MAP = _constants.get('RESOLUTION_MAP', {})
104
- except Exception as e:
105
- print(f"FATAL: Could not load constants from YAML. Error: {e}")
106
- MAX_LORAS, MAX_EMBEDDINGS, MAX_CONDITIONINGS, MAX_CONTROLNETS = 5, 5, 10, 5
107
- LORA_SOURCE_CHOICES = ["Civitai", "Custom URL", "File"]
108
- RESOLUTION_MAP = {}
109
-
110
-
111
- DEFAULT_NEGATIVE_PROMPT = ""
 
 
1
  import os
 
2
 
 
 
 
 
 
 
 
3
  INPUT_DIR = "input"
4
  OUTPUT_DIR = "output"
5
 
6
  _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
 
 
7
 
8
+ print("✅ Settings initialized for ControlNet Preprocessors.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/shared_state.py DELETED
@@ -1 +0,0 @@
1
- INVALID_MODEL_URLS = {}
 
 
core/workflow_assembler.py DELETED
@@ -1,179 +0,0 @@
1
- import yaml
2
- import os
3
- import importlib
4
- from copy import deepcopy
5
- from comfy_integration.nodes import NODE_CLASS_MAPPINGS
6
-
7
- class WorkflowAssembler:
8
- def __init__(self, recipe_path, dynamic_values=None):
9
- self.base_path = os.path.dirname(recipe_path)
10
- self.node_counter = 0
11
- self.workflow = {}
12
- self.node_map = {}
13
-
14
- self._load_injector_config()
15
-
16
- self.recipe = self._load_and_merge_recipe(os.path.basename(recipe_path), dynamic_values or {})
17
-
18
- def _load_injector_config(self):
19
- try:
20
- project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
21
- injectors_path = os.path.join(project_root, 'yaml', 'injectors.yaml')
22
-
23
- with open(injectors_path, 'r', encoding='utf-8') as f:
24
- injector_config = yaml.safe_load(f)
25
-
26
- definitions = injector_config.get("injector_definitions", {})
27
- self.injector_order = injector_config.get("injector_order", [])
28
- self.global_injectors = {}
29
-
30
- for chain_type, config in definitions.items():
31
- module_path = config.get("module")
32
- if not module_path:
33
- print(f"Warning: Injector '{chain_type}' in injectors.yaml is missing 'module' path.")
34
- continue
35
- try:
36
- module = importlib.import_module(module_path)
37
- if hasattr(module, 'inject'):
38
- self.global_injectors[chain_type] = module.inject
39
- print(f"✅ Successfully registered global injector: {chain_type} from {module_path}")
40
- else:
41
- print(f"⚠️ Warning: Module '{module_path}' for injector '{chain_type}' does not have an 'inject' function.")
42
- except ImportError as e:
43
- print(f"❌ Error importing module '{module_path}' for injector '{chain_type}': {e}")
44
-
45
- if not self.injector_order:
46
- print("⚠️ Warning: 'injector_order' is not defined in injectors.yaml. Using definition order.")
47
- self.injector_order = list(definitions.keys())
48
-
49
- except FileNotFoundError:
50
- print(f"❌ FATAL: Could not find injectors.yaml at {injectors_path}. Dynamic chains will not work.")
51
- self.injector_order = []
52
- self.global_injectors = {}
53
- except Exception as e:
54
- print(f"❌ FATAL: Could not load or parse injectors.yaml. Dynamic chains will not work. Error: {e}")
55
- self.injector_order = []
56
- self.global_injectors = {}
57
-
58
- def _get_unique_id(self):
59
- self.node_counter += 1
60
- return str(self.node_counter)
61
-
62
- def _get_node_template(self, class_type):
63
- if class_type not in NODE_CLASS_MAPPINGS:
64
- raise ValueError(f"Node class '{class_type}' not found. Ensure it's correctly imported in comfy_integration/nodes.py.")
65
-
66
- node_class = NODE_CLASS_MAPPINGS[class_type]
67
- input_types = node_class.INPUT_TYPES()
68
-
69
- template = {
70
- "inputs": {},
71
- "class_type": class_type,
72
- "_meta": {"title": node_class.NODE_NAME if hasattr(node_class, 'NODE_NAME') else class_type}
73
- }
74
-
75
- all_inputs = {**input_types.get('required', {}), **input_types.get('optional', {})}
76
- for name, details in all_inputs.items():
77
- config = details[1] if len(details) > 1 and isinstance(details[1], dict) else {}
78
- template["inputs"][name] = config.get("default")
79
-
80
- return template
81
-
82
- def _load_and_merge_recipe(self, recipe_filename, dynamic_values, search_context_dir=None):
83
- search_path = search_context_dir or self.base_path
84
- recipe_path_to_use = os.path.join(search_path, recipe_filename)
85
-
86
- if not os.path.exists(recipe_path_to_use):
87
- raise FileNotFoundError(f"Recipe file not found: {recipe_path_to_use}")
88
-
89
- with open(recipe_path_to_use, 'r', encoding='utf-8') as f:
90
- content = f.read()
91
-
92
- for key, value in dynamic_values.items():
93
- if value is not None:
94
- content = content.replace(f"{{{{ {key} }}}}", str(value))
95
-
96
- main_recipe = yaml.safe_load(content)
97
-
98
- merged_recipe = {'nodes': {}, 'connections': [], 'ui_map': {}}
99
- for key in self.injector_order:
100
- if key.startswith('dynamic_'):
101
- merged_recipe[key] = {}
102
-
103
- parent_recipe_dir = os.path.dirname(recipe_path_to_use)
104
- for import_path_template in main_recipe.get('imports', []):
105
- import_path = import_path_template
106
- for key, value in dynamic_values.items():
107
- if value is not None:
108
- import_path = import_path.replace(f"{{{{ {key} }}}}", str(value))
109
-
110
- try:
111
- imported_recipe = self._load_and_merge_recipe(import_path, dynamic_values, search_context_dir=parent_recipe_dir)
112
- merged_recipe['nodes'].update(imported_recipe.get('nodes', {}))
113
- merged_recipe['connections'].extend(imported_recipe.get('connections', []))
114
- merged_recipe['ui_map'].update(imported_recipe.get('ui_map', {}))
115
- for key in self.injector_order:
116
- if key in imported_recipe and key.startswith('dynamic_'):
117
- merged_recipe[key].update(imported_recipe.get(key, {}))
118
- except FileNotFoundError:
119
- print(f"Warning: Optional recipe partial '{import_path}' not found. Skipping.")
120
-
121
- merged_recipe['nodes'].update(main_recipe.get('nodes', {}))
122
- merged_recipe['connections'].extend(main_recipe.get('connections', []))
123
- merged_recipe['ui_map'].update(main_recipe.get('ui_map', {}))
124
- for key in self.injector_order:
125
- if key in main_recipe and key.startswith('dynamic_'):
126
- merged_recipe[key].update(main_recipe.get(key, {}))
127
-
128
- return merged_recipe
129
-
130
- def assemble(self, ui_values):
131
- for name, details in self.recipe['nodes'].items():
132
- class_type = details['class_type']
133
- template = self._get_node_template(class_type)
134
- node_data = deepcopy(template)
135
-
136
- unique_id = self._get_unique_id()
137
- self.node_map[name] = unique_id
138
-
139
- if 'params' in details:
140
- for param, value in details['params'].items():
141
- if param in node_data['inputs']:
142
- node_data['inputs'][param] = value
143
-
144
- self.workflow[unique_id] = node_data
145
-
146
- for ui_key, target in self.recipe.get('ui_map', {}).items():
147
- if ui_key in ui_values and ui_values[ui_key] is not None:
148
- target_list = target if isinstance(target, list) else [target]
149
- for t in target_list:
150
- target_name, target_param = t.split(':')
151
- if target_name in self.node_map:
152
- self.workflow[self.node_map[target_name]]['inputs'][target_param] = ui_values[ui_key]
153
-
154
- for conn in self.recipe.get('connections', []):
155
- from_name, from_output_idx = conn['from'].split(':')
156
- to_name, to_input_name = conn['to'].split(':')
157
-
158
- from_id = self.node_map.get(from_name)
159
- to_id = self.node_map.get(to_name)
160
-
161
- if from_id and to_id:
162
- self.workflow[to_id]['inputs'][to_input_name] = [from_id, int(from_output_idx)]
163
-
164
- print("--- [Assembler] Applying dynamic injectors ---")
165
- recipe_chain_types = {key for key in self.recipe if key.startswith('dynamic_')}
166
- processing_order = [key for key in self.injector_order if key in recipe_chain_types]
167
-
168
- for chain_type in processing_order:
169
- injector_func = self.global_injectors.get(chain_type)
170
- if injector_func:
171
- for chain_key, chain_def in self.recipe.get(chain_type, {}).items():
172
- if chain_key in ui_values and ui_values[chain_key]:
173
- print(f" -> Injecting '{chain_type}' for '{chain_key}'...")
174
- chain_items = ui_values[chain_key]
175
- injector_func(self, chain_def, chain_items)
176
-
177
- print("--- [Assembler] Finished applying injectors ---")
178
-
179
- return self.workflow
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
- comfyui-frontend-package==1.42.14
2
- comfyui-workflow-templates==0.9.59
3
- comfyui-embedded-docs==0.4.3
4
  torch==2.10.0
5
  torchsde
6
  torchvision==0.25.0
@@ -19,11 +19,11 @@ scipy
19
  tqdm
20
  psutil
21
  alembic
22
- SQLAlchemy>=2.0
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
 
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
@@ -1,32 +1,8 @@
1
  import gradio as gr
2
- import yaml
3
- import os
4
- import shutil
5
- from functools import lru_cache
6
- from core.settings import *
7
- 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
14
-
15
-
16
- @lru_cache(maxsize=1)
17
- def load_controlnet_config():
18
- _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19
- _CN_MODEL_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'controlnet_models.yaml')
20
- try:
21
- print("--- Loading controlnet_models.yaml ---")
22
- with open(_CN_MODEL_LIST_PATH, 'r', encoding='utf-8') as f:
23
- config = yaml.safe_load(f)
24
- print("--- ✅ controlnet_models.yaml loaded successfully ---")
25
- return config.get("ControlNet", {}).get("Qwen-Image", [])
26
- except Exception as e:
27
- print(f"Error loading controlnet_models.yaml: {e}")
28
- return []
29
-
30
 
31
  def attach_event_handlers(ui_components, demo):
32
  def update_cn_input_visibility(choice):
@@ -49,7 +25,6 @@ def attach_event_handlers(ui_components, demo):
49
  return gr.update(choices=[], value=None, visible=False)
50
 
51
  def update_preprocessor_settings_ui(preprocessor_name):
52
- from ui.layout import MAX_DYNAMIC_CONTROLS
53
  params = PREPROCESSOR_PARAMETER_MAP.get(preprocessor_name, [])
54
 
55
  slider_updates, dropdown_updates, checkbox_updates = [], [], []
@@ -138,403 +113,8 @@ def attach_event_handlers(ui_components, demo):
138
  outputs=[ui_components["output_gallery_cn"]]
139
  )
140
 
141
- def create_lora_event_handlers(prefix):
142
- lora_rows = ui_components[f'lora_rows_{prefix}']
143
- lora_ids = ui_components[f'lora_ids_{prefix}']
144
- lora_scales = ui_components[f'lora_scales_{prefix}']
145
- lora_uploads = ui_components[f'lora_uploads_{prefix}']
146
- count_state = ui_components[f'lora_count_state_{prefix}']
147
- add_button = ui_components[f'add_lora_button_{prefix}']
148
- del_button = ui_components[f'delete_lora_button_{prefix}']
149
-
150
- def add_lora_row(c):
151
- updates = {}
152
- if c < MAX_LORAS:
153
- c += 1
154
- updates[lora_rows[c - 1]] = gr.update(visible=True)
155
-
156
- updates[count_state] = c
157
- updates[add_button] = gr.update(visible=c < MAX_LORAS)
158
- updates[del_button] = gr.update(visible=c > 1)
159
- return updates
160
-
161
- def del_lora_row(c):
162
- updates = {}
163
- if c > 1:
164
- updates[lora_rows[c - 1]] = gr.update(visible=False)
165
- updates[lora_ids[c - 1]] = ""
166
- updates[lora_scales[c - 1]] = 0.0
167
- updates[lora_uploads[c - 1]] = None
168
- c -= 1
169
-
170
- updates[count_state] = c
171
- updates[add_button] = gr.update(visible=True)
172
- updates[del_button] = gr.update(visible=c > 1)
173
- return updates
174
-
175
- add_outputs = [count_state, add_button, del_button] + lora_rows
176
- del_outputs = [count_state, add_button, del_button] + lora_rows + lora_ids + lora_scales + lora_uploads
177
-
178
- add_button.click(add_lora_row, [count_state], add_outputs, show_progress=False)
179
- del_button.click(del_lora_row, [count_state], del_outputs, show_progress=False)
180
-
181
- def create_controlnet_event_handlers(prefix):
182
- cn_rows = ui_components[f'controlnet_rows_{prefix}']
183
- cn_types = ui_components[f'controlnet_types_{prefix}']
184
- cn_series = ui_components[f'controlnet_series_{prefix}']
185
- cn_filepaths = ui_components[f'controlnet_filepaths_{prefix}']
186
- cn_images = ui_components[f'controlnet_images_{prefix}']
187
- cn_strengths = ui_components[f'controlnet_strengths_{prefix}']
188
-
189
- count_state = ui_components[f'controlnet_count_state_{prefix}']
190
- add_button = ui_components[f'add_controlnet_button_{prefix}']
191
- del_button = ui_components[f'delete_controlnet_button_{prefix}']
192
- accordion = ui_components[f'controlnet_accordion_{prefix}']
193
-
194
- def add_cn_row(c):
195
- c += 1
196
- updates = {
197
- count_state: c,
198
- cn_rows[c-1]: gr.update(visible=True),
199
- add_button: gr.update(visible=c < MAX_CONTROLNETS),
200
- del_button: gr.update(visible=True)
201
- }
202
- return updates
203
-
204
- def del_cn_row(c):
205
- c -= 1
206
- updates = {
207
- count_state: c,
208
- cn_rows[c]: gr.update(visible=False),
209
- cn_images[c]: None,
210
- cn_strengths[c]: 1.0,
211
- add_button: gr.update(visible=True),
212
- del_button: gr.update(visible=c > 0)
213
- }
214
- return updates
215
-
216
- add_outputs = [count_state, add_button, del_button] + cn_rows
217
- del_outputs = [count_state, add_button, del_button] + cn_rows + cn_images + cn_strengths
218
- add_button.click(fn=add_cn_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
219
- del_button.click(fn=del_cn_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
220
-
221
- def on_cn_type_change(selected_type):
222
- cn_config = load_controlnet_config()
223
- series_choices = []
224
- if selected_type:
225
- series_choices = sorted(list(set(
226
- model.get("Series", "Default") for model in cn_config
227
- if selected_type in model.get("Type", [])
228
- )))
229
- default_series = series_choices[0] if series_choices else None
230
- filepath = "None"
231
- if default_series:
232
- for model in cn_config:
233
- if model.get("Series") == default_series and selected_type in model.get("Type", []):
234
- filepath = model.get("Filepath")
235
- break
236
- return gr.update(choices=series_choices, value=default_series), filepath
237
-
238
- def on_cn_series_change(selected_series, selected_type):
239
- cn_config = load_controlnet_config()
240
- filepath = "None"
241
- if selected_series and selected_type:
242
- for model in cn_config:
243
- if model.get("Series") == selected_series and selected_type in model.get("Type", []):
244
- filepath = model.get("Filepath")
245
- break
246
- return filepath
247
-
248
- for i in range(MAX_CONTROLNETS):
249
- cn_types[i].change(
250
- fn=on_cn_type_change,
251
- inputs=[cn_types[i]],
252
- outputs=[cn_series[i], cn_filepaths[i]],
253
- show_progress=False
254
- )
255
- cn_series[i].change(
256
- fn=on_cn_series_change,
257
- inputs=[cn_series[i], cn_types[i]],
258
- outputs=[cn_filepaths[i]],
259
- show_progress=False
260
- )
261
-
262
- def on_accordion_expand(*images):
263
- return [gr.update() for _ in images]
264
-
265
- accordion.expand(
266
- fn=on_accordion_expand,
267
- inputs=cn_images,
268
- outputs=cn_images,
269
- show_progress=False
270
- )
271
-
272
- def create_embedding_event_handlers(prefix):
273
- rows = ui_components[f'embedding_rows_{prefix}']
274
- ids = ui_components[f'embeddings_ids_{prefix}']
275
- files = ui_components[f'embeddings_files_{prefix}']
276
- count_state = ui_components[f'embedding_count_state_{prefix}']
277
- add_button = ui_components[f'add_embedding_button_{prefix}']
278
- del_button = ui_components[f'delete_embedding_button_{prefix}']
279
-
280
- def add_row(c):
281
- c += 1
282
- return {
283
- count_state: c,
284
- rows[c - 1]: gr.update(visible=True),
285
- add_button: gr.update(visible=c < MAX_EMBEDDINGS),
286
- del_button: gr.update(visible=True)
287
- }
288
-
289
- def del_row(c):
290
- c -= 1
291
- return {
292
- count_state: c,
293
- rows[c]: gr.update(visible=False),
294
- ids[c]: "",
295
- files[c]: None,
296
- add_button: gr.update(visible=True),
297
- del_button: gr.update(visible=c > 0)
298
- }
299
-
300
- add_outputs = [count_state, add_button, del_button] + rows
301
- del_outputs = [count_state, add_button, del_button] + rows + ids + files
302
- add_button.click(fn=add_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
303
- del_button.click(fn=del_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
304
-
305
- def create_conditioning_event_handlers(prefix):
306
- rows = ui_components[f'conditioning_rows_{prefix}']
307
- prompts = ui_components[f'conditioning_prompts_{prefix}']
308
- count_state = ui_components[f'conditioning_count_state_{prefix}']
309
- add_button = ui_components[f'add_conditioning_button_{prefix}']
310
- del_button = ui_components[f'delete_conditioning_button_{prefix}']
311
-
312
- def add_row(c):
313
- c += 1
314
- return {
315
- count_state: c,
316
- rows[c - 1]: gr.update(visible=True),
317
- add_button: gr.update(visible=c < MAX_CONDITIONINGS),
318
- del_button: gr.update(visible=True),
319
- }
320
-
321
- def del_row(c):
322
- c -= 1
323
- return {
324
- count_state: c,
325
- rows[c]: gr.update(visible=False),
326
- prompts[c]: "",
327
- add_button: gr.update(visible=True),
328
- del_button: gr.update(visible=c > 0),
329
- }
330
-
331
- add_outputs = [count_state, add_button, del_button] + rows
332
- del_outputs = [count_state, add_button, del_button] + rows + prompts
333
- add_button.click(fn=add_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
334
- del_button.click(fn=del_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
335
-
336
- def on_vae_upload(file_obj):
337
- if not file_obj:
338
- return gr.update(), gr.update(), None
339
-
340
- hashed_filename = save_uploaded_file_with_hash(file_obj, VAE_DIR)
341
- return hashed_filename, "File", file_obj
342
-
343
- def on_lora_upload(file_obj):
344
- if not file_obj:
345
- return gr.update(), gr.update()
346
-
347
- hashed_filename = save_uploaded_file_with_hash(file_obj, LORA_DIR)
348
- return hashed_filename, "File"
349
-
350
- def on_embedding_upload(file_obj):
351
- if not file_obj:
352
- return gr.update(), gr.update(), None
353
-
354
- hashed_filename = save_uploaded_file_with_hash(file_obj, EMBEDDING_DIR)
355
- return hashed_filename, "File", file_obj
356
-
357
-
358
- def create_run_event(prefix: str, task_type: str):
359
- run_inputs_map = {
360
- 'model_display_name': ui_components[f'base_model_{prefix}'],
361
- 'positive_prompt': ui_components[f'prompt_{prefix}'],
362
- 'negative_prompt': ui_components[f'neg_prompt_{prefix}'],
363
- 'seed': ui_components[f'seed_{prefix}'],
364
- 'batch_size': ui_components[f'batch_size_{prefix}'],
365
- 'guidance_scale': ui_components[f'cfg_{prefix}'],
366
- 'num_inference_steps': ui_components[f'steps_{prefix}'],
367
- 'sampler': ui_components[f'sampler_{prefix}'],
368
- 'scheduler': ui_components[f'scheduler_{prefix}'],
369
- 'zero_gpu_duration': ui_components[f'zero_gpu_{prefix}'],
370
- 'civitai_api_key': ui_components.get(f'civitai_api_key_{prefix}'),
371
- 'clip_skip': ui_components[f'clip_skip_{prefix}'],
372
- 'task_type': gr.State(task_type)
373
- }
374
-
375
- if task_type not in ['img2img', 'inpaint']:
376
- run_inputs_map.update({'width': ui_components[f'width_{prefix}'], 'height': ui_components[f'height_{prefix}']})
377
-
378
- task_specific_map = {
379
- 'img2img': {'img2img_image': f'input_image_{prefix}', 'img2img_denoise': f'denoise_{prefix}'},
380
- 'inpaint': {'inpaint_image_dict': f'input_image_dict_{prefix}'},
381
- '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}'},
382
- '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}'}
383
- }
384
- if task_type in task_specific_map:
385
- for key, comp_name in task_specific_map[task_type].items():
386
- run_inputs_map[key] = ui_components[comp_name]
387
-
388
- lora_data_components = ui_components.get(f'all_lora_components_flat_{prefix}', [])
389
- controlnet_data_components = ui_components.get(f'all_controlnet_components_flat_{prefix}', [])
390
- embedding_data_components = ui_components.get(f'all_embedding_components_flat_{prefix}', [])
391
- conditioning_data_components = ui_components.get(f'all_conditioning_components_flat_{prefix}', [])
392
-
393
- run_inputs_map['vae_source'] = ui_components.get(f'vae_source_{prefix}')
394
- run_inputs_map['vae_id'] = ui_components.get(f'vae_id_{prefix}')
395
- run_inputs_map['vae_file'] = ui_components.get(f'vae_file_{prefix}')
396
-
397
- input_keys = list(run_inputs_map.keys())
398
- input_list_flat = [v for v in run_inputs_map.values() if v is not None]
399
- input_list_flat += lora_data_components + controlnet_data_components + embedding_data_components + conditioning_data_components
400
-
401
- def create_ui_inputs_dict(*args):
402
- valid_keys = [k for k in input_keys if run_inputs_map[k] is not None]
403
- ui_dict = dict(zip(valid_keys, args[:len(valid_keys)]))
404
- arg_idx = len(valid_keys)
405
-
406
- ui_dict['lora_data'] = list(args[arg_idx : arg_idx + len(lora_data_components)])
407
- arg_idx += len(lora_data_components)
408
- ui_dict['controlnet_data'] = list(args[arg_idx : arg_idx + len(controlnet_data_components)])
409
- arg_idx += len(controlnet_data_components)
410
- ui_dict['embedding_data'] = list(args[arg_idx : arg_idx + len(embedding_data_components)])
411
- arg_idx += len(embedding_data_components)
412
- ui_dict['conditioning_data'] = list(args[arg_idx : arg_idx + len(conditioning_data_components)])
413
-
414
- return ui_dict
415
-
416
- ui_components[f'run_{prefix}'].click(
417
- fn=lambda *args, progress=gr.Progress(track_tqdm=True): generate_image_wrapper(create_ui_inputs_dict(*args), progress),
418
- inputs=input_list_flat,
419
- outputs=[ui_components[f'result_{prefix}']]
420
- )
421
-
422
-
423
- for prefix, task_type in [
424
- ("txt2img", "txt2img"), ("img2img", "img2img"), ("inpaint", "inpaint"),
425
- ("outpaint", "outpaint"), ("hires_fix", "hires_fix"),
426
- ]:
427
- if f'add_lora_button_{prefix}' in ui_components:
428
- create_lora_event_handlers(prefix)
429
- lora_uploads = ui_components[f'lora_uploads_{prefix}']
430
- lora_ids = ui_components[f'lora_ids_{prefix}']
431
- lora_sources = ui_components[f'lora_sources_{prefix}']
432
- for i in range(MAX_LORAS):
433
- lora_uploads[i].upload(
434
- fn=on_lora_upload,
435
- inputs=[lora_uploads[i]],
436
- outputs=[lora_ids[i], lora_sources[i]],
437
- show_progress=False
438
- )
439
- if f'add_controlnet_button_{prefix}' in ui_components: create_controlnet_event_handlers(prefix)
440
- if f'add_embedding_button_{prefix}' in ui_components:
441
- create_embedding_event_handlers(prefix)
442
- if f'embeddings_uploads_{prefix}' in ui_components:
443
- emb_uploads = ui_components[f'embeddings_uploads_{prefix}']
444
- emb_ids = ui_components[f'embeddings_ids_{prefix}']
445
- emb_sources = ui_components[f'embeddings_sources_{prefix}']
446
- emb_files = ui_components[f'embeddings_files_{prefix}']
447
- for i in range(MAX_EMBEDDINGS):
448
- emb_uploads[i].upload(
449
- fn=on_embedding_upload,
450
- inputs=[emb_uploads[i]],
451
- outputs=[emb_ids[i], emb_sources[i], emb_files[i]],
452
- show_progress=False
453
- )
454
- if f'add_conditioning_button_{prefix}' in ui_components: create_conditioning_event_handlers(prefix)
455
- if f'vae_source_{prefix}' in ui_components:
456
- upload_button = ui_components.get(f'vae_upload_button_{prefix}')
457
- if upload_button:
458
- upload_button.upload(
459
- fn=on_vae_upload,
460
- inputs=[upload_button],
461
- outputs=[
462
- ui_components[f'vae_id_{prefix}'],
463
- ui_components[f'vae_source_{prefix}'],
464
- ui_components[f'vae_file_{prefix}']
465
- ]
466
- )
467
-
468
- create_run_event(prefix, task_type)
469
-
470
- def on_aspect_ratio_change(ratio_key, model_display_name):
471
- model_type = MODEL_TYPE_MAP.get(model_display_name, 'sdxl').lower()
472
- res_map = RESOLUTION_MAP.get(model_type, RESOLUTION_MAP.get("sdxl", {}))
473
- w, h = res_map.get(ratio_key, (1024, 1024))
474
- return w, h
475
-
476
- for prefix in ["txt2img", "img2img", "outpaint", "hires_fix"]:
477
- if f'aspect_ratio_{prefix}' in ui_components:
478
- aspect_ratio_dropdown = ui_components[f'aspect_ratio_{prefix}']
479
- width_component = ui_components[f'width_{prefix}']
480
- height_component = ui_components[f'height_{prefix}']
481
- model_dropdown = ui_components[f'base_model_{prefix}']
482
- aspect_ratio_dropdown.change(fn=on_aspect_ratio_change, inputs=[aspect_ratio_dropdown, model_dropdown], outputs=[width_component, height_component], show_progress=False)
483
-
484
- if 'view_mode_inpaint' in ui_components:
485
- def toggle_inpaint_fullscreen_view(view_mode):
486
- is_fullscreen = (view_mode == "Fullscreen View")
487
- other_elements_visible = not is_fullscreen
488
- editor_height = 800 if is_fullscreen else 272
489
- return {
490
- ui_components['model_and_run_row_inpaint']: gr.update(visible=other_elements_visible),
491
- ui_components['prompts_column_inpaint']: gr.update(visible=other_elements_visible),
492
- ui_components['params_and_gallery_row_inpaint']: gr.update(visible=other_elements_visible),
493
- ui_components['accordion_wrapper_inpaint']: gr.update(visible=other_elements_visible),
494
- ui_components['input_image_dict_inpaint']: gr.update(height=editor_height),
495
- }
496
-
497
- output_components = [
498
- ui_components['model_and_run_row_inpaint'], ui_components['prompts_column_inpaint'],
499
- ui_components['params_and_gallery_row_inpaint'], ui_components['accordion_wrapper_inpaint'],
500
- ui_components['input_image_dict_inpaint']
501
- ]
502
- ui_components['view_mode_inpaint'].change(fn=toggle_inpaint_fullscreen_view, inputs=[ui_components['view_mode_inpaint']], outputs=output_components, show_progress=False)
503
-
504
- def initialize_all_cn_dropdowns():
505
- cn_config = load_controlnet_config()
506
- if not cn_config: return {}
507
-
508
- all_types = sorted(list(set(t for model in cn_config for t in model.get("Type", []))))
509
- default_type = all_types[0] if all_types else None
510
-
511
- series_choices = []
512
- if default_type:
513
- series_choices = sorted(list(set(model.get("Series", "Default") for model in cn_config if default_type in model.get("Type", []))))
514
- default_series = series_choices[0] if series_choices else None
515
-
516
- filepath = "None"
517
- if default_series and default_type:
518
- for model in cn_config:
519
- if model.get("Series") == default_series and default_type in model.get("Type", []):
520
- filepath = model.get("Filepath")
521
- break
522
-
523
- updates = {}
524
- for prefix in ["txt2img", "img2img", "inpaint", "outpaint", "hires_fix"]:
525
- if f'controlnet_types_{prefix}' in ui_components:
526
- for type_dd in ui_components[f'controlnet_types_{prefix}']:
527
- updates[type_dd] = gr.update(choices=all_types, value=default_type)
528
- for series_dd in ui_components[f'controlnet_series_{prefix}']:
529
- updates[series_dd] = gr.update(choices=series_choices, value=default_series)
530
- for filepath_state in ui_components[f'controlnet_filepaths_{prefix}']:
531
- updates[filepath_state] = filepath
532
- return updates
533
-
534
  def run_on_load():
535
  all_updates = {}
536
- cn_updates = initialize_all_cn_dropdowns()
537
- all_updates.update(cn_updates)
538
 
539
  default_preprocessor = "Canny Edge"
540
  model_update = update_preprocessor_models_dropdown(default_preprocessor)
@@ -551,24 +131,16 @@ def attach_event_handlers(ui_components, demo):
551
 
552
  return all_updates
553
 
554
- all_load_outputs = []
555
- for prefix in ["txt2img", "img2img", "inpaint", "outpaint", "hires_fix"]:
556
- if f'controlnet_types_{prefix}' in ui_components:
557
- all_load_outputs.extend(ui_components[f'controlnet_types_{prefix}'])
558
- all_load_outputs.extend(ui_components[f'controlnet_series_{prefix}'])
559
- all_load_outputs.extend(ui_components[f'controlnet_filepaths_{prefix}'])
560
-
561
- all_load_outputs.extend([
562
  ui_components["preprocessor_model_cn"],
563
  *ui_components["cn_sliders"],
564
  *ui_components["cn_dropdowns"],
565
  *ui_components["cn_checkboxes"],
566
  ui_components["run_cn"],
567
  ui_components["zero_gpu_cn"]
568
- ])
569
 
570
- if all_load_outputs:
571
- demo.load(
572
- fn=run_on_load,
573
- outputs=all_load_outputs
574
- )
 
1
  import gradio as gr
2
+ from core.generation_logic import run_cn_preprocessor_entry
 
 
 
 
 
 
 
 
3
  from core.pipelines.controlnet_preprocessor import CPU_ONLY_PREPROCESSORS
4
+ from utils.app_utils import PREPROCESSOR_MODEL_MAP, PREPROCESSOR_PARAMETER_MAP
5
+ from ui.layout import MAX_DYNAMIC_CONTROLS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  def attach_event_handlers(ui_components, demo):
8
  def update_cn_input_visibility(choice):
 
25
  return gr.update(choices=[], value=None, visible=False)
26
 
27
  def update_preprocessor_settings_ui(preprocessor_name):
 
28
  params = PREPROCESSOR_PARAMETER_MAP.get(preprocessor_name, [])
29
 
30
  slider_updates, dropdown_updates, checkbox_updates = [], [], []
 
113
  outputs=[ui_components["output_gallery_cn"]]
114
  )
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  def run_on_load():
117
  all_updates = {}
 
 
118
 
119
  default_preprocessor = "Canny Edge"
120
  model_update = update_preprocessor_models_dropdown(default_preprocessor)
 
131
 
132
  return all_updates
133
 
134
+ all_load_outputs = [
 
 
 
 
 
 
 
135
  ui_components["preprocessor_model_cn"],
136
  *ui_components["cn_sliders"],
137
  *ui_components["cn_dropdowns"],
138
  *ui_components["cn_checkboxes"],
139
  ui_components["run_cn"],
140
  ui_components["zero_gpu_cn"]
141
+ ]
142
 
143
+ demo.load(
144
+ fn=run_on_load,
145
+ outputs=all_load_outputs
146
+ )
 
ui/layout.py CHANGED
@@ -1,8 +1,5 @@
1
  import os
2
  import gradio as gr
3
- from core.settings import *
4
-
5
- from .shared import txt2img_ui, img2img_ui, inpaint_ui, outpaint_ui, hires_fix_ui
6
 
7
  MAX_DYNAMIC_CONTROLS = 10
8
 
@@ -16,72 +13,39 @@ def get_preprocessor_choices():
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 - Qwen-Image")
25
- gr.Markdown(
26
- "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. "
27
- "Other versions are also available: "
28
- "[FLUX.2](https://huggingface.co/spaces/RioShiina/ImageGen-FLUX.2), "
29
- "[Z-Image](https://huggingface.co/spaces/RioShiina/ImageGen-Z-Image), "
30
- "[Anima](https://huggingface.co/spaces/RioShiina/ImageGen-Anima), "
31
- "[Illustrious](https://huggingface.co/spaces/RioShiina/ImageGen-Illustrious), "
32
- "[NoobAI](https://huggingface.co/spaces/RioShiina/ImageGen-NoobAI), "
33
- "[Pony](https://huggingface.co/spaces/RioShiina/ImageGen-Pony)"
34
- )
35
- with gr.Tabs(elem_id="tabs_container") as tabs:
36
- with gr.TabItem("ImageGen", id=0):
37
- with gr.Tabs(elem_id="image_gen_tabs") as image_gen_tabs:
38
- with gr.TabItem("Txt2Img", id=0):
39
- ui_components.update(txt2img_ui.create_ui())
40
-
41
- with gr.TabItem("Img2Img", id=1):
42
- ui_components.update(img2img_ui.create_ui())
43
-
44
- with gr.TabItem("Inpaint", id=2):
45
- ui_components.update(inpaint_ui.create_ui())
46
-
47
- with gr.TabItem("Outpaint", id=3):
48
- ui_components.update(outpaint_ui.create_ui())
49
-
50
- with gr.TabItem("Hires. Fix", id=4):
51
- ui_components.update(hires_fix_ui.create_ui())
52
-
53
- ui_components['image_gen_tabs'] = image_gen_tabs
54
-
55
- with gr.TabItem("Controlnet Preprocessors", id=1):
56
- gr.Markdown("## ControlNet Auxiliary Preprocessors")
57
- gr.Markdown("Powered by [Fannovel16/comfyui_controlnet_aux](https://github.com/Fannovel16/comfyui_controlnet_aux).")
58
- gr.Markdown("Upload an image or video to process it with a ControlNet preprocessor.")
59
- with gr.Row():
60
- with gr.Column(scale=1):
61
- cn_input_type = gr.Radio(["Image", "Video"], label="Input Type", value="Image")
62
- cn_image_input = gr.Image(type="pil", label="Input Image", visible=True, height=384)
63
- cn_video_input = gr.Video(label="Input Video", visible=False)
64
- preprocessor_cn = gr.Dropdown(label="Preprocessor", choices=get_preprocessor_choices(), value="Canny Edge")
65
- preprocessor_model_cn = gr.Dropdown(label="Preprocessor Model", choices=[], value=None, visible=False)
66
- with gr.Column() as preprocessor_settings_ui:
67
- cn_sliders, cn_dropdowns, cn_checkboxes = [], [], []
68
- for i in range(MAX_DYNAMIC_CONTROLS):
69
- cn_sliders.append(gr.Slider(visible=False, label=f"dyn_slider_{i}"))
70
- cn_dropdowns.append(gr.Dropdown(visible=False, label=f"dyn_dropdown_{i}"))
71
- cn_checkboxes.append(gr.Checkbox(visible=False, label=f"dyn_checkbox_{i}"))
72
- run_cn = gr.Button("Run Preprocessor", variant="primary")
73
- with gr.Column(scale=1):
74
- output_gallery_cn = gr.Gallery(label="Output", show_label=False, object_fit="contain", height=512)
75
- zero_gpu_cn = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60s, Max: 120s", info="Optional")
76
- ui_components.update({
77
- "cn_input_type": cn_input_type, "cn_image_input": cn_image_input, "cn_video_input": cn_video_input,
78
- "preprocessor_cn": preprocessor_cn, "preprocessor_model_cn": preprocessor_model_cn, "run_cn": run_cn,
79
- "zero_gpu_cn": zero_gpu_cn, "output_gallery_cn": output_gallery_cn,
80
- "preprocessor_settings_ui": preprocessor_settings_ui, "cn_sliders": cn_sliders,
81
- "cn_dropdowns": cn_dropdowns, "cn_checkboxes": cn_checkboxes
82
- })
83
 
84
- ui_components['tabs'] = tabs
 
 
 
 
 
 
85
 
86
  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>")
87
 
 
1
  import os
2
  import gradio as gr
 
 
 
3
 
4
  MAX_DYNAMIC_CONTROLS = 10
5
 
 
13
  ]
14
  return sorted(list(set(preprocessor_names)))
15
 
 
16
  def build_ui(event_handler_function):
17
  ui_components = {}
18
 
19
  with gr.Blocks() as demo:
20
+ gr.Markdown("# ControlNet Preprocessors")
21
+ gr.Markdown("Powered by [Fannovel16/comfyui_controlnet_aux](https://github.com/Fannovel16/comfyui_controlnet_aux).")
22
+ gr.Markdown("Upload an image or video to process it with a ControlNet preprocessor.")
23
+
24
+ with gr.Row():
25
+ with gr.Column(scale=1):
26
+ cn_input_type = gr.Radio(["Image", "Video"], label="Input Type", value="Image")
27
+ cn_image_input = gr.Image(type="pil", label="Input Image", visible=True, height=384)
28
+ cn_video_input = gr.Video(label="Input Video", visible=False)
29
+ preprocessor_cn = gr.Dropdown(label="Preprocessor", choices=get_preprocessor_choices(), value="Canny Edge")
30
+ preprocessor_model_cn = gr.Dropdown(label="Preprocessor Model", choices=[], value=None, visible=False)
31
+ with gr.Column() as preprocessor_settings_ui:
32
+ cn_sliders, cn_dropdowns, cn_checkboxes = [], [], []
33
+ for i in range(MAX_DYNAMIC_CONTROLS):
34
+ cn_sliders.append(gr.Slider(visible=False, label=f"dyn_slider_{i}"))
35
+ cn_dropdowns.append(gr.Dropdown(visible=False, label=f"dyn_dropdown_{i}"))
36
+ cn_checkboxes.append(gr.Checkbox(visible=False, label=f"dyn_checkbox_{i}"))
37
+ run_cn = gr.Button("Run Preprocessor", variant="primary")
38
+ with gr.Column(scale=1):
39
+ output_gallery_cn = gr.Gallery(label="Output", show_label=False, object_fit="contain", height=512)
40
+ zero_gpu_cn = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60s, Max: 120s", info="Optional")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ ui_components.update({
43
+ "cn_input_type": cn_input_type, "cn_image_input": cn_image_input, "cn_video_input": cn_video_input,
44
+ "preprocessor_cn": preprocessor_cn, "preprocessor_model_cn": preprocessor_model_cn, "run_cn": run_cn,
45
+ "zero_gpu_cn": zero_gpu_cn, "output_gallery_cn": output_gallery_cn,
46
+ "preprocessor_settings_ui": preprocessor_settings_ui, "cn_sliders": cn_sliders,
47
+ "cn_dropdowns": cn_dropdowns, "cn_checkboxes": cn_checkboxes
48
+ })
49
 
50
  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>")
51
 
ui/shared/hires_fix_ui.py DELETED
@@ -1,73 +0,0 @@
1
- import gradio as gr
2
- 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_controlnet_ui, create_embedding_ui,
7
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui
8
- )
9
-
10
- def create_ui():
11
- prefix = "hires_fix"
12
- components = {}
13
-
14
- with gr.Column():
15
- with gr.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 Hires. Fix", variant="primary")
24
-
25
- with gr.Row():
26
- with gr.Column(scale=1):
27
- components[f'input_image_{prefix}'] = gr.Image(type="pil", label="Input Image", height=255)
28
- with gr.Column(scale=2):
29
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3, placeholder="Describe the final image...")
30
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3, value="")
31
-
32
- with gr.Row():
33
- with gr.Column(scale=1):
34
- with gr.Row():
35
- components[f'hires_upscaler_{prefix}'] = gr.Dropdown(
36
- label="Upscaler",
37
- choices=["nearest-exact", "bilinear", "area", "bicubic", "bislerp"],
38
- value="nearest-exact"
39
- )
40
- components[f'hires_scale_by_{prefix}'] = gr.Slider(
41
- label="Upscale by", minimum=1.0, maximum=4.0, step=0.1, value=1.5
42
- )
43
-
44
- with gr.Row():
45
- components[f'denoise_{prefix}'] = gr.Slider(label="Denoise Strength", minimum=0.0, maximum=1.0, step=0.01, value=0.55)
46
-
47
- with gr.Row():
48
- components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value="euler")
49
- components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value="simple")
50
- with gr.Row():
51
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=4)
52
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=1.0)
53
- with gr.Row():
54
- components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
55
- components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
56
- with gr.Row():
57
- 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.")
58
-
59
- components[f'clip_skip_{prefix}'] = gr.State(value=1)
60
- components[f'width_{prefix}'] = gr.State(value=512)
61
- components[f'height_{prefix}'] = gr.State(value=512)
62
-
63
- with gr.Column(scale=1):
64
- components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=610)
65
-
66
- components.update(create_api_key_ui(prefix))
67
- components.update(create_lora_settings_ui(prefix))
68
- components.update(create_controlnet_ui(prefix))
69
- # components.update(create_embedding_ui(prefix))
70
- components.update(create_conditioning_ui(prefix))
71
- # components.update(create_vae_override_ui(prefix))
72
-
73
- return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/shared/img2img_ui.py DELETED
@@ -1,56 +0,0 @@
1
- import gradio as gr
2
- 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_controlnet_ui, create_embedding_ui,
7
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui
8
- )
9
-
10
- def create_ui():
11
- prefix = "img2img"
12
- components = {}
13
-
14
- with gr.Column():
15
- with gr.Row():
16
- 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)
17
- with gr.Column(scale=1):
18
- components[f'run_{prefix}'] = gr.Button("Run", variant="primary")
19
-
20
- with gr.Row():
21
- with gr.Column(scale=1):
22
- components[f'input_image_{prefix}'] = gr.Image(type="pil", label="Input Image", height=255)
23
-
24
- with gr.Column(scale=2):
25
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3, placeholder="Enter your prompt")
26
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3, value="")
27
-
28
- with gr.Row():
29
- with gr.Column(scale=1):
30
- components[f'denoise_{prefix}'] = gr.Slider(label="Denoise Strength", minimum=0.0, maximum=1.0, step=0.01, value=0.7)
31
-
32
- with gr.Row():
33
- components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value="euler")
34
- components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value="simple")
35
- with gr.Row():
36
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=4)
37
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=1.0)
38
- with gr.Row():
39
- components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
40
- components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
41
- with gr.Row():
42
- 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.")
43
-
44
- components[f'clip_skip_{prefix}'] = gr.State(value=1)
45
-
46
- with gr.Column(scale=1):
47
- components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=505)
48
-
49
- components.update(create_api_key_ui(prefix))
50
- components.update(create_lora_settings_ui(prefix))
51
- components.update(create_controlnet_ui(prefix))
52
- # components.update(create_embedding_ui(prefix))
53
- components.update(create_conditioning_ui(prefix))
54
- # components.update(create_vae_override_ui(prefix))
55
-
56
- return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/shared/inpaint_ui.py DELETED
@@ -1,80 +0,0 @@
1
- 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_controlnet_ui, create_embedding_ui,
6
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui
7
- )
8
-
9
- def create_ui():
10
- prefix = "inpaint"
11
- components = {}
12
-
13
- with gr.Column():
14
- with gr.Row() as model_and_run_row:
15
- components[f'base_model_{prefix}'] = gr.Dropdown(
16
- label="Base Model",
17
- choices=list(MODEL_MAP_CHECKPOINT.keys()),
18
- value=list(MODEL_MAP_CHECKPOINT.keys())[0],
19
- scale=3
20
- )
21
- with gr.Column(scale=1):
22
- components[f'run_{prefix}'] = gr.Button("Run Inpaint", variant="primary")
23
-
24
- components[f'model_and_run_row_{prefix}'] = model_and_run_row
25
-
26
- with gr.Row() as main_content_row:
27
- with gr.Column(scale=1) as editor_column:
28
- components[f'view_mode_{prefix}'] = gr.Radio(
29
- ["Normal View", "Fullscreen View"],
30
- label="Editor View",
31
- value="Normal View",
32
- interactive=True
33
- )
34
- components[f'input_image_dict_{prefix}'] = gr.ImageEditor(
35
- type="pil",
36
- label="Image & Mask",
37
- height=272
38
- )
39
- components[f'editor_column_{prefix}'] = editor_column
40
-
41
- with gr.Column(scale=2) as prompts_column:
42
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=6, placeholder="Describe what to fill in the mask...")
43
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=6, value="")
44
- components[f'prompts_column_{prefix}'] = prompts_column
45
-
46
- with gr.Row() as params_and_gallery_row:
47
- with gr.Column(scale=1):
48
- param_defaults = {'w': 1024, 'h': 1024, 'cs_vis': False, 'cs_val': 1}
49
- from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
50
- with gr.Row():
51
- components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value="euler")
52
- components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value="simple")
53
- with gr.Row():
54
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=4)
55
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=1.0)
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'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.")
61
-
62
- components[f'clip_skip_{prefix}'] = gr.State(value=1)
63
- components[f'width_{prefix}'] = gr.State(value=512)
64
- components[f'height_{prefix}'] = gr.State(value=512)
65
-
66
- with gr.Column(scale=1):
67
- components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=414)
68
-
69
- components[f'params_and_gallery_row_{prefix}'] = params_and_gallery_row
70
-
71
- with gr.Column() as accordion_wrapper:
72
- components.update(create_api_key_ui(prefix))
73
- components.update(create_lora_settings_ui(prefix))
74
- components.update(create_controlnet_ui(prefix))
75
- # components.update(create_embedding_ui(prefix))
76
- components.update(create_conditioning_ui(prefix))
77
- # components.update(create_vae_override_ui(prefix))
78
- components[f'accordion_wrapper_{prefix}'] = accordion_wrapper
79
-
80
- return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/shared/outpaint_ui.py DELETED
@@ -1,67 +0,0 @@
1
- import gradio as gr
2
- 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_controlnet_ui, create_embedding_ui,
7
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui
8
- )
9
-
10
- def create_ui():
11
- prefix = "outpaint"
12
- components = {}
13
-
14
- with gr.Column():
15
- with gr.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 Outpaint", variant="primary")
24
-
25
- with gr.Row():
26
- with gr.Column(scale=1):
27
- components[f'input_image_{prefix}'] = gr.Image(type="pil", label="Input Image", height=255)
28
- with gr.Column(scale=2):
29
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3, placeholder="Describe the content for the expanded areas...")
30
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3, value="")
31
-
32
- with gr.Row():
33
- with gr.Column(scale=1):
34
- with gr.Row():
35
- components[f'outpaint_left_{prefix}'] = gr.Slider(label="Pad Left", minimum=0, maximum=512, step=64, value=0)
36
- components[f'outpaint_right_{prefix}'] = gr.Slider(label="Pad Right", minimum=0, maximum=512, step=64, value=256)
37
- with gr.Row():
38
- components[f'outpaint_top_{prefix}'] = gr.Slider(label="Pad Top", minimum=0, maximum=512, step=64, value=0)
39
- components[f'outpaint_bottom_{prefix}'] = gr.Slider(label="Pad Bottom", minimum=0, maximum=512, step=64, value=0)
40
-
41
- with gr.Row():
42
- components[f'sampler_{prefix}'] = gr.Dropdown(label="Sampler", choices=SAMPLER_CHOICES, value="euler")
43
- components[f'scheduler_{prefix}'] = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value="simple")
44
- with gr.Row():
45
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=4)
46
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=1.0)
47
- with gr.Row():
48
- components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
49
- components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
50
- with gr.Row():
51
- 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.")
52
-
53
- components[f'clip_skip_{prefix}'] = gr.State(value=1)
54
- components[f'width_{prefix}'] = gr.State(value=512)
55
- components[f'height_{prefix}'] = gr.State(value=512)
56
-
57
- with gr.Column(scale=1):
58
- components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=1, object_fit="contain", height=595)
59
-
60
- components.update(create_api_key_ui(prefix))
61
- components.update(create_lora_settings_ui(prefix))
62
- components.update(create_controlnet_ui(prefix))
63
- # components.update(create_embedding_ui(prefix))
64
- components.update(create_conditioning_ui(prefix))
65
- # components.update(create_vae_override_ui(prefix))
66
-
67
- return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/shared/txt2img_ui.py DELETED
@@ -1,37 +0,0 @@
1
- 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_controlnet_ui, create_embedding_ui,
6
- create_conditioning_ui, create_vae_override_ui, create_api_key_ui
7
- )
8
-
9
- def create_ui():
10
- """Creates the UI components for the Txt2Img tab."""
11
- prefix = "txt2img"
12
- components = {}
13
-
14
- with gr.Column():
15
- with gr.Row():
16
- 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)
17
- with gr.Column(scale=1):
18
- components[f'run_{prefix}'] = gr.Button("Run", variant="primary")
19
-
20
- components[f'prompt_{prefix}'] = gr.Text(label="Prompt", lines=3, placeholder="Enter your prompt")
21
- components[f'neg_prompt_{prefix}'] = gr.Text(label="Negative prompt", lines=3, value="")
22
-
23
- with gr.Row():
24
- with gr.Column(scale=1):
25
- param_defaults = {'w': 1328, 'h': 1328, 'cs_vis': False, 'cs_val': 1}
26
- components.update(create_base_parameter_ui(prefix, param_defaults))
27
- with gr.Column(scale=1):
28
- components[f'result_{prefix}'] = gr.Gallery(label="Result", show_label=False, columns=2, object_fit="contain", height=627)
29
-
30
- components.update(create_api_key_ui(prefix))
31
- components.update(create_lora_settings_ui(prefix))
32
- components.update(create_controlnet_ui(prefix))
33
- # components.update(create_embedding_ui(prefix))
34
- components.update(create_conditioning_ui(prefix))
35
- # components.update(create_vae_override_ui(prefix))
36
-
37
- return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/shared/ui_components.py DELETED
@@ -1,249 +0,0 @@
1
- import gradio as gr
2
- from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
3
- from core.settings import (
4
- MAX_LORAS, LORA_SOURCE_CHOICES, MAX_EMBEDDINGS, MAX_CONDITIONINGS,
5
- MAX_CONTROLNETS, RESOLUTION_MAP
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 = {}
14
-
15
- components = {}
16
-
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', 1328), interactive=True)
26
- components[f'height_{prefix}'] = gr.Number(label="Height", value=defaults.get('h', 1328), 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: 60s, Max: 120s", 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)
67
-
68
- for i in range(MAX_LORAS):
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)
76
- lora_sources.append(source)
77
- lora_ids.append(lora_id)
78
- lora_scales.append(scale)
79
- lora_uploads.append(upload)
80
-
81
- with gr.Row():
82
- components[f'add_lora_button_{prefix}'] = gr.Button("Add LoRA", variant="secondary")
83
- components[f'delete_lora_button_{prefix}'] = gr.Button("Remove LoRA", variant="secondary", visible=False)
84
-
85
- components[f'lora_rows_{prefix}'] = lora_rows
86
- components[f'lora_sources_{prefix}'] = lora_sources
87
- components[f'lora_ids_{prefix}'] = lora_ids
88
- components[f'lora_scales_{prefix}'] = lora_scales
89
- components[f'lora_uploads_{prefix}'] = lora_uploads
90
-
91
- all_lora_components_flat = []
92
- for i in range(MAX_LORAS):
93
- all_lora_components_flat.extend([lora_sources[i], lora_ids[i], lora_scales[i], lora_uploads[i]])
94
- components[f'all_lora_components_flat_{prefix}'] = all_lora_components_flat
95
-
96
- return components
97
-
98
- def create_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
99
- components = {}
100
- key = lambda name: f"{name}_{prefix}"
101
-
102
- with gr.Accordion("ControlNet Settings", open=False) as accordion:
103
- components[key('controlnet_accordion')] = accordion
104
-
105
- cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
106
- components.update({
107
- key('controlnet_rows'): cn_rows,
108
- key('controlnet_images'): images,
109
- key('controlnet_series'): series,
110
- key('controlnet_types'): types,
111
- key('controlnet_strengths'): strengths,
112
- key('controlnet_filepaths'): filepaths
113
- })
114
-
115
- for i in range(max_units):
116
- with gr.Row(visible=(i < 1)) as row:
117
- with gr.Column(scale=1):
118
- images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
119
- with gr.Column(scale=2):
120
- types.append(gr.Dropdown(label="Type", choices=[], interactive=True))
121
- series.append(gr.Dropdown(label="Series", choices=[], interactive=True))
122
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
123
- filepaths.append(gr.State(None))
124
- cn_rows.append(row)
125
-
126
- with gr.Row():
127
- components[key('add_controlnet_button')] = gr.Button("✚ Add ControlNet")
128
- components[key('delete_controlnet_button')] = gr.Button("➖ Delete ControlNet", visible=False)
129
- components[key('controlnet_count_state')] = gr.State(1)
130
-
131
- all_cn_components_flat = []
132
- for i in range(max_units):
133
- all_cn_components_flat.extend([
134
- images[i], types[i], series[i], strengths[i], filepaths[i]
135
- ])
136
- components[key('all_controlnet_components_flat')] = all_cn_components_flat
137
-
138
- return components
139
-
140
- def create_embedding_ui(prefix: str):
141
- components = {}
142
- key = lambda name: f"{name}_{prefix}"
143
-
144
- with gr.Accordion("Embedding Settings", open=False, visible=True) as accordion:
145
- components[key('embedding_accordion')] = accordion
146
- 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.")
147
-
148
- embedding_rows, sources, ids, files, upload_buttons = [], [], [], [], []
149
- components.update({
150
- key('embedding_rows'): embedding_rows,
151
- key('embeddings_sources'): sources,
152
- key('embeddings_ids'): ids,
153
- key('embeddings_files'): files,
154
- key('embeddings_uploads'): upload_buttons
155
- })
156
-
157
- for i in range(MAX_EMBEDDINGS):
158
- with gr.Row(visible=(i < 1)) as row:
159
- sources.append(gr.Dropdown(label=f"Embedding Source {i+1}", choices=LORA_SOURCE_CHOICES, value="Civitai", scale=1, interactive=True))
160
- ids.append(gr.Textbox(label="Civitai Version ID / File", placeholder="Civitai Version ID or Filename", scale=3, interactive=True, type="text"))
161
- upload_btn = gr.UploadButton("Upload", file_types=[".safetensors"], scale=1)
162
- files.append(gr.State(None))
163
- upload_buttons.append(upload_btn)
164
- embedding_rows.append(row)
165
-
166
- with gr.Row():
167
- components[key('add_embedding_button')] = gr.Button("✚ Add Embedding")
168
- components[key('delete_embedding_button')] = gr.Button("➖ Delete Embedding", visible=False)
169
- components[key('embedding_count_state')] = gr.State(1)
170
-
171
- all_embedding_components_flat = []
172
- for i in range(MAX_EMBEDDINGS):
173
- all_embedding_components_flat.extend([sources[i], ids[i], files[i]])
174
- components[key('all_embedding_components_flat')] = all_embedding_components_flat
175
-
176
- return components
177
-
178
- def create_conditioning_ui(prefix: str):
179
- components = {}
180
- key = lambda name: f"{name}_{prefix}"
181
-
182
- with gr.Accordion("Conditioning Settings", open=False) as accordion:
183
- components[key('conditioning_accordion')] = accordion
184
- gr.Markdown("💡 **Tip:** Define rectangular areas and assign specific prompts to them. Coordinates (X, Y) start from the top-left corner.")
185
-
186
- cond_rows, prompts, widths, heights, xs, ys, strengths = [], [], [], [], [], [], []
187
- components.update({
188
- key('conditioning_rows'): cond_rows,
189
- key('conditioning_prompts'): prompts,
190
- key('conditioning_widths'): widths,
191
- key('conditioning_heights'): heights,
192
- key('conditioning_xs'): xs,
193
- key('conditioning_ys'): ys,
194
- key('conditioning_strengths'): strengths
195
- })
196
-
197
- for i in range(MAX_CONDITIONINGS):
198
- with gr.Column(visible=(i < 1)) as row_wrapper:
199
- prompts.append(gr.Textbox(label=f"Area Prompt {i+1}", lines=2, interactive=True))
200
- with gr.Row():
201
- xs.append(gr.Number(label="X", value=0, interactive=True, step=8, scale=1))
202
- ys.append(gr.Number(label="Y", value=0, interactive=True, step=8, scale=1))
203
- widths.append(gr.Number(label="Width", value=512, interactive=True, step=8, scale=1))
204
- heights.append(gr.Number(label="Height", value=512, interactive=True, step=8, scale=1))
205
- strengths.append(gr.Slider(label="Strength", minimum=0.1, maximum=2.0, step=0.05, value=1.0, interactive=True, scale=2))
206
- cond_rows.append(row_wrapper)
207
-
208
- with gr.Row():
209
- components[key('add_conditioning_button')] = gr.Button("✚ Add Area")
210
- components[key('delete_conditioning_button')] = gr.Button("➖ Delete Area", visible=False)
211
- components[key('conditioning_count_state')] = gr.State(1)
212
-
213
- all_cond_components_flat = prompts + widths + heights + xs + ys + strengths
214
- components[key('all_conditioning_components_flat')] = all_cond_components_flat
215
-
216
- return components
217
-
218
- def create_vae_override_ui(prefix: str):
219
- components = {}
220
- key = lambda name: f"{name}_{prefix}"
221
- source_choices = ["None"] + LORA_SOURCE_CHOICES
222
-
223
- with gr.Accordion("VAE Settings (Override)", open=False) as accordion:
224
- components[key('vae_accordion')] = accordion
225
- 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.")
226
- with gr.Row():
227
- components[key('vae_source')] = gr.Dropdown(
228
- label="VAE Source",
229
- choices=source_choices,
230
- value="None",
231
- scale=1,
232
- interactive=True
233
- )
234
- components[key('vae_id')] = gr.Textbox(
235
- label="Civitai Version ID / File",
236
- placeholder="Civitai Version ID or Filename",
237
- scale=3,
238
- interactive=True,
239
- type="text"
240
- )
241
- upload_btn = gr.UploadButton(
242
- "Upload",
243
- file_types=[".safetensors"],
244
- scale=1
245
- )
246
- components[key('vae_upload_button')] = upload_btn
247
- components[key('vae_file')] = gr.State(None)
248
-
249
- return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/app_utils.py CHANGED
@@ -1,27 +1,14 @@
1
  import os
2
- import requests
3
  import hashlib
4
  import re
5
- from typing import Sequence, Mapping, Any, Union, Set
6
  from pathlib import Path
7
  import shutil
8
-
9
  import gradio as gr
10
- 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:
27
  return ""
@@ -48,75 +35,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
55
- return round(byte_size / (1024 ** 3), 2)
56
-
57
- def get_directory_size(path: str) -> int:
58
- total_size = 0
59
- if not os.path.exists(path):
60
- return 0
61
- try:
62
- for dirpath, _, filenames in os.walk(path):
63
- for f in filenames:
64
- fp = os.path.join(dirpath, f)
65
- if os.path.isfile(fp) and not os.path.islink(fp):
66
- total_size += os.path.getsize(fp)
67
- except OSError as e:
68
- print(f"Warning: Could not access {path} to calculate size: {e}")
69
- return total_size
70
-
71
- def enforce_disk_limit():
72
- disk_limit_bytes = DISK_LIMIT_GB * (1024 ** 3)
73
- cache_dir = hf_constants.HF_HUB_CACHE
74
-
75
- if not os.path.exists(cache_dir):
76
- return
77
-
78
- print(f"--- [Storage Manager] Checking disk usage in '{cache_dir}' (Limit: {DISK_LIMIT_GB} GB) ---")
79
-
80
- try:
81
- all_files = []
82
- current_size_bytes = 0
83
- for dirpath, _, filenames in os.walk(cache_dir):
84
- for f in filenames:
85
- if f.endswith(".incomplete") or f.endswith(".lock"):
86
- continue
87
- file_path = os.path.join(dirpath, f)
88
- if os.path.isfile(file_path) and not os.path.islink(file_path):
89
- try:
90
- file_size = os.path.getsize(file_path)
91
- creation_time = os.path.getctime(file_path)
92
- all_files.append((creation_time, file_path, file_size))
93
- current_size_bytes += file_size
94
- except OSError:
95
- continue
96
-
97
- print(f"--- [Storage Manager] Current usage: {bytes_to_gb(current_size_bytes)} GB ---")
98
-
99
- if current_size_bytes > disk_limit_bytes:
100
- print(f"--- [Storage Manager] Usage exceeds limit. Starting cleanup... ---")
101
- all_files.sort(key=lambda x: x[0])
102
-
103
- while current_size_bytes > disk_limit_bytes and all_files:
104
- oldest_file_time, oldest_file_path, oldest_file_size = all_files.pop(0)
105
- try:
106
- os.remove(oldest_file_path)
107
- current_size_bytes -= oldest_file_size
108
- print(f"--- [Storage Manager] Deleted oldest file: {os.path.basename(oldest_file_path)} ({bytes_to_gb(oldest_file_size)} GB freed) ---")
109
- except OSError as e:
110
- print(f"--- [Storage Manager] Error deleting file {oldest_file_path}: {e} ---")
111
-
112
- print(f"--- [Storage Manager] Cleanup finished. New usage: {bytes_to_gb(current_size_bytes)} GB ---")
113
- else:
114
- print("--- [Storage Manager] Disk usage is within the limit. No action needed. ---")
115
-
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]
@@ -126,259 +44,6 @@ def get_value_at_index(obj: Union[Sequence, Mapping], index: int) -> Any:
126
  except (KeyError, IndexError):
127
  return None
128
 
129
- def sanitize_prompt(prompt: str) -> str:
130
- if not isinstance(prompt, str):
131
- return ""
132
- return "".join(char for char in prompt if char.isprintable() or char in ('\n', '\t'))
133
-
134
- def sanitize_id(input_id: str) -> str:
135
- if not isinstance(input_id, str):
136
- return ""
137
- return re.sub(r'[^0-9]', '', input_id)
138
-
139
- def sanitize_url(url: str) -> str:
140
- if not isinstance(url, str):
141
- raise ValueError("URL must be a string.")
142
- url = url.strip()
143
- if not re.match(r'^https?://[^\s/$.?#].[^\s]*$', url):
144
- raise ValueError("Invalid URL format or scheme. Only HTTP and HTTPS are allowed.")
145
- return url
146
-
147
- def sanitize_filename(filename: str) -> str:
148
- if not isinstance(filename, str):
149
- return ""
150
- sanitized = filename.replace('..', '')
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:
158
- response = requests.get(api_url, timeout=10)
159
- response.raise_for_status()
160
- data = response.json()
161
-
162
- for file_data in data.get('files', []):
163
- if file_data.get('type') == 'Model' and file_data['name'].endswith(('.safetensors', '.pt', '.bin')):
164
- return file_data
165
-
166
- if data.get('files'):
167
- return data['files'][0]
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
-
175
- if os.path.exists(save_path):
176
- return f"File already exists: {os.path.basename(save_path)}"
177
-
178
- headers = {'Authorization': f'Bearer {api_key}'} if api_key and api_key.strip() else {}
179
- try:
180
- if progress:
181
- progress(0, desc=desc)
182
-
183
- response = requests.get(url, stream=True, headers=headers, timeout=15)
184
- response.raise_for_status()
185
- total_size = int(response.headers.get('content-length', 0))
186
-
187
- with open(save_path, "wb") as f:
188
- downloaded = 0
189
- for chunk in response.iter_content(chunk_size=8192):
190
- f.write(chunk)
191
- if progress and total_size > 0:
192
- downloaded += len(chunk)
193
- progress(downloaded / total_size, desc=desc)
194
- return f"Successfully downloaded: {os.path.basename(save_path)}"
195
- except Exception as e:
196
- if os.path.exists(save_path):
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."
204
-
205
- try:
206
- if source == "Civitai":
207
- version_id = sanitize_id(id_or_url)
208
- if not version_id:
209
- return None, "Invalid Civitai ID provided. Must be numeric."
210
- filename = sanitize_filename(f"civitai_{version_id}.safetensors")
211
- local_path = os.path.join(LORA_DIR, filename)
212
- file_info = get_civitai_file_info(version_id)
213
- api_key_to_use = civitai_key
214
- source_name = f"Civitai ID {version_id}"
215
- else:
216
- return None, "Invalid source."
217
-
218
- except ValueError as e:
219
- return None, f"Input validation failed: {e}"
220
-
221
- if os.path.exists(local_path):
222
- return local_path, "File already exists."
223
-
224
- if not file_info or not file_info.get('downloadUrl'):
225
- return None, f"Could not get download link for {source_name}."
226
-
227
- status = download_file(file_info['downloadUrl'], local_path, api_key_to_use, progress=progress, desc=f"Downloading {source_name}")
228
-
229
- return (local_path, status) if "Successfully" in status else (None, status)
230
-
231
- def get_embedding_path(source: str, id_or_url: str, civitai_key: str, progress) -> tuple[str | None, str]:
232
- if not id_or_url or not id_or_url.strip():
233
- return None, "No ID/URL provided."
234
-
235
- try:
236
- file_ext = ".safetensors"
237
-
238
- if source == "Civitai":
239
- version_id = sanitize_id(id_or_url)
240
- if not version_id:
241
- return None, "Invalid Civitai ID. Must be numeric."
242
-
243
- file_info = get_civitai_file_info(version_id)
244
- if file_info and file_info['name'].lower().endswith(('.pt', '.bin')):
245
- file_ext = os.path.splitext(file_info['name'])[1]
246
-
247
- filename = sanitize_filename(f"civitai_{version_id}{file_ext}")
248
- local_path = os.path.join(EMBEDDING_DIR, filename)
249
- api_key_to_use = civitai_key
250
- source_name = f"Embedding Civitai ID {version_id}"
251
- else:
252
- return None, "Invalid source."
253
-
254
- except ValueError as e:
255
- return None, f"Input validation failed: {e}"
256
-
257
- if os.path.exists(local_path):
258
- return local_path, "File already exists."
259
-
260
- if not file_info or not file_info.get('downloadUrl'):
261
- return None, f"Could not get download link for {source_name}."
262
-
263
- status = download_file(file_info['downloadUrl'], local_path, api_key_to_use, progress=progress, desc=f"Downloading {source_name}")
264
-
265
- return (local_path, status) if "Successfully" in status else (None, status)
266
-
267
- def get_vae_path(source: str, id_or_url: str, civitai_key: str, progress) -> tuple[str | None, str]:
268
- if not id_or_url or not id_or_url.strip():
269
- return None, "No ID/URL provided."
270
-
271
- try:
272
- file_ext = ".safetensors"
273
-
274
- if source == "Civitai":
275
- version_id = sanitize_id(id_or_url)
276
- if not version_id:
277
- return None, "Invalid Civitai ID. Must be numeric."
278
-
279
- file_info = get_civitai_file_info(version_id)
280
- if file_info and file_info['name'].lower().endswith(('.pt', '.bin')):
281
- file_ext = os.path.splitext(file_info['name'])[1]
282
-
283
- filename = sanitize_filename(f"civitai_{version_id}{file_ext}")
284
- local_path = os.path.join(VAE_DIR, filename)
285
- api_key_to_use = civitai_key
286
- source_name = f"VAE Civitai ID {version_id}"
287
- else:
288
- return None, "Invalid source."
289
-
290
- except ValueError as e:
291
- return None, f"Input validation failed: {e}"
292
-
293
- if os.path.exists(local_path):
294
- return local_path, "File already exists."
295
-
296
- if not file_info or not file_info.get('downloadUrl'):
297
- return None, f"Could not get download link for {source_name}."
298
-
299
- status = download_file(file_info['downloadUrl'], local_path, api_key_to_use, progress=progress, desc=f"Downloading {source_name}")
300
-
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
- "clip_vision": os.path.join(os.path.dirname(LORA_DIR), "clip_vision")
317
- }
318
-
319
- category = download_info.get('category')
320
- dest_dir = category_to_dir_map.get(category)
321
- if not dest_dir:
322
- raise ValueError(f"Unknown model category '{category}' for file '{filename}'.")
323
-
324
- dest_path = os.path.join(dest_dir, filename)
325
-
326
- if os.path.lexists(dest_path):
327
- if not os.path.exists(dest_path):
328
- print(f"⚠️ Found and removed broken symlink: {dest_path}")
329
- os.remove(dest_path)
330
- else:
331
- return filename
332
-
333
- source = download_info.get("source")
334
- try:
335
- progress(0, desc=f"Downloading: {filename}")
336
-
337
- if source == "hf":
338
- repo_id = download_info.get("repo_id")
339
- hf_filename = download_info.get("repository_file_path", filename)
340
- if not repo_id:
341
- raise ValueError(f"repo_id is missing for HF model '{filename}'")
342
-
343
- cached_path = hf_hub_download(repo_id=repo_id, filename=hf_filename)
344
- os.makedirs(dest_dir, exist_ok=True)
345
- os.symlink(cached_path, dest_path)
346
- print(f"✅ Symlinked '{cached_path}' to '{dest_path}'")
347
-
348
- elif source == "civitai":
349
- model_version_id = download_info.get("model_version_id")
350
- if not model_version_id:
351
- raise ValueError(f"model_version_id is missing for Civitai model '{filename}'")
352
-
353
- file_info = get_civitai_file_info(model_version_id)
354
- if not file_info or not file_info.get('downloadUrl'):
355
- raise ConnectionError(f"Could not get download URL for Civitai model version ID {model_version_id}")
356
-
357
- status = download_file(
358
- file_info['downloadUrl'], dest_path, progress=progress, desc=f"Downloading: {filename}"
359
- )
360
- if "Failed" in status:
361
- raise ConnectionError(status)
362
- else:
363
- raise NotImplementedError(f"Download source '{source}' is not implemented for '{filename}'")
364
-
365
- progress(1.0, desc=f"Downloaded: {filename}")
366
-
367
- except Exception as e:
368
- if os.path.lexists(dest_path):
369
- try:
370
- os.remove(dest_path)
371
- except OSError: pass
372
- raise gr.Error(f"Failed to download and link '{filename}': {e}")
373
-
374
- return filename
375
-
376
- def ensure_controlnet_model_downloaded(filename: str, progress):
377
- if not filename or filename == "None":
378
- return
379
- _ensure_model_downloaded(filename, progress)
380
-
381
-
382
  def build_preprocessor_model_map():
383
  global PREPROCESSOR_MODEL_MAP
384
  if PREPROCESSOR_MODEL_MAP is not None: return PREPROCESSOR_MODEL_MAP
@@ -388,7 +53,6 @@ def build_preprocessor_model_map():
388
  "densepose": [("LayerNorm/DensePose-TorchScript-with-hint-image", "densepose_r50_fpn_dl.torchscript"), ("LayerNorm/DensePose-TorchScript-with-hint-image", "densepose_r101_fpn_dl.torchscript")]
389
  }
390
  temp_map = {}
391
- from nodes import NODE_DISPLAY_NAME_MAPPINGS
392
  wrappers_dir = Path("./custom_nodes/comfyui_controlnet_aux/node_wrappers/")
393
  if not wrappers_dir.exists():
394
  print("⚠️ ControlNet AUX wrappers directory not found. Cannot build model map.")
@@ -442,20 +106,11 @@ def build_preprocessor_parameter_map():
442
  print("✅ ControlNet Preprocessor parameter map built.")
443
 
444
  def print_welcome_message():
445
- author_name = "RioShiina"
446
- project_url = "https://huggingface.co/RioShiina"
447
  border = "=" * 72
448
-
449
  message = (
450
  f"\n{border}\n\n"
451
- f" Thank you for using this project!\n\n"
452
- f" **Author:** {author_name}\n"
453
- f" **Find more from the author:** {project_url}\n\n"
454
- f" This project is open-source under the GNU General Public License v3.0 (GPL-3.0).\n"
455
- f" As it's built upon GPL-3.0 components (like ComfyUI), any modifications you\n"
456
- f" distribute must also be open-sourced under the same license.\n\n"
457
- f" Your respect for the principles of free software is greatly appreciated!\n\n"
458
  f"{border}\n"
459
  )
460
-
461
  print(message)
 
1
  import os
 
2
  import hashlib
3
  import re
 
4
  from pathlib import Path
5
  import shutil
6
+ from typing import Sequence, Mapping, Any, Union
7
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
8
 
9
  PREPROCESSOR_MODEL_MAP = None
10
  PREPROCESSOR_PARAMETER_MAP = None
11
 
 
12
  def save_uploaded_file_with_hash(file_obj: gr.File, target_dir: str) -> str:
13
  if not file_obj:
14
  return ""
 
35
 
36
  return hashed_filename
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  def get_value_at_index(obj: Union[Sequence, Mapping], index: int) -> Any:
39
  try:
40
  return obj[index]
 
44
  except (KeyError, IndexError):
45
  return None
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  def build_preprocessor_model_map():
48
  global PREPROCESSOR_MODEL_MAP
49
  if PREPROCESSOR_MODEL_MAP is not None: return PREPROCESSOR_MODEL_MAP
 
53
  "densepose": [("LayerNorm/DensePose-TorchScript-with-hint-image", "densepose_r50_fpn_dl.torchscript"), ("LayerNorm/DensePose-TorchScript-with-hint-image", "densepose_r101_fpn_dl.torchscript")]
54
  }
55
  temp_map = {}
 
56
  wrappers_dir = Path("./custom_nodes/comfyui_controlnet_aux/node_wrappers/")
57
  if not wrappers_dir.exists():
58
  print("⚠️ ControlNet AUX wrappers directory not found. Cannot build model map.")
 
106
  print("✅ ControlNet Preprocessor parameter map built.")
107
 
108
  def print_welcome_message():
 
 
109
  border = "=" * 72
 
110
  message = (
111
  f"\n{border}\n\n"
112
+ f" Welcome to ControlNet Preprocessors\n"
113
+ f" Based on comfyui_controlnet_aux\n\n"
 
 
 
 
 
114
  f"{border}\n"
115
  )
 
116
  print(message)
yaml/constants.yaml DELETED
@@ -1,15 +0,0 @@
1
- MAX_LORAS: 5
2
- MAX_CONTROLNETS: 5
3
- MAX_EMBEDDINGS: 5
4
- MAX_CONDITIONINGS: 10
5
- LORA_SOURCE_CHOICES: ["Civitai", "File"]
6
-
7
- RESOLUTION_MAP:
8
- sdxl:
9
- "1:1 (Square)": [1328, 1328]
10
- "16:9 (Landscape)": [1664, 928]
11
- "9:16 (Portrait)": [928, 1664]
12
- "4:3 (Classic)": [1472, 1104]
13
- "3:4 (Classic Portrait)": [1104, 1472]
14
- "3:2 (Photography)": [1584, 1056]
15
- "2:3 (Photography Portrait)": [1056, 1584]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
yaml/controlnet_models.yaml DELETED
@@ -1,5 +0,0 @@
1
- ControlNet:
2
- Qwen-Image:
3
- - Filepath: "Qwen-Image-InstantX-ControlNet-Union.safetensors"
4
- Series: "InstantX Union"
5
- Type: ["Canny", "Soft Edge", "Depth", "Pose"]
 
 
 
 
 
 
yaml/file_list.yaml DELETED
@@ -1,38 +0,0 @@
1
- file:
2
- controlnet:
3
- - filename: "Qwen-Image-InstantX-ControlNet-Union.safetensors"
4
- source: "hf"
5
- repo_id: "InstantX/Qwen-Image-ControlNet-Union"
6
- repository_file_path: "diffusion_pytorch_model.safetensors"
7
-
8
- diffusion_models:
9
- - filename: "qwen_image_2512_fp8_e4m3fn.safetensors"
10
- source: "hf"
11
- repo_id: "Comfy-Org/Qwen-Image_ComfyUI"
12
- repository_file_path: "split_files/diffusion_models/qwen_image_2512_fp8_e4m3fn.safetensors"
13
- - filename: "qwen_image_fp8_e4m3fn.safetensors"
14
- source: "hf"
15
- repo_id: "Comfy-Org/Qwen-Image_ComfyUI"
16
- repository_file_path: "split_files/diffusion_models/qwen_image_fp8_e4m3fn.safetensors"
17
-
18
- loras:
19
- - filename: "Qwen-Image-2512-Lightning-4steps-V1.0-bf16.safetensors"
20
- source: "hf"
21
- repo_id: "lightx2v/Qwen-Image-2512-Lightning"
22
- repository_file_path: "Qwen-Image-2512-Lightning-4steps-V1.0-bf16.safetensors"
23
- - filename: "Qwen-Image-fp8-e4m3fn-Lightning-4steps-V1.0-bf16.safetensors"
24
- source: "hf"
25
- repo_id: "lightx2v/Qwen-Image-Lightning"
26
- repository_file_path: "Qwen-Image-fp8-e4m3fn-Lightning-4steps-V1.0-bf16.safetensors"
27
-
28
- text_encoders:
29
- - filename: "qwen_2.5_vl_7b_fp8_scaled.safetensors"
30
- source: "hf"
31
- repo_id: "Comfy-Org/Qwen-Image_ComfyUI"
32
- repository_file_path: "split_files/text_encoders/qwen_2.5_vl_7b_fp8_scaled.safetensors"
33
-
34
- vae:
35
- - filename: "qwen_image_vae.safetensors"
36
- source: "hf"
37
- repo_id: "Comfy-Org/Qwen-Image_ComfyUI"
38
- repository_file_path: "split_files/vae/qwen_image_vae.safetensors"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
yaml/injectors.yaml DELETED
@@ -1,12 +0,0 @@
1
- injector_definitions:
2
- dynamic_lora_chains:
3
- module: "chain_injectors.lora_injector"
4
- dynamic_controlnet_chains:
5
- module: "chain_injectors.controlnet_injector"
6
- dynamic_conditioning_chains:
7
- module: "chain_injectors.conditioning_injector"
8
-
9
- injector_order:
10
- - dynamic_lora_chains
11
- - dynamic_conditioning_chains
12
- - dynamic_controlnet_chains
 
 
 
 
 
 
 
 
 
 
 
 
 
yaml/model_list.yaml DELETED
@@ -1,13 +0,0 @@
1
- Checkpoint:
2
- - display_name: "Qwen/Qwen-Image-2512 + Lightning-4steps-V1.0 LoRA"
3
- components:
4
- unet: "qwen_image_2512_fp8_e4m3fn.safetensors"
5
- vae: "qwen_image_vae.safetensors"
6
- clip: "qwen_2.5_vl_7b_fp8_scaled.safetensors"
7
- lora: "Qwen-Image-2512-Lightning-4steps-V1.0-bf16.safetensors"
8
- - display_name: "Qwen/Qwen-Image + Lightning-4steps-V1.0 LoRA"
9
- components:
10
- unet: "qwen_image_fp8_e4m3fn.safetensors"
11
- vae: "qwen_image_vae.safetensors"
12
- clip: "qwen_2.5_vl_7b_fp8_scaled.safetensors"
13
- lora: "Qwen-Image-fp8-e4m3fn-Lightning-4steps-V1.0-bf16.safetensors"