Files
EricRollei-Local_LLM_Prompt…/img2img_expansion_engine.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

272 lines
9.3 KiB
Python

"""
Image-to-Image Expansion Engine
Platform-aware prompt expansion for image generation models
"""
from typing import Dict, Tuple, Optional, Sequence
from .platforms import get_platform_config, format_for_platform, get_negative_prompt_for_platform
class ImageToImageExpander:
"""Expands image descriptions and change requests into platform-optimized prompts"""
def __init__(self):
self.platforms = {}
def expand_img2img_prompt(
self,
image_description: str,
change_request: str,
platform: str,
aesthetic_controls: Optional[Dict] = None,
custom_negatives: Optional[Sequence[str]] = None
) -> Tuple[str, str, Dict]:
"""
Expand image-to-image prompt with platform awareness
Args:
image_description: Vision model's description of input image
change_request: User's description of desired changes
platform: Target platform (flux, wan22, hunyuan_image, etc.)
aesthetic_controls: Style, lighting, composition controls
custom_negatives: Additional negative terms
Returns:
Tuple of (positive_prompt, negative_prompt, breakdown_dict)
"""
platform_config = get_platform_config(platform)
# Build the system prompt for LLM expansion
system_prompt = self._build_expansion_prompt(
platform,
platform_config,
aesthetic_controls
)
# Build user prompt combining image desc + changes
user_prompt = self._build_user_prompt(
image_description,
change_request,
platform_config
)
# Build breakdown for user reference
breakdown = {
"platform": platform,
"platform_name": platform_config["name"],
"prompt_style": platform_config["prompt_style"],
"optimal_length": platform_config["optimal_length"],
"image_description": image_description,
"change_request": change_request,
"aesthetic_controls": aesthetic_controls,
"quality_emphasis": bool(platform_config.get("quality_emphasis", True))
}
return system_prompt, user_prompt, breakdown
def _build_expansion_prompt(
self,
platform: str,
config: dict,
aesthetic_controls: Optional[Dict]
) -> str:
"""Build LLM system prompt with platform-specific instructions"""
quality_emphasis = bool(config.get("quality_emphasis", True))
# Profiles (core/profiles) supply the format in words; the comma-list rule only suits tag models.
format_instructions = config.get("format_instructions", "")
format_rule = (
"Follow the FORMAT section below exactly" if format_instructions
else "Write as a comma-separated list of descriptive elements"
)
prompt = f"""You are an expert prompt engineer for {config['name']} image generation.
CRITICAL OUTPUT RULES:
1. Output ONLY the final prompt - no labels, explanations, or meta-commentary
2. Do NOT include phrases like "Here is...", "Prompt:", etc.
3. {format_rule}
4. Start directly with the description
TARGET PLATFORM: {config['name']}
Platform Description: {config['description']}
Prompting Style: {config['prompt_style']}
Optimal Length: {config['optimal_length']}
"""
# Add platform-specific preferences
if config.get("preferences"):
prompt += "\nPLATFORM PREFERENCES:\n"
for pref in config["preferences"]:
prompt += f"- {pref}\n"
# Add quality tokens if enabled
if quality_emphasis and config.get("quality_tokens"):
prompt += f"\nQUALITY TOKENS TO USE: {', '.join(config['quality_tokens'][:5])}\n"
# Add things to avoid
if config.get("avoid"):
prompt += "\nAVOID:\n"
for avoid in config["avoid"]:
prompt += f"- {avoid}\n"
# Platform-specific instructions: from the platform's profile when there is one
if format_instructions:
low_words, high_words = config.get("min_words"), config.get("max_words")
prompt += f"\nFORMAT:\n{format_instructions}"
if low_words and high_words:
prompt += f"- Length: {low_words}-{high_words} words.\n"
elif platform == "qwen_image_edit":
prompt += """
EDIT MODE INSTRUCTIONS:
- Be VERY concise (20-50 tokens max)
- Focus ONLY on what changes
- Use clear change language: "change X to Y", "add Z", "remove W"
- Do NOT re-describe unchanged elements
- Include preservation hints if needed: "keep background", "maintain composition"
"""
elif platform in {"flux", "flux_kontex"}:
prompt += """
FLUX FAMILY INSTRUCTIONS:
- Use natural, detailed language with cinematic storytelling
- Include style references when appropriate
- Use photography/artistic terms and sensory details
- Front-load important concepts
- Quality modifiers are optional; focus on evocative description
"""
elif platform == "wan22":
prompt += """
WAN 2.2 INSTRUCTIONS:
- Use technical cinematography terminology
- Structure: subject, setting, lighting, composition
- Be specific about lighting types and composition
- Medium length (50-100 tokens)
"""
elif platform == "hunyuan_image":
prompt += """
HUNYUAN INSTRUCTIONS:
- Clear, simple English (avoid complex vocabulary)
- Focus on photorealism
- Concise but descriptive (40-80 tokens)
- Direct, straightforward descriptions
"""
elif platform == "qwen_image":
prompt += """
QWEN INSTRUCTIONS:
- Natural, conversational language
- Balanced detail level (50-100 tokens)
- Good with diverse styles and cultural elements
- Professional but not overly technical
"""
elif platform == "sd_xl":
prompt += """
SDXL INSTRUCTIONS:
- Token-aware: 40-75 tokens optimal
- Front-load important concepts
- Quality tokens at the start
- Can use emphasis with (parentheses)
"""
# Add aesthetic controls if provided
if aesthetic_controls:
prompt += self._format_aesthetic_controls(aesthetic_controls, platform)
prompt += """
FINAL REMINDERS:
- Output ONLY the prompt text
- Follow platform-specific preferences
- Combine image description with requested changes seamlessly
- Use appropriate length for platform
"""
return prompt
def _build_user_prompt(
self,
image_description: str,
change_request: str,
config: dict
) -> str:
"""Build user prompt for LLM"""
parts = []
if image_description:
parts.append(f"Current image: {image_description}")
if change_request:
parts.append(f"Requested changes: {change_request}")
else:
parts.append("No changes requested - enhance and optimize the description for generation")
return " | ".join(parts)
def _format_aesthetic_controls(self, controls: Dict, platform: str) -> str:
"""Format aesthetic controls for system prompt"""
formatted = "\n=== AESTHETIC CONTROLS ===\n"
formatted += "User has specified these requirements. You MUST incorporate them:\n\n"
control_map = {
"art_style": "Art Style",
"lighting_type": "Lighting",
"composition": "Composition",
"color_palette": "Color Palette",
"mood": "Mood/Atmosphere",
"detail_level": "Detail Level",
"quality_preset": "Quality Preset"
}
for key, value in controls.items():
if value and value.lower() not in ["auto", "none", ""]:
label = control_map.get(key, key.replace("_", " ").title())
formatted += f"- {label}: {value}\n"
formatted += "\nSeamlessly integrate these into the prompt.\n"
return formatted
def generate_negative_prompt(
self,
platform: str,
custom_negatives: Optional[Sequence[str]] = None
) -> str:
"""Generate platform-optimized negative prompt"""
return get_negative_prompt_for_platform(platform, custom_negatives)
def parse_llm_response(self, response: str) -> str:
"""Clean and parse LLM response"""
cleaned = response.strip()
# Remove common artifacts
artifacts = [
"here is the prompt:", "here's the prompt:",
"prompt:", "final prompt:", "output:",
"here is:", "here's:"
]
cleaned_lower = cleaned.lower()
for artifact in artifacts:
if artifact in cleaned_lower:
idx = cleaned_lower.index(artifact)
cleaned = cleaned[idx + len(artifact):].strip()
cleaned_lower = cleaned.lower()
# Remove markdown code blocks if present
if cleaned.startswith("```"):
lines = cleaned.split('\n')
cleaned = '\n'.join(lines[1:-1]) if len(lines) > 2 else cleaned
# Fallback: use original if cleaned is too short
if len(cleaned) < 20:
cleaned = response.strip()
return cleaned