feat(project): init
This commit is contained in:
115
app/api/generate/route.ts
Normal file
115
app/api/generate/route.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
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<typeof parseGenerateRequest>
|
||||
| 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user