import { NextResponse } from "next/server"; import { auth } from "@/app/(auth)/auth"; import { WorldLabsApiError, worldLabsRequest } from "@/lib/worldlabs/client"; import { getWorldLabsProviderConfig } from "@/lib/worldlabs/server"; import type { WorldLabsPrepareUploadResponse } from "@/lib/worldlabs/types"; export const runtime = "nodejs"; export const maxDuration = 120; const ALLOWED_IMAGE_TYPES = new Set(["image/png", "image/jpeg", "image/webp"]); const ALLOWED_VIDEO_TYPES = new Set([ "video/mp4", "video/quicktime", "video/webm", "video/x-m4v", ]); const MAX_IMAGE_UPLOAD_SIZE = 20 * 1024 * 1024; const MAX_VIDEO_UPLOAD_SIZE = 100 * 1024 * 1024; function guessExtension(file: File) { const fromName = String(file.name || "") .split(".") .pop() ?.trim() .toLowerCase(); if (fromName) return fromName; const byType: Record = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "video/mp4": "mp4", "video/quicktime": "mov", "video/webm": "webm", "video/x-m4v": "m4v", }; return byType[file.type] || "bin"; } function normalizeMediaAssetId(data: WorldLabsPrepareUploadResponse) { return String(data.media_asset?.media_asset_id || data.media_asset?.id || "").trim(); } export async function POST(request: Request) { try { const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ success: false, error: "未登录" }, { status: 401 }); } const formData = await request.formData(); const file = formData.get("file"); if (!(file instanceof File)) { return NextResponse.json({ success: false, error: "请上传文件" }, { status: 400 }); } const requestedKind = String(formData.get("kind") || "").trim(); const isImage = requestedKind ? requestedKind === "image" : ALLOWED_IMAGE_TYPES.has(file.type); const isVideo = requestedKind ? requestedKind === "video" : ALLOWED_VIDEO_TYPES.has(file.type); if (!isImage && !isVideo) { return NextResponse.json( { success: false, error: "仅支持 PNG/JPEG/WEBP 图片或 MP4/MOV/WEBM 视频" }, { status: 400 } ); } if (file.size <= 0) { return NextResponse.json({ success: false, error: "文件不能为空" }, { status: 400 }); } const maxSize = isImage ? MAX_IMAGE_UPLOAD_SIZE : MAX_VIDEO_UPLOAD_SIZE; if (file.size > maxSize) { return NextResponse.json( { success: false, error: isImage ? "图片大小不能超过 20MB" : "视频大小不能超过 100MB", }, { status: 400 } ); } const config = await getWorldLabsProviderConfig(); const extension = guessExtension(file); const prepareUpload = await worldLabsRequest(config, { method: "POST", path: "/marble/v1/media-assets:prepare_upload", json: { file_name: file.name || `upload.${extension}`, extension, kind: isImage ? "image" : "video", metadata: { mime_type: file.type || null, uploaded_from: "canvas_world_mode", }, }, }); const mediaAssetId = normalizeMediaAssetId(prepareUpload); const uploadUrl = String(prepareUpload.upload_info?.upload_url || "").trim(); if (!mediaAssetId || !uploadUrl) { throw new Error("World Labs 返回的上传信息不完整"); } const uploadHeaders = new Headers(); uploadHeaders.set("Content-Type", file.type || "application/octet-stream"); const requiredHeaders = prepareUpload.upload_info?.required_headers || {}; for (const [headerName, headerValue] of Object.entries(requiredHeaders)) { if (!headerName || headerValue === undefined || headerValue === null) continue; uploadHeaders.set(headerName, String(headerValue)); } const uploadMethod = String(prepareUpload.upload_info?.upload_method || "PUT").toUpperCase(); const uploadResponse = await fetch(uploadUrl, { method: uploadMethod === "PUT" ? "PUT" : uploadMethod, headers: uploadHeaders, body: file, cache: "no-store", }); if (!uploadResponse.ok) { const uploadError = await uploadResponse.text().catch(() => ""); throw new Error( uploadError || `World Labs 文件上传失败(HTTP ${uploadResponse.status})` ); } return NextResponse.json({ success: true, data: { mediaAssetId, mediaAsset: prepareUpload.media_asset, uploadInfo: prepareUpload.upload_info, }, }); } catch (error) { if (error instanceof WorldLabsApiError) { return NextResponse.json( { success: false, error: error.message, payload: error.payload, }, { status: error.httpStatus || 502 } ); } return NextResponse.json( { success: false, error: error instanceof Error ? error.message : "上传 World Labs 媒体失败", }, { status: 500 } ); } }