mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-04-20 11:23:29 +00:00
Added handling of sockets
Started rework of UI elements Added pnginfo handling
This commit is contained in:
parent
2eaa664089
commit
7e436ba9cc
346
web/index.html
346
web/index.html
@ -1,8 +1,8 @@
|
|||||||
<!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">
|
||||||
@ -16,224 +16,9 @@
|
|||||||
<script>
|
<script>
|
||||||
return;
|
return;
|
||||||
|
|
||||||
function postPrompt(number) {
|
|
||||||
let prompt = graphToPrompt();
|
|
||||||
let full_data = {client_id: clientId, prompt: prompt, extra_data: {extra_pnginfo: {workflow: graph.serialize()}}};
|
|
||||||
if (number == -1) {
|
|
||||||
full_data.front = true;
|
|
||||||
} else
|
|
||||||
if (number != 0) {
|
|
||||||
full_data.number = number;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('/prompt', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(full_data)
|
|
||||||
})
|
|
||||||
.then(data => promptPosted(data))
|
|
||||||
.catch(error => console.error(error))
|
|
||||||
|
|
||||||
// console.log(JSON.stringify(prompt));
|
|
||||||
// console.log(JSON.stringify(graph.serialize()));
|
|
||||||
|
|
||||||
// restore initial values replaced by dynamic prompting
|
|
||||||
for (let x in graph._nodes_by_id) {
|
|
||||||
let n = graph._nodes_by_id[x];
|
|
||||||
for (let w in n.widgets) {
|
|
||||||
let wid = n.widgets[w];
|
|
||||||
if (wid.dynamic_prompt && wid.dynamic_prompt === true)
|
|
||||||
wid.value = wid.value_initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function prompt_file_load(file)
|
|
||||||
{
|
|
||||||
if (file.type === 'image/png') {
|
|
||||||
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');
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the next chunk
|
|
||||||
offset += 12 + length;
|
|
||||||
}
|
|
||||||
console.log(txt_chunks);
|
|
||||||
// console.log(JSON.parse(txt_chunks["prompt"]));
|
|
||||||
loadGraphData(graph, JSON.parse(txt_chunks["workflow"]));
|
|
||||||
};
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onload = function() {
|
|
||||||
console.log(reader.result);
|
|
||||||
var jsonData = JSON.parse(reader.result);
|
|
||||||
loadGraphData(graph, jsonData);
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get prompt from dropped PNG or json
|
|
||||||
document.addEventListener('drop', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
console.log(file.type);
|
|
||||||
prompt_file_load(file);
|
|
||||||
});
|
|
||||||
|
|
||||||
let runningNodeId = null;
|
let runningNodeId = null;
|
||||||
let progress = null;
|
let progress = null;
|
||||||
let clientId = null;
|
let clientId = null;
|
||||||
const orig = LGraphCanvas.prototype.drawNodeShape;
|
|
||||||
LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
|
|
||||||
const res = orig.apply(this, arguments);
|
|
||||||
|
|
||||||
if(node.id + "" === 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(progress) {
|
|
||||||
ctx.fillStyle = "green";
|
|
||||||
ctx.fillRect(0, 0, size[0] * (progress.value / progress.max), 6);
|
|
||||||
ctx.fillStyle = bgcolor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNodeProgress(v) {
|
|
||||||
progress = v;
|
|
||||||
graph.setDirtyCanvas(true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRunningNode(id) {
|
|
||||||
progress = null;
|
|
||||||
runningNodeId = id;
|
|
||||||
graph.setDirtyCanvas(true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
function updateStatus(data) {
|
|
||||||
document.getElementById("queuesize").innerHTML = "Queue size: " + (data ? data.exec_info.queue_remaining : "ERR");
|
|
||||||
}
|
|
||||||
|
|
||||||
//fix for colab and other things that don't support websockets.
|
|
||||||
function manually_fetch_queue() {
|
|
||||||
fetch('/prompt')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
updateStatus(data);
|
|
||||||
}).catch((response) => {updateStatus(null)});
|
|
||||||
}
|
|
||||||
|
|
||||||
let ws;
|
|
||||||
function createSocket(isReconnect) {
|
|
||||||
if(ws) return;
|
|
||||||
|
|
||||||
let opened = false;
|
|
||||||
ws = new WebSocket(`ws${window.location.protocol === "https:"? "s" : ""}://${location.host}/ws`);
|
|
||||||
|
|
||||||
ws.addEventListener("open", () => {
|
|
||||||
opened = true;
|
|
||||||
if(isReconnect) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ws.addEventListener("error", () => {
|
|
||||||
if(ws) ws.close();
|
|
||||||
manually_fetch_queue();
|
|
||||||
});
|
|
||||||
|
|
||||||
ws.addEventListener("close", () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
ws = null;
|
|
||||||
createSocket(true);
|
|
||||||
}, 300);
|
|
||||||
if(opened) {
|
|
||||||
updateStatus(null);
|
|
||||||
showModal("Reconnecting...");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ws.addEventListener("message", (event) => {
|
|
||||||
try {
|
|
||||||
const msg = JSON.parse(event.data);
|
|
||||||
switch(msg.type) {
|
|
||||||
case "status":
|
|
||||||
if(msg.data.sid) {
|
|
||||||
clientId = msg.data.sid;
|
|
||||||
}
|
|
||||||
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() {
|
function clearGraph() {
|
||||||
graph.clear();
|
graph.clear();
|
||||||
@ -265,62 +50,60 @@ input.setAttribute("accept", ".json,image/png");
|
|||||||
input.style.display = "none";
|
input.style.display = "none";
|
||||||
document.body.appendChild(input);
|
document.body.appendChild(input);
|
||||||
|
|
||||||
input.addEventListener('change', function() {
|
input.addEventListener("change", function () {
|
||||||
var file = input.files[0];
|
var file = input.files[0];
|
||||||
prompt_file_load(file);
|
prompt_file_load(file);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function loadGraph() {
|
function loadGraph() {
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('paste', e=>{
|
document.addEventListener("paste", (e) => {
|
||||||
let data = (e.clipboardData || window.clipboardData).getData('text/plain');
|
let data = (e.clipboardData || window.clipboardData).getData("text/plain");
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data = data.slice(data.indexOf('{'));
|
data = data.slice(data.indexOf("{"));
|
||||||
j = JSON.parse(data);
|
j = JSON.parse(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
data = data.slice(data.indexOf('workflow\n'));
|
data = data.slice(data.indexOf("workflow\n"));
|
||||||
data = data.slice(data.indexOf('{'));
|
data = data.slice(data.indexOf("{"));
|
||||||
j = JSON.parse(data);
|
j = JSON.parse(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.hasOwn(j, "version") && Object.hasOwn(j, "nodes") && Object.hasOwn(j, "extra")) {
|
||||||
if (Object.hasOwn(j, 'version') && Object.hasOwn(j, 'nodes') && Object.hasOwn(j, 'extra')) {
|
|
||||||
loadGraphData(graph, j);
|
loadGraphData(graph, j);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function deleteQueueElement(type, delete_id, then) {
|
function deleteQueueElement(type, delete_id, then) {
|
||||||
fetch('/' + type, {
|
fetch("/" + type, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({"delete":[delete_id]})
|
body: JSON.stringify({ delete: [delete_id] }),
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
then();
|
then();
|
||||||
})
|
})
|
||||||
.catch(error => console.error(error))
|
.catch((error) => console.error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadQueue() {
|
function loadQueue() {
|
||||||
loadItems("queue")
|
loadItems("queue");
|
||||||
}
|
}
|
||||||
function loadHistory() {
|
function loadHistory() {
|
||||||
loadItems("history")
|
loadItems("history");
|
||||||
}
|
}
|
||||||
function loadItems(type) {
|
function loadItems(type) {
|
||||||
fetch('/' + type)
|
fetch("/" + type)
|
||||||
.then(response => response.json())
|
.then((response) => response.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
var queue_div = document.getElementById(type + "button-content");
|
var queue_div = document.getElementById(type + "button-content");
|
||||||
queue_div.style.display = 'block';
|
queue_div.style.display = "block";
|
||||||
var see_queue_button = document.getElementById("see" + type + "button");
|
var see_queue_button = document.getElementById("see" + type + "button");
|
||||||
let old_w = see_queue_button.style.width;
|
let old_w = see_queue_button.style.width;
|
||||||
see_queue_button.innerHTML = "Close";
|
see_queue_button.innerHTML = "Close";
|
||||||
@ -328,10 +111,10 @@ function loadItems(type) {
|
|||||||
let runningcontents;
|
let runningcontents;
|
||||||
if (type === "queue") {
|
if (type === "queue") {
|
||||||
runningcontents = document.getElementById("runningcontents");
|
runningcontents = document.getElementById("runningcontents");
|
||||||
runningcontents.innerHTML = '';
|
runningcontents.innerHTML = "";
|
||||||
}
|
}
|
||||||
let queuecontents = document.getElementById(type + "contents");
|
let queuecontents = document.getElementById(type + "contents");
|
||||||
queuecontents.innerHTML = '';
|
queuecontents.innerHTML = "";
|
||||||
function append_to_list(list_element, append_to_element, append_delete, state) {
|
function append_to_list(list_element, append_to_element, append_delete, state) {
|
||||||
let number = list_element[0];
|
let number = list_element[0];
|
||||||
let id = list_element[1];
|
let id = list_element[1];
|
||||||
@ -381,96 +164,51 @@ function loadItems(type) {
|
|||||||
for (let i of items) {
|
for (let i of items) {
|
||||||
append_to_list(type === "queue" ? i : i.prompt, queuecontents, true, i.outputs);
|
append_to_list(type === "queue" ? i : i.prompt, queuecontents, true, i.outputs);
|
||||||
}
|
}
|
||||||
}).catch((response) => {console.log(response)});
|
})
|
||||||
|
.catch((response) => {
|
||||||
|
console.log(response);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function seeItems(type) {
|
function seeItems(type) {
|
||||||
var queue_div = document.getElementById(type + "button-content");
|
var queue_div = document.getElementById(type + "button-content");
|
||||||
if (queue_div.style.display == 'block') {
|
if (queue_div.style.display == "block") {
|
||||||
closeItems(type)
|
closeItems(type);
|
||||||
} else {
|
} else {
|
||||||
loadItems(type);
|
loadItems(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function seeQueue() {
|
function seeQueue() {
|
||||||
closeItems("history")
|
closeItems("history");
|
||||||
seeItems("queue")
|
seeItems("queue");
|
||||||
}
|
}
|
||||||
|
|
||||||
function seeHistory() {
|
function seeHistory() {
|
||||||
closeItems("queue")
|
closeItems("queue");
|
||||||
seeItems("history")
|
seeItems("history");
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeItems(type) {
|
function closeItems(type) {
|
||||||
var queue_div = document.getElementById(type + "button-content");
|
var queue_div = document.getElementById(type + "button-content");
|
||||||
queue_div.style.display = 'none';
|
queue_div.style.display = "none";
|
||||||
var see_queue_button = document.getElementById("see" + type + "button");
|
var see_queue_button = document.getElementById("see" + type + "button");
|
||||||
see_queue_button.innerHTML = "See " + type[0].toUpperCase() + type.substr(1)
|
see_queue_button.innerHTML = "See " + type[0].toUpperCase() + type.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearItems(type) {
|
function clearItems(type) {
|
||||||
fetch('/' + type, {
|
fetch("/" + type, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({"clear":true})
|
body: JSON.stringify({ clear: true }),
|
||||||
}).then(data => {
|
})
|
||||||
|
.then((data) => {
|
||||||
loadItems(type);
|
loadItems(type);
|
||||||
})
|
})
|
||||||
.catch(error => console.error(error));
|
.catch((error) => console.error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -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
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 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 {
|
|||||||
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…
Reference in New Issue
Block a user