Merge pull request #49 from ComfyAssets/bug/size-fix

Bug/size fix
This commit is contained in:
Vito
2025-08-30 17:34:40 -07:00
committed by GitHub
4 changed files with 103 additions and 49 deletions
+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.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
View File
@@ -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
View File
@@ -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
View File
@@ -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