import { videoUpscaleSchema } from "@/lib/video-canvas/video-upscale-contract"; import { smartEditRequestSchema } from "@/lib/video-canvas/smart-edit/contract"; import { audioRequestSchema } from "@/lib/video-canvas/audio-contract"; import { canResumeCanvasTask } from "@/lib/video-canvas/task-recovery"; import { imageOperationSchema } from "@/lib/video-canvas/image-operation"; import { after, NextResponse } from "next/server"; import { auth } from "@/app/(auth)/auth"; import { z } from "zod"; import { canvasTaskRequestSchema } from "@/lib/video-canvas/task-contract"; import { cancelCanvasGenerationTask, createCanvasGenerationTask, listCanvasGenerationTasks, ownsTaskProject, } from "@/lib/video-canvas/task-store"; import { runCanvasGenerationQueue } from "@/lib/video-canvas/task-worker"; import { preflightCanvasProductionRequests } from "@/lib/video-canvas/production-services"; export const runtime = "nodejs"; export const maxDuration = 900; async function owner(projectId: unknown) { const session = await auth(); if (!session?.user?.id) throw Object.assign(Error("未登录"), { status: 401 }); if (!z.string().uuid().safeParse(projectId).success) throw Object.assign(Error("画布 ID 无效"), { status: 400 }); if (!(await ownsTaskProject(session.user.id, projectId as string))) throw Object.assign(Error("画布不存在"), { status: 404 }); return session.user.id; } const fail = (error: unknown) => NextResponse.json( { success: false, error: error instanceof Error ? error.message : "任务操作失败", code: (error as any)?.code }, { status: Number((error as any)?.status) || (error instanceof SyntaxError ? 400 : 500) }, ); const kick = (userId: string, projectId: string) => after(() => runCanvasGenerationQueue(userId, projectId).catch((error) => console.error("[Canvas tasks]", error), ), ); export async function POST(request: Request) { try { const text = await request.text(); if (new TextEncoder().encode(text).byteLength > 262144) throw Object.assign(Error("任务数据过大,请使用已上传的素材链接"), { status: 413 }); const parsed = canvasTaskRequestSchema.safeParse(JSON.parse(text)); if (!parsed.success) throw Object.assign(Error("任务参数不合法"), { status: 400 }); if(parsed.data.workflow)throw Object.assign(Error("编组任务必须使用整组提交接口"),{status:400}); if ( parsed.data.kind === "image-operation" && !imageOperationSchema.safeParse(parsed.data.params).success ) throw Object.assign(Error("图片处理参数不合法"), { status: 400 }); if ( parsed.data.attachment && ((parsed.data.attachment.type === "story-asset" && parsed.data.kind !== "image") || (parsed.data.attachment.type === "story-prompts" && parsed.data.kind !== "analysis")) ) throw Object.assign(Error("脚本结果类型不匹配"), { status: 400 }); if (parsed.data.kind === "audio" && !audioRequestSchema.safeParse(parsed.data.params).success) throw Object.assign(Error("音频参数不合法"), { status: 400 }); if ( parsed.data.kind === "smart-edit" && !smartEditRequestSchema.safeParse(parsed.data.params).success ) throw Object.assign(Error("剪辑参数无效"), { status: 400 }); if (parsed.data.kind === "video-upscale" && !videoUpscaleSchema.safeParse(parsed.data.params).success) throw Object.assign(Error("视频高清参数无效"), {status:400}); const userId = await owner(parsed.data.projectId); const task = await createCanvasGenerationTask(userId, parsed.data, () => preflightCanvasProductionRequests([parsed.data])); kick(userId, parsed.data.projectId); return NextResponse.json({ success: true, task }, { status: 202 }); } catch (error) { return fail(error); } } export async function GET(request: Request) { try { const projectId = new URL(request.url).searchParams.get("projectId") || ""; const userId = await owner(projectId); const tasks = await listCanvasGenerationTasks(userId, projectId); if (tasks.some((task) => task.status === "pending" || canResumeCanvasTask(task))) kick(userId, projectId); return NextResponse.json( { success: true, tasks }, { headers: { "Cache-Control": "no-store" } }, ); } catch (error) { return fail(error); } } export async function DELETE(request: Request) { try { const { projectId, taskId } = await request.json(); const userId = await owner(projectId); if (!z.string().uuid().safeParse(taskId).success) throw Object.assign(Error("任务 ID 无效"), { status: 400 }); const result = await cancelCanvasGenerationTask(userId, projectId, taskId); if (!result.task) return NextResponse.json({ success: false, error: "任务不存在" }, { status: 404 }); if (!result.ok) return NextResponse.json( { success: false, error: result.task.status === "running" ? "任务已提交供应商,当前线路不支持中止。结果会继续保存,请勿重复生成。" : "任务已结束或状态待核实,请先刷新任务状态。", task: result.task, }, { status: 409 }, ); return NextResponse.json({ success: true, task: result.task }); } catch (error) { return fail(error); } } export async function PATCH(request: Request) { try { const { projectId, taskId, nodeId } = await request.json(); const userId = await owner(projectId); if (!z.string().uuid().safeParse(taskId).success || typeof nodeId !== "string") return NextResponse.json({ success: false }, { status: 400 }); const { hideCanvasGenerationNode } = await import("@/lib/video-canvas/task-store"); const success = await hideCanvasGenerationNode(userId, projectId, taskId, nodeId); return NextResponse.json({ success }, { status: success ? 200 : 404 }); } catch (error) { return fail(error); } }