mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-27 22:14:09 -03:00
fix: keep bypass/mute state on frontend 1.53+ node shell state (#1123)
ComfyUI frontend 1.53 turned LGraphNode.mode into a prototype accessor backed by node._state, and serialize() now reads that state directly. Redefining mode on the instance shadowed the setter, so bypass/mute never reached the serialized workflow and silently reverted to Always on save/reload or workflow tab switch. Add interceptModeChange() in web/comfyui/utils.js: it delegates to the prototype accessor when one exists (observing changes only), and falls back to the legacy closure accessor on older frontends. Use it in lora_loader.js and in the Vue widgets' setupModeChangeHandler, which covers the LoRA provider/aggregator nodes with the same latent bug.
This commit is contained in:
@@ -50,6 +50,22 @@ vi.mock(UTILS_MODULE, () => ({
|
|||||||
chainCallback: (proto, property, callback) => {
|
chainCallback: (proto, property, callback) => {
|
||||||
proto[property] = callback;
|
proto[property] = callback;
|
||||||
},
|
},
|
||||||
|
interceptModeChange: (node, onModeChange) => {
|
||||||
|
let currentMode = node.mode;
|
||||||
|
Object.defineProperty(node, "mode", {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
return currentMode;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const oldValue = currentMode;
|
||||||
|
currentMode = value;
|
||||||
|
if (oldValue !== value) {
|
||||||
|
onModeChange(value, oldValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
getAllGraphNodes,
|
getAllGraphNodes,
|
||||||
getNodeFromGraph,
|
getNodeFromGraph,
|
||||||
getWidgetByName,
|
getWidgetByName,
|
||||||
|
|||||||
@@ -277,5 +277,44 @@ describe("Node mode change handling", () => {
|
|||||||
new Set(["LoaderLora1", "LoaderLora2"])
|
new Set(["LoaderLora1", "LoaderLora2"])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should keep bypass state in the shell state on ECS frontends (issue #1123)", async () => {
|
||||||
|
// ComfyUI frontend >= 1.53 backs `mode` with a prototype accessor over
|
||||||
|
// `node._state.mode` and serializes from `_state`; the interceptor must
|
||||||
|
// delegate to it instead of shadowing it.
|
||||||
|
class EcsLGraphNode {
|
||||||
|
constructor() {
|
||||||
|
this._state = { mode: 0 };
|
||||||
|
}
|
||||||
|
get mode() {
|
||||||
|
return this._state.mode;
|
||||||
|
}
|
||||||
|
set mode(value) {
|
||||||
|
this._state.mode = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ecsNode = new EcsLGraphNode();
|
||||||
|
Object.assign(ecsNode, {
|
||||||
|
comfyClass: "Lora Loader (LoraManager)",
|
||||||
|
widgets: [
|
||||||
|
{ name: "text", value: "", options: {}, callback: null },
|
||||||
|
{ name: "loras", value: [], options: {}, callback: null },
|
||||||
|
],
|
||||||
|
addInput: vi.fn(),
|
||||||
|
graph: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeType = { comfyClass: "Lora Loader (LoraManager)", prototype: {} };
|
||||||
|
await extension.beforeRegisterNodeDef(nodeType, {}, {});
|
||||||
|
nodeType.prototype.onNodeCreated.call(ecsNode);
|
||||||
|
|
||||||
|
// Bypass the node: the write must reach the shell state that
|
||||||
|
// serialization reads from.
|
||||||
|
ecsNode.mode = 4;
|
||||||
|
expect(ecsNode._state.mode).toBe(4);
|
||||||
|
expect(ecsNode.mode).toBe(4);
|
||||||
|
expect(updateConnectedTriggerWords).toHaveBeenCalledWith(ecsNode, expect.anything());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const { APP_MODULE, UTILS_MODULE } = vi.hoisted(() => ({
|
||||||
|
APP_MODULE: new URL("../../../scripts/app.js", import.meta.url).pathname,
|
||||||
|
UTILS_MODULE: new URL("../../../web/comfyui/utils.js", import.meta.url).pathname,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(APP_MODULE, () => ({
|
||||||
|
app: {
|
||||||
|
graph: null,
|
||||||
|
registerExtension: vi.fn(),
|
||||||
|
ui: {
|
||||||
|
settings: {
|
||||||
|
getSettingValue: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("interceptModeChange", () => {
|
||||||
|
let interceptModeChange;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.resetModules();
|
||||||
|
({ interceptModeChange } = await import(UTILS_MODULE));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("legacy frontend (mode as plain data property)", () => {
|
||||||
|
it("reads and writes the mode through the installed accessor", () => {
|
||||||
|
const node = { mode: 0 };
|
||||||
|
interceptModeChange(node, vi.fn());
|
||||||
|
|
||||||
|
node.mode = 4;
|
||||||
|
expect(node.mode).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("invokes the callback only when the mode actually changes", () => {
|
||||||
|
const node = { mode: 0 };
|
||||||
|
const onModeChange = vi.fn();
|
||||||
|
interceptModeChange(node, onModeChange);
|
||||||
|
|
||||||
|
node.mode = 0;
|
||||||
|
expect(onModeChange).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
node.mode = 4;
|
||||||
|
expect(onModeChange).toHaveBeenCalledWith(4, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ECS frontend (mode as prototype accessor backed by shell state)", () => {
|
||||||
|
function createEcsNode() {
|
||||||
|
class LGraphNode {
|
||||||
|
constructor() {
|
||||||
|
this._state = { mode: 0 };
|
||||||
|
}
|
||||||
|
get mode() {
|
||||||
|
return this._state.mode;
|
||||||
|
}
|
||||||
|
set mode(value) {
|
||||||
|
this._state.mode = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new LGraphNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
it("keeps writes flowing into the shell state so serialization stays correct", () => {
|
||||||
|
const node = createEcsNode();
|
||||||
|
interceptModeChange(node, vi.fn());
|
||||||
|
|
||||||
|
node.mode = 4;
|
||||||
|
|
||||||
|
expect(node._state.mode).toBe(4);
|
||||||
|
expect(node.mode).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("invokes the callback with new and old mode on change", () => {
|
||||||
|
const node = createEcsNode();
|
||||||
|
const onModeChange = vi.fn();
|
||||||
|
interceptModeChange(node, onModeChange);
|
||||||
|
|
||||||
|
node.mode = 4;
|
||||||
|
expect(onModeChange).toHaveBeenCalledWith(4, 0);
|
||||||
|
|
||||||
|
node.mode = 4;
|
||||||
|
expect(onModeChange).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
node.mode = 0;
|
||||||
|
expect(onModeChange).toHaveBeenCalledWith(0, 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the installed accessor configurable so it can be redefined", () => {
|
||||||
|
const node = createEcsNode();
|
||||||
|
interceptModeChange(node, vi.fn());
|
||||||
|
|
||||||
|
const descriptor = Object.getOwnPropertyDescriptor(node, "mode");
|
||||||
|
expect(descriptor.configurable).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,6 +7,9 @@
|
|||||||
* - Lora Cycler (LoraManager)
|
* - Lora Cycler (LoraManager)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { interceptModeChange } from '../../web/comfyui/utils.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of node types that act as LoRA providers in the workflow chain.
|
* List of node types that act as LoRA providers in the workflow chain.
|
||||||
* These nodes can be traversed when collecting active LoRAs and can trigger
|
* These nodes can be traversed when collecting active LoRAs and can trigger
|
||||||
@@ -120,8 +123,11 @@ export function isNodeActive(mode: number | undefined): boolean {
|
|||||||
/**
|
/**
|
||||||
* Setup a mode change handler for a node.
|
* Setup a mode change handler for a node.
|
||||||
*
|
*
|
||||||
* Intercepts the mode property setter to trigger a callback when the mode changes.
|
* Delegates to `interceptModeChange`, which observes the mode property
|
||||||
* This is needed because ComfyUI sets the mode property directly without using a setter.
|
* without shadowing the frontend's own `mode` accessor. Since ComfyUI
|
||||||
|
* frontend 1.53, `mode` is backed by shell state (`node._state.mode`) that
|
||||||
|
* serialization reads directly — redefining the property on the instance
|
||||||
|
* would silently revert bypass/mute on save/reload.
|
||||||
*
|
*
|
||||||
* @param node - The node to set up the handler for
|
* @param node - The node to set up the handler for
|
||||||
* @param onModeChange - Callback function called when mode changes (receives newMode and oldMode)
|
* @param onModeChange - Callback function called when mode changes (receives newMode and oldMode)
|
||||||
@@ -130,21 +136,7 @@ export function setupModeChangeHandler(
|
|||||||
node: any,
|
node: any,
|
||||||
onModeChange: (newMode: number, oldMode: number) => void
|
onModeChange: (newMode: number, oldMode: number) => void
|
||||||
): void {
|
): void {
|
||||||
let _mode = node.mode;
|
interceptModeChange(node, onModeChange);
|
||||||
|
|
||||||
Object.defineProperty(node, 'mode', {
|
|
||||||
get() {
|
|
||||||
return _mode;
|
|
||||||
},
|
|
||||||
set(value: number) {
|
|
||||||
const oldValue = _mode;
|
|
||||||
_mode = value;
|
|
||||||
|
|
||||||
if (oldValue !== value) {
|
|
||||||
onModeChange(value, oldValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+10
-16
@@ -10,6 +10,7 @@ import {
|
|||||||
collectActiveLorasFromChain,
|
collectActiveLorasFromChain,
|
||||||
updateConnectedTriggerWords,
|
updateConnectedTriggerWords,
|
||||||
chainCallback,
|
chainCallback,
|
||||||
|
interceptModeChange,
|
||||||
mergeLoras,
|
mergeLoras,
|
||||||
getAllGraphNodes,
|
getAllGraphNodes,
|
||||||
getNodeFromGraph,
|
getNodeFromGraph,
|
||||||
@@ -189,24 +190,17 @@ app.registerExtension({
|
|||||||
let isUpdating = false;
|
let isUpdating = false;
|
||||||
let isSyncingInput = false;
|
let isSyncingInput = false;
|
||||||
|
|
||||||
// Mechanism: Property descriptor to listen for mode changes
|
// Mechanism: Observe mode changes without shadowing the frontend's
|
||||||
|
// own `mode` accessor (frontend >= 1.53 serializes from shell state,
|
||||||
|
// so redefining `mode` here would silently revert bypass/mute).
|
||||||
const self = this;
|
const self = this;
|
||||||
let _mode = this.mode;
|
interceptModeChange(this, (newMode, oldMode) => {
|
||||||
Object.defineProperty(this, 'mode', {
|
// Trigger mode change handler
|
||||||
get() {
|
if (self.onModeChange) {
|
||||||
return _mode;
|
self.onModeChange(newMode, oldMode);
|
||||||
},
|
|
||||||
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}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[Lora Loader] Node mode changed from ${oldMode} to ${newMode}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define the mode change handler
|
// Define the mode change handler
|
||||||
|
|||||||
@@ -188,6 +188,74 @@ export function chainCallback(object, property, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a `mode` accessor (getter/setter) on the node's prototype chain.
|
||||||
|
* Since ComfyUI frontend 1.53, `LGraphNode.mode` is a prototype accessor
|
||||||
|
* backed by the node's shell state (`node._state.mode`); on legacy frontends
|
||||||
|
* `mode` is a plain data property on the instance and no accessor exists.
|
||||||
|
*/
|
||||||
|
function findModeAccessor(node) {
|
||||||
|
let proto = Object.getPrototypeOf(node);
|
||||||
|
while (proto && proto !== Object.prototype) {
|
||||||
|
const descriptor = Object.getOwnPropertyDescriptor(proto, "mode");
|
||||||
|
if (descriptor && (descriptor.get || descriptor.set)) {
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
proto = Object.getPrototypeOf(proto);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe node mode changes (Always/Never/On Trigger/Bypass) without
|
||||||
|
* breaking the frontend's own `mode` property.
|
||||||
|
*
|
||||||
|
* Since ComfyUI frontend 1.53, `mode` is a prototype accessor backed by
|
||||||
|
* shell state and serialization reads that state directly. Redefining `mode`
|
||||||
|
* on the instance would shadow the prototype setter, so bypass/mute never
|
||||||
|
* reaches the serialized workflow and silently reverts to Always on reload.
|
||||||
|
* When a prototype accessor exists we delegate to it and only observe the
|
||||||
|
* change; on legacy frontends we keep the value in a closure as before.
|
||||||
|
*
|
||||||
|
* @param {Object} node - The litegraph node instance
|
||||||
|
* @param {(newMode: number, oldMode: number) => void} onModeChange - Called when the mode actually changes
|
||||||
|
*/
|
||||||
|
export function interceptModeChange(node, onModeChange) {
|
||||||
|
const delegate = findModeAccessor(node);
|
||||||
|
|
||||||
|
if (delegate && typeof delegate.get === "function" && typeof delegate.set === "function") {
|
||||||
|
Object.defineProperty(node, "mode", {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
return delegate.get.call(this);
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const oldValue = delegate.get.call(this);
|
||||||
|
delegate.set.call(this, value);
|
||||||
|
if (oldValue !== value) {
|
||||||
|
onModeChange(value, oldValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentMode = node.mode;
|
||||||
|
Object.defineProperty(node, "mode", {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
return currentMode;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const oldValue = currentMode;
|
||||||
|
currentMode = value;
|
||||||
|
if (oldValue !== value) {
|
||||||
|
onModeChange(value, oldValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a toast notification
|
* Show a toast notification
|
||||||
* @param {Object|string} options - Toast options object or message string for backward compatibility
|
* @param {Object|string} options - Toast options object or message string for backward compatibility
|
||||||
|
|||||||
@@ -15715,82 +15715,6 @@ function createVueWidgetCleanup(vueApp, onCleanup) {
|
|||||||
onCleanup == null ? void 0 : onCleanup();
|
onCleanup == null ? void 0 : onCleanup();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const LORA_PROVIDER_NODE_TYPES$1 = [
|
|
||||||
"Lora Stacker (LoraManager)",
|
|
||||||
"Lora Randomizer (LoraManager)",
|
|
||||||
"Lora Cycler (LoraManager)",
|
|
||||||
"Create Hook LoRA (LoraManager)"
|
|
||||||
];
|
|
||||||
const LORA_STACK_AGGREGATOR_NODE_TYPES$1 = [
|
|
||||||
"Lora Stack Combiner (LoraManager)"
|
|
||||||
];
|
|
||||||
const LORA_CHAIN_NODE_TYPES$1 = [
|
|
||||||
...LORA_PROVIDER_NODE_TYPES$1,
|
|
||||||
...LORA_STACK_AGGREGATOR_NODE_TYPES$1
|
|
||||||
];
|
|
||||||
function isLoraStackAggregatorNode$1(comfyClass) {
|
|
||||||
return LORA_STACK_AGGREGATOR_NODE_TYPES$1.includes(comfyClass);
|
|
||||||
}
|
|
||||||
function getActiveLorasFromNodeByType(node) {
|
|
||||||
const comfyClass = node == null ? void 0 : node.comfyClass;
|
|
||||||
if (comfyClass === "Lora Cycler (LoraManager)") {
|
|
||||||
return extractFromCyclerConfig(node);
|
|
||||||
}
|
|
||||||
if (isLoraStackAggregatorNode$1(comfyClass)) {
|
|
||||||
return /* @__PURE__ */ new Set();
|
|
||||||
}
|
|
||||||
return extractFromLorasWidget(node);
|
|
||||||
}
|
|
||||||
function extractFromLorasWidget(node) {
|
|
||||||
var _a2;
|
|
||||||
const activeLoraNames = /* @__PURE__ */ new Set();
|
|
||||||
const lorasWidget = node.lorasWidget || ((_a2 = node.widgets) == null ? void 0 : _a2.find((w2) => w2.name === "loras"));
|
|
||||||
if (lorasWidget == null ? void 0 : lorasWidget.value) {
|
|
||||||
lorasWidget.value.forEach((lora) => {
|
|
||||||
if (lora.active) {
|
|
||||||
activeLoraNames.add(lora.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return activeLoraNames;
|
|
||||||
}
|
|
||||||
function extractFromCyclerConfig(node) {
|
|
||||||
var _a2, _b;
|
|
||||||
const activeLoraNames = /* @__PURE__ */ new Set();
|
|
||||||
const cyclerWidget = (_a2 = node.widgets) == null ? void 0 : _a2.find((w2) => w2.name === "cycler_config");
|
|
||||||
if ((_b = cyclerWidget == null ? void 0 : cyclerWidget.value) == null ? void 0 : _b.current_lora_filename) {
|
|
||||||
activeLoraNames.add(cyclerWidget.value.current_lora_filename);
|
|
||||||
}
|
|
||||||
return activeLoraNames;
|
|
||||||
}
|
|
||||||
function isNodeActive(mode) {
|
|
||||||
return mode === void 0 || mode === 0 || mode === 3;
|
|
||||||
}
|
|
||||||
function setupModeChangeHandler(node, onModeChange) {
|
|
||||||
let _mode = node.mode;
|
|
||||||
Object.defineProperty(node, "mode", {
|
|
||||||
get() {
|
|
||||||
return _mode;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
const oldValue = _mode;
|
|
||||||
_mode = value;
|
|
||||||
if (oldValue !== value) {
|
|
||||||
onModeChange(value, oldValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function createModeChangeCallback(node, updateDownstreamLoaders2, nodeSpecificCallback) {
|
|
||||||
return (newMode, _oldMode) => {
|
|
||||||
const isNodeCurrentlyActive = isNodeActive(newMode);
|
|
||||||
const activeLoraNames = isNodeCurrentlyActive ? getActiveLorasFromNodeByType(node) : /* @__PURE__ */ new Set();
|
|
||||||
if (nodeSpecificCallback) {
|
|
||||||
nodeSpecificCallback(activeLoraNames);
|
|
||||||
}
|
|
||||||
updateDownstreamLoaders2(node);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let _loraSyntaxFormatCache = null;
|
let _loraSyntaxFormatCache = null;
|
||||||
let _loraSyntaxFormatRefreshPromise = null;
|
let _loraSyntaxFormatRefreshPromise = null;
|
||||||
async function _fetchLoraSyntaxFormat() {
|
async function _fetchLoraSyntaxFormat() {
|
||||||
@@ -15913,24 +15837,24 @@ function lmUrl(path) {
|
|||||||
return `${getComfyUIBasePath()}${path}`;
|
return `${getComfyUIBasePath()}${path}`;
|
||||||
}
|
}
|
||||||
const ROOT_GRAPH_ID = "root";
|
const ROOT_GRAPH_ID = "root";
|
||||||
const LORA_PROVIDER_NODE_TYPES = [
|
const LORA_PROVIDER_NODE_TYPES$1 = [
|
||||||
"Lora Stacker (LoraManager)",
|
"Lora Stacker (LoraManager)",
|
||||||
"Lora Randomizer (LoraManager)",
|
"Lora Randomizer (LoraManager)",
|
||||||
"Lora Cycler (LoraManager)",
|
"Lora Cycler (LoraManager)",
|
||||||
"Create Hook LoRA (LoraManager)"
|
"Create Hook LoRA (LoraManager)"
|
||||||
];
|
];
|
||||||
const LORA_STACK_AGGREGATOR_NODE_TYPES = [
|
const LORA_STACK_AGGREGATOR_NODE_TYPES$1 = [
|
||||||
"Lora Stack Combiner (LoraManager)"
|
"Lora Stack Combiner (LoraManager)"
|
||||||
];
|
];
|
||||||
const LORA_CHAIN_NODE_TYPES = [
|
const LORA_CHAIN_NODE_TYPES$1 = [
|
||||||
...LORA_PROVIDER_NODE_TYPES,
|
...LORA_PROVIDER_NODE_TYPES$1,
|
||||||
...LORA_STACK_AGGREGATOR_NODE_TYPES
|
...LORA_STACK_AGGREGATOR_NODE_TYPES$1
|
||||||
];
|
];
|
||||||
function isLoraStackAggregatorNode(comfyClass) {
|
function isLoraStackAggregatorNode$1(comfyClass) {
|
||||||
return LORA_STACK_AGGREGATOR_NODE_TYPES.includes(comfyClass);
|
return LORA_STACK_AGGREGATOR_NODE_TYPES$1.includes(comfyClass);
|
||||||
}
|
}
|
||||||
function isLoraChainNode(comfyClass) {
|
function isLoraChainNode(comfyClass) {
|
||||||
return LORA_CHAIN_NODE_TYPES.includes(comfyClass);
|
return LORA_CHAIN_NODE_TYPES$1.includes(comfyClass);
|
||||||
}
|
}
|
||||||
function isMapLike(collection) {
|
function isMapLike(collection) {
|
||||||
return collection && typeof collection.entries === "function" && typeof collection.values === "function";
|
return collection && typeof collection.entries === "function" && typeof collection.values === "function";
|
||||||
@@ -15968,6 +15892,50 @@ function getLinkFromGraph(graph, linkId) {
|
|||||||
}
|
}
|
||||||
return graph.links[linkId] || null;
|
return graph.links[linkId] || null;
|
||||||
}
|
}
|
||||||
|
function findModeAccessor(node) {
|
||||||
|
let proto = Object.getPrototypeOf(node);
|
||||||
|
while (proto && proto !== Object.prototype) {
|
||||||
|
const descriptor = Object.getOwnPropertyDescriptor(proto, "mode");
|
||||||
|
if (descriptor && (descriptor.get || descriptor.set)) {
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
proto = Object.getPrototypeOf(proto);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function interceptModeChange(node, onModeChange) {
|
||||||
|
const delegate = findModeAccessor(node);
|
||||||
|
if (delegate && typeof delegate.get === "function" && typeof delegate.set === "function") {
|
||||||
|
Object.defineProperty(node, "mode", {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
return delegate.get.call(this);
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const oldValue = delegate.get.call(this);
|
||||||
|
delegate.set.call(this, value);
|
||||||
|
if (oldValue !== value) {
|
||||||
|
onModeChange(value, oldValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let currentMode = node.mode;
|
||||||
|
Object.defineProperty(node, "mode", {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
return currentMode;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const oldValue = currentMode;
|
||||||
|
currentMode = value;
|
||||||
|
if (oldValue !== value) {
|
||||||
|
onModeChange(value, oldValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
function isLoraStackInput(input) {
|
function isLoraStackInput(input) {
|
||||||
return (input == null ? void 0 : input.type) === "LORA_STACK";
|
return (input == null ? void 0 : input.type) === "LORA_STACK";
|
||||||
}
|
}
|
||||||
@@ -16025,7 +15993,7 @@ function getActiveLorasFromNode(node) {
|
|||||||
}
|
}
|
||||||
return activeLoraNames;
|
return activeLoraNames;
|
||||||
}
|
}
|
||||||
if (isLoraStackAggregatorNode(node.comfyClass)) {
|
if (isLoraStackAggregatorNode$1(node.comfyClass)) {
|
||||||
return activeLoraNames;
|
return activeLoraNames;
|
||||||
}
|
}
|
||||||
let lorasWidget = node.lorasWidget;
|
let lorasWidget = node.lorasWidget;
|
||||||
@@ -16136,6 +16104,70 @@ function updateDownstreamLoaders(startNode, visited = /* @__PURE__ */ new Set())
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const LORA_PROVIDER_NODE_TYPES = [
|
||||||
|
"Lora Stacker (LoraManager)",
|
||||||
|
"Lora Randomizer (LoraManager)",
|
||||||
|
"Lora Cycler (LoraManager)",
|
||||||
|
"Create Hook LoRA (LoraManager)"
|
||||||
|
];
|
||||||
|
const LORA_STACK_AGGREGATOR_NODE_TYPES = [
|
||||||
|
"Lora Stack Combiner (LoraManager)"
|
||||||
|
];
|
||||||
|
const LORA_CHAIN_NODE_TYPES = [
|
||||||
|
...LORA_PROVIDER_NODE_TYPES,
|
||||||
|
...LORA_STACK_AGGREGATOR_NODE_TYPES
|
||||||
|
];
|
||||||
|
function isLoraStackAggregatorNode(comfyClass) {
|
||||||
|
return LORA_STACK_AGGREGATOR_NODE_TYPES.includes(comfyClass);
|
||||||
|
}
|
||||||
|
function getActiveLorasFromNodeByType(node) {
|
||||||
|
const comfyClass = node == null ? void 0 : node.comfyClass;
|
||||||
|
if (comfyClass === "Lora Cycler (LoraManager)") {
|
||||||
|
return extractFromCyclerConfig(node);
|
||||||
|
}
|
||||||
|
if (isLoraStackAggregatorNode(comfyClass)) {
|
||||||
|
return /* @__PURE__ */ new Set();
|
||||||
|
}
|
||||||
|
return extractFromLorasWidget(node);
|
||||||
|
}
|
||||||
|
function extractFromLorasWidget(node) {
|
||||||
|
var _a2;
|
||||||
|
const activeLoraNames = /* @__PURE__ */ new Set();
|
||||||
|
const lorasWidget = node.lorasWidget || ((_a2 = node.widgets) == null ? void 0 : _a2.find((w2) => w2.name === "loras"));
|
||||||
|
if (lorasWidget == null ? void 0 : lorasWidget.value) {
|
||||||
|
lorasWidget.value.forEach((lora) => {
|
||||||
|
if (lora.active) {
|
||||||
|
activeLoraNames.add(lora.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return activeLoraNames;
|
||||||
|
}
|
||||||
|
function extractFromCyclerConfig(node) {
|
||||||
|
var _a2, _b;
|
||||||
|
const activeLoraNames = /* @__PURE__ */ new Set();
|
||||||
|
const cyclerWidget = (_a2 = node.widgets) == null ? void 0 : _a2.find((w2) => w2.name === "cycler_config");
|
||||||
|
if ((_b = cyclerWidget == null ? void 0 : cyclerWidget.value) == null ? void 0 : _b.current_lora_filename) {
|
||||||
|
activeLoraNames.add(cyclerWidget.value.current_lora_filename);
|
||||||
|
}
|
||||||
|
return activeLoraNames;
|
||||||
|
}
|
||||||
|
function isNodeActive(mode) {
|
||||||
|
return mode === void 0 || mode === 0 || mode === 3;
|
||||||
|
}
|
||||||
|
function setupModeChangeHandler(node, onModeChange) {
|
||||||
|
interceptModeChange(node, onModeChange);
|
||||||
|
}
|
||||||
|
function createModeChangeCallback(node, updateDownstreamLoaders2, nodeSpecificCallback) {
|
||||||
|
return (newMode, _oldMode) => {
|
||||||
|
const isNodeCurrentlyActive = isNodeActive(newMode);
|
||||||
|
const activeLoraNames = isNodeCurrentlyActive ? getActiveLorasFromNodeByType(node) : /* @__PURE__ */ new Set();
|
||||||
|
if (nodeSpecificCallback) {
|
||||||
|
nodeSpecificCallback(activeLoraNames);
|
||||||
|
}
|
||||||
|
updateDownstreamLoaders2(node);
|
||||||
|
};
|
||||||
|
}
|
||||||
const LORA_POOL_WIDGET_MIN_WIDTH = 500;
|
const LORA_POOL_WIDGET_MIN_WIDTH = 500;
|
||||||
const LORA_POOL_WIDGET_MIN_HEIGHT = 520;
|
const LORA_POOL_WIDGET_MIN_HEIGHT = 520;
|
||||||
const LORA_RANDOMIZER_WIDGET_MIN_WIDTH = 500;
|
const LORA_RANDOMIZER_WIDGET_MIN_WIDTH = 500;
|
||||||
@@ -16853,7 +16885,7 @@ app.registerExtension({
|
|||||||
return originalConfigure == null ? void 0 : originalConfigure.apply(this, arguments);
|
return originalConfigure == null ? void 0 : originalConfigure.apply(this, arguments);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (LORA_CHAIN_NODE_TYPES$1.includes(comfyClass)) {
|
if (LORA_CHAIN_NODE_TYPES.includes(comfyClass)) {
|
||||||
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
|
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||||
nodeType.prototype.onNodeCreated = function() {
|
nodeType.prototype.onNodeCreated = function() {
|
||||||
originalOnNodeCreated == null ? void 0 : originalOnNodeCreated.apply(this, arguments);
|
originalOnNodeCreated == null ? void 0 : originalOnNodeCreated.apply(this, arguments);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user