mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-15 01:48:06 +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",
|
||||
"@comfyorg/comfyui-electron-types": "0.4.73-0",
|
||||
"@comfyorg/design-system": "workspace:*",
|
||||
"@comfyorg/schemas": "workspace:*",
|
||||
"@comfyorg/tailwind-utils": "workspace:*",
|
||||
"@iconify/json": "^2.2.380",
|
||||
"@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':
|
||||
specifier: workspace:*
|
||||
version: link:packages/design-system
|
||||
'@comfyorg/schemas':
|
||||
specifier: workspace:*
|
||||
version: link:packages/schemas
|
||||
'@comfyorg/tailwind-utils':
|
||||
specifier: workspace:*
|
||||
version: link:packages/tailwind-utils
|
||||
@@ -368,6 +371,19 @@ importers:
|
||||
specifier: ^5.4.5
|
||||
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:
|
||||
dependencies:
|
||||
clsx:
|
||||
|
||||
@@ -1,526 +1 @@
|
||||
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>
|
||||
export * from '@comfyorg/schemas/apiSchema'
|
||||
|
||||
@@ -1,116 +1 @@
|
||||
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>
|
||||
export * from '@comfyorg/schemas/colorPaletteSchema'
|
||||
|
||||
@@ -1,25 +1 @@
|
||||
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>
|
||||
export * from '@comfyorg/schemas/keyBindingSchema'
|
||||
|
||||
@@ -1,143 +1 @@
|
||||
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]
|
||||
}
|
||||
export * from '@comfyorg/schemas/nodeDef/migration'
|
||||
|
||||
@@ -1,264 +1 @@
|
||||
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'
|
||||
}
|
||||
export * from '@comfyorg/schemas/nodeDef/nodeDefSchemaV2'
|
||||
|
||||
@@ -1,255 +1 @@
|
||||
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
|
||||
}
|
||||
export * from '@comfyorg/schemas/nodeDefSchema'
|
||||
|
||||
@@ -1,60 +1 @@
|
||||
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>
|
||||
export * from '@comfyorg/schemas/signInSchema'
|
||||
|
||||
Reference in New Issue
Block a user