Files
EricRollei-Local_LLM_Prompt…/core/variability/directions.py
T
Eric HissandClaude Fable 5.1 4459af9ea6 V2.0.0: wildcard engine, platform profiles, MiniMax H3 default, API models
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
2026-09-21 00:28:58 -07:00

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