mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-07 14:30:15 -03:00
feat(nodes): make Lora Stack Combiner inputs dynamic
This commit is contained in:
127
web/comfyui/lora_stack_dynamic_inputs.js
Normal file
127
web/comfyui/lora_stack_dynamic_inputs.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
/**
|
||||
* Extension for LoraStackCombinerLM node to support dynamic lora_stack inputs.
|
||||
* Defaults to two inputs; connecting the last slot adds a new empty one, and
|
||||
* disconnecting a non-last slot removes it (at least two are always kept).
|
||||
* Based on the dynamic input pattern from Impact Pack's Switch (Any) node.
|
||||
*/
|
||||
const STACK_INPUT_PATTERN = /^lora_stack\d+$/;
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.LoraManager.LoraStackCombiner",
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name !== "Lora Stack Combiner (LoraManager)") {
|
||||
return;
|
||||
}
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
|
||||
nodeType.prototype.onConnectionsChange = function(type, index, connected, link_info) {
|
||||
// Skip while the graph is being (re)configured (load, paste, subgraph ops)
|
||||
if (app.configuringGraph) {
|
||||
return onConnectionsChange?.apply?.(this, arguments);
|
||||
}
|
||||
|
||||
const stackTrace = new Error().stack;
|
||||
|
||||
// Skip during graph loading/pasting to avoid interference
|
||||
if (stackTrace.includes('loadGraphData') || stackTrace.includes('pasteFromClipboard')) {
|
||||
return onConnectionsChange?.apply?.(this, arguments);
|
||||
}
|
||||
|
||||
// Skip subgraph operations
|
||||
if (stackTrace.includes('convertToSubgraph') || stackTrace.includes('Subgraph.configure')) {
|
||||
return onConnectionsChange?.apply?.(this, arguments);
|
||||
}
|
||||
|
||||
if (!link_info) {
|
||||
return onConnectionsChange?.apply?.(this, arguments);
|
||||
}
|
||||
|
||||
// Handle input connections (type === 1)
|
||||
if (type === 1) {
|
||||
const input = this.inputs[index];
|
||||
|
||||
// Only process numbered lora_stack inputs (legacy a/b slots are left untouched)
|
||||
if (!input || !STACK_INPUT_PATTERN.test(input.name)) {
|
||||
return onConnectionsChange?.apply?.(this, arguments);
|
||||
}
|
||||
|
||||
// Count existing numbered lora_stack inputs
|
||||
let stackInputCount = 0;
|
||||
for (const inp of this.inputs) {
|
||||
if (STACK_INPUT_PATTERN.test(inp.name)) {
|
||||
stackInputCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Renumber all numbered lora_stack inputs sequentially
|
||||
let slotIndex = 1;
|
||||
for (const inp of this.inputs) {
|
||||
if (STACK_INPUT_PATTERN.test(inp.name)) {
|
||||
inp.name = `lora_stack${slotIndex}`;
|
||||
slotIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new input slot if connected and this was the last one
|
||||
if (connected) {
|
||||
const lastStackIndex = stackInputCount;
|
||||
if (index === lastStackIndex || index === this.inputs.findIndex(i => i.name === `lora_stack${lastStackIndex}`)) {
|
||||
this.addInput(`lora_stack${slotIndex}`, "LORA_STACK", {
|
||||
tooltip: "A LoRA stack to combine. Connect to add more inputs."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove disconnected input slots (but keep at least two).
|
||||
// LiteGraph fires this event only for slots that had a link, and
|
||||
// it has already cleared input.link by the time the event fires,
|
||||
// so the disconnected slot is always empty at this point.
|
||||
if (!connected && stackInputCount > 2) {
|
||||
const disconnectedInput = this.inputs[index];
|
||||
if (disconnectedInput && STACK_INPUT_PATTERN.test(disconnectedInput.name)) {
|
||||
// Keep the last slot so there is always an empty slot to reconnect into
|
||||
const isLastStackSlot = index === this.inputs.findLastIndex(i => STACK_INPUT_PATTERN.test(i.name));
|
||||
if (!isLastStackSlot) {
|
||||
this.removeInput(index);
|
||||
|
||||
// Renumber again after removal
|
||||
let newSlotIndex = 1;
|
||||
for (const inp of this.inputs) {
|
||||
if (STACK_INPUT_PATTERN.test(inp.name)) {
|
||||
inp.name = `lora_stack${newSlotIndex}`;
|
||||
newSlotIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return onConnectionsChange?.apply?.(this, arguments);
|
||||
};
|
||||
},
|
||||
|
||||
nodeCreated(node, app) {
|
||||
if (node.comfyClass !== "Lora Stack Combiner (LoraManager)") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Leave legacy (a/b) workflows untouched
|
||||
const hasLegacyInputs = node.inputs.some(inp => inp.name === "lora_stack_a" || inp.name === "lora_stack_b");
|
||||
if (hasLegacyInputs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure at least two numbered lora_stack inputs exist on creation
|
||||
const stackInputCount = node.inputs.filter(inp => STACK_INPUT_PATTERN.test(inp.name)).length;
|
||||
for (let i = stackInputCount + 1; i <= 2; i++) {
|
||||
node.addInput(`lora_stack${i}`, "LORA_STACK", {
|
||||
tooltip: "A LoRA stack to combine. Connect to add more inputs."
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user