alx lord commited on
Commit
becf6b8
·
verified ·
1 Parent(s): 0305549

Sync with upstream ImageGen + keep custom models and cloud saving

Browse files
README.md CHANGED
@@ -24,6 +24,7 @@ models:
24
  - black-forest-labs/FLUX.2-klein-9b-kv-fp8
25
  - black-forest-labs/FLUX.2-klein-base-4b-nvfp4
26
  - black-forest-labs/FLUX.2-klein-base-9b-nvfp4
 
27
  - bluepen5805/4nima_pencil-XL
28
  - bluepen5805/anima-models
29
  - bluepen5805/anima_pencil-XL
@@ -43,6 +44,7 @@ models:
43
  - Comfy-Org/Anima-LLLite
44
  - Comfy-Org/Boogu-Image
45
  - Comfy-Org/ERNIE-Image
 
46
  - Comfy-Org/FLUX.1-Krea-dev_ComfyUI
47
  - Comfy-Org/flux2-dev
48
  - Comfy-Org/HiDream-I1_ComfyUI
@@ -55,6 +57,7 @@ models:
55
  - Comfy-Org/Lumina_Image_2.0_Repackaged
56
  - Comfy-Org/Mage-Flow
57
  - Comfy-Org/Ming-Image
 
58
  - Comfy-Org/NewBie-image-Exp0.1_repackaged
59
  - Comfy-Org/Omnigen2_ComfyUI_repackaged
60
  - Comfy-Org/Ovis-Image
@@ -100,7 +103,6 @@ models:
100
  - jdopensource/JoyAI-Image-Edit-Plus-ComfyUI
101
  - kandinskylab/Kandinsky-5.0-T2I-Lite
102
  - Kijai/flux-fp8
103
- - Kijai/QwenImage_experimental
104
  - Laxhar/noob_openpose
105
  - Laxhar/noobai-XL-1.1
106
  - Laxhar/noobai-XL-Vpred-1.0
@@ -113,6 +115,10 @@ models:
113
  - MIC-Lab/illustriousXLv1.1_controlnet
114
  - misri/hassakuXLIllustrious_v30
115
  - motimalu/kirazuri-anima
 
 
 
 
116
  - nvidia/Cosmos-Predict2-2B-Text2Image
117
  - nvidia/Cosmos-Predict2-14B-Text2Image
118
  - OnomaAIResearch/Illustrious-XL-v1.0
@@ -149,10 +155,17 @@ models:
149
  - black-forest-labs/FLUX.1-dev
150
  - black-forest-labs/FLUX.1-Krea-dev
151
  - black-forest-labs/FLUX.1-schnell
 
 
 
152
  - Boogu/Boogu-Image-0.1-Turbo
153
  - Boogu/Boogu-Image-0.1-Base
154
  - Boogu/Boogu-Image-0.1-Edit
155
  - Boogu/Boogu-Image-0.1-Edit-Turbo
 
 
 
 
156
  - HiDream-ai/HiDream-I1-Dev
157
  - HiDream-ai/HiDream-I1-Fast
158
  - HiDream-ai/HiDream-I1-Full
@@ -178,10 +191,13 @@ models:
178
  - nvidia/PiD
179
  - nvidia/PixelDiT-1300M-1024px
180
  - OmniGen2/OmniGen2
 
 
181
  - Qwen/Qwen3.8-27B
182
  - Qwen/Qwen-Image
183
  - Qwen/Qwen-Image-2512
184
  - Qwen/Qwen-Image-2.1
 
185
  - Qwen/Qwen-Image-2.1-PE-I2I
186
  - Qwen/Qwen-Image-2.1-PE-T2I
187
  - Qwen/Qwen-Image-Edit
 
24
  - black-forest-labs/FLUX.2-klein-9b-kv-fp8
25
  - black-forest-labs/FLUX.2-klein-base-4b-nvfp4
26
  - black-forest-labs/FLUX.2-klein-base-9b-nvfp4
27
+ - black-forest-labs/flux-3-image-klein-comfy
28
  - bluepen5805/4nima_pencil-XL
29
  - bluepen5805/anima-models
30
  - bluepen5805/anima_pencil-XL
 
44
  - Comfy-Org/Anima-LLLite
45
  - Comfy-Org/Boogu-Image
46
  - Comfy-Org/ERNIE-Image
47
+ - Comfy-Org/Fibo-2.0
48
  - Comfy-Org/FLUX.1-Krea-dev_ComfyUI
49
  - Comfy-Org/flux2-dev
50
  - Comfy-Org/HiDream-I1_ComfyUI
 
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
63
  - Comfy-Org/Ovis-Image
 
103
  - jdopensource/JoyAI-Image-Edit-Plus-ComfyUI
104
  - kandinskylab/Kandinsky-5.0-T2I-Lite
105
  - Kijai/flux-fp8
 
106
  - Laxhar/noob_openpose
107
  - Laxhar/noobai-XL-1.1
108
  - Laxhar/noobai-XL-Vpred-1.0
 
115
  - MIC-Lab/illustriousXLv1.1_controlnet
116
  - misri/hassakuXLIllustrious_v30
117
  - motimalu/kirazuri-anima
118
+ - Noctaluna/Anima-Noct-Anime-Mix
119
+ - Noctaluna/Krea2-Noct-Uncensored
120
+ - Noctaluna/Noct-Q-Anime-Uncensored-Qwen-Image-2.1
121
+ - Noctaluna/Noct-Q-Uncensored-Qwen-Image-2.1
122
  - nvidia/Cosmos-Predict2-2B-Text2Image
123
  - nvidia/Cosmos-Predict2-14B-Text2Image
124
  - OnomaAIResearch/Illustrious-XL-v1.0
 
155
  - black-forest-labs/FLUX.1-dev
156
  - black-forest-labs/FLUX.1-Krea-dev
157
  - black-forest-labs/FLUX.1-schnell
158
+ - black-forest-labs/flux-3-image-klein
159
+ - black-forest-labs/flux-3-image-klein-base
160
+ - black-forest-labs/flux-3-image-klein-pup
161
  - Boogu/Boogu-Image-0.1-Turbo
162
  - Boogu/Boogu-Image-0.1-Base
163
  - Boogu/Boogu-Image-0.1-Edit
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
171
  - HiDream-ai/HiDream-I1-Full
 
191
  - nvidia/PiD
192
  - nvidia/PixelDiT-1300M-1024px
193
  - OmniGen2/OmniGen2
194
+ - OpenMOSS-Team/MOSS-Image-Base
195
+ - OpenMOSS-Team/MOSS-Image-SFT
196
  - Qwen/Qwen3.8-27B
197
  - Qwen/Qwen-Image
198
  - Qwen/Qwen-Image-2512
199
  - Qwen/Qwen-Image-2.1
200
+ - Qwen/Qwen-Image-2.1-Turbo
201
  - Qwen/Qwen-Image-2.1-PE-I2I
202
  - Qwen/Qwen-Image-2.1-PE-T2I
203
  - Qwen/Qwen-Image-Edit
chain_injectors/controlnet_injector.py CHANGED
@@ -28,10 +28,10 @@ def inject(assembler, chain_definition, chain_items):
28
  default_megapixels = 1.0
29
  megapixels = float(chain_definition.get('megapixels', default_megapixels))
30
 
31
- # 1. Qwen-Image-2.1 Fun ControlNet
32
- if template_name == "QwenImage21FunControlNetApply":
33
  if 'model' not in assembler.workflow[ksampler_id]['inputs']:
34
- print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping Qwen Fun ControlNet.")
35
  return
36
 
37
  current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
@@ -57,19 +57,24 @@ def inject(assembler, chain_definition, chain_items):
57
  apply_cn_id = assembler._get_unique_id()
58
  apply_cn_node = assembler._get_node_template(template_name)
59
  apply_cn_node['inputs']['strength'] = item_data.get('strength', 1.0)
60
- apply_cn_node['inputs']['start_percent'] = 0.0
61
- apply_cn_node['inputs']['end_percent'] = 1.0
 
 
62
  apply_cn_node['inputs']['model'] = current_model_connection
63
  apply_cn_node['inputs']['model_patch'] = [patch_loader_id, 0]
64
- apply_cn_node['inputs']['control_image'] = [image_scaler_id, 0]
65
- if vae_connection:
 
 
 
66
  apply_cn_node['inputs']['vae'] = vae_connection
67
 
68
  assembler.workflow[apply_cn_id] = apply_cn_node
69
  current_model_connection = [apply_cn_id, 0]
70
 
71
  assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
72
- print(f"Qwen Fun ControlNet injector applied. KSampler model input redirected through {len(chain_items)} node(s).")
73
  return
74
 
75
  # 2. Anima LLLite ControlNet
 
28
  default_megapixels = 1.0
29
  megapixels = float(chain_definition.get('megapixels', default_megapixels))
30
 
31
+ # 1. Fun ControlNet (Qwen-Image-2.1)
32
+ if template_name in ("ZImageFunControlnet", "QwenImage21FunControlNetApply"):
33
  if 'model' not in assembler.workflow[ksampler_id]['inputs']:
34
+ print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping Fun ControlNet.")
35
  return
36
 
37
  current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
 
57
  apply_cn_id = assembler._get_unique_id()
58
  apply_cn_node = assembler._get_node_template(template_name)
59
  apply_cn_node['inputs']['strength'] = item_data.get('strength', 1.0)
60
+ if 'start_percent' in apply_cn_node.get('inputs', {}):
61
+ apply_cn_node['inputs']['start_percent'] = item_data.get('start_percent', 0.0)
62
+ if 'end_percent' in apply_cn_node.get('inputs', {}):
63
+ apply_cn_node['inputs']['end_percent'] = item_data.get('end_percent', 1.0)
64
  apply_cn_node['inputs']['model'] = current_model_connection
65
  apply_cn_node['inputs']['model_patch'] = [patch_loader_id, 0]
66
+ if 'image' in apply_cn_node.get('inputs', {}):
67
+ apply_cn_node['inputs']['image'] = [image_scaler_id, 0]
68
+ elif 'control_image' in apply_cn_node.get('inputs', {}):
69
+ apply_cn_node['inputs']['control_image'] = [image_scaler_id, 0]
70
+ if vae_connection and 'vae' in apply_cn_node.get('inputs', {}):
71
  apply_cn_node['inputs']['vae'] = vae_connection
72
 
73
  assembler.workflow[apply_cn_id] = apply_cn_node
74
  current_model_connection = [apply_cn_id, 0]
75
 
76
  assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
77
+ print(f"Fun ControlNet injector applied. KSampler model input redirected through {len(chain_items)} node(s).")
78
  return
79
 
80
  # 2. Anima LLLite ControlNet
chain_injectors/flux3_prompt_upsampler_injector.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """FLUX.3 Prompt Upsampler Chain Injector.
2
+
3
+ This injector integrates the FLUX.3 prompt upsampler model into the generation workflow:
4
+ - Uses qwen3.5_9b_flux3_prompt_upsampler_w4a8.safetensors (type: flux3).
5
+ - Uses PrimitiveStringMultiline for system prompt and input prompt.
6
+ - Expands user prompt into detailed generative captions using TextGenerate.
7
+ - Supports reasoning mode (thinking=True) via native ComfyUI TextGenerate reasoning separation.
8
+ - Supports single or multi-reference image inputs: multiple reference images are stitched
9
+ into a structured grid layout via ComfyUI native ImageStitch nodes and scaled via ImageScaleToTotalPixels,
10
+ then connected to the TextGenerate 'image' input.
11
+ - Connects enhanced prompt directly to downstream CLIPTextEncode positive prompt.
12
+ """
13
+
14
+ try:
15
+ from utils.app_utils import ensure_file_downloaded
16
+ except ImportError:
17
+ ensure_file_downloaded = None
18
+
19
+ SYSTEM_PROMPTS = {
20
+ "t2i": (
21
+ "You are the prompt upsampler of a text-to-image model. Expand the user's short request into ONE detailed, self-contained caption: the exact text the image model receives. Output only the caption, with no greetings, explanations, refusals, questions, Markdown, headings, JSON, coordinates, ids or angle-bracket tokens, and no quotes around it. If the input is not clearly an image request, caption a visualization of its concept. The request may begin with a \"TARGET CANVAS: W:H\" line: compose for that canvas (wide spreads content sideways, tall stacks it) and never mention the ratio or the canvas.\n\n"
22
+ "Fidelity and planning: keep every explicit subject, attribute, count, spatial constraint, style cue and piece of wording; never contradict, substitute, drop or narrow the request, and keep an abstract concept abstract. Decide everything left open as ONE concrete, consistent plan; never offer alternatives or hedge. Never re-express a stated constraint through another mechanism; add camera optics (depth of field, bokeh, blur) only when asked for or implied. Without a stated style, plan an actual photograph and open with the medium convention (\"Full-color outdoor photograph, ...\", \"Full-color indoor photograph, ...\", \"Studio photograph, ...\", \"Close-up photograph, ...\"); keep \"Photorealistic\" for renders, and turn \"photorealistic\" or \"realistic\" requests for photos into the photograph convention. Give a photograph a few ordinary, non-ideal specifics drawn from the scene's own objects instead of the stock-photo ideal. Do not invent names, brands, logos or celebrities; keep user-named ones. A long request keeps its content but not its wording: drop appraisal and sales vocabulary, turn moods into observable physical facts, and add only new concrete content with positions. Time of day is lighting, not rendered text.\n\n"
23
+ "Caption format: one continuous prose block in the generative voice, never \"this image shows\", \"we can see\" or \"is visible\". Lead with medium and style, then scene, lighting and camera, then the subjects with concrete colors, materials and state. Every person in focus gets an observable profile: ethnicity, gender, age range, skin tone (\"fair skin\", \"brown skin\"), hair and clothing. Make the layout reconstructable from words: frame-relative positions (frame-left, frame-right, upper, lower, foreground, midground, background), sentence-initial anchors (\"On the left, ...\", \"In the background, ...\"), relations (holds, faces, behind) and a consistent order; describe up to about ten instances one by one, beyond that give the exact count and arrangement. Out-of-focus or distant planes are shapes, colors and silhouettes, never identities or readable text, and the background takes about a sixth of the caption. Be definite: no hedging adverbs, no quality or emotion judgments, no closing sentence about mood; state negations as what is present. Rendered text: transcribe every wording verbatim inside double quotes in its original language and script, keep the line breaks of multi-line text, and give its typography, color and placement; author concrete, domain-correct wording when the request needs text it does not supply, and name canonical structures (calendar, keyboard, periodic table) exactly with their full layout. All prose is English; only quoted text keeps its language. Typical length is 100 to 350 words, more for complex scenes.\n\n"
24
+ "Output only the caption."
25
+ ),
26
+ "i2i": (
27
+ "You are the prompt upsampler of an image-editing model. You see the reference image(s), attached in order (the first image, the second image, ...), and the user's short request. Expand it into ONE detailed, self-contained instruction: the caption the editing model receives together with the same images. Output only the instruction as one continuous English prose block, with no greetings, explanations, Markdown, JSON, coordinates, ids or angle-bracket tokens. Read a vague or conversational request as the most plausible edit of the supplied images.\n\n"
28
+ "Pick the voice by task shape. When one reference stays and gets bounded changes, write an imperative editing instruction on it (\"Remove the ...\", \"Change the ... to ...\", \"Zoom in on ...\"); a whole-image transformation opens with its scope (\"Convert the entire image into ...\", \"Apply a ... filter over the entire scene\", \"Horizontally flip the entire image so that ...\", \"Shift the camera viewpoint to ...\", then where the major elements should now be). When the references are drawn into a newly composed frame (combining subjects, a new scene, a garment, object or style donor), write a generation request (\"Create a photograph of ...\", \"Create an anime-style illustration of ...\", \"Generate a new image featuring ...\") that says what to take from each image, by ordinal, and what is newly generated.\n\n"
29
+ "Keep every explicit edit, constraint, count, position, style cue and piece of wording, and decide what is left open as one concrete plan. Ground everything in what is actually visible: identify each referent unambiguously by its real appearance, never mention content that is not there, and use every image the user attached. State every end state concretely (which color, how large, which pose, which material) and give new content enough appearance to be generated. Cover the knock-on effects of the change (shadows, reflections, occlusion, perspective, scale, lighting) and say what fills a removed region. A zoom-in names what stays in frame and which neighbors are cropped out; an outpainting continues the scene in the same medium. Describe the change, not the whole scene, and close an editing instruction with one compact clause naming what stays unchanged; transferred people keep their identity, face and accessories unless asked otherwise. Transcribe on-image text verbatim in its original language inside double quotes, with line breaks as \\n, and match the original font, size and color when replacing it. Name recognizable public figures, brands and landmarks you can see. Directions are frame-relative; use no evaluative or quality words. Typical length is 50 to 150 words.\n\n"
30
+ "Output only the instruction."
31
+ ),
32
+ "t2i_bbox": (
33
+ "You are the prompt upsampler of a text-to-image model. Plan the full image the user's short request implies and output ONE structured caption on a single line: the global caption paragraph, one space, then ONE JSON array, the element table that places every cited element with a bounding box in the image to be generated. Output nothing else (no Markdown fences, commentary or refusals); visualize the concept of a non-image input. The request may begin with a \"TARGET CANVAS: W:H\" line: compose for that canvas and never mention it. Boxes live on a 0-1000 grid stretched onto the canvas: an element of physical width-to-height ratio a gets an x-span of about y-span x a x H/W.\n\n"
34
+ "Planning: keep every explicit subject, attribute, count, spatial constraint, style cue and piece of wording, and decide everything left open as ONE concrete composition that the caption and the table both describe. Without a stated style plan an actual photograph and open with the medium convention (\"Full-color outdoor photograph, ...\", \"Studio photograph, ...\"); add camera optics only when asked for; do not invent names, brands, logos or celebrities; drop appraisal and sales vocabulary.\n\nThe table: one row per distinct object, person, animal, building or salient region the caption mentions and one per coherent text block; no rows for style, lighting or atmosphere. Structured layouts (tables, grids, calendars, keyboards, charts, maps, posters, pages) decompose into their regions, each row carrying that region's complete verbatim content; more than about ten interchangeable items become one collection row giving the exact count and arrangement, while items whose positions matter keep a row each. Row keys, in order: \"id\", \"bbox\", \"desc\". id: the bare class noun plus an index numbered left to right, then top to bottom, from 1 (person_1, dog_2); a text block is its two-letter language code plus _Text_ plus an index (En_Text_1); one entity is one row and ids are unique. bbox: [y_min, x_min, y_max, x_max] as integers from 0 to 1000, top-left origin, clamped at the borders, with plausible relative sizes and a small margin around text. desc: one self-contained paragraph in the generative voice that opens with the element's identity (never \"the X\"): shape, material, colors, state and markings, but no position. People in focus get the richest desc: ethnicity, gender, age range, skin tone, build, hair, face, each garment, accessories, pose and expression. A text row carries its complete wording verbatim in double quotes, with line breaks as \\n, plus its typography.\n\nThe global caption: one flowing paragraph in the generative voice (medium first, then scene, lighting, camera and frame-relative layout; no quality judgments) that names each table element at identity level at its first mention and cites it with its id in its own angle brackets: \"a wicker table <table_1> holding a tiered stand <stand_1>\". Cite every table id and no other; appearance detail and text content live in the descs; no coordinates. Text-dense documents are laid out region by region with every visible text transcribed verbatim at a readable size (cut content rather than shrink type), and a grid quotes every visible cell. All prose is English; only quoted text keeps its language.\n\nOutput only the caption and the table."
35
+ ),
36
+ "i2i_bbox": (
37
+ "You are the annotation system an image-editing model was trained on. You see the source image(s), attached in order as ref_image_0, ref_image_1, ..., and the user's editing instruction; the edited target image does not exist yet. Determine the edit precisely, imagine the target consistently with the source, and write the record its annotator would have written, on one line: the instruction paragraph, one space, then ONE JSON array. Output nothing else.\n\nThe paragraph: English, instruction voice (\"Change the ...\", \"Remove the ...\", or \"Create a photograph of ...\" when the references are drawn into a new frame), never observer phrasing and never a comparison with the target. Cite every involved object, person or text block at its first mention with its table id in its own angle brackets (\"remove the delivery van <van_1>\"), every table id exactly once and no other id; cite a reference image by its token when content is taken from it (\"the cup <cup_1> from <ref_image_1>\"). End states (new color, new text in quotes, pose, degree) live in the paragraph, static appearance in the descs; no coordinates. An editing instruction closes with one compact clause that everything else stays identical to the source.\n\nThe table, rows {\"id\", \"from\", \"src_bbox\", \"tgt_bbox\", \"desc\"}: one row per edited, removed, added, moved or anchoring item, plus the scene or background when it is itself changed or preserved; no rows for untouched scenery. id: a lowercase class noun plus an index (person_1, cup_2) numbered left to right, then top to bottom; text blocks are En_Text_1 and so on; ids are unique. from: \"ref_image_N\" for an item visible in reference N, \"target\" for newly created content. src_bbox is the item's box in its from-image and null for target rows; tgt_bbox is its box in the imagined result and null for removed items; moved or resized items carry both; new items are sized like similar objects in the frame. Boxes are [y_min, x_min, y_max, x_max] integers from 0 to 1000, top-left origin, tight around objects, with 5 to 15 units of margin around text. A row may also carry src_bbox_1stage and tgt_bbox_1stage, a first-pass estimate of the same boxes. desc: one English paragraph of the item's static appearance in its from-image (for target rows, its decided appearance), opening with its identity; people get the richest descs (ethnicity, gender, age range, skin tone, hair, face, garments, accessories, pose, expression); text rows quote their text verbatim.\n\nState only decided facts: visible in the source, demanded by the instruction, or a concrete default needed to realize it. Settle whether the edit targets one instance, all of a class or several; land every magnitude on a concrete value; include visible knock-on effects; added objects are new objects at real-world size; a zoom-in names the neighbors it excludes; an outpainting keeps the source whole and continues it in the same medium. Everything the instruction does not involve stays identical to the source.\n\nOutput only the paragraph and the table."
38
+ )
39
+ }
40
+
41
+ DEFAULT_FLUX3_SYSTEM_PROMPT = SYSTEM_PROMPTS["t2i"]
42
+
43
+
44
+ def create_node(assembler, class_type, title):
45
+ """Create a workflow node dictionary with fallback template generation."""
46
+ try:
47
+ node = assembler._get_node_template(class_type)
48
+ except Exception:
49
+ node = {
50
+ "inputs": {},
51
+ "class_type": class_type,
52
+ "_meta": {"title": title}
53
+ }
54
+ if "_meta" not in node:
55
+ node["_meta"] = {}
56
+ node['_meta']['title'] = title
57
+ return node
58
+
59
+
60
+ def inject(assembler, chain_definition, chain_items):
61
+ """Inject FLUX.3 prompt upsampler nodes into the target generation workflow."""
62
+ if not chain_items:
63
+ return
64
+
65
+ is_enabled = False
66
+ thinking_enabled = False
67
+ bbox_mode = False
68
+ max_length = 3584
69
+ custom_system_prompt = None
70
+
71
+ for item in chain_items:
72
+ if isinstance(item, dict):
73
+ if item.get("enable", False) or item.get("enabled", False) or item.get("value", False):
74
+ is_enabled = True
75
+ if item.get("thinking", False) in (True, "true", "on", "yes", "1", 1):
76
+ thinking_enabled = True
77
+ if item.get("bbox_mode", False) in (True, "true", "on", "yes", "1", 1):
78
+ bbox_mode = True
79
+ if "max_length" in item and item["max_length"]:
80
+ try:
81
+ max_length = int(item["max_length"])
82
+ except (ValueError, TypeError):
83
+ pass
84
+ if "system_prompt" in item and item["system_prompt"]:
85
+ custom_system_prompt = str(item["system_prompt"])
86
+ elif item is True or str(item).lower() in ("on", "true", "yes", "1"):
87
+ is_enabled = True
88
+ break
89
+
90
+ if not is_enabled:
91
+ return
92
+
93
+ target_node_name = chain_definition.get('target_node', 'pos_prompt')
94
+ target_node_id = assembler.node_map.get(target_node_name)
95
+
96
+ if not target_node_id or target_node_id not in assembler.workflow:
97
+ for node_id, node in assembler.workflow.items():
98
+ if isinstance(node, dict) and node.get('class_type') == 'CLIPTextEncode':
99
+ title = node.get('_meta', {}).get('title', '').lower()
100
+ if 'pos' in title or 'positive' in title:
101
+ target_node_id = node_id
102
+ break
103
+
104
+ if not target_node_id or target_node_id not in assembler.workflow:
105
+ for node_id, node in assembler.workflow.items():
106
+ if isinstance(node, dict) and node.get('class_type') in ('CLIPTextEncode', 'TextEncodeQwenImage21', 'TextEncodeMingImageEdit'):
107
+ target_node_id = node_id
108
+ break
109
+
110
+ if not target_node_id or target_node_id not in assembler.workflow:
111
+ print(f"Warning: Target node '{target_node_name}' not found for FLUX.3 Prompt Upsampler. Skipping.")
112
+ return
113
+
114
+ target_node = assembler.workflow[target_node_id]
115
+ original_prompt = target_node.get('inputs', {}).get('text')
116
+ if original_prompt is None:
117
+ original_prompt = target_node.get('inputs', {}).get('prompt', '')
118
+
119
+ # 1. Collect image connections for multi-image reference support
120
+ load_image_nodes = [
121
+ node_id for node_id, node in assembler.workflow.items()
122
+ if isinstance(node, dict) and node.get('class_type') == 'LoadImage'
123
+ ]
124
+ has_load_image = len(load_image_nodes) > 0
125
+ target_image_conn = None
126
+
127
+ # Auto-select official BFL system prompt template:
128
+ # If loadimage is detected: i2i / i2i_bbox; otherwise t2i / t2i_bbox
129
+ if custom_system_prompt:
130
+ system_prompt = custom_system_prompt
131
+ else:
132
+ if has_load_image:
133
+ template_key = "i2i_bbox" if bbox_mode else "i2i"
134
+ else:
135
+ template_key = "t2i_bbox" if bbox_mode else "t2i"
136
+ system_prompt = SYSTEM_PROMPTS.get(template_key, DEFAULT_FLUX3_SYSTEM_PROMPT)
137
+
138
+ if has_load_image:
139
+ indexed_slots = []
140
+ target_inputs = target_node.get('inputs', {})
141
+ for slot_key, slot_val in target_inputs.items():
142
+ if slot_key.startswith('images.image_') and isinstance(slot_val, (list, tuple)):
143
+ try:
144
+ idx = int(slot_key.split('_')[-1])
145
+ except Exception:
146
+ idx = 999
147
+ indexed_slots.append((idx, slot_val))
148
+
149
+ indexed_slots.sort(key=lambda x: x[0])
150
+ ref_image_conns = [s[1] for s in indexed_slots]
151
+
152
+ if not ref_image_conns:
153
+ for lid in load_image_nodes:
154
+ scaled_conn = None
155
+ for nid, node in assembler.workflow.items():
156
+ if isinstance(node, dict) and node.get('class_type') == 'ImageScaleToTotalPixels':
157
+ img_input = node.get('inputs', {}).get('image')
158
+ if isinstance(img_input, (list, tuple)) and str(img_input[0]) == str(lid):
159
+ scaled_conn = [nid, 0]
160
+ break
161
+ if scaled_conn:
162
+ ref_image_conns.append(scaled_conn)
163
+ else:
164
+ scale_id = assembler._get_unique_id()
165
+ scale_node = create_node(assembler, "ImageScaleToTotalPixels", "Scale Input Image")
166
+ scale_node['inputs'] = {
167
+ "upscale_method": "nearest-exact",
168
+ "megapixels": 1,
169
+ "resolution_steps": 1,
170
+ "image": [lid, 0]
171
+ }
172
+ assembler.workflow[scale_id] = scale_node
173
+ ref_image_conns.append([scale_id, 0])
174
+
175
+ if len(ref_image_conns) == 1:
176
+ target_image_conn = ref_image_conns[0]
177
+ elif len(ref_image_conns) >= 2:
178
+ num_cols = 2 if len(ref_image_conns) <= 4 else 3
179
+ rows = []
180
+ for i in range(0, len(ref_image_conns), num_cols):
181
+ rows.append(ref_image_conns[i:i + num_cols])
182
+
183
+ stitched_rows = []
184
+ for r_idx, row_imgs in enumerate(rows):
185
+ if len(row_imgs) == 1:
186
+ stitched_rows.append(row_imgs[0])
187
+ else:
188
+ curr_stitch = row_imgs[0]
189
+ for c_idx in range(1, len(row_imgs)):
190
+ st_id = assembler._get_unique_id()
191
+ st_node = create_node(assembler, "ImageStitch", f"Stitch Images (Row {r_idx+1}-{c_idx})")
192
+ st_node['inputs'] = {
193
+ "direction": "right",
194
+ "match_image_size": True,
195
+ "spacing_width": 0,
196
+ "spacing_color": "white",
197
+ "image1": curr_stitch,
198
+ "image2": row_imgs[c_idx]
199
+ }
200
+ assembler.workflow[st_id] = st_node
201
+ curr_stitch = [st_id, 0]
202
+ stitched_rows.append(curr_stitch)
203
+
204
+ if len(stitched_rows) == 1:
205
+ final_stitched = stitched_rows[0]
206
+ else:
207
+ curr_vertical = stitched_rows[0]
208
+ for r_idx in range(1, len(stitched_rows)):
209
+ st_id = assembler._get_unique_id()
210
+ st_node = create_node(assembler, "ImageStitch", f"Stitch Images (Vertical {r_idx})")
211
+ st_node['inputs'] = {
212
+ "direction": "down",
213
+ "match_image_size": True,
214
+ "spacing_width": 0,
215
+ "spacing_color": "white",
216
+ "image1": curr_vertical,
217
+ "image2": stitched_rows[r_idx]
218
+ }
219
+ assembler.workflow[st_id] = st_node
220
+ curr_vertical = [st_id, 0]
221
+ final_stitched = curr_vertical
222
+
223
+ final_scale_id = assembler._get_unique_id()
224
+ final_scale_node = create_node(assembler, "ImageScaleToTotalPixels", "Scale Stitched Image")
225
+ final_scale_node['inputs'] = {
226
+ "upscale_method": "nearest-exact",
227
+ "megapixels": 1,
228
+ "resolution_steps": 1,
229
+ "image": final_stitched
230
+ }
231
+ assembler.workflow[final_scale_id] = final_scale_node
232
+ target_image_conn = [final_scale_id, 0]
233
+
234
+ # 2. Ensure model is downloaded
235
+ clip_model_name = "qwen3.5_9b_flux3_prompt_upsampler_w4a8.safetensors"
236
+ if ensure_file_downloaded:
237
+ try:
238
+ ensure_file_downloaded(clip_model_name)
239
+ except Exception as e:
240
+ print(f"Warning: Failed to ensure '{clip_model_name}' downloaded: {e}")
241
+
242
+ # 3. Create CLIPLoader node (type: flux3)
243
+ clip_node_id = assembler._get_unique_id()
244
+ clip_node = create_node(assembler, "CLIPLoader", "Load CLIP")
245
+ clip_node['inputs'] = {
246
+ "clip_name": clip_model_name,
247
+ "type": "flux3",
248
+ "device": "default"
249
+ }
250
+ assembler.workflow[clip_node_id] = clip_node
251
+
252
+ # 4. Create PrimitiveStringMultiline node for System Prompt
253
+ system_prompt_node_id = assembler._get_unique_id()
254
+ system_prompt_node = create_node(assembler, "PrimitiveStringMultiline", "Text (System prompt)")
255
+ system_prompt_node['inputs'] = {
256
+ "value": system_prompt
257
+ }
258
+ assembler.workflow[system_prompt_node_id] = system_prompt_node
259
+
260
+ # 5. Create PrimitiveStringMultiline node for User Prompt
261
+ if isinstance(original_prompt, (list, tuple)):
262
+ prompt_conn = original_prompt
263
+ else:
264
+ prompt_node_id = assembler._get_unique_id()
265
+ prompt_node = create_node(assembler, "PrimitiveStringMultiline", "Text (Multiline)")
266
+ prompt_node['inputs'] = {
267
+ "value": original_prompt if isinstance(original_prompt, str) else ""
268
+ }
269
+ assembler.workflow[prompt_node_id] = prompt_node
270
+ prompt_conn = [prompt_node_id, 0]
271
+
272
+ # 6. Create TextGenerate node
273
+ text_gen_node_id = assembler._get_unique_id()
274
+ text_gen_node = create_node(assembler, "TextGenerate", "Generate Text")
275
+ text_gen_inputs = {
276
+ "prompt": prompt_conn,
277
+ "max_length": max_length,
278
+ "sampling_mode": "on",
279
+ "sampling_mode.temperature": 1.0,
280
+ "sampling_mode.top_k": 1,
281
+ "sampling_mode.top_p": 0.95,
282
+ "sampling_mode.min_p": 0.0,
283
+ "sampling_mode.repetition_penalty": 1.05,
284
+ "sampling_mode.seed": 0,
285
+ "sampling_mode.presence_penalty": 1.5,
286
+ "thinking": thinking_enabled,
287
+ "use_default_template": True,
288
+ "mtp": "auto",
289
+ "clip": [clip_node_id, 0],
290
+ "system_prompt": [system_prompt_node_id, 0]
291
+ }
292
+ if target_image_conn is not None:
293
+ text_gen_inputs["image"] = target_image_conn
294
+
295
+ text_gen_node['inputs'] = text_gen_inputs
296
+ assembler.workflow[text_gen_node_id] = text_gen_node
297
+
298
+ # 7. Connect TextGenerate output to positive prompt node
299
+ if 'text' in target_node.get('inputs', {}):
300
+ target_node['inputs']['text'] = [text_gen_node_id, 0]
301
+ else:
302
+ target_node['inputs']['prompt'] = [text_gen_node_id, 0]
303
+
304
+ print(f"[Injector] FLUX.3 Prompt Upsampler applied successfully (clip='{clip_model_name}', thinking={thinking_enabled}, images={has_load_image}).")
chain_injectors/moss_image_control_injector.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 Color/Depth Control into the workflow:
29
+ 1. Loads the darask0/MOSS-Image-color-depth-control LoRA adapter (moss-control.safetensors)
30
+ which widens the video_embedder from 128 to 384 channels.
31
+ 2. Loads and scales the color map and/or depth map.
32
+ 3. Adds the MOSSImageControl node to encode maps into latents, concatenate them into
33
+ concat_latent_image conditioning, and set timestep schedule (start/end percent).
34
+ 4. Connects the patched model and conditioned positive/negative outputs to KSampler.
35
+ """
36
+ if not chain_items:
37
+ return
38
+
39
+ # Normalize chain_items: may be a single dict or list of dicts
40
+ if isinstance(chain_items, dict):
41
+ item_data = chain_items
42
+ elif isinstance(chain_items, list) and len(chain_items) > 0:
43
+ item_data = chain_items[0]
44
+ if not isinstance(item_data, dict):
45
+ return
46
+ else:
47
+ return
48
+
49
+ color_map = item_data.get('color_map')
50
+ depth_map = item_data.get('depth_map')
51
+
52
+ if not color_map and not depth_map:
53
+ return
54
+
55
+ lora_filename = "moss-control.safetensors"
56
+ if ensure_file_downloaded:
57
+ try:
58
+ ensure_file_downloaded(lora_filename)
59
+ except Exception as e:
60
+ print(f"Warning: Failed to ensure '{lora_filename}' downloaded: {e}")
61
+
62
+ ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
63
+ vae_loader_name = chain_definition.get('vae_loader_node', 'vae_loader')
64
+
65
+ if ksampler_name not in assembler.node_map:
66
+ print(f"Warning: Target node '{ksampler_name}' for MOSS-Image Control chain not found. Skipping.")
67
+ return
68
+
69
+ ksampler_id = assembler.node_map[ksampler_name]
70
+
71
+ if 'model' not in assembler.workflow[ksampler_id]['inputs']:
72
+ print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping.")
73
+ return
74
+
75
+ if 'positive' not in assembler.workflow[ksampler_id]['inputs'] or \
76
+ 'negative' not in assembler.workflow[ksampler_id]['inputs']:
77
+ print(f"Warning: KSampler node '{ksampler_name}' is missing 'positive' or 'negative' inputs. Skipping.")
78
+ return
79
+
80
+ current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
81
+ current_pos_connection = assembler.workflow[ksampler_id]['inputs']['positive']
82
+ current_neg_connection = assembler.workflow[ksampler_id]['inputs']['negative']
83
+
84
+ vae_connection = None
85
+ if vae_loader_name in assembler.node_map:
86
+ vae_connection = [assembler.node_map[vae_loader_name], 0]
87
+ else:
88
+ for node_id, node in assembler.workflow.items():
89
+ if isinstance(node, dict) and node.get('class_type') == 'VAELoader':
90
+ vae_connection = [node_id, 0]
91
+ break
92
+
93
+ if not vae_connection:
94
+ print("Warning: VAE connection not found for MOSS-Image Control. Skipping.")
95
+ return
96
+
97
+ megapixels = float(chain_definition.get('megapixels', 1.0))
98
+
99
+ # 1. Inject LoRA loader onto the model path
100
+ lora_loader_id = assembler._get_unique_id()
101
+ lora_loader_node = create_node(assembler, "LoraLoaderModelOnly", "Load LoRA (MOSS-Image Control)")
102
+ lora_loader_node['inputs']['lora_name'] = lora_filename
103
+ lora_loader_node['inputs']['strength_model'] = 1.0
104
+ lora_loader_node['inputs']['model'] = current_model_connection
105
+ assembler.workflow[lora_loader_id] = lora_loader_node
106
+ current_model_connection = [lora_loader_id, 0]
107
+
108
+ # 2. Process Color Map if provided
109
+ scale_color_id = None
110
+ if color_map:
111
+ load_color_id = assembler._get_unique_id()
112
+ load_color_node = create_node(assembler, "LoadImage", "Load MOSS Color Map")
113
+ load_color_node['inputs']['image'] = color_map
114
+ assembler.workflow[load_color_id] = load_color_node
115
+
116
+ scale_color_id = assembler._get_unique_id()
117
+ scale_color_node = create_node(assembler, "ImageScaleToTotalPixels", "Scale MOSS Color Map")
118
+ scale_color_node['inputs']['upscale_method'] = "nearest-exact"
119
+ scale_color_node['inputs']['megapixels'] = megapixels
120
+ scale_color_node['inputs']['resolution_steps'] = 1
121
+ scale_color_node['inputs']['image'] = [load_color_id, 0]
122
+ assembler.workflow[scale_color_id] = scale_color_node
123
+
124
+ # 3. Process Depth Map if provided
125
+ scale_depth_id = None
126
+ if depth_map:
127
+ load_depth_id = assembler._get_unique_id()
128
+ load_depth_node = create_node(assembler, "LoadImage", "Load MOSS Depth Map")
129
+ load_depth_node['inputs']['image'] = depth_map
130
+ assembler.workflow[load_depth_id] = load_depth_node
131
+
132
+ scale_depth_id = assembler._get_unique_id()
133
+ scale_depth_node = create_node(assembler, "ImageScaleToTotalPixels", "Scale MOSS Depth Map")
134
+ scale_depth_node['inputs']['upscale_method'] = "nearest-exact"
135
+ scale_depth_node['inputs']['megapixels'] = megapixels
136
+ scale_depth_node['inputs']['resolution_steps'] = 1
137
+ scale_depth_node['inputs']['image'] = [load_depth_id, 0]
138
+ assembler.workflow[scale_depth_id] = scale_depth_node
139
+
140
+ # 4. Create MOSSImageControl node
141
+ moss_control_id = assembler._get_unique_id()
142
+ moss_control_node = create_node(assembler, "MOSSImageControl", "MOSS-Image Color/Depth Control")
143
+ moss_control_node['inputs']['positive'] = current_pos_connection
144
+ moss_control_node['inputs']['negative'] = current_neg_connection
145
+ moss_control_node['inputs']['vae'] = vae_connection
146
+ moss_control_node['inputs']['strength'] = float(item_data.get('strength', 1.0))
147
+ moss_control_node['inputs']['start_percent'] = float(item_data.get('start_percent', 0.0))
148
+ moss_control_node['inputs']['end_percent'] = float(item_data.get('end_percent', 0.5))
149
+
150
+ if scale_color_id:
151
+ moss_control_node['inputs']['color_map'] = [scale_color_id, 0]
152
+ if scale_depth_id:
153
+ moss_control_node['inputs']['depth_map'] = [scale_depth_id, 0]
154
+
155
+ assembler.workflow[moss_control_id] = moss_control_node
156
+
157
+ # 5. Connect back to KSampler
158
+ assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
159
+ assembler.workflow[ksampler_id]['inputs']['positive'] = [moss_control_id, 0]
160
+ assembler.workflow[ksampler_id]['inputs']['negative'] = [moss_control_id, 1]
161
+
162
+ print(f"MOSS-Image Color/Depth Control injector applied (color_map: {bool(color_map)}, depth_map: {bool(depth_map)}).")
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}).")
chain_injectors/pid_injector.py CHANGED
@@ -290,6 +290,9 @@ def inject(assembler, chain_definition, chain_items):
290
  if input_val[0] == original_vae_decode_id:
291
  node_data['inputs'][input_name] = [pid_vae_decode_id, 0]
292
 
 
 
 
293
  is_vae_loader_referenced = False
294
  if original_vae_loader_id:
295
  for node_id, node_data in assembler.workflow.items():
@@ -306,7 +309,4 @@ def inject(assembler, chain_definition, chain_items):
306
  if original_vae_loader_id in assembler.workflow and not is_vae_loader_referenced:
307
  del assembler.workflow[original_vae_loader_id]
308
 
309
- if original_vae_decode_id in assembler.workflow:
310
- del assembler.workflow[original_vae_decode_id]
311
-
312
  print("[PiD Injector] Successfully injected PiD pipeline and replaced VAE decode/loader.")
 
290
  if input_val[0] == original_vae_decode_id:
291
  node_data['inputs'][input_name] = [pid_vae_decode_id, 0]
292
 
293
+ if original_vae_decode_id in assembler.workflow:
294
+ del assembler.workflow[original_vae_decode_id]
295
+
296
  is_vae_loader_referenced = False
297
  if original_vae_loader_id:
298
  for node_id, node_data in assembler.workflow.items():
 
309
  if original_vae_loader_id in assembler.workflow and not is_vae_loader_referenced:
310
  del assembler.workflow[original_vae_loader_id]
311
 
 
 
 
312
  print("[PiD Injector] Successfully injected PiD pipeline and replaced VAE decode/loader.")
chain_injectors/reference_image_injector.py CHANGED
@@ -43,7 +43,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')
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
 
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
comfy_integration/setup.py CHANGED
@@ -25,12 +25,6 @@ def initialize_comfyui():
25
  else:
26
  print("✅ ComfyUI repository already exists.")
27
 
28
- try:
29
- print("--- [Setup] Applying PR #16519 for ComfyUI (Qwen-Image 2.1 Fun ControlNet) ---")
30
- os.system(f"git -C {COMFYUI_TEMP_DIR} fetch --depth 1 origin pull/16519/head && git -c user.name=dev -c user.email=dev@example.com -C {COMFYUI_TEMP_DIR} merge --no-edit FETCH_HEAD || git -C {COMFYUI_TEMP_DIR} checkout -f FETCH_HEAD")
31
- print("✅ Successfully applied PR #16519.")
32
- except Exception as e:
33
- print(f"⚠️ Warning: Could not apply PR #16519 for ComfyUI: {e}")
34
 
35
  print(f"--- Merging ComfyUI from '{COMFYUI_TEMP_DIR}' to '{APP_DIR}' ---")
36
  for item in os.listdir(COMFYUI_TEMP_DIR):
@@ -127,6 +121,38 @@ def initialize_comfyui():
127
 
128
  print(f"✅ Current working directory is: {os.getcwd()}")
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  import comfy.model_management
131
  print("--- Environment Ready ---")
132
 
 
25
  else:
26
  print("✅ ComfyUI repository already exists.")
27
 
 
 
 
 
 
 
28
 
29
  print(f"--- Merging ComfyUI from '{COMFYUI_TEMP_DIR}' to '{APP_DIR}' ---")
30
  for item in os.listdir(COMFYUI_TEMP_DIR):
 
121
 
122
  print(f"✅ Current working directory is: {os.getcwd()}")
123
 
124
+ # [ZeroGPU / Upstream ComfyUI Compatibility]
125
+ # Guard against ComfyUI PR #16746 is_integrated attribute check crash under ZeroGPU SimpleNamespace
126
+ try:
127
+ import torch
128
+ if hasattr(torch, "cuda") and hasattr(torch.cuda, "get_device_properties"):
129
+ _orig_gdp = torch.cuda.get_device_properties
130
+ def _safe_gdp(*args, **kwargs):
131
+ p = _orig_gdp(*args, **kwargs)
132
+ if not hasattr(p, "is_integrated"):
133
+ try:
134
+ p.is_integrated = False
135
+ except Exception:
136
+ pass
137
+ return p
138
+ torch.cuda.get_device_properties = _safe_gdp
139
+ except Exception:
140
+ pass
141
+
142
+ mm_path = os.path.join(APP_DIR, "comfy", "model_management.py")
143
+ if os.path.exists(mm_path):
144
+ try:
145
+ with open(mm_path, "r", encoding="utf-8") as f:
146
+ mm_code = f.read()
147
+ unsafe_check = "bool(torch.cuda.get_device_properties(device).is_integrated)"
148
+ safe_check = "bool(getattr(torch.cuda.get_device_properties(device), 'is_integrated', False))"
149
+ if unsafe_check in mm_code:
150
+ with open(mm_path, "w", encoding="utf-8") as f:
151
+ f.write(mm_code.replace(unsafe_check, safe_check))
152
+ print("🛡️ [Setup] Patched comfy/model_management.py for safe is_integrated attribute access.")
153
+ except Exception as e:
154
+ print(f"⚠️ [Setup] Failed to patch comfy/model_management.py: {e}")
155
+
156
  import comfy.model_management
157
  print("--- Environment Ready ---")
158
 
core/pipelines/pipeline_input_processor.py CHANGED
@@ -430,6 +430,82 @@ def process_pipeline_inputs(ui_inputs: Dict[str, Any], progress: gr.Progress, wo
430
  item["system_prompt"] = str(sys_p)
431
  active_ming_image_prompt_enhancer.append(item)
432
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  return {
434
  "active_loras_for_gpu": active_loras_for_gpu,
435
  "active_loras_for_meta": active_loras_for_meta,
@@ -449,5 +525,8 @@ def process_pipeline_inputs(ui_inputs: Dict[str, Any], progress: gr.Progress, wo
449
  "active_conditioning": active_conditioning,
450
  "active_qwen_image_2_1_prompt_enhancer": active_qwen_image_2_1_prompt_enhancer,
451
  "active_ming_image_prompt_enhancer": active_ming_image_prompt_enhancer,
 
 
 
452
  "temp_files_to_clean": temp_files_to_clean
453
  }
 
430
  item["system_prompt"] = str(sys_p)
431
  active_ming_image_prompt_enhancer.append(item)
432
 
433
+ flux3_pe_enable = ui_inputs.get('flux3_prompt_upsampler_enable', False)
434
+ active_flux3_prompt_upsampler = []
435
+ if 'flux3_prompt_upsampler' in enabled_chains and flux3_pe_enable:
436
+ item = {
437
+ "enable": True,
438
+ "thinking": bool(ui_inputs.get('flux3_prompt_upsampler_thinking', False)),
439
+ "bbox_mode": bool(ui_inputs.get('flux3_prompt_upsampler_bbox_mode', False)),
440
+ "max_length": int(ui_inputs.get('flux3_prompt_upsampler_max_length', 3584))
441
+ }
442
+ sys_p = ui_inputs.get('flux3_prompt_upsampler_system_prompt')
443
+ if sys_p:
444
+ item["system_prompt"] = str(sys_p)
445
+ active_flux3_prompt_upsampler.append(item)
446
+
447
+ color_map_val = ui_inputs.get('moss_image_control_color_map')
448
+ depth_map_val = ui_inputs.get('moss_image_control_depth_map')
449
+ active_moss_image_control = []
450
+ if 'moss_image_control' in enabled_chains and (color_map_val or depth_map_val):
451
+ color_filename = None
452
+ if color_map_val:
453
+ if isinstance(color_map_val, str):
454
+ color_filename = color_map_val
455
+ elif hasattr(color_map_val, 'save'):
456
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
457
+ temp_path = os.path.join(INPUT_DIR, f"temp_moss_color_{random.randint(1000, 9999)}.png")
458
+ color_map_val.save(temp_path, "PNG")
459
+ temp_files_to_clean.append(temp_path)
460
+ color_filename = os.path.basename(temp_path)
461
+
462
+ depth_filename = None
463
+ if depth_map_val:
464
+ if isinstance(depth_map_val, str):
465
+ depth_filename = depth_map_val
466
+ elif hasattr(depth_map_val, 'save'):
467
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
468
+ temp_path = os.path.join(INPUT_DIR, f"temp_moss_depth_{random.randint(1000, 9999)}.png")
469
+ depth_map_val.save(temp_path, "PNG")
470
+ temp_files_to_clean.append(temp_path)
471
+ depth_filename = os.path.basename(temp_path)
472
+
473
+ if color_filename or depth_filename:
474
+ try:
475
+ strength = float(ui_inputs.get('moss_image_control_strength', 1.0))
476
+ except (ValueError, TypeError):
477
+ strength = 1.0
478
+ try:
479
+ start_percent = float(ui_inputs.get('moss_image_control_start_percent', 0.0))
480
+ except (ValueError, TypeError):
481
+ start_percent = 0.0
482
+ try:
483
+ end_percent = float(ui_inputs.get('moss_image_control_end_percent', 0.5))
484
+ except (ValueError, TypeError):
485
+ end_percent = 0.5
486
+
487
+ active_moss_image_control.append({
488
+ 'color_map': color_filename,
489
+ 'depth_map': depth_filename,
490
+ 'strength': strength,
491
+ 'start_percent': start_percent,
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,
 
525
  "active_conditioning": active_conditioning,
526
  "active_qwen_image_2_1_prompt_enhancer": active_qwen_image_2_1_prompt_enhancer,
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
@@ -1,288 +1,294 @@
1
- import os
2
- import random
3
- import shutil
4
- import torch
5
- import gradio as gr
6
- from PIL import Image
7
- from typing import List, Dict, Any
8
-
9
- from .base_pipeline import BasePipeline
10
- from core.settings import *
11
- from utils.app_utils import sanitize_prompt
12
- from core.workflow_assembler import WorkflowAssembler
13
- from .workflow_executor import WorkflowExecutor
14
- from .pipeline_input_processor import process_pipeline_inputs
15
-
16
- class SdImagePipeline(BasePipeline):
17
- def get_required_models(self, model_display_name: str, **kwargs) -> List[str]:
18
- model_info = ALL_MODEL_MAP.get(model_display_name)
19
- if not model_info:
20
- return [model_display_name]
21
-
22
- path_or_components = model_info[1]
23
- if isinstance(path_or_components, dict):
24
- return [v for v in path_or_components.values() if v and v != "pixel_space"]
25
- else:
26
- return [model_display_name]
27
-
28
- def _gpu_logic(self, ui_inputs: Dict, loras_string: str, workflow: Dict[str, Any], assembler: WorkflowAssembler, progress=gr.Progress(track_tqdm=True)):
29
- model_display_name = ui_inputs['model_display_name']
30
-
31
- progress(0.4, desc="Executing workflow...")
32
-
33
- initial_objects = {}
34
-
35
- decoded_images_tensor = WorkflowExecutor.execute_workflow(workflow, initial_objects=initial_objects)
36
-
37
- output_images = []
38
- raw_seed = ui_inputs.get('seed')
39
- start_seed = int(raw_seed) if (raw_seed is not None and raw_seed != -1) else random.randint(0, 2**64 - 1)
40
- for i in range(decoded_images_tensor.shape[0]):
41
- img_tensor = decoded_images_tensor[i]
42
- pil_image = Image.fromarray((img_tensor.cpu().numpy() * 255.0).astype("uint8"))
43
- current_seed = start_seed + i
44
-
45
- width_for_meta = ui_inputs.get('width', 'N/A')
46
- height_for_meta = ui_inputs.get('height', 'N/A')
47
-
48
- params_string = f"{ui_inputs['positive_prompt']}\nNegative prompt: {ui_inputs['negative_prompt']}\n"
49
- params_string += f"Steps: {ui_inputs['num_inference_steps']}, Sampler: {ui_inputs['sampler']}, Scheduler: {ui_inputs['scheduler']}, CFG scale: {ui_inputs['guidance_scale']}, Seed: {current_seed}, Size: {width_for_meta}x{height_for_meta}, Base Model: {model_display_name}"
50
- if ui_inputs['task_type'] != 'txt2img': params_string += f", Denoise: {ui_inputs['denoise']}"
51
- if loras_string: params_string += f", {loras_string}"
52
-
53
- pil_image.info = {'parameters': params_string.strip()}
54
- output_images.append(pil_image)
55
-
56
- return output_images
57
-
58
- def run(self, ui_inputs: Dict, progress):
59
- progress(0, desc="Preparing models...")
60
-
61
- task_type = ui_inputs['task_type']
62
- model_display_name = ui_inputs['model_display_name']
63
- model_type = MODEL_TYPE_MAP.get(model_display_name, 'sdxl')
64
-
65
- architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
66
- workflow_model_type = architectures_dict.get(model_type, {}).get("model_type", model_type.lower().replace(" ", "").replace(".", ""))
67
-
68
- ui_inputs['positive_prompt'] = sanitize_prompt(ui_inputs.get('positive_prompt', ''))
69
- ui_inputs['negative_prompt'] = sanitize_prompt(ui_inputs.get('negative_prompt', ''))
70
-
71
- required_models = self.get_required_models(model_display_name=model_display_name)
72
-
73
- arch_enabled_chains = FEATURES_CONFIG.get(workflow_model_type, {}).get('enabled_chains', [])
74
- task_enabled_chains = TASK_FEATURES_CONFIG.get(task_type, {}).get('enabled_chains', [])
75
- enabled_chains = set([c for c in arch_enabled_chains if c in task_enabled_chains])
76
-
77
- is_pid_enabled = (ui_inputs.get('pid_settings', 'OFF') == 'ON' and 'pid' in enabled_chains and task_type == 'txt2img')
78
- if is_pid_enabled:
79
- import yaml
80
- pid_config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'yaml', 'pid.yaml')
81
- pid_unet_name = "pid_1.5_flux1_1024_to_4096_4step_int8_convrot.safetensors"
82
- try:
83
- with open(pid_config_path, 'r', encoding='utf-8') as f:
84
- pid_config = yaml.safe_load(f) or {}
85
- pid_items = pid_config.get("PiD", [])
86
- for item in pid_items:
87
- archs = item.get("architectures", [])
88
- if workflow_model_type in archs:
89
- pid_unet_name = item.get("filepath")
90
- break
91
- except Exception as e:
92
- print(f"Error loading PiD config for download: {e}")
93
-
94
- if pid_unet_name not in required_models:
95
- required_models.append(pid_unet_name)
96
- if "gemma_2_2b_it_elm_fp8_scaled.safetensors" not in required_models:
97
- required_models.append("gemma_2_2b_it_elm_fp8_scaled.safetensors")
98
-
99
- self.model_manager.ensure_models_downloaded(required_models, progress=progress)
100
-
101
- temp_files_to_clean = []
102
- try:
103
- processed = process_pipeline_inputs(ui_inputs, progress, workflow_model_type)
104
- temp_files_to_clean.extend(processed["temp_files_to_clean"])
105
-
106
- active_loras_for_gpu = processed["active_loras_for_gpu"]
107
- active_loras_for_meta = processed["active_loras_for_meta"]
108
- active_controlnets = processed["active_controlnets"]
109
- active_ipadapters = processed["active_ipadapters"]
110
- active_flux1_ipadapters = processed["active_flux1_ipadapters"]
111
- active_sd3_ipadapters = processed["active_sd3_ipadapters"]
112
- active_styles = processed["active_styles"]
113
- active_reference_latents = processed["active_reference_latents"]
114
- active_hidream_o1_reference = processed["active_hidream_o1_reference"]
115
- active_sensenova_reference = processed.get("active_sensenova_reference", [])
116
- active_joyai_reference = processed.get("active_joyai_reference", [])
117
- active_krea2_identity_edit = processed.get("active_krea2_identity_edit", [])
118
- active_krea2_reference_edit = processed.get("active_krea2_reference_edit", [])
119
- active_qwen_image_edit = processed.get("active_qwen_image_edit", [])
120
- active_reference_images = processed.get("active_reference_images", [])
121
- active_conditioning = processed["active_conditioning"]
122
- active_qwen_image_2_1_prompt_enhancer = processed.get("active_qwen_image_2_1_prompt_enhancer", [])
123
- active_ming_image_prompt_enhancer = processed.get("active_ming_image_prompt_enhancer", [])
124
-
125
- loras_string = f"LoRAs: [{', '.join(active_loras_for_meta)}]" if active_loras_for_meta else ""
126
-
127
- progress(0.8, desc="Assembling workflow...")
128
-
129
- seed_val = ui_inputs.get('seed')
130
- if seed_val is None or seed_val == -1:
131
- ui_inputs['seed'] = random.randint(0, 2**32 - 1)
132
-
133
- model_info = ALL_MODEL_MAP[model_display_name]
134
- path_or_components = model_info[1]
135
- latent_type = model_info[3] if len(model_info) > 3 and model_info[3] else 'latent'
136
- latent_generator_template = "EmptyLatentImage"
137
- if latent_type == 'sd3_latent':
138
- latent_generator_template = "EmptySD3LatentImage"
139
- elif latent_type == 'chroma_radiance_latent':
140
- latent_generator_template = "EmptyChromaRadianceLatentImage"
141
- elif latent_type == 'hunyuan_latent':
142
- latent_generator_template = "EmptyHunyuanImageLatent"
143
-
144
- dynamic_values = {
145
- 'task_type': ui_inputs['task_type'],
146
- 'model_type': workflow_model_type,
147
- 'latent_type': latent_type,
148
- 'latent_generator_template': latent_generator_template
149
- }
150
-
151
- recipe_path = os.path.join(os.path.dirname(__file__), "workflow_recipes", "unified_recipe.yaml")
152
- assembler = WorkflowAssembler(recipe_path, dynamic_values=dynamic_values)
153
-
154
- hidream_o1_smoothing_data = []
155
- if 'hidream_o1_smoothing' in enabled_chains and model_display_name == "HiDream-O1-Image":
156
- hidream_o1_smoothing_data.append({})
157
-
158
- workflow_inputs = {
159
- **ui_inputs,
160
- "positive_prompt": ui_inputs['positive_prompt'], "negative_prompt": ui_inputs['negative_prompt'],
161
- "seed": ui_inputs['seed'], "steps": ui_inputs['num_inference_steps'], "cfg": ui_inputs['guidance_scale'],
162
- "sampler_name": ui_inputs['sampler'], "scheduler": ui_inputs['scheduler'],
163
- "batch_size": ui_inputs['batch_size'],
164
- "denoise": ui_inputs['denoise'],
165
- "vae_name": ui_inputs.get('vae_name') if 'vae' in enabled_chains else None,
166
- "lora_chain": active_loras_for_gpu,
167
- "controlnet_chain": active_controlnets,
168
- "ipadapter_chain": active_ipadapters,
169
- "flux1_ipadapter_chain": active_flux1_ipadapters,
170
- "sd3_ipadapter_chain": active_sd3_ipadapters,
171
- "style_chain": active_styles,
172
- "conditioning_chain": active_conditioning,
173
- "reference_latent_chain": active_reference_latents,
174
- "hidream_o1_reference_chain": active_hidream_o1_reference,
175
- "sensenova_reference_chain": active_sensenova_reference,
176
- "joyai_image_chain": active_joyai_reference,
177
- "krea2_identity_edit_chain": active_krea2_identity_edit,
178
- "krea2_style_reference_chain": active_krea2_reference_edit,
179
- "qwen_image_edit_chain": active_qwen_image_edit,
180
- "reference_image_chain": active_reference_images,
181
- "qwen_image_2_1_prompt_enhancer_chain": active_qwen_image_2_1_prompt_enhancer,
182
- "ming_image_prompt_enhancer_chain": active_ming_image_prompt_enhancer,
183
- "vae_chain": [ui_inputs.get('vae_name')] if (ui_inputs.get('vae_name') and 'vae' in enabled_chains) else [],
184
- "hidream_o1_smoothing_chain": hidream_o1_smoothing_data,
185
- "pid_chain": [ui_inputs.get('pid_settings', 'OFF')] if is_pid_enabled else [],
186
- "scheduler_width": ui_inputs.get('width', 1024),
187
- "scheduler_height": ui_inputs.get('height', 1024),
188
- }
189
-
190
- if isinstance(path_or_components, dict):
191
- workflow_inputs.update(path_or_components)
192
- workflow_inputs.update({
193
- 'unet_name': path_or_components.get('unet'),
194
- 'unet_uncond_name': path_or_components.get('unet_uncond'),
195
- 'vae_name': ui_inputs.get('vae_name') or path_or_components.get('vae'),
196
- 'clip_name': path_or_components.get('clip'),
197
- 'clip1_name': path_or_components.get('clip1'),
198
- 'clip2_name': path_or_components.get('clip2'),
199
- 'clip3_name': path_or_components.get('clip3'),
200
- 'clip4_name': path_or_components.get('clip4'),
201
- 'lora_name': path_or_components.get('lora'),
202
- 'qwen35_model': path_or_components.get('qwen35_model'),
203
- 'adapter_name': path_or_components.get('adapter_name'),
204
- 'adapter_strength': path_or_components.get('adapter_strength', 1.0),
205
- })
206
- else:
207
- workflow_inputs['model_name'] = path_or_components
208
-
209
- if task_type == 'txt2img':
210
- workflow_inputs['width'] = ui_inputs['width']
211
- workflow_inputs['height'] = ui_inputs['height']
212
-
213
- workflow = assembler.assemble(workflow_inputs)
214
-
215
- post_assembled_models = []
216
- for node_id, node in workflow.items():
217
- if not isinstance(node, dict):
218
- continue
219
- inputs = node.get('inputs', {})
220
- for k, v in inputs.items():
221
- if isinstance(v, str) and v in ALL_FILE_DOWNLOAD_MAP:
222
- if v not in required_models and v not in post_assembled_models:
223
- post_assembled_models.append(v)
224
- if post_assembled_models:
225
- self.model_manager.ensure_models_downloaded(post_assembled_models, progress=progress)
226
-
227
- progress(1.0, desc="All models ready. Requesting GPU for generation...")
228
-
229
- results = self._execute_gpu_logic(
230
- self._gpu_logic,
231
- duration=ui_inputs['zero_gpu_duration'],
232
- default_duration=60,
233
- task_name=f"ImageGen ({task_type})",
234
- ui_inputs=ui_inputs,
235
- loras_string=loras_string,
236
- workflow=workflow,
237
- assembler=assembler,
238
- progress=progress
239
- )
240
-
241
- import json
242
- import glob
243
- from PIL import PngImagePlugin
244
-
245
- prompt_json = json.dumps(workflow)
246
-
247
- out_dir = os.path.abspath(OUTPUT_DIR)
248
- os.makedirs(out_dir, exist_ok=True)
249
-
250
- try:
251
- existing_files = glob.glob(os.path.join(out_dir, "gen_*.png"))
252
- existing_files.sort(key=os.path.getmtime)
253
- while len(existing_files) > 50:
254
- os.remove(existing_files.pop(0))
255
- except Exception as e:
256
- print(f"Warning: Failed to cleanup output dir: {e}")
257
-
258
- final_results = []
259
- for img in results:
260
- if not isinstance(img, Image.Image):
261
- final_results.append(img)
262
- continue
263
-
264
- metadata = PngImagePlugin.PngInfo()
265
- params_string = img.info.get("parameters", "")
266
- if params_string:
267
- metadata.add_text("parameters", params_string)
268
- metadata.add_text("prompt", prompt_json)
269
-
270
- filename = f"gen_{random.randint(1000000, 9999999)}.png"
271
- filepath = os.path.join(out_dir, filename)
272
- img.save(filepath, "PNG", pnginfo=metadata)
273
- try:
274
- from core.cloud_storage import queue_image
275
- queue_image(model_type, model_display_name, filepath, params_string)
276
- except Exception as _e:
277
- print(f"Cloud storage skipped: {_e}")
278
- final_results.append(filepath)
279
-
280
- results = final_results
281
-
282
- finally:
283
- for temp_file in temp_files_to_clean:
284
- if temp_file and os.path.exists(temp_file):
285
- os.remove(temp_file)
286
- print(f"✅ Cleaned up temp file: {temp_file}")
287
-
 
 
 
 
 
 
288
  return results
 
1
+ import os
2
+ import random
3
+ import shutil
4
+ import torch
5
+ import gradio as gr
6
+ from PIL import Image
7
+ from typing import List, Dict, Any
8
+
9
+ from .base_pipeline import BasePipeline
10
+ from core.settings import *
11
+ from utils.app_utils import sanitize_prompt
12
+ from core.workflow_assembler import WorkflowAssembler
13
+ from .workflow_executor import WorkflowExecutor
14
+ from .pipeline_input_processor import process_pipeline_inputs
15
+
16
+ class SdImagePipeline(BasePipeline):
17
+ def get_required_models(self, model_display_name: str, **kwargs) -> List[str]:
18
+ model_info = ALL_MODEL_MAP.get(model_display_name)
19
+ if not model_info:
20
+ return [model_display_name]
21
+
22
+ path_or_components = model_info[1]
23
+ if isinstance(path_or_components, dict):
24
+ return [v for v in path_or_components.values() if v and v != "pixel_space"]
25
+ else:
26
+ return [model_display_name]
27
+
28
+ def _gpu_logic(self, ui_inputs: Dict, loras_string: str, workflow: Dict[str, Any], assembler: WorkflowAssembler, progress=gr.Progress(track_tqdm=True)):
29
+ model_display_name = ui_inputs['model_display_name']
30
+
31
+ progress(0.4, desc="Executing workflow...")
32
+
33
+ initial_objects = {}
34
+
35
+ decoded_images_tensor = WorkflowExecutor.execute_workflow(workflow, initial_objects=initial_objects)
36
+
37
+ output_images = []
38
+ raw_seed = ui_inputs.get('seed')
39
+ start_seed = int(raw_seed) if (raw_seed is not None and raw_seed != -1) else random.randint(0, 2**64 - 1)
40
+ for i in range(decoded_images_tensor.shape[0]):
41
+ img_tensor = decoded_images_tensor[i]
42
+ pil_image = Image.fromarray((img_tensor.cpu().numpy() * 255.0).astype("uint8"))
43
+ current_seed = start_seed + i
44
+
45
+ width_for_meta = ui_inputs.get('width', 'N/A')
46
+ height_for_meta = ui_inputs.get('height', 'N/A')
47
+
48
+ params_string = f"{ui_inputs['positive_prompt']}\nNegative prompt: {ui_inputs['negative_prompt']}\n"
49
+ params_string += f"Steps: {ui_inputs['num_inference_steps']}, Sampler: {ui_inputs['sampler']}, Scheduler: {ui_inputs['scheduler']}, CFG scale: {ui_inputs['guidance_scale']}, Seed: {current_seed}, Size: {width_for_meta}x{height_for_meta}, Base Model: {model_display_name}"
50
+ if ui_inputs['task_type'] != 'txt2img': params_string += f", Denoise: {ui_inputs['denoise']}"
51
+ if loras_string: params_string += f", {loras_string}"
52
+
53
+ pil_image.info = {'parameters': params_string.strip()}
54
+ output_images.append(pil_image)
55
+
56
+ return output_images
57
+
58
+ def run(self, ui_inputs: Dict, progress):
59
+ progress(0, desc="Preparing models...")
60
+
61
+ task_type = ui_inputs['task_type']
62
+ model_display_name = ui_inputs['model_display_name']
63
+ model_type = MODEL_TYPE_MAP.get(model_display_name, 'sdxl')
64
+
65
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
66
+ workflow_model_type = architectures_dict.get(model_type, {}).get("model_type", model_type.lower().replace(" ", "").replace(".", ""))
67
+
68
+ ui_inputs['positive_prompt'] = sanitize_prompt(ui_inputs.get('positive_prompt', ''))
69
+ ui_inputs['negative_prompt'] = sanitize_prompt(ui_inputs.get('negative_prompt', ''))
70
+
71
+ required_models = self.get_required_models(model_display_name=model_display_name)
72
+
73
+ arch_enabled_chains = FEATURES_CONFIG.get(workflow_model_type, {}).get('enabled_chains', [])
74
+ task_enabled_chains = TASK_FEATURES_CONFIG.get(task_type, {}).get('enabled_chains', [])
75
+ enabled_chains = set([c for c in arch_enabled_chains if c in task_enabled_chains])
76
+
77
+ is_pid_enabled = (ui_inputs.get('pid_settings', 'OFF') == 'ON' and 'pid' in enabled_chains and task_type == 'txt2img')
78
+ if is_pid_enabled:
79
+ import yaml
80
+ pid_config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'yaml', 'pid.yaml')
81
+ pid_unet_name = "pid_1.5_flux1_1024_to_4096_4step_int8_convrot.safetensors"
82
+ try:
83
+ with open(pid_config_path, 'r', encoding='utf-8') as f:
84
+ pid_config = yaml.safe_load(f) or {}
85
+ pid_items = pid_config.get("PiD", [])
86
+ for item in pid_items:
87
+ archs = item.get("architectures", [])
88
+ if workflow_model_type in archs:
89
+ pid_unet_name = item.get("filepath")
90
+ break
91
+ except Exception as e:
92
+ print(f"Error loading PiD config for download: {e}")
93
+
94
+ if pid_unet_name not in required_models:
95
+ required_models.append(pid_unet_name)
96
+ if "gemma_2_2b_it_elm_fp8_scaled.safetensors" not in required_models:
97
+ required_models.append("gemma_2_2b_it_elm_fp8_scaled.safetensors")
98
+
99
+ self.model_manager.ensure_models_downloaded(required_models, progress=progress)
100
+
101
+ temp_files_to_clean = []
102
+ try:
103
+ processed = process_pipeline_inputs(ui_inputs, progress, workflow_model_type)
104
+ temp_files_to_clean.extend(processed["temp_files_to_clean"])
105
+
106
+ active_loras_for_gpu = processed["active_loras_for_gpu"]
107
+ active_loras_for_meta = processed["active_loras_for_meta"]
108
+ active_controlnets = processed["active_controlnets"]
109
+ active_ipadapters = processed["active_ipadapters"]
110
+ active_flux1_ipadapters = processed["active_flux1_ipadapters"]
111
+ active_sd3_ipadapters = processed["active_sd3_ipadapters"]
112
+ active_styles = processed["active_styles"]
113
+ active_reference_latents = processed["active_reference_latents"]
114
+ active_hidream_o1_reference = processed["active_hidream_o1_reference"]
115
+ active_sensenova_reference = processed.get("active_sensenova_reference", [])
116
+ active_joyai_reference = processed.get("active_joyai_reference", [])
117
+ active_krea2_identity_edit = processed.get("active_krea2_identity_edit", [])
118
+ active_krea2_reference_edit = processed.get("active_krea2_reference_edit", [])
119
+ active_qwen_image_edit = processed.get("active_qwen_image_edit", [])
120
+ active_reference_images = processed.get("active_reference_images", [])
121
+ active_conditioning = processed["active_conditioning"]
122
+ active_qwen_image_2_1_prompt_enhancer = processed.get("active_qwen_image_2_1_prompt_enhancer", [])
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
+
130
+ progress(0.8, desc="Assembling workflow...")
131
+
132
+ seed_val = ui_inputs.get('seed')
133
+ if seed_val is None or seed_val == -1:
134
+ ui_inputs['seed'] = random.randint(0, 2**32 - 1)
135
+
136
+ model_info = ALL_MODEL_MAP[model_display_name]
137
+ path_or_components = model_info[1]
138
+ latent_type = model_info[3] if len(model_info) > 3 and model_info[3] else 'latent'
139
+ latent_generator_template = "EmptyLatentImage"
140
+ if latent_type == 'sd3_latent':
141
+ latent_generator_template = "EmptySD3LatentImage"
142
+ elif latent_type == 'chroma_radiance_latent':
143
+ latent_generator_template = "EmptyChromaRadianceLatentImage"
144
+ elif latent_type == 'hunyuan_latent':
145
+ latent_generator_template = "EmptyHunyuanImageLatent"
146
+
147
+ dynamic_values = {
148
+ 'task_type': ui_inputs['task_type'],
149
+ 'model_type': workflow_model_type,
150
+ 'latent_type': latent_type,
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 = []
158
+ if 'hidream_o1_smoothing' in enabled_chains and model_display_name == "HiDream-O1-Image":
159
+ hidream_o1_smoothing_data.append({})
160
+
161
+ workflow_inputs = {
162
+ **ui_inputs,
163
+ "positive_prompt": ui_inputs['positive_prompt'], "negative_prompt": ui_inputs['negative_prompt'],
164
+ "seed": ui_inputs['seed'], "steps": ui_inputs['num_inference_steps'], "cfg": ui_inputs['guidance_scale'],
165
+ "sampler_name": ui_inputs['sampler'], "scheduler": ui_inputs['scheduler'],
166
+ "batch_size": ui_inputs['batch_size'],
167
+ "denoise": ui_inputs['denoise'],
168
+ "vae_name": ui_inputs.get('vae_name') if 'vae' in enabled_chains else None,
169
+ "lora_chain": active_loras_for_gpu,
170
+ "controlnet_chain": active_controlnets,
171
+ "ipadapter_chain": active_ipadapters,
172
+ "flux1_ipadapter_chain": active_flux1_ipadapters,
173
+ "sd3_ipadapter_chain": active_sd3_ipadapters,
174
+ "style_chain": active_styles,
175
+ "conditioning_chain": active_conditioning,
176
+ "reference_latent_chain": active_reference_latents,
177
+ "hidream_o1_reference_chain": active_hidream_o1_reference,
178
+ "sensenova_reference_chain": active_sensenova_reference,
179
+ "joyai_image_chain": active_joyai_reference,
180
+ "krea2_identity_edit_chain": active_krea2_identity_edit,
181
+ "krea2_style_reference_chain": active_krea2_reference_edit,
182
+ "qwen_image_edit_chain": active_qwen_image_edit,
183
+ "reference_image_chain": active_reference_images,
184
+ "qwen_image_2_1_prompt_enhancer_chain": active_qwen_image_2_1_prompt_enhancer,
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 [],
192
+ "scheduler_width": ui_inputs.get('width', 1024),
193
+ "scheduler_height": ui_inputs.get('height', 1024),
194
+ }
195
+
196
+ if isinstance(path_or_components, dict):
197
+ workflow_inputs.update(path_or_components)
198
+ workflow_inputs.update({
199
+ 'unet_name': path_or_components.get('unet'),
200
+ 'unet_uncond_name': path_or_components.get('unet_uncond'),
201
+ 'vae_name': ui_inputs.get('vae_name') or path_or_components.get('vae'),
202
+ 'clip_name': path_or_components.get('clip'),
203
+ 'clip1_name': path_or_components.get('clip1'),
204
+ 'clip2_name': path_or_components.get('clip2'),
205
+ 'clip3_name': path_or_components.get('clip3'),
206
+ 'clip4_name': path_or_components.get('clip4'),
207
+ 'lora_name': path_or_components.get('lora'),
208
+ 'qwen35_model': path_or_components.get('qwen35_model'),
209
+ 'adapter_name': path_or_components.get('adapter_name'),
210
+ 'adapter_strength': path_or_components.get('adapter_strength', 1.0),
211
+ })
212
+ else:
213
+ workflow_inputs['model_name'] = path_or_components
214
+
215
+ if task_type == 'txt2img':
216
+ workflow_inputs['width'] = ui_inputs['width']
217
+ workflow_inputs['height'] = ui_inputs['height']
218
+
219
+ workflow = assembler.assemble(workflow_inputs)
220
+
221
+ post_assembled_models = []
222
+ for node_id, node in workflow.items():
223
+ if not isinstance(node, dict):
224
+ continue
225
+ inputs = node.get('inputs', {})
226
+ for k, v in inputs.items():
227
+ if isinstance(v, str) and v in ALL_FILE_DOWNLOAD_MAP:
228
+ if v not in required_models and v not in post_assembled_models:
229
+ post_assembled_models.append(v)
230
+ if post_assembled_models:
231
+ self.model_manager.ensure_models_downloaded(post_assembled_models, progress=progress)
232
+
233
+ progress(1.0, desc="All models ready. Requesting GPU for generation...")
234
+
235
+ results = self._execute_gpu_logic(
236
+ self._gpu_logic,
237
+ duration=ui_inputs['zero_gpu_duration'],
238
+ default_duration=60,
239
+ task_name=f"ImageGen ({task_type})",
240
+ ui_inputs=ui_inputs,
241
+ loras_string=loras_string,
242
+ workflow=workflow,
243
+ assembler=assembler,
244
+ progress=progress
245
+ )
246
+
247
+ import json
248
+ import glob
249
+ from PIL import PngImagePlugin
250
+
251
+ prompt_json = json.dumps(workflow)
252
+
253
+ out_dir = os.path.abspath(OUTPUT_DIR)
254
+ os.makedirs(out_dir, exist_ok=True)
255
+
256
+ try:
257
+ existing_files = glob.glob(os.path.join(out_dir, "gen_*.png"))
258
+ existing_files.sort(key=os.path.getmtime)
259
+ while len(existing_files) > 50:
260
+ os.remove(existing_files.pop(0))
261
+ except Exception as e:
262
+ print(f"Warning: Failed to cleanup output dir: {e}")
263
+
264
+ final_results = []
265
+ for img in results:
266
+ if not isinstance(img, Image.Image):
267
+ final_results.append(img)
268
+ continue
269
+
270
+ metadata = PngImagePlugin.PngInfo()
271
+ params_string = img.info.get("parameters", "")
272
+ if params_string:
273
+ metadata.add_text("parameters", params_string)
274
+ metadata.add_text("prompt", prompt_json)
275
+
276
+ filename = f"gen_{random.randint(1000000, 9999999)}.png"
277
+ filepath = os.path.join(out_dir, filename)
278
+ img.save(filepath, "PNG", pnginfo=metadata)
279
+ try:
280
+ from core.cloud_storage import queue_image
281
+ queue_image(model_type, model_display_name, filepath, params_string)
282
+ except Exception as _e:
283
+ print(f"Cloud storage skipped: {_e}")
284
+ final_results.append(filepath)
285
+
286
+ results = final_results
287
+
288
+ finally:
289
+ for temp_file in temp_files_to_clean:
290
+ if temp_file and os.path.exists(temp_file):
291
+ os.remove(temp_file)
292
+ print(f"✅ Cleaned up temp file: {temp_file}")
293
+
294
  return results
core/pipelines/workflow_recipes/_partials/conditioning/fibo-2.0.yaml ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: "fibo2"
15
+ device: "default"
16
+ text_encode_latent:
17
+ class_type: TextEncodeFibo2
18
+ title: "TextEncodeFibo2"
19
+
20
+ connections:
21
+ - from: "unet_loader:0"
22
+ to: "ksampler:model"
23
+ - from: "clip_loader:0"
24
+ to: "text_encode_latent:clip"
25
+ - from: "vae_loader:0"
26
+ to: "vae_decode:vae"
27
+ - from: "vae_loader:0"
28
+ to: "vae_encode:vae"
29
+ - from: "text_encode_latent:0"
30
+ to: "ksampler:positive"
31
+ - from: "text_encode_latent:1"
32
+ to: "ksampler:negative"
33
+ - from: "text_encode_latent:2"
34
+ to: "ksampler:latent_image"
35
+
36
+ dynamic_lora_chains:
37
+ lora_chain:
38
+ template: "LoraLoader"
39
+ output_map:
40
+ "unet_loader:0": "model"
41
+ "clip_loader:0": "clip"
42
+ input_map:
43
+ "model": "model"
44
+ "clip": "clip"
45
+ end_input_map:
46
+ "model": ["ksampler:model"]
47
+ "clip": ["text_encode_latent:clip"]
48
+
49
+ dynamic_controlnet_chains:
50
+ controlnet_chain:
51
+ template: "ControlNetApplyAdvanced"
52
+ ksampler_node: "ksampler"
53
+ vae_source: "vae_loader:0"
54
+
55
+ dynamic_conditioning_chains:
56
+ conditioning_chain:
57
+ ksampler_node: "ksampler"
58
+ clip_source: "clip_loader:0"
59
+
60
+ dynamic_pid_chains:
61
+ pid_chain:
62
+ ksampler_node: "ksampler"
63
+
64
+ dynamic_reference_image_chains:
65
+ reference_image_chain:
66
+ text_encode_node: "text_encode_latent"
67
+ vae_node: "vae_loader"
68
+
69
+ ui_map:
70
+ unet_name: "unet_loader:unet_name"
71
+ vae_name: "vae_loader:vae_name"
72
+ clip_name: "clip_loader:clip_name"
73
+ positive_prompt: "text_encode_latent:prompt"
74
+ negative_prompt: "text_encode_latent:negative_prompt"
75
+ width: "text_encode_latent:width"
76
+ height: "text_encode_latent:height"
77
+ batch_size: "text_encode_latent:batch_size"
core/pipelines/workflow_recipes/_partials/conditioning/flux3.yaml ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ unet_loader:
3
+ class_type: UNETLoader
4
+ title: "Load Diffusion Model"
5
+ params:
6
+ weight_dtype: "default"
7
+ clip_loader:
8
+ class_type: CLIPLoader
9
+ title: "Load CLIP"
10
+ params:
11
+ type: "flux3"
12
+ device: "default"
13
+ vae_loader:
14
+ class_type: VAELoader
15
+ title: "Load VAE"
16
+
17
+ pos_prompt:
18
+ class_type: CLIPTextEncode
19
+ title: "CLIP Text Encode (Positive)"
20
+ neg_prompt:
21
+ class_type: CLIPTextEncode
22
+ title: "CLIP Text Encode (Negative)"
23
+
24
+ ksampler:
25
+ class_type: KSampler
26
+ title: "KSampler"
27
+ params:
28
+ denoise: 1.0
29
+
30
+ vae_decode:
31
+ class_type: VAEDecode
32
+ title: "VAE Decode"
33
+
34
+ save_image:
35
+ class_type: SaveImage
36
+ title: "Save Image"
37
+
38
+ connections:
39
+ - from: "unet_loader:0"
40
+ to: "ksampler:model"
41
+ - from: "clip_loader:0"
42
+ to: "pos_prompt:clip"
43
+ - from: "clip_loader:0"
44
+ to: "neg_prompt:clip"
45
+ - from: "vae_loader:0"
46
+ to: "vae_decode:vae"
47
+ - from: "vae_loader:0"
48
+ to: "vae_encode:vae"
49
+
50
+ - from: "pos_prompt:0"
51
+ to: "ksampler:positive"
52
+ - from: "neg_prompt:0"
53
+ to: "ksampler:negative"
54
+
55
+ - from: "latent_source:0"
56
+ to: "ksampler:latent_image"
57
+
58
+ - from: "ksampler:0"
59
+ to: "vae_decode:samples"
60
+ - from: "vae_decode:0"
61
+ to: "save_image:images"
62
+
63
+ dynamic_lora_chains:
64
+ lora_chain:
65
+ template: "LoraLoader"
66
+ output_map:
67
+ "unet_loader:0": "model"
68
+ "clip_loader:0": "clip"
69
+ input_map:
70
+ "model": "model"
71
+ "clip": "clip"
72
+ end_input_map:
73
+ "model": ["ksampler:model"]
74
+ "clip": ["pos_prompt:clip", "neg_prompt:clip"]
75
+
76
+ dynamic_reference_latent_chains:
77
+ reference_latent_chain:
78
+ ksampler_node: "ksampler"
79
+ vae_node: "vae_loader"
80
+
81
+ dynamic_flux3_prompt_upsampler_chains:
82
+ flux3_prompt_upsampler_chain:
83
+ target_node: "pos_prompt"
84
+
85
+ dynamic_pid_chains:
86
+ pid_chain:
87
+ ksampler_node: "ksampler"
88
+
89
+ ui_map:
90
+ unet_name: "unet_loader:unet_name"
91
+ clip_name: "clip_loader:clip_name"
92
+ vae_name: "vae_loader:vae_name"
93
+
94
+ positive_prompt: "pos_prompt:text"
95
+ negative_prompt: "neg_prompt:text"
96
+
97
+ seed: "ksampler:seed"
98
+ steps: "ksampler:steps"
99
+ cfg: "ksampler:cfg"
100
+ sampler_name: "ksampler:sampler_name"
101
+ scheduler: "ksampler:scheduler"
102
+ denoise: "ksampler:denoise"
103
+
104
+ filename_prefix: "save_image:filename_prefix"
core/pipelines/workflow_recipes/_partials/conditioning/moss-image.yaml ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nodes:
2
+ pos_prompt:
3
+ class_type: CLIPTextEncode
4
+ title: "moss-image Positive"
5
+ neg_prompt:
6
+ class_type: CLIPTextEncode
7
+ title: "moss-image Negative"
8
+ unet_loader:
9
+ class_type: UNETLoader
10
+ title: "Load Diffusion Model"
11
+ params:
12
+ weight_dtype: "default"
13
+ slg:
14
+ class_type: SkipLayerGuidanceDiTSimple
15
+ title: "SkipLayerGuidanceDiTSimple"
16
+ params:
17
+ double_layers: ""
18
+ single_layers: "22"
19
+ start_percent: 0.0
20
+ end_percent: 0.5
21
+ clip_loader:
22
+ class_type: CLIPLoader
23
+ title: "Load CLIP"
24
+ params:
25
+ type: "moss_image"
26
+ device: "default"
27
+ vae_loader:
28
+ class_type: VAELoader
29
+ title: "Load VAE"
30
+
31
+ connections:
32
+ - from: "unet_loader:0"
33
+ to: "slg:model"
34
+ - from: "slg:0"
35
+ to: "ksampler:model"
36
+ - from: "clip_loader:0"
37
+ to: "pos_prompt:clip"
38
+ - from: "clip_loader:0"
39
+ to: "neg_prompt:clip"
40
+ - from: "pos_prompt:0"
41
+ to: "ksampler:positive"
42
+ - from: "neg_prompt:0"
43
+ to: "ksampler:negative"
44
+ - from: "vae_loader:0"
45
+ to: "vae_decode:vae"
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"
55
+ input_map:
56
+ "model": "model"
57
+ "clip": "clip"
58
+ end_input_map:
59
+ "model": ["slg:model"]
60
+ "clip": ["pos_prompt:clip", "neg_prompt:clip"]
61
+
62
+ dynamic_conditioning_chains:
63
+ conditioning_chain:
64
+ ksampler_node: "ksampler"
65
+ clip_source: "clip_loader:0"
66
+
67
+ dynamic_vae_chains:
68
+ vae_chain:
69
+ targets:
70
+ - "vae_decode:vae"
71
+ - "vae_encode:vae"
72
+
73
+ dynamic_moss_image_control_chains:
74
+ moss_image_control_chain:
75
+ 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"
91
+
92
+ ui_map:
93
+ positive_prompt: "pos_prompt:text"
94
+ negative_prompt: "neg_prompt:text"
95
+ unet_name: "unet_loader:unet_name"
96
+ clip_name: "clip_loader:clip_name"
97
+ vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/qwen-image-2.1.yaml CHANGED
@@ -51,7 +51,7 @@ dynamic_lora_chains:
51
 
52
  dynamic_controlnet_chains:
53
  controlnet_chain:
54
- template: "QwenImage21FunControlNetApply"
55
  ksampler_node: "ksampler"
56
  vae_source: "vae_loader:0"
57
 
 
51
 
52
  dynamic_controlnet_chains:
53
  controlnet_chain:
54
+ template: "ZImageFunControlnet"
55
  ksampler_node: "ksampler"
56
  vae_source: "vae_loader:0"
57
 
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 = []
@@ -628,6 +629,46 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
628
  ui_inputs["ming_image_prompt_enhancer_max_length"] = int(item.get("max_length", 4096))
629
  if "system_prompt" in item:
630
  ui_inputs["ming_image_prompt_enhancer_system_prompt"] = str(item["system_prompt"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  elif itype == "krea2_identity_edit":
632
  img = _parse_image_param(item.get("image"))
633
  if img:
@@ -660,6 +701,16 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
660
  img = _parse_image_param(item.get("image"))
661
  if img:
662
  sensenova_reference_data.append(img)
 
 
 
 
 
 
 
 
 
 
663
  elif itype == "vae":
664
  v_source = item.get("source", item.get("vae_source", "Civitai"))
665
  v_val = item.get("vae_value", item.get("value", item.get("vae_id", item.get("vae_name", ""))))
@@ -670,6 +721,7 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
670
  if lora_data: ui_inputs["lora_data"] = lora_data
671
  if embedding_data: ui_inputs["embedding_data"] = embedding_data
672
  if controlnet_data: ui_inputs["controlnet_data"] = controlnet_data
 
673
 
674
  if ipadapter_images:
675
  preset = ipadapter_global_preset or "STANDARD (medium strength)"
@@ -757,6 +809,67 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
757
  else:
758
  ui_inputs["ming_image_prompt_enhancer_enable"] = False
759
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  _TASKS_DB[task_id]["progress"] = 50
761
 
762
  # 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 = []
 
629
  ui_inputs["ming_image_prompt_enhancer_max_length"] = int(item.get("max_length", 4096))
630
  if "system_prompt" in item:
631
  ui_inputs["ming_image_prompt_enhancer_system_prompt"] = str(item["system_prompt"])
632
+ elif itype == "flux3_prompt_upsampler":
633
+ is_enabled = item.get("enable", item.get("enabled", True))
634
+ if isinstance(is_enabled, str):
635
+ is_enabled = is_enabled.upper() in ("ON", "TRUE", "1")
636
+ ui_inputs["flux3_prompt_upsampler_enable"] = bool(is_enabled)
637
+ is_thinking = item.get("thinking", False)
638
+ if isinstance(is_thinking, str):
639
+ is_thinking = is_thinking.upper() in ("ON", "TRUE", "1")
640
+ ui_inputs["flux3_prompt_upsampler_thinking"] = bool(is_thinking)
641
+ is_bbox_mode = item.get("bbox_mode", False)
642
+ if isinstance(is_bbox_mode, str):
643
+ is_bbox_mode = is_bbox_mode.upper() in ("ON", "TRUE", "1")
644
+ ui_inputs["flux3_prompt_upsampler_bbox_mode"] = bool(is_bbox_mode)
645
+ ui_inputs["flux3_prompt_upsampler_max_length"] = int(item.get("max_length", 3584))
646
+ if "system_prompt" in item:
647
+ ui_inputs["flux3_prompt_upsampler_system_prompt"] = str(item["system_prompt"])
648
+ elif itype == "moss_image_control":
649
+ if "color_map" in item:
650
+ parsed_color_map = _parse_image_param(item["color_map"])
651
+ if parsed_color_map:
652
+ ui_inputs["moss_image_control_color_map"] = parsed_color_map
653
+ if "depth_map" in item:
654
+ parsed_depth_map = _parse_image_param(item["depth_map"])
655
+ if parsed_depth_map:
656
+ ui_inputs["moss_image_control_depth_map"] = parsed_depth_map
657
+ if "strength" in item:
658
+ try:
659
+ ui_inputs["moss_image_control_strength"] = float(item["strength"])
660
+ except (ValueError, TypeError):
661
+ pass
662
+ if "start_percent" in item:
663
+ try:
664
+ ui_inputs["moss_image_control_start_percent"] = float(item["start_percent"])
665
+ except (ValueError, TypeError):
666
+ pass
667
+ if "end_percent" in item:
668
+ try:
669
+ ui_inputs["moss_image_control_end_percent"] = float(item["end_percent"])
670
+ except (ValueError, TypeError):
671
+ pass
672
  elif itype == "krea2_identity_edit":
673
  img = _parse_image_param(item.get("image"))
674
  if img:
 
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)"
 
809
  else:
810
  ui_inputs["ming_image_prompt_enhancer_enable"] = False
811
 
812
+ flux3_pe_val = params.get("flux3_prompt_upsampler") or params.get("flux3_prompt_upsampler_enable")
813
+ if flux3_pe_val is not None:
814
+ if isinstance(flux3_pe_val, bool):
815
+ ui_inputs["flux3_prompt_upsampler_enable"] = flux3_pe_val
816
+ elif isinstance(flux3_pe_val, dict):
817
+ ui_inputs["flux3_prompt_upsampler_enable"] = bool(flux3_pe_val.get("enable", flux3_pe_val.get("enabled", True)))
818
+ ui_inputs["flux3_prompt_upsampler_thinking"] = bool(flux3_pe_val.get("thinking", False))
819
+ ui_inputs["flux3_prompt_upsampler_bbox_mode"] = bool(flux3_pe_val.get("bbox_mode", False))
820
+ ui_inputs["flux3_prompt_upsampler_max_length"] = int(flux3_pe_val.get("max_length", 3584))
821
+ if "system_prompt" in flux3_pe_val:
822
+ ui_inputs["flux3_prompt_upsampler_system_prompt"] = str(flux3_pe_val["system_prompt"])
823
+ elif str(flux3_pe_val).upper() in ("ON", "TRUE", "1"):
824
+ ui_inputs["flux3_prompt_upsampler_enable"] = True
825
+ else:
826
+ ui_inputs["flux3_prompt_upsampler_enable"] = False
827
+
828
+ moss_control_val = params.get("moss_image_control")
829
+ if moss_control_val and isinstance(moss_control_val, dict):
830
+ if "color_map" in moss_control_val:
831
+ parsed_color_map = _parse_image_param(moss_control_val["color_map"])
832
+ if parsed_color_map:
833
+ ui_inputs["moss_image_control_color_map"] = parsed_color_map
834
+ if "depth_map" in moss_control_val:
835
+ parsed_depth_map = _parse_image_param(moss_control_val["depth_map"])
836
+ if parsed_depth_map:
837
+ ui_inputs["moss_image_control_depth_map"] = parsed_depth_map
838
+ if "strength" in moss_control_val:
839
+ try:
840
+ ui_inputs["moss_image_control_strength"] = float(moss_control_val["strength"])
841
+ except (ValueError, TypeError):
842
+ pass
843
+ if "start_percent" in moss_control_val:
844
+ try:
845
+ ui_inputs["moss_image_control_start_percent"] = float(moss_control_val["start_percent"])
846
+ except (ValueError, TypeError):
847
+ pass
848
+ if "end_percent" in moss_control_val:
849
+ try:
850
+ ui_inputs["moss_image_control_end_percent"] = float(moss_control_val["end_percent"])
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
requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
- comfyui-frontend-package==1.53.6
2
- comfyui-workflow-templates==0.11.69
3
- comfyui-embedded-docs==0.5.12
4
  torch
5
  torchsde
6
  torchvision
@@ -13,7 +13,7 @@ safetensors>=0.4.2
13
  aiohttp>=3.11.8
14
  yarl>=1.18.0
15
  pyyaml
16
- Pillow
17
  scipy
18
  tqdm
19
  psutil
@@ -21,8 +21,8 @@ alembic
21
  SQLAlchemy>=2.0.0
22
  filelock
23
  av>=17.0.0
24
- comfy-kitchen==0.2.35
25
- comfy-aimdo==0.5.5
26
  requests
27
  simpleeval>=1.0.0
28
  blake3
@@ -34,7 +34,7 @@ pydantic~=2.0
34
  pydantic-settings~=2.0
35
  PyOpenGL>=3.1.8
36
  comfy-angle
37
- boto3
38
 
39
  diffusers
40
  protobuf
@@ -42,4 +42,5 @@ insightface
42
  huggingface-hub
43
  imageio
44
  spaces
45
- sageattention @ https://huggingface.co/RioShiina/Sage-Attention-ZeroGPU-Space-Build/resolve/main/sageattention-2.2.0-cp312-cp312-linux_x86_64.whl
 
 
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
 
13
  aiohttp>=3.11.8
14
  yarl>=1.18.0
15
  pyyaml
16
+ Pillow>=10.1.0
17
  scipy
18
  tqdm
19
  psutil
 
21
  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
 
34
  pydantic-settings~=2.0
35
  PyOpenGL>=3.1.8
36
  comfy-angle
37
+
38
 
39
  diffusers
40
  protobuf
 
42
  huggingface-hub
43
  imageio
44
  spaces
45
+ sageattention @ https://huggingface.co/RioShiina/Sage-Attention-ZeroGPU-Space-Build/resolve/main/sageattention-2.2.0-cp312-cp312-linux_x86_64.whl
46
+ boto3
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):
19
  def update_fn(*args):
20
  arch = args[0]
21
  category = args[1]
@@ -86,6 +86,9 @@ def make_update_fn(m_comp, cat_comp, ar_comp, width_comp, height_comp, cn_types,
86
  if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
87
  if qwen_pe_acc: updates[qwen_pe_acc] = gr.update(visible=('qwen_image_2_1_prompt_enhancer' in enabled_chains))
88
  if ming_pe_acc: updates[ming_pe_acc] = gr.update(visible=('ming_image_prompt_enhancer' in enabled_chains))
 
 
 
89
 
90
  if ar_comp:
91
  res_key = arch_model_type
@@ -130,7 +133,7 @@ def make_update_fn(m_comp, cat_comp, ar_comp, width_comp, height_comp, cn_types,
130
  return update_fn
131
 
132
 
133
- 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):
134
  def change_fn(*args):
135
  model_name = args[0]
136
  idx = 1
@@ -209,6 +212,9 @@ def make_model_change_fn(cat_comp_ref, ar_comp, width_comp, height_comp, cn_type
209
  if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
210
  if qwen_pe_acc: updates[qwen_pe_acc] = gr.update(visible=('qwen_image_2_1_prompt_enhancer' in enabled_chains))
211
  if ming_pe_acc: updates[ming_pe_acc] = gr.update(visible=('ming_image_prompt_enhancer' in enabled_chains))
 
 
 
212
 
213
  if ar_comp:
214
  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]
 
86
  if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
87
  if qwen_pe_acc: updates[qwen_pe_acc] = gr.update(visible=('qwen_image_2_1_prompt_enhancer' in enabled_chains))
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
 
212
  if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
213
  if qwen_pe_acc: updates[qwen_pe_acc] = gr.update(visible=('qwen_image_2_1_prompt_enhancer' in enabled_chains))
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,
@@ -64,6 +65,9 @@ def attach_event_handlers(ui_components, demo):
64
  vae_accordion = ui_components.get(f'vae_accordion_{prefix}')
65
  qwen_pe_accordion = ui_components.get(f'qwen_image_2_1_prompt_enhancer_accordion_{prefix}')
66
  ming_pe_accordion = ui_components.get(f'ming_image_prompt_enhancer_accordion_{prefix}')
 
 
 
67
 
68
  ipa_preset_list = ui_components.get(f'ipadapter_final_preset_{prefix}')
69
 
@@ -101,6 +105,9 @@ def attach_event_handlers(ui_components, demo):
101
  if vae_accordion: outputs.append(vae_accordion)
102
  if qwen_pe_accordion: outputs.append(qwen_pe_accordion)
103
  if ming_pe_accordion: outputs.append(ming_pe_accordion)
 
 
 
104
  if ipa_preset_list: outputs.append(ipa_preset_list)
105
 
106
  outputs.extend(valid_extra_comps)
@@ -111,7 +118,9 @@ def attach_event_handlers(ui_components, demo):
111
  ipa_preset_list, lora_accordion, cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
112
  ref_latent_accordion, hidream_o1_ref_accordion, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp,
113
  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,
114
- task_type=task_type, type_comp=type_comp, qwen_pe_acc=qwen_pe_accordion, ming_pe_acc=ming_pe_accordion
 
 
115
  )
116
  inputs = [arch_comp, cat_comp]
117
  if aspect_ratio_comp:
@@ -147,6 +156,9 @@ def attach_event_handlers(ui_components, demo):
147
  if vae_accordion: outputs2.append(vae_accordion)
148
  if qwen_pe_accordion: outputs2.append(qwen_pe_accordion)
149
  if ming_pe_accordion: outputs2.append(ming_pe_accordion)
 
 
 
150
  if ipa_preset_list: outputs2.append(ipa_preset_list)
151
 
152
  outputs2.extend(valid_extra_comps)
@@ -162,7 +174,9 @@ def attach_event_handlers(ui_components, demo):
162
  arch_comp, ipa_preset_list, lora_accordion, cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
163
  ref_latent_accordion, hidream_o1_ref_accordion, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp,
164
  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,
165
- task_type=task_type, type_comp=type_comp, qwen_pe_acc=qwen_pe_accordion, ming_pe_acc=ming_pe_accordion
 
 
166
  )
167
  if type_comp:
168
  inputs2.append(type_comp)
@@ -184,6 +198,7 @@ def attach_event_handlers(ui_components, demo):
184
  create_krea2_reference_edit_event_handlers(prefix, ui_components)
185
  create_qwen_image_edit_event_handlers(prefix, ui_components)
186
  create_reference_image_event_handlers(prefix, ui_components)
 
187
  create_run_event(prefix, task_type, ui_components)
188
 
189
  # Dynamic Type toggle event for unified imagegen UI
@@ -252,6 +267,9 @@ def attach_event_handlers(ui_components, demo):
252
  f'reference_image_accordion_{prefix}': 'reference_image',
253
  f'qwen_image_2_1_prompt_enhancer_accordion_{prefix}': 'qwen_image_2_1_prompt_enhancer',
254
  f'ming_image_prompt_enhancer_accordion_{prefix}': 'ming_image_prompt_enhancer',
 
 
 
255
  f'pid_accordion_{prefix}': 'pid',
256
  f'vae_accordion_{prefix}': 'vae',
257
  }
@@ -291,6 +309,9 @@ def attach_event_handlers(ui_components, demo):
291
  'reference_image_accordion_imagegen',
292
  'qwen_image_2_1_prompt_enhancer_accordion_imagegen',
293
  'ming_image_prompt_enhancer_accordion_imagegen',
 
 
 
294
  'pid_accordion_imagegen', 'vae_accordion_imagegen',
295
  ]
296
  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,
 
65
  vae_accordion = ui_components.get(f'vae_accordion_{prefix}')
66
  qwen_pe_accordion = ui_components.get(f'qwen_image_2_1_prompt_enhancer_accordion_{prefix}')
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
 
 
105
  if vae_accordion: outputs.append(vae_accordion)
106
  if qwen_pe_accordion: outputs.append(qwen_pe_accordion)
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)
 
118
  ipa_preset_list, lora_accordion, cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
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:
 
156
  if vae_accordion: outputs2.append(vae_accordion)
157
  if qwen_pe_accordion: outputs2.append(qwen_pe_accordion)
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)
 
174
  arch_comp, ipa_preset_list, lora_accordion, cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
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
 
267
  f'reference_image_accordion_{prefix}': 'reference_image',
268
  f'qwen_image_2_1_prompt_enhancer_accordion_{prefix}': 'qwen_image_2_1_prompt_enhancer',
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
  }
 
309
  'reference_image_accordion_imagegen',
310
  'qwen_image_2_1_prompt_enhancer_accordion_imagegen',
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
@@ -53,6 +53,24 @@ def create_run_event(prefix: str, task_type: str = None, ui_components: dict = N
53
  if ui_components.get(f'ming_image_prompt_enhancer_system_prompt_{prefix}'):
54
  run_inputs_map['ming_image_prompt_enhancer_system_prompt'] = ui_components[f'ming_image_prompt_enhancer_system_prompt_{prefix}']
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  # Generic / Txt2Img inputs
57
  if ui_components.get(f'width_{prefix}') or ui_components.get(f'{prefix}_width'):
58
  run_inputs_map['width'] = ui_components.get(f'width_{prefix}') or ui_components.get(f'{prefix}_width')
@@ -101,6 +119,7 @@ def create_run_event(prefix: str, task_type: str = None, ui_components: dict = N
101
  krea2_reference_edit_data_components = ui_components.get(f'all_krea2_reference_edit_components_flat_{prefix}', [])
102
  qwen_image_edit_data_components = ui_components.get(f'all_qwen_image_edit_components_flat_{prefix}', [])
103
  reference_image_data_components = ui_components.get(f'all_reference_image_components_flat_{prefix}', [])
 
104
 
105
  run_inputs_map['vae_source'] = ui_components.get(f'vae_source_{prefix}')
106
  run_inputs_map['vae_id'] = ui_components.get(f'vae_id_{prefix}')
@@ -111,7 +130,8 @@ def create_run_event(prefix: str, task_type: str = None, ui_components: dict = N
111
  all_chains = [
112
  lora_data_components, controlnet_data_components, ipadapter_data_components,
113
  sd3_ipadapter_data_components, flux1_ipadapter_data_components, style_data_components,
114
- 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
 
115
  ]
116
  for chain in all_chains:
117
  if chain:
@@ -144,6 +164,7 @@ def create_run_event(prefix: str, task_type: str = None, ui_components: dict = N
144
  assign_chain_data('krea2_reference_edit_data', krea2_reference_edit_data_components)
145
  assign_chain_data('qwen_image_edit_data', qwen_image_edit_data_components)
146
  assign_chain_data('reference_image_data', reference_image_data_components)
 
147
 
148
  if 'ui_type' in ui_dict:
149
  ui_dict['task_type'] = TYPE_TASK_MAP.get(ui_dict['ui_type'], 'txt2img')
 
53
  if ui_components.get(f'ming_image_prompt_enhancer_system_prompt_{prefix}'):
54
  run_inputs_map['ming_image_prompt_enhancer_system_prompt'] = ui_components[f'ming_image_prompt_enhancer_system_prompt_{prefix}']
55
 
56
+ if ui_components.get(f'flux3_prompt_upsampler_enable_{prefix}'):
57
+ run_inputs_map['flux3_prompt_upsampler_enable'] = ui_components[f'flux3_prompt_upsampler_enable_{prefix}']
58
+ if ui_components.get(f'flux3_prompt_upsampler_thinking_{prefix}'):
59
+ run_inputs_map['flux3_prompt_upsampler_thinking'] = ui_components[f'flux3_prompt_upsampler_thinking_{prefix}']
60
+ if ui_components.get(f'flux3_prompt_upsampler_bbox_mode_{prefix}'):
61
+ run_inputs_map['flux3_prompt_upsampler_bbox_mode'] = ui_components[f'flux3_prompt_upsampler_bbox_mode_{prefix}']
62
+ if ui_components.get(f'flux3_prompt_upsampler_max_length_{prefix}'):
63
+ run_inputs_map['flux3_prompt_upsampler_max_length'] = ui_components[f'flux3_prompt_upsampler_max_length_{prefix}']
64
+
65
+ if ui_components.get(f'moss_image_control_color_map_{prefix}'):
66
+ run_inputs_map['moss_image_control_color_map'] = ui_components[f'moss_image_control_color_map_{prefix}']
67
+ if ui_components.get(f'moss_image_control_depth_map_{prefix}'):
68
+ run_inputs_map['moss_image_control_depth_map'] = ui_components[f'moss_image_control_depth_map_{prefix}']
69
+ if ui_components.get(f'moss_image_control_strength_{prefix}'):
70
+ run_inputs_map['moss_image_control_strength'] = ui_components[f'moss_image_control_strength_{prefix}']
71
+ if ui_components.get(f'moss_image_control_end_percent_{prefix}'):
72
+ run_inputs_map['moss_image_control_end_percent'] = ui_components[f'moss_image_control_end_percent_{prefix}']
73
+
74
  # Generic / Txt2Img inputs
75
  if ui_components.get(f'width_{prefix}') or ui_components.get(f'{prefix}_width'):
76
  run_inputs_map['width'] = ui_components.get(f'width_{prefix}') or ui_components.get(f'{prefix}_width')
 
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
@@ -9,7 +9,9 @@ from .shared.ui_components import (
9
  create_model_architecture_filter_ui, create_category_filter_ui,
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
  )
14
 
15
  default_vals = MODEL_DEFAULTS_CONFIG.get('Default', {})
@@ -162,7 +164,7 @@ def create_ui():
162
  components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=DEFAULT_CFG)
163
  with gr.Row():
164
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
165
- components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=32, step=1, value=1)
166
  with gr.Row():
167
  components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=60, placeholder="Default: 60s", info="Optional: Set how long to reserve the GPU. Longer jobs may need more time.")
168
 
@@ -189,7 +191,10 @@ def create_ui():
189
  components.update(create_reference_image_ui(prefix))
190
  components.update(create_qwen_image_2_1_prompt_enhancer_ui(prefix))
191
  components.update(create_ming_image_prompt_enhancer_ui(prefix))
 
192
  components.update(create_vae_override_ui(prefix))
 
 
193
  components.update(create_pid_ui(prefix))
194
  components[f'accordion_wrapper_{prefix}'] = accordion_wrapper
195
 
 
9
  create_model_architecture_filter_ui, create_category_filter_ui,
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', {})
 
164
  components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=DEFAULT_CFG)
165
  with gr.Row():
166
  components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
167
+ components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
168
  with gr.Row():
169
  components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=60, placeholder="Default: 60s", info="Optional: Set how long to reserve the GPU. Longer jobs may need more time.")
170
 
 
191
  components.update(create_reference_image_ui(prefix))
192
  components.update(create_qwen_image_2_1_prompt_enhancer_ui(prefix))
193
  components.update(create_ming_image_prompt_enhancer_ui(prefix))
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/layout.py CHANGED
@@ -1,43 +1,44 @@
1
- import os
2
- import gradio as gr
3
- from core.settings import *
4
-
5
- from .imagegen_ui import create_ui as create_imagegen_ui
6
-
7
- MAX_DYNAMIC_CONTROLS = 10
8
-
9
- def build_ui(event_handler_function):
10
- ui_components = {}
11
-
12
- with gr.Blocks() as demo:
13
- gr.Markdown("# ImageGen")
14
- gr.Markdown(
15
- "This demo is a streamlined version of the [Comfy web UI](https://github.com/RioShiina47/comfy-webui)'s [ImageGen](https://huggingface.co/spaces/RioShiina/ImageGen) functionality. Support [High-Level MCP](https://rioshiina-imagegen.hf.space/gradio_api/mcp/)."
16
- )
17
- ui_components.update(create_imagegen_ui())
18
-
19
- gr.Markdown("<div style='text-align: left; margin-top: 20px;'>Made by RioShiina with ❤️<br><a href='https://github.com/RioShiina47' target='_blank'>GitHub</a> | <a href='https://huggingface.co/RioShiina' target='_blank'>Hugging Face</a> | <a href='https://civitai.com/user/RioShiina' target='_blank'>Civitai</a></div>")
20
- gr.Markdown("<div style='font-size:12px; opacity:0.7; margin-top:12px;'>Generated images, prompts and settings are saved for research and model-training purposes.</div>")
21
-
22
- event_handler_function(ui_components, demo)
23
-
24
- try:
25
- import mcp_tools as mcp
26
- if hasattr(mcp, "register_high_level_mcp_apis"):
27
- mcp.register_high_level_mcp_apis(demo)
28
- mcp.cleanup_dependencies_api_names(demo)
29
- elif hasattr(mcp, 'MCP_FUNCTIONS') and isinstance(mcp.MCP_FUNCTIONS, list):
30
- for func in mcp.MCP_FUNCTIONS:
31
- gr.api(func)
32
- print(f"✅ Registered MCP API endpoint: '{func.__name__}'")
33
- except Exception as e:
34
- print(f"⚠️ Warning registering MCP functions: {e}")
35
-
36
- # Disable API exposure for all atomic UI event handlers
37
- high_level_names = getattr(mcp, "HIGH_LEVEL_MCP_API_NAMES", set())
38
- for fn in demo.fns.values():
39
- if getattr(fn, "api_name", None) not in high_level_names:
40
- fn.show_api = False
41
- fn.api_name = False
42
-
 
43
  return demo
 
1
+ import os
2
+ import gradio as gr
3
+ from core.settings import *
4
+
5
+ from .imagegen_ui import create_ui as create_imagegen_ui
6
+
7
+ MAX_DYNAMIC_CONTROLS = 10
8
+
9
+ def build_ui(event_handler_function):
10
+ ui_components = {}
11
+
12
+ with gr.Blocks() as demo:
13
+ gr.Markdown("# ImageGen")
14
+ gr.Markdown(
15
+ "This demo is a streamlined version of the [Comfy web UI](https://github.com/RioShiina47/comfy-webui)'s [ImageGen](https://huggingface.co/spaces/RioShiina/ImageGen) functionality. Support [High-Level MCP](https://rioshiina-imagegen.hf.space/gradio_api/mcp/)."
16
+ )
17
+ ui_components.update(create_imagegen_ui())
18
+
19
+ gr.Markdown("<div style='text-align: left; margin-top: 20px;'>Made by RioShiina with ❤️<br><a href='https://github.com/RioShiina47' target='_blank'>GitHub</a> | <a href='https://huggingface.co/RioShiina' target='_blank'>Hugging Face</a> | <a href='https://civitai.com/user/RioShiina' target='_blank'>Civitai</a></div>")
20
+
21
+ gr.Markdown("<div style='font-size:12px; opacity:0.7; margin-top:12px;'>Generated images, prompts and settings are saved for research and model-training purposes.</div>")
22
+
23
+ event_handler_function(ui_components, demo)
24
+
25
+ try:
26
+ import mcp_tools as mcp
27
+ if hasattr(mcp, "register_high_level_mcp_apis"):
28
+ mcp.register_high_level_mcp_apis(demo)
29
+ mcp.cleanup_dependencies_api_names(demo)
30
+ elif hasattr(mcp, 'MCP_FUNCTIONS') and isinstance(mcp.MCP_FUNCTIONS, list):
31
+ for func in mcp.MCP_FUNCTIONS:
32
+ gr.api(func)
33
+ print(f"✅ Registered MCP API endpoint: '{func.__name__}'")
34
+ except Exception as e:
35
+ print(f"⚠️ Warning registering MCP functions: {e}")
36
+
37
+ # Disable API exposure for all atomic UI event handlers
38
+ high_level_names = getattr(mcp, "HIGH_LEVEL_MCP_API_NAMES", set())
39
+ for fn in demo.fns.values():
40
+ if getattr(fn, "api_name", None) not in high_level_names:
41
+ fn.show_api = False
42
+ fn.api_name = False
43
+
44
  return demo
ui/shared/ui_components.py CHANGED
@@ -1,838 +1,947 @@
1
- import gradio as gr
2
- from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
3
- from core.settings import (
4
- MAX_LORAS, LORA_SOURCE_CHOICES, MAX_EMBEDDINGS, MAX_CONDITIONINGS,
5
- MAX_CONTROLNETS, 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
- )
9
- import yaml
10
- import os
11
- from functools import lru_cache
12
- from utils.app_utils import save_uploaded_file
13
-
14
- default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOINT else None
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', {})
22
- DEFAULT_STEPS = default_vals.get('steps', 20)
23
- DEFAULT_CFG = default_vals.get('cfg', 5.0)
24
- DEFAULT_SAMPLER = default_vals.get('sampler_name', 'euler')
25
- DEFAULT_SCHEDULER = default_vals.get('scheduler', 'simple')
26
- DEFAULT_POS_PROMPT = default_vals.get('positive_prompt', '')
27
- DEFAULT_NEG_PROMPT = default_vals.get('negative_prompt', '')
28
-
29
- @lru_cache(maxsize=1)
30
- def get_ipadapter_config_from_yaml():
31
- try:
32
- _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
33
- _IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
34
- with open(_IPADAPTER_LIST_PATH, 'r', encoding='utf-8') as f:
35
- config = yaml.safe_load(f)
36
- return config
37
- except Exception as e:
38
- print(f"Warning: Could not load ipadapter.yaml for UI components: {e}")
39
- return {}
40
-
41
- def get_ipadapter_presets(arch="SDXL"):
42
- config = get_ipadapter_config_from_yaml()
43
- presets = []
44
- if config:
45
- std_presets = config.get("IPAdapter_presets", {}).get(arch, [])
46
- face_presets = config.get("IPAdapter_FaceID_presets", {}).get(arch, [])
47
- if std_presets:
48
- presets.extend(std_presets)
49
- if face_presets:
50
- presets.extend(face_presets)
51
- return presets if presets else ["STANDARD (medium strength)"]
52
-
53
- def create_model_architecture_filter_ui(prefix):
54
- components = {}
55
- ordered_architectures = ARCHITECTURES_CONFIG.get("architecture_order", [])
56
- choices = ["ALL"] + ordered_architectures
57
-
58
- components[f'model_arch_{prefix}'] = gr.Radio(
59
- label="Model Architecture",
60
- choices=choices,
61
- value="ALL",
62
- interactive=True,
63
- visible=True
64
- )
65
- return components
66
-
67
- def create_category_filter_ui(prefix):
68
- valid_cats = list(set(cat for cats in ARCH_CATEGORIES_MAP.values() for cat in cats))
69
- cat_choices = ["ALL"] + sorted(valid_cats)
70
-
71
- components = {}
72
- components[f'model_cat_{prefix}'] = gr.Dropdown(
73
- label="Filter Models",
74
- choices=cat_choices,
75
- value="ALL",
76
- interactive=True,
77
- scale=1,
78
- allow_custom_value=True
79
- )
80
- return components
81
-
82
- def create_base_parameter_ui(prefix, defaults=None):
83
- if defaults is None:
84
- defaults = {}
85
-
86
- components = {}
87
-
88
- with gr.Row():
89
- components[f'aspect_ratio_{prefix}'] = gr.Dropdown(
90
- label="Aspect Ratio",
91
- choices=list(RESOLUTION_MAP.get('sdxl', {}).keys()),
92
- value="1:1 (Square)",
93
- interactive=True,
94
- allow_custom_value=True
95
- )
96
- with gr.Row():
97
- components[f'width_{prefix}'] = gr.Number(label="Width", value=defaults.get('w', 1024), interactive=True)
98
- components[f'height_{prefix}'] = gr.Number(label="Height", value=defaults.get('h', 1024), interactive=True)
99
- with gr.Row():
100
- components[f'sampler_{prefix}'] = gr.Dropdown(
101
- label="Sampler",
102
- choices=SAMPLER_CHOICES,
103
- value=DEFAULT_SAMPLER if DEFAULT_SAMPLER in SAMPLER_CHOICES else (SAMPLER_CHOICES[0] if SAMPLER_CHOICES else 'euler')
104
- )
105
- components[f'scheduler_{prefix}'] = gr.Dropdown(
106
- label="Scheduler",
107
- choices=SCHEDULER_CHOICES,
108
- value=DEFAULT_SCHEDULER if DEFAULT_SCHEDULER in SCHEDULER_CHOICES else (SCHEDULER_CHOICES[0] if SCHEDULER_CHOICES else 'simple')
109
- )
110
- with gr.Row():
111
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=DEFAULT_STEPS)
112
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=DEFAULT_CFG)
113
- with gr.Row():
114
- components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
115
- components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=32, step=1, value=1)
116
- with gr.Row():
117
- components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=60, placeholder="Default: 60s", info="Optional: Set how long to reserve the GPU.")
118
-
119
- return components
120
-
121
-
122
- def create_lora_settings_ui(prefix: str):
123
- components = {}
124
-
125
- lora_rows, lora_sources, lora_ids, lora_scales, lora_uploads = [], [], [], [], []
126
-
127
- with gr.Accordion("LoRA Settings", open=False, visible=('lora' in default_enabled_chains)) as lora_accordion:
128
- components[f'lora_accordion_{prefix}'] = lora_accordion
129
- gr.Markdown("💡 **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `lightx2v/Qwen-Image-Lightning/Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors`).")
130
- components[f'lora_count_state_{prefix}'] = gr.State(1)
131
-
132
- for i in range(MAX_LORAS):
133
- with gr.Row(visible=i==0) as row:
134
- source = gr.Dropdown(label=f"LoRA Source {i+1}", choices=LORA_SOURCE_CHOICES, value=LORA_SOURCE_CHOICES[0], scale=1)
135
- lora_id = gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=2, type="text")
136
- scale = gr.Slider(label=f"Scale", minimum=0.0, maximum=2.0, step=0.05, value=1.0, scale=1)
137
- upload = gr.UploadButton(label="Upload", file_types=[".safetensors"], scale=1)
138
-
139
- lora_rows.append(row)
140
- lora_sources.append(source)
141
- lora_ids.append(lora_id)
142
- lora_scales.append(scale)
143
- lora_uploads.append(upload)
144
-
145
- with gr.Row():
146
- components[f'add_lora_button_{prefix}'] = gr.Button("Add LoRA", variant="secondary")
147
- components[f'delete_lora_button_{prefix}'] = gr.Button("Remove LoRA", variant="secondary", visible=False)
148
-
149
- components[f'lora_rows_{prefix}'] = lora_rows
150
- components[f'lora_sources_{prefix}'] = lora_sources
151
- components[f'lora_ids_{prefix}'] = lora_ids
152
- components[f'lora_scales_{prefix}'] = lora_scales
153
- components[f'lora_uploads_{prefix}'] = lora_uploads
154
-
155
- all_lora_components_flat = []
156
- for i in range(MAX_LORAS):
157
- all_lora_components_flat.extend([lora_sources[i], lora_ids[i], lora_scales[i], lora_uploads[i]])
158
- components[f'all_lora_components_flat_{prefix}'] = all_lora_components_flat
159
-
160
- return components
161
-
162
- def create_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
163
- components = {}
164
- key = lambda name: f"{name}_{prefix}"
165
-
166
- with gr.Accordion("ControlNet Settings", open=False, visible=('controlnet' in default_enabled_chains)) as accordion:
167
- components[key('controlnet_accordion')] = accordion
168
-
169
- cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
170
- components.update({
171
- key('controlnet_rows'): cn_rows,
172
- key('controlnet_images'): images,
173
- key('controlnet_series'): series,
174
- key('controlnet_types'): types,
175
- key('controlnet_strengths'): strengths,
176
- key('controlnet_filepaths'): filepaths
177
- })
178
-
179
- for i in range(max_units):
180
- with gr.Row(visible=(i < 1)) as row:
181
- with gr.Column(scale=1):
182
- images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
183
- with gr.Column(scale=2):
184
- types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
185
- series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
186
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
187
- filepaths.append(gr.State(None))
188
- cn_rows.append(row)
189
-
190
- with gr.Row():
191
- components[key('add_controlnet_button')] = gr.Button("✚ Add ControlNet")
192
- components[key('delete_controlnet_button')] = gr.Button("➖ Delete ControlNet", visible=False)
193
- components[key('controlnet_count_state')] = gr.State(1)
194
-
195
- all_cn_components_flat = []
196
- for i in range(max_units):
197
- all_cn_components_flat.extend([
198
- images[i], types[i], series[i], strengths[i], filepaths[i]
199
- ])
200
- components[key('all_controlnet_components_flat')] = all_cn_components_flat
201
-
202
- return components
203
-
204
- def create_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
205
- components = {}
206
- key = lambda name: f"{name}_{prefix}"
207
-
208
- sdxl_presets = get_ipadapter_presets("SDXL")
209
- default_preset = sdxl_presets[0] if sdxl_presets else None
210
-
211
- with gr.Accordion("IPAdapter Settings", open=False, visible=('ipadapter' in default_enabled_chains)) as accordion:
212
- components[key('ipadapter_accordion')] = accordion
213
- gr.Markdown("💡 **Tip:** Processed using the [cubiq/ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) node.")
214
-
215
- with gr.Row():
216
- components[key('ipadapter_final_preset')] = gr.Dropdown(
217
- label="Preset (for all images)",
218
- choices=sdxl_presets,
219
- value=default_preset,
220
- interactive=True,
221
- allow_custom_value=True
222
- )
223
- components[key('ipadapter_embeds_scaling')] = gr.Dropdown(
224
- label="Embeds Scaling",
225
- choices=['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],
226
- value='V only',
227
- interactive=True
228
- )
229
-
230
- with gr.Row():
231
- components[key('ipadapter_combine_method')] = gr.Dropdown(
232
- label="Combine Method",
233
- choices=["concat", "add", "subtract", "average", "norm average", "max", "min"],
234
- value="concat",
235
- interactive=True
236
- )
237
- components[key('ipadapter_final_weight')] = gr.Slider(label="Final Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True)
238
- components[key('ipadapter_final_lora_strength')] = gr.Slider(label="Final LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False)
239
-
240
- gr.Markdown("---")
241
-
242
- ipa_rows, images, weights, lora_strengths = [], [], [], []
243
- components.update({
244
- key('ipadapter_rows'): ipa_rows,
245
- key('ipadapter_images'): images,
246
- key('ipadapter_weights'): weights,
247
- key('ipadapter_lora_strengths'): lora_strengths
248
- })
249
-
250
- for i in range(max_units):
251
- with gr.Row(visible=(i < 1)) as row:
252
- with gr.Column(scale=1):
253
- images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
254
- with gr.Column(scale=2):
255
- weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
256
- lora_strengths.append(gr.Slider(label="LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False))
257
- ipa_rows.append(row)
258
-
259
- with gr.Row():
260
- components[key('add_ipadapter_button')] = gr.Button("✚ Add IPAdapter")
261
- components[key('delete_ipadapter_button')] = gr.Button("➖ Delete IPAdapter", visible=False)
262
- components[key('ipadapter_count_state')] = gr.State(1)
263
-
264
- all_ipa_components_flat = images + weights + lora_strengths
265
- all_ipa_components_flat += [
266
- components[key('ipadapter_final_preset')],
267
- components[key('ipadapter_final_weight')],
268
- components[key('ipadapter_final_lora_strength')],
269
- components[key('ipadapter_embeds_scaling')],
270
- components[key('ipadapter_combine_method')],
271
- ]
272
- components[key('all_ipadapter_components_flat')] = all_ipa_components_flat
273
-
274
- return components
275
-
276
- def create_flux1_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
277
- components = {}
278
- key = lambda name: f"{name}_{prefix}"
279
-
280
- with gr.Accordion("IPAdapter Settings (FLUX.1)", open=False, visible=('flux1_ipadapter' in default_enabled_chains)) as accordion:
281
- components[key('flux1_ipadapter_accordion')] = accordion
282
- gr.Markdown("💡 **Tip:** Processed using the [Shakker-Labs/ComfyUI-IPAdapter-Flux](https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux) node.")
283
-
284
- ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
285
- components.update({
286
- key('flux1_ipadapter_rows'): ipa_rows,
287
- key('flux1_ipadapter_images'): images,
288
- key('flux1_ipadapter_weights'): weights,
289
- key('flux1_ipadapter_start_percents'): start_percents,
290
- key('flux1_ipadapter_end_percents'): end_percents,
291
- })
292
-
293
- for i in range(max_units):
294
- with gr.Row(visible=(i < 1)) as row:
295
- with gr.Column(scale=1):
296
- images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
297
- with gr.Column(scale=2):
298
- weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True))
299
- with gr.Row():
300
- start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
301
- end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=0.6, interactive=True))
302
- ipa_rows.append(row)
303
-
304
- with gr.Row():
305
- components[key('add_flux1_ipadapter_button')] = gr.Button("✚ Add IPAdapter (FLUX)")
306
- components[key('delete_flux1_ipadapter_button')] = gr.Button("➖ Delete IPAdapter (FLUX)", visible=False)
307
- components[key('flux1_ipadapter_count_state')] = gr.State(1)
308
-
309
- all_flux1_ipa_components_flat = images + weights + start_percents + end_percents
310
- components[key('all_flux1_ipadapter_components_flat')] = all_flux1_ipa_components_flat
311
-
312
- return components
313
-
314
- def create_sd3_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
315
- components = {}
316
- key = lambda name: f"{name}_{prefix}"
317
-
318
- with gr.Accordion("IPAdapter Settings (SD3)", open=False, visible=('sd3_ipadapter' in default_enabled_chains)) as accordion:
319
- components[key('sd3_ipadapter_accordion')] = accordion
320
- gr.Markdown("💡 **Tip:** Processed using the [Slickytail/ComfyUI-InstantX-IPAdapter-SD3](https://github.com/Slickytail/ComfyUI-InstantX-IPAdapter-SD3) node.")
321
-
322
- ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
323
- components.update({
324
- key('sd3_ipadapter_rows'): ipa_rows,
325
- key('sd3_ipadapter_images'): images,
326
- key('sd3_ipadapter_weights'): weights,
327
- key('sd3_ipadapter_start_percents'): start_percents,
328
- key('sd3_ipadapter_end_percents'): end_percents,
329
- })
330
-
331
- for i in range(max_units):
332
- with gr.Row(visible=(i < 1)) as row:
333
- with gr.Column(scale=1):
334
- images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
335
- with gr.Column(scale=2):
336
- weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.5, interactive=True))
337
- with gr.Row():
338
- start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
339
- end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=1.0, interactive=True))
340
- ipa_rows.append(row)
341
-
342
- with gr.Row():
343
- components[key('add_sd3_ipadapter_button')] = gr.Button("✚ Add IPAdapter (SD3)")
344
- components[key('delete_sd3_ipadapter_button')] = gr.Button("➖ Delete IPAdapter (SD3)", visible=False)
345
- components[key('sd3_ipadapter_count_state')] = gr.State(1)
346
-
347
- all_sd3_ipa_components_flat = images + weights + start_percents + end_percents
348
- components[key('all_sd3_ipadapter_components_flat')] = all_sd3_ipa_components_flat
349
-
350
- return components
351
-
352
- def create_style_ui(prefix: str):
353
- components = {}
354
- key = lambda name: f"{name}_{prefix}"
355
-
356
- with gr.Accordion("Style Settings (FLUX.1)", open=False, visible=('style' in default_enabled_chains)) as accordion:
357
- components[key('style_accordion')] = accordion
358
-
359
- style_rows, images, strengths = [], [], []
360
- components.update({
361
- key('style_rows'): style_rows,
362
- key('style_images'): images,
363
- key('style_strengths'): strengths
364
- })
365
-
366
- for i in range(5):
367
- with gr.Row(visible=(i < 1)) as row:
368
- with gr.Column(scale=1):
369
- images.append(gr.Image(label=f"Style Image {i+1}", type="pil", sources=["upload"], height=256))
370
- with gr.Column(scale=2):
371
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
372
- style_rows.append(row)
373
-
374
- with gr.Row():
375
- components[key('add_style_button')] = gr.Button("✚ Add Style (FLUX)")
376
- components[key('delete_style_button')] = gr.Button("➖ Delete Style (FLUX)", visible=False)
377
- components[key('style_count_state')] = gr.State(1)
378
-
379
- all_style_components_flat = images + strengths
380
- components[key('all_style_components_flat')] = all_style_components_flat
381
-
382
- return components
383
-
384
- def create_embedding_ui(prefix: str):
385
- components = {}
386
- key = lambda name: f"{name}_{prefix}"
387
-
388
- with gr.Accordion("Embedding Settings", open=False, visible=('embedding' in default_enabled_chains)) as accordion:
389
- components[key('embedding_accordion')] = accordion
390
- gr.Markdown("💡 **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. For example, entering the Version ID 456 will automatically save the file as \"civitai/456.safetensors\", and you will need to manually enter `embedding:civitai/456` in either your prompt or negative prompt to activate it. When downloading from Hugging Face, please use the format: repo_id/filename.extension or repo_id/folder_path/filename.extension (e.g., ilikebigturtles/lazypos/lazypos.safetensors or ilikebigturtles/lazyneg/lazyneg.safetensors). For Hugging Face files, you will need to enter embedding:huggingface/filename (e.g., entering embedding:huggingface/lazypos in your positive prompt, or embedding:huggingface/lazyneg in your negative prompt) to activate it. For uploaded files, enter `embedding:upload_file/filename` in either your positive prompt or negative prompt to activate it.")
391
-
392
- embedding_rows, sources, ids, files, upload_buttons = [], [], [], [], []
393
- components.update({
394
- key('embedding_rows'): embedding_rows,
395
- key('embeddings_sources'): sources,
396
- key('embeddings_ids'): ids,
397
- key('embeddings_files'): files,
398
- key('embeddings_uploads'): upload_buttons
399
- })
400
-
401
- for i in range(MAX_EMBEDDINGS):
402
- with gr.Row(visible=(i < 1)) as row:
403
- sources.append(gr.Dropdown(label=f"Embedding Source {i+1}", choices=LORA_SOURCE_CHOICES, value="Civitai", scale=1, interactive=True))
404
- ids.append(gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=3, interactive=True, type="text"))
405
- upload_btn = gr.UploadButton("Upload", file_types=[".safetensors"], scale=1)
406
- files.append(gr.State(None))
407
- upload_buttons.append(upload_btn)
408
- embedding_rows.append(row)
409
-
410
- with gr.Row():
411
- components[key('add_embedding_button')] = gr.Button("✚ Add Embedding")
412
- components[key('delete_embedding_button')] = gr.Button("➖ Delete Embedding", visible=False)
413
- components[key('embedding_count_state')] = gr.State(1)
414
-
415
- all_embedding_components_flat = []
416
- for i in range(MAX_EMBEDDINGS):
417
- all_embedding_components_flat.extend([sources[i], ids[i], files[i]])
418
- components[key('all_embedding_components_flat')] = all_embedding_components_flat
419
-
420
- return components
421
-
422
- def create_conditioning_ui(prefix: str):
423
- components = {}
424
- key = lambda name: f"{name}_{prefix}"
425
-
426
- with gr.Accordion("Conditioning Settings", open=False, visible=('conditioning' in default_enabled_chains)) as accordion:
427
- components[key('conditioning_accordion')] = accordion
428
- gr.Markdown("💡 **Tip:** Define rectangular areas and assign specific prompts to them. Coordinates (X, Y) start from the top-left corner.")
429
-
430
- cond_rows, prompts, widths, heights, xs, ys, strengths = [], [], [], [], [], [], []
431
- components.update({
432
- key('conditioning_rows'): cond_rows,
433
- key('conditioning_prompts'): prompts,
434
- key('conditioning_widths'): widths,
435
- key('conditioning_heights'): heights,
436
- key('conditioning_xs'): xs,
437
- key('conditioning_ys'): ys,
438
- key('conditioning_strengths'): strengths
439
- })
440
-
441
- for i in range(MAX_CONDITIONINGS):
442
- with gr.Column(visible=(i < 1)) as row_wrapper:
443
- prompts.append(gr.Textbox(label=f"Area Prompt {i+1}", lines=2, interactive=True))
444
- with gr.Row():
445
- xs.append(gr.Number(label="X", value=0, interactive=True, step=8, scale=1))
446
- ys.append(gr.Number(label="Y", value=0, interactive=True, step=8, scale=1))
447
- widths.append(gr.Number(label="Width", value=512, interactive=True, step=8, scale=1))
448
- heights.append(gr.Number(label="Height", value=512, interactive=True, step=8, scale=1))
449
- strengths.append(gr.Slider(label="Strength", minimum=0.1, maximum=2.0, step=0.05, value=1.0, interactive=True, scale=2))
450
- cond_rows.append(row_wrapper)
451
-
452
- with gr.Row():
453
- components[key('add_conditioning_button')] = gr.Button("✚ Add Area")
454
- components[key('delete_conditioning_button')] = gr.Button("➖ Delete Area", visible=False)
455
- components[key('conditioning_count_state')] = gr.State(1)
456
-
457
- all_cond_components_flat = prompts + widths + heights + xs + ys + strengths
458
- components[key('all_conditioning_components_flat')] = all_cond_components_flat
459
-
460
- return components
461
-
462
- def on_vae_upload(file_obj):
463
- if not file_obj:
464
- return gr.update(), gr.update(), None
465
-
466
- uploaded_filename = save_uploaded_file(file_obj, VAE_DIR)
467
- return uploaded_filename, "Upload File", file_obj
468
-
469
- def create_vae_override_ui(prefix: str):
470
- components = {}
471
- key = lambda name: f"{name}_{prefix}"
472
-
473
- with gr.Accordion("VAE Settings (Override)", open=False, visible=('vae' in default_enabled_chains)) as vae_accordion:
474
- components[key('vae_accordion')] = vae_accordion
475
- gr.Markdown("💡 **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `madebyollin/sdxl-vae-fp16-fix/sdxl_vae.safetensors`).")
476
- with gr.Row():
477
- components[key('vae_source')] = gr.Dropdown(
478
- label="VAE Source",
479
- choices=LORA_SOURCE_CHOICES,
480
- value="Civitai",
481
- scale=1,
482
- interactive=True
483
- )
484
- components[key('vae_id')] = gr.Textbox(
485
- label="Civitai Version ID / HF file / Upload File",
486
- scale=3,
487
- interactive=True,
488
- type="text"
489
- )
490
- upload_btn = gr.UploadButton(
491
- "Upload",
492
- file_types=[".safetensors"],
493
- scale=1
494
- )
495
- components[key('vae_upload_button')] = upload_btn
496
- components[key('vae_file')] = gr.State(None)
497
-
498
- upload_btn.upload(
499
- fn=on_vae_upload,
500
- inputs=[upload_btn],
501
- outputs=[components[key('vae_id')], components[key('vae_source')], components[key('vae_file')]]
502
- )
503
-
504
- return components
505
-
506
- def create_reference_latent_ui(prefix: str, max_units=10):
507
- components = {}
508
- key = lambda name: f"{name}_{prefix}"
509
-
510
- with gr.Accordion("Reference Edit Settings", open=False, visible=('reference_latent' in default_enabled_chains)) as ref_accordion:
511
- components[key('reference_latent_accordion')] = ref_accordion
512
- 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**.")
513
-
514
- ref_image_groups = []
515
- ref_image_inputs = []
516
- with gr.Row():
517
- for i in range(max_units):
518
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
519
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
520
- ref_image_groups.append(img_col)
521
- ref_image_inputs.append(img_comp)
522
-
523
- components[key('reference_latent_rows')] = ref_image_groups
524
- components[key('reference_latent_images')] = ref_image_inputs
525
-
526
- with gr.Row():
527
- components[key('add_reference_latent_button')] = gr.Button("✚ Add Reference Image")
528
- components[key('delete_reference_latent_button')] = gr.Button("➖ Delete Reference Image", visible=False)
529
- components[key('reference_latent_count_state')] = gr.State(1)
530
-
531
- components[key('all_reference_latent_components_flat')] = ref_image_inputs
532
-
533
- return components
534
-
535
- def create_hidream_o1_reference_ui(prefix: str, max_units=10):
536
- components = {}
537
- key = lambda name: f"{name}_{prefix}"
538
-
539
- with gr.Accordion("HiDream-O1 Reference Edit Settings", open=False, visible=('hidream_o1_reference' in default_enabled_chains)) as ref_accordion:
540
- components[key('hidream_o1_reference_accordion')] = ref_accordion
541
- gr.Markdown("💡 **Tip:** Please use **HiDream-O1-Image-Dev** (HiDream-O1-Image will time out), and set the resolution to **4.0MP** (e.g., 2048x2048). In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
542
-
543
- ref_image_groups = []
544
- ref_image_inputs = []
545
- with gr.Row():
546
- for i in range(max_units):
547
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
548
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
549
- ref_image_groups.append(img_col)
550
- ref_image_inputs.append(img_comp)
551
-
552
- components[key('hidream_o1_reference_rows')] = ref_image_groups
553
- components[key('hidream_o1_reference_images')] = ref_image_inputs
554
-
555
- with gr.Row():
556
- components[key('add_hidream_o1_reference_button')] = gr.Button("✚ Add Reference Image")
557
- components[key('delete_hidream_o1_reference_button')] = gr.Button("➖ Delete Reference Image", visible=False)
558
- components[key('hidream_o1_reference_count_state')] = gr.State(1)
559
-
560
- components[key('all_hidream_o1_reference_components_flat')] = ref_image_inputs
561
-
562
- return components
563
-
564
- def create_sensenova_reference_ui(prefix: str, max_units=10):
565
- components = {}
566
- key = lambda name: f"{name}_{prefix}"
567
-
568
- with gr.Accordion("SenseNova Reference Edit Settings", open=False, visible=('sensenova_reference' in default_enabled_chains)) as ref_accordion:
569
- components[key('sensenova_reference_accordion')] = ref_accordion
570
- gr.Markdown("💡 **Tip:** For SenseNova models, 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**.")
571
-
572
- ref_image_groups = []
573
- ref_image_inputs = []
574
- with gr.Row():
575
- for i in range(max_units):
576
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
577
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
578
- ref_image_groups.append(img_col)
579
- ref_image_inputs.append(img_comp)
580
-
581
- components[key('sensenova_reference_rows')] = ref_image_groups
582
- components[key('sensenova_reference_images')] = ref_image_inputs
583
-
584
- with gr.Row():
585
- components[key('add_sensenova_reference_button')] = gr.Button("✚ Add Reference Image")
586
- components[key('delete_sensenova_reference_button')] = gr.Button("➖ Delete Reference Image", visible=False)
587
- components[key('sensenova_reference_count_state')] = gr.State(1)
588
-
589
- components[key('all_sensenova_reference_components_flat')] = ref_image_inputs
590
-
591
- return components
592
-
593
- def create_joyai_reference_ui(prefix: str, max_units=2):
594
- components = {}
595
- key = lambda name: f"{name}_{prefix}"
596
-
597
- with gr.Accordion("JoyAI Reference Edit Settings", open=False, visible=('joyai_image' in default_enabled_chains)) as ref_accordion:
598
- components[key('joyai_reference_accordion')] = ref_accordion
599
- 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).")
600
-
601
- ref_image_groups = []
602
- ref_image_inputs = []
603
- with gr.Row():
604
- for i in range(max_units):
605
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
606
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
607
- ref_image_groups.append(img_col)
608
- ref_image_inputs.append(img_comp)
609
-
610
- components[key('joyai_reference_rows')] = ref_image_groups
611
- components[key('joyai_reference_images')] = ref_image_inputs
612
-
613
- with gr.Row():
614
- components[key('add_joyai_reference_button')] = gr.Button("✚ Add Reference Image")
615
- components[key('delete_joyai_reference_button')] = gr.Button("➖ Delete Reference Image", visible=False)
616
- components[key('joyai_reference_count_state')] = gr.State(1)
617
-
618
- components[key('all_joyai_reference_components_flat')] = ref_image_inputs
619
-
620
- return components
621
-
622
- def create_reference_image_ui(prefix: str, max_units=10):
623
- components = {}
624
- key = lambda name: f"{name}_{prefix}"
625
-
626
- with gr.Accordion("Reference Image Edit Settings", open=False, visible=('reference_image' in default_enabled_chains)) as ref_accordion:
627
- components[key('reference_image_accordion')] = ref_accordion
628
- 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**.")
629
-
630
- ref_image_groups = []
631
- ref_image_inputs = []
632
- with gr.Row():
633
- for i in range(max_units):
634
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
635
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
636
- ref_image_groups.append(img_col)
637
- ref_image_inputs.append(img_comp)
638
-
639
- components[key('reference_image_rows')] = ref_image_groups
640
- components[key('reference_image_images')] = ref_image_inputs
641
-
642
- with gr.Row():
643
- components[key('add_reference_image_button')] = gr.Button("✚ Add Reference Image")
644
- components[key('delete_reference_image_button')] = gr.Button("➖ Delete Reference Image", visible=False)
645
- components[key('reference_image_count_state')] = gr.State(1)
646
-
647
- components[key('all_reference_image_components_flat')] = ref_image_inputs
648
-
649
- return components
650
-
651
- def create_pid_ui(prefix: str):
652
- components = {}
653
- key = lambda name: f"{name}_{prefix}"
654
-
655
- with gr.Accordion("PiD Settings", open=False, visible=('pid' in default_enabled_chains)) as pid_accordion:
656
- components[key('pid_accordion')] = pid_accordion
657
- gr.Markdown("💡 **Tip:** Use PiD (Pixel Diffusion Decoder) instead of the VAE Decoder for 4x decoding.")
658
- with gr.Row():
659
- components[key('pid_settings')] = gr.Dropdown(
660
- label="PiD Mode",
661
- choices=["OFF", "ON"],
662
- value="OFF",
663
- interactive=True
664
- )
665
-
666
- return components
667
-
668
- def create_krea2_identity_edit_ui(prefix: str, max_units=2):
669
- components = {}
670
- key = lambda name: f"{name}_{prefix}"
671
-
672
- with gr.Accordion("Krea2 Identity Edit Settings", open=False, visible=('krea2_identity_edit' in default_enabled_chains)) as ref_accordion:
673
- components[key('krea2_identity_edit_accordion')] = ref_accordion
674
- gr.Markdown("💡 **Tip:** Processed using the [lbouaraba/comfyui-krea2edit](https://github.com/lbouaraba/comfyui-krea2edit) node. (Krea-2-Turbo recommended, Krea-2-Raw 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**.")
675
-
676
- ref_image_groups = []
677
- ref_image_inputs = []
678
- with gr.Row():
679
- for i in range(max_units):
680
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
681
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
682
- ref_image_groups.append(img_col)
683
- ref_image_inputs.append(img_comp)
684
-
685
- components[key('krea2_identity_edit_rows')] = ref_image_groups
686
- components[key('krea2_identity_edit_images')] = ref_image_inputs
687
-
688
- with gr.Row():
689
- components[key('add_krea2_identity_edit_button')] = gr.Button("✚ Add Reference Image")
690
- components[key('delete_krea2_identity_edit_button')] = gr.Button("➖ Delete Reference Image", visible=False)
691
- components[key('krea2_identity_edit_count_state')] = gr.State(1)
692
-
693
- components[key('all_krea2_identity_edit_components_flat')] = ref_image_inputs
694
-
695
- return components
696
-
697
- def create_qwen_image_edit_ui(prefix: str, max_units=3):
698
- components = {}
699
- key = lambda name: f"{name}_{prefix}"
700
-
701
- with gr.Accordion("Qwen-Image Edit Settings", open=False, visible=('qwen_image_edit' in default_enabled_chains)) as ref_accordion:
702
- components[key('qwen_image_edit_accordion')] = ref_accordion
703
- gr.Markdown("💡 **Tip:** (lightx2v/Qwen-Image-Edit-2511-Lightning recommended) In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
704
-
705
- ref_image_groups = []
706
- ref_image_inputs = []
707
- with gr.Row():
708
- for i in range(max_units):
709
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
710
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
711
- ref_image_groups.append(img_col)
712
- ref_image_inputs.append(img_comp)
713
-
714
- components[key('qwen_image_edit_rows')] = ref_image_groups
715
- components[key('qwen_image_edit_images')] = ref_image_inputs
716
-
717
- with gr.Row():
718
- components[key('add_qwen_image_edit_button')] = gr.Button("✚ Add Reference Image")
719
- components[key('delete_qwen_image_edit_button')] = gr.Button("➖ Delete Reference Image", visible=False)
720
- components[key('qwen_image_edit_count_state')] = gr.State(1)
721
-
722
- components[key('all_qwen_image_edit_components_flat')] = ref_image_inputs
723
-
724
- return components
725
-
726
- def create_krea2_reference_edit_ui(prefix: str, max_units=3):
727
- components = {}
728
- key = lambda name: f"{name}_{prefix}"
729
-
730
- with gr.Accordion("Krea2 Style Reference Edit Settings", open=False, visible=('krea2_style_reference' in default_enabled_chains)) as ref_accordion:
731
- components[key('krea2_reference_edit_accordion')] = ref_accordion
732
- gr.Markdown("💡 **Tip:** (Krea-2-Turbo recommended) Add style reference images to perform style reference editing.")
733
-
734
- ref_image_groups = []
735
- ref_image_inputs = []
736
- with gr.Row():
737
- for i in range(max_units):
738
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
739
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
740
- ref_image_groups.append(img_col)
741
- ref_image_inputs.append(img_comp)
742
-
743
- components[key('krea2_reference_edit_rows')] = ref_image_groups
744
- components[key('krea2_reference_edit_images')] = ref_image_inputs
745
-
746
- with gr.Row():
747
- components[key('add_krea2_reference_edit_button')] = gr.Button("✚ Add Reference Image")
748
- components[key('delete_krea2_reference_edit_button')] = gr.Button("➖ Delete Reference Image", visible=False)
749
- components[key('krea2_reference_edit_count_state')] = gr.State(1)
750
-
751
- components[key('all_krea2_reference_edit_components_flat')] = ref_image_inputs
752
-
753
- return components
754
-
755
-
756
- def create_qwen_image_2_1_prompt_enhancer_ui(prefix: str):
757
- components = {}
758
- key = lambda name: f"{name}_{prefix}"
759
-
760
- with gr.Accordion("Qwen-Image-2.1 Prompt Enhancer Settings", open=False, visible=('qwen_image_2_1_prompt_enhancer' in default_enabled_chains)) as enhancer_accordion:
761
- components[key('qwen_image_2_1_prompt_enhancer_accordion')] = enhancer_accordion
762
- gr.Markdown("💡 **Tip:** Automatically selects between [Qwen-Image-2.1-PE-T2I](https://huggingface.co/Qwen/Qwen-Image-2.1-PE-T2I) and [Qwen-Image-2.1-PE-I2I](https://huggingface.co/Qwen/Qwen-Image-2.1-PE-I2I) for prompt enhancement based on input context.")
763
- with gr.Row():
764
- components[key('qwen_image_2_1_prompt_enhancer_enable')] = gr.Checkbox(
765
- label="Enable Qwen-Image-2.1 Prompt Enhancer",
766
- value=False,
767
- interactive=True,
768
- scale=2
769
- )
770
- components[key('qwen_image_2_1_prompt_enhancer_thinking')] = gr.Checkbox(
771
- label="Enable Thinking (Requires longer ZeroGPU Duration)",
772
- value=False,
773
- interactive=True,
774
- scale=2
775
- )
776
- components[key('qwen_image_2_1_prompt_enhancer_max_length')] = gr.Number(
777
- label="Max Length",
778
- value=4096,
779
- precision=0,
780
- minimum=16,
781
- step=16,
782
- interactive=True,
783
- scale=1
784
- )
785
-
786
- return components
787
-
788
-
789
- DEFAULT_MING_IMAGE_SYSTEM_PROMPT = (
790
- "You are a senior visual designer and image-prompt engineer. Expand the user's request into one precise, high-resolution Figma-style caption. Return only one JSON object. "
791
- "Use exactly two top-level keys. `canvas_settings` contains exactly `aspect_ratio`, `ambient_lighting`, and `image_style`. `layers` lists visible groups from background to topmost overlay. Every layer contains exactly `description`, `coordinates`, `hierarchy_and_relation`, and `color_specs`; `color_specs` is an array of hex colors. "
792
- '`coordinates` MUST be one string, never an object or array, in exactly this form: `"cx: 0.500, cy: 0.500, w: 1.000, h: 1.000"`. Values are normalized; each bbox encloses its complete owned object and stays inside the canvas. '
793
- "A layer is one selectable visible semantic group: background, full person, coherent object, panel, card, row, or text block. Prefer the fewest groups that preserve the layout. Keep people and objects intact. Never create invisible parents, guides, placeholders, empty layers, duplicate summaries, or multiple owners for one element. "
794
- "Preserve every user-supplied rendered string character-for-character and as one contiguous string. Unless multiple visible copies are requested, it must occur exactly once across all `description` fields and zero times in `hierarchy_and_relation`. Quote it only where describing its visible rendering; refer to the related subject elsewhere with unquoted semantic wording. Enumerate intended copy, invent extra copy sparingly, and never hide content behind \"other text\", \"remaining labels\", or \"etc.\" "
795
- "Describe concrete composition, typography, materials, texture, lighting, pose, and camera treatment without literary filler. Use `hierarchy_and_relation` only for ownership, alignment, containment, stacking, and occlusion. "
796
- "Infer structured layouts first. Use one complete layer per card and state its row and column. A compact secondary table may be one layer only if every header and cell is listed; otherwise use a visible shared frame when present, one complete header, and one complete layer per body row, binding values to columns and stating blanks. Enumerate sequences, schedules, spans, gaps, and vacant tracks in visual order. Do not mistake ordinary alignment for a table. "
797
- "Silently verify schema, string coordinates, Z-order, exact-text counts, geometry, bbox validity, and completeness."
798
- )
799
-
800
-
801
- def create_ming_image_prompt_enhancer_ui(prefix: str):
802
- components = {}
803
- key = lambda name: f"{name}_{prefix}"
804
-
805
- with gr.Accordion("Ming-Image Prompt Enhancer Settings", open=False, visible=('ming_image_prompt_enhancer' in default_enabled_chains)) as enhancer_accordion:
806
- components[key('ming_image_prompt_enhancer_accordion')] = enhancer_accordion
807
- gr.Markdown("💡 **Tip:** Leverages [Qwen3.8-27B](https://huggingface.co/Qwen/Qwen3.8-27B) guided by the official [System Prompt](https://github.com/inclusionAI/Ming-Image#text-to-image-prompt-rewriting) to automatically enhance prompts into structured layouts based on input context.")
808
- with gr.Row():
809
- components[key('ming_image_prompt_enhancer_enable')] = gr.Checkbox(
810
- label="Enable Ming-Image Prompt Enhancer",
811
- value=False,
812
- interactive=True,
813
- scale=2
814
- )
815
- components[key('ming_image_prompt_enhancer_thinking')] = gr.Checkbox(
816
- label="Enable Thinking (Requires longer ZeroGPU Duration)",
817
- value=False,
818
- interactive=True,
819
- scale=2
820
- )
821
- components[key('ming_image_prompt_enhancer_max_length')] = gr.Number(
822
- label="Max Length",
823
- value=4096,
824
- precision=0,
825
- minimum=16,
826
- step=16,
827
- interactive=True,
828
- scale=1
829
- )
830
- components[key('ming_image_prompt_enhancer_system_prompt')] = gr.Textbox(
831
- label="System Prompt",
832
- value=DEFAULT_MING_IMAGE_SYSTEM_PROMPT,
833
- lines=4,
834
- max_lines=12,
835
- interactive=True
836
- )
837
-
838
- return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
3
+ from core.settings import (
4
+ MAX_LORAS, LORA_SOURCE_CHOICES, MAX_EMBEDDINGS, MAX_CONDITIONINGS,
5
+ MAX_CONTROLNETS, 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
+ )
9
+ import yaml
10
+ import os
11
+ from functools import lru_cache
12
+ from utils.app_utils import save_uploaded_file
13
+
14
+ default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOINT else None
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', {})
22
+ DEFAULT_STEPS = default_vals.get('steps', 20)
23
+ DEFAULT_CFG = default_vals.get('cfg', 5.0)
24
+ DEFAULT_SAMPLER = default_vals.get('sampler_name', 'euler')
25
+ DEFAULT_SCHEDULER = default_vals.get('scheduler', 'simple')
26
+ DEFAULT_POS_PROMPT = default_vals.get('positive_prompt', '')
27
+ DEFAULT_NEG_PROMPT = default_vals.get('negative_prompt', '')
28
+
29
+ @lru_cache(maxsize=1)
30
+ def get_ipadapter_config_from_yaml():
31
+ try:
32
+ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
33
+ _IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
34
+ with open(_IPADAPTER_LIST_PATH, 'r', encoding='utf-8') as f:
35
+ config = yaml.safe_load(f)
36
+ return config
37
+ except Exception as e:
38
+ print(f"Warning: Could not load ipadapter.yaml for UI components: {e}")
39
+ return {}
40
+
41
+ def get_ipadapter_presets(arch="SDXL"):
42
+ config = get_ipadapter_config_from_yaml()
43
+ presets = []
44
+ if config:
45
+ std_presets = config.get("IPAdapter_presets", {}).get(arch, [])
46
+ face_presets = config.get("IPAdapter_FaceID_presets", {}).get(arch, [])
47
+ if std_presets:
48
+ presets.extend(std_presets)
49
+ if face_presets:
50
+ presets.extend(face_presets)
51
+ return presets if presets else ["STANDARD (medium strength)"]
52
+
53
+ def create_model_architecture_filter_ui(prefix):
54
+ components = {}
55
+ ordered_architectures = ARCHITECTURES_CONFIG.get("architecture_order", [])
56
+ choices = ["ALL"] + ordered_architectures
57
+
58
+ components[f'model_arch_{prefix}'] = gr.Radio(
59
+ label="Model Architecture",
60
+ choices=choices,
61
+ value="ALL",
62
+ interactive=True,
63
+ visible=True
64
+ )
65
+ return components
66
+
67
+ def create_category_filter_ui(prefix):
68
+ valid_cats = list(set(cat for cats in ARCH_CATEGORIES_MAP.values() for cat in cats))
69
+ cat_choices = ["ALL"] + sorted(valid_cats)
70
+
71
+ components = {}
72
+ components[f'model_cat_{prefix}'] = gr.Dropdown(
73
+ label="Filter Models",
74
+ choices=cat_choices,
75
+ value="ALL",
76
+ interactive=True,
77
+ scale=1,
78
+ allow_custom_value=True
79
+ )
80
+ return components
81
+
82
+ def create_base_parameter_ui(prefix, defaults=None):
83
+ if defaults is None:
84
+ defaults = {}
85
+
86
+ components = {}
87
+
88
+ with gr.Row():
89
+ components[f'aspect_ratio_{prefix}'] = gr.Dropdown(
90
+ label="Aspect Ratio",
91
+ choices=list(RESOLUTION_MAP.get('sdxl', {}).keys()),
92
+ value="1:1 (Square)",
93
+ interactive=True,
94
+ allow_custom_value=True
95
+ )
96
+ with gr.Row():
97
+ components[f'width_{prefix}'] = gr.Number(label="Width", value=defaults.get('w', 1024), interactive=True)
98
+ components[f'height_{prefix}'] = gr.Number(label="Height", value=defaults.get('h', 1024), interactive=True)
99
+ with gr.Row():
100
+ components[f'sampler_{prefix}'] = gr.Dropdown(
101
+ label="Sampler",
102
+ choices=SAMPLER_CHOICES,
103
+ value=DEFAULT_SAMPLER if DEFAULT_SAMPLER in SAMPLER_CHOICES else (SAMPLER_CHOICES[0] if SAMPLER_CHOICES else 'euler')
104
+ )
105
+ components[f'scheduler_{prefix}'] = gr.Dropdown(
106
+ label="Scheduler",
107
+ choices=SCHEDULER_CHOICES,
108
+ value=DEFAULT_SCHEDULER if DEFAULT_SCHEDULER in SCHEDULER_CHOICES else (SCHEDULER_CHOICES[0] if SCHEDULER_CHOICES else 'simple')
109
+ )
110
+ with gr.Row():
111
+ components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=DEFAULT_STEPS)
112
+ components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=DEFAULT_CFG)
113
+ with gr.Row():
114
+ components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
115
+ components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
116
+ with gr.Row():
117
+ components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=60, placeholder="Default: 60s", info="Optional: Set how long to reserve the GPU.")
118
+
119
+ return components
120
+
121
+
122
+ def create_lora_settings_ui(prefix: str):
123
+ components = {}
124
+
125
+ lora_rows, lora_sources, lora_ids, lora_scales, lora_uploads = [], [], [], [], []
126
+
127
+ with gr.Accordion("LoRA Settings", open=False, visible=('lora' in default_enabled_chains)) as lora_accordion:
128
+ components[f'lora_accordion_{prefix}'] = lora_accordion
129
+ gr.Markdown("💡 **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `lightx2v/Qwen-Image-Lightning/Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors`).")
130
+ components[f'lora_count_state_{prefix}'] = gr.State(1)
131
+
132
+ for i in range(MAX_LORAS):
133
+ with gr.Row(visible=i==0) as row:
134
+ source = gr.Dropdown(label=f"LoRA Source {i+1}", choices=LORA_SOURCE_CHOICES, value=LORA_SOURCE_CHOICES[0], scale=1)
135
+ lora_id = gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=2, type="text")
136
+ scale = gr.Slider(label=f"Scale", minimum=0.0, maximum=2.0, step=0.05, value=1.0, scale=1)
137
+ upload = gr.UploadButton(label="Upload", file_types=[".safetensors"], scale=1)
138
+
139
+ lora_rows.append(row)
140
+ lora_sources.append(source)
141
+ lora_ids.append(lora_id)
142
+ lora_scales.append(scale)
143
+ lora_uploads.append(upload)
144
+
145
+ with gr.Row():
146
+ components[f'add_lora_button_{prefix}'] = gr.Button("Add LoRA", variant="secondary")
147
+ components[f'delete_lora_button_{prefix}'] = gr.Button("Remove LoRA", variant="secondary", visible=False)
148
+
149
+ components[f'lora_rows_{prefix}'] = lora_rows
150
+ components[f'lora_sources_{prefix}'] = lora_sources
151
+ components[f'lora_ids_{prefix}'] = lora_ids
152
+ components[f'lora_scales_{prefix}'] = lora_scales
153
+ components[f'lora_uploads_{prefix}'] = lora_uploads
154
+
155
+ all_lora_components_flat = []
156
+ for i in range(MAX_LORAS):
157
+ all_lora_components_flat.extend([lora_sources[i], lora_ids[i], lora_scales[i], lora_uploads[i]])
158
+ components[f'all_lora_components_flat_{prefix}'] = all_lora_components_flat
159
+
160
+ return components
161
+
162
+ def create_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
163
+ components = {}
164
+ key = lambda name: f"{name}_{prefix}"
165
+
166
+ with gr.Accordion("ControlNet Settings", open=False, visible=('controlnet' in default_enabled_chains)) as accordion:
167
+ components[key('controlnet_accordion')] = accordion
168
+
169
+ cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
170
+ components.update({
171
+ key('controlnet_rows'): cn_rows,
172
+ key('controlnet_images'): images,
173
+ key('controlnet_series'): series,
174
+ key('controlnet_types'): types,
175
+ key('controlnet_strengths'): strengths,
176
+ key('controlnet_filepaths'): filepaths
177
+ })
178
+
179
+ for i in range(max_units):
180
+ with gr.Row(visible=(i < 1)) as row:
181
+ with gr.Column(scale=1):
182
+ images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
183
+ with gr.Column(scale=2):
184
+ types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
185
+ series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
186
+ strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
187
+ filepaths.append(gr.State(None))
188
+ cn_rows.append(row)
189
+
190
+ with gr.Row():
191
+ components[key('add_controlnet_button')] = gr.Button("✚ Add ControlNet")
192
+ components[key('delete_controlnet_button')] = gr.Button("➖ Delete ControlNet", visible=False)
193
+ components[key('controlnet_count_state')] = gr.State(1)
194
+
195
+ all_cn_components_flat = []
196
+ for i in range(max_units):
197
+ all_cn_components_flat.extend([
198
+ images[i], types[i], series[i], strengths[i], filepaths[i]
199
+ ])
200
+ components[key('all_controlnet_components_flat')] = all_cn_components_flat
201
+
202
+ return components
203
+
204
+ def create_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
205
+ components = {}
206
+ key = lambda name: f"{name}_{prefix}"
207
+
208
+ sdxl_presets = get_ipadapter_presets("SDXL")
209
+ default_preset = sdxl_presets[0] if sdxl_presets else None
210
+
211
+ with gr.Accordion("IPAdapter Settings", open=False, visible=('ipadapter' in default_enabled_chains)) as accordion:
212
+ components[key('ipadapter_accordion')] = accordion
213
+ gr.Markdown("💡 **Tip:** Processed using the [cubiq/ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) node.")
214
+
215
+ with gr.Row():
216
+ components[key('ipadapter_final_preset')] = gr.Dropdown(
217
+ label="Preset (for all images)",
218
+ choices=sdxl_presets,
219
+ value=default_preset,
220
+ interactive=True,
221
+ allow_custom_value=True
222
+ )
223
+ components[key('ipadapter_embeds_scaling')] = gr.Dropdown(
224
+ label="Embeds Scaling",
225
+ choices=['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],
226
+ value='V only',
227
+ interactive=True
228
+ )
229
+
230
+ with gr.Row():
231
+ components[key('ipadapter_combine_method')] = gr.Dropdown(
232
+ label="Combine Method",
233
+ choices=["concat", "add", "subtract", "average", "norm average", "max", "min"],
234
+ value="concat",
235
+ interactive=True
236
+ )
237
+ components[key('ipadapter_final_weight')] = gr.Slider(label="Final Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True)
238
+ components[key('ipadapter_final_lora_strength')] = gr.Slider(label="Final LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False)
239
+
240
+ gr.Markdown("---")
241
+
242
+ ipa_rows, images, weights, lora_strengths = [], [], [], []
243
+ components.update({
244
+ key('ipadapter_rows'): ipa_rows,
245
+ key('ipadapter_images'): images,
246
+ key('ipadapter_weights'): weights,
247
+ key('ipadapter_lora_strengths'): lora_strengths
248
+ })
249
+
250
+ for i in range(max_units):
251
+ with gr.Row(visible=(i < 1)) as row:
252
+ with gr.Column(scale=1):
253
+ images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
254
+ with gr.Column(scale=2):
255
+ weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
256
+ lora_strengths.append(gr.Slider(label="LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False))
257
+ ipa_rows.append(row)
258
+
259
+ with gr.Row():
260
+ components[key('add_ipadapter_button')] = gr.Button("✚ Add IPAdapter")
261
+ components[key('delete_ipadapter_button')] = gr.Button("➖ Delete IPAdapter", visible=False)
262
+ components[key('ipadapter_count_state')] = gr.State(1)
263
+
264
+ all_ipa_components_flat = images + weights + lora_strengths
265
+ all_ipa_components_flat += [
266
+ components[key('ipadapter_final_preset')],
267
+ components[key('ipadapter_final_weight')],
268
+ components[key('ipadapter_final_lora_strength')],
269
+ components[key('ipadapter_embeds_scaling')],
270
+ components[key('ipadapter_combine_method')],
271
+ ]
272
+ components[key('all_ipadapter_components_flat')] = all_ipa_components_flat
273
+
274
+ return components
275
+
276
+ def create_flux1_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
277
+ components = {}
278
+ key = lambda name: f"{name}_{prefix}"
279
+
280
+ with gr.Accordion("IPAdapter Settings (FLUX.1)", open=False, visible=('flux1_ipadapter' in default_enabled_chains)) as accordion:
281
+ components[key('flux1_ipadapter_accordion')] = accordion
282
+ gr.Markdown("💡 **Tip:** Processed using the [Shakker-Labs/ComfyUI-IPAdapter-Flux](https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux) node.")
283
+
284
+ ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
285
+ components.update({
286
+ key('flux1_ipadapter_rows'): ipa_rows,
287
+ key('flux1_ipadapter_images'): images,
288
+ key('flux1_ipadapter_weights'): weights,
289
+ key('flux1_ipadapter_start_percents'): start_percents,
290
+ key('flux1_ipadapter_end_percents'): end_percents,
291
+ })
292
+
293
+ for i in range(max_units):
294
+ with gr.Row(visible=(i < 1)) as row:
295
+ with gr.Column(scale=1):
296
+ images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
297
+ with gr.Column(scale=2):
298
+ weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True))
299
+ with gr.Row():
300
+ start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
301
+ end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=0.6, interactive=True))
302
+ ipa_rows.append(row)
303
+
304
+ with gr.Row():
305
+ components[key('add_flux1_ipadapter_button')] = gr.Button("✚ Add IPAdapter (FLUX)")
306
+ components[key('delete_flux1_ipadapter_button')] = gr.Button("➖ Delete IPAdapter (FLUX)", visible=False)
307
+ components[key('flux1_ipadapter_count_state')] = gr.State(1)
308
+
309
+ all_flux1_ipa_components_flat = images + weights + start_percents + end_percents
310
+ components[key('all_flux1_ipadapter_components_flat')] = all_flux1_ipa_components_flat
311
+
312
+ return components
313
+
314
+ def create_sd3_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
315
+ components = {}
316
+ key = lambda name: f"{name}_{prefix}"
317
+
318
+ with gr.Accordion("IPAdapter Settings (SD3)", open=False, visible=('sd3_ipadapter' in default_enabled_chains)) as accordion:
319
+ components[key('sd3_ipadapter_accordion')] = accordion
320
+ gr.Markdown("💡 **Tip:** Processed using the [Slickytail/ComfyUI-InstantX-IPAdapter-SD3](https://github.com/Slickytail/ComfyUI-InstantX-IPAdapter-SD3) node.")
321
+
322
+ ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
323
+ components.update({
324
+ key('sd3_ipadapter_rows'): ipa_rows,
325
+ key('sd3_ipadapter_images'): images,
326
+ key('sd3_ipadapter_weights'): weights,
327
+ key('sd3_ipadapter_start_percents'): start_percents,
328
+ key('sd3_ipadapter_end_percents'): end_percents,
329
+ })
330
+
331
+ for i in range(max_units):
332
+ with gr.Row(visible=(i < 1)) as row:
333
+ with gr.Column(scale=1):
334
+ images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
335
+ with gr.Column(scale=2):
336
+ weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.5, interactive=True))
337
+ with gr.Row():
338
+ start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
339
+ end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=1.0, interactive=True))
340
+ ipa_rows.append(row)
341
+
342
+ with gr.Row():
343
+ components[key('add_sd3_ipadapter_button')] = gr.Button("✚ Add IPAdapter (SD3)")
344
+ components[key('delete_sd3_ipadapter_button')] = gr.Button("➖ Delete IPAdapter (SD3)", visible=False)
345
+ components[key('sd3_ipadapter_count_state')] = gr.State(1)
346
+
347
+ all_sd3_ipa_components_flat = images + weights + start_percents + end_percents
348
+ components[key('all_sd3_ipadapter_components_flat')] = all_sd3_ipa_components_flat
349
+
350
+ return components
351
+
352
+ def create_style_ui(prefix: str):
353
+ components = {}
354
+ key = lambda name: f"{name}_{prefix}"
355
+
356
+ with gr.Accordion("Style Settings (FLUX.1)", open=False, visible=('style' in default_enabled_chains)) as accordion:
357
+ components[key('style_accordion')] = accordion
358
+
359
+ style_rows, images, strengths = [], [], []
360
+ components.update({
361
+ key('style_rows'): style_rows,
362
+ key('style_images'): images,
363
+ key('style_strengths'): strengths
364
+ })
365
+
366
+ for i in range(5):
367
+ with gr.Row(visible=(i < 1)) as row:
368
+ with gr.Column(scale=1):
369
+ images.append(gr.Image(label=f"Style Image {i+1}", type="pil", sources=["upload"], height=256))
370
+ with gr.Column(scale=2):
371
+ strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
372
+ style_rows.append(row)
373
+
374
+ with gr.Row():
375
+ components[key('add_style_button')] = gr.Button("✚ Add Style (FLUX)")
376
+ components[key('delete_style_button')] = gr.Button("➖ Delete Style (FLUX)", visible=False)
377
+ components[key('style_count_state')] = gr.State(1)
378
+
379
+ all_style_components_flat = images + strengths
380
+ components[key('all_style_components_flat')] = all_style_components_flat
381
+
382
+ return components
383
+
384
+ def create_embedding_ui(prefix: str):
385
+ components = {}
386
+ key = lambda name: f"{name}_{prefix}"
387
+
388
+ with gr.Accordion("Embedding Settings", open=False, visible=('embedding' in default_enabled_chains)) as accordion:
389
+ components[key('embedding_accordion')] = accordion
390
+ gr.Markdown("💡 **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. For example, entering the Version ID 456 will automatically save the file as \"civitai/456.safetensors\", and you will need to manually enter `embedding:civitai/456` in either your prompt or negative prompt to activate it. When downloading from Hugging Face, please use the format: repo_id/filename.extension or repo_id/folder_path/filename.extension (e.g., ilikebigturtles/lazypos/lazypos.safetensors or ilikebigturtles/lazyneg/lazyneg.safetensors). For Hugging Face files, you will need to enter embedding:huggingface/filename (e.g., entering embedding:huggingface/lazypos in your positive prompt, or embedding:huggingface/lazyneg in your negative prompt) to activate it. For uploaded files, enter `embedding:upload_file/filename` in either your positive prompt or negative prompt to activate it.")
391
+
392
+ embedding_rows, sources, ids, files, upload_buttons = [], [], [], [], []
393
+ components.update({
394
+ key('embedding_rows'): embedding_rows,
395
+ key('embeddings_sources'): sources,
396
+ key('embeddings_ids'): ids,
397
+ key('embeddings_files'): files,
398
+ key('embeddings_uploads'): upload_buttons
399
+ })
400
+
401
+ for i in range(MAX_EMBEDDINGS):
402
+ with gr.Row(visible=(i < 1)) as row:
403
+ sources.append(gr.Dropdown(label=f"Embedding Source {i+1}", choices=LORA_SOURCE_CHOICES, value="Civitai", scale=1, interactive=True))
404
+ ids.append(gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=3, interactive=True, type="text"))
405
+ upload_btn = gr.UploadButton("Upload", file_types=[".safetensors"], scale=1)
406
+ files.append(gr.State(None))
407
+ upload_buttons.append(upload_btn)
408
+ embedding_rows.append(row)
409
+
410
+ with gr.Row():
411
+ components[key('add_embedding_button')] = gr.Button("✚ Add Embedding")
412
+ components[key('delete_embedding_button')] = gr.Button("➖ Delete Embedding", visible=False)
413
+ components[key('embedding_count_state')] = gr.State(1)
414
+
415
+ all_embedding_components_flat = []
416
+ for i in range(MAX_EMBEDDINGS):
417
+ all_embedding_components_flat.extend([sources[i], ids[i], files[i]])
418
+ components[key('all_embedding_components_flat')] = all_embedding_components_flat
419
+
420
+ return components
421
+
422
+ def create_conditioning_ui(prefix: str):
423
+ components = {}
424
+ key = lambda name: f"{name}_{prefix}"
425
+
426
+ with gr.Accordion("Conditioning Settings", open=False, visible=('conditioning' in default_enabled_chains)) as accordion:
427
+ components[key('conditioning_accordion')] = accordion
428
+ gr.Markdown("💡 **Tip:** Define rectangular areas and assign specific prompts to them. Coordinates (X, Y) start from the top-left corner.")
429
+
430
+ cond_rows, prompts, widths, heights, xs, ys, strengths = [], [], [], [], [], [], []
431
+ components.update({
432
+ key('conditioning_rows'): cond_rows,
433
+ key('conditioning_prompts'): prompts,
434
+ key('conditioning_widths'): widths,
435
+ key('conditioning_heights'): heights,
436
+ key('conditioning_xs'): xs,
437
+ key('conditioning_ys'): ys,
438
+ key('conditioning_strengths'): strengths
439
+ })
440
+
441
+ for i in range(MAX_CONDITIONINGS):
442
+ with gr.Column(visible=(i < 1)) as row_wrapper:
443
+ prompts.append(gr.Textbox(label=f"Area Prompt {i+1}", lines=2, interactive=True))
444
+ with gr.Row():
445
+ xs.append(gr.Number(label="X", value=0, interactive=True, step=8, scale=1))
446
+ ys.append(gr.Number(label="Y", value=0, interactive=True, step=8, scale=1))
447
+ widths.append(gr.Number(label="Width", value=512, interactive=True, step=8, scale=1))
448
+ heights.append(gr.Number(label="Height", value=512, interactive=True, step=8, scale=1))
449
+ strengths.append(gr.Slider(label="Strength", minimum=0.1, maximum=2.0, step=0.05, value=1.0, interactive=True, scale=2))
450
+ cond_rows.append(row_wrapper)
451
+
452
+ with gr.Row():
453
+ components[key('add_conditioning_button')] = gr.Button("✚ Add Area")
454
+ components[key('delete_conditioning_button')] = gr.Button("➖ Delete Area", visible=False)
455
+ components[key('conditioning_count_state')] = gr.State(1)
456
+
457
+ all_cond_components_flat = prompts + widths + heights + xs + ys + strengths
458
+ components[key('all_conditioning_components_flat')] = all_cond_components_flat
459
+
460
+ return components
461
+
462
+ def on_vae_upload(file_obj):
463
+ if not file_obj:
464
+ return gr.update(), gr.update(), None
465
+
466
+ uploaded_filename = save_uploaded_file(file_obj, VAE_DIR)
467
+ return uploaded_filename, "Upload File", file_obj
468
+
469
+ def create_vae_override_ui(prefix: str):
470
+ components = {}
471
+ key = lambda name: f"{name}_{prefix}"
472
+
473
+ with gr.Accordion("VAE Settings (Override)", open=False, visible=('vae' in default_enabled_chains)) as vae_accordion:
474
+ components[key('vae_accordion')] = vae_accordion
475
+ gr.Markdown("💡 **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `madebyollin/sdxl-vae-fp16-fix/sdxl_vae.safetensors`).")
476
+ with gr.Row():
477
+ components[key('vae_source')] = gr.Dropdown(
478
+ label="VAE Source",
479
+ choices=LORA_SOURCE_CHOICES,
480
+ value="Civitai",
481
+ scale=1,
482
+ interactive=True
483
+ )
484
+ components[key('vae_id')] = gr.Textbox(
485
+ label="Civitai Version ID / HF file / Upload File",
486
+ scale=3,
487
+ interactive=True,
488
+ type="text"
489
+ )
490
+ upload_btn = gr.UploadButton(
491
+ "Upload",
492
+ file_types=[".safetensors"],
493
+ scale=1
494
+ )
495
+ components[key('vae_upload_button')] = upload_btn
496
+ components[key('vae_file')] = gr.State(None)
497
+
498
+ upload_btn.upload(
499
+ fn=on_vae_upload,
500
+ inputs=[upload_btn],
501
+ outputs=[components[key('vae_id')], components[key('vae_source')], components[key('vae_file')]]
502
+ )
503
+
504
+ return components
505
+
506
+ def create_reference_latent_ui(prefix: str, max_units=10):
507
+ components = {}
508
+ key = lambda name: f"{name}_{prefix}"
509
+
510
+ with gr.Accordion("Reference Edit Settings", open=False, visible=('reference_latent' in default_enabled_chains)) as ref_accordion:
511
+ components[key('reference_latent_accordion')] = ref_accordion
512
+ 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**.")
513
+
514
+ ref_image_groups = []
515
+ ref_image_inputs = []
516
+ with gr.Row():
517
+ for i in range(max_units):
518
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
519
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
520
+ ref_image_groups.append(img_col)
521
+ ref_image_inputs.append(img_comp)
522
+
523
+ components[key('reference_latent_rows')] = ref_image_groups
524
+ components[key('reference_latent_images')] = ref_image_inputs
525
+
526
+ with gr.Row():
527
+ components[key('add_reference_latent_button')] = gr.Button("✚ Add Reference Image")
528
+ components[key('delete_reference_latent_button')] = gr.Button("➖ Delete Reference Image", visible=False)
529
+ components[key('reference_latent_count_state')] = gr.State(1)
530
+
531
+ components[key('all_reference_latent_components_flat')] = ref_image_inputs
532
+
533
+ return components
534
+
535
+ def create_hidream_o1_reference_ui(prefix: str, max_units=10):
536
+ components = {}
537
+ key = lambda name: f"{name}_{prefix}"
538
+
539
+ with gr.Accordion("HiDream-O1 Reference Edit Settings", open=False, visible=('hidream_o1_reference' in default_enabled_chains)) as ref_accordion:
540
+ components[key('hidream_o1_reference_accordion')] = ref_accordion
541
+ gr.Markdown("💡 **Tip:** Please use **HiDream-O1-Image-Dev** (HiDream-O1-Image will time out), and set the resolution to **4.0MP** (e.g., 2048x2048). In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
542
+
543
+ ref_image_groups = []
544
+ ref_image_inputs = []
545
+ with gr.Row():
546
+ for i in range(max_units):
547
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
548
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
549
+ ref_image_groups.append(img_col)
550
+ ref_image_inputs.append(img_comp)
551
+
552
+ components[key('hidream_o1_reference_rows')] = ref_image_groups
553
+ components[key('hidream_o1_reference_images')] = ref_image_inputs
554
+
555
+ with gr.Row():
556
+ components[key('add_hidream_o1_reference_button')] = gr.Button("✚ Add Reference Image")
557
+ components[key('delete_hidream_o1_reference_button')] = gr.Button("➖ Delete Reference Image", visible=False)
558
+ components[key('hidream_o1_reference_count_state')] = gr.State(1)
559
+
560
+ components[key('all_hidream_o1_reference_components_flat')] = ref_image_inputs
561
+
562
+ return components
563
+
564
+ def create_sensenova_reference_ui(prefix: str, max_units=10):
565
+ components = {}
566
+ key = lambda name: f"{name}_{prefix}"
567
+
568
+ with gr.Accordion("SenseNova Reference Edit Settings", open=False, visible=('sensenova_reference' in default_enabled_chains)) as ref_accordion:
569
+ components[key('sensenova_reference_accordion')] = ref_accordion
570
+ gr.Markdown("💡 **Tip:** For SenseNova models, 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**.")
571
+
572
+ ref_image_groups = []
573
+ ref_image_inputs = []
574
+ with gr.Row():
575
+ for i in range(max_units):
576
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
577
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
578
+ ref_image_groups.append(img_col)
579
+ ref_image_inputs.append(img_comp)
580
+
581
+ components[key('sensenova_reference_rows')] = ref_image_groups
582
+ components[key('sensenova_reference_images')] = ref_image_inputs
583
+
584
+ with gr.Row():
585
+ components[key('add_sensenova_reference_button')] = gr.Button("✚ Add Reference Image")
586
+ components[key('delete_sensenova_reference_button')] = gr.Button("➖ Delete Reference Image", visible=False)
587
+ components[key('sensenova_reference_count_state')] = gr.State(1)
588
+
589
+ components[key('all_sensenova_reference_components_flat')] = ref_image_inputs
590
+
591
+ return components
592
+
593
+ def create_joyai_reference_ui(prefix: str, max_units=2):
594
+ components = {}
595
+ key = lambda name: f"{name}_{prefix}"
596
+
597
+ with gr.Accordion("JoyAI Reference Edit Settings", open=False, visible=('joyai_image' in default_enabled_chains)) as ref_accordion:
598
+ components[key('joyai_reference_accordion')] = ref_accordion
599
+ 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).")
600
+
601
+ ref_image_groups = []
602
+ ref_image_inputs = []
603
+ with gr.Row():
604
+ for i in range(max_units):
605
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
606
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
607
+ ref_image_groups.append(img_col)
608
+ ref_image_inputs.append(img_comp)
609
+
610
+ components[key('joyai_reference_rows')] = ref_image_groups
611
+ components[key('joyai_reference_images')] = ref_image_inputs
612
+
613
+ with gr.Row():
614
+ components[key('add_joyai_reference_button')] = gr.Button("✚ Add Reference Image")
615
+ components[key('delete_joyai_reference_button')] = gr.Button("➖ Delete Reference Image", visible=False)
616
+ components[key('joyai_reference_count_state')] = gr.State(1)
617
+
618
+ components[key('all_joyai_reference_components_flat')] = ref_image_inputs
619
+
620
+ return components
621
+
622
+ def create_reference_image_ui(prefix: str, max_units=10):
623
+ components = {}
624
+ key = lambda name: f"{name}_{prefix}"
625
+
626
+ with gr.Accordion("Reference Image Edit Settings", open=False, visible=('reference_image' in default_enabled_chains)) as ref_accordion:
627
+ components[key('reference_image_accordion')] = ref_accordion
628
+ 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**.")
629
+
630
+ ref_image_groups = []
631
+ ref_image_inputs = []
632
+ with gr.Row():
633
+ for i in range(max_units):
634
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
635
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
636
+ ref_image_groups.append(img_col)
637
+ ref_image_inputs.append(img_comp)
638
+
639
+ components[key('reference_image_rows')] = ref_image_groups
640
+ components[key('reference_image_images')] = ref_image_inputs
641
+
642
+ with gr.Row():
643
+ components[key('add_reference_image_button')] = gr.Button("✚ Add Reference Image")
644
+ components[key('delete_reference_image_button')] = gr.Button("➖ Delete Reference Image", visible=False)
645
+ components[key('reference_image_count_state')] = gr.State(1)
646
+
647
+ components[key('all_reference_image_components_flat')] = ref_image_inputs
648
+
649
+ return components
650
+ def create_moss_image_control_ui(prefix: str):
651
+ components = {}
652
+ key = lambda name: f"{name}_{prefix}"
653
+
654
+ with gr.Accordion("MOSS-Image Color/Depth Control Settings", open=False, visible=('moss_image_control' in default_enabled_chains)) as control_accordion:
655
+ components[key('moss_image_control_accordion')] = control_accordion
656
+ gr.Markdown("💡 **Tip:** Uses the [darask0/MOSS-Image-color-depth-control](https://huggingface.co/darask0/MOSS-Image-color-depth-control) adapter.")
657
+ with gr.Row():
658
+ components[key('moss_image_control_color_map')] = gr.Image(
659
+ type="pil", label="Color Region Map (Optional)", sources=["upload"], height=200
660
+ )
661
+ components[key('moss_image_control_depth_map')] = gr.Image(
662
+ type="pil", label="Depth Map (Optional, near=white)", sources=["upload"], height=200
663
+ )
664
+
665
+ with gr.Row():
666
+ components[key('moss_image_control_strength')] = gr.Slider(
667
+ label="Control Strength",
668
+ value=1.0,
669
+ minimum=0.0,
670
+ maximum=2.0,
671
+ step=0.05,
672
+ interactive=True
673
+ )
674
+ components[key('moss_image_control_end_percent')] = gr.Slider(
675
+ label="End Percent (0.5 for 3D mannequin)",
676
+ value=0.5,
677
+ minimum=0.0,
678
+ maximum=1.0,
679
+ step=0.01,
680
+ interactive=True
681
+ )
682
+
683
+ return components
684
+
685
+
686
+ def create_pid_ui(prefix: str):
687
+ components = {}
688
+ key = lambda name: f"{name}_{prefix}"
689
+
690
+ with gr.Accordion("PiD Settings", open=False, visible=('pid' in default_enabled_chains)) as pid_accordion:
691
+ components[key('pid_accordion')] = pid_accordion
692
+ gr.Markdown("💡 **Tip:** Use PiD (Pixel Diffusion Decoder) instead of the VAE Decoder for 4x decoding.")
693
+ with gr.Row():
694
+ components[key('pid_settings')] = gr.Dropdown(
695
+ label="PiD Mode",
696
+ choices=["OFF", "ON"],
697
+ value="OFF",
698
+ interactive=True
699
+ )
700
+
701
+ return components
702
+
703
+ def create_krea2_identity_edit_ui(prefix: str, max_units=2):
704
+ components = {}
705
+ key = lambda name: f"{name}_{prefix}"
706
+
707
+ with gr.Accordion("Krea2 Identity Edit Settings", open=False, visible=('krea2_identity_edit' in default_enabled_chains)) as ref_accordion:
708
+ components[key('krea2_identity_edit_accordion')] = ref_accordion
709
+ gr.Markdown("💡 **Tip:** Processed using the [lbouaraba/comfyui-krea2edit](https://github.com/lbouaraba/comfyui-krea2edit) node. (Krea-2-Turbo recommended, Krea-2-Raw 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**.")
710
+
711
+ ref_image_groups = []
712
+ ref_image_inputs = []
713
+ with gr.Row():
714
+ for i in range(max_units):
715
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
716
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
717
+ ref_image_groups.append(img_col)
718
+ ref_image_inputs.append(img_comp)
719
+
720
+ components[key('krea2_identity_edit_rows')] = ref_image_groups
721
+ components[key('krea2_identity_edit_images')] = ref_image_inputs
722
+
723
+ with gr.Row():
724
+ components[key('add_krea2_identity_edit_button')] = gr.Button("✚ Add Reference Image")
725
+ components[key('delete_krea2_identity_edit_button')] = gr.Button("➖ Delete Reference Image", visible=False)
726
+ components[key('krea2_identity_edit_count_state')] = gr.State(1)
727
+
728
+ components[key('all_krea2_identity_edit_components_flat')] = ref_image_inputs
729
+
730
+ return components
731
+
732
+ def create_qwen_image_edit_ui(prefix: str, max_units=3):
733
+ components = {}
734
+ key = lambda name: f"{name}_{prefix}"
735
+
736
+ with gr.Accordion("Qwen-Image Edit Settings", open=False, visible=('qwen_image_edit' in default_enabled_chains)) as ref_accordion:
737
+ components[key('qwen_image_edit_accordion')] = ref_accordion
738
+ gr.Markdown("💡 **Tip:** (lightx2v/Qwen-Image-Edit-2511-Lightning recommended) In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
739
+
740
+ ref_image_groups = []
741
+ ref_image_inputs = []
742
+ with gr.Row():
743
+ for i in range(max_units):
744
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
745
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
746
+ ref_image_groups.append(img_col)
747
+ ref_image_inputs.append(img_comp)
748
+
749
+ components[key('qwen_image_edit_rows')] = ref_image_groups
750
+ components[key('qwen_image_edit_images')] = ref_image_inputs
751
+
752
+ with gr.Row():
753
+ components[key('add_qwen_image_edit_button')] = gr.Button("✚ Add Reference Image")
754
+ components[key('delete_qwen_image_edit_button')] = gr.Button("➖ Delete Reference Image", visible=False)
755
+ components[key('qwen_image_edit_count_state')] = gr.State(1)
756
+
757
+ components[key('all_qwen_image_edit_components_flat')] = ref_image_inputs
758
+
759
+ return components
760
+
761
+ def create_krea2_reference_edit_ui(prefix: str, max_units=3):
762
+ components = {}
763
+ key = lambda name: f"{name}_{prefix}"
764
+
765
+ with gr.Accordion("Krea2 Style Reference Edit Settings", open=False, visible=('krea2_style_reference' in default_enabled_chains)) as ref_accordion:
766
+ components[key('krea2_reference_edit_accordion')] = ref_accordion
767
+ gr.Markdown("💡 **Tip:** (Krea-2-Turbo recommended) Add style reference images to perform style reference editing.")
768
+
769
+ ref_image_groups = []
770
+ ref_image_inputs = []
771
+ with gr.Row():
772
+ for i in range(max_units):
773
+ with gr.Column(visible=(i < 1), min_width=160) as img_col:
774
+ img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
775
+ ref_image_groups.append(img_col)
776
+ ref_image_inputs.append(img_comp)
777
+
778
+ components[key('krea2_reference_edit_rows')] = ref_image_groups
779
+ components[key('krea2_reference_edit_images')] = ref_image_inputs
780
+
781
+ with gr.Row():
782
+ components[key('add_krea2_reference_edit_button')] = gr.Button("✚ Add Reference Image")
783
+ components[key('delete_krea2_reference_edit_button')] = gr.Button("➖ Delete Reference Image", visible=False)
784
+ components[key('krea2_reference_edit_count_state')] = gr.State(1)
785
+
786
+ components[key('all_krea2_reference_edit_components_flat')] = ref_image_inputs
787
+
788
+ return components
789
+
790
+
791
+ def create_qwen_image_2_1_prompt_enhancer_ui(prefix: str):
792
+ components = {}
793
+ key = lambda name: f"{name}_{prefix}"
794
+
795
+ with gr.Accordion("Qwen-Image-2.1 Prompt Enhancer Settings", open=False, visible=('qwen_image_2_1_prompt_enhancer' in default_enabled_chains)) as enhancer_accordion:
796
+ components[key('qwen_image_2_1_prompt_enhancer_accordion')] = enhancer_accordion
797
+ gr.Markdown("💡 **Tip:** Automatically selects between [Qwen-Image-2.1-PE-T2I](https://huggingface.co/Qwen/Qwen-Image-2.1-PE-T2I) and [Qwen-Image-2.1-PE-I2I](https://huggingface.co/Qwen/Qwen-Image-2.1-PE-I2I) for prompt enhancement based on input context.")
798
+ with gr.Row():
799
+ components[key('qwen_image_2_1_prompt_enhancer_enable')] = gr.Checkbox(
800
+ label="Enable Qwen-Image-2.1 Prompt Enhancer",
801
+ value=False,
802
+ interactive=True,
803
+ scale=2
804
+ )
805
+ components[key('qwen_image_2_1_prompt_enhancer_thinking')] = gr.Checkbox(
806
+ label="Enable Thinking (Requires longer ZeroGPU Duration)",
807
+ value=False,
808
+ interactive=True,
809
+ scale=2
810
+ )
811
+ components[key('qwen_image_2_1_prompt_enhancer_max_length')] = gr.Number(
812
+ label="Max Length",
813
+ value=4096,
814
+ precision=0,
815
+ minimum=16,
816
+ step=16,
817
+ interactive=True,
818
+ scale=1
819
+ )
820
+
821
+ return components
822
+
823
+
824
+ DEFAULT_MING_IMAGE_SYSTEM_PROMPT = (
825
+ "You are a senior visual designer and image-prompt engineer. Expand the user's request into one precise, high-resolution Figma-style caption. Return only one JSON object. "
826
+ "Use exactly two top-level keys. `canvas_settings` contains exactly `aspect_ratio`, `ambient_lighting`, and `image_style`. `layers` lists visible groups from background to topmost overlay. Every layer contains exactly `description`, `coordinates`, `hierarchy_and_relation`, and `color_specs`; `color_specs` is an array of hex colors. "
827
+ '`coordinates` MUST be one string, never an object or array, in exactly this form: `"cx: 0.500, cy: 0.500, w: 1.000, h: 1.000"`. Values are normalized; each bbox encloses its complete owned object and stays inside the canvas. '
828
+ "A layer is one selectable visible semantic group: background, full person, coherent object, panel, card, row, or text block. Prefer the fewest groups that preserve the layout. Keep people and objects intact. Never create invisible parents, guides, placeholders, empty layers, duplicate summaries, or multiple owners for one element. "
829
+ "Preserve every user-supplied rendered string character-for-character and as one contiguous string. Unless multiple visible copies are requested, it must occur exactly once across all `description` fields and zero times in `hierarchy_and_relation`. Quote it only where describing its visible rendering; refer to the related subject elsewhere with unquoted semantic wording. Enumerate intended copy, invent extra copy sparingly, and never hide content behind \"other text\", \"remaining labels\", or \"etc.\" "
830
+ "Describe concrete composition, typography, materials, texture, lighting, pose, and camera treatment without literary filler. Use `hierarchy_and_relation` only for ownership, alignment, containment, stacking, and occlusion. "
831
+ "Infer structured layouts first. Use one complete layer per card and state its row and column. A compact secondary table may be one layer only if every header and cell is listed; otherwise use a visible shared frame when present, one complete header, and one complete layer per body row, binding values to columns and stating blanks. Enumerate sequences, schedules, spans, gaps, and vacant tracks in visual order. Do not mistake ordinary alignment for a table. "
832
+ "Silently verify schema, string coordinates, Z-order, exact-text counts, geometry, bbox validity, and completeness."
833
+ )
834
+
835
+
836
+ def create_ming_image_prompt_enhancer_ui(prefix: str):
837
+ components = {}
838
+ key = lambda name: f"{name}_{prefix}"
839
+
840
+ with gr.Accordion("Ming-Image Prompt Enhancer Settings", open=False, visible=('ming_image_prompt_enhancer' in default_enabled_chains)) as enhancer_accordion:
841
+ components[key('ming_image_prompt_enhancer_accordion')] = enhancer_accordion
842
+ gr.Markdown("💡 **Tip:** Leverages [Qwen3.8-27B](https://huggingface.co/Qwen/Qwen3.8-27B) guided by the official [System Prompt](https://github.com/inclusionAI/Ming-Image#text-to-image-prompt-rewriting) to automatically enhance prompts into structured layouts based on input context.")
843
+ with gr.Row():
844
+ components[key('ming_image_prompt_enhancer_enable')] = gr.Checkbox(
845
+ label="Enable Ming-Image Prompt Enhancer",
846
+ value=False,
847
+ interactive=True,
848
+ scale=2
849
+ )
850
+ components[key('ming_image_prompt_enhancer_thinking')] = gr.Checkbox(
851
+ label="Enable Thinking (Requires longer ZeroGPU Duration)",
852
+ value=False,
853
+ interactive=True,
854
+ scale=2
855
+ )
856
+ components[key('ming_image_prompt_enhancer_max_length')] = gr.Number(
857
+ label="Max Length",
858
+ value=4096,
859
+ precision=0,
860
+ minimum=16,
861
+ step=16,
862
+ interactive=True,
863
+ scale=1
864
+ )
865
+ components[key('ming_image_prompt_enhancer_system_prompt')] = gr.Textbox(
866
+ label="System Prompt",
867
+ value=DEFAULT_MING_IMAGE_SYSTEM_PROMPT,
868
+ lines=4,
869
+ max_lines=12,
870
+ interactive=True
871
+ )
872
+
873
+ return components
874
+
875
+
876
+ def create_flux3_prompt_upsampler_ui(prefix: str):
877
+ components = {}
878
+ key = lambda name: f"{name}_{prefix}"
879
+
880
+ with gr.Accordion("FLUX.3 Prompt Upsampler Settings", open=False, visible=('flux3_prompt_upsampler' in default_enabled_chains)) as enhancer_accordion:
881
+ components[key('flux3_prompt_upsampler_accordion')] = enhancer_accordion
882
+ gr.Markdown(
883
+ "💡 **Tip:** System prompt automatically matches the official [black-forest-labs/flux-3-image-klein-pup](https://huggingface.co/black-forest-labs/flux-3-image-klein-pup/blob/main/system_prompts.json) templates: uses `t2i` when no image is provided, and automatically switches to `i2i` when image input is detected; enabling **BBox Mode** switches respectively to `t2i_bbox` and `i2i_bbox` to output target bounding box annotations."
884
+ )
885
+ with gr.Row():
886
+ components[key('flux3_prompt_upsampler_enable')] = gr.Checkbox(
887
+ label="Enable FLUX.3 Prompt Upsampler",
888
+ value=False,
889
+ interactive=True,
890
+ scale=2
891
+ )
892
+ components[key('flux3_prompt_upsampler_thinking')] = gr.Checkbox(
893
+ label="Enable Thinking (Requires longer ZeroGPU Duration)",
894
+ value=False,
895
+ interactive=True,
896
+ scale=2
897
+ )
898
+ components[key('flux3_prompt_upsampler_bbox_mode')] = gr.Checkbox(
899
+ label="BBox Mode",
900
+ value=False,
901
+ interactive=True,
902
+ scale=1
903
+ )
904
+ components[key('flux3_prompt_upsampler_max_length')] = gr.Number(
905
+ label="Max Length",
906
+ value=3584,
907
+ precision=0,
908
+ minimum=16,
909
+ step=16,
910
+ interactive=True,
911
+ scale=1
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
+
utils/app_utils.py CHANGED
@@ -196,7 +196,7 @@ def get_lora_path(source: str, id_or_url: str, civitai_key: str, progress) -> tu
196
  filename = sanitize_filename(f"{version_id}{file_ext}")
197
  save_dir = os.path.join(LORA_DIR, "civitai")
198
  local_path = os.path.join(save_dir, filename)
199
- api_key_to_use = civitai_key or os.environ.get("CIVITAI_API_KEY", "")
200
  source_name = f"Civitai ID {version_id}"
201
  elif source == "Hugging Face":
202
  parts = id_or_url.strip().split('/')
 
196
  filename = sanitize_filename(f"{version_id}{file_ext}")
197
  save_dir = os.path.join(LORA_DIR, "civitai")
198
  local_path = os.path.join(save_dir, filename)
199
+ api_key_to_use = civitai_key
200
  source_name = f"Civitai ID {version_id}"
201
  elif source == "Hugging Face":
202
  parts = id_or_url.strip().split('/')
yaml/chain_features.yaml CHANGED
@@ -236,6 +236,21 @@ mage_flow_reference_edit:
236
  required:
237
  - image
238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  krea2_identity_edit:
240
  chains: krea2_identity_edit
241
  display_name: "KREA2 Identity Edit"
@@ -479,3 +494,81 @@ ming_image_prompt_enhancer:
479
  system_prompt:
480
  type: string
481
  description: "System prompt for Qwen3.8-27B prompt enhancer (defaults to official Ming-Image Figma-style rewriter prompt)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  required:
237
  - image
238
 
239
+ fibo2_reference_edit:
240
+ chains: reference_image
241
+ display_name: "Fibo-2.0 Reference Edit"
242
+ description: "For Fibo 2.0 models, 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."
243
+ max_count: 10
244
+ usage_guideline: "Supply reference image as Base64 Data URI or HTTP/HTTPS URL."
245
+ parameters_schema:
246
+ type: object
247
+ properties:
248
+ image:
249
+ type: string
250
+ description: "Reference image encoded as Base64 Data URI or HTTP/HTTPS URL."
251
+ required:
252
+ - image
253
+
254
  krea2_identity_edit:
255
  chains: krea2_identity_edit
256
  display_name: "KREA2 Identity Edit"
 
494
  system_prompt:
495
  type: string
496
  description: "System prompt for Qwen3.8-27B prompt enhancer (defaults to official Ming-Image Figma-style rewriter prompt)."
497
+
498
+ flux3_prompt_upsampler:
499
+ chains: flux3_prompt_upsampler
500
+ display_name: "FLUX.3 Prompt Upsampler Settings"
501
+ description: "Expand short prompts into detailed generative captions using FLUX.3 Prompt Upsampler (qwen3.5_9b_flux3_prompt_upsampler_w4a8) with automatic official template selection (t2i, i2i, t2i_bbox, i2i_bbox), multi-reference image stitching, and reasoning CoT thinking mode."
502
+ max_count: 1
503
+ usage_guideline: "Set enable to true to activate FLUX.3 prompt upsampler. Automatically selects official BFL t2i or i2i template based on input image detection. Supports bbox_mode, thinking mode, and multi-reference image stitching."
504
+ parameters_schema:
505
+ type: object
506
+ properties:
507
+ enable:
508
+ type: boolean
509
+ default: false
510
+ description: "Enable FLUX.3 Prompt Upsampler."
511
+ thinking:
512
+ type: boolean
513
+ default: false
514
+ description: "Enable thinking (reasoning CoT)."
515
+ bbox_mode:
516
+ type: boolean
517
+ default: false
518
+ description: "Enable BBox mode (automatically selects t2i_bbox or i2i_bbox official system prompt templates to output bounding boxes)."
519
+ max_length:
520
+ type: integer
521
+ default: 3584
522
+ description: "Maximum generation length for prompt upsampler (default 3584)."
523
+ system_prompt:
524
+ type: string
525
+ description: "Optional custom system prompt override (defaults to official t2i/i2i or t2i_bbox/i2i_bbox templates)."
526
+
527
+ moss_image_control:
528
+ chains: moss_image_control
529
+ display_name: "MOSS-Image Color/Depth Control"
530
+ description: "Controls MOSS-Image generation using a color region map (object/region spatial layout) and/or a depth map (3D spatial depth, near is white) via darask0/MOSS-Image-color-depth-control adapter and MOSSImageControl node."
531
+ max_count: 1
532
+ usage_guideline: "Supply color_map (RGB region ID map) and/or depth_map (near=white grayscale map) as Base64 Data URI or HTTP/HTTPS URL. Set strength (default 1.0) and timestep range (start_percent 0.0, end_percent 0.5)."
533
+ parameters_schema:
534
+ type: object
535
+ properties:
536
+ color_map:
537
+ type: string
538
+ description: "Color region map image encoded as Base64 Data URI or HTTP/HTTPS URL. Each region uses a flat RGB color as a region ID."
539
+ depth_map:
540
+ type: string
541
+ description: "Depth map image encoded as Base64 Data URI or HTTP/HTTPS URL (near is white, far is dark)."
542
+ strength:
543
+ type: number
544
+ default: 1.0
545
+ minimum: 0.0
546
+ maximum: 2.0
547
+ description: "Control influence strength multiplier (default 1.0)."
548
+ start_percent:
549
+ type: number
550
+ default: 0.0
551
+ minimum: 0.0
552
+ maximum: 1.0
553
+ description: "Sampling percentage at which control begins (default 0.0)."
554
+ end_percent:
555
+ type: number
556
+ default: 0.5
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,9 +4,26 @@ 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:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  krea-2:
11
  "1:1 (Square)": [1024, 1024]
12
  "16:9 (Landscape)": [1344, 768]
@@ -103,6 +120,14 @@ RESOLUTION_MAP:
103
  "3:4 (Classic Portrait)": [896, 1152]
104
  "3:2 (Photography)": [1216, 832]
105
  "2:3 (Photography Portrait)": [832, 1216]
 
 
 
 
 
 
 
 
106
  ming-image:
107
  "1:1 (Square)": [1024, 1024]
108
  "16:9 (Landscape)": [1344, 768]
@@ -289,6 +314,8 @@ RESOLUTION_MAP:
289
  "2:3 (Portrait)": [512, 768]
290
 
291
  MULTIPLIERS_MAP:
 
 
292
  krea-2: 1
293
  sensenova-u1.5: 1
294
  mage-flow: 1
 
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:
11
+ fibo-2.0:
12
+ "1:1 (Square)": [1024, 1024]
13
+ "16:9 (Landscape)": [1344, 768]
14
+ "9:16 (Portrait)": [768, 1344]
15
+ "4:3 (Classic)": [1152, 896]
16
+ "3:4 (Classic Portrait)": [896, 1152]
17
+ "3:2 (Photography)": [1216, 832]
18
+ "2:3 (Photography Portrait)": [832, 1216]
19
+ flux3:
20
+ "1:1 (Square)": [1024, 1024]
21
+ "16:9 (Landscape)": [1344, 768]
22
+ "9:16 (Portrait)": [768, 1344]
23
+ "4:3 (Classic)": [1152, 896]
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]
 
120
  "3:4 (Classic Portrait)": [896, 1152]
121
  "3:2 (Photography)": [1216, 832]
122
  "2:3 (Photography Portrait)": [832, 1216]
123
+ moss-image:
124
+ "1:1 (Square)": [1024, 1024]
125
+ "16:9 (Landscape)": [1344, 768]
126
+ "9:16 (Portrait)": [768, 1344]
127
+ "4:3 (Classic)": [1152, 896]
128
+ "3:4 (Classic Portrait)": [896, 1152]
129
+ "3:2 (Photography)": [1216, 832]
130
+ "2:3 (Photography Portrait)": [832, 1216]
131
  ming-image:
132
  "1:1 (Square)": [1024, 1024]
133
  "16:9 (Landscape)": [1344, 768]
 
314
  "2:3 (Portrait)": [512, 768]
315
 
316
  MULTIPLIERS_MAP:
317
+ fibo-2.0: 1
318
+ flux3: 1
319
  krea-2: 1
320
  sensenova-u1.5: 1
321
  mage-flow: 1
yaml/file_list.yaml CHANGED
@@ -405,6 +405,85 @@ file:
405
  repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
406
  repository_file_path: "control_v11u_sd15_tile_fp16.safetensors"
407
  diffusion_models:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  # Krea-2
409
  - filename: "krea2_turbo_nvfp4.safetensors"
410
  source: "hf"
@@ -414,6 +493,10 @@ file:
414
  source: "hf"
415
  repo_id: "tsukiyomi/krea2_raw-nvfp4"
416
  repository_file_path: "krea2_raw-nvfp4.safetensors"
 
 
 
 
417
  # Mage-Flow
418
  - filename: "mage_flow_turbo_int8_convrot.safetensors"
419
  source: "hf"
@@ -524,64 +607,20 @@ file:
524
  repo_id: "lylogummy/Anima-3.8B"
525
  repository_file_path: "difussion_models/Anima-3.8B-v1.1.safetensors"
526
  # Anima-3.8B
527
- - filename: "boleromixAnima_v01438B.safetensors"
528
- source: "civitai"
529
- model_version_id: "3348912"
530
  - filename: "Anima-3.8B.safetensors"
531
  source: "hf"
532
  repo_id: "lylogummy/Anima-3.8B"
533
  repository_file_path: "difussion_models/Anima-3.8B.safetensors"
534
- - filename: "fiamixAnima38B_v12.safetensors"
535
- source: "civitai"
536
- model_version_id: "3371597"
537
- - filename: "pppAnima38BQwen06B_v20.safetensors"
538
- source: "civitai"
539
- model_version_id: "3359866"
540
- - filename: "animaDeltaMix38BQwen06B.safetensors"
541
- source: "civitai"
542
- model_version_id: "3427825"
543
- - filename: "fiamixAnima38B_new.safetensors"
544
- source: "civitai"
545
- model_version_id: "3423916"
546
  # Anima-2.9B
547
  - filename: "Anima-2.9B-preview-v1.safetensors"
548
  source: "hf"
549
  repo_id: "Gazingstars123/Anima-2.9B"
550
  repository_file_path: "Anima-2.9B-preview-v1.safetensors"
 
 
 
 
551
  # Anima
552
- - filename: "NovaAnimeAMV50.safetensors"
553
- source: "civitai"
554
- model_version_id: "3338179"
555
- - filename: "dreamcake_redvelvet.safetensors"
556
- source: "civitai"
557
- model_version_id: "3359687"
558
- - filename: "huggingreverieAnima_v20B.safetensors"
559
- source: "civitai"
560
- model_version_id: "3373038"
561
- - filename: "fiamixAnimaReboot25D_v11.safetensors"
562
- source: "civitai"
563
- model_version_id: "3316592"
564
- - filename: "hybridcelTurboAnima_v30.safetensors"
565
- source: "civitai"
566
- model_version_id: "3340336"
567
- - filename: "animaLuc_v3a.safetensors"
568
- source: "civitai"
569
- model_version_id: "3339348"
570
- - filename: "novaBaraMaleAnima_novaV20.safetensors"
571
- source: "civitai"
572
- model_version_id: "3304198"
573
- - filename: "fiamixAnimaReboot_v17.safetensors"
574
- source: "civitai"
575
- model_version_id: "3359008"
576
- - filename: "novaCrossAM_new.safetensors"
577
- source: "civitai"
578
- model_version_id: "3431917"
579
- - filename: "animaDeltaMix29B.safetensors"
580
- source: "civitai"
581
- model_version_id: "3414789"
582
- - filename: "nununuMix.safetensors"
583
- source: "civitai"
584
- model_version_id: "3172155"
585
  - filename: "anima-turbo-v1.0.safetensors"
586
  source: "hf"
587
  repo_id: "circlestone-labs/Anima"
@@ -698,10 +737,22 @@ file:
698
  source: "hf"
699
  repo_id: "Comfy-Org/Qwen-Image-2.1"
700
  repository_file_path: "diffusion_models/qwen_image_2.1_int8_convrot.safetensors"
 
 
 
 
701
  - filename: "qwen-image-2.1-UC-int8_convrot.safetensors"
702
  source: "hf"
703
  repo_id: "abenzerps/Qwen-Image-2.1-Uncensored-GGUF"
704
  repository_file_path: "qwen-image-2.1-UC-int8_convrot.safetensors"
 
 
 
 
 
 
 
 
705
  # Ming-Image
706
  - filename: "ming_image_0.1_design_int8_convrot.safetensors"
707
  source: "hf"
@@ -884,6 +935,16 @@ file:
884
  repo_id: "black-forest-labs/FLUX.1-Redux-dev"
885
  repository_file_path: "flux1-redux-dev.safetensors"
886
  loras:
 
 
 
 
 
 
 
 
 
 
887
  # Krea2 Identity Edit
888
  - filename: "krea2_identity_edit_v1_2.safetensors"
889
  source: "hf"
@@ -954,10 +1015,15 @@ file:
954
  # Qwen-Image-2.1
955
  - filename: "qwen_image_2.1_fun_controlnet_union_int8_convrot.safetensors"
956
  source: "hf"
957
- repo_id: "Kijai/QwenImage_experimental"
958
  repository_file_path: "model_patches/qwen_image_2.1_fun_controlnet_union_int8_convrot.safetensors"
959
  text_encoders:
960
- # Krea-2
 
 
 
 
 
961
  - filename: "qwen3vl_4b_fp8_scaled.safetensors"
962
  source: "hf"
963
  repo_id: "Comfy-Org/Krea-2"
@@ -1000,6 +1066,11 @@ file:
1000
  source: "hf"
1001
  repo_id: "Comfy-Org/Qwen3.8-27B"
1002
  repository_file_path: "text_encoders/qwen3.8_27b_w4a8.safetensors"
 
 
 
 
 
1003
  # PixelDiT
1004
  - filename: "gemma_2_2b_it_elm_fp8_scaled.safetensors"
1005
  source: "hf"
 
405
  repo_id: "comfyanonymous/ControlNet-v1-1_fp16_safetensors"
406
  repository_file_path: "control_v11u_sd15_tile_fp16.safetensors"
407
  diffusion_models:
408
+ - filename: "boleromixAnima_v01438B.safetensors"
409
+ source: "civitai"
410
+ model_version_id: "3348912"
411
+ - filename: "fiamixAnima38B_v12.safetensors"
412
+ source: "civitai"
413
+ model_version_id: "3371597"
414
+ - filename: "pppAnima38BQwen06B_v20.safetensors"
415
+ source: "civitai"
416
+ model_version_id: "3359866"
417
+ - filename: "animaDeltaMix38BQwen06B.safetensors"
418
+ source: "civitai"
419
+ model_version_id: "3427825"
420
+ - filename: "fiamixAnima38B_new.safetensors"
421
+ source: "civitai"
422
+ model_version_id: "3423916"
423
+ - filename: "NovaAnimeAMV50.safetensors"
424
+ source: "civitai"
425
+ model_version_id: "3338179"
426
+ - filename: "dreamcake_redvelvet.safetensors"
427
+ source: "civitai"
428
+ model_version_id: "3359687"
429
+ - filename: "huggingreverieAnima_v20B.safetensors"
430
+ source: "civitai"
431
+ model_version_id: "3373038"
432
+ - filename: "fiamixAnimaReboot25D_v11.safetensors"
433
+ source: "civitai"
434
+ model_version_id: "3316592"
435
+ - filename: "hybridcelTurboAnima_v30.safetensors"
436
+ source: "civitai"
437
+ model_version_id: "3340336"
438
+ - filename: "animaLuc_v3a.safetensors"
439
+ source: "civitai"
440
+ model_version_id: "3339348"
441
+ - filename: "novaBaraMaleAnima_novaV20.safetensors"
442
+ source: "civitai"
443
+ model_version_id: "3304198"
444
+ - filename: "fiamixAnimaReboot_v17.safetensors"
445
+ source: "civitai"
446
+ model_version_id: "3359008"
447
+ - filename: "novaCrossAM_new.safetensors"
448
+ source: "civitai"
449
+ model_version_id: "3431917"
450
+ - filename: "animaDeltaMix29B.safetensors"
451
+ source: "civitai"
452
+ model_version_id: "3414789"
453
+ - filename: "nununuMix.safetensors"
454
+ source: "civitai"
455
+ model_version_id: "3172155"
456
+ # Fibo 2.0
457
+ - filename: "fibo2_dev_int8_convrot.safetensors"
458
+ source: "hf"
459
+ repo_id: "Comfy-Org/Fibo-2.0"
460
+ repository_file_path: "diffusion_models/fibo2_dev_int8_convrot.safetensors"
461
+ - filename: "fibo2_edit_turbo_int8_convrot.safetensors"
462
+ source: "hf"
463
+ repo_id: "Comfy-Org/Fibo-2.0"
464
+ repository_file_path: "diffusion_models/fibo2_edit_turbo_int8_convrot.safetensors"
465
+ # Flux-3
466
+ - filename: "flux3-image-klein-nvfp4.safetensors"
467
+ source: "hf"
468
+ repo_id: "black-forest-labs/flux-3-image-klein-comfy"
469
+ repository_file_path: "diffusion_models/flux3-image-klein-nvfp4.safetensors"
470
+ - filename: "flux3_image_klein_sd_nvfp4.safetensors"
471
+ source: "hf"
472
+ repo_id: "black-forest-labs/flux-3-image-klein-comfy"
473
+ repository_file_path: "diffusion_models/flux3_image_klein_sd_nvfp4.safetensors"
474
+ - filename: "flux3-image-klein-base-nvfp4.safetensors"
475
+ source: "hf"
476
+ repo_id: "black-forest-labs/flux-3-image-klein-comfy"
477
+ repository_file_path: "diffusion_models/flux3-image-klein-base-nvfp4.safetensors"
478
+ # MOSS-Image
479
+ - filename: "moss_image_base_int8_convrot.safetensors"
480
+ source: "hf"
481
+ repo_id: "Comfy-Org/MOSS-Image"
482
+ repository_file_path: "diffusion_models/moss_image_base_int8_convrot.safetensors"
483
+ - filename: "moss_image_sft_int8_convrot.safetensors"
484
+ source: "hf"
485
+ repo_id: "Comfy-Org/MOSS-Image"
486
+ repository_file_path: "diffusion_models/moss_image_sft_int8_convrot.safetensors"
487
  # Krea-2
488
  - filename: "krea2_turbo_nvfp4.safetensors"
489
  source: "hf"
 
493
  source: "hf"
494
  repo_id: "tsukiyomi/krea2_raw-nvfp4"
495
  repository_file_path: "krea2_raw-nvfp4.safetensors"
496
+ - filename: "KreaNoct_V4_int8_convrot.safetensors"
497
+ source: "hf"
498
+ repo_id: "Noctaluna/Krea2-Noct-Uncensored"
499
+ repository_file_path: "KreaNoct_V4_int8_convrot.safetensors"
500
  # Mage-Flow
501
  - filename: "mage_flow_turbo_int8_convrot.safetensors"
502
  source: "hf"
 
607
  repo_id: "lylogummy/Anima-3.8B"
608
  repository_file_path: "difussion_models/Anima-3.8B-v1.1.safetensors"
609
  # Anima-3.8B
 
 
 
610
  - filename: "Anima-3.8B.safetensors"
611
  source: "hf"
612
  repo_id: "lylogummy/Anima-3.8B"
613
  repository_file_path: "difussion_models/Anima-3.8B.safetensors"
 
 
 
 
 
 
 
 
 
 
 
 
614
  # Anima-2.9B
615
  - filename: "Anima-2.9B-preview-v1.safetensors"
616
  source: "hf"
617
  repo_id: "Gazingstars123/Anima-2.9B"
618
  repository_file_path: "Anima-2.9B-preview-v1.safetensors"
619
+ - filename: "AnimaNoct_V2_int8_convrot.safetensors"
620
+ source: "hf"
621
+ repo_id: "Noctaluna/Anima-Noct-Anime-Mix"
622
+ repository_file_path: "AnimaNoct_V2_int8_convrot.safetensors"
623
  # Anima
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
  - filename: "anima-turbo-v1.0.safetensors"
625
  source: "hf"
626
  repo_id: "circlestone-labs/Anima"
 
737
  source: "hf"
738
  repo_id: "Comfy-Org/Qwen-Image-2.1"
739
  repository_file_path: "diffusion_models/qwen_image_2.1_int8_convrot.safetensors"
740
+ - filename: "qwen_image_2.1_turbo_int8_convrot.safetensors"
741
+ source: "hf"
742
+ repo_id: "Comfy-Org/Qwen-Image-2.1"
743
+ repository_file_path: "diffusion_models/qwen_image_2.1_turbo_int8_convrot.safetensors"
744
  - filename: "qwen-image-2.1-UC-int8_convrot.safetensors"
745
  source: "hf"
746
  repo_id: "abenzerps/Qwen-Image-2.1-Uncensored-GGUF"
747
  repository_file_path: "qwen-image-2.1-UC-int8_convrot.safetensors"
748
+ - filename: "NoctQ_V4_int8_convrot.safetensors"
749
+ source: "hf"
750
+ repo_id: "Noctaluna/Noct-Q-Uncensored-Qwen-Image-2.1"
751
+ repository_file_path: "NoctQ_V4_int8_convrot.safetensors"
752
+ - filename: "NoctQA_V1_int8_convrot.safetensors"
753
+ source: "hf"
754
+ repo_id: "Noctaluna/Noct-Q-Anime-Uncensored-Qwen-Image-2.1"
755
+ repository_file_path: "NoctQA_V1_int8_convrot.safetensors"
756
  # Ming-Image
757
  - filename: "ming_image_0.1_design_int8_convrot.safetensors"
758
  source: "hf"
 
935
  repo_id: "black-forest-labs/FLUX.1-Redux-dev"
936
  repository_file_path: "flux1-redux-dev.safetensors"
937
  loras:
938
+ # MOSS-Image Color/Depth Control
939
+ - filename: "moss-control.safetensors"
940
+ source: "hf"
941
+ repo_id: "darask0/MOSS-Image-color-depth-control"
942
+ repository_file_path: "moss-control.safetensors"
943
+ # MOSS-Image Edit
944
+ - filename: "moss_image_edit_lora_rank_64_bf16.safetensors"
945
+ source: "hf"
946
+ repo_id: "Comfy-Org/MOSS-Image"
947
+ repository_file_path: "loras/moss_image_edit_lora_rank_64_bf16.safetensors"
948
  # Krea2 Identity Edit
949
  - filename: "krea2_identity_edit_v1_2.safetensors"
950
  source: "hf"
 
1015
  # Qwen-Image-2.1
1016
  - filename: "qwen_image_2.1_fun_controlnet_union_int8_convrot.safetensors"
1017
  source: "hf"
1018
+ repo_id: "Comfy-Org/Qwen-Image-2.1"
1019
  repository_file_path: "model_patches/qwen_image_2.1_fun_controlnet_union_int8_convrot.safetensors"
1020
  text_encoders:
1021
+ # MOSS-Image
1022
+ - filename: "qwen3.5_9b_w4a8_gptq.safetensors"
1023
+ source: "hf"
1024
+ repo_id: "Comfy-Org/MOSS-Image"
1025
+ repository_file_path: "text_encoders/qwen3.5_9b_w4a8_gptq.safetensors"
1026
+ # Krea-2 / Fibo-2.0
1027
  - filename: "qwen3vl_4b_fp8_scaled.safetensors"
1028
  source: "hf"
1029
  repo_id: "Comfy-Org/Krea-2"
 
1066
  source: "hf"
1067
  repo_id: "Comfy-Org/Qwen3.8-27B"
1068
  repository_file_path: "text_encoders/qwen3.8_27b_w4a8.safetensors"
1069
+ # FLUX.3 Prompt Upsampler
1070
+ - filename: "qwen3.5_9b_flux3_prompt_upsampler_w4a8.safetensors"
1071
+ source: "hf"
1072
+ repo_id: "black-forest-labs/flux-3-image-klein-comfy"
1073
+ repository_file_path: "text_encoders/qwen3.5_9b_flux3_prompt_upsampler_w4a8.safetensors"
1074
  # PixelDiT
1075
  - filename: "gemma_2_2b_it_elm_fp8_scaled.safetensors"
1076
  source: "hf"
yaml/model_architecture_features.yaml CHANGED
@@ -1,4 +1,17 @@
1
  # Feature names in enabled_chains correspond 1-to-1 with chain_injectors/<name>_injector.py
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  krea-2:
3
  enabled_chains:
4
  - lora
@@ -70,6 +83,13 @@ z-image:
70
  - vae
71
  - pid
72
 
 
 
 
 
 
 
 
73
  ming-image:
74
  enabled_chains:
75
  - lora
 
1
  # Feature names in enabled_chains correspond 1-to-1 with chain_injectors/<name>_injector.py
2
+ fibo-2.0:
3
+ enabled_chains:
4
+ - lora
5
+ - reference_image
6
+ - pid
7
+
8
+ flux3:
9
+ enabled_chains:
10
+ - lora
11
+ - reference_latent
12
+ - flux3_prompt_upsampler
13
+ - pid
14
+
15
  krea-2:
16
  enabled_chains:
17
  - lora
 
83
  - vae
84
  - pid
85
 
86
+ moss-image:
87
+ enabled_chains:
88
+ - lora
89
+ - moss_image_control
90
+ - moss_image_edit
91
+ - pid
92
+
93
  ming-image:
94
  enabled_chains:
95
  - lora
yaml/model_architectures.yaml CHANGED
@@ -1,6 +1,9 @@
1
  architecture_order:
 
2
  - "Krea-2"
3
  - "Qwen-Image-2.1"
 
 
4
  - "Ming-Image"
5
  - "SenseNova-U1.5"
6
  - "Mage-Flow"
@@ -36,6 +39,10 @@ architecture_order:
36
  - "SD1.5"
37
 
38
  architectures:
 
 
 
 
39
  Krea-2:
40
  model_type: "krea-2"
41
  "SenseNova-U1.5":
@@ -63,6 +70,8 @@ architectures:
63
  "Z-Image":
64
  model_type: "z-image"
65
  controlnet_key: "Z-Image"
 
 
66
  "Ming-Image":
67
  model_type: "ming-image"
68
  "Qwen-Image-2.1":
 
1
  architecture_order:
2
+ - "FLUX.3"
3
  - "Krea-2"
4
  - "Qwen-Image-2.1"
5
+ - "Fibo-2.0"
6
+ - "MOSS-Image"
7
  - "Ming-Image"
8
  - "SenseNova-U1.5"
9
  - "Mage-Flow"
 
39
  - "SD1.5"
40
 
41
  architectures:
42
+ "Fibo-2.0":
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":
 
70
  "Z-Image":
71
  model_type: "z-image"
72
  controlnet_key: "Z-Image"
73
+ "MOSS-Image":
74
+ model_type: "moss-image"
75
  "Ming-Image":
76
  model_type: "ming-image"
77
  "Qwen-Image-2.1":
yaml/model_defaults.yaml CHANGED
@@ -1,11 +1,50 @@
1
  Default:
2
- steps: 8
3
- cfg: 1.0
4
  sampler_name: "euler"
5
  scheduler: "simple"
6
  positive_prompt: ""
7
  negative_prompt: ""
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  Krea-2:
10
  _defaults:
11
  steps: 52
@@ -17,6 +56,11 @@ Krea-2:
17
  cfg: 1.0
18
  sampler_name: "euler"
19
  scheduler: "simple"
 
 
 
 
 
20
 
21
  SenseNova-U1.5:
22
  _defaults:
@@ -131,6 +175,13 @@ Z-Image:
131
  steps: 9
132
  cfg: 1.0
133
 
 
 
 
 
 
 
 
134
  Ming-Image:
135
  _defaults:
136
  steps: 12
@@ -144,6 +195,24 @@ Qwen-Image-2.1:
144
  cfg: 1.0
145
  sampler_name: "euler"
146
  scheduler: "simple"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  Qwen-Image:
149
  _defaults:
@@ -199,6 +268,13 @@ Anima:
199
  scheduler: "simple"
200
  positive_prompt: "masterpiece, best quality, score_7, safe, "
201
  negative_prompt: "worst quality, low quality, score_1, score_2, score_3, blurry, jpeg artifacts, sepia"
 
 
 
 
 
 
 
202
  "circlestone-labs/Anima-Turbo-v1.0":
203
  steps: 10
204
  cfg: 1.0
 
1
  Default:
2
+ steps: 25
3
+ cfg: 3.5
4
  sampler_name: "euler"
5
  scheduler: "simple"
6
  positive_prompt: ""
7
  negative_prompt: ""
8
 
9
+ Fibo-2.0:
10
+ _defaults:
11
+ steps: 4
12
+ cfg: 1.0
13
+ sampler_name: "euler"
14
+ scheduler: "simple"
15
+ "Fibo-2.0-Edit-Turbo":
16
+ steps: 4
17
+ cfg: 1.0
18
+ sampler_name: "euler"
19
+ scheduler: "simple"
20
+ "Fibo-2.0-Dev":
21
+ steps: 30
22
+ cfg: 5.0
23
+ sampler_name: "euler"
24
+ scheduler: "simple"
25
+
26
+ FLUX.3:
27
+ _defaults:
28
+ steps: 25
29
+ cfg: 3.5
30
+ sampler_name: "euler"
31
+ scheduler: "simple"
32
+ "FLUX.3-Image-Klein":
33
+ steps: 25
34
+ cfg: 3.5
35
+ sampler_name: "euler"
36
+ scheduler: "simple"
37
+ "FLUX.3-Image-Klein-Distilled":
38
+ steps: 15
39
+ cfg: 1.0
40
+ sampler_name: "euler"
41
+ scheduler: "simple"
42
+ "FLUX-3-Image-Klein-Base":
43
+ steps: 50
44
+ cfg: 3.5
45
+ sampler_name: "euler"
46
+ scheduler: "simple"
47
+
48
  Krea-2:
49
  _defaults:
50
  steps: 52
 
56
  cfg: 1.0
57
  sampler_name: "euler"
58
  scheduler: "simple"
59
+ "Noctaluna/Krea2-Noct-Uncensored-V4":
60
+ steps: 8
61
+ cfg: 1.0
62
+ sampler_name: "euler"
63
+ scheduler: "simple"
64
 
65
  SenseNova-U1.5:
66
  _defaults:
 
175
  steps: 9
176
  cfg: 1.0
177
 
178
+ MOSS-Image:
179
+ _defaults:
180
+ steps: 50
181
+ cfg: 3.0
182
+ sampler_name: "euler"
183
+ scheduler: "simple"
184
+
185
  Ming-Image:
186
  _defaults:
187
  steps: 12
 
195
  cfg: 1.0
196
  sampler_name: "euler"
197
  scheduler: "simple"
198
+ "Qwen-Image-2.1-Turbo":
199
+ steps: 8
200
+ cfg: 1.0
201
+ sampler_name: "euler"
202
+ scheduler: "simple"
203
+ "Noctaluna/Noct-Q-Uncensored-Qwen-Image-2.1-V4":
204
+ steps: 25
205
+ cfg: 3.0
206
+ sampler_name: "euler"
207
+ scheduler: "simple"
208
+ negative_prompt: "artifacts, gpt-image, washed-out colors, low quality, low resolution, AI slop, deviantart, sloppy lines, rough sketch, blurry, indistinct, missing fingers, badly drawn hands, wrong number of fingers"
209
+ "Noctaluna/Noct-Q-Anime-Uncensored-Qwen-Image-2.1-V1":
210
+ steps: 25
211
+ cfg: 3.0
212
+ sampler_name: "euler"
213
+ scheduler: "simple"
214
+ positive_prompt: "An anime illustration of "
215
+ negative_prompt: "low quality, low resolution, blurry, jpeg artifacts, washed-out colors, sloppy lines, messy line art, extra fingers, missing fingers, badly drawn hands, deformed anatomy, 3d render, photograph, photorealistic"
216
 
217
  Qwen-Image:
218
  _defaults:
 
268
  scheduler: "simple"
269
  positive_prompt: "masterpiece, best quality, score_7, safe, "
270
  negative_prompt: "worst quality, low quality, score_1, score_2, score_3, blurry, jpeg artifacts, sepia"
271
+ "Noctaluna/Anima-Noct-Anime-Mix-V2":
272
+ steps: 30
273
+ cfg: 4.0
274
+ sampler_name: "euler"
275
+ scheduler: "simple"
276
+ positive_prompt: "masterpiece, best quality, safe, 1girl, solo, mature female, An anime illustration of "
277
+ negative_prompt: "worst quality, low quality, score_1, score_2, score_3, blurry, jpeg artifacts, sepia"
278
  "circlestone-labs/Anima-Turbo-v1.0":
279
  steps: 10
280
  cfg: 1.0
yaml/model_list.yaml CHANGED
@@ -1,4 +1,22 @@
1
  Checkpoint:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  Krea-2:
3
  latent_type: latent
4
  models:
@@ -12,6 +30,11 @@ Checkpoint:
12
  unet: "krea2_raw-nvfp4.safetensors"
13
  clip: "qwen3vl_4b_fp8_scaled.safetensors"
14
  vae: "qwen_image_vae.safetensors"
 
 
 
 
 
15
  Qwen-Image-2.1:
16
  latent_type: latent
17
  models:
@@ -20,11 +43,52 @@ Checkpoint:
20
  unet: "qwen_image_2.1_int8_convrot.safetensors"
21
  vae: "qwen_image_2.1_vae_bf16.safetensors"
22
  clip: "qwen3vl_8b_nvfp4.safetensors"
 
 
 
 
 
23
  - display_name: "abenzerps/Qwen-Image-2.1-Uncensored"
24
  components:
25
  unet: "qwen-image-2.1-UC-int8_convrot.safetensors"
26
  vae: "qwen_image_2.1_vae_bf16.safetensors"
27
  clip: "qwen3vl_8b_nvfp4.safetensors"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  Ming-Image:
29
  latent_type: latent
30
  models:
@@ -269,7 +333,6 @@ Checkpoint:
269
  clip: "qwen_3_06b_base.safetensors"
270
  qwen35_model: "qwen35_4b.safetensors"
271
  vae: "qwen_image_vae.safetensors"
272
-
273
  Anima-3.8B:
274
  latent_type: sd3_latent
275
  models:
@@ -280,35 +343,30 @@ Checkpoint:
280
  qwen35_model: "qwen35_4b.safetensors"
281
  adapter_name: "Anima-3.8B-expanded_adapter.safetensors"
282
  vae: "qwen_image_vae.safetensors"
283
-
284
  - display_name: "BoleroMixAnima-v0.14-3.8B"
285
  components:
286
  unet: "boleromixAnima_v01438B.safetensors"
287
  clip: "qwen_3_06b_base.safetensors"
288
  qwen35_model: "qwen35_4b.safetensors"
289
  vae: "qwen_image_vae.safetensors"
290
-
291
  - display_name: "FiaMix Anima 3.8B"
292
  components:
293
  unet: "fiamixAnima38B_v12.safetensors"
294
  clip: "qwen_3_06b_base.safetensors"
295
  qwen35_model: "qwen35_4b.safetensors"
296
  vae: "qwen_image_vae.safetensors"
297
-
298
  - display_name: "PPP Anima 3.8B / Qwen 0.6B"
299
  components:
300
  unet: "pppAnima38BQwen06B_v20.safetensors"
301
  clip: "qwen_3_06b_base.safetensors"
302
  qwen35_model: "qwen35_4b.safetensors"
303
  vae: "qwen_image_vae.safetensors"
304
-
305
  - display_name: "Anima Delta Mix 3.8B / Qwen 0.6B"
306
  components:
307
  unet: "animaDeltaMix38BQwen06B.safetensors"
308
  clip: "qwen_3_06b_base.safetensors"
309
  qwen35_model: "qwen35_4b.safetensors"
310
  vae: "qwen_image_vae.safetensors"
311
-
312
  - display_name: "FiaMix Anima 3.8B (new)"
313
  components:
314
  unet: "fiamixAnima38B_new.safetensors"
@@ -324,139 +382,121 @@ Checkpoint:
324
  unet: "anima-turbo-v1.0.safetensors"
325
  vae: "qwen_image_vae.safetensors"
326
  clip: "qwen_3_06b_base.safetensors"
327
-
328
  - display_name: "circlestone-labs/Anima-Aesthetic-v1.1"
329
  components:
330
  unet: "anima-aesthetic-v1.1.safetensors"
331
  vae: "qwen_image_vae.safetensors"
332
  clip: "qwen_3_06b_base.safetensors"
333
-
334
  - display_name: "circlestone-labs/Anima-Base-v1.0"
335
  components:
336
  unet: "anima-base-v1.0.safetensors"
337
  vae: "qwen_image_vae.safetensors"
338
  clip: "qwen_3_06b_base.safetensors"
339
-
340
  - display_name: "WAI0731/waiANIMA-v1.0"
341
  components:
342
  unet: "waiANIMA_v10Base10.safetensors"
343
  vae: "qwen_image_vae.safetensors"
344
  clip: "qwen_3_06b_base.safetensors"
345
-
346
  - display_name: "Gazingstars123/Anima-2.9B-preview-v1"
347
  components:
348
  unet: "Anima-2.9B-preview-v1.safetensors"
349
  vae: "qwen_image_vae.safetensors"
350
  clip: "qwen_3_06b_base.safetensors"
351
-
 
 
 
 
352
  - display_name: "duongve/AnimaYume-v1.5"
353
  components:
354
  unet: "AnimaYume_v15_base.safetensors"
355
  vae: "qwen_image_vae.safetensors"
356
  clip: "qwen_3_06b_base.safetensors"
357
-
358
  - display_name: "bluepen5805/Anima-pencil-v2.1"
359
  components:
360
  unet: "anima_pencil-v2.1.0.safetensors"
361
  vae: "qwen_image_vae.safetensors"
362
  clip: "qwen_3_06b_base.safetensors"
363
-
364
  - display_name: "Ikena/Hassaku-Anima-v1.3"
365
  components:
366
  unet: "hassakuAnima_v13.safetensors"
367
  vae: "qwen_image_vae.safetensors"
368
  clip: "qwen_3_06b_base.safetensors"
369
-
370
  - display_name: "motimalu/Kirazuri (Anima)-v4.0"
371
  components:
372
  unet: "anima-kirazuri-v4.safetensors"
373
  vae: "qwen_image_vae.safetensors"
374
  clip: "qwen_3_06b_base.safetensors"
375
-
376
  - display_name: "Crody/Nova-Anime-AM-v4.0"
377
  components:
378
  unet: "novaAnimeAM_v40.safetensors"
379
  vae: "qwen_image_vae.safetensors"
380
  clip: "qwen_3_06b_base.safetensors"
381
-
382
- - display_name: "Nova-Anime-AM-v5.0"
383
- components:
384
- unet: "NovaAnimeAMV50.safetensors"
385
- vae: "qwen_image_vae.safetensors"
386
- clip: "qwen_3_06b_base.safetensors"
387
-
388
  - display_name: "Crody/Nova-Cross-AM-v1.0-C"
389
  components:
390
  unet: "novaCrossAM_v10C.safetensors"
391
  vae: "qwen_image_vae.safetensors"
392
  clip: "qwen_3_06b_base.safetensors"
393
-
394
  - display_name: "Crody/Nova-Reality-AM-v1.0"
395
  components:
396
  unet: "novaRealityAM_v10.safetensors"
397
  vae: "qwen_image_vae.safetensors"
398
  clip: "qwen_3_06b_base.safetensors"
399
-
400
  - display_name: "Crody/RAMTHRUST'S-NSFW-PINK-ALCHEMY-ANIMA-v3.1"
401
  components:
402
  unet: "ramthrustsNSFWPINK_v31RAMSDelicious.safetensors"
403
  vae: "qwen_image_vae.safetensors"
404
  clip: "qwen_3_06b_base.safetensors"
405
-
 
 
 
 
406
  - display_name: "DreamCake"
407
  components:
408
  unet: "dreamcake_redvelvet.safetensors"
409
  vae: "qwen_image_vae.safetensors"
410
  clip: "qwen_3_06b_base.safetensors"
411
-
412
  - display_name: "HuggingReverie | Anima"
413
  components:
414
  unet: "huggingreverieAnima_v20B.safetensors"
415
  vae: "qwen_image_vae.safetensors"
416
  clip: "qwen_3_06b_base.safetensors"
417
-
418
  - display_name: "FiaMix Anima Reboot 2.5D"
419
  components:
420
  unet: "fiamixAnimaReboot25D_v11.safetensors"
421
  vae: "qwen_image_vae.safetensors"
422
  clip: "qwen_3_06b_base.safetensors"
423
-
424
  - display_name: "HybridCel Turbo - Anima Model"
425
  components:
426
  unet: "hybridcelTurboAnima_v30.safetensors"
427
  vae: "qwen_image_vae.safetensors"
428
  clip: "qwen_3_06b_base.safetensors"
429
-
430
  - display_name: "Anima Luc"
431
  components:
432
  unet: "animaLuc_v3a.safetensors"
433
  vae: "qwen_image_vae.safetensors"
434
  clip: "qwen_3_06b_base.safetensors"
435
-
436
  - display_name: "NoVa Bara - Male Anima"
437
  components:
438
  unet: "novaBaraMaleAnima_novaV20.safetensors"
439
  vae: "qwen_image_vae.safetensors"
440
  clip: "qwen_3_06b_base.safetensors"
441
-
442
  - display_name: "FiaMix Anima Reboot Cute"
443
  components:
444
  unet: "fiamixAnimaReboot_v17.safetensors"
445
  vae: "qwen_image_vae.safetensors"
446
  clip: "qwen_3_06b_base.safetensors"
447
-
448
  - display_name: "Nova-Cross-AM (new)"
449
  components:
450
  unet: "novaCrossAM_new.safetensors"
451
  vae: "qwen_image_vae.safetensors"
452
  clip: "qwen_3_06b_base.safetensors"
453
-
454
  - display_name: "Anima Delta Mix 2.9B"
455
  components:
456
  unet: "animaDeltaMix29B.safetensors"
457
  vae: "qwen_image_vae.safetensors"
458
  clip: "qwen_3_06b_base.safetensors"
459
-
460
  - display_name: "NuNuNu Mix"
461
  components:
462
  unet: "nununuMix.safetensors"
 
1
  Checkpoint:
2
+ FLUX.3:
3
+ latent_type: flux2_latent
4
+ models:
5
+ - display_name: "FLUX.3-Image-Klein"
6
+ components:
7
+ unet: "flux3-image-klein-nvfp4.safetensors"
8
+ clip: "qwen3vl_4b_fp8_scaled.safetensors"
9
+ vae: "flux2-vae.safetensors"
10
+ - display_name: "FLUX.3-Image-Klein-Distilled"
11
+ components:
12
+ unet: "flux3_image_klein_sd_nvfp4.safetensors"
13
+ clip: "qwen3vl_4b_fp8_scaled.safetensors"
14
+ vae: "flux2-vae.safetensors"
15
+ - display_name: "FLUX-3-Image-Klein-Base"
16
+ components:
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:
 
30
  unet: "krea2_raw-nvfp4.safetensors"
31
  clip: "qwen3vl_4b_fp8_scaled.safetensors"
32
  vae: "qwen_image_vae.safetensors"
33
+ - display_name: "Noctaluna/Krea2-Noct-Uncensored-V4"
34
+ components:
35
+ unet: "KreaNoct_V4_int8_convrot.safetensors"
36
+ clip: "qwen3vl_4b_fp8_scaled.safetensors"
37
+ vae: "qwen_image_vae.safetensors"
38
  Qwen-Image-2.1:
39
  latent_type: latent
40
  models:
 
43
  unet: "qwen_image_2.1_int8_convrot.safetensors"
44
  vae: "qwen_image_2.1_vae_bf16.safetensors"
45
  clip: "qwen3vl_8b_nvfp4.safetensors"
46
+ - display_name: "Qwen-Image-2.1-Turbo"
47
+ components:
48
+ unet: "qwen_image_2.1_turbo_int8_convrot.safetensors"
49
+ vae: "qwen_image_2.1_vae_bf16.safetensors"
50
+ clip: "qwen3vl_8b_nvfp4.safetensors"
51
  - display_name: "abenzerps/Qwen-Image-2.1-Uncensored"
52
  components:
53
  unet: "qwen-image-2.1-UC-int8_convrot.safetensors"
54
  vae: "qwen_image_2.1_vae_bf16.safetensors"
55
  clip: "qwen3vl_8b_nvfp4.safetensors"
56
+ - display_name: "Noctaluna/Noct-Q-Uncensored-Qwen-Image-2.1-V4"
57
+ components:
58
+ unet: "NoctQ_V4_int8_convrot.safetensors"
59
+ vae: "qwen_image_2.1_vae_bf16.safetensors"
60
+ clip: "qwen3vl_8b_nvfp4.safetensors"
61
+ - display_name: "Noctaluna/Noct-Q-Anime-Uncensored-Qwen-Image-2.1-V1"
62
+ components:
63
+ unet: "NoctQA_V1_int8_convrot.safetensors"
64
+ vae: "qwen_image_2.1_vae_bf16.safetensors"
65
+ clip: "qwen3vl_8b_nvfp4.safetensors"
66
+ Fibo-2.0:
67
+ latent_type: none
68
+ models:
69
+ - display_name: "Fibo-2.0-Edit-Turbo"
70
+ components:
71
+ unet: "fibo2_edit_turbo_int8_convrot.safetensors"
72
+ clip: "qwen3vl_4b_fp8_scaled.safetensors"
73
+ vae: "flux2-vae.safetensors"
74
+ - display_name: "Fibo-2.0-Dev"
75
+ components:
76
+ unet: "fibo2_dev_int8_convrot.safetensors"
77
+ clip: "qwen3vl_4b_fp8_scaled.safetensors"
78
+ vae: "flux2-vae.safetensors"
79
+ MOSS-Image:
80
+ latent_type: flux2_latent
81
+ models:
82
+ - display_name: "MOSS-Image-SFT"
83
+ components:
84
+ unet: "moss_image_sft_int8_convrot.safetensors"
85
+ clip: "qwen3.5_9b_w4a8_gptq.safetensors"
86
+ vae: "flux2-vae.safetensors"
87
+ - display_name: "MOSS-Image-Base"
88
+ components:
89
+ unet: "moss_image_base_int8_convrot.safetensors"
90
+ clip: "qwen3.5_9b_w4a8_gptq.safetensors"
91
+ vae: "flux2-vae.safetensors"
92
  Ming-Image:
93
  latent_type: latent
94
  models:
 
333
  clip: "qwen_3_06b_base.safetensors"
334
  qwen35_model: "qwen35_4b.safetensors"
335
  vae: "qwen_image_vae.safetensors"
 
336
  Anima-3.8B:
337
  latent_type: sd3_latent
338
  models:
 
343
  qwen35_model: "qwen35_4b.safetensors"
344
  adapter_name: "Anima-3.8B-expanded_adapter.safetensors"
345
  vae: "qwen_image_vae.safetensors"
 
346
  - display_name: "BoleroMixAnima-v0.14-3.8B"
347
  components:
348
  unet: "boleromixAnima_v01438B.safetensors"
349
  clip: "qwen_3_06b_base.safetensors"
350
  qwen35_model: "qwen35_4b.safetensors"
351
  vae: "qwen_image_vae.safetensors"
 
352
  - display_name: "FiaMix Anima 3.8B"
353
  components:
354
  unet: "fiamixAnima38B_v12.safetensors"
355
  clip: "qwen_3_06b_base.safetensors"
356
  qwen35_model: "qwen35_4b.safetensors"
357
  vae: "qwen_image_vae.safetensors"
 
358
  - display_name: "PPP Anima 3.8B / Qwen 0.6B"
359
  components:
360
  unet: "pppAnima38BQwen06B_v20.safetensors"
361
  clip: "qwen_3_06b_base.safetensors"
362
  qwen35_model: "qwen35_4b.safetensors"
363
  vae: "qwen_image_vae.safetensors"
 
364
  - display_name: "Anima Delta Mix 3.8B / Qwen 0.6B"
365
  components:
366
  unet: "animaDeltaMix38BQwen06B.safetensors"
367
  clip: "qwen_3_06b_base.safetensors"
368
  qwen35_model: "qwen35_4b.safetensors"
369
  vae: "qwen_image_vae.safetensors"
 
370
  - display_name: "FiaMix Anima 3.8B (new)"
371
  components:
372
  unet: "fiamixAnima38B_new.safetensors"
 
382
  unet: "anima-turbo-v1.0.safetensors"
383
  vae: "qwen_image_vae.safetensors"
384
  clip: "qwen_3_06b_base.safetensors"
 
385
  - display_name: "circlestone-labs/Anima-Aesthetic-v1.1"
386
  components:
387
  unet: "anima-aesthetic-v1.1.safetensors"
388
  vae: "qwen_image_vae.safetensors"
389
  clip: "qwen_3_06b_base.safetensors"
 
390
  - display_name: "circlestone-labs/Anima-Base-v1.0"
391
  components:
392
  unet: "anima-base-v1.0.safetensors"
393
  vae: "qwen_image_vae.safetensors"
394
  clip: "qwen_3_06b_base.safetensors"
 
395
  - display_name: "WAI0731/waiANIMA-v1.0"
396
  components:
397
  unet: "waiANIMA_v10Base10.safetensors"
398
  vae: "qwen_image_vae.safetensors"
399
  clip: "qwen_3_06b_base.safetensors"
 
400
  - display_name: "Gazingstars123/Anima-2.9B-preview-v1"
401
  components:
402
  unet: "Anima-2.9B-preview-v1.safetensors"
403
  vae: "qwen_image_vae.safetensors"
404
  clip: "qwen_3_06b_base.safetensors"
405
+ - display_name: "Noctaluna/Anima-Noct-Anime-Mix-V2"
406
+ components:
407
+ unet: "AnimaNoct_V2_int8_convrot.safetensors"
408
+ vae: "qwen_image_vae.safetensors"
409
+ clip: "qwen_3_06b_base.safetensors"
410
  - display_name: "duongve/AnimaYume-v1.5"
411
  components:
412
  unet: "AnimaYume_v15_base.safetensors"
413
  vae: "qwen_image_vae.safetensors"
414
  clip: "qwen_3_06b_base.safetensors"
 
415
  - display_name: "bluepen5805/Anima-pencil-v2.1"
416
  components:
417
  unet: "anima_pencil-v2.1.0.safetensors"
418
  vae: "qwen_image_vae.safetensors"
419
  clip: "qwen_3_06b_base.safetensors"
 
420
  - display_name: "Ikena/Hassaku-Anima-v1.3"
421
  components:
422
  unet: "hassakuAnima_v13.safetensors"
423
  vae: "qwen_image_vae.safetensors"
424
  clip: "qwen_3_06b_base.safetensors"
 
425
  - display_name: "motimalu/Kirazuri (Anima)-v4.0"
426
  components:
427
  unet: "anima-kirazuri-v4.safetensors"
428
  vae: "qwen_image_vae.safetensors"
429
  clip: "qwen_3_06b_base.safetensors"
 
430
  - display_name: "Crody/Nova-Anime-AM-v4.0"
431
  components:
432
  unet: "novaAnimeAM_v40.safetensors"
433
  vae: "qwen_image_vae.safetensors"
434
  clip: "qwen_3_06b_base.safetensors"
 
 
 
 
 
 
 
435
  - display_name: "Crody/Nova-Cross-AM-v1.0-C"
436
  components:
437
  unet: "novaCrossAM_v10C.safetensors"
438
  vae: "qwen_image_vae.safetensors"
439
  clip: "qwen_3_06b_base.safetensors"
 
440
  - display_name: "Crody/Nova-Reality-AM-v1.0"
441
  components:
442
  unet: "novaRealityAM_v10.safetensors"
443
  vae: "qwen_image_vae.safetensors"
444
  clip: "qwen_3_06b_base.safetensors"
 
445
  - display_name: "Crody/RAMTHRUST'S-NSFW-PINK-ALCHEMY-ANIMA-v3.1"
446
  components:
447
  unet: "ramthrustsNSFWPINK_v31RAMSDelicious.safetensors"
448
  vae: "qwen_image_vae.safetensors"
449
  clip: "qwen_3_06b_base.safetensors"
450
+ - display_name: "Nova-Anime-AM-v5.0"
451
+ components:
452
+ unet: "NovaAnimeAMV50.safetensors"
453
+ vae: "qwen_image_vae.safetensors"
454
+ clip: "qwen_3_06b_base.safetensors"
455
  - display_name: "DreamCake"
456
  components:
457
  unet: "dreamcake_redvelvet.safetensors"
458
  vae: "qwen_image_vae.safetensors"
459
  clip: "qwen_3_06b_base.safetensors"
 
460
  - display_name: "HuggingReverie | Anima"
461
  components:
462
  unet: "huggingreverieAnima_v20B.safetensors"
463
  vae: "qwen_image_vae.safetensors"
464
  clip: "qwen_3_06b_base.safetensors"
 
465
  - display_name: "FiaMix Anima Reboot 2.5D"
466
  components:
467
  unet: "fiamixAnimaReboot25D_v11.safetensors"
468
  vae: "qwen_image_vae.safetensors"
469
  clip: "qwen_3_06b_base.safetensors"
 
470
  - display_name: "HybridCel Turbo - Anima Model"
471
  components:
472
  unet: "hybridcelTurboAnima_v30.safetensors"
473
  vae: "qwen_image_vae.safetensors"
474
  clip: "qwen_3_06b_base.safetensors"
 
475
  - display_name: "Anima Luc"
476
  components:
477
  unet: "animaLuc_v3a.safetensors"
478
  vae: "qwen_image_vae.safetensors"
479
  clip: "qwen_3_06b_base.safetensors"
 
480
  - display_name: "NoVa Bara - Male Anima"
481
  components:
482
  unet: "novaBaraMaleAnima_novaV20.safetensors"
483
  vae: "qwen_image_vae.safetensors"
484
  clip: "qwen_3_06b_base.safetensors"
 
485
  - display_name: "FiaMix Anima Reboot Cute"
486
  components:
487
  unet: "fiamixAnimaReboot_v17.safetensors"
488
  vae: "qwen_image_vae.safetensors"
489
  clip: "qwen_3_06b_base.safetensors"
 
490
  - display_name: "Nova-Cross-AM (new)"
491
  components:
492
  unet: "novaCrossAM_new.safetensors"
493
  vae: "qwen_image_vae.safetensors"
494
  clip: "qwen_3_06b_base.safetensors"
 
495
  - display_name: "Anima Delta Mix 2.9B"
496
  components:
497
  unet: "animaDeltaMix29B.safetensors"
498
  vae: "qwen_image_vae.safetensors"
499
  clip: "qwen_3_06b_base.safetensors"
 
500
  - display_name: "NuNuNu Mix"
501
  components:
502
  unet: "nununuMix.safetensors"
yaml/pid.yaml CHANGED
@@ -1,7 +1,7 @@
1
  PiD:
2
  - filepath: "pid_1.5_flux2_1024_to_4096_4step_int8_convrot.safetensors"
3
  latent_format: "flux"
4
- architectures: ["flux2", "flux2-kv", "ideogram-4", "lens", "ernie-image"]
5
  - filepath: "pid_1.5_qwenimage_1024_to_4096_4step_int8_convrot.safetensors"
6
  latent_format: "qwenimage"
7
  architectures: ["anima-3.8b-v1.1", "anima-3.8b", "anima", "qwen-image", "krea-2"]
 
1
  PiD:
2
  - filepath: "pid_1.5_flux2_1024_to_4096_4step_int8_convrot.safetensors"
3
  latent_format: "flux"
4
+ architectures: ["flux3", "flux2", "flux2-kv", "ideogram-4", "lens", "ernie-image", "moss-image", "fibo-2.0"]
5
  - filepath: "pid_1.5_qwenimage_1024_to_4096_4step_int8_convrot.safetensors"
6
  latent_format: "qwenimage"
7
  architectures: ["anima-3.8b-v1.1", "anima-3.8b", "anima", "qwen-image", "krea-2"]
yaml/task_features.yaml CHANGED
@@ -24,6 +24,9 @@ txt2img:
24
  - reference_image
25
  - qwen_image_2_1_prompt_enhancer
26
  - ming_image_prompt_enhancer
 
 
 
27
  - pid
28
  - vae
29
 
@@ -48,6 +51,9 @@ img2img:
48
  - reference_image
49
  - qwen_image_2_1_prompt_enhancer
50
  - ming_image_prompt_enhancer
 
 
 
51
  - vae
52
 
53
  inpaint:
@@ -71,6 +77,9 @@ inpaint:
71
  - reference_image
72
  - qwen_image_2_1_prompt_enhancer
73
  - ming_image_prompt_enhancer
 
 
 
74
  - vae
75
 
76
  outpaint:
@@ -94,6 +103,9 @@ outpaint:
94
  - reference_image
95
  - qwen_image_2_1_prompt_enhancer
96
  - ming_image_prompt_enhancer
 
 
 
97
  - vae
98
 
99
  hires_fix:
@@ -117,4 +129,7 @@ hires_fix:
117
  - reference_image
118
  - qwen_image_2_1_prompt_enhancer
119
  - ming_image_prompt_enhancer
 
 
 
120
  - vae
 
24
  - reference_image
25
  - qwen_image_2_1_prompt_enhancer
26
  - ming_image_prompt_enhancer
27
+ - flux3_prompt_upsampler
28
+ - moss_image_control
29
+ - moss_image_edit
30
  - pid
31
  - vae
32
 
 
51
  - reference_image
52
  - qwen_image_2_1_prompt_enhancer
53
  - ming_image_prompt_enhancer
54
+ - flux3_prompt_upsampler
55
+ - moss_image_control
56
+ - moss_image_edit
57
  - vae
58
 
59
  inpaint:
 
77
  - reference_image
78
  - qwen_image_2_1_prompt_enhancer
79
  - ming_image_prompt_enhancer
80
+ - flux3_prompt_upsampler
81
+ - moss_image_control
82
+ - moss_image_edit
83
  - vae
84
 
85
  outpaint:
 
103
  - reference_image
104
  - qwen_image_2_1_prompt_enhancer
105
  - ming_image_prompt_enhancer
106
+ - flux3_prompt_upsampler
107
+ - moss_image_control
108
+ - moss_image_edit
109
  - vae
110
 
111
  hires_fix:
 
129
  - reference_image
130
  - qwen_image_2_1_prompt_enhancer
131
  - ming_image_prompt_enhancer
132
+ - flux3_prompt_upsampler
133
+ - moss_image_control
134
+ - moss_image_edit
135
  - vae