diff --git a/js/cte_advanced.js b/js/cte_advanced.js new file mode 100644 index 0000000..453d34a --- /dev/null +++ b/js/cte_advanced.js @@ -0,0 +1,213 @@ +import { app } from "/scripts/app.js"; +import { api } from "/scripts/api.js" +const MultilineSymbol = Symbol(); +const MultilineResizeSymbol = Symbol(); + +function getStyles(name) { + //console.log("getStyles called " + name); + + return api.fetchApi('/nsuite/styles') + .then(response => response.json()) + .then(data => { + // Eseguire l'elaborazione dei dati + const styles = data.styles; + //console.log('Styles:', styles); + let positive_prompt = ""; + let negative_prompt = ""; + + // Funzione per ottenere positive_prompt e negative_prompt dato il name + for (let i = 0; i < styles[0].length; i++) { + const style = styles[0][i]; + if (style.name === name) { + positive_prompt = style.prompt; + negative_prompt = style.negative_prompt; + //console.log('Style:', style.name); + break; + } + } + + if (positive_prompt !== "") { + //console.log("Positive prompt:", positive_prompt); + //console.log("Negative prompt:", negative_prompt); + return { positive_prompt: positive_prompt, negative_prompt: negative_prompt }; + } else { + return { positive_prompt: "", negative_prompt: "" }; + } + }) + .catch(error => { + console.error('Error:', error); + throw error; // Rilancia l'errore per consentire al chiamante di gestirlo + }); + } + + function addStyles(name, positive_prompt, negative_prompt) { + return api.fetchApi('/nsuite/styles/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: name, + positive_prompt: positive_prompt, + negative_prompt: negative_prompt + }), + + }) + } + + function updateStyles(name, positive_prompt, negative_prompt) { + return api.fetchApi('/nsuite/styles/update', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: name, + positive_prompt: positive_prompt, + negative_prompt: negative_prompt + }), + }) +} + +function removeStyles(name) { + //confirmation + let ok = confirm("Are you sure you want to remove this style?"); + if (!ok) { + return; + } + + return api.fetchApi('/nsuite/styles/remove', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: name + }), + }) +} + +app.registerExtension({ + name: "n.CLIPTextEncodeAdvancedNSuite", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + + const onAdded = nodeType.prototype.onAdded; + if (nodeData.name === "CLIPTextEncodeAdvancedNSuite [n-suite]") { + nodeType.prototype.onAdded = function () { + onAdded?.apply(this, arguments); + const styles = this.widgets.find((w) => w.name === "styles"); + const p_prompt = this.widgets.find((w) => w.name === "positive_prompt"); + const n_prompt = this.widgets.find((w) => w.name === "negative_prompt"); + const cb = nodeData.callback; + let addedd_positive_prompt = ""; + let addedd_negative_prompt = ""; + styles.callback = function () { + let index = styles.options.values.indexOf(styles.value); + + + if (addedd_positive_prompt == "" && addedd_negative_prompt == "") { + getStyles(styles.options.values[index-1]).then(style_prompts => { + //wait 4 seconds + + console.log(style_prompts); + + addedd_positive_prompt = style_prompts.positive_prompt; + addedd_negative_prompt = style_prompts.negative_prompt; + //alert("Addedd positive prompt: " + addedd_positive_prompt + "\nAddedd negative prompt: " + addedd_negative_prompt); + }) + } + + + let current_positive_prompt = p_prompt.value; + let current_negative_prompt = n_prompt.value; + + getStyles(styles.value).then(style_prompts => { + //console.log(style_prompts) + + if ((current_positive_prompt.trim() != addedd_positive_prompt.trim() || current_negative_prompt.trim() != addedd_negative_prompt.trim())) { + + let ok = confirm("Style has been changed. Do you want to change style without saving?"); + + + if (!ok) { + if (styles.value === styles.options.values[0]) { + value = styles.options.values[0]; + } + styles.value = styles.options.values[index-1]; + + + return; + } + } + + // add the addedd prompt to the current prompt + p_prompt.value = style_prompts.positive_prompt; + n_prompt.value = style_prompts.negative_prompt; + + + addedd_positive_prompt = style_prompts.positive_prompt; + addedd_negative_prompt = style_prompts.negative_prompt; + if (cb) { + return cb.apply(this, arguments); + } + }) + .catch(error => { + console.error('Error:', error); + }); + + }; + + + + let savestyle; + let replacestyle; + let deletestyle; + + + // Create the button widget for selecting the files + savestyle = this.addWidget("button", "New", "image", () => { + ////console.log("Save called"); + //ask input name style + let inputName = prompt("Enter a name for the style:", styles.value); + if (inputName === null) { + return; + } + + + addStyles(inputName, p_prompt.value, n_prompt.value); + // Add the file to the dropdown list and update the widget value + + if (!styles.options.values.includes(inputName)) { + styles.options.values.push(inputName); + } + + },{ + cursor: "grab", + },); + replacestyle = this.addWidget("button", "Replace", "image", () => { + //console.log("Replace called"); + updateStyles(styles.value, p_prompt.value, n_prompt.value); + },{ + cursor: "grab", + },); + deletestyle = this.addWidget("button", "Delete", "image", () => { + //console.log("Delete called"); + removeStyles(styles.value); + + // Remove the file from the dropdown list + styles.options.values = styles.options.values.filter((value) => value !== styles.value); + },{ + cursor: "grab", + },); + savestyle.serialize = false; + + } + + + + + + }; + + }, +}); diff --git a/js/dynamicPrompt.js b/js/dynamicPrompt.js new file mode 100644 index 0000000..007e177 --- /dev/null +++ b/js/dynamicPrompt.js @@ -0,0 +1,44 @@ +import { app } from "/scripts/app.js"; +import { ComfyWidgets } from "/scripts/widgets.js"; + +app.registerExtension({ + name: "n.DynamicPrompt", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + + if (nodeData.name === "DynamicPrompt") { + console.warn("DynamicPrompt detected") + + const onExecuted = nodeType.prototype.onExecuted; + + + nodeType.prototype.onExecuted = function (message) { + onExecuted?.apply(this, arguments); + + const pos_cached = this.widgets.findIndex((w) => w.name === "cached"); + console.warn("value:"+pos_cached) + + if (this.widgets) { + const pos_text = this.widgets.findIndex((w) => w.name === "text"); + if (pos_text !== -1) { + for (let i = pos_text; i < this.widgets.length; i++) { + this.widgets[i].onRemove?.(); + } + this.widgets.length = pos_text; + } + } + + + if (this.widgets[pos_cached].value === "NO") { + + const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app); + //random seed + var rnm = Math.floor(Math.random() * 10000) + w.widget.value = rnm; + + + } + + }; + } + }, +}); diff --git a/js/extended_widgets.js b/js/extended_widgets.js new file mode 100644 index 0000000..5e9b087 --- /dev/null +++ b/js/extended_widgets.js @@ -0,0 +1,323 @@ +//extended_widgets.js +import { api } from "/scripts/api.js" +import { ComfyWidgets } from "/scripts/widgets.js"; + +const MultilineSymbol = Symbol(); +const MultilineResizeSymbol = Symbol(); +async function uploadFile(file, updateNode, node, pasted = false) { + const videoWidget = node.widgets.find((w) => w.name === "video"); + + + try { + // Wrap file in formdata so it includes filename + const body = new FormData(); + body.append("image", file); + if (pasted) { + body.append("subfolder", "pasted"); + } + else { + body.append("subfolder", "n-suite"); + } + + const resp = await api.fetchApi("/upload/image", { + method: "POST", + body, + }); + + if (resp.status === 200) { + const data = await resp.json(); + // Add the file to the dropdown list and update the widget value + let path = data.name; + + + if (!videoWidget.options.values.includes(path)) { + videoWidget.options.values.push(path); + } + + if (updateNode) { + + videoWidget.value = path; + if (data.subfolder) path = data.subfolder + "/" + path; + showVideoInput(path,node); + + } + } else { + alert(resp.status + " - " + resp.statusText); + } + } catch (error) { + alert(error); + } +} + +function addVideo(node, name,src, app,autoplay_value) { + const MIN_SIZE = 50; + + function computeSize(size) { + try{ + + if (node.widgets[0].last_y == null) return; + + let y = node.widgets[0].last_y; + let freeSpace = size[1] - y; + + // Compute the height of all non customvideo widgets + let widgetHeight = 0; + const multi = []; + for (let i = 0; i < node.widgets.length; i++) { + const w = node.widgets[i]; + if (w.type === "customvideo") { + multi.push(w); + } else { + if (w.computeSize) { + widgetHeight += w.computeSize()[1] + 4; + } else { + widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 4; + } + } + } + + // See how large each text input can be + freeSpace -= widgetHeight; + freeSpace /= multi.length + (!!node.imgs?.length); + + if (freeSpace < MIN_SIZE) { + // There isnt enough space for all the widgets, increase the size of the node + freeSpace = MIN_SIZE; + node.size[1] = y + widgetHeight + freeSpace * (multi.length + (!!node.imgs?.length)); + node.graph.setDirtyCanvas(true); + } + + // Position each of the widgets + for (const w of node.widgets) { + w.y = y; + if (w.type === "customvideo") { + y += freeSpace; + w.computedHeight = freeSpace - multi.length*4; + } else if (w.computeSize) { + y += w.computeSize()[1] + 4; + } else { + y += LiteGraph.NODE_WIDGET_HEIGHT + 4; + } + } + + node.inputHeight = freeSpace; + }catch(e){ + + } + } + const widget = { + type: "customvideo", + name, + get value() { + return this.inputEl.value; + }, + set value(x) { + this.inputEl.value = x; + }, + draw: function (ctx, _, widgetWidth, y, widgetHeight) { + if (!this.parent.inputHeight) { + // If we are initially offscreen when created we wont have received a resize event + // Calculate it here instead + node.setSizeForImage?.(); + + } + const visible = app.canvas.ds.scale > 0.5 && this.type === "customvideo"; + const margin = 10; + const elRect = ctx.canvas.getBoundingClientRect(); + const transform = new DOMMatrix() + .scaleSelf(elRect.width / ctx.canvas.width, elRect.height / ctx.canvas.height) + .multiplySelf(ctx.getTransform()) + .translateSelf(margin, margin + y); + + const scale = new DOMMatrix().scaleSelf(transform.a, transform.d) + Object.assign(this.inputEl.style, { + transformOrigin: "0 0", + transform: scale, + left: `${transform.a + transform.e}px`, + top: `${transform.d + transform.f}px`, + width: `${widgetWidth - (margin * 2)}px`, + height: `${this.parent.inputHeight - (margin * 2)}px`, + position: "absolute", + background: (!node.color)?'':node.color, + color: (!node.color)?'':'white', + zIndex: app.graph._nodes.indexOf(node), + }); + this.inputEl.hidden = !visible; + }, + }; + + + widget.inputEl = document.createElement("video"); + + + // Set the video attributes + Object.assign(widget.inputEl, { + controls: true, + src: src, + poster: "", + width: 400, + height: 300, + loop: true, + muted: true, + autoplay: autoplay_value, + type : "video/mp4" + + }); + //widget.inputEl.classList.add("dididi"); + + + + // Add video element to the body + document.body.appendChild(widget.inputEl); + + + + widget.parent = node; + document.body.appendChild(widget.inputEl); + + node.addCustomWidget(widget); + + app.canvas.onDrawBackground = function () { + // Draw node isnt fired once the node is off the screen + // if it goes off screen quickly, the input may not be removed + // this shifts it off screen so it can be moved back if the node is visible. + for (let n in app.graph._nodes) { + n = graph._nodes[n]; + for (let w in n.widgets) { + let wid = n.widgets[w]; + if (Object.hasOwn(wid, "inputEl")) { + wid.inputEl.style.left = -8000 + "px"; + wid.inputEl.style.position = "absolute"; + } + } + } + }; + + node.onRemoved = function () { + // When removing this node we need to remove the input from the DOM + for (let y in this.widgets) { + if (this.widgets[y].inputEl) { + this.widgets[y].inputEl.remove(); + } + } + }; + + widget.onRemove = () => { + widget.inputEl?.remove(); + + // Restore original size handler if we are the last + if (!--node[MultilineSymbol]) { + node.onResize = node[MultilineResizeSymbol]; + delete node[MultilineSymbol]; + delete node[MultilineResizeSymbol]; + } + }; + + if (node[MultilineSymbol]) { + node[MultilineSymbol]++; + } else { + node[MultilineSymbol] = 1; + const onResize = (node[MultilineResizeSymbol] = node.onResize); + + node.onResize = function (size) { + + computeSize(size); + // Call original resizer handler + if (onResize) { + onResize.apply(this, arguments); + } + }; + } + + return { minWidth: 400, minHeight: 200, widget }; +} + + +export function showVideoInput(name,node) { + const videoWidget = node.widgets.find((w) => w.name === "videoWidget"); + const temp_web_url = node.widgets.find((w) => w.name === "local_url"); + + + let folder_separator = name.lastIndexOf("/"); + let subfolder = "n-suite"; + if (folder_separator > -1) { + subfolder = name.substring(0, folder_separator); + name = name.substring(folder_separator + 1); + } + + let url_video = api.apiURL(`/view?filename=${encodeURIComponent(name)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}`); + videoWidget.inputEl.src = url_video + temp_web_url.value = url_video +} + +export function showVideoOutput(name,node) { + const videoWidget = node.widgets.find((w) => w.name === "videoOutWidget"); + + + + let folder_separator = name.lastIndexOf("/"); + let subfolder = "n-suite/videos"; + if (folder_separator > -1) { + subfolder = name.substring(0, folder_separator); + name = name.substring(folder_separator + 1); + } + + + let url_video = api.apiURL(`/view?filename=${encodeURIComponent(name)}&type=output&subfolder=${subfolder}${app.getPreviewFormatParam()}`); + videoWidget.inputEl.src = url_video + + return url_video; +} + + + +export const ExtendedComfyWidgets = { + ...ComfyWidgets, // Copy all the functions from ComfyWidgets + + VIDEO(node, inputName, inputData, src, app,type="input",autoplay_value=true) { + try { + const videoWidget = node.widgets.find((w) => w.name === "video"); + const autoplay = node.widgets.find((w) => w.name === "autoplay"); + const defaultVal = ""; + let res; + res = addVideo(node, inputName, src, app,autoplay_value); + + if (type == "input"){ + + const cb = node.callback; + videoWidget.callback = function () { + + showVideoInput(videoWidget.value, node); + if (cb) { + return cb.apply(this, arguments); + } + }; + autoplay.callback = function () { + const videoWidgetz = node.widgets.find((w) => w.name === "videoWidget"); + + videoWidgetz.inputEl.autoplay = autoplay.value; + showVideoInput(videoWidget.value, node); + if (cb) { + return cb.apply(this, arguments); + } + } + } + + if (node.type =="LoadVideoAdvanced"){ + + + } + + return res; + } + catch (error) { + + console.error("Errore in extended_widgets.js:", error); + throw error; + + } + +}, + + +}; diff --git a/js/gptSampler.js b/js/gptSampler.js new file mode 100644 index 0000000..d908e54 --- /dev/null +++ b/js/gptSampler.js @@ -0,0 +1,43 @@ +import { app } from "/scripts/app.js"; +import { ComfyWidgets } from "/scripts/widgets.js"; + +app.registerExtension({ + name: "n.GPTSampler", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + + if (nodeData.name === "GPT Sampler [n-suite]") { + console.warn("GPTSampler detected") + + const onExecuted = nodeType.prototype.onExecuted; + + nodeType.prototype.onExecuted = function (message) { + onExecuted?.apply(this, arguments); + + const pos_cached = this.widgets.findIndex((w) => w.name === "cached"); + console.warn("value:"+pos_cached) + + if (this.widgets) { + const pos_text = this.widgets.findIndex((w) => w.name === "text"); + if (pos_text !== -1) { + for (let i = pos_text; i < this.widgets.length; i++) { + this.widgets[i].onRemove?.(); + } + this.widgets.length = pos_text; + } + } + + + if (this.widgets[pos_cached].value === "NO") { + + const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app); + //random seed + var rnm = Math.floor(Math.random() * 10000) + w.widget.value = rnm; + + + } + + }; + } + }, +}); diff --git a/js/include_css.js b/js/include_css.js new file mode 100644 index 0000000..ac596fb --- /dev/null +++ b/js/include_css.js @@ -0,0 +1,22 @@ +import { $el } from "../../../scripts/ui.js"; + +function addStylesheet(url) { + if (url.endsWith(".js")) { + url = url.substr(0, url.length - 2) + "css"; + } + $el("link", { + parent: document.head, + rel: "stylesheet", + type: "text/css", + href: url.startsWith("http") ? url : getUrl(url), + }); +} +function getUrl(path, baseUrl) { + if (baseUrl) { + return new URL(path, baseUrl).toString(); + } else { + return new URL("../" + path, import.meta.url).toString(); + } +} + +addStylesheet(getUrl("styles.css", import.meta.url)); \ No newline at end of file diff --git a/js/styles.css b/js/styles.css new file mode 100644 index 0000000..77ab580 --- /dev/null +++ b/js/styles.css @@ -0,0 +1,20 @@ +textarea[placeholder="positive_prompt"] { + border: 1px solid #64d509; + + + } + textarea[placeholder="positive_prompt"]:focus-visible { + border: 1px solid #72eb0f; + + } + + +textarea[placeholder="negative_prompt"] { + border: 1px solid #a94442; + border-color: #a94442; +} + +textarea[placeholder="negative_prompt"]:focus-visible { + border: 1px solid #de5755; + border-color: #de5755; +} \ No newline at end of file diff --git a/js/videoLoadAdvanced.js b/js/videoLoadAdvanced.js new file mode 100644 index 0000000..0ef0909 --- /dev/null +++ b/js/videoLoadAdvanced.js @@ -0,0 +1,142 @@ +import { app } from "/scripts/app.js"; +import { api } from "/scripts/api.js" +import { ExtendedComfyWidgets,showVideoInput } from "./extended_widgets.js"; +const MultilineSymbol = Symbol(); +const MultilineResizeSymbol = Symbol(); + + +async function uploadFile(file, updateNode, node, pasted = false) { + const videoWidget = node.widgets.find((w) => w.name === "video"); + + + try { + // Wrap file in formdata so it includes filename + const body = new FormData(); + body.append("image", file); + if (pasted) { + body.append("subfolder", "pasted"); + } + else { + body.append("subfolder", "n-suite"); + } + + const resp = await api.fetchApi("/upload/image", { + method: "POST", + body, + }); + + if (resp.status === 200) { + const data = await resp.json(); + // Add the file to the dropdown list and update the widget value + let path = data.name; + + + if (!videoWidget.options.values.includes(path)) { + videoWidget.options.values.push(path); + } + + if (updateNode) { + + videoWidget.value = path; + if (data.subfolder) path = data.subfolder + "/" + path; + showVideoInput(path,node); + + } + } else { + alert(resp.status + " - " + resp.statusText); + } + } catch (error) { + alert(error); + } +} + + + + +let uploadWidget = ""; +app.registerExtension({ + name: "Comfy.VideoLoadAdvanced", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + + const onAdded = nodeType.prototype.onAdded; + if (nodeData.name === "LoadVideo [n-suite]") { + nodeType.prototype.onAdded = function () { + onAdded?.apply(this, arguments); + const temp_web_url = this.widgets.find((w) => w.name === "local_url"); + const autoplay_value = this.widgets.find((w) => w.name === "autoplay"); + + + let uploadWidget; + const fileInput = document.createElement("input"); + Object.assign(fileInput, { + type: "file", + accept: "video/mp4,image/gif,video/webm", + style: "display: none", + onchange: async () => { + if (fileInput.files.length) { + await uploadFile(fileInput.files[0], true,this); + } + }, + }); + document.body.append(fileInput); + // Create the button widget for selecting the files + uploadWidget = this.addWidget("button", "choose file to upload", "image", () => { + fileInput.click(); + },{ + cursor: "grab", + },); + uploadWidget.serialize = false; + + + setTimeout(() => { + ExtendedComfyWidgets["VIDEO"](this, "videoWidget", ["STRING"], temp_web_url.value, app,"input", autoplay_value.value); + + }, 100); + + + } + + + nodeType.prototype.onDragOver = function (e) { + if (e.dataTransfer && e.dataTransfer.items) { + const image = [...e.dataTransfer.items].find((f) => f.kind === "file"); + return !!image; + } + + return false; + }; + + // On drop upload files + nodeType.prototype.onDragDrop = function (e) { + console.log("onDragDrop called"); + let handled = false; + for (const file of e.dataTransfer.files) { + if (file.type.startsWith("video/mp4")) { + + const filePath = file.path || (file.webkitRelativePath || '').split('/').slice(1).join('/'); + + + uploadFile(file, !handled,this ); // Dont await these, any order is fine, only update on first one + + handled = true; + } + } + + return handled; + }; + + nodeType.prototype.pasteFile = function(file) { + if (file.type.startsWith("video/mp4")) { + + //uploadFile(file, true, is_pasted); + + return true; + } + return false; + } + + + }; + + }, +}); diff --git a/js/videoSave.js b/js/videoSave.js new file mode 100644 index 0000000..e965523 --- /dev/null +++ b/js/videoSave.js @@ -0,0 +1,87 @@ +import { app } from "/scripts/app.js"; +import { api } from "/scripts/api.js" +import { ExtendedComfyWidgets,showVideoOutput } from "./extended_widgets.js"; +const MultilineSymbol = Symbol(); +const MultilineResizeSymbol = Symbol(); + + +async function uploadFile(file, updateNode, node, pasted = false) { + const videoWidget = node.widgets.find((w) => w.name === "video"); + + + try { + // Wrap file in formdata so it includes filename + const body = new FormData(); + body.append("image", file); + if (pasted) { + body.append("subfolder", "pasted"); + } + else { + body.append("subfolder", "n-suite"); + } + + const resp = await api.fetchApi("/upload/image", { + method: "POST", + body, + }); + + if (resp.status === 200) { + const data = await resp.json(); + // Add the file to the dropdown list and update the widget value + let path = data.name; + + + if (!videoWidget.options.values.includes(path)) { + videoWidget.options.values.push(path); + } + + if (updateNode) { + // showVideo(path,node); + videoWidget.value = path; + if (data.subfolder) path = data.subfolder + "/" + path; + showVideo(path,node); + + } + } else { + alert(resp.status + " - " + resp.statusText); + } + } catch (error) { + alert(error); + } +} + + + + +let uploadWidget = ""; +app.registerExtension({ + name: "Comfy.VideoSave", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + + const onExecuted = nodeType.prototype.onExecuted; + + + const onAdded = nodeType.prototype.onAdded; + if (nodeData.name === "SaveVideo [n-suite]") { + nodeType.prototype.onAdded = function () { + + ExtendedComfyWidgets["VIDEO"](this, "videoOutWidget", ["STRING"], "", app,"output"); + + }; + nodeType.prototype.onExecuted = function (message) { + onExecuted?.apply(this, arguments); + console.log(nodeData) + + let full_path=""; + + for (const list of message.text) { + full_path = list; + } + + let fullweb= showVideoOutput(full_path,this) + +} + }; + + }, +});