import { NextResponse, type NextRequest } from "next/server"; import { randomUUID } from "node:crypto"; import { requireApiUser } from "@/lib/api-auth"; import { appConfig } from "@/lib/config"; import { generateImagesWithOpenAI } from "@/lib/openai"; import { parseGenerateRequest } from "@/lib/request-schema"; import { assertCanGenerate, getQuotaSnapshotForUser } from "@/lib/quota"; import { getCurrentPeriodKey } from "@/lib/quota-core"; import { jsonError, normalizeError } from "@/lib/http"; import { recordCompletedGeneration, recordGenerationJob, } from "@/lib/db"; import { buildStoredImageUrl, persistGeneratedImages, removePersistedImageFiles, } from "@/lib/image-storage"; export const runtime = "nodejs"; export async function POST(request: NextRequest) { const user = requireApiUser(request); let prompt = ""; let payload: | ReturnType | null = null; try { if (!appConfig.openAiApiKey) { return jsonError("服务端尚未配置 OPENAI_API_KEY。", 500); } assertCanGenerate(user); const body = (await request.json()) as unknown; payload = parseGenerateRequest(body, appConfig.openAiImageModel); prompt = payload.prompt; const result = await generateImagesWithOpenAI(payload); const jobId = randomUUID(); const persistedImages = await persistGeneratedImages({ userId: user.id, jobId, images: result.images, }); try { recordCompletedGeneration({ jobId, userId: user.id, prompt, imageCount: result.images.length, durationMs: result.meta.durationMs, model: result.meta.model, size: result.meta.size, quality: result.meta.quality, outputFormat: result.meta.outputFormat, background: result.meta.background, moderation: result.meta.moderation, outputCompression: payload.output_compression ?? null, images: persistedImages.images, usageMeta: { model: result.meta.model, size: result.meta.size, quality: result.meta.quality, outputFormat: result.meta.outputFormat, background: result.meta.background, }, periodKey: getCurrentPeriodKey(), }); } catch (error) { await removePersistedImageFiles(persistedImages.files); throw error; } const images = result.images.map((image, index) => ({ ...image, id: persistedImages.images[index]?.id ?? image.id, fileName: persistedImages.images[index]?.fileName ?? image.fileName, url: persistedImages.images[index] ? buildStoredImageUrl(persistedImages.images[index].id) : undefined, })); const nextQuota = getQuotaSnapshotForUser(user); return NextResponse.json({ ok: true, images, meta: result.meta, quota: nextQuota, }); } catch (error) { if (prompt) { recordGenerationJob({ userId: user.id, prompt, status: "error", model: payload?.model ?? null, size: payload?.size ?? null, quality: payload?.quality ?? null, outputFormat: payload?.output_format ?? null, background: payload?.background ?? null, moderation: payload?.moderation ?? null, outputCompression: payload?.output_compression ?? null, errorMessage: error instanceof Error ? error.message : "unknown error", }); } const normalizedError = normalizeError(error, "图片生成失败。"); return jsonError(normalizedError.message, normalizedError.statusCode, { detail: normalizedError.detail, }); } }