Your Name e7cc0c9555
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix: align workflow material upload limits
2026-04-02 00:24:16 +08:00

177 lines
5.7 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { auth } from "@/app/(auth)/auth";
import { executeRmbgJob, RmbgServiceError } from "@/lib/ai/rmbg/service";
import { upsertMediaAsset } from "@/lib/db/queries";
export const runtime = "nodejs";
export const maxDuration = 180;
const payloadSchema = z.object({
source: z.enum(["primary", "secondary"]).default("primary"),
query: z.string().trim().max(64).optional(),
queryType: z.enum(["sku", "spu"]).optional(),
matchedSku: z.string().trim().max(64).optional(),
matchedSpu: z.string().trim().max(64).optional(),
productModel: z.string().trim().max(128).optional(),
});
function getOptionalFormString(value: FormDataEntryValue | null) {
if (typeof value !== "string") {
return undefined;
}
const normalizedValue = value.trim();
return normalizedValue.length > 0 ? normalizedValue : undefined;
}
function isUploadLikeFile(value: FormDataEntryValue | null) {
return (
Boolean(value) &&
typeof value !== "string" &&
typeof (value as Blob).arrayBuffer === "function"
);
}
function sanitizeFilenameStem(value: string) {
return String(value || "")
.trim()
.replace(/\.[^.]+$/, "")
.replace(/[\\/:*?"<>|]+/g, "-")
.replace(/\s+/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 80);
}
function normalizeUploadedImageMimeType(file: Blob & { name?: string; type?: string }) {
const type = String(file.type || "").trim().toLowerCase();
if (type.startsWith("image/")) {
return type;
}
const fileName = String(file.name || "").trim().toLowerCase();
if (/\.(png)$/i.test(fileName)) return "image/png";
if (/\.(jpe?g)$/i.test(fileName)) return "image/jpeg";
if (/\.(webp)$/i.test(fileName)) return "image/webp";
if (/\.(gif)$/i.test(fileName)) return "image/gif";
if (/\.(bmp)$/i.test(fileName)) return "image/bmp";
if (/\.(heic)$/i.test(fileName)) return "image/heic";
if (/\.(heif)$/i.test(fileName)) return "image/heif";
return "";
}
export async function POST(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const formData = await request.formData();
const fileEntry = formData.get("file");
if (!isUploadLikeFile(fileEntry)) {
return NextResponse.json({ error: "缺少图片文件" }, { status: 400 });
}
const file = fileEntry as Blob & { name?: string; type?: string };
const mimeType = normalizeUploadedImageMimeType(file);
if (!mimeType) {
return NextResponse.json({ error: "请上传图片文件" }, { status: 400 });
}
const payload = payloadSchema.parse({
source: getOptionalFormString(formData.get("source")),
query: getOptionalFormString(formData.get("query")),
queryType: getOptionalFormString(formData.get("queryType")),
matchedSku: getOptionalFormString(formData.get("matchedSku")),
matchedSpu: getOptionalFormString(formData.get("matchedSpu")),
productModel: getOptionalFormString(formData.get("productModel")),
});
const arrayBuffer = await file.arrayBuffer();
const imageBuffer = Buffer.from(arrayBuffer);
if (imageBuffer.byteLength <= 0) {
return NextResponse.json({ error: "图片文件为空,请重新上传" }, { status: 400 });
}
const promptPrefix = payload.source === "secondary" ? "搭配SKU" : "主SPU";
const prompt = `${promptPrefix}素材抠图`;
const result = await executeRmbgJob({
userId: session.user.id,
imageBuffer,
prompt,
skipGallerySave: true,
});
const originalFileName = String(file.name || "").trim() || `workflow-upload-${Date.now()}.png`;
const originalStem = sanitizeFilenameStem(originalFileName) || `workflow-upload-${Date.now()}`;
const displayName = `${originalStem}-rmbg.png`;
const asset = await upsertMediaAsset({
userId: session.user.id,
mediaType: "image",
sourceType: "agent_workflow_material_upload",
status: "ready",
originalUrl: result.original_url,
previewUrl: result.original_url,
thumbnailUrl: result.thumbnail_url,
mimeType: "image/png",
displayName,
prompt,
modelId: result.provider,
sourceTable: "agent_workflow_material_upload",
metadata: {
source: payload.source,
query: payload.query || null,
queryType: payload.queryType || "sku",
matchedSku: payload.matchedSku || null,
matchedSpu: payload.matchedSpu || null,
productModel: payload.productModel || null,
originalFileName,
originalMimeType: mimeType,
provider: result.provider,
},
});
const imageId = asset?.id || `upload-${randomUUID()}`;
return NextResponse.json({
success: true,
data: {
id: imageId,
key: imageId,
label: "用户上传",
fileName: displayName,
imageUrl: result.original_url,
assetId: asset?.id ?? null,
mimeType: "image/png",
sourceType: "upload",
group: "other",
sortOrder: 0,
},
});
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.issues[0]?.message || "请求参数不合法" },
{ status: 400 }
);
}
if (error instanceof RmbgServiceError) {
return NextResponse.json(
{ error: error.message, detail: error.detail || error.message },
{ status: error.status }
);
}
console.error("[Agent Workflow Material Upload] Failed to process upload:", error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : "上传失败" },
{ status: 500 }
);
}
}