mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-05-09 09:56:44 -03:00
Compare commits
2 Commits
12bbb0572d
...
301ab14781
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
301ab14781 | ||
|
|
2626dbab8e |
@@ -7,6 +7,7 @@ try: # pragma: no cover - import fallback for pytest collection
|
|||||||
from .py.nodes.prompt import PromptLM
|
from .py.nodes.prompt import PromptLM
|
||||||
from .py.nodes.text import TextLM
|
from .py.nodes.text import TextLM
|
||||||
from .py.nodes.lora_stacker import LoraStackerLM
|
from .py.nodes.lora_stacker import LoraStackerLM
|
||||||
|
from .py.nodes.lora_stack_combiner import LoraStackCombinerLM
|
||||||
from .py.nodes.save_image import SaveImageLM
|
from .py.nodes.save_image import SaveImageLM
|
||||||
from .py.nodes.debug_metadata import DebugMetadataLM
|
from .py.nodes.debug_metadata import DebugMetadataLM
|
||||||
from .py.nodes.wanvideo_lora_select import WanVideoLoraSelectLM
|
from .py.nodes.wanvideo_lora_select import WanVideoLoraSelectLM
|
||||||
@@ -39,6 +40,9 @@ except (
|
|||||||
"py.nodes.trigger_word_toggle"
|
"py.nodes.trigger_word_toggle"
|
||||||
).TriggerWordToggleLM
|
).TriggerWordToggleLM
|
||||||
LoraStackerLM = importlib.import_module("py.nodes.lora_stacker").LoraStackerLM
|
LoraStackerLM = importlib.import_module("py.nodes.lora_stacker").LoraStackerLM
|
||||||
|
LoraStackCombinerLM = importlib.import_module(
|
||||||
|
"py.nodes.lora_stack_combiner"
|
||||||
|
).LoraStackCombinerLM
|
||||||
SaveImageLM = importlib.import_module("py.nodes.save_image").SaveImageLM
|
SaveImageLM = importlib.import_module("py.nodes.save_image").SaveImageLM
|
||||||
DebugMetadataLM = importlib.import_module("py.nodes.debug_metadata").DebugMetadataLM
|
DebugMetadataLM = importlib.import_module("py.nodes.debug_metadata").DebugMetadataLM
|
||||||
WanVideoLoraSelectLM = importlib.import_module(
|
WanVideoLoraSelectLM = importlib.import_module(
|
||||||
@@ -63,6 +67,7 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
UNETLoaderLM.NAME: UNETLoaderLM,
|
UNETLoaderLM.NAME: UNETLoaderLM,
|
||||||
TriggerWordToggleLM.NAME: TriggerWordToggleLM,
|
TriggerWordToggleLM.NAME: TriggerWordToggleLM,
|
||||||
LoraStackerLM.NAME: LoraStackerLM,
|
LoraStackerLM.NAME: LoraStackerLM,
|
||||||
|
LoraStackCombinerLM.NAME: LoraStackCombinerLM,
|
||||||
SaveImageLM.NAME: SaveImageLM,
|
SaveImageLM.NAME: SaveImageLM,
|
||||||
DebugMetadataLM.NAME: DebugMetadataLM,
|
DebugMetadataLM.NAME: DebugMetadataLM,
|
||||||
WanVideoLoraSelectLM.NAME: WanVideoLoraSelectLM,
|
WanVideoLoraSelectLM.NAME: WanVideoLoraSelectLM,
|
||||||
|
|||||||
26
py/nodes/lora_stack_combiner.py
Normal file
26
py/nodes/lora_stack_combiner.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
class LoraStackCombinerLM:
|
||||||
|
NAME = "Lora Stack Combiner (LoraManager)"
|
||||||
|
CATEGORY = "Lora Manager/stackers"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"lora_stack_a": ("LORA_STACK",),
|
||||||
|
"lora_stack_b": ("LORA_STACK",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("LORA_STACK",)
|
||||||
|
RETURN_NAMES = ("LORA_STACK",)
|
||||||
|
FUNCTION = "combine_stacks"
|
||||||
|
|
||||||
|
def combine_stacks(self, lora_stack_a, lora_stack_b):
|
||||||
|
combined_stack = []
|
||||||
|
|
||||||
|
if lora_stack_a:
|
||||||
|
combined_stack.extend(lora_stack_a)
|
||||||
|
if lora_stack_b:
|
||||||
|
combined_stack.extend(lora_stack_b)
|
||||||
|
|
||||||
|
return (combined_stack,)
|
||||||
@@ -37,6 +37,13 @@ const updateConnectedTriggerWords = vi.fn();
|
|||||||
const mergeLoras = vi.fn();
|
const mergeLoras = vi.fn();
|
||||||
const getAllGraphNodes = vi.fn();
|
const getAllGraphNodes = vi.fn();
|
||||||
const getNodeFromGraph = vi.fn();
|
const getNodeFromGraph = vi.fn();
|
||||||
|
const getWidgetByName = vi.fn((node, name) =>
|
||||||
|
node?.widgets?.find((widget) => widget?.name === name) ?? null
|
||||||
|
);
|
||||||
|
const getWidgetSerializedValue = vi.fn((node, name) => {
|
||||||
|
const index = node?.widgets?.findIndex((widget) => widget?.name === name) ?? -1;
|
||||||
|
return index >= 0 ? node.widgets_values?.[index] : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock(UTILS_MODULE, () => ({
|
vi.mock(UTILS_MODULE, () => ({
|
||||||
collectActiveLorasFromChain,
|
collectActiveLorasFromChain,
|
||||||
@@ -47,6 +54,8 @@ vi.mock(UTILS_MODULE, () => ({
|
|||||||
},
|
},
|
||||||
getAllGraphNodes,
|
getAllGraphNodes,
|
||||||
getNodeFromGraph,
|
getNodeFromGraph,
|
||||||
|
getWidgetByName,
|
||||||
|
getWidgetSerializedValue,
|
||||||
LORA_PATTERN: /<lora:([^:]+):([-\d.]+)(?::([-\d.]+))?>/g,
|
LORA_PATTERN: /<lora:([^:]+):([-\d.]+)(?::([-\d.]+))?>/g,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -71,6 +80,9 @@ describe("Lora Loader trigger word updates", () => {
|
|||||||
mergeLoras.mockClear();
|
mergeLoras.mockClear();
|
||||||
mergeLoras.mockImplementation(() => [{ name: "Alpha", active: true }]);
|
mergeLoras.mockImplementation(() => [{ name: "Alpha", active: true }]);
|
||||||
|
|
||||||
|
getWidgetByName.mockClear();
|
||||||
|
getWidgetSerializedValue.mockClear();
|
||||||
|
|
||||||
addLorasWidget.mockClear();
|
addLorasWidget.mockClear();
|
||||||
addLorasWidget.mockImplementation((_node, _name, _opts, callback) => ({
|
addLorasWidget.mockImplementation((_node, _name, _opts, callback) => ({
|
||||||
widget: { value: [], callback },
|
widget: { value: [], callback },
|
||||||
@@ -89,14 +101,21 @@ describe("Lora Loader trigger word updates", () => {
|
|||||||
|
|
||||||
// Create mock widget (AUTOCOMPLETE_TEXT_LORAS type created by Vue widgets)
|
// Create mock widget (AUTOCOMPLETE_TEXT_LORAS type created by Vue widgets)
|
||||||
const inputWidget = {
|
const inputWidget = {
|
||||||
|
name: "text",
|
||||||
value: "",
|
value: "",
|
||||||
options: {},
|
options: {},
|
||||||
callback: null, // Will be set by onNodeCreated
|
callback: null, // Will be set by onNodeCreated
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const metadataWidget = {
|
||||||
|
name: "__autocomplete_metadata_text",
|
||||||
|
value: { version: 1, textWidgetName: "text" },
|
||||||
|
options: {},
|
||||||
|
};
|
||||||
|
|
||||||
const node = {
|
const node = {
|
||||||
comfyClass: "Lora Loader (LoraManager)",
|
comfyClass: "Lora Loader (LoraManager)",
|
||||||
widgets: [inputWidget],
|
widgets: [metadataWidget, inputWidget],
|
||||||
addInput: vi.fn(),
|
addInput: vi.fn(),
|
||||||
graph: {},
|
graph: {},
|
||||||
};
|
};
|
||||||
@@ -106,6 +125,7 @@ describe("Lora Loader trigger word updates", () => {
|
|||||||
// The widget is now the AUTOCOMPLETE_TEXT_LORAS type, created automatically by Vue widgets
|
// The widget is now the AUTOCOMPLETE_TEXT_LORAS type, created automatically by Vue widgets
|
||||||
expect(node.inputWidget).toBe(inputWidget);
|
expect(node.inputWidget).toBe(inputWidget);
|
||||||
expect(node.lorasWidget).toBeDefined();
|
expect(node.lorasWidget).toBeDefined();
|
||||||
|
expect(getWidgetByName).toHaveBeenCalledWith(node, "text");
|
||||||
|
|
||||||
// The callback should have been set up by onNodeCreated
|
// The callback should have been set up by onNodeCreated
|
||||||
const inputCallback = inputWidget.callback;
|
const inputCallback = inputWidget.callback;
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ const getAllGraphNodes = vi.fn();
|
|||||||
const getNodeFromGraph = vi.fn();
|
const getNodeFromGraph = vi.fn();
|
||||||
const getNodeKey = vi.fn();
|
const getNodeKey = vi.fn();
|
||||||
const getLinkFromGraph = vi.fn();
|
const getLinkFromGraph = vi.fn();
|
||||||
|
const getWidgetByName = vi.fn((node, name) =>
|
||||||
|
node?.widgets?.find((widget) => widget?.name === name) ?? null
|
||||||
|
);
|
||||||
|
const getWidgetSerializedValue = vi.fn((node, name) => {
|
||||||
|
const index = node?.widgets?.findIndex((widget) => widget?.name === name) ?? -1;
|
||||||
|
return index >= 0 ? node.widgets_values?.[index] : undefined;
|
||||||
|
});
|
||||||
const chainCallback = vi.fn((proto, property, callback) => {
|
const chainCallback = vi.fn((proto, property, callback) => {
|
||||||
proto[property] = callback;
|
proto[property] = callback;
|
||||||
});
|
});
|
||||||
@@ -68,6 +75,8 @@ vi.mock(UTILS_MODULE, async (importOriginal) => {
|
|||||||
getNodeFromGraph,
|
getNodeFromGraph,
|
||||||
getNodeKey,
|
getNodeKey,
|
||||||
getLinkFromGraph,
|
getLinkFromGraph,
|
||||||
|
getWidgetByName,
|
||||||
|
getWidgetSerializedValue,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -98,6 +107,9 @@ describe("Node mode change handling", () => {
|
|||||||
mergeLoras.mockClear();
|
mergeLoras.mockClear();
|
||||||
mergeLoras.mockImplementation(() => [{ name: "Alpha", active: true }]);
|
mergeLoras.mockImplementation(() => [{ name: "Alpha", active: true }]);
|
||||||
|
|
||||||
|
getWidgetByName.mockClear();
|
||||||
|
getWidgetSerializedValue.mockClear();
|
||||||
|
|
||||||
addLorasWidget.mockClear();
|
addLorasWidget.mockClear();
|
||||||
addLorasWidget.mockImplementation((_node, _name, _opts, callback) => ({
|
addLorasWidget.mockImplementation((_node, _name, _opts, callback) => ({
|
||||||
widget: { value: [], callback },
|
widget: { value: [], callback },
|
||||||
@@ -119,8 +131,13 @@ describe("Node mode change handling", () => {
|
|||||||
|
|
||||||
await extension.beforeRegisterNodeDef(nodeType, nodeData, {});
|
await extension.beforeRegisterNodeDef(nodeType, nodeData, {});
|
||||||
|
|
||||||
// Create widgets with proper structure for lora_stacker.js
|
// Include a hidden metadata widget ahead of the actual text widget to match runtime ordering.
|
||||||
// Widget at index 0 is the AUTOCOMPLETE_TEXT_LORAS widget (created by Vue widgets)
|
const metadataWidget = {
|
||||||
|
name: "__autocomplete_metadata_text",
|
||||||
|
value: { version: 1, textWidgetName: "text" },
|
||||||
|
options: {},
|
||||||
|
};
|
||||||
|
|
||||||
const inputWidget = {
|
const inputWidget = {
|
||||||
name: "text",
|
name: "text",
|
||||||
value: "",
|
value: "",
|
||||||
@@ -139,7 +156,7 @@ describe("Node mode change handling", () => {
|
|||||||
|
|
||||||
node = {
|
node = {
|
||||||
comfyClass: "Lora Stacker (LoraManager)",
|
comfyClass: "Lora Stacker (LoraManager)",
|
||||||
widgets: [inputWidget, lorasWidget],
|
widgets: [metadataWidget, inputWidget, lorasWidget],
|
||||||
lorasWidget,
|
lorasWidget,
|
||||||
addInput: vi.fn(),
|
addInput: vi.fn(),
|
||||||
mode: 0, // Initial mode
|
mode: 0, // Initial mode
|
||||||
@@ -189,11 +206,18 @@ describe("Node mode change handling", () => {
|
|||||||
const nodeType = { comfyClass: "Lora Loader (LoraManager)", prototype: {} };
|
const nodeType = { comfyClass: "Lora Loader (LoraManager)", prototype: {} };
|
||||||
await extension.beforeRegisterNodeDef(nodeType, {}, {});
|
await extension.beforeRegisterNodeDef(nodeType, {}, {});
|
||||||
|
|
||||||
// Widget at index 0 is the AUTOCOMPLETE_TEXT_LORAS widget (created by Vue widgets)
|
const metadataWidget = {
|
||||||
|
name: "__autocomplete_metadata_text",
|
||||||
|
value: { version: 1, textWidgetName: "text" },
|
||||||
|
options: {},
|
||||||
|
};
|
||||||
|
|
||||||
node = {
|
node = {
|
||||||
comfyClass: "Lora Loader (LoraManager)",
|
comfyClass: "Lora Loader (LoraManager)",
|
||||||
widgets: [
|
widgets: [
|
||||||
|
metadataWidget,
|
||||||
{
|
{
|
||||||
|
name: "text",
|
||||||
value: "",
|
value: "",
|
||||||
options: {},
|
options: {},
|
||||||
callback: null, // Will be set by onNodeCreated
|
callback: null, // Will be set by onNodeCreated
|
||||||
|
|||||||
51
tests/nodes/test_lora_stack_combiner.py
Normal file
51
tests/nodes/test_lora_stack_combiner.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from py.nodes.lora_stack_combiner import LoraStackCombinerLM
|
||||||
|
|
||||||
|
|
||||||
|
def test_combine_stacks_preserves_order():
|
||||||
|
node = LoraStackCombinerLM()
|
||||||
|
stack_a = [
|
||||||
|
("folder/a.safetensors", 0.7, 0.6),
|
||||||
|
("folder/b.safetensors", 0.8, 0.8),
|
||||||
|
]
|
||||||
|
stack_b = [
|
||||||
|
("folder/c.safetensors", 1.0, 0.9),
|
||||||
|
]
|
||||||
|
|
||||||
|
(combined_stack,) = node.combine_stacks(stack_a, stack_b)
|
||||||
|
|
||||||
|
assert combined_stack == stack_a + stack_b
|
||||||
|
|
||||||
|
|
||||||
|
def test_combine_stacks_returns_second_when_first_empty():
|
||||||
|
node = LoraStackCombinerLM()
|
||||||
|
stack_b = [("folder/c.safetensors", 1.0, 0.9)]
|
||||||
|
|
||||||
|
(combined_stack,) = node.combine_stacks([], stack_b)
|
||||||
|
|
||||||
|
assert combined_stack == stack_b
|
||||||
|
|
||||||
|
|
||||||
|
def test_combine_stacks_returns_first_when_second_empty():
|
||||||
|
node = LoraStackCombinerLM()
|
||||||
|
stack_a = [("folder/a.safetensors", 0.7, 0.6)]
|
||||||
|
|
||||||
|
(combined_stack,) = node.combine_stacks(stack_a, [])
|
||||||
|
|
||||||
|
assert combined_stack == stack_a
|
||||||
|
|
||||||
|
|
||||||
|
def test_combine_stacks_returns_empty_when_both_empty():
|
||||||
|
node = LoraStackCombinerLM()
|
||||||
|
|
||||||
|
(combined_stack,) = node.combine_stacks([], [])
|
||||||
|
|
||||||
|
assert combined_stack == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_combine_stacks_allows_duplicate_entries():
|
||||||
|
node = LoraStackCombinerLM()
|
||||||
|
duplicate_entry = ("folder/shared.safetensors", 0.9, 0.5)
|
||||||
|
|
||||||
|
(combined_stack,) = node.combine_stacks([duplicate_entry], [duplicate_entry])
|
||||||
|
|
||||||
|
assert combined_stack == [duplicate_entry, duplicate_entry]
|
||||||
@@ -7,6 +7,8 @@ import {
|
|||||||
mergeLoras,
|
mergeLoras,
|
||||||
getAllGraphNodes,
|
getAllGraphNodes,
|
||||||
getNodeFromGraph,
|
getNodeFromGraph,
|
||||||
|
getWidgetByName,
|
||||||
|
getWidgetSerializedValue,
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { addLorasWidget } from "./loras_widget.js";
|
import { addLorasWidget } from "./loras_widget.js";
|
||||||
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
||||||
@@ -148,7 +150,11 @@ app.registerExtension({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get the text input widget (AUTOCOMPLETE_TEXT_LORAS type, created by Vue widgets)
|
// Get the text input widget (AUTOCOMPLETE_TEXT_LORAS type, created by Vue widgets)
|
||||||
const inputWidget = this.widgets[0];
|
const inputWidget = getWidgetByName(this, "text");
|
||||||
|
if (!inputWidget) {
|
||||||
|
console.warn("LoRA Manager: text widget not found for Lora Loader");
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.inputWidget = inputWidget;
|
this.inputWidget = inputWidget;
|
||||||
|
|
||||||
const scheduleInputSync = debounce((lorasValue) => {
|
const scheduleInputSync = debounce((lorasValue) => {
|
||||||
@@ -227,12 +233,16 @@ app.registerExtension({
|
|||||||
// Restore saved value if exists
|
// Restore saved value if exists
|
||||||
let existingLoras = [];
|
let existingLoras = [];
|
||||||
if (node.widgets_values && node.widgets_values.length > 0) {
|
if (node.widgets_values && node.widgets_values.length > 0) {
|
||||||
// 0 for input widget, 1 for loras widget
|
const savedValue = getWidgetSerializedValue(node, "loras");
|
||||||
const savedValue = node.widgets_values[1];
|
|
||||||
existingLoras = savedValue || [];
|
existingLoras = savedValue || [];
|
||||||
}
|
}
|
||||||
// Merge the loras data
|
// Merge the loras data
|
||||||
const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras);
|
const inputWidget = node.inputWidget || getWidgetByName(node, "text");
|
||||||
|
if (!inputWidget) {
|
||||||
|
console.warn("LoRA Manager: text widget not found while restoring Lora Loader");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const mergedLoras = mergeLoras(inputWidget.value, existingLoras);
|
||||||
node.lorasWidget.value = mergedLoras;
|
node.lorasWidget.value = mergedLoras;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
updateDownstreamLoaders,
|
updateDownstreamLoaders,
|
||||||
chainCallback,
|
chainCallback,
|
||||||
mergeLoras,
|
mergeLoras,
|
||||||
|
getWidgetByName,
|
||||||
|
getWidgetSerializedValue,
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { addLorasWidget } from "./loras_widget.js";
|
import { addLorasWidget } from "./loras_widget.js";
|
||||||
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
||||||
@@ -28,7 +30,11 @@ app.registerExtension({
|
|||||||
let isSyncingInput = false;
|
let isSyncingInput = false;
|
||||||
|
|
||||||
// Get the text input widget (AUTOCOMPLETE_TEXT_LORAS type, created by Vue widgets)
|
// Get the text input widget (AUTOCOMPLETE_TEXT_LORAS type, created by Vue widgets)
|
||||||
const inputWidget = this.widgets[0];
|
const inputWidget = getWidgetByName(this, "text");
|
||||||
|
if (!inputWidget) {
|
||||||
|
console.warn("LoRA Manager: text widget not found for Lora Stacker");
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.inputWidget = inputWidget;
|
this.inputWidget = inputWidget;
|
||||||
|
|
||||||
const scheduleInputSync = debounce((lorasValue) => {
|
const scheduleInputSync = debounce((lorasValue) => {
|
||||||
@@ -122,12 +128,15 @@ app.registerExtension({
|
|||||||
// Restore saved value if exists
|
// Restore saved value if exists
|
||||||
let existingLoras = [];
|
let existingLoras = [];
|
||||||
if (node.widgets_values && node.widgets_values.length > 0) {
|
if (node.widgets_values && node.widgets_values.length > 0) {
|
||||||
// 0 for input widget, 1 for loras widget
|
const savedValue = getWidgetSerializedValue(node, "loras");
|
||||||
const savedValue = node.widgets_values[1];
|
|
||||||
existingLoras = savedValue || [];
|
existingLoras = savedValue || [];
|
||||||
}
|
}
|
||||||
// Merge the loras data
|
// Merge the loras data
|
||||||
const inputWidget = node.inputWidget || node.widgets[0];
|
const inputWidget = node.inputWidget || getWidgetByName(node, "text");
|
||||||
|
if (!inputWidget) {
|
||||||
|
console.warn("LoRA Manager: text widget not found while restoring Lora Stacker");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const mergedLoras = mergeLoras(inputWidget.value, existingLoras);
|
const mergedLoras = mergeLoras(inputWidget.value, existingLoras);
|
||||||
node.lorasWidget.value = mergedLoras;
|
node.lorasWidget.value = mergedLoras;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,27 @@ export function getNodeKey(node) {
|
|||||||
return `${getNodeGraphId(node)}:${node.id}`;
|
return `${getNodeGraphId(node)}:${node.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getWidgetByName(node, widgetName) {
|
||||||
|
if (!node || !Array.isArray(node.widgets)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.widgets.find((widget) => widget?.name === widgetName) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWidgetSerializedValue(node, widgetName) {
|
||||||
|
if (!node || !Array.isArray(node.widgets) || !Array.isArray(node.widgets_values)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetIndex = node.widgets.findIndex((widget) => widget?.name === widgetName);
|
||||||
|
if (widgetIndex === -1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.widgets_values[widgetIndex];
|
||||||
|
}
|
||||||
|
|
||||||
export function getLinkFromGraph(graph, linkId) {
|
export function getLinkFromGraph(graph, linkId) {
|
||||||
if (!graph || graph.links == null) {
|
if (!graph || graph.links == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
updateConnectedTriggerWords,
|
updateConnectedTriggerWords,
|
||||||
chainCallback,
|
chainCallback,
|
||||||
mergeLoras,
|
mergeLoras,
|
||||||
|
getWidgetByName,
|
||||||
|
getWidgetSerializedValue,
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { addLorasWidget } from "./loras_widget.js";
|
import { addLorasWidget } from "./loras_widget.js";
|
||||||
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
||||||
@@ -31,7 +33,11 @@ app.registerExtension({
|
|||||||
let isSyncingInput = false;
|
let isSyncingInput = false;
|
||||||
|
|
||||||
// Get the text input widget (AUTOCOMPLETE_TEXT_LORAS type, at index 2 after low_mem_load and merge_loras)
|
// Get the text input widget (AUTOCOMPLETE_TEXT_LORAS type, at index 2 after low_mem_load and merge_loras)
|
||||||
const inputWidget = this.widgets[2];
|
const inputWidget = getWidgetByName(this, "text");
|
||||||
|
if (!inputWidget) {
|
||||||
|
console.warn("LoRA Manager: text widget not found for WanVideo Lora Select");
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.inputWidget = inputWidget;
|
this.inputWidget = inputWidget;
|
||||||
|
|
||||||
const scheduleInputSync = debounce((lorasValue) => {
|
const scheduleInputSync = debounce((lorasValue) => {
|
||||||
@@ -107,12 +113,15 @@ app.registerExtension({
|
|||||||
// Restore saved value if exists
|
// Restore saved value if exists
|
||||||
let existingLoras = [];
|
let existingLoras = [];
|
||||||
if (node.widgets_values && node.widgets_values.length > 0) {
|
if (node.widgets_values && node.widgets_values.length > 0) {
|
||||||
// 0 for low_mem_load, 1 for merge_loras, 2 for text widget, 3 for loras widget
|
const savedValue = getWidgetSerializedValue(node, "loras");
|
||||||
const savedValue = node.widgets_values[3];
|
|
||||||
existingLoras = savedValue || [];
|
existingLoras = savedValue || [];
|
||||||
}
|
}
|
||||||
// Merge the loras data
|
// Merge the loras data
|
||||||
const inputWidget = node.inputWidget || node.widgets[2];
|
const inputWidget = node.inputWidget || getWidgetByName(node, "text");
|
||||||
|
if (!inputWidget) {
|
||||||
|
console.warn("LoRA Manager: text widget not found while restoring WanVideo Lora Select");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const mergedLoras = mergeLoras(inputWidget.value, existingLoras);
|
const mergedLoras = mergeLoras(inputWidget.value, existingLoras);
|
||||||
node.lorasWidget.value = mergedLoras;
|
node.lorasWidget.value = mergedLoras;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user