This commit is contained in:
Crystian
2024-01-11 14:04:08 +01:00
parent d12ff29b7c
commit 284bfde3fa
14 changed files with 2163 additions and 4 deletions
+20
View File
@@ -0,0 +1,20 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
max_line_length = 120
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
+159
View File
@@ -0,0 +1,159 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-type-checked',
'plugin:@typescript-eslint/stylistic-type-checked'
],
parser: '@typescript-eslint/parser',
plugins: [],
parserOptions: {
tsconfigRootDir: __dirname,
sourceType: 'module',
ecmaVersion: 'latest',
ecmaFeatures: {
// experimentalObjectRestSpread: true,
modules: true,
legacyDecorators: true,
jsx: true,
},
project: ['./tsconfig.json'],
},
rules: {
'import/extensions': [
'error', 'always', {
pattern: {
'ts': 'never',
'js': 'never',
},
},
],
'import/no-unresolved': ['off'],
'class-methods-use-this': ['off'],
'radix': ['off'],
'import/prefer-default-export': ['off'],
'implicit-arrow-linebreak': ['off'],
'no-trailing-spaces': [
'error', {
'skipBlankLines': true,
'ignoreComments': true,
},
],
'max-len': [
'error', {
'code': 120,
},
],
'import/no-extraneous-dependencies': [
'error', {
'devDependencies': ['**/__tests__/*.ts', '**/__mocks__/*.ts'],
},
],
'one-var': ['off'],
// 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 },
"ImportDeclaration": {"multiline": true, "minProperties": 8 },
"ExportDeclaration": { "multiline": true, "minProperties": 1 }
}],
'@typescript-eslint/no-shadow': 'error',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/no-unused-expressions': [
'error',
{
'allowShortCircuit': true,
'allowTernary': true,
},
],
'@typescript-eslint/no-use-before-define': ['off'],
'@typescript-eslint/no-unused-vars': [
'error',
{'argsIgnorePattern': '^_'},
],
'@typescript-eslint/explicit-module-boundary-types': [
'error', {
allowArgumentsExplicitlyTypedAsAny: true,
},
],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': [
'error',
{'ts-expect-error': 'allow-with-description'},
],
'@typescript-eslint/no-empty-function': [
'error',
{
'allow': [
'methods',
'asyncMethods',
],
},
],
'lines-between-class-members': 'off',
'@typescript-eslint/lines-between-class-members': 'off',
'curly': ['error', 'multi-line', 'consistent'],
'no-unused-vars': 'off',
'jest/no-test-prefixes': 'off',
'jest/no-focused-tests': 'off',
'comma-dangle': ['error', 'only-multiline'],
'linebreak-style': ['error', 'unix'],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'eqeqeq': ['error', 'always'],
'complexity': [
'error', {
max: 6,
},
],
'block-scoped-var': 'error',
'no-else-return': [
'error', {
allowElseIf: true,
},
],
'no-eval': 'error',
'no-lone-blocks': 'error',
'no-multi-spaces': 'error',
'no-useless-return': 'error',
'no-var': 'error',
'no-console': [
// 'error', {
// allow: ['warn', 'error'],
// },
'off',
],
'no-throw-literal': 'error',
'newline-per-chained-call': [
'error', {
ignoreChainWithDepth: 4,
},
],
'no-extra-boolean-cast': [
'error', {
enforceForLogicalOperands: true,
},
],
'no-fallthrough': 'error',
'no-use-before-define': 'off',
'no-case-declarations': 'off',
},
ignorePatterns: [
'node_modules'
],
globals: {},
}
+1
View File
@@ -153,3 +153,4 @@ cython_debug/
*.pyc
.idea
/node_modules
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
import subprocess
import os
import shutil
import glob
import json
print('Running __dev__.py')
# from config import RGTHREE_CONFIG
#
# THIS_DIR=os.path.dirname(os.path.abspath(__file__))
# DIR_WEB = os.path.abspath(f'{THIS_DIR}/web/')
# DIR_WEB_COMFYUI = os.path.abspath(f'{DIR_WEB}/comfyui/')
#
# js_files = glob.glob(os.path.join(THIS_DIR, 'web/**/*.js'))
# for file in js_files:
# os.remove(file)
#
# subprocess.run(["./node_modules/typescript/bin/tsc"])
#
# with open(os.path.join(DIR_WEB_COMFYUI, 'rgthree_config.js'), 'w', encoding = 'UTF-8') as file:
# file.write('export const rgthreeConfig = ' + json.dumps(RGTHREE_CONFIG))
+1 -1
View File
@@ -94,4 +94,4 @@ NODE_DISPLAY_NAME_MAPPINGS = {
}
WEB_DIRECTORY = "./web"
WEB_DIRECTORY = "./web/js"
+8 -3
View File
@@ -3,11 +3,16 @@ import cpuinfo
import psutil
import torch
import pynvml
from comfy.model_management import get_torch_device_name, get_torch_device
import comfy.model_management
from ..core import logger
class CStats:
"""
This is only class to get information from hardware.
Specially for share it to other software.
"""
nvidia = False
pynvmlLoaded = False
cudaAvailable = False
@@ -127,12 +132,12 @@ class CStats:
if self.pynvmlLoaded and pynvml.nvmlDeviceGetCount() > 0:
self.cudaDevicesFound = pynvml.nvmlDeviceGetCount()
self.nvidia = True
logger.info(f'NVIDIA Driver detected - {pynvml.nvmlSystemGetDriverVersion()} - {get_torch_device_name(get_torch_device())}')
logger.info(f'NVIDIA Driver detected - {pynvml.nvmlSystemGetDriverVersion()} - {comfy.model_management.get_torch_device_name(comfy.model_management.get_torch_device())}')
else:
logger.warn('No NVIDIA GPU detected.')
try:
self.torchDevice = get_torch_device_name(get_torch_device())
self.torchDevice = comfy.model_management.get_torch_device_name(comfy.model_management.get_torch_device())
except Exception as e:
logger.error('Could not pick default device.' + str(e))
+1148
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
{
"name": "crystools-typescript",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "tsc",
"build": "tsc && vite build --emptyOutDir",
"tsc": "tsc",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint-fix": "eslint src --fix",
"validate": "npm test && npm run coverage && npm run lintcss && npm run tsc && npm run lint && npm run tsc",
"fix": "npm run lint-fix"
},
"devDependencies": {
"eslint": "^8.56.0",
"typescript": "^5.3.3"
}
}
+55
View File
@@ -0,0 +1,55 @@
{
"compilerOptions": {
"target": "es2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
// "typeRoots": [
// "./ts/typings",
// ],
"baseUrl": "./",
"paths": {
"*": ["web/ts/typings/*"],
},
"outDir": "web/",
"removeComments": true,
"strict": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
"exactOptionalPropertyTypes": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"allowUnusedLabels": true,
"skipLibCheck": true,
"useDefineForClassFields": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": [
"web/ts/*.ts", "web/ts/**/*.ts", "web/ts/typings/index.d.ts",
],
"exclude": [
"**/*.spec.ts",
"**/*.d.ts",
"node_modules/**/*.ts"
],
}
+75
View File
@@ -0,0 +1,75 @@
import { ComfyWidgets } from '../../../scripts/widgets.js';
export const commonPrefix = '🪛';
export function displayContext(nodeType, app, index = 0, serialize_widgets = false, isVirtualNode = false) {
function populate(text) {
if (this.widgets) {
const pos = this.widgets.findIndex((w) => w.name === 'text');
if (pos !== -1) {
for (let i = pos; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
this.widgets.length = pos;
}
}
// if want to not save properties in the node (be careful with F5)
// BUG on isVirtualNode, with "true", it ignores OUTPUT_NODE on py file!
this.serialize_widgets = serialize_widgets;
this.isVirtualNode = isVirtualNode;
const w = ComfyWidgets['STRING'](this, 'text', ['STRING', {multiline: true}], app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.6;
if (Array.isArray(text)) {
text = text[index];
}
w.value = text || '';
w.serializeValue = async() => {}; // just for no serialized to itself!
requestAnimationFrame(() => {
const sz = this.computeSize();
if (sz[0] < this.size[0]) {
sz[0] = this.size[0];
}
if (sz[1] < this.size[1]) {
sz[1] = this.size[1];
}
this.onResize?.(sz);
app.graph.setDirtyCanvas(true, false);
});
}
// When the node is executed we will be sent the input text, display this in the widget
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function(message) {
onExecuted?.apply(this, arguments);
populate.call(this, message.text);
};
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function() {
onConfigure?.apply(this, arguments);
if (this.widgets_values?.length) {
populate.call(this, this.widgets_values);
}
};
}
// propagate the output value to the dependents nodes, it does not work with some nodes ¯\_(ツ)_/¯
const propagateOutputToDependentsNodes = function(output, value) {
if (output.links && output.links.length) {
for (const l of output.links) {
const link_info = app.graph.links[l];
const outNode = app.graph.getNodeById(link_info.target_id);
const outIn = outNode && outNode.inputs && outNode.inputs[link_info.target_slot];
if (outIn.widget) {
const w = outNode.widgets.find((w) => w.name === outIn.widget.name);
if (!w) {
continue;
}
w.value = value;
}
}
}
};
+70
View File
@@ -0,0 +1,70 @@
import { app } from '../../../scripts/app.js';
import { api } from '../../../scripts/api.js';
import { ComfyWidgets } from '../../../scripts/widgets.js';
import { commonPrefix, displayContext } from './common.js';
// "Show any" Node
app.registerExtension({
name: 'Crystools.Debugger.ConsoleAny',
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === 'Show any [Crystools]') {
// 3 is the index of the text field in the node
displayContext(nodeType, app, 3);
}
},
});
app.registerExtension({
name: 'Crystools.Debugger.Metadata',
registerCustomNodes() {
class MetadataNode {
constructor() {
this.serialize_widgets = false;
this.isVirtualNode = true;
const w = ComfyWidgets.STRING(this, '', ['', {default: '', multiline: true}], app).widget;
w.inputEl.readOnly = true;
ComfyWidgets.BOOLEAN(this, 'Active', ['', {default: true}], app);
ComfyWidgets.BOOLEAN(this, 'Parsed', ['', {default: true}], app);
ComfyWidgets.COMBO(this, 'What', [['Prompt', 'Workflow'], {default: 'Prompt'}]);
// It runs at finish on each prompt queue
api.addEventListener('executed', this.fillMetadataWidget);
}
fillMetadataWidget = () => {
app.graphToPrompt()
.then(workflow => {
let result = 'inactive';
// debugger
const output = this.widgets[0];
const active = this.widgets[1].value;
const parsed = this.widgets[2].value;
let what = this.widgets[3].value.toLowerCase();
if (active) {
what = what === 'prompt' ? 'output' : what; // little fix for better understanding
result = workflow[what];
if (parsed) {
result = JSON.stringify(result, null, 2);
} else {
result = JSON.stringify(result);
}
}
output.value = result;
});
}
}
// I'm not sure for what they're using prototype and lots of black magic, don't change the order!
LiteGraph.registerNodeType('Show Metadata [Crystools]', MetadataNode);
MetadataNode.category = `crystools ${commonPrefix}/Debugger`;
MetadataNode.shape = LiteGraph.BOX_SHAPE;
MetadataNode.title = `Show Metadata ${commonPrefix}`;
// MetadataNode.collapsable = false;
// MetadataNode.color = "#FF2222";
// MetadataNode.bgcolor = "#000000";
},
});
+22
View File
@@ -0,0 +1,22 @@
import { app } from '../../../scripts/app.js';
import { displayContext } from './common.js';
let crystoolsExtensions = {
'Get resolution [Crystools]': 'Crystools.Image.GetResolution',
'Preview from image [Crystools]': 'Crystools.Image.PreviewFromImage',
'Preview from metadata [Crystools]': 'Crystools.Image.PreviewFromMetadata',
'Metadata comparator [Crystools]': 'Crystools.Metadata.MetadataComparator',
'Stats system [Crystools]': 'Crystools.Utils.StatsSystem',
'Show any to JSON [Crystools]': 'Crystools.Debugger.ConsoleAnyToJson',
};
Object.keys(crystoolsExtensions).forEach(key => {
app.registerExtension({
name: crystoolsExtensions[key],
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === key) {
displayContext(nodeType, app, 0);
}
},
});
});
+371
View File
@@ -0,0 +1,371 @@
import { app } from '../../../scripts/app.js';
import { api } from '../../../scripts/api.js';
import { commonPrefix } from './common.js';
class CrystoolsMonitor {
idExtensionName = 'Crystools.monitor';
menuPrefix = commonPrefix;
htmlIdCrystoolsRoot = 'crystools-root';
htmlIdCrystoolsMonitorContainer = 'crystools-monitor-container';
idInputRate = 'Crystools.inputRate';
defaultRate = .5;
/** MONITORS */
// CPU Variables
idSwitchCPU = 'Crystools.switchCPU';
defaultSwitchCPU = true;
htmlMonitorCPURef = null;
htmlMonitorCPUSliderRef = null;
htmlMonitorCPULabelRef = null;
cssColorCPU = '#0AA015';
// RAM Variables
idSwitchRAM = 'Crystools.switchRAM';
defaultSwitchRAM = true;
htmlMonitorRAMRef = null;
htmlMonitorRAMSliderRef = null;
htmlMonitorRAMLabelRef = null;
cssColorRAM = '#07630D';
// GPU Variables
idSwitchGPU = 'Crystools.switchGPU';
defaultSwitchGPU = true;
htmlMonitorGPURef = null;
htmlMonitorGPUSliderRef = null;
htmlMonitorGPULabelRef = null;
cssColorGPU = '#0C86F4';
// VRAM Variables
idSwitchVRAM = 'Crystools.switchVRAM';
defaultSwitchVRAM = true;
htmlMonitorVRAMRef = null;
htmlMonitorVRAMSliderRef = null;
htmlMonitorVRAMLabelRef = null;
cssColorVRAM = '#176EC7';
// HDD Variables
idSwitchHDD = 'Crystools.switchHDD';
defaultSwitchHDD = false;
htmlMonitorHDDRef = null;
htmlMonitorHDDSliderRef = null;
htmlMonitorHDDLabelRef = null;
cssColorHDD = '#730F92';
constructor() {
this.createSettings();
}
createSettings = () => {
app.ui.settings.addSetting({
id: this.idSwitchCPU,
name: this.menuPrefix + 'Display CPU monitor [menu]',
type: 'boolean',
defaultValue: this.defaultSwitchCPU,
onChange: async(value) => {
this.updateWidget(this.htmlMonitorCPURef, value);
await this.updateServer({switchCPU: value});
},
});
app.ui.settings.addSetting({
id: this.idSwitchRAM,
name: this.menuPrefix + 'Display RAM monitor [menu]',
type: 'boolean',
defaultValue: this.defaultSwitchRAM,
onChange: async(value) => {
this.updateWidget(this.htmlMonitorRAMRef, value);
await this.updateServer({switchRAM: value});
},
});
app.ui.settings.addSetting({
id: this.idSwitchGPU,
name: this.menuPrefix + 'Display GPU monitor [menu]',
type: 'boolean',
defaultValue: this.defaultSwitchGPU,
onChange: async(value) => {
this.updateWidget(this.htmlMonitorGPURef, value);
await this.updateServer({switchGPU: value});
},
});
app.ui.settings.addSetting({
id: this.idSwitchVRAM,
name: this.menuPrefix + 'Display Video RAM monitor [menu]',
type: 'boolean',
defaultValue: this.defaultSwitchVRAM,
onChange: async(value) => {
this.updateWidget(this.htmlMonitorVRAMRef, value);
await this.updateServer({switchVRAM: value});
},
});
app.ui.settings.addSetting({
id: this.idSwitchHDD,
name: this.menuPrefix + 'Display Hard disk monitor [menu]',
tooltip: 'Only the drive where the comfyUI is installed',
type: 'boolean',
defaultValue: this.defaultSwitchHDD,
onChange: async(value) => {
this.updateWidget(this.htmlMonitorHDDRef, value);
await this.updateServer({switchHDD: value});
},
});
app.ui.settings.addSetting({
id: this.idInputRate,
name: this.menuPrefix + 'Monitors refresh rate (in seconds) [menu]',
tooltip: 'This is the time between each update of the monitors, 0 means no refresh',
type: 'slider',
attrs: {
min: 0,
max: 5,
step: 0.25,
},
defaultValue: this.defaultRate,
onChange: async(value) => {
try {
value = parseFloat(value);
if (isNaN(value)) {
throw new Error('invalid value');
}
} catch (error) {
console.error(error);
return;
}
try {
await this.updateServer({rate: value});
} catch (error) {
console.error(error);
return;
}
if (value === 0) {
this.updateDisplay({
cpu_utilization: 0,
device: 'cpu',
gpus: [{
gpu_utilization: 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 (this.htmlMonitorCPUSliderRef) { // validation because this run before setup
value = value.toFixed(1);
const animationConfig = `width ${value}s`;
this.htmlMonitorCPUSliderRef.style.transition = animationConfig;
this.htmlMonitorGPUSliderRef.style.transition = animationConfig;
this.htmlMonitorHDDSliderRef.style.transition = animationConfig;
this.htmlMonitorRAMSliderRef.style.transition = animationConfig;
this.htmlMonitorVRAMSliderRef.style.transition = animationConfig;
this.htmlMonitorCPUSliderRef.style.width = value;
this.htmlMonitorGPUSliderRef.style.width = value;
this.htmlMonitorHDDSliderRef.style.width = value;
this.htmlMonitorRAMSliderRef.style.width = value;
this.htmlMonitorVRAMSliderRef.style.width = value;
}
},
});
};
updateServer = async(data) => {
const resp = await api.fetchApi('/crystools/monitor', {
method: 'PATCH',
body: JSON.stringify(data),
cache: 'no-store',
});
if (resp.status === 200) {
return await resp.text();
} else {
throw new Error(resp.statusText);
}
};
updateAllWidget = () => {
this.updateWidget(this.htmlMonitorCPURef, app.ui.settings.getSettingValue(this.idSwitchCPU, this.defaultSwitchCPU));
this.updateWidget(this.htmlMonitorGPURef, app.ui.settings.getSettingValue(this.idSwitchGPU, this.defaultSwitchGPU));
this.updateWidget(this.htmlMonitorHDDRef, app.ui.settings.getSettingValue(this.idSwitchHDD, this.defaultSwitchHDD));
this.updateWidget(this.htmlMonitorRAMRef, app.ui.settings.getSettingValue(this.idSwitchRAM, this.defaultSwitchRAM));
this.updateWidget(this.htmlMonitorVRAMRef, app.ui.settings.getSettingValue(this.idSwitchVRAM, this.defaultSwitchVRAM));
};
updateWidget = (container, value) => {
if (container) {
container.style.display = value ? 'flex' : 'none';
}
};
updateDisplay = (data) => {
// console.log('updateDisplay', data);
if (!this.htmlMonitorCPULabelRef) {
return;
}
this.htmlMonitorCPULabelRef.innerHTML = `${Math.floor(data.cpu_utilization)}%`;
this.htmlMonitorCPUSliderRef.style.width = this.htmlMonitorCPULabelRef.innerHTML;
const gpu = data.gpus[0];
if (gpu) {
this.htmlMonitorGPULabelRef.innerHTML = `${Math.floor(gpu.gpu_utilization)}%`;
this.htmlMonitorGPUSliderRef.style.width = this.htmlMonitorGPULabelRef.innerHTML;
this.htmlMonitorVRAMLabelRef.innerHTML = `${Math.floor(gpu.vram_used_percent)}%`;
this.htmlMonitorVRAMSliderRef.style.width = this.htmlMonitorVRAMLabelRef.innerHTML;
} else {
console.error('no gpu found');
}
this.htmlMonitorHDDLabelRef.innerHTML = `${Math.floor(data.hdd_used_percent)}%`;
this.htmlMonitorHDDSliderRef.style.width = this.htmlMonitorHDDLabelRef.innerHTML;
this.htmlMonitorRAMLabelRef.innerHTML = `${Math.floor(data.ram_used_percent)}%`;
this.htmlMonitorRAMSliderRef.style.width = this.htmlMonitorRAMLabelRef.innerHTML;
};
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.createMonitorCPU());
htmlContainer.append(this.createMonitorRAM());
htmlContainer.append(this.createMonitorGPU());
htmlContainer.append(this.createMonitorVRAM());
htmlContainer.append(this.createMonitorHDD());
this.updateAllWidget();
this.registerListeners();
}
createMonitor = (id, label, color) => {
const option = {
rootRef: null,
sliderRef: null,
labelRef: null,
};
const htmlMain = document.createElement('div');
htmlMain.setAttribute('id', 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';
option.rootRef = htmlMain;
const htmlMonitorText = document.createElement('div');
htmlMonitorText.style.width = '35px';
htmlMonitorText.style.fontSize = '10px';
htmlMonitorText.innerHTML = 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';
htmlMonitorSlider.style.backgroundColor = color;
htmlMonitorSlider.style.transition = 'width 0.5s';
option.sliderRef = 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%';
option.labelRef = htmlMonitorLabel;
htmlMonitorContent.append(htmlMonitorLabel);
return option;
};
createMonitorCPU = () => {
const options = this.createMonitor('crystools-monitor-cpu', 'CPU', this.cssColorCPU);
this.htmlMonitorCPURef = options.rootRef;
this.htmlMonitorCPUSliderRef = options.sliderRef;
this.htmlMonitorCPULabelRef = options.labelRef;
return this.htmlMonitorCPURef;
};
createMonitorRAM = () => {
const options = this.createMonitor('crystools-monitor-ram', 'RAM', this.cssColorRAM);
this.htmlMonitorRAMRef = options.rootRef;
this.htmlMonitorRAMSliderRef = options.sliderRef;
this.htmlMonitorRAMLabelRef = options.labelRef;
return this.htmlMonitorRAMRef;
};
createMonitorGPU = () => {
const options = this.createMonitor('crystools-monitor-gpu', 'GPU', this.cssColorGPU);
this.htmlMonitorGPURef = options.rootRef;
this.htmlMonitorGPUSliderRef = options.sliderRef;
this.htmlMonitorGPULabelRef = options.labelRef;
return this.htmlMonitorGPURef;
};
createMonitorVRAM = () => {
const options = this.createMonitor('crystools-monitor-vram', 'VRAM', this.cssColorVRAM);
this.htmlMonitorVRAMRef = options.rootRef;
this.htmlMonitorVRAMSliderRef = options.sliderRef;
this.htmlMonitorVRAMLabelRef = options.labelRef;
return this.htmlMonitorVRAMRef;
};
createMonitorHDD = () => {
const options = this.createMonitor('crystools-monitor-hdd', 'HDD', this.cssColorHDD);
this.htmlMonitorHDDRef = options.rootRef;
this.htmlMonitorHDDSliderRef = options.sliderRef;
this.htmlMonitorHDDLabelRef = options.labelRef;
return this.htmlMonitorHDDRef;
};
registerListeners = () => {
api.addEventListener('crystools.monitor', (event) => {
if (event?.detail === undefined) {
return;
}
this.updateDisplay(event.detail);
});
};
}
const crystoolsMonitor = new CrystoolsMonitor();
app.registerExtension({
name: crystoolsMonitor.idExtensionName,
setup: crystoolsMonitor.setup.bind(crystoolsMonitor),
});
+191
View File
@@ -0,0 +1,191 @@
import { api } from '../../../scripts/api.js';
import { app } from '../../../scripts/app.js';
import { commonPrefix } from './common.js';
const status = {
executing: 'Executing',
executed: 'Executed',
execution_error: 'Execution error',
};
class CrystoolsProgressBar {
idExtensionName = 'Crystools.progressBar';
idShowProgressBar = 'Crystools.showStatus';
defaultShowStatus = true;
menuPrefix = commonPrefix;
htmlIdCrystoolsRoot = 'crystools-root';
htmlIdCrystoolsProgressBarContainer = 'crystools-progress-bar-container';
currentStatus = status.executed;
currentProgress = 0;
currentNode = null;
timeStart = 0;
htmlProgressSliderRef = null;
htmlProgressLabelRef = null;
constructor() {
this.createSettings();
}
// not on setup because this affect the order on settings, I prefer to options at first
createSettings = () => {
app.ui.settings.addSetting({
id: this.idShowProgressBar,
name: this.menuPrefix + 'Show progress bar [menu]',
type: 'boolean',
defaultValue: this.defaultShowStatus,
onChange: this.showProgressBar,
});
};
showProgressBar = (value) => {
const container = document.getElementById(this.htmlIdCrystoolsProgressBarContainer);
// validation because this run before setup
if (container) {
container.style.display = value ? 'block' : 'none';
}
};
updateDisplay = () => {
if (this.currentStatus === status.executed) {
// finished
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';
} else if (this.currentStatus === status.execution_error) {
// an error occurred
this.htmlProgressLabelRef.innerHTML = 'ERROR';
this.htmlProgressSliderRef.style.backgroundColor = 'var(--error-text)';
console.log('execution_error');
} else if (this.currentStatus === status.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() {
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.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();
}
registerListeners = () => {
api.addEventListener('status', ({detail}) => {
this.currentStatus = this.currentStatus === status.execution_error ? status.execution_error : status.executed;
let queueRemaining = detail && detail.exec_info.queue_remaining;
if (queueRemaining) {
this.currentStatus = status.executing;
}
this.updateDisplay();
});
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();
});
api.addEventListener('executed', ({detail}) => {
if (detail?.node) {
this.currentNode = detail.node;
}
this.updateDisplay();
});
api.addEventListener('execution_start', ({detail}) => {
this.currentStatus = status.executing;
this.timeStart = Date.now();
this.updateDisplay();
});
api.addEventListener('execution_error', ({detail}) => {
this.currentStatus = status.execution_error;
this.updateDisplay();
});
};
centerNode = () => {
const id = this.currentNode;
if (!id) {
return;
}
const node = app.graph.getNodeById(id);
if (!node) {
return;
}
app.canvas.centerOnNode(node);
};
}
const crystoolsProgressBar = new CrystoolsProgressBar();
app.registerExtension({
name: crystoolsProgressBar.idExtensionName,
setup: crystoolsProgressBar.setup.bind(crystoolsProgressBar),
})