@@ -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
|
||||
|
||||
+45
-11
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
+1
-1
@@ -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"]
|
||||
|
||||
|
||||
+81
-4
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user