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.
This commit is contained in:
Will Miao
2026-01-15 14:34:05 +08:00
parent 10c0668b02
commit 5636437df2

View File

@@ -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<HTMLElement|null>} 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;