From 5636437df2f01f29e0b0d3a795fa0bffecaa125b Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Thu, 15 Jan 2026 14:34:05 +0800 Subject: [PATCH] fix: enable autocomplete in Vue DOM render mode In Vue DOM render mode, widget.inputEl is not in the DOM, causing autocomplete to fail. This commit: - Adds findWidgetInputElement() helper to search DOM for actual input elements - Checks if widget.inputEl is in document before using it - Falls back to DOM search for Vue-rendered widgets using .lg-node-widget containers - Implements async initialization with retry logic (20 attempts, 50ms interval) - Adds debug logging for troubleshooting - Prevents duplicate initialization with isInitializing flag Fixes autocomplete functionality for Lora Loader nodes when ComfyUI uses Vue DOM rendering instead of canvas rendering. --- web/comfyui/utils.js | 92 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/web/comfyui/utils.js b/web/comfyui/utils.js index c140200b..6fe79030 100644 --- a/web/comfyui/utils.js +++ b/web/comfyui/utils.js @@ -387,6 +387,58 @@ export function mergeLoras(lorasText, lorasArr) { return result; } +/** + * Find the actual input element for a widget + * @param {Object} node - The node instance + * @param {Object} widget - The widget to find input element for + * @returns {Promise} The input element or null + */ +async function findWidgetInputElement(node, widget) { + if (widget.inputEl && document.body.contains(widget.inputEl)) { + return widget.inputEl; + } + + const nodeId = node.id; + const widgetName = widget.name; + const maxAttempts = 20; + const searchInterval = 50; + + const searchForInput = (attempt = 0) => { + return new Promise((resolve) => { + const doSearch = () => { + let inputElement = null; + + const allWidgetContainers = document.querySelectorAll('.lg-node-widget'); + + for (const container of allWidgetContainers) { + const hasInput = !!container.querySelector('input, textarea'); + const textContent = container.textContent.toLowerCase(); + const containsWidgetName = textContent.includes(widgetName.toLowerCase()); + const containsNodeTitle = textContent.includes(node.title?.toLowerCase() || ''); + + if (hasInput && (containsWidgetName || (widgetName === 'text' && container.querySelector('textarea')))) { + inputElement = container.querySelector('input, textarea'); + break; + } + } + + if (inputElement) { + console.log(`[Lora Manager] Found input element for widget "${widgetName}" on node ${nodeId}`); + resolve(inputElement); + } else if (attempt < maxAttempts) { + setTimeout(() => searchForInput(attempt + 1).then(resolve), searchInterval); + } else { + console.warn(`[Lora Manager] Could not find input element for widget "${widgetName}" on node ${nodeId} after ${maxAttempts} attempts`); + resolve(null); + } + }; + doSearch(); + }); + }; + + return searchForInput(); +} + /** * Initialize autocomplete for an input widget and setup cleanup * @param {Object} node - The node instance @@ -398,6 +450,7 @@ export function mergeLoras(lorasText, lorasArr) { */ export function setupInputWidgetWithAutocomplete(node, inputWidget, originalCallback, modelType = 'loras', autocompleteOptions = {}) { let autocomplete = null; + let isInitializing = false; const defaultOptions = { maxItems: 20, minChars: 1, @@ -405,22 +458,45 @@ export function setupInputWidgetWithAutocomplete(node, inputWidget, originalCall }; const mergedOptions = { ...defaultOptions, ...autocompleteOptions }; - // Enhanced callback that initializes autocomplete and calls original callback + const initializeAutocomplete = async () => { + if (autocomplete || isInitializing) return; + isInitializing = true; + + try { + let inputElement = null; + + if (inputWidget.inputEl && document.body.contains(inputWidget.inputEl)) { + inputElement = inputWidget.inputEl; + console.log(`[Lora Manager] Using widget.inputEl for widget "${inputWidget.name}"`); + } else { + console.log(`[Lora Manager] Searching DOM for input element for widget "${inputWidget.name}"`); + inputElement = await findWidgetInputElement(node, inputWidget); + } + + if (inputElement) { + autocomplete = new AutoComplete(inputElement, modelType, mergedOptions); + node.autocomplete = autocomplete; + console.log(`[Lora Manager] Autocomplete initialized for widget "${inputWidget.name}" on node ${node.id}`); + } else { + console.warn(`[Lora Manager] Could not find input element for widget "${inputWidget.name}" on node ${node.id}`); + } + } catch (error) { + console.error('[Lora Manager] Error initializing autocomplete:', error); + } finally { + isInitializing = false; + } + }; + const enhancedCallback = (value) => { - // Initialize autocomplete on first callback if not already done - if (!autocomplete && inputWidget.inputEl) { - autocomplete = new AutoComplete(inputWidget.inputEl, modelType, mergedOptions); - // Store reference for cleanup - node.autocomplete = autocomplete; + if (!autocomplete && !isInitializing) { + initializeAutocomplete(); } - // Call the original callback if (typeof originalCallback === "function") { originalCallback.call(node, value); } }; - // Setup cleanup on node removal setupAutocompleteCleanup(node); return enhancedCallback;