import * as z from "zod"; /** Cap for a custom ComfyUI workflow (API-format JSON). */ export const MEDIA_WORKFLOW_MAX_LENGTH = 512 * 1024; /** Empty form fields mean "not set". */ function optionalText(max: number) { return z .string() .trim() .max(max) .optional() .transform((value) => value || undefined); } function isJsonObject(value: string | undefined): boolean { if (value === undefined) return true; try { const parsed: unknown = JSON.parse(value); return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed); } catch { return false; } } const workflow = optionalText(MEDIA_WORKFLOW_MAX_LENGTH).refine(isJsonObject, { message: "Workflow must be a JSON object", }); /** Image and video generation on the deployment owner's ComfyUI server. */ export const ComfyUiSettingsSchema = z.object({ baseUrl: z.string().trim().min(1).max(2048), apiKey: optionalText(4096), /** Checkpoint name; the first installed checkpoint when unset. */ imageModel: optionalText(512), /** Custom API-format workflows that replace the built-in defaults. */ imageWorkflow: workflow, videoWorkflow: workflow, }); export type ComfyUiSettings = z.infer; /** Speech to text on an OpenAI-compatible server (`POST /v1/audio/transcriptions`). */ export const TranscriptionSettingsSchema = z.object({ baseUrl: z.string().trim().min(1).max(2048), model: optionalText(512), apiKey: optionalText(4096), }); export type TranscriptionSettings = z.infer; /** Deployment-wide media backends; null means the backend is not configured. */ export const MediaSettingsSchema = z.object({ comfyui: ComfyUiSettingsSchema.nullable(), transcription: TranscriptionSettingsSchema.nullable(), }); export type MediaSettings = z.infer; /** What the settings screen reads: saved keys are never returned, only whether one exists. */ export const MediaSettingsViewSchema = z.object({ comfyui: ComfyUiSettingsSchema.omit({ apiKey: true }) .extend({ hasApiKey: z.boolean() }) .nullable(), transcription: TranscriptionSettingsSchema.omit({ apiKey: true }) .extend({ hasApiKey: z.boolean() }) .nullable(), }); export type MediaSettingsView = z.infer; const keyUpdate = z.string().trim().max(4096).optional(); /** * Saving replaces both backends. An omitted `apiKey` keeps the saved key while the server's * origin stays the same; an empty one removes it. */ export const MediaSettingsUpdateSchema = z.object({ comfyui: ComfyUiSettingsSchema.extend({ apiKey: keyUpdate }).nullable(), transcription: TranscriptionSettingsSchema.extend({ apiKey: keyUpdate }).nullable(), }); export type MediaSettingsUpdate = z.input;