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:
+4
-3
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user