185 lines
5.6 KiB
TypeScript
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 },
|
|
);
|
|
}
|
|
}
|