From 7e436ba9cceb6fe3427a223ade498f50472ca368 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Thu, 2 Mar 2023 21:34:29 +0000 Subject: [PATCH] Added handling of sockets Started rework of UI elements Added pnginfo handling --- web/index.html | 640 ++++++++++++----------------------------- web/scripts/app.js | 172 ++++++----- web/scripts/pnginfo.js | 45 +++ web/scripts/ui.js | 152 ++++++++++ web/scripts/widgets.js | 1 - web/style.css | 32 +++ 6 files changed, 522 insertions(+), 520 deletions(-) create mode 100644 web/scripts/pnginfo.js create mode 100644 web/scripts/ui.js diff --git a/web/index.html b/web/index.html index 278eeb2e..af0646b9 100644 --- a/web/index.html +++ b/web/index.html @@ -1,476 +1,214 @@ - - - - + + + + - - - - + + + - - -Queue size: X
-
- - - - -
-
- - - -
-
- -
-
-
-
- + function clearItems(type) { + fetch("/" + type, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ clear: true }), + }) + .then((data) => { + loadItems(type); + }) + .catch((error) => console.error(error)); + } + + diff --git a/web/scripts/app.js b/web/scripts/app.js index cc97f4d4..d3904a95 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -1,74 +1,8 @@ import { ComfyWidgets } from "./widgets.js"; +import { ComfyUI } from "./ui.js"; import { api } from "./api.js"; import { defaultGraph } from "./defaultGraph.js"; - -class ComfyDialog { - constructor() { - this.element = document.createElement("div"); - this.element.classList.add("comfy-modal"); - - const content = document.createElement("div"); - content.classList.add("comfy-modal-content"); - this.textElement = document.createElement("p"); - content.append(this.textElement); - - const closeBtn = document.createElement("button"); - closeBtn.type = "button"; - closeBtn.textContent = "CLOSE"; - content.append(closeBtn); - closeBtn.onclick = () => this.close(); - - this.element.append(content); - document.body.append(this.element); - } - - close() { - this.element.style.display = "none"; - } - - show(html) { - this.textElement.innerHTML = html; - this.element.style.display = "flex"; - } -} - -class ComfyQueue { - constructor() { - this.element = document.createElement("div"); - } - - async update() { - if (this.element.style.display !== "none") { - await this.load(); - } - } - - async show() { - this.element.style.display = "block"; - await this.load(); - } - - async load() { - const queue = await api.getQueue(); - } - - hide() { - this.element.style.display = "none"; - } -} - - -class ComfyUI { - constructor(app) { - this.app = app; - this.menuContainer = document.createElement("div"); - this.menuContainer.classList.add("comfy-menu"); - document.body.append(this.menuContainer); - - this.dialog = new ComfyDialog(); - this.queue = new ComfyQueue(); - } -} +import { getPngMetadata } from "./pnginfo.js"; class ComfyApp { constructor() { @@ -360,6 +294,103 @@ class ComfyApp { }; } + #addDropHandler() { + // Get prompt from dropped PNG or json + document.addEventListener("drop", async (event) => { + event.preventDefault(); + event.stopPropagation(); + const file = event.dataTransfer.files[0]; + + if (file.type === "image/png") { + const pngInfo = await getPngMetadata(file); + if (pngInfo && pngInfo.workflow) { + this.loadGraphData(JSON.parse(pngInfo.workflow)); + } + } else if (file.type === "application/json" || file.name.endsWith(".json")) { + const reader = new FileReader(); + reader.onload = () => { + this.loadGraphData(JSON.parse(reader.result)); + }; + reader.readAsText(file); + } + + prompt_file_load(file); + }); + } + + #addDrawNodeProgressHandler() { + const orig = LGraphCanvas.prototype.drawNodeShape; + const self = this; + LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) { + const res = orig.apply(this, arguments); + + if (node.id + "" === self.runningNodeId) { + const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; + ctx.lineWidth = 1; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE) + ctx.rect(-6, -6 + LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT); + else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed)) + ctx.roundRect( + -6, + -6 - LiteGraph.NODE_TITLE_HEIGHT, + 12 + size[0] + 1, + 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, + this.round_radius * 2 + ); + else if (shape == LiteGraph.CARD_SHAPE) + ctx.roundRect( + -6, + -6 + LiteGraph.NODE_TITLE_HEIGHT, + 12 + size[0] + 1, + 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, + this.round_radius * 2, + 2 + ); + else if (shape == LiteGraph.CIRCLE_SHAPE) + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2); + ctx.strokeStyle = "#0f0"; + ctx.stroke(); + ctx.strokeStyle = fgcolor; + ctx.globalAlpha = 1; + + if (self.progress) { + ctx.fillStyle = "green"; + ctx.fillRect(0, 0, size[0] * (self.progress.value / self.progress.max), 6); + ctx.fillStyle = bgcolor; + } + } + + return res; + }; + } + + #addApiUpdateHandlers() { + api.addEventListener("status", (status) => { + console.log(status); + }); + + api.addEventListener("reconnecting", () => {}); + + api.addEventListener("reconnected", () => {}); + + api.addEventListener("progress", ({ detail }) => { + this.progress = detail; + this.graph.setDirtyCanvas(true, false); + }); + + api.addEventListener("executing", ({ detail }) => { + this.progress = null; + this.runningNodeId = detail; + this.graph.setDirtyCanvas(true, false); + }); + + api.addEventListener("executed", (e) => {}); + + api.init(); + } + /** * Set up the app on the page */ @@ -406,6 +437,9 @@ class ComfyApp { // Save current workflow automatically setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000); + this.#addDrawNodeProgressHandler(); + this.#addApiUpdateHandlers(); + this.#addDropHandler(); await this.#invokeExtensionsAsync("setup"); } @@ -561,6 +595,8 @@ class ComfyApp { } } + // TODO: check dynamic prompts here + this.canvas.draw(true, true); await this.ui.queue.update(); } diff --git a/web/scripts/pnginfo.js b/web/scripts/pnginfo.js new file mode 100644 index 00000000..923f8745 --- /dev/null +++ b/web/scripts/pnginfo.js @@ -0,0 +1,45 @@ +export function getPngMetadata(file) { + return new Promise((r) => { + const reader = new FileReader(); + reader.onload = (event) => { + // Get the PNG data as a Uint8Array + const pngData = new Uint8Array(event.target.result); + const dataView = new DataView(pngData.buffer); + + // Check that the PNG signature is present + if (dataView.getUint32(0) !== 0x89504e47) { + console.error("Not a valid PNG file"); + r(); + return; + } + + // Start searching for chunks after the PNG signature + let offset = 8; + let txt_chunks = {}; + // Loop through the chunks in the PNG file + while (offset < pngData.length) { + // Get the length of the chunk + const length = dataView.getUint32(offset); + // Get the chunk type + const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8)); + if (type === "tEXt") { + // Get the keyword + let keyword_end = offset + 8; + while (pngData[keyword_end] !== 0) { + keyword_end++; + } + const keyword = String.fromCharCode(...pngData.slice(offset + 8, keyword_end)); + // Get the text + const text = String.fromCharCode(...pngData.slice(keyword_end + 1, offset + 8 + length)); + txt_chunks[keyword] = text; + } + + offset += 12 + length; + } + + r(txt_chunks); + }; + + reader.readAsArrayBuffer(file); + }); +} diff --git a/web/scripts/ui.js b/web/scripts/ui.js new file mode 100644 index 00000000..b7f24c4f --- /dev/null +++ b/web/scripts/ui.js @@ -0,0 +1,152 @@ +import { api } from "./api.js"; + +class ComfyDialog { + constructor() { + this.element = document.createElement("div"); + this.element.classList.add("comfy-modal"); + + const content = document.createElement("div"); + content.classList.add("comfy-modal-content"); + this.textElement = document.createElement("p"); + content.append(this.textElement); + + const closeBtn = document.createElement("button"); + closeBtn.type = "button"; + closeBtn.textContent = "CLOSE"; + content.append(closeBtn); + closeBtn.onclick = () => this.close(); + + this.element.append(content); + document.body.append(this.element); + } + + close() { + this.element.style.display = "none"; + } + + show(html) { + this.textElement.innerHTML = html; + this.element.style.display = "flex"; + } +} + +class ComfyList { + constructor() { + this.element = document.createElement("div"); + this.element.style.display = "none"; + this.element.textContent = "hello"; + } + + get visible() { + return this.element.style.display !== "none"; + } + + async load() { + // const queue = await api.getQueue(); + } + + async update() { + if (this.visible) { + await this.load(); + } + } + + async show() { + this.element.style.display = "block"; + await this.load(); + } + + hide() { + this.element.style.display = "none"; + } + + toggle() { + if (this.visible) { + this.hide(); + return false; + } else { + this.show(); + return true; + } + } +} + +export class ComfyUI { + constructor(app) { + this.app = app; + this.dialog = new ComfyDialog(); + this.queue = new ComfyList(); + this.history = new ComfyList(); + + this.menuContainer = document.createElement("div"); + this.menuContainer.classList.add("comfy-menu"); + + this.queueSize = document.createElement("span"); + this.menuContainer.append(this.queueSize); + + this.addAction("Queue Prompt", () => { + app.queuePrompt(0); + }, "queue"); + + this.btnContainer = document.createElement("div"); + this.btnContainer.classList.add("comfy-menu-btns"); + this.menuContainer.append(this.btnContainer); + + this.addAction( + "Queue Front", + () => { + app.queuePrompt(-1); + }, + "sm" + ); + + this.addAction( + "See Queue", + (btn) => { + btn.textContent = this.queue.toggle() ? "Close" : "See Queue"; + }, + "sm" + ); + + this.addAction( + "See History", + (btn) => { + btn.textContent = this.history.toggle() ? "Close" : "See History"; + }, + "sm" + ); + + this.menuContainer.append(this.queue.element); + this.menuContainer.append(this.history.element); + + this.addAction("Save", () => { + app.queuePrompt(-1); + }); + this.addAction("Load", () => { + app.queuePrompt(-1); + }); + this.addAction("Clear", () => { + app.queuePrompt(-1); + }); + this.addAction("Load Default", () => { + app.queuePrompt(-1); + }); + + document.body.append(this.menuContainer); + this.setStatus({ exec_info: { queue_remaining: "X" } }); + } + + addAction(text, cb, cls) { + const btn = document.createElement("button"); + btn.classList.add("comfy-menu-btn-" + (cls || "lg")); + btn.textContent = text; + btn.onclick = () => { + cb(btn); + }; + (cls === "sm" ? this.btnContainer : this.menuContainer).append(btn); + } + + setStatus(status) { + this.queueSize.textContent = "Queue size: " + (status ? status.exec_info.queue_remaining : "ERR"); + } +} diff --git a/web/scripts/widgets.js b/web/scripts/widgets.js index 1177f82f..8c680c84 100644 --- a/web/scripts/widgets.js +++ b/web/scripts/widgets.js @@ -44,7 +44,6 @@ function addMultilineWidget(node, name, defaultVal, dynamicPrompt, app) { const visible = app.canvas.ds.scale > 0.5; const t = ctx.getTransform(); const margin = 10; - console.log("back you go") Object.assign(this.inputEl.style, { left: `${t.a * margin + t.e}px`, top: `${t.d * (y + widgetHeight - margin) + t.f}px`, diff --git a/web/style.css b/web/style.css index 37344788..bb28ad9f 100644 --- a/web/style.css +++ b/web/style.css @@ -64,6 +64,38 @@ body { cursor: pointer; } +.comfy-menu { + width: 200px; + font-size: 15px; + position: absolute; + top: 50%; + right: 0%; + background-color: white; + text-align: center; + z-index: 100; + width: 170px; + display: flex; + flex-direction: column; + align-items: center; +} + +.comfy-menu-btns { + margin-bottom: 10px; +} + +.comfy-menu-btn-sm { + font-size: 10px; + width: 50%; +} + +.comfy-menu-btn-lg, .comfy-menu-btn-queue { + font-size: 20px; +} + +.comfy-menu-btn-queue { + width: 100%; +} + @media (prefers-color-scheme: dark) { body { background-color: #202020;