Your Name b4698be089
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: add external image generation api
2026-05-18 15:56:08 +08:00

185 lines
5.6 KiB
TypeScript

import { eq } from "drizzle-orm";
import { NextResponse } from "next/server";
import { z } from "zod";
import { db } from "@/lib/db";
import { externalImageApiRequestLog } from "@/lib/db/schema";
import { saveGeneratedImages } from "@/lib/ai/runtime/generated-image-persistence";
import {
authenticateExternalImageApiRequest,
buildExternalImageApiKeyFingerprint,
ensureHostedImageMarkdown,
extractGeneratedImagesFromMarkdown,
generateImagesWithModel,
getRequesterIp,
resolveExternalImageApiModel,
resolveExternalImageApiOwnerUserId,
serializeExternalImageApiModel,
} from "@/lib/external-image-api/service";
export const runtime = "nodejs";
export const maxDuration = 120;
const requestSchema = z.object({
model: z.string().trim().min(1).optional(),
prompt: z.string().trim().min(1, "prompt 不能为空"),
negativePrompt: z.string().trim().optional(),
imageCount: z.coerce.number().int().min(1).max(8).optional(),
aspectRatio: z.string().trim().max(32).optional(),
imageSize: z.string().trim().max(32).optional(),
metadata: z.record(z.string(), z.any()).optional(),
});
export async function POST(request: Request) {
const authResult = await authenticateExternalImageApiRequest(request);
if (!authResult.accessKey) {
return NextResponse.json(
{
success: false,
error: authResult.error,
},
{ status: authResult.status },
);
}
const startedAt = Date.now();
let logId: string | null = null;
try {
const json = await request.json();
const parsed = requestSchema.safeParse(json);
if (!parsed.success) {
return NextResponse.json(
{
success: false,
error: parsed.error.errors[0]?.message || "参数错误",
},
{ status: 400 },
);
}
const payload = parsed.data;
const selectedModel = await resolveExternalImageApiModel(payload.model);
const ownerUserId = await resolveExternalImageApiOwnerUserId(
authResult.config.defaultOwnerEmail,
);
const requesterIp = getRequesterIp(request);
const requesterUserAgent = request.headers.get("user-agent") || null;
const [createdLog] = await db
.insert(externalImageApiRequestLog)
.values({
apiKeyId: authResult.accessKey.id,
apiKeyName: authResult.accessKey.name,
ownerUserId,
modelDbId: selectedModel.model.id,
modelRuntimeId: selectedModel.model.modelId,
modelDisplayName: selectedModel.model.displayName,
prompt: payload.prompt,
negativePrompt: payload.negativePrompt || null,
imageCount: payload.imageCount || 1,
aspectRatio: payload.aspectRatio || null,
imageSize: payload.imageSize || null,
status: "pending",
requesterIp,
requesterUserAgent,
requestPayload: {
...payload,
apiKeyFingerprint: buildExternalImageApiKeyFingerprint(
authResult.accessKey,
),
},
})
.returning({ id: externalImageApiRequestLog.id });
logId = createdLog?.id || null;
const rawMarkdown = await generateImagesWithModel({
modelDbId: selectedModel.model.id,
prompt: payload.prompt,
negativePrompt: payload.negativePrompt || null,
imageCount: payload.imageCount || 1,
aspectRatio: payload.aspectRatio || null,
imageSize: payload.imageSize || null,
});
const hostedMarkdown = await ensureHostedImageMarkdown(
rawMarkdown,
payload.imageSize || "2K",
);
const images = extractGeneratedImagesFromMarkdown(hostedMarkdown);
if (!images.length) {
throw new Error("模型返回结果里未解析到图片");
}
const persistResult = await saveGeneratedImages(
hostedMarkdown,
ownerUserId,
null,
null,
payload.prompt,
selectedModel.model.id,
{
sourceType: "external_api_generated",
userPrompt: payload.prompt,
optimizedPrompt: payload.prompt,
requestedImageCount: payload.imageCount || images.length,
aspectRatio: payload.aspectRatio,
imageSize: payload.imageSize,
skipBilling: true,
},
);
if (logId) {
await db
.update(externalImageApiRequestLog)
.set({
status: "succeeded",
responsePayload: {
model: serializeExternalImageApiModel(selectedModel),
markdown: hostedMarkdown,
images,
},
generatedImageIds: persistResult.savedImageIds,
generatedAssetIds: persistResult.savedAssetIds,
generatedImageUrls: persistResult.savedOriginalUrls,
durationMs: Date.now() - startedAt,
completedAt: new Date(),
})
.where(eq(externalImageApiRequestLog.id, logId));
}
return NextResponse.json({
success: true,
data: {
requestId: logId,
model: serializeExternalImageApiModel(selectedModel),
images,
generatedImageIds: persistResult.savedImageIds,
generatedAssetIds: persistResult.savedAssetIds,
},
});
} catch (error) {
console.error("[External Image API] Generate failed:", error);
if (logId) {
await db
.update(externalImageApiRequestLog)
.set({
status: "failed",
errorMessage: error instanceof Error ? error.message : "图片生成失败",
durationMs: Date.now() - startedAt,
completedAt: new Date(),
})
.where(eq(externalImageApiRequestLog.id, logId));
}
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "图片生成失败",
},
{ status: 500 },
);
}
}