* 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)
263 lines
9.2 KiB
Python
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()
|