ui.js 16.7 KB
Newer Older
1
2
import { api } from "./api.js";
import { ComfyDialog as _ComfyDialog } from "./ui/dialog.js";
pythongosssss's avatar
pythongosssss committed
3
import { toggleSwitch } from "./ui/toggleSwitch.js";
4
5
6
import { ComfySettingsDialog } from "./ui/settings.js";

export const ComfyDialog = _ComfyDialog;
pythongosssss's avatar
pythongosssss committed
7

pythongosssss's avatar
pythongosssss committed
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * 
 * @param { string } tag HTML Element Tag and optional classes e.g. div.class1.class2
 * @param { string | Element | Element[] | {
 * 	 parent?: Element,
 *   $?: (el: Element) => void, 
 *   dataset?: DOMStringMap,
 *   style?: CSSStyleDeclaration,
 * 	 for?: string
 * } | undefined } propsOrChildren 
 * @param { Element[] | undefined } [children]
 * @returns 
 */
Jairo Correa's avatar
Jairo Correa committed
21
export function $el(tag, propsOrChildren, children) {
pythongosssss's avatar
pythongosssss committed
22
23
	const split = tag.split(".");
	const element = document.createElement(split.shift());
reaper47's avatar
reaper47 committed
24
25
26
27
	if (split.length > 0) {
		element.classList.add(...split);
	}

pythongosssss's avatar
pythongosssss committed
28
	if (propsOrChildren) {
pythongosssss's avatar
pythongosssss committed
29
30
31
32
33
		if (typeof propsOrChildren === "string") {
			propsOrChildren = { textContent: propsOrChildren };
		} else if (propsOrChildren instanceof Element) {
			propsOrChildren = [propsOrChildren];
		}
pythongosssss's avatar
pythongosssss committed
34
35
36
		if (Array.isArray(propsOrChildren)) {
			element.append(...propsOrChildren);
		} else {
reaper47's avatar
reaper47 committed
37
			const {parent, $: cb, dataset, style} = propsOrChildren;
pythongosssss's avatar
pythongosssss committed
38
39
			delete propsOrChildren.parent;
			delete propsOrChildren.$;
40
41
			delete propsOrChildren.dataset;
			delete propsOrChildren.style;
pythongosssss's avatar
pythongosssss committed
42

reaper47's avatar
reaper47 committed
43
44
45
46
			if (Object.hasOwn(propsOrChildren, "for")) {
				element.setAttribute("for", propsOrChildren.for)
			}

47
48
49
50
51
52
			if (style) {
				Object.assign(element.style, style);
			}

			if (dataset) {
				Object.assign(element.dataset, dataset);
pythongosssss's avatar
pythongosssss committed
53
54
			}

pythongosssss's avatar
pythongosssss committed
55
56
			Object.assign(element, propsOrChildren);
			if (children) {
pythongosssss's avatar
pythongosssss committed
57
				element.append(...(children instanceof Array ? children : [children]));
pythongosssss's avatar
pythongosssss committed
58
59
60
61
62
63
64
65
66
67
68
69
70
71
			}

			if (parent) {
				parent.append(element);
			}

			if (cb) {
				cb(element);
			}
		}
	}
	return element;
}

72
function dragElement(dragEl, settings) {
73
74
75
76
77
78
	var posDiffX = 0,
		posDiffY = 0,
		posStartX = 0,
		posStartY = 0,
		newPosX = 0,
		newPosY = 0;
79
	if (dragEl.getElementsByClassName("drag-handle")[0]) {
Jairo Correa's avatar
Jairo Correa committed
80
		// if present, the handle is where you move the DIV from:
81
		dragEl.getElementsByClassName("drag-handle")[0].onmousedown = dragMouseDown;
Jairo Correa's avatar
Jairo Correa committed
82
83
84
85
86
	} else {
		// otherwise, move the DIV from anywhere inside the DIV:
		dragEl.onmousedown = dragMouseDown;
	}

pythongosssss's avatar
pythongosssss committed
87
88
89
90
91
	// When the element resizes (e.g. view queue) ensure it is still in the windows bounds
	const resizeObserver = new ResizeObserver(() => {
		ensureInBounds();
	}).observe(dragEl);

92
	function ensureInBounds() {
93
		try {
pythongosssss's avatar
pythongosssss committed
94
95
			newPosX = Math.min(document.body.clientWidth - dragEl.clientWidth, Math.max(0, dragEl.offsetLeft));
			newPosY = Math.min(document.body.clientHeight - dragEl.clientHeight, Math.max(0, dragEl.offsetTop));
96

pythongosssss's avatar
pythongosssss committed
97
98
			positionElement();
		}
99
100
101
		catch(exception){
			// robust
		}
pythongosssss's avatar
pythongosssss committed
102
	}
103
104
105
106
107
108
109
110
111
112
113
114
115

	function positionElement() {
		const halfWidth = document.body.clientWidth / 2;
		const anchorRight = newPosX + dragEl.clientWidth / 2 > halfWidth;

		// set the element's new position:
		if (anchorRight) {
			dragEl.style.left = "unset";
			dragEl.style.right = document.body.clientWidth - newPosX - dragEl.clientWidth + "px";
		} else {
			dragEl.style.left = newPosX + "px";
			dragEl.style.right = "unset";
		}
116

pythongosssss's avatar
pythongosssss committed
117
118
		dragEl.style.top = newPosY + "px";
		dragEl.style.bottom = "unset";
119
120
121
122
123

		if (savePos) {
			localStorage.setItem(
				"Comfy.MenuPosition",
				JSON.stringify({
pythongosssss's avatar
pythongosssss committed
124
125
					x: dragEl.offsetLeft,
					y: dragEl.offsetTop,
126
127
128
129
130
131
132
133
134
				})
			);
		}
	}

	function restorePos() {
		let pos = localStorage.getItem("Comfy.MenuPosition");
		if (pos) {
			pos = JSON.parse(pos);
pythongosssss's avatar
pythongosssss committed
135
136
137
			newPosX = pos.x;
			newPosY = pos.y;
			positionElement();
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
			ensureInBounds();
		}
	}

	let savePos = undefined;
	settings.addSetting({
		id: "Comfy.MenuPosition",
		name: "Save menu position",
		type: "boolean",
		defaultValue: savePos,
		onChange(value) {
			if (savePos === undefined && value) {
				restorePos();
			}
			savePos = value;
		},
	});
reaper47's avatar
reaper47 committed
155

Jairo Correa's avatar
Jairo Correa committed
156
157
158
159
	function dragMouseDown(e) {
		e = e || window.event;
		e.preventDefault();
		// get the mouse cursor position at startup:
160
161
		posStartX = e.clientX;
		posStartY = e.clientY;
Jairo Correa's avatar
Jairo Correa committed
162
163
164
165
166
167
168
169
		document.onmouseup = closeDragElement;
		// call a function whenever the cursor moves:
		document.onmousemove = elementDrag;
	}

	function elementDrag(e) {
		e = e || window.event;
		e.preventDefault();
170
171
172

		dragEl.classList.add("comfy-menu-manual-pos");

Jairo Correa's avatar
Jairo Correa committed
173
		// calculate the new cursor position:
174
175
176
177
		posDiffX = e.clientX - posStartX;
		posDiffY = e.clientY - posStartY;
		posStartX = e.clientX;
		posStartY = e.clientY;
178
179
180
181
182

		newPosX = Math.min(document.body.clientWidth - dragEl.clientWidth, Math.max(0, dragEl.offsetLeft + posDiffX));
		newPosY = Math.min(document.body.clientHeight - dragEl.clientHeight, Math.max(0, dragEl.offsetTop + posDiffY));

		positionElement();
Jairo Correa's avatar
Jairo Correa committed
183
184
	}

185
	window.addEventListener("resize", () => {
186
		ensureInBounds();
187
188
	});

Jairo Correa's avatar
Jairo Correa committed
189
190
191
192
193
194
195
	function closeDragElement() {
		// stop moving when mouse button is released:
		document.onmouseup = null;
		document.onmousemove = null;
	}
}

pythongosssss's avatar
pythongosssss committed
196
class ComfyList {
pythongosssss's avatar
pythongosssss committed
197
198
	#type;
	#text;
199
	#reverse;
pythongosssss's avatar
pythongosssss committed
200

201
	constructor(text, type, reverse) {
pythongosssss's avatar
pythongosssss committed
202
203
		this.#text = text;
		this.#type = type || text.toLowerCase();
204
		this.#reverse = reverse || false;
pythongosssss's avatar
pythongosssss committed
205
		this.element = $el("div.comfy-list");
pythongosssss's avatar
pythongosssss committed
206
207
208
209
210
211
212
213
		this.element.style.display = "none";
	}

	get visible() {
		return this.element.style.display !== "none";
	}

	async load() {
pythongosssss's avatar
pythongosssss committed
214
215
216
217
218
219
220
		const items = await api.getItems(this.#type);
		this.element.replaceChildren(
			...Object.keys(items).flatMap((section) => [
				$el("h4", {
					textContent: section,
				}),
				$el("div.comfy-list-items", [
221
					...(this.#reverse ? items[section].reverse() : items[section]).map((item) => {
pythongosssss's avatar
pythongosssss committed
222
223
224
225
226
						// Allow items to specify a custom remove action (e.g. for interrupt current prompt)
						const removeAction = item.remove || {
							name: "Delete",
							cb: () => api.deleteItem(this.#type, item.prompt[1]),
						};
reaper47's avatar
reaper47 committed
227
						return $el("div", {textContent: item.prompt[0] + ": "}, [
pythongosssss's avatar
pythongosssss committed
228
229
							$el("button", {
								textContent: "Load",
pythongosssss's avatar
pythongosssss committed
230
231
								onclick: async () => {
									await app.loadGraphData(item.prompt[3].extra_pnginfo.workflow);
pythongosssss's avatar
pythongosssss committed
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
									if (item.outputs) {
										app.nodeOutputs = item.outputs;
									}
								},
							}),
							$el("button", {
								textContent: removeAction.name,
								onclick: async () => {
									await removeAction.cb();
									await this.update();
								},
							}),
						]);
					}),
				]),
			]),
			$el("div.comfy-list-actions", [
				$el("button", {
					textContent: "Clear " + this.#text,
					onclick: async () => {
						await api.clearItems(this.#type);
						await this.load();
					},
				}),
reaper47's avatar
reaper47 committed
256
				$el("button", {textContent: "Refresh", onclick: () => this.load()}),
pythongosssss's avatar
pythongosssss committed
257
258
			])
		);
pythongosssss's avatar
pythongosssss committed
259
260
261
262
263
264
265
266
267
268
	}

	async update() {
		if (this.visible) {
			await this.load();
		}
	}

	async show() {
		this.element.style.display = "block";
pythongosssss's avatar
pythongosssss committed
269
270
		this.button.textContent = "Close";

pythongosssss's avatar
pythongosssss committed
271
272
273
274
275
		await this.load();
	}

	hide() {
		this.element.style.display = "none";
comfyanonymous's avatar
comfyanonymous committed
276
		this.button.textContent = "View " + this.#text;
pythongosssss's avatar
pythongosssss committed
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
	}

	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();
294
		this.settings = new ComfySettingsDialog(app);
pythongosssss's avatar
pythongosssss committed
295

m957ymj75urz's avatar
m957ymj75urz committed
296
		this.batchCount = 1;
comfyanonymous's avatar
comfyanonymous committed
297
		this.lastQueueSize = 0;
pythongosssss's avatar
pythongosssss committed
298
		this.queue = new ComfyList("Queue");
299
		this.history = new ComfyList("History", "history", true);
pythongosssss's avatar
pythongosssss committed
300

pythongosssss's avatar
pythongosssss committed
301
302
303
304
		api.addEventListener("status", () => {
			this.queue.update();
			this.history.update();
		});
pythongosssss's avatar
pythongosssss committed
305

306
307
308
309
310
311
		const confirmClear = this.settings.addSetting({
			id: "Comfy.ConfirmClear",
			name: "Require confirmation when clearing workflow",
			type: "boolean",
			defaultValue: true,
		});
312

313
314
315
316
317
318
319
		const promptFilename = this.settings.addSetting({
			id: "Comfy.PromptFilename",
			name: "Prompt for filename when saving workflow",
			type: "boolean",
			defaultValue: true,
		});

320
321
322
		/**
		 * file format for preview
		 *
323
		 * format;quality
324
325
		 *
		 * ex)
326
		 * webp;50 -> webp, quality 50
327
328
329
330
331
332
		 * jpeg;80 -> rgb, jpeg, quality 80
		 *
		 * @type {string}
		 */
		const previewImage = this.settings.addSetting({
			id: "Comfy.PreviewFormat",
reaper47's avatar
reaper47 committed
333
334
			name: "When displaying a preview in the image widget, convert it to a lightweight image, e.g. webp, jpeg, webp;50, etc.",
			type: "text",
335
336
337
			defaultValue: "",
		});

338
339
340
341
342
343
344
		this.settings.addSetting({
			id: "Comfy.DisableSliders",
			name: "Disable sliders.",
			type: "boolean",
			defaultValue: false,
		});

345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
		this.settings.addSetting({
			id: "Comfy.DisableFloatRounding",
			name: "Disable rounding floats (requires page reload).",
			type: "boolean",
			defaultValue: false,
		});

		this.settings.addSetting({
			id: "Comfy.FloatRoundingPrecision",
			name: "Decimal places [0 = auto] (requires page reload).",
			type: "slider",
			attrs: {
				min: 0,
				max: 6,
				step: 1,
			},
			defaultValue: 0,
		});

pythongosssss's avatar
pythongosssss committed
364
		const fileInput = $el("input", {
365
			id: "comfy-file-input",
pythongosssss's avatar
pythongosssss committed
366
			type: "file",
367
			accept: ".json,image/png,.latent,.safetensors,image/webp",
reaper47's avatar
reaper47 committed
368
			style: {display: "none"},
pythongosssss's avatar
pythongosssss committed
369
370
371
372
			parent: document.body,
			onchange: () => {
				app.handleFile(fileInput.files[0]);
			},
pythongosssss's avatar
pythongosssss committed
373
		});
pythongosssss's avatar
pythongosssss committed
374

pythongosssss's avatar
pythongosssss committed
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
		const autoQueueModeEl = toggleSwitch(
			"autoQueueMode",
			[
				{ text: "instant", tooltip: "A new prompt will be queued as soon as the queue reaches 0" },
				{ text: "change", tooltip: "A new prompt will be queued when the queue is at 0 and the graph is/has changed" },
			],
			{
				onChange: (value) => {
					this.autoQueueMode = value.item.value;
				},
			}
		);
		autoQueueModeEl.style.display = "none";

		api.addEventListener("graphChanged", () => {
390
			if (this.autoQueueMode === "change" && this.autoQueueEnabled === true) {
pythongosssss's avatar
pythongosssss committed
391
392
393
394
395
396
397
398
399
				if (this.lastQueueSize === 0) {
					this.graphHasChanged = false;
					app.queuePrompt(0, this.batchCount);
				} else {
					this.graphHasChanged = true;
				}
			}
		});

pythongosssss's avatar
pythongosssss committed
400
401
402
403
404
405
406
407
408
409
410
411
412
413
		this.menuHamburger = $el(
			"div.comfy-menu-hamburger",
			{
				parent: document.body,
				onclick: () => {
					this.menuContainer.style.display = "block";
					this.menuHamburger.style.display = "none";
				},
			},
			[$el("div"), $el("div"), $el("div")]
		);

		this.menuContainer = $el("div.comfy-menu", { parent: document.body }, [
			$el("div.drag-handle.comfy-menu-header", {
reaper47's avatar
reaper47 committed
414
415
416
417
418
419
				style: {
					overflow: "hidden",
					position: "relative",
					width: "100%",
					cursor: "default"
				}
pythongosssss's avatar
pythongosssss committed
420
			}, 	[
Jairo Correa's avatar
Jairo Correa committed
421
				$el("span.drag-handle"),
pythongosssss's avatar
pythongosssss committed
422
423
424
425
426
427
428
429
430
431
432
433
434
435
				$el("span.comfy-menu-queue-size", { $: (q) => (this.queueSize = q) }),
				$el("div.comfy-menu-actions", [
					$el("button.comfy-settings-btn", {
						textContent: "⚙️",
						onclick: () => this.settings.show(),
					}),
					$el("button.comfy-close-menu-btn", {
						textContent: "\u00d7",
						onclick: () => {
							this.menuContainer.style.display = "none";
							this.menuHamburger.style.display = "flex";
						},
					}),
				]),
pythongosssss's avatar
pythongosssss committed
436
			]),
437
			$el("button.comfy-queue-btn", {
438
				id: "queue-button",
439
440
441
				textContent: "Queue Prompt",
				onclick: () => app.queuePrompt(0, this.batchCount),
			}),
m957ymj75urz's avatar
m957ymj75urz committed
442
			$el("div", {}, [
reaper47's avatar
reaper47 committed
443
				$el("label", {innerHTML: "Extra options"}, [
444
445
446
447
448
449
					$el("input", {
						type: "checkbox",
						onchange: (i) => {
							document.getElementById("extraOptions").style.display = i.srcElement.checked ? "block" : "none";
							this.batchCount = i.srcElement.checked ? document.getElementById("batchCountInputRange").value : 1;
							document.getElementById("autoQueueCheckbox").checked = false;
pythongosssss's avatar
pythongosssss committed
450
							this.autoQueueEnabled = false;
451
452
453
						},
					}),
				]),
m957ymj75urz's avatar
m957ymj75urz committed
454
			]),
reaper47's avatar
reaper47 committed
455
			$el("div", {id: "extraOptions", style: {width: "100%", display: "none"}}, [
456
457
458
				$el("div",[

					$el("label", {innerHTML: "Batch count"}),
459
460
461
462
463
					$el("input", {
						id: "batchCountInputNumber",
						type: "number",
						value: this.batchCount,
						min: "1",
reaper47's avatar
reaper47 committed
464
						style: {width: "35%", "margin-left": "0.4em"},
465
						oninput: (i) => {
m957ymj75urz's avatar
m957ymj75urz committed
466
							this.batchCount = i.target.value;
467
468
							document.getElementById("batchCountInputRange").value = this.batchCount;
						},
m957ymj75urz's avatar
m957ymj75urz committed
469
					}),
470
471
472
473
474
475
					$el("input", {
						id: "batchCountInputRange",
						type: "range",
						min: "1",
						max: "100",
						value: this.batchCount,
m957ymj75urz's avatar
m957ymj75urz committed
476
477
						oninput: (i) => {
							this.batchCount = i.srcElement.value;
478
479
							document.getElementById("batchCountInputNumber").value = i.srcElement.value;
						},
480
481
482
483
484
485
					}),		
				]),
				$el("div",[
					$el("label",{
						for:"autoQueueCheckbox",
						innerHTML: "Auto Queue"
486
487
488
489
490
					}),
					$el("input", {
						id: "autoQueueCheckbox",
						type: "checkbox",
						checked: false,
491
						title: "Automatically queue prompt when the queue size hits 0",
pythongosssss's avatar
pythongosssss committed
492
493
494
495
						onchange: (e) => {
							this.autoQueueEnabled = e.target.checked;
							autoQueueModeEl.style.display = this.autoQueueEnabled ? "" : "none";
						}
m957ymj75urz's avatar
m957ymj75urz committed
496
					}),
pythongosssss's avatar
pythongosssss committed
497
					autoQueueModeEl
498
				])
m957ymj75urz's avatar
m957ymj75urz committed
499
			]),
pythongosssss's avatar
pythongosssss committed
500
			$el("div.comfy-menu-btns", [
reaper47's avatar
reaper47 committed
501
502
503
504
505
				$el("button", {
					id: "queue-front-button",
					textContent: "Queue Front",
					onclick: () => app.queuePrompt(-1, this.batchCount)
				}),
pythongosssss's avatar
pythongosssss committed
506
507
				$el("button", {
					$: (b) => (this.queue.button = b),
508
					id: "comfy-view-queue-button",
pythongosssss's avatar
pythongosssss committed
509
510
511
512
513
514
515
516
					textContent: "View Queue",
					onclick: () => {
						this.history.hide();
						this.queue.toggle();
					},
				}),
				$el("button", {
					$: (b) => (this.history.button = b),
517
					id: "comfy-view-history-button",
pythongosssss's avatar
pythongosssss committed
518
519
520
521
522
523
524
525
526
527
					textContent: "View History",
					onclick: () => {
						this.queue.hide();
						this.history.toggle();
					},
				}),
			]),
			this.queue.element,
			this.history.element,
			$el("button", {
528
				id: "comfy-save-button",
pythongosssss's avatar
pythongosssss committed
529
530
				textContent: "Save",
				onclick: () => {
531
532
533
534
535
536
537
538
					let filename = "workflow.json";
					if (promptFilename.value) {
						filename = prompt("Save workflow as:", filename);
						if (!filename) return;
						if (!filename.toLowerCase().endsWith(".json")) {
							filename += ".json";
						}
					}
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
					app.graphToPrompt().then(p=>{
						const json = JSON.stringify(p.workflow, null, 2); // convert the data to a JSON string
						const blob = new Blob([json], {type: "application/json"});
						const url = URL.createObjectURL(blob);
						const a = $el("a", {
							href: url,
							download: filename,
							style: {display: "none"},
							parent: document.body,
						});
						a.click();
						setTimeout(function () {
							a.remove();
							window.URL.revokeObjectURL(url);
						}, 0);
pythongosssss's avatar
pythongosssss committed
554
555
556
					});
				},
			}),
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
			$el("button", {
				id: "comfy-dev-save-api-button",
				textContent: "Save (API Format)",
				style: {width: "100%", display: "none"},
				onclick: () => {
					let filename = "workflow_api.json";
					if (promptFilename.value) {
						filename = prompt("Save workflow (API) as:", filename);
						if (!filename) return;
						if (!filename.toLowerCase().endsWith(".json")) {
							filename += ".json";
						}
					}
					app.graphToPrompt().then(p=>{
						const json = JSON.stringify(p.output, null, 2); // convert the data to a JSON string
						const blob = new Blob([json], {type: "application/json"});
						const url = URL.createObjectURL(blob);
						const a = $el("a", {
							href: url,
							download: filename,
							style: {display: "none"},
							parent: document.body,
						});
						a.click();
						setTimeout(function () {
							a.remove();
							window.URL.revokeObjectURL(url);
						}, 0);
					});
				},
			}),
reaper47's avatar
reaper47 committed
588
589
590
591
592
593
594
595
596
597
598
599
			$el("button", {id: "comfy-load-button", textContent: "Load", onclick: () => fileInput.click()}),
			$el("button", {
				id: "comfy-refresh-button",
				textContent: "Refresh",
				onclick: () => app.refreshComboInNodes()
			}),
			$el("button", {id: "comfy-clipspace-button", textContent: "Clipspace", onclick: () => app.openClipspace()}),
			$el("button", {
				id: "comfy-clear-button", textContent: "Clear", onclick: () => {
					if (!confirmClear.value || confirm("Clear workflow?")) {
						app.clean();
						app.graph.clear();
600
						app.resetView();
reaper47's avatar
reaper47 committed
601
					}
602
				}
reaper47's avatar
reaper47 committed
603
604
			}),
			$el("button", {
pythongosssss's avatar
pythongosssss committed
605
				id: "comfy-load-default-button", textContent: "Load Default", onclick: async () => {
reaper47's avatar
reaper47 committed
606
					if (!confirmClear.value || confirm("Load default workflow?")) {
607
						app.resetView();
pythongosssss's avatar
pythongosssss committed
608
						await app.loadGraphData()
reaper47's avatar
reaper47 committed
609
					}
610
				}
reaper47's avatar
reaper47 committed
611
			}),
612
613
614
615
616
			$el("button", {
				id: "comfy-reset-view-button", textContent: "Reset View", onclick: async () => {
					app.resetView();
				}
			}),
pythongosssss's avatar
pythongosssss committed
617
		]);
pythongosssss's avatar
pythongosssss committed
618

619
620
621
622
623
624
625
626
		const devMode = this.settings.addSetting({
			id: "Comfy.DevMode",
			name: "Enable Dev mode Options",
			type: "boolean",
			defaultValue: false,
			onChange: function(value) { document.getElementById("comfy-dev-save-api-button").style.display = value ? "block" : "none"},
		});

627
		dragElement(this.menuContainer, this.settings);
Jairo Correa's avatar
Jairo Correa committed
628

reaper47's avatar
reaper47 committed
629
		this.setStatus({exec_info: {queue_remaining: "X"}});
pythongosssss's avatar
pythongosssss committed
630
631
632
633
	}

	setStatus(status) {
		this.queueSize.textContent = "Queue size: " + (status ? status.exec_info.queue_remaining : "ERR");
comfyanonymous's avatar
comfyanonymous committed
634
		if (status) {
635
636
637
			if (
				this.lastQueueSize != 0 &&
				status.exec_info.queue_remaining == 0 &&
pythongosssss's avatar
pythongosssss committed
638
639
640
				this.autoQueueEnabled &&
				(this.autoQueueMode === "instant" || this.graphHasChanged) &&
				!app.lastExecutionError
641
			) {
comfyanonymous's avatar
comfyanonymous committed
642
				app.queuePrompt(0, this.batchCount);
pythongosssss's avatar
pythongosssss committed
643
644
				status.exec_info.queue_remaining += this.batchCount;
				this.graphHasChanged = false;
comfyanonymous's avatar
comfyanonymous committed
645
			}
646
			this.lastQueueSize = status.exec_info.queue_remaining;
comfyanonymous's avatar
comfyanonymous committed
647
		}
pythongosssss's avatar
pythongosssss committed
648
649
	}
}