ai-chatbot/lib/video-canvas/task-worker.ts

104 lines
4.1 KiB
TypeScript

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);
}
}
}