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
52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
"""
|
|
Named direction axes: "ordinary -> uncanny", "new -> decayed", ...
|
|
|
|
An axis is two sets of example phrases (YAML: name -> {description, from: [...], to: [...]}).
|
|
Its vector is centroid(to) - centroid(from) in whatever embedder is in use, so the same file works
|
|
for every embedder; the vectors are computed once per embedder and kept in memory, and the example
|
|
phrases themselves go through the on-disk vector cache like any other phrase.
|
|
|
|
(c) Eric Hiss. Dual license, see LICENSE.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass, field
|
|
from typing import Dict, Iterable, List
|
|
|
|
NONE = "none"
|
|
|
|
|
|
@dataclass
|
|
class DirectionAxis:
|
|
name: str
|
|
description: str = ""
|
|
start: List[str] = field(default_factory=list) # the `from` phrases
|
|
end: List[str] = field(default_factory=list) # the `to` phrases
|
|
|
|
|
|
def load_directions(paths: Iterable[str]) -> Dict[str, DirectionAxis]:
|
|
"""Read axis files in order; a later file replaces an axis of the same name. Bad files are skipped."""
|
|
axes: Dict[str, DirectionAxis] = {}
|
|
for path in paths:
|
|
if not os.path.isfile(path):
|
|
continue
|
|
try:
|
|
import yaml
|
|
with open(path, "r", encoding="utf-8-sig") as handle:
|
|
data = yaml.safe_load(handle) or {}
|
|
except Exception as error:
|
|
print(f"[Semantic Variation] direction file {path} could not be read: {error}")
|
|
continue
|
|
if not isinstance(data, dict):
|
|
continue
|
|
for name, body in data.items():
|
|
if not isinstance(body, dict):
|
|
continue
|
|
start = [str(item) for item in (body.get("from") or []) if str(item).strip()]
|
|
end = [str(item) for item in (body.get("to") or []) if str(item).strip()]
|
|
if len(start) >= 2 and len(end) >= 2:
|
|
axes[str(name)] = DirectionAxis(str(name), str(body.get("description", "")), start, end)
|
|
return axes
|