RioShiina commited on
Commit
9331538
·
verified ·
1 Parent(s): 305273b

Optimize workflows and configuration files, and standardize file naming.

Browse files
chain_injectors/__init__.py CHANGED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import importlib
3
+ import pkgutil
4
+
5
+ def discover_injectors():
6
+ injectors = {}
7
+ package_dir = os.path.dirname(__file__)
8
+
9
+ for _, module_name, is_pkg in pkgutil.iter_modules([package_dir]):
10
+ if is_pkg or module_name.startswith('_'):
11
+ continue
12
+
13
+ full_module_name = f"chain_injectors.{module_name}"
14
+ try:
15
+ module = importlib.import_module(full_module_name)
16
+ if hasattr(module, 'inject') and callable(module.inject):
17
+ feature_name = getattr(module, 'FEATURE_NAME', None)
18
+ if not feature_name:
19
+ feature_name = module_name[:-9] if module_name.endswith('_injector') else module_name
20
+
21
+ chain_type = getattr(module, 'CHAIN_TYPE', None)
22
+ if not chain_type:
23
+ chain_type = f"dynamic_{feature_name}_chains"
24
+
25
+ injectors[chain_type] = module.inject
26
+ else:
27
+ print(f"Warning: Module '{full_module_name}' does not have a callable 'inject' function.")
28
+ except Exception as e:
29
+ print(f"Error importing injector module '{full_module_name}': {e}")
30
+
31
+ return injectors
32
+
33
+ def get_registered_features():
34
+ features = {}
35
+ package_dir = os.path.dirname(__file__)
36
+
37
+ for _, module_name, is_pkg in pkgutil.iter_modules([package_dir]):
38
+ if is_pkg or module_name.startswith('_'):
39
+ continue
40
+
41
+ feature_name = module_name[:-9] if module_name.endswith('_injector') else module_name
42
+ full_module_name = f"chain_injectors.{module_name}"
43
+ chain_type = f"dynamic_{feature_name}_chains"
44
+
45
+ features[feature_name] = {
46
+ 'module': full_module_name,
47
+ 'chain_type': chain_type
48
+ }
49
+
50
+ return features
chain_injectors/{boogu_edit_injector.py → boogu_image_edit_injector.py} RENAMED
@@ -1,5 +1,3 @@
1
- import os
2
-
3
  def create_node(assembler, class_type, title):
4
  try:
5
  node = assembler._get_node_template(class_type)
@@ -31,64 +29,30 @@ def inject(assembler, chain_definition, chain_items):
31
 
32
  valid_images = valid_images[:10]
33
 
34
- ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
35
- pos_prompt_name = chain_definition.get('pos_prompt_node', 'pos_prompt')
36
- neg_prompt_name = chain_definition.get('neg_prompt_node', 'neg_prompt')
37
- clip_loader_name = chain_definition.get('clip_loader_node', 'clip_loader')
38
  vae_loader_name = chain_definition.get('vae_loader_node', 'vae_loader')
39
 
40
- if ksampler_name not in assembler.node_map:
41
- print(f"Warning: Target node '{ksampler_name}' for Boogu Edit chain not found. Skipping.")
42
- return
43
-
44
- ksampler_id = assembler.node_map[ksampler_name]
 
45
 
46
- if 'model' not in assembler.workflow[ksampler_id]['inputs']:
47
- print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping.")
48
  return
49
 
50
- vae_connection = None
51
- if vae_loader_name in assembler.node_map:
52
- vae_connection = [assembler.node_map[vae_loader_name], 0]
53
- else:
54
  for node_id, node in assembler.workflow.items():
55
  if isinstance(node, dict) and node.get('class_type') == 'VAELoader':
56
- vae_connection = [node_id, 0]
57
  break
58
 
59
- clip_connection = None
60
- if clip_loader_name in assembler.node_map:
61
- clip_connection = [assembler.node_map[clip_loader_name], 0]
62
- elif pos_prompt_name in assembler.node_map:
63
- pos_id = assembler.node_map[pos_prompt_name]
64
- clip_connection = assembler.workflow[pos_id]['inputs'].get('clip')
65
-
66
- pos_prompt_id = assembler.node_map.get(pos_prompt_name)
67
- neg_prompt_id = assembler.node_map.get(neg_prompt_name)
68
-
69
- pos_text = ""
70
- if pos_prompt_id and pos_prompt_id in assembler.workflow:
71
- pos_text = assembler.workflow[pos_prompt_id]['inputs'].get('text', '')
72
- elif hasattr(assembler, 'ui_values') and isinstance(assembler.ui_values, dict):
73
- pos_text = assembler.ui_values.get('positive_prompt') or assembler.ui_values.get('prompt') or ''
74
 
75
- if not pos_text:
76
- for node_id, node in assembler.workflow.items():
77
- if isinstance(node, dict):
78
- cls = node.get('class_type', '')
79
- if cls in ['TextEncodeBooguEdit', 'Krea2EditGroundedEncode', 'TextEncodeQwenImageEditPlus', 'CLIPTextEncode']:
80
- t = node.get('inputs', {}).get('prompt') or node.get('inputs', {}).get('text')
81
- if t:
82
- pos_text = t
83
- break
84
-
85
- neg_text = ""
86
- if neg_prompt_id and neg_prompt_id in assembler.workflow:
87
- neg_text = assembler.workflow[neg_prompt_id]['inputs'].get('text', '')
88
- elif hasattr(assembler, 'ui_values') and isinstance(assembler.ui_values, dict):
89
- neg_text = assembler.ui_values.get('negative_prompt') or assembler.ui_values.get('neg_prompt') or ''
90
-
91
- scaled_image_ids = []
92
  for i, img_filename in enumerate(valid_images):
93
  load_id = assembler._get_unique_id()
94
  load_node = create_node(assembler, "LoadImage", f"Load Reference Image {i+1}")
@@ -102,27 +66,8 @@ def inject(assembler, chain_definition, chain_items):
102
  scale_node['inputs']['resolution_steps'] = 1
103
  scale_node['inputs']['image'] = [load_id, 0]
104
  assembler.workflow[scale_id] = scale_node
105
- scaled_image_ids.append(scale_id)
106
-
107
- boogu_encode_id = assembler._get_unique_id()
108
- boogu_encode_node = create_node(assembler, "TextEncodeBooguEdit", "TextEncodeBooguEdit")
109
- boogu_encode_node['inputs']['prompt'] = pos_text
110
- boogu_encode_node['inputs']['negative_prompt'] = neg_text
111
- if clip_connection:
112
- boogu_encode_node['inputs']['clip'] = clip_connection
113
- if vae_connection:
114
- boogu_encode_node['inputs']['vae'] = vae_connection
115
- for idx, s_id in enumerate(scaled_image_ids):
116
- boogu_encode_node['inputs'][f"images.image_{idx+1}"] = [s_id, 0]
117
- assembler.workflow[boogu_encode_id] = boogu_encode_node
118
-
119
- assembler.workflow[ksampler_id]['inputs']['positive'] = [boogu_encode_id, 0]
120
- assembler.workflow[ksampler_id]['inputs']['negative'] = [boogu_encode_id, 1]
121
-
122
- if pos_prompt_id and pos_prompt_id in assembler.workflow:
123
- del assembler.workflow[pos_prompt_id]
124
 
125
- if neg_prompt_id and neg_prompt_id in assembler.workflow:
126
- del assembler.workflow[neg_prompt_id]
127
 
128
- print(f"Boogu Edit injector applied with {len(valid_images)} reference image(s). Original CLIPTextEncode nodes replaced.")
 
 
 
1
  def create_node(assembler, class_type, title):
2
  try:
3
  node = assembler._get_node_template(class_type)
 
29
 
30
  valid_images = valid_images[:10]
31
 
32
+ boogu_prompt_name = chain_definition.get('boogu_prompt_node', 'boogu_prompt')
 
 
 
33
  vae_loader_name = chain_definition.get('vae_loader_node', 'vae_loader')
34
 
35
+ boogu_prompt_id = assembler.node_map.get(boogu_prompt_name)
36
+ if not boogu_prompt_id or boogu_prompt_id not in assembler.workflow:
37
+ for node_id, node in assembler.workflow.items():
38
+ if isinstance(node, dict) and node.get('class_type') == 'TextEncodeBooguEdit':
39
+ boogu_prompt_id = node_id
40
+ break
41
 
42
+ if not boogu_prompt_id:
43
+ print(f"Warning: Target node '{boogu_prompt_name}' (TextEncodeBooguEdit) for Boogu Edit chain not found. Skipping.")
44
  return
45
 
46
+ vae_id = assembler.node_map.get(vae_loader_name)
47
+ if not vae_id:
 
 
48
  for node_id, node in assembler.workflow.items():
49
  if isinstance(node, dict) and node.get('class_type') == 'VAELoader':
50
+ vae_id = node_id
51
  break
52
 
53
+ if vae_id:
54
+ assembler.workflow[boogu_prompt_id]['inputs']['vae'] = [vae_id, 0]
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  for i, img_filename in enumerate(valid_images):
57
  load_id = assembler._get_unique_id()
58
  load_node = create_node(assembler, "LoadImage", f"Load Reference Image {i+1}")
 
66
  scale_node['inputs']['resolution_steps'] = 1
67
  scale_node['inputs']['image'] = [load_id, 0]
68
  assembler.workflow[scale_id] = scale_node
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ image_key = f"images.image_{i+1}"
71
+ assembler.workflow[boogu_prompt_id]['inputs'][image_key] = [scale_id, 0]
72
 
73
+ print(f"Boogu Edit injector applied with {len(valid_images)} reference image(s). Connected VAE dynamically.")
chain_injectors/{joyai_reference_injector.py → joyai_image_injector.py} RENAMED
File without changes
chain_injectors/{krea2_reference_edit_injector.py → krea2_style_reference_injector.py} RENAMED
File without changes
chain_injectors/newbie_lora_injector.py DELETED
@@ -1,63 +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
- output_map = chain_definition.get('output_map', {})
8
- current_connections = {}
9
- for key, type_name in output_map.items():
10
- if ':' in str(key):
11
- node_name, idx_str = key.split(':')
12
- if node_name not in assembler.node_map:
13
- print(f"Warning: [NewBie LoRA Injector] Node '{node_name}' in chain's output_map not found. Skipping.")
14
- continue
15
- node_id = assembler.node_map[node_name]
16
- start_output_idx = int(idx_str)
17
- current_connections[type_name] = [node_id, start_output_idx]
18
- else:
19
- print(f"Warning: [NewBie LoRA Injector] output_map key '{key}' is not in 'node:index' format. Skipping this connection.")
20
-
21
- template_name = chain_definition.get('template')
22
- if not template_name:
23
- print(f"Warning: [NewBie LoRA Injector] No 'template' defined for chain. Skipping.")
24
- return
25
-
26
- for item_data in chain_items:
27
- template = assembler._get_node_template(template_name)
28
- node_data = deepcopy(template)
29
-
30
- node_data['inputs']['lora_name'] = item_data.get('lora_name')
31
- node_data['inputs']['strength'] = item_data.get('strength_model', 1.0)
32
- node_data['inputs']['enabled'] = True
33
-
34
- if 'model' in current_connections:
35
- node_data['inputs']['model'] = current_connections['model']
36
- if 'clip' in current_connections:
37
- node_data['inputs']['clip'] = current_connections['clip']
38
-
39
- new_node_id = assembler._get_unique_id()
40
- assembler.workflow[new_node_id] = node_data
41
-
42
- current_connections['model'] = [new_node_id, 0]
43
- current_connections['clip'] = [new_node_id, 1]
44
-
45
- end_input_map = chain_definition.get('end_input_map', {})
46
- for type_name, targets in end_input_map.items():
47
- if type_name in current_connections:
48
- if not isinstance(targets, list):
49
- targets = [targets]
50
-
51
- for target_str in targets:
52
- try:
53
- end_node_name, end_input_name = target_str.split(':')
54
- if end_node_name in assembler.node_map:
55
- end_node_id = assembler.node_map[end_node_name]
56
- assembler.workflow[end_node_id]['inputs'][end_input_name] = current_connections[type_name]
57
- else:
58
- print(f"Warning: [NewBie LoRA Injector] End node '{end_node_name}' for dynamic chain not found. Skipping connection.")
59
- except ValueError:
60
- print(f"Warning: [NewBie LoRA Injector] Invalid target format '{target_str}' in end_input_map. Skipping.")
61
-
62
- if chain_items:
63
- print(f"NewBie LoRA injector applied. Re-routed model and clip through {len(chain_items)} LoRA(s).")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chain_injectors/qwen_image_edit_injector.py CHANGED
@@ -1,5 +1,3 @@
1
- import os
2
-
3
  def create_node(assembler, class_type, title):
4
  try:
5
  node = assembler._get_node_template(class_type)
@@ -34,7 +32,6 @@ def inject(assembler, chain_definition, chain_items):
34
  ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
35
  pos_prompt_name = chain_definition.get('pos_prompt_node', 'pos_prompt')
36
  neg_prompt_name = chain_definition.get('neg_prompt_node', 'neg_prompt')
37
- clip_loader_name = chain_definition.get('clip_loader_node', 'clip_loader')
38
  vae_loader_name = chain_definition.get('vae_loader_node', 'vae_loader')
39
  model_sampler_name = chain_definition.get('model_sampler_node', 'model_sampler')
40
 
@@ -43,53 +40,24 @@ def inject(assembler, chain_definition, chain_items):
43
  return
44
 
45
  ksampler_id = assembler.node_map[ksampler_name]
 
 
46
 
47
- if 'model' not in assembler.workflow[ksampler_id]['inputs']:
48
- print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping.")
49
  return
50
 
51
- vae_connection = None
52
- if vae_loader_name in assembler.node_map:
53
- vae_connection = [assembler.node_map[vae_loader_name], 0]
54
- else:
55
  for node_id, node in assembler.workflow.items():
56
  if isinstance(node, dict) and node.get('class_type') == 'VAELoader':
57
- vae_connection = [node_id, 0]
58
  break
59
 
60
- clip_connection = None
61
- if clip_loader_name in assembler.node_map:
62
- clip_connection = [assembler.node_map[clip_loader_name], 0]
63
- elif pos_prompt_name in assembler.node_map:
64
- pos_id = assembler.node_map[pos_prompt_name]
65
- clip_connection = assembler.workflow[pos_id]['inputs'].get('clip')
66
-
67
- pos_prompt_id = assembler.node_map.get(pos_prompt_name)
68
- neg_prompt_id = assembler.node_map.get(neg_prompt_name)
69
 
70
- pos_text = ""
71
- if pos_prompt_id and pos_prompt_id in assembler.workflow:
72
- pos_text = assembler.workflow[pos_prompt_id]['inputs'].get('text', '')
73
- elif hasattr(assembler, 'ui_values') and isinstance(assembler.ui_values, dict):
74
- pos_text = assembler.ui_values.get('positive_prompt') or assembler.ui_values.get('prompt') or ''
75
-
76
- if not pos_text:
77
- for node_id, node in assembler.workflow.items():
78
- if isinstance(node, dict):
79
- cls = node.get('class_type', '')
80
- if cls in ['Krea2EditGroundedEncode', 'TextEncodeQwenImageEditPlus', 'CLIPTextEncode']:
81
- t = node.get('inputs', {}).get('prompt') or node.get('inputs', {}).get('text')
82
- if t:
83
- pos_text = t
84
- break
85
-
86
- neg_text = ""
87
- if neg_prompt_id and neg_prompt_id in assembler.workflow:
88
- neg_text = assembler.workflow[neg_prompt_id]['inputs'].get('text', '')
89
- elif hasattr(assembler, 'ui_values') and isinstance(assembler.ui_values, dict):
90
- neg_text = assembler.ui_values.get('negative_prompt') or assembler.ui_values.get('neg_prompt') or ''
91
-
92
- scaled_image_ids = []
93
  for i, img_filename in enumerate(valid_images):
94
  load_id = assembler._get_unique_id()
95
  load_node = create_node(assembler, "LoadImage", f"Load Reference Image {i+1}")
@@ -103,40 +71,21 @@ def inject(assembler, chain_definition, chain_items):
103
  scale_node['inputs']['resolution_steps'] = 1
104
  scale_node['inputs']['image'] = [load_id, 0]
105
  assembler.workflow[scale_id] = scale_node
106
- scaled_image_ids.append(scale_id)
107
-
108
- pos_encode_id = assembler._get_unique_id()
109
- pos_encode_node = create_node(assembler, "TextEncodeQwenImageEditPlus", "TextEncodeQwenImageEditPlus (Positive)")
110
- pos_encode_node['inputs']['prompt'] = pos_text
111
- if clip_connection:
112
- pos_encode_node['inputs']['clip'] = clip_connection
113
- if vae_connection:
114
- pos_encode_node['inputs']['vae'] = vae_connection
115
- for idx, s_id in enumerate(scaled_image_ids):
116
- pos_encode_node['inputs'][f"image{idx+1}"] = [s_id, 0]
117
- assembler.workflow[pos_encode_id] = pos_encode_node
118
-
119
- neg_encode_id = assembler._get_unique_id()
120
- neg_encode_node = create_node(assembler, "TextEncodeQwenImageEditPlus", "TextEncodeQwenImageEditPlus")
121
- neg_encode_node['inputs']['prompt'] = neg_text
122
- if clip_connection:
123
- neg_encode_node['inputs']['clip'] = clip_connection
124
- if vae_connection:
125
- neg_encode_node['inputs']['vae'] = vae_connection
126
- for idx, s_id in enumerate(scaled_image_ids):
127
- neg_encode_node['inputs'][f"image{idx+1}"] = [s_id, 0]
128
- assembler.workflow[neg_encode_id] = neg_encode_node
129
 
130
  pos_ref_id = assembler._get_unique_id()
131
  pos_ref_node = create_node(assembler, "FluxKontextMultiReferenceLatentMethod", "Edit Model Reference Method")
132
  pos_ref_node['inputs']['reference_latents_method'] = "index_timestep_zero"
133
- pos_ref_node['inputs']['conditioning'] = [pos_encode_id, 0]
134
  assembler.workflow[pos_ref_id] = pos_ref_node
135
 
136
  neg_ref_id = assembler._get_unique_id()
137
  neg_ref_node = create_node(assembler, "FluxKontextMultiReferenceLatentMethod", "Edit Model Reference Method")
138
  neg_ref_node['inputs']['reference_latents_method'] = "index_timestep_zero"
139
- neg_ref_node['inputs']['conditioning'] = [neg_encode_id, 0]
140
  assembler.workflow[neg_ref_id] = neg_ref_node
141
 
142
  assembler.workflow[ksampler_id]['inputs']['positive'] = [pos_ref_id, 0]
@@ -161,10 +110,4 @@ def inject(assembler, chain_definition, chain_items):
161
  assembler.workflow[cfg_norm_id] = cfg_norm_node
162
  assembler.workflow[ksampler_id]['inputs']['model'] = [cfg_norm_id, 0]
163
 
164
- if pos_prompt_id and pos_prompt_id in assembler.workflow:
165
- del assembler.workflow[pos_prompt_id]
166
-
167
- if neg_prompt_id and neg_prompt_id in assembler.workflow:
168
- del assembler.workflow[neg_prompt_id]
169
-
170
- print(f"Qwen-Image Edit injector applied with {len(valid_images)} reference image(s). Original CLIPTextEncode nodes replaced.")
 
 
 
1
  def create_node(assembler, class_type, title):
2
  try:
3
  node = assembler._get_node_template(class_type)
 
32
  ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
33
  pos_prompt_name = chain_definition.get('pos_prompt_node', 'pos_prompt')
34
  neg_prompt_name = chain_definition.get('neg_prompt_node', 'neg_prompt')
 
35
  vae_loader_name = chain_definition.get('vae_loader_node', 'vae_loader')
36
  model_sampler_name = chain_definition.get('model_sampler_node', 'model_sampler')
37
 
 
40
  return
41
 
42
  ksampler_id = assembler.node_map[ksampler_name]
43
+ pos_prompt_id = assembler.node_map.get(pos_prompt_name)
44
+ neg_prompt_id = assembler.node_map.get(neg_prompt_name)
45
 
46
+ if not pos_prompt_id or not neg_prompt_id:
47
+ print("Warning: Positive or negative prompt node not found for Qwen-Image Edit chain. Skipping.")
48
  return
49
 
50
+ vae_id = assembler.node_map.get(vae_loader_name)
51
+ if not vae_id:
 
 
52
  for node_id, node in assembler.workflow.items():
53
  if isinstance(node, dict) and node.get('class_type') == 'VAELoader':
54
+ vae_id = node_id
55
  break
56
 
57
+ if vae_id:
58
+ assembler.workflow[pos_prompt_id]['inputs']['vae'] = [vae_id, 0]
59
+ assembler.workflow[neg_prompt_id]['inputs']['vae'] = [vae_id, 0]
 
 
 
 
 
 
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  for i, img_filename in enumerate(valid_images):
62
  load_id = assembler._get_unique_id()
63
  load_node = create_node(assembler, "LoadImage", f"Load Reference Image {i+1}")
 
71
  scale_node['inputs']['resolution_steps'] = 1
72
  scale_node['inputs']['image'] = [load_id, 0]
73
  assembler.workflow[scale_id] = scale_node
74
+
75
+ image_key = f"image{i+1}"
76
+ assembler.workflow[pos_prompt_id]['inputs'][image_key] = [scale_id, 0]
77
+ assembler.workflow[neg_prompt_id]['inputs'][image_key] = [scale_id, 0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  pos_ref_id = assembler._get_unique_id()
80
  pos_ref_node = create_node(assembler, "FluxKontextMultiReferenceLatentMethod", "Edit Model Reference Method")
81
  pos_ref_node['inputs']['reference_latents_method'] = "index_timestep_zero"
82
+ pos_ref_node['inputs']['conditioning'] = [pos_prompt_id, 0]
83
  assembler.workflow[pos_ref_id] = pos_ref_node
84
 
85
  neg_ref_id = assembler._get_unique_id()
86
  neg_ref_node = create_node(assembler, "FluxKontextMultiReferenceLatentMethod", "Edit Model Reference Method")
87
  neg_ref_node['inputs']['reference_latents_method'] = "index_timestep_zero"
88
+ neg_ref_node['inputs']['conditioning'] = [neg_prompt_id, 0]
89
  assembler.workflow[neg_ref_id] = neg_ref_node
90
 
91
  assembler.workflow[ksampler_id]['inputs']['positive'] = [pos_ref_id, 0]
 
110
  assembler.workflow[cfg_norm_id] = cfg_norm_node
111
  assembler.workflow[ksampler_id]['inputs']['model'] = [cfg_norm_id, 0]
112
 
113
+ print(f"Qwen-Image Edit injector applied with {len(valid_images)} reference image(s). Connected VAE dynamically.")
 
 
 
 
 
 
comfy_integration/setup.py CHANGED
@@ -72,15 +72,7 @@ def initialize_comfyui():
72
  except Exception as e:
73
  print(f"⚠️ Warning: Could not apply PR #108 compatibility patch for ComfyUI-IPAdapter-Flux: {e}")
74
 
75
- # 4. ComfyUI-Newbie-Nodes
76
- newbie_nodes_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-Newbie-Nodes")
77
- if not os.path.exists(newbie_nodes_path):
78
- os.system(f"git clone https://github.com/NewBieAI-Lab/ComfyUI-Newbie-Nodes.git {newbie_nodes_path}")
79
- print("✅ ComfyUI-Newbie-Nodes extension cloned.")
80
- else:
81
- print("✅ ComfyUI-Newbie-Nodes extension already exists.")
82
-
83
- # 5. comfyui-krea2-controlnet
84
  krea2_controlnet_nodes_path = os.path.join(APP_DIR, "custom_nodes", "comfyui-krea2-controlnet")
85
  if not os.path.exists(krea2_controlnet_nodes_path):
86
  os.system(f"git clone https://github.com/facok/comfyui-krea2-controlnet.git {krea2_controlnet_nodes_path}")
@@ -88,7 +80,7 @@ def initialize_comfyui():
88
  else:
89
  print("✅ comfyui-krea2-controlnet extension already exists.")
90
 
91
- # 6. comfyui-krea2edit
92
  krea2edit_nodes_path = os.path.join(APP_DIR, "custom_nodes", "comfyui-krea2edit")
93
  if not os.path.exists(krea2edit_nodes_path):
94
  os.system(f"git clone https://github.com/lbouaraba/comfyui-krea2edit.git {krea2edit_nodes_path}")
 
72
  except Exception as e:
73
  print(f"⚠️ Warning: Could not apply PR #108 compatibility patch for ComfyUI-IPAdapter-Flux: {e}")
74
 
75
+ # 4. comfyui-krea2-controlnet
 
 
 
 
 
 
 
 
76
  krea2_controlnet_nodes_path = os.path.join(APP_DIR, "custom_nodes", "comfyui-krea2-controlnet")
77
  if not os.path.exists(krea2_controlnet_nodes_path):
78
  os.system(f"git clone https://github.com/facok/comfyui-krea2-controlnet.git {krea2_controlnet_nodes_path}")
 
80
  else:
81
  print("✅ comfyui-krea2-controlnet extension already exists.")
82
 
83
+ # 5. comfyui-krea2edit
84
  krea2edit_nodes_path = os.path.join(APP_DIR, "custom_nodes", "comfyui-krea2edit")
85
  if not os.path.exists(krea2edit_nodes_path):
86
  os.system(f"git clone https://github.com/lbouaraba/comfyui-krea2edit.git {krea2edit_nodes_path}")
core/pipelines/sd_image_pipeline.py CHANGED
@@ -151,7 +151,7 @@ class SdImagePipeline(BasePipeline):
151
  'latent_generator_template': latent_generator_template
152
  }
153
 
154
- recipe_path = os.path.join(os.path.dirname(__file__), "workflow_recipes", "sd_unified_recipe.yaml")
155
  assembler = WorkflowAssembler(recipe_path, dynamic_values=dynamic_values)
156
 
157
  hidream_o1_smoothing_data = []
@@ -180,11 +180,11 @@ class SdImagePipeline(BasePipeline):
180
  "conditioning_chain": active_conditioning,
181
  "reference_latent_chain": active_reference_latents,
182
  "hidream_o1_reference_chain": active_hidream_o1_reference,
183
- "joyai_reference_chain": active_joyai_reference,
184
  "krea2_identity_edit_chain": active_krea2_identity_edit,
185
- "krea2_reference_edit_chain": active_krea2_reference_edit,
186
  "qwen_image_edit_chain": active_qwen_image_edit,
187
- "boogu_edit_chain": active_boogu_edit,
188
  "reference_image_chain": active_reference_images,
189
  "vae_chain": [ui_inputs.get('vae_name')] if ui_inputs.get('vae_name') else [],
190
  "hidream_o1_smoothing_chain": hidream_o1_smoothing_data,
 
151
  'latent_generator_template': latent_generator_template
152
  }
153
 
154
+ recipe_path = os.path.join(os.path.dirname(__file__), "workflow_recipes", "unified_recipe.yaml")
155
  assembler = WorkflowAssembler(recipe_path, dynamic_values=dynamic_values)
156
 
157
  hidream_o1_smoothing_data = []
 
180
  "conditioning_chain": active_conditioning,
181
  "reference_latent_chain": active_reference_latents,
182
  "hidream_o1_reference_chain": active_hidream_o1_reference,
183
+ "joyai_image_chain": active_joyai_reference,
184
  "krea2_identity_edit_chain": active_krea2_identity_edit,
185
+ "krea2_style_reference_chain": active_krea2_reference_edit,
186
  "qwen_image_edit_chain": active_qwen_image_edit,
187
+ "boogu_image_edit_chain": active_boogu_edit,
188
  "reference_image_chain": active_reference_images,
189
  "vae_chain": [ui_inputs.get('vae_name')] if ui_inputs.get('vae_name') else [],
190
  "hidream_o1_smoothing_chain": hidream_o1_smoothing_data,
core/pipelines/workflow_recipes/_partials/{_base_sampler_sd.yaml → _base_sampler.yaml} RENAMED
@@ -1,28 +1,28 @@
1
- nodes:
2
- ksampler:
3
- class_type: KSampler
4
- title: "KSampler"
5
- params:
6
- denoise: 1.0
7
- vae_decode:
8
- class_type: VAEDecode
9
- title: "VAE Decode"
10
- save_image:
11
- class_type: SaveImage
12
- title: "Save Image"
13
- params: {}
14
-
15
- connections:
16
- - from: "ksampler:0"
17
- to: "vae_decode:samples"
18
- - from: "vae_decode:0"
19
- to: "save_image:images"
20
-
21
- ui_map:
22
- seed: "ksampler:seed"
23
- steps: "ksampler:steps"
24
- cfg: "ksampler:cfg"
25
- sampler_name: "ksampler:sampler_name"
26
- scheduler: "ksampler:scheduler"
27
- denoise: "ksampler:denoise"
28
  filename_prefix: "save_image:filename_prefix"
 
1
+ nodes:
2
+ ksampler:
3
+ class_type: KSampler
4
+ title: "KSampler"
5
+ params:
6
+ denoise: 1.0
7
+ vae_decode:
8
+ class_type: VAEDecode
9
+ title: "VAE Decode"
10
+ save_image:
11
+ class_type: SaveImage
12
+ title: "Save Image"
13
+ params: {}
14
+
15
+ connections:
16
+ - from: "ksampler:0"
17
+ to: "vae_decode:samples"
18
+ - from: "vae_decode:0"
19
+ to: "save_image:images"
20
+
21
+ ui_map:
22
+ seed: "ksampler:seed"
23
+ steps: "ksampler:steps"
24
+ cfg: "ksampler:cfg"
25
+ sampler_name: "ksampler:sampler_name"
26
+ scheduler: "ksampler:scheduler"
27
+ denoise: "ksampler:denoise"
28
  filename_prefix: "save_image:filename_prefix"
core/pipelines/workflow_recipes/_partials/conditioning/boogu-image.yaml CHANGED
@@ -1,74 +1,67 @@
1
- nodes:
2
- pos_prompt:
3
- class_type: CLIPTextEncode
4
- title: "CLIP Text Encode (Positive)"
5
- neg_prompt:
6
- class_type: CLIPTextEncode
7
- title: "CLIP Text Encode (Negative)"
8
- unet_loader:
9
- class_type: UNETLoader
10
- title: "Load Diffusion Model"
11
- params:
12
- weight_dtype: "default"
13
- clip_loader:
14
- class_type: CLIPLoader
15
- title: "Load CLIP"
16
- params:
17
- type: "boogu"
18
- device: "default"
19
- vae_loader:
20
- class_type: VAELoader
21
- title: "Load VAE"
22
-
23
- connections:
24
- - from: "unet_loader:0"
25
- to: "ksampler:model"
26
- - from: "clip_loader:0"
27
- to: "pos_prompt:clip"
28
- - from: "clip_loader:0"
29
- to: "neg_prompt:clip"
30
- - from: "pos_prompt:0"
31
- to: "ksampler:positive"
32
- - from: "neg_prompt:0"
33
- to: "ksampler:negative"
34
- - from: "vae_loader:0"
35
- to: "vae_decode:vae"
36
- - from: "vae_loader:0"
37
- to: "vae_encode:vae"
38
-
39
- dynamic_lora_chains:
40
- lora_chain:
41
- template: "LoraLoader"
42
- output_map:
43
- "unet_loader:0": "model"
44
- "clip_loader:0": "clip"
45
- input_map:
46
- "model": "model"
47
- "clip": "clip"
48
- end_input_map:
49
- "model": ["ksampler:model"]
50
- "clip": ["pos_prompt:clip", "neg_prompt:clip"]
51
-
52
- dynamic_conditioning_chains:
53
- conditioning_chain:
54
- ksampler_node: "ksampler"
55
- clip_source: "clip_loader:0"
56
-
57
- dynamic_boogu_edit_chains:
58
- boogu_edit_chain:
59
- ksampler_node: "ksampler"
60
- pos_prompt_node: "pos_prompt"
61
- neg_prompt_node: "neg_prompt"
62
- clip_loader_node: "clip_loader"
63
- vae_loader_node: "vae_loader"
64
-
65
- dynamic_pid_chains:
66
- pid_chain:
67
- ksampler_node: "ksampler"
68
-
69
- ui_map:
70
- positive_prompt: "pos_prompt:text"
71
- negative_prompt: "neg_prompt:text"
72
- unet_name: "unet_loader:unet_name"
73
- clip_name: "clip_loader:clip_name"
74
- vae_name: "vae_loader:vae_name"
 
1
+ nodes:
2
+ boogu_prompt:
3
+ class_type: TextEncodeBooguEdit
4
+ title: "Text Encode Boogu Edit"
5
+ unet_loader:
6
+ class_type: UNETLoader
7
+ title: "Load Diffusion Model"
8
+ params:
9
+ weight_dtype: "default"
10
+ clip_loader:
11
+ class_type: CLIPLoader
12
+ title: "Load CLIP"
13
+ params:
14
+ type: "boogu"
15
+ device: "default"
16
+ vae_loader:
17
+ class_type: VAELoader
18
+ title: "Load VAE"
19
+
20
+ connections:
21
+ - from: "unet_loader:0"
22
+ to: "ksampler:model"
23
+ - from: "clip_loader:0"
24
+ to: "boogu_prompt:clip"
25
+ - from: "boogu_prompt:0"
26
+ to: "ksampler:positive"
27
+ - from: "boogu_prompt:1"
28
+ to: "ksampler:negative"
29
+ - from: "vae_loader:0"
30
+ to: "vae_decode:vae"
31
+ - from: "vae_loader:0"
32
+ to: "vae_encode:vae"
33
+
34
+ dynamic_lora_chains:
35
+ lora_chain:
36
+ template: "LoraLoader"
37
+ output_map:
38
+ "unet_loader:0": "model"
39
+ "clip_loader:0": "clip"
40
+ input_map:
41
+ "model": "model"
42
+ "clip": "clip"
43
+ end_input_map:
44
+ "model": ["ksampler:model"]
45
+ "clip": ["boogu_prompt:clip"]
46
+
47
+ dynamic_conditioning_chains:
48
+ conditioning_chain:
49
+ ksampler_node: "ksampler"
50
+ clip_source: "clip_loader:0"
51
+
52
+ dynamic_boogu_image_edit_chains:
53
+ boogu_image_edit_chain:
54
+ ksampler_node: "ksampler"
55
+ boogu_prompt_node: "boogu_prompt"
56
+ vae_loader_node: "vae_loader"
57
+
58
+ dynamic_pid_chains:
59
+ pid_chain:
60
+ ksampler_node: "ksampler"
61
+
62
+ ui_map:
63
+ positive_prompt: "boogu_prompt:prompt"
64
+ negative_prompt: "boogu_prompt:negative_prompt"
65
+ unet_name: "unet_loader:unet_name"
66
+ clip_name: "clip_loader:clip_name"
67
+ vae_name: "vae_loader:vae_name"
 
 
 
 
 
 
 
core/pipelines/workflow_recipes/_partials/conditioning/joyai-image.yaml CHANGED
@@ -71,8 +71,8 @@ dynamic_pid_chains:
71
  pid_chain:
72
  ksampler_node: "ksampler"
73
 
74
- dynamic_joyai_reference_chains:
75
- joyai_reference_chain:
76
  pos_prompt_node: "pos_prompt"
77
  neg_prompt_node: "neg_prompt"
78
  vae_node: "vae_loader"
 
71
  pid_chain:
72
  ksampler_node: "ksampler"
73
 
74
+ dynamic_joyai_image_chains:
75
+ joyai_image_chain:
76
  pos_prompt_node: "pos_prompt"
77
  neg_prompt_node: "neg_prompt"
78
  vae_node: "vae_loader"
core/pipelines/workflow_recipes/_partials/conditioning/krea-2.yaml CHANGED
@@ -62,8 +62,8 @@ dynamic_krea2_identity_edit_chains:
62
  clip_loader_node: "clip_loader"
63
  vae_loader_node: "vae_loader"
64
 
65
- dynamic_krea2_reference_edit_chains:
66
- krea2_reference_edit_chain:
67
  ksampler_node: "ksampler"
68
  pos_prompt_node: "pos_prompt"
69
  neg_prompt_node: "neg_prompt"
 
62
  clip_loader_node: "clip_loader"
63
  vae_loader_node: "vae_loader"
64
 
65
+ dynamic_krea2_style_reference_chains:
66
+ krea2_style_reference_chain:
67
  ksampler_node: "ksampler"
68
  pos_prompt_node: "pos_prompt"
69
  neg_prompt_node: "neg_prompt"
core/pipelines/workflow_recipes/_partials/conditioning/newbie-image.yaml CHANGED
@@ -46,9 +46,9 @@ connections:
46
  - from: "vae_loader:0"
47
  to: "vae_encode:vae"
48
 
49
- dynamic_newbie_lora_chains:
50
  lora_chain:
51
- template: "NewBieLoraLoader"
52
  output_map:
53
  "unet_loader:0": "model"
54
  "clip_loader:0": "clip"
 
46
  - from: "vae_loader:0"
47
  to: "vae_encode:vae"
48
 
49
+ dynamic_lora_chains:
50
  lora_chain:
51
+ template: "LoraLoader"
52
  output_map:
53
  "unet_loader:0": "model"
54
  "clip_loader:0": "clip"
core/pipelines/workflow_recipes/_partials/conditioning/qwen-image.yaml CHANGED
@@ -1,92 +1,91 @@
1
- nodes:
2
- pos_prompt:
3
- class_type: CLIPTextEncode
4
- title: "CLIP Text Encode (Positive)"
5
- neg_prompt:
6
- class_type: CLIPTextEncode
7
- title: "CLIP Text Encode (Negative)"
8
- unet_loader:
9
- class_type: UNETLoader
10
- title: "Load Qwen UNET"
11
- params:
12
- weight_dtype: "default"
13
- vae_loader:
14
- class_type: VAELoader
15
- title: "Load Qwen VAE"
16
- clip_loader:
17
- class_type: CLIPLoader
18
- title: "Load Qwen CLIP"
19
- params:
20
- type: "qwen_image"
21
- device: "default"
22
- model_sampler:
23
- class_type: ModelSamplingAuraFlow
24
- title: "ModelSamplingAuraFlow"
25
- params:
26
- shift: 3.1
27
-
28
- connections:
29
- - from: "unet_loader:0"
30
- to: "model_sampler:model"
31
-
32
- - from: "model_sampler:0"
33
- to: "ksampler:model"
34
-
35
- - from: "clip_loader:0"
36
- to: "pos_prompt:clip"
37
- - from: "clip_loader:0"
38
- to: "neg_prompt:clip"
39
-
40
- - from: "vae_loader:0"
41
- to: "vae_decode:vae"
42
- - from: "vae_loader:0"
43
- to: "vae_encode:vae"
44
-
45
- - from: "pos_prompt:0"
46
- to: "ksampler:positive"
47
- - from: "neg_prompt:0"
48
- to: "ksampler:negative"
49
-
50
- dynamic_lora_chains:
51
- lora_chain:
52
- template: "LoraLoader"
53
- output_map:
54
- "unet_loader:0": "model"
55
- "clip_loader:0": "clip"
56
- input_map:
57
- "model": "model"
58
- "clip": "clip"
59
- end_input_map:
60
- "model": ["model_sampler:model"]
61
- "clip": ["pos_prompt:clip", "neg_prompt:clip"]
62
-
63
- dynamic_controlnet_chains:
64
- controlnet_chain:
65
- template: "ControlNetApplyAdvanced"
66
- ksampler_node: "ksampler"
67
- vae_source: "vae_loader:0"
68
-
69
- dynamic_conditioning_chains:
70
- conditioning_chain:
71
- ksampler_node: "ksampler"
72
- clip_source: "clip_loader:0"
73
-
74
- dynamic_qwen_image_edit_chains:
75
- qwen_image_edit_chain:
76
- ksampler_node: "ksampler"
77
- pos_prompt_node: "pos_prompt"
78
- neg_prompt_node: "neg_prompt"
79
- clip_loader_node: "clip_loader"
80
- vae_loader_node: "vae_loader"
81
- model_sampler_node: "model_sampler"
82
-
83
- dynamic_pid_chains:
84
- pid_chain:
85
- ksampler_node: "ksampler"
86
-
87
- ui_map:
88
- positive_prompt: "pos_prompt:text"
89
- negative_prompt: "neg_prompt:text"
90
- unet_name: "unet_loader:unet_name"
91
- vae_name: "vae_loader:vae_name"
92
  clip_name: "clip_loader:clip_name"
 
1
+ nodes:
2
+ pos_prompt:
3
+ class_type: TextEncodeQwenImageEditPlus
4
+ title: "Text Encode Qwen Image Edit Plus (Positive)"
5
+ neg_prompt:
6
+ class_type: TextEncodeQwenImageEditPlus
7
+ title: "Text Encode Qwen Image Edit Plus (Negative)"
8
+ unet_loader:
9
+ class_type: UNETLoader
10
+ title: "Load Qwen UNET"
11
+ params:
12
+ weight_dtype: "default"
13
+ vae_loader:
14
+ class_type: VAELoader
15
+ title: "Load Qwen VAE"
16
+ clip_loader:
17
+ class_type: CLIPLoader
18
+ title: "Load Qwen CLIP"
19
+ params:
20
+ type: "qwen_image"
21
+ device: "default"
22
+ model_sampler:
23
+ class_type: ModelSamplingAuraFlow
24
+ title: "ModelSamplingAuraFlow"
25
+ params:
26
+ shift: 3.1
27
+
28
+ connections:
29
+ - from: "unet_loader:0"
30
+ to: "model_sampler:model"
31
+
32
+ - from: "model_sampler:0"
33
+ to: "ksampler:model"
34
+
35
+ - from: "clip_loader:0"
36
+ to: "pos_prompt:clip"
37
+ - from: "clip_loader:0"
38
+ to: "neg_prompt:clip"
39
+
40
+ - from: "vae_loader:0"
41
+ to: "vae_decode:vae"
42
+ - from: "vae_loader:0"
43
+ to: "vae_encode:vae"
44
+
45
+ - from: "pos_prompt:0"
46
+ to: "ksampler:positive"
47
+ - from: "neg_prompt:0"
48
+ to: "ksampler:negative"
49
+
50
+ dynamic_lora_chains:
51
+ lora_chain:
52
+ template: "LoraLoader"
53
+ output_map:
54
+ "unet_loader:0": "model"
55
+ "clip_loader:0": "clip"
56
+ input_map:
57
+ "model": "model"
58
+ "clip": "clip"
59
+ end_input_map:
60
+ "model": ["model_sampler:model"]
61
+ "clip": ["pos_prompt:clip", "neg_prompt:clip"]
62
+
63
+ dynamic_controlnet_chains:
64
+ controlnet_chain:
65
+ template: "ControlNetApplyAdvanced"
66
+ ksampler_node: "ksampler"
67
+ vae_source: "vae_loader:0"
68
+
69
+ dynamic_conditioning_chains:
70
+ conditioning_chain:
71
+ ksampler_node: "ksampler"
72
+ clip_source: "clip_loader:0"
73
+
74
+ dynamic_qwen_image_edit_chains:
75
+ qwen_image_edit_chain:
76
+ ksampler_node: "ksampler"
77
+ pos_prompt_node: "pos_prompt"
78
+ neg_prompt_node: "neg_prompt"
79
+ vae_loader_node: "vae_loader"
80
+ model_sampler_node: "model_sampler"
81
+
82
+ dynamic_pid_chains:
83
+ pid_chain:
84
+ ksampler_node: "ksampler"
85
+
86
+ ui_map:
87
+ positive_prompt: "pos_prompt:prompt"
88
+ negative_prompt: "neg_prompt:prompt"
89
+ unet_name: "unet_loader:unet_name"
90
+ vae_name: "vae_loader:vae_name"
 
91
  clip_name: "clip_loader:clip_name"
core/pipelines/workflow_recipes/{sd_unified_recipe.yaml → unified_recipe.yaml} RENAMED
@@ -1,8 +1,8 @@
1
- imports:
2
- - "_partials/_base_sampler_sd.yaml"
3
- - "_partials/input/{{ task_type }}.yaml"
4
- - "_partials/conditioning/{{ model_type }}.yaml"
5
-
6
- connections:
7
- - from: "latent_source:0"
8
  to: "ksampler:latent_image"
 
1
+ imports:
2
+ - "_partials/_base_sampler.yaml"
3
+ - "_partials/input/{{ task_type }}.yaml"
4
+ - "_partials/conditioning/{{ model_type }}.yaml"
5
+
6
+ connections:
7
+ - from: "latent_source:0"
8
  to: "ksampler:latent_image"
core/workflow_assembler.py CHANGED
@@ -1,179 +1,165 @@
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
- else:
40
- print(f"⚠️ Warning: Module '{module_path}' for injector '{chain_type}' does not have an 'inject' function.")
41
- except ImportError as e:
42
- print(f"❌ Error importing module '{module_path}' for injector '{chain_type}': {e}")
43
-
44
- if not self.injector_order:
45
- print("⚠️ Warning: 'injector_order' is not defined in injectors.yaml. Using definition order.")
46
- self.injector_order = list(definitions.keys())
47
-
48
- except FileNotFoundError:
49
- print(f"❌ FATAL: Could not find injectors.yaml at {injectors_path}. Dynamic chains will not work.")
50
- self.injector_order = []
51
- self.global_injectors = {}
52
- except Exception as e:
53
- print(f"❌ FATAL: Could not load or parse injectors.yaml. Dynamic chains will not work. Error: {e}")
54
- self.injector_order = []
55
- self.global_injectors = {}
56
-
57
- def _get_unique_id(self):
58
- self.node_counter += 1
59
- return str(self.node_counter)
60
-
61
- def _get_node_template(self, class_type):
62
- if class_type not in NODE_CLASS_MAPPINGS:
63
- raise ValueError(f"Node class '{class_type}' not found. Ensure it's correctly imported in comfy_integration/nodes.py.")
64
-
65
- node_class = NODE_CLASS_MAPPINGS[class_type]
66
- input_types = node_class.INPUT_TYPES()
67
-
68
- template = {
69
- "inputs": {},
70
- "class_type": class_type,
71
- "_meta": {"title": node_class.NODE_NAME if hasattr(node_class, 'NODE_NAME') else class_type}
72
- }
73
-
74
- all_inputs = {**input_types.get('required', {}), **input_types.get('optional', {})}
75
- for name, details in all_inputs.items():
76
- config = details[1] if len(details) > 1 and isinstance(details[1], dict) else {}
77
- template["inputs"][name] = config.get("default")
78
-
79
- return template
80
-
81
- def _load_and_merge_recipe(self, recipe_filename, dynamic_values, search_context_dir=None):
82
- search_path = search_context_dir or self.base_path
83
- recipe_path_to_use = os.path.join(search_path, recipe_filename)
84
-
85
- if not os.path.exists(recipe_path_to_use):
86
- raise FileNotFoundError(f"Recipe file not found: {recipe_path_to_use}")
87
-
88
- with open(recipe_path_to_use, 'r', encoding='utf-8') as f:
89
- content = f.read()
90
-
91
- for key, value in dynamic_values.items():
92
- if value is not None:
93
- content = content.replace(f"{{{{ {key} }}}}", str(value))
94
-
95
- main_recipe = yaml.safe_load(content)
96
-
97
- merged_recipe = {'nodes': {}, 'connections': [], 'ui_map': {}}
98
- for key in self.injector_order:
99
- if key.startswith('dynamic_'):
100
- merged_recipe[key] = {}
101
-
102
- parent_recipe_dir = os.path.dirname(recipe_path_to_use)
103
- for import_path_template in main_recipe.get('imports', []):
104
- import_path = import_path_template
105
- for key, value in dynamic_values.items():
106
- if value is not None:
107
- import_path = import_path.replace(f"{{{{ {key} }}}}", str(value))
108
-
109
- try:
110
- imported_recipe = self._load_and_merge_recipe(import_path, dynamic_values, search_context_dir=parent_recipe_dir)
111
- merged_recipe['nodes'].update(imported_recipe.get('nodes', {}))
112
- merged_recipe['connections'].extend(imported_recipe.get('connections', []))
113
- merged_recipe['ui_map'].update(imported_recipe.get('ui_map', {}))
114
- for key in self.injector_order:
115
- if key in imported_recipe and key.startswith('dynamic_'):
116
- merged_recipe[key].update(imported_recipe.get(key, {}))
117
- except FileNotFoundError:
118
- print(f"Warning: Optional recipe partial '{import_path}' not found. Skipping.")
119
-
120
- merged_recipe['nodes'].update(main_recipe.get('nodes', {}))
121
- merged_recipe['connections'].extend(main_recipe.get('connections', []))
122
- merged_recipe['ui_map'].update(main_recipe.get('ui_map', {}))
123
- for key in self.injector_order:
124
- if key in main_recipe and key.startswith('dynamic_'):
125
- merged_recipe[key].update(main_recipe.get(key, {}))
126
-
127
- return merged_recipe
128
-
129
- def assemble(self, ui_values):
130
- self.ui_values = 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
 
1
+ import yaml
2
+ import os
3
+ import importlib
4
+ from copy import deepcopy
5
+ from comfy_integration.nodes import NODE_CLASS_MAPPINGS
6
+ from chain_injectors import discover_injectors, get_registered_features
7
+ from core.settings import FEATURES_CONFIG
8
+
9
+ class WorkflowAssembler:
10
+ def __init__(self, recipe_path, dynamic_values=None):
11
+ self.base_path = os.path.dirname(recipe_path)
12
+ self.dynamic_values = dynamic_values or {}
13
+ self.node_counter = 0
14
+ self.workflow = {}
15
+ self.node_map = {}
16
+
17
+ model_type = self.dynamic_values.get('model_type')
18
+ self._load_injector_config(model_type=model_type)
19
+
20
+ self.recipe = self._load_and_merge_recipe(os.path.basename(recipe_path), self.dynamic_values)
21
+
22
+ def _load_injector_config(self, model_type=None):
23
+ self.global_injectors = discover_injectors()
24
+ registered_features = get_registered_features()
25
+
26
+ order = []
27
+ if model_type and model_type in FEATURES_CONFIG:
28
+ enabled_features = FEATURES_CONFIG[model_type].get('enabled_chains', [])
29
+ for feat in enabled_features:
30
+ if feat in registered_features:
31
+ chain_key = registered_features[feat]['chain_type']
32
+ else:
33
+ chain_key = f"dynamic_{feat}_chains"
34
+ if chain_key in self.global_injectors and chain_key not in order:
35
+ order.append(chain_key)
36
+
37
+ for chain_key in self.global_injectors.keys():
38
+ if chain_key not in order:
39
+ order.append(chain_key)
40
+
41
+ self.injector_order = order
42
+
43
+ def _get_unique_id(self):
44
+ self.node_counter += 1
45
+ return str(self.node_counter)
46
+
47
+ def _get_node_template(self, class_type):
48
+ if class_type not in NODE_CLASS_MAPPINGS:
49
+ raise ValueError(f"Node class '{class_type}' not found. Ensure it's correctly imported in comfy_integration/nodes.py.")
50
+
51
+ node_class = NODE_CLASS_MAPPINGS[class_type]
52
+ input_types = node_class.INPUT_TYPES()
53
+
54
+ template = {
55
+ "inputs": {},
56
+ "class_type": class_type,
57
+ "_meta": {"title": node_class.NODE_NAME if hasattr(node_class, 'NODE_NAME') else class_type}
58
+ }
59
+
60
+ all_inputs = {**input_types.get('required', {}), **input_types.get('optional', {})}
61
+ for name, details in all_inputs.items():
62
+ config = details[1] if len(details) > 1 and isinstance(details[1], dict) else {}
63
+ template["inputs"][name] = config.get("default")
64
+
65
+ return template
66
+
67
+ def _load_and_merge_recipe(self, recipe_filename, dynamic_values, search_context_dir=None):
68
+ search_path = search_context_dir or self.base_path
69
+ recipe_path_to_use = os.path.join(search_path, recipe_filename)
70
+
71
+ if not os.path.exists(recipe_path_to_use):
72
+ raise FileNotFoundError(f"Recipe file not found: {recipe_path_to_use}")
73
+
74
+ with open(recipe_path_to_use, 'r', encoding='utf-8') as f:
75
+ content = f.read()
76
+
77
+ for key, value in dynamic_values.items():
78
+ if value is not None:
79
+ content = content.replace(f"{{{{ {key} }}}}", str(value))
80
+
81
+ main_recipe = yaml.safe_load(content)
82
+
83
+ merged_recipe = {'nodes': {}, 'connections': [], 'ui_map': {}}
84
+ for key in self.injector_order:
85
+ if key.startswith('dynamic_'):
86
+ merged_recipe[key] = {}
87
+
88
+ parent_recipe_dir = os.path.dirname(recipe_path_to_use)
89
+ for import_path_template in main_recipe.get('imports', []):
90
+ import_path = import_path_template
91
+ for key, value in dynamic_values.items():
92
+ if value is not None:
93
+ import_path = import_path.replace(f"{{{{ {key} }}}}", str(value))
94
+
95
+ try:
96
+ imported_recipe = self._load_and_merge_recipe(import_path, dynamic_values, search_context_dir=parent_recipe_dir)
97
+ merged_recipe['nodes'].update(imported_recipe.get('nodes', {}))
98
+ merged_recipe['connections'].extend(imported_recipe.get('connections', []))
99
+ merged_recipe['ui_map'].update(imported_recipe.get('ui_map', {}))
100
+ for key in self.injector_order:
101
+ if key in imported_recipe and key.startswith('dynamic_'):
102
+ merged_recipe[key].update(imported_recipe.get(key, {}))
103
+ except FileNotFoundError:
104
+ print(f"Warning: Optional recipe partial '{import_path}' not found. Skipping.")
105
+
106
+ merged_recipe['nodes'].update(main_recipe.get('nodes', {}))
107
+ merged_recipe['connections'].extend(main_recipe.get('connections', []))
108
+ merged_recipe['ui_map'].update(main_recipe.get('ui_map', {}))
109
+ for key in self.injector_order:
110
+ if key in main_recipe and key.startswith('dynamic_'):
111
+ merged_recipe[key].update(main_recipe.get(key, {}))
112
+
113
+ return merged_recipe
114
+
115
+ def assemble(self, ui_values):
116
+ self.ui_values = ui_values
117
+ for name, details in self.recipe['nodes'].items():
118
+ class_type = details['class_type']
119
+ template = self._get_node_template(class_type)
120
+ node_data = deepcopy(template)
121
+
122
+ unique_id = self._get_unique_id()
123
+ self.node_map[name] = unique_id
124
+
125
+ if 'params' in details:
126
+ for param, value in details['params'].items():
127
+ if param in node_data['inputs']:
128
+ node_data['inputs'][param] = value
129
+
130
+ self.workflow[unique_id] = node_data
131
+
132
+ for ui_key, target in self.recipe.get('ui_map', {}).items():
133
+ if ui_key in ui_values and ui_values[ui_key] is not None:
134
+ target_list = target if isinstance(target, list) else [target]
135
+ for t in target_list:
136
+ target_name, target_param = t.split(':')
137
+ if target_name in self.node_map:
138
+ self.workflow[self.node_map[target_name]]['inputs'][target_param] = ui_values[ui_key]
139
+
140
+ for conn in self.recipe.get('connections', []):
141
+ from_name, from_output_idx = conn['from'].split(':')
142
+ to_name, to_input_name = conn['to'].split(':')
143
+
144
+ from_id = self.node_map.get(from_name)
145
+ to_id = self.node_map.get(to_name)
146
+
147
+ if from_id and to_id:
148
+ self.workflow[to_id]['inputs'][to_input_name] = [from_id, int(from_output_idx)]
149
+
150
+ print("--- [Assembler] Applying dynamic injectors ---")
151
+ recipe_chain_types = {key for key in self.recipe if key.startswith('dynamic_')}
152
+ processing_order = [key for key in self.injector_order if key in recipe_chain_types]
153
+
154
+ for chain_type in processing_order:
155
+ injector_func = self.global_injectors.get(chain_type)
156
+ if injector_func:
157
+ for chain_key, chain_def in self.recipe.get(chain_type, {}).items():
158
+ if chain_key in ui_values and ui_values[chain_key]:
159
+ print(f" -> Injecting '{chain_type}' for '{chain_key}'...")
160
+ chain_items = ui_values[chain_key]
161
+ injector_func(self, chain_def, chain_items)
162
+
163
+ print("--- [Assembler] Finished applying injectors ---")
164
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  return self.workflow
ui/events/change_handlers.py CHANGED
@@ -52,13 +52,13 @@ def make_update_fn(m_comp, cat_comp, cs_comp, ar_comp, width_comp, height_comp,
52
  architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
53
  arch_model_type = architectures_dict.get(m_type, {}).get("model_type", m_type.lower().replace(" ", "").replace(".", ""))
54
 
55
- arch_features = FEATURES_CONFIG.get(arch_model_type, FEATURES_CONFIG.get('default', {}))
56
  enabled_chains = arch_features.get('enabled_chains', [])
57
 
58
  if lora_acc: updates[lora_acc] = gr.update(visible=('lora' in enabled_chains))
59
  if cn_acc: updates[cn_acc] = gr.update(visible=('controlnet' in enabled_chains))
60
  if anima_cn_acc: updates[anima_cn_acc] = gr.update(visible=('anima_controlnet_lllite' in enabled_chains))
61
- if diffsynth_cn_acc: updates[diffsynth_cn_acc] = gr.update(visible=('controlnet_model_patch' in enabled_chains))
62
  if krea2_cn_acc: updates[krea2_cn_acc] = gr.update(visible=('krea2_controlnet' in enabled_chains))
63
  if ipa_acc: updates[ipa_acc] = gr.update(visible=('ipadapter' in enabled_chains))
64
  if flux1_ipa_acc: updates[flux1_ipa_acc] = gr.update(visible=('flux1_ipadapter' in enabled_chains))
@@ -68,11 +68,11 @@ def make_update_fn(m_comp, cat_comp, cs_comp, ar_comp, width_comp, height_comp,
68
  if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
69
  if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
70
  if hidream_o1_ref_acc: updates[hidream_o1_ref_acc] = gr.update(visible=('hidream_o1_reference' in enabled_chains))
71
- if joyai_ref_acc: updates[joyai_ref_acc] = gr.update(visible=('joyai_reference' in enabled_chains))
72
  if krea2_identity_edit_acc: updates[krea2_identity_edit_acc] = gr.update(visible=('krea2_identity_edit' in enabled_chains))
73
- if krea2_reference_edit_acc: updates[krea2_reference_edit_acc] = gr.update(visible=('krea2_reference_edit' in enabled_chains))
74
  if qwen_image_edit_acc: updates[qwen_image_edit_acc] = gr.update(visible=('qwen_image_edit' in enabled_chains))
75
- if boogu_edit_acc: updates[boogu_edit_acc] = gr.update(visible=('boogu_edit' in enabled_chains))
76
  if ref_img_acc: updates[ref_img_acc] = gr.update(visible=('reference_image' in enabled_chains))
77
  if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
78
  if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
@@ -188,13 +188,13 @@ def make_model_change_fn(cat_comp_ref, cs_comp, ar_comp, width_comp, height_comp
188
  architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
189
  arch_model_type = architectures_dict.get(m_type, {}).get("model_type", m_type.lower().replace(" ", "").replace(".", ""))
190
 
191
- arch_features = FEATURES_CONFIG.get(arch_model_type, FEATURES_CONFIG.get('default', {}))
192
  enabled_chains = arch_features.get('enabled_chains', [])
193
 
194
  if lora_acc: updates[lora_acc] = gr.update(visible=('lora' in enabled_chains))
195
  if cn_acc: updates[cn_acc] = gr.update(visible=('controlnet' in enabled_chains))
196
  if anima_cn_acc: updates[anima_cn_acc] = gr.update(visible=('anima_controlnet_lllite' in enabled_chains))
197
- if diffsynth_cn_acc: updates[diffsynth_cn_acc] = gr.update(visible=('controlnet_model_patch' in enabled_chains))
198
  if krea2_cn_acc: updates[krea2_cn_acc] = gr.update(visible=('krea2_controlnet' in enabled_chains))
199
  if ipa_acc: updates[ipa_acc] = gr.update(visible=('ipadapter' in enabled_chains))
200
  if flux1_ipa_acc: updates[flux1_ipa_acc] = gr.update(visible=('flux1_ipadapter' in enabled_chains))
@@ -204,11 +204,11 @@ def make_model_change_fn(cat_comp_ref, cs_comp, ar_comp, width_comp, height_comp
204
  if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
205
  if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
206
  if hidream_o1_ref_acc: updates[hidream_o1_ref_acc] = gr.update(visible=('hidream_o1_reference' in enabled_chains))
207
- if joyai_ref_acc: updates[joyai_ref_acc] = gr.update(visible=('joyai_reference' in enabled_chains))
208
  if krea2_identity_edit_acc: updates[krea2_identity_edit_acc] = gr.update(visible=('krea2_identity_edit' in enabled_chains))
209
- if krea2_reference_edit_acc: updates[krea2_reference_edit_acc] = gr.update(visible=('krea2_reference_edit' in enabled_chains))
210
  if qwen_image_edit_acc: updates[qwen_image_edit_acc] = gr.update(visible=('qwen_image_edit' in enabled_chains))
211
- if boogu_edit_acc: updates[boogu_edit_acc] = gr.update(visible=('boogu_edit' in enabled_chains))
212
  if ref_img_acc: updates[ref_img_acc] = gr.update(visible=('reference_image' in enabled_chains))
213
  if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
214
  if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
 
52
  architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
53
  arch_model_type = architectures_dict.get(m_type, {}).get("model_type", m_type.lower().replace(" ", "").replace(".", ""))
54
 
55
+ arch_features = FEATURES_CONFIG.get(arch_model_type, {})
56
  enabled_chains = arch_features.get('enabled_chains', [])
57
 
58
  if lora_acc: updates[lora_acc] = gr.update(visible=('lora' in enabled_chains))
59
  if cn_acc: updates[cn_acc] = gr.update(visible=('controlnet' in enabled_chains))
60
  if anima_cn_acc: updates[anima_cn_acc] = gr.update(visible=('anima_controlnet_lllite' in enabled_chains))
61
+ if diffsynth_cn_acc: updates[diffsynth_cn_acc] = gr.update(visible=('diffsynth_controlnet' in enabled_chains))
62
  if krea2_cn_acc: updates[krea2_cn_acc] = gr.update(visible=('krea2_controlnet' in enabled_chains))
63
  if ipa_acc: updates[ipa_acc] = gr.update(visible=('ipadapter' in enabled_chains))
64
  if flux1_ipa_acc: updates[flux1_ipa_acc] = gr.update(visible=('flux1_ipadapter' in enabled_chains))
 
68
  if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
69
  if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
70
  if hidream_o1_ref_acc: updates[hidream_o1_ref_acc] = gr.update(visible=('hidream_o1_reference' in enabled_chains))
71
+ if joyai_ref_acc: updates[joyai_ref_acc] = gr.update(visible=('joyai_image' in enabled_chains))
72
  if krea2_identity_edit_acc: updates[krea2_identity_edit_acc] = gr.update(visible=('krea2_identity_edit' in enabled_chains))
73
+ if krea2_reference_edit_acc: updates[krea2_reference_edit_acc] = gr.update(visible=('krea2_style_reference' in enabled_chains))
74
  if qwen_image_edit_acc: updates[qwen_image_edit_acc] = gr.update(visible=('qwen_image_edit' in enabled_chains))
75
+ if boogu_edit_acc: updates[boogu_edit_acc] = gr.update(visible=('boogu_image_edit' in enabled_chains))
76
  if ref_img_acc: updates[ref_img_acc] = gr.update(visible=('reference_image' in enabled_chains))
77
  if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
78
  if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
 
188
  architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
189
  arch_model_type = architectures_dict.get(m_type, {}).get("model_type", m_type.lower().replace(" ", "").replace(".", ""))
190
 
191
+ arch_features = FEATURES_CONFIG.get(arch_model_type, {})
192
  enabled_chains = arch_features.get('enabled_chains', [])
193
 
194
  if lora_acc: updates[lora_acc] = gr.update(visible=('lora' in enabled_chains))
195
  if cn_acc: updates[cn_acc] = gr.update(visible=('controlnet' in enabled_chains))
196
  if anima_cn_acc: updates[anima_cn_acc] = gr.update(visible=('anima_controlnet_lllite' in enabled_chains))
197
+ if diffsynth_cn_acc: updates[diffsynth_cn_acc] = gr.update(visible=('diffsynth_controlnet' in enabled_chains))
198
  if krea2_cn_acc: updates[krea2_cn_acc] = gr.update(visible=('krea2_controlnet' in enabled_chains))
199
  if ipa_acc: updates[ipa_acc] = gr.update(visible=('ipadapter' in enabled_chains))
200
  if flux1_ipa_acc: updates[flux1_ipa_acc] = gr.update(visible=('flux1_ipadapter' in enabled_chains))
 
204
  if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
205
  if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
206
  if hidream_o1_ref_acc: updates[hidream_o1_ref_acc] = gr.update(visible=('hidream_o1_reference' in enabled_chains))
207
+ if joyai_ref_acc: updates[joyai_ref_acc] = gr.update(visible=('joyai_image' in enabled_chains))
208
  if krea2_identity_edit_acc: updates[krea2_identity_edit_acc] = gr.update(visible=('krea2_identity_edit' in enabled_chains))
209
+ if krea2_reference_edit_acc: updates[krea2_reference_edit_acc] = gr.update(visible=('krea2_style_reference' in enabled_chains))
210
  if qwen_image_edit_acc: updates[qwen_image_edit_acc] = gr.update(visible=('qwen_image_edit' in enabled_chains))
211
+ if boogu_edit_acc: updates[boogu_edit_acc] = gr.update(visible=('boogu_image_edit' in enabled_chains))
212
  if ref_img_acc: updates[ref_img_acc] = gr.update(visible=('reference_image' in enabled_chains))
213
  if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
214
  if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
ui/shared/ui_components.py CHANGED
@@ -15,7 +15,7 @@ default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOIN
15
  default_m_type = MODEL_TYPE_MAP.get(default_model_name, "SDXL") if default_model_name else "SDXL"
16
  default_architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
17
  default_arch_model_type = default_architectures_dict.get(default_m_type, {}).get("model_type", default_m_type.lower().replace(" ", "").replace(".", ""))
18
- default_arch_features = FEATURES_CONFIG.get(default_arch_model_type, FEATURES_CONFIG.get('default', {}))
19
  default_enabled_chains = default_arch_features.get('enabled_chains', [])
20
 
21
  default_vals = MODEL_DEFAULTS_CONFIG.get('Default', {})
@@ -297,7 +297,7 @@ def create_diffsynth_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
297
  components = {}
298
  key = lambda name: f"{name}_{prefix}"
299
 
300
- with gr.Accordion("DiffSynth ControlNet Settings", open=False, visible=('controlnet_model_patch' in default_enabled_chains)) as accordion:
301
  components[key('diffsynth_controlnet_accordion')] = accordion
302
 
303
  cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
@@ -700,7 +700,7 @@ def create_joyai_reference_ui(prefix: str, max_units=2):
700
  components = {}
701
  key = lambda name: f"{name}_{prefix}"
702
 
703
- with gr.Accordion("JoyAI Reference Edit Settings", open=False, visible=('joyai_reference' in default_enabled_chains)) as ref_accordion:
704
  components[key('joyai_reference_accordion')] = ref_accordion
705
  gr.Markdown("💡 **Tip:** For multimodal models, this feature enables powerful editing and combining capabilities. In txt2img mode, adding a single reference image performs an **Image Edit** (JoyAI-Image-Edit recommended), while adding multiple images performs an **Image Combine** (JoyAI-Image-Edit-Plus recommended with ZeroGPU Duration (s) set to 120).")
706
 
@@ -833,7 +833,7 @@ def create_krea2_reference_edit_ui(prefix: str, max_units=3):
833
  components = {}
834
  key = lambda name: f"{name}_{prefix}"
835
 
836
- with gr.Accordion("Krea2 Style Reference Edit Settings", open=False, visible=('krea2_reference_edit' in default_enabled_chains)) as ref_accordion:
837
  components[key('krea2_reference_edit_accordion')] = ref_accordion
838
  gr.Markdown("💡 **Tip:** (Krea-2-Turbo recommended) Add style reference images to perform style reference editing.")
839
 
@@ -862,7 +862,7 @@ def create_boogu_edit_ui(prefix: str, max_units=10):
862
  components = {}
863
  key = lambda name: f"{name}_{prefix}"
864
 
865
- with gr.Accordion("Boogu-Image Edit Settings", open=False, visible=('boogu_edit' in default_enabled_chains)) as ref_accordion:
866
  components[key('boogu_edit_accordion')] = ref_accordion
867
  gr.Markdown("💡 **Tip:** (Boogu-Image-Edit-Turbo/Boogu-Image-Edit recommended, Boogu-Image-Edit need set ZeroGPU Duration (s) to 120 ) In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
868
 
 
15
  default_m_type = MODEL_TYPE_MAP.get(default_model_name, "SDXL") if default_model_name else "SDXL"
16
  default_architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
17
  default_arch_model_type = default_architectures_dict.get(default_m_type, {}).get("model_type", default_m_type.lower().replace(" ", "").replace(".", ""))
18
+ default_arch_features = FEATURES_CONFIG.get(default_arch_model_type, {})
19
  default_enabled_chains = default_arch_features.get('enabled_chains', [])
20
 
21
  default_vals = MODEL_DEFAULTS_CONFIG.get('Default', {})
 
297
  components = {}
298
  key = lambda name: f"{name}_{prefix}"
299
 
300
+ with gr.Accordion("DiffSynth ControlNet Settings", open=False, visible=('diffsynth_controlnet' in default_enabled_chains)) as accordion:
301
  components[key('diffsynth_controlnet_accordion')] = accordion
302
 
303
  cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
 
700
  components = {}
701
  key = lambda name: f"{name}_{prefix}"
702
 
703
+ with gr.Accordion("JoyAI Reference Edit Settings", open=False, visible=('joyai_image' in default_enabled_chains)) as ref_accordion:
704
  components[key('joyai_reference_accordion')] = ref_accordion
705
  gr.Markdown("💡 **Tip:** For multimodal models, this feature enables powerful editing and combining capabilities. In txt2img mode, adding a single reference image performs an **Image Edit** (JoyAI-Image-Edit recommended), while adding multiple images performs an **Image Combine** (JoyAI-Image-Edit-Plus recommended with ZeroGPU Duration (s) set to 120).")
706
 
 
833
  components = {}
834
  key = lambda name: f"{name}_{prefix}"
835
 
836
+ with gr.Accordion("Krea2 Style Reference Edit Settings", open=False, visible=('krea2_style_reference' in default_enabled_chains)) as ref_accordion:
837
  components[key('krea2_reference_edit_accordion')] = ref_accordion
838
  gr.Markdown("💡 **Tip:** (Krea-2-Turbo recommended) Add style reference images to perform style reference editing.")
839
 
 
862
  components = {}
863
  key = lambda name: f"{name}_{prefix}"
864
 
865
+ with gr.Accordion("Boogu-Image Edit Settings", open=False, visible=('boogu_image_edit' in default_enabled_chains)) as ref_accordion:
866
  components[key('boogu_edit_accordion')] = ref_accordion
867
  gr.Markdown("💡 **Tip:** (Boogu-Image-Edit-Turbo/Boogu-Image-Edit recommended, Boogu-Image-Edit need set ZeroGPU Duration (s) to 120 ) In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
868
 
yaml/image_gen_features.yaml CHANGED
@@ -1,208 +1,200 @@
1
- default:
2
- enabled_chains:
3
- - lora
4
- - controlnet
5
- - ipadapter
6
- - embedding
7
- - style
8
- - conditioning
9
- - vae
10
-
11
- mage-flow:
12
- enabled_chains:
13
- - reference_image
14
-
15
- joyai-image:
16
- enabled_chains:
17
- - joyai_reference
18
-
19
- krea-2:
20
- enabled_chains:
21
- - lora
22
- - krea2_controlnet
23
- - krea2_identity_edit
24
- - krea2_reference_edit
25
- - pid
26
-
27
- boogu-image:
28
- enabled_chains:
29
- - lora
30
- - boogu_edit
31
- - pid
32
-
33
- pixeldit:
34
- enabled_chains:
35
- - conditioning
36
-
37
- ideogram-4:
38
- enabled_chains:
39
- - vae
40
- - pid
41
-
42
- lens:
43
- enabled_chains:
44
- - conditioning
45
- - pid
46
-
47
- flux2-kv:
48
- enabled_chains:
49
- - lora
50
- - conditioning
51
- - reference_latent
52
- - vae
53
- - pid
54
-
55
- flux2:
56
- enabled_chains:
57
- - lora
58
- - conditioning
59
- - reference_latent
60
- - vae
61
- - pid
62
-
63
- ernie-image:
64
- enabled_chains:
65
- - conditioning
66
- - pid
67
-
68
- z-image:
69
- enabled_chains:
70
- - lora
71
- - conditioning
72
- - controlnet_model_patch
73
- - vae
74
- - pid
75
-
76
- qwen-image:
77
- enabled_chains:
78
- - lora
79
- - controlnet
80
- - conditioning
81
- - qwen_image_edit
82
- - vae
83
- - pid
84
-
85
- longcat-image:
86
- enabled_chains:
87
- - lora
88
- - conditioning
89
- - pid
90
-
91
- cosmos-predict2:
92
- enabled_chains:
93
- - conditioning
94
- - vae
95
-
96
- anima:
97
- enabled_chains:
98
- - lora
99
- - anima_controlnet_lllite
100
- - conditioning
101
- - vae
102
- - pid
103
-
104
- newbie-image:
105
- enabled_chains:
106
- - lora
107
- - embedding
108
- - conditioning
109
- - vae
110
- - pid
111
-
112
- kandinsky-5:
113
- enabled_chains:
114
- - conditioning
115
- - vae
116
- - pid
117
-
118
- ovis-image:
119
- enabled_chains:
120
- - conditioning
121
- - vae
122
- - pid
123
-
124
- hunyuanimage:
125
- enabled_chains:
126
- - conditioning
127
- - vae
128
-
129
- chroma1-radiance:
130
- enabled_chains:
131
- - conditioning
132
-
133
- chroma1:
134
- enabled_chains:
135
- - conditioning
136
- - vae
137
- - pid
138
-
139
- omnigen2:
140
- enabled_chains:
141
- - conditioning
142
- - reference_latent
143
- - pid
144
-
145
- lumina:
146
- enabled_chains:
147
- - lora
148
- - embedding
149
- - conditioning
150
- - vae
151
- - pid
152
-
153
- hidream-o1:
154
- enabled_chains:
155
- - lora
156
- - conditioning
157
- - hidream_o1_reference
158
-
159
- hidream-i1:
160
- enabled_chains:
161
- - lora
162
- - conditioning
163
- - pid
164
-
165
- flux1:
166
- enabled_chains:
167
- - lora
168
- - controlnet
169
- - style
170
- - conditioning
171
- - flux1_ipadapter
172
- - vae
173
- - pid
174
-
175
- auraflow:
176
- enabled_chains:
177
- - lora
178
- - conditioning
179
- - vae
180
-
181
- sd35:
182
- enabled_chains:
183
- - lora
184
- - controlnet
185
- - embedding
186
- - conditioning
187
- - sd3_ipadapter
188
- - vae
189
- - pid
190
-
191
- sdxl:
192
- enabled_chains:
193
- - lora
194
- - controlnet
195
- - ipadapter
196
- - embedding
197
- - conditioning
198
- - vae
199
- - pid
200
-
201
- sd15:
202
- enabled_chains:
203
- - lora
204
- - controlnet
205
- - ipadapter
206
- - embedding
207
- - conditioning
208
  - vae
 
1
+ # Feature names in enabled_chains correspond 1-to-1 with chain_injectors/<name>_injector.py
2
+ krea-2:
3
+ enabled_chains:
4
+ - lora
5
+ - krea2_controlnet
6
+ - krea2_identity_edit
7
+ - krea2_style_reference
8
+ - pid
9
+
10
+ mage-flow:
11
+ enabled_chains:
12
+ - reference_image
13
+
14
+ joyai-image:
15
+ enabled_chains:
16
+ - joyai_image
17
+
18
+ boogu-image:
19
+ enabled_chains:
20
+ - lora
21
+ - boogu_image_edit
22
+ - pid
23
+
24
+ pixeldit:
25
+ enabled_chains:
26
+ - conditioning
27
+
28
+ ideogram-4:
29
+ enabled_chains:
30
+ - vae
31
+ - pid
32
+
33
+ lens:
34
+ enabled_chains:
35
+ - conditioning
36
+ - pid
37
+
38
+ flux2-kv:
39
+ enabled_chains:
40
+ - lora
41
+ - reference_latent
42
+ - conditioning
43
+ - vae
44
+ - pid
45
+
46
+ flux2:
47
+ enabled_chains:
48
+ - lora
49
+ - reference_latent
50
+ - conditioning
51
+ - vae
52
+ - pid
53
+
54
+ ernie-image:
55
+ enabled_chains:
56
+ - conditioning
57
+ - pid
58
+
59
+ z-image:
60
+ enabled_chains:
61
+ - lora
62
+ - diffsynth_controlnet
63
+ - conditioning
64
+ - vae
65
+ - pid
66
+
67
+ qwen-image:
68
+ enabled_chains:
69
+ - lora
70
+ - conditioning
71
+ - controlnet
72
+ - qwen_image_edit
73
+ - vae
74
+ - pid
75
+
76
+ longcat-image:
77
+ enabled_chains:
78
+ - lora
79
+ - conditioning
80
+ - pid
81
+
82
+ cosmos-predict2:
83
+ enabled_chains:
84
+ - conditioning
85
+ - vae
86
+
87
+ anima:
88
+ enabled_chains:
89
+ - lora
90
+ - conditioning
91
+ - anima_controlnet_lllite
92
+ - vae
93
+ - pid
94
+
95
+ newbie-image:
96
+ enabled_chains:
97
+ - lora
98
+ - embedding
99
+ - conditioning
100
+ - vae
101
+ - pid
102
+
103
+ kandinsky-5:
104
+ enabled_chains:
105
+ - conditioning
106
+ - vae
107
+ - pid
108
+
109
+ ovis-image:
110
+ enabled_chains:
111
+ - conditioning
112
+ - vae
113
+ - pid
114
+
115
+ hunyuanimage:
116
+ enabled_chains:
117
+ - conditioning
118
+ - vae
119
+
120
+ chroma1-radiance:
121
+ enabled_chains:
122
+ - conditioning
123
+
124
+ chroma1:
125
+ enabled_chains:
126
+ - conditioning
127
+ - vae
128
+ - pid
129
+
130
+ omnigen2:
131
+ enabled_chains:
132
+ - reference_latent
133
+ - conditioning
134
+ - pid
135
+
136
+ lumina:
137
+ enabled_chains:
138
+ - lora
139
+ - embedding
140
+ - conditioning
141
+ - vae
142
+ - pid
143
+
144
+ hidream-o1:
145
+ enabled_chains:
146
+ - lora
147
+ - conditioning
148
+ - hidream_o1_smoothing
149
+ - hidream_o1_reference
150
+
151
+ hidream-i1:
152
+ enabled_chains:
153
+ - lora
154
+ - conditioning
155
+ - pid
156
+
157
+ flux1:
158
+ enabled_chains:
159
+ - lora
160
+ - flux1_ipadapter
161
+ - conditioning
162
+ - style
163
+ - controlnet
164
+ - vae
165
+ - pid
166
+
167
+ auraflow:
168
+ enabled_chains:
169
+ - lora
170
+ - conditioning
171
+ - vae
172
+
173
+ sd35:
174
+ enabled_chains:
175
+ - lora
176
+ - sd3_ipadapter
177
+ - embedding
178
+ - conditioning
179
+ - controlnet
180
+ - vae
181
+ - pid
182
+
183
+ sdxl:
184
+ enabled_chains:
185
+ - lora
186
+ - ipadapter
187
+ - embedding
188
+ - conditioning
189
+ - controlnet
190
+ - vae
191
+ - pid
192
+
193
+ sd15:
194
+ enabled_chains:
195
+ - lora
196
+ - ipadapter
197
+ - embedding
198
+ - conditioning
199
+ - controlnet
 
 
 
 
 
 
 
 
200
  - vae
yaml/injectors.yaml DELETED
@@ -1,69 +0,0 @@
1
- injector_definitions:
2
- dynamic_vae_chains:
3
- module: "chain_injectors.vae_injector"
4
- dynamic_lora_chains:
5
- module: "chain_injectors.lora_injector"
6
- dynamic_newbie_lora_chains:
7
- module: "chain_injectors.newbie_lora_injector"
8
- dynamic_controlnet_chains:
9
- module: "chain_injectors.controlnet_injector"
10
- dynamic_krea2_controlnet_chains:
11
- module: "chain_injectors.krea2_controlnet_injector"
12
- dynamic_anima_controlnet_lllite_chains:
13
- module: "chain_injectors.anima_controlnet_lllite_injector"
14
- dynamic_diffsynth_controlnet_chains:
15
- module: "chain_injectors.diffsynth_controlnet_injector"
16
- dynamic_ipadapter_chains:
17
- module: "chain_injectors.ipadapter_injector"
18
- dynamic_flux1_ipadapter_chains:
19
- module: "chain_injectors.flux1_ipadapter_injector"
20
- dynamic_sd3_ipadapter_chains:
21
- module: "chain_injectors.sd3_ipadapter_injector"
22
- dynamic_reference_latent_chains:
23
- module: "chain_injectors.reference_latent_injector"
24
- dynamic_conditioning_chains:
25
- module: "chain_injectors.conditioning_injector"
26
- dynamic_style_chains:
27
- module: "chain_injectors.style_injector"
28
- dynamic_hidream_o1_smoothing_chains:
29
- module: "chain_injectors.hidream_o1_smoothing_injector"
30
- dynamic_hidream_o1_reference_chains:
31
- module: "chain_injectors.hidream_o1_reference_injector"
32
- dynamic_joyai_reference_chains:
33
- module: "chain_injectors.joyai_reference_injector"
34
- dynamic_krea2_identity_edit_chains:
35
- module: "chain_injectors.krea2_identity_edit_injector"
36
- dynamic_krea2_reference_edit_chains:
37
- module: "chain_injectors.krea2_reference_edit_injector"
38
- dynamic_qwen_image_edit_chains:
39
- module: "chain_injectors.qwen_image_edit_injector"
40
- dynamic_boogu_edit_chains:
41
- module: "chain_injectors.boogu_edit_injector"
42
- dynamic_reference_image_chains:
43
- module: "chain_injectors.reference_image_injector"
44
- dynamic_pid_chains:
45
- module: "chain_injectors.pid_injector"
46
-
47
- injector_order:
48
- - dynamic_vae_chains
49
- - dynamic_lora_chains
50
- - dynamic_newbie_lora_chains
51
- - dynamic_diffsynth_controlnet_chains
52
- - dynamic_ipadapter_chains
53
- - dynamic_flux1_ipadapter_chains
54
- - dynamic_sd3_ipadapter_chains
55
- - dynamic_reference_latent_chains
56
- - dynamic_conditioning_chains
57
- - dynamic_style_chains
58
- - dynamic_controlnet_chains
59
- - dynamic_krea2_controlnet_chains
60
- - dynamic_krea2_identity_edit_chains
61
- - dynamic_krea2_reference_edit_chains
62
- - dynamic_qwen_image_edit_chains
63
- - dynamic_boogu_edit_chains
64
- - dynamic_anima_controlnet_lllite_chains
65
- - dynamic_hidream_o1_smoothing_chains
66
- - dynamic_hidream_o1_reference_chains
67
- - dynamic_joyai_reference_chains
68
- - dynamic_reference_image_chains
69
- - dynamic_pid_chains