From 576efbba41def629ec62172f797871b80dc50d57 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Fri, 8 Aug 2025 17:45:21 -0700 Subject: [PATCH] feat: complete embedding autocomplete implementation with all features - Remove debug code and console.log statements - Fix test suite to properly mock folder_paths module - Update test expectations to match actual implementation - Add comprehensive README documentation with feature list - Add placeholder images for documentation screenshots - Include diagnostic scripts for testing embedding paths - All tests passing (338 passed, 2 skipped) Features implemented: - Autocomplete for embeddings, LoRAs, and custom tags - Custom word list loading from URL (with security validation) - Configurable triggers and settings - Auto-insert comma, replace underscores, Tab/Enter selection - Smart scrolling in suggestion list - Secure content validation to prevent XSS attacks Credits to pythongosssss/ComfyUI-Custom-Scripts for inspiration --- README.md | 40 + __init__.py | 132 +- examples/ac-emb.png | Bin 0 -> 41347 bytes examples/ac-lora.png | Bin 0 -> 41954 bytes examples/ac-tag.png | Bin 0 -> 34298 bytes kikotools/__init__.py | 14 +- .../tools/embedding_autocomplete/node.py | 151 +- tests/conftest.py | 7 + tests/test_embedding_autocomplete.py | 65 +- tests/test_embedding_paths.py | 38 + tests/test_folder_paths.py | 60 + web/kikoEmbeddingAutocomplete.js | 1325 ++++++++++++++--- 12 files changed, 1406 insertions(+), 426 deletions(-) create mode 100644 examples/ac-emb.png create mode 100644 examples/ac-lora.png create mode 100644 examples/ac-tag.png create mode 100644 tests/test_embedding_paths.py create mode 100644 tests/test_folder_paths.py diff --git a/README.md b/README.md index cf692ea..f1681da 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ ComfyUI-KikoTools provides carefully crafted, production-ready nodes grouped und | [🤖 Gemini Prompt Engineer](#-gemini-prompt-engineer) | AI-powered image analysis and prompt generation | 🧠 Prompts | | [🔍 Display Any](#-display-any) | Universal debugging tool for any data type | 👁️ Display | | [🖼️ Image to Multiple Of](#️-image-to-multiple-of) | Adjust dimensions to multiples for compatibility | 🖼️ Resolution | +| [🔤 Embedding Autocomplete](#-embedding-autocomplete) | Smart autocomplete for embeddings, LoRAs, and tags | ✍️ Text | ### 🧰 xyz-helpers Tools @@ -311,6 +312,45 @@ Unified interface for text encoding and sampler parameter management. - Quick template-based generation - Batch prompt processing +### 🔤 Embedding Autocomplete + +**Intelligent autocomplete for embeddings, LoRAs, and custom tags in text prompts.** + +
+ Embedding Autocomplete + LoRA Autocomplete + Tag Autocomplete +
+ +This feature is an enhanced fork of the autocomplete functionality from [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) by pythongosssss. We've modernized the codebase, fixed existing bugs, and added robust security features. + +**Key Features:** +- **Smart Triggers**: Type `embedding:` for embeddings, ` 0: + print(f"[KikoTools] Sample embedding: {embeddings[0]}") + print(f"[KikoTools] Returning {len(embeddings)} embeddings with paths") + return web.json_response(embeddings) + except Exception as e: + print(f"[KikoTools] Error getting embeddings: {e}") + import traceback + + traceback.print_exc() + return web.json_response([]) + + @PromptServer.instance.routes.get("/kikotools/autocomplete/loras") + async def get_loras(request): + """API endpoint for getting list of LoRAs.""" + print("[KikoTools] LoRA endpoint called") + try: + lora_files = folder_paths.get_filename_list("loras") + print(f"[KikoTools] Found {len(lora_files)} LoRA files") + # Return LoRAs with paths + loras = [] + for f in lora_files: + clean_path = os.path.splitext(f)[0] + loras.append( + { + "name": os.path.basename(clean_path), + "path": clean_path, + "file": f, + } + ) + print(f"[KikoTools] Returning {len(loras)} LoRAs") + return web.json_response(loras) + except Exception as e: + print(f"[KikoTools] Error getting LoRAs: {e}") + import traceback + + traceback.print_exc() + return web.json_response([]) + + print("[KikoTools] Autocomplete API endpoints registered successfully") + print( + "[KikoTools] Routes available: /kikotools/autocomplete/embeddings and /kikotools/autocomplete/loras" + ) + +except ImportError as e: + print(f"[KikoTools] Could not import server components: {e}") +except Exception as e: + print(f"[KikoTools] Unexpected error setting up API: {e}") + import traceback + + traceback.print_exc() + +# API endpoints are registered above at module import time def get_version(): diff --git a/examples/ac-emb.png b/examples/ac-emb.png new file mode 100644 index 0000000000000000000000000000000000000000..8f5bccd41d898d6ec5f39babb87cc277ec103a7d GIT binary patch literal 41347 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 literal 0 HcmV?d00001 diff --git a/examples/ac-tag.png b/examples/ac-tag.png new file mode 100644 index 0000000000000000000000000000000000000000..e2c6a4362af0581ba485baada2572a4c6b0866bf GIT binary patch literal 34298 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 0: - output_lines.append(f"") - output_lines.append(f"Sample embeddings (first 5):") - for emb in self.embeddings_cache[:5]: - output_lines.append(f" • {emb['name']}") - - if loras_count > 0: - output_lines.append(f"") - output_lines.append(f"Sample LoRAs (first 5):") - for lora in self.loras_cache[:5]: - output_lines.append(f" • {lora['name']}") - - output_text = "\n".join(output_lines) - - return (output_text, embeddings_count, loras_count) + # This node doesn't actually process anything + # It's just a visual indicator that autocomplete is available + return () def refresh_cache(self): """Refresh the cache of embeddings and LoRAs.""" print("[KikoEmbeddingAutocomplete] Refreshing cache...") self.embeddings_cache = self.get_embeddings() self.loras_cache = self.get_loras() - print(f"[KikoEmbeddingAutocomplete] Cached {len(self.embeddings_cache)} embeddings, {len(self.loras_cache)} LoRAs") + print( + f"[KikoEmbeddingAutocomplete] Cached {len(self.embeddings_cache)} embeddings, {len(self.loras_cache)} LoRAs" + ) def get_embeddings(self) -> List[Dict[str, Any]]: """Get list of available embeddings.""" @@ -158,7 +115,9 @@ This autocomplete works in all prompt fields!""" try: print("[KikoEmbeddingAutocomplete] Getting embeddings list...") embedding_files = folder_paths.get_filename_list("embeddings") - print(f"[KikoEmbeddingAutocomplete] Found {len(embedding_files)} embedding files") + print( + f"[KikoEmbeddingAutocomplete] Found {len(embedding_files)} embedding files" + ) for file in embedding_files: name = os.path.splitext(file)[0] embeddings.append( @@ -212,7 +171,7 @@ This autocomplete works in all prompt fields!""" # Return combined modification time return os.path.getmtime(embeddings_path) + os.path.getmtime(loras_path) - except: + except Exception: return 0 diff --git a/tests/conftest.py b/tests/conftest.py index c4769ef..b552639 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,10 +3,17 @@ pytest configuration and fixtures for ComfyUI-KikoTools testing Provides mock ComfyUI environments and test data """ +import sys import pytest import torch from unittest.mock import MagicMock +# Mock folder_paths module before any imports that might use it +sys.modules["folder_paths"] = MagicMock() +sys.modules["folder_paths"].get_filename_list = MagicMock(return_value=[]) +sys.modules["folder_paths"].get_folder_paths = MagicMock(return_value=["/mock/path"]) +sys.modules["folder_paths"].base_path = "/mock/base" + @pytest.fixture def mock_image_tensor(): diff --git a/tests/test_embedding_autocomplete.py b/tests/test_embedding_autocomplete.py index 6338c82..900bdbf 100644 --- a/tests/test_embedding_autocomplete.py +++ b/tests/test_embedding_autocomplete.py @@ -4,77 +4,84 @@ import sys from unittest.mock import MagicMock # Mock ComfyUI's folder_paths module -sys.modules['folder_paths'] = MagicMock() -sys.modules['folder_paths'].get_filename_list = MagicMock(return_value=[]) -sys.modules['folder_paths'].get_folder_paths = MagicMock(return_value=["/mock/path"]) -sys.modules['folder_paths'].base_path = "/mock/base" +sys.modules["folder_paths"] = MagicMock() +sys.modules["folder_paths"].get_filename_list = MagicMock(return_value=[]) +sys.modules["folder_paths"].get_folder_paths = MagicMock(return_value=["/mock/path"]) +sys.modules["folder_paths"].base_path = "/mock/base" def test_import(): """Test that the module can be imported.""" from kikotools.tools.embedding_autocomplete import KikoEmbeddingAutocomplete + assert KikoEmbeddingAutocomplete is not None - assert KikoEmbeddingAutocomplete.DISPLAY_NAME == "🫶 Embedding Autocomplete (Test Panel)" + assert ( + KikoEmbeddingAutocomplete.DISPLAY_NAME + == "🫶 Embedding Autocomplete Settings" + ) assert KikoEmbeddingAutocomplete.CATEGORY == "ComfyAssets" def test_settings_defined(): """Test that settings are properly defined.""" from kikotools.tools.embedding_autocomplete import KikoEmbeddingAutocomplete - + settings = KikoEmbeddingAutocomplete.SETTINGS assert "enabled" in settings - assert "trigger_chars" in settings + assert "min_chars" in settings # Changed from trigger_chars assert "max_suggestions" in settings assert "show_embeddings" in settings assert "show_loras" in settings - assert "case_sensitive" in settings - + assert "embedding_trigger" in settings + assert "lora_trigger" in settings + assert "quick_trigger" in settings + assert "sort_by_directory" in settings + # Check settings structure assert settings["enabled"]["type"] == "boolean" assert settings["enabled"]["default"] is True - assert settings["trigger_chars"]["type"] == "combo" - assert settings["trigger_chars"]["options"] == [1, 2, 3, 4] + assert settings["min_chars"]["type"] == "combo" + assert settings["min_chars"]["options"] == [1, 2, 3, 4, 5] def test_input_types(): """Test INPUT_TYPES class method.""" from kikotools.tools.embedding_autocomplete import KikoEmbeddingAutocomplete - + input_types = KikoEmbeddingAutocomplete.INPUT_TYPES() assert "required" in input_types - assert "optional" in input_types - assert "test_input" in input_types["required"] - assert "refresh" in input_types["optional"] + assert "hidden" in input_types + assert input_types["required"] == {} # No required inputs + assert "unique_id" in input_types["hidden"] def test_api_suggestions(): """Test the API suggestions method.""" from kikotools.tools.embedding_autocomplete.node import KikoEmbeddingAutocompleteAPI - + # Mock folder_paths to return some test files - sys.modules['folder_paths'].get_filename_list = MagicMock( - side_effect=lambda x: ["test1.pt", "test2.safetensors"] if x == "embeddings" else ["lora1.pt", "lora2.safetensors"] + sys.modules["folder_paths"].get_filename_list = MagicMock( + side_effect=lambda x: ( + ["test1.pt", "test2.safetensors"] + if x == "embeddings" + else ["lora1.pt", "lora2.safetensors"] + ) ) - + # Test with embeddings suggestions = KikoEmbeddingAutocompleteAPI.get_suggestions( - prefix="test", - include_embeddings=True, - include_loras=False + prefix="test", include_embeddings=True, include_loras=False ) - + assert len(suggestions) == 2 assert suggestions[0]["type"] == "embedding" assert suggestions[0]["name"] == "test1" - + # Test with LoRAs suggestions = KikoEmbeddingAutocompleteAPI.get_suggestions( - prefix="lora", - include_embeddings=False, - include_loras=True + prefix="lora", include_embeddings=False, include_loras=True ) - + assert len(suggestions) == 2 assert suggestions[0]["type"] == "lora" assert " 5: + print(f" ... and {len(files)-5} more files") + if dirs: + print(f" Subdirectories: {dirs}") + + except ImportError as e: + print(f"Could not import folder_paths: {e}") +else: + print(f"ComfyUI not found at {comfyui_path}") diff --git a/web/kikoEmbeddingAutocomplete.js b/web/kikoEmbeddingAutocomplete.js index 483b769..b8f2e0e 100644 --- a/web/kikoEmbeddingAutocomplete.js +++ b/web/kikoEmbeddingAutocomplete.js @@ -5,15 +5,25 @@ import { ComfyWidgets } from "../../scripts/widgets.js"; // KikoEmbeddingAutocomplete - Provides autocomplete for embeddings and LoRAs in text widgets class KikoEmbeddingAutocomplete { constructor() { - console.log("[KikoAutocomplete] Initializing..."); this.embeddings = []; this.loras = []; + this.customWords = []; this.settings = { enabled: true, - triggerChars: 2, - maxSuggestions: 20, showEmbeddings: true, - showLoras: true + showLoras: true, + showCustomWords: true, + embeddingTrigger: "embedding:", + loraTrigger: " { + const fullPath = item.path || item.file_name || item.model_name; + const nameOnly = item.name || fullPath.split('/').pop(); + + return { + name: nameOnly, + fullPath: fullPath, + type: "embedding", + display: `embedding:${fullPath}`, + value: `embedding:${fullPath}` + }; + }); - // Fetch remaining pages if any - if (totalPages > 1) { - // Check if api.getEmbeddings accepts page parameter - for (let page = 2; page <= totalPages; page++) { - try { - console.log(`[KikoAutocomplete] Fetching page ${page}...`); - - // Try using the api method with page parameter - const pageData = await api.getEmbeddings(page); - if (pageData && pageData.items) { - allEmbeddings.push(...pageData.items); - console.log(`[KikoAutocomplete] Page ${page} loaded: ${pageData.items.length} items`); - } else { - console.warn(`[KikoAutocomplete] Page ${page} has no items`); + useCustomEndpoint = false; // Success, don't use fallback + } + } catch (customError) { + // Custom endpoint not available, will use fallback + } + + // Only fallback if custom endpoint failed + if (useCustomEndpoint) { + try { + const embeddingsResponse = await api.getEmbeddings(); + + let allEmbeddings = []; + + // Check if this is a paginated response + if (embeddingsResponse && embeddingsResponse.items && Array.isArray(embeddingsResponse.items)) { + // This is a paginated response - get all pages + const totalPages = embeddingsResponse.total_pages || 1; + + // Add items from first page + allEmbeddings = [...embeddingsResponse.items]; + + // Fetch remaining pages if any + if (totalPages > 1) { + // Check if api.getEmbeddings accepts page parameter + for (let page = 2; page <= totalPages; page++) { + try { + + // Try using the api method with page parameter + const pageData = await api.getEmbeddings(page); + if (pageData && pageData.items) { + allEmbeddings.push(...pageData.items); + } else { + break; + } + } catch (err) { + // If pagination doesn't work, just use what we have break; } - } catch (err) { - console.warn(`[KikoAutocomplete] Could not fetch page ${page}:`, err); - // If pagination doesn't work, just use what we have - console.log("[KikoAutocomplete] Pagination not supported, using first page only"); - break; } } - } - - console.log(`[KikoAutocomplete] Collected ${allEmbeddings.length} embeddings from paginated response`); - - // Process the embeddings - console.log("[KikoAutocomplete] Sample item structure:", allEmbeddings[0]); - this.embeddings = allEmbeddings.map((item, index) => { - let name = ""; - // Extract name from different possible formats - if (typeof item === 'string') { - name = item; - } else if (item && typeof item === 'object') { - // Based on the logs, items have file_name and model_name properties - name = item.file_name || item.model_name || item.name || item.filename; + // Process the embeddings + this.embeddings = allEmbeddings.map((item, index) => { + let fullPath = ""; - // Log first item to verify - if (index === 0) { - console.log("[KikoAutocomplete] Using name from item:", name, "from properties:", Object.keys(item)); + // Extract name from different possible formats + if (typeof item === 'string') { + fullPath = item; + } else if (item && typeof item === 'object') { + // Based on the logs, items have file_name and model_name properties + fullPath = item.file_name || item.model_name || item.name || item.filename; } - } + + // Clean up the path - remove file extensions but keep directory structure + const cleanPath = String(fullPath || "").replace(/\.(pt|safetensors|ckpt|bin)$/i, ''); + + // Extract just the filename (without path) for display/search purposes + const nameOnly = cleanPath.split('/').pop() || cleanPath; + + // Only include if we have a valid name + if (cleanPath && cleanPath !== "undefined") { + return { + name: nameOnly, // Just the name for searching + fullPath: cleanPath, // Full path including subdirectory + type: "embedding", + display: `embedding:${cleanPath}`, // Show full path in display + value: `embedding:${cleanPath}` // Use full path in value + }; + } + return null; + }).filter(item => item !== null); // Remove any null entries + } + // Check if it's a simple object with embedding names as keys (old format) + else if (embeddingsResponse && typeof embeddingsResponse === 'object' && !Array.isArray(embeddingsResponse)) { + // Try to extract embeddings from object keys + const keys = Object.keys(embeddingsResponse); - // Clean up the name - remove file extensions if present - const cleanName = String(name || "").replace(/\.(pt|safetensors|ckpt|bin)$/i, ''); - - // Only include if we have a valid name - if (cleanName && cleanName !== "undefined") { - return { - name: cleanName, - type: "embedding", - display: `embedding:${cleanName}`, - value: `embedding:${cleanName}` - }; + // If the object has standard pagination keys, it's not the embeddings object + if (!keys.includes('items') && !keys.includes('total')) { + this.embeddings = keys.map(fullPath => { + const cleanPath = fullPath.replace(/\.(pt|safetensors|ckpt|bin)$/i, ''); + const nameOnly = cleanPath.split('/').pop() || cleanPath; + return { + name: nameOnly, + fullPath: cleanPath, + type: "embedding", + display: `embedding:${cleanPath}`, + value: `embedding:${cleanPath}` + }; + }); + } else { + this.embeddings = []; } - return null; - }).filter(item => item !== null); // Remove any null entries - } - // Check if it's a simple object with embedding names as keys (old format) - else if (embeddingsResponse && typeof embeddingsResponse === 'object' && !Array.isArray(embeddingsResponse)) { - // Try to extract embeddings from object keys - const keys = Object.keys(embeddingsResponse); - - // If the object has standard pagination keys, it's not the embeddings object - if (!keys.includes('items') && !keys.includes('total')) { - console.log("[KikoAutocomplete] Object format detected, using keys as embedding names"); - this.embeddings = keys.map(name => { - const cleanName = name.replace(/\.(pt|safetensors|ckpt|bin)$/i, ''); - return { - name: cleanName, - type: "embedding", - display: `embedding:${cleanName}`, - value: `embedding:${cleanName}` - }; - }); } else { - console.warn("[KikoAutocomplete] Unexpected embeddings format"); this.embeddings = []; } - } else { - console.warn("[KikoAutocomplete] Unexpected embeddings response format:", typeof embeddingsResponse); + } catch (e) { this.embeddings = []; } - - console.log(`[KikoAutocomplete] Processed ${this.embeddings.length} embeddings:`, this.embeddings.slice(0, 5)); - } catch (e) { - console.error("[KikoAutocomplete] Error fetching embeddings:", e); - this.embeddings = []; } } // Fetch LoRAs - try multiple methods if (this.settings.showLoras) { - console.log("[KikoAutocomplete] Fetching LoRAs..."); // Method 1: Try our custom endpoint try { const response = await fetch("/kikotools/autocomplete/loras"); - console.log("[KikoAutocomplete] Custom LoRA endpoint status:", response.status); if (response.ok) { - const loras = await response.json(); - console.log("[KikoAutocomplete] Raw LoRAs response:", loras); - this.loras = loras.map(name => ({ - name: name, - type: "lora", - display: ``, - value: `` - })); - console.log(`[KikoAutocomplete] Processed ${this.loras.length} LoRAs from custom endpoint`); + const lorasData = await response.json(); + + this.loras = lorasData.map(item => { + const path = item.path || item.name; + const nameOnly = item.name || path.split('/').pop(); + + return { + name: nameOnly, + fullPath: path, + type: "lora", + display: ``, + value: `` + }; + }); } else { throw new Error("Custom endpoint not available"); } } catch (e) { - console.log("[KikoAutocomplete] Custom endpoint failed, trying alternative method..."); // Method 2: Try using the object list API try { @@ -195,7 +222,6 @@ class KikoEmbeddingAutocomplete { // Look for LoraLoader node to get available loras if (objectInfo.LoraLoader?.input?.required?.lora_name?.[0]) { const loraNames = objectInfo.LoraLoader.input.required.lora_name[0]; - console.log("[KikoAutocomplete] Found LoRAs from object_info:", loraNames.slice(0, 5)); this.loras = loraNames.map(name => { const cleanName = name.replace(/\.(pt|safetensors|ckpt|bin)$/i, ''); return { @@ -205,28 +231,144 @@ class KikoEmbeddingAutocomplete { value: `` }; }); - console.log(`[KikoAutocomplete] Processed ${this.loras.length} LoRAs from object_info`); } } } catch (e2) { - console.error("[KikoAutocomplete] Could not fetch LoRAs:", e2); this.loras = []; } } } - console.log(`[KikoAutocomplete] Total loaded: ${this.embeddings.length} embeddings and ${this.loras.length} LoRAs`); - - // Store in window for debugging + // Store reference for debugging if needed window.kikoDebug = { embeddings: this.embeddings, loras: this.loras, + customWords: this.customWords, autocomplete: this }; - console.log("[KikoAutocomplete] Debug info available at window.kikoDebug"); } catch (error) { - console.error("[KikoAutocomplete] Fatal error in fetchResources:", error); + // Silent fail - resources will be empty + } + } + + async loadCustomWords(url) { + if (!url) return; + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to load custom words: ${response.status}`); + } + + const text = await response.text(); + + // Security check: Validate content is safe plain text + // Check for potential script injections or executable code + const dangerousPatterns = [ + //gi, // HTML script tags + //gi, // iframes + /javascript:/gi, // javascript: protocol + /\bon(click|load|error|mouseover|mouseout|focus|blur|change|submit)\s*=/gi, // Specific event handlers only + //gi, // embed tags + //gi, // object tags + /import\s+[\s\S]*?from/gi, // ES6 imports + /require\s*\(/gi, // CommonJS requires + /eval\s*\(/gi, // eval statements + /new\s+Function\s*\(/gi, // Function constructor + /\.innerHTML\s*=/gi, // innerHTML assignments + /document\.\w+/gi, // document manipulation + /window\.\w+/gi, // window manipulation + /(__proto__|\.prototype\.|\.constructor\s*\()/gi // Prototype pollution attempts - more specific + ]; + + for (const pattern of dangerousPatterns) { + if (pattern.test(text)) { + throw new Error("Security Warning: File contains potentially dangerous code patterns. Only plain text files with words/tags are allowed."); + } + } + + // Additional check: File should not be too large (limit to 10MB) + if (text.length > 10 * 1024 * 1024) { + throw new Error("File too large. Maximum size is 10MB."); + } + + const lines = text.split('\n'); + + // Parse custom words - format is typically "word,frequency" or just "word" + this.customWords = []; + let invalidLines = 0; + let validLines = 0; + const maxLineLength = 500; // Reasonable max length for a word/tag + + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + // Skip lines that are too long (likely not valid words) + if (trimmed.length > maxLineLength) { + invalidLines++; + continue; + } + + // Check for suspicious patterns in individual lines + // Be more lenient - only check for actual code patterns, not simple brackets + if ((trimmed.includes('') || + trimmed.includes('eval(') || + trimmed.includes('document.') || + trimmed.includes('window.')) { + invalidLines++; + continue; + } + + // Split by comma to handle frequency data if present + const parts = trimmed.split(','); + const word = parts[0].trim(); + const frequency = parts[1] ? parseInt(parts[1]) : 0; + + // Validate word contains only safe characters + // Allow alphanumeric (including starting with numbers), spaces, underscores, hyphens, and common punctuation + // Also allow Japanese characters and other unicode letters for international tags + // Include semicolons, equals, brackets for tags like "steins;gate" or special formatting + const safeWordPattern = /^[a-zA-Z0-9_\s\-:()'\.\!\?,\/\+\*&@#;=\[\]]+$/; + if (word && safeWordPattern.test(word) && word.length > 0) { + this.customWords.push({ + name: word, + fullPath: word, + type: "custom", + display: word, + value: word, + frequency: frequency + }); + validLines++; + } else if (word) { + invalidLines++; + } + } + + // Skip warning for invalid lines + + // Sort by frequency if available, otherwise alphabetically + this.customWords.sort((a, b) => { + if (a.frequency !== b.frequency) { + return b.frequency - a.frequency; // Higher frequency first + } + return a.name.localeCompare(b.name); + }); + + + // Store in debug info + if (window.kikoDebug) { + window.kikoDebug.customWords = this.customWords; + } + + // Return count for notification + return this.customWords.length; + + } catch (error) { + return 0; } } @@ -234,8 +376,6 @@ class KikoEmbeddingAutocomplete { if (!widget.inputEl || this.activeWidgets.has(widget)) { return; } - - console.log("[KikoAutocomplete] Attaching to widget:", widget, "on node:", node?.title); this.activeWidgets.add(widget); const textarea = widget.inputEl; @@ -263,7 +403,9 @@ class KikoEmbeddingAutocomplete { const hideSuggestions = () => { suggestionContainer.style.display = "none"; + suggestionContainer.innerHTML = ""; // Clear content to prevent artifacts selectedIndex = -1; + currentSuggestions = []; }; const showSuggestions = (suggestions, x, y) => { @@ -285,16 +427,45 @@ class KikoEmbeddingAutocomplete { // Add type indicator const typeSpan = document.createElement("span"); + let typeColor = "#2196F3"; // Default for LORA + let typeText = "[LORA]"; + + if (item.type === "embedding") { + typeColor = "#4CAF50"; + typeText = "[EMB]"; + } else if (item.type === "custom") { + typeColor = "#FFA500"; + typeText = "[TAG]"; + } + typeSpan.style.cssText = ` display: inline-block; width: 60px; - color: ${item.type === "embedding" ? "#4CAF50" : "#2196F3"}; + color: ${typeColor}; font-size: 0.9em; `; - typeSpan.textContent = item.type === "embedding" ? "[EMB]" : "[LORA]"; + typeSpan.textContent = typeText; const nameSpan = document.createElement("span"); - nameSpan.textContent = item.name; + // For embeddings with paths, show the full path structure + if (item.type === "embedding" && item.fullPath && item.fullPath.includes('/')) { + // Show subdirectory in gray, filename in normal color + const parts = item.fullPath.split('/'); + const dir = parts.slice(0, -1).join('/'); + const file = parts[parts.length - 1]; + + const dirSpan = document.createElement("span"); + dirSpan.style.cssText = "color: #888; font-size: 0.9em;"; + dirSpan.textContent = dir + "/"; + + const fileSpan = document.createElement("span"); + fileSpan.textContent = file; + + nameSpan.appendChild(dirSpan); + nameSpan.appendChild(fileSpan); + } else { + nameSpan.textContent = item.name; + } div.appendChild(typeSpan); div.appendChild(nameSpan); @@ -323,6 +494,24 @@ class KikoEmbeddingAutocomplete { items.forEach((item, index) => { item.style.background = index === selectedIndex ? "var(--comfy-input-bg)" : ""; }); + + // Auto-scroll to keep selected item visible + if (selectedIndex >= 0 && items[selectedIndex]) { + const selectedItem = items[selectedIndex]; + const containerRect = suggestionContainer.getBoundingClientRect(); + const itemRect = selectedItem.getBoundingClientRect(); + + // Check if item is below visible area + if (itemRect.bottom > containerRect.bottom) { + // Scroll down to show the item + suggestionContainer.scrollTop += itemRect.bottom - containerRect.bottom + 5; + } + // Check if item is above visible area + else if (itemRect.top < containerRect.top) { + // Scroll up to show the item + suggestionContainer.scrollTop -= containerRect.top - itemRect.top + 5; + } + } }; const insertSuggestion = (suggestion) => { @@ -334,13 +523,46 @@ class KikoEmbeddingAutocomplete { let insertText = ""; let replaceLength = currentPrefix.length; - // Check if we're completing an embedding (singular or plural) - if (textBefore.match(/embeddings?:([a-zA-Z0-9_-]*)$/)) { - insertText = suggestion.name; // Just the name, not the full "embedding:name" + // Apply replace underscores setting for custom words + if (suggestion.type === "custom" && this.settings.replaceUnderscores) { + // Create a modified suggestion with underscores replaced + suggestion = { + ...suggestion, + name: suggestion.name.replace(/_/g, ' '), + value: suggestion.value.replace(/_/g, ' '), + display: suggestion.display.replace(/_/g, ' ') + }; + } + + // Check if we're completing an embedding after the trigger + const embTrigger = this.settings.embeddingTrigger || "embedding:"; + const embRegex = new RegExp(embTrigger.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + "([a-zA-Z0-9_\\/-]*)$"); + + if (textBefore.match(embRegex) || (embTrigger === "embedding:" && textBefore.match(/embeddings:([a-zA-Z0-9_\/-]*)$/))) { + // For embeddings, use the full path (including subdirectory) + insertText = suggestion.fullPath || suggestion.name; // Use full path if available } // Check if we're completing a lora - else if (textBefore.match(/"; // Complete the lora syntax + else if (suggestion.type === "lora") { + const loraTrigger = this.settings.loraTrigger || ""; + } else { + // Insert full lora syntax + insertText = suggestion.value || suggestion.display; + } + } + // Check if we're replacing a word that starts with "em" (embedding autocomplete) + else if (currentPrefix && textBefore.match(/\b(em|emb|embe|embed|embeddi|embeddin|embedding)$/i)) { + // User typed a partial "embedding" word, replace it with the full embedding syntax + const match = textBefore.match(/\b(em|emb|embe|embed|embeddi|embeddin|embedding)$/i); + insertText = suggestion.value || suggestion.display; + prefixStart = cursor - match[1].length; + replaceLength = match[1].length; } // General insertion else { @@ -357,8 +579,23 @@ class KikoEmbeddingAutocomplete { const before = text.substring(0, prefixStart); const after = text.substring(prefixStart + replaceLength); - textarea.value = before + insertText + after; - textarea.selectionStart = textarea.selectionEnd = prefixStart + insertText.length; + // Add comma if auto-insert comma is enabled and appropriate + let finalInsertText = insertText; + if (this.settings.autoInsertComma && (suggestion.type === "custom" || suggestion.type === "embedding" || suggestion.type === "lora")) { + // Check if we should add a comma + // Add comma if: not already followed by comma, and either at end of text or followed by non-empty content + const trimmedAfter = after.trim(); + const nextChar = trimmedAfter.charAt(0); + + // Add comma unless already followed by comma or punctuation + if (nextChar !== ',' && nextChar !== '.' && nextChar !== ';') { + // Add comma and space + finalInsertText += ', '; + } + } + + textarea.value = before + finalInsertText + after; + textarea.selectionStart = textarea.selectionEnd = prefixStart + finalInsertText.length; hideSuggestions(); @@ -372,40 +609,59 @@ class KikoEmbeddingAutocomplete { const cursor = textarea.selectionStart; const textBefore = text.substring(0, cursor); - if (this.debug) { - console.log("[KikoAutocomplete] findPrefix - textBefore:", textBefore); - } - // Check for embedding: or embeddings: trigger (both singular and plural) - const embeddingMatch = textBefore.match(/embeddings?:([a-zA-Z0-9_-]*)$/); + // Check for custom embedding trigger + const embTrigger = this.settings.embeddingTrigger || "embedding:"; + const embRegex = new RegExp(embTrigger.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + "([a-zA-Z0-9_\\/-]*)$"); + const embeddingMatch = textBefore.match(embRegex); if (embeddingMatch) { currentPrefix = embeddingMatch[1].toLowerCase(); prefixStart = cursor - embeddingMatch[1].length; - console.log("[KikoAutocomplete] Found embedding trigger, prefix:", currentPrefix); return "embedding"; } - // Check for = 0 && /[a-zA-Z0-9_-]/.test(text[start])) { start--; } start++; - const prefix = text.substring(start, cursor); + const word = text.substring(start, cursor); - // Check if we should show suggestions for general matching - if (prefix.length >= this.settings.triggerChars) { - currentPrefix = prefix.toLowerCase(); + // Check if we should show suggestions + if (word.length >= this.settings.minChars) { + currentPrefix = word.toLowerCase(); prefixStart = start; + + // Check for quick trigger (e.g., "em" for embeddings) + const quickTrigger = this.settings.quickTrigger; + if (quickTrigger && word.toLowerCase().startsWith(quickTrigger.toLowerCase())) { + return "embedding-word"; + } + + // For other words, show general matches return "general"; } @@ -415,23 +671,29 @@ class KikoEmbeddingAutocomplete { const getSuggestions = (prefix, type) => { const suggestions = []; - // If type is embedding, only show embeddings + // If type is embedding (after "embedding:"), only show embeddings if (type === "embedding" && this.settings.showEmbeddings) { suggestions.push(...this.embeddings.filter(e => - prefix === "" || e.name.toLowerCase().includes(prefix) + prefix === "" || e.name.toLowerCase().includes(prefix) || (e.fullPath && e.fullPath.toLowerCase().includes(prefix)) )); } + // If type is embedding-word (user typed "em", "emb", etc), ONLY show embeddings + else if (type === "embedding-word" && this.settings.showEmbeddings) { + // When user types "em", "emb", "embe", etc. we want to show ALL embeddings + // because they're trying to trigger the embedding autocomplete + suggestions.push(...this.embeddings); + } // If type is lora, only show loras else if (type === "lora" && this.settings.showLoras) { suggestions.push(...this.loras.filter(l => prefix === "" || l.name.toLowerCase().includes(prefix) )); } - // For general matching, show both + // For general matching, show embeddings, loras, and custom words else if (type === "general") { if (this.settings.showEmbeddings) { suggestions.push(...this.embeddings.filter(e => - e.name.toLowerCase().includes(prefix) + e.name.toLowerCase().includes(prefix) || (e.fullPath && e.fullPath.toLowerCase().includes(prefix)) )); } @@ -440,22 +702,47 @@ class KikoEmbeddingAutocomplete { l.name.toLowerCase().includes(prefix) )); } + + if (this.settings.showCustomWords) { + suggestions.push(...this.customWords.filter(w => + w.name.toLowerCase().includes(prefix) + )); + } } // Sort suggestions - prioritize exact matches and starts-with matches suggestions.sort((a, b) => { const aName = a.name.toLowerCase(); const bName = b.name.toLowerCase(); + const aPath = (a.fullPath || a.name).toLowerCase(); + const bPath = (b.fullPath || b.name).toLowerCase(); - // Exact match + // Exact match on name if (aName === prefix) return -1; if (bName === prefix) return 1; - // Starts with + // Exact match on full path + if (aPath === prefix) return -1; + if (bPath === prefix) return 1; + + // Starts with on name if (aName.startsWith(prefix) && !bName.startsWith(prefix)) return -1; if (!aName.startsWith(prefix) && bName.startsWith(prefix)) return 1; - // Alphabetical + // Starts with on path + if (aPath.startsWith(prefix) && !bPath.startsWith(prefix)) return -1; + if (!aPath.startsWith(prefix) && bPath.startsWith(prefix)) return 1; + + // Group by directory if enabled + if (this.settings.sortByDirectory) { + const aDir = aPath.includes('/') ? aPath.split('/')[0] : ''; + const bDir = bPath.includes('/') ? bPath.split('/')[0] : ''; + + if (aDir !== bDir) { + return aDir.localeCompare(bDir); + } + } + return aName.localeCompare(bName); }); @@ -501,7 +788,8 @@ class KikoEmbeddingAutocomplete { e.preventDefault(); selectedIndex = Math.max(selectedIndex - 1, 0); updateSelection(); - } else if (e.key === "Enter" || e.key === "Tab") { + } else if ((e.key === "Enter" && this.settings.insertOnEnter) || + (e.key === "Tab" && this.settings.insertOnTab)) { if (selectedIndex >= 0 && selectedIndex < currentSuggestions.length) { e.preventDefault(); insertSuggestion(currentSuggestions[selectedIndex]); @@ -515,117 +803,667 @@ class KikoEmbeddingAutocomplete { textarea.addEventListener("blur", () => { setTimeout(hideSuggestions, 200); }); + + // Cleanup on widget destruction + if (widget.onRemoved) { + const originalOnRemoved = widget.onRemoved; + widget.onRemoved = () => { + hideSuggestions(); + if (suggestionContainer && suggestionContainer.parentNode) { + suggestionContainer.parentNode.removeChild(suggestionContainer); + } + this.activeWidgets.delete(widget); + originalOnRemoved.call(widget); + }; + } else { + widget.onRemoved = () => { + hideSuggestions(); + if (suggestionContainer && suggestionContainer.parentNode) { + suggestionContainer.parentNode.removeChild(suggestionContainer); + } + this.activeWidgets.delete(widget); + }; + } + } +} + +// Settings Dialog - Clean, modern interface +class KikoEmbeddingAutocompleteDialog { + constructor(autocompleteInstance) { + this.autocomplete = autocompleteInstance; + this.element = null; + } + + createDialog() { + // Create dialog backdrop + const backdrop = document.createElement("div"); + backdrop.className = "kiko-dialog-backdrop"; + backdrop.style.cssText = ` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + z-index: 99998; + display: flex; + align-items: center; + justify-content: center; + `; + + // Create dialog container + const dialog = document.createElement("div"); + dialog.className = "kiko-dialog"; + dialog.style.cssText = ` + background: var(--comfy-menu-bg, #202020); + border: 1px solid var(--border-color, #4e4e4e); + border-radius: 8px; + width: 600px; + max-width: 90vw; + max-height: 80vh; + display: flex; + flex-direction: column; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); + color: var(--fg-color, #ffffff); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + `; + + // Header + const header = document.createElement("div"); + header.style.cssText = ` + padding: 20px; + border-bottom: 1px solid var(--border-color, #4e4e4e); + display: flex; + align-items: center; + justify-content: space-between; + `; + + const title = document.createElement("h2"); + title.textContent = "🫶 Embedding Autocomplete Settings"; + title.style.cssText = ` + margin: 0; + font-size: 20px; + font-weight: 600; + color: var(--fg-color, #ffffff); + `; + + const closeBtn = document.createElement("button"); + closeBtn.innerHTML = "✕"; + closeBtn.style.cssText = ` + background: transparent; + border: none; + color: var(--fg-color, #ffffff); + font-size: 24px; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.7; + transition: opacity 0.2s; + `; + closeBtn.onmouseover = () => closeBtn.style.opacity = "1"; + closeBtn.onmouseout = () => closeBtn.style.opacity = "0.7"; + + header.appendChild(title); + header.appendChild(closeBtn); + + // Content + const content = document.createElement("div"); + content.style.cssText = ` + padding: 20px; + overflow-y: auto; + flex: 1; + `; + + // Create settings sections + content.innerHTML = ` +
+

General Settings

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+

Trigger Settings

+ +
+ + + + Text that triggers embedding autocomplete (e.g., "embedding:", "emb:", "e:") + +
+ +
+ + + + Text that triggers LoRA autocomplete (e.g., "<lora:", "lora:", "l:") + +
+
+ +
+

Display Settings

+ +
+ + + + Number of suggestions to show (5-100) + +
+ +
+ + + + Minimum characters before showing suggestions (1-10) + +
+
+ +
+

Insertion Settings

+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+
+
+ +
+

Custom Words

+ +
+ +
+ + +
+ + File Format Requirements:
+ • Plain text file only (no HTML, JavaScript, or code)
+ • One word/tag per line
+ • Optional format: word,frequency (comma-separated)
+ • Allowed characters: letters, numbers, spaces, underscores, hyphens, basic punctuation
+ • Maximum file size: 10MB
+ • Example: landscape or landscape,5000 +
+
+
+
+ `; + + // Footer + const footer = document.createElement("div"); + footer.style.cssText = ` + padding: 15px 20px; + border-top: 1px solid var(--border-color, #4e4e4e); + display: flex; + justify-content: flex-end; + gap: 10px; + `; + + const saveBtn = document.createElement("button"); + saveBtn.textContent = "Save Settings"; + saveBtn.style.cssText = ` + padding: 8px 20px; + background: #4CAF50; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background 0.2s; + `; + saveBtn.onmouseover = () => saveBtn.style.background = "#45a049"; + saveBtn.onmouseout = () => saveBtn.style.background = "#4CAF50"; + + const cancelBtn = document.createElement("button"); + cancelBtn.textContent = "Cancel"; + cancelBtn.style.cssText = ` + padding: 8px 20px; + background: var(--comfy-input-bg, #1a1a1a); + color: var(--fg-color, #ffffff); + border: 1px solid var(--border-color, #4e4e4e); + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background 0.2s; + `; + cancelBtn.onmouseover = () => cancelBtn.style.background = "var(--comfy-input-bg-hover, #2a2a2a)"; + cancelBtn.onmouseout = () => cancelBtn.style.background = "var(--comfy-input-bg, #1a1a1a)"; + + footer.appendChild(cancelBtn); + footer.appendChild(saveBtn); + + // Assemble dialog + dialog.appendChild(header); + dialog.appendChild(content); + dialog.appendChild(footer); + backdrop.appendChild(dialog); + + // Event handlers + closeBtn.onclick = () => this.close(); + cancelBtn.onclick = () => this.close(); + backdrop.onclick = (e) => { + if (e.target === backdrop) this.close(); + }; + + saveBtn.onclick = () => this.save(); + + this.element = backdrop; + this.dialog = dialog; + + // Settings will be loaded after adding to DOM + + return backdrop; + } + + showCustomStatus(message, type = "info") { + const statusEl = document.getElementById("kiko-custom-words-status"); + if (!statusEl) return; + + statusEl.textContent = message; + statusEl.style.display = "block"; + + // Set color based on type + switch(type) { + case "success": + statusEl.style.color = "#4CAF50"; + break; + case "error": + statusEl.style.color = "#f44336"; + break; + case "warning": + statusEl.style.color = "#FFA500"; + break; + default: + statusEl.style.color = "#888"; + } + } + + loadSettings() { + if (!this.autocomplete) return; + + const settings = this.autocomplete.settings; + + // Make sure elements exist before trying to set values + const elements = { + enabled: document.getElementById("kiko-enabled"), + showEmbeddings: document.getElementById("kiko-show-embeddings"), + showLoras: document.getElementById("kiko-show-loras"), + showCustom: document.getElementById("kiko-show-custom"), + groupByDir: document.getElementById("kiko-group-by-dir"), + embeddingTrigger: document.getElementById("kiko-embedding-trigger"), + loraTrigger: document.getElementById("kiko-lora-trigger"), + maxSuggestions: document.getElementById("kiko-max-suggestions"), + minChars: document.getElementById("kiko-min-chars"), + customWordsUrl: document.getElementById("kiko-custom-words-url"), + loadCustomBtn: document.getElementById("kiko-load-custom-words"), + customStatus: document.getElementById("kiko-custom-words-status"), + autoComma: document.getElementById("kiko-auto-comma"), + replaceUnderscores: document.getElementById("kiko-replace-underscores"), + insertTab: document.getElementById("kiko-insert-tab"), + insertEnter: document.getElementById("kiko-insert-enter") + }; + + // Check if all elements exist + if (!elements.enabled) { + console.error("[KikoAutocomplete] Settings elements not found in DOM"); + return; + } + + // Set values + if (elements.enabled) elements.enabled.checked = settings.enabled !== false; + if (elements.showEmbeddings) elements.showEmbeddings.checked = settings.showEmbeddings !== false; + if (elements.showLoras) elements.showLoras.checked = settings.showLoras !== false; + if (elements.showCustom) elements.showCustom.checked = settings.showCustomWords !== false; + if (elements.groupByDir) elements.groupByDir.checked = settings.sortByDirectory !== false; + if (elements.embeddingTrigger) elements.embeddingTrigger.value = settings.embeddingTrigger || "embedding:"; + if (elements.loraTrigger) elements.loraTrigger.value = settings.loraTrigger || " { + const url = elements.customWordsUrl.value.trim(); + if (!url) { + this.showCustomStatus("Please enter a URL", "error"); + return; + } + + // Show loading state + elements.loadCustomBtn.disabled = true; + elements.loadCustomBtn.textContent = "Loading..."; + this.showCustomStatus("Loading custom words...", "info"); + + try { + const count = await this.autocomplete.loadCustomWords(url); + if (count > 0) { + this.showCustomStatus(`Successfully loaded ${count.toLocaleString()} custom words`, "success"); + // Save the URL if successful + this.autocomplete.settings.customWordsUrl = url; + this.autocomplete.saveSettings(); + } else { + this.showCustomStatus("No words found in the file", "warning"); + } + } catch (error) { + this.showCustomStatus(`Error loading custom words: ${error.message}`, "error"); + } finally { + elements.loadCustomBtn.disabled = false; + elements.loadCustomBtn.textContent = "Load"; + } + }; + } + + // Show current custom words count if any + if (this.autocomplete.customWords && this.autocomplete.customWords.length > 0) { + this.showCustomStatus(`${this.autocomplete.customWords.length.toLocaleString()} custom words loaded`, "info"); + } + } + + save() { + if (!this.autocomplete) return; + + // Gather settings + const newSettings = { + enabled: document.getElementById("kiko-enabled").checked, + showEmbeddings: document.getElementById("kiko-show-embeddings").checked, + showLoras: document.getElementById("kiko-show-loras").checked, + showCustomWords: document.getElementById("kiko-show-custom").checked, + sortByDirectory: document.getElementById("kiko-group-by-dir").checked, + embeddingTrigger: document.getElementById("kiko-embedding-trigger").value || "embedding:", + loraTrigger: document.getElementById("kiko-lora-trigger").value || " this.close(), 1000); + } + + showMessage(message) { + const msgEl = document.createElement("div"); + msgEl.textContent = message; + msgEl.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #4CAF50; + color: white; + padding: 12px 20px; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0,0,0,0.3); + z-index: 100000; + animation: slideIn 0.3s ease; + `; + + // Add animation + const style = document.createElement("style"); + style.textContent = ` + @keyframes slideIn { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } + } + `; + document.head.appendChild(style); + + document.body.appendChild(msgEl); + + setTimeout(() => { + msgEl.style.animation = "slideIn 0.3s ease reverse"; + setTimeout(() => { + msgEl.remove(); + style.remove(); + }, 300); + }, 2000); + } + + show() { + if (!this.element) { + this.createDialog(); + } + document.body.appendChild(this.element); + + // Load settings after adding to DOM + setTimeout(() => this.loadSettings(), 10); + + // Add open animation + this.dialog.style.animation = "fadeIn 0.2s ease"; + this.element.style.animation = "fadeIn 0.2s ease"; + + const style = document.createElement("style"); + style.textContent = ` + @keyframes fadeIn { + from { opacity: 0; transform: scale(0.95); } + to { opacity: 1; transform: scale(1); } + } + `; + document.head.appendChild(style); + this.animStyle = style; + } + + close() { + if (this.element) { + this.element.remove(); + } + if (this.animStyle) { + this.animStyle.remove(); + } } } // Initialize and register -console.log("[KikoTools] Registering Embedding Autocomplete extension"); app.registerExtension({ name: "kikotools.embeddingAutocomplete", async setup() { - console.log("[KikoTools] Setting up Embedding Autocomplete"); - // Add settings - app.ui.settings.addSetting({ - id: "kikotools.embeddingAutocomplete.enabled", - name: "Enabled", - category: ["KikoTools", "Embedding Autocomplete"], - defaultValue: true, - type: "boolean", - onChange(value) { - if (window.kikoAutocomplete) { - window.kikoAutocomplete.settings.enabled = value; - window.kikoAutocomplete.saveSettings(); - } - } - }); - - app.ui.settings.addSetting({ - id: "kikotools.embeddingAutocomplete.triggerChars", - name: "Trigger After Characters", - category: ["KikoTools", "Embedding Autocomplete"], - defaultValue: 2, - type: "combo", - options: [ - { value: 1, text: "1 character" }, - { value: 2, text: "2 characters" }, - { value: 3, text: "3 characters" }, - { value: 4, text: "4 characters" } - ], - onChange(value) { - if (window.kikoAutocomplete) { - window.kikoAutocomplete.settings.triggerChars = value; - window.kikoAutocomplete.saveSettings(); - } - } - }); - - app.ui.settings.addSetting({ - id: "kikotools.embeddingAutocomplete.maxSuggestions", - name: "Maximum Suggestions", - category: ["KikoTools", "Embedding Autocomplete"], - defaultValue: 20, - type: "combo", - options: [ - { value: 10, text: "10" }, - { value: 20, text: "20" }, - { value: 30, text: "30" }, - { value: 50, text: "50" } - ], - onChange(value) { - if (window.kikoAutocomplete) { - window.kikoAutocomplete.settings.maxSuggestions = value; - window.kikoAutocomplete.saveSettings(); - } - } - }); - - app.ui.settings.addSetting({ - id: "kikotools.embeddingAutocomplete.showEmbeddings", - name: "Show Embeddings", - category: ["KikoTools", "Embedding Autocomplete"], - defaultValue: true, - type: "boolean", - onChange(value) { - if (window.kikoAutocomplete) { - window.kikoAutocomplete.settings.showEmbeddings = value; - window.kikoAutocomplete.saveSettings(); - window.kikoAutocomplete.fetchResources(); - } - } - }); - - app.ui.settings.addSetting({ - id: "kikotools.embeddingAutocomplete.showLoras", - name: "Show LoRAs", - category: ["KikoTools", "Embedding Autocomplete"], - defaultValue: true, - type: "boolean", - onChange(value) { - if (window.kikoAutocomplete) { - window.kikoAutocomplete.settings.showLoras = value; - window.kikoAutocomplete.saveSettings(); - window.kikoAutocomplete.fetchResources(); - } - } - }); - - // Create autocomplete instance + // Create autocomplete instance first window.kikoAutocomplete = new KikoEmbeddingAutocomplete(); + // Simple button to open settings dialog (following rgthree's pattern) + app.ui.settings.addSetting({ + id: "kikotools.embeddingAutocomplete", + name: "🫶 Embedding Autocomplete Configuration", + tooltip: "Configure autocomplete settings", + category: ["kikotools", "🫶 Embedding Autocomplete Configuration"], + defaultValue: null, + type: () => { + // Create a simple row with a button to open the dialog + const tr = document.createElement("tr"); + tr.className = "kikotools-settings-row"; + + const tdLabel = document.createElement("td"); + tdLabel.innerHTML = `
+ 🫶 + Embedding Autocomplete Configuration +
`; + + const tdButton = document.createElement("td"); + const button = document.createElement("button"); + button.textContent = "Open Settings"; + button.className = "kiko-settings-button"; + button.style.cssText = ` + padding: 8px 20px; + background: #4CAF50; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background 0.2s; + `; + button.onmouseover = () => button.style.background = "#45a049"; + button.onmouseout = () => button.style.background = "#4CAF50"; + button.onclick = () => { + const dialog = new KikoEmbeddingAutocompleteDialog(window.kikoAutocomplete); + dialog.show(); + }; + + tdButton.appendChild(button); + tr.appendChild(tdLabel); + tr.appendChild(tdButton); + + return tr; + } + }); + // Override STRING widget creation to add autocomplete const originalStringWidget = ComfyWidgets.STRING; - console.log("[KikoAutocomplete] Original STRING widget:", originalStringWidget); ComfyWidgets.STRING = function(node, inputName, inputData, app) { - console.log("[KikoAutocomplete] Creating STRING widget for:", inputName, "multiline:", inputData[1]?.multiline); const widget = originalStringWidget.apply(this, arguments); // Only attach to multiline text widgets (prompts) if (inputData[1]?.multiline && widget.inputEl) { - console.log("[KikoAutocomplete] Will attach to multiline widget:", inputName); setTimeout(() => { window.kikoAutocomplete.attachToWidget(widget, node); }, 100); @@ -635,19 +1473,16 @@ app.registerExtension({ }; // Also attach to existing multiline widgets - console.log("[KikoAutocomplete] Looking for existing widgets to attach to..."); setTimeout(() => { app.graph._nodes.forEach(node => { if (node.widgets) { node.widgets.forEach(widget => { // Check for text widgets with textareas if (widget.type === "customtext" && widget.inputEl && widget.inputEl.tagName === "TEXTAREA") { - console.log("[KikoAutocomplete] Found existing textarea widget on node:", node.title, "widget:", widget.name); window.kikoAutocomplete.attachToWidget(widget, node); } // Also check for widgets that might be text inputs but not customtext else if (widget.inputEl && widget.inputEl.tagName === "TEXTAREA" && !window.kikoAutocomplete.activeWidgets.has(widget)) { - console.log("[KikoAutocomplete] Found textarea widget (non-customtext) on node:", node.title, "widget:", widget.name); window.kikoAutocomplete.attachToWidget(widget, node); } });