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
125 lines
4.2 KiB
Python
125 lines
4.2 KiB
Python
"""
|
||
Save Prompt to Database Node
|
||
ComfyUI node that saves prompts to the local SQLite prompt database.
|
||
Passthrough design – outputs the same prompt it receives so it can be
|
||
inserted into any workflow without breaking connections.
|
||
|
||
Author: Eric Hiss (GitHub: EricRollei)
|
||
"""
|
||
|
||
import logging
|
||
from .prompt_database import PromptDatabase, PLATFORMS, CATEGORIES
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class SavePromptToDatabase:
|
||
"""
|
||
Saves a prompt (and optional negative prompt) to the local prompt
|
||
database. The positive prompt is passed through unchanged so this
|
||
node can sit between a prompt-generating node and a sampler.
|
||
"""
|
||
|
||
def __init__(self):
|
||
self.db = PromptDatabase()
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"prompt_text": ("STRING", {
|
||
"multiline": True,
|
||
"default": "",
|
||
"tooltip": "The prompt to save. Connect this to the output of any prompt enhancer node.",
|
||
}),
|
||
"category": (CATEGORIES, {
|
||
"default": "any",
|
||
"tooltip": "Subject category for organisation.",
|
||
}),
|
||
"platform": (PLATFORMS, {
|
||
"default": "any",
|
||
"tooltip": "Target generation platform (Flux, SDXL, Pony, etc.).",
|
||
}),
|
||
"tags": ("STRING", {
|
||
"default": "",
|
||
"tooltip": "Comma-separated tags for easy searching later.",
|
||
}),
|
||
"rating": ("INT", {
|
||
"default": 0,
|
||
"min": 0,
|
||
"max": 5,
|
||
"step": 1,
|
||
"tooltip": "Quality rating 0-5 (0 = unrated).",
|
||
}),
|
||
"save_enabled": ("BOOLEAN", {
|
||
"default": True,
|
||
"tooltip": "Toggle saving on/off without disconnecting the node.",
|
||
}),
|
||
},
|
||
"optional": {
|
||
"negative_prompt": ("STRING", {
|
||
"multiline": True,
|
||
"default": "",
|
||
"forceInput": True,
|
||
"tooltip": "Optional negative prompt to store alongside.",
|
||
}),
|
||
"notes": ("STRING", {
|
||
"multiline": True,
|
||
"default": "",
|
||
"tooltip": "Free-form notes about this prompt.",
|
||
}),
|
||
"favorite": ("BOOLEAN", {
|
||
"default": False,
|
||
"tooltip": "Mark this prompt as a favorite.",
|
||
}),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING", "STRING", "INT", "STRING")
|
||
RETURN_NAMES = ("prompt_text", "negative_prompt", "prompt_id", "status")
|
||
FUNCTION = "save_prompt"
|
||
CATEGORY = "Eric Prompt Enhancers/Database"
|
||
OUTPUT_NODE = True
|
||
|
||
def save_prompt(
|
||
self,
|
||
prompt_text: str,
|
||
category: str,
|
||
platform: str,
|
||
tags: str,
|
||
rating: int,
|
||
save_enabled: bool,
|
||
negative_prompt: str = "",
|
||
notes: str = "",
|
||
favorite: bool = False,
|
||
):
|
||
if not save_enabled:
|
||
return (prompt_text, negative_prompt, 0, "Save disabled – passthrough only")
|
||
|
||
if not prompt_text.strip():
|
||
return (prompt_text, negative_prompt, 0, "Empty prompt – nothing saved")
|
||
|
||
try:
|
||
prompt_id = self.db.save_prompt(
|
||
prompt_text=prompt_text,
|
||
negative_prompt=negative_prompt,
|
||
category=category,
|
||
platform=platform,
|
||
tags=tags,
|
||
rating=rating,
|
||
source="manual",
|
||
notes=notes,
|
||
favorite=favorite,
|
||
)
|
||
stats = self.db.stats()
|
||
status = (
|
||
f"Saved as #{prompt_id} | "
|
||
f"DB total: {stats['total_prompts']} prompts"
|
||
)
|
||
logger.info(f"Prompt saved to database with id={prompt_id}")
|
||
return (prompt_text, negative_prompt, prompt_id, status)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error saving prompt: {e}")
|
||
return (prompt_text, negative_prompt, 0, f"Error: {e}")
|