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.
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.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.59**: A lot of Javascript fixing to avoid resizing and better properties mangement / recoveries
# 📝 Nodes descriptions

View File

@@ -1,7 +1,7 @@
[project]
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."
version = "0.58"
version = "0.59"
license = {file = "LICENSE"}
[project.urls]

View File

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

View File

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

View File

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

View File

@@ -4,63 +4,95 @@ app.registerExtension({
name: "Bjornulf.LoopLoraSelector",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoopLoraSelector") {
node.properties = node.properties || {};
const updateLoraInputs = () => {
const initialWidth = node.size[0];
const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras");
if (!numLorasWidget) return;
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 =>
!w.name.startsWith("lora_") &&
!w.name.startsWith("strength_model_") &&
!w.name.startsWith("strength_clip_") ||
parseInt(w.name.split("_").pop()) <= numLoras
!w.name.startsWith("strength_clip_")
);
// 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++) {
const loraWidgetName = `lora_${i}`;
const strengthModelWidgetName = `strength_model_${i}`;
const strengthClipWidgetName = `strength_clip_${i}`;
if (!node.widgets.find(w => w.name === loraWidgetName)) {
const defaultIndex = Math.min(i - 1, loraList.length - 1);
node.addWidget("combo", loraWidgetName, loraList[defaultIndex], () => {}, {
// Add lora widget
const savedLoraValue = node.properties[loraWidgetName];
const loraWidget = node.addWidget("combo", loraWidgetName,
savedLoraValue !== undefined ? savedLoraValue : loraList[0],
(value) => {
node.properties[loraWidgetName] = value;
}, {
values: loraList
});
}
}
);
if (!node.widgets.find(w => w.name === strengthModelWidgetName)) {
node.addWidget("number", strengthModelWidgetName, 1.0, () => {}, {
// Add strength model widget
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
});
}
}
);
if (!node.widgets.find(w => w.name === strengthClipWidgetName)) {
node.addWidget("number", strengthClipWidgetName, 1.0, () => {}, {
// Add strength clip widget
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
});
}
}
// 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)));
node.widgets = orderedWidgets.filter(w => w !== undefined);
node.widgets = orderedWidgets;
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");
if (numLorasWidget) {
numLorasWidget.callback = () => {
@@ -76,32 +108,47 @@ app.registerExtension({
originalOnConfigure.call(this, info);
}
// Restore lora widgets and strength widgets based on saved properties
const savedProperties = info.properties;
if (savedProperties) {
Object.keys(savedProperties).forEach(key => {
if (key.startsWith("lora_") || key.startsWith("strength_model_") || key.startsWith("strength_clip_")) {
const widgetName = key;
const widgetValue = savedProperties[key];
const existingWidget = node.widgets.find(w => w.name === widgetName);
if (existingWidget) {
existingWidget.value = widgetValue;
} else {
if (key.startsWith("lora_")) {
node.addWidget("combo", widgetName, widgetValue, () => {}, {
values: node.widgets.find(w => w.name === "lora_1").options.values
});
} else {
node.addWidget("number", widgetName, widgetValue, () => {}, {
min: -100.0, max: 100.0, step: 0.01
});
}
}
}
});
// if (info.properties) {
// // Restore properties
// Object.assign(this.properties, info.properties);
// }
// const savedProperties = info.properties;
// if (savedProperties) {
// Object.keys(savedProperties).forEach(key => {
// if (key.startsWith("lora_") || key.startsWith("strength_model_") || key.startsWith("strength_clip_")) {
// const widgetName = key;
// const widgetValue = savedProperties[key];
// const existingWidget = node.widgets.find(w =>
// w.name === widgetName
// );
// if (existingWidget) {
// existingWidget.value = widgetValue;
// } else {
// const baseWidget = node.widgets.find(w =>
// 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);
}
// Update lora inputs after restoring saved state
info.properties = { ...this.properties };
};
// Update the widgets based on the current number_of_loras value
updateLoraInputs();
};

View File

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

View File

@@ -4,43 +4,64 @@ app.registerExtension({
name: "Bjornulf.LoopModelSelector",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoopModelSelector") {
node.properties = node.properties || {};
const updateModelInputs = () => {
const initialWidth = node.size[0];
const numModelsWidget = node.widgets.find(w => w.name === "number_of_models");
if (!numModelsWidget) return;
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
node.widgets = node.widgets.filter(w => !w.name.startsWith("model_") || parseInt(w.name.split("_")[1]) <= numModels);
// Add new model widgets if needed
node.widgets = node.widgets.filter(w =>
!w.name.startsWith("model_") || parseInt(w.name.split("_")[1]) <= numModels
);
// 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++) {
const widgetName = `model_${i}`;
if (!node.widgets.find(w => w.name === widgetName)) {
const defaultIndex = Math.min(i - 1, checkpointsList.length - 1);
node.addWidget("combo", widgetName, checkpointsList[defaultIndex], () => {}, {
values: checkpointsList
});
}
const savedValue = node.properties[widgetName];
const defaultIndex = Math.min(i - 1, checkpointsList.length - 1);
const defaultValue = savedValue !== undefined ? savedValue : checkpointsList[defaultIndex];
const modelWidget = node.addWidget("combo", widgetName, defaultValue, (value) => {
node.properties[widgetName] = value;
}, {
values: checkpointsList
});
}
// Reorder widgets
node.widgets.sort((a, b) => {
if (a.name === "number_of_models") return -1;
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);
});
// 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);
}
// Add any remaining widgets
orderedWidgets.push(...node.widgets.filter(w => !orderedWidgets.includes(w)));
node.widgets = orderedWidgets;
// Adjust node size
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");
if (numModelsWidget) {
numModelsWidget.callback = () => {
@@ -49,48 +70,56 @@ 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
const originalOnConfigure = node.onConfigure;
node.onConfigure = function(info) {
if (originalOnConfigure) {
originalOnConfigure.call(this, info);
}
// Restore model widgets based on saved properties
const savedProperties = info.properties;
if (savedProperties) {
Object.keys(savedProperties).forEach(key => {
if (key.startsWith("model_")) {
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 === "model_1").options.values
});
}
}
});
}
// const savedProperties = info.properties;
// if (savedProperties) {
// Object.keys(savedProperties).forEach(key => {
// if (key.startsWith("model_")) {
// const widgetName = key;
// const widgetValue = savedProperties[key];
// const existingWidget = node.widgets.find(w => w.name === widgetName);
// if (existingWidget) {
// existingWidget.value = widgetValue;
// } else {
// 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
// });
// }
// }
// });
// }
// Ensure seed is a valid integer
const seedWidget = node.widgets.find(w => w.name === "seed");
if (seedWidget && isNaN(parseInt(seedWidget.value))) {
seedWidget.value = 0; // Set a default value if invalid
// 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
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
updateModelInputs();
}

View File

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

View File

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

View File

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

View File

@@ -4,43 +4,69 @@ app.registerExtension({
name: "Bjornulf.RandomLoraSelector",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_RandomLoraSelector") {
node.properties = node.properties || {};
const updateLoraInputs = () => {
const initialWidth = node.size[0];
const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras");
if (!numLorasWidget) return;
const numLoras = numLorasWidget.value;
const loraList = node.widgets.find(w => w.name === "lora_1").options.values;
// Remove excess lora widgets
node.widgets = node.widgets.filter(w => !w.name.startsWith("lora_") || parseInt(w.name.split("_")[1]) <= numLoras);
// Add new lora widgets if needed
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
});
const loraList = node.widgets.find(w => w.name === "lora_1")?.options?.values || [];
// Save existing properties before clearing widgets
node.widgets.forEach(w => {
if (w.name.startsWith("lora_") || ["strength_model", "strength_clip", "seed", "control_after_generate"].includes(w.name)) {
node.properties[w.name] = w.value;
}
}
// 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);
});
// 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;
}
};
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
// Set up number_of_loras widget callback
const numLorasWidget = node.widgets.find(w => w.name === "number_of_loras");
if (numLorasWidget) {
numLorasWidget.callback = () => {
@@ -49,50 +75,33 @@ 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
const originalOnConfigure = node.onConfigure;
node.onConfigure = function(info) {
if (originalOnConfigure) {
originalOnConfigure.call(this, info);
}
// Restore lora widgets based on saved properties
const savedProperties = info.properties;
if (savedProperties) {
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
});
}
}
});
// Restore saved properties
if (info.properties) {
Object.assign(this.properties, info.properties);
}
// Ensure seed is a valid integer
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
// Update the widgets based on the current number_of_loras value
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
updateLoraInputs();
}
}
});
});