Your Name 6648f43a20
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: queue psd template exports
2026-04-10 23:04:38 +08:00

162 lines
4.6 KiB
TypeScript

import { type NextRequest, NextResponse } from "next/server";
import { auth } from "@/app/(auth)/auth";
import {
AGENT_PSD_RENDER_TASK_POLL_AFTER_MS,
agentPsdTemplateRenderRequestSchema,
} from "@/lib/agents/psd-render-task-contract";
import {
createAgentPsdRenderTask,
enqueueAgentPsdRenderTask,
getAgentPsdRenderTaskForUser,
} from "@/lib/agents/psd-render-task-queue";
import { describeRemoteAssetLocation } from "@/lib/agents/psd-remote-asset";
import {
type AgentPsdTemplateRenderResponse,
type AgentPsdTemplateRenderStatus,
} from "@/lib/agents/psd-template-config";
import {
getFriendlyRateLimitMessage,
getUserFacingRequestErrorMessage,
isRateLimitError,
} from "@/lib/request-error";
export const runtime = "nodejs";
function jsonError(status: number, error: string) {
const payload: AgentPsdTemplateRenderResponse = {
success: false,
error,
};
return NextResponse.json(payload, { status });
}
function normalizeTaskStatus(
status: string
): Extract<AgentPsdTemplateRenderStatus, "pending" | "running"> {
return status === "running" ? "running" : "pending";
}
export async function GET(request: NextRequest) {
const session = await auth().catch(() => null);
if (!session?.user?.id) {
return jsonError(401, "未登录");
}
const { searchParams } = new URL(request.url);
const taskId = String(searchParams.get("taskId") || "").trim();
if (!taskId) {
return jsonError(400, "缺少 taskId 参数");
}
try {
const task = await getAgentPsdRenderTaskForUser({
taskId,
userId: session.user.id,
});
if (!task) {
return jsonError(404, "导出任务不存在");
}
if (task.status === "succeeded") {
const result = task.result as AgentPsdTemplateRenderResponse["result"] | null;
if (!result?.url) {
return NextResponse.json(
{
success: false,
status: "failed",
taskId: task.id,
error: "导出任务已完成,但未返回结果图",
} satisfies AgentPsdTemplateRenderResponse
);
}
return NextResponse.json({
success: true,
status: "succeeded",
taskId: task.id,
result,
} satisfies AgentPsdTemplateRenderResponse);
}
if (task.status === "failed") {
return NextResponse.json({
success: false,
status: "failed",
taskId: task.id,
error: task.error || "PSD 模板渲染失败",
} satisfies AgentPsdTemplateRenderResponse);
}
return NextResponse.json({
success: true,
processing: true,
status: normalizeTaskStatus(task.status),
taskId: task.id,
pollAfterMs: AGENT_PSD_RENDER_TASK_POLL_AFTER_MS,
} satisfies AgentPsdTemplateRenderResponse);
} catch (error) {
console.error("[PSD Templates] render status failed:", { taskId, error });
return jsonError(500, "查询 PSD 导出任务失败");
}
}
export async function POST(request: NextRequest) {
const session = await auth().catch(() => null);
if (!session?.user?.id) {
return jsonError(401, "未登录");
}
let body: ReturnType<typeof agentPsdTemplateRenderRequestSchema.parse>;
try {
body = agentPsdTemplateRenderRequestSchema.parse(await request.json());
} catch {
return jsonError(400, "渲染参数不合法");
}
try {
const task = await createAgentPsdRenderTask({
userId: session.user.id,
request: body,
});
await enqueueAgentPsdRenderTask(task.id);
return NextResponse.json({
success: true,
processing: true,
status: normalizeTaskStatus(task.status),
taskId: task.id,
pollAfterMs: AGENT_PSD_RENDER_TASK_POLL_AFTER_MS,
} satisfies AgentPsdTemplateRenderResponse);
} catch (error) {
console.error("[PSD Templates] render failed:", {
templateId: body.templateId,
sourceImage: body.sourceImageUrl
? describeRemoteAssetLocation(body.sourceImageUrl)
: null,
assignedImageCount: Object.keys(body.imageAssignments || {}).length,
error,
});
if (isRateLimitError(error)) {
return NextResponse.json(
{
success: false,
error: getFriendlyRateLimitMessage("当前操作过于频繁,请稍后再试"),
} satisfies AgentPsdTemplateRenderResponse,
{
status: 429,
headers: {
"Retry-After": "10",
},
}
);
}
const message =
getUserFacingRequestErrorMessage(error, "PSD 模板渲染失败", {
rateLimitMessage: getFriendlyRateLimitMessage("当前操作过于频繁,请稍后再试"),
});
return jsonError(502, message);
}
}