Files
ethanfel-ComfyUI-LoRA-Optim…/js/lora_stack_dynamic.js
T
EthanfelandClaude Opus 4.6 e9304617f4 feat: interference-aware auto-strength, save to loras folder, dynamic stacker JS
- Auto-strength now uses pairwise cosine similarity for exact vector-sum
  energy normalization instead of assuming orthogonal LoRAs
- Save Merged LoRA defaults to ComfyUI loras folder; supports absolute paths
- Add JS extension for LoRA Stack (Dynamic) widget show/hide
- Fix LTX architecture detection for middle_block/mid_block keys
- Update banner SVG with new feature tags

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 23:07:41 +01:00

93 lines
2.9 KiB
JavaScript

import { app } from "/scripts/app.js";
// Widget hide/show via type-mangling (standard ComfyUI pattern)
const HIDDEN_TAG = "loraopt_hidden";
const origProps = {};
function toggleWidget(node, widget, show) {
if (!widget) return;
if (!origProps[widget.name]) {
origProps[widget.name] = {
origType: widget.type,
origComputeSize: widget.computeSize,
};
}
widget.type = show ? origProps[widget.name].origType : HIDDEN_TAG;
widget.computeSize = show
? origProps[widget.name].origComputeSize
: () => [0, -4];
if (widget.linkedWidgets) {
for (const w of widget.linkedWidgets) {
toggleWidget(node, w, show);
}
}
}
function findWidget(node, name) {
return node.widgets ? node.widgets.find((w) => w.name === name) : null;
}
function updateVisibility(node) {
const modeWidget = findWidget(node, "mode");
const countWidget = findWidget(node, "lora_count");
if (!modeWidget || !countWidget) return;
const isSimple = modeWidget.value === "simple";
const count = countWidget.value;
const MAX = 10;
for (let i = 1; i <= MAX; i++) {
const visible = i <= count;
toggleWidget(node, findWidget(node, `lora_name_${i}`), visible);
toggleWidget(node, findWidget(node, `strength_${i}`), visible && isSimple);
toggleWidget(node, findWidget(node, `model_strength_${i}`), visible && !isSimple);
toggleWidget(node, findWidget(node, `clip_strength_${i}`), visible && !isSimple);
}
const newHeight = node.computeSize()[1];
node.setSize([node.size[0], newHeight]);
}
app.registerExtension({
name: "LoRAOptimizer.LoRAStackDynamic",
nodeCreated(node) {
if (node.comfyClass !== "LoRAStackDynamic") return;
// Intercept mode and lora_count changes to update visibility
for (const w of node.widgets || []) {
if (w.name !== "mode" && w.name !== "lora_count") continue;
let widgetValue = w.value;
const originalDescriptor =
Object.getOwnPropertyDescriptor(w, "value") ||
Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(w),
"value"
);
Object.defineProperty(w, "value", {
get() {
return originalDescriptor?.get
? originalDescriptor.get.call(w)
: widgetValue;
},
set(newVal) {
if (originalDescriptor?.set) {
originalDescriptor.set.call(w, newVal);
} else {
widgetValue = newVal;
}
updateVisibility(node);
},
});
}
// Initial visibility update (after a tick so widgets are fully initialized)
requestAnimationFrame(() => updateVisibility(node));
},
});