mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
fix(autocomplete): resolve instability in Vue DOM mode and fix WanVideo node binding
- Fix infinite reinitialization loop by only validating stale widget.inputEl when it's actually in DOM - Improve findWidgetInputElement to specifically search for textarea for text widgets, avoiding mismatches with checkbox inputs on nodes like WanVideo Lora Select that have toggle switches - Add data-node-id based element search as primary strategy for better reliability across rendering modes - Fix autocomplete initialization to properly handle element DOM state transitions Fixes autocomplete failing after Canvas ↔ Vue DOM mode switches and WanVideo node always failing to trigger autocomplete. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,46 @@ app.registerExtension({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ComfyUI Dual Rendering Modes
|
||||||
|
|
||||||
|
ComfyUI frontend supports two rendering modes:
|
||||||
|
|
||||||
|
| Mode | Description | DOM Structure |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Canvas Mode** | Traditional rendering where widgets are rendered on top of canvas using absolute positioning | Uses `.dom-widget` class on containers |
|
||||||
|
| **Vue DOM Mode** | New rendering mode where nodes and widgets are rendered as Vue components | Uses `.lg-node-widget` class on containers with dynamic IDs (e.g., `v-1-0`) |
|
||||||
|
|
||||||
|
### Mode Switching
|
||||||
|
|
||||||
|
The frontend switches between modes via `LiteGraph.vueNodesMode` boolean:
|
||||||
|
- `LiteGraph.vueNodesMode = true` → Vue DOM Mode
|
||||||
|
- `LiteGraph.vueNodesMode = false` → Canvas Mode
|
||||||
|
|
||||||
|
**Key Behavior**: Mode switching triggers DOM re-rendering WITHOUT page reload. Widget elements are destroyed and recreated, so any event listeners or references to old DOM elements become invalid.
|
||||||
|
|
||||||
|
### Testing Mode Switches via Chrome DevTools MCP
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Trigger render mode change
|
||||||
|
LiteGraph.vueNodesMode = !LiteGraph.vueNodesMode;
|
||||||
|
|
||||||
|
// Force canvas redraw (optional but helps trigger re-render)
|
||||||
|
if (app.canvas) {
|
||||||
|
app.canvas.draw(true, true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Notes
|
||||||
|
|
||||||
|
When implementing widgets that attach event listeners or maintain external references:
|
||||||
|
1. **Use `node.onRemoved`** to clean up when node is deleted
|
||||||
|
2. **Detect DOM changes** by checking if widget input element is still in document: `document.body.contains(inputElement)`
|
||||||
|
3. **Poll for mode changes** by watching `LiteGraph.vueNodesMode` and re-initializing when it changes
|
||||||
|
4. **Use `loadedGraphNode` hook** for initial setup (guarantees DOM is fully rendered)
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. The `addDOMWidget` API
|
## 3. The `addDOMWidget` API
|
||||||
|
|||||||
@@ -293,6 +293,57 @@ class AutoComplete {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener('click', this.onDocumentClick);
|
document.addEventListener('click', this.onDocumentClick);
|
||||||
|
|
||||||
|
// Mark this element as having autocomplete events bound
|
||||||
|
this.inputElement._autocompleteEventsBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the autocomplete is valid (input element is in DOM and events are bound)
|
||||||
|
*/
|
||||||
|
isValid() {
|
||||||
|
return this.inputElement &&
|
||||||
|
document.body.contains(this.inputElement) &&
|
||||||
|
this.inputElement._autocompleteEventsBound === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if events need to be rebound (element exists but events not bound)
|
||||||
|
*/
|
||||||
|
needsRebind() {
|
||||||
|
return this.inputElement &&
|
||||||
|
document.body.contains(this.inputElement) &&
|
||||||
|
this.inputElement._autocompleteEventsBound !== true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebind events to the input element (useful after Vue moves the element)
|
||||||
|
*/
|
||||||
|
rebindEvents() {
|
||||||
|
// Remove old listeners if they exist
|
||||||
|
if (this.onInput) {
|
||||||
|
this.inputElement.removeEventListener('input', this.onInput);
|
||||||
|
}
|
||||||
|
if (this.onKeyDown) {
|
||||||
|
this.inputElement.removeEventListener('keydown', this.onKeyDown);
|
||||||
|
}
|
||||||
|
if (this.onBlur) {
|
||||||
|
this.inputElement.removeEventListener('blur', this.onBlur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebind all events
|
||||||
|
this.bindEvents();
|
||||||
|
|
||||||
|
console.log('[Lora Manager] Autocomplete events rebound');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the TextAreaCaretHelper (useful after element properties change)
|
||||||
|
*/
|
||||||
|
refreshHelper() {
|
||||||
|
if (this.inputElement && document.body.contains(this.inputElement)) {
|
||||||
|
this.helper = new TextAreaCaretHelper(this.inputElement, () => app.canvas.ds.scale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInput(value = '') {
|
handleInput(value = '') {
|
||||||
@@ -638,7 +689,26 @@ class AutoComplete {
|
|||||||
}
|
}
|
||||||
return `${trimmed}, `;
|
return `${trimmed}, `;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the autocomplete instance is still valid
|
||||||
|
* (input element exists and is in the DOM)
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isValid() {
|
||||||
|
return this.inputElement && document.body.contains(this.inputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the TextAreaCaretHelper to update cached measurements
|
||||||
|
* Useful after element is moved in DOM (e.g., Vue mode switch)
|
||||||
|
*/
|
||||||
|
refreshCaretHelper() {
|
||||||
|
if (this.inputElement && document.body.contains(this.inputElement)) {
|
||||||
|
this.helper = new TextAreaCaretHelper(this.inputElement, () => app.canvas.ds.scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.debounceTimer) {
|
if (this.debounceTimer) {
|
||||||
clearTimeout(this.debounceTimer);
|
clearTimeout(this.debounceTimer);
|
||||||
|
|||||||
@@ -241,6 +241,27 @@ app.registerExtension({
|
|||||||
// Merge the loras data
|
// Merge the loras data
|
||||||
const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras);
|
const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras);
|
||||||
node.lorasWidget.value = mergedLoras;
|
node.lorasWidget.value = mergedLoras;
|
||||||
|
|
||||||
|
// Initialize autocomplete after DOM is fully rendered
|
||||||
|
const inputWidget = node.inputWidget || node.widgets[0];
|
||||||
|
if (inputWidget && !node.autocomplete) {
|
||||||
|
const { setupInputWidgetWithAutocomplete } = await import("./utils.js");
|
||||||
|
const modelType = "loras";
|
||||||
|
const autocompleteOptions = {
|
||||||
|
maxItems: 20,
|
||||||
|
minChars: 1,
|
||||||
|
debounceDelay: 200,
|
||||||
|
};
|
||||||
|
// Fix: Assign the enhanced callback to replace the original
|
||||||
|
inputWidget.callback = setupInputWidgetWithAutocomplete(node, inputWidget, inputWidget.callback, modelType, autocompleteOptions);
|
||||||
|
|
||||||
|
// Eager initialization: trigger callback after short delay to ensure DOM is ready
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!node.autocomplete && inputWidget.callback) {
|
||||||
|
inputWidget.callback(inputWidget.value);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -146,12 +146,6 @@ app.registerExtension({
|
|||||||
isUpdating = false;
|
isUpdating = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inputWidget.callback = setupInputWidgetWithAutocomplete(
|
|
||||||
this,
|
|
||||||
inputWidget,
|
|
||||||
originalCallback
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -167,6 +161,18 @@ app.registerExtension({
|
|||||||
// Merge the loras data
|
// Merge the loras data
|
||||||
const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras);
|
const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras);
|
||||||
node.lorasWidget.value = mergedLoras;
|
node.lorasWidget.value = mergedLoras;
|
||||||
|
|
||||||
|
const inputWidget = node.inputWidget || node.widgets[0];
|
||||||
|
if (inputWidget && !node.autocomplete) {
|
||||||
|
const { setupInputWidgetWithAutocomplete } = await import("./utils.js");
|
||||||
|
const modelType = "loras";
|
||||||
|
const autocompleteOptions = {
|
||||||
|
maxItems: 20,
|
||||||
|
minChars: 1,
|
||||||
|
debounceDelay: 200,
|
||||||
|
};
|
||||||
|
inputWidget.callback = setupInputWidgetWithAutocomplete(node, inputWidget, inputWidget.callback, modelType, autocompleteOptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,4 +26,19 @@ app.registerExtension({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async loadedGraphNode(node) {
|
||||||
|
if (node.comfyClass == "Prompt (LoraManager)") {
|
||||||
|
const textWidget = node.widgets?.[0];
|
||||||
|
if (textWidget && !node.autocomplete) {
|
||||||
|
const { setupInputWidgetWithAutocomplete } = await import("./utils.js");
|
||||||
|
const modelType = "embeddings";
|
||||||
|
const autocompleteOptions = {
|
||||||
|
maxItems: 20,
|
||||||
|
minChars: 1,
|
||||||
|
debounceDelay: 200,
|
||||||
|
};
|
||||||
|
textWidget.callback = setupInputWidgetWithAutocomplete(node, textWidget, textWidget.callback, modelType, autocompleteOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -408,22 +408,55 @@ async function findWidgetInputElement(node, widget) {
|
|||||||
const doSearch = () => {
|
const doSearch = () => {
|
||||||
let inputElement = null;
|
let inputElement = null;
|
||||||
|
|
||||||
const allWidgetContainers = document.querySelectorAll('.lg-node-widget');
|
// PRIORITY 1: Use data-node-id attribute (most reliable)
|
||||||
|
// Always try this first, regardless of mode - Vue elements may still exist after mode switch
|
||||||
|
const nodeContainer = document.querySelector(`[data-node-id="${nodeId}"]`);
|
||||||
|
if (nodeContainer) {
|
||||||
|
// For text widgets, specifically look for textarea (not checkbox/toggle inputs)
|
||||||
|
if (widgetName === 'text') {
|
||||||
|
const textarea = nodeContainer.querySelector('textarea');
|
||||||
|
if (textarea) {
|
||||||
|
inputElement = textarea;
|
||||||
|
console.log(`[Lora Manager] Found textarea for widget "${widgetName}" on node ${nodeId} via data-node-id`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For other widgets, find input within widget containers
|
||||||
|
const widgetContainers = nodeContainer.querySelectorAll('.lg-node-widget');
|
||||||
|
for (const container of widgetContainers) {
|
||||||
|
const input = container.querySelector('input:not([type="checkbox"]), textarea');
|
||||||
|
if (input) {
|
||||||
|
inputElement = input;
|
||||||
|
console.log(`[Lora Manager] Found input element for widget "${widgetName}" on node ${nodeId} via data-node-id`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const container of allWidgetContainers) {
|
// PRIORITY 2: Fallback - heuristic search using widget containers
|
||||||
const hasInput = !!container.querySelector('input, textarea');
|
if (!inputElement) {
|
||||||
const textContent = container.textContent.toLowerCase();
|
const allWidgetContainers = document.querySelectorAll('.lg-node-widget, .dom-widget');
|
||||||
const containsWidgetName = textContent.includes(widgetName.toLowerCase());
|
|
||||||
const containsNodeTitle = textContent.includes(node.title?.toLowerCase() || '');
|
|
||||||
|
|
||||||
if (hasInput && (containsWidgetName || (widgetName === 'text' && container.querySelector('textarea')))) {
|
for (const container of allWidgetContainers) {
|
||||||
inputElement = container.querySelector('input, textarea');
|
const hasInput = !!container.querySelector('input, textarea');
|
||||||
break;
|
if (!hasInput) continue;
|
||||||
|
|
||||||
|
const textContent = container.textContent.toLowerCase();
|
||||||
|
const containsWidgetName = textContent.includes(widgetName.toLowerCase());
|
||||||
|
const containsNodeTitle = textContent.includes(node.title?.toLowerCase() || '');
|
||||||
|
|
||||||
|
// For text widgets, check if it's a textarea
|
||||||
|
const isTextareaWidget = widgetName === 'text' && container.querySelector('textarea');
|
||||||
|
|
||||||
|
if (containsWidgetName || containsNodeTitle || isTextareaWidget) {
|
||||||
|
inputElement = container.querySelector('input, textarea');
|
||||||
|
console.log(`[Lora Manager] Found input element for widget "${widgetName}" on node ${nodeId} via heuristic`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputElement) {
|
if (inputElement) {
|
||||||
console.log(`[Lora Manager] Found input element for widget "${widgetName}" on node ${nodeId}`);
|
|
||||||
resolve(inputElement);
|
resolve(inputElement);
|
||||||
} else if (attempt < maxAttempts) {
|
} else if (attempt < maxAttempts) {
|
||||||
setTimeout(() => searchForInput(attempt + 1).then(resolve), searchInterval);
|
setTimeout(() => searchForInput(attempt + 1).then(resolve), searchInterval);
|
||||||
@@ -449,32 +482,59 @@ async function findWidgetInputElement(node, widget) {
|
|||||||
* @returns {Function} Enhanced callback function with autocomplete
|
* @returns {Function} Enhanced callback function with autocomplete
|
||||||
*/
|
*/
|
||||||
export function setupInputWidgetWithAutocomplete(node, inputWidget, originalCallback, modelType = 'loras', autocompleteOptions = {}) {
|
export function setupInputWidgetWithAutocomplete(node, inputWidget, originalCallback, modelType = 'loras', autocompleteOptions = {}) {
|
||||||
let autocomplete = null;
|
|
||||||
let isInitializing = false;
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
maxItems: 20,
|
maxItems: 20,
|
||||||
minChars: 1,
|
minChars: 1,
|
||||||
debounceDelay: 200,
|
debounceDelay: 200,
|
||||||
};
|
};
|
||||||
const mergedOptions = { ...defaultOptions, ...autocompleteOptions };
|
const mergedOptions = { ...defaultOptions, ...autocompleteOptions };
|
||||||
|
|
||||||
|
setupAutocompleteCleanup(node);
|
||||||
|
|
||||||
|
// Track rendering mode changes per node
|
||||||
|
let lastVueNodesMode = typeof LiteGraph !== 'undefined' ? LiteGraph.vueNodesMode : false;
|
||||||
|
|
||||||
const initializeAutocomplete = async () => {
|
const initializeAutocomplete = async () => {
|
||||||
if (autocomplete || isInitializing) return;
|
if (node.autocomplete) {
|
||||||
isInitializing = true;
|
console.log(`[Lora Manager] Autocomplete already initialized for widget "${inputWidget.name}" on node ${node.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let inputElement = null;
|
let inputElement = null;
|
||||||
|
|
||||||
if (inputWidget.inputEl && document.body.contains(inputWidget.inputEl)) {
|
// PRIORITY 1: Always prefer widget.inputEl if it exists (even if not yet in DOM)
|
||||||
|
// This is the authoritative element created by ComfyUI
|
||||||
|
if (inputWidget.inputEl) {
|
||||||
inputElement = inputWidget.inputEl;
|
inputElement = inputWidget.inputEl;
|
||||||
console.log(`[Lora Manager] Using widget.inputEl for widget "${inputWidget.name}"`);
|
// If not yet in DOM, wait for it to be added
|
||||||
} else {
|
if (!document.body.contains(inputElement)) {
|
||||||
console.log(`[Lora Manager] Searching DOM for input element for widget "${inputWidget.name}"`);
|
console.log(`[Lora Manager] widget.inputEl exists but not in DOM yet, waiting for node ${node.id}`);
|
||||||
|
const maxWait = 1000; // 1 second max
|
||||||
|
const checkInterval = 50;
|
||||||
|
let waited = 0;
|
||||||
|
while (!document.body.contains(inputElement) && waited < maxWait) {
|
||||||
|
await new Promise(r => setTimeout(r, checkInterval));
|
||||||
|
waited += checkInterval;
|
||||||
|
}
|
||||||
|
if (!document.body.contains(inputElement)) {
|
||||||
|
console.warn(`[Lora Manager] widget.inputEl still not in DOM after ${maxWait}ms for node ${node.id}`);
|
||||||
|
inputElement = null; // Fall through to DOM search
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inputElement) {
|
||||||
|
console.log(`[Lora Manager] Using widget.inputEl for widget "${inputWidget.name}" on node ${node.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIORITY 2: DOM search only if widget.inputEl doesn't exist
|
||||||
|
if (!inputElement) {
|
||||||
|
console.log(`[Lora Manager] Searching DOM for input element for widget "${inputWidget.name}" on node ${node.id}`);
|
||||||
inputElement = await findWidgetInputElement(node, inputWidget);
|
inputElement = await findWidgetInputElement(node, inputWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputElement) {
|
if (inputElement) {
|
||||||
autocomplete = new AutoComplete(inputElement, modelType, mergedOptions);
|
const autocomplete = new AutoComplete(inputElement, modelType, mergedOptions);
|
||||||
node.autocomplete = autocomplete;
|
node.autocomplete = autocomplete;
|
||||||
console.log(`[Lora Manager] Autocomplete initialized for widget "${inputWidget.name}" on node ${node.id}`);
|
console.log(`[Lora Manager] Autocomplete initialized for widget "${inputWidget.name}" on node ${node.id}`);
|
||||||
} else {
|
} else {
|
||||||
@@ -482,23 +542,65 @@ export function setupInputWidgetWithAutocomplete(node, inputWidget, originalCall
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Lora Manager] Error initializing autocomplete:', error);
|
console.error('[Lora Manager] Error initializing autocomplete:', error);
|
||||||
} finally {
|
|
||||||
isInitializing = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkAndInvalidateAutocomplete = () => {
|
||||||
|
// Check for rendering mode change
|
||||||
|
const currentMode = typeof LiteGraph !== 'undefined' ? LiteGraph.vueNodesMode : false;
|
||||||
|
if (currentMode !== lastVueNodesMode) {
|
||||||
|
lastVueNodesMode = currentMode;
|
||||||
|
if (node.autocomplete) {
|
||||||
|
console.log(`[Lora Manager] Rendering mode changed, reinitializing autocomplete for node ${node.id}`);
|
||||||
|
node.autocomplete.destroy();
|
||||||
|
node.autocomplete = null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if existing autocomplete's input element is still valid
|
||||||
|
if (node.autocomplete) {
|
||||||
|
const currentInputEl = node.autocomplete.inputElement;
|
||||||
|
if (!currentInputEl || !document.body.contains(currentInputEl)) {
|
||||||
|
console.log(`[Lora Manager] Autocomplete element detached, reinitializing for node ${node.id}`);
|
||||||
|
node.autocomplete.destroy();
|
||||||
|
node.autocomplete = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if autocomplete is bound to wrong element (different from widget.inputEl)
|
||||||
|
// Only do this check if widget.inputEl is actually in the DOM - it may be stale
|
||||||
|
if (inputWidget.inputEl && document.body.contains(inputWidget.inputEl) && currentInputEl !== inputWidget.inputEl) {
|
||||||
|
console.log(`[Lora Manager] Autocomplete bound to wrong element, rebinding for node ${node.id}`);
|
||||||
|
node.autocomplete.destroy();
|
||||||
|
node.autocomplete = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if events need rebinding (element exists but events not bound)
|
||||||
|
// This can happen when Vue moves the element in the DOM
|
||||||
|
if (node.autocomplete.needsRebind()) {
|
||||||
|
console.log(`[Lora Manager] Autocomplete events need rebinding for node ${node.id}`);
|
||||||
|
node.autocomplete.rebindEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const enhancedCallback = (value) => {
|
const enhancedCallback = (value) => {
|
||||||
if (!autocomplete && !isInitializing) {
|
// Check validity and invalidate if needed
|
||||||
|
checkAndInvalidateAutocomplete();
|
||||||
|
|
||||||
|
if (!node.autocomplete) {
|
||||||
initializeAutocomplete();
|
initializeAutocomplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof originalCallback === "function") {
|
if (typeof originalCallback === "function") {
|
||||||
originalCallback.call(node, value);
|
originalCallback.call(node, value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setupAutocompleteCleanup(node);
|
|
||||||
|
|
||||||
return enhancedCallback;
|
return enhancedCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -119,6 +119,18 @@ app.registerExtension({
|
|||||||
// Merge the loras data
|
// Merge the loras data
|
||||||
const mergedLoras = mergeLoras(node.widgets[2].value, existingLoras);
|
const mergedLoras = mergeLoras(node.widgets[2].value, existingLoras);
|
||||||
node.lorasWidget.value = mergedLoras;
|
node.lorasWidget.value = mergedLoras;
|
||||||
|
|
||||||
|
const inputWidget = node.inputWidget || node.widgets[2];
|
||||||
|
if (inputWidget && !node.autocomplete) {
|
||||||
|
const { setupInputWidgetWithAutocomplete } = await import("./utils.js");
|
||||||
|
const modelType = "loras";
|
||||||
|
const autocompleteOptions = {
|
||||||
|
maxItems: 20,
|
||||||
|
minChars: 1,
|
||||||
|
debounceDelay: 200,
|
||||||
|
};
|
||||||
|
inputWidget.callback = setupInputWidgetWithAutocomplete(node, inputWidget, inputWidget.callback, modelType, autocompleteOptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user