import { z } from 'zod' const fileFilter = z.object({ name: z.string(), extensions: z.array(z.string()) }) function ipcResult(valueShape: T) { return z.discriminatedUnion('success', [ z.object({ success: z.literal(true), ...valueShape }), z.object({ success: z.literal(false), error: z.string() }), ]) } export type IpcResult = z.infer>> const emptyResult = ipcResult({}) const exportClip = z.object({ path: z.string(), type: z.string(), startTime: z.number(), duration: z.number(), trimStart: z.number(), speed: z.number(), reversed: z.boolean(), flipH: z.boolean(), flipV: z.boolean(), opacity: z.number(), trackIndex: z.number(), muted: z.boolean(), volume: z.number(), }) const exportSubtitle = z.object({ text: z.string(), startTime: z.number(), endTime: z.number(), style: z.object({ fontSize: z.number(), fontFamily: z.string(), fontWeight: z.string(), color: z.string(), backgroundColor: z.string(), position: z.string(), italic: z.boolean(), }), }) const logsResponse = z.object({ logPath: z.string(), lines: z.array(z.string()), error: z.string().optional(), }) const backendHealthStatus = z.object({ status: z.enum(['alive', 'restarting', 'dead']), exitCode: z.number().nullable().optional(), }) export type BackendHealthStatus = z.infer const updateStatePayload = z.object({ status: z.enum(['idle', 'checking', 'available', 'downloading', 'downloaded', 'not-available']), currentVersion: z.string(), version: z.string().optional(), releaseNotes: z.string().optional(), percent: z.number().optional(), message: z.string().optional(), }) export type UpdateStatePayload = z.infer export const electronAPISchemas = { // App info getBackend: { input: z.object({}), output: z.object({ url: z.string(), token: z.string() }), }, getModelsPath: { input: z.object({}), output: z.string(), }, readLocalFile: { input: z.object({ filePath: z.string() }), output: z.object({ data: z.string(), mimeType: z.string() }), }, checkGpu: { input: z.object({}), output: z.object({ available: z.boolean(), name: z.string().optional(), vram: z.number().optional() }), }, getAppInfo: { input: z.object({}), output: z.object({ version: z.string(), isPackaged: z.boolean(), modelsPath: z.string(), userDataPath: z.string() }), }, // First-run setup checkFirstRun: { input: z.object({}), output: z.object({ needsSetup: z.boolean(), needsLicense: z.boolean() }), }, acceptLicense: { input: z.object({}), output: z.boolean(), }, completeSetup: { input: z.object({}), output: z.boolean(), }, fetchLicenseText: { input: z.object({}), output: z.string(), }, getNoticesText: { input: z.object({}), output: z.string(), }, // Open external pages / folders openLtxApiKeyPage: { input: z.object({}), output: z.boolean(), }, openLtxBillingPage: { input: z.object({}), output: z.boolean(), }, openFalApiKeyPage: { input: z.object({}), output: z.boolean(), }, openHuggingFaceRepo: { input: z.object({ repoId: z.string() }), output: z.boolean(), }, openExternalUrl: { input: z.object({ url: z.string() }), output: z.boolean(), }, openHuggingFaceAuth: { input: z.object({ clientId: z.string(), redirectUri: z.string(), scope: z.string(), state: z.string(), codeChallenge: z.string(), codeChallengeMethod: z.string(), }), output: z.boolean(), }, openParentFolderOfFile: { input: z.object({ filePath: z.string() }), output: z.void(), }, showItemInFolder: { input: z.object({ filePath: z.string() }), output: z.void(), }, // Logs getLogs: { input: z.object({ query: z.string().optional() }), output: logsResponse, }, getLogPath: { input: z.object({}), output: z.object({ logPath: z.string(), logDir: z.string() }), }, openLogFolder: { input: z.object({}), output: z.boolean(), }, // Paths getResourcePath: { input: z.object({}), output: z.string().nullable(), }, getDownloadsPath: { input: z.object({}), output: z.string(), }, getFreeDiskSpace: { input: z.object({ path: z.string() }), output: ipcResult({ bytes: z.number() }), }, // Project assets addVisualAssetToProject: { input: z.object({ srcPath: z.string(), projectId: z.string(), type: z.enum(['video', 'image']) }), output: ipcResult({ path: z.string(), bigThumbnailPath: z.string(), smallThumbnailPath: z.string(), width: z.number(), height: z.number(), }), }, addGenericAssetToProject: { input: z.object({ srcPath: z.string(), projectId: z.string() }), output: ipcResult({ path: z.string() }), }, prepareGenerationInput: { input: z.object({ path: z.string() }), output: ipcResult({ path: z.string() }), }, makeThumbnailsForProjectAsset: { input: z.object({ path: z.string(), type: z.enum(['video', 'image']) }), output: ipcResult({ bigThumbnailPath: z.string(), smallThumbnailPath: z.string(), }), }, makeDimensionsForProjectAsset: { input: z.object({ path: z.string(), type: z.enum(['video', 'image']) }), output: ipcResult({ width: z.number(), height: z.number(), }), }, getProjectAssetsPath: { input: z.object({}), output: z.string(), }, openProjectAssetsPathChangeDialog: { input: z.object({}), output: ipcResult({ path: z.string() }), }, // File dialogs & save showSaveDialog: { input: z.object({ title: z.string().optional(), defaultPath: z.string().optional(), filters: z.array(fileFilter).optional(), }), output: z.string().nullable(), }, saveFile: { input: z.object({ filePath: z.string(), data: z.string(), encoding: z.string().optional() }), output: ipcResult({ path: z.string() }), }, saveBinaryFile: { input: z.object({ filePath: z.string(), data: z.instanceof(ArrayBuffer) }), output: ipcResult({ path: z.string() }), }, showOpenDirectoryDialog: { input: z.object({ title: z.string().optional() }), output: z.string().nullable(), }, searchDirectoryForFiles: { input: z.object({ directory: z.string(), filenames: z.array(z.string()) }), output: z.record(z.string(), z.string()), }, checkFilesExist: { input: z.object({ filePaths: z.array(z.string()) }), output: z.record(z.string(), z.boolean()), }, showOpenFileDialog: { input: z.object({ title: z.string().optional(), filters: z.array(fileFilter).optional(), properties: z.array(z.string()).optional(), }), output: z.array(z.string()).nullable(), }, // Video export exportNative: { input: z.object({ clips: z.array(exportClip), outputPath: z.string(), codec: z.string(), width: z.number(), height: z.number(), fps: z.number(), quality: z.number(), letterbox: z.object({ ratio: z.number(), color: z.string(), opacity: z.number() }).optional(), subtitles: z.array(exportSubtitle).optional(), }), output: emptyResult, }, exportCancel: { input: z.object({ sessionId: z.string() }), output: emptyResult, }, // Python setup checkPythonReady: { input: z.object({}), output: z.object({ ready: z.boolean() }), }, startPythonSetup: { input: z.object({}), output: z.void(), }, startPythonBackend: { input: z.object({}), output: z.void(), }, getBackendHealthStatus: { input: z.object({}), output: backendHealthStatus.nullable(), }, // Tells the liveness monitor a generation is known to be in flight, so it doesn't mistake a // long-running local generation (MPS/CUDA compute can starve the backend's own event loop for // tens of seconds, delaying /health) for a genuinely hung process and kill it mid-generation. notifyGenerationActive: { input: z.object({ active: z.boolean() }), output: z.void(), }, // Video processing extractVideoFrame: { input: z.object({ videoPath: z.string(), seekTime: z.number(), width: z.number().optional(), quality: z.number().optional() }), output: z.object({ path: z.string() }), }, // Logging writeLog: { input: z.object({ level: z.string(), message: z.string() }), output: z.void(), }, // Models openModelsDirChangeDialog: { input: z.object({}), output: ipcResult({ path: z.string() }), }, openModelsFolder: { // No path argument by design — the main process resolves the configured models dir // from the backend so a renderer can't ask to open an arbitrary location. input: z.object({}), output: ipcResult({}), }, // Analytics getAnalyticsState: { input: z.object({}), output: z.object({ analyticsEnabled: z.boolean(), installationId: z.string() }), }, setAnalyticsEnabled: { input: z.object({ enabled: z.boolean() }), output: z.void(), }, sendAnalyticsEvent: { input: z.object({ eventName: z.string(), extraDetails: z.record(z.string(), z.unknown()).nullable().optional() }), output: z.void(), }, // --- App updates --- getUpdateState: { input: z.object({}), output: updateStatePayload, }, checkForUpdatesNow: { input: z.object({}), output: emptyResult, }, startUpdateDownload: { input: z.object({}), output: emptyResult, }, installUpdateAndRestart: { input: z.object({}), output: emptyResult, }, skipUpdateVersion: { input: z.object({ version: z.string() }), output: emptyResult, }, getAutoCheckUpdates: { input: z.object({}), output: z.object({ enabled: z.boolean() }), }, setAutoCheckUpdates: { input: z.object({ enabled: z.boolean() }), output: emptyResult, }, } as const type Schemas = typeof electronAPISchemas type InvokeAPI = { [K in keyof Schemas]: z.infer extends Record ? () => Promise> : (input: z.infer) => Promise> } export type ElectronAPI = InvokeAPI & { onPythonSetupProgress: (cb: (data: unknown) => void) => void removePythonSetupProgress: () => void onBackendHealthStatus: (cb: (data: BackendHealthStatus) => void) => (() => void) onUpdateEvent: (cb: (data: UpdateStatePayload) => void) => (() => void) getPathForFile: (file: File) => string platform: string }