refactor(lora-provider): extract mode change logic to shared TypeScript module

- Extract common mode change logic from lora_randomizer.js and lora_stacker.js
  into new mode-change-handler.ts TypeScript module
- Add LORA_PROVIDER_NODE_TYPES constant to centralize LoRA provider node types
- Update getActiveLorasFromNode in utils.js to support Lora Cycler's
  cycler_config widget (single current_lora_filename)
- Update getConnectedInputStackers and updateDownstreamLoaders to use
  isLoraProviderNode helper instead of hardcoded class checks
- Register mode change handlers in main.ts for all LoRA provider nodes
  (Lora Stacker, Lora Randomizer, Lora Cycler)
- Add value change callback to Lora Cycler widget to trigger
  updateDownstreamLoaders when current_lora_filename changes
- Remove duplicate mode change logic from lora_stacker.js
- Delete lora_randomizer.js (logic now centralized)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Will Miao
2026-01-22 20:46:09 +08:00
parent 6832469889
commit 932d85617c
7 changed files with 320 additions and 85 deletions

View File

@@ -1,40 +0,0 @@
import { app } from "../../scripts/app.js";
import {
getActiveLorasFromNode,
updateDownstreamLoaders,
chainCallback,
} from "./utils.js";
app.registerExtension({
name: "LoraManager.LoraRandomizer",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeType.comfyClass === "Lora Randomizer (LoraManager)") {
chainCallback(nodeType.prototype, "onNodeCreated", async function () {
this.serialize_widgets = true;
let _mode = this.mode;
const self = this;
Object.defineProperty(this, 'mode', {
get() {
return _mode;
},
set(value) {
const oldValue = _mode;
_mode = value;
if (self.onModeChange) {
self.onModeChange(value, oldValue);
}
}
});
this.onModeChange = function(newMode, oldMode) {
const isNodeActive = newMode === 0 || newMode === 3;
const activeLoraNames = isNodeActive ? getActiveLorasFromNode(self) : new Set();
updateDownstreamLoaders(self);
};
});
}
},
});

View File

@@ -31,34 +31,6 @@ app.registerExtension({
let isUpdating = false;
let isSyncingInput = false;
// Mechanism 3: Property descriptor to listen for mode changes
const self = this;
let _mode = this.mode;
Object.defineProperty(this, 'mode', {
get() {
return _mode;
},
set(value) {
const oldValue = _mode;
_mode = value;
// Trigger mode change handler
if (self.onModeChange) {
self.onModeChange(value, oldValue);
}
}
});
// Define the mode change handler
this.onModeChange = function(newMode, oldMode) {
// Update connected trigger word toggle nodes and downstream loader trigger word toggle nodes
// when mode changes, similar to when loras change
const isNodeActive = newMode === 0 || newMode === 3; // Active when mode is Always (0) or On Trigger (3)
const activeLoraNames = isNodeActive ? getActiveLorasFromNode(self) : new Set();
updateConnectedTriggerWords(self, activeLoraNames);
updateDownstreamLoaders(self);
};
const inputWidget = this.widgets[0];
inputWidget.options.getMaxHeight = () => 100;
this.inputWidget = inputWidget;

View File

@@ -4,6 +4,16 @@ import { AutoComplete } from "./autocomplete.js";
const ROOT_GRAPH_ID = "root";
export const LORA_PROVIDER_NODE_TYPES = [
"Lora Stacker (LoraManager)",
"Lora Randomizer (LoraManager)",
"Lora Cycler (LoraManager)",
];
export function isLoraProviderNode(comfyClass) {
return LORA_PROVIDER_NODE_TYPES.includes(comfyClass);
}
function isMapLike(collection) {
return collection && typeof collection.entries === "function" && typeof collection.values === "function";
}
@@ -233,7 +243,7 @@ export function getConnectedInputStackers(node) {
}
const sourceNode = node.graph?.getNodeById?.(link.origin_id);
if (sourceNode && (sourceNode.comfyClass === "Lora Stacker (LoraManager)" || sourceNode.comfyClass === "Lora Randomizer (LoraManager)")) {
if (sourceNode && isLoraProviderNode(sourceNode.comfyClass)) {
connectedStackers.push(sourceNode);
}
}
@@ -273,12 +283,22 @@ export function getConnectedTriggerToggleNodes(node) {
// Extract active lora names from a node's widgets
export function getActiveLorasFromNode(node) {
const activeLoraNames = new Set();
// Handle Lora Cycler (single LoRA from cycler_config widget)
if (node.comfyClass === "Lora Cycler (LoraManager)") {
const cyclerWidget = node.widgets?.find(w => w.name === 'cycler_config');
if (cyclerWidget?.value?.current_lora_filename) {
activeLoraNames.add(cyclerWidget.value.current_lora_filename);
}
return activeLoraNames;
}
// Handle Lora Stacker and Lora Randomizer (lorasWidget)
let lorasWidget = node.lorasWidget;
if (!lorasWidget && node.widgets) {
lorasWidget = node.widgets.find(w => w.name === 'loras');
}
if (lorasWidget && lorasWidget.value) {
lorasWidget.value.forEach(lora => {
if (lora.active) {
@@ -286,7 +306,7 @@ export function getActiveLorasFromNode(node) {
}
});
}
return activeLoraNames;
}
@@ -715,12 +735,8 @@ export function updateDownstreamLoaders(startNode, visited = new Set()) {
collectActiveLorasFromChain(targetNode);
updateConnectedTriggerWords(targetNode, allActiveLoraNames);
}
// If target is another Lora Stacker or Lora Randomizer, recursively check its outputs
else if (
targetNode &&
(targetNode.comfyClass === "Lora Stacker (LoraManager)" ||
targetNode.comfyClass === "Lora Randomizer (LoraManager)")
) {
// If target is another LoRA provider node, recursively check its outputs
else if (targetNode && isLoraProviderNode(targetNode.comfyClass)) {
updateDownstreamLoaders(targetNode, visited);
}
}

View File

@@ -13225,8 +13225,78 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
}
});
const JsonDisplayWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-0f202476"]]);
const LORA_PROVIDER_NODE_TYPES$1 = [
"Lora Stacker (LoraManager)",
"Lora Randomizer (LoraManager)",
"Lora Cycler (LoraManager)"
];
function getActiveLorasFromNodeByType(node) {
const comfyClass = node == null ? void 0 : node.comfyClass;
if (comfyClass === "Lora Cycler (LoraManager)") {
return extractFromCyclerConfig(node);
}
return extractFromLorasWidget(node);
}
function extractFromLorasWidget(node) {
var _a;
const activeLoraNames = /* @__PURE__ */ new Set();
const lorasWidget = node.lorasWidget || ((_a = node.widgets) == null ? void 0 : _a.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 _a, _b;
const activeLoraNames = /* @__PURE__ */ new Set();
const cyclerWidget = (_a = node.widgets) == null ? void 0 : _a.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);
};
}
const app = {};
const ROOT_GRAPH_ID = "root";
const LORA_PROVIDER_NODE_TYPES = [
"Lora Stacker (LoraManager)",
"Lora Randomizer (LoraManager)",
"Lora Cycler (LoraManager)"
];
function isLoraProviderNode(comfyClass) {
return LORA_PROVIDER_NODE_TYPES.includes(comfyClass);
}
function isMapLike(collection) {
return collection && typeof collection.entries === "function" && typeof collection.values === "function";
}
@@ -13278,7 +13348,7 @@ function getConnectedInputStackers(node) {
continue;
}
const sourceNode = (_b = (_a = node.graph) == null ? void 0 : _a.getNodeById) == null ? void 0 : _b.call(_a, link.origin_id);
if (sourceNode && (sourceNode.comfyClass === "Lora Stacker (LoraManager)" || sourceNode.comfyClass === "Lora Randomizer (LoraManager)")) {
if (sourceNode && isLoraProviderNode(sourceNode.comfyClass)) {
connectedStackers.push(sourceNode);
}
}
@@ -13308,7 +13378,15 @@ function getConnectedTriggerToggleNodes(node) {
return connectedNodes;
}
function getActiveLorasFromNode(node) {
var _a, _b;
const activeLoraNames = /* @__PURE__ */ new Set();
if (node.comfyClass === "Lora Cycler (LoraManager)") {
const cyclerWidget = (_a = node.widgets) == null ? void 0 : _a.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;
}
let lorasWidget = node.lorasWidget;
if (!lorasWidget && node.widgets) {
lorasWidget = node.widgets.find((w2) => w2.name === "loras");
@@ -13331,8 +13409,8 @@ function collectActiveLorasFromChain(node, visited = /* @__PURE__ */ new Set())
return /* @__PURE__ */ new Set();
}
visited.add(nodeKey);
const isNodeActive = node.mode === void 0 || node.mode === 0 || node.mode === 3;
const allActiveLoraNames = isNodeActive ? getActiveLorasFromNode(node) : /* @__PURE__ */ new Set();
const isNodeActive2 = node.mode === void 0 || node.mode === 0 || node.mode === 3;
const allActiveLoraNames = isNodeActive2 ? getActiveLorasFromNode(node) : /* @__PURE__ */ new Set();
const inputStackers = getConnectedInputStackers(node);
for (const stacker of inputStackers) {
const stackerLoras = collectActiveLorasFromChain(stacker, visited);
@@ -13383,8 +13461,8 @@ function getPoolConfigFromConnectedNode(node) {
if (!poolNode) {
return null;
}
const isNodeActive = poolNode.mode === void 0 || poolNode.mode === 0 || poolNode.mode === 3;
if (!isNodeActive) {
const isNodeActive2 = poolNode.mode === void 0 || poolNode.mode === 0 || poolNode.mode === 3;
if (!isNodeActive2) {
return null;
}
const poolWidget = (_a = poolNode.widgets) == null ? void 0 : _a.find((w2) => w2.name === "pool_config");
@@ -13405,7 +13483,7 @@ function updateDownstreamLoaders(startNode, visited = /* @__PURE__ */ new Set())
if (targetNode && targetNode.comfyClass === "Lora Loader (LoraManager)") {
const allActiveLoraNames = collectActiveLorasFromChain(targetNode);
updateConnectedTriggerWords(targetNode, allActiveLoraNames);
} else if (targetNode && (targetNode.comfyClass === "Lora Stacker (LoraManager)" || targetNode.comfyClass === "Lora Randomizer (LoraManager)")) {
} else if (targetNode && isLoraProviderNode(targetNode.comfyClass)) {
updateDownstreamLoaders(targetNode, visited);
}
}
@@ -13597,10 +13675,14 @@ function createLoraCyclerWidget(node) {
return internalValue;
},
setValue(v2) {
const oldFilename = internalValue == null ? void 0 : internalValue.current_lora_filename;
internalValue = v2;
if (typeof widget.onSetValue === "function") {
widget.onSetValue(v2);
}
if (oldFilename !== (v2 == null ? void 0 : v2.current_lora_filename)) {
updateDownstreamLoaders(node);
}
},
serialize: true,
getMinHeight() {
@@ -13609,7 +13691,11 @@ function createLoraCyclerWidget(node) {
}
);
widget.updateConfig = (v2) => {
const oldFilename = internalValue == null ? void 0 : internalValue.current_lora_filename;
internalValue = v2;
if (oldFilename !== (v2 == null ? void 0 : v2.current_lora_filename)) {
updateDownstreamLoaders(node);
}
};
node.getPoolConfig = () => getPoolConfigFromConnectedNode(node);
const vueApp = createApp(LoraCyclerWidget, {
@@ -13726,8 +13812,19 @@ app$1.registerExtension({
};
},
// Add display-only widget to Debug Metadata node
// Register mode change handlers for LoRA provider nodes
// @ts-ignore
async beforeRegisterNodeDef(nodeType, nodeData) {
const comfyClass = nodeType.comfyClass;
if (LORA_PROVIDER_NODE_TYPES$1.includes(comfyClass)) {
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
originalOnNodeCreated == null ? void 0 : originalOnNodeCreated.apply(this, arguments);
const nodeSpecificCallback = comfyClass === "Lora Stacker (LoraManager)" ? (activeLoraNames) => updateConnectedTriggerWords(this, activeLoraNames) : void 0;
const onModeChange = createModeChangeCallback(this, updateDownstreamLoaders, nodeSpecificCallback);
setupModeChangeHandler(this, onModeChange);
};
}
if (nodeData.name === "Debug Metadata (LoraManager)") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {

File diff suppressed because one or more lines are too long