mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-20 06:44:32 +00:00
Remove class-transformer dependency (#1187)
* nit * Fix test * Remove class-transformer and its deps * nit * Fix invalid type for dummy node
This commit is contained in:
@@ -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<T = any> {
|
||||
export interface BaseInputSpec<T = any> {
|
||||
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<number> {
|
||||
@Type(() => Number)
|
||||
export interface NumericInputSpec extends BaseInputSpec<number> {
|
||||
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<boolean> {
|
||||
type: 'BOOLEAN' = 'BOOLEAN'
|
||||
|
||||
export interface BooleanInputSpec extends BaseInputSpec<boolean> {
|
||||
type: 'BOOLEAN'
|
||||
labelOn?: string
|
||||
labelOff?: string
|
||||
}
|
||||
|
||||
export class StringInputSpec extends BaseInputSpec<string> {
|
||||
type: 'STRING' = 'STRING'
|
||||
|
||||
@Type(() => Boolean)
|
||||
export interface StringInputSpec extends BaseInputSpec<string> {
|
||||
type: 'STRING'
|
||||
multiline?: boolean
|
||||
|
||||
@Type(() => Boolean)
|
||||
dynamicPrompts?: boolean
|
||||
}
|
||||
|
||||
export class ComboInputSpec extends BaseInputSpec<any> {
|
||||
type: string = 'COMBO'
|
||||
|
||||
@Transform(({ value }) => value[0])
|
||||
export interface ComboInputSpec extends BaseInputSpec<any> {
|
||||
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<string, BaseInputSpec> = {}
|
||||
|
||||
@Transform(({ value }) => ComfyInputsSpec.transformInputSpecRecord(value))
|
||||
optional: Record<string, BaseInputSpec> = {}
|
||||
|
||||
required: Record<string, BaseInputSpec>
|
||||
optional: Record<string, BaseInputSpec>
|
||||
hidden?: Record<string, any>
|
||||
|
||||
constructor(obj: ComfyInputsSpecSchema) {
|
||||
this.required = ComfyInputsSpec.transformInputSpecRecord(obj.required) ?? {}
|
||||
this.optional = ComfyInputsSpec.transformInputSpecRecord(obj.optional) ?? {}
|
||||
this.hidden = obj.hidden
|
||||
}
|
||||
|
||||
private static transformInputSpecRecord(
|
||||
record: Record<string, any>
|
||||
): Record<string, BaseInputSpec> {
|
||||
@@ -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
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user