import "server-only"; import { NextResponse } from "next/server"; import { BoxCutoutServiceError, executeBoxCutoutJob, } from "@/lib/ai/box-cutout/service"; import { RmbgServiceError } from "@/lib/ai/rmbg/service"; import { getPointsSystemConfig } from "@/lib/db/queries"; import { prepareFeaturePointsCharge } from "@/lib/points/feature-charge"; import { getImageToolCreditCost } from "@/lib/points/config"; import { formatRequestFailure, getRequestFailureDetails } from "@/lib/request-error"; type BoxCutoutRequest = { imageUrl?: string; cropRect?: { x?: number; y?: number; width?: number; height?: number; }; paddingRatio?: number; prompt?: string; chatId?: string | null; messageId?: string | null; skipGallerySave?: boolean; sourceNodeId?: string; }; export async function executeBoxCutoutPOST(request: Request, userId: string) { const requestId = request.headers.get("x-request-id") || crypto.randomUUID(); try { if (!userId) { return NextResponse.json({ success: false, error: "未授权,请先登录" }, { status: 401 }); } const body = (await request.json()) as BoxCutoutRequest; const rawImageUrl = String(body.imageUrl || "").trim(); if (!rawImageUrl) { throw new BoxCutoutServiceError("缺少原图链接,请重新选择图片后提交。", 400); } const resolvedImageUrl = new URL(rawImageUrl, request.url).toString(); const pointsConfig = await getPointsSystemConfig(); const pointsCharge = await prepareFeaturePointsCharge({ userId, unitCost: getImageToolCreditCost(pointsConfig, "box-cutout"), reason: "image_box_cutout", modelId: "box-cutout", note: "框选抠图消耗积分", pointsConfig, }); if (!pointsCharge.ok) { return NextResponse.json(pointsCharge.payload, { status: pointsCharge.status }); } const result = await executeBoxCutoutJob({ requestId, userId, imageUrl: resolvedImageUrl, cropRect: body.cropRect || {}, paddingRatio: body.paddingRatio, prompt: body.prompt || "框选抠图", chatId: body.chatId || null, messageId: body.messageId || null, skipGallerySave: body.skipGallerySave === true, sourceNodeId: body.sourceNodeId, }); if (pointsCharge.chargeable) { try { await pointsCharge.consume(); } catch (pointsError) { console.error("[Points] Failed to consume box cutout points:", pointsError); } } return NextResponse.json({ ...result, requestId }); } catch (error) { const failure = getRequestFailureDetails(error, "框选抠图未能完成"); const payload = { success: false, error: formatRequestFailure(failure, requestId), detail: error instanceof Error ? error.message : "框选抠图未能完成", code: failure.code, failure, requestId, }; if (error instanceof BoxCutoutServiceError || error instanceof RmbgServiceError) { return NextResponse.json(payload, { status: error.status }); } console.error("[BoxCutout] 处理失败:", error); return NextResponse.json(payload, { status: 500 }); } }