* fix: release 3.2.3 — metadata parsing, null-safe saves, auto-hide, tag search - fix(#75): stop SaveImage patch from writing the last-run prompt into every PromptManager node (negative prompts were saved as copies of the positive) - fix(#75): replace four duplicated metadata parsers with a shared web/js/comfy-metadata.js that traces sampler links (KSampler, CFGGuider / SamplerCustomAdvanced, LoRA chains, ControlNet pass-through, any checkpoint loader) and recovers per-node text for images saved by older versions - fix(#143): treat null JSON body fields as empty in prompt API handlers - fix(#149): keep auto-hide timeout 0 (disabled) instead of falling back to the default; validate result_timeout on save - feat(#150): support tag:name filters in the node search box - docs(#142): scan dialog mentions A1111/Forge metadata support - docs(#144): fix ComfyUI-Lora-Manager links - ci: run JS tests with node --test * fix: bound metadata graph traversal to prevent tab freeze on crafted PNGs Link resolution tried every linked input recursively with only a depth cap, so a PNG whose prompt graph has cycles and fan-out could force ~inputs^25 paths and hang the gallery/metadata page. Each lookup now shares a visited set (each node expanded once) with a hard cap of 500 visits. * fix: read the matching output of multi-output settings nodes KSampler settings linked to a node like SamplerCombo all resolved to its first value, so steps, cfg and sampler showed the sampler name. Scalar resolution now prefers the linked node's input with the same field name. * fix: address review — trust API text, restore workflow-only parsing - Workflow widget text now only replaces API text for metadata showing the pre-3.2.3 corruption signature (2+ PromptManager nodes with identical API text but different widgets). Otherwise the API graph wins, so expanded dynamic prompts and linked text display what actually ran. - Images with only a UI workflow are converted to the API graph shape and traced the same way: prompts, KSamplerAdvanced, UNET/checkpoint loaders, KJNodes Set/Get, Reroute, bypassed and muted nodes.
231 lines
8.2 KiB
Python
231 lines
8.2 KiB
Python
"""
|
|
PromptManagerText: A text-only version of PromptManager that outputs STRING
|
|
without CLIP encoding, while maintaining all database and search features.
|
|
"""
|
|
|
|
import time
|
|
from typing import Tuple
|
|
|
|
try:
|
|
from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict
|
|
except ImportError:
|
|
# Fallback for older ComfyUI versions
|
|
class ComfyNodeABC:
|
|
pass
|
|
|
|
class IO:
|
|
STRING = "STRING"
|
|
CLIP = "CLIP"
|
|
CONDITIONING = "CONDITIONING"
|
|
|
|
InputTypeDict = dict
|
|
|
|
try:
|
|
from .prompt_manager_base import PromptManagerBase
|
|
except ImportError:
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from prompt_manager_base import PromptManagerBase
|
|
|
|
|
|
class PromptManagerText(PromptManagerBase, ComfyNodeABC):
|
|
"""
|
|
A ComfyUI custom node that provides all PromptManager features but outputs
|
|
only a STRING without CLIP encoding. Includes:
|
|
- Persistent storage of all prompts in SQLite database
|
|
- Search and retrieval capabilities
|
|
- Metadata management (categories, tags, ratings, notes)
|
|
- Duplicate detection via SHA256 hashing
|
|
- Text concatenation with prepend/append functionality
|
|
"""
|
|
|
|
def __init__(self):
|
|
super().__init__(logger_name="prompt_manager_text.node")
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls) -> InputTypeDict:
|
|
return {
|
|
"required": {
|
|
"text": (
|
|
IO.STRING,
|
|
{
|
|
"multiline": True,
|
|
"dynamicPrompts": True,
|
|
"tooltip": "The text prompt to be processed and saved to database.",
|
|
},
|
|
)
|
|
},
|
|
"optional": {
|
|
"category": (
|
|
IO.STRING,
|
|
{
|
|
"default": "",
|
|
"tooltip": "Optional category for organizing prompts (e.g., 'landscapes', 'portraits')",
|
|
},
|
|
),
|
|
"tags": (
|
|
IO.STRING,
|
|
{
|
|
"default": "",
|
|
"tooltip": "Comma-separated tags for the prompt (e.g., 'anime, detailed, sunset')",
|
|
},
|
|
),
|
|
"search_text": (
|
|
IO.STRING,
|
|
{
|
|
"default": "",
|
|
"tooltip": 'Search past prompts by text; add tag:name (or tag:"two words") to filter by tag',
|
|
},
|
|
),
|
|
"prepend_text": (
|
|
IO.STRING,
|
|
{
|
|
"default": "",
|
|
"tooltip": "Text to prepend to the main prompt (connected STRING nodes will be added before the main text)",
|
|
},
|
|
),
|
|
"append_text": (
|
|
IO.STRING,
|
|
{
|
|
"default": "",
|
|
"tooltip": "Text to append to the main prompt (connected STRING nodes will be added after the main text)",
|
|
},
|
|
),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (IO.STRING,)
|
|
OUTPUT_TOOLTIPS = (
|
|
"The final combined text string (with prepend/append applied) ready for use in other nodes.",
|
|
)
|
|
FUNCTION = "process_text"
|
|
OUTPUT_NODE = True
|
|
CATEGORY = "🫶 ComfyAssets/🧠 Prompts"
|
|
DESCRIPTION = (
|
|
"Processes and manages text prompts with database storage and search capabilities. "
|
|
"Outputs a plain STRING that can be used with any node that accepts text input. "
|
|
"Includes all PromptManager features: categorization, tagging, search, and prepend/append functionality."
|
|
)
|
|
|
|
def process_text(
|
|
self,
|
|
text: str,
|
|
category: str = "",
|
|
tags: str = "",
|
|
search_text: str = "",
|
|
prepend_text: str = "",
|
|
append_text: str = "",
|
|
) -> Tuple[str]:
|
|
"""
|
|
Process the text prompt and save it to the database.
|
|
|
|
Args:
|
|
text: The text prompt to process
|
|
category: Optional category for organization
|
|
tags: Comma-separated tags
|
|
search_text: Text to search for in past prompts
|
|
prepend_text: Text to prepend to the main prompt
|
|
append_text: Text to append to the main prompt
|
|
|
|
Returns:
|
|
Tuple containing the final processed text string
|
|
"""
|
|
# Combine prepend, main text, and append text
|
|
parts = []
|
|
if prepend_text and prepend_text.strip():
|
|
parts.append(prepend_text.strip())
|
|
if text:
|
|
parts.append(text)
|
|
if append_text and append_text.strip():
|
|
parts.append(append_text.strip())
|
|
final_text = " ".join(parts)
|
|
|
|
# Inject LoRA trigger words if integration is enabled
|
|
final_text = self._inject_lora_trigger_words(final_text)
|
|
|
|
# For database storage, save the original main text with metadata about prepend/append
|
|
storage_text = text
|
|
|
|
# Save prompt to database and set execution context for gallery tracking
|
|
prompt_id = None
|
|
extended_tags = []
|
|
if storage_text and storage_text.strip():
|
|
self.logger.debug(f"Processing prompt text: {storage_text[:100]}...")
|
|
|
|
# Add prepend/append info to tags if they exist
|
|
extended_tags = self._parse_tags(tags) or []
|
|
if prepend_text and prepend_text.strip():
|
|
extended_tags.append(f"prepend:{prepend_text.strip()[:50]}")
|
|
if append_text and append_text.strip():
|
|
extended_tags.append(f"append:{append_text.strip()[:50]}")
|
|
|
|
try:
|
|
prompt_id = self._save_prompt_to_database(
|
|
text=storage_text.strip(),
|
|
category=category.strip() if category else None,
|
|
tags=extended_tags if extended_tags else None,
|
|
)
|
|
|
|
# Set current prompt for image tracking
|
|
if prompt_id:
|
|
execution_id = self.prompt_tracker.set_current_prompt(
|
|
prompt_text=final_text.strip(),
|
|
additional_data={
|
|
"category": category.strip() if category else None,
|
|
"tags": extended_tags,
|
|
"prompt_id": prompt_id,
|
|
"prepend_text": (
|
|
prepend_text.strip() if prepend_text else None
|
|
),
|
|
"append_text": append_text.strip() if append_text else None,
|
|
},
|
|
)
|
|
self.logger.debug(
|
|
f"Set execution context: {execution_id} for prompt ID: {prompt_id}"
|
|
)
|
|
|
|
except Exception as e:
|
|
# Log error but don't fail the processing
|
|
self.logger.warning(f"Failed to save prompt to database: {e}")
|
|
|
|
# Register with ComfyUI integration for standard metadata compatibility
|
|
node_id = f"promptmanagertext_{int(time.time() * 1000)}"
|
|
self.comfyui_integration.register_prompt(
|
|
node_id,
|
|
final_text.strip(),
|
|
{
|
|
"category": category.strip() if category else None,
|
|
"tags": extended_tags,
|
|
"prompt_id": prompt_id,
|
|
"prepend_text": prepend_text.strip() if prepend_text else None,
|
|
"append_text": append_text.strip() if append_text else None,
|
|
},
|
|
)
|
|
|
|
self.logger.debug(f"Text processing completed: {final_text[:100]}...")
|
|
return (final_text,)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(
|
|
cls,
|
|
text="",
|
|
category="",
|
|
tags="",
|
|
search_text="",
|
|
prepend_text="",
|
|
append_text="",
|
|
**kwargs,
|
|
):
|
|
"""
|
|
ComfyUI method to determine if node needs re-execution.
|
|
|
|
Returns a hash of input values that affect the text output.
|
|
This enables proper branch execution - only re-execute when inputs change.
|
|
"""
|
|
import hashlib
|
|
|
|
combined = f"{text}|{prepend_text}|{append_text}"
|
|
return hashlib.sha256(combined.encode()).hexdigest()
|