This commit is contained in:
justumen
2024-11-22 19:20:35 +01:00
parent 3d8d94597f
commit 0a968c1540
12 changed files with 270 additions and 170 deletions

View File

@@ -1,4 +1,4 @@
# 🔗 Comfyui : Bjornulf_custom_nodes v0.58 🔗 # 🔗 Comfyui : Bjornulf_custom_nodes v0.59 🔗
A list of 68 custom nodes for Comfyui : Display, manipulate, and edit text, images, videos, loras and more. A list of 68 custom nodes for Comfyui : Display, manipulate, and edit text, images, videos, loras and more.
You can manage looping operations, generate randomized content, trigger logical conditions, pause and manually control your workflows and even work with external AI tools, like Ollama or Text To Speech. You can manage looping operations, generate randomized content, trigger logical conditions, pause and manually control your workflows and even work with external AI tools, like Ollama or Text To Speech.
@@ -284,6 +284,7 @@ cd /where/you/installed/ComfyUI && python main.py
- **0.56**: ❗Breaking changes : ollama node simplified, no ollama_ip.txt needed, waiting for collection ollama nodes to be ready. - **0.56**: ❗Breaking changes : ollama node simplified, no ollama_ip.txt needed, waiting for collection ollama nodes to be ready.
- **0.57**: ❗❗Huge changes, new Ollama node "Ollama Chat" with real functionalities. 5 Ollama nodes total. (Model selector + Job selector + Persona selector + Ollama vision + Ollama Talk) Ollama talk use context and can use context file. Add number of lines / current counter + next to sequential nodes. Add new node STT. (+ faster_whisper dep) better management of empty loras/checkpoints on selectors. (list preset) Add "default_for_language" for TTS node, taking the default voice for a language (ex: fr/default.wav) Otherwise take the first wav with the selected language. - **0.57**: ❗❗Huge changes, new Ollama node "Ollama Chat" with real functionalities. 5 Ollama nodes total. (Model selector + Job selector + Persona selector + Ollama vision + Ollama Talk) Ollama talk use context and can use context file. Add number of lines / current counter + next to sequential nodes. Add new node STT. (+ faster_whisper dep) better management of empty loras/checkpoints on selectors. (list preset) Add "default_for_language" for TTS node, taking the default voice for a language (ex: fr/default.wav) Otherwise take the first wav with the selected language.
- **0.58**: small fix in model selector default value. (Set to None by default) - **0.58**: small fix in model selector default value. (Set to None by default)
- **0.59**: A lot of Javascript fixing to avoid resizing and better properties mangement / recoveries
# 📝 Nodes descriptions # 📝 Nodes descriptions

View File

@@ -1,7 +1,7 @@
[project] [project]
name = "bjornulf_custom_nodes" name = "bjornulf_custom_nodes"
description = "61 ComfyUI nodes : Display, manipulate, and edit text, images, videos, loras and more. Manage looping operations, generate randomized content, use logical conditions and work with external AI tools, like Ollama or Text To Speech." description = "61 ComfyUI nodes : Display, manipulate, and edit text, images, videos, loras and more. Manage looping operations, generate randomized content, use logical conditions and work with external AI tools, like Ollama or Text To Speech."
version = "0.58" version = "0.59"
license = {file = "LICENSE"} license = {file = "LICENSE"}
[project.urls] [project.urls]

View File

@@ -5,6 +5,7 @@ app.registerExtension({
async nodeCreated(node) { async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_CombineImages") { if (node.comfyClass === "Bjornulf_CombineImages") {
const updateInputs = () => { const updateInputs = () => {
const initialWidth = node.size[0];
const numInputsWidget = node.widgets.find(w => w.name === "number_of_images"); const numInputsWidget = node.widgets.find(w => w.name === "number_of_images");
if (!numInputsWidget) return; if (!numInputsWidget) return;
@@ -33,6 +34,7 @@ app.registerExtension({
} }
node.setSize(node.computeSize()); node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
}; };
// Move number_of_images to the top initially // Move number_of_images to the top initially

View File

@@ -5,6 +5,7 @@ app.registerExtension({
async nodeCreated(node) { async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_CombineTexts") { if (node.comfyClass === "Bjornulf_CombineTexts") {
const updateInputs = () => { const updateInputs = () => {
const initialWidth = node.size[0];
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs"); const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
if (!numInputsWidget) return; if (!numInputsWidget) return;
@@ -33,6 +34,7 @@ app.registerExtension({
} }
node.setSize(node.computeSize()); node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
}; };
// Move number_of_inputs to the top initially // Move number_of_inputs to the top initially

View File

@@ -5,6 +5,7 @@ app.registerExtension({
async nodeCreated(node) { async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoopImages") { if (node.comfyClass === "Bjornulf_LoopImages") {
const updateInputs = () => { const updateInputs = () => {
const initialWidth = node.size[0];
const numInputsWidget = node.widgets.find(w => w.name === "number_of_images"); const numInputsWidget = node.widgets.find(w => w.name === "number_of_images");
if (!numInputsWidget) return; if (!numInputsWidget) return;
@@ -33,6 +34,7 @@ app.registerExtension({
} }
node.setSize(node.computeSize()); node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
}; };
// Move number_of_images to the top initially // Move number_of_images to the top initially

View File

@@ -4,63 +4,95 @@ app.registerExtension({
name: "Bjornulf.LoopLoraSelector", name: "Bjornulf.LoopLoraSelector",
async nodeCreated(node) { async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoopLoraSelector") { if (node.comfyClass === "Bjornulf_LoopLoraSelector") {
node.properties = node.properties || {};
const updateLoraInputs = () => { const updateLoraInputs = () => {
const initialWidth = node.size[0];
const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras"); const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras");
if (!numLorasWidget) return; if (!numLorasWidget) return;
const numLoras = numLorasWidget.value; const numLoras = numLorasWidget.value;
const loraList = node.widgets.find(w => w.name === "lora_1").options.values; const loraList = node.widgets.find(w => w.name === "lora_1")?.options?.values || [];
// Remove excess lora widgets and their corresponding strength widgets // Remove excess lora widgets
node.widgets = node.widgets.filter(w => !w.name.startsWith("lora_") || parseInt(w.name.split("_")[1]) <= numLoras);
// Store current widget values in properties
node.widgets.forEach(w => {
if (w.name.startsWith("lora_") ||
w.name.startsWith("strength_model_") ||
w.name.startsWith("strength_clip_")) {
node.properties[w.name] = w.value;
}
});
// Remove all lora-related widgets
node.widgets = node.widgets.filter(w => node.widgets = node.widgets.filter(w =>
!w.name.startsWith("lora_") && !w.name.startsWith("lora_") &&
!w.name.startsWith("strength_model_") && !w.name.startsWith("strength_model_") &&
!w.name.startsWith("strength_clip_") || !w.name.startsWith("strength_clip_")
parseInt(w.name.split("_").pop()) <= numLoras
); );
// Add new lora widgets and their corresponding strength widgets if needed // Add widgets only for the specified number of loras
for (let i = 1; i <= numLoras; i++) { for (let i = 1; i <= numLoras; i++) {
const loraWidgetName = `lora_${i}`; const loraWidgetName = `lora_${i}`;
const strengthModelWidgetName = `strength_model_${i}`; const strengthModelWidgetName = `strength_model_${i}`;
const strengthClipWidgetName = `strength_clip_${i}`; const strengthClipWidgetName = `strength_clip_${i}`;
if (!node.widgets.find(w => w.name === loraWidgetName)) { // Add lora widget
const defaultIndex = Math.min(i - 1, loraList.length - 1); const savedLoraValue = node.properties[loraWidgetName];
node.addWidget("combo", loraWidgetName, loraList[defaultIndex], () => {}, { const loraWidget = node.addWidget("combo", loraWidgetName,
savedLoraValue !== undefined ? savedLoraValue : loraList[0],
(value) => {
node.properties[loraWidgetName] = value;
}, {
values: loraList values: loraList
});
} }
);
if (!node.widgets.find(w => w.name === strengthModelWidgetName)) { // Add strength model widget
node.addWidget("number", strengthModelWidgetName, 1.0, () => {}, { const savedModelValue = node.properties[strengthModelWidgetName];
const strengthModelWidget = node.addWidget("number", strengthModelWidgetName,
savedModelValue !== undefined ? savedModelValue : 1.0,
(value) => {
node.properties[strengthModelWidgetName] = value;
}, {
min: -100.0, max: 100.0, step: 0.01 min: -100.0, max: 100.0, step: 0.01
});
} }
);
if (!node.widgets.find(w => w.name === strengthClipWidgetName)) { // Add strength clip widget
node.addWidget("number", strengthClipWidgetName, 1.0, () => {}, { const savedClipValue = node.properties[strengthClipWidgetName];
const strengthClipWidget = node.addWidget("number", strengthClipWidgetName,
savedClipValue !== undefined ? savedClipValue : 1.0,
(value) => {
node.properties[strengthClipWidgetName] = value;
}, {
min: -100.0, max: 100.0, step: 0.01 min: -100.0, max: 100.0, step: 0.01
});
} }
}
// Reorder widgets
const orderedWidgets = [node.widgets.find(w => w.name === "number_of_loras")];
for (let i = 1; i <= numLoras; i++) {
orderedWidgets.push(
node.widgets.find(w => w.name === `lora_${i}`),
node.widgets.find(w => w.name === `strength_model_${i}`),
node.widgets.find(w => w.name === `strength_clip_${i}`)
); );
} }
// Reorder widgets: number_of_loras first, then grouped lora widgets
const orderedWidgets = [node.widgets.find(w => w.name === "number_of_loras")];
for (let i = 1; i <= numLoras; i++) {
const loraWidgets = node.widgets.filter(w =>
w.name === `lora_${i}` ||
w.name === `strength_model_${i}` ||
w.name === `strength_clip_${i}`
);
orderedWidgets.push(...loraWidgets);
}
// Add any remaining widgets
orderedWidgets.push(...node.widgets.filter(w => !orderedWidgets.includes(w))); orderedWidgets.push(...node.widgets.filter(w => !orderedWidgets.includes(w)));
node.widgets = orderedWidgets.filter(w => w !== undefined); node.widgets = orderedWidgets;
node.setSize(node.computeSize()); node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
}; };
// Set up number_of_loras widget // Set up number_of_loras widget callback
const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras"); const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras");
if (numLorasWidget) { if (numLorasWidget) {
numLorasWidget.callback = () => { numLorasWidget.callback = () => {
@@ -76,32 +108,47 @@ app.registerExtension({
originalOnConfigure.call(this, info); originalOnConfigure.call(this, info);
} }
// Restore lora widgets and strength widgets based on saved properties // if (info.properties) {
const savedProperties = info.properties; // // Restore properties
if (savedProperties) { // Object.assign(this.properties, info.properties);
Object.keys(savedProperties).forEach(key => { // }
if (key.startsWith("lora_") || key.startsWith("strength_model_") || key.startsWith("strength_clip_")) { // const savedProperties = info.properties;
const widgetName = key; // if (savedProperties) {
const widgetValue = savedProperties[key]; // Object.keys(savedProperties).forEach(key => {
const existingWidget = node.widgets.find(w => w.name === widgetName); // if (key.startsWith("lora_") || key.startsWith("strength_model_") || key.startsWith("strength_clip_")) {
if (existingWidget) { // const widgetName = key;
existingWidget.value = widgetValue; // const widgetValue = savedProperties[key];
} else { // const existingWidget = node.widgets.find(w =>
if (key.startsWith("lora_")) { // w.name === widgetName
node.addWidget("combo", widgetName, widgetValue, () => {}, { // );
values: node.widgets.find(w => w.name === "lora_1").options.values // if (existingWidget) {
}); // existingWidget.value = widgetValue;
} else { // } else {
node.addWidget("number", widgetName, widgetValue, () => {}, { // const baseWidget = node.widgets.find(w =>
min: -100.0, max: 100.0, step: 0.01 // w.name === "lora_1" ||
}); // w.name === "strength_model_1" ||
} // w.name === "strength_clip_1"
} // );
} // if (baseWidget) {
}); // node.addWidget("combo", widgetName, widgetValue, () => {}, {
// values: baseWidget.options.values
// });
// }
// }
// }
// });
// }
// Save properties during serialization
const originalOnSerialize = node.onSerialize;
node.onSerialize = function(info) {
if (originalOnSerialize) {
originalOnSerialize.call(this, info);
} }
info.properties = { ...this.properties };
};
// Update lora inputs after restoring saved state
// Update the widgets based on the current number_of_loras value
updateLoraInputs(); updateLoraInputs();
}; };

View File

@@ -5,6 +5,7 @@ app.registerExtension({
async nodeCreated(node) { async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoopModelClipVae") { if (node.comfyClass === "Bjornulf_LoopModelClipVae") {
const updateInputs = () => { const updateInputs = () => {
const initialWidth = node.size[0];
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs"); const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
if (!numInputsWidget) return; if (!numInputsWidget) return;
@@ -46,6 +47,7 @@ app.registerExtension({
} }
node.setSize(node.computeSize()); node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
}; };
// Move number_of_inputs to the top initially // Move number_of_inputs to the top initially

View File

@@ -4,43 +4,64 @@ app.registerExtension({
name: "Bjornulf.LoopModelSelector", name: "Bjornulf.LoopModelSelector",
async nodeCreated(node) { async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoopModelSelector") { if (node.comfyClass === "Bjornulf_LoopModelSelector") {
node.properties = node.properties || {};
const updateModelInputs = () => { const updateModelInputs = () => {
const initialWidth = node.size[0];
const numModelsWidget = node.widgets.find(w => w.name === "number_of_models"); const numModelsWidget = node.widgets.find(w => w.name === "number_of_models");
if (!numModelsWidget) return; if (!numModelsWidget) return;
const numModels = numModelsWidget.value; const numModels = numModelsWidget.value;
const checkpointsList = node.widgets.find(w => w.name === "model_1").options.values; const checkpointsList = node.widgets.find(w => w.name === "model_1")?.options?.values || [];
// Remove excess model widgets // Remove excess model widgets
node.widgets = node.widgets.filter(w => !w.name.startsWith("model_") || parseInt(w.name.split("_")[1]) <= numModels); node.widgets = node.widgets.filter(w =>
!w.name.startsWith("model_") || parseInt(w.name.split("_")[1]) <= numModels
);
// Add new model widgets if needed // Store current widget values in properties
node.widgets.forEach(w => {
if (w.name.startsWith("model_")) {
node.properties[w.name] = w.value;
}
});
// Remove all model-related widgets
node.widgets = node.widgets.filter(w =>
!w.name.startsWith("model_")
);
// Add new model widgets
for (let i = 1; i <= numModels; i++) { for (let i = 1; i <= numModels; i++) {
const widgetName = `model_${i}`; const widgetName = `model_${i}`;
if (!node.widgets.find(w => w.name === widgetName)) { const savedValue = node.properties[widgetName];
const defaultIndex = Math.min(i - 1, checkpointsList.length - 1); const defaultIndex = Math.min(i - 1, checkpointsList.length - 1);
node.addWidget("combo", widgetName, checkpointsList[defaultIndex], () => {}, { const defaultValue = savedValue !== undefined ? savedValue : checkpointsList[defaultIndex];
const modelWidget = node.addWidget("combo", widgetName, defaultValue, (value) => {
node.properties[widgetName] = value;
}, {
values: checkpointsList values: checkpointsList
}); });
} }
// Reorder widgets: number_of_models first, then the model widgets
const orderedWidgets = [node.widgets.find(w => w.name === "number_of_models")];
for (let i = 1; i <= numModels; i++) {
const modelWidgets = node.widgets.filter(w => w.name === `model_${i}`);
orderedWidgets.push(...modelWidgets);
} }
// Reorder widgets // Add any remaining widgets
node.widgets.sort((a, b) => { orderedWidgets.push(...node.widgets.filter(w => !orderedWidgets.includes(w)));
if (a.name === "number_of_models") return -1; node.widgets = orderedWidgets;
if (b.name === "number_of_models") return 1;
if (a.name === "seed") return 1;
if (b.name === "seed") return -1;
if (a.name.startsWith("model_") && b.name.startsWith("model_")) {
return parseInt(a.name.split("_")[1]) - parseInt(b.name.split("_")[1]);
}
return a.name.localeCompare(b.name);
});
// Adjust node size
node.setSize(node.computeSize()); node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
}; };
// Set up number_of_models widget // Set up number_of_models widget callback
const numModelsWidget = node.widgets.find(w => w.name === "number_of_models"); const numModelsWidget = node.widgets.find(w => w.name === "number_of_models");
if (numModelsWidget) { if (numModelsWidget) {
numModelsWidget.callback = () => { numModelsWidget.callback = () => {
@@ -49,12 +70,6 @@ app.registerExtension({
}; };
} }
// Set seed widget to integer input
const seedWidget = node.widgets.find((w) => w.name === "seed");
if (seedWidget) {
seedWidget.type = "HIDDEN"; // Hide seed widget after restoring saved state
}
// Handle deserialization // Handle deserialization
const originalOnConfigure = node.onConfigure; const originalOnConfigure = node.onConfigure;
node.onConfigure = function(info) { node.onConfigure = function(info) {
@@ -63,34 +78,48 @@ app.registerExtension({
} }
// Restore model widgets based on saved properties // Restore model widgets based on saved properties
const savedProperties = info.properties; // const savedProperties = info.properties;
if (savedProperties) { // if (savedProperties) {
Object.keys(savedProperties).forEach(key => { // Object.keys(savedProperties).forEach(key => {
if (key.startsWith("model_")) { // if (key.startsWith("model_")) {
const widgetName = key; // const widgetName = key;
const widgetValue = savedProperties[key]; // const widgetValue = savedProperties[key];
const existingWidget = node.widgets.find(w => w.name === widgetName); // const existingWidget = node.widgets.find(w => w.name === widgetName);
if (existingWidget) {
existingWidget.value = widgetValue;
} else {
node.addWidget("combo", widgetName, widgetValue, () => {}, {
values: node.widgets.find(w => w.name === "model_1").options.values
});
}
}
});
}
// Ensure seed is a valid integer // if (existingWidget) {
const seedWidget = node.widgets.find(w => w.name === "seed"); // existingWidget.value = widgetValue;
if (seedWidget && isNaN(parseInt(seedWidget.value))) { // } else {
seedWidget.value = 0; // Set a default value if invalid // const checkpointsList = node.widgets.find(w => w.name === "model_1")?.options?.values || [];
// const defaultIndex = Math.min(parseInt(widgetName.split("_")[1]) - 1, checkpointsList.length - 1);
// node.addWidget("combo", widgetName, widgetValue || checkpointsList[defaultIndex], () => {}, {
// values: checkpointsList
// });
// }
// }
// });
// }
// Save properties during serialization
const originalOnSerialize = node.onSerialize;
node.onSerialize = function(info) {
if (originalOnSerialize) {
originalOnSerialize.call(this, info);
} }
info.properties = { ...this.properties };
};
// Update model inputs after restoring saved state // Update model inputs after restoring saved state
updateModelInputs(); updateModelInputs();
}; };
// Serialize method to save properties
const originalSerialize = node.serialize;
node.serialize = function() {
const data = originalSerialize ? originalSerialize.call(this) : {};
data.properties = { ...this.properties };
return data;
};
// Initial update // Initial update
updateModelInputs(); updateModelInputs();
} }

View File

@@ -5,6 +5,7 @@ app.registerExtension({
async nodeCreated(node) { async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoopTexts") { if (node.comfyClass === "Bjornulf_LoopTexts") {
const updateInputs = () => { const updateInputs = () => {
const initialWidth = node.size[0];
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs"); const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
if (!numInputsWidget) return; if (!numInputsWidget) return;
@@ -33,6 +34,7 @@ app.registerExtension({
} }
node.setSize(node.computeSize()); node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
}; };
// Move number_of_inputs to the top initially // Move number_of_inputs to the top initially

View File

@@ -43,6 +43,7 @@ app.registerExtension({
}; };
const updateInputs = () => { const updateInputs = () => {
const initialWidth = node.size[0];
const numInputsWidget = node.widgets.find( const numInputsWidget = node.widgets.find(
(w) => w.name === "number_of_inputs" (w) => w.name === "number_of_inputs"
); );
@@ -82,6 +83,7 @@ app.registerExtension({
updateInputButtons(numInputs); updateInputButtons(numInputs);
node.setSize(node.computeSize()); node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
}; };
// Move number_of_inputs to the top initially // Move number_of_inputs to the top initially

View File

@@ -5,6 +5,7 @@ app.registerExtension({
async nodeCreated(node) { async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_RandomImage") { if (node.comfyClass === "Bjornulf_RandomImage") {
const updateInputs = () => { const updateInputs = () => {
const initialWidth = node.size[0];
const numInputsWidget = node.widgets.find(w => w.name === "number_of_images"); const numInputsWidget = node.widgets.find(w => w.name === "number_of_images");
if (!numInputsWidget) return; if (!numInputsWidget) return;
@@ -33,6 +34,7 @@ app.registerExtension({
} }
node.setSize(node.computeSize()); node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
}; };
// Set seed widget to hidden input // Set seed widget to hidden input

View File

@@ -4,43 +4,69 @@ app.registerExtension({
name: "Bjornulf.RandomLoraSelector", name: "Bjornulf.RandomLoraSelector",
async nodeCreated(node) { async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_RandomLoraSelector") { if (node.comfyClass === "Bjornulf_RandomLoraSelector") {
node.properties = node.properties || {};
const updateLoraInputs = () => { const updateLoraInputs = () => {
const initialWidth = node.size[0];
const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras"); const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras");
if (!numLorasWidget) return; if (!numLorasWidget) return;
const numLoras = numLorasWidget.value; const numLoras = numLorasWidget.value;
const loraList = node.widgets.find(w => w.name === "lora_1").options.values; const loraList = node.widgets.find(w => w.name === "lora_1")?.options?.values || [];
// Remove excess lora widgets // Save existing properties before clearing widgets
node.widgets = node.widgets.filter(w => !w.name.startsWith("lora_") || parseInt(w.name.split("_")[1]) <= numLoras); node.widgets.forEach(w => {
if (w.name.startsWith("lora_") || ["strength_model", "strength_clip", "seed", "control_after_generate"].includes(w.name)) {
// Add new lora widgets if needed node.properties[w.name] = w.value;
for (let i = 1; i <= numLoras; i++) {
const widgetName = `lora_${i}`;
if (!node.widgets.find(w => w.name === widgetName)) {
const defaultIndex = Math.min(i - 1, loraList.length - 1);
node.addWidget("combo", widgetName, loraList[defaultIndex], () => {}, {
values: loraList
});
} }
}
// Reorder widgets
node.widgets.sort((a, b) => {
if (a.name === "number_of_loras") return -1;
if (b.name === "number_of_loras") return 1;
if (a.name === "seed") return 1;
if (b.name === "seed") return -1;
if (a.name.startsWith("lora_") && b.name.startsWith("lora_")) {
return parseInt(a.name.split("_")[1]) - parseInt(b.name.split("_")[1]);
}
return a.name.localeCompare(b.name);
}); });
node.setSize(node.computeSize()); // Remove all LORA widgets
node.widgets = node.widgets.filter(w => !w.name.startsWith("lora_"));
// Ensure shared strength widgets exist (top section)
const ensureWidget = (name, type, defaultValue, config) => {
let widget = node.widgets.find(w => w.name === name);
if (!widget) {
const savedValue = node.properties[name];
widget = node.addWidget(type, name,
savedValue !== undefined ? savedValue : defaultValue,
value => { node.properties[name] = value; },
config
);
} else {
widget.value = node.properties[name] || widget.value;
}
}; };
// Set up number_of_loras widget ensureWidget("number_of_loras", "number", 3, { min: 1, max: 20, step: 1 });
ensureWidget("strength_model", "number", 1.0, { min: -100.0, max: 100.0, step: 0.01 });
ensureWidget("strength_clip", "number", 1.0, { min: -100.0, max: 100.0, step: 0.01 });
ensureWidget("seed", "number", 0, { step: 1 });
ensureWidget("control_after_generate", "checkbox", false, {});
// Add LORA widgets (bottom section)
for (let i = 1; i <= numLoras; i++) {
const loraWidgetName = `lora_${i}`;
const savedLoraValue = node.properties[loraWidgetName];
node.addWidget("combo", loraWidgetName,
savedLoraValue !== undefined ? savedLoraValue : loraList[0],
value => { node.properties[loraWidgetName] = value; },
{ values: loraList }
);
}
// Reorder widgets: shared widgets first, followed by LORA widgets
const sharedWidgetNames = ["number_of_loras", "strength_model", "strength_clip", "seed", "control_after_generate"];
const sharedWidgets = sharedWidgetNames.map(name => node.widgets.find(w => w.name === name));
const loraWidgets = node.widgets.filter(w => w.name.startsWith("lora_"));
const remainingWidgets = node.widgets.filter(w => !sharedWidgets.includes(w) && !loraWidgets.includes(w));
node.widgets = [...sharedWidgets, ...remainingWidgets, ...loraWidgets];
node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
};
// Set up number_of_loras widget callback
const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras"); const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras");
if (numLorasWidget) { if (numLorasWidget) {
numLorasWidget.callback = () => { numLorasWidget.callback = () => {
@@ -49,12 +75,6 @@ app.registerExtension({
}; };
} }
// Set seed widget to integer input
const seedWidget = node.widgets.find((w) => w.name === "seed");
if (seedWidget) {
seedWidget.type = "HIDDEN"; // Hide seed widget after restoring saved state
}
// Handle deserialization // Handle deserialization
const originalOnConfigure = node.onConfigure; const originalOnConfigure = node.onConfigure;
node.onConfigure = function(info) { node.onConfigure = function(info) {
@@ -62,35 +82,24 @@ app.registerExtension({
originalOnConfigure.call(this, info); originalOnConfigure.call(this, info);
} }
// Restore lora widgets based on saved properties // Restore saved properties
const savedProperties = info.properties; if (info.properties) {
if (savedProperties) { Object.assign(this.properties, info.properties);
Object.keys(savedProperties).forEach(key => {
if (key.startsWith("lora_")) {
const widgetName = key;
const widgetValue = savedProperties[key];
const existingWidget = node.widgets.find(w => w.name === widgetName);
if (existingWidget) {
existingWidget.value = widgetValue;
} else {
node.addWidget("combo", widgetName, widgetValue, () => {}, {
values: node.widgets.find(w => w.name === "lora_1").options.values
});
}
}
});
} }
// Ensure seed is a valid integer // Update the widgets based on the current number_of_loras value
const seedWidget = node.widgets.find(w => w.name === "seed");
if (seedWidget && isNaN(parseInt(seedWidget.value))) {
seedWidget.value = 0; // Set a default value if invalid
}
// Update lora inputs after restoring saved state
updateLoraInputs(); updateLoraInputs();
}; };
// Save properties during serialization
const originalOnSerialize = node.onSerialize;
node.onSerialize = function(info) {
if (originalOnSerialize) {
originalOnSerialize.call(this, info);
}
info.properties = { ...this.properties };
};
// Initial update // Initial update
updateLoraInputs(); updateLoraInputs();
} }