mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-28 18:22:40 +00:00
[Refactor] Move zod schemas to schemas/ folder (#2753)
This commit is contained in:
@@ -1,612 +0,0 @@
|
||||
import { LinkMarkerShape } from '@comfyorg/litegraph'
|
||||
import { ZodType, z } from 'zod'
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
|
||||
import { colorPalettesSchema } from './colorPaletteTypes'
|
||||
import { zComfyWorkflow, zNodeId } from './comfyWorkflow'
|
||||
import { zKeybinding } from './keyBindingTypes'
|
||||
import { NodeBadgeMode } from './nodeSource'
|
||||
import { LinkReleaseTriggerAction } from './searchBoxTypes'
|
||||
|
||||
const zNodeType = z.string()
|
||||
const zQueueIndex = z.number()
|
||||
const zPromptId = z.string()
|
||||
const zResultItem = z.object({
|
||||
filename: z.string().optional(),
|
||||
subfolder: z.string().optional(),
|
||||
type: z.string().optional()
|
||||
})
|
||||
export type ResultItem = z.infer<typeof zResultItem>
|
||||
const zOutputs = z
|
||||
.object({
|
||||
audio: z.array(zResultItem).optional(),
|
||||
images: z.array(zResultItem).optional(),
|
||||
animated: z.array(z.boolean()).optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
// WS messages
|
||||
const zStatusWsMessageStatus = z.object({
|
||||
exec_info: z.object({
|
||||
queue_remaining: z.number().int()
|
||||
})
|
||||
})
|
||||
|
||||
const zStatusWsMessage = z.object({
|
||||
status: zStatusWsMessageStatus.nullish(),
|
||||
sid: z.string().nullish()
|
||||
})
|
||||
|
||||
const zProgressWsMessage = z.object({
|
||||
value: z.number().int(),
|
||||
max: z.number().int(),
|
||||
prompt_id: zPromptId,
|
||||
node: zNodeId
|
||||
})
|
||||
|
||||
const zExecutingWsMessage = z.object({
|
||||
node: zNodeId,
|
||||
display_node: zNodeId,
|
||||
prompt_id: zPromptId
|
||||
})
|
||||
|
||||
const zExecutedWsMessage = zExecutingWsMessage.extend({
|
||||
output: zOutputs,
|
||||
merge: z.boolean().optional()
|
||||
})
|
||||
|
||||
const zExecutionWsMessageBase = z.object({
|
||||
prompt_id: zPromptId,
|
||||
timestamp: z.number().int()
|
||||
})
|
||||
|
||||
const zExecutionStartWsMessage = zExecutionWsMessageBase
|
||||
const zExecutionSuccessWsMessage = zExecutionWsMessageBase
|
||||
const zExecutionCachedWsMessage = zExecutionWsMessageBase.extend({
|
||||
nodes: z.array(zNodeId)
|
||||
})
|
||||
const zExecutionInterruptedWsMessage = zExecutionWsMessageBase.extend({
|
||||
node_id: zNodeId,
|
||||
node_type: zNodeType,
|
||||
executed: z.array(zNodeId)
|
||||
})
|
||||
const zExecutionErrorWsMessage = zExecutionWsMessageBase.extend({
|
||||
node_id: zNodeId,
|
||||
node_type: zNodeType,
|
||||
executed: z.array(zNodeId),
|
||||
exception_message: z.string(),
|
||||
exception_type: z.string(),
|
||||
traceback: z.array(z.string()),
|
||||
current_inputs: z.any(),
|
||||
current_outputs: z.any()
|
||||
})
|
||||
|
||||
const zTerminalSize = z.object({
|
||||
cols: z.number(),
|
||||
row: z.number()
|
||||
})
|
||||
const zLogEntry = z.object({
|
||||
t: z.string(),
|
||||
m: z.string()
|
||||
})
|
||||
const zLogsWsMessage = z.object({
|
||||
size: zTerminalSize.optional(),
|
||||
entries: z.array(zLogEntry)
|
||||
})
|
||||
const zLogRawResponse = z.object({
|
||||
size: zTerminalSize,
|
||||
entries: z.array(zLogEntry)
|
||||
})
|
||||
|
||||
export type StatusWsMessageStatus = z.infer<typeof zStatusWsMessageStatus>
|
||||
export type StatusWsMessage = z.infer<typeof zStatusWsMessage>
|
||||
export type ProgressWsMessage = z.infer<typeof zProgressWsMessage>
|
||||
export type ExecutingWsMessage = z.infer<typeof zExecutingWsMessage>
|
||||
export type ExecutedWsMessage = z.infer<typeof zExecutedWsMessage>
|
||||
export type ExecutionStartWsMessage = z.infer<typeof zExecutionStartWsMessage>
|
||||
export type ExecutionSuccessWsMessage = z.infer<
|
||||
typeof zExecutionSuccessWsMessage
|
||||
>
|
||||
export type ExecutionCachedWsMessage = z.infer<typeof zExecutionCachedWsMessage>
|
||||
export type ExecutionInterruptedWsMessage = z.infer<
|
||||
typeof zExecutionInterruptedWsMessage
|
||||
>
|
||||
export type ExecutionErrorWsMessage = z.infer<typeof zExecutionErrorWsMessage>
|
||||
export type LogsWsMessage = z.infer<typeof zLogsWsMessage>
|
||||
// End of ws messages
|
||||
|
||||
const zPromptInputItem = z.object({
|
||||
inputs: z.record(z.string(), z.any()),
|
||||
class_type: zNodeType
|
||||
})
|
||||
|
||||
const zPromptInputs = z.record(zPromptInputItem)
|
||||
|
||||
const zExtraPngInfo = z
|
||||
.object({
|
||||
workflow: zComfyWorkflow
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zExtraData = z.object({
|
||||
/** extra_pnginfo can be missing is backend execution gets a validation error. */
|
||||
extra_pnginfo: zExtraPngInfo.optional(),
|
||||
client_id: z.string()
|
||||
})
|
||||
const zOutputsToExecute = z.array(zNodeId)
|
||||
|
||||
const zExecutionStartMessage = z.tuple([
|
||||
z.literal('execution_start'),
|
||||
zExecutionStartWsMessage
|
||||
])
|
||||
|
||||
const zExecutionSuccessMessage = z.tuple([
|
||||
z.literal('execution_success'),
|
||||
zExecutionSuccessWsMessage
|
||||
])
|
||||
|
||||
const zExecutionCachedMessage = z.tuple([
|
||||
z.literal('execution_cached'),
|
||||
zExecutionCachedWsMessage
|
||||
])
|
||||
|
||||
const zExecutionInterruptedMessage = z.tuple([
|
||||
z.literal('execution_interrupted'),
|
||||
zExecutionInterruptedWsMessage
|
||||
])
|
||||
|
||||
const zExecutionErrorMessage = z.tuple([
|
||||
z.literal('execution_error'),
|
||||
zExecutionErrorWsMessage
|
||||
])
|
||||
|
||||
const zStatusMessage = z.union([
|
||||
zExecutionStartMessage,
|
||||
zExecutionSuccessMessage,
|
||||
zExecutionCachedMessage,
|
||||
zExecutionInterruptedMessage,
|
||||
zExecutionErrorMessage
|
||||
])
|
||||
|
||||
const zStatus = z.object({
|
||||
status_str: z.enum(['success', 'error']),
|
||||
completed: z.boolean(),
|
||||
messages: z.array(zStatusMessage)
|
||||
})
|
||||
|
||||
const zTaskPrompt = z.tuple([
|
||||
zQueueIndex,
|
||||
zPromptId,
|
||||
zPromptInputs,
|
||||
zExtraData,
|
||||
zOutputsToExecute
|
||||
])
|
||||
|
||||
const zRunningTaskItem = z.object({
|
||||
taskType: z.literal('Running'),
|
||||
prompt: zTaskPrompt,
|
||||
// @Deprecated
|
||||
remove: z.object({
|
||||
name: z.literal('Cancel'),
|
||||
cb: z.function()
|
||||
})
|
||||
})
|
||||
|
||||
const zPendingTaskItem = z.object({
|
||||
taskType: z.literal('Pending'),
|
||||
prompt: zTaskPrompt
|
||||
})
|
||||
|
||||
const zTaskOutput = z.record(zNodeId, zOutputs)
|
||||
|
||||
const zNodeOutputsMeta = z.object({
|
||||
node_id: zNodeId,
|
||||
display_node: zNodeId,
|
||||
prompt_id: zPromptId.optional(),
|
||||
read_node_id: zNodeId.optional()
|
||||
})
|
||||
|
||||
const zTaskMeta = z.record(zNodeId, zNodeOutputsMeta)
|
||||
|
||||
const zHistoryTaskItem = z.object({
|
||||
taskType: z.literal('History'),
|
||||
prompt: zTaskPrompt,
|
||||
status: zStatus.optional(),
|
||||
outputs: zTaskOutput,
|
||||
meta: zTaskMeta.optional()
|
||||
})
|
||||
|
||||
const zTaskItem = z.union([
|
||||
zRunningTaskItem,
|
||||
zPendingTaskItem,
|
||||
zHistoryTaskItem
|
||||
])
|
||||
|
||||
const zTaskType = z.union([
|
||||
z.literal('Running'),
|
||||
z.literal('Pending'),
|
||||
z.literal('History')
|
||||
])
|
||||
|
||||
export type TaskType = z.infer<typeof zTaskType>
|
||||
export type TaskPrompt = z.infer<typeof zTaskPrompt>
|
||||
export type TaskStatus = z.infer<typeof zStatus>
|
||||
export type TaskOutput = z.infer<typeof zTaskOutput>
|
||||
|
||||
// `/queue`
|
||||
export type RunningTaskItem = z.infer<typeof zRunningTaskItem>
|
||||
export type PendingTaskItem = z.infer<typeof zPendingTaskItem>
|
||||
// `/history`
|
||||
export type HistoryTaskItem = z.infer<typeof zHistoryTaskItem>
|
||||
export type TaskItem = z.infer<typeof zTaskItem>
|
||||
|
||||
export function validateTaskItem(taskItem: unknown) {
|
||||
const result = zTaskItem.safeParse(taskItem)
|
||||
if (!result.success) {
|
||||
const zodError = fromZodError(result.error)
|
||||
// TODO accept a callback to report error.
|
||||
console.warn(
|
||||
`Invalid TaskItem: ${JSON.stringify(taskItem)}\n${zodError.message}`
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function inputSpec<TType extends ZodType, TSpec extends ZodType>(
|
||||
spec: [TType, TSpec],
|
||||
allowUpcast: boolean = true
|
||||
) {
|
||||
const [inputType, inputSpec] = spec
|
||||
// e.g. "INT" => ["INT", {}]
|
||||
const upcastTypes = allowUpcast
|
||||
? [inputType.transform((type) => [type, {}])]
|
||||
: []
|
||||
|
||||
return z.union([
|
||||
z.tuple([inputType, inputSpec]),
|
||||
z.tuple([inputType]).transform(([type]) => [type, {}]),
|
||||
...upcastTypes
|
||||
])
|
||||
}
|
||||
|
||||
const zRemoteWidgetConfig = z.object({
|
||||
route: z.string().url().or(z.string().startsWith('/')),
|
||||
refresh: z.number().gte(128).safe().or(z.number().lte(0).safe()).optional(),
|
||||
response_key: z.string().optional(),
|
||||
query_params: z.record(z.string(), z.string()).optional(),
|
||||
refresh_button: z.boolean().optional(),
|
||||
control_after_refresh: z.enum(['first', 'last']).optional(),
|
||||
timeout: z.number().gte(0).optional(),
|
||||
max_retries: z.number().gte(0).optional()
|
||||
})
|
||||
|
||||
const zBaseInputSpecValue = z
|
||||
.object({
|
||||
default: z.any().optional(),
|
||||
defaultInput: z.boolean().optional(),
|
||||
forceInput: z.boolean().optional(),
|
||||
lazy: z.boolean().optional(),
|
||||
rawLink: z.boolean().optional(),
|
||||
tooltip: z.string().optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
advanced: z.boolean().optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zIntInputSpec = inputSpec([
|
||||
z.literal('INT'),
|
||||
zBaseInputSpecValue.extend({
|
||||
min: z.number().optional(),
|
||||
max: z.number().optional(),
|
||||
step: z.number().optional(),
|
||||
// Note: Many node authors are using INT to pass list of INT.
|
||||
// TODO: Add list of ints type.
|
||||
default: z.union([z.number(), z.array(z.number())]).optional(),
|
||||
/**
|
||||
* If true, a linked widget will be added to the node to select the mode
|
||||
* of `control_after_generate`.
|
||||
*/
|
||||
control_after_generate: z.boolean().optional()
|
||||
})
|
||||
])
|
||||
|
||||
const zFloatInputSpec = inputSpec([
|
||||
z.literal('FLOAT'),
|
||||
zBaseInputSpecValue.extend({
|
||||
min: z.number().optional(),
|
||||
max: z.number().optional(),
|
||||
step: z.number().optional(),
|
||||
round: z.union([z.number(), z.literal(false)]).optional(),
|
||||
// Note: Many node authors are using FLOAT to pass list of FLOAT.
|
||||
// TODO: Add list of floats type.
|
||||
default: z.union([z.number(), z.array(z.number())]).optional()
|
||||
})
|
||||
])
|
||||
|
||||
const zBooleanInputSpec = inputSpec([
|
||||
z.literal('BOOLEAN'),
|
||||
zBaseInputSpecValue.extend({
|
||||
label_on: z.string().optional(),
|
||||
label_off: z.string().optional(),
|
||||
default: z.boolean().optional()
|
||||
})
|
||||
])
|
||||
|
||||
const zStringInputSpec = inputSpec([
|
||||
z.literal('STRING'),
|
||||
zBaseInputSpecValue.extend({
|
||||
default: z.string().optional(),
|
||||
multiline: z.boolean().optional(),
|
||||
dynamicPrompts: z.boolean().optional(),
|
||||
|
||||
// Multiline-only fields
|
||||
defaultVal: z.string().optional(),
|
||||
placeholder: z.string().optional()
|
||||
})
|
||||
])
|
||||
|
||||
const zComboInputProps = zBaseInputSpecValue.extend({
|
||||
control_after_generate: z.boolean().optional(),
|
||||
image_upload: z.boolean().optional(),
|
||||
image_folder: z.enum(['input', 'output', 'temp']).optional(),
|
||||
allow_batch: z.boolean().optional(),
|
||||
remote: zRemoteWidgetConfig.optional()
|
||||
})
|
||||
|
||||
// Dropdown Selection.
|
||||
const zComboInputSpec = inputSpec(
|
||||
[z.array(z.any()), zComboInputProps],
|
||||
/* allowUpcast=*/ false
|
||||
)
|
||||
|
||||
const zComboInputSpecV2 = inputSpec(
|
||||
[z.literal('COMBO'), zComboInputProps],
|
||||
/* allowUpcast=*/ false
|
||||
)
|
||||
export function isComboInputSpecV1(
|
||||
inputSpec: InputSpec
|
||||
): inputSpec is ComboInputSpec {
|
||||
return Array.isArray(inputSpec[0])
|
||||
}
|
||||
|
||||
const excludedLiterals = new Set(['INT', 'FLOAT', 'BOOLEAN', 'STRING', 'COMBO'])
|
||||
|
||||
const zCustomInputSpec = inputSpec([
|
||||
z.string().refine((value) => !excludedLiterals.has(value)),
|
||||
zBaseInputSpecValue
|
||||
])
|
||||
|
||||
const zInputSpec = z.union([
|
||||
zIntInputSpec,
|
||||
zFloatInputSpec,
|
||||
zBooleanInputSpec,
|
||||
zStringInputSpec,
|
||||
zComboInputSpec,
|
||||
zComboInputSpecV2,
|
||||
zCustomInputSpec
|
||||
])
|
||||
|
||||
const zComfyInputsSpec = z.object({
|
||||
required: z.record(zInputSpec).optional(),
|
||||
optional: z.record(zInputSpec).optional(),
|
||||
// Frontend repo is not using it, but some custom nodes are using the
|
||||
// hidden field to pass various values.
|
||||
hidden: z.record(z.any()).optional()
|
||||
})
|
||||
|
||||
const zComfyNodeDataType = z.string()
|
||||
const zComfyComboOutput = z.array(z.any())
|
||||
const zComfyOutputTypesSpec = z.array(
|
||||
z.union([zComfyNodeDataType, zComfyComboOutput])
|
||||
)
|
||||
|
||||
const zComfyNodeDef = z.object({
|
||||
input: zComfyInputsSpec.optional(),
|
||||
output: zComfyOutputTypesSpec.optional(),
|
||||
output_is_list: z.array(z.boolean()).optional(),
|
||||
output_name: z.array(z.string()).optional(),
|
||||
output_tooltips: z.array(z.string()).optional(),
|
||||
name: z.string(),
|
||||
display_name: z.string(),
|
||||
description: z.string(),
|
||||
category: z.string(),
|
||||
output_node: z.boolean(),
|
||||
python_module: z.string(),
|
||||
deprecated: z.boolean().optional(),
|
||||
experimental: z.boolean().optional()
|
||||
})
|
||||
|
||||
// `/object_info`
|
||||
export type ComboInputSpec = z.infer<typeof zComboInputSpec>
|
||||
export type ComboInputSpecV2 = z.infer<typeof zComboInputSpecV2>
|
||||
export type InputSpec = z.infer<typeof zInputSpec>
|
||||
export type ComfyInputsSpec = z.infer<typeof zComfyInputsSpec>
|
||||
export type ComfyOutputTypesSpec = z.infer<typeof zComfyOutputTypesSpec>
|
||||
export type ComfyNodeDef = z.infer<typeof zComfyNodeDef>
|
||||
|
||||
export function validateComfyNodeDef(
|
||||
data: any,
|
||||
onError: (error: string) => void = console.warn
|
||||
): ComfyNodeDef | null {
|
||||
const result = zComfyNodeDef.safeParse(data)
|
||||
if (!result.success) {
|
||||
const zodError = fromZodError(result.error)
|
||||
onError(
|
||||
`Invalid ComfyNodeDef: ${JSON.stringify(data)}\n${zodError.message}`
|
||||
)
|
||||
return null
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
const zEmbeddingsResponse = z.array(z.string())
|
||||
const zExtensionsResponse = z.array(z.string())
|
||||
const zPromptResponse = z.object({
|
||||
node_errors: z.array(z.string()).optional(),
|
||||
prompt_id: z.string().optional(),
|
||||
exec_info: z
|
||||
.object({
|
||||
queue_remaining: z.number().optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
|
||||
const zDeviceStats = z.object({
|
||||
name: z.string(),
|
||||
type: z.string(),
|
||||
index: z.number(),
|
||||
vram_total: z.number(),
|
||||
vram_free: z.number(),
|
||||
torch_vram_total: z.number(),
|
||||
torch_vram_free: z.number()
|
||||
})
|
||||
|
||||
export const zSystemStats = z.object({
|
||||
system: z.object({
|
||||
os: z.string(),
|
||||
python_version: z.string(),
|
||||
embedded_python: z.boolean(),
|
||||
comfyui_version: z.string(),
|
||||
pytorch_version: z.string(),
|
||||
argv: z.array(z.string()),
|
||||
ram_total: z.number(),
|
||||
ram_free: z.number()
|
||||
}),
|
||||
devices: z.array(zDeviceStats)
|
||||
})
|
||||
const zUser = z.object({
|
||||
storage: z.enum(['server']),
|
||||
// `migrated` is only available in single-user mode.
|
||||
migrated: z.boolean().optional(),
|
||||
// `users` is only available in multi-user server mode.
|
||||
users: z.record(z.string(), z.string()).optional()
|
||||
})
|
||||
const zUserData = z.array(z.array(z.string(), z.string()))
|
||||
const zUserDataFullInfo = z.object({
|
||||
path: z.string(),
|
||||
size: z.number(),
|
||||
modified: z.number()
|
||||
})
|
||||
const zBookmarkCustomization = z.object({
|
||||
icon: z.string().optional(),
|
||||
color: z.string().optional()
|
||||
})
|
||||
export type BookmarkCustomization = z.infer<typeof zBookmarkCustomization>
|
||||
|
||||
const zLinkReleaseTriggerAction = z.enum(
|
||||
Object.values(LinkReleaseTriggerAction) as [string, ...string[]]
|
||||
)
|
||||
|
||||
const zNodeBadgeMode = z.enum(
|
||||
Object.values(NodeBadgeMode) as [string, ...string[]]
|
||||
)
|
||||
|
||||
const zSettings = z.record(z.any()).and(
|
||||
z
|
||||
.object({
|
||||
'Comfy.ColorPalette': z.string(),
|
||||
'Comfy.CustomColorPalettes': colorPalettesSchema,
|
||||
'Comfy.ConfirmClear': z.boolean(),
|
||||
'Comfy.DevMode': z.boolean(),
|
||||
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
|
||||
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
|
||||
'Comfy.DisableFloatRounding': z.boolean(),
|
||||
'Comfy.DisableSliders': z.boolean(),
|
||||
'Comfy.DOMClippingEnabled': z.boolean(),
|
||||
'Comfy.EditAttention.Delta': z.number(),
|
||||
'Comfy.EnableTooltips': z.boolean(),
|
||||
'Comfy.EnableWorkflowViewRestore': z.boolean(),
|
||||
'Comfy.FloatRoundingPrecision': z.number(),
|
||||
'Comfy.Graph.CanvasInfo': z.boolean(),
|
||||
'Comfy.Graph.CanvasMenu': z.boolean(),
|
||||
'Comfy.Graph.CtrlShiftZoom': z.boolean(),
|
||||
'Comfy.Graph.LinkMarkers': z.nativeEnum(LinkMarkerShape),
|
||||
'Comfy.Graph.ZoomSpeed': z.number(),
|
||||
'Comfy.Group.DoubleClickTitleToEdit': z.boolean(),
|
||||
'Comfy.GroupSelectedNodes.Padding': z.number(),
|
||||
'Comfy.InvertMenuScrolling': z.boolean(),
|
||||
'Comfy.Locale': z.string(),
|
||||
'Comfy.Logging.Enabled': z.boolean(),
|
||||
'Comfy.NodeLibrary.Bookmarks': z.array(z.string()),
|
||||
'Comfy.NodeLibrary.Bookmarks.V2': z.array(z.string()),
|
||||
'Comfy.NodeLibrary.BookmarksCustomization': z.record(
|
||||
z.string(),
|
||||
zBookmarkCustomization
|
||||
),
|
||||
'Comfy.NodeInputConversionSubmenus': z.boolean(),
|
||||
'Comfy.LinkRelease.Action': zLinkReleaseTriggerAction,
|
||||
'Comfy.LinkRelease.ActionShift': zLinkReleaseTriggerAction,
|
||||
'Comfy.ModelLibrary.AutoLoadAll': z.boolean(),
|
||||
'Comfy.ModelLibrary.NameFormat': z.enum(['filename', 'title']),
|
||||
'Comfy.NodeSearchBoxImpl.NodePreview': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl': z.enum(['default', 'simple']),
|
||||
'Comfy.NodeSearchBoxImpl.ShowCategory': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl.ShowIdName': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl.ShowNodeFrequency': z.boolean(),
|
||||
'Comfy.NodeSuggestions.number': z.number(),
|
||||
'Comfy.Node.BypassAllLinksOnDelete': z.boolean(),
|
||||
'Comfy.Node.Opacity': z.number(),
|
||||
'Comfy.Node.MiddleClickRerouteNode': z.boolean(),
|
||||
'Comfy.Node.ShowDeprecated': z.boolean(),
|
||||
'Comfy.Node.ShowExperimental': z.boolean(),
|
||||
'Comfy.Pointer.ClickBufferTime': z.number(),
|
||||
'Comfy.Pointer.ClickDrift': z.number(),
|
||||
'Comfy.Pointer.DoubleClickTime': z.number(),
|
||||
'Comfy.PreviewFormat': z.string(),
|
||||
'Comfy.PromptFilename': z.boolean(),
|
||||
'Comfy.Sidebar.Location': z.enum(['left', 'right']),
|
||||
'Comfy.Sidebar.Size': z.enum(['small', 'normal']),
|
||||
'Comfy.SwitchUser': z.any(),
|
||||
'Comfy.SnapToGrid.GridSize': z.number(),
|
||||
'Comfy.TextareaWidget.FontSize': z.number(),
|
||||
'Comfy.TextareaWidget.Spellcheck': z.boolean(),
|
||||
'Comfy.UseNewMenu': z.enum(['Disabled', 'Top', 'Bottom']),
|
||||
'Comfy.TreeExplorer.ItemPadding': z.number(),
|
||||
'Comfy.Validation.Workflows': z.boolean(),
|
||||
'Comfy.Validation.NodeDefs': z.boolean(),
|
||||
'Comfy.Workflow.SortNodeIdOnSave': z.boolean(),
|
||||
'Comfy.Queue.ImageFit': z.enum(['contain', 'cover']),
|
||||
'Comfy.Workflow.WorkflowTabsPosition': z.enum([
|
||||
'Sidebar',
|
||||
'Topbar',
|
||||
'Topbar (2nd-row)'
|
||||
]),
|
||||
'Comfy.Node.DoubleClickTitleToEdit': z.boolean(),
|
||||
'Comfy.WidgetControlMode': z.enum(['before', 'after']),
|
||||
'Comfy.Window.UnloadConfirmation': z.boolean(),
|
||||
'Comfy.NodeBadge.NodeSourceBadgeMode': zNodeBadgeMode,
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode': zNodeBadgeMode,
|
||||
'Comfy.NodeBadge.NodeLifeCycleBadgeMode': zNodeBadgeMode,
|
||||
'Comfy.QueueButton.BatchCountLimit': z.number(),
|
||||
'Comfy.Queue.MaxHistoryItems': z.number(),
|
||||
'Comfy.Keybinding.UnsetBindings': z.array(zKeybinding),
|
||||
'Comfy.Keybinding.NewBindings': z.array(zKeybinding),
|
||||
'Comfy.Extension.Disabled': z.array(z.string()),
|
||||
'Comfy.Settings.ExtensionPanel': z.boolean(),
|
||||
'Comfy.LinkRenderMode': z.number(),
|
||||
'Comfy.Node.AutoSnapLinkToSlot': z.boolean(),
|
||||
'Comfy.Node.SnapHighlightsNode': z.boolean(),
|
||||
'Comfy.Server.ServerConfigValues': z.record(z.string(), z.any()),
|
||||
'Comfy.Server.LaunchArgs': z.record(z.string(), z.string()),
|
||||
'LiteGraph.Canvas.MaximumFps': z.number(),
|
||||
'Comfy.Workflow.ConfirmDelete': z.boolean(),
|
||||
'Comfy.RerouteBeta': z.boolean(),
|
||||
'LiteGraph.Canvas.LowQualityRenderingZoomThreshold': z.number(),
|
||||
'Comfy.Canvas.SelectionToolbox': z.boolean()
|
||||
})
|
||||
.optional()
|
||||
)
|
||||
|
||||
export type EmbeddingsResponse = z.infer<typeof zEmbeddingsResponse>
|
||||
export type ExtensionsResponse = z.infer<typeof zExtensionsResponse>
|
||||
export type PromptResponse = z.infer<typeof zPromptResponse>
|
||||
export type Settings = z.infer<typeof zSettings>
|
||||
export type DeviceStats = z.infer<typeof zDeviceStats>
|
||||
export type SystemStats = z.infer<typeof zSystemStats>
|
||||
export type User = z.infer<typeof zUser>
|
||||
export type UserData = z.infer<typeof zUserData>
|
||||
export type UserDataFullInfo = z.infer<typeof zUserDataFullInfo>
|
||||
export type TerminalSize = z.infer<typeof zTerminalSize>
|
||||
export type LogEntry = z.infer<typeof zLogEntry>
|
||||
export type LogsRawResponse = z.infer<typeof zLogRawResponse>
|
||||
export type RemoteWidgetConfig = z.infer<typeof zRemoteWidgetConfig>
|
||||
@@ -1,114 +0,0 @@
|
||||
import { LiteGraph } from '@comfyorg/litegraph'
|
||||
import { z } from 'zod'
|
||||
|
||||
const nodeSlotSchema = z.object({
|
||||
CLIP: z.string(),
|
||||
CLIP_VISION: z.string(),
|
||||
CLIP_VISION_OUTPUT: z.string(),
|
||||
CONDITIONING: z.string(),
|
||||
CONTROL_NET: z.string(),
|
||||
IMAGE: z.string(),
|
||||
LATENT: z.string(),
|
||||
MASK: z.string(),
|
||||
MODEL: z.string(),
|
||||
STYLE_MODEL: z.string(),
|
||||
VAE: z.string(),
|
||||
NOISE: z.string(),
|
||||
GUIDER: z.string(),
|
||||
SAMPLER: z.string(),
|
||||
SIGMAS: z.string(),
|
||||
TAESD: z.string()
|
||||
})
|
||||
|
||||
const litegraphBaseSchema = z.object({
|
||||
BACKGROUND_IMAGE: z.string(),
|
||||
CLEAR_BACKGROUND_COLOR: z.string(),
|
||||
NODE_TITLE_COLOR: z.string(),
|
||||
NODE_SELECTED_TITLE_COLOR: z.string(),
|
||||
NODE_TEXT_SIZE: z.number(),
|
||||
NODE_TEXT_COLOR: z.string(),
|
||||
NODE_TEXT_HIGHLIGHT_COLOR: z.string(),
|
||||
NODE_SUBTEXT_SIZE: z.number(),
|
||||
NODE_DEFAULT_COLOR: z.string(),
|
||||
NODE_DEFAULT_BGCOLOR: z.string(),
|
||||
NODE_DEFAULT_BOXCOLOR: z.string(),
|
||||
NODE_DEFAULT_SHAPE: z.union([
|
||||
z.literal(LiteGraph.BOX_SHAPE),
|
||||
z.literal(LiteGraph.ROUND_SHAPE),
|
||||
z.literal(LiteGraph.CARD_SHAPE),
|
||||
// Legacy palettes have string field for NODE_DEFAULT_SHAPE.
|
||||
z.string()
|
||||
]),
|
||||
NODE_BOX_OUTLINE_COLOR: z.string(),
|
||||
NODE_BYPASS_BGCOLOR: z.string(),
|
||||
NODE_ERROR_COLOUR: z.string(),
|
||||
DEFAULT_SHADOW_COLOR: z.string(),
|
||||
DEFAULT_GROUP_FONT: z.number(),
|
||||
WIDGET_BGCOLOR: z.string(),
|
||||
WIDGET_OUTLINE_COLOR: z.string(),
|
||||
WIDGET_TEXT_COLOR: z.string(),
|
||||
WIDGET_SECONDARY_TEXT_COLOR: z.string(),
|
||||
LINK_COLOR: z.string(),
|
||||
EVENT_LINK_COLOR: z.string(),
|
||||
CONNECTING_LINK_COLOR: z.string(),
|
||||
BADGE_FG_COLOR: z.string(),
|
||||
BADGE_BG_COLOR: z.string()
|
||||
})
|
||||
|
||||
const comfyBaseSchema = z.object({
|
||||
['fg-color']: z.string(),
|
||||
['bg-color']: z.string(),
|
||||
['bg-img']: z.string().optional(),
|
||||
['comfy-menu-bg']: z.string(),
|
||||
['comfy-menu-secondary-bg']: z.string(),
|
||||
['comfy-input-bg']: z.string(),
|
||||
['input-text']: z.string(),
|
||||
['descrip-text']: z.string(),
|
||||
['drag-text']: z.string(),
|
||||
['error-text']: z.string(),
|
||||
['border-color']: z.string(),
|
||||
['tr-even-bg-color']: z.string(),
|
||||
['tr-odd-bg-color']: z.string(),
|
||||
['content-bg']: z.string(),
|
||||
['content-fg']: z.string(),
|
||||
['content-hover-bg']: z.string(),
|
||||
['content-hover-fg']: z.string(),
|
||||
['bar-shadow']: z.string()
|
||||
})
|
||||
|
||||
const colorsSchema = z.object({
|
||||
node_slot: nodeSlotSchema,
|
||||
litegraph_base: litegraphBaseSchema,
|
||||
comfy_base: comfyBaseSchema
|
||||
})
|
||||
|
||||
const partialColorsSchema = z.object({
|
||||
node_slot: nodeSlotSchema.partial(),
|
||||
litegraph_base: litegraphBaseSchema.partial(),
|
||||
comfy_base: comfyBaseSchema.partial()
|
||||
})
|
||||
|
||||
// Palette in the wild can have custom metadata fields such as 'version'.
|
||||
export const paletteSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
colors: partialColorsSchema,
|
||||
light_theme: z.boolean().optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export const completedPaletteSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
colors: colorsSchema
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export const colorPalettesSchema = z.record(paletteSchema)
|
||||
|
||||
export type Colors = z.infer<typeof colorsSchema>
|
||||
export type Palette = z.infer<typeof paletteSchema>
|
||||
export type CompletedPalette = z.infer<typeof completedPaletteSchema>
|
||||
export type ColorPalettes = z.infer<typeof colorPalettesSchema>
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import { Positionable } from '@comfyorg/litegraph/dist/interfaces'
|
||||
|
||||
import type { ComfyNodeDef } from '@/schemas/apiSchema'
|
||||
import type { ComfyWorkflowJSON } from '@/schemas/comfyWorkflowSchema'
|
||||
import type { Keybinding } from '@/schemas/keyBindingSchema'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
import type { ComfyWidgetConstructor } from '@/scripts/widgets'
|
||||
import type { ComfyCommand } from '@/stores/commandStore'
|
||||
import type { ComfyNodeDef } from '@/types/apiTypes'
|
||||
import type { ComfyWorkflowJSON } from '@/types/comfyWorkflow'
|
||||
import type { BottomPanelExtension } from '@/types/extensionTypes'
|
||||
import type { Keybinding } from '@/types/keyBindingTypes'
|
||||
import type { SettingParams } from '@/types/settingTypes'
|
||||
|
||||
export type Widgets = Record<string, ComfyWidgetConstructor>
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
import { type SafeParseReturnType, z } from 'zod'
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
|
||||
// GroupNode is hacking node id to be a string, so we need to allow that.
|
||||
// innerNode.id = `${this.node.id}:${i}`
|
||||
// Remove it after GroupNode is redesigned.
|
||||
export const zNodeId = z.union([z.number().int(), z.string()])
|
||||
export const zNodeInputName = z.string()
|
||||
export type NodeId = z.infer<typeof zNodeId>
|
||||
export const zSlotIndex = z.union([
|
||||
z.number().int(),
|
||||
z
|
||||
.string()
|
||||
.transform((val) => parseInt(val))
|
||||
.refine((val) => !isNaN(val), {
|
||||
message: 'Invalid number'
|
||||
})
|
||||
])
|
||||
|
||||
// TODO: Investigate usage of array and number as data type usage in custom nodes.
|
||||
// Known usage:
|
||||
// - https://github.com/rgthree/rgthree-comfy Context Big node is using array as type.
|
||||
export const zDataType = z.union([z.string(), z.array(z.string()), z.number()])
|
||||
|
||||
const zVector2 = z.union([
|
||||
z
|
||||
.object({ 0: z.number(), 1: z.number() })
|
||||
.passthrough()
|
||||
.transform((v) => [v[0], v[1]]),
|
||||
z.tuple([z.number(), z.number()])
|
||||
])
|
||||
|
||||
// Definition of an AI model file used in the workflow.
|
||||
const zModelFile = z.object({
|
||||
name: z.string(),
|
||||
url: z.string().url(),
|
||||
hash: z.string().optional(),
|
||||
hash_type: z.string().optional(),
|
||||
directory: z.string()
|
||||
})
|
||||
|
||||
const zGraphState = z
|
||||
.object({
|
||||
lastGroupid: z.number().optional(),
|
||||
lastNodeId: z.number().optional(),
|
||||
lastLinkId: z.number().optional(),
|
||||
lastRerouteId: z.number().optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zComfyLink = z.tuple([
|
||||
z.number(), // Link id
|
||||
zNodeId, // Node id of source node
|
||||
zSlotIndex, // Output slot# of source node
|
||||
zNodeId, // Node id of destination node
|
||||
zSlotIndex, // Input slot# of destination node
|
||||
zDataType // Data type
|
||||
])
|
||||
|
||||
/** Extension to 0.4 schema (links as arrays): parent reroute ID */
|
||||
const zComfyLinkExtension = z
|
||||
.object({
|
||||
id: z.number(),
|
||||
parentId: z.number()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zComfyLinkObject = z
|
||||
.object({
|
||||
id: z.number(),
|
||||
origin_id: zNodeId,
|
||||
origin_slot: zSlotIndex,
|
||||
target_id: zNodeId,
|
||||
target_slot: zSlotIndex,
|
||||
type: zDataType,
|
||||
parentId: z.number().optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zReroute = z
|
||||
.object({
|
||||
id: z.number(),
|
||||
parentId: z.number().optional(),
|
||||
pos: zVector2,
|
||||
linkIds: z.array(z.number()).nullish()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zNodeOutput = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
type: zDataType,
|
||||
links: z.array(z.number()).nullable().optional(),
|
||||
slot_index: zSlotIndex.optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zNodeInput = z
|
||||
.object({
|
||||
name: zNodeInputName,
|
||||
type: zDataType,
|
||||
link: z.number().nullable().optional(),
|
||||
slot_index: zSlotIndex.optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zFlags = z
|
||||
.object({
|
||||
collapsed: z.boolean().optional(),
|
||||
pinned: z.boolean().optional(),
|
||||
allow_interaction: z.boolean().optional(),
|
||||
horizontal: z.boolean().optional(),
|
||||
skip_repeated_outputs: z.boolean().optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const repoLikeIdPattern = /^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$/
|
||||
const githubUsernamePattern = /^(?!-)(?!.*--)[a-zA-Z0-9-]+(?<!-)$/
|
||||
const gitHashPattern = /^[0-9a-f]{4,40}$/i
|
||||
const semverPattern =
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-z-]+(?:\.[\da-z-]+)*))?(?:\+([\da-z-]+(?:\.[\da-z-]+)*))?$/
|
||||
|
||||
// Shared schema for Comfy Node Registry IDs and GitHub repo names
|
||||
const zRepoLikeId = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(100)
|
||||
.regex(repoLikeIdPattern, {
|
||||
message: "ID can only contain ASCII letters, digits, '_', '-', and '.'"
|
||||
})
|
||||
.refine((id) => !/^[_\-.]|[_\-.]$/.test(id), {
|
||||
message: "ID must not start or end with '_', '-', or '.'"
|
||||
})
|
||||
|
||||
const zCnrId = zRepoLikeId
|
||||
const zGithubRepoName = zRepoLikeId
|
||||
|
||||
// GitHub username/organization schema
|
||||
const zGithubUsername = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(39)
|
||||
.regex(githubUsernamePattern, 'Invalid GitHub username/org')
|
||||
|
||||
// Auxiliary ID identifies node packs not installed via the Comfy Node Registry
|
||||
const zAuxId = z
|
||||
.string()
|
||||
.regex(/^[^/]+\/[^/]+$/, "Invalid format. Must be 'github-user/repo-name'")
|
||||
.transform((id) => id.split('/'))
|
||||
.refine(
|
||||
([username, repo]) =>
|
||||
zGithubUsername.safeParse(username).success &&
|
||||
zGithubRepoName.safeParse(repo).success,
|
||||
"Invalid aux_id: Must be valid 'github-username/github-repo-name'"
|
||||
)
|
||||
.transform(([username, repo]) => `${username}/${repo}`)
|
||||
|
||||
const zSemVer = z
|
||||
.string()
|
||||
.regex(semverPattern, 'Invalid semantic version (x.y.z)')
|
||||
const zGitHash = z.string().regex(gitHashPattern, 'Invalid Git commit hash')
|
||||
const zVersion = z.union([zSemVer, zGitHash])
|
||||
|
||||
const zProperties = z
|
||||
.object({
|
||||
['Node name for S&R']: z.string().optional(),
|
||||
cnr_id: zCnrId.optional(),
|
||||
aux_id: zAuxId.optional(),
|
||||
ver: zVersion.optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zWidgetValues = z.union([z.array(z.any()), z.record(z.any())])
|
||||
|
||||
const zComfyNode = z
|
||||
.object({
|
||||
id: zNodeId,
|
||||
type: z.string(),
|
||||
pos: zVector2,
|
||||
size: zVector2,
|
||||
flags: zFlags,
|
||||
order: z.number(),
|
||||
mode: z.number(),
|
||||
inputs: z.array(zNodeInput).optional(),
|
||||
outputs: z.array(zNodeOutput).optional(),
|
||||
properties: zProperties,
|
||||
widgets_values: zWidgetValues.optional(),
|
||||
color: z.string().optional(),
|
||||
bgcolor: z.string().optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zGroup = z
|
||||
.object({
|
||||
title: z.string(),
|
||||
bounding: z.tuple([z.number(), z.number(), z.number(), z.number()]),
|
||||
color: z.string().optional(),
|
||||
font_size: z.number().optional(),
|
||||
locked: z.boolean().optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zInfo = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
author: z.string(),
|
||||
description: z.string(),
|
||||
version: z.string(),
|
||||
created: z.string(),
|
||||
modified: z.string(),
|
||||
software: z.string()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zDS = z
|
||||
.object({
|
||||
scale: z.number(),
|
||||
offset: zVector2
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zConfig = z
|
||||
.object({
|
||||
links_ontop: z.boolean().optional(),
|
||||
align_to_grid: z.boolean().optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
const zExtra = z
|
||||
.object({
|
||||
ds: zDS.optional(),
|
||||
info: zInfo.optional(),
|
||||
linkExtensions: z.array(zComfyLinkExtension).optional(),
|
||||
reroutes: z.array(zReroute).optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
/** Schema version 0.4 */
|
||||
export const zComfyWorkflow = z
|
||||
.object({
|
||||
last_node_id: zNodeId,
|
||||
last_link_id: z.number(),
|
||||
nodes: z.array(zComfyNode),
|
||||
links: z.array(zComfyLink),
|
||||
groups: z.array(zGroup).optional(),
|
||||
config: zConfig.optional().nullable(),
|
||||
extra: zExtra.optional().nullable(),
|
||||
version: z.number(),
|
||||
models: z.array(zModelFile).optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
/** Schema version 1 */
|
||||
export const zComfyWorkflow1 = z
|
||||
.object({
|
||||
version: z.literal(1),
|
||||
config: zConfig.optional().nullable(),
|
||||
state: zGraphState,
|
||||
groups: z.array(zGroup).optional(),
|
||||
nodes: z.array(zComfyNode),
|
||||
links: z.array(zComfyLinkObject).optional(),
|
||||
reroutes: z.array(zReroute).optional(),
|
||||
extra: zExtra.optional().nullable(),
|
||||
models: z.array(zModelFile).optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export type NodeInput = z.infer<typeof zNodeInput>
|
||||
export type NodeOutput = z.infer<typeof zNodeOutput>
|
||||
export type ComfyLink = z.infer<typeof zComfyLink>
|
||||
export type ComfyNode = z.infer<typeof zComfyNode>
|
||||
export type ComfyWorkflowJSON = z.infer<
|
||||
typeof zComfyWorkflow | typeof zComfyWorkflow1
|
||||
>
|
||||
|
||||
const zWorkflowVersion = z.object({
|
||||
version: z.number()
|
||||
})
|
||||
|
||||
export async function validateComfyWorkflow(
|
||||
data: unknown,
|
||||
onError: (error: string) => void = console.warn
|
||||
): Promise<ComfyWorkflowJSON | null> {
|
||||
const versionResult = zWorkflowVersion.safeParse(data)
|
||||
|
||||
let result: SafeParseReturnType<unknown, ComfyWorkflowJSON>
|
||||
if (!versionResult.success) {
|
||||
// Invalid workflow
|
||||
const error = fromZodError(versionResult.error)
|
||||
onError(`Workflow does not contain a valid version. Zod error:\n${error}`)
|
||||
return null
|
||||
} else if (versionResult.data.version === 1) {
|
||||
// Schema version 1
|
||||
result = await zComfyWorkflow1.safeParseAsync(data)
|
||||
} else {
|
||||
// Unknown or old version: 0.4
|
||||
result = await zComfyWorkflow.safeParseAsync(data)
|
||||
}
|
||||
if (result.success) return result.data
|
||||
|
||||
const error = fromZodError(result.error)
|
||||
onError(`Invalid workflow against zod schema:\n${error}`)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* API format workflow for direct API usage.
|
||||
*/
|
||||
const zNodeInputValue = z.union([
|
||||
// For widget values (can be any type)
|
||||
z.any(),
|
||||
// For node links [nodeId, slotIndex]
|
||||
z.tuple([zNodeId, zSlotIndex])
|
||||
])
|
||||
|
||||
const zNodeData = z.object({
|
||||
inputs: z.record(zNodeInputName, zNodeInputValue),
|
||||
class_type: z.string(),
|
||||
_meta: z.object({
|
||||
title: z.string()
|
||||
})
|
||||
})
|
||||
|
||||
export const zComfyApiWorkflow = z.record(zNodeId, zNodeData)
|
||||
export type ComfyApiWorkflow = z.infer<typeof zComfyApiWorkflow>
|
||||
@@ -1,5 +1,3 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export type DefaultField = 'Workflow' | 'Logs' | 'SystemStats' | 'Settings'
|
||||
|
||||
export interface ReportField {
|
||||
@@ -51,15 +49,3 @@ export interface IssueReportPanelProps {
|
||||
*/
|
||||
title?: string
|
||||
}
|
||||
|
||||
const checkboxField = z.boolean().optional()
|
||||
export const issueReportSchema = z
|
||||
.object({
|
||||
contactInfo: z.string().email().max(320).optional().or(z.literal('')),
|
||||
details: z.string().max(5_000).optional()
|
||||
})
|
||||
.catchall(checkboxField)
|
||||
.refine((data) => Object.values(data).some((value) => value), {
|
||||
path: ['details']
|
||||
})
|
||||
export type IssueReportFormData = z.infer<typeof issueReportSchema>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
// KeyCombo schema
|
||||
export const zKeyCombo = z.object({
|
||||
key: z.string(),
|
||||
ctrl: z.boolean().optional(),
|
||||
alt: z.boolean().optional(),
|
||||
shift: z.boolean().optional(),
|
||||
meta: z.boolean().optional()
|
||||
})
|
||||
|
||||
// Keybinding schema
|
||||
export const zKeybinding = z.object({
|
||||
commandId: z.string(),
|
||||
combo: zKeyCombo,
|
||||
// Optional target element ID to limit keybinding to.
|
||||
// Note: Currently only used to distinguish between global keybindings
|
||||
// and litegraph canvas keybindings.
|
||||
// Do NOT use this field in extensions as it has no effect.
|
||||
targetElementId: z.string().optional()
|
||||
})
|
||||
|
||||
// Infer types from schemas
|
||||
export type KeyCombo = z.infer<typeof zKeyCombo>
|
||||
export type Keybinding = z.infer<typeof zKeybinding>
|
||||
4
src/types/litegraph-augmentation.d.ts
vendored
4
src/types/litegraph-augmentation.d.ts
vendored
@@ -1,10 +1,10 @@
|
||||
import '@comfyorg/litegraph'
|
||||
import type { LLink, Size } from '@comfyorg/litegraph'
|
||||
|
||||
import type { ComfyNodeDef } from '@/schemas/apiSchema'
|
||||
import type { DOMWidget, DOMWidgetOptions } from '@/scripts/domWidget'
|
||||
import type { ComfyNodeDef } from '@/types/apiTypes'
|
||||
|
||||
import type { NodeId } from './comfyWorkflow'
|
||||
import type { NodeId } from '../schemas/comfyWorkflowSchema'
|
||||
|
||||
/** ComfyUI extensions of litegraph */
|
||||
declare module '@comfyorg/litegraph/dist/types/widgets' {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Settings } from './apiTypes'
|
||||
import type { Settings } from '@/schemas/apiSchema'
|
||||
|
||||
export type SettingInputType =
|
||||
| 'boolean'
|
||||
|
||||
Reference in New Issue
Block a user