+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.19"
|
||||
version = "3.0.20"
|
||||
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"]
|
||||
|
||||
|
||||
+11
-21
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Basic tests for KikoTextEncode functionality.
|
||||
Basic tests for PromptManager functionality.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -17,7 +17,7 @@ from utils.validators import validate_prompt_text, validate_rating, validate_tag
|
||||
|
||||
|
||||
class TestBasicFunctionality(unittest.TestCase):
|
||||
"""Test basic functionality of KikoTextEncode components."""
|
||||
"""Test basic functionality of PromptManager components."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
@@ -118,7 +118,6 @@ class TestBasicFunctionality(unittest.TestCase):
|
||||
tags=["test", "database"],
|
||||
rating=4,
|
||||
notes="Test notes",
|
||||
workflow_name="test_workflow",
|
||||
prompt_hash=generate_prompt_hash("Test prompt for database")
|
||||
)
|
||||
|
||||
@@ -133,7 +132,6 @@ class TestBasicFunctionality(unittest.TestCase):
|
||||
self.assertEqual(retrieved['tags'], ["test", "database"])
|
||||
self.assertEqual(retrieved['rating'], 4)
|
||||
self.assertEqual(retrieved['notes'], "Test notes")
|
||||
self.assertEqual(retrieved['workflow_name'], "test_workflow")
|
||||
|
||||
def test_duplicate_detection(self):
|
||||
"""Test duplicate prompt detection."""
|
||||
@@ -220,7 +218,7 @@ class TestNodeIntegration(unittest.TestCase):
|
||||
if os.path.exists(self.temp_db.name):
|
||||
os.unlink(self.temp_db.name)
|
||||
|
||||
@patch('kiko_text_encode.PromptDatabase')
|
||||
@patch('prompt_manager.PromptDatabase')
|
||||
def test_node_encode_function(self, mock_db_class):
|
||||
"""Test the node's encode function with mocked dependencies."""
|
||||
# Mock the database
|
||||
@@ -233,44 +231,36 @@ class TestNodeIntegration(unittest.TestCase):
|
||||
mock_clip.encode_from_tokens_scheduled.return_value = "mock_conditioning"
|
||||
|
||||
# Import and test the node
|
||||
from kiko_text_encode import KikoTextEncode
|
||||
from prompt_manager import PromptManager
|
||||
|
||||
node = KikoTextEncode()
|
||||
node = PromptManager()
|
||||
|
||||
# Test encoding
|
||||
result = node.encode(
|
||||
clip=mock_clip,
|
||||
text="Test prompt",
|
||||
category="test",
|
||||
tags="tag1, tag2",
|
||||
rating=4,
|
||||
notes="Test notes",
|
||||
workflow_name="test_workflow"
|
||||
search_text=""
|
||||
)
|
||||
|
||||
# Verify CLIP was called correctly
|
||||
mock_clip.tokenize.assert_called_once_with("Test prompt")
|
||||
mock_clip.encode_from_tokens_scheduled.assert_called_once_with("mock_tokens")
|
||||
|
||||
# Verify result
|
||||
self.assertEqual(result, ("mock_conditioning",))
|
||||
# Verify result - PromptManager returns both conditioning and prompt text
|
||||
self.assertEqual(result, ("mock_conditioning", "Test prompt"))
|
||||
|
||||
def test_node_input_types(self):
|
||||
"""Test the node's input type definitions."""
|
||||
from kiko_text_encode import KikoTextEncode
|
||||
from prompt_manager import PromptManager
|
||||
|
||||
input_types = KikoTextEncode.INPUT_TYPES()
|
||||
input_types = PromptManager.INPUT_TYPES()
|
||||
|
||||
# Check required inputs
|
||||
self.assertIn("text", input_types["required"])
|
||||
self.assertIn("clip", input_types["required"])
|
||||
|
||||
# Check optional inputs
|
||||
self.assertIn("category", input_types["optional"])
|
||||
self.assertIn("tags", input_types["optional"])
|
||||
self.assertIn("rating", input_types["optional"])
|
||||
self.assertIn("notes", input_types["optional"])
|
||||
self.assertIn("workflow_name", input_types["optional"])
|
||||
self.assertIn("search_text", input_types["optional"])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+20
-16
@@ -2969,23 +2969,25 @@
|
||||
// Prompts - need to identify which is positive vs negative
|
||||
if (node.class_type === 'PromptManager' && node.inputs && node.inputs.text) {
|
||||
// PromptManager typically contains the positive prompt
|
||||
positivePrompt = node.inputs.text;
|
||||
console.log('Found PromptManager with text:', positivePrompt.substring(0, 100));
|
||||
const promptValue = typeof node.inputs.text === 'string' ? node.inputs.text : (Array.isArray(node.inputs.text) ? node.inputs.text[0] : String(node.inputs.text));
|
||||
positivePrompt = promptValue;
|
||||
console.log('Found PromptManager with text:', promptValue.substring(0, 100));
|
||||
}
|
||||
|
||||
if (node.class_type === 'CLIPTextEncode' && node.inputs && node.inputs.text) {
|
||||
console.log('Found CLIPTextEncode:', node.inputs.text.substring(0, 50));
|
||||
// Check if this looks like a negative prompt
|
||||
const text = node.inputs.text.toLowerCase();
|
||||
const textValue = typeof node.inputs.text === 'string' ? node.inputs.text : (Array.isArray(node.inputs.text) ? node.inputs.text[0] : String(node.inputs.text));
|
||||
console.log('Found CLIPTextEncode:', textValue.substring(0, 50));
|
||||
const text = textValue.toLowerCase();
|
||||
if (text.includes('bad anatomy') || text.includes('unfinished') ||
|
||||
text.includes('censored') || text.includes('weird anatomy') ||
|
||||
text.includes('negative') || text.includes('embedding:')) {
|
||||
negativePrompt = node.inputs.text;
|
||||
console.log('Found negative prompt:', negativePrompt.substring(0, 100));
|
||||
negativePrompt = textValue;
|
||||
console.log('Found negative prompt:', textValue.substring(0, 100));
|
||||
} else if (positivePrompt === 'No prompt found') {
|
||||
// If we haven't found a positive prompt yet, this might be it
|
||||
positivePrompt = node.inputs.text;
|
||||
console.log('Found potential positive prompt:', positivePrompt.substring(0, 100));
|
||||
positivePrompt = textValue;
|
||||
console.log('Found potential positive prompt:', textValue.substring(0, 100));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3385,19 +3387,21 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
// Prompts - need to identify which is positive vs negative
|
||||
if (node.class_type === 'PromptManager' && node.inputs && node.inputs.text) {
|
||||
// PromptManager typically contains the positive prompt
|
||||
positivePrompt = node.inputs.text;
|
||||
const promptValue = typeof node.inputs.text === 'string' ? node.inputs.text : (Array.isArray(node.inputs.text) ? node.inputs.text[0] : String(node.inputs.text));
|
||||
positivePrompt = promptValue;
|
||||
}
|
||||
|
||||
if (node.class_type === 'CLIPTextEncode' && node.inputs && node.inputs.text) {
|
||||
// Check if this looks like a negative prompt
|
||||
const text = node.inputs.text.toLowerCase();
|
||||
const textValue = typeof node.inputs.text === 'string' ? node.inputs.text : (Array.isArray(node.inputs.text) ? node.inputs.text[0] : String(node.inputs.text));
|
||||
const text = textValue.toLowerCase();
|
||||
if (text.includes('bad anatomy') || text.includes('unfinished') ||
|
||||
text.includes('censored') || text.includes('weird anatomy') ||
|
||||
text.includes('negative') || text.includes('embedding:')) {
|
||||
negativePrompt = node.inputs.text;
|
||||
negativePrompt = textValue;
|
||||
} else if (positivePrompt === 'No prompt found') {
|
||||
// If we haven't found a positive prompt yet, this might be it
|
||||
positivePrompt = node.inputs.text;
|
||||
positivePrompt = textValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3928,12 +3932,12 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
// Close any open modals
|
||||
if (!document.getElementById('galleryModal').classList.contains('hidden')) {
|
||||
if (document.getElementById('galleryModal') && !document.getElementById('galleryModal').classList.contains('hidden')) {
|
||||
admin.closeGallery();
|
||||
} else if (!document.getElementById('imageViewerModal').classList.contains('hidden')) {
|
||||
} else if (document.getElementById('imageViewerModal') && !document.getElementById('imageViewerModal').classList.contains('hidden')) {
|
||||
admin.closeImageViewer();
|
||||
}
|
||||
} else if (!document.getElementById('imageViewerModal').classList.contains('hidden')) {
|
||||
} else if (document.getElementById('imageViewerModal') && !document.getElementById('imageViewerModal').classList.contains('hidden')) {
|
||||
// Handle arrow keys in image viewer
|
||||
if (e.key === 'ArrowLeft') {
|
||||
e.preventDefault();
|
||||
@@ -3948,7 +3952,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
// Window resize listener for responsive image sizing
|
||||
window.addEventListener('resize', function() {
|
||||
// Only apply resize adjustments if image viewer is open and in fit mode
|
||||
if (!document.getElementById('imageViewerModal').classList.contains('hidden') && admin.imageViewMode === 'fit') {
|
||||
if (document.getElementById('imageViewerModal') && !document.getElementById('imageViewerModal').classList.contains('hidden') && admin.imageViewMode === 'fit') {
|
||||
admin.applyImageSizing();
|
||||
}
|
||||
});
|
||||
|
||||
+71
-11
@@ -35,25 +35,57 @@ app.registerExtension({
|
||||
// Load settings from API
|
||||
this.loadSettings();
|
||||
|
||||
// Create DOM widget container
|
||||
// Create DOM widget container with unique ID for scoped styling
|
||||
const container = document.createElement("div");
|
||||
container.style.padding = "8px";
|
||||
container.style.backgroundColor = "#2a2a2a";
|
||||
container.style.borderRadius = "4px";
|
||||
container.style.marginTop = "5px";
|
||||
const uniqueId = `pm-${Math.random().toString(36).substring(2, 9)}`;
|
||||
container.id = uniqueId;
|
||||
|
||||
// Add inline styles for perfect sizing like KikoLocalImageLoader
|
||||
const styleSheet = document.createElement("style");
|
||||
styleSheet.textContent = `
|
||||
#${uniqueId} {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 8px;
|
||||
background-color: #2a2a2a;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
#${uniqueId} > div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styleSheet);
|
||||
|
||||
// Store style reference for cleanup
|
||||
this._styleSheet = styleSheet;
|
||||
|
||||
this.createSearchUI(container);
|
||||
|
||||
// Add as DOM widget
|
||||
// Add as DOM widget with proper configuration
|
||||
this.searchWidget = this.addDOMWidget(
|
||||
"prompt_manager_search_ui",
|
||||
"div",
|
||||
container,
|
||||
{
|
||||
serialize: false, // Don't save UI state
|
||||
}
|
||||
);
|
||||
|
||||
// Set initial node size, but preserve user resizes
|
||||
if (!this._userHasResized) {
|
||||
this.size = [400, 400]; // width=400, height=300 pixels
|
||||
// For nodes with text widgets, we need more height
|
||||
const baseHeight = 400;
|
||||
const widgetHeight = this.widgets ? this.widgets.length * 30 : 0;
|
||||
this.size = [600, baseHeight + widgetHeight]; // Dynamic size based on widgets
|
||||
this.setSize(this.size); // IMPORTANT: Actually apply the size
|
||||
}
|
||||
|
||||
// Hook into resize to track user changes
|
||||
@@ -97,19 +129,44 @@ app.registerExtension({
|
||||
this.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
// Override onRemoved to cleanup styles
|
||||
const onRemoved = nodeType.prototype.onRemoved;
|
||||
nodeType.prototype.onRemoved = function () {
|
||||
// Cleanup styles when node is removed
|
||||
if (this._styleSheet && this._styleSheet.parentNode) {
|
||||
this._styleSheet.parentNode.removeChild(this._styleSheet);
|
||||
}
|
||||
|
||||
// Call original onRemoved if it exists
|
||||
if (onRemoved) {
|
||||
onRemoved.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
// Method to create search UI elements
|
||||
nodeType.prototype.createSearchUI = function (container) {
|
||||
container.innerHTML = "";
|
||||
|
||||
// Main wrapper to match KikoLocalImageLoader structure
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.style.width = "100%";
|
||||
wrapper.style.height = "100%";
|
||||
wrapper.style.display = "flex";
|
||||
wrapper.style.flexDirection = "column";
|
||||
wrapper.style.gap = "8px";
|
||||
|
||||
// Search controls section
|
||||
const searchSection = document.createElement("div");
|
||||
searchSection.style.marginBottom = "10px";
|
||||
searchSection.style.display = "flex";
|
||||
searchSection.style.flexDirection = "column";
|
||||
searchSection.style.height = "100%";
|
||||
searchSection.style.gap = "8px";
|
||||
|
||||
// Search buttons row
|
||||
const buttonRow = document.createElement("div");
|
||||
buttonRow.style.display = "flex";
|
||||
buttonRow.style.gap = "8px";
|
||||
buttonRow.style.marginBottom = "8px";
|
||||
buttonRow.style.flexShrink = "0"; // Don't shrink buttons
|
||||
|
||||
// Search button
|
||||
const searchButton = document.createElement("button");
|
||||
@@ -178,7 +235,8 @@ app.registerExtension({
|
||||
|
||||
// Results section
|
||||
const resultsSection = document.createElement("div");
|
||||
resultsSection.style.maxHeight = "200px";
|
||||
resultsSection.style.flex = "1"; // Take remaining space
|
||||
resultsSection.style.minHeight = "100px"; // Minimum height
|
||||
resultsSection.style.overflowY = "auto";
|
||||
resultsSection.style.border = "1px solid #444";
|
||||
resultsSection.style.borderRadius = "4px";
|
||||
@@ -192,7 +250,9 @@ app.registerExtension({
|
||||
this.resultsSection = resultsSection;
|
||||
searchSection.appendChild(resultsSection);
|
||||
|
||||
container.appendChild(searchSection);
|
||||
// Add search section to wrapper, then wrapper to container
|
||||
wrapper.appendChild(searchSection);
|
||||
container.appendChild(wrapper);
|
||||
};
|
||||
|
||||
// Method to perform search
|
||||
|
||||
Reference in New Issue
Block a user