diff --git a/README.md b/README.md index ac55b13..22fd403 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,13 @@ ComfyUI Prompt Manager functions as a drop-in replacement for ComfyUI's standard - **Generation Parameters**: Recover steps, CFG scale, sampler, seed, and model information - **Multi-Node Support**: Handles PromptManager, CLIPTextEncode, and other node types - **Copy & Export**: One-click copying of prompts and workflow JSON download -- **Standalone Viewer**: Dedicated metadata analysis tool at `/prompt_manager/gallery.html` +- **Standalone Viewer**: Dedicated metadata analysis tool at `/prompt_manager/metadata.html` - **Drag & Drop Interface**: Easy analysis by dropping PNG files directly into the viewer - **Error Handling**: Robust parsing with NaN value cleaning and malformed JSON recovery ### 📋 Standalone Metadata Viewer -- **Independent Tool**: Access at `http://localhost:8188/prompt_manager/gallery.html` +- **Independent Tool**: Access at `http://localhost:8188/prompt_manager/metadata.html` - **Real-time Analysis**: Instant PNG metadata extraction and display - **Comprehensive Display**: Shows all discoverable ComfyUI workflow information - **Export Capabilities**: Download workflow JSON files for reuse @@ -131,7 +131,7 @@ ComfyUI Prompt Manager functions as a drop-in replacement for ComfyUI's standard 5. **Access the web interface**: - **Admin Dashboard**: `http://localhost:8188/prompt_manager/admin` - Full management interface - - **Metadata Viewer**: `http://localhost:8188/prompt_manager/gallery.html` - Standalone PNG analysis tool + - **Metadata Viewer**: `http://localhost:8188/prompt_manager/metadata.html` - Standalone PNG analysis tool - **Simple Interface**: `http://localhost:8188/prompt_manager/` - Basic prompt browser ### Prerequisites @@ -182,7 +182,7 @@ The image gallery automatically captures and links generated images: Use the standalone metadata viewer to analyze any ComfyUI-generated PNG: -1. **Open the metadata viewer** at `http://localhost:8188/prompt_manager/gallery.html` +1. **Open the metadata viewer** at `http://localhost:8188/prompt_manager/metadata.html` 2. **Drag and drop** any PNG file with ComfyUI workflow data 3. **View extracted information**: - **Prompts**: Copy positive/negative prompts with one click @@ -334,7 +334,7 @@ CREATE TABLE generated_images ( - **`web/admin.html`** - Advanced admin dashboard with metadata panel - **`web/index.html`** - Simple web interface - **`web/prompt_manager.js`** - JavaScript functionality -- **`gallery.html`** - Standalone PNG metadata viewer +- **`web/metadata.html`** - Standalone PNG metadata viewer ### File Structure @@ -362,8 +362,8 @@ ComfyUI_PromptManager/ ├── web/ │ ├── admin.html # Advanced admin dashboard │ ├── index.html # Simple web interface +│ ├── metadata.html # Standalone metadata viewer │ └── prompt_manager.js # JavaScript functionality -├── gallery.html # Standalone metadata viewer ├── tests/ │ ├── __init__.py │ └── test_basic.py # Test suite @@ -554,7 +554,7 @@ MIT License - see LICENSE file for details. ### v3.0.0 (Metadata Analysis Release) - **🔬 PNG Metadata Analysis**: Advanced ComfyUI workflow extraction from PNG images -- **📋 Standalone Metadata Viewer**: Dedicated tool at `/gallery.html` for analyzing any ComfyUI image +- **📋 Standalone Metadata Viewer**: Dedicated tool at `/web/metadata.html` for analyzing any ComfyUI image - **🖼️ Integrated Metadata Panel**: Real-time workflow analysis in the image viewer - **📸 Bulk Image Scanning**: Mass import and analysis of existing ComfyUI output directories - **🛠️ System Management Suite**: Complete backup, restore, maintenance, and diagnostic tools diff --git a/prompt_manager.py b/prompt_manager.py index 6fb19a1..f54848c 100644 --- a/prompt_manager.py +++ b/prompt_manager.py @@ -92,6 +92,12 @@ class PromptManager(ComfyNodeABC): "search_text": (IO.STRING, { "default": "", "tooltip": "Search for past prompts containing this text" + }), + "prepend_text": (IO.STRING, { + "tooltip": "Text to prepend to the main prompt (connected STRING nodes will be added before the main text)" + }), + "append_text": (IO.STRING, { + "tooltip": "Text to append to the main prompt (connected STRING nodes will be added after the main text)" }) } } @@ -114,7 +120,9 @@ class PromptManager(ComfyNodeABC): text: str, category: str = "", tags: str = "", - search_text: str = "" + search_text: str = "", + prepend_text: str = "", + append_text: str = "" ) -> Tuple[Any]: """ Encode the text prompt and save it to the database. @@ -125,6 +133,8 @@ class PromptManager(ComfyNodeABC): 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 conditioning for the diffusion model @@ -132,6 +142,20 @@ class PromptManager(ComfyNodeABC): Raises: RuntimeError: If clip input is invalid """ + # Combine prepend, main text, and append text + final_text = "" + if prepend_text and prepend_text.strip(): + final_text += prepend_text.strip() + " " + final_text += text + if append_text and append_text.strip(): + final_text += " " + append_text.strip() + + # Use the combined text for encoding + encoding_text = final_text + + # For database storage, save the original main text with metadata about prepend/append + storage_text = text + # Search functionality is now handled by the JavaScript UI # The search parameters are still available for backend processing if needed @@ -147,23 +171,33 @@ class PromptManager(ComfyNodeABC): # Save prompt to database and set execution context for gallery tracking prompt_id = None - if text and text.strip(): - self.logger.debug(f"Processing prompt text: {text[:100]}...") + 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=text.strip(), # Always strip whitespace + text=storage_text.strip(), # Always strip whitespace category=category.strip() if category else None, - tags=self._parse_tags(tags) + 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=text.strip(), + prompt_text=encoding_text.strip(), # Use final combined text for tracking additional_data={ 'category': category.strip() if category else None, - 'tags': self._parse_tags(tags), - 'prompt_id': prompt_id + '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.info(f"Set execution context: {execution_id} for prompt ID: {prompt_id}") @@ -173,9 +207,9 @@ class PromptManager(ComfyNodeABC): self.logger.warning(f"Failed to save prompt to database: {e}") # Already logged above, no need for additional print - # Perform standard CLIP text encoding - self.logger.debug("Performing CLIP text encoding") - tokens = clip.tokenize(text) + # Perform standard CLIP text encoding using the combined text + self.logger.debug(f"Performing CLIP text encoding on combined text: {encoding_text[:100]}...") + tokens = clip.tokenize(encoding_text) conditioning = clip.encode_from_tokens_scheduled(tokens) self.logger.debug("CLIP encoding completed successfully") diff --git a/py/api.py b/py/api.py index 63d6204..4b96101 100644 --- a/py/api.py +++ b/py/api.py @@ -131,7 +131,7 @@ class PromptManagerAPI: current_dir = os.path.dirname( os.path.dirname(os.path.abspath(__file__)) ) - html_path = os.path.join(current_dir, "gallery.html") + html_path = os.path.join(current_dir, "web", "metadata.html") if os.path.exists(html_path): with open(html_path, "r", encoding="utf-8") as f: diff --git a/pyproject.toml b/pyproject.toml index 843fd5b..5770aea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "promptmanager" description = "A powerful ComfyUI custom node that extends the standard text encoder with persistent prompt storage, advanced search capabilities, and an automatic image gallery system using SQLite." -version = "3.0.0" +version = "3.0.1" license = {file = "LICENSE"} dependencies = ["# Core dependencies for PromptManager", "# Note: Most dependencies are already included with ComfyUI", "# Already included with Python standard library:", "# - sqlite3", "# - hashlib", "# - json", "# - datetime", "# - os", "# - typing", "# - threading", "# - uuid", "# Required for gallery functionality:", "watchdog>=2.1.0 # For file system monitoring", "Pillow>=8.0.0 # For image metadata extraction (usually included with ComfyUI)", "# Optional dependencies for enhanced search functionality:", "# fuzzywuzzy[speedup]>=0.18.0 # For fuzzy string matching (optional)", "# sqlalchemy>=1.4.0 # For advanced ORM features (optional)", "# Development dependencies (optional):", "# pytest>=6.0.0 # For running tests", "# black>=22.0.0 # For code formatting", "# flake8>=4.0.0 # For linting", "# mypy>=0.910 # For type checking"] diff --git a/web/admin.html b/web/admin.html index 7c22d44..69c76b8 100644 --- a/web/admin.html +++ b/web/admin.html @@ -1256,7 +1256,13 @@ showNotification(message, type = "info") { const notification = document.createElement("div"); - notification.className = `fixed top-4 right-4 px-6 py-4 rounded-lg shadow-lg z-50 transition-all duration-300 transform translate-x-full`; + + // Check if image viewer is open and use higher z-index + const imageViewerModal = document.getElementById('imageViewerModal'); + const isImageViewerOpen = imageViewerModal && !imageViewerModal.classList.contains('hidden'); + const zIndex = isImageViewerOpen ? 'z-[10000]' : 'z-50'; + + notification.className = `fixed top-4 right-4 px-6 py-4 rounded-lg shadow-lg ${zIndex} transition-all duration-300 transform translate-x-full`; const colors = { success: "bg-green-600 text-white", @@ -1273,7 +1279,11 @@ setTimeout(() => notification.classList.remove("translate-x-full"), 100); setTimeout(() => { notification.classList.add("translate-x-full"); - setTimeout(() => document.body.removeChild(notification), 300); + setTimeout(() => { + if (notification.parentNode) { + document.body.removeChild(notification); + } + }, 300); }, 3000); } @@ -2843,7 +2853,9 @@ this.updateMetadataPanel(comfyData, imageSrc); } catch (error) { console.error('Error extracting metadata:', error); - this.showMetadataError(); + + // Try fallback: get prompt from database using current image + await this.tryFallbackMetadata(imageSrc); } } @@ -3129,12 +3141,77 @@ } async copyPrompt(type) { - if (!this.currentMetadata) return; + if (!this.currentMetadata) { + this.showNotification('❌ No metadata available for copying', 'error'); + return; + } const text = type === 'positive' ? this.currentMetadata.positivePrompt : this.currentMetadata.negativePrompt; + + if (!text || text === 'No prompt found' || text === 'No negative prompt found') { + this.showNotification(`❌ No ${type} prompt available`, 'error'); + return; + } + await this.copyToClipboard(text); } + async tryFallbackMetadata(imageSrc) { + console.log('Trying fallback metadata extraction for:', imageSrc); + + try { + // Try to get prompt from current gallery image data + let fallbackPrompt = 'No prompt found'; + + if (this.currentGalleryImages && this.currentImageIndex !== null && this.currentGalleryImages[this.currentImageIndex]) { + const currentImage = this.currentGalleryImages[this.currentImageIndex]; + console.log('Current image data:', currentImage); + + // If we have prompt_id, try to get the prompt from our local prompts array + if (currentImage.prompt_id && this.prompts) { + const prompt = this.prompts.find(p => p.id === currentImage.prompt_id); + if (prompt) { + fallbackPrompt = prompt.text; + console.log('Found prompt from database:', fallbackPrompt.substring(0, 100)); + } + } + } + + // Set fallback metadata + this.currentMetadata = { + positivePrompt: fallbackPrompt, + negativePrompt: 'No negative prompt found', + checkpoint: 'Unknown', + steps: 'Unknown', + cfgScale: 'Unknown', + sampler: 'Unknown', + seed: 'Unknown', + workflow: null, + prompt: null + }; + + // Update metadata panel with fallback data + this.updateMetadataPanel({}, imageSrc); + + } catch (error) { + console.error('Fallback metadata extraction failed:', error); + this.showMetadataError(); + + // Set empty metadata as last resort + this.currentMetadata = { + positivePrompt: 'No prompt found', + negativePrompt: 'No negative prompt found', + checkpoint: 'Unknown', + steps: 'Unknown', + cfgScale: 'Unknown', + sampler: 'Unknown', + seed: 'Unknown', + workflow: null, + prompt: null + }; + } + } + async copyAllMetadata() { if (!this.currentMetadata) return; diff --git a/gallery.html b/web/metadata.html similarity index 100% rename from gallery.html rename to web/metadata.html