Files
Burgstall-labs-ComfyUI-BETA…/js/betaHelperNodes.js
Burgstall-labs e7e28bcd39 refactor: Simplify Scene Detection node with fixed 5-scene output and chaining
- Remove JavaScript dynamic output logic (not working in ComfyUI)
- Fix MAX_SCENES at 5 (fixed maximum scene outputs)
- Remove max_scenes parameter (no longer needed)
- Add remaining_frames output for chaining to additional Scene Detect nodes
- Enhance scene_summary with processing statistics:
  * Total frames processed
  * Scenes detected vs. output
  * Remaining frames count for chaining
  * Frame ranges for each scene
- Update readme.md with Scene Detect documentation
- Add PySceneDetect dependency to readme
2025-11-11 18:13:34 +02:00

168 lines
9.5 KiB
JavaScript

import { app } from "../../../scripts/app.js";
console.log("[BETA Helper Nodes] betaHelperNodes.js (v_add_remove_resize) script loaded.");
app.registerExtension({
name: "Comfy.BETAHelperNodes.IndexedLoraLoaderLogic",
async nodeCreated(node) {
const expectedNodeTitle = "Indexed LoRA Loader 🎯 🅑🅔🅣🅐"; // Ensure this matches your node's display name
if (node.title === expectedNodeTitle) {
console.log(`[BETA Helper Nodes] MATCHED node by TITLE: '${node.title}'`);
if (!node.properties) {
node.properties = {};
}
const updateDynamicWidgets = () => {
const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras");
if (!numLorasWidget) {
console.error("[BETA Helper Nodes] 'number_of_loras' widget not found!");
return;
}
const numLoras = parseInt(numLorasWidget.value, 10) || 1;
const indexWidget = node.widgets.find(w => w.name === "index");
// CRITICAL: Save ALL current widget values BEFORE clearing widgets
// This ensures we capture the most up-to-date values directly from widgets,
// even if widget callbacks haven't fired yet or properties are stale
for (let i = 1; i <= 20; i++) {
const loraWidget = node.widgets.find(w => w.name === `lora_${i}`);
if (loraWidget) {
// Always read the current widget value directly, don't rely on stale properties
node.properties[`lora_${i}`] = loraWidget.value;
}
}
if (indexWidget) {
node.properties["index"] = indexWidget.value;
}
const staticWidgets = node.widgets.filter(w => !w.name.startsWith("lora_"));
node.widgets = [...staticWidgets];
let loraOptionsList = ["none"];
const firstLoraWidgetInProperties = node.properties["lora_1"]; // Check if a value was saved
// Try to get options from a previously existing lora_1 if its definition is in node.widgets_values
// This part is heuristic as JS doesn't re-run Python INPUT_TYPES.
// If you have access to the original widget definition somehow, that's better.
if (node.widgets_values && node.widgets_values["lora_1"] && Array.isArray(node.widgets_values["lora_1"])) {
// This case is unlikely; widgets_values usually stores the *selected value*, not the options list.
// loraOptionsList = node.widgets_values["lora_1"];
} else {
// Fallback: Check if the Python side somehow made options available on the node object
// or if we can find a template widget that defines them.
// This is often the hardest part for fully dynamic JS widgets if options are defined in Python.
// For now, we assume 'none' or a fixed list if not inferable.
// A common pattern is to have a hidden widget or property that stores the original options list.
}
// If your LoRA list is populated from folder_paths.get_filename_list("loras") in Python,
// that list isn't directly available here. The options for the *combo* widget are set
// when node.addWidget is called. If it's the first time, we need a source for `loraOptionsList`.
// If `lora_X` widgets are defined in `optional_inputs` in Python, their initial options
// are set there. We need to preserve/reuse those.
// Let's assume for now `loraOptionsList` should be derived from the node's initial config if possible
// or a fixed default. The Bjornulf code did:
// const loraList = node.widgets.find(w => w.name === "lora_1")?.options?.values || [];
// This implies lora_1 must exist or have existed to get its options.
// We can try:
const anExistingLoraWidgetBeforeRemoval = node.constructor.nodeData?.widgets?.find(w => w.name === "lora_1"); // Check original node data
if(anExistingLoraWidgetBeforeRemoval && anExistingLoraWidgetBeforeRemoval.options && anExistingLoraWidgetBeforeRemoval.options.values) {
loraOptionsList = anExistingLoraWidgetBeforeRemoval.options.values;
} else if (node.widgets_values && typeof node.widgets_values.lora_1_options !== "undefined") {
// A convention could be to save options in widgets_values if they are dynamic.
loraOptionsList = node.widgets_values.lora_1_options;
}
// If still no list, use a hardcoded default or ensure Python provides it.
// For now, it might default to just ["none"] if no other source is found by this logic.
for (let i = 1; i <= numLoras; i++) {
const loraWidgetName = `lora_${i}`;
const savedValue = node.properties[loraWidgetName];
const defaultValue = loraOptionsList.includes(savedValue) ? savedValue : loraOptionsList[0] || "none";
node.addWidget(
"combo",
loraWidgetName,
defaultValue,
(value) => { node.properties[loraWidgetName] = value; },
{ values: loraOptionsList }
);
}
if (indexWidget) {
const newMax = Math.max(1, numLoras);
indexWidget.options.max = newMax;
if (indexWidget.inputEl && typeof indexWidget.inputEl.max !== 'undefined') {
indexWidget.inputEl.max = newMax.toString();
}
let currentIndexValue = parseInt(indexWidget.value, 10);
const minVal = parseInt(indexWidget.options.min || 1, 10);
if(isNaN(currentIndexValue)) currentIndexValue = minVal;
let clampedIndexValue = currentIndexValue;
if (clampedIndexValue > newMax) clampedIndexValue = newMax;
if (clampedIndexValue < minVal) clampedIndexValue = minVal;
if (indexWidget.value !== clampedIndexValue) {
indexWidget.value = clampedIndexValue;
}
}
// --- 5. Finalize ---
const new_computed_size = node.computeSize(); // Calculate the ideal size based on new content
node.setSize(new_computed_size); // Set the node to this computed size
node.setDirtyCanvas(true, true);
};
const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras");
if (numLorasWidget) {
const originalCallback = numLorasWidget.callback;
numLorasWidget.callback = (value) => {
numLorasWidget.value = parseInt(value, 10);
if (isNaN(numLorasWidget.value)) numLorasWidget.value = 1;
if (originalCallback) originalCallback.call(numLorasWidget, numLorasWidget.value);
updateDynamicWidgets();
};
}
const onNodeConfigure = node.onConfigure;
node.onConfigure = function(info) {
if (onNodeConfigure) onNodeConfigure.apply(this, arguments);
if (!this.properties) this.properties = {}; // Ensure properties exists
if (info.widgets_values) {
for (let i = 1; i <= 20; i++) {
const loraWidgetName = `lora_${i}`;
if (info.widgets_values[loraWidgetName] !== undefined) {
this.properties[loraWidgetName] = info.widgets_values[loraWidgetName];
}
}
// If lora options were saved, restore them
if (typeof info.widgets_values.lora_1_options !== "undefined") {
this.widgets_values.lora_1_options = info.widgets_values.lora_1_options;
}
}
setTimeout(updateDynamicWidgets, 50);
};
// To ensure loraOptionsList is available when the node is first created or loaded
// we might need to extract it from INPUT_TYPES definition if possible, or have Python save it.
// One way: Python adds a hidden widget or property with the LoRA list.
// For now, make a placeholder for how Python might provide this:
if (!node.widgets_values) node.widgets_values = {};
if (node.widgets && node.widgets.find(w=>w.name==="lora_1") && node.widgets.find(w=>w.name==="lora_1").options.values) {
node.widgets_values.lora_1_options = node.widgets.find(w=>w.name==="lora_1").options.values;
} else {
// Attempt to get it from the class's default widget definitions if available
// This part is highly dependent on how ComfyUI stores original widget definitions accessible to JS
// For now, if it's not found, loraOptionsList will default to ["none"] inside updateDynamicWidgets.
}
setTimeout(updateDynamicWidgets, 100);
}
}
});