mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-25 16:59:45 +00:00
Compare commits
8 Commits
feat/remot
...
schema-as-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f94873ae3 | ||
|
|
b717a21a6a | ||
|
|
d9abeec10f | ||
|
|
240255d9e9 | ||
|
|
c5b5fb4021 | ||
|
|
0cebc52fd2 | ||
|
|
ca243be027 | ||
|
|
1d908e18fa |
@@ -106,6 +106,7 @@
|
|||||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||||
"@comfyorg/comfyui-electron-types": "0.4.73-0",
|
"@comfyorg/comfyui-electron-types": "0.4.73-0",
|
||||||
"@comfyorg/design-system": "workspace:*",
|
"@comfyorg/design-system": "workspace:*",
|
||||||
|
"@comfyorg/schemas": "workspace:*",
|
||||||
"@comfyorg/tailwind-utils": "workspace:*",
|
"@comfyorg/tailwind-utils": "workspace:*",
|
||||||
"@iconify/json": "^2.2.380",
|
"@iconify/json": "^2.2.380",
|
||||||
"@primeuix/forms": "0.0.2",
|
"@primeuix/forms": "0.0.2",
|
||||||
|
|||||||
33
packages/schemas/package.json
Normal file
33
packages/schemas/package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@comfyorg/schemas",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"description": "Shared Zod schemas for ComfyUI Frontend",
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
"./apiSchema": "./src/apiSchema.ts",
|
||||||
|
"./colorPaletteSchema": "./src/colorPaletteSchema.ts",
|
||||||
|
"./keyBindingSchema": "./src/keyBindingSchema.ts",
|
||||||
|
"./nodeDefSchema": "./src/nodeDefSchema.ts",
|
||||||
|
"./nodeDef/nodeDefSchemaV2": "./src/nodeDef/nodeDefSchemaV2.ts",
|
||||||
|
"./nodeDef/migration": "./src/nodeDef/migration.ts",
|
||||||
|
"./signInSchema": "./src/signInSchema.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"nx": {
|
||||||
|
"tags": [
|
||||||
|
"scope:shared",
|
||||||
|
"type:schema"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"zod": "^3.23.8",
|
||||||
|
"zod-validation-error": "^3.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.4.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
526
packages/schemas/src/apiSchema.ts
Normal file
526
packages/schemas/src/apiSchema.ts
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
import { LinkMarkerShape } from '@/lib/litegraph/src/litegraph'
|
||||||
|
import {
|
||||||
|
zComfyWorkflow,
|
||||||
|
zNodeId
|
||||||
|
} from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||||
|
import { colorPalettesSchema } from '@/schemas/colorPaletteSchema'
|
||||||
|
import { zKeybinding } from '@/schemas/keyBindingSchema'
|
||||||
|
import { NodeBadgeMode } from '@/types/nodeSource'
|
||||||
|
import { LinkReleaseTriggerAction } from '@/types/searchBoxTypes'
|
||||||
|
|
||||||
|
const zNodeType = z.string()
|
||||||
|
const zQueueIndex = z.number()
|
||||||
|
const zPromptId = z.string()
|
||||||
|
export const resultItemType = z.enum(['input', 'output', 'temp'])
|
||||||
|
export type ResultItemType = z.infer<typeof resultItemType>
|
||||||
|
|
||||||
|
const zResultItem = z.object({
|
||||||
|
filename: z.string().optional(),
|
||||||
|
subfolder: z.string().optional(),
|
||||||
|
type: resultItemType.optional()
|
||||||
|
})
|
||||||
|
export type ResultItem = z.infer<typeof zResultItem>
|
||||||
|
const zOutputs = z
|
||||||
|
.object({
|
||||||
|
audio: z.array(zResultItem).optional(),
|
||||||
|
images: z.array(zResultItem).optional(),
|
||||||
|
video: 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 zNodeProgressState = z.object({
|
||||||
|
value: z.number(),
|
||||||
|
max: z.number(),
|
||||||
|
state: z.enum(['pending', 'running', 'finished', 'error']),
|
||||||
|
node_id: zNodeId,
|
||||||
|
prompt_id: zPromptId,
|
||||||
|
display_node_id: zNodeId.optional(),
|
||||||
|
parent_node_id: zNodeId.optional(),
|
||||||
|
real_node_id: zNodeId.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zProgressStateWsMessage = z.object({
|
||||||
|
prompt_id: zPromptId,
|
||||||
|
nodes: z.record(zNodeId, zNodeProgressState)
|
||||||
|
})
|
||||||
|
|
||||||
|
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 zProgressTextWsMessage = z.object({
|
||||||
|
nodeId: zNodeId,
|
||||||
|
text: z.string()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zDisplayComponentWsMessage = z.object({
|
||||||
|
node_id: zNodeId,
|
||||||
|
component: z.enum(['ChatHistoryWidget']),
|
||||||
|
props: z.record(z.string(), z.any()).optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
const zFeatureFlagsWsMessage = z.record(z.string(), z.any())
|
||||||
|
|
||||||
|
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>
|
||||||
|
export type ProgressTextWsMessage = z.infer<typeof zProgressTextWsMessage>
|
||||||
|
export type DisplayComponentWsMessage = z.infer<
|
||||||
|
typeof zDisplayComponentWsMessage
|
||||||
|
>
|
||||||
|
export type NodeProgressState = z.infer<typeof zNodeProgressState>
|
||||||
|
export type ProgressStateWsMessage = z.infer<typeof zProgressStateWsMessage>
|
||||||
|
export type FeatureFlagsWsMessage = z.infer<typeof zFeatureFlagsWsMessage>
|
||||||
|
// 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>
|
||||||
|
|
||||||
|
const zEmbeddingsResponse = z.array(z.string())
|
||||||
|
const zExtensionsResponse = z.array(z.string())
|
||||||
|
const zError = z.object({
|
||||||
|
type: z.string(),
|
||||||
|
message: z.string(),
|
||||||
|
details: z.string(),
|
||||||
|
extra_info: z
|
||||||
|
.object({
|
||||||
|
input_name: z.string().optional()
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
const zNodeError = z.object({
|
||||||
|
errors: z.array(zError),
|
||||||
|
class_type: z.string(),
|
||||||
|
dependent_outputs: z.array(z.any())
|
||||||
|
})
|
||||||
|
const zPromptResponse = z.object({
|
||||||
|
node_errors: z.record(zNodeId, zNodeError).optional(),
|
||||||
|
prompt_id: z.string().optional(),
|
||||||
|
exec_info: z
|
||||||
|
.object({
|
||||||
|
queue_remaining: z.number().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
error: z.union([z.string(), zError])
|
||||||
|
})
|
||||||
|
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
|
||||||
|
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(),
|
||||||
|
required_frontend_version: z.string().optional(),
|
||||||
|
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.object({
|
||||||
|
'Comfy.ColorPalette': z.string(),
|
||||||
|
'Comfy.CustomColorPalettes': colorPalettesSchema,
|
||||||
|
'Comfy.Canvas.BackgroundImage': z.string().optional(),
|
||||||
|
'Comfy.ConfirmClear': z.boolean(),
|
||||||
|
'Comfy.DevMode': z.boolean(),
|
||||||
|
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
|
||||||
|
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
|
||||||
|
'Comfy.Workflow.WarnBlueprintOverwrite': 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.Locale': z.string(),
|
||||||
|
'Comfy.NodeLibrary.Bookmarks': z.array(z.string()),
|
||||||
|
'Comfy.NodeLibrary.Bookmarks.V2': z.array(z.string()),
|
||||||
|
'Comfy.NodeLibrary.BookmarksCustomization': z.record(
|
||||||
|
z.string(),
|
||||||
|
zBookmarkCustomization
|
||||||
|
),
|
||||||
|
'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.Sidebar.UnifiedWidth': z.boolean(),
|
||||||
|
'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.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.NodeBadge.ShowApiPricing': z.boolean(),
|
||||||
|
'Comfy.Notification.ShowVersionUpdates': z.boolean(),
|
||||||
|
'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.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.Workflow.AutoSaveDelay': z.number(),
|
||||||
|
'Comfy.Workflow.AutoSave': z.enum(['off', 'after delay']),
|
||||||
|
'Comfy.RerouteBeta': z.boolean(),
|
||||||
|
'LiteGraph.Canvas.MinFontSizeForLOD': z.number(),
|
||||||
|
'Comfy.Canvas.SelectionToolbox': z.boolean(),
|
||||||
|
'LiteGraph.Node.TooltipDelay': z.number(),
|
||||||
|
'LiteGraph.ContextMenu.Scaling': z.boolean(),
|
||||||
|
'LiteGraph.Reroute.SplineOffset': z.number(),
|
||||||
|
'LiteGraph.Canvas.LowQualityRenderingZoomThreshold': z.number(),
|
||||||
|
'Comfy.Toast.DisableReconnectingToast': z.boolean(),
|
||||||
|
'Comfy.Workflow.Persist': z.boolean(),
|
||||||
|
'Comfy.TutorialCompleted': z.boolean(),
|
||||||
|
'Comfy.InstalledVersion': z.string().nullable(),
|
||||||
|
'Comfy.Node.AllowImageSizeDraw': z.boolean(),
|
||||||
|
'Comfy.Minimap.Visible': z.boolean(),
|
||||||
|
'Comfy.Minimap.NodeColors': z.boolean(),
|
||||||
|
'Comfy.Minimap.ShowLinks': z.boolean(),
|
||||||
|
'Comfy.Minimap.ShowGroups': z.boolean(),
|
||||||
|
'Comfy.Minimap.RenderBypassState': z.boolean(),
|
||||||
|
'Comfy.Minimap.RenderErrorState': z.boolean(),
|
||||||
|
'Comfy.Canvas.NavigationMode': z.string(),
|
||||||
|
'Comfy.Canvas.LeftMouseClickBehavior': z.string(),
|
||||||
|
'Comfy.Canvas.MouseWheelScroll': z.string(),
|
||||||
|
'Comfy.VueNodes.Enabled': z.boolean(),
|
||||||
|
'Comfy.Assets.UseAssetAPI': z.boolean(),
|
||||||
|
'Comfy-Desktop.AutoUpdate': z.boolean(),
|
||||||
|
'Comfy-Desktop.SendStatistics': z.boolean(),
|
||||||
|
'Comfy-Desktop.WindowStyle': z.string(),
|
||||||
|
'Comfy-Desktop.UV.PythonInstallMirror': z.string(),
|
||||||
|
'Comfy-Desktop.UV.PypiInstallMirror': z.string(),
|
||||||
|
'Comfy-Desktop.UV.TorchInstallMirror': z.string(),
|
||||||
|
'Comfy.MaskEditor.UseNewEditor': z.boolean(),
|
||||||
|
'Comfy.MaskEditor.BrushAdjustmentSpeed': z.number(),
|
||||||
|
'Comfy.MaskEditor.UseDominantAxis': z.boolean(),
|
||||||
|
'Comfy.Load3D.ShowGrid': z.boolean(),
|
||||||
|
'Comfy.Load3D.ShowPreview': z.boolean(),
|
||||||
|
'Comfy.Load3D.BackgroundColor': z.string(),
|
||||||
|
'Comfy.Load3D.LightIntensity': z.number(),
|
||||||
|
'Comfy.Load3D.LightIntensityMaximum': z.number(),
|
||||||
|
'Comfy.Load3D.LightIntensityMinimum': z.number(),
|
||||||
|
'Comfy.Load3D.LightAdjustmentIncrement': z.number(),
|
||||||
|
'Comfy.Load3D.CameraType': z.enum(['perspective', 'orthographic']),
|
||||||
|
'Comfy.Load3D.3DViewerEnable': z.boolean(),
|
||||||
|
'Comfy.Memory.AllowManualUnload': z.boolean(),
|
||||||
|
'pysssss.SnapToGrid': z.boolean(),
|
||||||
|
/** VHS setting is used for queue video preview support. */
|
||||||
|
'VHS.AdvancedPreviews': z.string(),
|
||||||
|
/** Release data settings */
|
||||||
|
'Comfy.Release.Version': z.string(),
|
||||||
|
'Comfy.Release.Status': z.enum([
|
||||||
|
'skipped',
|
||||||
|
'changelog seen',
|
||||||
|
"what's new seen"
|
||||||
|
]),
|
||||||
|
'Comfy.Release.Timestamp': z.number(),
|
||||||
|
/** Settings used for testing */
|
||||||
|
'test.setting': z.any(),
|
||||||
|
'main.sub.setting.name': z.any(),
|
||||||
|
'single.setting': z.any(),
|
||||||
|
'LiteGraph.Node.DefaultPadding': z.boolean(),
|
||||||
|
'LiteGraph.Pointer.TrackpadGestures': z.boolean()
|
||||||
|
})
|
||||||
|
|
||||||
|
export type EmbeddingsResponse = z.infer<typeof zEmbeddingsResponse>
|
||||||
|
export type ExtensionsResponse = z.infer<typeof zExtensionsResponse>
|
||||||
|
export type PromptResponse = z.infer<typeof zPromptResponse>
|
||||||
|
export type NodeError = z.infer<typeof zNodeError>
|
||||||
|
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>
|
||||||
116
packages/schemas/src/colorPaletteSchema.ts
Normal file
116
packages/schemas/src/colorPaletteSchema.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
|
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(),
|
||||||
|
WIDGET_DISABLED_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()
|
||||||
|
|
||||||
|
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>
|
||||||
25
packages/schemas/src/keyBindingSchema.ts
Normal file
25
packages/schemas/src/keyBindingSchema.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
// KeyCombo schema
|
||||||
|
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>
|
||||||
143
packages/schemas/src/nodeDef/migration.ts
Normal file
143
packages/schemas/src/nodeDef/migration.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import type {
|
||||||
|
ComfyNodeDef as ComfyNodeDefV2,
|
||||||
|
InputSpec as InputSpecV2,
|
||||||
|
OutputSpec as OutputSpecV2
|
||||||
|
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
|
import type {
|
||||||
|
ComfyNodeDef as ComfyNodeDefV1,
|
||||||
|
InputSpec as InputSpecV1
|
||||||
|
} from '@/schemas/nodeDefSchema'
|
||||||
|
import {
|
||||||
|
getComboSpecComboOptions,
|
||||||
|
isComboInputSpec,
|
||||||
|
isComboInputSpecV1
|
||||||
|
} from '@/schemas/nodeDefSchema'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a V1 node definition to V2 format
|
||||||
|
* @param nodeDefV1 The V1 node definition to transform
|
||||||
|
* @returns The transformed V2 node definition
|
||||||
|
*/
|
||||||
|
export function transformNodeDefV1ToV2(
|
||||||
|
nodeDefV1: ComfyNodeDefV1
|
||||||
|
): ComfyNodeDefV2 {
|
||||||
|
// Transform inputs
|
||||||
|
const inputs: Record<string, InputSpecV2> = {}
|
||||||
|
|
||||||
|
// Process required inputs
|
||||||
|
if (nodeDefV1.input?.required) {
|
||||||
|
Object.entries(nodeDefV1.input.required).forEach(([name, inputSpecV1]) => {
|
||||||
|
inputs[name] = transformInputSpecV1ToV2(inputSpecV1, {
|
||||||
|
name,
|
||||||
|
isOptional: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process optional inputs
|
||||||
|
if (nodeDefV1.input?.optional) {
|
||||||
|
Object.entries(nodeDefV1.input.optional).forEach(([name, inputSpecV1]) => {
|
||||||
|
inputs[name] = transformInputSpecV1ToV2(inputSpecV1, {
|
||||||
|
name,
|
||||||
|
isOptional: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform outputs
|
||||||
|
const outputs: OutputSpecV2[] = []
|
||||||
|
|
||||||
|
if (nodeDefV1.output) {
|
||||||
|
if (Array.isArray(nodeDefV1.output)) {
|
||||||
|
nodeDefV1.output.forEach((outputType, index) => {
|
||||||
|
const outputSpec: OutputSpecV2 = {
|
||||||
|
index,
|
||||||
|
name: nodeDefV1.output_name?.[index] || `output_${index}`,
|
||||||
|
type: Array.isArray(outputType) ? 'COMBO' : outputType,
|
||||||
|
is_list: nodeDefV1.output_is_list?.[index] || false,
|
||||||
|
tooltip: nodeDefV1.output_tooltips?.[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add options for combo outputs
|
||||||
|
if (Array.isArray(outputType)) {
|
||||||
|
outputSpec.options = outputType
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs.push(outputSpec)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.warn('nodeDefV1.output is not an array:', nodeDefV1.output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the V2 node definition
|
||||||
|
return {
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
hidden: nodeDefV1.input?.hidden,
|
||||||
|
name: nodeDefV1.name,
|
||||||
|
display_name: nodeDefV1.display_name,
|
||||||
|
description: nodeDefV1.description,
|
||||||
|
category: nodeDefV1.category,
|
||||||
|
output_node: nodeDefV1.output_node,
|
||||||
|
python_module: nodeDefV1.python_module,
|
||||||
|
deprecated: nodeDefV1.deprecated,
|
||||||
|
experimental: nodeDefV1.experimental
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a V1 input specification to V2 format
|
||||||
|
* @param inputSpecV1 The V1 input specification to transform
|
||||||
|
* @param name The name of the input
|
||||||
|
* @param isOptional Whether the input is optional
|
||||||
|
* @returns The transformed V2 input specification
|
||||||
|
*/
|
||||||
|
export function transformInputSpecV1ToV2(
|
||||||
|
inputSpecV1: InputSpecV1,
|
||||||
|
kwargs: {
|
||||||
|
name: string
|
||||||
|
isOptional?: boolean
|
||||||
|
}
|
||||||
|
): InputSpecV2 {
|
||||||
|
const { name, isOptional = false } = kwargs
|
||||||
|
|
||||||
|
// Extract options from the input spec
|
||||||
|
const options = inputSpecV1[1] || {}
|
||||||
|
|
||||||
|
// Base properties for all input types
|
||||||
|
const baseProps = {
|
||||||
|
name,
|
||||||
|
isOptional,
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle different input types
|
||||||
|
if (isComboInputSpec(inputSpecV1)) {
|
||||||
|
return {
|
||||||
|
type: 'COMBO',
|
||||||
|
...baseProps,
|
||||||
|
options: isComboInputSpecV1(inputSpecV1)
|
||||||
|
? inputSpecV1[0]
|
||||||
|
: getComboSpecComboOptions(inputSpecV1)
|
||||||
|
}
|
||||||
|
} else if (typeof inputSpecV1[0] === 'string') {
|
||||||
|
// Handle standard types (INT, FLOAT, BOOLEAN, STRING) and custom types
|
||||||
|
return {
|
||||||
|
type: inputSpecV1[0],
|
||||||
|
...baseProps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for any unhandled cases
|
||||||
|
return {
|
||||||
|
type: 'UNKNOWN',
|
||||||
|
...baseProps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformInputSpecV2ToV1(
|
||||||
|
inputSpecV2: InputSpecV2
|
||||||
|
): InputSpecV1 {
|
||||||
|
return [inputSpecV2.type, inputSpecV2]
|
||||||
|
}
|
||||||
264
packages/schemas/src/nodeDef/nodeDefSchemaV2.ts
Normal file
264
packages/schemas/src/nodeDef/nodeDefSchemaV2.ts
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
import {
|
||||||
|
zBaseInputOptions,
|
||||||
|
zBooleanInputOptions,
|
||||||
|
zComboInputOptions,
|
||||||
|
zFloatInputOptions,
|
||||||
|
zIntInputOptions,
|
||||||
|
zStringInputOptions
|
||||||
|
} from '@/schemas/nodeDefSchema'
|
||||||
|
|
||||||
|
const zIntInputSpec = zIntInputOptions.extend({
|
||||||
|
type: z.literal('INT'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zFloatInputSpec = zFloatInputOptions.extend({
|
||||||
|
type: z.literal('FLOAT'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zBooleanInputSpec = zBooleanInputOptions.extend({
|
||||||
|
type: z.literal('BOOLEAN'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zStringInputSpec = zStringInputOptions.extend({
|
||||||
|
type: z.literal('STRING'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zComboInputSpec = zComboInputOptions.extend({
|
||||||
|
type: z.literal('COMBO'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zColorInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('COLOR'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z
|
||||||
|
.object({
|
||||||
|
default: z.string().optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zFileUploadInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('FILEUPLOAD'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z.record(z.unknown()).optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zImageInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('IMAGE'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z.record(z.unknown()).optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zImageCompareInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('IMAGECOMPARE'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z.record(z.unknown()).optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zMarkdownInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('MARKDOWN'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z
|
||||||
|
.object({
|
||||||
|
content: z.string().optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zTreeSelectInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('TREESELECT'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z
|
||||||
|
.object({
|
||||||
|
multiple: z.boolean().optional(),
|
||||||
|
values: z.array(z.unknown()).optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zMultiSelectInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('MULTISELECT'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z
|
||||||
|
.object({
|
||||||
|
values: z.array(z.string()).optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zChartInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('CHART'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z
|
||||||
|
.object({
|
||||||
|
type: z.enum(['bar', 'line']).optional(),
|
||||||
|
data: z.object({}).optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zGalleriaInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('GALLERIA'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z
|
||||||
|
.object({
|
||||||
|
images: z.array(z.string()).optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zSelectButtonInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('SELECTBUTTON'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z
|
||||||
|
.object({
|
||||||
|
values: z.array(z.string()).optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zTextareaInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.literal('TEXTAREA'),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
options: z
|
||||||
|
.object({
|
||||||
|
rows: z.number().optional(),
|
||||||
|
cols: z.number().optional(),
|
||||||
|
default: z.string().optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zCustomInputSpec = zBaseInputOptions.extend({
|
||||||
|
type: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
isOptional: z.boolean().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zInputSpec = z.union([
|
||||||
|
zIntInputSpec,
|
||||||
|
zFloatInputSpec,
|
||||||
|
zBooleanInputSpec,
|
||||||
|
zStringInputSpec,
|
||||||
|
zComboInputSpec,
|
||||||
|
zColorInputSpec,
|
||||||
|
zFileUploadInputSpec,
|
||||||
|
zImageInputSpec,
|
||||||
|
zImageCompareInputSpec,
|
||||||
|
zMarkdownInputSpec,
|
||||||
|
zTreeSelectInputSpec,
|
||||||
|
zMultiSelectInputSpec,
|
||||||
|
zChartInputSpec,
|
||||||
|
zGalleriaInputSpec,
|
||||||
|
zSelectButtonInputSpec,
|
||||||
|
zTextareaInputSpec,
|
||||||
|
zCustomInputSpec
|
||||||
|
])
|
||||||
|
|
||||||
|
// Output specs
|
||||||
|
const zOutputSpec = z.object({
|
||||||
|
index: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
is_list: z.boolean(),
|
||||||
|
options: z.array(z.any()).optional(),
|
||||||
|
tooltip: z.string().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Main node definition schema
|
||||||
|
export const zComfyNodeDef = z.object({
|
||||||
|
inputs: z.record(zInputSpec),
|
||||||
|
outputs: z.array(zOutputSpec),
|
||||||
|
hidden: z.record(z.any()).optional(),
|
||||||
|
|
||||||
|
name: z.string(),
|
||||||
|
display_name: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
help: z.string().optional(),
|
||||||
|
category: z.string(),
|
||||||
|
output_node: z.boolean(),
|
||||||
|
python_module: z.string(),
|
||||||
|
deprecated: z.boolean().optional(),
|
||||||
|
experimental: z.boolean().optional(),
|
||||||
|
api_node: z.boolean().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Export types
|
||||||
|
type IntInputSpec = z.infer<typeof zIntInputSpec>
|
||||||
|
type FloatInputSpec = z.infer<typeof zFloatInputSpec>
|
||||||
|
type BooleanInputSpec = z.infer<typeof zBooleanInputSpec>
|
||||||
|
type StringInputSpec = z.infer<typeof zStringInputSpec>
|
||||||
|
export type ComboInputSpec = z.infer<typeof zComboInputSpec>
|
||||||
|
export type ColorInputSpec = z.infer<typeof zColorInputSpec>
|
||||||
|
export type FileUploadInputSpec = z.infer<typeof zFileUploadInputSpec>
|
||||||
|
export type ImageCompareInputSpec = z.infer<typeof zImageCompareInputSpec>
|
||||||
|
export type TreeSelectInputSpec = z.infer<typeof zTreeSelectInputSpec>
|
||||||
|
export type MultiSelectInputSpec = z.infer<typeof zMultiSelectInputSpec>
|
||||||
|
export type ChartInputSpec = z.infer<typeof zChartInputSpec>
|
||||||
|
export type GalleriaInputSpec = z.infer<typeof zGalleriaInputSpec>
|
||||||
|
export type SelectButtonInputSpec = z.infer<typeof zSelectButtonInputSpec>
|
||||||
|
export type TextareaInputSpec = z.infer<typeof zTextareaInputSpec>
|
||||||
|
export type CustomInputSpec = z.infer<typeof zCustomInputSpec>
|
||||||
|
|
||||||
|
export type InputSpec = z.infer<typeof zInputSpec>
|
||||||
|
export type OutputSpec = z.infer<typeof zOutputSpec>
|
||||||
|
export type ComfyNodeDef = z.infer<typeof zComfyNodeDef>
|
||||||
|
|
||||||
|
export const isIntInputSpec = (
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is IntInputSpec => {
|
||||||
|
return inputSpec.type === 'INT'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isFloatInputSpec = (
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is FloatInputSpec => {
|
||||||
|
return inputSpec.type === 'FLOAT'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isBooleanInputSpec = (
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is BooleanInputSpec => {
|
||||||
|
return inputSpec.type === 'BOOLEAN'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isStringInputSpec = (
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is StringInputSpec => {
|
||||||
|
return inputSpec.type === 'STRING'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isComboInputSpec = (
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is ComboInputSpec => {
|
||||||
|
return inputSpec.type === 'COMBO'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isChartInputSpec = (
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is ChartInputSpec => {
|
||||||
|
return inputSpec.type === 'CHART'
|
||||||
|
}
|
||||||
255
packages/schemas/src/nodeDefSchema.ts
Normal file
255
packages/schemas/src/nodeDefSchema.ts
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
import { fromZodError } from 'zod-validation-error'
|
||||||
|
|
||||||
|
import { resultItemType } from '@/schemas/apiSchema'
|
||||||
|
|
||||||
|
const zComboOption = z.union([z.string(), z.number()])
|
||||||
|
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 zMultiSelectOption = z.object({
|
||||||
|
placeholder: z.string().optional(),
|
||||||
|
chip: z.boolean().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const zBaseInputOptions = z
|
||||||
|
.object({
|
||||||
|
default: z.any().optional(),
|
||||||
|
defaultInput: z.boolean().optional(),
|
||||||
|
forceInput: z.boolean().optional(),
|
||||||
|
tooltip: z.string().optional(),
|
||||||
|
hidden: z.boolean().optional(),
|
||||||
|
advanced: z.boolean().optional(),
|
||||||
|
widgetType: z.string().optional(),
|
||||||
|
/** Backend-only properties. */
|
||||||
|
rawLink: z.boolean().optional(),
|
||||||
|
lazy: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
|
||||||
|
const zNumericInputOptions = zBaseInputOptions.extend({
|
||||||
|
min: z.number().optional(),
|
||||||
|
max: z.number().optional(),
|
||||||
|
step: z.number().optional(),
|
||||||
|
/** Note: Many node authors are using INT/FLOAT to pass list of INT/FLOAT. */
|
||||||
|
default: z.union([z.number(), z.array(z.number())]).optional(),
|
||||||
|
display: z.enum(['slider', 'number', 'knob']).optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const zIntInputOptions = zNumericInputOptions.extend({
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const zFloatInputOptions = zNumericInputOptions.extend({
|
||||||
|
round: z.union([z.number(), z.literal(false)]).optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const zBooleanInputOptions = zBaseInputOptions.extend({
|
||||||
|
label_on: z.string().optional(),
|
||||||
|
label_off: z.string().optional(),
|
||||||
|
default: z.boolean().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const zStringInputOptions = zBaseInputOptions.extend({
|
||||||
|
default: z.string().optional(),
|
||||||
|
multiline: z.boolean().optional(),
|
||||||
|
dynamicPrompts: z.boolean().optional(),
|
||||||
|
|
||||||
|
// Multiline-only fields
|
||||||
|
defaultVal: z.string().optional(),
|
||||||
|
placeholder: z.string().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const zComboInputOptions = zBaseInputOptions.extend({
|
||||||
|
control_after_generate: z.boolean().optional(),
|
||||||
|
image_upload: z.boolean().optional(),
|
||||||
|
image_folder: resultItemType.optional(),
|
||||||
|
allow_batch: z.boolean().optional(),
|
||||||
|
video_upload: z.boolean().optional(),
|
||||||
|
audio_upload: z.boolean().optional(),
|
||||||
|
animated_image_upload: z.boolean().optional(),
|
||||||
|
options: z.array(zComboOption).optional(),
|
||||||
|
remote: zRemoteWidgetConfig.optional(),
|
||||||
|
/** Whether the widget is a multi-select widget. */
|
||||||
|
multi_select: zMultiSelectOption.optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const zIntInputSpec = z.tuple([z.literal('INT'), zIntInputOptions.optional()])
|
||||||
|
const zFloatInputSpec = z.tuple([
|
||||||
|
z.literal('FLOAT'),
|
||||||
|
zFloatInputOptions.optional()
|
||||||
|
])
|
||||||
|
const zBooleanInputSpec = z.tuple([
|
||||||
|
z.literal('BOOLEAN'),
|
||||||
|
zBooleanInputOptions.optional()
|
||||||
|
])
|
||||||
|
const zStringInputSpec = z.tuple([
|
||||||
|
z.literal('STRING'),
|
||||||
|
zStringInputOptions.optional()
|
||||||
|
])
|
||||||
|
/**
|
||||||
|
* Legacy combo syntax.
|
||||||
|
* @deprecated Use `zComboInputSpecV2` instead.
|
||||||
|
*/
|
||||||
|
const zComboInputSpec = z.tuple([
|
||||||
|
z.array(zComboOption),
|
||||||
|
zComboInputOptions.optional()
|
||||||
|
])
|
||||||
|
const zComboInputSpecV2 = z.tuple([
|
||||||
|
z.literal('COMBO'),
|
||||||
|
zComboInputOptions.optional()
|
||||||
|
])
|
||||||
|
|
||||||
|
export function isComboInputSpecV1(
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is ComboInputSpec {
|
||||||
|
return Array.isArray(inputSpec[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isIntInputSpec(
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is IntInputSpec {
|
||||||
|
return inputSpec[0] === 'INT'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFloatInputSpec(
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is FloatInputSpec {
|
||||||
|
return inputSpec[0] === 'FLOAT'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isComboInputSpecV2(
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is ComboInputSpecV2 {
|
||||||
|
return inputSpec[0] === 'COMBO'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isComboInputSpec(
|
||||||
|
inputSpec: InputSpec
|
||||||
|
): inputSpec is ComboInputSpec | ComboInputSpecV2 {
|
||||||
|
return isComboInputSpecV1(inputSpec) || isComboInputSpecV2(inputSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of an input spec.
|
||||||
|
*
|
||||||
|
* @param inputSpec - The input spec to get the type of.
|
||||||
|
* @returns The type of the input spec.
|
||||||
|
*/
|
||||||
|
export function getInputSpecType(inputSpec: InputSpec): string {
|
||||||
|
return isComboInputSpec(inputSpec) ? 'COMBO' : inputSpec[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the combo options from a combo input spec.
|
||||||
|
*
|
||||||
|
* @param inputSpec - The input spec to get the combo options from.
|
||||||
|
* @returns The combo options.
|
||||||
|
*/
|
||||||
|
export function getComboSpecComboOptions(
|
||||||
|
inputSpec: ComboInputSpec | ComboInputSpecV2
|
||||||
|
): (number | string)[] {
|
||||||
|
return (
|
||||||
|
(isComboInputSpecV2(inputSpec) ? inputSpec[1]?.options : inputSpec[0]) ?? []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const excludedLiterals = new Set(['INT', 'FLOAT', 'BOOLEAN', 'STRING', 'COMBO'])
|
||||||
|
const zCustomInputSpec = z.tuple([
|
||||||
|
z.string().refine((value) => !excludedLiterals.has(value)),
|
||||||
|
zBaseInputOptions.optional()
|
||||||
|
])
|
||||||
|
|
||||||
|
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(zComboOption)
|
||||||
|
const zComfyOutputTypesSpec = z.array(
|
||||||
|
z.union([zComfyNodeDataType, zComfyComboOutput])
|
||||||
|
)
|
||||||
|
|
||||||
|
export 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(),
|
||||||
|
help: z.string().optional(),
|
||||||
|
category: z.string(),
|
||||||
|
output_node: z.boolean(),
|
||||||
|
python_module: z.string(),
|
||||||
|
deprecated: z.boolean().optional(),
|
||||||
|
experimental: z.boolean().optional(),
|
||||||
|
/**
|
||||||
|
* Whether the node is an API node. Running API nodes requires login to
|
||||||
|
* Comfy Org account.
|
||||||
|
* https://docs.comfy.org/tutorials/api-nodes/overview
|
||||||
|
*/
|
||||||
|
api_node: z.boolean().optional(),
|
||||||
|
/**
|
||||||
|
* Specifies the order of inputs for each input category.
|
||||||
|
* Used to ensure consistent widget ordering regardless of JSON serialization.
|
||||||
|
* Keys are 'required', 'optional', etc., values are arrays of input names.
|
||||||
|
*/
|
||||||
|
input_order: z.record(z.array(z.string())).optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
// `/object_info`
|
||||||
|
export type ComfyInputsSpec = z.infer<typeof zComfyInputsSpec>
|
||||||
|
export type ComfyOutputTypesSpec = z.infer<typeof zComfyOutputTypesSpec>
|
||||||
|
export type ComfyNodeDef = z.infer<typeof zComfyNodeDef>
|
||||||
|
export type RemoteWidgetConfig = z.infer<typeof zRemoteWidgetConfig>
|
||||||
|
|
||||||
|
export type ComboInputOptions = z.infer<typeof zComboInputOptions>
|
||||||
|
export type NumericInputOptions = z.infer<typeof zNumericInputOptions>
|
||||||
|
|
||||||
|
export type IntInputSpec = z.infer<typeof zIntInputSpec>
|
||||||
|
export type FloatInputSpec = z.infer<typeof zFloatInputSpec>
|
||||||
|
export type ComboInputSpec = z.infer<typeof zComboInputSpec>
|
||||||
|
export type ComboInputSpecV2 = z.infer<typeof zComboInputSpecV2>
|
||||||
|
export type InputSpec = z.infer<typeof zInputSpec>
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
60
packages/schemas/src/signInSchema.ts
Normal file
60
packages/schemas/src/signInSchema.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
|
export const apiKeySchema = z.object({
|
||||||
|
apiKey: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.startsWith('comfyui-', t('validation.prefix', { prefix: 'comfyui-' }))
|
||||||
|
.length(72, t('validation.length', { length: 72 }))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const signInSchema = z.object({
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.email(t('validation.invalidEmail'))
|
||||||
|
.min(1, t('validation.required')),
|
||||||
|
password: z.string().min(1, t('validation.required'))
|
||||||
|
})
|
||||||
|
|
||||||
|
export type SignInData = z.infer<typeof signInSchema>
|
||||||
|
|
||||||
|
const passwordSchema = z.object({
|
||||||
|
password: z
|
||||||
|
.string()
|
||||||
|
.min(8, t('validation.minLength', { length: 8 }))
|
||||||
|
.max(32, t('validation.maxLength', { length: 32 }))
|
||||||
|
.regex(/[A-Z]/, t('validation.password.uppercase'))
|
||||||
|
.regex(/[a-z]/, t('validation.password.lowercase'))
|
||||||
|
.regex(/\d/, t('validation.password.number'))
|
||||||
|
.regex(/[^A-Za-z0-9]/, t('validation.password.special')),
|
||||||
|
confirmPassword: z.string().min(1, t('validation.required'))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updatePasswordSchema = passwordSchema.refine(
|
||||||
|
(data) => data.password === data.confirmPassword,
|
||||||
|
{
|
||||||
|
message: t('validation.password.match'),
|
||||||
|
path: ['confirmPassword']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const signUpSchema = passwordSchema
|
||||||
|
.extend({
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.email(t('validation.invalidEmail'))
|
||||||
|
.min(1, t('validation.required')),
|
||||||
|
personalDataConsent: z.boolean()
|
||||||
|
})
|
||||||
|
.refine((data) => data.password === data.confirmPassword, {
|
||||||
|
message: t('validation.password.match'),
|
||||||
|
path: ['confirmPassword']
|
||||||
|
})
|
||||||
|
.refine((data) => data.personalDataConsent === true, {
|
||||||
|
message: t('validation.personalDataConsentRequired'),
|
||||||
|
path: ['personalDataConsent']
|
||||||
|
})
|
||||||
|
|
||||||
|
export type SignUpData = z.infer<typeof signUpSchema>
|
||||||
8
packages/schemas/tsconfig.json
Normal file
8
packages/schemas/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -20,6 +20,9 @@ importers:
|
|||||||
'@comfyorg/design-system':
|
'@comfyorg/design-system':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:packages/design-system
|
version: link:packages/design-system
|
||||||
|
'@comfyorg/schemas':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:packages/schemas
|
||||||
'@comfyorg/tailwind-utils':
|
'@comfyorg/tailwind-utils':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:packages/tailwind-utils
|
version: link:packages/tailwind-utils
|
||||||
@@ -368,6 +371,19 @@ importers:
|
|||||||
specifier: ^5.4.5
|
specifier: ^5.4.5
|
||||||
version: 5.9.2
|
version: 5.9.2
|
||||||
|
|
||||||
|
packages/schemas:
|
||||||
|
dependencies:
|
||||||
|
zod:
|
||||||
|
specifier: ^3.23.8
|
||||||
|
version: 3.24.1
|
||||||
|
zod-validation-error:
|
||||||
|
specifier: ^3.3.0
|
||||||
|
version: 3.3.0(zod@3.24.1)
|
||||||
|
devDependencies:
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.4.5
|
||||||
|
version: 5.9.2
|
||||||
|
|
||||||
packages/tailwind-utils:
|
packages/tailwind-utils:
|
||||||
dependencies:
|
dependencies:
|
||||||
clsx:
|
clsx:
|
||||||
|
|||||||
@@ -1,526 +1 @@
|
|||||||
import { z } from 'zod'
|
export * from '@comfyorg/schemas/apiSchema'
|
||||||
|
|
||||||
import { LinkMarkerShape } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import {
|
|
||||||
zComfyWorkflow,
|
|
||||||
zNodeId
|
|
||||||
} from '@/platform/workflow/validation/schemas/workflowSchema'
|
|
||||||
import { colorPalettesSchema } from '@/schemas/colorPaletteSchema'
|
|
||||||
import { zKeybinding } from '@/schemas/keyBindingSchema'
|
|
||||||
import { NodeBadgeMode } from '@/types/nodeSource'
|
|
||||||
import { LinkReleaseTriggerAction } from '@/types/searchBoxTypes'
|
|
||||||
|
|
||||||
const zNodeType = z.string()
|
|
||||||
const zQueueIndex = z.number()
|
|
||||||
const zPromptId = z.string()
|
|
||||||
export const resultItemType = z.enum(['input', 'output', 'temp'])
|
|
||||||
export type ResultItemType = z.infer<typeof resultItemType>
|
|
||||||
|
|
||||||
const zResultItem = z.object({
|
|
||||||
filename: z.string().optional(),
|
|
||||||
subfolder: z.string().optional(),
|
|
||||||
type: resultItemType.optional()
|
|
||||||
})
|
|
||||||
export type ResultItem = z.infer<typeof zResultItem>
|
|
||||||
const zOutputs = z
|
|
||||||
.object({
|
|
||||||
audio: z.array(zResultItem).optional(),
|
|
||||||
images: z.array(zResultItem).optional(),
|
|
||||||
video: 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 zNodeProgressState = z.object({
|
|
||||||
value: z.number(),
|
|
||||||
max: z.number(),
|
|
||||||
state: z.enum(['pending', 'running', 'finished', 'error']),
|
|
||||||
node_id: zNodeId,
|
|
||||||
prompt_id: zPromptId,
|
|
||||||
display_node_id: zNodeId.optional(),
|
|
||||||
parent_node_id: zNodeId.optional(),
|
|
||||||
real_node_id: zNodeId.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zProgressStateWsMessage = z.object({
|
|
||||||
prompt_id: zPromptId,
|
|
||||||
nodes: z.record(zNodeId, zNodeProgressState)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 zProgressTextWsMessage = z.object({
|
|
||||||
nodeId: zNodeId,
|
|
||||||
text: z.string()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zDisplayComponentWsMessage = z.object({
|
|
||||||
node_id: zNodeId,
|
|
||||||
component: z.enum(['ChatHistoryWidget']),
|
|
||||||
props: z.record(z.string(), z.any()).optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
const zFeatureFlagsWsMessage = z.record(z.string(), z.any())
|
|
||||||
|
|
||||||
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>
|
|
||||||
export type ProgressTextWsMessage = z.infer<typeof zProgressTextWsMessage>
|
|
||||||
export type DisplayComponentWsMessage = z.infer<
|
|
||||||
typeof zDisplayComponentWsMessage
|
|
||||||
>
|
|
||||||
export type NodeProgressState = z.infer<typeof zNodeProgressState>
|
|
||||||
export type ProgressStateWsMessage = z.infer<typeof zProgressStateWsMessage>
|
|
||||||
export type FeatureFlagsWsMessage = z.infer<typeof zFeatureFlagsWsMessage>
|
|
||||||
// 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>
|
|
||||||
|
|
||||||
const zEmbeddingsResponse = z.array(z.string())
|
|
||||||
const zExtensionsResponse = z.array(z.string())
|
|
||||||
const zError = z.object({
|
|
||||||
type: z.string(),
|
|
||||||
message: z.string(),
|
|
||||||
details: z.string(),
|
|
||||||
extra_info: z
|
|
||||||
.object({
|
|
||||||
input_name: z.string().optional()
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
const zNodeError = z.object({
|
|
||||||
errors: z.array(zError),
|
|
||||||
class_type: z.string(),
|
|
||||||
dependent_outputs: z.array(z.any())
|
|
||||||
})
|
|
||||||
const zPromptResponse = z.object({
|
|
||||||
node_errors: z.record(zNodeId, zNodeError).optional(),
|
|
||||||
prompt_id: z.string().optional(),
|
|
||||||
exec_info: z
|
|
||||||
.object({
|
|
||||||
queue_remaining: z.number().optional()
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
error: z.union([z.string(), zError])
|
|
||||||
})
|
|
||||||
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
|
|
||||||
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(),
|
|
||||||
required_frontend_version: z.string().optional(),
|
|
||||||
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.object({
|
|
||||||
'Comfy.ColorPalette': z.string(),
|
|
||||||
'Comfy.CustomColorPalettes': colorPalettesSchema,
|
|
||||||
'Comfy.Canvas.BackgroundImage': z.string().optional(),
|
|
||||||
'Comfy.ConfirmClear': z.boolean(),
|
|
||||||
'Comfy.DevMode': z.boolean(),
|
|
||||||
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
|
|
||||||
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
|
|
||||||
'Comfy.Workflow.WarnBlueprintOverwrite': 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.Locale': z.string(),
|
|
||||||
'Comfy.NodeLibrary.Bookmarks': z.array(z.string()),
|
|
||||||
'Comfy.NodeLibrary.Bookmarks.V2': z.array(z.string()),
|
|
||||||
'Comfy.NodeLibrary.BookmarksCustomization': z.record(
|
|
||||||
z.string(),
|
|
||||||
zBookmarkCustomization
|
|
||||||
),
|
|
||||||
'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.Sidebar.UnifiedWidth': z.boolean(),
|
|
||||||
'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.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.NodeBadge.ShowApiPricing': z.boolean(),
|
|
||||||
'Comfy.Notification.ShowVersionUpdates': z.boolean(),
|
|
||||||
'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.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.Workflow.AutoSaveDelay': z.number(),
|
|
||||||
'Comfy.Workflow.AutoSave': z.enum(['off', 'after delay']),
|
|
||||||
'Comfy.RerouteBeta': z.boolean(),
|
|
||||||
'LiteGraph.Canvas.MinFontSizeForLOD': z.number(),
|
|
||||||
'Comfy.Canvas.SelectionToolbox': z.boolean(),
|
|
||||||
'LiteGraph.Node.TooltipDelay': z.number(),
|
|
||||||
'LiteGraph.ContextMenu.Scaling': z.boolean(),
|
|
||||||
'LiteGraph.Reroute.SplineOffset': z.number(),
|
|
||||||
'LiteGraph.Canvas.LowQualityRenderingZoomThreshold': z.number(),
|
|
||||||
'Comfy.Toast.DisableReconnectingToast': z.boolean(),
|
|
||||||
'Comfy.Workflow.Persist': z.boolean(),
|
|
||||||
'Comfy.TutorialCompleted': z.boolean(),
|
|
||||||
'Comfy.InstalledVersion': z.string().nullable(),
|
|
||||||
'Comfy.Node.AllowImageSizeDraw': z.boolean(),
|
|
||||||
'Comfy.Minimap.Visible': z.boolean(),
|
|
||||||
'Comfy.Minimap.NodeColors': z.boolean(),
|
|
||||||
'Comfy.Minimap.ShowLinks': z.boolean(),
|
|
||||||
'Comfy.Minimap.ShowGroups': z.boolean(),
|
|
||||||
'Comfy.Minimap.RenderBypassState': z.boolean(),
|
|
||||||
'Comfy.Minimap.RenderErrorState': z.boolean(),
|
|
||||||
'Comfy.Canvas.NavigationMode': z.string(),
|
|
||||||
'Comfy.Canvas.LeftMouseClickBehavior': z.string(),
|
|
||||||
'Comfy.Canvas.MouseWheelScroll': z.string(),
|
|
||||||
'Comfy.VueNodes.Enabled': z.boolean(),
|
|
||||||
'Comfy.Assets.UseAssetAPI': z.boolean(),
|
|
||||||
'Comfy-Desktop.AutoUpdate': z.boolean(),
|
|
||||||
'Comfy-Desktop.SendStatistics': z.boolean(),
|
|
||||||
'Comfy-Desktop.WindowStyle': z.string(),
|
|
||||||
'Comfy-Desktop.UV.PythonInstallMirror': z.string(),
|
|
||||||
'Comfy-Desktop.UV.PypiInstallMirror': z.string(),
|
|
||||||
'Comfy-Desktop.UV.TorchInstallMirror': z.string(),
|
|
||||||
'Comfy.MaskEditor.UseNewEditor': z.boolean(),
|
|
||||||
'Comfy.MaskEditor.BrushAdjustmentSpeed': z.number(),
|
|
||||||
'Comfy.MaskEditor.UseDominantAxis': z.boolean(),
|
|
||||||
'Comfy.Load3D.ShowGrid': z.boolean(),
|
|
||||||
'Comfy.Load3D.ShowPreview': z.boolean(),
|
|
||||||
'Comfy.Load3D.BackgroundColor': z.string(),
|
|
||||||
'Comfy.Load3D.LightIntensity': z.number(),
|
|
||||||
'Comfy.Load3D.LightIntensityMaximum': z.number(),
|
|
||||||
'Comfy.Load3D.LightIntensityMinimum': z.number(),
|
|
||||||
'Comfy.Load3D.LightAdjustmentIncrement': z.number(),
|
|
||||||
'Comfy.Load3D.CameraType': z.enum(['perspective', 'orthographic']),
|
|
||||||
'Comfy.Load3D.3DViewerEnable': z.boolean(),
|
|
||||||
'Comfy.Memory.AllowManualUnload': z.boolean(),
|
|
||||||
'pysssss.SnapToGrid': z.boolean(),
|
|
||||||
/** VHS setting is used for queue video preview support. */
|
|
||||||
'VHS.AdvancedPreviews': z.string(),
|
|
||||||
/** Release data settings */
|
|
||||||
'Comfy.Release.Version': z.string(),
|
|
||||||
'Comfy.Release.Status': z.enum([
|
|
||||||
'skipped',
|
|
||||||
'changelog seen',
|
|
||||||
"what's new seen"
|
|
||||||
]),
|
|
||||||
'Comfy.Release.Timestamp': z.number(),
|
|
||||||
/** Settings used for testing */
|
|
||||||
'test.setting': z.any(),
|
|
||||||
'main.sub.setting.name': z.any(),
|
|
||||||
'single.setting': z.any(),
|
|
||||||
'LiteGraph.Node.DefaultPadding': z.boolean(),
|
|
||||||
'LiteGraph.Pointer.TrackpadGestures': z.boolean()
|
|
||||||
})
|
|
||||||
|
|
||||||
export type EmbeddingsResponse = z.infer<typeof zEmbeddingsResponse>
|
|
||||||
export type ExtensionsResponse = z.infer<typeof zExtensionsResponse>
|
|
||||||
export type PromptResponse = z.infer<typeof zPromptResponse>
|
|
||||||
export type NodeError = z.infer<typeof zNodeError>
|
|
||||||
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>
|
|
||||||
|
|||||||
@@ -1,116 +1 @@
|
|||||||
import { z } from 'zod'
|
export * from '@comfyorg/schemas/colorPaletteSchema'
|
||||||
|
|
||||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
|
||||||
|
|
||||||
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(),
|
|
||||||
WIDGET_DISABLED_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()
|
|
||||||
|
|
||||||
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,25 +1 @@
|
|||||||
import { z } from 'zod'
|
export * from '@comfyorg/schemas/keyBindingSchema'
|
||||||
|
|
||||||
// KeyCombo schema
|
|
||||||
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>
|
|
||||||
|
|||||||
@@ -1,143 +1 @@
|
|||||||
import type {
|
export * from '@comfyorg/schemas/nodeDef/migration'
|
||||||
ComfyNodeDef as ComfyNodeDefV2,
|
|
||||||
InputSpec as InputSpecV2,
|
|
||||||
OutputSpec as OutputSpecV2
|
|
||||||
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
|
||||||
import type {
|
|
||||||
ComfyNodeDef as ComfyNodeDefV1,
|
|
||||||
InputSpec as InputSpecV1
|
|
||||||
} from '@/schemas/nodeDefSchema'
|
|
||||||
import {
|
|
||||||
getComboSpecComboOptions,
|
|
||||||
isComboInputSpec,
|
|
||||||
isComboInputSpecV1
|
|
||||||
} from '@/schemas/nodeDefSchema'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms a V1 node definition to V2 format
|
|
||||||
* @param nodeDefV1 The V1 node definition to transform
|
|
||||||
* @returns The transformed V2 node definition
|
|
||||||
*/
|
|
||||||
export function transformNodeDefV1ToV2(
|
|
||||||
nodeDefV1: ComfyNodeDefV1
|
|
||||||
): ComfyNodeDefV2 {
|
|
||||||
// Transform inputs
|
|
||||||
const inputs: Record<string, InputSpecV2> = {}
|
|
||||||
|
|
||||||
// Process required inputs
|
|
||||||
if (nodeDefV1.input?.required) {
|
|
||||||
Object.entries(nodeDefV1.input.required).forEach(([name, inputSpecV1]) => {
|
|
||||||
inputs[name] = transformInputSpecV1ToV2(inputSpecV1, {
|
|
||||||
name,
|
|
||||||
isOptional: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process optional inputs
|
|
||||||
if (nodeDefV1.input?.optional) {
|
|
||||||
Object.entries(nodeDefV1.input.optional).forEach(([name, inputSpecV1]) => {
|
|
||||||
inputs[name] = transformInputSpecV1ToV2(inputSpecV1, {
|
|
||||||
name,
|
|
||||||
isOptional: true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform outputs
|
|
||||||
const outputs: OutputSpecV2[] = []
|
|
||||||
|
|
||||||
if (nodeDefV1.output) {
|
|
||||||
if (Array.isArray(nodeDefV1.output)) {
|
|
||||||
nodeDefV1.output.forEach((outputType, index) => {
|
|
||||||
const outputSpec: OutputSpecV2 = {
|
|
||||||
index,
|
|
||||||
name: nodeDefV1.output_name?.[index] || `output_${index}`,
|
|
||||||
type: Array.isArray(outputType) ? 'COMBO' : outputType,
|
|
||||||
is_list: nodeDefV1.output_is_list?.[index] || false,
|
|
||||||
tooltip: nodeDefV1.output_tooltips?.[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add options for combo outputs
|
|
||||||
if (Array.isArray(outputType)) {
|
|
||||||
outputSpec.options = outputType
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs.push(outputSpec)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.warn('nodeDefV1.output is not an array:', nodeDefV1.output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the V2 node definition
|
|
||||||
return {
|
|
||||||
inputs,
|
|
||||||
outputs,
|
|
||||||
hidden: nodeDefV1.input?.hidden,
|
|
||||||
name: nodeDefV1.name,
|
|
||||||
display_name: nodeDefV1.display_name,
|
|
||||||
description: nodeDefV1.description,
|
|
||||||
category: nodeDefV1.category,
|
|
||||||
output_node: nodeDefV1.output_node,
|
|
||||||
python_module: nodeDefV1.python_module,
|
|
||||||
deprecated: nodeDefV1.deprecated,
|
|
||||||
experimental: nodeDefV1.experimental
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms a V1 input specification to V2 format
|
|
||||||
* @param inputSpecV1 The V1 input specification to transform
|
|
||||||
* @param name The name of the input
|
|
||||||
* @param isOptional Whether the input is optional
|
|
||||||
* @returns The transformed V2 input specification
|
|
||||||
*/
|
|
||||||
export function transformInputSpecV1ToV2(
|
|
||||||
inputSpecV1: InputSpecV1,
|
|
||||||
kwargs: {
|
|
||||||
name: string
|
|
||||||
isOptional?: boolean
|
|
||||||
}
|
|
||||||
): InputSpecV2 {
|
|
||||||
const { name, isOptional = false } = kwargs
|
|
||||||
|
|
||||||
// Extract options from the input spec
|
|
||||||
const options = inputSpecV1[1] || {}
|
|
||||||
|
|
||||||
// Base properties for all input types
|
|
||||||
const baseProps = {
|
|
||||||
name,
|
|
||||||
isOptional,
|
|
||||||
...options
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different input types
|
|
||||||
if (isComboInputSpec(inputSpecV1)) {
|
|
||||||
return {
|
|
||||||
type: 'COMBO',
|
|
||||||
...baseProps,
|
|
||||||
options: isComboInputSpecV1(inputSpecV1)
|
|
||||||
? inputSpecV1[0]
|
|
||||||
: getComboSpecComboOptions(inputSpecV1)
|
|
||||||
}
|
|
||||||
} else if (typeof inputSpecV1[0] === 'string') {
|
|
||||||
// Handle standard types (INT, FLOAT, BOOLEAN, STRING) and custom types
|
|
||||||
return {
|
|
||||||
type: inputSpecV1[0],
|
|
||||||
...baseProps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for any unhandled cases
|
|
||||||
return {
|
|
||||||
type: 'UNKNOWN',
|
|
||||||
...baseProps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformInputSpecV2ToV1(
|
|
||||||
inputSpecV2: InputSpecV2
|
|
||||||
): InputSpecV1 {
|
|
||||||
return [inputSpecV2.type, inputSpecV2]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,264 +1 @@
|
|||||||
import { z } from 'zod'
|
export * from '@comfyorg/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
|
|
||||||
import {
|
|
||||||
zBaseInputOptions,
|
|
||||||
zBooleanInputOptions,
|
|
||||||
zComboInputOptions,
|
|
||||||
zFloatInputOptions,
|
|
||||||
zIntInputOptions,
|
|
||||||
zStringInputOptions
|
|
||||||
} from '@/schemas/nodeDefSchema'
|
|
||||||
|
|
||||||
const zIntInputSpec = zIntInputOptions.extend({
|
|
||||||
type: z.literal('INT'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zFloatInputSpec = zFloatInputOptions.extend({
|
|
||||||
type: z.literal('FLOAT'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zBooleanInputSpec = zBooleanInputOptions.extend({
|
|
||||||
type: z.literal('BOOLEAN'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zStringInputSpec = zStringInputOptions.extend({
|
|
||||||
type: z.literal('STRING'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zComboInputSpec = zComboInputOptions.extend({
|
|
||||||
type: z.literal('COMBO'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zColorInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('COLOR'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z
|
|
||||||
.object({
|
|
||||||
default: z.string().optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zFileUploadInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('FILEUPLOAD'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z.record(z.unknown()).optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zImageInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('IMAGE'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z.record(z.unknown()).optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zImageCompareInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('IMAGECOMPARE'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z.record(z.unknown()).optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zMarkdownInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('MARKDOWN'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z
|
|
||||||
.object({
|
|
||||||
content: z.string().optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zTreeSelectInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('TREESELECT'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z
|
|
||||||
.object({
|
|
||||||
multiple: z.boolean().optional(),
|
|
||||||
values: z.array(z.unknown()).optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zMultiSelectInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('MULTISELECT'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z
|
|
||||||
.object({
|
|
||||||
values: z.array(z.string()).optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zChartInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('CHART'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z
|
|
||||||
.object({
|
|
||||||
type: z.enum(['bar', 'line']).optional(),
|
|
||||||
data: z.object({}).optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zGalleriaInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('GALLERIA'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z
|
|
||||||
.object({
|
|
||||||
images: z.array(z.string()).optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zSelectButtonInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('SELECTBUTTON'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z
|
|
||||||
.object({
|
|
||||||
values: z.array(z.string()).optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zTextareaInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('TEXTAREA'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z
|
|
||||||
.object({
|
|
||||||
rows: z.number().optional(),
|
|
||||||
cols: z.number().optional(),
|
|
||||||
default: z.string().optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zCustomInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zInputSpec = z.union([
|
|
||||||
zIntInputSpec,
|
|
||||||
zFloatInputSpec,
|
|
||||||
zBooleanInputSpec,
|
|
||||||
zStringInputSpec,
|
|
||||||
zComboInputSpec,
|
|
||||||
zColorInputSpec,
|
|
||||||
zFileUploadInputSpec,
|
|
||||||
zImageInputSpec,
|
|
||||||
zImageCompareInputSpec,
|
|
||||||
zMarkdownInputSpec,
|
|
||||||
zTreeSelectInputSpec,
|
|
||||||
zMultiSelectInputSpec,
|
|
||||||
zChartInputSpec,
|
|
||||||
zGalleriaInputSpec,
|
|
||||||
zSelectButtonInputSpec,
|
|
||||||
zTextareaInputSpec,
|
|
||||||
zCustomInputSpec
|
|
||||||
])
|
|
||||||
|
|
||||||
// Output specs
|
|
||||||
const zOutputSpec = z.object({
|
|
||||||
index: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
is_list: z.boolean(),
|
|
||||||
options: z.array(z.any()).optional(),
|
|
||||||
tooltip: z.string().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Main node definition schema
|
|
||||||
export const zComfyNodeDef = z.object({
|
|
||||||
inputs: z.record(zInputSpec),
|
|
||||||
outputs: z.array(zOutputSpec),
|
|
||||||
hidden: z.record(z.any()).optional(),
|
|
||||||
|
|
||||||
name: z.string(),
|
|
||||||
display_name: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
help: z.string().optional(),
|
|
||||||
category: z.string(),
|
|
||||||
output_node: z.boolean(),
|
|
||||||
python_module: z.string(),
|
|
||||||
deprecated: z.boolean().optional(),
|
|
||||||
experimental: z.boolean().optional(),
|
|
||||||
api_node: z.boolean().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Export types
|
|
||||||
type IntInputSpec = z.infer<typeof zIntInputSpec>
|
|
||||||
type FloatInputSpec = z.infer<typeof zFloatInputSpec>
|
|
||||||
type BooleanInputSpec = z.infer<typeof zBooleanInputSpec>
|
|
||||||
type StringInputSpec = z.infer<typeof zStringInputSpec>
|
|
||||||
export type ComboInputSpec = z.infer<typeof zComboInputSpec>
|
|
||||||
export type ColorInputSpec = z.infer<typeof zColorInputSpec>
|
|
||||||
export type FileUploadInputSpec = z.infer<typeof zFileUploadInputSpec>
|
|
||||||
export type ImageCompareInputSpec = z.infer<typeof zImageCompareInputSpec>
|
|
||||||
export type TreeSelectInputSpec = z.infer<typeof zTreeSelectInputSpec>
|
|
||||||
export type MultiSelectInputSpec = z.infer<typeof zMultiSelectInputSpec>
|
|
||||||
export type ChartInputSpec = z.infer<typeof zChartInputSpec>
|
|
||||||
export type GalleriaInputSpec = z.infer<typeof zGalleriaInputSpec>
|
|
||||||
export type SelectButtonInputSpec = z.infer<typeof zSelectButtonInputSpec>
|
|
||||||
export type TextareaInputSpec = z.infer<typeof zTextareaInputSpec>
|
|
||||||
export type CustomInputSpec = z.infer<typeof zCustomInputSpec>
|
|
||||||
|
|
||||||
export type InputSpec = z.infer<typeof zInputSpec>
|
|
||||||
export type OutputSpec = z.infer<typeof zOutputSpec>
|
|
||||||
export type ComfyNodeDef = z.infer<typeof zComfyNodeDef>
|
|
||||||
|
|
||||||
export const isIntInputSpec = (
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is IntInputSpec => {
|
|
||||||
return inputSpec.type === 'INT'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isFloatInputSpec = (
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is FloatInputSpec => {
|
|
||||||
return inputSpec.type === 'FLOAT'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isBooleanInputSpec = (
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is BooleanInputSpec => {
|
|
||||||
return inputSpec.type === 'BOOLEAN'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isStringInputSpec = (
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is StringInputSpec => {
|
|
||||||
return inputSpec.type === 'STRING'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isComboInputSpec = (
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is ComboInputSpec => {
|
|
||||||
return inputSpec.type === 'COMBO'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isChartInputSpec = (
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is ChartInputSpec => {
|
|
||||||
return inputSpec.type === 'CHART'
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,255 +1 @@
|
|||||||
import { z } from 'zod'
|
export * from '@comfyorg/schemas/nodeDefSchema'
|
||||||
import { fromZodError } from 'zod-validation-error'
|
|
||||||
|
|
||||||
import { resultItemType } from '@/schemas/apiSchema'
|
|
||||||
|
|
||||||
const zComboOption = z.union([z.string(), z.number()])
|
|
||||||
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 zMultiSelectOption = z.object({
|
|
||||||
placeholder: z.string().optional(),
|
|
||||||
chip: z.boolean().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
export const zBaseInputOptions = z
|
|
||||||
.object({
|
|
||||||
default: z.any().optional(),
|
|
||||||
defaultInput: z.boolean().optional(),
|
|
||||||
forceInput: z.boolean().optional(),
|
|
||||||
tooltip: z.string().optional(),
|
|
||||||
hidden: z.boolean().optional(),
|
|
||||||
advanced: z.boolean().optional(),
|
|
||||||
widgetType: z.string().optional(),
|
|
||||||
/** Backend-only properties. */
|
|
||||||
rawLink: z.boolean().optional(),
|
|
||||||
lazy: z.boolean().optional()
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
|
|
||||||
const zNumericInputOptions = zBaseInputOptions.extend({
|
|
||||||
min: z.number().optional(),
|
|
||||||
max: z.number().optional(),
|
|
||||||
step: z.number().optional(),
|
|
||||||
/** Note: Many node authors are using INT/FLOAT to pass list of INT/FLOAT. */
|
|
||||||
default: z.union([z.number(), z.array(z.number())]).optional(),
|
|
||||||
display: z.enum(['slider', 'number', 'knob']).optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
export const zIntInputOptions = zNumericInputOptions.extend({
|
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
})
|
|
||||||
|
|
||||||
export const zFloatInputOptions = zNumericInputOptions.extend({
|
|
||||||
round: z.union([z.number(), z.literal(false)]).optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
export const zBooleanInputOptions = zBaseInputOptions.extend({
|
|
||||||
label_on: z.string().optional(),
|
|
||||||
label_off: z.string().optional(),
|
|
||||||
default: z.boolean().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
export const zStringInputOptions = zBaseInputOptions.extend({
|
|
||||||
default: z.string().optional(),
|
|
||||||
multiline: z.boolean().optional(),
|
|
||||||
dynamicPrompts: z.boolean().optional(),
|
|
||||||
|
|
||||||
// Multiline-only fields
|
|
||||||
defaultVal: z.string().optional(),
|
|
||||||
placeholder: z.string().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
export const zComboInputOptions = zBaseInputOptions.extend({
|
|
||||||
control_after_generate: z.boolean().optional(),
|
|
||||||
image_upload: z.boolean().optional(),
|
|
||||||
image_folder: resultItemType.optional(),
|
|
||||||
allow_batch: z.boolean().optional(),
|
|
||||||
video_upload: z.boolean().optional(),
|
|
||||||
audio_upload: z.boolean().optional(),
|
|
||||||
animated_image_upload: z.boolean().optional(),
|
|
||||||
options: z.array(zComboOption).optional(),
|
|
||||||
remote: zRemoteWidgetConfig.optional(),
|
|
||||||
/** Whether the widget is a multi-select widget. */
|
|
||||||
multi_select: zMultiSelectOption.optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zIntInputSpec = z.tuple([z.literal('INT'), zIntInputOptions.optional()])
|
|
||||||
const zFloatInputSpec = z.tuple([
|
|
||||||
z.literal('FLOAT'),
|
|
||||||
zFloatInputOptions.optional()
|
|
||||||
])
|
|
||||||
const zBooleanInputSpec = z.tuple([
|
|
||||||
z.literal('BOOLEAN'),
|
|
||||||
zBooleanInputOptions.optional()
|
|
||||||
])
|
|
||||||
const zStringInputSpec = z.tuple([
|
|
||||||
z.literal('STRING'),
|
|
||||||
zStringInputOptions.optional()
|
|
||||||
])
|
|
||||||
/**
|
|
||||||
* Legacy combo syntax.
|
|
||||||
* @deprecated Use `zComboInputSpecV2` instead.
|
|
||||||
*/
|
|
||||||
const zComboInputSpec = z.tuple([
|
|
||||||
z.array(zComboOption),
|
|
||||||
zComboInputOptions.optional()
|
|
||||||
])
|
|
||||||
const zComboInputSpecV2 = z.tuple([
|
|
||||||
z.literal('COMBO'),
|
|
||||||
zComboInputOptions.optional()
|
|
||||||
])
|
|
||||||
|
|
||||||
export function isComboInputSpecV1(
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is ComboInputSpec {
|
|
||||||
return Array.isArray(inputSpec[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isIntInputSpec(
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is IntInputSpec {
|
|
||||||
return inputSpec[0] === 'INT'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFloatInputSpec(
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is FloatInputSpec {
|
|
||||||
return inputSpec[0] === 'FLOAT'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isComboInputSpecV2(
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is ComboInputSpecV2 {
|
|
||||||
return inputSpec[0] === 'COMBO'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isComboInputSpec(
|
|
||||||
inputSpec: InputSpec
|
|
||||||
): inputSpec is ComboInputSpec | ComboInputSpecV2 {
|
|
||||||
return isComboInputSpecV1(inputSpec) || isComboInputSpecV2(inputSpec)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the type of an input spec.
|
|
||||||
*
|
|
||||||
* @param inputSpec - The input spec to get the type of.
|
|
||||||
* @returns The type of the input spec.
|
|
||||||
*/
|
|
||||||
export function getInputSpecType(inputSpec: InputSpec): string {
|
|
||||||
return isComboInputSpec(inputSpec) ? 'COMBO' : inputSpec[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the combo options from a combo input spec.
|
|
||||||
*
|
|
||||||
* @param inputSpec - The input spec to get the combo options from.
|
|
||||||
* @returns The combo options.
|
|
||||||
*/
|
|
||||||
export function getComboSpecComboOptions(
|
|
||||||
inputSpec: ComboInputSpec | ComboInputSpecV2
|
|
||||||
): (number | string)[] {
|
|
||||||
return (
|
|
||||||
(isComboInputSpecV2(inputSpec) ? inputSpec[1]?.options : inputSpec[0]) ?? []
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const excludedLiterals = new Set(['INT', 'FLOAT', 'BOOLEAN', 'STRING', 'COMBO'])
|
|
||||||
const zCustomInputSpec = z.tuple([
|
|
||||||
z.string().refine((value) => !excludedLiterals.has(value)),
|
|
||||||
zBaseInputOptions.optional()
|
|
||||||
])
|
|
||||||
|
|
||||||
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(zComboOption)
|
|
||||||
const zComfyOutputTypesSpec = z.array(
|
|
||||||
z.union([zComfyNodeDataType, zComfyComboOutput])
|
|
||||||
)
|
|
||||||
|
|
||||||
export 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(),
|
|
||||||
help: z.string().optional(),
|
|
||||||
category: z.string(),
|
|
||||||
output_node: z.boolean(),
|
|
||||||
python_module: z.string(),
|
|
||||||
deprecated: z.boolean().optional(),
|
|
||||||
experimental: z.boolean().optional(),
|
|
||||||
/**
|
|
||||||
* Whether the node is an API node. Running API nodes requires login to
|
|
||||||
* Comfy Org account.
|
|
||||||
* https://docs.comfy.org/tutorials/api-nodes/overview
|
|
||||||
*/
|
|
||||||
api_node: z.boolean().optional(),
|
|
||||||
/**
|
|
||||||
* Specifies the order of inputs for each input category.
|
|
||||||
* Used to ensure consistent widget ordering regardless of JSON serialization.
|
|
||||||
* Keys are 'required', 'optional', etc., values are arrays of input names.
|
|
||||||
*/
|
|
||||||
input_order: z.record(z.array(z.string())).optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
// `/object_info`
|
|
||||||
export type ComfyInputsSpec = z.infer<typeof zComfyInputsSpec>
|
|
||||||
export type ComfyOutputTypesSpec = z.infer<typeof zComfyOutputTypesSpec>
|
|
||||||
export type ComfyNodeDef = z.infer<typeof zComfyNodeDef>
|
|
||||||
export type RemoteWidgetConfig = z.infer<typeof zRemoteWidgetConfig>
|
|
||||||
|
|
||||||
export type ComboInputOptions = z.infer<typeof zComboInputOptions>
|
|
||||||
export type NumericInputOptions = z.infer<typeof zNumericInputOptions>
|
|
||||||
|
|
||||||
export type IntInputSpec = z.infer<typeof zIntInputSpec>
|
|
||||||
export type FloatInputSpec = z.infer<typeof zFloatInputSpec>
|
|
||||||
export type ComboInputSpec = z.infer<typeof zComboInputSpec>
|
|
||||||
export type ComboInputSpecV2 = z.infer<typeof zComboInputSpecV2>
|
|
||||||
export type InputSpec = z.infer<typeof zInputSpec>
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,60 +1 @@
|
|||||||
import { z } from 'zod'
|
export * from '@comfyorg/schemas/signInSchema'
|
||||||
|
|
||||||
import { t } from '@/i18n'
|
|
||||||
|
|
||||||
export const apiKeySchema = z.object({
|
|
||||||
apiKey: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.startsWith('comfyui-', t('validation.prefix', { prefix: 'comfyui-' }))
|
|
||||||
.length(72, t('validation.length', { length: 72 }))
|
|
||||||
})
|
|
||||||
|
|
||||||
export const signInSchema = z.object({
|
|
||||||
email: z
|
|
||||||
.string()
|
|
||||||
.email(t('validation.invalidEmail'))
|
|
||||||
.min(1, t('validation.required')),
|
|
||||||
password: z.string().min(1, t('validation.required'))
|
|
||||||
})
|
|
||||||
|
|
||||||
export type SignInData = z.infer<typeof signInSchema>
|
|
||||||
|
|
||||||
const passwordSchema = z.object({
|
|
||||||
password: z
|
|
||||||
.string()
|
|
||||||
.min(8, t('validation.minLength', { length: 8 }))
|
|
||||||
.max(32, t('validation.maxLength', { length: 32 }))
|
|
||||||
.regex(/[A-Z]/, t('validation.password.uppercase'))
|
|
||||||
.regex(/[a-z]/, t('validation.password.lowercase'))
|
|
||||||
.regex(/\d/, t('validation.password.number'))
|
|
||||||
.regex(/[^A-Za-z0-9]/, t('validation.password.special')),
|
|
||||||
confirmPassword: z.string().min(1, t('validation.required'))
|
|
||||||
})
|
|
||||||
|
|
||||||
export const updatePasswordSchema = passwordSchema.refine(
|
|
||||||
(data) => data.password === data.confirmPassword,
|
|
||||||
{
|
|
||||||
message: t('validation.password.match'),
|
|
||||||
path: ['confirmPassword']
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export const signUpSchema = passwordSchema
|
|
||||||
.extend({
|
|
||||||
email: z
|
|
||||||
.string()
|
|
||||||
.email(t('validation.invalidEmail'))
|
|
||||||
.min(1, t('validation.required')),
|
|
||||||
personalDataConsent: z.boolean()
|
|
||||||
})
|
|
||||||
.refine((data) => data.password === data.confirmPassword, {
|
|
||||||
message: t('validation.password.match'),
|
|
||||||
path: ['confirmPassword']
|
|
||||||
})
|
|
||||||
.refine((data) => data.personalDataConsent === true, {
|
|
||||||
message: t('validation.personalDataConsentRequired'),
|
|
||||||
path: ['personalDataConsent']
|
|
||||||
})
|
|
||||||
|
|
||||||
export type SignUpData = z.infer<typeof signUpSchema>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user