import { requireStoryPromptResult } from "./story-task-attachment"; import { executeImageOperation, ImageOperationUncertainError } from "./image-operation"; import { executeEnhancePOST, executeEnhanceGET } from "@/lib/canvas/operations/enhance"; import { executeExtendPOST } from "@/lib/canvas/operations/extend"; import { executeRemoveBackgroundPOST } from "@/lib/canvas/operations/rmbg"; import { executeAngleEditPOST } from "@/lib/canvas/operations/angle-edit"; import { checkpointCanvasGenerationTask, interruptCanvasGenerationTask } from "./task-store"; import "server-only"; import { executeDirectVideoGeneration } from "@/lib/ai/tools/generate-video"; import { executeCanvasImageResponse } from "@/lib/canvas/generate-image-response"; import { executeWorkflowAnalysisResponse } from "./workflow-analysis-response"; import { requireAnalysisResult, type CanvasTaskRequest } from "./task-contract"; import { validateVideoReferences } from "./reference-input"; import { probeRemoteUrl } from "@/lib/net/url-access"; import { claimCanvasGenerationTask, finishCanvasGenerationTask, heartbeatCanvasGenerationTask, } from "./task-store"; async function execute(request: CanvasTaskRequest, userId: string, taskId: string) { const params = request.params; if (request.kind === "image-operation") return executeImageOperation( params, userId, { enhance: executeEnhancePOST, extend: executeExtendPOST, removeBackground: executeRemoveBackgroundPOST, angleEdit: executeAngleEditPOST, pollEnhance: executeEnhanceGET, }, { checkpoint: (result) => checkpointCanvasGenerationTask(taskId, result) }, ); if (request.kind === "video") { const checked = await validateVideoReferences(params.images, (url) => probeRemoteUrl(url, { timeoutMs: 8000 }), ); if (checked.error) throw Error(checked.error); const result = await executeDirectVideoGeneration({ ...params, prompt: String(params.prompt || ""), mode: params.mode as any, images: checked.images, }); if (!result.success || !result.videoUrl) throw Error("视频生成未返回结果"); return { url: result.videoUrl, metadata: { ...result.metadata, temporaryVideoUrl: result.videoUrl, storageStatus: result.metadata?.permanentVideoUrl === result.videoUrl ? "archived" : "temporary", }, modelId: result.modelId, modelRef: result.modelRef, }; } const response = request.kind === "image" ? await executeCanvasImageResponse( { ...params, prompt: String(params.prompt || ""), async: false }, userId, ) : await executeWorkflowAnalysisResponse(params); const body = await response.json(); if (!response.ok || !body.success) throw Error(body.error || "任务执行失败"); if ( request.kind === "image" && !body.data?.images?.some((image: any) => image.originalUrl || image.thumbnailUrl) ) throw Error("图片生成没有返回结果"); if (request.kind === "analysis") requireAnalysisResult(body.data, String(params.mode)); requireStoryPromptResult(body.data, request.attachment); return body.data; } export async function runCanvasGenerationQueue(userId: string, projectId: string) { // Work is independent of the browser request. Atomic DB claiming also covers multiple processes. for (let i = 0; i < 40; i++) { const task = await claimCanvasGenerationTask(userId, projectId); if (!task) return; const timer = setInterval( () => void heartbeatCanvasGenerationTask(task.id).catch(() => {}), 20000, ); try { await finishCanvasGenerationTask(task.id, await execute(task.request, userId, task.id)); } catch (error) { if (error instanceof ImageOperationUncertainError) { await interruptCanvasGenerationTask(task.id, error.message); continue; } await finishCanvasGenerationTask( task.id, undefined, error instanceof Error ? error.message : "任务执行失败", ); } finally { clearInterval(timer); } } }