ComfyUI/webshit/index.html

1118 lines
45 KiB
HTML

<html>
<head>
<link rel="stylesheet" type="text/css" href="litegraph.css">
<script type="text/javascript" src="litegraph.core.js"></script>
</head>
<style>
.customtext_input {
background-color: #FFFFFF;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #202020;
}
.customtext_input {
background-color: #202020;
color: white;
}
}
</style>
<body style='width:100%; height:100%; overflow: hidden;'>
<style>
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 100; /* Sit on top */
left: 20%; /* Center the modal horizontally */
right: 20%; /* Center the modal horizontally */
bottom: 30%; /* Center the modal vertically */
/* transform: translate(-50%, -50%); /* Use this to center the modal */ */
width: 50%; /* Set a width for the modal */
height: auto; /* Set a height for the modal */
padding: 30px;
background-color: #ff0000; /* Modal background */
box-shadow: 0px 0px 20px #888888;
border-radius: 10px;
text-align: center;
}
.close {
color: #aaaaaa;
font-size: 24px; /* Decreased font-size */
font-weight: bold;
position: absolute;
bottom: 10px; /* move the close button up a bit */
left: 50%; /* center the close button horizontally */
transform: translateX(-50%); /* use this to center the close button horizontally */
width: 100%;
text-align: center; /* center the text inside the button */
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
#modal-text {
white-space: pre-line; /* This will respect line breaks */
margin-bottom: 20px; /* Add some margin between the text and the close button*/
}
@media only screen and (max-height: 850px) {
#menu {
margin-top: -70px;
}
}
</style>
<div id="myErrorModal" class="modal">
<div class="modal-content">
<p id="modal-text"></p>
<span class="close">CLOSE</span>
</div>
</div>
<canvas id='mycanvas' width='1000' height='1000' style='width: 100%; height: 100%;'></canvas>
<script>
var graph = new LGraph();
var canvas = new LGraphCanvas("#mycanvas", graph);
const ccc = document.getElementById("mycanvas");
const ctx = ccc.getContext("2d");
let nodeOutputs = {}
// Resize the canvas to match the size of the canvas element
function resizeCanvas() {
ccc.width = ccc.offsetWidth;
ccc.height = ccc.offsetHeight;
canvas.draw(true, true);
}
// call the function when the page loads
resizeCanvas();
// call the function when the window is resized
window.addEventListener("resize", resizeCanvas);
var default_graph = {"last_node_id":9,"last_link_id":9,"nodes":[{"id":7,"type":"CLIPTextEncode","pos":[413,389],"size":{"0":425.27801513671875,"1":180.6060791015625},"flags":{},"order":3,"mode":0,"inputs":[{"name":"clip","type":"CLIP","link":5}],"outputs":[{"name":"CONDITIONING","type":"CONDITIONING","links":[6],"slot_index":0}],"properties":{},"widgets_values":["bad hands"]},{"id":6,"type":"CLIPTextEncode","pos":[415,186],"size":{"0":422.84503173828125,"1":164.31304931640625},"flags":{},"order":2,"mode":0,"inputs":[{"name":"clip","type":"CLIP","link":3}],"outputs":[{"name":"CONDITIONING","type":"CONDITIONING","links":[4],"slot_index":0}],"properties":{},"widgets_values":["masterpiece best quality girl"]},{"id":5,"type":"EmptyLatentImage","pos":[473,609],"size":{"0":315,"1":106},"flags":{},"order":1,"mode":0,"outputs":[{"name":"LATENT","type":"LATENT","links":[2],"slot_index":0}],"properties":{},"widgets_values":[512,512,1]},{"id":3,"type":"KSampler","pos":[863,186],"size":{"0":315,"1":262},"flags":{},"order":4,"mode":0,"inputs":[{"name":"model","type":"MODEL","link":1},{"name":"positive","type":"CONDITIONING","link":4},{"name":"negative","type":"CONDITIONING","link":6},{"name":"latent_image","type":"LATENT","link":2}],"outputs":[{"name":"LATENT","type":"LATENT","links":[7],"slot_index":0}],"properties":{},"widgets_values":[8566257,true,20,8,"euler","normal",1]},{"id":8,"type":"VAEDecode","pos":[1209,188],"size":{"0":210,"1":46},"flags":{},"order":5,"mode":0,"inputs":[{"name":"samples","type":"LATENT","link":7},{"name":"vae","type":"VAE","link":8}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[9],"slot_index":0}],"properties":{}},{"id":9,"type":"SaveImage","pos":[1451,189],"size":{"0":210,"1":26},"flags":{},"order":6,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":9}],"properties":{}},{"id":4,"type":"CheckpointLoader","pos":[26,474],"size":{"0":315,"1":122},"flags":{},"order":0,"mode":0,"outputs":[{"name":"MODEL","type":"MODEL","links":[1],"slot_index":0},{"name":"CLIP","type":"CLIP","links":[3,5],"slot_index":1},{"name":"VAE","type":"VAE","links":[8],"slot_index":2}],"properties":{},"widgets_values":["v1-inference.yaml","v1-5-pruned-emaonly.ckpt"]}],"links":[[1,4,0,3,0,"MODEL"],[2,5,0,3,3,"LATENT"],[3,4,1,6,0,"CLIP"],[4,6,0,3,1,"CONDITIONING"],[5,4,1,7,0,"CLIP"],[6,7,0,3,2,"CONDITIONING"],[7,3,0,8,0,"LATENT"],[8,4,2,8,1,"VAE"],[9,8,0,9,0,"IMAGE"]],"groups":[],"config":{},"extra":{},"version":0.4}
function loadGraphData(graph, graph_data)
{
graph.configure( graph_data);
for (let n in graph._nodes) {
n = graph._nodes[n];
s = n.computeSize();
s[0] = Math.max(n.size[0], s[0]);
s[1] = Math.max(n.size[1], s[1]);
n.size = s;
// If you break something in the backend and want to patch workflows in the frontend
// This is the place to do this
for (let w in n.widgets) {
let wid = n.widgets[w];
if (n.class_comfy == "KSampler" || n.class_comfy == "KSamplerAdvanced") {
if (wid.name == "sampler_name") {
if (wid.value.startsWith("sample_")) {
wid.value = wid.value.slice(7);
}
}
}
}
}
}
function afterLoadGraph()
{
let workflow = null;
try {
workflow = JSON.parse(localStorage.getItem("workflow"));
loadGraphData(graph, workflow);
} catch(err) {
}
if (!workflow) {
loadGraphData(graph, default_graph);
}
function saveGraph() {
localStorage.setItem("workflow", JSON.stringify(graph.serialize()));
}
setInterval(saveGraph, 1000);
}
function onObjectInfo(json) {
for (let key in json) {
function MyNode()
{
j = MyNode.__json_data;
inp = j['input']['required'];
this.class_comfy = MyNode.class_type_comfy;
this._widgets = []
min_height = 1;
min_width = 1;
for (let x in inp) {
let default_val = min_val = max_val = step_val = multiline = dynamic_prompt = undefined;
if (inp[x].length > 1) {
default_val = inp[x][1]['default'];
min_val = inp[x][1]['min'];
max_val = inp[x][1]['max'];
step_val = inp[x][1]['step'];
multiline = inp[x][1]['multiline'];
dynamic_prompt = inp[x][1]['dynamic_prompt'];
}
let type = inp[x][0];
if (Array.isArray(type)) {
w = this.addWidget("combo", x, type[0], function(v){}, { values: type } );
this._widgets += [w]
} else if (type == "INT") {
if (default_val == undefined) default_val = 0;
if (min_val == undefined) min_val = 0;
if (max_val == undefined) max_val = 2048;
if (step_val == undefined) step_val = 1;
w = this.addWidget("number", x, default_val, function(v){let s = this.options.step / 10;this.value = Math.round( v / s ) * s;}, { min: min_val, max: max_val, step: 10.0 * step_val} );
this._widgets += [w]
if (x == "seed" || x == "noise_seed") {
w1 = this.addWidget("toggle", "Random seed after every gen", true, function(v){}, { on: "enabled", off:"disabled"} );
w1.to_randomize = w;
this._widgets += [w1]
}
} else if (type == "FLOAT") {
if (default_val == undefined) default_val = 0;
if (min_val == undefined) min_val = 0;
if (max_val == undefined) max_val = 2048;
if (step_val == undefined) step_val = 0.5;
// if (min_val == 0.0 && max_val == 1.0) {
// w = this.slider = this.addWidget("slider", x, default_val, function(v){}, { min: min_val, max: max_val} );
// } else {
w = this.addWidget("number", x, default_val, function(v){}, { min: min_val, max: max_val, step: 10.0 * step_val} );
// }
this._widgets += [w]
} else if (type == "STRING") {
if (default_val == undefined) default_val = "";
if (multiline == undefined) multiline = false;
if (dynamic_prompt == undefined) dynamic_prompt = false;
if (multiline) {
var w = {
type: "customtext",
name: x,
dynamic_prompt: dynamic_prompt,
get value() { return this.input_div.value;},
set value(x) { this.input_div.value = x;},
callback: function(v){console.log(v);},
options: {},
draw: function(ctx, node, widget_width, y, H){
var show_text = canvas.ds.scale > 0.5;
// this.input_div.style.top = `${y}px`;
let t = ctx.getTransform();
let margin = 10;
let x_div = t.a * margin * 2 + t.e;
let y_div = t.d * (y + H) + t.f;
let width_div = (widget_width - margin * 2 - 3) * t.a;
let height_div = (this.parent.size[1] - (y + H) - 3)* t.d;
this.input_div.style.left = `${x_div}px`;
this.input_div.style.top = `${y_div}px`;
this.input_div.style.width = width_div;
this.input_div.style.height = height_div;
this.input_div.style.position = 'absolute';
this.input_div.style.zIndex = 1;
this.input_div.style.fontSize = t.d * 10.0;
if (show_text) {
this.input_div.hidden = false;
} else {
this.input_div.hidden = true;
}
ctx.save();
// ctx.fillText(String(this.value).substr(0,30), 0, y + H * 0.7);
ctx.restore();
},
};
w.input_div = document.createElement('textarea');
w.input_div.contentEditable = true;
w.input_div.className = "customtext_input";
w.input_div.style.overflow = 'hidden';
w.input_div.style.overflowY = 'auto';
w.input_div.style.padding = 2;
w.input_div.style.resize = 'none';
w.input_div.style.border = 'none';
w.input_div.value = default_val;
document.addEventListener('click', function(event) {
if (!w.input_div.contains(event.target)) {
w.input_div.blur();
}
});
w.parent = this;
min_height = Math.max(min_height, 200);
min_width = Math.max(min_width, 400);
ccc.parentNode.appendChild(w.input_div);
w = this.addCustomWidget(w);
canvas.onDrawBackground = function() {
for (let n in graph._nodes) {
n = graph._nodes[n];
for (let w in n.widgets) {
let wid = n.widgets[w];
if (Object.hasOwn(wid, 'input_div')) {
wid.input_div.style.left = -8000;
wid.input_div.style.position = 'absolute';
}
}
}
}
// w = this.addWidget("text", x, "", function(v){}, { multiline:true } );
console.log(w, this);
this._widgets += [w]
this.onRemoved = function() {
for (let y in this.widgets) {
if (this.widgets[y].input_div) {
this.widgets[y].input_div.remove();
}
}
}
} else {
w = this.addWidget("text", x, default_val, function(v){}, { multiline:false } );
w.dynamic_prompt = dynamic_prompt;
this._widgets += [w];
}
} else {
this.addInput(x, type);
}
MyNode.prototype.getExtraMenuOptions = function(canvas, options) {
if(this.imgs) {
let img;
if(this.imageIndex != null) {
img = this.imgs[this.imageIndex];
} else if(this.overIndex != null) {
img = this.imgs[this.overIndex];
}
if(img) {
options.unshift({
content: "Open Image",
callback: () => window.open(img.src, "_blank")
});
}
}
}
MyNode.prototype.onDrawBackground = function(ctx) {
if(!this.flags.collapsed) {
const output = nodeOutputs[this.id + ""];
if(output && output.images) {
if(this.images !== output.images) {
this.images = output.images;
this.imgs = null;
this.imageIndex = null;
Promise.all(output.images.map(src => {
return new Promise(r => {
const img = new Image();
img.onload = () => r(img);
img.onerror = () => r(null);
img.src = "/view/" + src;
});
})).then(imgs => {
if(this.images === output.images) {
this.imgs = imgs.filter(Boolean);
if(this.size[1] < 100) {
this.size[1] = 250;
}
graph.setDirtyCanvas(true);
}
});
}
if(this.imgs) {
const canvas = graph.list_of_graphcanvas[0];
const mouse = canvas.graph_mouse;
if(!canvas.pointer_is_down && this.pointerDown) {
if(mouse[0] === this.pointerDown.pos[0] && mouse[1] === this.pointerDown.pos[1]) {
this.imageIndex = this.pointerDown.index;
}
this.pointerDown = null;
}
let w = this.imgs[0].naturalWidth;
let h = this.imgs[0].naturalHeight;
let imageIndex = this.imageIndex;
const numImages = this.imgs.length;
if(numImages === 1 && !imageIndex) {
this.imageIndex = imageIndex = 0;
}
let shiftY = this.type === "SaveImage" ? 55 : 0;
let dw = this.size[0];
let dh = this.size[1];
dh -= shiftY;
if(imageIndex == null) {
let best = 0;
let cellWidth;
let cellHeight;
let cols = 0;
let shiftX = 0;
for (let c = 1; c <= numImages; c++) {
const rows = Math.ceil(numImages / c);
const cW = dw / c;
const cH = dh / rows;
const scaleX = cW / w;
const scaleY = cH / h;
const scale = Math.min(scaleX, scaleY, 1);
const imageW = w * scale;
const imageH = h * scale;
const area = imageW * imageH * numImages;
if(area > best) {
best = area;
cellWidth = imageW;
cellHeight = imageH;
cols = c;
shiftX = c * ((cW - imageW) / 2);
}
}
let anyHovered = false;
this.imageRects = [];
for (let i = 0; i < numImages; i++) {
const img = this.imgs[i];
const row = Math.floor(i / cols);
const col = i % cols;
const x = col * cellWidth + shiftX;
const y = row * cellHeight + shiftY;
if(!anyHovered) {
anyHovered = LiteGraph.isInsideRectangle(mouse[0], mouse[1], x + this.pos[0], y + this.pos[1], cellWidth, cellHeight);
if(anyHovered) {
this.overIndex = i;
let value = 110;
if(canvas.pointer_is_down) {
if(!this.pointerDown || this.pointerDown.index !== i) {
this.pointerDown = {index: i, pos: [...mouse]};
}
value = 125;
}
ctx.filter = `contrast(${value}%) brightness(${value}%)`;
canvas.canvas.style.cursor = "pointer";
}
}
this.imageRects.push([x, y, cellWidth, cellHeight]);
ctx.drawImage(img, x, y, cellWidth, cellHeight);
ctx.filter = "none";
}
if(!anyHovered) {
this.pointerDown = null;
this.overIndex = null;
}
} else {
// Draw individual
const scaleX = dw / w;
const scaleY = dh / h;
const scale = Math.min(scaleX, scaleY, 1);
w *= scale;
h *= scale;
let x = (dw - w) / 2;
let y = (dh - h) / 2 + shiftY;
ctx.drawImage(this.imgs[imageIndex], x, y, w, h);
const drawButton = (x, y, sz, text) => {
const hovered = LiteGraph.isInsideRectangle(mouse[0], mouse[1], x + this.pos[0], y + this.pos[1], sz, sz);
let fill = "#333";
let textFill = "#fff";
let isClicking = false;
if(hovered) {
canvas.canvas.style.cursor = "pointer";
if(canvas.pointer_is_down) {
fill = "#1e90ff";
isClicking = true;
} else {
fill = "#eee";
textFill = "#000";
}
} else {
this.pointerWasDown = null;
}
ctx.fillStyle = fill;
ctx.beginPath();
ctx.roundRect(x, y, sz, sz, [4]);
ctx.fill();
ctx.fillStyle = textFill;
ctx.font = "12px Arial";
ctx.textAlign = "center";
ctx.fillText(text, x + 15, y + 20);
return isClicking;
}
if(numImages > 1) {
if(drawButton(x + w - 35, y + h - 35, 30, `${this.imageIndex+1}/${numImages}`)) {
let i = this.imageIndex + 1 >= numImages ? 0 : this.imageIndex + 1;
if(!this.pointerDown || !this.pointerDown.index === i) {
this.pointerDown = {index: i, pos: [...mouse]};
}
}
if(drawButton(x + w - 35, y + 5, 30, `x`)) {
if(!this.pointerDown || !this.pointerDown.index === null) {
this.pointerDown = {index: null, pos: [...mouse]};
}
}
}
}
}
}
}
};
}
out = j['output'];
for (let x in out) {
this.addOutput(out[x], out[x]);
}
s = this.computeSize();
s[0] *= 1.5;
s[0] = Math.max(min_width, s[0]);
s[1] = Math.max(min_height, s[1]);
this.size = s;
this.serialize_widgets = true;
}
MyNode.title = json[key]['name'];
MyNode.class_type_comfy = json[key]['name'];
MyNode.__json_data = json[key]
LiteGraph.registerNodeType(key, MyNode);
MyNode.category = json[key]['category'];
};
afterLoadGraph();
// loadGraphData(graph, JSON.parse(base_txt2img_graph));
}
fetch("object_info", {cache: "no-store"})
.then(response => response.json())
.then(json => onObjectInfo(json));
//register in the system
graph.start();
// LiteGraph.registerNodeType("testing", MyAddNode);
graph.onNodeRemoved = function(n) {
for (let y in n.widgets) {
if (n.widgets[y].input_div) {
n.widgets[y].input_div.remove();
}
}
}
function graphToPrompt() {
let s = graph.serialize();
let output = {};
// console.log(s['nodes']);
nodes = s['nodes']
for (let x in nodes) {
let n = graph.getNodeById(nodes[x].id);
let input_ = {};
// dynamic prompts handling
if (n.widgets && n.widgets.length > 0) {
// find widgets declared as supporting dynamic prompting
var supportedWidgets = n.widgets.filter(e => e.dynamic_prompt === true);
if (supportedWidgets.length > 0) {
// resolve dynamic prompts for all widgets supporting it in this node
for (let i in supportedWidgets)
{
var widget = supportedWidgets[i];
// store the unresolved prompt to restore it after sending the resolved prompt to the backend
// use of .innerText which keep \n symbols in order to not break multilines support
widget.value_initial = widget.input_div.value;
// resolve the string
var prompt = widget.input_div.value;
while (prompt.replace("\\{", "").includes('{') && prompt.replace("\\}", "").includes('}')) {
const startIndex = prompt.replace("\\{", "00").indexOf('{');
const endIndex = prompt.replace("\\}", "00").indexOf('}');
const optionsString = prompt.substring(startIndex + 1, endIndex);
const options = optionsString.split('|');
const randomIndex = Math.floor(Math.random() * options.length);
const randomOption = options[randomIndex];
prompt = prompt.substring(0, startIndex) + randomOption + prompt.substring(endIndex + 1);
}
widget.value = prompt;
}
}
}
for (let y in n.widgets) {
if (!Object.hasOwn(n.widgets[y], 'to_randomize')) { //don't include "Random seed after every gen" in prompt.
if (n.widgets[y].dynamic_prompt && n.widgets[y].dynamic_prompt === true) {
input_[n.widgets[y].name] = n.widgets[y].value.replace("\\{", "{").replace("\\}", "}");
} else {
input_[n.widgets[y].name] = n.widgets[y].value;
}
}
}
for (let y in n.inputs) {
let parent_node = n.getInputNode(y);
if (parent_node) {
for (let z in parent_node.outputs) {
let c_nodes = parent_node.getOutputNodes(z);
// console.log(c_nodes, z);
if (c_nodes) {
for (let zz in c_nodes) {
if (c_nodes[zz].id == n.id && parent_node.outputs[z].links.includes(n.inputs[y].link)) {
input_[n.inputs[y].name] = [String(parent_node.id), parseInt(z)];
break;
}
}
}
}
}
}
let node = {}
node['inputs'] = input_;
node['class_type'] = n.class_comfy;
// inputs = x['inputs']
// inputs['name'], inputs['id']
// console.log(x, n);
// console.log(node);
output[String(n.id)] = node;
}
return output;
}
function closeModal() {
var modal = document.getElementById("myErrorModal");
modal.style.display = "none";
}
function showModal(text) {
var modal = document.getElementById("myErrorModal");
var modalText = document.getElementById("modal-text");
modalText.innerHTML = text;
modal.style.display = "block";
var closeBtn = modal.getElementsByClassName("close")[0];
closeBtn.onclick = function(event) {closeModal();}
}
function promptPosted(data)
{
if (data.status != 200) {
data.text().then(dt => showModal(dt));
return;
}
let s = graph.serialize();
let output = {};
// console.log(s['nodes']);
nodes = s['nodes']
for (let x in nodes) {
let n = graph.getNodeById(nodes[x].id);
for (let w in n.widgets) {
let wid = n.widgets[w];
if (Object.hasOwn(wid, 'to_randomize')) {
if (wid.value) {
wid.to_randomize.value = Math.floor(Math.random() * 1125899906842624);
}
}
}
}
canvas.draw(true, true);
loadQueueIfVisible();
}
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 promptToGraph(prompt) {
for (let x in prompt) {
}
}
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 progress = 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() {
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 loadQueueIfVisible()
{
var queue_div = document.getElementById("queuebutton-content");
if (queue_div.style.display == 'block') {
loadQueue();
}
}
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>