Files
ComfyAssets-ComfyUI_PromptM…/prompt_manager_text.py
T
Vito bae542c9ac feat(history): persist exact execution prompt contexts (#213)
* feat(history): define exact bounded execution contexts

* fix(history): retain observable sources and validate evidence

* feat(history): migrate execution contexts to schema v2

* feat(history): persist immutable execution context snapshots

* fix(history): preserve package identity for execution contexts

* feat(history): bound context payloads beside lifecycle finalization

* fix(history): fence context commits and resolve stored run identity

* fix(history): bound persisted identifier bytes before restore reads

* feat(history): bind exact runtime context to execution attempts

* test(history): discover owner invalidation callback regression

* feat(history): capture cached prompt strings without mutating host caches

* test(history): verify durable contexts on real ComfyUI hosts

* fix(history): scope transforms to captured source paths

* test(ci): update execution context tooling fixtures (#209)

* fix(history): harden context validation and worker fixtures (#209)

* fix(history): honor close deadlines after early joins (#209)
2026-10-17 19:51:22 -07:00

263 lines
9.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 Any, Dict, Optional, Tuple
if __package__:
from .utils.execution_context_capture import record_runtime_context
else:
from utils.execution_context_capture import record_runtime_context
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)"
),
},
),
},
"hidden": {
# The queued graph and this node's id: used to tell the positive
# prompt of a run from negative-prompt PromptManager nodes
"prompt": "PROMPT",
"unique_id": "UNIQUE_ID",
},
}
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 = "",
prompt: Optional[Dict[str, Any]] = None,
unique_id: Optional[str] = None,
) -> 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._track_prompt_execution(
text=storage_text.strip(),
encoding_text=final_text.strip(),
category=category.strip() if category else None,
tags=extended_tags if extended_tags else None,
additional_data={
"category": category.strip() if category else None,
"tags": extended_tags,
"prepend_text": (
prepend_text.strip() if prepend_text else None
),
"append_text": append_text.strip() if append_text else None,
},
prompt_graph=prompt,
unique_id=unique_id,
)
self.logger.debug(f"Tracked 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]}...")
result = (final_text,)
record_runtime_context(
type(self).__name__, text, prepend_text, append_text, final_text
)
return result
@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 or the
saved prompt's metadata. This enables proper branch execution - only
re-execute when inputs change.
"""
import hashlib
combined = f"{text}|{prepend_text}|{append_text}|{category}|{tags}"
return hashlib.sha256(combined.encode()).hexdigest()