diff --git a/index.html b/index.html index 07f29df3e..5e6f66d54 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,6 @@ - diff --git a/package-lock.json b/package-lock.json index 1cbe09f1f..52b57ede8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,14 +14,12 @@ "@vitejs/plugin-vue": "^5.0.5", "@vueuse/core": "^11.0.0", "axios": "^1.7.4", - "class-transformer": "^0.5.1", "dotenv": "^16.4.5", "fuse.js": "^7.0.0", "lodash": "^4.17.21", "pinia": "^2.1.7", "primeicons": "^7.0.0", "primevue": "^4.0.5", - "reflect-metadata": "^0.2.2", "vue": "^3.4.31", "vue-i18n": "^9.13.1", "vue-router": "^4.4.3", @@ -5313,11 +5311,6 @@ "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, - "node_modules/class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" - }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -10666,11 +10659,6 @@ "node": ">=8.10.0" } }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", diff --git a/package.json b/package.json index 79a515a27..6adbec2b7 100644 --- a/package.json +++ b/package.json @@ -69,14 +69,12 @@ "@vitejs/plugin-vue": "^5.0.5", "@vueuse/core": "^11.0.0", "axios": "^1.7.4", - "class-transformer": "^0.5.1", "dotenv": "^16.4.5", "fuse.js": "^7.0.0", "lodash": "^4.17.21", "pinia": "^2.1.7", "primeicons": "^7.0.0", "primevue": "^4.0.5", - "reflect-metadata": "^0.2.2", "vue": "^3.4.31", "vue-i18n": "^9.13.1", "vue-router": "^4.4.3", diff --git a/src/main.ts b/src/main.ts index 69b60c253..7f3f32990 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,6 @@ import Aura from '@primevue/themes/aura' import ConfirmationService from 'primevue/confirmationservice' import ToastService from 'primevue/toastservice' import Tooltip from 'primevue/tooltip' -import 'reflect-metadata' import '@comfyorg/litegraph/style.css' import '@/assets/css/style.css' diff --git a/src/stores/nodeDefStore.ts b/src/stores/nodeDefStore.ts index 3bd00fc3c..ecd10df71 100644 --- a/src/stores/nodeDefStore.ts +++ b/src/stores/nodeDefStore.ts @@ -2,9 +2,11 @@ import { NodeSearchService, type SearchAuxScore } from '@/services/nodeSearchService' -import { ComfyNodeDef } from '@/types/apiTypes' +import { + type ComfyNodeDef, + type ComfyInputsSpec as ComfyInputsSpecSchema +} from '@/types/apiTypes' import { defineStore } from 'pinia' -import { Type, Transform, plainToClass, Expose } from 'class-transformer' import { ComfyWidgetConstructor } from '@/scripts/widgets' import { TreeNode } from 'primevue/treenode' import { buildTree } from '@/utils/treeUtil' @@ -12,87 +14,60 @@ import { computed, ref } from 'vue' import axios from 'axios' import { type NodeSource, getNodeSource } from '@/types/nodeSource' -export class BaseInputSpec { +export interface BaseInputSpec { name: string type: string tooltip?: string default?: T - @Type(() => Boolean) forceInput?: boolean - - static isInputSpec(obj: any): boolean { - return ( - Array.isArray(obj) && - obj.length >= 1 && - (typeof obj[0] === 'string' || Array.isArray(obj[0])) - ) - } } -export class NumericInputSpec extends BaseInputSpec { - @Type(() => Number) +export interface NumericInputSpec extends BaseInputSpec { min?: number - - @Type(() => Number) max?: number - - @Type(() => Number) step?: number } -export class IntInputSpec extends NumericInputSpec { - type: 'INT' = 'INT' +export interface IntInputSpec extends NumericInputSpec { + type: 'INT' } -export class FloatInputSpec extends NumericInputSpec { - type: 'FLOAT' = 'FLOAT' - - @Type(() => Number) +export interface FloatInputSpec extends NumericInputSpec { + type: 'FLOAT' round?: number } -export class BooleanInputSpec extends BaseInputSpec { - type: 'BOOLEAN' = 'BOOLEAN' - +export interface BooleanInputSpec extends BaseInputSpec { + type: 'BOOLEAN' labelOn?: string labelOff?: string } -export class StringInputSpec extends BaseInputSpec { - type: 'STRING' = 'STRING' - - @Type(() => Boolean) +export interface StringInputSpec extends BaseInputSpec { + type: 'STRING' multiline?: boolean - - @Type(() => Boolean) dynamicPrompts?: boolean } -export class ComboInputSpec extends BaseInputSpec { - type: string = 'COMBO' - - @Transform(({ value }) => value[0]) +export interface ComboInputSpec extends BaseInputSpec { + type: 'COMBO' comboOptions: any[] - - @Type(() => Boolean) controlAfterGenerate?: boolean - - @Type(() => Boolean) imageUpload?: boolean } -export class CustomInputSpec extends BaseInputSpec {} - export class ComfyInputsSpec { - @Transform(({ value }) => ComfyInputsSpec.transformInputSpecRecord(value)) - required: Record = {} - - @Transform(({ value }) => ComfyInputsSpec.transformInputSpecRecord(value)) - optional: Record = {} - + required: Record + optional: Record hidden?: Record + constructor(obj: ComfyInputsSpecSchema) { + this.required = ComfyInputsSpec.transformInputSpecRecord(obj.required) ?? {} + this.optional = ComfyInputsSpec.transformInputSpecRecord(obj.optional) ?? {} + this.hidden = obj.hidden + } + private static transformInputSpecRecord( record: Record ): Record { @@ -104,35 +79,39 @@ export class ComfyInputsSpec { return result } + private static isInputSpec(obj: any): boolean { + return ( + Array.isArray(obj) && + obj.length >= 1 && + (typeof obj[0] === 'string' || Array.isArray(obj[0])) + ) + } + private static transformSingleInputSpec( name: string, value: any ): BaseInputSpec { - if (!BaseInputSpec.isInputSpec(value)) return value + if (!ComfyInputsSpec.isInputSpec(value)) return value const [typeRaw, _spec] = value const spec = _spec ?? {} const type = Array.isArray(typeRaw) ? 'COMBO' : value[0] switch (type) { - case 'INT': - return plainToClass(IntInputSpec, { name, type, ...spec }) - case 'FLOAT': - return plainToClass(FloatInputSpec, { name, type, ...spec }) - case 'BOOLEAN': - return plainToClass(BooleanInputSpec, { name, type, ...spec }) - case 'STRING': - return plainToClass(StringInputSpec, { name, type, ...spec }) case 'COMBO': - return plainToClass(ComboInputSpec, { + return { name, type, ...spec, comboOptions: typeRaw, default: spec.default ?? typeRaw[0] - }) + } as ComboInputSpec + case 'INT': + case 'FLOAT': + case 'BOOLEAN': + case 'STRING': default: - return plainToClass(CustomInputSpec, { name, type, ...spec }) + return { name, type, ...spec } as BaseInputSpec } } @@ -165,42 +144,36 @@ export class ComfyOutputsSpec { } } +/** + * Note: This class does not implement the ComfyNodeDef interface, as we are + * using a custom output spec for output definitions. + */ export class ComfyNodeDefImpl { name: string display_name: string category: string python_module: string description: string - - @Transform(({ value, obj }) => value ?? obj.category === '', { - toClassOnly: true - }) - @Type(() => Boolean) - @Expose() deprecated: boolean - - @Transform( - ({ value, obj }) => value ?? obj.category.startsWith('_for_testing'), - { - toClassOnly: true - } - ) - @Type(() => Boolean) - @Expose() experimental: boolean - - @Type(() => ComfyInputsSpec) input: ComfyInputsSpec - - @Transform(({ obj }) => ComfyNodeDefImpl.transformOutputSpec(obj)) output: ComfyOutputsSpec - - @Transform(({ obj }) => getNodeSource(obj.python_module), { - toClassOnly: true - }) - @Expose() nodeSource: NodeSource + constructor(obj: ComfyNodeDef) { + this.name = obj.name + this.display_name = obj.display_name + this.category = obj.category + this.python_module = obj.python_module + this.description = obj.description + this.deprecated = obj.deprecated ?? obj.category === '' + this.experimental = + obj.experimental ?? obj.category.startsWith('_for_testing') + this.input = new ComfyInputsSpec(obj.input) + this.output = ComfyNodeDefImpl.transformOutputSpec(obj) + this.nodeSource = getNodeSource(obj.python_module) + } + private static transformOutputSpec(obj: any): ComfyOutputsSpec { const { output, output_is_list, output_name, output_tooltips } = obj const result = output.map((type: string | any[], index: number) => { @@ -276,13 +249,17 @@ export function buildNodeDefTree(nodeDefs: ComfyNodeDefImpl[]): TreeNode { } export function createDummyFolderNodeDef(folderPath: string): ComfyNodeDefImpl { - return plainToClass(ComfyNodeDefImpl, { + return new ComfyNodeDefImpl({ name: '', display_name: '', category: folderPath.endsWith('/') ? folderPath.slice(0, -1) : folderPath, python_module: 'nodes', - description: 'Dummy Folder Node (User should never see this string)' - }) + description: 'Dummy Folder Node (User should never see this string)', + input: {}, + output: [], + output_name: [], + output_is_list: [] + } as ComfyNodeDef) } interface State { @@ -325,7 +302,7 @@ export const useNodeDefStore = defineStore('nodeDef', { const newNodeDefsByName: { [key: string]: ComfyNodeDefImpl } = {} const nodeDefsByDisplayName: { [key: string]: ComfyNodeDefImpl } = {} for (const nodeDef of nodeDefs) { - const nodeDefImpl = plainToClass(ComfyNodeDefImpl, nodeDef) + const nodeDefImpl = new ComfyNodeDefImpl(nodeDef) newNodeDefsByName[nodeDef.name] = nodeDefImpl nodeDefsByDisplayName[nodeDef.display_name] = nodeDefImpl } @@ -333,7 +310,7 @@ export const useNodeDefStore = defineStore('nodeDef', { this.nodeDefsByDisplayName = nodeDefsByDisplayName }, addNodeDef(nodeDef: ComfyNodeDef) { - const nodeDefImpl = plainToClass(ComfyNodeDefImpl, nodeDef) + const nodeDefImpl = new ComfyNodeDefImpl(nodeDef) this.nodeDefsByName[nodeDef.name] = nodeDefImpl this.nodeDefsByDisplayName[nodeDef.display_name] = nodeDefImpl }, diff --git a/tests-ui/globalSetup.ts b/tests-ui/globalSetup.ts index 54643f774..e0e5365f0 100644 --- a/tests-ui/globalSetup.ts +++ b/tests-ui/globalSetup.ts @@ -5,7 +5,6 @@ module.exports = async function () { disconnect() {} } - require('reflect-metadata') const { nop } = require('./utils/nopProxy') global.enableWebGLCanvas = nop diff --git a/tests-ui/tests/nodeDef.test.ts b/tests-ui/tests/nodeDef.test.ts index 01fba5cb4..664531f32 100644 --- a/tests-ui/tests/nodeDef.test.ts +++ b/tests-ui/tests/nodeDef.test.ts @@ -1,12 +1,9 @@ -import { plainToClass } from 'class-transformer' import { ComfyInputsSpec, IntInputSpec, StringInputSpec, BooleanInputSpec, FloatInputSpec, - CustomInputSpec, - ComboInputSpec, ComfyNodeDefImpl } from '@/stores/nodeDefStore' // Adjust the import path as needed @@ -29,7 +26,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) + const result = new ComfyInputsSpec(plainObject) expect(result).toBeInstanceOf(ComfyInputsSpec) expect(result.required).toBeDefined() @@ -45,10 +42,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.required.intInput).toBeInstanceOf(IntInputSpec) - expect(result.required.stringInput).toBeInstanceOf(StringInputSpec) + const result = new ComfyInputsSpec(plainObject) const intInput = result.required.intInput as IntInputSpec const stringInput = result.required.stringInput as StringInputSpec @@ -73,10 +67,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.optional.booleanInput).toBeInstanceOf(BooleanInputSpec) - expect(result.optional.floatInput).toBeInstanceOf(FloatInputSpec) + const result = new ComfyInputsSpec(plainObject) const booleanInput = result.optional.booleanInput as BooleanInputSpec const floatInput = result.optional.floatInput as FloatInputSpec @@ -96,9 +87,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.optional.comboInput).toBeInstanceOf(ComboInputSpec) + const result = new ComfyInputsSpec(plainObject) expect(result.optional.comboInput.type).toBe('COMBO') expect(result.optional.comboInput.default).toBe(2) }) @@ -110,9 +99,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.optional.comboInput).toBeInstanceOf(ComboInputSpec) + const result = new ComfyInputsSpec(plainObject) expect(result.optional.comboInput.type).toBe('COMBO') // Should pick the first choice as default expect(result.optional.comboInput.default).toBe(1) @@ -125,9 +112,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.optional.customInput).toBeInstanceOf(CustomInputSpec) + const result = new ComfyInputsSpec(plainObject) expect(result.optional.customInput.type).toBe('CUSTOM_TYPE') expect(result.optional.customInput.default).toBe('custom value') }) @@ -140,7 +125,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) + const result = new ComfyInputsSpec(plainObject) expect(result.hidden).toEqual(plainObject.hidden) expect(result.hidden?.someHiddenValue).toBe(42) @@ -150,7 +135,7 @@ describe('ComfyInputsSpec', () => { it('should handle empty or undefined fields', () => { const plainObject = {} - const result = plainToClass(ComfyInputsSpec, plainObject) + const result = new ComfyInputsSpec(plainObject) expect(result).toBeInstanceOf(ComfyInputsSpec) expect(result.required).toEqual({}) @@ -177,7 +162,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['intOutput'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result).toBeInstanceOf(ComfyNodeDefImpl) expect(result.name).toBe('TestNode') @@ -215,7 +200,7 @@ describe('ComfyNodeDefImpl', () => { deprecated: true } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.deprecated).toBe(true) }) @@ -238,7 +223,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['intOutput'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.deprecated).toBe(true) }) @@ -255,7 +240,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['stringOutput', 'comboOutput', 'floatOutput'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.output.all).toEqual([ { @@ -293,7 +278,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['INT', 'FLOAT', 'FLOAT'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.output.all).toEqual([ { @@ -330,7 +315,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['output', 'output', 'uniqueOutput'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.output.all).toEqual([ { index: 0, @@ -366,7 +351,7 @@ describe('ComfyNodeDefImpl', () => { output_name: [] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.output.all).toEqual([]) }) @@ -393,7 +378,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['result'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.input).toBeInstanceOf(ComfyInputsSpec) expect(result.input.required).toBeDefined() diff --git a/tests-ui/tests/nodeSearchService.test.ts b/tests-ui/tests/nodeSearchService.test.ts index fea642ac9..e89244a30 100644 --- a/tests-ui/tests/nodeSearchService.test.ts +++ b/tests-ui/tests/nodeSearchService.test.ts @@ -1,6 +1,5 @@ import { NodeSearchService } from '@/services/nodeSearchService' import { ComfyNodeDefImpl } from '@/stores/nodeDefStore' -import { plainToClass } from 'class-transformer' const EXAMPLE_NODE_DEFS: ComfyNodeDefImpl[] = [ { @@ -52,7 +51,7 @@ const EXAMPLE_NODE_DEFS: ComfyNodeDefImpl[] = [ output_node: false } ].map((nodeDef) => { - const def = plainToClass(ComfyNodeDefImpl, nodeDef) + const def = new ComfyNodeDefImpl(nodeDef) def['postProcessSearchScores'] = (s) => s return def })