125 lines
3.8 KiB
TypeScript
125 lines
3.8 KiB
TypeScript
import { NextResponse } from "next/server";
|
||
import { auth } from "@/app/(auth)/auth";
|
||
import {
|
||
createCanvasImageTask,
|
||
enqueueCanvasImageTask,
|
||
} from "@/lib/canvas/image-task-queue";
|
||
import { getPointsSystemConfig } from "@/lib/db/queries";
|
||
import { prepareFeaturePointsCharge } from "@/lib/points/feature-charge";
|
||
import { getImageToolCreditCost } from "@/lib/points/config";
|
||
import type { CanvasLayoutReplaceTaskRequest } from "@/lib/canvas/layout-replace-task";
|
||
|
||
export const maxDuration = 1200;
|
||
|
||
function normalizeHttpUrl(value: unknown) {
|
||
const url = String(value || "").trim();
|
||
return /^https?:\/\//i.test(url) ? url : "";
|
||
}
|
||
|
||
function normalizeUuid(value: unknown) {
|
||
const uuid = String(value || "").trim();
|
||
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(uuid)
|
||
? uuid
|
||
: undefined;
|
||
}
|
||
|
||
export async function POST(request: Request) {
|
||
try {
|
||
const session = await auth();
|
||
if (!session?.user?.id) {
|
||
return NextResponse.json(
|
||
{ success: false, error: "未登录" },
|
||
{ status: 401 },
|
||
);
|
||
}
|
||
|
||
const body = await request.json().catch(() => ({}));
|
||
const sourceImageUrl = normalizeHttpUrl(body?.sourceImageUrl);
|
||
const referenceImageUrl = normalizeHttpUrl(body?.referenceImageUrl);
|
||
if (!sourceImageUrl) {
|
||
return NextResponse.json(
|
||
{ success: false, error: "缺少当前产品图片" },
|
||
{ status: 400 },
|
||
);
|
||
}
|
||
if (!referenceImageUrl) {
|
||
return NextResponse.json(
|
||
{ success: false, error: "缺少排版参考图" },
|
||
{ status: 400 },
|
||
);
|
||
}
|
||
|
||
const pointsConfig = await getPointsSystemConfig();
|
||
const pointsCharge = await prepareFeaturePointsCharge({
|
||
userId: session.user.id,
|
||
unitCost: getImageToolCreditCost(pointsConfig, "repaint"),
|
||
reason: "image_repaint",
|
||
modelId: "canvas-layout-replace",
|
||
note: "排版替换消耗积分",
|
||
pointsConfig,
|
||
});
|
||
if (!pointsCharge.ok) {
|
||
return NextResponse.json(pointsCharge.payload, {
|
||
status: pointsCharge.status,
|
||
});
|
||
}
|
||
|
||
const taskRequest: CanvasLayoutReplaceTaskRequest = {
|
||
taskKind: "canvas-layout-replace",
|
||
sourceImageUrl,
|
||
referenceImageUrl,
|
||
supplementalPrompt:
|
||
typeof body?.supplementalPrompt === "string"
|
||
? body.supplementalPrompt
|
||
: undefined,
|
||
model: typeof body?.model === "string" ? body.model : undefined,
|
||
imageSize:
|
||
typeof body?.imageSize === "string" && body.imageSize.trim()
|
||
? body.imageSize.trim()
|
||
: "2K",
|
||
sourceNodeId:
|
||
typeof body?.sourceNodeId === "string" ? body.sourceNodeId : undefined,
|
||
agentProjectId: normalizeUuid(body?.agentProjectId),
|
||
referenceLabel:
|
||
typeof body?.referenceLabel === "string" ? body.referenceLabel : undefined,
|
||
debugMeta:
|
||
body?.debugMeta &&
|
||
typeof body.debugMeta === "object" &&
|
||
!Array.isArray(body.debugMeta)
|
||
? body.debugMeta
|
||
: undefined,
|
||
};
|
||
|
||
const task = await createCanvasImageTask({
|
||
userId: session.user.id,
|
||
request: taskRequest as unknown as Record<string, unknown>,
|
||
});
|
||
await enqueueCanvasImageTask(task.id);
|
||
|
||
if (pointsCharge.chargeable) {
|
||
try {
|
||
await pointsCharge.consume({
|
||
note: `排版替换消耗积分(${task.id})`,
|
||
});
|
||
} catch (pointsError) {
|
||
console.error("[Points] Failed to consume canvas layout replace points:", pointsError);
|
||
}
|
||
}
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
taskId: task.id,
|
||
status: task.status,
|
||
});
|
||
} catch (error) {
|
||
console.error("[Canvas Layout Replace] Error:", error);
|
||
return NextResponse.json(
|
||
{
|
||
success: false,
|
||
error: error instanceof Error ? error.message : "排版替换提交失败",
|
||
},
|
||
{ status: 500 },
|
||
);
|
||
}
|
||
}
|