mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
Use V2 schema in widget constructors (Part 1) (#2860)
This commit is contained in:
@@ -1,28 +1,33 @@
|
|||||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||||
|
|
||||||
import { type InputSpec, isBooleanInputSpec } from '@/schemas/nodeDefSchema'
|
import {
|
||||||
import type { ComfyWidgetConstructor } from '@/scripts/widgets'
|
type InputSpec,
|
||||||
|
isBooleanInputSpec
|
||||||
|
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
|
import { type ComfyWidgetConstructorV2 } from '@/scripts/widgets'
|
||||||
|
|
||||||
export const useBooleanWidget = () => {
|
export const useBooleanWidget = () => {
|
||||||
const widgetConstructor: ComfyWidgetConstructor = (
|
const widgetConstructor: ComfyWidgetConstructorV2 = (
|
||||||
node: LGraphNode,
|
node: LGraphNode,
|
||||||
inputName: string,
|
inputSpec: InputSpec
|
||||||
inputData: InputSpec
|
|
||||||
) => {
|
) => {
|
||||||
if (!isBooleanInputSpec(inputData)) {
|
if (!isBooleanInputSpec(inputSpec)) {
|
||||||
throw new Error(`Invalid input data: ${inputData}`)
|
throw new Error(`Invalid input data: ${inputSpec}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputOptions = inputData[1] ?? {}
|
const defaultVal = inputSpec.default ?? false
|
||||||
const defaultVal = inputOptions?.default ?? false
|
|
||||||
const options = {
|
const options = {
|
||||||
on: inputOptions?.label_on,
|
on: inputSpec.label_on,
|
||||||
off: inputOptions?.label_off
|
off: inputSpec.label_off
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return node.addWidget(
|
||||||
widget: node.addWidget('toggle', inputName, defaultVal, () => {}, options)
|
'toggle',
|
||||||
}
|
inputSpec.name,
|
||||||
|
defaultVal,
|
||||||
|
() => {},
|
||||||
|
options
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return widgetConstructor
|
return widgetConstructor
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import type { LGraphNode } from '@comfyorg/litegraph'
|
|||||||
import type { INumericWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
import type { INumericWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import { type InputSpec, isFloatInputSpec } from '@/schemas/nodeDefSchema'
|
import {
|
||||||
import type { ComfyWidgetConstructor } from '@/scripts/widgets'
|
type InputSpec,
|
||||||
|
isFloatInputSpec
|
||||||
|
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
|
import { type ComfyWidgetConstructorV2 } from '@/scripts/widgets'
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
import { useSettingStore } from '@/stores/settingStore'
|
||||||
|
|
||||||
function onFloatValueChange(this: INumericWidget, v: number) {
|
function onFloatValueChange(this: INumericWidget, v: number) {
|
||||||
@@ -22,23 +25,18 @@ export const _for_testing = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useFloatWidget = () => {
|
export const useFloatWidget = () => {
|
||||||
const widgetConstructor: ComfyWidgetConstructor = (
|
const widgetConstructor: ComfyWidgetConstructorV2 = (
|
||||||
node: LGraphNode,
|
node: LGraphNode,
|
||||||
inputName: string,
|
inputSpec: InputSpec
|
||||||
inputData: InputSpec
|
|
||||||
) => {
|
) => {
|
||||||
if (!isFloatInputSpec(inputData)) {
|
if (!isFloatInputSpec(inputSpec)) {
|
||||||
throw new Error(`Invalid input data: ${inputData}`)
|
throw new Error(`Invalid input data: ${inputSpec}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move to outer scope to avoid re-initializing on every call
|
|
||||||
// Blocked on ComfyWidgets lazy initialization.
|
|
||||||
|
|
||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
const sliderEnabled = !settingStore.get('Comfy.DisableSliders')
|
const sliderEnabled = !settingStore.get('Comfy.DisableSliders')
|
||||||
const inputOptions = inputData[1] ?? {}
|
|
||||||
|
|
||||||
const display_type = inputOptions?.display
|
const display_type = inputSpec.display
|
||||||
const widgetType =
|
const widgetType =
|
||||||
sliderEnabled && display_type == 'slider'
|
sliderEnabled && display_type == 'slider'
|
||||||
? 'slider'
|
? 'slider'
|
||||||
@@ -46,33 +44,31 @@ export const useFloatWidget = () => {
|
|||||||
? 'knob'
|
? 'knob'
|
||||||
: 'number'
|
: 'number'
|
||||||
|
|
||||||
const step = inputOptions.step ?? 0.5
|
const step = inputSpec.step ?? 0.5
|
||||||
const precision =
|
const precision =
|
||||||
settingStore.get('Comfy.FloatRoundingPrecision') ||
|
settingStore.get('Comfy.FloatRoundingPrecision') ||
|
||||||
Math.max(0, -Math.floor(Math.log10(step)))
|
Math.max(0, -Math.floor(Math.log10(step)))
|
||||||
const enableRounding = !settingStore.get('Comfy.DisableFloatRounding')
|
const enableRounding = !settingStore.get('Comfy.DisableFloatRounding')
|
||||||
|
|
||||||
const defaultValue = inputOptions.default ?? 0
|
const defaultValue = inputSpec.default ?? 0
|
||||||
return {
|
return node.addWidget(
|
||||||
widget: node.addWidget(
|
widgetType,
|
||||||
widgetType,
|
inputSpec.name,
|
||||||
inputName,
|
defaultValue,
|
||||||
defaultValue,
|
onFloatValueChange,
|
||||||
onFloatValueChange,
|
{
|
||||||
{
|
min: inputSpec.min ?? 0,
|
||||||
min: inputOptions.min ?? 0,
|
max: inputSpec.max ?? 2048,
|
||||||
max: inputOptions.max ?? 2048,
|
round:
|
||||||
round:
|
enableRounding && precision && !inputSpec.round
|
||||||
enableRounding && precision && !inputOptions.round
|
? (1_000_000 * Math.pow(0.1, precision)) / 1_000_000
|
||||||
? (1_000_000 * Math.pow(0.1, precision)) / 1_000_000
|
: (inputSpec.round as number),
|
||||||
: (inputOptions.round as number),
|
/** @deprecated Use step2 instead. The 10x value is a legacy implementation. */
|
||||||
/** @deprecated Use step2 instead. The 10x value is a legacy implementation. */
|
step: step * 10.0,
|
||||||
step: step * 10.0,
|
step2: step,
|
||||||
step2: step,
|
precision
|
||||||
precision
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return widgetConstructor
|
return widgetConstructor
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||||
import type { INumericWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
import type { INumericWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||||
|
|
||||||
import { type InputSpec, isIntInputSpec } from '@/schemas/nodeDefSchema'
|
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
|
||||||
import type { ComfyApp } from '@/scripts/app'
|
|
||||||
import {
|
import {
|
||||||
type ComfyWidgetConstructor,
|
type InputSpec,
|
||||||
|
isIntInputSpec
|
||||||
|
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
|
import {
|
||||||
|
type ComfyWidgetConstructorV2,
|
||||||
addValueControlWidget
|
addValueControlWidget
|
||||||
} from '@/scripts/widgets'
|
} from '@/scripts/widgets'
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
import { useSettingStore } from '@/stores/settingStore'
|
||||||
@@ -33,21 +36,17 @@ export const _for_testing = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useIntWidget = () => {
|
export const useIntWidget = () => {
|
||||||
const widgetConstructor: ComfyWidgetConstructor = (
|
const widgetConstructor: ComfyWidgetConstructorV2 = (
|
||||||
node: LGraphNode,
|
node: LGraphNode,
|
||||||
inputName: string,
|
inputSpec: InputSpec
|
||||||
inputData: InputSpec,
|
|
||||||
app: ComfyApp,
|
|
||||||
widgetName?: string
|
|
||||||
) => {
|
) => {
|
||||||
if (!isIntInputSpec(inputData)) {
|
if (!isIntInputSpec(inputSpec)) {
|
||||||
throw new Error(`Invalid input data: ${inputData}`)
|
throw new Error(`Invalid input data: ${inputSpec}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
const sliderEnabled = !settingStore.get('Comfy.DisableSliders')
|
const sliderEnabled = !settingStore.get('Comfy.DisableSliders')
|
||||||
const inputOptions = inputData[1] ?? {}
|
const display_type = inputSpec.display
|
||||||
const display_type = inputOptions?.display
|
|
||||||
const widgetType =
|
const widgetType =
|
||||||
sliderEnabled && display_type == 'slider'
|
sliderEnabled && display_type == 'slider'
|
||||||
? 'slider'
|
? 'slider'
|
||||||
@@ -55,38 +54,36 @@ export const useIntWidget = () => {
|
|||||||
? 'knob'
|
? 'knob'
|
||||||
: 'number'
|
: 'number'
|
||||||
|
|
||||||
const step = inputOptions.step ?? 1
|
const step = inputSpec.step ?? 1
|
||||||
const defaultValue = inputOptions.default ?? 0
|
const defaultValue = inputSpec.default ?? 0
|
||||||
const result = {
|
const widget = node.addWidget(
|
||||||
widget: node.addWidget(
|
widgetType,
|
||||||
widgetType,
|
inputSpec.name,
|
||||||
inputName,
|
defaultValue,
|
||||||
defaultValue,
|
onValueChange,
|
||||||
onValueChange,
|
{
|
||||||
{
|
min: inputSpec.min ?? 0,
|
||||||
min: inputOptions.min ?? 0,
|
max: inputSpec.max ?? 2048,
|
||||||
max: inputOptions.max ?? 2048,
|
/** @deprecated Use step2 instead. The 10x value is a legacy implementation. */
|
||||||
/** @deprecated Use step2 instead. The 10x value is a legacy implementation. */
|
step: step * 10,
|
||||||
step: step * 10,
|
step2: step,
|
||||||
step2: step,
|
precision: 0
|
||||||
precision: 0
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputOptions.control_after_generate) {
|
if (inputSpec.control_after_generate) {
|
||||||
const seedControl = addValueControlWidget(
|
const seedControl = addValueControlWidget(
|
||||||
node,
|
node,
|
||||||
result.widget,
|
widget,
|
||||||
'randomize',
|
'randomize',
|
||||||
undefined,
|
undefined,
|
||||||
widgetName,
|
undefined,
|
||||||
inputData
|
transformInputSpecV2ToV1(inputSpec)
|
||||||
)
|
)
|
||||||
result.widget.linkedWidgets = [seedControl]
|
widget.linkedWidgets = [seedControl]
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return widget
|
||||||
}
|
}
|
||||||
return widgetConstructor
|
return widgetConstructor
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
|
||||||
|
|
||||||
import { type InputSpec, isIntInputSpec } from '@/schemas/nodeDefSchema'
|
|
||||||
import type { ComfyWidgetConstructor } from '@/scripts/widgets'
|
|
||||||
import type { ComfyApp } from '@/types'
|
|
||||||
|
|
||||||
import { useIntWidget } from './useIntWidget'
|
|
||||||
|
|
||||||
export const useSeedWidget = () => {
|
|
||||||
const IntWidget = useIntWidget()
|
|
||||||
|
|
||||||
const widgetConstructor: ComfyWidgetConstructor = (
|
|
||||||
node: LGraphNode,
|
|
||||||
inputName: string,
|
|
||||||
inputData: InputSpec,
|
|
||||||
app: ComfyApp,
|
|
||||||
widgetName?: string
|
|
||||||
) => {
|
|
||||||
if (!isIntInputSpec(inputData)) {
|
|
||||||
throw new Error(`Invalid input data: ${inputData}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return IntWidget(
|
|
||||||
node,
|
|
||||||
inputName,
|
|
||||||
[
|
|
||||||
'INT',
|
|
||||||
{
|
|
||||||
...inputData[1],
|
|
||||||
control_after_generate: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
app,
|
|
||||||
widgetName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return widgetConstructor
|
|
||||||
}
|
|
||||||
@@ -25,14 +25,20 @@ export function transformNodeDefV1ToV2(
|
|||||||
// Process required inputs
|
// Process required inputs
|
||||||
if (nodeDefV1.input?.required) {
|
if (nodeDefV1.input?.required) {
|
||||||
Object.entries(nodeDefV1.input.required).forEach(([name, inputSpecV1]) => {
|
Object.entries(nodeDefV1.input.required).forEach(([name, inputSpecV1]) => {
|
||||||
inputs[name] = transformInputSpec(inputSpecV1, name, false)
|
inputs[name] = transformInputSpecV1ToV2(inputSpecV1, {
|
||||||
|
name,
|
||||||
|
isOptional: false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process optional inputs
|
// Process optional inputs
|
||||||
if (nodeDefV1.input?.optional) {
|
if (nodeDefV1.input?.optional) {
|
||||||
Object.entries(nodeDefV1.input.optional).forEach(([name, inputSpecV1]) => {
|
Object.entries(nodeDefV1.input.optional).forEach(([name, inputSpecV1]) => {
|
||||||
inputs[name] = transformInputSpec(inputSpecV1, name, true)
|
inputs[name] = transformInputSpecV1ToV2(inputSpecV1, {
|
||||||
|
name,
|
||||||
|
isOptional: true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,11 +87,15 @@ export function transformNodeDefV1ToV2(
|
|||||||
* @param isOptional Whether the input is optional
|
* @param isOptional Whether the input is optional
|
||||||
* @returns The transformed V2 input specification
|
* @returns The transformed V2 input specification
|
||||||
*/
|
*/
|
||||||
function transformInputSpec(
|
export function transformInputSpecV1ToV2(
|
||||||
inputSpecV1: InputSpecV1,
|
inputSpecV1: InputSpecV1,
|
||||||
name: string,
|
kwargs: {
|
||||||
isOptional: boolean
|
name: string
|
||||||
|
isOptional?: boolean
|
||||||
|
}
|
||||||
): InputSpecV2 {
|
): InputSpecV2 {
|
||||||
|
const { name, isOptional = false } = kwargs
|
||||||
|
|
||||||
// Extract options from the input spec
|
// Extract options from the input spec
|
||||||
const options = inputSpecV1[1] || {}
|
const options = inputSpecV1[1] || {}
|
||||||
|
|
||||||
@@ -119,3 +129,9 @@ function transformInputSpec(
|
|||||||
...baseProps
|
...baseProps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transformInputSpecV2ToV1(
|
||||||
|
inputSpecV2: InputSpecV2
|
||||||
|
): InputSpecV1 {
|
||||||
|
return [inputSpecV2.type, inputSpecV2]
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,15 +11,21 @@ import { useFloatWidget } from '@/composables/widgets/useFloatWidget'
|
|||||||
import { useImageUploadWidget } from '@/composables/widgets/useImageUploadWidget'
|
import { useImageUploadWidget } from '@/composables/widgets/useImageUploadWidget'
|
||||||
import { useIntWidget } from '@/composables/widgets/useIntWidget'
|
import { useIntWidget } from '@/composables/widgets/useIntWidget'
|
||||||
import { useMarkdownWidget } from '@/composables/widgets/useMarkdownWidget'
|
import { useMarkdownWidget } from '@/composables/widgets/useMarkdownWidget'
|
||||||
import { useSeedWidget } from '@/composables/widgets/useSeedWidget'
|
|
||||||
import { useStringWidget } from '@/composables/widgets/useStringWidget'
|
import { useStringWidget } from '@/composables/widgets/useStringWidget'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
import { transformInputSpecV1ToV2 } from '@/schemas/nodeDef/migration'
|
||||||
|
import type { InputSpec as InputSpecV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
import type { InputSpec } from '@/schemas/nodeDefSchema'
|
import type { InputSpec } from '@/schemas/nodeDefSchema'
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
import { useSettingStore } from '@/stores/settingStore'
|
||||||
|
|
||||||
import type { ComfyApp } from './app'
|
import type { ComfyApp } from './app'
|
||||||
import './domWidget'
|
import './domWidget'
|
||||||
|
|
||||||
|
export type ComfyWidgetConstructorV2 = (
|
||||||
|
node: LGraphNode,
|
||||||
|
inputSpec: InputSpecV2
|
||||||
|
) => IWidget
|
||||||
|
|
||||||
export type ComfyWidgetConstructor = (
|
export type ComfyWidgetConstructor = (
|
||||||
node: LGraphNode,
|
node: LGraphNode,
|
||||||
inputName: string,
|
inputName: string,
|
||||||
@@ -28,6 +34,24 @@ export type ComfyWidgetConstructor = (
|
|||||||
widgetName?: string
|
widgetName?: string
|
||||||
) => { widget: IWidget; minWidth?: number; minHeight?: number }
|
) => { widget: IWidget; minWidth?: number; minHeight?: number }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a V2 widget constructor to a V1 widget constructor.
|
||||||
|
* @param widgetConstructorV2 The V2 widget constructor to transform.
|
||||||
|
* @returns The transformed V1 widget constructor.
|
||||||
|
*/
|
||||||
|
const transformWidgetConstructorV2ToV1 = (
|
||||||
|
widgetConstructorV2: ComfyWidgetConstructorV2
|
||||||
|
): ComfyWidgetConstructor => {
|
||||||
|
return (node, inputName, inputData) => {
|
||||||
|
const inputSpec = transformInputSpecV1ToV2(inputData, {
|
||||||
|
name: inputName
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
widget: widgetConstructorV2(node, inputSpec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function controlValueRunBefore() {
|
function controlValueRunBefore() {
|
||||||
return useSettingStore().get('Comfy.WidgetControlMode') === 'before'
|
return useSettingStore().get('Comfy.WidgetControlMode') === 'before'
|
||||||
}
|
}
|
||||||
@@ -251,14 +275,19 @@ export function addValueControlWidgets(
|
|||||||
return widgets
|
return widgets
|
||||||
}
|
}
|
||||||
|
|
||||||
const SeedWidget = useSeedWidget()
|
const seedWidget = transformWidgetConstructorV2ToV1((node, inputSpec) => {
|
||||||
|
return useIntWidget()(node, {
|
||||||
|
...inputSpec,
|
||||||
|
control_after_generate: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
export const ComfyWidgets: Record<string, ComfyWidgetConstructor> = {
|
export const ComfyWidgets: Record<string, ComfyWidgetConstructor> = {
|
||||||
'INT:seed': SeedWidget,
|
'INT:seed': seedWidget,
|
||||||
'INT:noise_seed': SeedWidget,
|
'INT:noise_seed': seedWidget,
|
||||||
INT: useIntWidget(),
|
INT: transformWidgetConstructorV2ToV1(useIntWidget()),
|
||||||
FLOAT: useFloatWidget(),
|
FLOAT: transformWidgetConstructorV2ToV1(useFloatWidget()),
|
||||||
BOOLEAN: useBooleanWidget(),
|
BOOLEAN: transformWidgetConstructorV2ToV1(useBooleanWidget()),
|
||||||
STRING: useStringWidget(),
|
STRING: useStringWidget(),
|
||||||
MARKDOWN: useMarkdownWidget(),
|
MARKDOWN: useMarkdownWidget(),
|
||||||
COMBO: useComboWidget(),
|
COMBO: useComboWidget(),
|
||||||
|
|||||||
Reference in New Issue
Block a user