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

183 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Browse / Random Prompt from Database Node
ComfyUI node that retrieves prompts from the database for reuse –
either by browsing (with ID) or by pulling a filtered random prompt.
Author: Eric Hiss (GitHub: EricRollei)
"""
import logging
from .prompt_database import PromptDatabase, PLATFORMS, CATEGORIES
logger = logging.getLogger(__name__)
class BrowsePromptDatabase:
"""
Browse or randomly select a prompt from the database.
Two modes:
- **Browse by ID**: Set prompt_id > 0 to fetch a specific saved prompt.
- **Random / Filtered**: Set prompt_id = 0 and use the filters + seed
to get a random prompt matching your criteria.
The output can be wired directly into a sampler's positive prompt input.
"""
def __init__(self):
self.db = PromptDatabase()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"mode": (["random", "browse_by_id", "newest", "highest_rated"], {
"default": "random",
"tooltip": (
"random = pick a random prompt matching filters. "
"browse_by_id = fetch a specific prompt by its ID. "
"newest = most recently saved. "
"highest_rated = best rated prompt matching filters."
),
}),
"prompt_id": ("INT", {
"default": 0,
"min": 0,
"max": 9999999,
"step": 1,
"tooltip": "Used in browse_by_id mode. Set to 0 for other modes.",
}),
"category": (CATEGORIES, {
"default": "any",
"tooltip": "Filter by category (ignored in browse_by_id mode).",
}),
"platform": (PLATFORMS, {
"default": "any",
"tooltip": "Filter by platform (ignored in browse_by_id mode).",
}),
"min_rating": ("INT", {
"default": 0,
"min": 0,
"max": 5,
"step": 1,
"tooltip": "Only return prompts rated at least this value.",
}),
"favorites_only": ("BOOLEAN", {
"default": False,
"tooltip": "Only pick from prompts marked as favorites.",
}),
"tags_filter": ("STRING", {
"default": "",
"tooltip": "Comma-separated tags – all must be present.",
}),
"seed": ("INT", {
"default": 0,
"min": 0,
"max": 0xFFFFFFFF,
"step": 1,
"tooltip": (
"Random seed. In random mode, changing the seed "
"gives a different prompt from the matching set. "
"Set to 0 for true random."
),
}),
},
}
RETURN_TYPES = ("STRING", "STRING", "INT", "STRING", "STRING")
RETURN_NAMES = ("prompt_text", "negative_prompt", "prompt_id", "prompt_info", "db_stats")
FUNCTION = "browse_prompt"
CATEGORY = "Eric Prompt Enhancers/Database"
OUTPUT_NODE = True
def browse_prompt(
self,
mode: str,
prompt_id: int,
category: str,
platform: str,
min_rating: int,
favorites_only: bool,
tags_filter: str,
seed: int,
):
try:
# ----------------------------------------------------------
# Browse by ID
# ----------------------------------------------------------
if mode == "browse_by_id":
if prompt_id <= 0:
return ("", "", 0, "Set prompt_id > 0 for browse_by_id mode.", self._stats_text())
row = self.db.get_prompt(prompt_id)
if row is None:
return ("", "", 0, f"No prompt found with ID #{prompt_id}.", self._stats_text())
return self._format_result(row)
# ----------------------------------------------------------
# Filtered search (random / newest / highest_rated)
# ----------------------------------------------------------
order_map = {
"random": "random",
"newest": "newest",
"highest_rated": "rating",
}
order = order_map.get(mode, "random")
results, total = self.db.search(
category=category,
platform=platform,
min_rating=min_rating,
favorites_only=favorites_only,
tags_filter=tags_filter,
order_by=order,
limit=1,
offset=0,
)
if not results:
return ("", "", 0, "No prompts match the current filters.", self._stats_text())
return self._format_result(results[0], total=total)
except Exception as e:
logger.error(f"Browse error: {e}")
return ("", "", 0, f"Error: {e}", "")
# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------
def _format_result(self, row: dict, total: int = 1) -> tuple:
info = (
f"ID #{row['id']} | "
f"Category: {row['category']} | "
f"Platform: {row['platform']} | "
f"Rating: {'★' * row['rating']}{'☆' * (5 - row['rating'])} | "
f"Tags: {row['tags'] or '(none)'} | "
f"Favorite: {'Yes' if row.get('favorite') else 'No'} | "
f"Saved: {row['created_at']}"
)
if total > 1:
info += f" | Matches: {total}"
return (
row["prompt_text"],
row.get("negative_prompt", ""),
row["id"],
info,
self._stats_text(),
)
def _stats_text(self) -> str:
try:
s = self.db.stats()
parts = [f"Total: {s['total_prompts']} prompts, {s['favorites']} favorites"]
if s["by_category"]:
cats = ", ".join(f"{k}:{v}" for k, v in list(s["by_category"].items())[:6])
parts.append(f"Categories: {cats}")
if s["by_platform"]:
plats = ", ".join(f"{k}:{v}" for k, v in list(s["by_platform"].items())[:6])
parts.append(f"Platforms: {plats}")
return " | ".join(parts)
except Exception:
return ""