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

feat: integrate MOSS-Image Edit with reference images and LoRA

Browse files
README.md CHANGED
@@ -164,6 +164,7 @@ models:
164
  - Boogu/Boogu-Image-0.1-Edit-Turbo
165
  - briaai/Fibo-2.0-dev
166
  - briaai/Fibo-2.0-edit-turbo
 
167
  - darask0/MOSS-Image-color-depth-control
168
  - HiDream-ai/HiDream-I1-Dev
169
  - HiDream-ai/HiDream-I1-Fast
 
164
  - Boogu/Boogu-Image-0.1-Edit-Turbo
165
  - briaai/Fibo-2.0-dev
166
  - briaai/Fibo-2.0-edit-turbo
167
+ - chinoll/Moss-Image-Edit
168
  - darask0/MOSS-Image-color-depth-control
169
  - HiDream-ai/HiDream-I1-Dev
170
  - HiDream-ai/HiDream-I1-Fast
chain_injectors/moss_image_edit_injector.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ try:
4
+ from utils.app_utils import ensure_file_downloaded
5
+ except ImportError:
6
+ ensure_file_downloaded = None
7
+
8
+
9
+ def create_node(assembler, class_type, title=None):
10
+ try:
11
+ node = assembler._get_node_template_from_api(class_type)
12
+ except Exception:
13
+ try:
14
+ node = assembler._get_node_template(class_type)
15
+ except Exception:
16
+ node = {
17
+ "inputs": {},
18
+ "class_type": class_type,
19
+ "_meta": {"title": title or class_type}
20
+ }
21
+ if title:
22
+ node.setdefault('_meta', {})['title'] = title
23
+ return node
24
+
25
+
26
+ def inject(assembler, chain_definition, chain_items):
27
+ """
28
+ Injects MOSS-Image Edit (Reference Image Edit) into the workflow:
29
+ 1. Loads the officially packaged Edit LoRA (moss_image_edit_lora_rank_64_bf16.safetensors)
30
+ onto the model path.
31
+ 2. Loads and scales up to 10 reference images with ImageScaleToTotalPixels.
32
+ 3. Replaces positive/negative prompt encoding with native TextEncodeMOSSImageEdit nodes,
33
+ which attach reference image latents and text tokens.
34
+ 4. Connects patched model and conditioned positive/negative outputs to KSampler.
35
+ """
36
+ if not chain_items:
37
+ return
38
+
39
+ # Normalize chain_items: dict or list of dicts/strings
40
+ edit_format = "Moss-Image-Edit"
41
+ strength = 1.0
42
+ valid_images = []
43
+
44
+ if isinstance(chain_items, dict):
45
+ chain_items = [chain_items]
46
+
47
+ if isinstance(chain_items, list):
48
+ for item in chain_items:
49
+ if not item:
50
+ continue
51
+ if isinstance(item, dict):
52
+ raw_imgs = item.get('images')
53
+ if isinstance(raw_imgs, list):
54
+ for img in raw_imgs:
55
+ p = img.get('image') or img.get('filename') or img.get('path') if isinstance(img, dict) else img
56
+ if p and isinstance(p, str) and p.strip():
57
+ valid_images.append(p.strip())
58
+ elif isinstance(raw_imgs, str) and raw_imgs.strip():
59
+ valid_images.append(raw_imgs.strip())
60
+
61
+ p = item.get('image') or item.get('filename') or item.get('path')
62
+ if p and isinstance(p, str) and p.strip():
63
+ valid_images.append(p.strip())
64
+
65
+ if 'edit_format' in item:
66
+ edit_format = item['edit_format']
67
+ if 'strength' in item:
68
+ try:
69
+ strength = float(item['strength'])
70
+ except (ValueError, TypeError):
71
+ pass
72
+ elif isinstance(item, str) and item.strip():
73
+ valid_images.append(item.strip())
74
+
75
+ if not valid_images:
76
+ return
77
+
78
+ valid_images = valid_images[:10]
79
+
80
+ lora_filename = "moss_image_edit_lora_rank_64_bf16.safetensors"
81
+ if ensure_file_downloaded:
82
+ try:
83
+ ensure_file_downloaded(lora_filename)
84
+ except Exception as e:
85
+ print(f"Warning: Failed to ensure '{lora_filename}' downloaded: {e}")
86
+
87
+ ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
88
+ pos_prompt_name = chain_definition.get('pos_prompt_node', 'pos_prompt')
89
+ neg_prompt_name = chain_definition.get('neg_prompt_node', 'neg_prompt')
90
+ clip_loader_name = chain_definition.get('clip_loader_node', 'clip_loader')
91
+ vae_loader_name = chain_definition.get('vae_loader_node', 'vae_loader')
92
+
93
+ if ksampler_name not in assembler.node_map:
94
+ print(f"Warning: Target node '{ksampler_name}' for MOSS-Image Edit chain not found. Skipping.")
95
+ return
96
+
97
+ ksampler_id = assembler.node_map[ksampler_name]
98
+
99
+ if 'model' not in assembler.workflow[ksampler_id]['inputs']:
100
+ print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping.")
101
+ return
102
+
103
+ current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
104
+
105
+ vae_connection = None
106
+ if vae_loader_name in assembler.node_map:
107
+ vae_connection = [assembler.node_map[vae_loader_name], 0]
108
+ else:
109
+ for node_id, node in assembler.workflow.items():
110
+ if isinstance(node, dict) and node.get('class_type') == 'VAELoader':
111
+ vae_connection = [node_id, 0]
112
+ break
113
+
114
+ if not vae_connection:
115
+ print("Warning: VAE connection not found for MOSS-Image Edit. Skipping.")
116
+ return
117
+
118
+ pos_prompt_id = assembler.node_map.get(pos_prompt_name)
119
+ neg_prompt_id = assembler.node_map.get(neg_prompt_name)
120
+
121
+ clip_connection = None
122
+ if clip_loader_name in assembler.node_map:
123
+ clip_connection = [assembler.node_map[clip_loader_name], 0]
124
+ elif pos_prompt_id and 'clip' in assembler.workflow.get(pos_prompt_id, {}).get('inputs', {}):
125
+ clip_connection = assembler.workflow[pos_prompt_id]['inputs']['clip']
126
+ else:
127
+ for node_id, node in assembler.workflow.items():
128
+ if isinstance(node, dict) and node.get('class_type') == 'CLIPLoader':
129
+ clip_connection = [node_id, 0]
130
+ break
131
+
132
+ if not clip_connection:
133
+ print("Warning: CLIP connection not found for MOSS-Image Edit. Skipping.")
134
+ return
135
+
136
+ # 1. Inject LoRA loader on model path if strength > 0
137
+ if strength > 0.0:
138
+ lora_loader_id = assembler._get_unique_id()
139
+ lora_loader_node = create_node(assembler, "LoraLoaderModelOnly", "Load LoRA (MOSS-Image Edit)")
140
+ lora_loader_node['inputs']['lora_name'] = lora_filename
141
+ lora_loader_node['inputs']['strength_model'] = strength
142
+ lora_loader_node['inputs']['model'] = current_model_connection
143
+ assembler.workflow[lora_loader_id] = lora_loader_node
144
+ current_model_connection = [lora_loader_id, 0]
145
+
146
+ megapixels = float(chain_definition.get('megapixels', 1.0))
147
+
148
+ # 2. Process Reference Images
149
+ scale_image_ids = []
150
+ for i, img_filename in enumerate(valid_images):
151
+ load_id = assembler._get_unique_id()
152
+ load_node = create_node(assembler, "LoadImage", f"Load Reference Image {i+1} (MOSS Edit)")
153
+ load_node['inputs']['image'] = img_filename
154
+ assembler.workflow[load_id] = load_node
155
+
156
+ scale_id = assembler._get_unique_id()
157
+ scale_node = create_node(assembler, "ImageScaleToTotalPixels", f"Scale Reference {i+1} (MOSS Edit)")
158
+ scale_node['inputs']['upscale_method'] = "lanczos"
159
+ scale_node['inputs']['megapixels'] = megapixels
160
+ scale_node['inputs']['resolution_steps'] = 1
161
+ scale_node['inputs']['image'] = [load_id, 0]
162
+ assembler.workflow[scale_id] = scale_node
163
+ scale_image_ids.append(scale_id)
164
+
165
+ # 3. Extract Prompt Texts
166
+ pos_text = ""
167
+ if pos_prompt_id and pos_prompt_id in assembler.workflow:
168
+ pos_text = assembler.workflow[pos_prompt_id]['inputs'].get('text', '') or assembler.workflow[pos_prompt_id]['inputs'].get('prompt', '')
169
+ elif hasattr(assembler, 'ui_values') and isinstance(assembler.ui_values, dict):
170
+ pos_text = assembler.ui_values.get('positive_prompt') or assembler.ui_values.get('prompt') or ''
171
+
172
+ neg_text = ""
173
+ if neg_prompt_id and neg_prompt_id in assembler.workflow:
174
+ neg_text = assembler.workflow[neg_prompt_id]['inputs'].get('text', '') or assembler.workflow[neg_prompt_id]['inputs'].get('prompt', '')
175
+ elif hasattr(assembler, 'ui_values') and isinstance(assembler.ui_values, dict):
176
+ neg_text = assembler.ui_values.get('negative_prompt') or assembler.ui_values.get('neg_prompt') or ''
177
+
178
+ # 4. Positive TextEncodeMOSSImageEdit
179
+ pos_edit_id = assembler._get_unique_id()
180
+ pos_edit_node = create_node(assembler, "TextEncodeMOSSImageEdit", "Text Encode MOSS-Image Edit")
181
+ pos_edit_node['inputs']['clip'] = clip_connection
182
+ pos_edit_node['inputs']['prompt'] = pos_text
183
+ pos_edit_node['inputs']['vae'] = vae_connection
184
+ pos_edit_node['inputs']['edit_format'] = edit_format
185
+ pos_edit_node['inputs']['image_strength'] = 1.0
186
+ for i, sc_id in enumerate(scale_image_ids):
187
+ pos_edit_node['inputs'][f"images.image_{i}"] = [sc_id, 0]
188
+ pos_edit_node['inputs'] = {k: v for k, v in pos_edit_node['inputs'].items() if v is not None}
189
+ assembler.workflow[pos_edit_id] = pos_edit_node
190
+
191
+ # 5. Negative TextEncodeMOSSImageEdit
192
+ neg_edit_id = assembler._get_unique_id()
193
+ neg_edit_node = create_node(assembler, "TextEncodeMOSSImageEdit", "Text Encode MOSS-Image Edit")
194
+ neg_edit_node['inputs']['clip'] = clip_connection
195
+ neg_edit_node['inputs']['prompt'] = neg_text
196
+ neg_edit_node['inputs']['vae'] = vae_connection
197
+ neg_edit_node['inputs']['edit_format'] = edit_format
198
+ neg_edit_node['inputs']['image_strength'] = 1.0
199
+ for i, sc_id in enumerate(scale_image_ids):
200
+ neg_edit_node['inputs'][f"images.image_{i}"] = [sc_id, 0]
201
+ neg_edit_node['inputs'] = {k: v for k, v in neg_edit_node['inputs'].items() if v is not None}
202
+ assembler.workflow[neg_edit_id] = neg_edit_node
203
+
204
+ # 6. Reconnect model and redirect conditioning references in workflow
205
+ assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
206
+
207
+ # Redirect all existing connections that referenced pos_prompt_id / neg_prompt_id
208
+ if pos_prompt_id:
209
+ for node in assembler.workflow.values():
210
+ if isinstance(node, dict) and 'inputs' in node:
211
+ for k, v in node['inputs'].items():
212
+ if isinstance(v, list) and len(v) == 2 and v[0] == pos_prompt_id:
213
+ node['inputs'][k] = [pos_edit_id, 0]
214
+
215
+ if neg_prompt_id:
216
+ for node in assembler.workflow.values():
217
+ if isinstance(node, dict) and 'inputs' in node:
218
+ for k, v in node['inputs'].items():
219
+ if isinstance(v, list) and len(v) == 2 and v[0] == neg_prompt_id:
220
+ node['inputs'][k] = [neg_edit_id, 0]
221
+
222
+ # Clean up original CLIPTextEncode nodes if they are in workflow
223
+ if pos_prompt_id and pos_prompt_id in assembler.workflow:
224
+ del assembler.workflow[pos_prompt_id]
225
+ if neg_prompt_id and neg_prompt_id in assembler.workflow:
226
+ del assembler.workflow[neg_prompt_id]
227
+
228
+ print(f"MOSS-Image Edit injector applied with {len(valid_images)} reference image(s) (format: {edit_format}, strength: {strength}).")
core/pipelines/pipeline_input_processor.py CHANGED
@@ -492,6 +492,20 @@ def process_pipeline_inputs(ui_inputs: Dict[str, Any], progress: gr.Progress, wo
492
  'end_percent': end_percent
493
  })
494
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  return {
496
  "active_loras_for_gpu": active_loras_for_gpu,
497
  "active_loras_for_meta": active_loras_for_meta,
@@ -513,5 +527,6 @@ def process_pipeline_inputs(ui_inputs: Dict[str, Any], progress: gr.Progress, wo
513
  "active_ming_image_prompt_enhancer": active_ming_image_prompt_enhancer,
514
  "active_flux3_prompt_upsampler": active_flux3_prompt_upsampler,
515
  "active_moss_image_control": active_moss_image_control,
 
516
  "temp_files_to_clean": temp_files_to_clean
517
  }
 
492
  'end_percent': end_percent
493
  })
494
 
495
+ moss_image_edit_data = ui_inputs.get('moss_image_edit_data', [])
496
+ active_moss_image_edit = []
497
+ if 'moss_image_edit' in enabled_chains and moss_image_edit_data:
498
+ for img in moss_image_edit_data:
499
+ if img:
500
+ if isinstance(img, str):
501
+ active_moss_image_edit.append(img)
502
+ elif hasattr(img, 'save'):
503
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
504
+ temp_path = os.path.join(INPUT_DIR, f"temp_moss_edit_{random.randint(1000, 9999)}.png")
505
+ img.save(temp_path, "PNG")
506
+ temp_files_to_clean.append(temp_path)
507
+ active_moss_image_edit.append(os.path.basename(temp_path))
508
+
509
  return {
510
  "active_loras_for_gpu": active_loras_for_gpu,
511
  "active_loras_for_meta": active_loras_for_meta,
 
527
  "active_ming_image_prompt_enhancer": active_ming_image_prompt_enhancer,
528
  "active_flux3_prompt_upsampler": active_flux3_prompt_upsampler,
529
  "active_moss_image_control": active_moss_image_control,
530
+ "active_moss_image_edit": active_moss_image_edit,
531
  "temp_files_to_clean": temp_files_to_clean
532
  }
core/pipelines/sd_image_pipeline.py CHANGED
@@ -123,6 +123,7 @@ class SdImagePipeline(BasePipeline):
123
  active_ming_image_prompt_enhancer = processed.get("active_ming_image_prompt_enhancer", [])
124
  active_flux3_prompt_upsampler = processed.get("active_flux3_prompt_upsampler", [])
125
  active_moss_image_control = processed.get("active_moss_image_control", [])
 
126
 
127
  loras_string = f"LoRAs: [{', '.join(active_loras_for_meta)}]" if active_loras_for_meta else ""
128
 
@@ -184,6 +185,7 @@ class SdImagePipeline(BasePipeline):
184
  "ming_image_prompt_enhancer_chain": active_ming_image_prompt_enhancer,
185
  "flux3_prompt_upsampler_chain": active_flux3_prompt_upsampler,
186
  "moss_image_control_chain": active_moss_image_control,
 
187
  "vae_chain": [ui_inputs.get('vae_name')] if (ui_inputs.get('vae_name') and 'vae' in enabled_chains) else [],
188
  "hidream_o1_smoothing_chain": hidream_o1_smoothing_data,
189
  "pid_chain": [ui_inputs.get('pid_settings', 'OFF')] if is_pid_enabled else [],
 
123
  active_ming_image_prompt_enhancer = processed.get("active_ming_image_prompt_enhancer", [])
124
  active_flux3_prompt_upsampler = processed.get("active_flux3_prompt_upsampler", [])
125
  active_moss_image_control = processed.get("active_moss_image_control", [])
126
+ active_moss_image_edit = processed.get("active_moss_image_edit", [])
127
 
128
  loras_string = f"LoRAs: [{', '.join(active_loras_for_meta)}]" if active_loras_for_meta else ""
129
 
 
185
  "ming_image_prompt_enhancer_chain": active_ming_image_prompt_enhancer,
186
  "flux3_prompt_upsampler_chain": active_flux3_prompt_upsampler,
187
  "moss_image_control_chain": active_moss_image_control,
188
+ "moss_image_edit_chain": active_moss_image_edit,
189
  "vae_chain": [ui_inputs.get('vae_name')] if (ui_inputs.get('vae_name') and 'vae' in enabled_chains) else [],
190
  "hidream_o1_smoothing_chain": hidream_o1_smoothing_data,
191
  "pid_chain": [ui_inputs.get('pid_settings', 'OFF')] if is_pid_enabled else [],
core/pipelines/workflow_recipes/_partials/conditioning/moss-image.yaml CHANGED
@@ -76,6 +76,15 @@ dynamic_moss_image_control_chains:
76
  vae_loader_node: "vae_loader"
77
  megapixels: 1.0
78
 
 
 
 
 
 
 
 
 
 
79
  dynamic_pid_chains:
80
  pid_chain:
81
  ksampler_node: "ksampler"
 
76
  vae_loader_node: "vae_loader"
77
  megapixels: 1.0
78
 
79
+ dynamic_moss_image_edit_chains:
80
+ moss_image_edit_chain:
81
+ ksampler_node: "ksampler"
82
+ clip_loader_node: "clip_loader"
83
+ vae_loader_node: "vae_loader"
84
+ pos_prompt_node: "pos_prompt"
85
+ neg_prompt_node: "neg_prompt"
86
+ megapixels: 1.0
87
+
88
  dynamic_pid_chains:
89
  pid_chain:
90
  ksampler_node: "ksampler"
core/settings.py CHANGED
@@ -198,6 +198,7 @@ try:
198
  MAX_CONDITIONINGS = _constants.get('MAX_CONDITIONINGS', 10)
199
  MAX_CONTROLNETS = _constants.get('MAX_CONTROLNETS', 5)
200
  MAX_IPADAPTERS = _constants.get('MAX_IPADAPTERS', 5)
 
201
  LORA_SOURCE_CHOICES = _constants.get('LORA_SOURCE_CHOICES', ["Civitai", "Hugging Face", "Upload File"])
202
  RESOLUTION_MAP = _constants.get('RESOLUTION_MAP', {})
203
  MULTIPLIERS_MAP = _constants.get('MULTIPLIERS_MAP', {})
@@ -207,7 +208,7 @@ try:
207
  MODEL_DEFAULTS_CONFIG = load_model_defaults()
208
  except Exception as e:
209
  print(f"FATAL: Could not load constants from YAML. Error: {e}")
210
- MAX_LORAS, MAX_EMBEDDINGS, MAX_CONDITIONINGS, MAX_CONTROLNETS, MAX_IPADAPTERS = 10, 5, 10, 5, 5
211
  LORA_SOURCE_CHOICES = ["Civitai", "Hugging Face", "Upload File"]
212
  RESOLUTION_MAP = {}
213
  MULTIPLIERS_MAP = {}
 
198
  MAX_CONDITIONINGS = _constants.get('MAX_CONDITIONINGS', 10)
199
  MAX_CONTROLNETS = _constants.get('MAX_CONTROLNETS', 5)
200
  MAX_IPADAPTERS = _constants.get('MAX_IPADAPTERS', 5)
201
+ MAX_MOSS_IMAGE_EDITS = _constants.get('MAX_MOSS_IMAGE_EDITS', 10)
202
  LORA_SOURCE_CHOICES = _constants.get('LORA_SOURCE_CHOICES', ["Civitai", "Hugging Face", "Upload File"])
203
  RESOLUTION_MAP = _constants.get('RESOLUTION_MAP', {})
204
  MULTIPLIERS_MAP = _constants.get('MULTIPLIERS_MAP', {})
 
208
  MODEL_DEFAULTS_CONFIG = load_model_defaults()
209
  except Exception as e:
210
  print(f"FATAL: Could not load constants from YAML. Error: {e}")
211
+ MAX_LORAS, MAX_EMBEDDINGS, MAX_CONDITIONINGS, MAX_CONTROLNETS, MAX_IPADAPTERS, MAX_MOSS_IMAGE_EDITS = 10, 5, 10, 5, 5, 10
212
  LORA_SOURCE_CHOICES = ["Civitai", "Hugging Face", "Upload File"]
213
  RESOLUTION_MAP = {}
214
  MULTIPLIERS_MAP = {}
mcp_tools/common.py CHANGED
@@ -447,6 +447,7 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
447
  qwen_image_edit_data = []
448
  hidream_o1_reference_data = []
449
  sensenova_reference_data = []
 
450
  cond_prompts = []
451
  cond_widths = []
452
  cond_heights = []
@@ -700,6 +701,16 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
700
  img = _parse_image_param(item.get("image"))
701
  if img:
702
  sensenova_reference_data.append(img)
 
 
 
 
 
 
 
 
 
 
703
  elif itype == "vae":
704
  v_source = item.get("source", item.get("vae_source", "Civitai"))
705
  v_val = item.get("vae_value", item.get("value", item.get("vae_id", item.get("vae_name", ""))))
@@ -710,6 +721,7 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
710
  if lora_data: ui_inputs["lora_data"] = lora_data
711
  if embedding_data: ui_inputs["embedding_data"] = embedding_data
712
  if controlnet_data: ui_inputs["controlnet_data"] = controlnet_data
 
713
 
714
  if ipadapter_images:
715
  preset = ipadapter_global_preset or "STANDARD (medium strength)"
@@ -839,6 +851,25 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
839
  except (ValueError, TypeError):
840
  pass
841
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
842
  _TASKS_DB[task_id]["progress"] = 50
843
 
844
  # Execute Pipeline
 
447
  qwen_image_edit_data = []
448
  hidream_o1_reference_data = []
449
  sensenova_reference_data = []
450
+ moss_image_edit_data = []
451
  cond_prompts = []
452
  cond_widths = []
453
  cond_heights = []
 
701
  img = _parse_image_param(item.get("image"))
702
  if img:
703
  sensenova_reference_data.append(img)
704
+ elif itype == "moss_image_edit":
705
+ if "images" in item and isinstance(item["images"], list):
706
+ for img_item in item["images"]:
707
+ p_img = _parse_image_param(img_item)
708
+ if p_img:
709
+ moss_image_edit_data.append(p_img)
710
+ elif "image" in item:
711
+ p_img = _parse_image_param(item["image"])
712
+ if p_img:
713
+ moss_image_edit_data.append(p_img)
714
  elif itype == "vae":
715
  v_source = item.get("source", item.get("vae_source", "Civitai"))
716
  v_val = item.get("vae_value", item.get("value", item.get("vae_id", item.get("vae_name", ""))))
 
721
  if lora_data: ui_inputs["lora_data"] = lora_data
722
  if embedding_data: ui_inputs["embedding_data"] = embedding_data
723
  if controlnet_data: ui_inputs["controlnet_data"] = controlnet_data
724
+ if moss_image_edit_data: ui_inputs["moss_image_edit_data"] = moss_image_edit_data
725
 
726
  if ipadapter_images:
727
  preset = ipadapter_global_preset or "STANDARD (medium strength)"
 
851
  except (ValueError, TypeError):
852
  pass
853
 
854
+ moss_edit_val = params.get("moss_image_edit")
855
+ if moss_edit_val:
856
+ m_imgs = []
857
+ if isinstance(moss_edit_val, dict):
858
+ raw_imgs = moss_edit_val.get("images", [])
859
+ if isinstance(raw_imgs, list):
860
+ for img_item in raw_imgs:
861
+ p_img = _parse_image_param(img_item)
862
+ if p_img: m_imgs.append(p_img)
863
+ elif "image" in moss_edit_val:
864
+ p_img = _parse_image_param(moss_edit_val["image"])
865
+ if p_img: m_imgs.append(p_img)
866
+ elif isinstance(moss_edit_val, list):
867
+ for img_item in moss_edit_val:
868
+ p_img = _parse_image_param(img_item)
869
+ if p_img: m_imgs.append(p_img)
870
+ if m_imgs:
871
+ ui_inputs["moss_image_edit_data"] = m_imgs
872
+
873
  _TASKS_DB[task_id]["progress"] = 50
874
 
875
  # Execute Pipeline
ui/events/chain_handlers.py CHANGED
@@ -689,4 +689,39 @@ def create_krea2_reference_edit_event_handlers(prefix, ui_components):
689
  add_outputs = [count_state, add_button, del_button] + ref_rows
690
  del_outputs = [count_state, add_button, del_button] + ref_rows + images
691
  add_button.click(fn=add_ref_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
692
- del_button.click(fn=del_ref_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
689
  add_outputs = [count_state, add_button, del_button] + ref_rows
690
  del_outputs = [count_state, add_button, del_button] + ref_rows + images
691
  add_button.click(fn=add_ref_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
692
+ del_button.click(fn=del_ref_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
693
+
694
+
695
+ def create_moss_image_edit_event_handlers(prefix, ui_components):
696
+ ref_rows = ui_components.get(f'moss_image_edit_rows_{prefix}')
697
+ if not ref_rows: return
698
+ count_state = ui_components[f'moss_image_edit_count_state_{prefix}']
699
+ add_button = ui_components[f'add_moss_image_edit_button_{prefix}']
700
+ del_button = ui_components[f'delete_moss_image_edit_button_{prefix}']
701
+ images = ui_components[f'moss_image_edit_images_{prefix}']
702
+ max_count = len(ref_rows)
703
+
704
+ def add_ref_row(c):
705
+ c += 1
706
+ return {
707
+ count_state: c,
708
+ ref_rows[c - 1]: gr.update(visible=True),
709
+ add_button: gr.update(visible=c < max_count),
710
+ del_button: gr.update(visible=True),
711
+ }
712
+
713
+ def del_ref_row(c):
714
+ c -= 1
715
+ return {
716
+ count_state: c,
717
+ ref_rows[c]: gr.update(visible=False),
718
+ images[c]: None,
719
+ add_button: gr.update(visible=True),
720
+ del_button: gr.update(visible=c > 1),
721
+ }
722
+
723
+ add_outputs = [count_state, add_button, del_button] + ref_rows
724
+ del_outputs = [count_state, add_button, del_button] + ref_rows + images
725
+ add_button.click(fn=add_ref_row, inputs=[count_state], outputs=add_outputs, show_progress=False)
726
+ del_button.click(fn=del_ref_row, inputs=[count_state], outputs=del_outputs, show_progress=False)
727
+
ui/events/change_handlers.py CHANGED
@@ -15,7 +15,7 @@ from .config_loaders import (
15
  load_ipadapter_config
16
  )
17
 
18
- def make_update_fn(m_comp, cat_comp, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, ipa_preset, lora_acc, cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, hidream_o1_ref_acc, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp, pid_acc=None, vae_acc=None, joyai_ref_acc=None, krea2_identity_edit_acc=None, krea2_reference_edit_acc=None, qwen_image_edit_acc=None, ref_img_acc=None, sensenova_ref_acc=None, task_type=None, type_comp=None, qwen_pe_acc=None, ming_pe_acc=None, flux3_pe_acc=None, moss_image_control_acc=None):
19
  def update_fn(*args):
20
  arch = args[0]
21
  category = args[1]
@@ -88,6 +88,7 @@ def make_update_fn(m_comp, cat_comp, ar_comp, width_comp, height_comp, cn_types,
88
  if ming_pe_acc: updates[ming_pe_acc] = gr.update(visible=('ming_image_prompt_enhancer' in enabled_chains))
89
  if flux3_pe_acc: updates[flux3_pe_acc] = gr.update(visible=('flux3_prompt_upsampler' in enabled_chains))
90
  if moss_image_control_acc: updates[moss_image_control_acc] = gr.update(visible=('moss_image_control' in enabled_chains))
 
91
 
92
  if ar_comp:
93
  res_key = arch_model_type
@@ -132,7 +133,7 @@ def make_update_fn(m_comp, cat_comp, ar_comp, width_comp, height_comp, cn_types,
132
  return update_fn
133
 
134
 
135
- def make_model_change_fn(cat_comp_ref, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, arch_comp_ref, ipa_preset, lora_acc, cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, hidream_o1_ref_acc, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp, pid_acc=None, vae_acc=None, joyai_ref_acc=None, krea2_identity_edit_acc=None, krea2_reference_edit_acc=None, qwen_image_edit_acc=None, ref_img_acc=None, sensenova_ref_acc=None, task_type=None, type_comp=None, qwen_pe_acc=None, ming_pe_acc=None, flux3_pe_acc=None, moss_image_control_acc=None):
136
  def change_fn(*args):
137
  model_name = args[0]
138
  idx = 1
@@ -213,6 +214,7 @@ def make_model_change_fn(cat_comp_ref, ar_comp, width_comp, height_comp, cn_type
213
  if ming_pe_acc: updates[ming_pe_acc] = gr.update(visible=('ming_image_prompt_enhancer' in enabled_chains))
214
  if flux3_pe_acc: updates[flux3_pe_acc] = gr.update(visible=('flux3_prompt_upsampler' in enabled_chains))
215
  if moss_image_control_acc: updates[moss_image_control_acc] = gr.update(visible=('moss_image_control' in enabled_chains))
 
216
 
217
  if ar_comp:
218
  res_key = arch_model_type
 
15
  load_ipadapter_config
16
  )
17
 
18
+ def make_update_fn(m_comp, cat_comp, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, ipa_preset, lora_acc, cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, hidream_o1_ref_acc, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp, pid_acc=None, vae_acc=None, joyai_ref_acc=None, krea2_identity_edit_acc=None, krea2_reference_edit_acc=None, qwen_image_edit_acc=None, ref_img_acc=None, sensenova_ref_acc=None, task_type=None, type_comp=None, qwen_pe_acc=None, ming_pe_acc=None, flux3_pe_acc=None, moss_image_control_acc=None, moss_image_edit_acc=None):
19
  def update_fn(*args):
20
  arch = args[0]
21
  category = args[1]
 
88
  if ming_pe_acc: updates[ming_pe_acc] = gr.update(visible=('ming_image_prompt_enhancer' in enabled_chains))
89
  if flux3_pe_acc: updates[flux3_pe_acc] = gr.update(visible=('flux3_prompt_upsampler' in enabled_chains))
90
  if moss_image_control_acc: updates[moss_image_control_acc] = gr.update(visible=('moss_image_control' in enabled_chains))
91
+ if moss_image_edit_acc: updates[moss_image_edit_acc] = gr.update(visible=('moss_image_edit' in enabled_chains))
92
 
93
  if ar_comp:
94
  res_key = arch_model_type
 
133
  return update_fn
134
 
135
 
136
+ def make_model_change_fn(cat_comp_ref, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, arch_comp_ref, ipa_preset, lora_acc, cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, hidream_o1_ref_acc, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp, pid_acc=None, vae_acc=None, joyai_ref_acc=None, krea2_identity_edit_acc=None, krea2_reference_edit_acc=None, qwen_image_edit_acc=None, ref_img_acc=None, sensenova_ref_acc=None, task_type=None, type_comp=None, qwen_pe_acc=None, ming_pe_acc=None, flux3_pe_acc=None, moss_image_control_acc=None, moss_image_edit_acc=None):
137
  def change_fn(*args):
138
  model_name = args[0]
139
  idx = 1
 
214
  if ming_pe_acc: updates[ming_pe_acc] = gr.update(visible=('ming_image_prompt_enhancer' in enabled_chains))
215
  if flux3_pe_acc: updates[flux3_pe_acc] = gr.update(visible=('flux3_prompt_upsampler' in enabled_chains))
216
  if moss_image_control_acc: updates[moss_image_control_acc] = gr.update(visible=('moss_image_control' in enabled_chains))
217
+ if moss_image_edit_acc: updates[moss_image_edit_acc] = gr.update(visible=('moss_image_edit' in enabled_chains))
218
 
219
  if ar_comp:
220
  res_key = arch_model_type
ui/events/main.py CHANGED
@@ -15,7 +15,8 @@ from .chain_handlers import (
15
  create_krea2_identity_edit_event_handlers,
16
  create_krea2_reference_edit_event_handlers,
17
  create_qwen_image_edit_event_handlers,
18
- create_reference_image_event_handlers
 
19
  )
20
  from .change_handlers import (
21
  make_update_fn,
@@ -66,6 +67,7 @@ def attach_event_handlers(ui_components, demo):
66
  ming_pe_accordion = ui_components.get(f'ming_image_prompt_enhancer_accordion_{prefix}')
67
  flux3_pe_accordion = ui_components.get(f'flux3_prompt_upsampler_accordion_{prefix}')
68
  moss_control_accordion = ui_components.get(f'moss_image_control_accordion_{prefix}')
 
69
 
70
  ipa_preset_list = ui_components.get(f'ipadapter_final_preset_{prefix}')
71
 
@@ -105,6 +107,7 @@ def attach_event_handlers(ui_components, demo):
105
  if ming_pe_accordion: outputs.append(ming_pe_accordion)
106
  if flux3_pe_accordion: outputs.append(flux3_pe_accordion)
107
  if moss_control_accordion: outputs.append(moss_control_accordion)
 
108
  if ipa_preset_list: outputs.append(ipa_preset_list)
109
 
110
  outputs.extend(valid_extra_comps)
@@ -116,7 +119,8 @@ def attach_event_handlers(ui_components, demo):
116
  ref_latent_accordion, hidream_o1_ref_accordion, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp,
117
  pid_acc=pid_accordion, vae_acc=vae_accordion, joyai_ref_acc=joyai_ref_accordion, krea2_identity_edit_acc=krea2_identity_edit_accordion, krea2_reference_edit_acc=krea2_reference_edit_accordion, qwen_image_edit_acc=qwen_image_edit_accordion, ref_img_acc=ref_img_accordion, sensenova_ref_acc=sensenova_ref_accordion,
118
  task_type=task_type, type_comp=type_comp, qwen_pe_acc=qwen_pe_accordion, ming_pe_acc=ming_pe_accordion, flux3_pe_acc=flux3_pe_accordion,
119
- moss_image_control_acc=moss_control_accordion
 
120
  )
121
  inputs = [arch_comp, cat_comp]
122
  if aspect_ratio_comp:
@@ -154,6 +158,7 @@ def attach_event_handlers(ui_components, demo):
154
  if ming_pe_accordion: outputs2.append(ming_pe_accordion)
155
  if flux3_pe_accordion: outputs2.append(flux3_pe_accordion)
156
  if moss_control_accordion: outputs2.append(moss_control_accordion)
 
157
  if ipa_preset_list: outputs2.append(ipa_preset_list)
158
 
159
  outputs2.extend(valid_extra_comps)
@@ -170,7 +175,8 @@ def attach_event_handlers(ui_components, demo):
170
  ref_latent_accordion, hidream_o1_ref_accordion, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp,
171
  pid_acc=pid_accordion, vae_acc=vae_accordion, joyai_ref_acc=joyai_ref_accordion, krea2_identity_edit_acc=krea2_identity_edit_accordion, krea2_reference_edit_acc=krea2_reference_edit_accordion, qwen_image_edit_acc=qwen_image_edit_accordion, ref_img_acc=ref_img_accordion, sensenova_ref_acc=sensenova_ref_accordion,
172
  task_type=task_type, type_comp=type_comp, qwen_pe_acc=qwen_pe_accordion, ming_pe_acc=ming_pe_accordion, flux3_pe_acc=flux3_pe_accordion,
173
- moss_image_control_acc=moss_control_accordion
 
174
  )
175
  if type_comp:
176
  inputs2.append(type_comp)
@@ -192,6 +198,7 @@ def attach_event_handlers(ui_components, demo):
192
  create_krea2_reference_edit_event_handlers(prefix, ui_components)
193
  create_qwen_image_edit_event_handlers(prefix, ui_components)
194
  create_reference_image_event_handlers(prefix, ui_components)
 
195
  create_run_event(prefix, task_type, ui_components)
196
 
197
  # Dynamic Type toggle event for unified imagegen UI
@@ -262,6 +269,7 @@ def attach_event_handlers(ui_components, demo):
262
  f'ming_image_prompt_enhancer_accordion_{prefix}': 'ming_image_prompt_enhancer',
263
  f'flux3_prompt_upsampler_accordion_{prefix}': 'flux3_prompt_upsampler',
264
  f'moss_image_control_accordion_{prefix}': 'moss_image_control',
 
265
  f'pid_accordion_{prefix}': 'pid',
266
  f'vae_accordion_{prefix}': 'vae',
267
  }
@@ -303,6 +311,7 @@ def attach_event_handlers(ui_components, demo):
303
  'ming_image_prompt_enhancer_accordion_imagegen',
304
  'flux3_prompt_upsampler_accordion_imagegen',
305
  'moss_image_control_accordion_imagegen',
 
306
  'pid_accordion_imagegen', 'vae_accordion_imagegen',
307
  ]
308
  for key in accordion_keys:
 
15
  create_krea2_identity_edit_event_handlers,
16
  create_krea2_reference_edit_event_handlers,
17
  create_qwen_image_edit_event_handlers,
18
+ create_reference_image_event_handlers,
19
+ create_moss_image_edit_event_handlers
20
  )
21
  from .change_handlers import (
22
  make_update_fn,
 
67
  ming_pe_accordion = ui_components.get(f'ming_image_prompt_enhancer_accordion_{prefix}')
68
  flux3_pe_accordion = ui_components.get(f'flux3_prompt_upsampler_accordion_{prefix}')
69
  moss_control_accordion = ui_components.get(f'moss_image_control_accordion_{prefix}')
70
+ moss_edit_accordion = ui_components.get(f'moss_image_edit_accordion_{prefix}')
71
 
72
  ipa_preset_list = ui_components.get(f'ipadapter_final_preset_{prefix}')
73
 
 
107
  if ming_pe_accordion: outputs.append(ming_pe_accordion)
108
  if flux3_pe_accordion: outputs.append(flux3_pe_accordion)
109
  if moss_control_accordion: outputs.append(moss_control_accordion)
110
+ if moss_edit_accordion: outputs.append(moss_edit_accordion)
111
  if ipa_preset_list: outputs.append(ipa_preset_list)
112
 
113
  outputs.extend(valid_extra_comps)
 
119
  ref_latent_accordion, hidream_o1_ref_accordion, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp,
120
  pid_acc=pid_accordion, vae_acc=vae_accordion, joyai_ref_acc=joyai_ref_accordion, krea2_identity_edit_acc=krea2_identity_edit_accordion, krea2_reference_edit_acc=krea2_reference_edit_accordion, qwen_image_edit_acc=qwen_image_edit_accordion, ref_img_acc=ref_img_accordion, sensenova_ref_acc=sensenova_ref_accordion,
121
  task_type=task_type, type_comp=type_comp, qwen_pe_acc=qwen_pe_accordion, ming_pe_acc=ming_pe_accordion, flux3_pe_acc=flux3_pe_accordion,
122
+ moss_image_control_acc=moss_control_accordion,
123
+ moss_image_edit_acc=moss_edit_accordion
124
  )
125
  inputs = [arch_comp, cat_comp]
126
  if aspect_ratio_comp:
 
158
  if ming_pe_accordion: outputs2.append(ming_pe_accordion)
159
  if flux3_pe_accordion: outputs2.append(flux3_pe_accordion)
160
  if moss_control_accordion: outputs2.append(moss_control_accordion)
161
+ if moss_edit_accordion: outputs2.append(moss_edit_accordion)
162
  if ipa_preset_list: outputs2.append(ipa_preset_list)
163
 
164
  outputs2.extend(valid_extra_comps)
 
175
  ref_latent_accordion, hidream_o1_ref_accordion, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp,
176
  pid_acc=pid_accordion, vae_acc=vae_accordion, joyai_ref_acc=joyai_ref_accordion, krea2_identity_edit_acc=krea2_identity_edit_accordion, krea2_reference_edit_acc=krea2_reference_edit_accordion, qwen_image_edit_acc=qwen_image_edit_accordion, ref_img_acc=ref_img_accordion, sensenova_ref_acc=sensenova_ref_accordion,
177
  task_type=task_type, type_comp=type_comp, qwen_pe_acc=qwen_pe_accordion, ming_pe_acc=ming_pe_accordion, flux3_pe_acc=flux3_pe_accordion,
178
+ moss_image_control_acc=moss_control_accordion,
179
+ moss_image_edit_acc=moss_edit_accordion
180
  )
181
  if type_comp:
182
  inputs2.append(type_comp)
 
198
  create_krea2_reference_edit_event_handlers(prefix, ui_components)
199
  create_qwen_image_edit_event_handlers(prefix, ui_components)
200
  create_reference_image_event_handlers(prefix, ui_components)
201
+ create_moss_image_edit_event_handlers(prefix, ui_components)
202
  create_run_event(prefix, task_type, ui_components)
203
 
204
  # Dynamic Type toggle event for unified imagegen UI
 
269
  f'ming_image_prompt_enhancer_accordion_{prefix}': 'ming_image_prompt_enhancer',
270
  f'flux3_prompt_upsampler_accordion_{prefix}': 'flux3_prompt_upsampler',
271
  f'moss_image_control_accordion_{prefix}': 'moss_image_control',
272
+ f'moss_image_edit_accordion_{prefix}': 'moss_image_edit',
273
  f'pid_accordion_{prefix}': 'pid',
274
  f'vae_accordion_{prefix}': 'vae',
275
  }
 
311
  'ming_image_prompt_enhancer_accordion_imagegen',
312
  'flux3_prompt_upsampler_accordion_imagegen',
313
  'moss_image_control_accordion_imagegen',
314
+ 'moss_image_edit_accordion_imagegen',
315
  'pid_accordion_imagegen', 'vae_accordion_imagegen',
316
  ]
317
  for key in accordion_keys:
ui/events/run_handlers.py CHANGED
@@ -119,6 +119,7 @@ def create_run_event(prefix: str, task_type: str = None, ui_components: dict = N
119
  krea2_reference_edit_data_components = ui_components.get(f'all_krea2_reference_edit_components_flat_{prefix}', [])
120
  qwen_image_edit_data_components = ui_components.get(f'all_qwen_image_edit_components_flat_{prefix}', [])
121
  reference_image_data_components = ui_components.get(f'all_reference_image_components_flat_{prefix}', [])
 
122
 
123
  run_inputs_map['vae_source'] = ui_components.get(f'vae_source_{prefix}')
124
  run_inputs_map['vae_id'] = ui_components.get(f'vae_id_{prefix}')
@@ -129,7 +130,8 @@ def create_run_event(prefix: str, task_type: str = None, ui_components: dict = N
129
  all_chains = [
130
  lora_data_components, controlnet_data_components, ipadapter_data_components,
131
  sd3_ipadapter_data_components, flux1_ipadapter_data_components, style_data_components,
132
- embedding_data_components, conditioning_data_components, reference_latent_data_components, hidream_o1_reference_data_components, sensenova_reference_data_components, joyai_reference_data_components, krea2_identity_edit_data_components, krea2_reference_edit_data_components, qwen_image_edit_data_components, reference_image_data_components
 
133
  ]
134
  for chain in all_chains:
135
  if chain:
@@ -162,6 +164,7 @@ def create_run_event(prefix: str, task_type: str = None, ui_components: dict = N
162
  assign_chain_data('krea2_reference_edit_data', krea2_reference_edit_data_components)
163
  assign_chain_data('qwen_image_edit_data', qwen_image_edit_data_components)
164
  assign_chain_data('reference_image_data', reference_image_data_components)
 
165
 
166
  if 'ui_type' in ui_dict:
167
  ui_dict['task_type'] = TYPE_TASK_MAP.get(ui_dict['ui_type'], 'txt2img')
 
119
  krea2_reference_edit_data_components = ui_components.get(f'all_krea2_reference_edit_components_flat_{prefix}', [])
120
  qwen_image_edit_data_components = ui_components.get(f'all_qwen_image_edit_components_flat_{prefix}', [])
121
  reference_image_data_components = ui_components.get(f'all_reference_image_components_flat_{prefix}', [])
122
+ moss_image_edit_data_components = ui_components.get(f'all_moss_image_edit_components_flat_{prefix}', [])
123
 
124
  run_inputs_map['vae_source'] = ui_components.get(f'vae_source_{prefix}')
125
  run_inputs_map['vae_id'] = ui_components.get(f'vae_id_{prefix}')
 
130
  all_chains = [
131
  lora_data_components, controlnet_data_components, ipadapter_data_components,
132
  sd3_ipadapter_data_components, flux1_ipadapter_data_components, style_data_components,
133
+ embedding_data_components, conditioning_data_components, reference_latent_data_components, hidream_o1_reference_data_components, sensenova_reference_data_components, joyai_reference_data_components, krea2_identity_edit_data_components, krea2_reference_edit_data_components, qwen_image_edit_data_components, reference_image_data_components,
134
+ moss_image_edit_data_components
135
  ]
136
  for chain in all_chains:
137
  if chain:
 
164
  assign_chain_data('krea2_reference_edit_data', krea2_reference_edit_data_components)
165
  assign_chain_data('qwen_image_edit_data', qwen_image_edit_data_components)
166
  assign_chain_data('reference_image_data', reference_image_data_components)
167
+ assign_chain_data('moss_image_edit_data', moss_image_edit_data_components)
168
 
169
  if 'ui_type' in ui_dict:
170
  ui_dict['task_type'] = TYPE_TASK_MAP.get(ui_dict['ui_type'], 'txt2img')
ui/imagegen_ui.py CHANGED
@@ -10,7 +10,8 @@ from .shared.ui_components import (
10
  create_sd3_ipadapter_ui, create_flux1_ipadapter_ui, create_style_ui,
11
  create_reference_latent_ui, create_hidream_o1_reference_ui, create_sensenova_reference_ui, create_joyai_reference_ui, create_reference_image_ui, create_krea2_identity_edit_ui, create_krea2_reference_edit_ui, create_qwen_image_edit_ui,
12
  create_pid_ui, create_qwen_image_2_1_prompt_enhancer_ui, create_ming_image_prompt_enhancer_ui,
13
- create_flux3_prompt_upsampler_ui, create_moss_image_control_ui
 
14
  )
15
 
16
  default_vals = MODEL_DEFAULTS_CONFIG.get('Default', {})
@@ -193,6 +194,7 @@ def create_ui():
193
  components.update(create_flux3_prompt_upsampler_ui(prefix))
194
  components.update(create_vae_override_ui(prefix))
195
  components.update(create_moss_image_control_ui(prefix))
 
196
  components.update(create_pid_ui(prefix))
197
  components[f'accordion_wrapper_{prefix}'] = accordion_wrapper
198
 
 
10
  create_sd3_ipadapter_ui, create_flux1_ipadapter_ui, create_style_ui,
11
  create_reference_latent_ui, create_hidream_o1_reference_ui, create_sensenova_reference_ui, create_joyai_reference_ui, create_reference_image_ui, create_krea2_identity_edit_ui, create_krea2_reference_edit_ui, create_qwen_image_edit_ui,
12
  create_pid_ui, create_qwen_image_2_1_prompt_enhancer_ui, create_ming_image_prompt_enhancer_ui,
13
+ create_flux3_prompt_upsampler_ui, create_moss_image_control_ui,
14
+ create_moss_image_edit_ui
15
  )
16
 
17
  default_vals = MODEL_DEFAULTS_CONFIG.get('Default', {})
 
194
  components.update(create_flux3_prompt_upsampler_ui(prefix))
195
  components.update(create_vae_override_ui(prefix))
196
  components.update(create_moss_image_control_ui(prefix))
197
+ components.update(create_moss_image_edit_ui(prefix))
198
  components.update(create_pid_ui(prefix))
199
  components[f'accordion_wrapper_{prefix}'] = accordion_wrapper
200
 
ui/shared/ui_components.py CHANGED
@@ -2,7 +2,7 @@ import gradio as gr
2
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
3
  from core.settings import (
4
  MAX_LORAS, LORA_SOURCE_CHOICES, MAX_EMBEDDINGS, MAX_CONDITIONINGS,
5
- MAX_CONTROLNETS, MAX_IPADAPTERS, RESOLUTION_MAP, ARCHITECTURES_CONFIG,
6
  MODEL_MAP_CHECKPOINT, MODEL_TYPE_MAP, FEATURES_CONFIG, ARCH_CATEGORIES_MAP,
7
  VAE_DIR, MODEL_DEFAULTS_CONFIG
8
  )
@@ -912,4 +912,36 @@ def create_flux3_prompt_upsampler_ui(prefix: str):
912
  )
913
 
914
  return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
915
 
 
2
  from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
3
  from core.settings import (
4
  MAX_LORAS, LORA_SOURCE_CHOICES, MAX_EMBEDDINGS, MAX_CONDITIONINGS,
5
+ MAX_CONTROLNETS, MAX_IPADAPTERS, MAX_MOSS_IMAGE_EDITS, RESOLUTION_MAP, ARCHITECTURES_CONFIG,
6
  MODEL_MAP_CHECKPOINT, MODEL_TYPE_MAP, FEATURES_CONFIG, ARCH_CATEGORIES_MAP,
7
  VAE_DIR, MODEL_DEFAULTS_CONFIG
8
  )
 
912
  )
913
 
914
  return components
915
+
916
+
917
+ def create_moss_image_edit_ui(prefix: str, max_units=None):
918
+ if max_units is None:
919
+ max_units = MAX_MOSS_IMAGE_EDITS
920
+ components = {}
921
+ key = lambda name: f"{name}_{prefix}"
922
+
923
+ with gr.Accordion("MOSS-Image Edit Settings", open=False, visible=('moss_image_edit' in default_enabled_chains)) as ref_accordion:
924
+ components[key('moss_image_edit_accordion')] = ref_accordion
925
+ gr.Markdown("💡 **Tip:** For multimodal models, this feature enables powerful editing and combining capabilities. In txt2img mode, adding a single reference image performs an Image Edit, while adding multiple images performs an Image Combine. Uses [chinoll/Moss-Image-Edit](https://huggingface.co/chinoll/Moss-Image-Edit).")
926
+
927
+ ref_image_groups = []
928
+ ref_image_inputs = []
929
+ with gr.Row():
930
+ for i in range(max_units):
931
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
932
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
933
+ ref_image_groups.append(img_col)
934
+ ref_image_inputs.append(img_comp)
935
+
936
+ components[key('moss_image_edit_rows')] = ref_image_groups
937
+ components[key('moss_image_edit_images')] = ref_image_inputs
938
+
939
+ with gr.Row():
940
+ components[key('add_moss_image_edit_button')] = gr.Button("✚ Add Reference Image")
941
+ components[key('delete_moss_image_edit_button')] = gr.Button("➖ Delete Reference Image", visible=False)
942
+ components[key('moss_image_edit_count_state')] = gr.State(1)
943
+
944
+ components[key('all_moss_image_edit_components_flat')] = ref_image_inputs
945
+
946
+ return components
947
 
yaml/chain_features.yaml CHANGED
@@ -557,3 +557,18 @@ moss_image_control:
557
  minimum: 0.0
558
  maximum: 1.0
559
  description: "Sampling percentage at which control ends (default 0.5, use 0.5 for 3D mannequin)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  minimum: 0.0
558
  maximum: 1.0
559
  description: "Sampling percentage at which control ends (default 0.5, use 0.5 for 3D mannequin)."
560
+
561
+ moss_image_edit:
562
+ chains: moss_image_edit
563
+ display_name: "MOSS-Image Edit"
564
+ description: "Edits images or generates variants using MOSS-Image Edit LoRA (officially packaged by Comfy-Org as moss_image_edit_lora_rank_64_bf16.safetensors) and native TextEncodeMOSSImageEdit node."
565
+ max_count: 10
566
+ usage_guideline: "Upload 1-10 reference images and specify prompt. Automatically uses the official MOSS-Image Edit LoRA layout."
567
+ parameters_schema:
568
+ type: object
569
+ properties:
570
+ images:
571
+ type: array
572
+ items:
573
+ type: string
574
+ description: "List of 1 to 10 reference images encoded as Base64 Data URI, HTTP/HTTPS URL, or local file path."
yaml/constants.yaml CHANGED
@@ -4,6 +4,7 @@ MAX_IPADAPTERS: 5
4
  MAX_EMBEDDINGS: 5
5
  MAX_CONDITIONINGS: 10
6
  MAX_REFERENCE_LATENTS: 10
 
7
  LORA_SOURCE_CHOICES: ["Civitai", "Hugging Face", "Upload File"]
8
 
9
  RESOLUTION_MAP:
 
4
  MAX_EMBEDDINGS: 5
5
  MAX_CONDITIONINGS: 10
6
  MAX_REFERENCE_LATENTS: 10
7
+ MAX_MOSS_IMAGE_EDITS: 10
8
  LORA_SOURCE_CHOICES: ["Civitai", "Hugging Face", "Upload File"]
9
 
10
  RESOLUTION_MAP:
yaml/file_list.yaml CHANGED
@@ -892,6 +892,11 @@ file:
892
  source: "hf"
893
  repo_id: "darask0/MOSS-Image-color-depth-control"
894
  repository_file_path: "moss-control.safetensors"
 
 
 
 
 
895
  # Krea2 Identity Edit
896
  - filename: "krea2_identity_edit_v1_2.safetensors"
897
  source: "hf"
 
892
  source: "hf"
893
  repo_id: "darask0/MOSS-Image-color-depth-control"
894
  repository_file_path: "moss-control.safetensors"
895
+ # MOSS-Image Edit
896
+ - filename: "moss_image_edit_lora_rank_64_bf16.safetensors"
897
+ source: "hf"
898
+ repo_id: "Comfy-Org/MOSS-Image"
899
+ repository_file_path: "loras/moss_image_edit_lora_rank_64_bf16.safetensors"
900
  # Krea2 Identity Edit
901
  - filename: "krea2_identity_edit_v1_2.safetensors"
902
  source: "hf"
yaml/model_architecture_features.yaml CHANGED
@@ -87,6 +87,7 @@ moss-image:
87
  enabled_chains:
88
  - lora
89
  - moss_image_control
 
90
  - pid
91
 
92
  ming-image:
 
87
  enabled_chains:
88
  - lora
89
  - moss_image_control
90
+ - moss_image_edit
91
  - pid
92
 
93
  ming-image:
yaml/task_features.yaml CHANGED
@@ -26,6 +26,7 @@ txt2img:
26
  - ming_image_prompt_enhancer
27
  - flux3_prompt_upsampler
28
  - moss_image_control
 
29
  - pid
30
  - vae
31
 
@@ -52,6 +53,7 @@ img2img:
52
  - ming_image_prompt_enhancer
53
  - flux3_prompt_upsampler
54
  - moss_image_control
 
55
  - vae
56
 
57
  inpaint:
@@ -77,6 +79,7 @@ inpaint:
77
  - ming_image_prompt_enhancer
78
  - flux3_prompt_upsampler
79
  - moss_image_control
 
80
  - vae
81
 
82
  outpaint:
@@ -102,6 +105,7 @@ outpaint:
102
  - ming_image_prompt_enhancer
103
  - flux3_prompt_upsampler
104
  - moss_image_control
 
105
  - vae
106
 
107
  hires_fix:
@@ -127,4 +131,5 @@ hires_fix:
127
  - ming_image_prompt_enhancer
128
  - flux3_prompt_upsampler
129
  - moss_image_control
 
130
  - vae
 
26
  - ming_image_prompt_enhancer
27
  - flux3_prompt_upsampler
28
  - moss_image_control
29
+ - moss_image_edit
30
  - pid
31
  - vae
32
 
 
53
  - ming_image_prompt_enhancer
54
  - flux3_prompt_upsampler
55
  - moss_image_control
56
+ - moss_image_edit
57
  - vae
58
 
59
  inpaint:
 
79
  - ming_image_prompt_enhancer
80
  - flux3_prompt_upsampler
81
  - moss_image_control
82
+ - moss_image_edit
83
  - vae
84
 
85
  outpaint:
 
105
  - ming_image_prompt_enhancer
106
  - flux3_prompt_upsampler
107
  - moss_image_control
108
+ - moss_image_edit
109
  - vae
110
 
111
  hires_fix:
 
131
  - ming_image_prompt_enhancer
132
  - flux3_prompt_upsampler
133
  - moss_image_control
134
+ - moss_image_edit
135
  - vae