97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
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 });
|
|
}
|
|
}
|