Your Name d272bd5ee8
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat(canvas): add Tripo3D generation workspace and provider config
2026-02-28 21:57:51 +08:00

109 lines
3.2 KiB
TypeScript

import { NextResponse } from "next/server";
import { auth } from "@/app/(auth)/auth";
import { getTripoProviderConfig } from "@/lib/tripo/server";
import { TripoApiError, tripoRequest } from "@/lib/tripo/client";
export const runtime = "nodejs";
export const maxDuration = 60;
const ALLOWED_IMAGE_TYPES = new Set(["image/png", "image/jpeg", "image/webp"]);
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024;
function extractImageToken(data: unknown): string | null {
if (!data) return null;
if (typeof data === "string") return data.trim() || null;
if (typeof data !== "object") return null;
const record = data as Record<string, unknown>;
const tokenCandidates = [record.image_token, record.file_token, record.token];
for (const candidate of tokenCandidates) {
if (typeof candidate === "string" && candidate.trim()) {
return candidate.trim();
}
}
return null;
}
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 });
}
if (!ALLOWED_IMAGE_TYPES.has(file.type)) {
return NextResponse.json(
{ success: false, error: "仅支持 PNG/JPEG/WEBP 图片" },
{ status: 400 }
);
}
if (file.size <= 0 || file.size > MAX_UPLOAD_SIZE) {
return NextResponse.json(
{ success: false, error: "图片大小需在 1B~10MB 之间" },
{ status: 400 }
);
}
const config = await getTripoProviderConfig();
const uploadPaths = ["/upload/sts", "/upload"];
let lastError: unknown = null;
for (const path of uploadPaths) {
try {
const submitData = new FormData();
submitData.set("file", file, file.name || "upload.png");
const result = await tripoRequest<Record<string, unknown>>(config, {
method: "POST",
path,
formData: submitData,
});
const imageToken = extractImageToken(result.data);
return NextResponse.json({
success: true,
data: {
imageToken,
file: result.data,
},
traceId: result.traceId,
});
} catch (error) {
lastError = error;
if (error instanceof TripoApiError && error.httpStatus === 404) {
continue;
}
throw error;
}
}
throw lastError instanceof Error ? lastError : new Error("图片上传失败");
} catch (error) {
if (error instanceof TripoApiError) {
return NextResponse.json(
{
success: false,
error: error.message,
code: error.code,
suggestion: error.suggestion,
traceId: error.traceId,
},
{ status: error.httpStatus || 502 }
);
}
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "图片上传失败",
},
{ status: 500 }
);
}
}