mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-23 14:12:11 -03:00
refactor: replace comfy built-in text widget with custome autocomplete text widget for better event handler binding
- Change `STRING` input type to `AUTOCOMPLETE_TEXT_LORAS` in LoraManagerLoader, LoraStacker, and WanVideoLoraSelectLM nodes for LoRA syntax input - Change `STRING` input type to `AUTOCOMPLETE_TEXT_EMBEDDINGS` in PromptLoraManager node for prompt input - Remove manual multiline, autocomplete, and dynamicPrompts configurations in favor of built-in autocomplete types - Update placeholder text for consistency across nodes - Remove unused `setupInputWidgetWithAutocomplete` mock from frontend tests - Add Vue app cleanup logic to prevent memory leaks in widget management
This commit is contained in:
@@ -5,7 +5,6 @@ import {
|
||||
updateConnectedTriggerWords,
|
||||
chainCallback,
|
||||
mergeLoras,
|
||||
setupInputWidgetWithAutocomplete,
|
||||
getAllGraphNodes,
|
||||
getNodeFromGraph,
|
||||
} from "./utils.js";
|
||||
@@ -129,12 +128,12 @@ app.registerExtension({
|
||||
set(value) {
|
||||
const oldValue = _mode;
|
||||
_mode = value;
|
||||
|
||||
|
||||
// Trigger mode change handler
|
||||
if (self.onModeChange) {
|
||||
self.onModeChange(value, oldValue);
|
||||
}
|
||||
|
||||
|
||||
console.log(`[Lora Loader] Node mode changed from ${oldValue} to ${value}`);
|
||||
}
|
||||
});
|
||||
@@ -142,14 +141,14 @@ app.registerExtension({
|
||||
// Define the mode change handler
|
||||
this.onModeChange = function(newMode, oldMode) {
|
||||
console.log(`Lora Loader node mode changed: from ${oldMode} to ${newMode}`);
|
||||
|
||||
|
||||
// Update connected trigger word toggle nodes when mode changes
|
||||
const allActiveLoraNames = collectActiveLorasFromChain(self);
|
||||
updateConnectedTriggerWords(self, allActiveLoraNames);
|
||||
};
|
||||
|
||||
// Get the text input widget (AUTOCOMPLETE_TEXT_LORAS type, created by Vue widgets)
|
||||
const inputWidget = this.widgets[0];
|
||||
inputWidget.options.getMaxHeight = () => 100;
|
||||
this.inputWidget = inputWidget;
|
||||
|
||||
const scheduleInputSync = debounce((lorasValue) => {
|
||||
@@ -202,7 +201,8 @@ app.registerExtension({
|
||||
}
|
||||
).widget;
|
||||
|
||||
const originalCallback = (value) => {
|
||||
// Set up callback for the text input widget to trigger merge logic
|
||||
inputWidget.callback = (value) => {
|
||||
if (isUpdating) return;
|
||||
isUpdating = true;
|
||||
|
||||
@@ -218,13 +218,6 @@ app.registerExtension({
|
||||
isUpdating = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Setup input widget with autocomplete
|
||||
inputWidget.callback = setupInputWidgetWithAutocomplete(
|
||||
this,
|
||||
inputWidget,
|
||||
originalCallback
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -241,27 +234,6 @@ app.registerExtension({
|
||||
// Merge the loras data
|
||||
const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras);
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import {
|
||||
getActiveLorasFromNode,
|
||||
collectActiveLorasFromChain,
|
||||
updateConnectedTriggerWords,
|
||||
updateDownstreamLoaders,
|
||||
chainCallback,
|
||||
mergeLoras,
|
||||
setupInputWidgetWithAutocomplete,
|
||||
getLinkFromGraph,
|
||||
getNodeKey,
|
||||
} from "./utils.js";
|
||||
import { addLorasWidget } from "./loras_widget.js";
|
||||
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
||||
@@ -31,8 +27,8 @@ app.registerExtension({
|
||||
let isUpdating = false;
|
||||
let isSyncingInput = false;
|
||||
|
||||
// Get the text input widget (AUTOCOMPLETE_TEXT_LORAS type, created by Vue widgets)
|
||||
const inputWidget = this.widgets[0];
|
||||
inputWidget.options.getMaxHeight = () => 100;
|
||||
this.inputWidget = inputWidget;
|
||||
|
||||
const scheduleInputSync = debounce((lorasValue) => {
|
||||
@@ -95,39 +91,32 @@ app.registerExtension({
|
||||
|
||||
this.lorasWidget = result.widget;
|
||||
|
||||
// Wrap the callback with autocomplete setup
|
||||
const originalCallback = (value) => {
|
||||
// Set up callback for the text input widget to trigger merge logic
|
||||
inputWidget.callback = (value) => {
|
||||
if (isUpdating) return;
|
||||
isUpdating = true;
|
||||
|
||||
try {
|
||||
const currentLoras = this.lorasWidget.value || [];
|
||||
const currentLoras = this.lorasWidget?.value || [];
|
||||
const mergedLoras = mergeLoras(value, currentLoras);
|
||||
|
||||
this.lorasWidget.value = mergedLoras;
|
||||
|
||||
if (this.lorasWidget) {
|
||||
this.lorasWidget.value = mergedLoras;
|
||||
}
|
||||
// Update this stacker's direct trigger toggles with its own active loras
|
||||
// Only if the stacker node itself is active (mode 0 for Always, mode 3 for On Trigger)
|
||||
const isNodeActive = this.mode === undefined || this.mode === 0 || this.mode === 3;
|
||||
const activeLoraNames = isNodeActive ? getActiveLorasFromNode(this) : new Set();
|
||||
updateConnectedTriggerWords(this, activeLoraNames);
|
||||
|
||||
// Find all Lora Loader nodes in the chain that might need updates
|
||||
updateDownstreamLoaders(this);
|
||||
} finally {
|
||||
isUpdating = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Setup input widget with autocomplete
|
||||
inputWidget.callback = setupInputWidgetWithAutocomplete(
|
||||
this,
|
||||
inputWidget,
|
||||
originalCallback
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async loadedGraphNode(node) {
|
||||
if (node.comfyClass == "Lora Stacker (LoraManager)") {
|
||||
// Restore saved value if exists
|
||||
@@ -138,20 +127,9 @@ app.registerExtension({
|
||||
existingLoras = savedValue || [];
|
||||
}
|
||||
// Merge the loras data
|
||||
const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras);
|
||||
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);
|
||||
}
|
||||
const mergedLoras = mergeLoras(inputWidget.value, existingLoras);
|
||||
node.lorasWidget.value = mergedLoras;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { chainCallback, setupInputWidgetWithAutocomplete } from "./utils.js";
|
||||
import { chainCallback } from "./utils.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "LoraManager.Prompt",
|
||||
@@ -9,36 +9,12 @@ app.registerExtension({
|
||||
chainCallback(nodeType.prototype, "onNodeCreated", function () {
|
||||
this.serialize_widgets = true;
|
||||
|
||||
const textWidget = this.widgets?.[0];
|
||||
if (!textWidget) {
|
||||
return;
|
||||
// Get the text input widget (AUTOCOMPLETE_TEXT_EMBEDDINGS type, created by Vue widgets)
|
||||
const inputWidget = this.widgets?.[0];
|
||||
if (inputWidget) {
|
||||
this.inputWidget = inputWidget;
|
||||
}
|
||||
|
||||
const originalCallback =
|
||||
typeof textWidget.callback === "function" ? textWidget.callback : null;
|
||||
|
||||
textWidget.callback = setupInputWidgetWithAutocomplete(
|
||||
this,
|
||||
textWidget,
|
||||
originalCallback,
|
||||
"embeddings"
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -4,7 +4,6 @@ import {
|
||||
updateConnectedTriggerWords,
|
||||
chainCallback,
|
||||
mergeLoras,
|
||||
setupInputWidgetWithAutocomplete,
|
||||
} from "./utils.js";
|
||||
import { addLorasWidget } from "./loras_widget.js";
|
||||
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
||||
@@ -31,8 +30,8 @@ app.registerExtension({
|
||||
let isUpdating = false;
|
||||
let isSyncingInput = false;
|
||||
|
||||
// Get the text input widget (AUTOCOMPLETE_TEXT_LORAS type, at index 2 after low_mem_load and merge_loras)
|
||||
const inputWidget = this.widgets[2];
|
||||
inputWidget.options.getMaxHeight = () => 100;
|
||||
this.inputWidget = inputWidget;
|
||||
|
||||
const scheduleInputSync = debounce((lorasValue) => {
|
||||
@@ -81,17 +80,17 @@ app.registerExtension({
|
||||
|
||||
this.lorasWidget = result.widget;
|
||||
|
||||
// Wrap the callback with autocomplete setup
|
||||
const originalCallback = (value) => {
|
||||
// Set up callback for the text input widget to trigger merge logic
|
||||
inputWidget.callback = (value) => {
|
||||
if (isUpdating) return;
|
||||
isUpdating = true;
|
||||
|
||||
try {
|
||||
const currentLoras = this.lorasWidget.value || [];
|
||||
const currentLoras = this.lorasWidget?.value || [];
|
||||
const mergedLoras = mergeLoras(value, currentLoras);
|
||||
|
||||
this.lorasWidget.value = mergedLoras;
|
||||
|
||||
if (this.lorasWidget) {
|
||||
this.lorasWidget.value = mergedLoras;
|
||||
}
|
||||
// Update this node's direct trigger toggles with its own active loras
|
||||
const activeLoraNames = getActiveLorasFromNode(this);
|
||||
updateConnectedTriggerWords(this, activeLoraNames);
|
||||
@@ -99,14 +98,10 @@ app.registerExtension({
|
||||
isUpdating = false;
|
||||
}
|
||||
};
|
||||
inputWidget.callback = setupInputWidgetWithAutocomplete(
|
||||
this,
|
||||
inputWidget,
|
||||
originalCallback
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async loadedGraphNode(node) {
|
||||
if (node.comfyClass == "WanVideo Lora Select (LoraManager)") {
|
||||
// Restore saved value if exists
|
||||
@@ -117,20 +112,9 @@ app.registerExtension({
|
||||
existingLoras = savedValue || [];
|
||||
}
|
||||
// Merge the loras data
|
||||
const mergedLoras = mergeLoras(node.widgets[2].value, existingLoras);
|
||||
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);
|
||||
}
|
||||
const mergedLoras = mergeLoras(inputWidget.value, existingLoras);
|
||||
node.lorasWidget.value = mergedLoras;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user