"test/git@developer.sourcefind.cn:hehl2/torchaudio.git" did not exist on "72ae755ab8951f08949ea53983418ecef02d85bb"
Unverified Commit 7e436ba9 authored by pythongosssss's avatar pythongosssss Committed by GitHub
Browse files

Added handling of sockets

Started rework of UI elements
Added pnginfo handling
parent 2eaa6640
<!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>
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 { ...@@ -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 { ...@@ -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 { ...@@ -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();
} }
......
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);
});
}
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) { ...@@ -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`,
......
...@@ -64,6 +64,38 @@ body { ...@@ -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;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment