Merge branch 'main' into dev

This commit is contained in:
Crystian
2024-11-02 14:05:20 +01:00
60 changed files with 4631 additions and 1223 deletions
+3 -3
View File
@@ -58,10 +58,10 @@ module.exports = {
// false positive https://github.com/typescript-eslint/typescript-eslint/issues/2483
'no-shadow': 'off',
'object-curly-newline': ["error", {
"ObjectExpression": { "multiline": true, "minProperties": 1 },
"ObjectPattern": { "multiline": true, "minProperties": 1 },
"ObjectExpression": { "multiline": true, "minProperties": 8 },
"ObjectPattern": { "multiline": true, "minProperties": 8 },
"ImportDeclaration": {"multiline": true, "minProperties": 8 },
"ExportDeclaration": { "multiline": true, "minProperties": 1 }
"ExportDeclaration": { "multiline": true, "minProperties": 8 }
}],
"@typescript-eslint/explicit-function-return-type": 'error',
'@typescript-eslint/triple-slash-reference': 'off',
+3 -4
View File
@@ -9,13 +9,12 @@
<env name="CRYSTOOLS_LOGLEVEL" value="10" />
</envs>
<option name="SDK_HOME" value="" />
<option name="SDK_NAME" value="Python 3.11 (.env)" />
<option name="WORKING_DIRECTORY" value="E:\AI\SD\comfyUI\ComfyUI" />
<option name="IS_MODULE_SDK" value="false" />
<option name="WORKING_DIRECTORY" value="X:\comfy\ComfyUI" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
<option name="PARAMETERS" value="--windows-standalone-build --extra-model-paths-config ..\extra_model_paths.yaml --output-directory ..\..\comfyUI.output --input-directory ..\..\comfyUI.input --disable-auto-launch" />
<option name="PARAMETERS" value="--extra-model-paths-config ..\extra_model_paths.yaml --output-directory ..\..\comfyUI.output --input-directory ..\..\comfyUI.input --disable-auto-launch --preview-size 1024 --front-end-version aComfy-Org/ComfyUI_frontend@latest" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# REMEMBER TO COPY THIS FILE INTO .git/hooks/pre-commit
cd ../comfyUI/ComfyUI
cd ../comfy/ComfyUI
rm ./custom_nodes/ComfyUI-Crystools/web/*.js -f
LOCAL_COMMAND="npm --prefix ./custom_nodes/ComfyUI-Crystools run validate"
+2 -2
View File
@@ -1,11 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="typescript" type="js.build_tools.npm">
<package-json value="$PROJECT_DIR$/custom_nodes/ComfyUI-Crystools/package.json" />
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="start" />
</scripts>
<node-interpreter value="project" />
<node-interpreter value="W:/!Workstation/nodejs/node" />
<envs />
<method v="2" />
</configuration>
+20 -1
View File
@@ -27,7 +27,11 @@ This provides better nodes to load/save images, previews, etc, and see "hidden"
**🎉Finally, you can see the resources used by ComfyUI (CPU, GPU, RAM, VRAM, GPU Temp and space) on the menu in real-time!**
![Monitors](./docs/monitor.webp)
Horizontal:
![Monitors](./docs/monitor1.webp)
Vertical:
![Monitors](./docs/monitor3.webp)
Now you can identify the bottlenecks in your workflow and know when it's time to restart the server, unload models or even close some tabs!
@@ -512,6 +516,21 @@ You have predefined switches (string, latent, image, conditioning) but you can u
### Crystools
### 1.20.0 (21/10/2024)
- BETA of JSON file reader and extractor, to allow you to read your own JSON files and extract the values to use in your workflow
### 1.19.0 (06/10/2024)
- HORIZONTAL UI! New version is ready! 🎉
### 1.18.0 (21/09/2024)
- HORIZONTAL UI! 🎉
- Configurable size of monitors on settings menu
### 1.17.0 (21/09/2024)
- Settings menu reorganized
- Preparing for horizontal UI
- Update from ComfyUI (typescript and new features)
### 1.16.0 (31/07/2024)
- Rollback of AMD support by manager does not support other repository parameter (https://test.pypi.org/simple by pyrsmi)
+8 -2
View File
@@ -2,7 +2,7 @@
@author: Crystian
@title: Crystools
@nickname: Crystools
@version: 1.16.6
@version: 1.20.0
@project: "https://github.com/crystian/ComfyUI-Crystools",
@description: Plugins for multiples uses, mainly for debugging, you need them! IG: https://www.instagram.com/crystian.ia
"""
@@ -21,6 +21,7 @@ from .nodes.list import CListAny, CListString
from .nodes.pipe import CPipeToAny, CPipeFromAny
from .nodes.utils import CUtilsCompareJsons, CUtilsStatSystem
from .nodes.metadata import CMetadataExtractor, CMetadataCompare
from .nodes.parameters import CJsonFile, CJsonExtractor
from .server import *
from .general import *
@@ -56,7 +57,9 @@ NODE_CLASS_MAPPINGS = {
CLASSES.CMETADATA_EXTRACTOR_NAME.value: CMetadataExtractor,
CLASSES.CMETADATA_COMPARATOR_NAME.value: CMetadataCompare,
CLASSES.CUTILS_JSON_COMPARATOR_NAME.value: CUtilsCompareJsons,
CLASSES.CUTILS_STAT_SYSTEM_NAME.value: CUtilsStatSystem
CLASSES.CUTILS_STAT_SYSTEM_NAME.value: CUtilsStatSystem,
CLASSES.CJSONFILE_NAME.value: CJsonFile,
CLASSES.CJSONEXTRACTOR_NAME.value: CJsonExtractor,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -93,6 +96,9 @@ NODE_DISPLAY_NAME_MAPPINGS = {
CLASSES.CUTILS_JSON_COMPARATOR_NAME.value: CLASSES.CUTILS_JSON_COMPARATOR_DESC.value,
CLASSES.CUTILS_STAT_SYSTEM_NAME.value: CLASSES.CUTILS_STAT_SYSTEM_DESC.value,
CLASSES.CJSONFILE_NAME.value: CLASSES.CJSONFILE_DESC.value,
CLASSES.CJSONEXTRACTOR_NAME.value: CLASSES.CJSONEXTRACTOR_DESC.value,
}
+10
View File
@@ -105,3 +105,13 @@ def get_size(path):
return f"{round(size / (pow(1024, 2)), 2)} MB"
elif size < pow(1024, 4):
return f"{round(size / (pow(1024, 3)), 2)} GB"
def get_nested_value(data, dotted_key, default=None):
keys = dotted_key.split('.')
for key in keys:
if isinstance(data, dict) and key in data:
data = data[key]
else:
return default
return data
+1 -1
View File
@@ -1 +1 @@
version = "1.16.6"
version = "1.20.0"
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

+10 -10
View File
@@ -110,11 +110,11 @@ class CGPUInfo:
if self.cudaDevice == 'cpu':
gpuType = 'cpu'
gpus.append({
'gpu_utilization': 0,
'gpu_temperature': 0,
'vram_total': 0,
'vram_used': 0,
'vram_used_percent': 0,
'gpu_utilization': -1,
'gpu_temperature': -1,
'vram_total': -1,
'vram_used': -1,
'vram_used_percent': -1,
})
else:
gpuType = self.cudaDevice
@@ -126,11 +126,11 @@ class CGPUInfo:
for deviceIndex in range(self.cudaDevicesFound):
deviceHandle = self.deviceGetHandleByIndex(deviceIndex)
gpuUtilization = 0
vramPercent = 0
vramUsed = 0
vramTotal = 0
gpuTemperature = 0
gpuUtilization = -1
vramPercent = -1
vramUsed = -1
vramTotal = -1
gpuTemperature = -1
# GPU Utilization
if self.switchGPU and self.gpusUtilization[deviceIndex]:
+11 -2
View File
@@ -63,7 +63,16 @@ class CLASSES(Enum):
CMETADATA_COMPARATOR_NAME = 'Metadata comparator [Crystools]'
CMETADATA_COMPARATOR_DESC = prefix + 'Metadata comparator'
CUTILS_JSON_COMPARATOR_NAME = 'Json comparator [Crystools]'
CUTILS_JSON_COMPARATOR_DESC = prefix + 'Json comparator'
CUTILS_JSON_COMPARATOR_NAME = 'JSON comparator [Crystools]'
CUTILS_JSON_COMPARATOR_DESC = prefix + 'JSON comparator'
CUTILS_STAT_SYSTEM_NAME = 'Stats system [Crystools]'
CUTILS_STAT_SYSTEM_DESC = prefix + 'Stats system (powered by WAS)'
# CPARAMETERS_NAME = 'External parameter from JSON file [Crystools]'
# CPARAMETERS_DESC = prefix + 'External parameters from JSON file'
CJSONFILE_NAME = 'Read JSON file [Crystools]'
CJSONFILE_DESC = prefix + 'Read JSON file (BETA)'
CJSONEXTRACTOR_NAME = 'JSON extractor [Crystools]'
CJSONEXTRACTOR_DESC = prefix + 'JSON extractor (BETA)'
+170
View File
@@ -0,0 +1,170 @@
import json
from ..core import CONFIG, any, JSON_WIDGET, CATEGORY, STRING, INT, FLOAT, BOOLEAN, logger, get_nested_value
# class CParameter:
# def __init__(self):
# pass
#
# @classmethod
# def INPUT_TYPES(cls):
# return {
# "required": {
# },
# "optional": {
# "path_to_json": STRING,
# "key": STRING,
# "default": STRING,
# },
# }
#
# CATEGORY = CATEGORY.MAIN.value + CATEGORY.UTILS.value
# INPUT_IS_LIST = False
#
# RETURN_TYPES = (any,)
# RETURN_NAMES = ("any",)
#
# FUNCTION = "execute"
#
# def execute(self, path_to_json=None, key=True, default=None):
# text = default
# value = text
#
# if path_to_json is not None and path_to_json != "":
# logger.debug(f"External parameter from: '{path_to_json}'")
# try:
# with open(path_to_json, 'r') as file:
# data = json.load(file)
# logger.debug(f"File found, data: '{data}'")
#
# result = get_value(data, key, default)
# text = result["text"]
# value = result["value"]
#
# except Exception as e:
# logger.error(e)
# text = f"Error reading file: {e}\nReturning default value: '{default}'"
# value = default
#
# return {"ui": {"text": [text]}, "result": [value]}
class CJsonFile:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
},
"optional": {
"path_to_json": STRING,
},
}
CATEGORY = CATEGORY.MAIN.value + CATEGORY.UTILS.value
INPUT_IS_LIST = False
RETURN_TYPES = ("JSON",)
RETURN_NAMES = ("json",)
FUNCTION = "execute"
def IS_CHANGED(path_to_json=None):
return True
def execute(self, path_to_json=None):
text = ""
data = {}
if path_to_json is not None and path_to_json != "":
logger.debug(f"Open json file: '{path_to_json}'")
try:
with open(path_to_json, 'r') as file:
data = json.load(file)
text = json.dumps(data, indent=CONFIG["indent"])
logger.debug(f"File found, data: '{str(data)}'")
except Exception as e:
logger.error(e)
text = f"Error reading file: {e}"
return {"ui": {"text": [text]}, "result": [data]}
class CJsonExtractor:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"json": JSON_WIDGET,
},
"optional": {
"key": STRING,
"default": STRING,
},
}
CATEGORY = CATEGORY.MAIN.value + CATEGORY.UTILS.value
INPUT_IS_LIST = False
RETURN_TYPES = (any, "STRING", "INT", "FLOAT", "BOOLEAN")
RETURN_NAMES = ("any", "string", "int", "float", "boolean")
# OUTPUT_IS_LIST = (False,)
FUNCTION = "execute"
def execute(cls, json=None, key=True, default=None):
result = get_value(json, key, default)
result["any"] = result["value"]
try:
result["string"] = str(result["value"])
except Exception as e:
result["string"] = result["value"]
try:
result["int"] = int(result["value"])
except Exception as e:
result["int"] = result["value"]
try:
result["float"] = float(result["value"])
except Exception as e:
result["float"] = result["value"]
try:
result["boolean"] = result["value"].lower() == "true"
except Exception as e:
result["boolean"] = result["value"]
return {
"ui": {"text": [result["text"]]},
"result": [
result["any"],
result["string"],
result["int"],
result["float"],
result["boolean"]
]
}
def get_value(data, key, default=None):
text = ""
val = ""
if key is not None and key != "":
val = get_nested_value(data, key, default)
if default != val:
text = f"Key found, return value: '{val}'"
else:
text = f"Key no found, return default value: '{val}'"
else:
text = f"Key is empty, return default value: '{val}'"
return {
"text": text,
"value": val
}
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-crystools"
description = "With this suit, you can see the resources monitor, progress bar & time elapsed, metadata and compare between two images, compare between two JSONs, show any value to console/display, pipes, and more!\nThis provides better nodes to load/save images, previews, etc, and see \"hidden\" data without loading a new workflow."
version = "1.16.6"
version = "1.20.0"
license = { file = "LICENSE" }
dependencies = ["deepdiff", "torch", "numpy", "Pillow", "pynvml", "py-cpuinfo"]
+1 -1
View File
@@ -1,7 +1,7 @@
deepdiff
torch
numpy
Pillow
Pillow>=9.5.0
pynvml
py-cpuinfo
piexif
+13 -12
View File
@@ -6,19 +6,16 @@
"typeRoots": [
// "./web/ts/typings",
],
"baseUrl": ".",
// "rootDir": "../",
"baseUrl": "../../web",
// "baseUrl": "./",
"paths": {
"/scripts/*": ["../../web/scripts/*"],
"/lib/*": ["../../web/lib/*"],
"/extensions/*": ["../../web/extensions/*"],
"/types/*": ["../../web/types/*"]
// "/scripts/*": ["../../web/scripts/*"], // works
},
// "rootDir": "./web/src",
// "rootDir": "../../",
// "rootDirs": [
// "../../web",
// "web/src"
// ],
"rootDirs": [
"../../web",
// "web"
],
"preserveSymlinks": true,
"checkJs": false,
"allowJs": true,
@@ -58,7 +55,11 @@
"noEmit": false
},
"include": [
"./web/*.ts"
// "./custom_nodes/ComfyUI-Crystools/web/*.ts",
// "../../../web/*.ts",
// "../../web/*.ts",
"web/*.ts",
// "web/types.d.ts"
],
"exclude": [
"node_modules"
+1 -1
View File
@@ -1 +1 @@
1.16.6
1.20.0
+5
View File
@@ -0,0 +1,5 @@
export type * from './scripts.js';
export * from './scripts.js';
export type { ComfyApp } from './typings/comfy.js';
export * from './liteGraph.js';
export type * from './liteGraph.js';
+2
View File
@@ -0,0 +1,2 @@
export * from './scripts.js';
export * from './liteGraph.js';
+5
View File
@@ -0,0 +1,5 @@
export type * from './scripts.js';
export * from './scripts.js';
export type { ComfyApp } from './typings/comfy.js';
export * from './liteGraph.js';
export type * from './liteGraph.js';
+2 -5
View File
@@ -1,6 +1,5 @@
export type * from './liteGraph.types';
export * from '/scripts/widgets.js';
import type { IWidget as IWidgetOld, LGraphNode as TypeGraphNode, TypeLiteGraph } from './liteGraph.types';
export type * from './liteGraph.types.js';
import type { IWidget as IWidgetOld, LGraphNode as TypeGraphNode, TypeLiteGraph } from './liteGraph.types.js';
declare const LGraphNode: typeof TypeGraphNode;
export interface IWidget extends IWidgetOld {
onRemove?: () => void;
@@ -12,9 +11,7 @@ export declare class TLGraphNode extends LGraphNode {
static color: string;
static bgcolor: string;
static collapsable: boolean;
widgets?: IWidget[];
isVirtualNode?: boolean;
onResize?: (size: [number, number]) => void;
widgets_values?: any[];
name?: string;
prototype: TLGraphNode;
@@ -1,25 +1,12 @@
export * from '/scripts/widgets.js';
export class TLGraphNode extends LGraphNode {
constructor() {
super(...arguments);
Object.defineProperty(this, "widgets", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "isVirtualNode", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onResize", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "widgets_values", {
enumerable: true,
configurable: true,
+4 -5
View File
@@ -1,9 +1,8 @@
// / <reference path="/types/litegraph.d.ts" />
// A LOTS OF PATCHES FOR LITEGRAPH TYPES ¯\_(ツ)_/¯
export type * from './liteGraph.types';
export * from '/scripts/widgets.js';
export type * from './liteGraph.types.js';
import type { IWidget as IWidgetOld, LGraphNode as TypeGraphNode, TypeLiteGraph } from './liteGraph.types';
import type { IWidget as IWidgetOld, LGraphNode as TypeGraphNode, TypeLiteGraph } from './liteGraph.types.js';
declare const LGraphNode: typeof TypeGraphNode; // just for get the type
@@ -20,9 +19,9 @@ export class TLGraphNode extends LGraphNode {
static bgcolor: string;
static collapsable: boolean;
widgets?: IWidget[];
// widgets?: IWidget[];
isVirtualNode?: boolean;
onResize?: (size: [number, number]) => void;
// override onResize?: (size: [number, number]) => void;
widgets_values?: any[];
name?: string;
@@ -1,10 +1,7 @@
// A LOTS OF PATCHES FOR LITEGRAPH TYPES ¯\_(ツ)_/¯
export type * from '/types/litegraph';
export type * from '/scripts/widgets';
export type * from '/scripts/app';
export type { api } from '/scripts/api';
import { LGraph } from './typings/litegraph.js';
import { LGraph, LiteGraph } from '/types/litegraph';
export type * from './typings/litegraph.js';
export declare type TypeLiteGraph = typeof LiteGraph & {
graph: LGraph;
+5
View File
@@ -0,0 +1,5 @@
export { ComfyWidgets } from '../../../scripts/widgets.js';
export { app } from '../../../scripts/app.js';
export { api } from '../../../scripts/api.js';
export * as utils from '../../../scripts/utils.js';
export { ComfyButtonGroup } from '../../../scripts/ui/components/buttonGroup.js';
+5
View File
@@ -0,0 +1,5 @@
export { ComfyWidgets } from '../../../scripts/widgets.js';
export { app } from '../../../scripts/app.js';
export { api } from '../../../scripts/api.js';
export * as utils from '../../../scripts/utils.js';
export { ComfyButtonGroup } from '../../../scripts/ui/components/buttonGroup.js';
+10
View File
@@ -0,0 +1,10 @@
// @ts-expect-error I could not find a way to make this work
export { ComfyWidgets } from '../../../scripts/widgets.js';
// @ts-expect-error I could not find a way to make this work
export { app } from '../../../scripts/app.js';
// @ts-expect-error I could not find a way to make this work
export { api } from '../../../scripts/api.js';
// @ts-expect-error I could not find a way to make this work
export * as utils from '../../../scripts/utils.js';
// @ts-expect-error I could not find a way to make this work
export { ComfyButtonGroup } from '../../../scripts/ui/components/buttonGroup.js';
+5
View File
@@ -0,0 +1,5 @@
COPIED FROM rgthree
The typings in node_modules or in ComfyUI's web/ directory were not that well covered. These typings are hacked together with some of the inconsistencies I found.
To be honest, I have no idea why I needed a bizarre workaround for litegraph's types. Usually the '/// &lt;reference>' comment should have picked up the types, but it wasn't having it. ¯\_(ツ)_/¯
+227
View File
@@ -0,0 +1,227 @@
import type { LGraphGroup as TLGraphGroup, LGraphNode as TLGraphNode, IWidget, SerializedLGraphNode, LGraph as TLGraph, LGraphCanvas as TLGraphCanvas, LiteGraph as TLiteGraph } from "./litegraph.js";
import type {Constructor, SerializedGraph} from './index.js';
declare global {
const LiteGraph: typeof TLiteGraph;
const LGraph: typeof TLGraph;
const LGraphNode: typeof TLGraphNode;
const LGraphCanvas: typeof TLGraphCanvas;
const LGraphGroup: typeof TLGraphGroup;
}
// @rgthree: Types on ComfyApp as needed.
export interface ComfyApp {
extensions: ComfyExtension[];
async queuePrompt(number?: number, batchCount = 1): Promise<void>;
graph: TLGraph;
canvas: TLGraphCanvas;
clean() : void;
registerExtension(extension: ComfyExtension): void;
getPreviewFormatParam(): string;
getRandParam(): string;
loadApiJson(apiData: {}, fileName: string): void;
async graphToPrompt(graph?: TLGraph, clean?: boolean): Promise<void>;
// workflow: ComfyWorkflowInstance ???
async loadGraphData(graphData: {}, clean?: boolean, restore_view?: boolean, workflow?: any|null): Promise<void>
ui: {
settings: {
addSetting(config: {id: string, name: string, type: () => HTMLElement}) : void;
}
}
// Just marking as any for now.
menu?: any;
}
export interface ComfyWidget extends IWidget {
// https://github.com/comfyanonymous/ComfyUI/issues/2193 Changes from SerializedLGraphNode to
// LGraphNode...
serializeValue(nodeType: TLGraphNode, index: number): Promise<TValue>;
afterQueued(): void;
inputEl?: HTMLTextAreaElement;
width: number;
}
export interface ComfyGraphNode extends TLGraphNode {
getExtraMenuOptions: (node: TLGraphNode, options: ContextMenuItem[]) => void;
onExecuted(message: any): void;
}
export interface ComfyNode extends TLGraphNode {
comfyClass: string;
}
// @rgthree
export interface ComfyNodeConstructor extends Constructor<ComfyNode> {
static title: string;
static type?: string;
static comfyClass: string;
}
export type NodeMode = 0|1|2|3|4|undefined;
export interface ComfyExtension {
/**
* The name of the extension
*/
name: string;
/**
* Allows any initialisation, e.g. loading resources. Called after the canvas is created but before nodes are added
* @param app The ComfyUI app instance
*/
init?(app: ComfyApp): Promise<void>;
/**
* Allows any additonal setup, called after the application is fully set up and running
* @param app The ComfyUI app instance
*/
setup?(app: ComfyApp): Promise<void>;
/**
* Called before nodes are registered with the graph
* @param defs The collection of node definitions, add custom ones or edit existing ones
* @param app The ComfyUI app instance
*/
addCustomNodeDefs?(defs: Record<string, ComfyObjectInfo>, app: ComfyApp): Promise<void>;
/**
* Allows the extension to add custom widgets
* @param app The ComfyUI app instance
* @returns An array of {[widget name]: widget data}
*/
getCustomWidgets?(
app: ComfyApp
): Promise<
Record<string, (node, inputName, inputData, app) => { widget?: IWidget; minWidth?: number; minHeight?: number }>
>;
/**
* Allows the extension to add additional handling to the node before it is registered with LGraph
* @rgthree changed nodeType from `typeof LGraphNode` to `ComfyNodeConstructor`
* @param nodeType The node class (not an instance)
* @param nodeData The original node object info config object
* @param app The ComfyUI app instance
*/
beforeRegisterNodeDef?(nodeType: ComfyNodeConstructor, nodeData: ComfyObjectInfo, app: ComfyApp): Promise<void>;
/**
* Allows the extension to register additional nodes with LGraph after standard nodes are added
* @param app The ComfyUI app instance
*/
// @rgthree - add void for non async
registerCustomNodes?(app: ComfyApp): void|Promise<void>;
/**
* Allows the extension to modify a node that has been reloaded onto the graph.
* If you break something in the backend and want to patch workflows in the frontend
* This is the place to do this
* @param node The node that has been loaded
* @param app The ComfyUI app instance
*/
loadedGraphNode?(node: TLGraphNode, app: ComfyApp);
/**
* Allows the extension to run code after the constructor of the node
* @param node The node that has been created
* @param app The ComfyUI app instance
*/
nodeCreated?(node: TLGraphNode, app: ComfyApp);
}
export type ComfyObjectInfo = {
name: string;
display_name?: string;
description?: string;
category: string;
input?: {
required?: Record<string, ComfyObjectInfoConfig>;
optional?: Record<string, ComfyObjectInfoConfig>;
hidden?: Record<string, ComfyObjectInfoConfig>;
};
output?: string[];
output_name: string[];
// @rgthree
output_node?: boolean;
};
export type ComfyObjectInfoConfig = [string | any[]] | [string | any[], any];
// @rgthree
type ComfyApiInputLink = [
/** The id string of the connected node. */
string,
/** The output index. */
number,
]
// @rgthree
export type ComfyApiFormatNode = {
"inputs": {
[input_name: string]: string|number|boolean|ComfyApiInputLink,
},
"class_type": string,
"_meta": {
"title": string,
}
}
// @rgthree
export type ComfyApiFormat = {
[node_id: string]: ComfyApiFormatNode
}
// @rgthree
export type ComfyApiPrompt = {
workflow: SerializedGraph,
output: ComfyApiFormat,
}
// @rgthree
export type ComfyApiEventDetailStatus = {
exec_info: {
queue_remaining: number;
};
};
// @rgthree
export type ComfyApiEventDetailExecutionStart = {
prompt_id: string;
};
// @rgthree
export type ComfyApiEventDetailExecuting = null | string;
// @rgthree
export type ComfyApiEventDetailProgress = {
node: string;
prompt_id: string;
max: number;
value: number;
};
// @rgthree
export type ComfyApiEventDetailExecuted = {
node: string;
prompt_id: string;
output: any;
};
// @rgthree
export type ComfyApiEventDetailCached = {
nodes: string[];
prompt_id: string;
};
// @rgthree
export type ComfyApiEventDetailExecuted = {
prompt_id: string;
node: string;
output: any;
};
// @rgthree
export type ComfyApiEventDetailError = {
prompt_id: string;
exception_type: string;
exception_message: string;
node_id: string;
node_type: string;
node_id: string;
traceback: string;
executed: any[];
current_inputs: {[key: string]: (number[]|string[])};
current_outputs: {[key: string]: (number[]|string[])};
}
+55
View File
@@ -0,0 +1,55 @@
import { LGraph } from "./litegraph.js";
export type Constructor<T> = new(...args: any[]) => T;
export type SerializedLink = [
number, // this.id,
number, // this.origin_id,
number, // this.origin_slot,
number, // this.target_id,
number, // this.target_slot,
string, // this.type
];
export interface SerializedNodeInput {
name: string;
type: string;
link: number;
}
export interface SerializedNodeOutput {
name: string;
type: string;
link: number;
slot_index: number;
links: number[];
}
export interface SerializedNode {
id: number;
inputs: SerializedNodeInput[];
outputs: SerializedNodeOutput[];
mode: number;
order: number;
pos: [number, number];
properties: any;
size: [number, number];
type: string;
widgets_values: Array<number | string>;
}
export interface SerializedGraph {
config: any;
extra: any;
groups: any;
last_link_id: number;
last_node_id: number;
links: SerializedLink[];
nodes: SerializedNode[];
}
export interface BadLinksData<T = SerializedGraph|LGraph> {
hasBadLinks: boolean;
fixed: boolean;
graph: T;
patched: number;
deleted: number;
}
+1750
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1,3 +1,4 @@
import type { ComfyApp, TLGraphNode } from './liteGraph.js';
import type { TLGraphNode } from './comfy/index.js';
import { ComfyApp } from './comfy/index.js';
export declare const commonPrefix = "\uD83E\uDE9B";
export declare function displayContext(nodeType: TLGraphNode, appFromArg: ComfyApp, index?: number, serialize_widgets?: boolean, isVirtualNode?: boolean): void;
+2 -11
View File
@@ -1,4 +1,4 @@
import { ComfyWidgets } from './liteGraph.js';
import { ComfyWidgets } from './comfy/index.js';
export const commonPrefix = '🪛';
export function displayContext(nodeType, appFromArg, index = 0, serialize_widgets = false, isVirtualNode = false) {
function populate(text) {
@@ -14,9 +14,7 @@ export function displayContext(nodeType, appFromArg, index = 0, serialize_widget
this.serialize_widgets = serialize_widgets;
this.isVirtualNode = isVirtualNode;
const widget = ComfyWidgets.STRING(this, 'text', [
'STRING', {
multiline: true,
},
'STRING', { multiline: true },
], appFromArg).widget;
widget.inputEl.readOnly = true;
widget.inputEl.style.opacity = 0.6;
@@ -50,10 +48,3 @@ export function displayContext(nodeType, appFromArg, index = 0, serialize_widget
}
};
}
const style = document.createElement('style');
style.innerHTML = `
#comfy-settings-dialog label[for^=Crystools-] {
white-space: pre-line;
}
`;
document.head.appendChild(style);
+6 -16
View File
@@ -1,5 +1,5 @@
import type { ComfyApp, TLGraphNode } from './liteGraph.js';
import { ComfyWidgets } from './liteGraph.js';
import type { TLGraphNode } from './comfy/index.js';
import { ComfyWidgets, ComfyApp } from './comfy/index.js';
export const commonPrefix = '🪛';
@@ -25,9 +25,7 @@ export function displayContext(
this.isVirtualNode = isVirtualNode;
const widget = ComfyWidgets.STRING(this, 'text', [
'STRING', {
multiline: true,
},
'STRING', { multiline: true },
], appFromArg).widget;
widget.inputEl.readOnly = true;
widget.inputEl.style.opacity = 0.6;
@@ -53,10 +51,12 @@ export function displayContext(
}
// When the node is executed we will be sent the input text, display this in the widget
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/unbound-method
const onExecutedOriginal = nodeType.prototype.onExecuted;
// @ts-ignore
nodeType.prototype.onExecuted = function(message: { text: string }): void {
// @ts-ignore
onExecutedOriginal?.apply(this, arguments);
populate.call(this, message.text);
};
@@ -90,13 +90,3 @@ export function displayContext(
// }
// }
// };
// HACKS
// for settings using break lines on label
const style = document.createElement('style');
style.innerHTML = `
#comfy-settings-dialog label[for^=Crystools-] {
white-space: pre-line;
}
`;
document.head.appendChild(style);
+6 -16
View File
@@ -1,7 +1,5 @@
import { app } from '/scripts/app.js';
import { api } from '/scripts/api.js';
import { app, api, ComfyWidgets, LiteGraph, TLGraphNode } from './comfy/index.js';
import { commonPrefix, displayContext } from './common.js';
import { LiteGraph, TLGraphNode, ComfyWidgets } from './liteGraph.js';
app.registerExtension({
name: 'Crystools.Debugger.ConsoleAny',
beforeRegisterNodeDef(nodeType, nodeData, appFromArg) {
@@ -20,7 +18,7 @@ app.registerExtension({
enumerable: true,
configurable: true,
writable: true,
value: async () => {
value: () => {
return app.graphToPrompt()
.then((workflow) => {
let result = 'inactive';
@@ -56,25 +54,17 @@ app.registerExtension({
this.serialize_widgets = false;
this.isVirtualNode = true;
const widget = ComfyWidgets.STRING(this, '', [
'', {
default: '', multiline: true,
},
'', { default: '', multiline: true },
], app).widget;
widget.inputEl.readOnly = true;
ComfyWidgets.BOOLEAN(this, 'Active', [
'', {
default: true,
},
'', { default: true },
]);
ComfyWidgets.BOOLEAN(this, 'Parsed', [
'', {
default: true,
},
'', { default: true },
]);
ComfyWidgets.COMBO(this, 'What', [
['Prompt', 'Workflow'], {
default: 'Prompt',
},
['Prompt', 'Workflow'], { default: 'Prompt' },
]);
api.addEventListener('executed', this.fillMetadataWidget, false);
}
+9 -19
View File
@@ -1,8 +1,6 @@
import { app } from '/scripts/app.js';
import { api } from '/scripts/api.js';
import type { ComfyNode } from './comfy/index.js';
import { app, api, ComfyWidgets, LiteGraph, TLGraphNode, ComfyApp } from './comfy/index.js';
import { commonPrefix, displayContext } from './common.js';
import type { ComfyNode, ComfyApp } from './liteGraph.js';
import { LiteGraph, TLGraphNode, ComfyWidgets } from './liteGraph.js';
// "Show any" Node
app.registerExtension({
@@ -25,36 +23,28 @@ app.registerExtension({
this.isVirtualNode = true;
const widget = ComfyWidgets.STRING(this, '', [
'', {
default: '', multiline: true,
},
'', {default: '', multiline: true},
], app).widget;
widget.inputEl.readOnly = true;
ComfyWidgets.BOOLEAN(this, 'Active', [
'', {
default: true,
},
'', {default: true},
]);
ComfyWidgets.BOOLEAN(this, 'Parsed', [
'', {
default: true,
},
'', {default: true},
]);
ComfyWidgets.COMBO(this, 'What', [
['Prompt', 'Workflow'], {
default: 'Prompt',
},
['Prompt', 'Workflow'], {default: 'Prompt'},
]);
// It runs at finish on each prompt queue
api.addEventListener('executed', this.fillMetadataWidget, false);
}
fillMetadataWidget = async (): Promise<string> => {
fillMetadataWidget = (): Promise<string> => {
return app.graphToPrompt()
.then((workflow): string => {
.then((workflow: any): string => {
let result = 'inactive';
if(this.widgets?.length !== 4) {
if (this.widgets?.length !== 4) {
console.error('Something is wrong with the widgets, should be 4!');
return 'error';
}
+14 -2
View File
@@ -1,5 +1,9 @@
import { app } from '/scripts/app.js';
import { app } from './comfy/index.js';
import { displayContext } from './common.js';
const crystoolsExtensionsSerialized = {
'Read JSON file [Crystools]': 'Crystools.Utils.ReadJsonFile',
'JSON extractor [Crystools]': 'Crystools.Utils.JsonExtractor',
};
const crystoolsExtensions = {
'Get resolution [Crystools]': 'Crystools.Image.GetResolution',
'Preview from image [Crystools]': 'Crystools.Image.PreviewFromImage',
@@ -8,12 +12,20 @@ const crystoolsExtensions = {
'Stats system [Crystools]': 'Crystools.Utils.StatsSystem',
'Show any to JSON [Crystools]': 'Crystools.Debugger.ConsoleAnyToJson',
};
Object.keys(crystoolsExtensionsSerialized).forEach(prop => {
crystoolsExtensions[prop] = crystoolsExtensionsSerialized[prop];
});
Object.keys(crystoolsExtensions).forEach(key => {
app.registerExtension({
name: crystoolsExtensions[key],
beforeRegisterNodeDef(nodeType, nodeData, appFromArg) {
if (nodeData.name === key) {
displayContext(nodeType, appFromArg, 0);
if (nodeData.name in crystoolsExtensionsSerialized) {
displayContext(nodeType, appFromArg, 0, true);
}
else {
displayContext(nodeType, appFromArg, 0);
}
}
},
});
+18 -4
View File
@@ -1,7 +1,12 @@
import { app } from '/scripts/app.js';
import { app, TLGraphNode, ComfyApp } from './comfy/index.js';
import type { ComfyNode } from './comfy/index.js';
import { displayContext } from './common.js';
import type { ComfyNode, ComfyApp } from './liteGraph.types';
import { TLGraphNode } from './liteGraph';
const crystoolsExtensionsSerialized: Record<string, string> = {
// 'External parameter from JSON file [Crystools]': 'Crystools.Utils.ExternalParameterFromJson',
'Read JSON file [Crystools]': 'Crystools.Utils.ReadJsonFile',
'JSON extractor [Crystools]': 'Crystools.Utils.JsonExtractor',
};
const crystoolsExtensions: Record<string, string> = {
'Get resolution [Crystools]': 'Crystools.Image.GetResolution',
@@ -12,12 +17,21 @@ const crystoolsExtensions: Record<string, string> = {
'Show any to JSON [Crystools]': 'Crystools.Debugger.ConsoleAnyToJson',
};
Object.keys(crystoolsExtensionsSerialized).forEach(prop => {
// @ts-ignore
crystoolsExtensions[prop] = crystoolsExtensionsSerialized[prop];
});
Object.keys(crystoolsExtensions).forEach(key => {
app.registerExtension({
name: crystoolsExtensions[key],
beforeRegisterNodeDef(nodeType: ComfyNode, nodeData: TLGraphNode, appFromArg: ComfyApp) {
if (nodeData.name === key) {
displayContext(nodeType, appFromArg, 0);
if (nodeData.name in crystoolsExtensionsSerialized) {
displayContext(nodeType, appFromArg, 0, true); // serialize_widgets = true
} else {
displayContext(nodeType, appFromArg, 0);
}
}
},
});
+179
View File
@@ -0,0 +1,179 @@
/* Vertical UI */
.comfy-menu {
#crystools-monitors-root {
/*background-color: red;*/
box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.3);
background-color: var(--comfy-menu-bg);
border-radius: 6px;
border: 1px solid var(--border-color);
display: flex;
width: 100%;
flex-direction: column;
margin: 7px;
padding: 5px 0;
cursor: crosshair;
/* MONITORS */
.crystools-monitor {
margin: 1px 10px;
height: 12px;
display: flex;
align-items: center;
.crystools-text {
width: 40px;
font-size: 10px;
text-align: right;
margin-right: 3px;
&:hover {
font-weight: 600;
color: var(--input-text);
}
}
.crystools-label {
&:hover {
font-weight: 700;
color: var(--input-text);
}
}
}
.crystools-content {
flex-grow: 1;
position: relative;
background-color: var(--content-hover-bg)
}
.crystools-slider {
position: absolute;
height: 100%;
width: 0;
box-shadow: inset 2px 2px 10px rgba(0, 0, 0, 0.2);
}
.crystools-label {
position: relative;
color: var(--input-text);
font-size: 10px;
}
}
/* PROGRESS BAR */
#crystools-progressBar-root {
cursor: pointer;
margin: 0 12px 4px;
height: 18px;
position: relative;
border-radius: 3px;
width: 100%;
background-color: var(--comfy-input-bg);
.crystools-slider {
position: absolute;
height: 100%;
width: 50%;
transition: width 0.2s;
background-color: green;
box-shadow: inset 2px 2px 10px rgba(0, 0, 0, 0.3);
}
.crystools-label {
position: absolute;
margin: auto 0;
width: 100%;
color: var(--input-text);
font-size: 14px;
}
}
}
.comfyui-queue-button {
/*width: unset !important;*/
}
/* Horizontal UI */
.comfyui-menu #crystools-monitors-root {
/*background-color: red;*/
/*display: none;*/
flex-direction: row;
/*flex-grow: 5;*/
justify-content: flex-end;
/*flex: auto;*/
flex-shrink: 1;
/*min-width: max-content;*/
/*height: 100%;*/
/* max-width: 40vw;*/
/* min-width: 10vw;*/
/*gap: 5px;*/
/*margin: 0 auto;*/
/*align-items: center;*/
/*align-self: center;*/
/*width: min-content;*/
/*width: 100%;*/
cursor: crosshair;
/*margin: 4px 0;*/
display: flex; /* by progress bar base */
flex-wrap: wrap;
/*flex-direction: row;*/
/*justify-content: flex-end;*/
/*align-items: center;*/
gap: 5px;
align-self: center;
.crystools-monitor {
background-color: var(--comfy-input-bg);
position: relative;
align-items: center;
flex-direction: row;
/*width: 60px;*/
/*height: 100%;*/
.crystools-text {
font-size: 10px;
text-align: right;
margin-left: 3px;
position: absolute;
font-weight: 100;
bottom: 2px;
z-index: 10;
/*&:hover {*/
/* font-weight: 600;*/
/* color: var(--input-text);*/
/*}*/
}
/*.crystools-label {*/
/* &:hover {*/
/* font-weight: 800;*/
/* color: var(--input-text);*/
/* }*/
/*}*/
}
.crystools-content {
position: relative;
}
.crystools-slider {
position: absolute;
height: 100%;
width: 0;
box-shadow: inset 2px 2px 10px rgba(0, 0, 0, 0.2);
}
.crystools-label {
position: relative;
width: 100%;
color: var(--input-text);
font-weight: 500;
font-size: 11px;
right: 2px;
top: 2px;
text-align: right;
}
}
+461 -402
View File
@@ -1,6 +1,9 @@
import { app } from '/scripts/app.js';
import { api } from '/scripts/api.js';
import { app, api, ComfyButtonGroup } from './comfy/index.js';
import { commonPrefix } from './common.js';
import { MonitorUI } from './monitorUI.js';
import { Colors } from './styles.js';
import { convertNumberToPascalCase } from './utils.js';
import { ComfyKeyMenuDisplayOption, MenuDisplayOptions } from './progressBarUIBase.js';
class CrystoolsMonitor {
constructor() {
Object.defineProperty(this, "idExtensionName", {
@@ -15,41 +18,59 @@ class CrystoolsMonitor {
writable: true,
value: commonPrefix
});
Object.defineProperty(this, "htmlIdCrystoolsRoot", {
Object.defineProperty(this, "menuDisplayOption", {
enumerable: true,
configurable: true,
writable: true,
value: 'crystools-root'
value: MenuDisplayOptions.Disabled
});
Object.defineProperty(this, "htmlIdCrystoolsMonitorContainer", {
Object.defineProperty(this, "crystoolsButtonGroup", {
enumerable: true,
configurable: true,
writable: true,
value: 'crystools-monitor-container'
value: null
});
Object.defineProperty(this, "idInputRate", {
Object.defineProperty(this, "settingsRate", {
enumerable: true,
configurable: true,
writable: true,
value: 'Crystools.inputRate'
value: void 0
});
Object.defineProperty(this, "defaultRate", {
Object.defineProperty(this, "settingsMonitorHeight", {
enumerable: true,
configurable: true,
writable: true,
value: .5
value: void 0
});
Object.defineProperty(this, "idWhichHDD", {
Object.defineProperty(this, "settingsMonitorWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 'Crystools.whichHDD'
value: void 0
});
Object.defineProperty(this, "defaultWhichHDD", {
Object.defineProperty(this, "monitorCPUElement", {
enumerable: true,
configurable: true,
writable: true,
value: '/'
value: void 0
});
Object.defineProperty(this, "monitorRAMElement", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "monitorHDDElement", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "settingsHDD", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "monitorGPUSettings", {
enumerable: true,
@@ -69,95 +90,53 @@ class CrystoolsMonitor {
writable: true,
value: []
});
Object.defineProperty(this, "monitorCPUElement", {
Object.defineProperty(this, "monitorUI", {
enumerable: true,
configurable: true,
writable: true,
value: {
id: 'Crystools.switchCPU',
name: this.menuPrefix + ' [monitor] CPU Usage',
type: 'boolean',
label: 'CPU',
symbol: '%',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#0AA015',
onChange: async (value) => {
this.updateWidget(this.monitorCPUElement);
await this.updateServer({
switchCPU: value,
});
},
}
value: void 0
});
Object.defineProperty(this, "monitorRAMElement", {
Object.defineProperty(this, "monitorWidthId", {
enumerable: true,
configurable: true,
writable: true,
value: {
id: 'Crystools.switchRAM',
name: this.menuPrefix + ' [monitor] RAM Used',
type: 'boolean',
label: 'RAM',
symbol: '%',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#07630D',
onChange: async (value) => {
this.updateWidget(this.monitorRAMElement);
await this.updateServer({
switchRAM: value,
});
},
}
value: 'Crystools.MonitorWidth'
});
Object.defineProperty(this, "monitorHDDElement", {
Object.defineProperty(this, "monitorWidth", {
enumerable: true,
configurable: true,
writable: true,
value: {
id: 'Crystools.switchHDD',
name: this.menuPrefix + ' [monitor] HDD Used',
type: 'boolean',
label: 'HDD',
symbol: '%',
tooltip: 'See Partition to show (HDD)',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#730F92',
onChange: async (value) => {
this.updateWidget(this.monitorHDDElement);
await this.updateServer({
switchHDD: value,
});
},
}
value: 60
});
Object.defineProperty(this, "createSettings", {
Object.defineProperty(this, "monitorHeightId", {
enumerable: true,
configurable: true,
writable: true,
value: 'Crystools.MonitorHeight'
});
Object.defineProperty(this, "monitorHeight", {
enumerable: true,
configurable: true,
writable: true,
value: 30
});
Object.defineProperty(this, "createSettingsRate", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
app.ui.settings.addSetting(this.monitorCPUElement);
app.ui.settings.addSetting(this.monitorRAMElement);
app.ui.settings.addSetting(this.monitorHDDElement);
app.ui.settings.addSetting({
id: this.idInputRate,
name: this.menuPrefix + '[monitor] Refresh rate',
this.settingsRate = {
id: 'Crystools.RefreshRate',
name: 'Refresh per second',
category: ['Crystools', this.menuPrefix + ' Configuration', 'refresh'],
tooltip: 'This is the time (in seconds) between each update of the monitors, 0 means no refresh',
type: 'slider',
attrs: {
min: 0,
max: 5,
step: 0.25,
max: 2,
step: .25,
},
defaultValue: this.defaultRate,
defaultValue: .5,
onChange: async (value) => {
let valueNumber;
try {
@@ -171,143 +150,408 @@ class CrystoolsMonitor {
return;
}
try {
await this.updateServer({
rate: valueNumber,
});
await this.updateServer({ rate: valueNumber });
}
catch (error) {
console.error(error);
return;
}
const data = {
cpu_utilization: 0,
device: 'cpu',
gpus: [
{
gpu_utilization: 0,
gpu_temperature: 0,
vram_total: 0,
vram_used: 0,
vram_used_percent: 0,
},
],
hdd_total: 0,
hdd_used: 0,
hdd_used_percent: 0,
ram_total: 0,
ram_used: 0,
ram_used_percent: 0,
};
if (valueNumber === 0) {
this.updateAllMonitors({
cpu_utilization: 0,
device: 'cpu',
gpus: [
{
gpu_utilization: 0,
gpu_temperature: 0,
vram_total: 0,
vram_used: 0,
vram_used_percent: 0,
},
],
hdd_total: 0,
hdd_used: 0,
hdd_used_percent: 0,
ram_total: 0,
ram_used: 0,
ram_used_percent: 0,
});
this.monitorUI.updateDisplay(data);
}
this.updateAllAnimationDuration(valueNumber);
this.monitorUI?.updateAllAnimationDuration(valueNumber);
},
});
void this.getHDDsFromServer().then((data) => {
const which = app.ui.settings.getSettingValue(this.idWhichHDD, this.defaultWhichHDD);
app.ui.settings.addSetting({
id: this.idWhichHDD,
name: this.menuPrefix + '[monitor] Partition to show',
type: 'combo',
defaultValue: this.defaultWhichHDD,
options: (value) => data.map((m) => ({
};
}
});
Object.defineProperty(this, "createSettingsCPU", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.monitorCPUElement = {
id: 'Crystools.ShowCpu',
name: 'CPU Usage',
category: ['Crystools', this.menuPrefix + ' Hardware', 'Cpu'],
type: 'boolean',
label: 'CPU',
symbol: '%',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.CPU,
onChange: async (value) => {
this.updateWidget(this.monitorCPUElement);
await this.updateServer({ switchCPU: value });
},
};
}
});
Object.defineProperty(this, "createSettingsRAM", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.monitorRAMElement = {
id: 'Crystools.ShowRam',
name: 'RAM Used',
category: ['Crystools', this.menuPrefix + ' Hardware', 'Ram'],
type: 'boolean',
label: 'RAM',
symbol: '%',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.RAM,
onChange: async (value) => {
this.updateWidget(this.monitorRAMElement);
await this.updateServer({ switchRAM: value });
},
};
}
});
Object.defineProperty(this, "createSettingsGPUUsage", {
enumerable: true,
configurable: true,
writable: true,
value: (name, index, moreThanOneGPU) => {
if (name === undefined || index === undefined) {
console.warn('getGPUsFromServer: name or index undefined', name, index);
return;
}
let label = 'GPU ';
label += moreThanOneGPU ? index : '';
const monitorGPUNElement = {
id: 'Crystools.ShowGpuUsage' + convertNumberToPascalCase(index),
name: ' Usage',
category: ['Crystools', `${this.menuPrefix} Show GPU [${index}] ${name}`, 'Usage'],
type: 'boolean',
label,
symbol: '%',
monitorTitle: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.GPU,
onChange: async (value) => {
this.updateWidget(monitorGPUNElement);
void await this.updateServerGPU(index, { utilization: value });
},
};
this.monitorGPUSettings[index] = monitorGPUNElement;
app.ui.settings.addSetting(this.monitorGPUSettings[index]);
this.monitorUI.createDOMGPUMonitor(this.monitorGPUSettings[index]);
}
});
Object.defineProperty(this, "createSettingsGPUVRAM", {
enumerable: true,
configurable: true,
writable: true,
value: (name, index, moreThanOneGPU) => {
if (name === undefined || index === undefined) {
console.warn('getGPUsFromServer: name or index undefined', name, index);
return;
}
let label = 'VRAM ';
label += moreThanOneGPU ? index : '';
const monitorVRAMNElement = {
id: 'Crystools.ShowGpuVram' + convertNumberToPascalCase(index),
name: 'VRAM',
category: ['Crystools', `${this.menuPrefix} Show GPU [${index}] ${name}`, 'VRAM'],
type: 'boolean',
label: label,
symbol: '%',
monitorTitle: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.VRAM,
onChange: async (value) => {
this.updateWidget(monitorVRAMNElement);
void await this.updateServerGPU(index, { vram: value });
},
};
this.monitorVRAMSettings[index] = monitorVRAMNElement;
app.ui.settings.addSetting(this.monitorVRAMSettings[index]);
this.monitorUI.createDOMGPUMonitor(this.monitorVRAMSettings[index]);
}
});
Object.defineProperty(this, "createSettingsGPUTemp", {
enumerable: true,
configurable: true,
writable: true,
value: (name, index, moreThanOneGPU) => {
if (name === undefined || index === undefined) {
console.warn('getGPUsFromServer: name or index undefined', name, index);
return;
}
let label = 'Temp ';
label += moreThanOneGPU ? index : '';
const monitorTemperatureNElement = {
id: 'Crystools.ShowGpuTemperature' + convertNumberToPascalCase(index),
name: 'Temperature',
category: ['Crystools', `${this.menuPrefix} Show GPU [${index}] ${name}`, 'Temperature'],
type: 'boolean',
label: label,
symbol: '°',
monitorTitle: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.TEMP_START,
cssColorFinal: Colors.TEMP_END,
onChange: async (value) => {
this.updateWidget(monitorTemperatureNElement);
void await this.updateServerGPU(index, { temperature: value });
},
};
this.monitorTemperatureSettings[index] = monitorTemperatureNElement;
app.ui.settings.addSetting(this.monitorTemperatureSettings[index]);
this.monitorUI.createDOMGPUMonitor(this.monitorTemperatureSettings[index]);
}
});
Object.defineProperty(this, "createSettingsHDD", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.monitorHDDElement = {
id: 'Crystools.ShowHdd',
name: 'Show HDD Used',
category: ['Crystools', this.menuPrefix + ' Show Hard Disk', 'Show'],
type: 'boolean',
label: 'HDD',
symbol: '%',
defaultValue: false,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.DISK,
onChange: async (value) => {
this.updateWidget(this.monitorHDDElement);
await this.updateServer({ switchHDD: value });
},
};
this.settingsHDD = {
id: 'Crystools.WhichHdd',
name: 'Partition to show',
category: ['Crystools', this.menuPrefix + ' Show Hard Disk', 'Which'],
type: 'combo',
defaultValue: '/',
data: [],
options: (value) => {
const which = app.ui.settings.getSettingValue(this.settingsHDD.id, this.settingsHDD.defaultValue);
return this.settingsHDD.data.map((m) => ({
value: m,
text: m,
selected: !value ? m === which : m === value,
})),
onChange: async (value) => {
await this.updateServer({
whichHDD: value,
});
},
});
}));
},
onChange: async (value) => {
await this.updateServer({ whichHDD: value });
},
};
}
});
Object.defineProperty(this, "createSettingsMonitorWidth", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.settingsMonitorWidth = {
id: this.monitorWidthId,
name: 'Pixel Width',
category: ['Crystools', this.menuPrefix + ' Configuration', 'width'],
tooltip: 'The width of the monitor in pixels on the UI (only on floating UI)',
type: 'slider',
attrs: {
min: 60,
max: 100,
step: 1,
},
defaultValue: this.monitorWidth,
onChange: (value) => {
let valueNumber;
try {
valueNumber = parseInt(value);
if (isNaN(valueNumber)) {
throw new Error('invalid value');
}
}
catch (error) {
console.error(error);
return;
}
const h = app.ui.settings.getSettingValue(this.monitorHeightId, this.monitorHeight);
this.monitorUI?.updateMonitorSize(valueNumber, h);
},
};
}
});
Object.defineProperty(this, "createSettingsMonitorHeight", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.settingsMonitorHeight = {
id: this.monitorHeightId,
name: 'Pixel Height',
category: ['Crystools', this.menuPrefix + ' Configuration', 'height'],
tooltip: 'The height of the monitor in pixels on the UI (only on floating UI)',
type: 'slider',
attrs: {
min: 16,
max: 50,
step: 1,
},
defaultValue: this.monitorHeight,
onChange: async (value) => {
let valueNumber;
try {
valueNumber = parseInt(value);
if (isNaN(valueNumber)) {
throw new Error('invalid value');
}
}
catch (error) {
console.error(error);
return;
}
const w = await app.ui.settings.getSettingValue(this.monitorWidthId, this.monitorWidth);
this.monitorUI?.updateMonitorSize(w, valueNumber);
},
};
}
});
Object.defineProperty(this, "createSettings", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
app.ui.settings.addSetting(this.settingsRate);
app.ui.settings.addSetting(this.settingsMonitorHeight);
app.ui.settings.addSetting(this.settingsMonitorWidth);
app.ui.settings.addSetting(this.monitorRAMElement);
app.ui.settings.addSetting(this.monitorCPUElement);
void this.getHDDsFromServer().then((data) => {
this.settingsHDD.data = data;
app.ui.settings.addSetting(this.settingsHDD);
});
app.ui.settings.addSetting(this.monitorHDDElement);
void this.getGPUsFromServer().then((gpus) => {
let moreThanOneGPU = false;
if (gpus.length > 1) {
moreThanOneGPU = true;
}
gpus?.forEach(({ name, index }) => {
if (name === undefined || index === undefined) {
console.warn('getGPUsFromServer: name or index undefined', name, index);
return;
}
let label = 'GPU';
let labelVRAM = 'VRAM';
let labelTemperature = 'Temp';
if (moreThanOneGPU) {
label = 'GPU ' + index;
labelVRAM = 'VRAM' + index;
labelTemperature = 'Temp ' + index;
}
const monitorGPUNElement = {
id: 'Crystools.switchGPU' + index,
name: this.menuPrefix + `[menu] Display GPU\r\n[${index}] ${name}`,
type: 'boolean',
label,
symbol: '%',
title: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#0C86F4',
onChange: async (value) => {
this.updateWidget(monitorGPUNElement);
void await this.updateServerGPU(index, {
utilization: value
});
},
};
const monitorVRAMNElement = {
id: 'Crystools.switchVRAM' + index,
name: this.menuPrefix + `[menu] Display GPU VRAM\r\n[${index}] ${name}`,
type: 'boolean',
label: labelVRAM,
symbol: '%',
title: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#176EC7',
onChange: async (value) => {
this.updateWidget(monitorVRAMNElement);
void await this.updateServerGPU(index, {
vram: value
});
},
};
const monitorTemperatureNElement = {
id: 'Crystools.switchTemperature' + index,
name: this.menuPrefix + `[menu] Display GPU Temperature\r\n[${index}] ${name}`,
type: 'boolean',
label: labelTemperature,
symbol: '°',
title: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#00ff00',
cssColorFinal: '#ff0000',
onChange: async (value) => {
this.updateWidget(monitorTemperatureNElement);
void await this.updateServerGPU(index, {
temperature: value
});
},
};
this.monitorGPUSettings[index] = monitorGPUNElement;
this.monitorVRAMSettings[index] = monitorVRAMNElement;
this.monitorTemperatureSettings[index] = monitorTemperatureNElement;
app.ui.settings.addSetting(this.monitorGPUSettings[index]);
app.ui.settings.addSetting(this.monitorVRAMSettings[index]);
app.ui.settings.addSetting(this.monitorTemperatureSettings[index]);
this.createSettingsGPUTemp(name, index, moreThanOneGPU);
this.createSettingsGPUVRAM(name, index, moreThanOneGPU);
this.createSettingsGPUUsage(name, index, moreThanOneGPU);
});
this.finishedLoad();
});
}
});
Object.defineProperty(this, "finishedLoad", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.monitorUI.orderMonitors();
this.updateAllWidget();
this.moveMonitor(this.menuDisplayOption);
const w = app.ui.settings.getSettingValue(this.monitorWidthId, this.monitorWidth);
const h = app.ui.settings.getSettingValue(this.monitorHeightId, this.monitorHeight);
this.monitorUI.updateMonitorSize(w, h);
}
});
Object.defineProperty(this, "updateDisplay", {
enumerable: true,
configurable: true,
writable: true,
value: (value) => {
if (value !== this.menuDisplayOption) {
this.menuDisplayOption = value;
this.moveMonitor(this.menuDisplayOption);
}
}
});
Object.defineProperty(this, "moveMonitor", {
enumerable: true,
configurable: true,
writable: true,
value: (position) => {
let parentElement;
switch (position) {
case MenuDisplayOptions.Disabled:
parentElement = document.getElementById('queue-button');
if (parentElement && this.monitorUI.rootElement) {
parentElement.insertAdjacentElement('afterend', this.crystoolsButtonGroup.element);
}
else {
console.error('Crystools: parentElement to move monitors not found!', parentElement);
}
break;
case MenuDisplayOptions.Top:
case MenuDisplayOptions.Bottom:
app.menu?.settingsGroup.element.before(this.crystoolsButtonGroup.element);
}
}
});
Object.defineProperty(this, "updateAllWidget", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.updateWidget(this.monitorCPUElement);
this.updateWidget(this.monitorRAMElement);
this.updateWidget(this.monitorHDDElement);
this.monitorGPUSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
this.monitorVRAMSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
this.monitorTemperatureSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
}
});
Object.defineProperty(this, "updateWidget", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings) => {
if (this.monitorUI) {
const value = app.ui.settings.getSettingValue(monitorSettings.id, monitorSettings.defaultValue);
this.monitorUI.showMonitor(monitorSettings, value);
}
}
});
Object.defineProperty(this, "updateServer", {
enumerable: true,
configurable: true,
@@ -371,183 +615,31 @@ class CrystoolsMonitor {
throw new Error(resp.statusText);
}
});
Object.defineProperty(this, "updateAllWidget", {
Object.defineProperty(this, "setup", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.updateWidget(this.monitorCPUElement);
this.updateWidget(this.monitorRAMElement);
this.updateWidget(this.monitorHDDElement);
this.monitorGPUSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
this.monitorVRAMSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
this.monitorTemperatureSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
}
});
Object.defineProperty(this, "updateWidget", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings) => {
const value = app.ui.settings.getSettingValue(monitorSettings.id, monitorSettings.defaultValue);
if (monitorSettings.htmlMonitorRef) {
monitorSettings.htmlMonitorRef.style.display = value ? 'flex' : 'none';
}
}
});
Object.defineProperty(this, "updateAllMonitors", {
enumerable: true,
configurable: true,
writable: true,
value: (data) => {
this.updateMonitor(this.monitorCPUElement, data.cpu_utilization);
this.updateMonitor(this.monitorRAMElement, data.ram_used_percent);
this.updateMonitor(this.monitorHDDElement, data.hdd_used_percent);
if (data.gpus === undefined || data.gpus.length === 0) {
console.warn('UpdateAllMonitors: no GPU data');
if (this.monitorUI) {
return;
}
this.monitorGPUSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
return;
}
this.updateMonitor(monitorSettings, gpu.gpu_utilization);
}
else {
}
this.createSettingsRate();
this.createSettingsMonitorHeight();
this.createSettingsMonitorWidth();
this.createSettingsCPU();
this.createSettingsRAM();
this.createSettingsHDD();
this.createSettings();
const currentRate = parseFloat(app.ui.settings.getSettingValue(this.settingsRate.id, this.settingsRate.defaultValue));
this.menuDisplayOption = app.ui.settings.getSettingValue(ComfyKeyMenuDisplayOption, MenuDisplayOptions.Disabled);
app.ui.settings.addEventListener(`${ComfyKeyMenuDisplayOption}.change`, (e) => {
this.updateDisplay(e.detail.value);
});
this.monitorVRAMSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
return;
}
this.updateMonitor(monitorSettings, gpu.vram_used_percent);
}
else {
}
});
this.monitorTemperatureSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
return;
}
this.updateMonitor(monitorSettings, gpu.gpu_temperature);
if (monitorSettings.cssColorFinal && monitorSettings.htmlMonitorSliderRef) {
monitorSettings.htmlMonitorSliderRef.style.backgroundColor =
`color-mix(in srgb, ${monitorSettings.cssColorFinal} ${gpu.gpu_temperature}%, ${monitorSettings.cssColor})`;
}
}
else {
}
});
}
});
Object.defineProperty(this, "updateMonitor", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings, percent) => {
if (!(monitorSettings.htmlMonitorSliderRef && monitorSettings.htmlMonitorLabelRef)) {
return;
}
monitorSettings.htmlMonitorLabelRef.innerHTML = `${Math.floor(percent)}${monitorSettings.symbol}`;
monitorSettings.htmlMonitorSliderRef.style.width = `${Math.floor(percent)}%`;
}
});
Object.defineProperty(this, "updateAllAnimationDuration", {
enumerable: true,
configurable: true,
writable: true,
value: (value) => {
this.updatedAnimationDuration(this.monitorCPUElement, value);
this.updatedAnimationDuration(this.monitorRAMElement, value);
this.updatedAnimationDuration(this.monitorHDDElement, value);
this.monitorGPUSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
this.monitorVRAMSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
this.monitorTemperatureSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
}
});
Object.defineProperty(this, "updatedAnimationDuration", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings, value) => {
const slider = monitorSettings.htmlMonitorSliderRef;
if (!slider) {
return;
}
slider.style.transition = `width ${value.toFixed(1)}s`;
}
});
Object.defineProperty(this, "createMonitor", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings) => {
if (!monitorSettings) {
return document.createElement('div');
}
const htmlMain = document.createElement('div');
htmlMain.setAttribute('id', monitorSettings.id);
htmlMain.style.margin = '2px 10px';
htmlMain.style.height = '12px';
htmlMain.style.position = 'relative';
htmlMain.style.display = 'flex';
htmlMain.style.alignItems = 'center';
htmlMain.style.flexDirection = 'row';
monitorSettings.htmlMonitorRef = htmlMain;
if (monitorSettings.title) {
htmlMain.title = monitorSettings.title;
}
const htmlMonitorText = document.createElement('div');
htmlMonitorText.style.width = '35px';
htmlMonitorText.style.fontSize = '10px';
htmlMonitorText.innerHTML = monitorSettings.label;
htmlMain.append(htmlMonitorText);
const htmlMonitorContent = document.createElement('div');
htmlMonitorContent.style.height = '100%';
htmlMonitorContent.style.flexGrow = '1';
htmlMonitorContent.style.position = 'relative';
htmlMonitorContent.style.backgroundColor = 'var(--bg-color)';
htmlMain.append(htmlMonitorContent);
const htmlMonitorSlider = document.createElement('div');
htmlMonitorSlider.style.position = 'absolute';
htmlMonitorSlider.style.height = '100%';
htmlMonitorSlider.style.width = '0';
if (monitorSettings.cssColorFinal) {
htmlMonitorSlider.style.backgroundColor =
`color-mix(in srgb, ${monitorSettings.cssColorFinal} 0%, ${monitorSettings.cssColor})`;
}
else {
htmlMonitorSlider.style.backgroundColor = monitorSettings.cssColor;
}
monitorSettings.htmlMonitorSliderRef = htmlMonitorSlider;
htmlMonitorContent.append(htmlMonitorSlider);
const htmlMonitorLabel = document.createElement('div');
htmlMonitorLabel.style.position = 'relative';
htmlMonitorLabel.style.width = '100%';
htmlMonitorLabel.style.color = 'var(--drag-text)';
htmlMonitorLabel.style.fontSize = '10px';
htmlMonitorLabel.innerHTML = '0%';
monitorSettings.htmlMonitorLabelRef = htmlMonitorLabel;
htmlMonitorContent.append(htmlMonitorLabel);
return monitorSettings.htmlMonitorRef;
this.crystoolsButtonGroup = new ComfyButtonGroup();
app.menu?.settingsGroup.element.before(this.crystoolsButtonGroup.element);
this.monitorUI = new MonitorUI(this.crystoolsButtonGroup.element, this.monitorCPUElement, this.monitorRAMElement, this.monitorHDDElement, this.monitorGPUSettings, this.monitorVRAMSettings, this.monitorTemperatureSettings, currentRate);
this.updateDisplay(this.menuDisplayOption);
this.registerListeners();
}
});
Object.defineProperty(this, "registerListeners", {
@@ -559,47 +651,14 @@ class CrystoolsMonitor {
if (event?.detail === undefined) {
return;
}
this.updateAllMonitors(event.detail);
this.monitorUI.updateDisplay(event.detail);
}, false);
}
});
this.createSettings();
}
setup() {
const parentElement = document.getElementById('queue-button');
let ctoolsRoot = document.getElementById(this.htmlIdCrystoolsRoot);
if (!ctoolsRoot) {
ctoolsRoot = document.createElement('div');
ctoolsRoot.setAttribute('id', this.htmlIdCrystoolsRoot);
ctoolsRoot.style.display = 'flex';
ctoolsRoot.style.width = '100%';
ctoolsRoot.style.flexDirection = 'column';
parentElement?.insertAdjacentElement('afterend', ctoolsRoot);
}
const htmlContainer = document.createElement('div');
htmlContainer.setAttribute('id', this.htmlIdCrystoolsMonitorContainer);
htmlContainer.style.width = '100%';
htmlContainer.style.cursor = 'crosshair';
htmlContainer.style.order = '3';
htmlContainer.style.margin = '4px 0';
ctoolsRoot.append(htmlContainer);
htmlContainer.append(this.createMonitor(this.monitorCPUElement));
htmlContainer.append(this.createMonitor(this.monitorRAMElement));
this.monitorGPUSettings.forEach((_monitorSettings, index) => {
this.monitorGPUSettings[index] && htmlContainer.append(this.createMonitor(this.monitorGPUSettings[index]));
this.monitorVRAMSettings[index] && htmlContainer.append(this.createMonitor(this.monitorVRAMSettings[index]));
this.monitorTemperatureSettings[index] &&
htmlContainer.append(this.createMonitor(this.monitorTemperatureSettings[index]));
});
htmlContainer.append(this.createMonitor(this.monitorHDDElement));
const currentRate = parseFloat(app.ui.settings.getSettingValue(this.idInputRate, this.defaultRate));
this.updateAllAnimationDuration(currentRate);
this.updateAllWidget();
this.registerListeners();
}
}
const crystoolsMonitor = new CrystoolsMonitor();
app.registerExtension({
name: crystoolsMonitor.idExtensionName,
setup: crystoolsMonitor.setup.bind(crystoolsMonitor),
setup: crystoolsMonitor.setup,
});
+431 -438
View File
@@ -1,103 +1,50 @@
import { app } from '/scripts/app.js';
import { api } from '/scripts/api.js';
import { app, api, ComfyButtonGroup } from './comfy/index.js';
import { commonPrefix } from './common.js';
import { MonitorUI } from './monitorUI.js';
import { Colors } from './styles.js';
import { convertNumberToPascalCase } from './utils.js';
import { ComfyKeyMenuDisplayOption, MenuDisplayOptions } from './progressBarUIBase.js';
class CrystoolsMonitor {
idExtensionName = 'Crystools.monitor';
menuPrefix = commonPrefix;
htmlIdCrystoolsRoot = 'crystools-root';
htmlIdCrystoolsMonitorContainer = 'crystools-monitor-container';
readonly idExtensionName = 'Crystools.monitor';
private readonly menuPrefix = commonPrefix;
private menuDisplayOption: MenuDisplayOptions = MenuDisplayOptions.Disabled;
private crystoolsButtonGroup: ComfyButtonGroup = null;
idInputRate = 'Crystools.inputRate';
defaultRate = .5;
idWhichHDD = 'Crystools.whichHDD';
defaultWhichHDD = '/';
monitorGPUSettings: TMonitorSettings[] = [];
monitorVRAMSettings: TMonitorSettings[] = [];
monitorTemperatureSettings: TMonitorSettings[] = [];
private settingsRate: TMonitorSettings;
private settingsMonitorHeight: TMonitorSettings;
private settingsMonitorWidth: TMonitorSettings;
private monitorCPUElement: TMonitorSettings;
private monitorRAMElement: TMonitorSettings;
private monitorHDDElement: TMonitorSettings;
private settingsHDD: TMonitorSettings;
private monitorGPUSettings: TMonitorSettings[] = [];
private monitorVRAMSettings: TMonitorSettings[] = [];
private monitorTemperatureSettings: TMonitorSettings[] = [];
// CPU Variables
monitorCPUElement: TMonitorSettings = {
id: 'Crystools.switchCPU',
name: this.menuPrefix + ' [monitor] CPU Usage',
type: 'boolean',
label: 'CPU',
symbol: '%',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#0AA015',
onChange: async(value: boolean) => {
this.updateWidget(this.monitorCPUElement);
await this.updateServer({
switchCPU: value,
});
},
};
private monitorUI: MonitorUI;
// RAM Variables
monitorRAMElement: TMonitorSettings = {
id: 'Crystools.switchRAM',
name: this.menuPrefix + ' [monitor] RAM Used',
type: 'boolean',
label: 'RAM',
symbol: '%',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#07630D',
onChange: async(value: boolean) => {
this.updateWidget(this.monitorRAMElement);
await this.updateServer({
switchRAM: value,
});
},
};
private readonly monitorWidthId = 'Crystools.MonitorWidth';
private readonly monitorWidth = 60;
private readonly monitorHeightId = 'Crystools.MonitorHeight';
private readonly monitorHeight = 30;
// HDD Variables
monitorHDDElement: TMonitorSettings = {
id: 'Crystools.switchHDD',
name: this.menuPrefix + ' [monitor] HDD Used',
type: 'boolean',
label: 'HDD',
symbol: '%',
tooltip: 'See Partition to show (HDD)',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#730F92',
onChange: async(value: boolean) => {
this.updateWidget(this.monitorHDDElement);
await this.updateServer({
switchHDD: value,
});
},
};
constructor() {
this.createSettings();
}
createSettings = (): void => {
app.ui.settings.addSetting(this.monitorCPUElement);
app.ui.settings.addSetting(this.monitorRAMElement);
app.ui.settings.addSetting(this.monitorHDDElement);
app.ui.settings.addSetting({
id: this.idInputRate,
name: this.menuPrefix + '[monitor] Refresh rate',
createSettingsRate = (): void => {
this.settingsRate = {
id: 'Crystools.RefreshRate',
name: 'Refresh per second',
category: ['Crystools', this.menuPrefix + ' Configuration', 'refresh'],
tooltip: 'This is the time (in seconds) between each update of the monitors, 0 means no refresh',
type: 'slider',
attrs: {
min: 0,
max: 5,
step: 0.25,
max: 2,
step: .25,
},
defaultValue: this.defaultRate,
onChange: async(value: string) => {
defaultValue: .5,
// @ts-ignore
onChange: async(value: string): Promise<void> => {
let valueNumber: number;
try {
@@ -110,62 +57,313 @@ class CrystoolsMonitor {
return;
}
try {
await this.updateServer({
rate: valueNumber,
});
await this.updateServer({rate: valueNumber});
} catch (error) {
console.error(error);
return;
}
if (valueNumber === 0) {
this.updateAllMonitors({
cpu_utilization: 0,
device: 'cpu',
const data = {
cpu_utilization: 0,
device: 'cpu',
gpus: [
{
gpu_utilization: 0,
gpu_temperature: 0,
vram_total: 0,
vram_used: 0,
vram_used_percent: 0,
},
],
hdd_total: 0,
hdd_used: 0,
hdd_used_percent: 0,
ram_total: 0,
ram_used: 0,
ram_used_percent: 0,
});
gpus: [
{
gpu_utilization: 0,
gpu_temperature: 0,
vram_total: 0,
vram_used: 0,
vram_used_percent: 0,
},
],
hdd_total: 0,
hdd_used: 0,
hdd_used_percent: 0,
ram_total: 0,
ram_used: 0,
ram_used_percent: 0,
};
if (valueNumber === 0) {
this.monitorUI.updateDisplay(data);
}
this.updateAllAnimationDuration(valueNumber);
this.monitorUI?.updateAllAnimationDuration(valueNumber);
},
});
};
};
createSettingsCPU = (): void => {
// CPU Variables
this.monitorCPUElement = {
id: 'Crystools.ShowCpu',
name: 'CPU Usage',
category: ['Crystools', this.menuPrefix + ' Hardware', 'Cpu'],
type: 'boolean',
label: 'CPU',
symbol: '%',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.CPU,
// @ts-ignore
onChange: async(value: boolean): Promise<void> => {
this.updateWidget(this.monitorCPUElement);
await this.updateServer({switchCPU: value});
},
};
};
createSettingsRAM = (): void => {
// RAM Variables
this.monitorRAMElement = {
id: 'Crystools.ShowRam',
name: 'RAM Used',
category: ['Crystools', this.menuPrefix + ' Hardware', 'Ram'],
type: 'boolean',
label: 'RAM',
symbol: '%',
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.RAM,
// @ts-ignore
onChange: async(value: boolean): Promise<void> => {
this.updateWidget(this.monitorRAMElement);
await this.updateServer({switchRAM: value});
},
};
};
createSettingsGPUUsage = (name: string, index: number, moreThanOneGPU: boolean): void => {
if (name === undefined || index === undefined) {
console.warn('getGPUsFromServer: name or index undefined', name, index);
return;
}
let label = 'GPU ';
label += moreThanOneGPU ? index : '';
const monitorGPUNElement: TMonitorSettings = {
id: 'Crystools.ShowGpuUsage' + convertNumberToPascalCase(index),
name: ' Usage',
category: ['Crystools', `${this.menuPrefix} Show GPU [${index}] ${name}`, 'Usage'],
type: 'boolean',
label,
symbol: '%',
monitorTitle: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.GPU,
// @ts-ignore
onChange: async(value: boolean): Promise<void> => {
this.updateWidget(monitorGPUNElement);
void await this.updateServerGPU(index, {utilization: value});
},
};
this.monitorGPUSettings[index] = monitorGPUNElement;
app.ui.settings.addSetting(this.monitorGPUSettings[index]);
this.monitorUI.createDOMGPUMonitor(this.monitorGPUSettings[index]);
};
createSettingsGPUVRAM = (name: string, index: number, moreThanOneGPU: boolean): void => {
if (name === undefined || index === undefined) {
console.warn('getGPUsFromServer: name or index undefined', name, index);
return;
}
let label = 'VRAM ';
label += moreThanOneGPU ? index : '';
// GPU VRAM Variables
const monitorVRAMNElement: TMonitorSettings = {
id: 'Crystools.ShowGpuVram' + convertNumberToPascalCase(index),
name: 'VRAM',
category: ['Crystools', `${this.menuPrefix} Show GPU [${index}] ${name}`, 'VRAM'],
type: 'boolean',
label: label,
symbol: '%',
monitorTitle: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.VRAM,
// @ts-ignore
onChange: async(value: boolean): Promise<void> => {
this.updateWidget(monitorVRAMNElement);
void await this.updateServerGPU(index, {vram: value});
},
};
this.monitorVRAMSettings[index] = monitorVRAMNElement;
app.ui.settings.addSetting(this.monitorVRAMSettings[index]);
this.monitorUI.createDOMGPUMonitor(this.monitorVRAMSettings[index]);
};
createSettingsGPUTemp = (name: string, index: number, moreThanOneGPU: boolean): void => {
if (name === undefined || index === undefined) {
console.warn('getGPUsFromServer: name or index undefined', name, index);
return;
}
let label = 'Temp ';
label += moreThanOneGPU ? index : '';
// GPU Temperature Variables
const monitorTemperatureNElement: TMonitorSettings = {
id: 'Crystools.ShowGpuTemperature' + convertNumberToPascalCase(index),
name: 'Temperature',
category: ['Crystools', `${this.menuPrefix} Show GPU [${index}] ${name}`, 'Temperature'],
type: 'boolean',
label: label,
symbol: '°',
monitorTitle: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.TEMP_START,
cssColorFinal: Colors.TEMP_END,
// @ts-ignore
onChange: async(value: boolean): Promise<void> => {
this.updateWidget(monitorTemperatureNElement);
void await this.updateServerGPU(index, {temperature: value});
},
};
this.monitorTemperatureSettings[index] = monitorTemperatureNElement;
app.ui.settings.addSetting(this.monitorTemperatureSettings[index]);
this.monitorUI.createDOMGPUMonitor(this.monitorTemperatureSettings[index]);
};
createSettingsHDD = (): void => {
// HDD Variables
this.monitorHDDElement = {
id: 'Crystools.ShowHdd',
name: 'Show HDD Used',
category: ['Crystools', this.menuPrefix + ' Show Hard Disk', 'Show'],
type: 'boolean',
label: 'HDD',
symbol: '%',
// tooltip: 'See Partition to show (HDD)',
defaultValue: false,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: Colors.DISK,
// @ts-ignore
onChange: async(value: boolean): Promise<void> => {
this.updateWidget(this.monitorHDDElement);
await this.updateServer({switchHDD: value});
},
};
this.settingsHDD = {
id: 'Crystools.WhichHdd',
name: 'Partition to show',
category: ['Crystools', this.menuPrefix + ' Show Hard Disk', 'Which'],
type: 'combo',
defaultValue: '/',
data: [],
// @ts-ignore bad definition from comfyUI: `options?: undefined;`??
options: (value: string): any => {
const which = app.ui.settings.getSettingValue(this.settingsHDD.id, this.settingsHDD.defaultValue);
return this.settingsHDD.data.map((m: any) => ({
value: m,
text: m,
selected: !value ? m === which : m === value,
}));
},
// @ts-ignore
onChange: async(value: string): Promise<void> => {
await this.updateServer({whichHDD: value});
},
};
};
createSettingsMonitorWidth = (): void => {
this.settingsMonitorWidth = {
id: this.monitorWidthId,
name: 'Pixel Width',
category: ['Crystools', this.menuPrefix + ' Configuration', 'width'],
tooltip: 'The width of the monitor in pixels on the UI (only on floating UI)',
type: 'slider',
attrs: {
min: 60,
max: 100,
step: 1,
},
defaultValue: this.monitorWidth,
// @ts-ignore
onChange: (value: string): void => {
let valueNumber: number;
try {
valueNumber = parseInt(value);
if (isNaN(valueNumber)) {
throw new Error('invalid value');
}
} catch (error) {
console.error(error);
return;
}
const h = app.ui.settings.getSettingValue(this.monitorHeightId, this.monitorHeight);
this.monitorUI?.updateMonitorSize(valueNumber, h);
},
};
};
createSettingsMonitorHeight = (): void => {
this.settingsMonitorHeight = {
id: this.monitorHeightId,
name: 'Pixel Height',
category: ['Crystools', this.menuPrefix + ' Configuration', 'height'],
tooltip: 'The height of the monitor in pixels on the UI (only on floating UI)',
type: 'slider',
attrs: {
min: 16,
max: 50,
step: 1,
},
defaultValue: this.monitorHeight,
// @ts-ignore
onChange: async(value: string): void => {
let valueNumber: number;
try {
valueNumber = parseInt(value);
if (isNaN(valueNumber)) {
throw new Error('invalid value');
}
} catch (error) {
console.error(error);
return;
}
const w = await app.ui.settings.getSettingValue(this.monitorWidthId, this.monitorWidth);
this.monitorUI?.updateMonitorSize(w, valueNumber);
},
};
};
createSettings = (): void => {
app.ui.settings.addSetting(this.settingsRate);
app.ui.settings.addSetting(this.settingsMonitorHeight);
app.ui.settings.addSetting(this.settingsMonitorWidth);
app.ui.settings.addSetting(this.monitorRAMElement);
app.ui.settings.addSetting(this.monitorCPUElement);
void this.getHDDsFromServer().then((data: string[]): void => {
const which = app.ui.settings.getSettingValue(this.idWhichHDD, this.defaultWhichHDD);
app.ui.settings.addSetting({
id: this.idWhichHDD,
name: this.menuPrefix + '[monitor] Partition to show',
type: 'combo',
defaultValue: this.defaultWhichHDD,
// @ts-ignore bad definition from comfyUI: `options?: undefined;`??
options: (value: string) =>
data.map((m) => ({
value: m,
text: m,
selected: !value ? m === which : m === value,
})),
onChange: async(value: string) => {
await this.updateServer({
whichHDD: value,
});
},
});
this.settingsHDD.data = data;
app.ui.settings.addSetting(this.settingsHDD);
});
app.ui.settings.addSetting(this.monitorHDDElement);
void this.getGPUsFromServer().then((gpus: TGpuName[]): void => {
let moreThanOneGPU = false;
@@ -173,101 +371,79 @@ class CrystoolsMonitor {
moreThanOneGPU = true;
}
gpus?.forEach(({
name, index
}) => {
if(name === undefined || index === undefined) {
console.warn('getGPUsFromServer: name or index undefined', name, index);
return;
}
let label = 'GPU';
let labelVRAM = 'VRAM';
let labelTemperature = 'Temp';
if (moreThanOneGPU) {
label = 'GPU '+index;
labelVRAM = 'VRAM'+index;
labelTemperature = 'Temp '+index;
}
// GPU Utilization Variables
const monitorGPUNElement: TMonitorSettings = {
id: 'Crystools.switchGPU' + index,
name: this.menuPrefix + `[menu] Display GPU\r\n[${index}] ${name}`,
type: 'boolean',
label,
symbol: '%',
title: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#0C86F4',
onChange: async(value: boolean) => {
this.updateWidget(monitorGPUNElement);
void await this.updateServerGPU(index,{
utilization: value
});
},
};
// GPU VRAM Variables
const monitorVRAMNElement: TMonitorSettings = {
id: 'Crystools.switchVRAM' + index,
name: this.menuPrefix + `[menu] Display GPU VRAM\r\n[${index}] ${name}`,
type: 'boolean',
label: labelVRAM,
symbol: '%',
title: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#176EC7',
onChange: async(value: boolean) => {
this.updateWidget(monitorVRAMNElement);
void await this.updateServerGPU(index,{
vram: value
});
},
};
// GPU Temperature Variables
const monitorTemperatureNElement: TMonitorSettings = {
id: 'Crystools.switchTemperature' + index,
name: this.menuPrefix + `[menu] Display GPU Temperature\r\n[${index}] ${name}`,
type: 'boolean',
label: labelTemperature,
symbol: '°',
title: `${index}: ${name}`,
defaultValue: true,
htmlMonitorRef: undefined,
htmlMonitorSliderRef: undefined,
htmlMonitorLabelRef: undefined,
cssColor: '#00ff00',
cssColorFinal: '#ff0000',
onChange: async(value: boolean) => {
this.updateWidget(monitorTemperatureNElement);
void await this.updateServerGPU(index,{
temperature: value
});
},
};
this.monitorGPUSettings[index] = monitorGPUNElement;
this.monitorVRAMSettings[index] = monitorVRAMNElement;
this.monitorTemperatureSettings[index] = monitorTemperatureNElement;
// @ts-ignore
app.ui.settings.addSetting(this.monitorGPUSettings[index]);
// @ts-ignore
app.ui.settings.addSetting(this.monitorVRAMSettings[index]);
// @ts-ignore
app.ui.settings.addSetting(this.monitorTemperatureSettings[index]);
gpus?.forEach(({name, index}) => {
this.createSettingsGPUTemp(name, index, moreThanOneGPU);
this.createSettingsGPUVRAM(name, index, moreThanOneGPU);
this.createSettingsGPUUsage(name, index, moreThanOneGPU);
});
this.finishedLoad();
});
};
finishedLoad = (): void => {
this.monitorUI.orderMonitors();
this.updateAllWidget();
this.moveMonitor(this.menuDisplayOption);
const w = app.ui.settings.getSettingValue(this.monitorWidthId, this.monitorWidth);
const h = app.ui.settings.getSettingValue(this.monitorHeightId, this.monitorHeight);
this.monitorUI.updateMonitorSize(w, h);
};
updateDisplay = (value: MenuDisplayOptions): void => {
if (value !== this.menuDisplayOption) {
this.menuDisplayOption = value;
this.moveMonitor(this.menuDisplayOption);
}
};
moveMonitor = (position: MenuDisplayOptions): void => {
let parentElement: Element | null | undefined;
switch (position) {
case MenuDisplayOptions.Disabled:
parentElement = document.getElementById('queue-button');
if (parentElement && this.monitorUI.rootElement) {
parentElement.insertAdjacentElement('afterend', this.crystoolsButtonGroup.element);
} else {
console.error('Crystools: parentElement to move monitors not found!', parentElement);
}
break;
case MenuDisplayOptions.Top:
case MenuDisplayOptions.Bottom:
app.menu?.settingsGroup.element.before(this.crystoolsButtonGroup.element);
}
};
updateAllWidget = (): void => {
this.updateWidget(this.monitorCPUElement);
this.updateWidget(this.monitorRAMElement);
this.updateWidget(this.monitorHDDElement);
this.monitorGPUSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
this.monitorVRAMSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
this.monitorTemperatureSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
};
/**
* for the settings menu
* @param monitorSettings
*/
updateWidget = (monitorSettings: TMonitorSettings): void => {
if (this.monitorUI) {
const value = app.ui.settings.getSettingValue(monitorSettings.id, monitorSettings.defaultValue);
this.monitorUI.showMonitor(monitorSettings, value);
}
};
updateServer = async(data: TStatsSettings): Promise<string> => {
const resp = await api.fetchApi('/crystools/monitor', {
@@ -301,7 +477,7 @@ class CrystoolsMonitor {
return this.getDataFromServer<TGpuName>('GPU');
};
getDataFromServer = async<T>(what: string): Promise<T[]> => {
getDataFromServer = async <T>(what: string): Promise<T[]> => {
const resp = await api.fetchApi(`/crystools/monitor/${what}`, {
method: 'GET',
cache: 'no-store',
@@ -312,227 +488,44 @@ class CrystoolsMonitor {
throw new Error(resp.statusText);
};
updateAllWidget = (): void => {
this.updateWidget(this.monitorCPUElement);
this.updateWidget(this.monitorRAMElement);
this.updateWidget(this.monitorHDDElement);
this.monitorGPUSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
this.monitorVRAMSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
this.monitorTemperatureSettings.forEach((monitorSettings) => {
monitorSettings && this.updateWidget(monitorSettings);
});
};
updateWidget = (monitorSettings: TMonitorSettings): void => {
const value = app.ui.settings.getSettingValue(monitorSettings.id, monitorSettings.defaultValue);
if (monitorSettings.htmlMonitorRef) {
monitorSettings.htmlMonitorRef.style.display = value ? 'flex' : 'none';
}
};
updateAllMonitors = (data: TStatsData): void => {
// console.log('updateAllMonitors', data);
this.updateMonitor(this.monitorCPUElement, data.cpu_utilization);
this.updateMonitor(this.monitorRAMElement, data.ram_used_percent);
this.updateMonitor(this.monitorHDDElement, data.hdd_used_percent);
if (data.gpus === undefined || data.gpus.length === 0) {
console.warn('UpdateAllMonitors: no GPU data');
setup = (): void => {
if (this.monitorUI) {
return;
}
this.monitorGPUSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
// console.error('UpdateAllMonitors: no GPU data for index', index);
return;
}
this.createSettingsRate();
this.createSettingsMonitorHeight();
this.createSettingsMonitorWidth();
this.createSettingsCPU();
this.createSettingsRAM();
this.createSettingsHDD();
this.createSettings();
this.updateMonitor(monitorSettings, gpu.gpu_utilization);
} else {
// console.error('UpdateAllMonitors: no GPU data for index', index);
}
});
const currentRate =
parseFloat(app.ui.settings.getSettingValue(this.settingsRate.id, this.settingsRate.defaultValue));
this.monitorVRAMSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
// console.error('UpdateAllMonitors: no GPU VRAM data for index', index);
return;
}
this.menuDisplayOption = app.ui.settings.getSettingValue(ComfyKeyMenuDisplayOption, MenuDisplayOptions.Disabled);
app.ui.settings.addEventListener(`${ComfyKeyMenuDisplayOption}.change`, (e: any) => {
this.updateDisplay(e.detail.value);
},
);
this.updateMonitor(monitorSettings, gpu.vram_used_percent);
} else {
// console.error('UpdateAllMonitors: no GPU VRAM data for index', index);
}
});
this.crystoolsButtonGroup = new ComfyButtonGroup();
app.menu?.settingsGroup.element.before(this.crystoolsButtonGroup.element);
this.monitorTemperatureSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
// console.error('UpdateAllMonitors: no GPU VRAM data for index', index);
return;
}
this.monitorUI = new MonitorUI(
this.crystoolsButtonGroup.element,
this.monitorCPUElement,
this.monitorRAMElement,
this.monitorHDDElement,
this.monitorGPUSettings,
this.monitorVRAMSettings,
this.monitorTemperatureSettings,
currentRate,
);
this.updateMonitor(monitorSettings, gpu.gpu_temperature);
if(monitorSettings.cssColorFinal && monitorSettings.htmlMonitorSliderRef){
monitorSettings.htmlMonitorSliderRef.style.backgroundColor =
`color-mix(in srgb, ${monitorSettings.cssColorFinal} ${gpu.gpu_temperature}%, ${monitorSettings.cssColor})`;
}
} else {
// console.error('UpdateAllMonitors: no GPU VRAM data for index', index);
}
});
};
updateMonitor = (monitorSettings: TMonitorSettings, percent: number): void => {
if (!(monitorSettings.htmlMonitorSliderRef && monitorSettings.htmlMonitorLabelRef)) {
return;
}
monitorSettings.htmlMonitorLabelRef.innerHTML = `${Math.floor(percent)}${monitorSettings.symbol}`;
monitorSettings.htmlMonitorSliderRef.style.width = `${Math.floor(percent)}%`;
};
updateAllAnimationDuration = (value: number): void => {
this.updatedAnimationDuration(this.monitorCPUElement, value);
this.updatedAnimationDuration(this.monitorRAMElement, value);
this.updatedAnimationDuration(this.monitorHDDElement, value);
this.monitorGPUSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
this.monitorVRAMSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
this.monitorTemperatureSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
};
updatedAnimationDuration = (monitorSettings: TMonitorSettings, value: number): void => {
const slider = monitorSettings.htmlMonitorSliderRef;
if (!slider) {
return;
}
slider.style.transition = `width ${value.toFixed(1)}s`;
};
setup(): void {
const parentElement = document.getElementById('queue-button');
let ctoolsRoot = document.getElementById(this.htmlIdCrystoolsRoot);
if (!ctoolsRoot) {
ctoolsRoot = document.createElement('div');
ctoolsRoot.setAttribute('id', this.htmlIdCrystoolsRoot);
ctoolsRoot.style.display = 'flex';
ctoolsRoot.style.width = '100%';
ctoolsRoot.style.flexDirection = 'column';
parentElement?.insertAdjacentElement('afterend', ctoolsRoot);
}
const htmlContainer = document.createElement('div');
htmlContainer.setAttribute('id', this.htmlIdCrystoolsMonitorContainer);
htmlContainer.style.width = '100%';
htmlContainer.style.cursor = 'crosshair';
htmlContainer.style.order = '3';
htmlContainer.style.margin = '4px 0';
ctoolsRoot.append(htmlContainer);
htmlContainer.append(this.createMonitor(this.monitorCPUElement));
htmlContainer.append(this.createMonitor(this.monitorRAMElement));
// gpu0 > gpu1 > vram0 > vram1
// this.monitorGPUSettings.forEach((monitorSettings) => {
// monitorSettings && htmlContainer.append(this.createMonitor(monitorSettings));
// });
// this.monitorVRAMSettings.forEach((monitorSettings) => {
// monitorSettings && htmlContainer.append(this.createMonitor(monitorSettings));
// });
// this.monitorTemperatureSettings.forEach((monitorSettings) => {
// monitorSettings && htmlContainer.append(this.createMonitor(monitorSettings));
// });
// gpu0 > vram0 > gpu1 > vram1
this.monitorGPUSettings.forEach((_monitorSettings, index) => {
this.monitorGPUSettings[index] && htmlContainer.append(this.createMonitor(this.monitorGPUSettings[index]));
this.monitorVRAMSettings[index] && htmlContainer.append(this.createMonitor(this.monitorVRAMSettings[index]));
this.monitorTemperatureSettings[index] &&
htmlContainer.append(this.createMonitor(this.monitorTemperatureSettings[index]));
});
htmlContainer.append(this.createMonitor(this.monitorHDDElement));
const currentRate = parseFloat(app.ui.settings.getSettingValue(this.idInputRate, this.defaultRate));
this.updateAllAnimationDuration(currentRate);
this.updateAllWidget();
this.updateDisplay(this.menuDisplayOption);
this.registerListeners();
}
createMonitor = (monitorSettings?: TMonitorSettings): HTMLDivElement => {
if (!monitorSettings) {
// just for typescript
return document.createElement('div');
}
const htmlMain = document.createElement('div');
htmlMain.setAttribute('id', monitorSettings.id);
htmlMain.style.margin = '2px 10px';
htmlMain.style.height = '12px';
htmlMain.style.position = 'relative';
htmlMain.style.display = 'flex';
htmlMain.style.alignItems = 'center';
htmlMain.style.flexDirection = 'row';
monitorSettings.htmlMonitorRef = htmlMain;
if (monitorSettings.title) {
htmlMain.title = monitorSettings.title;
}
const htmlMonitorText = document.createElement('div');
htmlMonitorText.style.width = '35px';
htmlMonitorText.style.fontSize = '10px';
htmlMonitorText.innerHTML = monitorSettings.label;
htmlMain.append(htmlMonitorText);
const htmlMonitorContent = document.createElement('div');
htmlMonitorContent.style.height = '100%';
htmlMonitorContent.style.flexGrow = '1';
htmlMonitorContent.style.position = 'relative';
htmlMonitorContent.style.backgroundColor = 'var(--bg-color)';
htmlMain.append(htmlMonitorContent);
const htmlMonitorSlider = document.createElement('div');
htmlMonitorSlider.style.position = 'absolute';
htmlMonitorSlider.style.height = '100%';
htmlMonitorSlider.style.width = '0';
if(monitorSettings.cssColorFinal){
htmlMonitorSlider.style.backgroundColor =
`color-mix(in srgb, ${monitorSettings.cssColorFinal} 0%, ${monitorSettings.cssColor})`;
} else {
htmlMonitorSlider.style.backgroundColor = monitorSettings.cssColor;
}
monitorSettings.htmlMonitorSliderRef = htmlMonitorSlider;
htmlMonitorContent.append(htmlMonitorSlider);
const htmlMonitorLabel = document.createElement('div');
htmlMonitorLabel.style.position = 'relative';
htmlMonitorLabel.style.width = '100%';
htmlMonitorLabel.style.color = 'var(--drag-text)';
htmlMonitorLabel.style.fontSize = '10px';
htmlMonitorLabel.innerHTML = '0%';
monitorSettings.htmlMonitorLabelRef = htmlMonitorLabel;
htmlMonitorContent.append(htmlMonitorLabel);
return monitorSettings.htmlMonitorRef;
};
registerListeners = (): void => {
@@ -540,7 +533,7 @@ class CrystoolsMonitor {
if (event?.detail === undefined) {
return;
}
this.updateAllMonitors(event.detail);
this.monitorUI.updateDisplay(event.detail);
}, false);
};
}
@@ -548,5 +541,5 @@ class CrystoolsMonitor {
const crystoolsMonitor = new CrystoolsMonitor();
app.registerExtension({
name: crystoolsMonitor.idExtensionName,
setup: crystoolsMonitor.setup.bind(crystoolsMonitor),
setup: crystoolsMonitor.setup,
});
+24
View File
@@ -0,0 +1,24 @@
import { ProgressBarUIBase } from './progressBarUIBase.js';
export declare class MonitorUI extends ProgressBarUIBase {
rootElement: HTMLElement;
private monitorCPUElement;
private monitorRAMElement;
private monitorHDDElement;
private monitorGPUSettings;
private monitorVRAMSettings;
private monitorTemperatureSettings;
private currentRate;
lastMonitor: number;
styleSheet: HTMLStyleElement;
constructor(rootElement: HTMLElement, monitorCPUElement: TMonitorSettings, monitorRAMElement: TMonitorSettings, monitorHDDElement: TMonitorSettings, monitorGPUSettings: TMonitorSettings[], monitorVRAMSettings: TMonitorSettings[], monitorTemperatureSettings: TMonitorSettings[], currentRate: number);
createDOM: () => void;
createDOMGPUMonitor: (monitorSettings?: TMonitorSettings) => void;
orderMonitors: () => void;
updateDisplay: (data: TStatsData) => void;
updateMonitor: (monitorSettings: TMonitorSettings, percent: number, used?: number, total?: number) => void;
updateAllAnimationDuration: (value: number) => void;
updatedAnimationDuration: (monitorSettings: TMonitorSettings, value: number) => void;
createMonitor: (monitorSettings?: TMonitorSettings) => HTMLDivElement;
updateMonitorSize: (width: number, height: number) => void;
showMonitor: (monitorSettings: TMonitorSettings, value: boolean) => void;
}
+281
View File
@@ -0,0 +1,281 @@
import { ProgressBarUIBase } from './progressBarUIBase.js';
import { createStyleSheet, formatBytes } from './utils.js';
export class MonitorUI extends ProgressBarUIBase {
constructor(rootElement, monitorCPUElement, monitorRAMElement, monitorHDDElement, monitorGPUSettings, monitorVRAMSettings, monitorTemperatureSettings, currentRate) {
super('crystools-monitors-root', rootElement);
Object.defineProperty(this, "rootElement", {
enumerable: true,
configurable: true,
writable: true,
value: rootElement
});
Object.defineProperty(this, "monitorCPUElement", {
enumerable: true,
configurable: true,
writable: true,
value: monitorCPUElement
});
Object.defineProperty(this, "monitorRAMElement", {
enumerable: true,
configurable: true,
writable: true,
value: monitorRAMElement
});
Object.defineProperty(this, "monitorHDDElement", {
enumerable: true,
configurable: true,
writable: true,
value: monitorHDDElement
});
Object.defineProperty(this, "monitorGPUSettings", {
enumerable: true,
configurable: true,
writable: true,
value: monitorGPUSettings
});
Object.defineProperty(this, "monitorVRAMSettings", {
enumerable: true,
configurable: true,
writable: true,
value: monitorVRAMSettings
});
Object.defineProperty(this, "monitorTemperatureSettings", {
enumerable: true,
configurable: true,
writable: true,
value: monitorTemperatureSettings
});
Object.defineProperty(this, "currentRate", {
enumerable: true,
configurable: true,
writable: true,
value: currentRate
});
Object.defineProperty(this, "lastMonitor", {
enumerable: true,
configurable: true,
writable: true,
value: 1
});
Object.defineProperty(this, "styleSheet", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "createDOM", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
if (!this.rootElement) {
throw Error('Crystools: MonitorUI - Container not found');
}
this.rootElement.appendChild(this.createMonitor(this.monitorCPUElement));
this.rootElement.appendChild(this.createMonitor(this.monitorRAMElement));
this.rootElement.appendChild(this.createMonitor(this.monitorHDDElement));
this.updateAllAnimationDuration(this.currentRate);
}
});
Object.defineProperty(this, "createDOMGPUMonitor", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings) => {
if (!(monitorSettings && this.rootElement)) {
return;
}
this.rootElement.appendChild(this.createMonitor(monitorSettings));
this.updateAllAnimationDuration(this.currentRate);
}
});
Object.defineProperty(this, "orderMonitors", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
try {
this.monitorCPUElement.htmlMonitorRef.style.order = '' + this.lastMonitor++;
this.monitorRAMElement.htmlMonitorRef.style.order = '' + this.lastMonitor++;
this.monitorGPUSettings.forEach((_monitorSettings, index) => {
this.monitorGPUSettings[index].htmlMonitorRef.style.order = '' + this.lastMonitor++;
this.monitorVRAMSettings[index].htmlMonitorRef.style.order = '' + this.lastMonitor++;
this.monitorTemperatureSettings[index].htmlMonitorRef.style.order = '' + this.lastMonitor++;
});
this.monitorHDDElement.htmlMonitorRef.style.order = '' + this.lastMonitor++;
}
catch (error) {
console.error('orderMonitors', error);
}
}
});
Object.defineProperty(this, "updateDisplay", {
enumerable: true,
configurable: true,
writable: true,
value: (data) => {
this.updateMonitor(this.monitorCPUElement, data.cpu_utilization);
this.updateMonitor(this.monitorRAMElement, data.ram_used_percent, data.ram_used, data.ram_total);
this.updateMonitor(this.monitorHDDElement, data.hdd_used_percent, data.hdd_used, data.hdd_total);
if (data.gpus === undefined || data.gpus.length === 0) {
console.warn('UpdateAllMonitors: no GPU data');
return;
}
this.monitorGPUSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
return;
}
this.updateMonitor(monitorSettings, gpu.gpu_utilization);
}
else {
}
});
this.monitorVRAMSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
return;
}
this.updateMonitor(monitorSettings, gpu.vram_used_percent, gpu.vram_used, gpu.vram_total);
}
else {
}
});
this.monitorTemperatureSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
return;
}
this.updateMonitor(monitorSettings, gpu.gpu_temperature);
if (monitorSettings.cssColorFinal && monitorSettings.htmlMonitorSliderRef) {
monitorSettings.htmlMonitorSliderRef.style.backgroundColor =
`color-mix(in srgb, ${monitorSettings.cssColorFinal} ${gpu.gpu_temperature}%, ${monitorSettings.cssColor})`;
}
}
else {
}
});
}
});
Object.defineProperty(this, "updateMonitor", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings, percent, used, total) => {
if (!(monitorSettings.htmlMonitorSliderRef && monitorSettings.htmlMonitorLabelRef)) {
return;
}
if (percent < 0) {
return;
}
const prefix = monitorSettings.monitorTitle ? monitorSettings.monitorTitle + ' - ' : '';
let title = `${Math.floor(percent)}${monitorSettings.symbol}`;
let postfix = '';
if (used !== undefined && total !== undefined) {
postfix = ` - ${formatBytes(used)} / ${formatBytes(total)} GB`;
}
title = `${prefix}${title}${postfix}`;
if (monitorSettings.htmlMonitorRef) {
monitorSettings.htmlMonitorRef.title = title;
}
monitorSettings.htmlMonitorLabelRef.innerHTML = `${Math.floor(percent)}${monitorSettings.symbol}`;
monitorSettings.htmlMonitorSliderRef.style.width = `${Math.floor(percent)}%`;
}
});
Object.defineProperty(this, "updateAllAnimationDuration", {
enumerable: true,
configurable: true,
writable: true,
value: (value) => {
this.updatedAnimationDuration(this.monitorCPUElement, value);
this.updatedAnimationDuration(this.monitorRAMElement, value);
this.updatedAnimationDuration(this.monitorHDDElement, value);
this.monitorGPUSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
this.monitorVRAMSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
this.monitorTemperatureSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
}
});
Object.defineProperty(this, "updatedAnimationDuration", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings, value) => {
const slider = monitorSettings.htmlMonitorSliderRef;
if (!slider) {
return;
}
slider.style.transition = `width ${value.toFixed(1)}s`;
}
});
Object.defineProperty(this, "createMonitor", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings) => {
if (!monitorSettings) {
return document.createElement('div');
}
const htmlMain = document.createElement('div');
htmlMain.classList.add(monitorSettings.id);
htmlMain.classList.add('crystools-monitor');
monitorSettings.htmlMonitorRef = htmlMain;
if (monitorSettings.title) {
htmlMain.title = monitorSettings.title;
}
const htmlMonitorText = document.createElement('div');
htmlMonitorText.classList.add('crystools-text');
htmlMonitorText.innerHTML = monitorSettings.label;
htmlMain.append(htmlMonitorText);
const htmlMonitorContent = document.createElement('div');
htmlMonitorContent.classList.add('crystools-content');
htmlMain.append(htmlMonitorContent);
const htmlMonitorSlider = document.createElement('div');
htmlMonitorSlider.classList.add('crystools-slider');
if (monitorSettings.cssColorFinal) {
htmlMonitorSlider.style.backgroundColor =
`color-mix(in srgb, ${monitorSettings.cssColorFinal} 0%, ${monitorSettings.cssColor})`;
}
else {
htmlMonitorSlider.style.backgroundColor = monitorSettings.cssColor;
}
monitorSettings.htmlMonitorSliderRef = htmlMonitorSlider;
htmlMonitorContent.append(htmlMonitorSlider);
const htmlMonitorLabel = document.createElement('div');
htmlMonitorLabel.classList.add('crystools-label');
monitorSettings.htmlMonitorLabelRef = htmlMonitorLabel;
htmlMonitorContent.append(htmlMonitorLabel);
htmlMonitorLabel.innerHTML = '0%';
return monitorSettings.htmlMonitorRef;
}
});
Object.defineProperty(this, "updateMonitorSize", {
enumerable: true,
configurable: true,
writable: true,
value: (width, height) => {
this.styleSheet.innerText = `.comfyui-menu #crystools-monitors-root .crystools-monitor .crystools-content {height: ${height}px; width: ${width}px;}`;
}
});
Object.defineProperty(this, "showMonitor", {
enumerable: true,
configurable: true,
writable: true,
value: (monitorSettings, value) => {
if (monitorSettings.htmlMonitorRef) {
monitorSettings.htmlMonitorRef.style.display = value ? 'flex' : 'none';
}
}
});
this.createDOM();
this.styleSheet = createStyleSheet('crystools-monitors-size');
}
}
+229
View File
@@ -0,0 +1,229 @@
import { ProgressBarUIBase } from './progressBarUIBase.js';
import { createStyleSheet, formatBytes } from './utils.js';
export class MonitorUI extends ProgressBarUIBase {
lastMonitor = 1; // just for order on monitors section
styleSheet: HTMLStyleElement;
constructor(
public override rootElement: HTMLElement,
private monitorCPUElement: TMonitorSettings,
private monitorRAMElement: TMonitorSettings,
private monitorHDDElement: TMonitorSettings,
private monitorGPUSettings: TMonitorSettings[],
private monitorVRAMSettings: TMonitorSettings[],
private monitorTemperatureSettings: TMonitorSettings[],
private currentRate: number,
) {
super('crystools-monitors-root', rootElement);
this.createDOM();
this.styleSheet = createStyleSheet('crystools-monitors-size');
}
createDOM = (): void => {
if (!this.rootElement) {
throw Error('Crystools: MonitorUI - Container not found');
}
// this.container.style.order = '2';
this.rootElement.appendChild(this.createMonitor(this.monitorCPUElement));
this.rootElement.appendChild(this.createMonitor(this.monitorRAMElement));
this.rootElement.appendChild(this.createMonitor(this.monitorHDDElement));
this.updateAllAnimationDuration(this.currentRate);
};
createDOMGPUMonitor = (monitorSettings?: TMonitorSettings): void => {
if (!(monitorSettings && this.rootElement)) {
return;
}
this.rootElement.appendChild(this.createMonitor(monitorSettings));
this.updateAllAnimationDuration(this.currentRate);
};
orderMonitors = (): void => {
try {
// @ts-ignore
this.monitorCPUElement.htmlMonitorRef.style.order = '' + this.lastMonitor++;
// @ts-ignore
this.monitorRAMElement.htmlMonitorRef.style.order = '' + this.lastMonitor++;
// @ts-ignore
this.monitorGPUSettings.forEach((_monitorSettings, index) => {
// @ts-ignore
this.monitorGPUSettings[index].htmlMonitorRef.style.order = '' + this.lastMonitor++;
// @ts-ignore
this.monitorVRAMSettings[index].htmlMonitorRef.style.order = '' + this.lastMonitor++;
// @ts-ignore
this.monitorTemperatureSettings[index].htmlMonitorRef.style.order = '' + this.lastMonitor++;
});
// @ts-ignore
this.monitorHDDElement.htmlMonitorRef.style.order = '' + this.lastMonitor++;
} catch (error) {
console.error('orderMonitors', error);
}
};
updateDisplay = (data: TStatsData): void => {
this.updateMonitor(this.monitorCPUElement, data.cpu_utilization);
this.updateMonitor(this.monitorRAMElement, data.ram_used_percent, data.ram_used, data.ram_total);
this.updateMonitor(this.monitorHDDElement, data.hdd_used_percent, data.hdd_used, data.hdd_total);
if (data.gpus === undefined || data.gpus.length === 0) {
console.warn('UpdateAllMonitors: no GPU data');
return;
}
this.monitorGPUSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
// console.error('UpdateAllMonitors: no GPU data for index', index);
return;
}
this.updateMonitor(monitorSettings, gpu.gpu_utilization);
} else {
// console.error('UpdateAllMonitors: no GPU data for index', index);
}
});
this.monitorVRAMSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
// console.error('UpdateAllMonitors: no GPU VRAM data for index', index);
return;
}
this.updateMonitor(monitorSettings, gpu.vram_used_percent, gpu.vram_used, gpu.vram_total);
} else {
// console.error('UpdateAllMonitors: no GPU VRAM data for index', index);
}
});
this.monitorTemperatureSettings.forEach((monitorSettings, index) => {
if (data.gpus[index]) {
const gpu = data.gpus[index];
if (gpu === undefined) {
// console.error('UpdateAllMonitors: no GPU VRAM data for index', index);
return;
}
this.updateMonitor(monitorSettings, gpu.gpu_temperature);
if (monitorSettings.cssColorFinal && monitorSettings.htmlMonitorSliderRef) {
monitorSettings.htmlMonitorSliderRef.style.backgroundColor =
`color-mix(in srgb, ${monitorSettings.cssColorFinal} ${gpu.gpu_temperature}%, ${monitorSettings.cssColor})`;
}
} else {
// console.error('UpdateAllMonitors: no GPU VRAM data for index', index);
}
});
};
updateMonitor = (monitorSettings: TMonitorSettings, percent: number, used?: number, total?: number): void => {
if (!(monitorSettings.htmlMonitorSliderRef && monitorSettings.htmlMonitorLabelRef)) {
return;
}
if (percent < 0) {
return;
}
// console.log('updateMonitor', monitorSettings.id, percent);
const prefix = monitorSettings.monitorTitle ? monitorSettings.monitorTitle + ' - ' : '';
let title = `${Math.floor(percent)}${monitorSettings.symbol}`;
let postfix = '';
if (used !== undefined && total !== undefined) {
postfix = ` - ${formatBytes(used)} / ${formatBytes(total)} GB`;
}
title = `${prefix}${title}${postfix}`;
if (monitorSettings.htmlMonitorRef) {
monitorSettings.htmlMonitorRef.title = title;
}
monitorSettings.htmlMonitorLabelRef.innerHTML = `${Math.floor(percent)}${monitorSettings.symbol}`;
monitorSettings.htmlMonitorSliderRef.style.width = `${Math.floor(percent)}%`;
};
updateAllAnimationDuration = (value: number): void => {
this.updatedAnimationDuration(this.monitorCPUElement, value);
this.updatedAnimationDuration(this.monitorRAMElement, value);
this.updatedAnimationDuration(this.monitorHDDElement, value);
this.monitorGPUSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
this.monitorVRAMSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
this.monitorTemperatureSettings.forEach((monitorSettings) => {
monitorSettings && this.updatedAnimationDuration(monitorSettings, value);
});
};
updatedAnimationDuration = (monitorSettings: TMonitorSettings, value: number): void => {
const slider = monitorSettings.htmlMonitorSliderRef;
if (!slider) {
return;
}
slider.style.transition = `width ${value.toFixed(1)}s`;
};
createMonitor = (monitorSettings?: TMonitorSettings): HTMLDivElement => {
if (!monitorSettings) {
// just for typescript
return document.createElement('div');
}
const htmlMain = document.createElement('div');
htmlMain.classList.add(monitorSettings.id);
htmlMain.classList.add('crystools-monitor');
monitorSettings.htmlMonitorRef = htmlMain;
if (monitorSettings.title) {
htmlMain.title = monitorSettings.title;
}
const htmlMonitorText = document.createElement('div');
htmlMonitorText.classList.add('crystools-text');
htmlMonitorText.innerHTML = monitorSettings.label;
htmlMain.append(htmlMonitorText);
const htmlMonitorContent = document.createElement('div');
htmlMonitorContent.classList.add('crystools-content');
htmlMain.append(htmlMonitorContent);
const htmlMonitorSlider = document.createElement('div');
htmlMonitorSlider.classList.add('crystools-slider');
if (monitorSettings.cssColorFinal) {
htmlMonitorSlider.style.backgroundColor =
`color-mix(in srgb, ${monitorSettings.cssColorFinal} 0%, ${monitorSettings.cssColor})`;
} else {
htmlMonitorSlider.style.backgroundColor = monitorSettings.cssColor;
}
monitorSettings.htmlMonitorSliderRef = htmlMonitorSlider;
htmlMonitorContent.append(htmlMonitorSlider);
const htmlMonitorLabel = document.createElement('div');
htmlMonitorLabel.classList.add('crystools-label');
monitorSettings.htmlMonitorLabelRef = htmlMonitorLabel;
htmlMonitorContent.append(htmlMonitorLabel);
htmlMonitorLabel.innerHTML = '0%';
return monitorSettings.htmlMonitorRef;
};
updateMonitorSize = (width: number, height: number): void => {
// eslint-disable-next-line max-len
this.styleSheet.innerText = `.comfyui-menu #crystools-monitors-root .crystools-monitor .crystools-content {height: ${height}px; width: ${width}px;}`;
};
showMonitor = (monitorSettings: TMonitorSettings, value: boolean): void => {
if (monitorSettings.htmlMonitorRef) {
monitorSettings.htmlMonitorRef.style.display = value ? 'flex' : 'none';
}
};
}
+58 -118
View File
@@ -1,12 +1,7 @@
import { api } from '/scripts/api.js';
import { app } from '/scripts/app.js';
import { app, api } from './comfy/index.js';
import { commonPrefix } from './common.js';
var EStatus;
(function (EStatus) {
EStatus["executing"] = "Executing";
EStatus["executed"] = "Executed";
EStatus["execution_error"] = "Execution error";
})(EStatus || (EStatus = {}));
import { ProgressBarUI } from './progressBarUI.js';
import { ComfyKeyMenuDisplayOption, EStatus, MenuDisplayOptions } from './progressBarUIBase.js';
class CrystoolsProgressBar {
constructor() {
Object.defineProperty(this, "idExtensionName", {
@@ -19,7 +14,7 @@ class CrystoolsProgressBar {
enumerable: true,
configurable: true,
writable: true,
value: 'Crystools.showStatus'
value: 'Crystools.ProgressBar'
});
Object.defineProperty(this, "defaultShowStatus", {
enumerable: true,
@@ -33,17 +28,11 @@ class CrystoolsProgressBar {
writable: true,
value: commonPrefix
});
Object.defineProperty(this, "htmlIdCrystoolsRoot", {
Object.defineProperty(this, "menuDisplayOption", {
enumerable: true,
configurable: true,
writable: true,
value: 'crystools-root'
});
Object.defineProperty(this, "htmlIdCrystoolsProgressBarContainer", {
enumerable: true,
configurable: true,
writable: true,
value: 'crystools-progress-bar-container'
value: MenuDisplayOptions.Disabled
});
Object.defineProperty(this, "currentStatus", {
enumerable: true,
@@ -69,17 +58,11 @@ class CrystoolsProgressBar {
writable: true,
value: 0
});
Object.defineProperty(this, "htmlProgressSliderRef", {
Object.defineProperty(this, "progressBarUI", {
enumerable: true,
configurable: true,
writable: true,
value: undefined
});
Object.defineProperty(this, "htmlProgressLabelRef", {
enumerable: true,
configurable: true,
writable: true,
value: undefined
value: void 0
});
Object.defineProperty(this, "createSettings", {
enumerable: true,
@@ -88,50 +71,58 @@ class CrystoolsProgressBar {
value: () => {
app.ui.settings.addSetting({
id: this.idShowProgressBar,
name: this.menuPrefix + '[monitor] Show progress bar',
name: 'Show progress bar',
category: ['Crystools', this.menuPrefix + ' Progress Bar', 'Show'],
tooltip: 'This apply only on "Disabled" (old) menu',
type: 'boolean',
defaultValue: this.defaultShowStatus,
onChange: this.showProgressBar,
onChange: this.progressBarUI.showProgressBar,
});
}
});
Object.defineProperty(this, "showProgressBar", {
enumerable: true,
configurable: true,
writable: true,
value: (value) => {
const container = document.getElementById(this.htmlIdCrystoolsProgressBarContainer);
if (container) {
container.style.display = value ? 'block' : 'none';
}
}
});
Object.defineProperty(this, "updateDisplay", {
enumerable: true,
configurable: true,
writable: true,
value: (menuDisplayOption) => {
if (menuDisplayOption !== this.menuDisplayOption) {
this.menuDisplayOption = menuDisplayOption;
this.progressBarUI.showSection(this.menuDisplayOption === MenuDisplayOptions.Disabled);
}
if (this.menuDisplayOption === MenuDisplayOptions.Disabled && this.progressBarUI.showProgressBarFlag) {
console.log('updateDisplay progress bar');
this.progressBarUI.updateDisplay(this.currentStatus, this.timeStart, this.currentProgress);
}
}
});
Object.defineProperty(this, "setup", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
if (!(this.htmlProgressLabelRef && this.htmlProgressSliderRef)) {
console.error('htmlProgressLabelRef or htmlProgressSliderRef is undefined');
if (this.progressBarUI) {
this.progressBarUI
.showProgressBar(app.ui.settings.getSettingValue(this.idShowProgressBar, this.defaultShowStatus));
return;
}
if (this.currentStatus === EStatus.executed) {
this.htmlProgressLabelRef.innerHTML = 'cached';
const timeElapsed = Date.now() - this.timeStart;
if (this.timeStart > 0 && timeElapsed > 0) {
this.htmlProgressLabelRef.innerHTML = new Date(timeElapsed).toISOString().substr(11, 8);
}
this.htmlProgressSliderRef.style.width = '0';
this.menuDisplayOption = app.ui.settings.getSettingValue(ComfyKeyMenuDisplayOption, MenuDisplayOptions.Disabled);
app.ui.settings.addEventListener(`${ComfyKeyMenuDisplayOption}.change`, (e) => {
console.log('Comfy.UseNewMenu.change desde progress bar', e.detail.value);
this.updateDisplay(e.detail.value);
});
const progressBarElement = document.createElement('div');
progressBarElement.classList.add('crystools-monitors-container');
this.progressBarUI = new ProgressBarUI(progressBarElement, (this.menuDisplayOption === MenuDisplayOptions.Disabled), this.centerNode);
const parentElement = document.getElementById('queue-button');
if (parentElement) {
parentElement.insertAdjacentElement('afterend', progressBarElement);
}
else if (this.currentStatus === EStatus.execution_error) {
this.htmlProgressLabelRef.innerHTML = 'ERROR';
this.htmlProgressSliderRef.style.backgroundColor = 'var(--error-text)';
}
else if (this.currentStatus === EStatus.executing) {
this.htmlProgressLabelRef.innerHTML = `${this.currentProgress}%`;
this.htmlProgressSliderRef.style.width = this.htmlProgressLabelRef.innerHTML;
this.htmlProgressSliderRef.style.backgroundColor = 'green';
else {
console.error('Crystools: parentElement to move monitors not found!', parentElement);
}
this.createSettings();
this.updateDisplay(this.menuDisplayOption);
this.registerListeners();
}
});
Object.defineProperty(this, "registerListeners", {
@@ -139,37 +130,37 @@ class CrystoolsProgressBar {
configurable: true,
writable: true,
value: () => {
api.addEventListener('status', ({ detail, }) => {
api.addEventListener('status', ({ detail }) => {
this.currentStatus = this.currentStatus === EStatus.execution_error ? EStatus.execution_error : EStatus.executed;
const queueRemaining = detail?.exec_info.queue_remaining;
if (queueRemaining) {
this.currentStatus = EStatus.executing;
}
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
api.addEventListener('progress', ({ detail, }) => {
const { value, max, node, } = detail;
api.addEventListener('progress', ({ detail }) => {
const { value, max, node } = detail;
const progress = Math.floor((value / max) * 100);
if (!isNaN(progress) && progress >= 0 && progress <= 100) {
this.currentProgress = progress;
this.currentNode = node;
}
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
api.addEventListener('executed', ({ detail, }) => {
api.addEventListener('executed', ({ detail }) => {
if (detail?.node) {
this.currentNode = detail.node;
}
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
api.addEventListener('execution_start', ({ _detail, }) => {
api.addEventListener('execution_start', ({ _detail }) => {
this.currentStatus = EStatus.executing;
this.timeStart = Date.now();
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
api.addEventListener('execution_error', ({ _detail, }) => {
api.addEventListener('execution_error', ({ _detail }) => {
this.currentStatus = EStatus.execution_error;
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
}
});
@@ -189,61 +180,10 @@ class CrystoolsProgressBar {
app.canvas.centerOnNode(node);
}
});
this.createSettings();
}
setup() {
const parentElement = document.getElementById('queue-button');
if (!parentElement) {
console.error('queue-button not found');
return;
}
let ctoolsRoot = document.getElementById(this.htmlIdCrystoolsRoot);
if (!ctoolsRoot) {
ctoolsRoot = document.createElement('div');
ctoolsRoot.setAttribute('id', this.htmlIdCrystoolsRoot);
ctoolsRoot.style.display = 'flex';
ctoolsRoot.style.width = '100%';
ctoolsRoot.style.flexDirection = 'column';
parentElement.insertAdjacentElement('afterend', ctoolsRoot);
}
const htmlContainer = document.createElement('div');
htmlContainer.setAttribute('id', this.htmlIdCrystoolsProgressBarContainer);
htmlContainer.setAttribute('title', 'click to see the current working node');
htmlContainer.style.margin = '4px 0';
htmlContainer.style.width = '100%';
htmlContainer.style.cursor = 'pointer';
htmlContainer.style.order = '1';
htmlContainer.addEventListener('click', this.centerNode);
ctoolsRoot.append(htmlContainer);
const progressBar = document.createElement('div');
progressBar.style.margin = '0 10px';
progressBar.style.height = '18px';
progressBar.style.position = 'relative';
progressBar.style.backgroundColor = 'var(--bg-color)';
htmlContainer.append(progressBar);
const progressSlider = document.createElement('div');
progressSlider.style.position = 'absolute';
progressSlider.style.height = '100%';
progressSlider.style.width = '0';
progressSlider.style.transition = 'width 0.2s';
progressSlider.style.backgroundColor = 'green';
this.htmlProgressSliderRef = progressSlider;
progressBar.append(this.htmlProgressSliderRef);
const progressLabel = document.createElement('div');
progressLabel.style.position = 'absolute';
progressLabel.style.margin = 'auto 0';
progressLabel.style.width = '100%';
progressLabel.style.color = 'var(--drag-text)';
progressLabel.style.fontSize = '14px';
progressLabel.innerHTML = '0%';
this.htmlProgressLabelRef = progressLabel;
progressBar.append(this.htmlProgressLabelRef);
this.showProgressBar(app.ui.settings.getSettingValue(this.idShowProgressBar, this.defaultShowStatus));
this.registerListeners();
}
}
const crystoolsProgressBar = new CrystoolsProgressBar();
app.registerExtension({
name: crystoolsProgressBar.idExtensionName,
setup: crystoolsProgressBar.setup.bind(crystoolsProgressBar),
setup: crystoolsProgressBar.setup,
});
+55 -124
View File
@@ -1,158 +1,95 @@
import { api } from '/scripts/api.js';
import { app } from '/scripts/app.js';
import { app, api } from './comfy/index.js';
import { commonPrefix } from './common.js';
enum EStatus {
executing = 'Executing',
executed = 'Executed',
execution_error = 'Execution error',
}
import { ProgressBarUI } from './progressBarUI.js';
import { ComfyKeyMenuDisplayOption, EStatus, MenuDisplayOptions } from './progressBarUIBase.js';
class CrystoolsProgressBar {
idExtensionName = 'Crystools.progressBar';
idShowProgressBar = 'Crystools.showStatus';
idShowProgressBar = 'Crystools.ProgressBar';
defaultShowStatus = true;
menuPrefix = commonPrefix;
htmlIdCrystoolsRoot = 'crystools-root';
htmlIdCrystoolsProgressBarContainer = 'crystools-progress-bar-container';
menuDisplayOption: MenuDisplayOptions = MenuDisplayOptions.Disabled;
currentStatus = EStatus.executed;
currentProgress = 0;
currentNode?: number = undefined;
timeStart = 0;
htmlProgressSliderRef?: HTMLDivElement = undefined;
htmlProgressLabelRef?: HTMLDivElement = undefined;
constructor() {
this.createSettings();
}
progressBarUI: ProgressBarUI;
// not on setup because this affect the order on settings, I prefer to options at first
createSettings = (): void => {
app.ui.settings.addSetting({
id: this.idShowProgressBar,
name: this.menuPrefix + '[monitor] Show progress bar',
name: 'Show progress bar',
category: ['Crystools', this.menuPrefix + ' Progress Bar', 'Show'],
tooltip: 'This apply only on "Disabled" (old) menu',
type: 'boolean',
defaultValue: this.defaultShowStatus,
onChange: this.showProgressBar,
onChange: this.progressBarUI.showProgressBar,
});
};
showProgressBar = (value: boolean): void => {
const container = document.getElementById(this.htmlIdCrystoolsProgressBarContainer);
// validation because this run before setup
if (container) {
container.style.display = value ? 'block' : 'none';
updateDisplay = (menuDisplayOption: MenuDisplayOptions): void => {
if (menuDisplayOption !== this.menuDisplayOption) {
this.menuDisplayOption = menuDisplayOption;
this.progressBarUI.showSection(this.menuDisplayOption === MenuDisplayOptions.Disabled);
}
if (this.menuDisplayOption === MenuDisplayOptions.Disabled && this.progressBarUI.showProgressBarFlag) {
console.log('updateDisplay progress bar');
this.progressBarUI.updateDisplay(this.currentStatus, this.timeStart, this.currentProgress);
}
};
updateDisplay = (): void => {
if (!(this.htmlProgressLabelRef && this.htmlProgressSliderRef)) {
console.error('htmlProgressLabelRef or htmlProgressSliderRef is undefined');
// automatically called by ComfyUI
setup = (): void => {
if (this.progressBarUI) {
this.progressBarUI
.showProgressBar(app.ui.settings.getSettingValue(this.idShowProgressBar, this.defaultShowStatus));
return;
}
if (this.currentStatus === EStatus.executed) {
// finished
this.htmlProgressLabelRef.innerHTML = 'cached';
this.menuDisplayOption = app.ui.settings.getSettingValue(ComfyKeyMenuDisplayOption, MenuDisplayOptions.Disabled);
app.ui.settings.addEventListener(`${ComfyKeyMenuDisplayOption}.change`, (e: any) => {
console.log('Comfy.UseNewMenu.change desde progress bar', e.detail.value);
this.updateDisplay(e.detail.value);
},
);
const timeElapsed = Date.now() - this.timeStart;
if (this.timeStart > 0 && timeElapsed > 0) {
this.htmlProgressLabelRef.innerHTML = new Date(timeElapsed).toISOString().substr(11, 8);
}
this.htmlProgressSliderRef.style.width = '0';
const progressBarElement = document.createElement('div');
progressBarElement.classList.add('crystools-monitors-container');
} else if (this.currentStatus === EStatus.execution_error) {
// an error occurred
this.htmlProgressLabelRef.innerHTML = 'ERROR';
this.htmlProgressSliderRef.style.backgroundColor = 'var(--error-text)';
this.progressBarUI = new ProgressBarUI(
progressBarElement,
(this.menuDisplayOption === MenuDisplayOptions.Disabled),
this.centerNode,
);
} else if (this.currentStatus === EStatus.executing) {
// on going
this.htmlProgressLabelRef.innerHTML = `${this.currentProgress}%`;
this.htmlProgressSliderRef.style.width = this.htmlProgressLabelRef.innerHTML;
this.htmlProgressSliderRef.style.backgroundColor = 'green'; // by reset the color
}
};
setup(): void {
const parentElement = document.getElementById('queue-button');
if (!parentElement) {
console.error('queue-button not found');
return;
if (parentElement) {
parentElement.insertAdjacentElement('afterend', progressBarElement);
} else {
console.error('Crystools: parentElement to move monitors not found!', parentElement);
}
let ctoolsRoot = document.getElementById(this.htmlIdCrystoolsRoot);
if (!ctoolsRoot) {
ctoolsRoot = document.createElement('div');
ctoolsRoot.setAttribute('id', this.htmlIdCrystoolsRoot);
ctoolsRoot.style.display = 'flex';
ctoolsRoot.style.width = '100%';
ctoolsRoot.style.flexDirection = 'column';
parentElement.insertAdjacentElement('afterend', ctoolsRoot);
}
const htmlContainer = document.createElement('div');
htmlContainer.setAttribute('id', this.htmlIdCrystoolsProgressBarContainer);
htmlContainer.setAttribute('title', 'click to see the current working node');
htmlContainer.style.margin = '4px 0';
htmlContainer.style.width = '100%';
htmlContainer.style.cursor = 'pointer';
htmlContainer.style.order = '1';
htmlContainer.addEventListener('click', this.centerNode);
ctoolsRoot.append(htmlContainer);
const progressBar = document.createElement('div');
progressBar.style.margin = '0 10px';
progressBar.style.height = '18px';
progressBar.style.position = 'relative';
progressBar.style.backgroundColor = 'var(--bg-color)';
htmlContainer.append(progressBar);
const progressSlider = document.createElement('div');
progressSlider.style.position = 'absolute';
progressSlider.style.height = '100%';
progressSlider.style.width = '0';
progressSlider.style.transition = 'width 0.2s';
progressSlider.style.backgroundColor = 'green';
this.htmlProgressSliderRef = progressSlider;
progressBar.append(this.htmlProgressSliderRef);
const progressLabel = document.createElement('div');
progressLabel.style.position = 'absolute';
progressLabel.style.margin = 'auto 0';
progressLabel.style.width = '100%';
progressLabel.style.color = 'var(--drag-text)';
progressLabel.style.fontSize = '14px';
progressLabel.innerHTML = '0%';
this.htmlProgressLabelRef = progressLabel;
progressBar.append(this.htmlProgressLabelRef);
this.showProgressBar(app.ui.settings.getSettingValue(this.idShowProgressBar, this.defaultShowStatus));
this.createSettings();
this.updateDisplay(this.menuDisplayOption);
this.registerListeners();
}
};
registerListeners = (): void => {
api.addEventListener('status', ({
detail,
}: any) => {
api.addEventListener('status', ({detail}: any) => {
this.currentStatus = this.currentStatus === EStatus.execution_error ? EStatus.execution_error : EStatus.executed;
const queueRemaining = detail?.exec_info.queue_remaining;
if (queueRemaining) {
this.currentStatus = EStatus.executing;
}
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
api.addEventListener('progress', ({
detail,
}: any) => {
const {
value, max, node,
} = detail;
api.addEventListener('progress', ({detail}: any) => {
const {value, max, node} = detail;
const progress = Math.floor((value / max) * 100);
if (!isNaN(progress) && progress >= 0 && progress <= 100) {
@@ -160,34 +97,28 @@ class CrystoolsProgressBar {
this.currentNode = node;
}
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
api.addEventListener('executed', ({
detail,
}: any) => {
api.addEventListener('executed', ({detail}: any) => {
if (detail?.node) {
this.currentNode = detail.node;
}
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
api.addEventListener('execution_start', ({
_detail,
}: any) => {
api.addEventListener('execution_start', ({_detail}: any) => {
this.currentStatus = EStatus.executing;
this.timeStart = Date.now();
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
api.addEventListener('execution_error', ({
_detail,
}: any) => {
api.addEventListener('execution_error', ({_detail}: any) => {
this.currentStatus = EStatus.execution_error;
this.updateDisplay();
this.updateDisplay(this.menuDisplayOption);
}, false);
};
@@ -207,5 +138,5 @@ class CrystoolsProgressBar {
const crystoolsProgressBar = new CrystoolsProgressBar();
app.registerExtension({
name: crystoolsProgressBar.idExtensionName,
setup: crystoolsProgressBar.setup.bind(crystoolsProgressBar),
setup: crystoolsProgressBar.setup,
});
+18
View File
@@ -0,0 +1,18 @@
import { EStatus, ProgressBarUIBase } from './progressBarUIBase.js';
export declare class ProgressBarUI extends ProgressBarUIBase {
rootElement: HTMLElement;
showSectionFlag: boolean;
private centerNode;
htmlProgressSliderRef: HTMLDivElement;
htmlProgressLabelRef: HTMLDivElement;
currentStatus: EStatus;
timeStart: number;
currentProgress: number;
showProgressBarFlag: boolean;
constructor(rootElement: HTMLElement, showSectionFlag: boolean, centerNode: () => void);
createDOM: () => void;
updateDisplay: (currentStatus: EStatus, timeStart: number, currentProgress: number) => void;
showSection: (value: boolean) => void;
showProgressBar: (value: boolean) => void;
private displaySection;
}
+142
View File
@@ -0,0 +1,142 @@
import { EStatus, ProgressBarUIBase } from './progressBarUIBase.js';
export class ProgressBarUI extends ProgressBarUIBase {
constructor(rootElement, showSectionFlag, centerNode) {
super('crystools-progressBar-root', rootElement);
Object.defineProperty(this, "rootElement", {
enumerable: true,
configurable: true,
writable: true,
value: rootElement
});
Object.defineProperty(this, "showSectionFlag", {
enumerable: true,
configurable: true,
writable: true,
value: showSectionFlag
});
Object.defineProperty(this, "centerNode", {
enumerable: true,
configurable: true,
writable: true,
value: centerNode
});
Object.defineProperty(this, "htmlProgressSliderRef", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "htmlProgressLabelRef", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "currentStatus", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "timeStart", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "currentProgress", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "showProgressBarFlag", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "createDOM", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.rootElement.setAttribute('title', 'click to see the current working node');
this.rootElement.addEventListener('click', this.centerNode);
const progressBar = document.createElement('div');
progressBar.classList.add('crystools-progress-bar');
this.rootElement.append(progressBar);
const progressSlider = document.createElement('div');
this.htmlProgressSliderRef = progressSlider;
progressSlider.classList.add('crystools-slider');
progressBar.append(this.htmlProgressSliderRef);
const progressLabel = document.createElement('div');
progressLabel.classList.add('crystools-label');
progressLabel.innerHTML = '0%';
this.htmlProgressLabelRef = progressLabel;
progressBar.append(this.htmlProgressLabelRef);
}
});
Object.defineProperty(this, "updateDisplay", {
enumerable: true,
configurable: true,
writable: true,
value: (currentStatus, timeStart, currentProgress) => {
if (!(this.showSectionFlag && this.showProgressBarFlag)) {
return;
}
if (!(this.htmlProgressLabelRef && this.htmlProgressSliderRef)) {
console.error('htmlProgressLabelRef or htmlProgressSliderRef is undefined');
return;
}
this.currentStatus = currentStatus;
this.timeStart = timeStart;
this.currentProgress = currentProgress;
if (currentStatus === EStatus.executed) {
this.htmlProgressLabelRef.innerHTML = 'cached';
const timeElapsed = Date.now() - timeStart;
if (timeStart > 0 && timeElapsed > 0) {
this.htmlProgressLabelRef.innerHTML = new Date(timeElapsed).toISOString().substr(11, 8);
}
this.htmlProgressSliderRef.style.width = '0';
}
else if (currentStatus === EStatus.execution_error) {
this.htmlProgressLabelRef.innerHTML = 'ERROR';
this.htmlProgressSliderRef.style.backgroundColor = 'var(--error-text)';
}
else if (currentStatus === EStatus.executing) {
this.htmlProgressLabelRef.innerHTML = `${currentProgress}%`;
this.htmlProgressSliderRef.style.width = this.htmlProgressLabelRef.innerHTML;
this.htmlProgressSliderRef.style.backgroundColor = 'green';
}
}
});
Object.defineProperty(this, "showSection", {
enumerable: true,
configurable: true,
writable: true,
value: (value) => {
this.showSectionFlag = value;
this.displaySection();
}
});
Object.defineProperty(this, "showProgressBar", {
enumerable: true,
configurable: true,
writable: true,
value: (value) => {
this.showProgressBarFlag = value;
this.displaySection();
}
});
Object.defineProperty(this, "displaySection", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.rootElement.style.display = (this.showSectionFlag && this.showProgressBarFlag) ? 'block' : 'none';
}
});
this.createDOM();
}
}
+95
View File
@@ -0,0 +1,95 @@
import { EStatus, ProgressBarUIBase } from './progressBarUIBase.js';
export class ProgressBarUI extends ProgressBarUIBase {
htmlProgressSliderRef: HTMLDivElement;
htmlProgressLabelRef: HTMLDivElement;
currentStatus: EStatus;
timeStart: number;
currentProgress: number;
showProgressBarFlag: boolean;
constructor(
public override rootElement: HTMLElement,
public showSectionFlag: boolean,
private centerNode: () => void,
) {
super('crystools-progressBar-root', rootElement);
this.createDOM();
}
createDOM = (): void => {
this.rootElement.setAttribute('title', 'click to see the current working node');
this.rootElement.addEventListener('click', this.centerNode);
const progressBar = document.createElement('div');
progressBar.classList.add('crystools-progress-bar');
this.rootElement.append(progressBar);
const progressSlider = document.createElement('div');
this.htmlProgressSliderRef = progressSlider;
progressSlider.classList.add('crystools-slider');
progressBar.append(this.htmlProgressSliderRef);
const progressLabel = document.createElement('div');
progressLabel.classList.add('crystools-label');
progressLabel.innerHTML = '0%';
this.htmlProgressLabelRef = progressLabel;
progressBar.append(this.htmlProgressLabelRef);
};
// eslint-disable-next-line complexity
updateDisplay = (currentStatus: EStatus, timeStart: number, currentProgress: number): void => {
if (!(this.showSectionFlag && this.showProgressBarFlag)) {
return;
}
if (!(this.htmlProgressLabelRef && this.htmlProgressSliderRef)) {
console.error('htmlProgressLabelRef or htmlProgressSliderRef is undefined');
return;
}
// console.log('only if showSection and progressBar', timeStart, currentProgress);
this.currentStatus = currentStatus;
this.timeStart = timeStart;
this.currentProgress = currentProgress;
if (currentStatus === EStatus.executed) {
// finished
this.htmlProgressLabelRef.innerHTML = 'cached';
const timeElapsed = Date.now() - timeStart;
if (timeStart > 0 && timeElapsed > 0) {
this.htmlProgressLabelRef.innerHTML = new Date(timeElapsed).toISOString().substr(11, 8);
}
this.htmlProgressSliderRef.style.width = '0';
} else if (currentStatus === EStatus.execution_error) {
// an error occurred
this.htmlProgressLabelRef.innerHTML = 'ERROR';
this.htmlProgressSliderRef.style.backgroundColor = 'var(--error-text)';
} else if (currentStatus === EStatus.executing) {
// on going
this.htmlProgressLabelRef.innerHTML = `${currentProgress}%`;
this.htmlProgressSliderRef.style.width = this.htmlProgressLabelRef.innerHTML;
this.htmlProgressSliderRef.style.backgroundColor = 'green'; // by reset the color
}
};
public showSection = (value: boolean): void => {
this.showSectionFlag = value;
this.displaySection();
};
// remember it can't have more parameters because it is used on settings automatically
public showProgressBar = (value: boolean): void => {
this.showProgressBarFlag = value;
this.displaySection();
};
private displaySection = (): void => {
this.rootElement.style.display = (this.showSectionFlag && this.showProgressBarFlag) ? 'block' : 'none';
};
}
+18
View File
@@ -0,0 +1,18 @@
export declare enum EStatus {
executing = "Executing",
executed = "Executed",
execution_error = "Execution error"
}
export declare const ComfyKeyMenuDisplayOption = "Comfy.UseNewMenu";
export declare enum MenuDisplayOptions {
'Disabled' = "Disabled",
'Top' = "Top",
'Bottom' = "Bottom"
}
export declare abstract class ProgressBarUIBase {
rootId: string;
rootElement: HTMLElement | null | undefined;
protected htmlClassMonitor: string;
protected constructor(rootId: string, rootElement: HTMLElement | null | undefined);
abstract createDOM(): void;
}
+42
View File
@@ -0,0 +1,42 @@
export var EStatus;
(function (EStatus) {
EStatus["executing"] = "Executing";
EStatus["executed"] = "Executed";
EStatus["execution_error"] = "Execution error";
})(EStatus || (EStatus = {}));
export const ComfyKeyMenuDisplayOption = 'Comfy.UseNewMenu';
export var MenuDisplayOptions;
(function (MenuDisplayOptions) {
MenuDisplayOptions["Disabled"] = "Disabled";
MenuDisplayOptions["Top"] = "Top";
MenuDisplayOptions["Bottom"] = "Bottom";
})(MenuDisplayOptions || (MenuDisplayOptions = {}));
export class ProgressBarUIBase {
constructor(rootId, rootElement) {
Object.defineProperty(this, "rootId", {
enumerable: true,
configurable: true,
writable: true,
value: rootId
});
Object.defineProperty(this, "rootElement", {
enumerable: true,
configurable: true,
writable: true,
value: rootElement
});
Object.defineProperty(this, "htmlClassMonitor", {
enumerable: true,
configurable: true,
writable: true,
value: 'crystools-monitors-container'
});
if (this.rootElement && this.rootElement.children.length === 0) {
this.rootElement.setAttribute('id', this.rootId);
this.rootElement.classList.add(this.htmlClassMonitor);
this.rootElement.classList.add(this.constructor.name);
}
else {
}
}
}
+32
View File
@@ -0,0 +1,32 @@
export enum EStatus {
executing = 'Executing',
executed = 'Executed',
execution_error = 'Execution error',
}
export const ComfyKeyMenuDisplayOption = 'Comfy.UseNewMenu';
export enum MenuDisplayOptions {
'Disabled' = 'Disabled',
'Top' = 'Top',
'Bottom' = 'Bottom',
}
export abstract class ProgressBarUIBase {
protected htmlClassMonitor = 'crystools-monitors-container';
protected constructor(
public rootId: string,
public rootElement: HTMLElement | null | undefined,
) {
// IMPORTANT duplicate on crystools-save
if (this.rootElement && this.rootElement.children.length === 0) {
this.rootElement.setAttribute('id', this.rootId);
this.rootElement.classList.add(this.htmlClassMonitor);
this.rootElement.classList.add(this.constructor.name);
} else {
// it was created before
}
}
abstract createDOM(): void;
}
+12
View File
@@ -0,0 +1,12 @@
export declare enum Styles {
'BARS' = "BARS"
}
export declare enum Colors {
'CPU' = "#0AA015",
'RAM' = "#07630D",
'DISK' = "#730F92",
'GPU' = "#0C86F4",
'VRAM' = "#176EC7",
'TEMP_START' = "#00ff00",
'TEMP_END' = "#ff0000"
}
+16
View File
@@ -0,0 +1,16 @@
import { utils } from './comfy/index.js';
utils.addStylesheet('extensions/ComfyUI-Crystools/monitor.css');
export var Styles;
(function (Styles) {
Styles["BARS"] = "BARS";
})(Styles || (Styles = {}));
export var Colors;
(function (Colors) {
Colors["CPU"] = "#0AA015";
Colors["RAM"] = "#07630D";
Colors["DISK"] = "#730F92";
Colors["GPU"] = "#0C86F4";
Colors["VRAM"] = "#176EC7";
Colors["TEMP_START"] = "#00ff00";
Colors["TEMP_END"] = "#ff0000";
})(Colors || (Colors = {}));
+17
View File
@@ -0,0 +1,17 @@
import { utils } from './comfy/index.js';
utils.addStylesheet('extensions/ComfyUI-Crystools/monitor.css');
export enum Styles {
'BARS' = 'BARS'
}
export enum Colors {
'CPU' = '#0AA015',
'RAM' = '#07630D',
'DISK' = '#730F92',
'GPU' = '#0C86F4',
'VRAM' = '#176EC7',
'TEMP_START' = '#00ff00',
'TEMP_END' = '#ff0000',
}
+7 -3
View File
@@ -1,3 +1,4 @@
type TGpuStatData = {
gpu_utilization: number,
gpu_temperature: number,
@@ -42,13 +43,16 @@ type TStatsSettings = {
type TMonitorSettings = {
id: string,
name: string,
category: string[], // for settings location
label: string, // on monitor
symbol: string, // on monitor
monitorTitle?: string, // on monitor
title?: string, // on monitor
tooltip?: string, // on settings
type: 'boolean' | 'number' | 'string',
defaultValue: boolean,
onChange: (value: boolean) => Promise<void>,
type: 'boolean' | 'number' | 'string' | 'slider' | 'combo',
defaultValue: boolean | number | string,
data?: any,
onChange: (value: boolean | number | string) => Promise<void>,
htmlMonitorRef?: HTMLDivElement,
htmlMonitorSliderRef?: HTMLDivElement,
htmlMonitorLabelRef?: HTMLDivElement,
+5
View File
@@ -0,0 +1,5 @@
export declare function numberToWords(num: number): string;
export declare function toPascalCase(strings: string[]): string;
export declare function convertNumberToPascalCase(num: number): string;
export declare function formatBytes(bytes: number): string;
export declare function createStyleSheet(id: string): HTMLStyleElement;
+56
View File
@@ -0,0 +1,56 @@
export function numberToWords(num) {
if (num === 0)
return 'zero';
const belowTwenty = [
'', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten',
'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'
];
const tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
const thousands = ['', 'thousand', 'million', 'billion'];
function helper(n) {
if (n === 0)
return '';
else if (n < 20)
return belowTwenty[n] + ' ';
else if (n < 100)
return tens[Math.floor(n / 10)] + ' ' + helper(n % 10);
return belowTwenty[Math.floor(n / 100)] + ' hundred ' + helper(n % 100);
}
let word = '';
let i = 0;
while (num > 0) {
if (num % 1000 !== 0) {
word = helper(num % 1000) + thousands[i] + ' ' + word;
}
num = Math.floor(num / 1000);
i++;
}
return word.trim();
}
export function toPascalCase(strings) {
if (!Array.isArray(strings))
return '';
function capitalize(word) {
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}
return strings.map(capitalize).join('');
}
export function convertNumberToPascalCase(num) {
return toPascalCase(numberToWords(num).split(' '));
}
export function formatBytes(bytes) {
if (bytes === 0)
return '0 Bytes';
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
const formattedSize = (bytes / Math.pow(1024, i)).toFixed(2);
return `${formattedSize} ${sizes[i]}`;
}
export function createStyleSheet(id) {
const style = document.createElement('style');
style.setAttribute('id', id);
style.setAttribute('rel', 'stylesheet');
style.setAttribute('type', 'text/css');
document.head.appendChild(style);
return style;
}
+64
View File
@@ -0,0 +1,64 @@
export function numberToWords(num: number): string {
if (num === 0) return 'zero';
const belowTwenty = [
'', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten',
'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'
];
const tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
const thousands = ['', 'thousand', 'million', 'billion'];
function helper(n: number): string {
if (n === 0) return '';
else if (n < 20) return belowTwenty[n] + ' ';
else if (n < 100) return tens[Math.floor(n / 10)] + ' ' + helper(n % 10);
return belowTwenty[Math.floor(n / 100)] + ' hundred ' + helper(n % 100);
}
let word = '';
let i = 0;
while (num > 0) {
if (num % 1000 !== 0) {
word = helper(num % 1000) + thousands[i] + ' ' + word;
}
num = Math.floor(num / 1000);
i++;
}
return word.trim();
}
export function toPascalCase(strings: string[]): string {
if (!Array.isArray(strings)) return '';
function capitalize(word: string): string {
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}
return strings.map(capitalize).join('');
}
export function convertNumberToPascalCase(num: number): string {
return toPascalCase(numberToWords(num).split(' '));
}
export function formatBytes(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
const formattedSize = (bytes / Math.pow(1024, i)).toFixed(2);
return `${formattedSize} ${sizes[i]}`;
}
export function createStyleSheet(id: string): HTMLStyleElement {
const style = document.createElement('style');
style.setAttribute('id', id);
style.setAttribute('rel', 'stylesheet');
style.setAttribute('type', 'text/css');
document.head.appendChild(style);
return style;
}