merge main into rh-test

This commit is contained in:
bymyself
2025-09-28 15:33:29 -07:00
parent 1c0f151d02
commit ff0c15b119
1317 changed files with 85439 additions and 18373 deletions

View File

@@ -1,9 +1,11 @@
import { z } from 'zod'
import { fromZodError } from 'zod-validation-error'
import { LinkMarkerShape } from '@/lib/litegraph/src/litegraph'
import {
zComfyWorkflow,
zNodeId
} from '@/platform/workflow/validation/schemas/workflowSchema'
import { colorPalettesSchema } from '@/schemas/colorPaletteSchema'
import { zComfyWorkflow, zNodeId } from '@/schemas/comfyWorkflowSchema'
import { zKeybinding } from '@/schemas/keyBindingSchema'
import { NodeBadgeMode } from '@/types/nodeSource'
import { LinkReleaseTriggerAction } from '@/types/searchBoxTypes'
@@ -292,18 +294,6 @@ export type RawHistoryItem = z.infer<typeof zRawHistoryItem>
export type HistoryResponse = z.infer<typeof zHistoryResponse>
export type TaskItem = z.infer<typeof zTaskItem>
export function validateTaskItem(taskItem: unknown) {
const result = zTaskItem.safeParse(taskItem)
if (!result.success) {
const zodError = fromZodError(result.error)
// TODO accept a callback to report error.
console.warn(
`Invalid TaskItem: ${JSON.stringify(taskItem)}\n${zodError.message}`
)
}
return result
}
const zEmbeddingsResponse = z.array(z.string())
const zExtensionsResponse = z.array(z.string())
const zError = z.object({
@@ -343,7 +333,7 @@ const zDeviceStats = z.object({
torch_vram_free: z.number()
})
export const zSystemStats = z.object({
const zSystemStats = z.object({
system: z.object({
os: z.string(),
python_version: z.string(),
@@ -392,6 +382,7 @@ const zSettings = z.object({
'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(),
@@ -472,11 +463,12 @@ const zSettings = z.object({
'Comfy.Workflow.AutoSaveDelay': z.number(),
'Comfy.Workflow.AutoSave': z.enum(['off', 'after delay']),
'Comfy.RerouteBeta': z.boolean(),
'LiteGraph.Canvas.LowQualityRenderingZoomThreshold': z.number(),
'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(),
@@ -489,6 +481,10 @@ const zSettings = z.object({
'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(),
@@ -507,6 +503,7 @@ const zSettings = z.object({
'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(),

View File

@@ -100,7 +100,7 @@ export const paletteSchema = z
})
.passthrough()
export const completedPaletteSchema = z
const completedPaletteSchema = z
.object({
id: z.string(),
name: z.string(),

View File

@@ -1,529 +0,0 @@
import { type SafeParseReturnType, z } from 'zod'
import { fromZodError } from 'zod-validation-error'
// GroupNode is hacking node id to be a string, so we need to allow that.
// innerNode.id = `${this.node.id}:${i}`
// Remove it after GroupNode is redesigned.
export const zNodeId = z.union([z.number().int(), z.string()])
export const zNodeInputName = z.string()
export type NodeId = z.infer<typeof zNodeId>
export const zSlotIndex = z.union([
z.number().int(),
z
.string()
.transform((val) => parseInt(val))
.refine((val) => !isNaN(val), {
message: 'Invalid number'
})
])
// TODO: Investigate usage of array and number as data type usage in custom nodes.
// Known usage:
// - https://github.com/rgthree/rgthree-comfy Context Big node is using array as type.
export const zDataType = z.union([z.string(), z.array(z.string()), z.number()])
const zVector2 = z.union([
z
.object({ 0: z.number(), 1: z.number() })
.passthrough()
.transform((v) => [v[0], v[1]] as [number, number]),
z.tuple([z.number(), z.number()])
])
// Definition of an AI model file used in the workflow.
const zModelFile = z.object({
name: z.string(),
url: z.string().url(),
hash: z.string().optional(),
hash_type: z.string().optional(),
directory: z.string()
})
const zGraphState = z
.object({
lastGroupId: z.number(),
lastNodeId: z.number(),
lastLinkId: z.number(),
lastRerouteId: z.number()
})
.passthrough()
const zComfyLink = z.tuple([
z.number(), // Link id
zNodeId, // Node id of source node
zSlotIndex, // Output slot# of source node
zNodeId, // Node id of destination node
zSlotIndex, // Input slot# of destination node
zDataType // Data type
])
/** Extension to 0.4 schema (links as arrays): parent reroute ID */
const zComfyLinkExtension = z
.object({
id: z.number(),
parentId: z.number()
})
.passthrough()
const zComfyLinkObject = z
.object({
id: z.number(),
origin_id: zNodeId,
origin_slot: zSlotIndex,
target_id: zNodeId,
target_slot: zSlotIndex,
type: zDataType,
parentId: z.number().optional()
})
.passthrough()
const zReroute = z
.object({
id: z.number(),
parentId: z.number().optional(),
pos: zVector2,
linkIds: z.array(z.number()).nullish(),
floating: z
.object({
slotType: z.enum(['input', 'output'])
})
.optional()
})
.passthrough()
const zNodeOutput = z
.object({
name: z.string(),
type: zDataType,
links: z.array(z.number()).nullable().optional(),
slot_index: zSlotIndex.optional()
})
.passthrough()
const zNodeInput = z
.object({
name: zNodeInputName,
type: zDataType,
link: z.number().nullable().optional(),
slot_index: zSlotIndex.optional()
})
.passthrough()
const zFlags = z
.object({
collapsed: z.boolean().optional(),
pinned: z.boolean().optional(),
allow_interaction: z.boolean().optional(),
horizontal: z.boolean().optional(),
skip_repeated_outputs: z.boolean().optional()
})
.passthrough()
const repoLikeIdPattern = /^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$/
const githubUsernamePattern = /^(?!-)(?!.*--)[a-zA-Z0-9-]+(?<!-)$/
const gitHashPattern = /^[0-9a-f]{4,40}$/i
const semverPattern =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-z-]+(?:\.[\da-z-]+)*))?(?:\+([\da-z-]+(?:\.[\da-z-]+)*))?$/
// Shared schema for Comfy Node Registry IDs and GitHub repo names
const zRepoLikeId = z
.string()
.min(1)
.max(100)
.regex(repoLikeIdPattern, {
message: "ID can only contain ASCII letters, digits, '_', '-', and '.'"
})
.refine((id) => !/^[_\-.]|[_\-.]$/.test(id), {
message: "ID must not start or end with '_', '-', or '.'"
})
const zCnrId = zRepoLikeId
const zGithubRepoName = zRepoLikeId
// GitHub username/organization schema
const zGithubUsername = z
.string()
.min(1)
.max(39)
.regex(githubUsernamePattern, 'Invalid GitHub username/org')
// Auxiliary ID identifies node packs not installed via the Comfy Node Registry
const zAuxId = z
.string()
.regex(/^[^/]+\/[^/]+$/, "Invalid format. Must be 'github-user/repo-name'")
.transform((id) => id.split('/'))
.refine(
([username, repo]) =>
zGithubUsername.safeParse(username).success &&
zGithubRepoName.safeParse(repo).success,
"Invalid aux_id: Must be valid 'github-username/github-repo-name'"
)
.transform(([username, repo]) => `${username}/${repo}`)
const zGitHash = z.string().superRefine((val: string, ctx) => {
if (!gitHashPattern.test(val)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Node pack version has invalid Git commit hash: "${val}"`
})
}
})
const zSemVer = z.string().superRefine((val: string, ctx) => {
if (!semverPattern.test(val)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Node pack version has invalid semantic version: "${val}"`
})
}
})
const zVersion = z.union([
z
.string()
.transform((ver) => ver.replace(/^v/, '')) // Strip leading 'v'
.pipe(z.union([zSemVer, zGitHash])),
z.literal('unknown')
])
const zProperties = z
.object({
['Node name for S&R']: z.string().optional(),
cnr_id: zCnrId.optional(),
aux_id: zAuxId.optional(),
ver: zVersion.optional(),
models: z.array(zModelFile).optional()
})
.passthrough()
const zWidgetValues = z.union([z.array(z.any()), z.record(z.any())])
const zComfyNode = z
.object({
id: zNodeId,
type: z.string(),
pos: zVector2,
size: zVector2,
flags: zFlags,
order: z.number(),
mode: z.number(),
inputs: z.array(zNodeInput).optional(),
outputs: z.array(zNodeOutput).optional(),
properties: zProperties,
widgets_values: zWidgetValues.optional(),
color: z.string().optional(),
bgcolor: z.string().optional()
})
.passthrough()
export const zSubgraphIO = zNodeInput.extend({
/** Slot ID (internal; never changes once instantiated). */
id: z.string().uuid(),
/** The data type this slot uses. Unlike nodes, this does not support legacy numeric types. */
type: z.string(),
/** Links connected to this slot, or `undefined` if not connected. An ouptut slot should only ever have one link. */
linkIds: z.array(z.number()).optional()
})
const zSubgraphInstance = z
.object({
id: zNodeId,
type: z.string().uuid(),
pos: zVector2,
size: zVector2,
flags: zFlags,
order: z.number(),
mode: z.number(),
inputs: z.array(zSubgraphIO).optional(),
outputs: z.array(zSubgraphIO).optional(),
widgets_values: zWidgetValues.optional(),
color: z.string().optional(),
bgcolor: z.string().optional()
})
.passthrough()
const zGroup = z
.object({
id: z.number().optional(),
title: z.string(),
bounding: z.tuple([z.number(), z.number(), z.number(), z.number()]),
color: z.string().optional(),
font_size: z.number().optional(),
locked: z.boolean().optional()
})
.passthrough()
const zDS = z
.object({
scale: z.number(),
offset: zVector2
})
.passthrough()
const zConfig = z
.object({
links_ontop: z.boolean().optional(),
align_to_grid: z.boolean().optional()
})
.passthrough()
const zExtra = z
.object({
ds: zDS.optional(),
frontendVersion: z.string().optional(),
linkExtensions: z.array(zComfyLinkExtension).optional(),
reroutes: z.array(zReroute).optional()
})
.passthrough()
export const zGraphDefinitions = z.object({
subgraphs: z.lazy(() => z.array(zSubgraphDefinition))
})
export const zBaseExportableGraph = z.object({
/** Unique graph ID. Automatically generated if not provided. */
id: z.string().uuid().optional(),
revision: z.number().optional(),
config: zConfig.optional().nullable(),
/** Details of the appearance and location of subgraphs shown in this graph. Similar to */
subgraphs: z.array(zSubgraphInstance).optional()
})
/** Schema version 0.4 */
export const zComfyWorkflow = zBaseExportableGraph
.extend({
id: z.string().uuid().optional(),
revision: z.number().optional(),
last_node_id: zNodeId,
last_link_id: z.number(),
nodes: z.array(zComfyNode),
links: z.array(zComfyLink),
floatingLinks: z.array(zComfyLinkObject).optional(),
groups: z.array(zGroup).optional(),
config: zConfig.optional().nullable(),
extra: zExtra.optional().nullable(),
version: z.number(),
models: z.array(zModelFile).optional(),
definitions: zGraphDefinitions.optional()
})
.passthrough()
/** Required for recursive definition of subgraphs. */
interface ComfyWorkflow1BaseType {
id?: string
revision?: number
version: 1
models?: z.infer<typeof zModelFile>[]
state: z.infer<typeof zGraphState>
}
/** Required for recursive definition of subgraphs w/ZodEffects. */
interface ComfyWorkflow1BaseInput extends ComfyWorkflow1BaseType {
groups?: z.input<typeof zGroup>[]
nodes: z.input<typeof zComfyNode>[]
links?: z.input<typeof zComfyLinkObject>[]
floatingLinks?: z.input<typeof zComfyLinkObject>[]
reroutes?: z.input<typeof zReroute>[]
definitions?: {
subgraphs: SubgraphDefinitionBase<ComfyWorkflow1BaseInput>[]
}
}
/** Required for recursive definition of subgraphs w/ZodEffects. */
interface ComfyWorkflow1BaseOutput extends ComfyWorkflow1BaseType {
groups?: z.output<typeof zGroup>[]
nodes: z.output<typeof zComfyNode>[]
links?: z.output<typeof zComfyLinkObject>[]
floatingLinks?: z.output<typeof zComfyLinkObject>[]
reroutes?: z.output<typeof zReroute>[]
definitions?: {
subgraphs: SubgraphDefinitionBase<ComfyWorkflow1BaseOutput>[]
}
}
/** Schema version 1 */
export const zComfyWorkflow1 = zBaseExportableGraph
.extend({
id: z.string().uuid().optional(),
revision: z.number().optional(),
version: z.literal(1),
config: zConfig.optional().nullable(),
state: zGraphState,
groups: z.array(zGroup).optional(),
nodes: z.array(zComfyNode),
links: z.array(zComfyLinkObject).optional(),
floatingLinks: z.array(zComfyLinkObject).optional(),
reroutes: z.array(zReroute).optional(),
extra: zExtra.optional().nullable(),
models: z.array(zModelFile).optional(),
definitions: z
.object({
subgraphs: z.lazy(
(): z.ZodArray<
z.ZodType<
SubgraphDefinitionBase<ComfyWorkflow1BaseOutput>,
z.ZodTypeDef,
SubgraphDefinitionBase<ComfyWorkflow1BaseInput>
>,
'many'
> => z.array(zSubgraphDefinition)
)
})
.optional()
})
.passthrough()
export const zExportedSubgraphIONode = z.object({
id: zNodeId,
bounding: z.tuple([z.number(), z.number(), z.number(), z.number()]),
pinned: z.boolean().optional()
})
export const zExposedWidget = z.object({
id: z.string(),
name: z.string()
})
interface SubgraphDefinitionBase<
T extends ComfyWorkflow1BaseInput | ComfyWorkflow1BaseOutput
> {
/** Unique graph ID. Automatically generated if not provided. */
id: string
revision: number
name: string
inputNode: T extends ComfyWorkflow1BaseInput
? z.input<typeof zExportedSubgraphIONode>
: z.output<typeof zExportedSubgraphIONode>
outputNode: T extends ComfyWorkflow1BaseInput
? z.input<typeof zExportedSubgraphIONode>
: z.output<typeof zExportedSubgraphIONode>
/** Ordered list of inputs to the subgraph itself. Similar to a reroute, with the input side in the graph, and the output side in the subgraph. */
inputs?: T extends ComfyWorkflow1BaseInput
? z.input<typeof zSubgraphIO>[]
: z.output<typeof zSubgraphIO>[]
/** Ordered list of outputs from the subgraph itself. Similar to a reroute, with the input side in the subgraph, and the output side in the graph. */
outputs?: T extends ComfyWorkflow1BaseInput
? z.input<typeof zSubgraphIO>[]
: z.output<typeof zSubgraphIO>[]
/** A list of node widgets displayed in the parent graph, on the subgraph object. */
widgets?: T extends ComfyWorkflow1BaseInput
? z.input<typeof zExposedWidget>[]
: z.output<typeof zExposedWidget>[]
definitions?: {
subgraphs: SubgraphDefinitionBase<T>[]
}
}
/** A subgraph definition `worfklow.definitions.subgraphs` */
export const zSubgraphDefinition = zComfyWorkflow1
.extend({
/** Unique graph ID. Automatically generated if not provided. */
id: z.string().uuid(),
revision: z.number(),
name: z.string(),
inputNode: zExportedSubgraphIONode,
outputNode: zExportedSubgraphIONode,
/** Ordered list of inputs to the subgraph itself. Similar to a reroute, with the input side in the graph, and the output side in the subgraph. */
inputs: z.array(zSubgraphIO).optional(),
/** Ordered list of outputs from the subgraph itself. Similar to a reroute, with the input side in the subgraph, and the output side in the graph. */
outputs: z.array(zSubgraphIO).optional(),
/** A list of node widgets displayed in the parent graph, on the subgraph object. */
widgets: z.array(zExposedWidget).optional(),
definitions: z
.object({
subgraphs: z.lazy(
(): z.ZodArray<
z.ZodType<
SubgraphDefinitionBase<ComfyWorkflow1BaseInput>,
z.ZodTypeDef,
SubgraphDefinitionBase<ComfyWorkflow1BaseInput>
>,
'many'
> => zSubgraphDefinition.array()
)
})
.optional()
})
.passthrough()
export type ModelFile = z.infer<typeof zModelFile>
export type NodeInput = z.infer<typeof zNodeInput>
export type NodeOutput = z.infer<typeof zNodeOutput>
export type ComfyLink = z.infer<typeof zComfyLink>
export type ComfyLinkObject = z.infer<typeof zComfyLinkObject>
export type ComfyNode = z.infer<typeof zComfyNode>
export type Reroute = z.infer<typeof zReroute>
export type WorkflowJSON04 = z.infer<typeof zComfyWorkflow>
export type WorkflowJSON10 = z.infer<typeof zComfyWorkflow1>
export type ComfyWorkflowJSON = z.infer<
typeof zComfyWorkflow | typeof zComfyWorkflow1
>
export type SubgraphDefinition = z.infer<typeof zSubgraphDefinition>
/**
* Type guard to check if an object is a SubgraphDefinition.
* This helps TypeScript understand the type when z.lazy() breaks inference.
*/
export function isSubgraphDefinition(obj: any): obj is SubgraphDefinition {
return (
obj &&
typeof obj === 'object' &&
'id' in obj &&
'name' in obj &&
'nodes' in obj &&
Array.isArray(obj.nodes) &&
'inputNode' in obj &&
'outputNode' in obj
)
}
const zWorkflowVersion = z.object({
version: z.number()
})
export async function validateComfyWorkflow(
data: unknown,
onError: (error: string) => void = console.warn
): Promise<ComfyWorkflowJSON | null> {
const versionResult = zWorkflowVersion.safeParse(data)
let result: SafeParseReturnType<unknown, ComfyWorkflowJSON>
if (!versionResult.success) {
// Invalid workflow
const error = fromZodError(versionResult.error)
onError(`Workflow does not contain a valid version. Zod error:\n${error}`)
return null
} else if (versionResult.data.version === 1) {
// Schema version 1
result = await zComfyWorkflow1.safeParseAsync(data)
} else {
// Unknown or old version: 0.4
result = await zComfyWorkflow.safeParseAsync(data)
}
if (result.success) return result.data
const error = fromZodError(result.error)
onError(`Invalid workflow against zod schema:\n${error}`)
return null
}
/**
* API format workflow for direct API usage.
*/
const zNodeInputValue = z.union([
// For widget values (can be any type)
z.any(),
// For node links [nodeId, slotIndex]
z.tuple([zNodeId, zSlotIndex])
])
const zNodeData = z.object({
inputs: z.record(zNodeInputName, zNodeInputValue),
class_type: z.string(),
_meta: z.object({
title: z.string()
})
})
export const zComfyApiWorkflow = z.record(zNodeId, zNodeData)
export type ComfyApiWorkflow = z.infer<typeof zComfyApiWorkflow>

View File

@@ -1,28 +0,0 @@
import { z } from 'zod'
import { t } from '@/i18n'
const checkboxField = z.boolean().optional()
export const issueReportSchema = z
.object({
contactInfo: z.string().email().max(320).optional().or(z.literal('')),
details: z
.string()
.min(1, { message: t('validation.descriptionRequired') })
.max(5_000, { message: t('validation.maxLength', { length: 5_000 }) })
.optional(),
helpType: z.string().optional()
})
.catchall(checkboxField)
.refine((data) => Object.values(data).some((value) => value), {
path: ['details', 'helpType']
})
.refine((data) => data.helpType !== undefined && data.helpType !== '', {
message: t('issueReport.validation.helpTypeRequired'),
path: ['helpType']
})
.refine((data) => data.details !== undefined && data.details !== '', {
message: t('issueReport.validation.descriptionRequired'),
path: ['details']
})
export type IssueReportFormData = z.infer<typeof issueReportSchema>

View File

@@ -1,7 +1,7 @@
import { z } from 'zod'
// KeyCombo schema
export const zKeyCombo = z.object({
const zKeyCombo = z.object({
key: z.string(),
ctrl: z.boolean().optional(),
alt: z.boolean().optional(),

View File

@@ -1,11 +1,13 @@
import {
import type {
ComfyNodeDef as ComfyNodeDefV2,
InputSpec as InputSpecV2,
OutputSpec as OutputSpecV2
} from '@/schemas/nodeDef/nodeDefSchemaV2'
import {
import type {
ComfyNodeDef as ComfyNodeDefV1,
InputSpec as InputSpecV1,
InputSpec as InputSpecV1
} from '@/schemas/nodeDefSchema'
import {
getComboSpecComboOptions,
isComboInputSpec,
isComboInputSpecV1

View File

@@ -39,6 +39,119 @@ const zComboInputSpec = zComboInputOptions.extend({
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(),
@@ -51,6 +164,17 @@ const zInputSpec = z.union([
zBooleanInputSpec,
zStringInputSpec,
zComboInputSpec,
zColorInputSpec,
zFileUploadInputSpec,
zImageInputSpec,
zImageCompareInputSpec,
zMarkdownInputSpec,
zTreeSelectInputSpec,
zMultiSelectInputSpec,
zChartInputSpec,
zGalleriaInputSpec,
zSelectButtonInputSpec,
zTextareaInputSpec,
zCustomInputSpec
])
@@ -83,11 +207,20 @@ export const zComfyNodeDef = z.object({
})
// Export types
export type IntInputSpec = z.infer<typeof zIntInputSpec>
export type FloatInputSpec = z.infer<typeof zFloatInputSpec>
export type BooleanInputSpec = z.infer<typeof zBooleanInputSpec>
export type StringInputSpec = z.infer<typeof zStringInputSpec>
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>
@@ -124,18 +257,8 @@ export const isComboInputSpec = (
return inputSpec.type === 'COMBO'
}
/**
* Check if a node definition is a valid ComfyUI node definition.
*
* Note: This is just a simple check against the V1 schema.
*
* @param nodeDef - The node definition to check.
* @returns True if the node definition is valid, false otherwise.
*/
export const isComfyNodeDef = (nodeDef: unknown): nodeDef is ComfyNodeDef => {
return (
!!nodeDef &&
typeof nodeDef === 'object' &&
['inputs', 'outputs'].every((key) => key in nodeDef)
)
export const isChartInputSpec = (
inputSpec: InputSpec
): inputSpec is ChartInputSpec => {
return inputSpec.type === 'CHART'
}

View File

@@ -34,7 +34,7 @@ export const zBaseInputOptions = z
})
.passthrough()
export const zNumericInputOptions = zBaseInputOptions.extend({
const zNumericInputOptions = zBaseInputOptions.extend({
min: z.number().optional(),
max: z.number().optional(),
step: z.number().optional(),
@@ -77,6 +77,7 @@ export const zComboInputOptions = zBaseInputOptions.extend({
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(),
@@ -128,30 +129,12 @@ export function isFloatInputSpec(
return inputSpec[0] === 'FLOAT'
}
export function isBooleanInputSpec(
inputSpec: InputSpec
): inputSpec is BooleanInputSpec {
return inputSpec[0] === 'BOOLEAN'
}
export function isStringInputSpec(
inputSpec: InputSpec
): inputSpec is StringInputSpec {
return inputSpec[0] === 'STRING'
}
export function isComboInputSpecV2(
inputSpec: InputSpec
): inputSpec is ComboInputSpecV2 {
return inputSpec[0] === 'COMBO'
}
export function isCustomInputSpec(
inputSpec: InputSpec
): inputSpec is CustomInputSpec {
return typeof inputSpec[0] === 'string' && !excludedLiterals.has(inputSpec[0])
}
export function isComboInputSpec(
inputSpec: InputSpec
): inputSpec is ComboInputSpec | ComboInputSpecV2 {
@@ -247,22 +230,13 @@ export type ComfyOutputTypesSpec = z.infer<typeof zComfyOutputTypesSpec>
export type ComfyNodeDef = z.infer<typeof zComfyNodeDef>
export type RemoteWidgetConfig = z.infer<typeof zRemoteWidgetConfig>
// Input specs
export type IntInputOptions = z.infer<typeof zIntInputOptions>
export type FloatInputOptions = z.infer<typeof zFloatInputOptions>
export type BooleanInputOptions = z.infer<typeof zBooleanInputOptions>
export type StringInputOptions = z.infer<typeof zStringInputOptions>
export type ComboInputOptions = z.infer<typeof zComboInputOptions>
export type BaseInputOptions = z.infer<typeof zBaseInputOptions>
export type NumericInputOptions = z.infer<typeof zNumericInputOptions>
export type IntInputSpec = z.infer<typeof zIntInputSpec>
export type FloatInputSpec = z.infer<typeof zFloatInputSpec>
export type BooleanInputSpec = z.infer<typeof zBooleanInputSpec>
export type StringInputSpec = z.infer<typeof zStringInputSpec>
export type ComboInputSpec = z.infer<typeof zComboInputSpec>
export type ComboInputSpecV2 = z.infer<typeof zComboInputSpecV2>
export type CustomInputSpec = z.infer<typeof zCustomInputSpec>
export type InputSpec = z.infer<typeof zInputSpec>
export function validateComfyNodeDef(

View File

@@ -10,8 +10,6 @@ export const apiKeySchema = z.object({
.length(72, t('validation.length', { length: 72 }))
})
export type ApiKeyData = z.infer<typeof apiKeySchema>
export const signInSchema = z.object({
email: z
.string()
@@ -42,8 +40,6 @@ export const updatePasswordSchema = passwordSchema.refine(
}
)
export type UpdatePasswordData = z.infer<typeof updatePasswordSchema>
export const signUpSchema = passwordSchema
.extend({
email: z