RioShiina commited on
Commit
d9dfe83
·
verified ·
1 Parent(s): 7481b8b

feat: add MiniMax-H3-Image, fix reference image slot indices, bump requirements

Browse files
README.md CHANGED
@@ -57,6 +57,7 @@ models:
57
  - Comfy-Org/Lumina_Image_2.0_Repackaged
58
  - Comfy-Org/Mage-Flow
59
  - Comfy-Org/Ming-Image
 
60
  - Comfy-Org/MOSS-Image
61
  - Comfy-Org/NewBie-image-Exp0.1_repackaged
62
  - Comfy-Org/Omnigen2_ComfyUI_repackaged
@@ -187,6 +188,7 @@ models:
187
  - meituan-longcat/LongCat-Image
188
  - microsoft/Lens
189
  - microsoft/Lens-Turbo
 
190
  - NewBie-AI/NewBie-image-Exp0.1
191
  - nvidia/PiD
192
  - nvidia/PixelDiT-1300M-1024px
 
57
  - Comfy-Org/Lumina_Image_2.0_Repackaged
58
  - Comfy-Org/Mage-Flow
59
  - Comfy-Org/Ming-Image
60
+ - Comfy-Org/MiniMax-H3-Image
61
  - Comfy-Org/MOSS-Image
62
  - Comfy-Org/NewBie-image-Exp0.1_repackaged
63
  - Comfy-Org/Omnigen2_ComfyUI_repackaged
 
188
  - meituan-longcat/LongCat-Image
189
  - microsoft/Lens
190
  - microsoft/Lens-Turbo
191
+ - MiniMaxAI/MiniMax-H3-Image
192
  - NewBie-AI/NewBie-image-Exp0.1
193
  - nvidia/PiD
194
  - nvidia/PixelDiT-1300M-1024px
chain_injectors/reference_image_injector.py CHANGED
@@ -12,6 +12,80 @@ def create_node(assembler, class_type, title):
12
  node['_meta']['title'] = title
13
  return node
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def inject(assembler, chain_definition, chain_items):
16
  if not chain_items:
17
  return
@@ -43,7 +117,7 @@ def inject(assembler, chain_definition, chain_items):
43
  target_node_id = assembler.node_map.get(target_node_name)
44
 
45
  if not target_node_id or target_node_id not in assembler.workflow:
46
- supported_types = ('TextEncodeMageFlowEdit', 'TextEncodeQwenImage21', 'TextEncodeBooguEdit', 'TextEncodeMingImageEdit', 'TextEncodeFibo2')
47
  for node_id, node in assembler.workflow.items():
48
  if isinstance(node, dict) and node.get('class_type') in supported_types:
49
  target_node_id = node_id
@@ -69,7 +143,24 @@ def inject(assembler, chain_definition, chain_items):
69
  megapixels = chain_definition.get('megapixels', 1.0)
70
  resolution_steps = chain_definition.get('resolution_steps', 1)
71
 
 
 
 
 
 
 
 
72
  for i, img_filename in enumerate(valid_images):
 
 
 
 
 
 
 
 
 
 
73
  load_id = assembler._get_unique_id()
74
  load_node = create_node(assembler, "LoadImage", f"Load Reference Image {i+1}")
75
  load_node['inputs']['image'] = img_filename
@@ -83,8 +174,7 @@ def inject(assembler, chain_definition, chain_items):
83
  scale_node['inputs']['image'] = [load_id, 0]
84
  assembler.workflow[scale_id] = scale_node
85
 
86
- input_key = f"images.image_{i+1}"
87
- assembler.workflow[target_node_id]['inputs'][input_key] = [scale_id, 0]
88
 
89
  node_class = assembler.workflow[target_node_id].get('class_type', 'Unknown')
90
- print(f"Reference Image injector applied: {len(valid_images)} image(s) injected into '{target_node_id}' ({node_class}), max_images={max_images}.")
 
12
  node['_meta']['title'] = title
13
  return node
14
 
15
+
16
+ def _get_node_input_spec(class_type, input_name):
17
+ """Fetch the raw input spec (``[type, config]``) for ``class_type.input_name``."""
18
+ # 1. ComfyUI /object_info backed manager (used by the standalone comfy-webui app)
19
+ try:
20
+ from comfy_webui_core import node_info_manager
21
+ node_info = node_info_manager.get_node_info(class_type)
22
+ if isinstance(node_info, dict):
23
+ inputs_cfg = node_info.get('input', {}) or {}
24
+ for category in ('required', 'optional'):
25
+ candidate = (inputs_cfg.get(category) or {}).get(input_name)
26
+ if candidate:
27
+ return candidate
28
+ except Exception:
29
+ pass
30
+
31
+ # 2. In-process ComfyUI node classes (used by the Hugging Face Spaces runtime)
32
+ try:
33
+ from comfy_integration.nodes import NODE_CLASS_MAPPINGS
34
+ node_class = NODE_CLASS_MAPPINGS.get(class_type)
35
+ if node_class is not None:
36
+ input_types = node_class.INPUT_TYPES() or {}
37
+ for category in ('required', 'optional'):
38
+ candidate = (input_types.get(category) or {}).get(input_name)
39
+ if candidate:
40
+ return candidate
41
+ except Exception:
42
+ pass
43
+
44
+ return None
45
+
46
+
47
+ def _get_autogrow_image_slot_names(assembler, target_node_id, input_name='images'):
48
+ """
49
+ Resolve the real per-image slot key template for an Autogrow image input.
50
+
51
+ ComfyUI exposes two flavours of Autogrow template:
52
+ * TemplatePrefix(prefix="image_") -> names are 0-based: image_0, image_1, ...
53
+ * TemplateNames(names=["image_1", ...]) -> names start at image_1
54
+
55
+ Returns a list of slot names indexed from 0 (i.e. index i is the name for the i-th image),
56
+ or None when the node schema cannot be introspected.
57
+ """
58
+ target_node = assembler.workflow.get(target_node_id)
59
+ if not isinstance(target_node, dict):
60
+ return None
61
+
62
+ class_type = target_node.get('class_type')
63
+ if not class_type:
64
+ return None
65
+
66
+ spec = _get_node_input_spec(class_type, input_name)
67
+ if not isinstance(spec, (list, tuple)) or len(spec) < 2 or not isinstance(spec[1], dict):
68
+ return None
69
+
70
+ template = spec[1].get('template')
71
+ if not isinstance(template, dict):
72
+ return None
73
+
74
+ names = template.get('names')
75
+ if isinstance(names, (list, tuple)) and names:
76
+ return [str(n) for n in names]
77
+
78
+ prefix = template.get('prefix')
79
+ if isinstance(prefix, str):
80
+ # TemplatePrefix is numbered from 0 in ComfyUI (range(self.max)).
81
+ max_count = template.get('max', 100)
82
+ if not isinstance(max_count, int) or max_count <= 0:
83
+ max_count = 100
84
+ return [f"{prefix}{i}" for i in range(max_count)]
85
+
86
+ return None
87
+
88
+
89
  def inject(assembler, chain_definition, chain_items):
90
  if not chain_items:
91
  return
 
117
  target_node_id = assembler.node_map.get(target_node_name)
118
 
119
  if not target_node_id or target_node_id not in assembler.workflow:
120
+ supported_types = ('TextEncodeMageFlowEdit', 'TextEncodeQwenImage21', 'TextEncodeBooguEdit', 'TextEncodeMingImageEdit', 'TextEncodeFibo2', 'MiniMaxH3ImageConditioning')
121
  for node_id, node in assembler.workflow.items():
122
  if isinstance(node, dict) and node.get('class_type') in supported_types:
123
  target_node_id = node_id
 
143
  megapixels = chain_definition.get('megapixels', 1.0)
144
  resolution_steps = chain_definition.get('resolution_steps', 1)
145
 
146
+ # Derive the node's real autogrow slot naming (0-based prefix vs 1-based names)
147
+ # instead of assuming image_1..image_N, which silently breaks 0-based nodes.
148
+ input_name = chain_definition.get('input_name', 'images')
149
+ slot_names = _get_autogrow_image_slot_names(assembler, target_node_id, input_name=input_name)
150
+ index_base = chain_definition.get('slot_index_base')
151
+ slot_key = None
152
+
153
  for i, img_filename in enumerate(valid_images):
154
+ if slot_names is not None:
155
+ if i >= len(slot_names):
156
+ print(f"Warning: Reference Image chain reached the node's slot limit ({len(slot_names)}). Stopping.")
157
+ break
158
+ slot_key = f"{input_name}.{slot_names[i]}"
159
+ else:
160
+ # Fallback when node schema is unavailable: honor explicit chain config or use 1-based legacy default.
161
+ base = 1 if index_base is None else int(index_base)
162
+ slot_key = f"{input_name}.image_{i + base}"
163
+
164
  load_id = assembler._get_unique_id()
165
  load_node = create_node(assembler, "LoadImage", f"Load Reference Image {i+1}")
166
  load_node['inputs']['image'] = img_filename
 
174
  scale_node['inputs']['image'] = [load_id, 0]
175
  assembler.workflow[scale_id] = scale_node
176
 
177
+ assembler.workflow[target_node_id]['inputs'][slot_key] = [scale_id, 0]
 
178
 
179
  node_class = assembler.workflow[target_node_id].get('class_type', 'Unknown')
180
+ print(f"Reference Image injector applied: {len(valid_images)} image(s) injected into '{target_node_id}' ({node_class}), max_images={max_images}, slots={slot_key or '-'}.")
core/pipelines/workflow_recipes/_partials/conditioning/minimax-h3-image.yaml ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ unet_loader:
3
+ class_type: UNETLoader
4
+ title: "Load Diffusion Model"
5
+ params:
6
+ weight_dtype: "default"
7
+ vae_loader:
8
+ class_type: VAELoader
9
+ title: "Load VAE"
10
+ clip_loader:
11
+ class_type: CLIPLoader
12
+ title: "Load CLIP"
13
+ params:
14
+ type: "minimax"
15
+ device: "default"
16
+ prompt:
17
+ class_type: MiniMaxH3ImageConditioning
18
+ title: "MiniMax H3 Image Conditioning"
19
+ params:
20
+ width: 2048
21
+ height: 2048
22
+ negative_zero_out:
23
+ class_type: ConditioningZeroOut
24
+ title: "Conditioning Zero Out (Negative)"
25
+
26
+ connections:
27
+ - from: "unet_loader:0"
28
+ to: "ksampler:model"
29
+ - from: "clip_loader:0"
30
+ to: "prompt:clip"
31
+ - from: "vae_loader:0"
32
+ to: "prompt:vae"
33
+ - from: "prompt:0"
34
+ to: "ksampler:positive"
35
+ - from: "prompt:0"
36
+ to: "negative_zero_out:conditioning"
37
+ - from: "negative_zero_out:0"
38
+ to: "ksampler:negative"
39
+ - from: "prompt:1"
40
+ to: "ksampler:latent_image"
41
+ - from: "vae_loader:0"
42
+ to: "vae_decode:vae"
43
+ - from: "vae_loader:0"
44
+ to: "vae_encode:vae"
45
+
46
+ dynamic_lora_chains:
47
+ lora_chain:
48
+ template: "LoraLoader"
49
+ output_map:
50
+ "unet_loader:0": "model"
51
+ "clip_loader:0": "clip"
52
+ input_map:
53
+ "model": "model"
54
+ "clip": "clip"
55
+ end_input_map:
56
+ "model": ["ksampler:model"]
57
+ "clip": ["prompt:clip"]
58
+
59
+ dynamic_reference_image_chains:
60
+ reference_image_chain:
61
+ text_encode_node: "prompt"
62
+ vae_node: "vae_loader"
63
+ max_images: 8
64
+
65
+ ui_map:
66
+ positive_prompt: "prompt:prompt"
67
+ negative_prompt: "dummy:negative_prompt"
68
+ unet_name: "unet_loader:unet_name"
69
+ clip_name: "clip_loader:clip_name"
70
+ vae_name: "vae_loader:vae_name"
71
+ width: "prompt:width"
72
+ height: "prompt:height"
requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
- comfyui-frontend-package==1.57.1
2
- comfyui-workflow-templates==0.11.96
3
- comfyui-embedded-docs==0.5.14
4
  torch
5
  torchsde
6
  torchvision
@@ -22,7 +22,7 @@ SQLAlchemy>=2.0.0
22
  filelock
23
  av>=17.0.0
24
  comfy-aimdo==0.6.0
25
- comfy-kitchen==0.2.40
26
  requests
27
  simpleeval>=1.0.0
28
  blake3
 
1
+ comfyui-frontend-package==1.58.5
2
+ comfyui-workflow-templates==0.11.106
3
+ comfyui-embedded-docs==0.5.16
4
  torch
5
  torchsde
6
  torchvision
 
22
  filelock
23
  av>=17.0.0
24
  comfy-aimdo==0.6.0
25
+ comfy-kitchen==0.2.41
26
  requests
27
  simpleeval>=1.0.0
28
  blake3
yaml/chain_features.yaml CHANGED
@@ -251,6 +251,21 @@ fibo2_reference_edit:
251
  required:
252
  - image
253
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  krea2_identity_edit:
255
  chains: krea2_identity_edit
256
  display_name: "KREA2 Identity Edit"
 
251
  required:
252
  - image
253
 
254
+ minimax_h3_image_reference_edit:
255
+ chains: reference_image
256
+ display_name: "MiniMax-H3-Image Reference Edit"
257
+ description: "For MiniMax-H3-Image, this feature enables reference image editing and combining capabilities. In txt2img mode, adding a single reference image performs an Image Edit, while adding multiple images performs an Image Combine. The prompt refers to them as <Picture 1>, <Picture 2>, ..."
258
+ max_count: 8
259
+ usage_guideline: "Supply reference image(s) encoded as Base64 Data URI or HTTP/HTTPS URL (up to 8). Refer to them in the prompt as <Picture 1>, <Picture 2>, ...; <Picture 1> is the picture to edit."
260
+ parameters_schema:
261
+ type: object
262
+ properties:
263
+ image:
264
+ type: string
265
+ description: "Reference image encoded as Base64 Data URI or HTTP/HTTPS URL."
266
+ required:
267
+ - image
268
+
269
  krea2_identity_edit:
270
  chains: krea2_identity_edit
271
  display_name: "KREA2 Identity Edit"
yaml/constants.yaml CHANGED
@@ -24,6 +24,14 @@ RESOLUTION_MAP:
24
  "3:4 (Classic Portrait)": [896, 1152]
25
  "3:2 (Photography)": [1216, 832]
26
  "2:3 (Photography Portrait)": [832, 1216]
 
 
 
 
 
 
 
 
27
  krea-2:
28
  "1:1 (Square)": [1024, 1024]
29
  "16:9 (Landscape)": [1344, 768]
 
24
  "3:4 (Classic Portrait)": [896, 1152]
25
  "3:2 (Photography)": [1216, 832]
26
  "2:3 (Photography Portrait)": [832, 1216]
27
+ minimax-h3-image:
28
+ "1:1 (Square)": [2048, 2048]
29
+ "16:9 (Landscape)": [2048, 1152]
30
+ "9:16 (Portrait)": [1152, 2048]
31
+ "4:3 (Classic)": [1792, 1344]
32
+ "3:4 (Classic Portrait)": [1344, 1792]
33
+ "3:2 (Photography)": [1920, 1280]
34
+ "2:3 (Photography Portrait)": [1280, 1920]
35
  krea-2:
36
  "1:1 (Square)": [1024, 1024]
37
  "16:9 (Landscape)": [1344, 768]
yaml/file_list.yaml CHANGED
@@ -405,6 +405,11 @@ file:
405
  repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
406
  repository_file_path: "control_v11u_sd15_tile_fp16.safetensors"
407
  diffusion_models:
 
 
 
 
 
408
  # Fibo 2.0
409
  - filename: "fibo2_dev_int8_convrot.safetensors"
410
  source: "hf"
@@ -970,6 +975,11 @@ file:
970
  repo_id: "Comfy-Org/Qwen-Image-2.1"
971
  repository_file_path: "model_patches/qwen_image_2.1_fun_controlnet_union_int8_convrot.safetensors"
972
  text_encoders:
 
 
 
 
 
973
  # MOSS-Image
974
  - filename: "qwen3.5_9b_w4a8_gptq.safetensors"
975
  source: "hf"
@@ -1119,6 +1129,11 @@ file:
1119
  repo_id: "Comfy-Org/Omnigen2_ComfyUI_repackaged"
1120
  repository_file_path: "split_files/text_encoders/qwen_2.5_vl_fp16.safetensors"
1121
  vae:
 
 
 
 
 
1122
  - filename: "wan_2.1_vae.safetensors"
1123
  source: "hf"
1124
  repo_id: "Comfy-Org/Wan_2.1_ComfyUI_repackaged"
 
405
  repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
406
  repository_file_path: "control_v11u_sd15_tile_fp16.safetensors"
407
  diffusion_models:
408
+ # MiniMax-H3-Image
409
+ - filename: "minimax_h3_image_pruned_w4a8.safetensors"
410
+ source: "hf"
411
+ repo_id: "Comfy-Org/MiniMax-H3-Image"
412
+ repository_file_path: "diffusion_models/minimax_h3_image_pruned_w4a8.safetensors"
413
  # Fibo 2.0
414
  - filename: "fibo2_dev_int8_convrot.safetensors"
415
  source: "hf"
 
975
  repo_id: "Comfy-Org/Qwen-Image-2.1"
976
  repository_file_path: "model_patches/qwen_image_2.1_fun_controlnet_union_int8_convrot.safetensors"
977
  text_encoders:
978
+ # MiniMax-H3-Image
979
+ - filename: "qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors"
980
+ source: "hf"
981
+ repo_id: "Comfy-Org/MiniMax-H3-Image"
982
+ repository_file_path: "text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors"
983
  # MOSS-Image
984
  - filename: "qwen3.5_9b_w4a8_gptq.safetensors"
985
  source: "hf"
 
1129
  repo_id: "Comfy-Org/Omnigen2_ComfyUI_repackaged"
1130
  repository_file_path: "split_files/text_encoders/qwen_2.5_vl_fp16.safetensors"
1131
  vae:
1132
+ # MiniMax-H3-Image
1133
+ - filename: "minimax_h3_image_vae_fp16.safetensors"
1134
+ source: "hf"
1135
+ repo_id: "Comfy-Org/MiniMax-H3-Image"
1136
+ repository_file_path: "vae/minimax_h3_image_vae_fp16.safetensors"
1137
  - filename: "wan_2.1_vae.safetensors"
1138
  source: "hf"
1139
  repo_id: "Comfy-Org/Wan_2.1_ComfyUI_repackaged"
yaml/model_architecture_features.yaml CHANGED
@@ -12,6 +12,11 @@ flux3:
12
  - flux3_prompt_upsampler
13
  - pid
14
 
 
 
 
 
 
15
  krea-2:
16
  enabled_chains:
17
  - lora
 
12
  - flux3_prompt_upsampler
13
  - pid
14
 
15
+ minimax-h3-image:
16
+ enabled_chains:
17
+ - lora
18
+ - reference_image
19
+
20
  krea-2:
21
  enabled_chains:
22
  - lora
yaml/model_architectures.yaml CHANGED
@@ -1,5 +1,6 @@
1
  architecture_order:
2
  - "FLUX.3"
 
3
  - "Krea-2"
4
  - "Qwen-Image-2.1"
5
  - "Fibo-2.0"
@@ -43,6 +44,8 @@ architectures:
43
  model_type: "fibo-2.0"
44
  "FLUX.3":
45
  model_type: "flux3"
 
 
46
  Krea-2:
47
  model_type: "krea-2"
48
  "SenseNova-U1.5":
 
1
  architecture_order:
2
  - "FLUX.3"
3
+ - "MiniMax-H3-Image"
4
  - "Krea-2"
5
  - "Qwen-Image-2.1"
6
  - "Fibo-2.0"
 
44
  model_type: "fibo-2.0"
45
  "FLUX.3":
46
  model_type: "flux3"
47
+ "MiniMax-H3-Image":
48
+ model_type: "minimax-h3-image"
49
  Krea-2:
50
  model_type: "krea-2"
51
  "SenseNova-U1.5":
yaml/model_defaults.yaml CHANGED
@@ -45,6 +45,13 @@ FLUX.3:
45
  sampler_name: "euler"
46
  scheduler: "simple"
47
 
 
 
 
 
 
 
 
48
  Krea-2:
49
  _defaults:
50
  steps: 52
 
45
  sampler_name: "euler"
46
  scheduler: "simple"
47
 
48
+ MiniMax-H3-Image:
49
+ _defaults:
50
+ steps: 6
51
+ cfg: 1.0
52
+ sampler_name: "euler"
53
+ scheduler: "simple"
54
+
55
  Krea-2:
56
  _defaults:
57
  steps: 52
yaml/model_list.yaml CHANGED
@@ -17,6 +17,14 @@ Checkpoint:
17
  unet: "flux3-image-klein-base-nvfp4.safetensors"
18
  clip: "qwen3vl_4b_fp8_scaled.safetensors"
19
  vae: "flux2-vae.safetensors"
 
 
 
 
 
 
 
 
20
  Krea-2:
21
  latent_type: latent
22
  models:
 
17
  unet: "flux3-image-klein-base-nvfp4.safetensors"
18
  clip: "qwen3vl_4b_fp8_scaled.safetensors"
19
  vae: "flux2-vae.safetensors"
20
+ MiniMax-H3-Image:
21
+ latent_type: none
22
+ models:
23
+ - display_name: "MiniMaxAI/MiniMax-H3-Image"
24
+ components:
25
+ unet: "minimax_h3_image_pruned_w4a8.safetensors"
26
+ clip: "qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors"
27
+ vae: "minimax_h3_image_vae_fp16.safetensors"
28
  Krea-2:
29
  latent_type: latent
30
  models: