Browse Source

Added handling of sockets

Started rework of UI elements
Added pnginfo handling
pull/49/head
pythongosssss 2 years ago committed by GitHub
parent
commit
7e436ba9cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 684
      web/index.html
  2. 172
      web/scripts/app.js
  3. 45
      web/scripts/pnginfo.js
  4. 152
      web/scripts/ui.js
  5. 1
      web/scripts/widgets.js
  6. 32
      web/style.css

684
web/index.html

@ -1,476 +1,214 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<link rel="stylesheet" type="text/css" href="lib/litegraph.css"> <link rel="stylesheet" type="text/css" href="lib/litegraph.css" />
<link rel="stylesheet" type="text/css" href="style.css"> <link rel="stylesheet" type="text/css" href="style.css" />
<script type="text/javascript" src="lib/litegraph.core.js"></script> <script type="text/javascript" src="lib/litegraph.core.js"></script>
<script type="module"> <script type="module">
import { app } from "/scripts/app.js"; import { app } from "/scripts/app.js";
await app.setup(); await app.setup();
window.app = app; window.app = app;
window.graph = app.graph; window.graph = app.graph;
</script> </script>
</head> </head>
<body> <body>
<script> <script>
return; return;
function postPrompt(number) { let runningNodeId = null;
let prompt = graphToPrompt(); let progress = null;
let full_data = {client_id: clientId, prompt: prompt, extra_data: {extra_pnginfo: {workflow: graph.serialize()}}}; let clientId = null;
if (number == -1) {
full_data.front = true; function clearGraph() {
} else graph.clear();
if (number != 0) { }
full_data.number = number;
} function loadTxt2Img() {
loadGraphData(graph, default_graph);
fetch('/prompt', { }
method: 'POST',
headers: { function saveGraph() {
'Content-Type': 'application/json' var json = JSON.stringify(graph.serialize()); // convert the data to a JSON string
}, var blob = new Blob([json], { type: "application/json" });
body: JSON.stringify(full_data) var url = URL.createObjectURL(blob);
}) var a = document.createElement("a");
.then(data => promptPosted(data)) a.style = "display: none";
.catch(error => console.error(error)) a.href = url;
a.download = "workflow.json";
// console.log(JSON.stringify(prompt)); document.body.appendChild(a);
// console.log(JSON.stringify(graph.serialize())); a.click();
setTimeout(function () {
// restore initial values replaced by dynamic prompting document.body.removeChild(a);
for (let x in graph._nodes_by_id) { window.URL.revokeObjectURL(url);
let n = graph._nodes_by_id[x]; }, 0);
for (let w in n.widgets) { }
let wid = n.widgets[w];
if (wid.dynamic_prompt && wid.dynamic_prompt === true) var input = document.createElement("input");
wid.value = wid.value_initial; input.setAttribute("type", "file");
} input.setAttribute("accept", ".json,image/png");
} input.style.display = "none";
} document.body.appendChild(input);
function prompt_file_load(file) input.addEventListener("change", function () {
{ var file = input.files[0];
if (file.type === 'image/png') { prompt_file_load(file);
const reader = new FileReader(); });
reader.onload = (event) => {
// Get the PNG data as a Uint8Array function loadGraph() {
const pngData = new Uint8Array(event.target.result); input.click();
const dataView = new DataView(pngData.buffer); }
// Check that the PNG signature is present document.addEventListener("paste", (e) => {
if (dataView.getUint32(0) !== 0x89504e47) { let data = (e.clipboardData || window.clipboardData).getData("text/plain");
console.error('Not a valid PNG file'); console.log(data);
return;
} try {
data = data.slice(data.indexOf("{"));
// Start searching for chunks after the PNG signature j = JSON.parse(data);
let offset = 8; } catch (err) {
let txt_chunks = {} data = data.slice(data.indexOf("workflow\n"));
// Loop through the chunks in the PNG file data = data.slice(data.indexOf("{"));
while (offset < pngData.length) { j = JSON.parse(data);
// Get the length of the chunk }
const length = dataView.getUint32(offset);
// Get the chunk type if (Object.hasOwn(j, "version") && Object.hasOwn(j, "nodes") && Object.hasOwn(j, "extra")) {
const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8)); loadGraphData(graph, j);
if (type === 'tEXt') { }
// Get the keyword });
let keyword_end = offset + 8;
while (pngData[keyword_end] !== 0) { function deleteQueueElement(type, delete_id, then) {
keyword_end++; fetch("/" + type, {
} method: "POST",
const keyword = String.fromCharCode(...pngData.slice(offset + 8, keyword_end)); headers: {
// Get the text "Content-Type": "application/json",
const text = String.fromCharCode(...pngData.slice(keyword_end + 1, offset + 8 + length)); },
txt_chunks[keyword] = text; body: JSON.stringify({ delete: [delete_id] }),
} })
.then((data) => {
// Get the next chunk console.log(data);
offset += 12 + length; then();
} })
console.log(txt_chunks); .catch((error) => console.error(error));
// console.log(JSON.parse(txt_chunks["prompt"])); }
loadGraphData(graph, JSON.parse(txt_chunks["workflow"]));
}; function loadQueue() {
reader.readAsArrayBuffer(file); loadItems("queue");
} else if (file.type === "application/json" || file.name.endsWith(".json")) { }
var reader = new FileReader(); function loadHistory() {
reader.onload = function() { loadItems("history");
console.log(reader.result); }
var jsonData = JSON.parse(reader.result); function loadItems(type) {
loadGraphData(graph, jsonData); fetch("/" + type)
}; .then((response) => response.json())
reader.readAsText(file); .then((data) => {
} var queue_div = document.getElementById(type + "button-content");
} queue_div.style.display = "block";
var see_queue_button = document.getElementById("see" + type + "button");
// Get prompt from dropped PNG or json let old_w = see_queue_button.style.width;
document.addEventListener('drop', (event) => { see_queue_button.innerHTML = "Close";
event.preventDefault();
event.stopPropagation(); let runningcontents;
const file = event.dataTransfer.files[0]; if (type === "queue") {
console.log(file.type); runningcontents = document.getElementById("runningcontents");
prompt_file_load(file); runningcontents.innerHTML = "";
}); }
let queuecontents = document.getElementById(type + "contents");
let runningNodeId = null; queuecontents.innerHTML = "";
let progress = null; function append_to_list(list_element, append_to_element, append_delete, state) {
let clientId = null; let number = list_element[0];
const orig = LGraphCanvas.prototype.drawNodeShape; let id = list_element[1];
LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolor, selected, mouse_over) { let prompt = list_element[2];
const res = orig.apply(this, arguments); let workflow = list_element[3].extra_pnginfo.workflow;
let a = document.createElement("a");
if(node.id + "" === runningNodeId) { a.innerHTML = number + ": ";
const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; append_to_element.appendChild(a);
ctx.lineWidth = 1; let button = document.createElement("button");
ctx.globalAlpha = 0.8; button.innerHTML = "Load";
ctx.beginPath(); button.style.fontSize = "10px";
if( shape == LiteGraph.BOX_SHAPE ) button.workflow = workflow;
ctx.rect(-6,-6 + LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0]+1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT ); button.onclick = function (event) {
else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) ) loadGraphData(graph, event.target.workflow);
ctx.roundRect(-6,-6 - LiteGraph.NODE_TITLE_HEIGHT, 12 +size[0]+1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT , this.round_radius * 2); if (state) {
else if (shape == LiteGraph.CARD_SHAPE) nodeOutputs = state;
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" append_to_element.appendChild(button);
ctx.stroke(); if (append_delete) {
ctx.strokeStyle = fgcolor; let button = document.createElement("button");
ctx.globalAlpha = 1; button.innerHTML = "Delete";
button.style.fontSize = "10px";
if(progress) { button.delete_id = id;
ctx.fillStyle = "green"; button.onclick = function (event) {
ctx.fillRect(0, 0, size[0] * (progress.value / progress.max), 6); deleteQueueElement(type, event.target.delete_id, () => loadItems(type));
ctx.fillStyle = bgcolor; };
} append_to_element.appendChild(button);
} }
append_to_element.appendChild(document.createElement("br"));
return res; }
}
if (runningcontents) {
function updateNodeProgress(v) { for (let x in data.queue_running) {
progress = v; append_to_list(data.queue_running[x], runningcontents, false);
graph.setDirtyCanvas(true, false); }
} }
function setRunningNode(id) { let items;
progress = null; if (type === "queue") {
runningNodeId = id; items = data.queue_pending;
graph.setDirtyCanvas(true, false); } else {
} items = Object.values(data);
}
(() => { items.sort((a, b) => a[0] - b[0]);
function updateStatus(data) { for (let i of items) {
document.getElementById("queuesize").innerHTML = "Queue size: " + (data ? data.exec_info.queue_remaining : "ERR"); append_to_list(type === "queue" ? i : i.prompt, queuecontents, true, i.outputs);
} }
})
//fix for colab and other things that don't support websockets. .catch((response) => {
function manually_fetch_queue() { console.log(response);
fetch('/prompt') });
.then(response => response.json()) }
.then(data => {
updateStatus(data); function seeItems(type) {
}).catch((response) => {updateStatus(null)}); var queue_div = document.getElementById(type + "button-content");
} if (queue_div.style.display == "block") {
closeItems(type);
let ws; } else {
function createSocket(isReconnect) { loadItems(type);
if(ws) return; }
}
let opened = false;
ws = new WebSocket(`ws${window.location.protocol === "https:"? "s" : ""}://${location.host}/ws`); function seeQueue() {
closeItems("history");
ws.addEventListener("open", () => { seeItems("queue");
opened = true; }
if(isReconnect) {
closeModal(); function seeHistory() {
} closeItems("queue");
}); seeItems("history");
}
ws.addEventListener("error", () => {
if(ws) ws.close(); function closeItems(type) {
manually_fetch_queue(); var queue_div = document.getElementById(type + "button-content");
}); queue_div.style.display = "none";
var see_queue_button = document.getElementById("see" + type + "button");
ws.addEventListener("close", () => { see_queue_button.innerHTML = "See " + type[0].toUpperCase() + type.substr(1);
setTimeout(() => { }
ws = null;
createSocket(true); function clearItems(type) {
}, 300); fetch("/" + type, {
if(opened) { method: "POST",
updateStatus(null); headers: {
showModal("Reconnecting..."); "Content-Type": "application/json",
} },
}); body: JSON.stringify({ clear: true }),
})
ws.addEventListener("message", (event) => { .then((data) => {
try { loadItems(type);
const msg = JSON.parse(event.data); })
switch(msg.type) { .catch((error) => console.error(error));
case "status": }
if(msg.data.sid) { </script>
clientId = msg.data.sid; </body>
}
updateStatus(msg.data.status);
break;
case "progress":
updateNodeProgress(msg.data)
break;
case "executing":
setRunningNode(msg.data.node);
break;
case "executed":
nodeOutputs[msg.data.node] = msg.data.output;
break;
default:
throw new Error("Unknown message type")
}
} catch (error) {
console.warn("Unhandled message:", event.data)
}
});
}
createSocket();
})();
function clearGraph() {
graph.clear();
}
function loadTxt2Img() {
loadGraphData(graph, default_graph);
}
function saveGraph() {
var json = JSON.stringify(graph.serialize()); // convert the data to a JSON string
var blob = new Blob([json], {type: "application/json"});
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.style = "display: none";
a.href = url;
a.download = "workflow.json";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
var input = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("accept", ".json,image/png");
input.style.display = "none";
document.body.appendChild(input);
input.addEventListener('change', function() {
var file = input.files[0];
prompt_file_load(file);
});
function loadGraph() {
input.click();
}
document.addEventListener('paste', e=>{
let data = (e.clipboardData || window.clipboardData).getData('text/plain');
console.log(data);
try {
data = data.slice(data.indexOf('{'));
j = JSON.parse(data);
} catch(err) {
data = data.slice(data.indexOf('workflow\n'));
data = data.slice(data.indexOf('{'));
j = JSON.parse(data);
}
if (Object.hasOwn(j, 'version') && Object.hasOwn(j, 'nodes') && Object.hasOwn(j, 'extra')) {
loadGraphData(graph, j);
}
});
function deleteQueueElement(type, delete_id, then) {
fetch('/' + type, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({"delete":[delete_id]})
})
.then(data => {
console.log(data);
then();
})
.catch(error => console.error(error))
}
function loadQueue() {
loadItems("queue")
}
function loadHistory() {
loadItems("history")
}
function loadItems(type) {
fetch('/' + type)
.then(response => response.json())
.then(data => {
var queue_div = document.getElementById(type + "button-content");
queue_div.style.display = 'block';
var see_queue_button = document.getElementById("see" + type + "button");
let old_w = see_queue_button.style.width;
see_queue_button.innerHTML = "Close";
let runningcontents;
if(type === "queue") {
runningcontents = document.getElementById("runningcontents");
runningcontents.innerHTML = '';
}
let queuecontents = document.getElementById(type + "contents");
queuecontents.innerHTML = '';
function append_to_list(list_element, append_to_element, append_delete, state) {
let number = list_element[0];
let id = list_element[1];
let prompt = list_element[2];
let workflow = list_element[3].extra_pnginfo.workflow;
let a = document.createElement("a");
a.innerHTML = number + ": ";
append_to_element.appendChild(a);
let button = document.createElement("button");
button.innerHTML = "Load";
button.style.fontSize = "10px";
button.workflow = workflow;
button.onclick = function(event) {
loadGraphData(graph, event.target.workflow);
if(state) {
nodeOutputs = state;
}
};
append_to_element.appendChild(button);
if (append_delete) {
let button = document.createElement("button");
button.innerHTML = "Delete";
button.style.fontSize = "10px";
button.delete_id = id;
button.onclick = function(event) {
deleteQueueElement(type, event.target.delete_id, () => loadItems(type));
};
append_to_element.appendChild(button);
}
append_to_element.appendChild(document.createElement("br"));
}
if(runningcontents) {
for (let x in data.queue_running) {
append_to_list(data.queue_running[x], runningcontents, false);
}
}
let items;
if(type === "queue") {
items = data.queue_pending;
} else {
items = Object.values(data);
}
items.sort((a, b) => a[0] - b[0]);
for (let i of items) {
append_to_list(type === "queue" ? i : i.prompt, queuecontents, true, i.outputs);
}
}).catch((response) => {console.log(response)});
}
function seeItems(type) {
var queue_div = document.getElementById(type + "button-content");
if (queue_div.style.display == 'block') {
closeItems(type)
} else {
loadItems(type);
}
}
function seeQueue() {
closeItems("history")
seeItems("queue")
}
function seeHistory() {
closeItems("queue")
seeItems("history")
}
function closeItems(type) {
var queue_div = document.getElementById(type + "button-content");
queue_div.style.display = 'none';
var see_queue_button = document.getElementById("see" + type + "button");
see_queue_button.innerHTML = "See " + type[0].toUpperCase() + type.substr(1)
}
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));
}
</script>
<span id="menu" style="font-size: 15px;position: absolute; top: 50%; right: 0%; background-color: white; text-align: center; z-index: 100;width:170px">
<span id="queuesize">Queue size: X</span><br>
<button style="font-size: 20px;width: 100%;" id="queuebutton" onclick="postPrompt(0)">Queue Prompt</button><br>
<span style="left: 0%;">
<button style="font-size: 10px;" id="queuebutton" onclick="postPrompt(-1)">Queue Front</button>
<button style="font-size: 10px; width: 50%;" id="seequeuebutton" onclick="seeQueue()">See Queue</button>
<button style="font-size: 10px; width: 50%;" id="seehistorybutton" onclick="seeHistory()">See History</button>
<br>
</span>
<div id="queuebutton-content" style="background-color: #e1e1e1;min-width: 160px;display: none;z-index: 101;">
<span style="width:100%;padding: 3px;display:inline-block;">Running:</span>
<div id="runningcontents" style="background-color: #d0d0d0; padding: 5px;">
<a>1</a>
<button style="font-size: 10px;">Load</button>
<br>
</div>
<span style="left: 0%;padding: 3px;display:inline-block;">Queued:</span>
<div id="queuecontents" style="overflow-y: scroll;height: 100px;background-color: #d0d0d0;padding: 5px;">
<a>1</a>
<button style="font-size: 10px;">Load</button>
<button style="font-size: 10px;">Delete</button>
<br>
<br>
</div>
<span style="padding: 5px;display:inline-block;">
<button style="font-size: 12px;" onclick="clearItems('queue')">Clear Queue</button>
<button style="font-size: 12px;" onclick="loadQueue()">Refresh</button>
</span>
</div>
<div id="historybutton-content" style="background-color: #e1e1e1;min-width: 160px;display: none;z-index: 101;">
<span style="width:100%;padding: 3px;display:inline-block;">History:</span>
<div id="historycontents" style="overflow-y: scroll;height: 100px;background-color: #d0d0d0;padding: 5px;">
</div>
<span style="padding: 5px;display:inline-block;">
<button style="font-size: 12px;" onclick="clearItems('history')">Clear History</button>
<button style="font-size: 12px;" onclick="loadHistory()">Refresh</button>
</span>
</div>
<br>
<button style="font-size: 20px;" onclick="saveGraph()">Save</button><br>
<button style="font-size: 20px;" onclick="loadGraph()">Load</button>
<br>
<button style="font-size: 20px;" onclick="clearGraph()">Clear</button><br>
<button style="font-size: 20px;" onclick="loadTxt2Img()">Load Default</button><br>
</span>
</body>
</html> </html>

172
web/scripts/app.js

@ -1,74 +1,8 @@
import { ComfyWidgets } from "./widgets.js"; import { ComfyWidgets } from "./widgets.js";
import { ComfyUI } from "./ui.js";
import { api } from "./api.js"; import { api } from "./api.js";
import { defaultGraph } from "./defaultGraph.js"; import { defaultGraph } from "./defaultGraph.js";
import { getPngMetadata } from "./pnginfo.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();
}
}
class ComfyApp { class ComfyApp {
constructor() { 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 * Set up the app on the page
*/ */
@ -406,6 +437,9 @@ class ComfyApp {
// Save current workflow automatically // Save current workflow automatically
setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000); setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000);
this.#addDrawNodeProgressHandler();
this.#addApiUpdateHandlers();
this.#addDropHandler();
await this.#invokeExtensionsAsync("setup"); await this.#invokeExtensionsAsync("setup");
} }
@ -561,6 +595,8 @@ class ComfyApp {
} }
} }
// TODO: check dynamic prompts here
this.canvas.draw(true, true); this.canvas.draw(true, true);
await this.ui.queue.update(); await this.ui.queue.update();
} }

45
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);
});
}

152
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");
}
}

1
web/scripts/widgets.js

@ -44,7 +44,6 @@ function addMultilineWidget(node, name, defaultVal, dynamicPrompt, app) {
const visible = app.canvas.ds.scale > 0.5; const visible = app.canvas.ds.scale > 0.5;
const t = ctx.getTransform(); const t = ctx.getTransform();
const margin = 10; const margin = 10;
console.log("back you go")
Object.assign(this.inputEl.style, { Object.assign(this.inputEl.style, {
left: `${t.a * margin + t.e}px`, left: `${t.a * margin + t.e}px`,
top: `${t.d * (y + widgetHeight - margin) + t.f}px`, top: `${t.d * (y + widgetHeight - margin) + t.f}px`,

32
web/style.css

@ -64,6 +64,38 @@ body {
cursor: pointer; 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) { @media (prefers-color-scheme: dark) {
body { body {
background-color: #202020; background-color: #202020;

Loading…
Cancel
Save