refactor: one prompt hash implementation

utils.hashing.generate_prompt_hash is now the only prompt hash:
PromptManagerBase._generate_hash delegates to it and the inline
hashlib.sha256 copies in the prompt tracker and image monitor are gone.

Remove PromptManagerBase._open_web_interface (it targeted a web_interface
folder that never existed) and the unused restart_gallery.py script.

IS_CHANGED on PromptManager and PromptManagerText now includes category and
tags, so editing either re-runs the node and updates the saved metadata.
This commit is contained in:
Vito Sansevero
2026-10-08 18:01:34 -07:00
parent 59ad23201b
commit 7d65ee5516
8 changed files with 81 additions and 122 deletions
+4 -3
View File
@@ -251,10 +251,11 @@ class PromptManager(PromptManagerBase, ComfyNodeABC):
"""
ComfyUI method to determine if node needs re-execution.
Returns a hash of input values that affect the conditioning output.
This enables proper branch execution - only re-execute when inputs change.
Returns a hash of input values that affect the conditioning 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}"
combined = f"{text}|{prepend_text}|{append_text}|{category}|{tags}"
return hashlib.sha256(combined.encode()).hexdigest()
+2 -38
View File
@@ -6,9 +6,7 @@ search, gallery system management, and cleanup — extracted from the
duplicate code in prompt_manager.py and prompt_manager_text.py.
"""
import hashlib
import os
import webbrowser
from typing import Any, Dict, List, Optional
try:
@@ -225,16 +223,8 @@ class PromptManagerBase:
return modified
def _generate_hash(self, text: str) -> str:
"""Generate SHA256 hash for the prompt text.
Args:
text: The prompt text to hash
Returns:
Hexadecimal string representation of the SHA256 hash
"""
normalized_text = text.strip().lower()
return hashlib.sha256(normalized_text.encode("utf-8")).hexdigest()
"""The prompt's deduplication hash (see utils.hashing.generate_prompt_hash)."""
return generate_prompt_hash(text)
def _parse_tags(self, tags_string: str) -> Optional[list]:
"""Parse comma-separated tags string into a list.
@@ -285,32 +275,6 @@ class PromptManagerBase:
self.logger.error(f"Error searching prompts: {e}")
return []
def _open_web_interface(self):
"""Open the web interface in the default browser."""
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
web_dir = os.path.join(current_dir, "web_interface")
if os.path.exists(web_dir):
index_path = os.path.join(web_dir, "index.html")
if os.path.exists(index_path):
webbrowser.open(f"file://{index_path}")
self.logger.info("Web interface opened in browser")
else:
self.logger.warning(
f"Web interface directory found but no index.html. "
f"Please check {web_dir} for setup instructions"
)
else:
self.logger.info(
"Web interface not yet implemented. This feature will open a "
"web-based prompt management interface when the web_interface "
"directory is created."
)
except Exception as e:
self.logger.error(f"Error opening web interface: {e}")
def search_prompts_api(self, search_text: str = "") -> List[Dict[str, Any]]:
"""API method for JavaScript UI to search prompts."""
return self._search_prompts(search_text=search_text)
+4 -3
View File
@@ -223,10 +223,11 @@ class PromptManagerText(PromptManagerBase, ComfyNodeABC):
"""
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.
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}"
combined = f"{text}|{prepend_text}|{append_text}|{category}|{tags}"
return hashlib.sha256(combined.encode()).hexdigest()
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env python3
"""
ComfyUI_PromptManager Gallery System Restart Script.
This script reinitializes and restarts the automatic image gallery monitoring system
for ComfyUI_PromptManager. Use this script after code modifications or when the
gallery system needs to be reloaded.
The script performs the following operations:
1. Initializes the database connection
2. Creates a PromptTracker instance for linking prompts to images
3. Sets up the ImageMonitor for real-time file system monitoring
4. Starts monitoring ComfyUI output directories
5. Reports system status and readiness
Usage:
python restart_gallery.py
This is particularly useful during development when you need to restart the
gallery monitoring system without restarting the entire ComfyUI application.
"""
import sys
import os
# Add current directory to Python path
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, current_dir)
try:
# Import and initialize the gallery system
from database.operations import PromptDatabase
from utils.prompt_tracker import get_prompt_tracker
from utils.image_monitor import get_image_monitor
print("[RESTART] Restarting PromptManager Gallery System...")
# Initialize components using singleton getters
db = PromptDatabase()
tracker = get_prompt_tracker(db)
monitor = get_image_monitor(db, tracker)
print("[SUCCESS] Components initialized successfully")
# Test image monitoring directories
status = monitor.get_status()
print(f"[INFO] Monitoring status: {status}")
# Start monitoring
monitor.start_monitoring()
print("[SUCCESS] Image monitoring restarted")
print("\n[READY] Gallery system restart completed!")
print("Generate some images now to test the automatic linking.")
except ImportError as e:
print(f"[ERROR] Import error: {e}")
print("Make sure you're running this from the PromptManager directory")
except Exception as e:
print(f"[ERROR] Error: {e}")
import traceback
traceback.print_exc()
+44
View File
@@ -18,6 +18,50 @@ from utils.hashing import (
is_duplicate_prompt,
)
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class TestSingleHashImplementation(unittest.TestCase):
"""generate_prompt_hash is the only prompt hash; nothing re-implements it."""
def test_node_base_delegates_to_generate_prompt_hash(self):
from prompt_manager_base import PromptManagerBase
node = PromptManagerBase.__new__(PromptManagerBase)
self.assertEqual(node._generate_hash(" A "), generate_prompt_hash(" A "))
self.assertEqual(node._generate_hash(" A "), generate_prompt_hash("a"))
def test_tracker_and_monitor_use_generate_prompt_hash(self):
for relative in (
os.path.join("utils", "prompt_tracker.py"),
os.path.join("utils", "image_monitor.py"),
"prompt_manager_base.py",
):
with open(os.path.join(ROOT, relative), encoding="utf-8") as f:
source = f.read()
self.assertFalse("hashlib.sha256" in source, f"inline hash in {relative}")
self.assertTrue("generate_prompt_hash" in source, relative)
def test_tracker_finds_an_existing_prompt_by_the_shared_hash(self):
import unittest.mock as mock
from utils.prompt_tracker import PromptTracker
db = mock.Mock()
db.get_prompt_by_hash.return_value = {"id": 42}
tracker = PromptTracker(db)
tracker.set_current_prompt(" Hello World ")
db.get_prompt_by_hash.assert_called_once_with(
generate_prompt_hash(" Hello World ")
)
self.assertEqual(tracker.get_current_prompt()["id"], 42)
def test_node_base_has_no_browser_opener(self):
from prompt_manager_base import PromptManagerBase
self.assertFalse(hasattr(PromptManagerBase, "_open_web_interface"))
self.assertFalse(os.path.exists(os.path.join(ROOT, "restart_gallery.py")))
class TestGenerateContentHash(unittest.TestCase):
"""Test generate_content_hash function."""
+19
View File
@@ -70,6 +70,16 @@ class TestIsChangedPromptManager(unittest.TestCase):
result = PromptManager.IS_CHANGED(clip=None, text="hello")
self.assertIsInstance(result, str)
def test_different_tags_return_different_hash(self):
result1 = PromptManager.IS_CHANGED(clip=None, text="hello", tags="a, b")
result2 = PromptManager.IS_CHANGED(clip=None, text="hello", tags="a, c")
self.assertNotEqual(result1, result2)
def test_different_category_returns_different_hash(self):
result1 = PromptManager.IS_CHANGED(clip=None, text="hello", category="art")
result2 = PromptManager.IS_CHANGED(clip=None, text="hello", category="photo")
self.assertNotEqual(result1, result2)
class TestIsChangedPromptManagerText(unittest.TestCase):
"""IS_CHANGED for PromptManagerText should behave identically to
@@ -99,6 +109,15 @@ class TestIsChangedPromptManagerText(unittest.TestCase):
result2 = PromptManagerText.IS_CHANGED(text="hello", append_text="b")
self.assertNotEqual(result1, result2)
def test_different_tags_or_category_return_different_hash(self):
base = PromptManagerText.IS_CHANGED(text="hello", category="x", tags="a")
self.assertNotEqual(
base, PromptManagerText.IS_CHANGED(text="hello", category="x", tags="b")
)
self.assertNotEqual(
base, PromptManagerText.IS_CHANGED(text="hello", category="y", tags="a")
)
def test_returns_string(self):
result = PromptManagerText.IS_CHANGED(text="hello")
self.assertIsInstance(result, str)
+4 -5
View File
@@ -31,6 +31,7 @@ from typing import Optional, Dict, Any, Tuple
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from .hashing import generate_prompt_hash
from .metadata_extractor import ComfyUIMetadataExtractor
from .logging_config import get_logger
from .prompt_graph import resolve_text, run_prompt_nodes
@@ -399,11 +400,9 @@ class ImageGenerationHandler(FileSystemEventHandler):
# Look up by hash in database
try:
import hashlib
normalized = prompt_text.strip().lower()
prompt_hash = hashlib.sha256(normalized.encode("utf-8")).hexdigest()
existing = self.db_manager.get_prompt_by_hash(prompt_hash)
existing = self.db_manager.get_prompt_by_hash(
generate_prompt_hash(prompt_text)
)
if existing:
self.logger.debug(
f"Found DB prompt {existing['id']} from metadata text"
+4 -9
View File
@@ -28,12 +28,11 @@ Or using the context manager:
import threading
import time
import uuid
import hashlib
from typing import Optional, Dict, Any
from datetime import datetime, timezone
# Import logging system
try:
from .hashing import generate_prompt_hash
from .logging_config import get_logger
except ImportError:
import sys
@@ -41,6 +40,7 @@ except ImportError:
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, current_dir)
from utils.hashing import generate_prompt_hash
from utils.logging_config import get_logger
@@ -132,13 +132,8 @@ class PromptTracker:
if not prompt_id:
# Fallback: try to find existing prompt using consistent hash calculation
try:
# Use consistent hash calculation (same as PromptManager)
import hashlib
normalized_text = prompt_text.strip().lower()
prompt_hash = hashlib.sha256(
normalized_text.encode("utf-8")
).hexdigest()
# Same hash as the nodes, so the lookup finds their saved prompt
prompt_hash = generate_prompt_hash(prompt_text)
existing_prompt = self.db_manager.get_prompt_by_hash(prompt_hash)
if existing_prompt: