Spaces:
Running on Zero
Running on Zero
refactor: unify prompt enhancer chains into a data-driven pipeline
Browse filesReplace the three per-architecture prompt enhancer injectors with a single
registry-driven implementation, and modernize the node it emits.
- chain_injectors/__init__.py +12 -0
- chain_injectors/flux3_prompt_upsampler_injector.py +0 -304
- chain_injectors/ming_image_prompt_enhancer_injector.py +0 -262
- chain_injectors/prompt_enhancer_injector.py +489 -0
- chain_injectors/qwen_image_2_1_prompt_enhancer_injector.py +0 -245
- core/pipelines/pipeline_input_processor.py +13 -35
- core/pipelines/sd_image_pipeline.py +2 -6
- core/pipelines/workflow_executor.py +86 -1
- core/pipelines/workflow_recipes/_partials/conditioning/flux3.yaml +3 -2
- core/pipelines/workflow_recipes/_partials/conditioning/ming-image.yaml +3 -2
- core/pipelines/workflow_recipes/_partials/conditioning/qwen-image-2.1.yaml +3 -2
- core/workflow_assembler.py +30 -6
- mcp_tools/common.py +25 -145
- ui/events/change_handlers.py +13 -9
- ui/events/main.py +13 -17
- ui/events/run_handlers.py +10 -25
- ui/imagegen_ui.py +2 -5
- ui/shared/ui_components.py +916 -947
- yaml/chain_features.yaml +13 -61
- yaml/model_architecture_features.yaml +3 -3
- yaml/prompt_enhancer.yaml +65 -0
- yaml/task_features.yaml +5 -15
chain_injectors/__init__.py
CHANGED
|
@@ -42,6 +42,18 @@ def get_registered_features():
|
|
| 42 |
full_module_name = f"chain_injectors.{module_name}"
|
| 43 |
chain_type = f"dynamic_{feature_name}_chains"
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
features[feature_name] = {
|
| 46 |
'module': full_module_name,
|
| 47 |
'chain_type': chain_type
|
|
|
|
| 42 |
full_module_name = f"chain_injectors.{module_name}"
|
| 43 |
chain_type = f"dynamic_{feature_name}_chains"
|
| 44 |
|
| 45 |
+
# Honour an explicit CHAIN_TYPE the same way discover_injectors() does.
|
| 46 |
+
# Without this, a module that declares CHAIN_TYPE = "foo" is registered
|
| 47 |
+
# under "foo" by discover_injectors() but looked up here as
|
| 48 |
+
# "dynamic_foo_chains", so the assembler silently skips it.
|
| 49 |
+
try:
|
| 50 |
+
module = importlib.import_module(full_module_name)
|
| 51 |
+
declared = getattr(module, 'CHAIN_TYPE', None)
|
| 52 |
+
if declared:
|
| 53 |
+
chain_type = declared
|
| 54 |
+
except Exception:
|
| 55 |
+
pass
|
| 56 |
+
|
| 57 |
features[feature_name] = {
|
| 58 |
'module': full_module_name,
|
| 59 |
'chain_type': chain_type
|
chain_injectors/flux3_prompt_upsampler_injector.py
DELETED
|
@@ -1,304 +0,0 @@
|
|
| 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/ming_image_prompt_enhancer_injector.py
DELETED
|
@@ -1,262 +0,0 @@
|
|
| 1 |
-
"""Ming-Image Prompt Enhancer Chain Injector.
|
| 2 |
-
|
| 3 |
-
This injector automatically integrates the Qwen3.8-27B prompt enhancement model into
|
| 4 |
-
the Ming-Image generation workflow:
|
| 5 |
-
- Detects whether the task is pure text-to-image (T2I) or involves images (I2I, inpainting,
|
| 6 |
-
outpainting, hires.fix, or multi-reference images).
|
| 7 |
-
- Uses the Qwen3.8-27B PE text encoder: qwen3.8_27b_w4a8.safetensors (type: qwen_image).
|
| 8 |
-
- Handles single or multi-reference inputs: multiple reference images are stitched into
|
| 9 |
-
a structured grid layout via ComfyUI native ImageStitch nodes and scaled via ImageScaleToTotalPixels.
|
| 10 |
-
- Supports reasoning mode (thinking=True) leveraging ComfyUI TextGenerate native reasoning separation
|
| 11 |
-
(output slot 0 outputs clean prompt text, while output slot 1 isolates reasoning chain).
|
| 12 |
-
"""
|
| 13 |
-
|
| 14 |
-
try:
|
| 15 |
-
from utils.app_utils import ensure_file_downloaded
|
| 16 |
-
except ImportError:
|
| 17 |
-
ensure_file_downloaded = None
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
DEFAULT_MING_IMAGE_SYSTEM_PROMPT = (
|
| 21 |
-
"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. "
|
| 22 |
-
"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. "
|
| 23 |
-
'`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. '
|
| 24 |
-
"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. "
|
| 25 |
-
"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.\" "
|
| 26 |
-
"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. "
|
| 27 |
-
"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. "
|
| 28 |
-
"Silently verify schema, string coordinates, Z-order, exact-text counts, geometry, bbox validity, and completeness."
|
| 29 |
-
)
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
def create_node(assembler, class_type, title):
|
| 33 |
-
"""Create a workflow node dictionary with fallback template generation."""
|
| 34 |
-
try:
|
| 35 |
-
node = assembler._get_node_template(class_type)
|
| 36 |
-
except Exception:
|
| 37 |
-
node = {
|
| 38 |
-
"inputs": {},
|
| 39 |
-
"class_type": class_type,
|
| 40 |
-
"_meta": {"title": title}
|
| 41 |
-
}
|
| 42 |
-
if "_meta" not in node:
|
| 43 |
-
node["_meta"] = {}
|
| 44 |
-
node['_meta']['title'] = title
|
| 45 |
-
return node
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
def inject(assembler, chain_definition, chain_items):
|
| 49 |
-
"""Inject Ming-Image prompt enhancer nodes into the target generation workflow."""
|
| 50 |
-
if not chain_items:
|
| 51 |
-
return
|
| 52 |
-
|
| 53 |
-
is_enabled = False
|
| 54 |
-
thinking_enabled = False
|
| 55 |
-
max_length = 4096
|
| 56 |
-
system_prompt = DEFAULT_MING_IMAGE_SYSTEM_PROMPT
|
| 57 |
-
for item in chain_items:
|
| 58 |
-
if isinstance(item, dict):
|
| 59 |
-
if item.get("enable", False) or item.get("enabled", False) or item.get("value", False):
|
| 60 |
-
is_enabled = True
|
| 61 |
-
if item.get("thinking", False) in (True, "true", "on", "yes", "1", 1):
|
| 62 |
-
thinking_enabled = True
|
| 63 |
-
if "max_length" in item and item["max_length"]:
|
| 64 |
-
try:
|
| 65 |
-
max_length = int(item["max_length"])
|
| 66 |
-
except (ValueError, TypeError):
|
| 67 |
-
pass
|
| 68 |
-
if "system_prompt" in item and item["system_prompt"]:
|
| 69 |
-
system_prompt = str(item["system_prompt"])
|
| 70 |
-
elif item is True or str(item).lower() in ("on", "true", "yes", "1"):
|
| 71 |
-
is_enabled = True
|
| 72 |
-
break
|
| 73 |
-
|
| 74 |
-
if not is_enabled:
|
| 75 |
-
return
|
| 76 |
-
|
| 77 |
-
target_node_name = chain_definition.get('target_node', 'prompt')
|
| 78 |
-
target_node_id = assembler.node_map.get(target_node_name)
|
| 79 |
-
|
| 80 |
-
if not target_node_id or target_node_id not in assembler.workflow:
|
| 81 |
-
for node_id, node in assembler.workflow.items():
|
| 82 |
-
if isinstance(node, dict) and node.get('class_type') == 'TextEncodeMingImageEdit':
|
| 83 |
-
target_node_id = node_id
|
| 84 |
-
break
|
| 85 |
-
|
| 86 |
-
if not target_node_id or target_node_id not in assembler.workflow:
|
| 87 |
-
print(f"Warning: Target node '{target_node_name}' (TextEncodeMingImageEdit) not found for Ming-Image Prompt Enhancer. Skipping.")
|
| 88 |
-
return
|
| 89 |
-
|
| 90 |
-
target_node = assembler.workflow[target_node_id]
|
| 91 |
-
original_prompt = target_node.get('inputs', {}).get('prompt', '')
|
| 92 |
-
|
| 93 |
-
# 1. Check if any LoadImage nodes exist in the workflow
|
| 94 |
-
load_image_nodes = [
|
| 95 |
-
node_id for node_id, node in assembler.workflow.items()
|
| 96 |
-
if isinstance(node, dict) and node.get('class_type') == 'LoadImage'
|
| 97 |
-
]
|
| 98 |
-
has_load_image = len(load_image_nodes) > 0
|
| 99 |
-
|
| 100 |
-
target_image_conn = None
|
| 101 |
-
clip_model_name = "qwen3.8_27b_w4a8.safetensors"
|
| 102 |
-
|
| 103 |
-
if has_load_image:
|
| 104 |
-
# Collect image connections
|
| 105 |
-
# Prioritize extracting all images.image_* slots from target_node['inputs'] (injected by reference image injectors)
|
| 106 |
-
indexed_slots = []
|
| 107 |
-
target_inputs = target_node.get('inputs', {})
|
| 108 |
-
for slot_key, slot_val in target_inputs.items():
|
| 109 |
-
if slot_key.startswith('images.image_') and isinstance(slot_val, (list, tuple)):
|
| 110 |
-
try:
|
| 111 |
-
idx = int(slot_key.split('_')[-1])
|
| 112 |
-
except Exception:
|
| 113 |
-
idx = 999
|
| 114 |
-
indexed_slots.append((idx, slot_val))
|
| 115 |
-
|
| 116 |
-
indexed_slots.sort(key=lambda x: x[0])
|
| 117 |
-
ref_image_conns = [s[1] for s in indexed_slots]
|
| 118 |
-
|
| 119 |
-
# If no reference image slots exist (e.g. pure img2img / inpaint / outpaint), retrieve from LoadImage nodes
|
| 120 |
-
if not ref_image_conns:
|
| 121 |
-
for lid in load_image_nodes:
|
| 122 |
-
scaled_conn = None
|
| 123 |
-
for nid, node in assembler.workflow.items():
|
| 124 |
-
if isinstance(node, dict) and node.get('class_type') == 'ImageScaleToTotalPixels':
|
| 125 |
-
img_input = node.get('inputs', {}).get('image')
|
| 126 |
-
if isinstance(img_input, (list, tuple)) and str(img_input[0]) == str(lid):
|
| 127 |
-
scaled_conn = [nid, 0]
|
| 128 |
-
break
|
| 129 |
-
if scaled_conn:
|
| 130 |
-
ref_image_conns.append(scaled_conn)
|
| 131 |
-
else:
|
| 132 |
-
scale_id = assembler._get_unique_id()
|
| 133 |
-
scale_node = create_node(assembler, "ImageScaleToTotalPixels", "Scale Input Image")
|
| 134 |
-
scale_node['inputs'] = {
|
| 135 |
-
"upscale_method": "nearest-exact",
|
| 136 |
-
"megapixels": 1,
|
| 137 |
-
"resolution_steps": 1,
|
| 138 |
-
"image": [lid, 0]
|
| 139 |
-
}
|
| 140 |
-
assembler.workflow[scale_id] = scale_node
|
| 141 |
-
ref_image_conns.append([scale_id, 0])
|
| 142 |
-
|
| 143 |
-
# Organize multiple reference images into grid stitching
|
| 144 |
-
if len(ref_image_conns) == 1:
|
| 145 |
-
target_image_conn = ref_image_conns[0]
|
| 146 |
-
elif len(ref_image_conns) >= 2:
|
| 147 |
-
num_cols = 2 if len(ref_image_conns) <= 4 else 3
|
| 148 |
-
|
| 149 |
-
rows = []
|
| 150 |
-
for i in range(0, len(ref_image_conns), num_cols):
|
| 151 |
-
rows.append(ref_image_conns[i:i + num_cols])
|
| 152 |
-
|
| 153 |
-
# Horizontally stitch images in each row
|
| 154 |
-
stitched_rows = []
|
| 155 |
-
for r_idx, row_imgs in enumerate(rows):
|
| 156 |
-
if len(row_imgs) == 1:
|
| 157 |
-
stitched_rows.append(row_imgs[0])
|
| 158 |
-
else:
|
| 159 |
-
curr_stitch = row_imgs[0]
|
| 160 |
-
for c_idx in range(1, len(row_imgs)):
|
| 161 |
-
st_id = assembler._get_unique_id()
|
| 162 |
-
st_node = create_node(assembler, "ImageStitch", f"Stitch Images (Row {r_idx+1}-{c_idx})")
|
| 163 |
-
st_node['inputs'] = {
|
| 164 |
-
"direction": "right",
|
| 165 |
-
"match_image_size": True,
|
| 166 |
-
"spacing_width": 0,
|
| 167 |
-
"spacing_color": "white",
|
| 168 |
-
"image1": curr_stitch,
|
| 169 |
-
"image2": row_imgs[c_idx]
|
| 170 |
-
}
|
| 171 |
-
assembler.workflow[st_id] = st_node
|
| 172 |
-
curr_stitch = [st_id, 0]
|
| 173 |
-
stitched_rows.append(curr_stitch)
|
| 174 |
-
|
| 175 |
-
# Vertically stitch multiple rows
|
| 176 |
-
if len(stitched_rows) == 1:
|
| 177 |
-
final_stitched = stitched_rows[0]
|
| 178 |
-
else:
|
| 179 |
-
curr_vertical = stitched_rows[0]
|
| 180 |
-
for r_idx in range(1, len(stitched_rows)):
|
| 181 |
-
st_id = assembler._get_unique_id()
|
| 182 |
-
st_node = create_node(assembler, "ImageStitch", f"Stitch Images (Vertical {r_idx})")
|
| 183 |
-
st_node['inputs'] = {
|
| 184 |
-
"direction": "down",
|
| 185 |
-
"match_image_size": True,
|
| 186 |
-
"spacing_width": 0,
|
| 187 |
-
"spacing_color": "white",
|
| 188 |
-
"image1": curr_vertical,
|
| 189 |
-
"image2": stitched_rows[r_idx]
|
| 190 |
-
}
|
| 191 |
-
assembler.workflow[st_id] = st_node
|
| 192 |
-
curr_vertical = [st_id, 0]
|
| 193 |
-
final_stitched = curr_vertical
|
| 194 |
-
|
| 195 |
-
# Scale final stitched image to ~1024 level via ImageScaleToTotalPixels
|
| 196 |
-
final_scale_id = assembler._get_unique_id()
|
| 197 |
-
final_scale_node = create_node(assembler, "ImageScaleToTotalPixels", "Scale Stitched Image")
|
| 198 |
-
final_scale_node['inputs'] = {
|
| 199 |
-
"upscale_method": "nearest-exact",
|
| 200 |
-
"megapixels": 1,
|
| 201 |
-
"resolution_steps": 1,
|
| 202 |
-
"image": final_stitched
|
| 203 |
-
}
|
| 204 |
-
assembler.workflow[final_scale_id] = final_scale_node
|
| 205 |
-
target_image_conn = [final_scale_id, 0]
|
| 206 |
-
|
| 207 |
-
# 2. Ensure model is downloaded in CPU stage before workflow execution
|
| 208 |
-
if ensure_file_downloaded:
|
| 209 |
-
try:
|
| 210 |
-
ensure_file_downloaded(clip_model_name)
|
| 211 |
-
except Exception as e:
|
| 212 |
-
print(f"Warning: Failed to ensure '{clip_model_name}' downloaded: {e}")
|
| 213 |
-
|
| 214 |
-
# 3. Create CLIPLoader node (using qwen_image type for Qwen3.8-27B PE)
|
| 215 |
-
clip_node_id = assembler._get_unique_id()
|
| 216 |
-
clip_node = create_node(assembler, "CLIPLoader", "Load CLIP")
|
| 217 |
-
clip_node['inputs'] = {
|
| 218 |
-
"clip_name": clip_model_name,
|
| 219 |
-
"type": "qwen_image",
|
| 220 |
-
"device": "default"
|
| 221 |
-
}
|
| 222 |
-
assembler.workflow[clip_node_id] = clip_node
|
| 223 |
-
|
| 224 |
-
# 4. Create PrimitiveString node for System Prompt (Text node)
|
| 225 |
-
system_prompt_node_id = assembler._get_unique_id()
|
| 226 |
-
system_prompt_node = create_node(assembler, "PrimitiveString", "Text")
|
| 227 |
-
system_prompt_node['inputs'] = {
|
| 228 |
-
"value": system_prompt
|
| 229 |
-
}
|
| 230 |
-
assembler.workflow[system_prompt_node_id] = system_prompt_node
|
| 231 |
-
|
| 232 |
-
# 5. Create TextGenerate node
|
| 233 |
-
text_gen_node_id = assembler._get_unique_id()
|
| 234 |
-
text_gen_node = create_node(assembler, "TextGenerate", "Generate Text")
|
| 235 |
-
text_gen_inputs = {
|
| 236 |
-
"prompt": original_prompt,
|
| 237 |
-
"max_length": max_length,
|
| 238 |
-
"sampling_mode": "on",
|
| 239 |
-
"sampling_mode.temperature": 0.7,
|
| 240 |
-
"sampling_mode.top_k": 64,
|
| 241 |
-
"sampling_mode.top_p": 0.95,
|
| 242 |
-
"sampling_mode.min_p": 0.05,
|
| 243 |
-
"sampling_mode.repetition_penalty": 1.05,
|
| 244 |
-
"sampling_mode.seed": 0,
|
| 245 |
-
"sampling_mode.presence_penalty": 0,
|
| 246 |
-
"thinking": thinking_enabled,
|
| 247 |
-
"use_default_template": True,
|
| 248 |
-
"mtp": "auto",
|
| 249 |
-
"clip": [clip_node_id, 0],
|
| 250 |
-
"system_prompt": [system_prompt_node_id, 0]
|
| 251 |
-
}
|
| 252 |
-
if target_image_conn is not None:
|
| 253 |
-
text_gen_inputs["image"] = target_image_conn
|
| 254 |
-
|
| 255 |
-
text_gen_node['inputs'] = text_gen_inputs
|
| 256 |
-
assembler.workflow[text_gen_node_id] = text_gen_node
|
| 257 |
-
|
| 258 |
-
# 6. Connect TextGenerate output directly to target node prompt
|
| 259 |
-
# ComfyUI TextGenerate node natively strips reasoning tags (<think>...</think>) into output slot 1,
|
| 260 |
-
# leaving clean enhanced prompt in output slot 0.
|
| 261 |
-
target_node['inputs']['prompt'] = [text_gen_node_id, 0]
|
| 262 |
-
print(f"[Injector] Ming-Image Prompt Enhancer applied successfully ({'I2I' if has_load_image else 'T2I'}, clip='{clip_model_name}', thinking={thinking_enabled}).")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chain_injectors/prompt_enhancer_injector.py
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Prompt Enhancer Chain Injector (data-driven).
|
| 2 |
+
|
| 3 |
+
Merges the former qwen_image_2_1_prompt_enhancer / ming_image_prompt_enhancer /
|
| 4 |
+
flux3_prompt_upsampler injectors into a single implementation driven by
|
| 5 |
+
``yaml/prompt_enhancer.yaml``.
|
| 6 |
+
|
| 7 |
+
The YAML declares three independent modules, named after their UI labels in the
|
| 8 |
+
"Prompt Enhancer Settings" accordion:
|
| 9 |
+
|
| 10 |
+
* ``architecture`` Model framework -> default ``clip`` (Prompt Enhancer Model) and
|
| 11 |
+
default ``system_prompt`` (System Prompt). These are the dropdown defaults
|
| 12 |
+
applied whenever the base model changes. A missing entry falls back to the
|
| 13 |
+
first registry item.
|
| 14 |
+
* ``clip`` Every name shown in the "Prompt Enhancer Model" dropdown,
|
| 15 |
+
mapped to its checkpoint ``filename`` and ``clip_type``. Declaration order is
|
| 16 |
+
dropdown order.
|
| 17 |
+
* ``system_prompt`` Every name shown in the "System Prompt" dropdown, mapped to
|
| 18 |
+
the system prompt sent to ``TextGenerateV2``. An empty value means no system
|
| 19 |
+
prompt.
|
| 20 |
+
|
| 21 |
+
The modules are fully decoupled: any framework may pick any clip, and any clip
|
| 22 |
+
may be paired with any system prompt. ``clip_type`` therefore travels with the
|
| 23 |
+
clip, not with the framework. There is no image-based or bbox auto-detection.
|
| 24 |
+
|
| 25 |
+
The injector then:
|
| 26 |
+
|
| 27 |
+
1. Resolves the target text-encode node that receives the enhanced prompt.
|
| 28 |
+
2. Collects reference images (native ``images.image_N`` slots first, otherwise
|
| 29 |
+
``LoadImage`` nodes) and feeds each one into its own autogrow slot.
|
| 30 |
+
3. Loads the selected checkpoint through ``CLIPLoader`` and, when the chosen
|
| 31 |
+
system prompt carries text, materialises it as a primitive string node.
|
| 32 |
+
4. Emits a ``TextGenerateV2`` node (Sampling Mode ``off``) and wires its clean
|
| 33 |
+
``generated_text`` output into the downstream positive prompt.
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
import os
|
| 37 |
+
|
| 38 |
+
import yaml
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
from utils.app_utils import ensure_file_downloaded
|
| 42 |
+
except ImportError:
|
| 43 |
+
ensure_file_downloaded = None
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
CHAIN_TYPE = "prompt_enhancer"
|
| 47 |
+
|
| 48 |
+
_YAML_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "yaml")
|
| 49 |
+
_CONFIG_PATH = os.path.join(_YAML_DIR, "prompt_enhancer.yaml")
|
| 50 |
+
|
| 51 |
+
# Node class -> (target node names to try, input keys holding the source prompt)
|
| 52 |
+
_TARGET_HINTS = (
|
| 53 |
+
("TextEncodeQwenImage21", ("prompt",), ("prompt",)),
|
| 54 |
+
("TextEncodeMingImageEdit", ("prompt",), ("prompt",)),
|
| 55 |
+
("CLIPTextEncode", ("pos_prompt",), ("text", "prompt")),
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
_config_cache = None
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def load_prompt_enhancer_config():
|
| 62 |
+
"""Load and cache the prompt enhancer registry."""
|
| 63 |
+
global _config_cache
|
| 64 |
+
if _config_cache is None:
|
| 65 |
+
try:
|
| 66 |
+
with open(_CONFIG_PATH, "r", encoding="utf-8") as f:
|
| 67 |
+
_config_cache = yaml.safe_load(f) or {}
|
| 68 |
+
except (OSError, yaml.YAMLError) as e:
|
| 69 |
+
print(f"Warning: Failed to load prompt enhancer config '{_CONFIG_PATH}': {e}")
|
| 70 |
+
_config_cache = {}
|
| 71 |
+
return _config_cache
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def get_clip_registry():
|
| 75 |
+
"""Return the ordered ``clip name -> {filename, clip_type}`` registry."""
|
| 76 |
+
return load_prompt_enhancer_config().get("clip", {}) or {}
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def get_system_prompt_registry():
|
| 80 |
+
"""Return the ``system_prompt name -> text`` map (``""`` = no prompt)."""
|
| 81 |
+
return load_prompt_enhancer_config().get("system_prompt", {}) or {}
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def get_architectures():
|
| 85 |
+
"""Return the ``model framework -> {clip, system_prompt}`` defaults map."""
|
| 86 |
+
return load_prompt_enhancer_config().get("architecture", {}) or {}
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def get_clip_names():
|
| 90 |
+
"""Return the selectable clip names, in declared order."""
|
| 91 |
+
return [str(name) for name in get_clip_registry().keys()]
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def get_clip_entry(name):
|
| 95 |
+
"""Return the ``{filename, clip_type}`` registry entry for a clip name."""
|
| 96 |
+
entry = get_clip_registry().get(str(name))
|
| 97 |
+
return entry if isinstance(entry, dict) else {}
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def get_system_prompt_names():
|
| 101 |
+
"""Return the selectable system_prompt names, including empty (no-prompt) ones."""
|
| 102 |
+
return [str(name) for name in get_system_prompt_registry().keys()]
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def get_all_system_prompt_keys():
|
| 106 |
+
"""Return the system_prompt names that carry text (empty ones are dropped)."""
|
| 107 |
+
return [name for name, text in get_system_prompt_registry().items() if text]
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def get_architecture_defaults(framework=None):
|
| 111 |
+
"""Return ``{model, system_prompt}`` defaults for a model framework.
|
| 112 |
+
|
| 113 |
+
Reads the ``architecture`` module. Falls back to the first registry entry
|
| 114 |
+
when the framework is unknown or omits a field, so the UI always has a
|
| 115 |
+
usable default.
|
| 116 |
+
"""
|
| 117 |
+
defaults = get_architectures().get(str(framework), {}) if framework else {}
|
| 118 |
+
if not isinstance(defaults, dict):
|
| 119 |
+
defaults = {}
|
| 120 |
+
|
| 121 |
+
clip_names = get_clip_names()
|
| 122 |
+
prompt_names = get_system_prompt_names()
|
| 123 |
+
|
| 124 |
+
model = defaults.get("clip")
|
| 125 |
+
if model not in clip_names:
|
| 126 |
+
model = clip_names[0] if clip_names else None
|
| 127 |
+
|
| 128 |
+
system_prompt = defaults.get("system_prompt")
|
| 129 |
+
if system_prompt not in prompt_names:
|
| 130 |
+
system_prompt = prompt_names[0] if prompt_names else None
|
| 131 |
+
|
| 132 |
+
return {"model": model, "system_prompt": system_prompt}
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def create_node(assembler, class_type, title):
|
| 136 |
+
"""Create a workflow node dictionary with fallback template generation."""
|
| 137 |
+
try:
|
| 138 |
+
node = assembler._get_node_template(class_type)
|
| 139 |
+
except Exception:
|
| 140 |
+
node = {
|
| 141 |
+
"inputs": {},
|
| 142 |
+
"class_type": class_type,
|
| 143 |
+
"_meta": {"title": title}
|
| 144 |
+
}
|
| 145 |
+
if "_meta" not in node:
|
| 146 |
+
node["_meta"] = {}
|
| 147 |
+
node['_meta']['title'] = title
|
| 148 |
+
return node
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def _as_bool(value, default=False):
|
| 152 |
+
if isinstance(value, bool):
|
| 153 |
+
return value
|
| 154 |
+
if value is None:
|
| 155 |
+
return default
|
| 156 |
+
return str(value).strip().lower() in ("on", "true", "yes", "1")
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def _parse_chain_items(chain_items):
|
| 160 |
+
"""Normalise the chain items into ``{enable, thinking, model, system_prompt, max_length}``."""
|
| 161 |
+
params = {
|
| 162 |
+
"enable": False,
|
| 163 |
+
"thinking": False,
|
| 164 |
+
"model": None,
|
| 165 |
+
"system_prompt": None,
|
| 166 |
+
"max_length": None,
|
| 167 |
+
}
|
| 168 |
+
if isinstance(chain_items, dict):
|
| 169 |
+
chain_items = [chain_items]
|
| 170 |
+
|
| 171 |
+
for item in chain_items or []:
|
| 172 |
+
if isinstance(item, dict):
|
| 173 |
+
if item.get("enable", False) or item.get("enabled", False) or item.get("value", False):
|
| 174 |
+
params["enable"] = True
|
| 175 |
+
params["thinking"] = _as_bool(item.get("thinking"), False)
|
| 176 |
+
if item.get("model"):
|
| 177 |
+
params["model"] = str(item["model"])
|
| 178 |
+
if item.get("system_prompt"):
|
| 179 |
+
params["system_prompt"] = str(item["system_prompt"])
|
| 180 |
+
if item.get("max_length"):
|
| 181 |
+
try:
|
| 182 |
+
params["max_length"] = int(item["max_length"])
|
| 183 |
+
except (ValueError, TypeError):
|
| 184 |
+
pass
|
| 185 |
+
elif item is True or str(item).strip().lower() in ("on", "true", "yes", "1"):
|
| 186 |
+
params["enable"] = True
|
| 187 |
+
break
|
| 188 |
+
|
| 189 |
+
return params
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _infer_clip_type(clip_filename):
|
| 193 |
+
"""Best-effort ``CLIPLoader`` type when ``clip_type`` is missing from the YAML.
|
| 194 |
+
|
| 195 |
+
A wrong type silently loads the checkpoint into the wrong text encoder, so the
|
| 196 |
+
filename is inspected before falling back to the most common type.
|
| 197 |
+
"""
|
| 198 |
+
name = str(clip_filename or "").lower()
|
| 199 |
+
if "flux3" in name:
|
| 200 |
+
print(f"Warning: 'clip_type' missing for '{clip_filename}'; inferred 'flux3' from the filename.")
|
| 201 |
+
return "flux3"
|
| 202 |
+
if "flux2" in name:
|
| 203 |
+
print(f"Warning: 'clip_type' missing for '{clip_filename}'; inferred 'flux2' from the filename.")
|
| 204 |
+
return "flux2"
|
| 205 |
+
print(f"Warning: 'clip_type' missing for '{clip_filename}'; defaulting to 'qwen_image'. Add it to prompt_enhancer.yaml.")
|
| 206 |
+
return "qwen_image"
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def _resolve_model(params, chain_definition=None):
|
| 210 |
+
"""Pick the Prompt Enhancer Model: explicit request, then framework default, then first registered."""
|
| 211 |
+
available = get_clip_names()
|
| 212 |
+
if not available:
|
| 213 |
+
return None
|
| 214 |
+
|
| 215 |
+
requested = params.get("model")
|
| 216 |
+
if not requested:
|
| 217 |
+
defaults = get_architecture_defaults((chain_definition or {}).get("framework"))
|
| 218 |
+
requested = defaults["model"]
|
| 219 |
+
|
| 220 |
+
if requested:
|
| 221 |
+
requested = str(requested)
|
| 222 |
+
if requested in available:
|
| 223 |
+
return requested
|
| 224 |
+
# Tolerate the checkpoint filename being supplied instead of the name.
|
| 225 |
+
for name in available:
|
| 226 |
+
if str(get_clip_entry(name).get("filename") or "") == requested:
|
| 227 |
+
return name
|
| 228 |
+
# Unknown name: fall through to the framework default.
|
| 229 |
+
|
| 230 |
+
defaults = get_architecture_defaults((chain_definition or {}).get("framework"))
|
| 231 |
+
return defaults["model"] if defaults["model"] in available else available[0]
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def _select_system_prompt(requested_system_prompt, framework=None):
|
| 235 |
+
"""Resolve the system prompt text from the global system_prompt registry."""
|
| 236 |
+
prompts = get_system_prompt_registry()
|
| 237 |
+
if not prompts:
|
| 238 |
+
return None
|
| 239 |
+
|
| 240 |
+
# An explicit UI choice always wins, including an empty prompt (no prompt).
|
| 241 |
+
if requested_system_prompt and str(requested_system_prompt) in prompts:
|
| 242 |
+
return prompts[str(requested_system_prompt)] or None
|
| 243 |
+
|
| 244 |
+
# Otherwise use the framework default, falling back to any non-empty prompt.
|
| 245 |
+
default_prompt = get_architecture_defaults(framework)["system_prompt"]
|
| 246 |
+
if default_prompt and str(default_prompt) in prompts:
|
| 247 |
+
return prompts[str(default_prompt)] or None
|
| 248 |
+
|
| 249 |
+
for text in prompts.values():
|
| 250 |
+
if text:
|
| 251 |
+
return text
|
| 252 |
+
return None
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def _find_target_node(assembler, chain_definition=None):
|
| 256 |
+
"""Locate the text-encode node that should receive the enhanced prompt."""
|
| 257 |
+
target_node_name = (chain_definition or {}).get("target_node")
|
| 258 |
+
target_node_id = assembler.node_map.get(target_node_name) if target_node_name else None
|
| 259 |
+
if target_node_id and target_node_id in assembler.workflow:
|
| 260 |
+
return target_node_id, None
|
| 261 |
+
|
| 262 |
+
for class_type, node_names, input_keys in _TARGET_HINTS:
|
| 263 |
+
return_class = (class_type, input_keys)
|
| 264 |
+
for node_name in node_names:
|
| 265 |
+
node_id = assembler.node_map.get(node_name)
|
| 266 |
+
if node_id and assembler.workflow.get(node_id, {}).get('class_type') == class_type:
|
| 267 |
+
return node_id, return_class
|
| 268 |
+
|
| 269 |
+
# Prefer a positive-prompt encode node when the class_type is shared.
|
| 270 |
+
for node_id, node in assembler.workflow.items():
|
| 271 |
+
if not isinstance(node, dict) or node.get('class_type') != class_type:
|
| 272 |
+
continue
|
| 273 |
+
title = str(node.get('_meta', {}).get('title', '')).lower()
|
| 274 |
+
if 'pos' in title or 'positive' in title:
|
| 275 |
+
return node_id, return_class
|
| 276 |
+
|
| 277 |
+
for node_id, node in assembler.workflow.items():
|
| 278 |
+
if isinstance(node, dict) and node.get('class_type') == class_type:
|
| 279 |
+
return node_id, return_class
|
| 280 |
+
|
| 281 |
+
return None, None
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def _get_autogrow_slot_names(class_type, input_name='images'):
|
| 285 |
+
"""Resolve the per-image autogrow slot names for ``class_type.input_name``.
|
| 286 |
+
|
| 287 |
+
ComfyUI exposes two flavours of Autogrow template:
|
| 288 |
+
|
| 289 |
+
* ``TemplateNames(names=["image1", ...])`` -> explicit names
|
| 290 |
+
* ``TemplatePrefix(prefix="image", max=N)`` -> ``prefix + range(max)``
|
| 291 |
+
|
| 292 |
+
Returns ``None`` when the node schema cannot be introspected.
|
| 293 |
+
"""
|
| 294 |
+
try:
|
| 295 |
+
from comfy_integration.nodes import NODE_CLASS_MAPPINGS
|
| 296 |
+
node_class = NODE_CLASS_MAPPINGS.get(class_type)
|
| 297 |
+
except Exception:
|
| 298 |
+
node_class = None
|
| 299 |
+
|
| 300 |
+
if node_class is not None:
|
| 301 |
+
try:
|
| 302 |
+
input_types = node_class.INPUT_TYPES() or {}
|
| 303 |
+
except Exception:
|
| 304 |
+
input_types = {}
|
| 305 |
+
for category in ('required', 'optional'):
|
| 306 |
+
spec = (input_types.get(category) or {}).get(input_name)
|
| 307 |
+
if not spec or len(spec) < 2 or not isinstance(spec[1], dict):
|
| 308 |
+
continue
|
| 309 |
+
template = spec[1].get('template')
|
| 310 |
+
if not isinstance(template, dict):
|
| 311 |
+
continue
|
| 312 |
+
names = template.get('names')
|
| 313 |
+
if isinstance(names, (list, tuple)) and names:
|
| 314 |
+
return [str(n) for n in names]
|
| 315 |
+
prefix = template.get('prefix')
|
| 316 |
+
if isinstance(prefix, str):
|
| 317 |
+
max_count = template.get('max', 100)
|
| 318 |
+
if not isinstance(max_count, int) or max_count <= 0:
|
| 319 |
+
max_count = 100
|
| 320 |
+
return [f"{prefix}{i}" for i in range(max_count)]
|
| 321 |
+
|
| 322 |
+
# Fall back to the published TextGenerateV2 contract.
|
| 323 |
+
if class_type == "TextGenerateV2":
|
| 324 |
+
return [f"image{i}" for i in range(1, 51)]
|
| 325 |
+
return None
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def _collect_reference_images(assembler, target_node):
|
| 329 |
+
"""Collect the reference image connections feeding the prompt node.
|
| 330 |
+
|
| 331 |
+
Native autogrow slots on the target node (``images.image_N``) win; otherwise
|
| 332 |
+
every ``LoadImage`` node is scaled and used. Each image keeps its own
|
| 333 |
+
connection so the caller can wire them into separate autogrow slots.
|
| 334 |
+
"""
|
| 335 |
+
load_image_nodes = [
|
| 336 |
+
node_id for node_id, node in assembler.workflow.items()
|
| 337 |
+
if isinstance(node, dict) and node.get('class_type') == 'LoadImage'
|
| 338 |
+
]
|
| 339 |
+
|
| 340 |
+
indexed_slots = []
|
| 341 |
+
for slot_key, slot_val in target_node.get('inputs', {}).items():
|
| 342 |
+
if slot_key.startswith('images.image_') and isinstance(slot_val, (list, tuple)):
|
| 343 |
+
try:
|
| 344 |
+
idx = int(slot_key.split('_')[-1])
|
| 345 |
+
except (ValueError, IndexError):
|
| 346 |
+
idx = 999
|
| 347 |
+
indexed_slots.append((idx, slot_val))
|
| 348 |
+
|
| 349 |
+
indexed_slots.sort(key=lambda x: x[0])
|
| 350 |
+
if indexed_slots:
|
| 351 |
+
return [slot[1] for slot in indexed_slots]
|
| 352 |
+
|
| 353 |
+
if not load_image_nodes:
|
| 354 |
+
return []
|
| 355 |
+
|
| 356 |
+
scaled_conns = []
|
| 357 |
+
for load_id in load_image_nodes:
|
| 358 |
+
scaled_conn = None
|
| 359 |
+
for node_id, node in assembler.workflow.items():
|
| 360 |
+
if isinstance(node, dict) and node.get('class_type') == 'ImageScaleToTotalPixels':
|
| 361 |
+
img_input = node.get('inputs', {}).get('image')
|
| 362 |
+
if isinstance(img_input, (list, tuple)) and str(img_input[0]) == str(load_id):
|
| 363 |
+
scaled_conn = [node_id, 0]
|
| 364 |
+
break
|
| 365 |
+
if scaled_conn is None:
|
| 366 |
+
scale_id = assembler._get_unique_id()
|
| 367 |
+
scale_node = create_node(assembler, "ImageScaleToTotalPixels", "Scale Input Image")
|
| 368 |
+
scale_node['inputs'] = {
|
| 369 |
+
"upscale_method": "nearest-exact",
|
| 370 |
+
"megapixels": 1,
|
| 371 |
+
"resolution_steps": 1,
|
| 372 |
+
"image": [load_id, 0]
|
| 373 |
+
}
|
| 374 |
+
assembler.workflow[scale_id] = scale_node
|
| 375 |
+
scaled_conn = [scale_id, 0]
|
| 376 |
+
scaled_conns.append(scaled_conn)
|
| 377 |
+
return scaled_conns
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def inject(assembler, chain_definition, chain_items):
|
| 381 |
+
"""Inject prompt enhancer nodes into the target generation workflow."""
|
| 382 |
+
if not chain_items:
|
| 383 |
+
return
|
| 384 |
+
|
| 385 |
+
params = _parse_chain_items(chain_items)
|
| 386 |
+
if not params["enable"]:
|
| 387 |
+
return
|
| 388 |
+
|
| 389 |
+
model_name = _resolve_model(params, chain_definition)
|
| 390 |
+
if not model_name:
|
| 391 |
+
print("Warning: No Prompt Enhancer Models configured in prompt_enhancer.yaml. Skipping.")
|
| 392 |
+
return
|
| 393 |
+
|
| 394 |
+
framework = (chain_definition or {}).get("framework")
|
| 395 |
+
entry = get_clip_entry(model_name)
|
| 396 |
+
clip_model_name = str(entry.get("filename") or model_name)
|
| 397 |
+
clip_type = str(entry.get("clip_type") or _infer_clip_type(clip_model_name))
|
| 398 |
+
|
| 399 |
+
target_node_id, class_hint = _find_target_node(assembler, chain_definition)
|
| 400 |
+
if not target_node_id:
|
| 401 |
+
print(f"Warning: Target text-encode node for prompt enhancer '{model_name}' not found. Skipping.")
|
| 402 |
+
return
|
| 403 |
+
|
| 404 |
+
target_node = assembler.workflow[target_node_id]
|
| 405 |
+
|
| 406 |
+
# Fall back to the detected node class when the recipe gave no target_node.
|
| 407 |
+
prompt_input_keys = (class_hint[1] if class_hint else None) or ("text", "prompt")
|
| 408 |
+
original_prompt = None
|
| 409 |
+
for input_key in prompt_input_keys:
|
| 410 |
+
original_prompt = target_node.get('inputs', {}).get(input_key)
|
| 411 |
+
if original_prompt is not None:
|
| 412 |
+
break
|
| 413 |
+
if original_prompt is None:
|
| 414 |
+
original_prompt = ""
|
| 415 |
+
|
| 416 |
+
image_conns = _collect_reference_images(assembler, target_node)
|
| 417 |
+
system_prompt_name = params["system_prompt"] if params["system_prompt"] else None
|
| 418 |
+
if ensure_file_downloaded:
|
| 419 |
+
try:
|
| 420 |
+
ensure_file_downloaded(clip_model_name)
|
| 421 |
+
except Exception as e:
|
| 422 |
+
print(f"Warning: Failed to ensure '{clip_model_name}' downloaded: {e}")
|
| 423 |
+
|
| 424 |
+
# 1. Load the selected Prompt Enhancer Model checkpoint
|
| 425 |
+
clip_node_id = assembler._get_unique_id()
|
| 426 |
+
clip_node = create_node(assembler, "CLIPLoader", "Load CLIP")
|
| 427 |
+
clip_node['inputs'] = {
|
| 428 |
+
"clip_name": clip_model_name,
|
| 429 |
+
"type": clip_type,
|
| 430 |
+
"device": "default"
|
| 431 |
+
}
|
| 432 |
+
assembler.workflow[clip_node_id] = clip_node
|
| 433 |
+
|
| 434 |
+
# 2. Materialise the system prompt when the selected System Prompt carries text
|
| 435 |
+
system_prompt_conn = None
|
| 436 |
+
system_prompt = _select_system_prompt(system_prompt_name, framework)
|
| 437 |
+
if system_prompt:
|
| 438 |
+
system_prompt_node_id = assembler._get_unique_id()
|
| 439 |
+
system_prompt_node = create_node(assembler, "PrimitiveStringMultiline", "Text (System prompt)")
|
| 440 |
+
system_prompt_node['inputs'] = {"value": system_prompt}
|
| 441 |
+
assembler.workflow[system_prompt_node_id] = system_prompt_node
|
| 442 |
+
system_prompt_conn = [system_prompt_node_id, 0]
|
| 443 |
+
|
| 444 |
+
# 3. Emit the TextGenerateV2 node (Sampling Mode off: sampling-only sub-inputs are omitted)
|
| 445 |
+
max_length = params["max_length"] or 4096
|
| 446 |
+
text_gen_node_id = assembler._get_unique_id()
|
| 447 |
+
text_gen_node = create_node(assembler, "TextGenerateV2", "Generate Text")
|
| 448 |
+
text_gen_inputs = {
|
| 449 |
+
"prompt": original_prompt,
|
| 450 |
+
"max_length": max_length,
|
| 451 |
+
"sampling_mode": "off",
|
| 452 |
+
"thinking": params["thinking"],
|
| 453 |
+
"use_default_template": True,
|
| 454 |
+
"mtp": "auto",
|
| 455 |
+
"clip": [clip_node_id, 0]
|
| 456 |
+
}
|
| 457 |
+
if system_prompt_conn is not None:
|
| 458 |
+
text_gen_inputs["system_prompt"] = system_prompt_conn
|
| 459 |
+
|
| 460 |
+
# Wire each reference image into its own autogrow slot (image1, image2, ...).
|
| 461 |
+
if image_conns:
|
| 462 |
+
slot_names = _get_autogrow_slot_names("TextGenerateV2", "images")
|
| 463 |
+
if slot_names:
|
| 464 |
+
for conn, slot_name in zip(image_conns, slot_names):
|
| 465 |
+
text_gen_inputs[slot_name] = conn
|
| 466 |
+
if len(image_conns) > len(slot_names):
|
| 467 |
+
print(
|
| 468 |
+
f"Warning: {len(image_conns)} reference images but TextGenerateV2 "
|
| 469 |
+
f"exposes only {len(slot_names)} slots; extra images were dropped."
|
| 470 |
+
)
|
| 471 |
+
else:
|
| 472 |
+
print("Warning: could not resolve TextGenerateV2 autogrow slots; images not wired.")
|
| 473 |
+
|
| 474 |
+
text_gen_node['inputs'] = text_gen_inputs
|
| 475 |
+
assembler.workflow[text_gen_node_id] = text_gen_node
|
| 476 |
+
|
| 477 |
+
# 4. Route the clean generated_text output into the downstream prompt input
|
| 478 |
+
# (slot 0 carries the enhanced prompt; ComfyUI puts reasoning into slot 1)
|
| 479 |
+
target_inputs = target_node.setdefault('inputs', {})
|
| 480 |
+
if 'text' in target_inputs or ('prompt' not in target_inputs and 'text' in prompt_input_keys):
|
| 481 |
+
target_inputs['text'] = [text_gen_node_id, 0]
|
| 482 |
+
else:
|
| 483 |
+
target_inputs['prompt'] = [text_gen_node_id, 0]
|
| 484 |
+
|
| 485 |
+
print(
|
| 486 |
+
f"[Injector] Prompt Enhancer '{model_name}' applied successfully "
|
| 487 |
+
f"(clip='{clip_model_name}', type='{clip_type}', "
|
| 488 |
+
f"system_prompt={system_prompt_name or 'auto'})."
|
| 489 |
+
)
|
chain_injectors/qwen_image_2_1_prompt_enhancer_injector.py
DELETED
|
@@ -1,245 +0,0 @@
|
|
| 1 |
-
"""Qwen-Image-2.1 Prompt Enhancer Chain Injector.
|
| 2 |
-
|
| 3 |
-
This injector automatically integrates the Qwen-Image-2.1 prompt enhancement model into
|
| 4 |
-
the generation workflow:
|
| 5 |
-
- Detects whether the task is pure text-to-image (T2I) or involves images (I2I, inpainting,
|
| 6 |
-
outpainting, hires.fix, or multi-reference images).
|
| 7 |
-
- Automatically selects the appropriate prompt enhancement checkpoint:
|
| 8 |
-
- T2I: qwen3.5_9b_qwen_image_2.1_pe_t2i.int8_convrot.safetensors
|
| 9 |
-
- I2I / Reference: qwen3.5_9b_qwen_image_2.1_pe_i2i.int8_convrot.safetensors
|
| 10 |
-
- Handles single or multi-reference inputs: multiple reference images are stitched into
|
| 11 |
-
a structured grid layout via ComfyUI native ImageStitch nodes and scaled via ImageScaleToTotalPixels.
|
| 12 |
-
- Supports reasoning mode (thinking=True) leveraging ComfyUI TextGenerate native reasoning separation
|
| 13 |
-
(output slot 0 outputs clean prompt text, while output slot 1 isolates reasoning chain).
|
| 14 |
-
"""
|
| 15 |
-
|
| 16 |
-
try:
|
| 17 |
-
from utils.app_utils import ensure_file_downloaded
|
| 18 |
-
except ImportError:
|
| 19 |
-
ensure_file_downloaded = None
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
def create_node(assembler, class_type, title):
|
| 23 |
-
"""Create a workflow node dictionary with fallback template generation."""
|
| 24 |
-
try:
|
| 25 |
-
node = assembler._get_node_template(class_type)
|
| 26 |
-
except Exception:
|
| 27 |
-
node = {
|
| 28 |
-
"inputs": {},
|
| 29 |
-
"class_type": class_type,
|
| 30 |
-
"_meta": {"title": title}
|
| 31 |
-
}
|
| 32 |
-
if "_meta" not in node:
|
| 33 |
-
node["_meta"] = {}
|
| 34 |
-
node['_meta']['title'] = title
|
| 35 |
-
return node
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
def inject(assembler, chain_definition, chain_items):
|
| 39 |
-
"""Inject Qwen-Image-2.1 prompt enhancer nodes into the target generation workflow."""
|
| 40 |
-
if not chain_items:
|
| 41 |
-
return
|
| 42 |
-
|
| 43 |
-
is_enabled = False
|
| 44 |
-
thinking_enabled = False
|
| 45 |
-
max_length = 4096
|
| 46 |
-
for item in chain_items:
|
| 47 |
-
if isinstance(item, dict):
|
| 48 |
-
if item.get("enable", False) or item.get("enabled", False) or item.get("value", False):
|
| 49 |
-
is_enabled = True
|
| 50 |
-
if item.get("thinking", False) in (True, "true", "on", "yes", "1", 1):
|
| 51 |
-
thinking_enabled = True
|
| 52 |
-
if "max_length" in item and item["max_length"]:
|
| 53 |
-
try:
|
| 54 |
-
max_length = int(item["max_length"])
|
| 55 |
-
except (ValueError, TypeError):
|
| 56 |
-
pass
|
| 57 |
-
elif item is True or str(item).lower() in ("on", "true", "yes", "1"):
|
| 58 |
-
is_enabled = True
|
| 59 |
-
break
|
| 60 |
-
|
| 61 |
-
if not is_enabled:
|
| 62 |
-
return
|
| 63 |
-
|
| 64 |
-
target_node_name = chain_definition.get('target_node', 'prompt')
|
| 65 |
-
target_node_id = assembler.node_map.get(target_node_name)
|
| 66 |
-
|
| 67 |
-
if not target_node_id or target_node_id not in assembler.workflow:
|
| 68 |
-
for node_id, node in assembler.workflow.items():
|
| 69 |
-
if isinstance(node, dict) and node.get('class_type') == 'TextEncodeQwenImage21':
|
| 70 |
-
target_node_id = node_id
|
| 71 |
-
break
|
| 72 |
-
|
| 73 |
-
if not target_node_id or target_node_id not in assembler.workflow:
|
| 74 |
-
print(f"Warning: Target node '{target_node_name}' (TextEncodeQwenImage21) not found for Qwen-Image-2.1 Prompt Enhancer. Skipping.")
|
| 75 |
-
return
|
| 76 |
-
|
| 77 |
-
target_node = assembler.workflow[target_node_id]
|
| 78 |
-
original_prompt = target_node.get('inputs', {}).get('prompt', '')
|
| 79 |
-
|
| 80 |
-
# 1. Check if any LoadImage nodes exist in the workflow
|
| 81 |
-
load_image_nodes = [
|
| 82 |
-
node_id for node_id, node in assembler.workflow.items()
|
| 83 |
-
if isinstance(node, dict) and node.get('class_type') == 'LoadImage'
|
| 84 |
-
]
|
| 85 |
-
has_load_image = len(load_image_nodes) > 0
|
| 86 |
-
|
| 87 |
-
target_image_conn = None
|
| 88 |
-
|
| 89 |
-
if not has_load_image:
|
| 90 |
-
# Pure text-to-image (txt2img) task without reference images
|
| 91 |
-
clip_model_name = "qwen3.5_9b_qwen_image_2.1_pe_t2i.int8_convrot.safetensors"
|
| 92 |
-
else:
|
| 93 |
-
# Task with reference images or img2img / inpaint / outpaint / hires.fix
|
| 94 |
-
clip_model_name = "qwen3.5_9b_qwen_image_2.1_pe_i2i.int8_convrot.safetensors"
|
| 95 |
-
|
| 96 |
-
# Collect image connections
|
| 97 |
-
# Prioritize extracting all images.image_* slots from target_node['inputs'] (injected by reference image injectors)
|
| 98 |
-
indexed_slots = []
|
| 99 |
-
target_inputs = target_node.get('inputs', {})
|
| 100 |
-
for slot_key, slot_val in target_inputs.items():
|
| 101 |
-
if slot_key.startswith('images.image_') and isinstance(slot_val, (list, tuple)):
|
| 102 |
-
try:
|
| 103 |
-
idx = int(slot_key.split('_')[-1])
|
| 104 |
-
except Exception:
|
| 105 |
-
idx = 999
|
| 106 |
-
indexed_slots.append((idx, slot_val))
|
| 107 |
-
|
| 108 |
-
indexed_slots.sort(key=lambda x: x[0])
|
| 109 |
-
ref_image_conns = [s[1] for s in indexed_slots]
|
| 110 |
-
|
| 111 |
-
# If no reference image slots exist (e.g. pure img2img / inpaint / outpaint), retrieve from LoadImage nodes
|
| 112 |
-
if not ref_image_conns:
|
| 113 |
-
for lid in load_image_nodes:
|
| 114 |
-
scaled_conn = None
|
| 115 |
-
for nid, node in assembler.workflow.items():
|
| 116 |
-
if isinstance(node, dict) and node.get('class_type') == 'ImageScaleToTotalPixels':
|
| 117 |
-
img_input = node.get('inputs', {}).get('image')
|
| 118 |
-
if isinstance(img_input, (list, tuple)) and str(img_input[0]) == str(lid):
|
| 119 |
-
scaled_conn = [nid, 0]
|
| 120 |
-
break
|
| 121 |
-
if scaled_conn:
|
| 122 |
-
ref_image_conns.append(scaled_conn)
|
| 123 |
-
else:
|
| 124 |
-
scale_id = assembler._get_unique_id()
|
| 125 |
-
scale_node = create_node(assembler, "ImageScaleToTotalPixels", "Scale Input Image")
|
| 126 |
-
scale_node['inputs'] = {
|
| 127 |
-
"upscale_method": "nearest-exact",
|
| 128 |
-
"megapixels": 1,
|
| 129 |
-
"resolution_steps": 1,
|
| 130 |
-
"image": [lid, 0]
|
| 131 |
-
}
|
| 132 |
-
assembler.workflow[scale_id] = scale_node
|
| 133 |
-
ref_image_conns.append([scale_id, 0])
|
| 134 |
-
|
| 135 |
-
# Organize multiple reference images into grid stitching
|
| 136 |
-
if len(ref_image_conns) == 1:
|
| 137 |
-
target_image_conn = ref_image_conns[0]
|
| 138 |
-
elif len(ref_image_conns) >= 2:
|
| 139 |
-
num_cols = 2 if len(ref_image_conns) <= 4 else 3
|
| 140 |
-
|
| 141 |
-
rows = []
|
| 142 |
-
for i in range(0, len(ref_image_conns), num_cols):
|
| 143 |
-
rows.append(ref_image_conns[i:i + num_cols])
|
| 144 |
-
|
| 145 |
-
# Horizontally stitch images in each row
|
| 146 |
-
stitched_rows = []
|
| 147 |
-
for r_idx, row_imgs in enumerate(rows):
|
| 148 |
-
if len(row_imgs) == 1:
|
| 149 |
-
stitched_rows.append(row_imgs[0])
|
| 150 |
-
else:
|
| 151 |
-
curr_stitch = row_imgs[0]
|
| 152 |
-
for c_idx in range(1, len(row_imgs)):
|
| 153 |
-
st_id = assembler._get_unique_id()
|
| 154 |
-
st_node = create_node(assembler, "ImageStitch", f"Stitch Images (Row {r_idx+1}-{c_idx})")
|
| 155 |
-
st_node['inputs'] = {
|
| 156 |
-
"direction": "right",
|
| 157 |
-
"match_image_size": True,
|
| 158 |
-
"spacing_width": 0,
|
| 159 |
-
"spacing_color": "white",
|
| 160 |
-
"image1": curr_stitch,
|
| 161 |
-
"image2": row_imgs[c_idx]
|
| 162 |
-
}
|
| 163 |
-
assembler.workflow[st_id] = st_node
|
| 164 |
-
curr_stitch = [st_id, 0]
|
| 165 |
-
stitched_rows.append(curr_stitch)
|
| 166 |
-
|
| 167 |
-
# Vertically stitch multiple rows
|
| 168 |
-
if len(stitched_rows) == 1:
|
| 169 |
-
final_stitched = stitched_rows[0]
|
| 170 |
-
else:
|
| 171 |
-
curr_vertical = stitched_rows[0]
|
| 172 |
-
for r_idx in range(1, len(stitched_rows)):
|
| 173 |
-
st_id = assembler._get_unique_id()
|
| 174 |
-
st_node = create_node(assembler, "ImageStitch", f"Stitch Images (Vertical {r_idx})")
|
| 175 |
-
st_node['inputs'] = {
|
| 176 |
-
"direction": "down",
|
| 177 |
-
"match_image_size": True,
|
| 178 |
-
"spacing_width": 0,
|
| 179 |
-
"spacing_color": "white",
|
| 180 |
-
"image1": curr_vertical,
|
| 181 |
-
"image2": stitched_rows[r_idx]
|
| 182 |
-
}
|
| 183 |
-
assembler.workflow[st_id] = st_node
|
| 184 |
-
curr_vertical = [st_id, 0]
|
| 185 |
-
final_stitched = curr_vertical
|
| 186 |
-
|
| 187 |
-
# Scale final stitched image to ~1024 level via ImageScaleToTotalPixels
|
| 188 |
-
final_scale_id = assembler._get_unique_id()
|
| 189 |
-
final_scale_node = create_node(assembler, "ImageScaleToTotalPixels", "Scale Stitched Image")
|
| 190 |
-
final_scale_node['inputs'] = {
|
| 191 |
-
"upscale_method": "nearest-exact",
|
| 192 |
-
"megapixels": 1,
|
| 193 |
-
"resolution_steps": 1,
|
| 194 |
-
"image": final_stitched
|
| 195 |
-
}
|
| 196 |
-
assembler.workflow[final_scale_id] = final_scale_node
|
| 197 |
-
target_image_conn = [final_scale_id, 0]
|
| 198 |
-
|
| 199 |
-
# 2. Ensure model is downloaded in CPU stage before workflow execution
|
| 200 |
-
if ensure_file_downloaded:
|
| 201 |
-
try:
|
| 202 |
-
ensure_file_downloaded(clip_model_name)
|
| 203 |
-
except Exception as e:
|
| 204 |
-
print(f"Warning: Failed to ensure '{clip_model_name}' downloaded: {e}")
|
| 205 |
-
|
| 206 |
-
# 2. Create CLIPLoader node
|
| 207 |
-
clip_node_id = assembler._get_unique_id()
|
| 208 |
-
clip_node = create_node(assembler, "CLIPLoader", "Load CLIP")
|
| 209 |
-
clip_node['inputs'] = {
|
| 210 |
-
"clip_name": clip_model_name,
|
| 211 |
-
"type": "qwen_image",
|
| 212 |
-
"device": "default"
|
| 213 |
-
}
|
| 214 |
-
assembler.workflow[clip_node_id] = clip_node
|
| 215 |
-
|
| 216 |
-
# 3. Create TextGenerate node
|
| 217 |
-
text_gen_node_id = assembler._get_unique_id()
|
| 218 |
-
text_gen_node = create_node(assembler, "TextGenerate", "Generate Text")
|
| 219 |
-
text_gen_inputs = {
|
| 220 |
-
"prompt": original_prompt,
|
| 221 |
-
"max_length": max_length,
|
| 222 |
-
"sampling_mode": "on",
|
| 223 |
-
"sampling_mode.temperature": 0.7,
|
| 224 |
-
"sampling_mode.top_k": 20,
|
| 225 |
-
"sampling_mode.top_p": 0.95,
|
| 226 |
-
"sampling_mode.min_p": 0.0,
|
| 227 |
-
"sampling_mode.repetition_penalty": 1.05,
|
| 228 |
-
"sampling_mode.seed": 0,
|
| 229 |
-
"sampling_mode.presence_penalty": 0.0 if has_load_image else 1.5,
|
| 230 |
-
"thinking": thinking_enabled,
|
| 231 |
-
"use_default_template": True,
|
| 232 |
-
"mtp": "auto",
|
| 233 |
-
"clip": [clip_node_id, 0]
|
| 234 |
-
}
|
| 235 |
-
if target_image_conn is not None:
|
| 236 |
-
text_gen_inputs["image"] = target_image_conn
|
| 237 |
-
|
| 238 |
-
text_gen_node['inputs'] = text_gen_inputs
|
| 239 |
-
assembler.workflow[text_gen_node_id] = text_gen_node
|
| 240 |
-
|
| 241 |
-
# 4. Connect TextGenerate output directly to target node prompt
|
| 242 |
-
# ComfyUI TextGenerate node natively strips reasoning tags (<think>...</think>) into output slot 1,
|
| 243 |
-
# leaving clean enhanced prompt in output slot 0.
|
| 244 |
-
target_node['inputs']['prompt'] = [text_gen_node_id, 0]
|
| 245 |
-
print(f"[Injector] Qwen-Image-2.1 Prompt Enhancer applied successfully ({'I2I' if has_load_image else 'T2I'}, clip='{clip_model_name}', thinking={thinking_enabled}).")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core/pipelines/pipeline_input_processor.py
CHANGED
|
@@ -408,41 +408,21 @@ def process_pipeline_inputs(ui_inputs: Dict[str, Any], progress: gr.Progress, wo
|
|
| 408 |
"x": int(xs[i]), "y": int(ys[i]), "strength": float(strengths[i])
|
| 409 |
})
|
| 410 |
|
| 411 |
-
pe_enable = ui_inputs.get('
|
| 412 |
-
|
| 413 |
-
if '
|
| 414 |
-
active_qwen_image_2_1_prompt_enhancer.append({
|
| 415 |
-
"enable": True,
|
| 416 |
-
"thinking": bool(ui_inputs.get('qwen_image_2_1_prompt_enhancer_thinking', False)),
|
| 417 |
-
"max_length": int(ui_inputs.get('qwen_image_2_1_prompt_enhancer_max_length', 4096))
|
| 418 |
-
})
|
| 419 |
-
|
| 420 |
-
ming_pe_enable = ui_inputs.get('ming_image_prompt_enhancer_enable', False)
|
| 421 |
-
active_ming_image_prompt_enhancer = []
|
| 422 |
-
if 'ming_image_prompt_enhancer' in enabled_chains and ming_pe_enable:
|
| 423 |
-
item = {
|
| 424 |
-
"enable": True,
|
| 425 |
-
"thinking": bool(ui_inputs.get('ming_image_prompt_enhancer_thinking', False)),
|
| 426 |
-
"max_length": int(ui_inputs.get('ming_image_prompt_enhancer_max_length', 4096))
|
| 427 |
-
}
|
| 428 |
-
sys_p = ui_inputs.get('ming_image_prompt_enhancer_system_prompt')
|
| 429 |
-
if sys_p:
|
| 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('
|
| 439 |
-
"
|
| 440 |
-
"max_length": int(ui_inputs.get('flux3_prompt_upsampler_max_length', 3584))
|
| 441 |
}
|
| 442 |
-
|
| 443 |
-
if
|
| 444 |
-
item["
|
| 445 |
-
|
|
|
|
|
|
|
|
|
|
| 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')
|
|
@@ -523,9 +503,7 @@ def process_pipeline_inputs(ui_inputs: Dict[str, Any], progress: gr.Progress, wo
|
|
| 523 |
"active_qwen_image_edit": active_qwen_image_edit,
|
| 524 |
"active_reference_images": active_reference_images,
|
| 525 |
"active_conditioning": active_conditioning,
|
| 526 |
-
"
|
| 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
|
|
|
|
| 408 |
"x": int(xs[i]), "y": int(ys[i]), "strength": float(strengths[i])
|
| 409 |
})
|
| 410 |
|
| 411 |
+
pe_enable = ui_inputs.get('prompt_enhancer_enable', False)
|
| 412 |
+
active_prompt_enhancer = []
|
| 413 |
+
if 'prompt_enhancer' in enabled_chains and pe_enable:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
item = {
|
| 415 |
"enable": True,
|
| 416 |
+
"thinking": bool(ui_inputs.get('prompt_enhancer_thinking', False)),
|
| 417 |
+
"max_length": int(ui_inputs.get('prompt_enhancer_max_length', 4096))
|
|
|
|
| 418 |
}
|
| 419 |
+
model_val = ui_inputs.get('prompt_enhancer_model')
|
| 420 |
+
if model_val and str(model_val) != 'auto':
|
| 421 |
+
item["model"] = str(model_val)
|
| 422 |
+
prompt_val = ui_inputs.get('prompt_enhancer_system_prompt')
|
| 423 |
+
if prompt_val and str(prompt_val) != 'auto':
|
| 424 |
+
item["system_prompt"] = str(prompt_val)
|
| 425 |
+
active_prompt_enhancer.append(item)
|
| 426 |
|
| 427 |
color_map_val = ui_inputs.get('moss_image_control_color_map')
|
| 428 |
depth_map_val = ui_inputs.get('moss_image_control_depth_map')
|
|
|
|
| 503 |
"active_qwen_image_edit": active_qwen_image_edit,
|
| 504 |
"active_reference_images": active_reference_images,
|
| 505 |
"active_conditioning": active_conditioning,
|
| 506 |
+
"active_prompt_enhancer": active_prompt_enhancer,
|
|
|
|
|
|
|
| 507 |
"active_moss_image_control": active_moss_image_control,
|
| 508 |
"active_moss_image_edit": active_moss_image_edit,
|
| 509 |
"temp_files_to_clean": temp_files_to_clean
|
core/pipelines/sd_image_pipeline.py
CHANGED
|
@@ -119,9 +119,7 @@ class SdImagePipeline(BasePipeline):
|
|
| 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 |
-
|
| 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 |
|
|
@@ -181,9 +179,7 @@ class SdImagePipeline(BasePipeline):
|
|
| 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 |
-
"
|
| 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 [],
|
|
|
|
| 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_prompt_enhancer = processed.get("active_prompt_enhancer", [])
|
|
|
|
|
|
|
| 123 |
active_moss_image_control = processed.get("active_moss_image_control", [])
|
| 124 |
active_moss_image_edit = processed.get("active_moss_image_edit", [])
|
| 125 |
|
|
|
|
| 179 |
"krea2_style_reference_chain": active_krea2_reference_edit,
|
| 180 |
"qwen_image_edit_chain": active_qwen_image_edit,
|
| 181 |
"reference_image_chain": active_reference_images,
|
| 182 |
+
"prompt_enhancer_chain": active_prompt_enhancer,
|
|
|
|
|
|
|
| 183 |
"moss_image_control_chain": active_moss_image_control,
|
| 184 |
"moss_image_edit_chain": active_moss_image_edit,
|
| 185 |
"vae_chain": [ui_inputs.get('vae_name')] if (ui_inputs.get('vae_name') and 'vae' in enabled_chains) else [],
|
core/pipelines/workflow_executor.py
CHANGED
|
@@ -4,6 +4,83 @@ from typing import Dict, Any, List
|
|
| 4 |
from comfy_integration.nodes import NODE_CLASS_MAPPINGS
|
| 5 |
from utils.app_utils import get_value_at_index
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
class WorkflowExecutor:
|
| 8 |
@staticmethod
|
| 9 |
def topological_sort(workflow: Dict[str, Any]) -> List[str]:
|
|
@@ -117,9 +194,17 @@ class WorkflowExecutor:
|
|
| 117 |
if class_type == 'TextEncodeMingImageEdit' and ('images' not in kwargs or kwargs.get('images') is None):
|
| 118 |
kwargs['images'] = {}
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
function_name = getattr(node_class, 'FUNCTION')
|
| 121 |
execution_method = getattr(node_instance, function_name)
|
| 122 |
-
|
| 123 |
result = execution_method(**kwargs)
|
| 124 |
computed_outputs[node_id] = result
|
| 125 |
|
|
|
|
| 4 |
from comfy_integration.nodes import NODE_CLASS_MAPPINGS
|
| 5 |
from utils.app_utils import get_value_at_index
|
| 6 |
|
| 7 |
+
|
| 8 |
+
def _normalize_dynamic_inputs(node_class, class_type, kwargs):
|
| 9 |
+
"""Rebuild the structures ComfyUI's executor would have built for this node.
|
| 10 |
+
|
| 11 |
+
Two V3 dynamic shapes need post-processing when nodes are called directly:
|
| 12 |
+
|
| 13 |
+
* ``COMFY_DYNAMICCOMBO_V3`` — the widget arrives as a bare string but the
|
| 14 |
+
node signature expects ``{id: <option>, **sub_keys}`` (e.g.
|
| 15 |
+
``sampling_mode={"sampling_mode": "off"}``).
|
| 16 |
+
* ``COMFY_AUTOGROW_V3`` — slots arrive flattened as ``image1``, ``image2``,
|
| 17 |
+
... but the node signature expects ``images={"image1": <value>, ...}``.
|
| 18 |
+
|
| 19 |
+
ComfyUI's ``comfy_api.latest._io.build_nested_inputs`` does both; this
|
| 20 |
+
Space executes nodes directly and therefore has to redo the work.
|
| 21 |
+
"""
|
| 22 |
+
schema = getattr(node_class, 'SCHEMA', None)
|
| 23 |
+
inputs = getattr(schema, 'inputs', None) if schema is not None else None
|
| 24 |
+
if not inputs:
|
| 25 |
+
return kwargs
|
| 26 |
+
|
| 27 |
+
# --- COMFY_AUTOGROW_V3 first: consume image1/video1/... slots ---
|
| 28 |
+
for spec in inputs:
|
| 29 |
+
sub_id = getattr(spec, 'id', None)
|
| 30 |
+
template = getattr(spec, 'template', None)
|
| 31 |
+
if not sub_id or template is None:
|
| 32 |
+
continue
|
| 33 |
+
|
| 34 |
+
names = getattr(template, 'names', None) or []
|
| 35 |
+
prefix = getattr(template, 'prefix', None)
|
| 36 |
+
candidates = list(names)
|
| 37 |
+
if prefix:
|
| 38 |
+
max_n = getattr(template, 'max', 0) or 0
|
| 39 |
+
if not candidates:
|
| 40 |
+
candidates = [f"{prefix}{i}" for i in range(max_n)]
|
| 41 |
+
|
| 42 |
+
collected = {}
|
| 43 |
+
for name in candidates:
|
| 44 |
+
if name in kwargs:
|
| 45 |
+
collected[name] = kwargs.pop(name)
|
| 46 |
+
|
| 47 |
+
if collected and not isinstance(kwargs.get(sub_id), dict):
|
| 48 |
+
kwargs[sub_id] = collected
|
| 49 |
+
|
| 50 |
+
# --- COMFY_DYNAMICCOMBO_V3 ---
|
| 51 |
+
for spec in inputs:
|
| 52 |
+
sub_id = getattr(spec, 'id', None)
|
| 53 |
+
options = getattr(spec, 'options', None)
|
| 54 |
+
if not sub_id or not options or sub_id not in kwargs:
|
| 55 |
+
continue
|
| 56 |
+
|
| 57 |
+
value = kwargs[sub_id]
|
| 58 |
+
if isinstance(value, dict):
|
| 59 |
+
continue # already structured
|
| 60 |
+
|
| 61 |
+
option_keys = []
|
| 62 |
+
for opt in options:
|
| 63 |
+
if isinstance(opt, dict):
|
| 64 |
+
option_keys.append(opt.get('key'))
|
| 65 |
+
else:
|
| 66 |
+
option_keys.append(getattr(opt, 'key', None))
|
| 67 |
+
|
| 68 |
+
if value not in option_keys:
|
| 69 |
+
print(f"Warning: '{sub_id}' value {value!r} is not one of {option_keys} for {class_type}.")
|
| 70 |
+
continue
|
| 71 |
+
|
| 72 |
+
# Pull sibling sub-keys (e.g. "sampling_mode.temperature") into the dict.
|
| 73 |
+
nested = {sub_id: str(value)}
|
| 74 |
+
prefix = f"{sub_id}."
|
| 75 |
+
for key in list(kwargs.keys()):
|
| 76 |
+
if key.startswith(prefix):
|
| 77 |
+
nested[key[len(prefix):]] = kwargs.pop(key)
|
| 78 |
+
kwargs[sub_id] = nested
|
| 79 |
+
print(f" -> Normalized dynamic input '{sub_id}' for {class_type}: {nested}")
|
| 80 |
+
|
| 81 |
+
return kwargs
|
| 82 |
+
|
| 83 |
+
|
| 84 |
class WorkflowExecutor:
|
| 85 |
@staticmethod
|
| 86 |
def topological_sort(workflow: Dict[str, Any]) -> List[str]:
|
|
|
|
| 194 |
if class_type == 'TextEncodeMingImageEdit' and ('images' not in kwargs or kwargs.get('images') is None):
|
| 195 |
kwargs['images'] = {}
|
| 196 |
|
| 197 |
+
# ComfyUI's own executor restructures V3 dynamic inputs into the
|
| 198 |
+
# nested dict the node signature expects (see
|
| 199 |
+
# comfy_api.latest._io.build_nested_inputs). This direct-call
|
| 200 |
+
# path bypasses that step, so a COMFY_DYNAMICCOMBO_V3 widget
|
| 201 |
+
# arrives as a bare string and the node fails on
|
| 202 |
+
# `sampling_mode.get(...)`. Rebuild it here.
|
| 203 |
+
kwargs = _normalize_dynamic_inputs(node_class, class_type, kwargs)
|
| 204 |
+
|
| 205 |
function_name = getattr(node_class, 'FUNCTION')
|
| 206 |
execution_method = getattr(node_instance, function_name)
|
| 207 |
+
|
| 208 |
result = execution_method(**kwargs)
|
| 209 |
computed_outputs[node_id] = result
|
| 210 |
|
core/pipelines/workflow_recipes/_partials/conditioning/flux3.yaml
CHANGED
|
@@ -78,9 +78,10 @@ dynamic_reference_latent_chains:
|
|
| 78 |
ksampler_node: "ksampler"
|
| 79 |
vae_node: "vae_loader"
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
target_node: "pos_prompt"
|
|
|
|
| 84 |
|
| 85 |
dynamic_pid_chains:
|
| 86 |
pid_chain:
|
|
|
|
| 78 |
ksampler_node: "ksampler"
|
| 79 |
vae_node: "vae_loader"
|
| 80 |
|
| 81 |
+
dynamic_prompt_enhancer_chains:
|
| 82 |
+
prompt_enhancer_chain:
|
| 83 |
target_node: "pos_prompt"
|
| 84 |
+
framework: "flux3"
|
| 85 |
|
| 86 |
dynamic_pid_chains:
|
| 87 |
pid_chain:
|
core/pipelines/workflow_recipes/_partials/conditioning/ming-image.yaml
CHANGED
|
@@ -89,9 +89,10 @@ dynamic_reference_image_chains:
|
|
| 89 |
vae_node: "vae_loader"
|
| 90 |
max_images: 8
|
| 91 |
|
| 92 |
-
|
| 93 |
-
|
| 94 |
target_node: "prompt"
|
|
|
|
| 95 |
|
| 96 |
|
| 97 |
ui_map:
|
|
|
|
| 89 |
vae_node: "vae_loader"
|
| 90 |
max_images: 8
|
| 91 |
|
| 92 |
+
dynamic_prompt_enhancer_chains:
|
| 93 |
+
prompt_enhancer_chain:
|
| 94 |
target_node: "prompt"
|
| 95 |
+
framework: "ming-image"
|
| 96 |
|
| 97 |
|
| 98 |
ui_map:
|
core/pipelines/workflow_recipes/_partials/conditioning/qwen-image-2.1.yaml
CHANGED
|
@@ -70,9 +70,10 @@ dynamic_reference_image_chains:
|
|
| 70 |
vae_node: "vae_loader"
|
| 71 |
max_images: 10
|
| 72 |
|
| 73 |
-
|
| 74 |
-
|
| 75 |
target_node: "prompt"
|
|
|
|
| 76 |
|
| 77 |
ui_map:
|
| 78 |
positive_prompt: "prompt:prompt"
|
|
|
|
| 70 |
vae_node: "vae_loader"
|
| 71 |
max_images: 10
|
| 72 |
|
| 73 |
+
dynamic_prompt_enhancer_chains:
|
| 74 |
+
prompt_enhancer_chain:
|
| 75 |
target_node: "prompt"
|
| 76 |
+
framework: "qwen-image-2.1"
|
| 77 |
|
| 78 |
ui_map:
|
| 79 |
positive_prompt: "prompt:prompt"
|
core/workflow_assembler.py
CHANGED
|
@@ -38,6 +38,26 @@ class WorkflowAssembler:
|
|
| 38 |
if chain_key not in order:
|
| 39 |
order.append(chain_key)
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
self.injector_order = order
|
| 42 |
|
| 43 |
def _get_unique_id(self):
|
|
@@ -82,7 +102,11 @@ class WorkflowAssembler:
|
|
| 82 |
|
| 83 |
merged_recipe = {'nodes': {}, 'connections': [], 'ui_map': {}}
|
| 84 |
for key in self.injector_order:
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
merged_recipe[key] = {}
|
| 87 |
|
| 88 |
parent_recipe_dir = os.path.dirname(recipe_path_to_use)
|
|
@@ -98,8 +122,8 @@ class WorkflowAssembler:
|
|
| 98 |
merged_recipe['connections'].extend(imported_recipe.get('connections', []))
|
| 99 |
merged_recipe['ui_map'].update(imported_recipe.get('ui_map', {}))
|
| 100 |
for key in self.injector_order:
|
| 101 |
-
if key in imported_recipe
|
| 102 |
-
merged_recipe
|
| 103 |
except FileNotFoundError:
|
| 104 |
print(f"Warning: Optional recipe partial '{import_path}' not found. Skipping.")
|
| 105 |
|
|
@@ -107,8 +131,8 @@ class WorkflowAssembler:
|
|
| 107 |
merged_recipe['connections'].extend(main_recipe.get('connections', []))
|
| 108 |
merged_recipe['ui_map'].update(main_recipe.get('ui_map', {}))
|
| 109 |
for key in self.injector_order:
|
| 110 |
-
if key in main_recipe
|
| 111 |
-
merged_recipe
|
| 112 |
|
| 113 |
return merged_recipe
|
| 114 |
|
|
@@ -148,7 +172,7 @@ class WorkflowAssembler:
|
|
| 148 |
self.workflow[to_id]['inputs'][to_input_name] = [from_id, int(from_output_idx)]
|
| 149 |
|
| 150 |
print("--- [Assembler] Applying dynamic injectors ---")
|
| 151 |
-
recipe_chain_types = {key for key in self.recipe if key.
|
| 152 |
processing_order = [key for key in self.injector_order if key in recipe_chain_types]
|
| 153 |
|
| 154 |
for chain_type in processing_order:
|
|
|
|
| 38 |
if chain_key not in order:
|
| 39 |
order.append(chain_key)
|
| 40 |
|
| 41 |
+
# An injector may register under a bare CHAIN_TYPE while the recipe uses
|
| 42 |
+
# the derived "dynamic_<feature>_chains" key (or vice versa). Alias both
|
| 43 |
+
# directions so either spelling resolves to the same callable.
|
| 44 |
+
for chain_key in list(order):
|
| 45 |
+
func = self.global_injectors.get(chain_key)
|
| 46 |
+
if not func:
|
| 47 |
+
continue
|
| 48 |
+
if chain_key.startswith('dynamic_') and chain_key.endswith('_chains'):
|
| 49 |
+
feature = chain_key[8:-7]
|
| 50 |
+
alias = f"dynamic_{feature}_chains"
|
| 51 |
+
else:
|
| 52 |
+
feature = chain_key
|
| 53 |
+
alias = f"dynamic_{feature}_chains"
|
| 54 |
+
self.global_injectors.setdefault(alias, func)
|
| 55 |
+
self.global_injectors.setdefault(feature, func)
|
| 56 |
+
if alias not in order:
|
| 57 |
+
order.append(alias)
|
| 58 |
+
if feature not in order:
|
| 59 |
+
order.append(feature)
|
| 60 |
+
|
| 61 |
self.injector_order = order
|
| 62 |
|
| 63 |
def _get_unique_id(self):
|
|
|
|
| 102 |
|
| 103 |
merged_recipe = {'nodes': {}, 'connections': [], 'ui_map': {}}
|
| 104 |
for key in self.injector_order:
|
| 105 |
+
# Injector chains may be registered under a bare CHAIN_TYPE
|
| 106 |
+
# (e.g. "prompt_enhancer") or the derived "dynamic_*_chains" form.
|
| 107 |
+
# Both must survive the merge, otherwise the assembler has nothing
|
| 108 |
+
# to look up at injection time.
|
| 109 |
+
if key:
|
| 110 |
merged_recipe[key] = {}
|
| 111 |
|
| 112 |
parent_recipe_dir = os.path.dirname(recipe_path_to_use)
|
|
|
|
| 122 |
merged_recipe['connections'].extend(imported_recipe.get('connections', []))
|
| 123 |
merged_recipe['ui_map'].update(imported_recipe.get('ui_map', {}))
|
| 124 |
for key in self.injector_order:
|
| 125 |
+
if key in imported_recipe:
|
| 126 |
+
merged_recipe.setdefault(key, {}).update(imported_recipe.get(key, {}))
|
| 127 |
except FileNotFoundError:
|
| 128 |
print(f"Warning: Optional recipe partial '{import_path}' not found. Skipping.")
|
| 129 |
|
|
|
|
| 131 |
merged_recipe['connections'].extend(main_recipe.get('connections', []))
|
| 132 |
merged_recipe['ui_map'].update(main_recipe.get('ui_map', {}))
|
| 133 |
for key in self.injector_order:
|
| 134 |
+
if key in main_recipe:
|
| 135 |
+
merged_recipe.setdefault(key, {}).update(main_recipe.get(key, {}))
|
| 136 |
|
| 137 |
return merged_recipe
|
| 138 |
|
|
|
|
| 172 |
self.workflow[to_id]['inputs'][to_input_name] = [from_id, int(from_output_idx)]
|
| 173 |
|
| 174 |
print("--- [Assembler] Applying dynamic injectors ---")
|
| 175 |
+
recipe_chain_types = {key for key in self.recipe if key in self.injector_order}
|
| 176 |
processing_order = [key for key in self.injector_order if key in recipe_chain_types]
|
| 177 |
|
| 178 |
for chain_type in processing_order:
|
mcp_tools/common.py
CHANGED
|
@@ -607,116 +607,26 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
|
|
| 607 |
if isinstance(is_enabled, str):
|
| 608 |
is_enabled = is_enabled.upper() in ("ON", "TRUE", "1")
|
| 609 |
ui_inputs["pid_settings"] = "ON" if is_enabled else "OFF"
|
| 610 |
-
elif itype == "
|
| 611 |
is_enabled = item.get("enable", item.get("enabled", True))
|
| 612 |
if isinstance(is_enabled, str):
|
| 613 |
is_enabled = is_enabled.upper() in ("ON", "TRUE", "1")
|
| 614 |
-
ui_inputs["
|
| 615 |
-
|
| 616 |
-
if isinstance(is_thinking, str):
|
| 617 |
-
is_thinking = is_thinking.upper() in ("ON", "TRUE", "1")
|
| 618 |
-
ui_inputs["qwen_image_2_1_prompt_enhancer_thinking"] = bool(is_thinking)
|
| 619 |
-
ui_inputs["qwen_image_2_1_prompt_enhancer_max_length"] = int(item.get("max_length", 4096))
|
| 620 |
-
elif itype == "ming_image_prompt_enhancer":
|
| 621 |
-
is_enabled = item.get("enable", item.get("enabled", True))
|
| 622 |
-
if isinstance(is_enabled, str):
|
| 623 |
-
is_enabled = is_enabled.upper() in ("ON", "TRUE", "1")
|
| 624 |
-
ui_inputs["ming_image_prompt_enhancer_enable"] = bool(is_enabled)
|
| 625 |
-
is_thinking = item.get("thinking", False)
|
| 626 |
-
if isinstance(is_thinking, str):
|
| 627 |
-
is_thinking = is_thinking.upper() in ("ON", "TRUE", "1")
|
| 628 |
-
ui_inputs["ming_image_prompt_enhancer_thinking"] = bool(is_thinking)
|
| 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["
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
if
|
| 650 |
-
|
| 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:
|
| 675 |
-
krea2_identity_edit_data.append(img)
|
| 676 |
-
elif itype == "krea2_style_reference":
|
| 677 |
-
img = _parse_image_param(item.get("image"))
|
| 678 |
-
if img:
|
| 679 |
-
krea2_reference_edit_data.append(img)
|
| 680 |
-
elif itype in ("reference_latent", "reference_edit"):
|
| 681 |
-
img = _parse_image_param(item.get("image"))
|
| 682 |
-
if img:
|
| 683 |
-
reference_latent_data.append(img)
|
| 684 |
-
elif itype in ("reference_image", "mage_flow_reference_edit", "boogu_image_edit", "boogu_edit", "qwen_image_2_1_edit"):
|
| 685 |
-
img = _parse_image_param(item.get("image"))
|
| 686 |
-
if img:
|
| 687 |
-
reference_image_data.append(img)
|
| 688 |
-
elif itype in ("joyai_image", "joyai_reference_edit"):
|
| 689 |
-
img = _parse_image_param(item.get("image"))
|
| 690 |
-
if img:
|
| 691 |
-
joyai_reference_data.append(img)
|
| 692 |
-
elif itype == "qwen_image_edit":
|
| 693 |
-
img = _parse_image_param(item.get("image"))
|
| 694 |
-
if img:
|
| 695 |
-
qwen_image_edit_data.append(img)
|
| 696 |
-
elif itype == "hidream_o1_reference":
|
| 697 |
-
img = _parse_image_param(item.get("image"))
|
| 698 |
-
if img:
|
| 699 |
-
hidream_o1_reference_data.append(img)
|
| 700 |
-
elif itype == "sensenova_reference":
|
| 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", ""))))
|
| 717 |
-
if v_source and v_val:
|
| 718 |
-
ui_inputs["vae_source"] = v_source
|
| 719 |
-
ui_inputs["vae_id"] = str(v_val)
|
| 720 |
|
| 721 |
if lora_data: ui_inputs["lora_data"] = lora_data
|
| 722 |
if embedding_data: ui_inputs["embedding_data"] = embedding_data
|
|
@@ -781,49 +691,19 @@ def _execute_imagegen_pipeline(task_id: str, params: dict):
|
|
| 781 |
else:
|
| 782 |
ui_inputs["pid_settings"] = "OFF"
|
| 783 |
|
| 784 |
-
pe_val = params.get("
|
| 785 |
if pe_val is not None:
|
| 786 |
-
if isinstance(pe_val,
|
| 787 |
-
ui_inputs["
|
| 788 |
-
|
| 789 |
-
ui_inputs["
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
else:
|
| 795 |
-
ui_inputs["qwen_image_2_1_prompt_enhancer_enable"] = False
|
| 796 |
-
|
| 797 |
-
ming_pe_val = params.get("ming_image_prompt_enhancer") or params.get("ming_image_prompt_enhancer_enable")
|
| 798 |
-
if ming_pe_val is not None:
|
| 799 |
-
if isinstance(ming_pe_val, bool):
|
| 800 |
-
ui_inputs["ming_image_prompt_enhancer_enable"] = ming_pe_val
|
| 801 |
-
elif isinstance(ming_pe_val, dict):
|
| 802 |
-
ui_inputs["ming_image_prompt_enhancer_enable"] = bool(ming_pe_val.get("enable", ming_pe_val.get("enabled", True)))
|
| 803 |
-
ui_inputs["ming_image_prompt_enhancer_thinking"] = bool(ming_pe_val.get("thinking", False))
|
| 804 |
-
ui_inputs["ming_image_prompt_enhancer_max_length"] = int(ming_pe_val.get("max_length", 4096))
|
| 805 |
-
if "system_prompt" in ming_pe_val:
|
| 806 |
-
ui_inputs["ming_image_prompt_enhancer_system_prompt"] = str(ming_pe_val["system_prompt"])
|
| 807 |
-
elif str(ming_pe_val).upper() in ("ON", "TRUE", "1"):
|
| 808 |
-
ui_inputs["ming_image_prompt_enhancer_enable"] = True
|
| 809 |
else:
|
| 810 |
-
ui_inputs["
|
| 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):
|
|
|
|
| 607 |
if isinstance(is_enabled, str):
|
| 608 |
is_enabled = is_enabled.upper() in ("ON", "TRUE", "1")
|
| 609 |
ui_inputs["pid_settings"] = "ON" if is_enabled else "OFF"
|
| 610 |
+
elif itype == "prompt_enhancer":
|
| 611 |
is_enabled = item.get("enable", item.get("enabled", True))
|
| 612 |
if isinstance(is_enabled, str):
|
| 613 |
is_enabled = is_enabled.upper() in ("ON", "TRUE", "1")
|
| 614 |
+
ui_inputs["prompt_enhancer_enable"] = bool(is_enabled)
|
| 615 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
is_thinking = item.get("thinking", False)
|
| 617 |
if isinstance(is_thinking, str):
|
| 618 |
is_thinking = is_thinking.upper() in ("ON", "TRUE", "1")
|
| 619 |
+
ui_inputs["prompt_enhancer_thinking"] = bool(is_thinking)
|
| 620 |
+
|
| 621 |
+
ui_inputs["prompt_enhancer_max_length"] = int(item.get("max_length", 4096))
|
| 622 |
+
|
| 623 |
+
model_value = item.get("model")
|
| 624 |
+
if model_value:
|
| 625 |
+
ui_inputs["prompt_enhancer_model"] = str(model_value)
|
| 626 |
+
|
| 627 |
+
prompt_value = item.get("system_prompt")
|
| 628 |
+
if prompt_value:
|
| 629 |
+
ui_inputs["prompt_enhancer_system_prompt"] = str(prompt_value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 630 |
|
| 631 |
if lora_data: ui_inputs["lora_data"] = lora_data
|
| 632 |
if embedding_data: ui_inputs["embedding_data"] = embedding_data
|
|
|
|
| 691 |
else:
|
| 692 |
ui_inputs["pid_settings"] = "OFF"
|
| 693 |
|
| 694 |
+
pe_val = params.get("prompt_enhancer")
|
| 695 |
if pe_val is not None:
|
| 696 |
+
if isinstance(pe_val, dict):
|
| 697 |
+
ui_inputs["prompt_enhancer_enable"] = bool(pe_val.get("enable", pe_val.get("enabled", True)))
|
| 698 |
+
ui_inputs["prompt_enhancer_thinking"] = bool(pe_val.get("thinking", False))
|
| 699 |
+
ui_inputs["prompt_enhancer_max_length"] = int(pe_val.get("max_length", 4096))
|
| 700 |
+
if pe_val.get("model"):
|
| 701 |
+
ui_inputs["prompt_enhancer_model"] = str(pe_val["model"])
|
| 702 |
+
if pe_val.get("system_prompt"):
|
| 703 |
+
ui_inputs["prompt_enhancer_system_prompt"] = str(pe_val["system_prompt"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 704 |
else:
|
| 705 |
+
ui_inputs["prompt_enhancer_enable"] = bool(pe_val)
|
| 706 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 707 |
|
| 708 |
moss_control_val = params.get("moss_image_control")
|
| 709 |
if moss_control_val and isinstance(moss_control_val, dict):
|
ui/events/change_handlers.py
CHANGED
|
@@ -9,13 +9,13 @@ from core.settings import (
|
|
| 9 |
ARCH_CATEGORIES_MAP
|
| 10 |
)
|
| 11 |
from utils.app_utils import get_model_generation_defaults
|
| 12 |
-
from ui.shared.ui_components import RESOLUTION_MAP
|
| 13 |
from .config_loaders import (
|
| 14 |
get_cn_defaults,
|
| 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,
|
| 19 |
def update_fn(*args):
|
| 20 |
arch = args[0]
|
| 21 |
category = args[1]
|
|
@@ -84,9 +84,11 @@ def make_update_fn(m_comp, cat_comp, ar_comp, width_comp, height_comp, cn_types,
|
|
| 84 |
if ref_img_acc: updates[ref_img_acc] = gr.update(visible=('reference_image' in enabled_chains))
|
| 85 |
if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
|
| 86 |
if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
|
| 87 |
-
if
|
| 88 |
-
if
|
| 89 |
-
|
|
|
|
|
|
|
| 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 |
|
|
@@ -133,7 +135,7 @@ def make_update_fn(m_comp, cat_comp, ar_comp, width_comp, height_comp, cn_types,
|
|
| 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,
|
| 137 |
def change_fn(*args):
|
| 138 |
model_name = args[0]
|
| 139 |
idx = 1
|
|
@@ -210,9 +212,11 @@ def make_model_change_fn(cat_comp_ref, ar_comp, width_comp, height_comp, cn_type
|
|
| 210 |
if ref_img_acc: updates[ref_img_acc] = gr.update(visible=('reference_image' in enabled_chains))
|
| 211 |
if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
|
| 212 |
if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
|
| 213 |
-
if
|
| 214 |
-
if
|
| 215 |
-
|
|
|
|
|
|
|
| 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 |
|
|
|
|
| 9 |
ARCH_CATEGORIES_MAP
|
| 10 |
)
|
| 11 |
from utils.app_utils import get_model_generation_defaults
|
| 12 |
+
from ui.shared.ui_components import RESOLUTION_MAP, prompt_enhancer_defaults
|
| 13 |
from .config_loaders import (
|
| 14 |
get_cn_defaults,
|
| 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, pe_acc=None, pe_model_acc=None, pe_prompt_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]
|
|
|
|
| 84 |
if ref_img_acc: updates[ref_img_acc] = gr.update(visible=('reference_image' in enabled_chains))
|
| 85 |
if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
|
| 86 |
if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
|
| 87 |
+
if pe_acc: updates[pe_acc] = gr.update(visible=('prompt_enhancer' in enabled_chains))
|
| 88 |
+
if pe_model_acc or pe_prompt_acc:
|
| 89 |
+
pe_defaults = prompt_enhancer_defaults(arch_model_type)
|
| 90 |
+
if pe_model_acc: updates[pe_model_acc] = gr.update(value=pe_defaults['model'])
|
| 91 |
+
if pe_prompt_acc: updates[pe_prompt_acc] = gr.update(value=pe_defaults['system_prompt'])
|
| 92 |
if moss_image_control_acc: updates[moss_image_control_acc] = gr.update(visible=('moss_image_control' in enabled_chains))
|
| 93 |
if moss_image_edit_acc: updates[moss_image_edit_acc] = gr.update(visible=('moss_image_edit' in enabled_chains))
|
| 94 |
|
|
|
|
| 135 |
return update_fn
|
| 136 |
|
| 137 |
|
| 138 |
+
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, pe_acc=None, pe_model_acc=None, pe_prompt_acc=None, moss_image_control_acc=None, moss_image_edit_acc=None):
|
| 139 |
def change_fn(*args):
|
| 140 |
model_name = args[0]
|
| 141 |
idx = 1
|
|
|
|
| 212 |
if ref_img_acc: updates[ref_img_acc] = gr.update(visible=('reference_image' in enabled_chains))
|
| 213 |
if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
|
| 214 |
if vae_acc: updates[vae_acc] = gr.update(visible=('vae' in enabled_chains))
|
| 215 |
+
if pe_acc: updates[pe_acc] = gr.update(visible=('prompt_enhancer' in enabled_chains))
|
| 216 |
+
if pe_model_acc or pe_prompt_acc:
|
| 217 |
+
pe_defaults = prompt_enhancer_defaults(arch_model_type)
|
| 218 |
+
if pe_model_acc: updates[pe_model_acc] = gr.update(value=pe_defaults['model'])
|
| 219 |
+
if pe_prompt_acc: updates[pe_prompt_acc] = gr.update(value=pe_defaults['system_prompt'])
|
| 220 |
if moss_image_control_acc: updates[moss_image_control_acc] = gr.update(visible=('moss_image_control' in enabled_chains))
|
| 221 |
if moss_image_edit_acc: updates[moss_image_edit_acc] = gr.update(visible=('moss_image_edit' in enabled_chains))
|
| 222 |
|
ui/events/main.py
CHANGED
|
@@ -63,9 +63,9 @@ def attach_event_handlers(ui_components, demo):
|
|
| 63 |
ref_img_accordion = ui_components.get(f'reference_image_accordion_{prefix}')
|
| 64 |
pid_accordion = ui_components.get(f'pid_accordion_{prefix}')
|
| 65 |
vae_accordion = ui_components.get(f'vae_accordion_{prefix}')
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 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 |
|
|
@@ -103,9 +103,9 @@ def attach_event_handlers(ui_components, demo):
|
|
| 103 |
if ref_img_accordion: outputs.append(ref_img_accordion)
|
| 104 |
if pid_accordion: outputs.append(pid_accordion)
|
| 105 |
if vae_accordion: outputs.append(vae_accordion)
|
| 106 |
-
if
|
| 107 |
-
if
|
| 108 |
-
if
|
| 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)
|
|
@@ -118,7 +118,7 @@ def attach_event_handlers(ui_components, demo):
|
|
| 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,
|
| 122 |
moss_image_control_acc=moss_control_accordion,
|
| 123 |
moss_image_edit_acc=moss_edit_accordion
|
| 124 |
)
|
|
@@ -154,9 +154,9 @@ def attach_event_handlers(ui_components, demo):
|
|
| 154 |
if ref_img_accordion: outputs2.append(ref_img_accordion)
|
| 155 |
if pid_accordion: outputs2.append(pid_accordion)
|
| 156 |
if vae_accordion: outputs2.append(vae_accordion)
|
| 157 |
-
if
|
| 158 |
-
if
|
| 159 |
-
if
|
| 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)
|
|
@@ -174,7 +174,7 @@ def attach_event_handlers(ui_components, demo):
|
|
| 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,
|
| 178 |
moss_image_control_acc=moss_control_accordion,
|
| 179 |
moss_image_edit_acc=moss_edit_accordion
|
| 180 |
)
|
|
@@ -265,9 +265,7 @@ def attach_event_handlers(ui_components, demo):
|
|
| 265 |
f'krea2_reference_edit_accordion_{prefix}': 'krea2_style_reference',
|
| 266 |
f'qwen_image_edit_accordion_{prefix}': 'qwen_image_edit',
|
| 267 |
f'reference_image_accordion_{prefix}': 'reference_image',
|
| 268 |
-
f'
|
| 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',
|
|
@@ -307,9 +305,7 @@ def attach_event_handlers(ui_components, demo):
|
|
| 307 |
'krea2_identity_edit_accordion_imagegen', 'krea2_reference_edit_accordion_imagegen',
|
| 308 |
'qwen_image_edit_accordion_imagegen',
|
| 309 |
'reference_image_accordion_imagegen',
|
| 310 |
-
'
|
| 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',
|
|
|
|
| 63 |
ref_img_accordion = ui_components.get(f'reference_image_accordion_{prefix}')
|
| 64 |
pid_accordion = ui_components.get(f'pid_accordion_{prefix}')
|
| 65 |
vae_accordion = ui_components.get(f'vae_accordion_{prefix}')
|
| 66 |
+
pe_accordion = ui_components.get(f'prompt_enhancer_accordion_{prefix}')
|
| 67 |
+
pe_model_comp = ui_components.get(f'prompt_enhancer_model_{prefix}')
|
| 68 |
+
pe_prompt_comp = ui_components.get(f'prompt_enhancer_system_prompt_{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 |
|
|
|
|
| 103 |
if ref_img_accordion: outputs.append(ref_img_accordion)
|
| 104 |
if pid_accordion: outputs.append(pid_accordion)
|
| 105 |
if vae_accordion: outputs.append(vae_accordion)
|
| 106 |
+
if pe_accordion: outputs.append(pe_accordion)
|
| 107 |
+
if pe_model_comp: outputs.append(pe_model_comp)
|
| 108 |
+
if pe_prompt_comp: outputs.append(pe_prompt_comp)
|
| 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)
|
|
|
|
| 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, pe_acc=pe_accordion, pe_model_acc=pe_model_comp, pe_prompt_acc=pe_prompt_comp,
|
| 122 |
moss_image_control_acc=moss_control_accordion,
|
| 123 |
moss_image_edit_acc=moss_edit_accordion
|
| 124 |
)
|
|
|
|
| 154 |
if ref_img_accordion: outputs2.append(ref_img_accordion)
|
| 155 |
if pid_accordion: outputs2.append(pid_accordion)
|
| 156 |
if vae_accordion: outputs2.append(vae_accordion)
|
| 157 |
+
if pe_accordion: outputs2.append(pe_accordion)
|
| 158 |
+
if pe_model_comp: outputs2.append(pe_model_comp)
|
| 159 |
+
if pe_prompt_comp: outputs2.append(pe_prompt_comp)
|
| 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)
|
|
|
|
| 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, pe_acc=pe_accordion, pe_model_acc=pe_model_comp, pe_prompt_acc=pe_prompt_comp,
|
| 178 |
moss_image_control_acc=moss_control_accordion,
|
| 179 |
moss_image_edit_acc=moss_edit_accordion
|
| 180 |
)
|
|
|
|
| 265 |
f'krea2_reference_edit_accordion_{prefix}': 'krea2_style_reference',
|
| 266 |
f'qwen_image_edit_accordion_{prefix}': 'qwen_image_edit',
|
| 267 |
f'reference_image_accordion_{prefix}': 'reference_image',
|
| 268 |
+
f'prompt_enhancer_accordion_{prefix}': 'prompt_enhancer',
|
|
|
|
|
|
|
| 269 |
f'moss_image_control_accordion_{prefix}': 'moss_image_control',
|
| 270 |
f'moss_image_edit_accordion_{prefix}': 'moss_image_edit',
|
| 271 |
f'pid_accordion_{prefix}': 'pid',
|
|
|
|
| 305 |
'krea2_identity_edit_accordion_imagegen', 'krea2_reference_edit_accordion_imagegen',
|
| 306 |
'qwen_image_edit_accordion_imagegen',
|
| 307 |
'reference_image_accordion_imagegen',
|
| 308 |
+
'prompt_enhancer_accordion_imagegen',
|
|
|
|
|
|
|
| 309 |
'moss_image_control_accordion_imagegen',
|
| 310 |
'moss_image_edit_accordion_imagegen',
|
| 311 |
'pid_accordion_imagegen', 'vae_accordion_imagegen',
|
ui/events/run_handlers.py
CHANGED
|
@@ -37,31 +37,16 @@ def create_run_event(prefix: str, task_type: str = None, ui_components: dict = N
|
|
| 37 |
if ui_components.get(f'pid_settings_{prefix}'):
|
| 38 |
run_inputs_map['pid_settings'] = ui_components[f'pid_settings_{prefix}']
|
| 39 |
|
| 40 |
-
if ui_components.get(f'
|
| 41 |
-
run_inputs_map['
|
| 42 |
-
if ui_components.get(f'
|
| 43 |
-
run_inputs_map['
|
| 44 |
-
if ui_components.get(f'
|
| 45 |
-
run_inputs_map['
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
run_inputs_map['ming_image_prompt_enhancer_thinking'] = ui_components[f'ming_image_prompt_enhancer_thinking_{prefix}']
|
| 51 |
-
if ui_components.get(f'ming_image_prompt_enhancer_max_length_{prefix}'):
|
| 52 |
-
run_inputs_map['ming_image_prompt_enhancer_max_length'] = ui_components[f'ming_image_prompt_enhancer_max_length_{prefix}']
|
| 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}'):
|
|
|
|
| 37 |
if ui_components.get(f'pid_settings_{prefix}'):
|
| 38 |
run_inputs_map['pid_settings'] = ui_components[f'pid_settings_{prefix}']
|
| 39 |
|
| 40 |
+
if ui_components.get(f'prompt_enhancer_enable_{prefix}'):
|
| 41 |
+
run_inputs_map['prompt_enhancer_enable'] = ui_components[f'prompt_enhancer_enable_{prefix}']
|
| 42 |
+
if ui_components.get(f'prompt_enhancer_thinking_{prefix}'):
|
| 43 |
+
run_inputs_map['prompt_enhancer_thinking'] = ui_components[f'prompt_enhancer_thinking_{prefix}']
|
| 44 |
+
if ui_components.get(f'prompt_enhancer_max_length_{prefix}'):
|
| 45 |
+
run_inputs_map['prompt_enhancer_max_length'] = ui_components[f'prompt_enhancer_max_length_{prefix}']
|
| 46 |
+
if ui_components.get(f'prompt_enhancer_model_{prefix}'):
|
| 47 |
+
run_inputs_map['prompt_enhancer_model'] = ui_components[f'prompt_enhancer_model_{prefix}']
|
| 48 |
+
if ui_components.get(f'prompt_enhancer_system_prompt_{prefix}'):
|
| 49 |
+
run_inputs_map['prompt_enhancer_system_prompt'] = ui_components[f'prompt_enhancer_system_prompt_{prefix}']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
if ui_components.get(f'moss_image_control_color_map_{prefix}'):
|
| 51 |
run_inputs_map['moss_image_control_color_map'] = ui_components[f'moss_image_control_color_map_{prefix}']
|
| 52 |
if ui_components.get(f'moss_image_control_depth_map_{prefix}'):
|
ui/imagegen_ui.py
CHANGED
|
@@ -9,8 +9,7 @@ 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,
|
| 13 |
-
create_flux3_prompt_upsampler_ui, create_moss_image_control_ui,
|
| 14 |
create_moss_image_edit_ui
|
| 15 |
)
|
| 16 |
|
|
@@ -189,9 +188,7 @@ def create_ui():
|
|
| 189 |
components.update(create_krea2_reference_edit_ui(prefix))
|
| 190 |
components.update(create_qwen_image_edit_ui(prefix))
|
| 191 |
components.update(create_reference_image_ui(prefix))
|
| 192 |
-
components.update(
|
| 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))
|
|
|
|
| 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_prompt_enhancer_ui, create_moss_image_control_ui,
|
|
|
|
| 13 |
create_moss_image_edit_ui
|
| 14 |
)
|
| 15 |
|
|
|
|
| 188 |
components.update(create_krea2_reference_edit_ui(prefix))
|
| 189 |
components.update(create_qwen_image_edit_ui(prefix))
|
| 190 |
components.update(create_reference_image_ui(prefix))
|
| 191 |
+
components.update(create_prompt_enhancer_ui(prefix))
|
|
|
|
|
|
|
| 192 |
components.update(create_vae_override_ui(prefix))
|
| 193 |
components.update(create_moss_image_control_ui(prefix))
|
| 194 |
components.update(create_moss_image_edit_ui(prefix))
|
ui/shared/ui_components.py
CHANGED
|
@@ -1,947 +1,916 @@
|
|
| 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
|
| 792 |
-
components = {}
|
| 793 |
-
key = lambda name: f"{name}_{prefix}"
|
| 794 |
-
|
| 795 |
-
with gr.Accordion("
|
| 796 |
-
components[key('
|
| 797 |
-
gr.Markdown(
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
)
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
|
| 902 |
-
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
|
| 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 |
-
|
|
|
|
| 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_prompt_enhancer_ui(prefix: str):
|
| 792 |
+
components = {}
|
| 793 |
+
key = lambda name: f"{name}_{prefix}"
|
| 794 |
+
|
| 795 |
+
with gr.Accordion("Prompt Enhancer Settings", open=False, visible=('prompt_enhancer' in default_enabled_chains)) as enhancer_accordion:
|
| 796 |
+
components[key('prompt_enhancer_accordion')] = enhancer_accordion
|
| 797 |
+
gr.Markdown(
|
| 798 |
+
"💡 **Tip:** Automatically selects Prompt Enhancer Model and System Prompt based on the selected Base Model; "
|
| 799 |
+
"free mixing is also supported."
|
| 800 |
+
)
|
| 801 |
+
with gr.Row():
|
| 802 |
+
components[key('prompt_enhancer_enable')] = gr.Checkbox(
|
| 803 |
+
label="Enable Prompt Enhancer",
|
| 804 |
+
value=False,
|
| 805 |
+
interactive=True,
|
| 806 |
+
scale=2
|
| 807 |
+
)
|
| 808 |
+
components[key('prompt_enhancer_thinking')] = gr.Checkbox(
|
| 809 |
+
label="Enable Thinking",
|
| 810 |
+
value=False,
|
| 811 |
+
interactive=True,
|
| 812 |
+
scale=2
|
| 813 |
+
)
|
| 814 |
+
components[key('prompt_enhancer_max_length')] = gr.Number(
|
| 815 |
+
label="Max Length",
|
| 816 |
+
value=4096,
|
| 817 |
+
precision=0,
|
| 818 |
+
minimum=16,
|
| 819 |
+
step=16,
|
| 820 |
+
interactive=True,
|
| 821 |
+
scale=1
|
| 822 |
+
)
|
| 823 |
+
with gr.Row():
|
| 824 |
+
components[key('prompt_enhancer_model')] = gr.Dropdown(
|
| 825 |
+
choices=_prompt_enhancer_choices("model"),
|
| 826 |
+
value=_prompt_enhancer_initial_defaults()["model"],
|
| 827 |
+
label="Prompt Enhancer Model",
|
| 828 |
+
interactive=True,
|
| 829 |
+
scale=3
|
| 830 |
+
)
|
| 831 |
+
components[key('prompt_enhancer_system_prompt')] = gr.Dropdown(
|
| 832 |
+
choices=_prompt_enhancer_choices("system_prompt"),
|
| 833 |
+
value=_prompt_enhancer_initial_defaults()["system_prompt"],
|
| 834 |
+
label="System Prompt",
|
| 835 |
+
interactive=True,
|
| 836 |
+
scale=2
|
| 837 |
+
)
|
| 838 |
+
|
| 839 |
+
return components
|
| 840 |
+
|
| 841 |
+
|
| 842 |
+
def _prompt_enhancer_registry():
|
| 843 |
+
"""Load yaml/prompt_enhancer.yaml once per UI build."""
|
| 844 |
+
import yaml
|
| 845 |
+
path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "yaml", "prompt_enhancer.yaml")
|
| 846 |
+
try:
|
| 847 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 848 |
+
return yaml.safe_load(f) or {}
|
| 849 |
+
except Exception as e:
|
| 850 |
+
print(f"Warning: could not load prompt_enhancer.yaml: {e}")
|
| 851 |
+
return {}
|
| 852 |
+
|
| 853 |
+
|
| 854 |
+
def _prompt_enhancer_choices(kind):
|
| 855 |
+
cfg = _prompt_enhancer_registry()
|
| 856 |
+
names = list((cfg.get("clip" if kind == "model" else "system_prompt") or {}).keys())
|
| 857 |
+
return ["auto"] + names
|
| 858 |
+
|
| 859 |
+
|
| 860 |
+
def _prompt_enhancer_initial_defaults():
|
| 861 |
+
"""Seed the dropdowns from the Base Model selected on first page load.
|
| 862 |
+
|
| 863 |
+
``default_arch_model_type`` is resolved at import time from the first
|
| 864 |
+
checkpoint in the model list, so the panel opens on a real selection
|
| 865 |
+
instead of ``auto`` even when Model Architecture is ALL.
|
| 866 |
+
"""
|
| 867 |
+
return prompt_enhancer_defaults(default_arch_model_type)
|
| 868 |
+
|
| 869 |
+
|
| 870 |
+
def prompt_enhancer_defaults(framework):
|
| 871 |
+
"""Return {model, system_prompt} defaults declared for a model framework."""
|
| 872 |
+
cfg = _prompt_enhancer_registry()
|
| 873 |
+
clips = list((cfg.get("clip") or {}).keys())
|
| 874 |
+
prompts = list((cfg.get("system_prompt") or {}).keys())
|
| 875 |
+
entry = (cfg.get("architecture") or {}).get(str(framework)) or {}
|
| 876 |
+
|
| 877 |
+
model = entry.get("clip")
|
| 878 |
+
if model not in clips:
|
| 879 |
+
model = clips[0] if clips else "auto"
|
| 880 |
+
prompt = entry.get("system_prompt")
|
| 881 |
+
if prompt not in prompts:
|
| 882 |
+
prompt = prompts[0] if prompts else "auto"
|
| 883 |
+
return {"model": model, "system_prompt": prompt}
|
| 884 |
+
|
| 885 |
+
|
| 886 |
+
|
| 887 |
+
def create_moss_image_edit_ui(prefix: str, max_units=None):
|
| 888 |
+
if max_units is None:
|
| 889 |
+
max_units = MAX_MOSS_IMAGE_EDITS
|
| 890 |
+
components = {}
|
| 891 |
+
key = lambda name: f"{name}_{prefix}"
|
| 892 |
+
|
| 893 |
+
with gr.Accordion("MOSS-Image Edit Settings", open=False, visible=('moss_image_edit' in default_enabled_chains)) as ref_accordion:
|
| 894 |
+
components[key('moss_image_edit_accordion')] = ref_accordion
|
| 895 |
+
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).")
|
| 896 |
+
|
| 897 |
+
ref_image_groups = []
|
| 898 |
+
ref_image_inputs = []
|
| 899 |
+
with gr.Row():
|
| 900 |
+
for i in range(max_units):
|
| 901 |
+
with gr.Column(visible=(i < 1), min_width=160) as img_col:
|
| 902 |
+
img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
|
| 903 |
+
ref_image_groups.append(img_col)
|
| 904 |
+
ref_image_inputs.append(img_comp)
|
| 905 |
+
|
| 906 |
+
components[key('moss_image_edit_rows')] = ref_image_groups
|
| 907 |
+
components[key('moss_image_edit_images')] = ref_image_inputs
|
| 908 |
+
|
| 909 |
+
with gr.Row():
|
| 910 |
+
components[key('add_moss_image_edit_button')] = gr.Button("✚ Add Reference Image")
|
| 911 |
+
components[key('delete_moss_image_edit_button')] = gr.Button("➖ Delete Reference Image", visible=False)
|
| 912 |
+
components[key('moss_image_edit_count_state')] = gr.State(1)
|
| 913 |
+
|
| 914 |
+
components[key('all_moss_image_edit_components_flat')] = ref_image_inputs
|
| 915 |
+
|
| 916 |
+
return components
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
yaml/chain_features.yaml
CHANGED
|
@@ -478,81 +478,33 @@ embedding:
|
|
| 478 |
- source
|
| 479 |
- embedding_value
|
| 480 |
|
| 481 |
-
|
| 482 |
-
chains:
|
| 483 |
-
display_name: "
|
| 484 |
-
description: "Enhance prompts
|
| 485 |
max_count: 1
|
| 486 |
-
usage_guideline: "Set enable to true to activate prompt enhancement. Enable thinking
|
| 487 |
parameters_schema:
|
| 488 |
type: object
|
| 489 |
properties:
|
| 490 |
enable:
|
| 491 |
type: boolean
|
| 492 |
default: false
|
| 493 |
-
description: "Enable
|
| 494 |
thinking:
|
| 495 |
type: boolean
|
| 496 |
default: false
|
| 497 |
-
description: "Enable thinking (reasoning CoT). Thought text inside <think> tags
|
| 498 |
-
|
| 499 |
-
type:
|
| 500 |
-
|
| 501 |
-
description: "Maximum generation length for prompt enhancement (default 4096)."
|
| 502 |
-
|
| 503 |
-
ming_image_prompt_enhancer:
|
| 504 |
-
chains: ming_image_prompt_enhancer
|
| 505 |
-
display_name: "Ming-Image Prompt Enhancer Settings"
|
| 506 |
-
description: "Enhance prompts using Ming-Image Prompt Enhancer (Qwen3.8-27B) guided by official Figma-style rewriter System Prompt, with optional reasoning CoT thinking mode."
|
| 507 |
-
max_count: 1
|
| 508 |
-
usage_guideline: "Set enable to true to activate Ming-Image prompt enhancement. Supports single/multi-reference images, thinking mode, and custom system prompt."
|
| 509 |
-
parameters_schema:
|
| 510 |
-
type: object
|
| 511 |
-
properties:
|
| 512 |
-
enable:
|
| 513 |
-
type: boolean
|
| 514 |
-
default: false
|
| 515 |
-
description: "Enable Ming-Image Prompt Enhancer."
|
| 516 |
-
thinking:
|
| 517 |
-
type: boolean
|
| 518 |
-
default: false
|
| 519 |
-
description: "Enable thinking (reasoning CoT)."
|
| 520 |
-
max_length:
|
| 521 |
-
type: integer
|
| 522 |
-
default: 4096
|
| 523 |
-
description: "Maximum generation length for prompt enhancement (default 4096)."
|
| 524 |
system_prompt:
|
| 525 |
type: string
|
| 526 |
-
description: "System
|
| 527 |
-
|
| 528 |
-
flux3_prompt_upsampler:
|
| 529 |
-
chains: flux3_prompt_upsampler
|
| 530 |
-
display_name: "FLUX.3 Prompt Upsampler Settings"
|
| 531 |
-
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."
|
| 532 |
-
max_count: 1
|
| 533 |
-
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."
|
| 534 |
-
parameters_schema:
|
| 535 |
-
type: object
|
| 536 |
-
properties:
|
| 537 |
-
enable:
|
| 538 |
-
type: boolean
|
| 539 |
-
default: false
|
| 540 |
-
description: "Enable FLUX.3 Prompt Upsampler."
|
| 541 |
-
thinking:
|
| 542 |
-
type: boolean
|
| 543 |
-
default: false
|
| 544 |
-
description: "Enable thinking (reasoning CoT)."
|
| 545 |
-
bbox_mode:
|
| 546 |
-
type: boolean
|
| 547 |
-
default: false
|
| 548 |
-
description: "Enable BBox mode (automatically selects t2i_bbox or i2i_bbox official system prompt templates to output bounding boxes)."
|
| 549 |
max_length:
|
| 550 |
type: integer
|
| 551 |
-
default:
|
| 552 |
-
description: "Maximum generation length for prompt
|
| 553 |
-
system_prompt:
|
| 554 |
-
type: string
|
| 555 |
-
description: "Optional custom system prompt override (defaults to official t2i/i2i or t2i_bbox/i2i_bbox templates)."
|
| 556 |
|
| 557 |
moss_image_control:
|
| 558 |
chains: moss_image_control
|
|
|
|
| 478 |
- source
|
| 479 |
- embedding_value
|
| 480 |
|
| 481 |
+
prompt_enhancer:
|
| 482 |
+
chains: prompt_enhancer
|
| 483 |
+
display_name: "Prompt Enhancer Settings"
|
| 484 |
+
description: "Enhance or upsample prompts with any registered prompt-enhancer model (Qwen-Image-2.1 PE T2I/I2I, Qwen3.8-27B, FLUX.3 Prompt Upsampler), optional reasoning CoT thinking mode, and optional system prompt. The clip and system_prompt registries are independent, so they are freely mixable across architectures."
|
| 485 |
max_count: 1
|
| 486 |
+
usage_guideline: "Set enable to true to activate prompt enhancement. Pick model from the clip registry and system_prompt from the system_prompt registry in yaml/prompt_enhancer.yaml; both lists are global, so any architecture may select any clip paired with any system prompt. Defaults for each framework come from the architecture section and reset when the base model changes. Nothing is auto-detected from images, so an explicit system prompt always wins. Enable thinking for reasoning CoT (reasoning tags are stripped from the result), and set max_length for generation length."
|
| 487 |
parameters_schema:
|
| 488 |
type: object
|
| 489 |
properties:
|
| 490 |
enable:
|
| 491 |
type: boolean
|
| 492 |
default: false
|
| 493 |
+
description: "Enable prompt enhancement."
|
| 494 |
thinking:
|
| 495 |
type: boolean
|
| 496 |
default: false
|
| 497 |
+
description: "Enable thinking (reasoning CoT). Thought text inside <think> tags is automatically stripped before the prompt reaches downstream text encoding."
|
| 498 |
+
model:
|
| 499 |
+
type: string
|
| 500 |
+
description: "Prompt Enhancer Model name from the clip registry in yaml/prompt_enhancer.yaml (e.g. Qwen-Image-2.1-Prompt-Enhancer-T2I, Qwen-Image-2.1-Prompt-Enhancer-I2I, Qwen3.8-27B, Qwen3.5-9B-FLUX.3-Prompt-Upsampler). Any model may select any registered clip regardless of architecture; clip_type travels with the clip. Defaults to the architecture entry for the active framework."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
system_prompt:
|
| 502 |
type: string
|
| 503 |
+
description: "System Prompt name from the system_prompt registry in yaml/prompt_enhancer.yaml (e.g. none, Ming-Image-PE-Template, FLUX.3-T2I-PE-Template, FLUX.3-I2I-PE-Template, FLUX.3-T2I-Bbox-PE-Template, FLUX.3-I2I-Bbox-PE-Template). Independent of the selected clip and never inferred from images; an empty prompt means no system prompt."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
max_length:
|
| 505 |
type: integer
|
| 506 |
+
default: 4096
|
| 507 |
+
description: "Maximum generation length for prompt enhancement (default 4096)."
|
|
|
|
|
|
|
|
|
|
| 508 |
|
| 509 |
moss_image_control:
|
| 510 |
chains: moss_image_control
|
yaml/model_architecture_features.yaml
CHANGED
|
@@ -9,7 +9,7 @@ flux3:
|
|
| 9 |
enabled_chains:
|
| 10 |
- lora
|
| 11 |
- reference_latent
|
| 12 |
-
-
|
| 13 |
- pid
|
| 14 |
|
| 15 |
minimax-h3-image:
|
|
@@ -104,14 +104,14 @@ ming-image:
|
|
| 104 |
enabled_chains:
|
| 105 |
- lora
|
| 106 |
- reference_image
|
| 107 |
-
-
|
| 108 |
|
| 109 |
qwen-image-2.1:
|
| 110 |
enabled_chains:
|
| 111 |
- lora
|
| 112 |
- controlnet
|
| 113 |
- reference_image
|
| 114 |
-
-
|
| 115 |
|
| 116 |
qwen-image:
|
| 117 |
enabled_chains:
|
|
|
|
| 9 |
enabled_chains:
|
| 10 |
- lora
|
| 11 |
- reference_latent
|
| 12 |
+
- prompt_enhancer
|
| 13 |
- pid
|
| 14 |
|
| 15 |
minimax-h3-image:
|
|
|
|
| 104 |
enabled_chains:
|
| 105 |
- lora
|
| 106 |
- reference_image
|
| 107 |
+
- prompt_enhancer
|
| 108 |
|
| 109 |
qwen-image-2.1:
|
| 110 |
enabled_chains:
|
| 111 |
- lora
|
| 112 |
- controlnet
|
| 113 |
- reference_image
|
| 114 |
+
- prompt_enhancer
|
| 115 |
|
| 116 |
qwen-image:
|
| 117 |
enabled_chains:
|
yaml/prompt_enhancer.yaml
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
architecture:
|
| 2 |
+
flux3:
|
| 3 |
+
clip: "Qwen3.5-9B-FLUX.3-Prompt-Upsampler"
|
| 4 |
+
system_prompt: "FLUX.3-T2I-PE-Template"
|
| 5 |
+
ming-image:
|
| 6 |
+
clip: "Qwen3.8-27B"
|
| 7 |
+
system_prompt: "Ming-Image-PE-Template"
|
| 8 |
+
qwen-image-2.1:
|
| 9 |
+
clip: "Qwen-Image-2.1-Prompt-Enhancer-T2I"
|
| 10 |
+
system_prompt: "none"
|
| 11 |
+
|
| 12 |
+
clip:
|
| 13 |
+
Qwen-Image-2.1-Prompt-Enhancer-T2I:
|
| 14 |
+
filename: "qwen3.5_9b_qwen_image_2.1_pe_t2i.int8_convrot.safetensors"
|
| 15 |
+
clip_type: qwen_image
|
| 16 |
+
Qwen-Image-2.1-Prompt-Enhancer-I2I:
|
| 17 |
+
filename: "qwen3.5_9b_qwen_image_2.1_pe_i2i.int8_convrot.safetensors"
|
| 18 |
+
clip_type: qwen_image
|
| 19 |
+
Qwen3.8-27B:
|
| 20 |
+
filename: "qwen3.8_27b_w4a8.safetensors"
|
| 21 |
+
clip_type: qwen_image
|
| 22 |
+
Qwen3.5-9B-FLUX.3-Prompt-Upsampler:
|
| 23 |
+
filename: "qwen3.5_9b_flux3_prompt_upsampler_w4a8.safetensors"
|
| 24 |
+
clip_type: flux3
|
| 25 |
+
|
| 26 |
+
system_prompt:
|
| 27 |
+
none: ""
|
| 28 |
+
Ming-Image-PE-Template: |-
|
| 29 |
+
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. 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. `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. 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. 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." 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. 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. Silently verify schema, string coordinates, Z-order, exact-text counts, geometry, bbox validity, and completeness.
|
| 30 |
+
FLUX.3-T2I-PE-Template: |-
|
| 31 |
+
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.
|
| 32 |
+
|
| 33 |
+
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.
|
| 34 |
+
|
| 35 |
+
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.
|
| 36 |
+
|
| 37 |
+
Output only the caption.
|
| 38 |
+
FLUX.3-I2I-PE-Template: |-
|
| 39 |
+
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.
|
| 40 |
+
|
| 41 |
+
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.
|
| 42 |
+
|
| 43 |
+
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.
|
| 44 |
+
|
| 45 |
+
Output only the instruction.
|
| 46 |
+
FLUX.3-T2I-Bbox-PE-Template: |-
|
| 47 |
+
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.
|
| 48 |
+
|
| 49 |
+
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.
|
| 50 |
+
|
| 51 |
+
The 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.
|
| 52 |
+
|
| 53 |
+
The 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.
|
| 54 |
+
|
| 55 |
+
Output only the caption and the table.
|
| 56 |
+
FLUX.3-I2I-Bbox-PE-Template: |-
|
| 57 |
+
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.
|
| 58 |
+
|
| 59 |
+
The 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.
|
| 60 |
+
|
| 61 |
+
The 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.
|
| 62 |
+
|
| 63 |
+
State 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.
|
| 64 |
+
|
| 65 |
+
Output only the paragraph and the table.
|
yaml/task_features.yaml
CHANGED
|
@@ -22,9 +22,7 @@ txt2img:
|
|
| 22 |
- krea2_style_reference
|
| 23 |
- qwen_image_edit
|
| 24 |
- reference_image
|
| 25 |
-
-
|
| 26 |
-
- ming_image_prompt_enhancer
|
| 27 |
-
- flux3_prompt_upsampler
|
| 28 |
- moss_image_control
|
| 29 |
- moss_image_edit
|
| 30 |
- pid
|
|
@@ -49,9 +47,7 @@ img2img:
|
|
| 49 |
- krea2_style_reference
|
| 50 |
- qwen_image_edit
|
| 51 |
- reference_image
|
| 52 |
-
-
|
| 53 |
-
- ming_image_prompt_enhancer
|
| 54 |
-
- flux3_prompt_upsampler
|
| 55 |
- moss_image_control
|
| 56 |
- moss_image_edit
|
| 57 |
- vae
|
|
@@ -75,9 +71,7 @@ inpaint:
|
|
| 75 |
- krea2_style_reference
|
| 76 |
- qwen_image_edit
|
| 77 |
- reference_image
|
| 78 |
-
-
|
| 79 |
-
- ming_image_prompt_enhancer
|
| 80 |
-
- flux3_prompt_upsampler
|
| 81 |
- moss_image_control
|
| 82 |
- moss_image_edit
|
| 83 |
- vae
|
|
@@ -101,9 +95,7 @@ outpaint:
|
|
| 101 |
- krea2_style_reference
|
| 102 |
- qwen_image_edit
|
| 103 |
- reference_image
|
| 104 |
-
-
|
| 105 |
-
- ming_image_prompt_enhancer
|
| 106 |
-
- flux3_prompt_upsampler
|
| 107 |
- moss_image_control
|
| 108 |
- moss_image_edit
|
| 109 |
- vae
|
|
@@ -127,9 +119,7 @@ hires_fix:
|
|
| 127 |
- krea2_style_reference
|
| 128 |
- qwen_image_edit
|
| 129 |
- reference_image
|
| 130 |
-
-
|
| 131 |
-
- ming_image_prompt_enhancer
|
| 132 |
-
- flux3_prompt_upsampler
|
| 133 |
- moss_image_control
|
| 134 |
- moss_image_edit
|
| 135 |
- vae
|
|
|
|
| 22 |
- krea2_style_reference
|
| 23 |
- qwen_image_edit
|
| 24 |
- reference_image
|
| 25 |
+
- prompt_enhancer
|
|
|
|
|
|
|
| 26 |
- moss_image_control
|
| 27 |
- moss_image_edit
|
| 28 |
- pid
|
|
|
|
| 47 |
- krea2_style_reference
|
| 48 |
- qwen_image_edit
|
| 49 |
- reference_image
|
| 50 |
+
- prompt_enhancer
|
|
|
|
|
|
|
| 51 |
- moss_image_control
|
| 52 |
- moss_image_edit
|
| 53 |
- vae
|
|
|
|
| 71 |
- krea2_style_reference
|
| 72 |
- qwen_image_edit
|
| 73 |
- reference_image
|
| 74 |
+
- prompt_enhancer
|
|
|
|
|
|
|
| 75 |
- moss_image_control
|
| 76 |
- moss_image_edit
|
| 77 |
- vae
|
|
|
|
| 95 |
- krea2_style_reference
|
| 96 |
- qwen_image_edit
|
| 97 |
- reference_image
|
| 98 |
+
- prompt_enhancer
|
|
|
|
|
|
|
| 99 |
- moss_image_control
|
| 100 |
- moss_image_edit
|
| 101 |
- vae
|
|
|
|
| 119 |
- krea2_style_reference
|
| 120 |
- qwen_image_edit
|
| 121 |
- reference_image
|
| 122 |
+
- prompt_enhancer
|
|
|
|
|
|
|
| 123 |
- moss_image_control
|
| 124 |
- moss_image_edit
|
| 125 |
- vae
|