This commit is contained in:
justumen
2025-03-19 17:36:25 +01:00
parent 44d69e8907
commit 39dfb0220a
76 changed files with 3207 additions and 955 deletions

View File

@@ -26,4 +26,52 @@ app.registerExtension({
});
}
}
});
app.registerExtension({
name: "Bjornulf.LoadCivitAILinks",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoadCivitAILinks") {
// Add a refresh button widget
const refreshButton = node.addWidget(
"button",
"Refresh File List",
null,
() => {
fetch("/get_civitai_links_files", {
method: "POST",
})
.then((response) => response.json())
.then((data) => {
if (data.success) {
// Update the dropdown with the new file list
const dropdownWidget = node.widgets.find(w => w.name === "selected_file");
if (dropdownWidget) {
dropdownWidget.options.values = ["Not selected", ...data.files];
dropdownWidget.value = "Not selected";
app.ui.dialog.show(
"[LoadCivitAILinks] File list refreshed successfully!"
);
}
} else {
app.ui.dialog.show(
`[LoadCivitAILinks] Failed to refresh file list: ${
data.error || "Unknown error"
}`
);
}
})
.catch((error) => {
console.error(
"[LoadCivitAILinks] Error fetching links files:",
error
);
app.ui.dialog.show(
"[LoadCivitAILinks] An error occurred while refreshing the file list."
);
});
}
);
}
},
});

81
web/js/audio_preview.js Normal file
View File

@@ -0,0 +1,81 @@
import { api } from '../../../scripts/api.js';
import { app } from "../../../scripts/app.js";
// Function to display the audio preview
function displayAudioPreview(component, filename, category, autoplay, mute, loop) {
let audioWidget = component._audioWidget;
// Create the audio widget if it doesn't exist
if (!audioWidget) {
const container = document.createElement("div");
// Add the DOM widget to the component
audioWidget = component.addDOMWidget("Bjornulf", "preview", container, {
serialize: false,
hideOnZoom: false,
getValue() {
return container.value;
},
setValue(v) {
container.value = v;
},
});
// Initialize widget properties
audioWidget.value = { hidden: false, paused: false, params: {} };
audioWidget.parentElement = document.createElement("div");
audioWidget.parentElement.className = "audio_preview";
audioWidget.parentElement.style.width = "100%";
audioWidget.parentElement.style.marginBottom = "50px";
container.appendChild(audioWidget.parentElement);
// Create the audio element
audioWidget.audioElement = document.createElement("audio");
audioWidget.audioElement.controls = true;
audioWidget.audioElement.style.width = "100%";
// Hide the audio player on error
audioWidget.audioElement.addEventListener("error", () => {
audioWidget.parentElement.hidden = true;
});
audioWidget.parentElement.hidden = audioWidget.value.hidden;
audioWidget.parentElement.appendChild(audioWidget.audioElement);
component._audioWidget = audioWidget; // Store for reuse
}
// Set audio source and properties
const params = {
"filename": filename,
"subfolder": category,
"type": "temp",
"rand": Math.random().toString().slice(2, 12) // Cache-busting random parameter
};
const urlParams = new URLSearchParams(params);
audioWidget.audioElement.src = `api/view?${urlParams.toString()}`;
audioWidget.audioElement.autoplay = autoplay && !audioWidget.value.paused && !audioWidget.value.hidden;
audioWidget.audioElement.loop = loop;
}
// Register the extension
app.registerExtension({
name: "Bjornulf.AudioPreview",
async beforeRegisterNodeDef(nodeType, nodeData, appInstance) {
if (nodeData?.name === "Bjornulf_AudioPreview") {
// Define behavior when the node executes
nodeType.prototype.onExecuted = function(data) {
const autoplay = this.widgets.find(w => w.name === "autoplay")?.value ?? false;
const loop = this.widgets.find(w => w.name === "loop")?.value ?? false;
displayAudioPreview(this, data.audio[0], data.audio[1], autoplay, loop);
};
// Override computeSize to set a fixed height
nodeType.prototype.computeSize = function() {
const size = LiteGraph.LGraphNode.prototype.computeSize.call(this);
size[1] = 150; // Fixed height of 150px
return size;
};
}
}
});

53
web/js/fix_face.js Normal file
View File

@@ -0,0 +1,53 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.FixFace",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_FixFace") {
const updateInputs = () => {
const initialWidth = node.size[0];
const numFacesWidget = node.widgets.find(w => w.name === "number_of_faces");
if (!numFacesWidget) return;
const numFaces = numFacesWidget.value;
// Initialize node.inputs if it doesn't exist
if (!node.inputs) {
node.inputs = [];
}
// Filter existing FACE_SETTINGS inputs
const existingInputs = node.inputs.filter(input => input.name.startsWith('FACE_SETTINGS_'));
// Add or remove inputs based on number_of_faces
if (existingInputs.length < numFaces) {
for (let i = existingInputs.length + 1; i <= numFaces; i++) {
const inputName = `FACE_SETTINGS_${i}`;
if (!node.inputs.find(input => input.name === inputName)) {
node.addInput(inputName, "FACE_SETTINGS");
}
}
} else {
node.inputs = node.inputs.filter(input => !input.name.startsWith('FACE_SETTINGS_') ||
parseInt(input.name.split('_')[2]) <= numFaces);
}
node.setSize(node.computeSize());
node.size[0] = initialWidth; // Keep width fixed
};
// Move number_of_faces widget to the top and set callback
const numFacesWidget = node.widgets.find(w => w.name === "number_of_faces");
if (numFacesWidget) {
node.widgets = [numFacesWidget, ...node.widgets.filter(w => w !== numFacesWidget)];
numFacesWidget.callback = () => {
updateInputs();
app.graph.setDirtyCanvas(true);
};
}
// Initial update after node creation
setTimeout(updateInputs, 0);
}
}
});

View File

@@ -1,122 +1,135 @@
import { app } from "../../../scripts/app.js";
import { api } from "../../../scripts/api.js";
// Helper function to construct image URLs for ComfyUI
function imageDataToUrl(data) {
return api.apiURL(
`/view?filename=${encodeURIComponent(data.filename)}&type=${data.type}&subfolder=${data.subfolder}${app.getPreviewFormatParam()}`
`/view?filename=${encodeURIComponent(data.filename)}&type=${
data.type
}&subfolder=${data.subfolder}` + app.getPreviewFormatParam()
);
}
app.registerExtension({
name: "Bjornulf.FourImageViewer",
async nodeCreated(node) {
// Ensure this applies only to the specific node type
if (node.comfyClass !== "Bjornulf_FourImageViewer") return;
const marginTop = 90;
const verticalOffset = -30;
const minSize = 256; // Minimum size for the node
const maxSize = 2048; // Maximum size for the node
node.size = [512, 512 + marginTop];
node.images = new Array(4).fill(null);
const padding = 10;
// Constants for layout and sizing
const marginTop = 90; // Space at the top for node UI elements
const verticalOffset = -30; // Adjustment for canvas positioning
const minSize = 256; // Minimum size of the node
const maxSize = 2048; // Maximum size of the node
const padding = 10; // Padding around the image area
// Add resize handles
// Initialize node properties
node.size = [512, 512 + marginTop];
node.data = {
images: new Array(4).fill(null), // Array to hold up to 4 images
sliderX: null, // X position of the vertical slider
sliderY: null, // Y position of the horizontal slider
};
// Enable resizing
node.flags |= LiteGraph.RESIZABLE;
node.onResize = function(size) {
// Ensure square aspect ratio (excluding marginTop)
const minDimension = Math.max(minSize, Math.min(size[0], size[1] - marginTop));
// Handle resizing to maintain square aspect ratio
node.onResize = function (size) {
const minDimension = Math.max(
minSize,
Math.min(size[0], size[1] - marginTop)
);
const maxDimension = Math.min(maxSize, minDimension);
size[0] = maxDimension;
size[1] = maxDimension + marginTop;
// Update slider positions proportionally
const fullImgWidth = size[0] - 3 * padding;
const fullImgHeight = size[1] - padding - marginTop;
// Only update sliders if they exist (node has been initialized)
if (node.hasOwnProperty('sliderX')) {
// Update slider positions proportionally
if (node.data.hasOwnProperty("sliderX")) {
const oldWidth = node.size[0] - 3 * padding;
const oldHeight = node.size[1] - padding - marginTop;
// Calculate relative positions (0 to 1)
const relativeX = (node.sliderX - padding) / oldWidth;
const relativeY = (node.sliderY - marginTop) / oldHeight;
// Update slider positions
node.sliderX = padding + (fullImgWidth * relativeX);
node.sliderY = marginTop + (fullImgHeight * relativeY);
const relativeX = (node.data.sliderX - padding) / oldWidth;
const relativeY = (node.data.sliderY - marginTop) / oldHeight;
node.data.sliderX = padding + fullImgWidth * relativeX;
node.data.sliderY = marginTop + fullImgHeight * relativeY;
} else {
// Initial slider positions
node.sliderX = padding + fullImgWidth / 2;
node.sliderY = marginTop + fullImgHeight / 2;
node.data.sliderX = padding + fullImgWidth / 2;
node.data.sliderY = marginTop + fullImgHeight / 2;
}
node.size = size;
return size;
};
// Full area where images get drawn
const fullImgWidth = node.size[0] - 3 * padding;
const fullImgHeight = node.size[1] - padding - marginTop;
node.sliderX = padding + fullImgWidth / 2;
node.sliderY = marginTop + fullImgHeight / 2;
node.onMouseDown = function(e) {
// Handle mouse down to move sliders
node.onMouseDown = function (e) {
const rect = node.getBounding();
const [clickX, clickY] = [
e.canvasX - rect[0],
e.canvasY - rect[1] + verticalOffset
e.canvasY - rect[1] + verticalOffset,
];
const imgWidth = rect[2] - 3 * padding;
const imgHeight = rect[3] - padding - marginTop;
const xStart = padding;
const xEnd = xStart + imgWidth;
const yStart = marginTop;
const yEnd = yStart + imgHeight;
if (clickX >= xStart && clickX <= xEnd && clickY >= yStart && clickY <= yEnd) {
const hasImage2 = node.images[1] !== null;
const hasImage3 = node.images[2] !== null;
const hasImage4 = node.images[3] !== null;
const xStart = padding;
const xEnd = xStart + (node.size[0] - 3 * padding);
const yStart = marginTop;
const yEnd = yStart + (node.size[1] - padding - marginTop);
if (
clickX >= xStart &&
clickX <= xEnd &&
clickY >= yStart &&
clickY <= yEnd
) {
const hasImage2 = node.data.images[1] !== null;
const hasImage3 = node.data.images[2] !== null;
const hasImage4 = node.data.images[3] !== null;
// Lock sliderY to bottom if only two images are present
if (hasImage2 && !hasImage3 && !hasImage4) {
node.sliderY = yEnd;
node.data.sliderY = yEnd;
}
node.sliderX = Math.max(xStart, Math.min(clickX, xEnd));
node.sliderY = hasImage3 || hasImage4
? Math.max(yStart, Math.min(clickY, yEnd))
: yEnd;
node.data.sliderX = Math.max(xStart, Math.min(clickX, xEnd));
node.data.sliderY =
hasImage3 || hasImage4
? Math.max(yStart, Math.min(clickY, yEnd))
: yEnd;
app.graph.setDirtyCanvas(true, true);
return true;
}
return false;
};
node.onExecuted = async function(message) {
node.images = new Array(4).fill(null);
// Load images when the node is executed
node.onExecuted = async function (message) {
node.data.images = new Array(4).fill(null);
for (let i = 1; i <= 4; i++) {
const images = message[`images_${i}`] || [];
if (images.length) {
const imgData = images[0];
const img = new Image();
img.src = imageDataToUrl(imgData);
await new Promise(resolve => (img.onload = img.onerror = resolve));
node.images[i - 1] = img;
await new Promise((resolve) => (img.onload = img.onerror = resolve));
node.data.images[i - 1] = img;
}
}
app.graph.setDirtyCanvas(true, true);
};
node.onDrawForeground = function(ctx) {
const padding = 10;
// Render images and sliders
node.onDrawForeground = function (ctx) {
const xStart = padding;
const xEnd = xStart + (node.size[0] - 3 * padding);
const yStart = marginTop;
const yEnd = yStart + (node.size[1] - padding - marginTop);
const fullImgWidth = node.size[0] - 3 * padding;
const fullImgHeight = node.size[1] - padding - marginTop;
// Calculate fitted rectangle for image display
function getFittedDestRect(dx, dy, dWidth, dHeight, targetRatio) {
let newWidth = dWidth;
let newHeight = dWidth / targetRatio;
@@ -128,18 +141,34 @@ app.registerExtension({
const offsetY = dy + (dHeight - newHeight) / 2;
return [offsetX, offsetY, newWidth, newHeight];
}
// Draw a cropped image within specified bounds
function drawCroppedImage(img, dx, dy, dWidth, dHeight) {
if (!img) return;
let targetRatio = dWidth / dHeight;
if (node.images[0] && node.images[0].naturalWidth && node.images[0].naturalHeight) {
targetRatio = node.images[0].naturalWidth / node.images[0].naturalHeight;
if (
node.data.images[0] &&
node.data.images[0].naturalWidth &&
node.data.images[0].naturalHeight
) {
targetRatio =
node.data.images[0].naturalWidth /
node.data.images[0].naturalHeight;
}
const [ndx, ndy, ndWidth, ndHeight] = getFittedDestRect(dx, dy, dWidth, dHeight, targetRatio);
const [ndx, ndy, ndWidth, ndHeight] = getFittedDestRect(
dx,
dy,
dWidth,
dHeight,
targetRatio
);
const imgRatio = img.naturalWidth / img.naturalHeight;
let sx = 0, sy = 0, sWidth = img.naturalWidth, sHeight = img.naturalHeight;
let sx = 0,
sy = 0,
sWidth = img.naturalWidth,
sHeight = img.naturalHeight;
if (imgRatio > targetRatio) {
sWidth = img.naturalHeight * targetRatio;
sx = (img.naturalWidth - sWidth) / 2;
@@ -147,64 +176,139 @@ app.registerExtension({
sHeight = img.naturalWidth / targetRatio;
sy = (img.naturalHeight - sHeight) / 2;
}
ctx.drawImage(img, sx, sy, sWidth, sHeight, ndx, ndy, ndWidth, ndHeight);
ctx.drawImage(
img,
sx,
sy,
sWidth,
sHeight,
ndx,
ndy,
ndWidth,
ndHeight
);
}
const connectedImages = node.images.slice(1).filter(img => img !== null).length;
const connectedImages = node.data.images
.slice(1)
.filter((img) => img !== null).length;
if (connectedImages === 0) {
if (node.images[0]) {
drawCroppedImage(node.images[0], xStart, yStart, fullImgWidth, fullImgHeight);
// Single image display
if (node.data.images[0]) {
drawCroppedImage(
node.data.images[0],
xStart,
yStart,
fullImgWidth,
fullImgHeight
);
}
} else if (connectedImages === 1 && node.images[1]) {
const splitX = node.sliderX;
} else if (connectedImages === 1 && node.data.images[1]) {
// Two images with vertical split
const splitX = node.data.sliderX;
ctx.save();
ctx.beginPath();
ctx.rect(xStart, yStart, splitX - xStart, fullImgHeight);
ctx.clip();
drawCroppedImage(node.images[0], xStart, yStart, fullImgWidth, fullImgHeight);
drawCroppedImage(
node.data.images[0],
xStart,
yStart,
fullImgWidth,
fullImgHeight
);
ctx.restore();
ctx.save();
ctx.beginPath();
ctx.rect(splitX, yStart, xEnd - splitX, fullImgHeight);
ctx.clip();
drawCroppedImage(node.images[1], xStart, yStart, fullImgWidth, fullImgHeight);
drawCroppedImage(
node.data.images[1],
xStart,
yStart,
fullImgWidth,
fullImgHeight
);
ctx.restore();
} else {
// Three or four images with quadrants
const drawQuadrant = (imgIndex, clipX, clipY, clipW, clipH) => {
if (!node.images[imgIndex]) return;
if (!node.data.images[imgIndex]) return;
ctx.save();
ctx.beginPath();
ctx.rect(clipX, clipY, clipW, clipH);
ctx.clip();
drawCroppedImage(node.images[imgIndex], xStart, yStart, fullImgWidth, fullImgHeight);
drawCroppedImage(
node.data.images[imgIndex],
xStart,
yStart,
fullImgWidth,
fullImgHeight
);
ctx.restore();
};
drawQuadrant(0, xStart, yStart, node.sliderX - xStart, node.sliderY - yStart);
drawQuadrant(1, node.sliderX, yStart, xEnd - node.sliderX, node.sliderY - yStart);
if (node.images[3] === null) {
drawQuadrant(2, xStart, node.sliderY, xEnd - xStart, yEnd - node.sliderY);
drawQuadrant(
0,
xStart,
yStart,
node.data.sliderX - xStart,
node.data.sliderY - yStart
);
drawQuadrant(
1,
node.data.sliderX,
yStart,
xEnd - node.data.sliderX,
node.data.sliderY - yStart
);
if (node.data.images[3] === null) {
drawQuadrant(
2,
xStart,
node.data.sliderY,
xEnd - xStart,
yEnd - node.data.sliderY
);
} else {
drawQuadrant(2, xStart, node.sliderY, node.sliderX - xStart, yEnd - node.sliderY);
drawQuadrant(3, node.sliderX, node.sliderY, xEnd - node.sliderX, yEnd - node.sliderY);
drawQuadrant(
2,
xStart,
node.data.sliderY,
node.data.sliderX - xStart,
yEnd - node.data.sliderY
);
drawQuadrant(
3,
node.data.sliderX,
node.data.sliderY,
xEnd - node.data.sliderX,
yEnd - node.data.sliderY
);
}
}
// Draw sliders
ctx.strokeStyle = "#FFF";
ctx.lineWidth = 1;
if (connectedImages > 0) {
ctx.beginPath();
ctx.moveTo(node.sliderX, yStart);
ctx.lineTo(node.sliderX, yEnd);
if (node.data.images[3] === null && node.data.images[2] !== null) {
ctx.moveTo(node.data.sliderX, yStart);
ctx.lineTo(node.data.sliderX, node.data.sliderY);
} else {
ctx.moveTo(node.data.sliderX, yStart);
ctx.lineTo(node.data.sliderX, yEnd);
}
if (connectedImages >= 2) {
ctx.moveTo(xStart, node.sliderY);
ctx.lineTo(xEnd, node.sliderY);
ctx.moveTo(xStart, node.data.sliderY);
ctx.lineTo(xEnd, node.data.sliderY);
}
ctx.stroke();
}
};
},
});
});

47
web/js/load_text.js Normal file
View File

@@ -0,0 +1,47 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.LoadTextFromFolder",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoadTextFromFolder") {
// Add a refresh button widget
// Assuming this is inside your node's setup function
const refreshButton = node.addWidget(
"button",
"Refresh File List",
null,
() => {
fetch("/get_text_files", {
method: "POST",
})
.then((response) => response.json())
.then((data) => {
if (data.success) {
// Update the dropdown with the new file list
node.widgets[0].options.values = data.files; // Assuming the dropdown is the first widget
node.widgets[0].value = data.files[0] || ""; // Set default value
app.ui.dialog.show(
"[LoadTextFromFolder] File list refreshed successfully!"
);
} else {
app.ui.dialog.show(
`[LoadTextFromFolder] Failed to refresh file list: ${
data.error || "Unknown error"
}`
);
}
})
.catch((error) => {
console.error(
"[LoadTextFromFolder] Error fetching text files:",
error
);
app.ui.dialog.show(
"[LoadTextFromFolder] An error occurred while refreshing the file list."
);
});
}
);
}
},
});

58
web/js/math_node.js Normal file
View File

@@ -0,0 +1,58 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.MathNode",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_MathNode") {
const updateInputs = () => {
const initialWidth = node.size[0];
const numInputsWidget = node.widgets.find(w => w.name === "num_inputs");
if (!numInputsWidget) return;
const numInputs = numInputsWidget.value;
// Initialize node.inputs if it doesn't exist
if (!node.inputs) {
node.inputs = [];
}
// Filter existing value inputs
const existingInputs = node.inputs.filter(input => input.name.startsWith("value_"));
// Add new inputs if needed
if (existingInputs.length < numInputs) {
for (let i = existingInputs.length + 1; i <= numInputs; i++) {
const inputName = `value_${i}`;
if (!node.inputs.find(input => input.name === inputName)) {
node.addInput(inputName); // Type is defined in Python
}
}
}
// Remove excess inputs if too many
else {
node.inputs = node.inputs.filter(input =>
!input.name.startsWith("value_") ||
parseInt(input.name.split("_")[1]) <= numInputs
);
}
// Adjust node size while preserving width
node.setSize(node.computeSize());
node.size[0] = initialWidth;
};
// Ensure num_inputs widget is at the top and set its callback
const numInputsWidget = node.widgets.find(w => w.name === "num_inputs");
if (numInputsWidget) {
node.widgets = [numInputsWidget, ...node.widgets.filter(w => w !== numInputsWidget)];
numInputsWidget.callback = () => {
updateInputs();
app.graph.setDirtyCanvas(true);
};
}
// Perform initial input update after node creation
setTimeout(updateInputs, 0);
}
}
});

View File

@@ -1,44 +1,44 @@
import { app } from "../../../scripts/app.js";
// import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.ImageNoteLoadImage",
async nodeCreated(node) {
// Ensure the node is of the specific class
if (node.comfyClass !== "Bjornulf_ImageNoteLoadImage") return;
console.log("node created");
// app.registerExtension({
// name: "Bjornulf.ImageNoteLoadImage",
// async nodeCreated(node) {
// // Ensure the node is of the specific class
// if (node.comfyClass !== "Bjornulf_ImageNoteLoadImage") return;
// console.log("node created");
// Store the initial node size
let prevSize = [...node.size];
let stableCount = 0;
const minStableFrames = 3; // Number of frames the size must remain stable
// // Store the initial node size
// let prevSize = [...node.size];
// let stableCount = 0;
// const minStableFrames = 3; // Number of frames the size must remain stable
// Function to check if the node's size has stabilized
const checkSizeStable = () => {
if (node.size[0] === prevSize[0] && node.size[1] === prevSize[1]) {
stableCount++;
if (stableCount >= minStableFrames) {
// Size has been stable, simulate a resize to trigger layout update
const originalSize = [...node.size];
node.setSize([originalSize[0] + 1, originalSize[1]]); // Slightly increase width
setTimeout(() => {
node.setSize(originalSize); // Revert to original size
app.graph.setDirtyCanvas(true, true); // Trigger canvas redraw
}, 0);
} else {
// Size is stable but not for enough frames yet, check again
requestAnimationFrame(checkSizeStable);
}
} else {
// Size changed, reset counter and update prevSize
prevSize = [...node.size];
stableCount = 0;
requestAnimationFrame(checkSizeStable);
}
};
// // Function to check if the node's size has stabilized
// const checkSizeStable = () => {
// if (node.size[0] === prevSize[0] && node.size[1] === prevSize[1]) {
// stableCount++;
// if (stableCount >= minStableFrames) {
// // Size has been stable, simulate a resize to trigger layout update
// const originalSize = [...node.size];
// node.setSize([originalSize[0] + 1, originalSize[1]]); // Slightly increase width
// setTimeout(() => {
// node.setSize(originalSize); // Revert to original size
// app.graph.setDirtyCanvas(true, true); // Trigger canvas redraw
// }, 0);
// } else {
// // Size is stable but not for enough frames yet, check again
// requestAnimationFrame(checkSizeStable);
// }
// } else {
// // Size changed, reset counter and update prevSize
// prevSize = [...node.size];
// stableCount = 0;
// requestAnimationFrame(checkSizeStable);
// }
// };
// Start checking after a short delay to allow node initialization
setTimeout(() => {
requestAnimationFrame(checkSizeStable);
}, 5000);
}
});
// // Start checking after a short delay to allow node initialization
// setTimeout(() => {
// requestAnimationFrame(checkSizeStable);
// }, 5000);
// }
// });

View File

@@ -7,10 +7,10 @@ app.registerExtension({
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_OllamaTalk") {
// Set seed widget to hidden input
const seedWidget = node.widgets.find((w) => w.name === "seed");
if (seedWidget) {
seedWidget.type = "HIDDEN";
}
// const seedWidget = node.widgets.find((w) => w.name === "seed");
// if (seedWidget) {
// seedWidget.type = "HIDDEN";
// }
// Function to update the Reset Button text
const updateResetButtonTextNode = () => {

186
web/js/style_selector.js Normal file
View File

@@ -0,0 +1,186 @@
// // style_selector.js
// import { app } from "../../../scripts/app.js";
// app.registerExtension({
// name: "Bjornulf.StyleSelector",
// async nodeCreated(node) {
// // Only apply to the Bjornulf_StyleSelector node
// if (node.comfyClass !== "Bjornulf_StyleSelector") return;
// // Find the widgets for LOOP_random_LIST and LOOP_style_LIST
// const loopRandomWidget = node.widgets.find(w => w.name === "LOOP_random_LIST");
// const loopStyleWidget = node.widgets.find(w => w.name === "LOOP_style_LIST");
// // Check if widgets exist to avoid errors
// if (!loopRandomWidget || !loopStyleWidget) {
// console.error("LOOP widgets not found in Bjornulf_StyleSelector node.");
// return;
// }
// // Function to toggle the other widget off when one is turned on
// const toggleExclusive = (widgetToToggle, otherWidget) => {
// if (widgetToToggle.value === true) {
// otherWidget.value = false;
// }
// };
// // Add event listeners to handle toggling
// loopRandomWidget.callback = () => {
// toggleExclusive(loopRandomWidget, loopStyleWidget);
// };
// loopStyleWidget.callback = () => {
// toggleExclusive(loopStyleWidget, loopRandomWidget);
// };
// // Ensure initial state has only one enabled (if both are true, disable one)
// if (loopRandomWidget.value && loopStyleWidget.value) {
// loopStyleWidget.value = false; // Default to disabling LOOP_style_LIST
// }
// // Find the category and style widgets
// const categoryWidget = node.widgets.find(w => w.name === "category");
// const styleWidget = node.widgets.find(w => w.name === "style");
// // Define categories and styles (must match the Python file)
// const BRANCHES = {
// "Painting": [
// "Renaissance", "Baroque", "Rococo", "Neoclassicism",
// "Romanticism", "Realism", "Impressionism", "Post-Impressionism",
// "Expressionism", "Fauvism", "Cubism", "Futurism", "Dadaism",
// "Surrealism", "Abstract Expressionism", "Pop Art", "Op Art",
// "Minimalism"
// ],
// "Photography": [
// "Black and White", "Color", "Vintage", "Sepia Tone", "HDR",
// "Long Exposure", "Macro", "Portrait", "Landscape", "Street",
// "Fashion", "Analog Film", "Cinematic"
// ],
// "Digital Art": [
// "Digital Painting", "Vector Art", "Pixel Art", "Fractal Art",
// "Algorithmic Art", "Glitch Art"
// ],
// "3D Rendering": [
// "Low Poly", "Voxel", "Isometric", "Ray Tracing"
// ],
// "Illustration": [
// "Line Art", "Cartoon", "Comic Book", "Manga", "Anime",
// "Technical Illustration", "Botanical Illustration",
// "Architectural Rendering", "Concept Art", "Storyboard Art"
// ],
// };
// // Function to update the style dropdown based on the selected category
// const updateStyles = () => {
// const selectedCategory = categoryWidget.value;
// const styles = BRANCHES[selectedCategory] || [];
// styleWidget.options.values = styles;
// if (styles.length > 0) {
// styleWidget.value = styles[0]; // Set to the first style
// } else {
// styleWidget.value = ""; // Fallback if no styles
// }
// node.setDirtyCanvas(true); // Refresh the UI
// };
// // Initialize the style dropdown
// updateStyles();
// // Update the style dropdown whenever the category changes
// categoryWidget.callback = updateStyles;
// }
// });
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.StyleSelector",
async nodeCreated(node) {
if (node.comfyClass !== "Bjornulf_StyleSelector") return;
// Find loop widgets
const loopRandomWidget = node.widgets.find(w => w.name === "LOOP_random_LIST");
const loopStyleWidget = node.widgets.find(w => w.name === "LOOP_style_LIST");
const loopSequentialWidget = node.widgets.find(w => w.name === "LOOP_SEQUENTIAL");
// Function to toggle the other widget off when one is turned on
const toggleExclusive = (widgetToToggle, otherWidget) => {
if (widgetToToggle.value === true) {
otherWidget.value = false;
}
};
// Add event listeners to handle toggling
loopRandomWidget.callback = () => {
toggleExclusive(loopRandomWidget, loopStyleWidget);
};
loopStyleWidget.callback = () => {
toggleExclusive(loopStyleWidget, loopRandomWidget);
};
// Ensure initial state has only one enabled (if both are true, disable one)
if (loopRandomWidget.value && loopStyleWidget.value) {
loopStyleWidget.value = false; // Default to disabling LOOP_style_LIST
}
// Add reset button for style list counter
const styleResetButton = node.addWidget(
"button",
"Reset Style Counter",
null,
async () => {
try {
const response = await fetch("/reset_style_list_counter", { method: "POST" });
const data = await response.json();
if (data.success) {
app.ui.dialog.show("[Style Selector] Style counter reset successfully.");
} else {
app.ui.dialog.show("[Style Selector] Failed to reset style counter.");
}
} catch (error) {
app.ui.dialog.show("[Style Selector] Error resetting style counter.");
}
}
);
// Add reset button for model list counter
const modelResetButton = node.addWidget(
"button",
"Reset Model Counter",
null,
async () => {
try {
const response = await fetch("/reset_model_list_counter", { method: "POST" });
const data = await response.json();
if (data.success) {
app.ui.dialog.show("[Style Selector] Model counter reset successfully.");
} else {
app.ui.dialog.show("[Style Selector] Failed to reset model counter.");
}
} catch (error) {
app.ui.dialog.show("[Style Selector] Error resetting model counter.");
}
}
);
// Function to update visibility of reset buttons
const updateButtonVisibility = () => {
const sequentialEnabled = loopSequentialWidget.value;
styleResetButton.type = sequentialEnabled && loopStyleWidget.value ? "button" : "hidden";
modelResetButton.type = sequentialEnabled && loopRandomWidget.value ? "button" : "hidden";
};
// Initial update of button visibility
setTimeout(updateButtonVisibility, 0);
// Update visibility when widgets change
loopSequentialWidget.callback = updateButtonVisibility;
loopStyleWidget.callback = () => {
toggleExclusive(loopStyleWidget, loopRandomWidget);
updateButtonVisibility();
};
loopRandomWidget.callback = () => {
toggleExclusive(loopRandomWidget, loopStyleWidget);
updateButtonVisibility();
};
}
});

View File

@@ -31,7 +31,8 @@ app.registerExtension({
// Function to update voices based on selected language
const updateVoicesForLanguage = async (selectedLanguage) => {
try {
const response = await fetch('/bjornulf_xtts_get_voices', {
// const response = await fetch('/bjornulf_xtts_get_voices', {
const response = await fetch('/bjornulf_TTS_get_voices', {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@@ -1,12 +1,16 @@
import { api } from '../../../scripts/api.js';
import { app } from "../../../scripts/app.js";
function displayVideoPreview(component, filename, category, autoplay, mute) {
// Function to display the video preview
function displayVideoPreview(component, filename, category, autoplay, mute, loop) {
let videoWidget = component._videoWidget;
// Create the video widget if it doesn't exist
if (!videoWidget) {
// Create the widget if it doesn't exist
var container = document.createElement("div");
const container = document.createElement("div");
const currentNode = component;
// Add the DOM widget to the component
videoWidget = component.addDOMWidget("videopreview", "preview", container, {
serialize: false,
hideOnZoom: false,
@@ -17,73 +21,83 @@ function displayVideoPreview(component, filename, category, autoplay, mute) {
container.value = v;
},
});
// Define how the widget computes its size
videoWidget.computeSize = function(width) {
if (this.aspectRatio && !this.parentElement.hidden) {
let height = (currentNode.size[0] - 20) / this.aspectRatio + 10;
if (!(height > 0)) {
height = 0;
}
return [width, height];
return [width, height > 0 ? height : 0];
}
return [width, -4];
};
// Initialize widget properties
videoWidget.value = { hidden: false, paused: false, params: {} };
videoWidget.parentElement = document.createElement("div");
videoWidget.parentElement.className = "video_preview";
videoWidget.parentElement.style['width'] = "100%";
videoWidget.parentElement.style.width = "100%";
container.appendChild(videoWidget.parentElement);
// Create the video element
videoWidget.videoElement = document.createElement("video");
videoWidget.videoElement.controls = true;
videoWidget.videoElement.loop = false;
videoWidget.videoElement.muted = false;
videoWidget.videoElement.style['width'] = "100%";
videoWidget.videoElement.style.width = "100%";
// Update aspect ratio when metadata is loaded
videoWidget.videoElement.addEventListener("loadedmetadata", () => {
videoWidget.aspectRatio = videoWidget.videoElement.videoWidth / videoWidget.videoElement.videoHeight;
adjustSize(component);
});
// Hide the video on error
videoWidget.videoElement.addEventListener("error", () => {
videoWidget.parentElement.hidden = true;
adjustSize(component);
});
videoWidget.parentElement.hidden = videoWidget.value.hidden;
videoWidget.videoElement.autoplay = !videoWidget.value.paused && !videoWidget.value.hidden;
videoWidget.videoElement.hidden = false;
videoWidget.parentElement.appendChild(videoWidget.videoElement);
component._videoWidget = videoWidget; // Store the widget for future reference
component._videoWidget = videoWidget; // Store for reuse
}
// Update the video source
let params = {
// Set video source and properties
const params = {
"filename": filename,
"subfolder": category,
"type": "output",
"rand": Math.random().toString().slice(2, 12)
"rand": Math.random().toString().slice(2, 12) // Cache-busting random parameter
};
const urlParams = new URLSearchParams(params);
if(mute) videoWidget.videoElement.muted = true;
else videoWidget.videoElement.muted = false;
if(autoplay) videoWidget.videoElement.autoplay = !videoWidget.value.paused && !videoWidget.value.hidden;
else videoWidget.videoElement.autoplay = false;
// videoWidget.videoElement.src = `http://localhost:8188/api/view?${urlParams.toString()}`;
videoWidget.videoElement.src = `api/view?${urlParams.toString()}`;
adjustSize(component); // Adjust the component size
const urlParams = new URLSearchParams(params);
videoWidget.videoElement.src = `api/view?${urlParams.toString()}`;
videoWidget.videoElement.muted = mute;
videoWidget.videoElement.autoplay = autoplay && !videoWidget.value.paused && !videoWidget.value.hidden;
videoWidget.videoElement.loop = loop;
// Adjust the component size after setting the video
adjustSize(component);
}
// Function to adjust the component size
function adjustSize(component) {
component.setSize([component.size[0], component.computeSize([component.size[0], component.size[1]])[1]]);
const newSize = component.computeSize([component.size[0], component.size[1]]);
component.setSize([component.size[0], newSize[1]]);
component?.graph?.setDirtyCanvas(true);
}
// Register the extension
app.registerExtension({
name: "Bjornulf.VideoPreview",
async beforeRegisterNodeDef(nodeType, nodeData, appInstance) {
if (nodeData?.name == "Bjornulf_VideoPreview") {
nodeType.prototype.onExecuted = function (data) {
if (nodeData?.name === "Bjornulf_VideoPreview") {
nodeType.prototype.onExecuted = function(data) {
// Retrieve widget values with defaults
const autoplay = this.widgets.find(w => w.name === "autoplay")?.value ?? false;
const mute = this.widgets.find(w => w.name === "mute")?.value ?? true;
displayVideoPreview(this, data.video[0], data.video[1], autoplay, mute);
const loop = this.widgets.find(w => w.name === "loop")?.value ?? false;
// Display the video preview with the retrieved values
displayVideoPreview(this, data.video[0], data.video[1], autoplay, mute, loop);
};
}
}