diff --git a/__init__.py b/__init__.py
index 9fb84db..a6d0368 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,14 +1,14 @@
-from .api.dependency_installer import *
-from .elegant_resource_monitor import ElegantResourceMonitor
from .api.http_server import *
+from .elegant_resource_monitor import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+import random
-NODE_CLASS_MAPPINGS = {
- "ElegantResourceMonitor": ElegantResourceMonitor,
-}
+tech_rambling = [
+ "Beepity beep beep!", "Clickity-clackity!", "Algorithm: Activated!", "Nerd mode: OFF [ON]",
+ "Geeks unite!", "Data crunch time!", "Reboot-a-rama!", "Quantum leap engaged!"
+]
-NODE_DISPLAY_NAME_MAPPINGS = {
- "ElegantResourceMonitor": "📈 Resource Monitor",
-}
+print(f"\033[1;34m[CCTech Suite]: 🤖🤖🤖 \033[96m\033[3m{random.choice(tech_rambling)}\033[0m 🤖🤖🤖")
+print(f"\033[1;34m[CCTech Suite]:\033[0m Activated \033[96m{len(NODE_CLASS_MAPPINGS)}\033[0m server nodes.")
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
diff --git a/elegant_resource_monitor.py b/elegant_resource_monitor.py
index 09206d3..a7bdd2f 100644
--- a/elegant_resource_monitor.py
+++ b/elegant_resource_monitor.py
@@ -7,17 +7,14 @@ class ElegantResourceMonitor():
@classmethod
def INPUT_TYPES(s):
- return {
- "required": {
- "directory_path": ("STRING", {"default": ""}),
- },
- }
+ return { }
- RETURN_TYPES = ("INT", "FLOAT", "STRING",)
+ RETURN_TYPES = ()
+ RETURN_NAMES = ()
FUNCTION = "run_resource_monitor"
- CATEGORY = "CCTech/Utilities"
+ CATEGORY = "🤖 CCTech/Utilities"
def run_resource_monitor(self):
current_time = time.time()
@@ -25,9 +22,9 @@ class ElegantResourceMonitor():
NODE_CLASS_MAPPINGS = {
- "ElegantResourceMonitor": ElegantResourceMonitor,
+ "Resource Monitor": ElegantResourceMonitor,
}
NODE_DISPLAY_NAME_MAPPINGS = {
- "ElegantResourceMonitor": "📈 Resource Monitor",
+ "Resource Monitor": "Resource Monitor 📈",
}
diff --git a/pyproject.toml b/pyproject.toml
index 7cdb5e7..a416768 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "comfyui-elegant-resource-monitor"
description = "This custom node for ComfyUI will add a simple and elegant resource monitor."
-version = "1.0.8"
+version = "1.0.6"
license = {file = "LICENSE"}
[project.urls]
diff --git a/web/assets/js/dependencies.js b/web/assets/js/dependencies.js
index 4ed8961..72cd4d2 100644
--- a/web/assets/js/dependencies.js
+++ b/web/assets/js/dependencies.js
@@ -4,7 +4,6 @@ styles.property = 'stylesheet'
styles.rel = 'stylesheet'
styles.onload = async function () {
- console.log('Stylesheet loaded and applied.')
if (localStorage.getItem('lastClass') && localStorage.getItem('lastInactiveClass')) {
var lastClass = JSON.parse(localStorage.getItem('lastClass'))
var lastInactiveClass = JSON.parse(localStorage.getItem('lastInactiveClass'))
diff --git a/web/assets/js/elegant-base-server-node.js b/web/assets/js/elegant-base-server-node.js
new file mode 100644
index 0000000..6ad5c23
--- /dev/null
+++ b/web/assets/js/elegant-base-server-node.js
@@ -0,0 +1,287 @@
+import { ComfyWidgets } from '/scripts/widgets.js'
+import { app } from '/scripts/app.js'
+export class ElegantResourceMonitorBaseNode extends LGraphNode {
+ constructor(title = ElegantResourceMonitorBaseNode.title, skipOnConstructedCall = true) {
+ super(title)
+ this.comfyClass = '__NEED_COMFY_CLASS__'
+ this.nickname = 'ElegantResourceMonitor'
+ this.isVirtualNode = false
+ this.isDropEnabled = false
+ this.removed = false
+ this.configuring = false
+ this._tempWidth = 0
+ this.__constructed__ = false
+ this.helpDialog = null
+ if (title == '__NEED_CLASS_TITLE__') {
+ throw new Error('ElegantResourceMonitorBaseNode needs overrides.')
+ }
+ this.widgets = this.widgets || []
+ this.properties = this.properties || {}
+ setTimeout(() => {
+ if (this.comfyClass == '__NEED_COMFY_CLASS__') {
+ throw new Error('ElegantResourceMonitorBaseNode needs a comfy class override.')
+ }
+ this.checkAndRunOnConstructed()
+ })
+ }
+ checkAndRunOnConstructed() {
+ var _a
+ if (!this.__constructed__) {
+ this.onConstructed()
+ console.log(
+ `[ElegantResourceMonitorBaseNode] Child class did not call onConstructed for "${this.type}.`,
+ )
+ }
+ return this.__constructed__
+ }
+ onDragOver(e) {
+ if (!this.isDropEnabled) return false
+ return importIndividualNodesInnerOnDragOver(this, e)
+ }
+ async onDragDrop(e) {
+ if (!this.isDropEnabled) return false
+ return importIndividualNodesInnerOnDragDrop(this, e)
+ }
+ async invokeExtensionsAsync(method, ...args) {
+ return Promise.resolve()
+ }
+ onConstructed() {
+ var _a
+ if (this.__constructed__) return false
+ this.type = (_a = this.type) !== null && _a !== void 0 ? _a : undefined
+ this.__constructed__ = true
+ this.invokeExtensionsAsync('nodeCreated', this)
+ return this.__constructed__
+ }
+ configure(info) {
+ this.configuring = true
+ super.configure(info)
+ for (const w of this.widgets || []) {
+ w.last_y = w.last_y || 0
+ }
+ this.configuring = false
+ }
+ clone() {
+ const cloned = super.clone()
+ if (cloned.properties && !!window.structuredClone) {
+ cloned.properties = structuredClone(cloned.properties)
+ }
+ return cloned
+ }
+ set mode(mode) {
+ if (this.mode_ != mode) {
+ const oldMode = this.mode_
+ this.mode_ = mode
+ this.onModeChange(oldMode, mode)
+ }
+ }
+ get mode() {
+ return this.mode_
+ }
+ onModeChange(from, to) {}
+ async handleAction(action) {
+ action
+ }
+ removeWidget(widgetOrSlot) {
+ if (typeof widgetOrSlot === 'number') {
+ this.widgets.splice(widgetOrSlot, 1)
+ } else if (widgetOrSlot) {
+ const index = this.widgets.indexOf(widgetOrSlot)
+ if (index > -1) {
+ this.widgets.splice(index, 1)
+ }
+ }
+ }
+ defaultGetSlotMenuOptions(slot) {
+ var _a, _b
+ const menu_info = []
+ if (
+ (_b =
+ (_a = slot === null || slot === void 0 ? void 0 : slot.output) === null || _a === void 0
+ ? void 0
+ : _a.links) === null || _b === void 0
+ ? void 0
+ : _b.length
+ ) {
+ menu_info.push({ content: 'Disconnect Links', slot: slot })
+ }
+ let inputOrOutput = slot.input || slot.output
+ if (inputOrOutput) {
+ if (inputOrOutput.removable) {
+ menu_info.push(
+ inputOrOutput.locked ? { content: 'Cannot remove' } : { content: 'Remove Slot', slot },
+ )
+ }
+ if (!inputOrOutput.nameLocked) {
+ menu_info.push({ content: 'Rename Slot', slot })
+ }
+ }
+ return menu_info
+ }
+ onRemoved() {
+ var _a
+ ;(_a = super.onRemoved) === null || _a === void 0 ? void 0 : _a.call(this)
+ this.removed = true
+ }
+ static setUp(...args) {}
+ getHelp() {
+ return ''
+ }
+ showHelp() {
+ const help = this.getHelp() || this.constructor.help
+ if (help) {
+ this.helpDialog = new ElegantResourceMonitorHelpDialog(this, help).show()
+ this.helpDialog.addEventListener('close', (e) => {
+ this.helpDialog = null
+ })
+ }
+ }
+ onKeyDown(event) {
+ KEY_EVENT_SERVICE.handleKeyDownOrUp(event)
+ if (event.key == '?' && !this.helpDialog) {
+ this.showHelp()
+ }
+ }
+ onKeyUp(event) {
+ KEY_EVENT_SERVICE.handleKeyDownOrUp(event)
+ }
+ getExtraMenuOptions(canvas, options) {
+ var _a, _b, _c, _d, _e, _f
+ if (super.getExtraMenuOptions) {
+ ;(_a = super.getExtraMenuOptions) === null || _a === void 0
+ ? void 0
+ : _a.apply(this, [canvas, options])
+ } else if (
+ (_c = (_b = this.constructor.nodeType) === null || _b === void 0 ? void 0 : _b.prototype) ===
+ null || _c === void 0
+ ? void 0
+ : _c.getExtraMenuOptions
+ ) {
+ ;(_f =
+ (_e =
+ (_d = this.constructor.nodeType) === null || _d === void 0 ? void 0 : _d.prototype) ===
+ null || _e === void 0
+ ? void 0
+ : _e.getExtraMenuOptions) === null || _f === void 0
+ ? void 0
+ : _f.apply(this, [canvas, options])
+ }
+ const help = this.getHelp() || this.constructor.help
+ if (help) {
+ addHelpMenuItem(this, help, options)
+ }
+ }
+}
+
+export class ElegantResourceMonitorBaseServerNode extends ElegantResourceMonitorBaseNode {
+ constructor(title) {
+ super(title, true)
+ this.isDropEnabled = true
+ this.serialize_widgets = true
+ this.setupFromServerNodeData()
+ this.onConstructed()
+ }
+ getWidgets() {
+ return ComfyWidgets
+ }
+ async setupFromServerNodeData() {
+ var _a, _b, _c
+ const nodeData = this.constructor.nodeData
+ if (!nodeData) {
+ throw Error('No node data')
+ }
+ this.comfyClass = nodeData.name
+ let inputs = nodeData['input']['required']
+ if (nodeData['input']['optional'] != undefined) {
+ inputs = Object.assign({}, inputs, nodeData['input']['optional'])
+ }
+ const WIDGETS = this.getWidgets()
+ const config = {
+ minWidth: 1,
+ minHeight: 1,
+ widget: null,
+ }
+ for (const inputName in inputs) {
+ const inputData = inputs[inputName]
+ const type = inputData[0]
+ if ((_a = inputData[1]) === null || _a === void 0 ? void 0 : _a.forceInput) {
+ this.addInput(inputName, type)
+ } else {
+ let widgetCreated = true
+ if (Array.isArray(type)) {
+ Object.assign(config, WIDGETS.COMBO(this, inputName, inputData, app) || {})
+ } else if (`${type}:${inputName}` in WIDGETS) {
+ Object.assign(
+ config,
+ WIDGETS[`${type}:${inputName}`](this, inputName, inputData, app) || {},
+ )
+ } else if (type in WIDGETS) {
+ Object.assign(config, WIDGETS[type](this, inputName, inputData, app) || {})
+ } else {
+ this.addInput(inputName, type)
+ widgetCreated = false
+ }
+ if (
+ widgetCreated &&
+ ((_b = inputData[1]) === null || _b === void 0 ? void 0 : _b.forceInput) &&
+ (config === null || config === void 0 ? void 0 : config.widget)
+ ) {
+ if (!config.widget.options) config.widget.options = {}
+ config.widget.options.forceInput = inputData[1].forceInput
+ }
+ if (
+ widgetCreated &&
+ ((_c = inputData[1]) === null || _c === void 0 ? void 0 : _c.defaultInput) &&
+ (config === null || config === void 0 ? void 0 : config.widget)
+ ) {
+ if (!config.widget.options) config.widget.options = {}
+ config.widget.options.defaultInput = inputData[1].defaultInput
+ }
+ }
+ }
+ for (const o in nodeData['output']) {
+ let output = nodeData['output'][o]
+ if (output instanceof Array) output = 'COMBO'
+ const outputName = nodeData['output_name'][o] || output
+ const outputShape = nodeData['output_is_list'][o]
+ ? LiteGraph.GRID_SHAPE
+ : LiteGraph.CIRCLE_SHAPE
+ this.addOutput(outputName, output, { shape: outputShape })
+ }
+ const s = this.computeSize()
+ s[0] = Math.max(config.minWidth, s[0] * 1.5)
+ s[1] = Math.max(config.minHeight, s[1])
+ this.size = s
+ this.serialize_widgets = true
+ }
+ static registerForOverride(comfyClass, nodeData, ElegantResourceMonitorClass) {
+ if (OVERRIDDEN_SERVER_NODES.has(comfyClass)) {
+ throw Error(
+ `Already have a class to override ${
+ comfyClass.type || comfyClass.name || comfyClass.title
+ }`,
+ )
+ }
+ OVERRIDDEN_SERVER_NODES.set(comfyClass, ElegantResourceMonitorClass)
+ if (!ElegantResourceMonitorClass.__registeredForOverride__) {
+ ElegantResourceMonitorClass.__registeredForOverride__ = true
+ ElegantResourceMonitorClass.nodeType = comfyClass
+ ElegantResourceMonitorClass.nodeData = nodeData
+ ElegantResourceMonitorClass.onRegisteredForOverride(comfyClass, ElegantResourceMonitorClass)
+ }
+ }
+ static onRegisteredForOverride(comfyClass, ElegantResourceMonitorClass) {}
+}
+ElegantResourceMonitorBaseServerNode.nodeData = null
+ElegantResourceMonitorBaseServerNode.nodeType = null
+ElegantResourceMonitorBaseServerNode.__registeredForOverride__ = false
+const OVERRIDDEN_SERVER_NODES = new Map()
+const oldregisterNodeType = LiteGraph.registerNodeType
+LiteGraph.registerNodeType = async function (nodeId, baseClass) {
+ var _a
+ const clazz = OVERRIDDEN_SERVER_NODES.get(baseClass) || baseClass
+ if (clazz !== baseClass) {
+ const classLabel = clazz.type || clazz.name || clazz.title
+ }
+ return oldregisterNodeType.call(LiteGraph, nodeId, clazz)
+}
diff --git a/web/assets/js/elegant-resource-monitor-node.js b/web/assets/js/elegant-resource-monitor-node.js
new file mode 100644
index 0000000..bf60b39
--- /dev/null
+++ b/web/assets/js/elegant-resource-monitor-node.js
@@ -0,0 +1,208 @@
+import { app } from '/scripts/app.js'
+import { ComfyWidgets } from '/scripts/widgets.js'
+import { ElegantResourceMonitorBaseServerNode } from './elegant-base-server-node.js'
+import { elegant } from '../../comfyui/elegant.js'
+const LAST_SEED_BUTTON_LABEL = '♻️ (Use Last Queued Seed)'
+const SPECIAL_SEED_RANDOM = -1
+const SPECIAL_SEED_INCREMENT = -2
+const SPECIAL_SEED_DECREMENT = -3
+const SPECIAL_SEEDS = [SPECIAL_SEED_RANDOM, SPECIAL_SEED_INCREMENT, SPECIAL_SEED_DECREMENT]
+class ElegantResourceMonitor extends ElegantResourceMonitorBaseServerNode {
+ constructor(title = ElegantResourceMonitor.title) {
+ super(title)
+ this.title = 'ElegantResourceMonitor'
+ this.serialize_widgets = true
+ this.lastSeed = undefined
+ this.serializedCtx = {}
+ this.lastSeedValue = null
+ this.randMax = 1125899906842624
+ this.randMin = 0
+ this.randomRange = 1125899906842624
+ this.handleApiHijackingBound = this.handleApiHijacking.bind(this)
+ elegant.addEventListener('comfy-api-queue-prompt-before', this.handleApiHijackingBound)
+ }
+ onRemoved() {
+ elegant.addEventListener('comfy-api-queue-prompt-before', this.handleApiHijackingBound)
+ }
+ configure(info) {
+ var _a
+ super.configure(info)
+ if ((_a = this.properties) === null || _a === void 0 ? void 0 : _a['showLastSeed']) {
+ this.addLastSeedValue()
+ }
+ }
+ async handleAction(action) {
+ if (action === 'Randomize Each Time') {
+ this.seedWidget.value = SPECIAL_SEED_RANDOM
+ } else if (action === 'Use Last Queued Seed') {
+ this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value
+ this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL
+ this.lastSeedButton.disabled = true
+ }
+ }
+ onNodeCreated() {
+ var _a
+ ;(_a = super.onNodeCreated) === null || _a === void 0 ? void 0 : _a.call(this)
+
+ for (const [i, w] of this.widgets.entries()) {
+ }
+ this.addWidget(
+ 'button',
+ '🎲 Randomize Each Time',
+ null,
+ () => {
+ console.log('random')
+ },
+ { serialize: false },
+ )
+ this.addWidget(
+ 'button',
+ '🎲 New Fixed Random',
+ null,
+ () => {
+ console.log('fixed')
+ },
+ { serialize: false },
+ )
+ this.lastSeedButton = this.addWidget(
+ 'button',
+ '🎲 Last Seed',
+ null,
+ () => {
+ console.log('last seed')
+ },
+ { width: 50, serialize: false },
+ )
+ this.lastSeedButton.disabled = true
+ }
+ getExtraMenuOptions(canvas, options) {
+ var _a
+ ;(_a = super.getExtraMenuOptions) === null || _a === void 0
+ ? void 0
+ : _a.apply(this, [...arguments])
+ options.splice(options.length - 1, 0, {
+ content: 'Show/Hide Last Seed Value',
+ callback: (_value, _options, _event, _parentMenu, _node) => {
+ this.properties['showLastSeed'] = !this.properties['showLastSeed']
+ if (this.properties['showLastSeed']) {
+ this.addLastSeedValue()
+ } else {
+ this.removeLastSeedValue()
+ }
+ },
+ })
+ }
+ addLastSeedValue() {
+ if (this.lastSeedValue) return
+ this.lastSeedValue = ComfyWidgets['STRING'](
+ this,
+ 'last_seed',
+ ['STRING', { multiline: true }],
+ app,
+ ).widget
+ this.lastSeedValue.inputEl.readOnly = true
+ this.lastSeedValue.inputEl.style.fontSize = '0.75rem'
+ this.lastSeedValue.inputEl.style.textAlign = 'center'
+ this.computeSize()
+ }
+ removeLastSeedValue() {
+ if (!this.lastSeedValue) return
+ this.lastSeedValue.inputEl.remove()
+ this.widgets.splice(this.widgets.indexOf(this.lastSeedValue), 1)
+ this.lastSeedValue = null
+ this.computeSize()
+ }
+ handleApiHijacking(e) {
+ var _a, _b, _c, _d
+ if (this.mode === LiteGraph.NEVER || this.mode === 4) {
+ return
+ }
+ const workflow = e.detail.workflow
+ const output = e.detail.output
+ let workflowNode =
+ (_b =
+ (_a = workflow === null || workflow === void 0 ? void 0 : workflow.nodes) === null ||
+ _a === void 0
+ ? void 0
+ : _a.find((n) => n.id === this.id)) !== null && _b !== void 0
+ ? _b
+ : null
+ let outputInputs =
+ (_c = output === null || output === void 0 ? void 0 : output[this.id]) === null ||
+ _c === void 0
+ ? void 0
+ : _c.inputs
+ if (
+ !workflowNode ||
+ !outputInputs ||
+ outputInputs[this.seedWidget.name || 'seed'] === undefined
+ ) {
+ const [n, v] = this.logger.warnParts(
+ `Node ${this.id} not found in prompt data sent to server. This may be fine if only ` +
+ `queuing part of the workflow. If not, then this could be a bug.`,
+ )
+ ;(_d = console[n]) === null || _d === void 0 ? void 0 : _d.call(console, ...v)
+ return
+ }
+ const seedToUse = this.getSeedToUse()
+ const seedWidgetndex = this.widgets.indexOf(this.seedWidget)
+ workflowNode.widgets_values[seedWidgetndex] = seedToUse
+ outputInputs[this.seedWidget.name || 'seed'] = seedToUse
+ this.lastSeed = seedToUse
+ if (seedToUse != this.seedWidget.value) {
+ this.lastSeedButton.name = `♻️ ${this.lastSeed}`
+ this.lastSeedButton.disabled = false
+ } else {
+ this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL
+ this.lastSeedButton.disabled = true
+ }
+ if (this.lastSeedValue) {
+ this.lastSeedValue.value = `Last Seed: ${this.lastSeed}`
+ }
+ }
+ getSeedToUse() {
+ const inputSeed = this.seedWidget.value
+ let seedToUse = null
+ if (SPECIAL_SEEDS.includes(inputSeed)) {
+ if (typeof this.lastSeed === 'number' && !SPECIAL_SEEDS.includes(this.lastSeed)) {
+ if (inputSeed === SPECIAL_SEED_INCREMENT) {
+ seedToUse = this.lastSeed + 1
+ } else if (inputSeed === SPECIAL_SEED_DECREMENT) {
+ seedToUse = this.lastSeed - 1
+ }
+ }
+ if (seedToUse == null || SPECIAL_SEEDS.includes(seedToUse)) {
+ seedToUse =
+ Math.floor(Math.random() * this.randomRange) *
+ ((this.seedWidget.options.step || 1) / 10) +
+ this.randMin
+ }
+ }
+ return seedToUse !== null && seedToUse !== void 0 ? seedToUse : inputSeed
+ }
+ static setUp(comfyClass, nodeData) {
+ ElegantResourceMonitorBaseServerNode.registerForOverride(
+ comfyClass,
+ nodeData,
+ ElegantResourceMonitor,
+ )
+ }
+ static onRegisteredForOverride(comfyClass, ctxClass) {
+ setTimeout(() => {
+ ElegantResourceMonitor.category = comfyClass.category
+ })
+ }
+}
+
+ElegantResourceMonitor.title = 'Resource Monitor 📈'
+ElegantResourceMonitor.type = 'Resource Monitor'
+ElegantResourceMonitor.comfyClass = 'Resource Monitor'
+ElegantResourceMonitor.exposedActions = ['Randomize Each Time', 'Use Last Queued Seed']
+app.registerExtension({
+ name: 'Resource Monitor',
+ async beforeRegisterNodeDef(nodeType, nodeData) {
+ if (nodeData.name === ElegantResourceMonitor.type) {
+ ElegantResourceMonitor.setUp(nodeType, nodeData)
+ }
+ },
+})
diff --git a/web/assets/js/elegant-resource-monitor-service.js b/web/assets/js/elegant-resource-monitor-service.js
index 798d4ec..a577e96 100644
--- a/web/assets/js/elegant-resource-monitor-service.js
+++ b/web/assets/js/elegant-resource-monitor-service.js
@@ -1,5 +1,5 @@
import { api } from '/scripts/api.js'
-import { getResolver } from './shared_utils.js'
+import { getResolver } from '../../comfyui/shared_utils.js'
export class ElegantResourceMonitorExecution {
constructor(id) {
this.promptApi = null
diff --git a/web/assets/js/elegant-resource-monitor.js b/web/assets/js/elegant-resource-monitor.js
index e3a88ce..23ad58f 100644
--- a/web/assets/js/elegant-resource-monitor.js
+++ b/web/assets/js/elegant-resource-monitor.js
@@ -255,7 +255,6 @@ class ElegantResourceMonitor extends HTMLElement {
if (e === null || e === void 0 ? void 0 : e.detail.queue) {
console.log(`(${e.detail.queue}) Running... in another tab`)
} else {
- console.log('Idle')
}
}
@@ -265,7 +264,6 @@ class ElegantResourceMonitor extends HTMLElement {
connectedCallback() {
if (!this.connected) {
- console.log('Adding event listener to MONITOR_SERVICE')
MONITOR_SERVICE.addEventListener(
'elegant-resource-monitor-update',
this.onProgressUpdateBound,
diff --git a/web/assets/js/script.js b/web/assets/js/script.js
index 51c64ce..e2ab92b 100644
--- a/web/assets/js/script.js
+++ b/web/assets/js/script.js
@@ -406,7 +406,7 @@ setTimeout(() => {
}
function getCoordinates(isOutside) {
- var position = localStorage.getItem('perf-monitor-position') ?? "bottom-right"
+ var position = localStorage.getItem('perf-monitor-position')
if (isOutside) {
var outsidePosition = getNearestPosition()
diff --git a/web/comfyui/elegant.css b/web/comfyui/elegant.css
new file mode 100644
index 0000000..6b58652
--- /dev/null
+++ b/web/comfyui/elegant.css
@@ -0,0 +1,623 @@
+.elegant-top-messages-container {
+ position: fixed;
+ z-index: 9999;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: start;
+}
+
+.elegant-top-messages-container > div {
+ position: relative;
+ height: fit-content;
+ padding: 4px;
+ margin-top: -100px; /* re-set by JS */
+ opacity: 0;
+ transition: all 0.33s ease-in-out;
+ z-index: 3;
+}
+
+.elegant-top-messages-container > div:last-child {
+ z-index: 2;
+}
+
+.elegant-top-messages-container > div:not(.-show) {
+ z-index: 1;
+}
+
+.elegant-top-messages-container > div.-show {
+ opacity: 1;
+ margin-top: 0px !important;
+}
+
+.elegant-top-messages-container > div.-show {
+ opacity: 1;
+ transform: translateY(0%);
+}
+
+.elegant-top-messages-container > div > div {
+ position: relative;
+ background: #353535;
+ color: #fff;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ height: fit-content;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.88);
+ padding: 6px 12px;
+ border-radius: 4px;
+ font-family: Arial, sans-serif;
+ font-size: 14px;
+}
+
+.elegant-top-messages-container > div > div > span {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+}
+
+.elegant-top-messages-container > div > div > span svg {
+ width: 20px;
+ height: auto;
+ margin-right: 8px;
+}
+
+.elegant-top-messages-container > div > div > span svg.icon-checkmark {
+ fill: #2e9720;
+}
+
+.elegant-top-messages-container [type='warn']::before,
+.elegant-top-messages-container [type='success']::before {
+ content: '⚠️';
+ display: inline-block;
+ flex: 0 0 auto;
+ font-size: 18px;
+ margin-right: 4px;
+ line-height: 1;
+}
+
+.elegant-top-messages-container [type='success']::before {
+ content: '🎉';
+}
+
+.elegant-top-messages-container a {
+ cursor: pointer;
+ text-decoration: underline;
+ color: #fc0;
+ margin-left: 4px;
+ display: inline-block;
+ line-height: 1;
+}
+
+.elegant-top-messages-container a:hover {
+ color: #fc0;
+ text-decoration: none;
+}
+
+/* Fix node selector being crazy long b/c of array types. */
+.litegraph.litesearchbox input,
+.litegraph.litesearchbox select {
+ max-width: 250px;
+}
+
+/* There's no reason for this z-index to be so high. It layers on top of things it shouldn't,
+ (like pythongssss' image gallery, the properties panel, etc.) */
+.comfy-multiline-input {
+ z-index: 1 !important;
+}
+
+.comfy-multiline-input:focus {
+ z-index: 2 !important;
+}
+
+.litegraph .dialog {
+ z-index: 3 !important; /* This is set to 1, but goes under the multi-line inputs, so bump it. */
+}
+
+:not(#fakeid) .elegant-button-reset {
+ position: relative;
+ appearance: none;
+ cursor: pointer;
+ border: 0;
+ background: transparent;
+ color: inherit;
+ padding: 0;
+ margin: 0;
+}
+
+:not(#fakeid) .elegant-button {
+ --padding-top: 7px;
+ --padding-bottom: 9px;
+ --padding-x: 16px;
+ position: relative;
+ cursor: pointer;
+ border: 0;
+ border-radius: 0.25rem;
+ background: rgba(0, 0, 0, 0.5);
+ color: white;
+ font-family: system-ui, sans-serif;
+ font-size: 1rem;
+ line-height: 1;
+ white-space: nowrap;
+ text-decoration: none;
+ margin: 0.25rem;
+ box-shadow: 0px 0px 2px rgb(0, 0, 0);
+ background: #212121;
+ transition: all 0.1s ease-in-out;
+ padding: var(--padding-top) var(--padding-x) var(--padding-bottom);
+ display: inline-flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+}
+:not(#fakeid) .elegant-button::before,
+:not(#fakeid) .elegant-button::after {
+ content: '';
+ display: block;
+ position: absolute;
+ border-radius: 0.25rem;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.12), inset -1px -1px 0px rgba(0, 0, 0, 0.75);
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15));
+ mix-blend-mode: screen;
+}
+:not(#fakeid) .elegant-button::after {
+ mix-blend-mode: multiply;
+}
+:not(#fakeid) .elegant-button:hover {
+ background: #303030;
+}
+:not(#fakeid) .elegant-button:active {
+ box-shadow: 0px 0px 0px rgba(0, 0, 0, 0);
+ background: #121212;
+ padding: calc(var(--padding-top) + 1px) calc(var(--padding-x) - 1px)
+ calc(var(--padding-bottom) - 1px) calc(var(--padding-x) + 1px);
+}
+:not(#fakeid) .elegant-button:active::before,
+:not(#fakeid) .elegant-button:active::after {
+ box-shadow: 1px 1px 0px rgba(255, 255, 255, 0.15), inset 1px 1px 0px rgba(0, 0, 0, 0.5),
+ inset 1px 3px 5px rgba(0, 0, 0, 0.33);
+}
+:not(#fakeid) .elegant-button.-blue {
+ background: #346599 !important;
+}
+:not(#fakeid) .elegant-button.-blue:hover {
+ background: #3b77b8 !important;
+}
+:not(#fakeid) .elegant-button.-blue:active {
+ background: #1d5086 !important;
+}
+:not(#fakeid) .elegant-button.-green {
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15)), #14580b;
+}
+:not(#fakeid) .elegant-button.-green:hover {
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15)), #1a6d0f;
+}
+:not(#fakeid) .elegant-button.-green:active {
+ background: linear-gradient(to bottom, rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.06)), #0f3f09;
+}
+:not(#fakeid) .elegant-button[disabled] {
+ box-shadow: none;
+ background: #666 !important;
+ color: #aaa;
+ pointer-events: none;
+}
+:not(#fakeid) .elegant-button[disabled]::before,
+:not(#fakeid) .elegant-button[disabled]::after {
+ display: none;
+}
+
+.elegant-dialog {
+ outline: 0;
+ border: 0;
+ border-radius: 6px;
+ background: #414141;
+ color: #fff;
+ box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.05), inset -1px -1px 0px rgba(0, 0, 0, 0.5),
+ 2px 2px 20px rgb(0, 0, 0);
+ max-width: 800px;
+ box-sizing: border-box;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ font-size: 1rem;
+ padding: 0;
+ max-height: calc(100% - 32px);
+}
+.elegant-dialog *,
+.elegant-dialog *::before,
+.elegant-dialog *::after {
+ box-sizing: inherit;
+}
+
+.elegant-dialog-container > * {
+ padding: 8px 16px;
+}
+.elegant-dialog-container > *:first-child {
+ padding-top: 16px;
+}
+.elegant-dialog-container > *:last-child {
+ padding-bottom: 16px;
+}
+
+.elegant-dialog.-iconed::after {
+ content: '';
+ font-size: 276px;
+ position: absolute;
+ right: 0px;
+ bottom: 0px;
+ opacity: 0.15;
+ display: block;
+ width: 237px;
+ overflow: hidden;
+ height: 186px;
+ line-height: 1;
+ pointer-events: none;
+ z-index: -1;
+}
+
+.elegant-dialog.-iconed.-help::after {
+ content: '🛟';
+}
+
+.elegant-dialog.-iconed.-settings::after {
+ content: '⚙️';
+}
+
+@media (max-width: 832px) {
+ .elegant-dialog {
+ max-width: calc(100% - 32px);
+ }
+}
+.elegant-dialog-container-title {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: start;
+}
+
+.elegant-dialog-container-title > svg:first-child {
+ width: 36px;
+ height: 36px;
+ margin-right: 16px;
+}
+
+.elegant-dialog-container-title h2 {
+ font-size: 1.375rem;
+ margin: 0;
+ font-weight: bold;
+}
+
+.elegant-dialog-container-title h2 small {
+ font-size: 0.8125rem;
+ font-weight: normal;
+ opacity: 0.75;
+}
+
+.elegant-dialog-container-content {
+ overflow: auto;
+ max-height: calc(100vh - 200px); /* Arbitrary height to copensate for margin, title, and footer.*/
+}
+
+.elegant-dialog-container-content p {
+ font-size: 0.8125rem;
+ margin-top: 0;
+}
+
+.elegant-dialog-container-content ul li p {
+ margin-bottom: 4px;
+}
+
+.elegant-dialog-container-content ul li p + p {
+ margin-top: 0.5em;
+}
+
+.elegant-dialog-container-content ul li ul {
+ margin-top: 0.5em;
+ margin-bottom: 1em;
+}
+
+.elegant-dialog-container-content p code {
+ display: inline-block;
+ padding: 2px 4px;
+ margin: 0px 2px;
+ border: 1px solid rgba(255, 255, 255, 0.25);
+ border-radius: 3px;
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.elegant-dialog-container-footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+body.elegant-dialog-open > *:not(.elegant-dialog):not(.elegant-top-messages-container) {
+ filter: blur(5px);
+}
+
+.elegant-menu {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ position: fixed;
+ z-index: 999999;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.08s ease-in-out;
+ color: #dde;
+ background-color: #111;
+ font-size: 12px;
+ box-shadow: 0 0 10px black !important;
+}
+.elegant-menu > li {
+ position: relative;
+ padding: 4px 6px;
+ z-index: 9999;
+ white-space: nowrap;
+}
+.elegant-menu > li[role='button'] {
+ background-color: var(--comfy-menu-bg) !important;
+ color: var(--input-text);
+ cursor: pointer;
+}
+.elegant-menu > li[role='button']:hover {
+ filter: brightness(155%);
+}
+.elegant-menu[state^='measuring'] {
+ display: block;
+ opacity: 0;
+}
+.elegant-menu[state='open'] {
+ display: block;
+ opacity: 1;
+ pointer-events: all;
+}
+
+.elegant-top-menu {
+ box-sizing: border-box;
+ white-space: nowrap;
+ background: var(--content-bg);
+ color: var(--content-fg);
+ display: flex;
+ flex-direction: column;
+}
+.elegant-top-menu * {
+ box-sizing: inherit;
+}
+.elegant-top-menu menu {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+.elegant-top-menu menu > li:not(#fakeid) {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+.elegant-top-menu menu > li:not(#fakeid) > button {
+ cursor: pointer;
+ padding: 8px 12px 8px 8px;
+ width: 100%;
+ text-align: start;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: start;
+}
+.elegant-top-menu menu > li:not(#fakeid) > button:hover {
+ background-color: var(--comfy-input-bg);
+}
+.elegant-top-menu menu > li:not(#fakeid) > button svg {
+ height: 16px;
+ width: auto;
+ margin-inline-end: 0.6em;
+}
+.elegant-top-menu menu > li:not(#fakeid) > button svg.github-star {
+ fill: rgb(227, 179, 65);
+}
+.elegant-top-menu menu > li:not(#fakeid).elegant-message {
+ min-height: 32px;
+}
+.elegant-top-menu menu > li:not(#fakeid).elegant-message > span {
+ padding: 8px 12px;
+ display: block;
+ width: 100%;
+ text-align: center;
+ font-style: italic;
+ font-size: 12px;
+}
+
+.elegant-dialog.-settings {
+ width: 100%;
+}
+
+.elegant-dialog.-settings fieldset {
+ border: 1px solid rgba(255, 255, 255, 0.25);
+ padding: 0 12px 8px;
+ margin-bottom: 16px;
+}
+
+.elegant-dialog.-settings fieldset > legend {
+ margin-left: 8px;
+ padding: 0 8px;
+ opacity: 0.5;
+}
+
+.elegant-dialog.-settings .formrow {
+ display: flex;
+ flex-direction: column;
+}
+
+.elegant-dialog.-settings .formrow + .formrow {
+ border-top: 1px solid rgba(255, 255, 255, 0.25);
+}
+
+.elegant-dialog.-settings .fieldrow {
+ display: flex;
+ flex-direction: row;
+}
+
+.elegant-dialog.-settings .fieldrow > label {
+ flex: 1 1 auto;
+ user-select: none;
+ padding: 8px 12px 12px;
+}
+
+.elegant-dialog.-settings .fieldrow > label span {
+ font-weight: bold;
+}
+
+.elegant-dialog.-settings .fieldrow > label small {
+ display: block;
+ margin-top: 4px;
+ font-size: 0.6875rem;
+ opacity: 0.75;
+ padding-left: 16px;
+}
+
+.elegant-dialog.-settings .fieldrow ~ .fieldrow {
+ font-size: 0.9rem;
+ border-top: 1px dotted rgba(255, 255, 255, 0.25);
+}
+
+.elegant-dialog.-settings .fieldrow ~ .fieldrow label {
+ padding-left: 28px;
+}
+
+.elegant-dialog.-settings .fieldrow:first-child:not(.-checked) ~ .fieldrow {
+ display: none;
+}
+
+.elegant-dialog.-settings .fieldrow:hover {
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.elegant-dialog.-settings .fieldrow ~ .fieldrow span {
+ font-weight: normal;
+}
+
+.elegant-dialog.-settings .fieldrow > .fieldrow-value {
+ display: flex;
+ align-items: center;
+ justify-content: end;
+ flex: 0 0 auto;
+ width: 50%;
+ max-width: 230px;
+}
+
+.elegant-dialog.-settings .fieldrow.-type-boolean > .fieldrow-value {
+ max-width: 64px;
+}
+
+.elegant-dialog.-settings .fieldrow.-type-number input {
+ width: 48px;
+ text-align: right;
+}
+
+.elegant-dialog.-settings .fieldrow input[type='checkbox'] {
+ width: 24px;
+ height: 24px;
+ cursor: pointer;
+}
+
+.elegant-comfyui-settings-row div {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: end;
+}
+
+.elegant-comfyui-settings-row div svg {
+ width: 36px;
+ height: 36px;
+ margin-right: 16px;
+}
+
+.litegraph.litecontextmenu .litemenu-title .elegant-contextmenu-title-elegant-comfy,
+.litegraph.litecontextmenu .litemenu-entry.elegant-contextmenu-item {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: start;
+}
+
+.litegraph.litecontextmenu .litemenu-title .elegant-contextmenu-title-elegant-comfy svg,
+.litegraph.litecontextmenu .litemenu-entry.elegant-contextmenu-item svg {
+ fill: currentColor;
+ width: auto;
+ height: 16px;
+ margin-right: 6px;
+}
+
+.litegraph.litecontextmenu .litemenu-entry.elegant-contextmenu-item svg.github-star {
+ fill: rgb(227, 179, 65);
+}
+
+.litegraph.litecontextmenu .litemenu-title .elegant-contextmenu-title-elegant-comfy,
+.litegraph.litecontextmenu .litemenu-entry.elegant-contextmenu-label {
+ color: #dde;
+ background-color: #212121 !important;
+ margin: 0;
+ padding: 2px;
+ cursor: default;
+ opacity: 1;
+ padding: 4px;
+ font-weight: bold;
+}
+
+.litegraph.litecontextmenu .litemenu-title .elegant-contextmenu-title-elegant-comfy {
+ font-size: 1.1em;
+ color: #fff;
+ background-color: #090909 !important;
+ justify-content: center;
+ padding: 4px 8px;
+}
+
+elegant-progress-bar {
+ display: block;
+ position: relative;
+ z-index: 999;
+ top: 0;
+ left: 0;
+ height: 14px;
+ font-size: 10px;
+ width: 100%;
+ overflow: hidden;
+ box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.25);
+ box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.25), 0px 1px 0px rgba(255, 255, 255, 0.125);
+}
+
+* ~ elegant-progress-bar,
+.comfyui-body-bottom elegant-progress-bar {
+ box-shadow: 0px -1px 0px rgb(0, 0, 0), inset 0px 1px 0px rgba(255, 255, 255, 0.15),
+ inset 0px -1px 0px rgba(0, 0, 0, 0.25), 0px 1px 0px rgba(255, 255, 255, 0.125);
+}
+
+body:not([style*='grid']) elegant-progress-bar {
+ position: fixed;
+ top: 0px;
+ bottom: auto;
+}
+body:not([style*='grid']) elegant-progress-bar.elegant-pos-bottom {
+ top: auto;
+ bottom: 0px;
+}
+
+.elegant-debug-keydowns {
+ display: block;
+ position: fixed;
+ z-index: 999;
+ top: 3px;
+ right: 8px;
+ font-size: 10px;
+ color: #fff;
+ font-family: sans-serif;
+}
diff --git a/web/comfyui/elegant.js b/web/comfyui/elegant.js
new file mode 100644
index 0000000..f29f0c1
--- /dev/null
+++ b/web/comfyui/elegant.js
@@ -0,0 +1,291 @@
+import { app, ComfyApp } from '/scripts/app.js'
+import { api } from '/scripts/api.js'
+import { iconGear, iconNode, iconReplace, iconStarFilled } from './svgs.js'
+import { injectCss } from './shared_utils.js'
+
+class Elegant extends EventTarget {
+ constructor() {
+ var _a, _b, _c, _d
+ super()
+ this.api = api
+ this.settingsDialog = null
+ this.progressBarEl = null
+ this.queueNodeIds = null
+ this.monitorBadLinksAlerted = false
+ this.monitorLinkTimeout = null
+ this.processingQueue = false
+ this.loadingApiJson = false
+ this.replacingReroute = null
+ this.processingMouseDown = false
+ this.processingMouseUp = false
+ this.processingMouseMove = false
+ this.lastAdjustedMouseEvent = null
+ this.canvasCurrentlyCopyingToClipboard = false
+ this.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false
+ this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = null
+ this.elDebugKeydowns = null
+ this.nodes = []
+ this.isMac = !!(
+ ((_a = navigator.platform) === null || _a === void 0
+ ? void 0
+ : _a.toLocaleUpperCase().startsWith('MAC')) ||
+ ((_c = (_b = navigator.userAgentData) === null || _b === void 0 ? void 0 : _b.platform) ===
+ null || _c === void 0
+ ? void 0
+ : _c.toLocaleUpperCase().startsWith('MAC'))
+ )
+
+ this.initializeGraphAndCanvasHooks()
+ // Wrap the method
+ const originalGetNodeDefs = api.getNodeDefs.bind(api)
+
+ api.getNodeDefs = async function (...args) {
+ // Trigger a custom event before calling the original method
+ const event = new CustomEvent('getNodeDefsCalled', {
+ detail: { args },
+ })
+ window.dispatchEvent(event)
+
+ // Call the original method
+ const result = await originalGetNodeDefs(...args)
+
+ // Optionally, you can also dispatch another event after the method completes
+ window.dispatchEvent(
+ new CustomEvent('getNodeDefsCompleted', {
+ detail: { result },
+ }),
+ )
+
+ return result
+ }
+
+ // Add event listeners
+ window.addEventListener('getNodeDefsCalled', (event) => {})
+
+ window.addEventListener('getNodeDefsCompleted', (event) => {
+ this.nodes = Object.values(event.detail.result).filter((n) => n.category.includes('CCTech'))
+ })
+
+ this.initializeContextMenu()
+ this.cssPromise = injectCss('extensions/ComfyUI-Elegant-Resource-Monitor/comfyui/elegant.css')
+ }
+
+ async initializeGraphAndCanvasHooks() {
+ const elegant = this
+ const graphSerialize = LGraph.prototype.serialize
+ LGraph.prototype.serialize = function () {
+ const response = graphSerialize.apply(this, [...arguments])
+ elegant.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = response
+ return response
+ }
+ const processMouseDown = LGraphCanvas.prototype.processMouseDown
+ LGraphCanvas.prototype.processMouseDown = function (e) {
+ elegant.processingMouseDown = true
+ const returnVal = processMouseDown.apply(this, [...arguments])
+ elegant.dispatchCustomEvent('on-process-mouse-down', { originalEvent: e })
+ elegant.processingMouseDown = false
+ return returnVal
+ }
+ const adjustMouseEvent = LGraphCanvas.prototype.adjustMouseEvent
+ LGraphCanvas.prototype.adjustMouseEvent = function (e) {
+ adjustMouseEvent.apply(this, [...arguments])
+ elegant.lastAdjustedMouseEvent = e
+ }
+ const copyToClipboard = LGraphCanvas.prototype.copyToClipboard
+ LGraphCanvas.prototype.copyToClipboard = function (nodes) {
+ elegant.canvasCurrentlyCopyingToClipboard = true
+ elegant.canvasCurrentlyCopyingToClipboardWithMultipleNodes =
+ Object.values(nodes || this.selected_nodes || []).length > 1
+ copyToClipboard.apply(this, [...arguments])
+ elegant.canvasCurrentlyCopyingToClipboard = false
+ elegant.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false
+ }
+ const onGroupAdd = LGraphCanvas.onGroupAdd
+ LGraphCanvas.onGroupAdd = function (...args) {
+ const graph = app.graph
+ onGroupAdd.apply(this, [...args])
+ LGraphCanvas.onShowPropertyEditor(
+ {},
+ null,
+ null,
+ null,
+ graph._groups[graph._groups.length - 1],
+ )
+ }
+ }
+
+ async invokeExtensionsAsync(method, ...args) {
+ return Promise.resolve()
+ }
+ dispatchCustomEvent(event, detail) {
+ if (detail != null) {
+ return this.dispatchEvent(new CustomEvent(event, { detail }))
+ }
+ return this.dispatchEvent(new CustomEvent(event))
+ }
+ async initializeContextMenu() {
+ const that = this
+ setTimeout(async () => {
+ const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions
+ LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
+ let existingOptions = getCanvasMenuOptions.apply(this, [...args])
+ const options = []
+ options.push(null)
+ options.push(null)
+ options.push(null)
+ options.push({
+ content: `🤖 CCTech`,
+ className: 'elegant-contextmenu-item elegant-contextmenu-main-item-elegant-comfy',
+ submenu: {
+ options: that.getElegantContextMenuItems(),
+ },
+ })
+ options.push(null)
+ options.push(null)
+ let idx = null
+ idx =
+ idx ||
+ existingOptions.findIndex((o) => {
+ var _a, _b
+ return (_b =
+ (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0
+ ? void 0
+ : _a.startsWith) === null || _b === void 0
+ ? void 0
+ : _b.call(_a, 'Queue Group')
+ }) + 1
+ idx =
+ idx ||
+ existingOptions.findIndex((o) => {
+ var _a, _b
+ return (_b =
+ (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0
+ ? void 0
+ : _a.startsWith) === null || _b === void 0
+ ? void 0
+ : _b.call(_a, 'Queue Selected')
+ }) + 1
+ idx =
+ idx ||
+ existingOptions.findIndex((o) => {
+ var _a, _b
+ return (_b =
+ (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0
+ ? void 0
+ : _a.startsWith) === null || _b === void 0
+ ? void 0
+ : _b.call(_a, 'Convert to Group')
+ })
+ idx =
+ idx ||
+ existingOptions.findIndex((o) => {
+ var _a, _b
+ return (_b =
+ (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0
+ ? void 0
+ : _a.startsWith) === null || _b === void 0
+ ? void 0
+ : _b.call(_a, 'Arrange (')
+ })
+ idx = idx || existingOptions.findIndex((o) => !o) + 1
+ idx = idx || 3
+ existingOptions.splice(idx, 0, ...options)
+ for (let i = existingOptions.length; i > 0; i--) {
+ if (existingOptions[i] === null && existingOptions[i + 1] === null) {
+ existingOptions.splice(i, 1)
+ }
+ }
+ return existingOptions
+ }
+ }, 1016)
+ }
+ getElegantContextMenuItems() {
+ const [canvas, graph] = [app.canvas, app.graph]
+ const selectedNodes = Object.values(canvas.selected_nodes || {})
+ let rerouteNodes = []
+ if (selectedNodes.length) {
+ rerouteNodes = selectedNodes.filter((n) => n.type === 'Reroute')
+ } else {
+ rerouteNodes = graph._nodes.filter((n) => n.type == 'Reroute')
+ }
+ const rerouteLabel = selectedNodes.length ? 'selected' : 'all'
+ const bookmarkMenuItems = getBookmarks() ?? []
+ return [
+ {
+ content: 'Nodes',
+ disabled: true,
+ className: 'elegant-contextmenu-item elegant-contextmenu-label',
+ },
+ {
+ content: iconNode + 'All',
+ className: 'elegant-contextmenu-item',
+ has_submenu: true,
+ submenu: {
+ options: this.nodes.map((n) => n.display_name),
+ callback: (value, options, event) => {
+ const nodeValue = this.nodes.find((n) => n.display_name.includes(value))
+ const node = LiteGraph.createNode(nodeValue.name)
+ node.pos = [this.lastAdjustedMouseEvent.canvasX, this.lastAdjustedMouseEvent.canvasY]
+ canvas.graph.add(node)
+ canvas.selectNode(node)
+ app.graph.setDirtyCanvas(true, true)
+ },
+ extra: { elegant_doNotNest: true },
+ },
+ },
+ // {
+ // content: 'Actions',
+ // disabled: true,
+ // className: 'elegant-contextmenu-item elegant-contextmenu-label',
+ // },
+ // {
+ // content: iconGear + 'Settings (coming soon)',
+ // disabled: true,
+ // className: 'elegant-contextmenu-item',
+ // callback: (...args) => {
+ // this.settingsDialog.addEventListener('close', (e) => {
+ // this.settingsDialog = null
+ // })
+ // },
+ // },
+ ...bookmarkMenuItems,
+ {
+ content: 'More...',
+ disabled: true,
+ className: 'elegant-contextmenu-item elegant-contextmenu-label',
+ },
+ {
+ content: iconStarFilled + 'Star on Github',
+ className: 'elegant-contextmenu-item elegant-contextmenu-github',
+ callback: (...args) => {
+ window.open('https://github.com/ChrisColeTech/', '_blank')
+ },
+ },
+ ]
+ }
+}
+function getBookmarks() {
+ const graph = app.graph
+ const bookmarks = graph._nodes
+ .filter((n) => n.type === 'NodeTypesString.BOOKMARK')
+ .sort((a, b) => a.title.localeCompare(b.title))
+ .map((n) => ({
+ content: `[${n.shortcutKey}] ${n.title}`,
+ className: 'elegant-contextmenu-item',
+ callback: () => {
+ n.canvasToBookmark()
+ },
+ }))
+ return !bookmarks.length
+ ? []
+ : [
+ {
+ content: '🔖 Bookmarks',
+ disabled: true,
+ className: 'elegant-contextmenu-item elegant-contextmenu-label',
+ },
+ ...bookmarks,
+ ]
+}
+export const elegant = new Elegant()
+window.elegant = elegant
diff --git a/web/comfyui/shared_utils.js b/web/comfyui/shared_utils.js
new file mode 100644
index 0000000..dad89f6
--- /dev/null
+++ b/web/comfyui/shared_utils.js
@@ -0,0 +1,115 @@
+export function getResolver(timeout = 5000) {
+ const resolver = {}
+ resolver.id = generateId(8)
+ resolver.completed = false
+ resolver.resolved = false
+ resolver.rejected = false
+ resolver.promise = new Promise((resolve, reject) => {
+ resolver.reject = () => {
+ resolver.completed = true
+ resolver.rejected = true
+ reject()
+ }
+ resolver.resolve = (data) => {
+ resolver.completed = true
+ resolver.resolved = true
+ resolve(data)
+ }
+ })
+ resolver.timeout = setTimeout(() => {
+ if (!resolver.completed) {
+ resolver.reject()
+ }
+ }, timeout)
+ return resolver
+}
+const DEBOUNCE_FN_TO_PROMISE = new WeakMap()
+export function debounce(fn, ms = 64) {
+ if (!DEBOUNCE_FN_TO_PROMISE.get(fn)) {
+ DEBOUNCE_FN_TO_PROMISE.set(
+ fn,
+ wait(ms).then(() => {
+ DEBOUNCE_FN_TO_PROMISE.delete(fn)
+ fn()
+ }),
+ )
+ }
+ return DEBOUNCE_FN_TO_PROMISE.get(fn)
+}
+export function wait(ms = 16) {
+ if (ms === 16) {
+ return new Promise((resolve) => {
+ requestAnimationFrame(() => {
+ resolve()
+ })
+ })
+ }
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve()
+ }, ms)
+ })
+}
+function dec2hex(dec) {
+ return dec.toString(16).padStart(2, '0')
+}
+export function generateId(length) {
+ const arr = new Uint8Array(length / 2)
+ crypto.getRandomValues(arr)
+ return Array.from(arr, dec2hex).join('')
+}
+export function getObjectValue(obj, objKey, def) {
+ if (!obj || !objKey) return def
+ const keys = objKey.split('.')
+ const key = keys.shift()
+ const found = obj[key]
+ if (keys.length) {
+ return getObjectValue(found, keys.join('.'), def)
+ }
+ return found
+}
+export function setObjectValue(obj, objKey, value, createMissingObjects = true) {
+ if (!obj || !objKey) return obj
+ const keys = objKey.split('.')
+ const key = keys.shift()
+ if (obj[key] === undefined) {
+ if (!createMissingObjects) {
+ return
+ }
+ obj[key] = {}
+ }
+ if (!keys.length) {
+ obj[key] = value
+ } else {
+ if (typeof obj[key] != 'object') {
+ obj[key] = {}
+ }
+ setObjectValue(obj[key], keys.join('.'), value, createMissingObjects)
+ }
+ return obj
+}
+export function moveArrayItem(arr, itemOrFrom, to) {
+ const from = typeof itemOrFrom === 'number' ? itemOrFrom : arr.indexOf(itemOrFrom)
+ arr.splice(to, 0, arr.splice(from, 1)[0])
+}
+export function removeArrayItem(arr, itemOrIndex) {
+ const index = typeof itemOrIndex === 'number' ? itemOrIndex : arr.indexOf(itemOrIndex)
+ arr.splice(index, 1)
+}
+export function injectCss(href) {
+ if (document.querySelector(`link[href^="${href}"]`)) {
+ return Promise.resolve()
+ }
+ return new Promise((resolve) => {
+ const link = document.createElement('link')
+ link.setAttribute('rel', 'stylesheet')
+ link.setAttribute('type', 'text/css')
+ const timeout = setTimeout(resolve, 1000)
+ link.addEventListener('load', (e) => {
+ clearInterval(timeout)
+ resolve()
+ })
+ link.href = href
+ document.head.appendChild(link)
+ })
+}
diff --git a/web/comfyui/svgs.js b/web/comfyui/svgs.js
new file mode 100644
index 0000000..d21c72f
--- /dev/null
+++ b/web/comfyui/svgs.js
@@ -0,0 +1,160 @@
+import { createElement as $el } from './utils_dom.js'
+export const logoElegant = ``
+export const github = ``
+export const iconStarFilled = ``
+export const iconReplace = ``
+export const iconNode = ``
+export const iconGear = ``
+export const checkmark = ``
+export const logoCivitai = ``
+export const iconOutLink = ``
+export const link = ``
+export const pencil = ``
+export const dotdotdot = ``
+export const models = ``
+export const pencilColored = ``
+export const diskColored = ``
+export const folderColored = ``
+export const modelsColored = ``
+export const legoBlocksColored = ``
+export const legoBlockColored = ``
+export const gearColored = ``
+export function $svg(markup, attrs) {
+ if (!markup.match(/^\s*