mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-09-14 05:25:23 +00:00
Added handling of sockets
Started rework of UI elements Added pnginfo handling
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
45
web/scripts/pnginfo.js
Normal file
45
web/scripts/pnginfo.js
Normal file
@@ -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);
|
||||
});
|
||||
}
|
152
web/scripts/ui.js
Normal file
152
web/scripts/ui.js
Normal file
@@ -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");
|
||||
}
|
||||
}
|
@@ -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`,
|
||||
|
Reference in New Issue
Block a user