Every node id, input name and existing widget position is unchanged, so saved
workflows load; new inputs are appended only.
Wildcards
- core/wildcards: Dynamic Prompts / Impact Pack grammar, tag queries, variables,
conditionals, concept / verbatim / loose bindings, reproducible seeds.
Wired into every enhancer node; reads ComfyUI/models/wildcards.
- New nodes: Wildcard Processor, Semantic Variation (direction axes),
Concept Survival Check. Starter lists in wildcards/.
- Random choices use a private generator; global random state is untouched.
Platform profiles
- core/profiles + profiles/*.yaml: one file per model (format, length,
negative prompt or none, quality tokens, text and dialogue syntax, vendor
rewriter prompt fetched once and cached). profiles_user/ overrides by id.
- New node: Platform Prompt Rewriter (prose, tags, edit instructions, JSON,
MiniMax H3 fields, Hailuo brackets), with validators for each format.
- Text-to-Image and Image-to-Image read profiles through platforms.py;
corrected facts for FLUX, Kontext, Chroma, Wan, AuraFlow, Kolors, NoobAI,
Qwen-Image, HunyuanImage. Negative prompt is empty for models without one.
- Hosted / API models: Seedream 5, Nano Banana, GPT Image, Midjourney V8,
FLUX.2 pro, Ideogram 3, Recraft V4, Seedance 2.0 / 2.5, Kling 3.0, Veo 3.1,
Gemini Omni Flash, Runway, Wan 3.0, Luma, hosted H3, Grok, Vidu, PixVerse.
Reports say how to switch the vendor's own prompt rewriting off.
- Illustrious: spaced tags, quality tags last, 25-80 words (OnomaAI paper).
Video nodes
- target_model and duration_seconds appended to the three video expanders.
Default target is MiniMax H3; wan22_video is the original path, unchanged.
A workflow saved earlier loads with the default until the input is set.
Also included: the Alice, Ideogram Magic Prompt and prompt-database nodes that
were already registered in __init__.py but had not been committed.
Fixes: v1 {animal} / {label:a|b} wildcards were inert in the video nodes;
Image-to-Video save_to_file raised NameError; T2I padded short-prompt models.
.gitattributes added (text=auto) so CRLF working files stop showing as
whole-file changes. prompt_database.db and the cached Ideogram prompt are
git-ignored.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VpCi6nw5UECMdZ7ayYJvN
126 lines
5.5 KiB
Python
126 lines
5.5 KiB
Python
"""
|
|
Wildcard Processor Node
|
|
|
|
Resolves wildcard syntax in a prompt with no LLM involved. Use it on its own in front of
|
|
any text encoder, or in front of the prompt enhancer nodes: the `llm_instructions` output
|
|
tells an LLM rewriter which elements were chosen at random and how tightly to keep them.
|
|
|
|
Syntax (Dynamic Prompts compatible, existing wildcard packs work as they are):
|
|
__animal__ __light/setups__ __camera/**__ file wildcards, folders, globs
|
|
__objects[metal AND handheld]__ __[hollow AND antique]__ pick by tag
|
|
{a|b|c} {3::common|1::rare} {2$$ and $$a|b|c} variants, weights, multi-select
|
|
${who=!__person__} ... ${who} pick once, reuse
|
|
[if rain : umbrella | sunhat] conditionals
|
|
__db:red door__ draw from the prompt database
|
|
__=name__ {=a|b} exact words must survive an LLM rewrite
|
|
__?name__ {?a|b} inspiration only
|
|
|
|
Packs are read from ComfyUI/models/wildcards (and any `wildcards:` entry in
|
|
extra_model_paths.yaml), then from this package's own wildcards/ folder. When a name exists
|
|
in both, your pack wins. `extra_paths` (one folder per line) takes priority over everything.
|
|
|
|
Author: Eric Hiss (GitHub: EricRollei)
|
|
License: Dual License (Non-Commercial and Commercial Use). See LICENSE.
|
|
"""
|
|
|
|
import traceback
|
|
from typing import Tuple
|
|
|
|
from .core.wildcards import MODES, split_paths
|
|
from .wildcard_support import database_provider, get_wildcard_engine
|
|
|
|
|
|
class WildcardProcessor:
|
|
"""Resolve wildcards, variants, variables and tag queries in a prompt."""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"text": ("STRING", {
|
|
"multiline": True,
|
|
"default": "__person__, __gestures__, beside __objects[hollow AND antique]__ in __spaces/**__. __light/setups__. __camera/lenses__.",
|
|
"tooltip": "Prompt with wildcard syntax. __name__ = file wildcard, {a|b} = variant, __objects[tag AND tag]__ = pick by tag.",
|
|
}),
|
|
"seed": ("INT", {
|
|
"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF,
|
|
"tooltip": "Same seed + same wildcard files = same text.",
|
|
}),
|
|
"mode": (list(MODES), {
|
|
"default": "random",
|
|
"tooltip": (
|
|
"random = weighted random picks decided by the seed.\n"
|
|
"combinatorial = no randomness: the `index` input walks through every possible combination in order."
|
|
),
|
|
}),
|
|
},
|
|
"optional": {
|
|
"index": ("INT", {
|
|
"default": 0, "min": 0, "max": 0xFFFFFFFF,
|
|
"tooltip": (
|
|
"A counter, not a seed. Connect a number that goes up by one per run.\n"
|
|
"It selects which combination comes out when mode = combinatorial (wraps around at the end),\n"
|
|
"and it steps cyclical variants {@a|b|c} in either mode."
|
|
),
|
|
}),
|
|
"legacy_syntax": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Also accept the v1 forms {animal} and {label:a|b|c}.",
|
|
}),
|
|
"use_prompt_database": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Let __db:query__ draw a matching prompt from the prompt database.",
|
|
}),
|
|
"extra_paths": ("STRING", {
|
|
"multiline": True,
|
|
"default": "",
|
|
"tooltip": "Extra wildcard folders, one per line, highest priority. ComfyUI/models/wildcards is always searched; this is for folders elsewhere.",
|
|
}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
|
|
RETURN_NAMES = ("text", "llm_instructions", "record_json", "report")
|
|
FUNCTION = "process"
|
|
CATEGORY = "Eric Prompt Enhancers"
|
|
|
|
def process(
|
|
self,
|
|
text: str,
|
|
seed: int,
|
|
mode: str,
|
|
index: int = 0,
|
|
legacy_syntax: bool = True,
|
|
use_prompt_database: bool = True,
|
|
extra_paths: str = "",
|
|
) -> Tuple[str, str, str, str]:
|
|
try:
|
|
engine = get_wildcard_engine(split_paths(extra_paths))
|
|
providers = {"db": database_provider} if use_prompt_database else {}
|
|
resolution = engine.process(
|
|
text,
|
|
seed=int(seed),
|
|
mode=mode,
|
|
index=int(index),
|
|
cycle_index=int(index),
|
|
legacy=bool(legacy_syntax),
|
|
providers=providers,
|
|
)
|
|
|
|
report_lines = [resolution.summary()]
|
|
if resolution.mode == "combinatorial":
|
|
report_lines.append(
|
|
f"combination {int(index) % max(1, resolution.combination_space) + 1} of {resolution.combination_space}"
|
|
)
|
|
report_lines.append("search folders: " + "; ".join(engine.library.search_paths))
|
|
|
|
return (
|
|
resolution.text,
|
|
resolution.format_for_llm(),
|
|
resolution.to_json(),
|
|
"\n".join(report_lines),
|
|
)
|
|
except Exception as error: # never break the graph over a prompt
|
|
traceback.print_exc()
|
|
return (text, "", "{}", f"Wildcard Processor error, text passed through unchanged: {error}")
|