import { createStudioWorkResult } from '../domain/factories' import type { StudioFileAttachment, StudioTask, StudioWork, StudioWorkResult, StudioWorkResultStore, StudioWorkStore } from '../domain/types' import type { JobResult } from '../../types' import { resolveJobResultCode, resolveJobResultCodeLanguage } from '../domain/render-result-metadata' import type { StudioBlobStore } from '../storage/studio-blob-store' export interface StudioRenderWorkSyncStores { workStore: StudioWorkStore workResultStore: StudioWorkResultStore blobStore?: StudioBlobStore } export async function syncRenderWorkFromTask( stores: StudioRenderWorkSyncStores, task: StudioTask ): Promise<{ work: StudioWork; result?: StudioWorkResult } | null> { if (!task.workId) { return null } const work = await stores.workStore.getById(task.workId) if (!work) { return null } const nextStatus = toWorkStatus(task.status) const nextMetadata = { ...(work.metadata ?? {}), ...(task.metadata ?? {}) } const renderResult = getRenderResult(task) if (!renderResult) { const updatedWork = await stores.workStore.update(work.id, { latestTaskId: task.id, status: nextStatus, metadata: nextMetadata }) return updatedWork ? { work: updatedWork } : { work } } const nextWorkResult = await buildRenderWorkResult(work.id, task, renderResult, stores.blobStore) const persistedResult = work.currentResultId ? await stores.workResultStore.update(work.currentResultId, nextWorkResult) : await stores.workResultStore.create(createStudioWorkResult(nextWorkResult)) const updatedWork = await stores.workStore.update(work.id, { latestTaskId: task.id, currentResultId: persistedResult?.id ?? work.currentResultId, status: nextStatus, metadata: nextMetadata }) return { work: updatedWork ?? work, result: persistedResult ?? undefined } } function getRenderResult(task: StudioTask): JobResult | null { const candidate = task.metadata?.result if (!candidate || typeof candidate !== 'object') { return null } const status = (candidate as { status?: unknown }).status if (status !== 'completed' && status !== 'failed') { return null } return candidate as JobResult } async function buildRenderWorkResult( workId: string, task: StudioTask, result: JobResult, blobStore?: StudioBlobStore ): Promise> { if (result.status === 'completed') { const outputMode = result.data.outputMode const attachments = await buildCompletedAttachments(result, blobStore) const summary = outputMode === 'video' ? `Render completed${result.data.videoUrl ? `: ${result.data.videoUrl}` : ''}` : `Render completed with ${result.data.imageCount ?? result.data.imageUrls?.length ?? 0} image output(s)` return { workId, kind: 'render-output', summary, attachments, metadata: { taskId: task.id, jobId: task.metadata?.jobId, outputMode, quality: result.data.quality, generationType: result.data.generationType, usedAI: result.data.usedAI, renderPeakMemoryMB: result.data.renderPeakMemoryMB, timings: result.data.timings, code: resolveJobResultCode(result), codeLanguage: resolveJobResultCodeLanguage(result), imageCount: result.data.imageCount, workspaceVideoPath: result.data.workspaceVideoPath, workspaceImagePaths: result.data.workspaceImagePaths } } } return { workId, kind: 'failure-report', summary: result.data.error, metadata: { taskId: task.id, jobId: task.metadata?.jobId, outputMode: result.data.outputMode, error: result.data.error, details: result.data.details, cancelReason: result.data.cancelReason, stage: task.metadata?.stage, bullStatus: task.metadata?.bullStatus } } } async function buildCompletedAttachments( result: Extract, blobStore?: StudioBlobStore ): Promise { const attachments: StudioFileAttachment[] = [] if (result.data.videoUrl) { attachments.push(await resolveAttachment({ blobStore, path: result.data.videoUrl, name: fileNameFromPath(result.data.videoUrl), mimeType: 'video/mp4' })) } for (const imageUrl of result.data.imageUrls ?? []) { attachments.push(await resolveAttachment({ blobStore, path: imageUrl, name: fileNameFromPath(imageUrl), mimeType: 'image/png' })) } return attachments.length > 0 ? attachments : undefined } async function resolveAttachment(input: { blobStore?: StudioBlobStore path: string name?: string mimeType?: string }): Promise { if (!input.blobStore) { return { kind: 'file', path: input.path, name: input.name, mimeType: input.mimeType, } } return input.blobStore.resolveAttachment({ path: input.path, name: input.name, mimeType: input.mimeType, }) } function toWorkStatus(taskStatus: StudioTask['status']): StudioWork['status'] { switch (taskStatus) { case 'completed': return 'completed' case 'failed': return 'failed' case 'cancelled': return 'cancelled' default: return 'running' } } function fileNameFromPath(path: string): string { const parts = path.split(/[\\/]/) return parts[parts.length - 1] || path }