Merge pull request #23 from ComfyAssets/text_input

Text input
This commit is contained in:
Vito
2025-06-03 17:08:34 -07:00
committed by GitHub
6 changed files with 135 additions and 24 deletions
+7 -7
View File
@@ -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
View File
@@ -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")
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
View File