92 lines
2.8 KiB
TypeScript
92 lines
2.8 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { randomBytes } from 'node:crypto';
|
|
import { recordEvent } from '@/lib/auditDb';
|
|
import { detectProvider, generateGptImages, generateMock } from '@/lib/providers';
|
|
import { saveSession, saveGeneratedImage, saveRefImage } from '@/lib/storage';
|
|
import type { GenerateRequest, GenerateResponse, GenSession } from '@/lib/types';
|
|
|
|
export const runtime = 'nodejs';
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export async function POST(req: Request) {
|
|
const body = (await req.json()) as GenerateRequest;
|
|
const { prompt, refImages = [], count = 8, style } = body;
|
|
|
|
if (!prompt || typeof prompt !== 'string') {
|
|
return NextResponse.json({ error: 'prompt required' }, { status: 400 });
|
|
}
|
|
|
|
const sessionId = `s_${Date.now().toString(36)}_${randomBytes(3).toString('hex')}`;
|
|
const finalPrompt = style ? `${prompt}, style: ${style}` : prompt;
|
|
const provider = detectProvider();
|
|
recordEvent({
|
|
action: 'idea.generate_started',
|
|
sessionId,
|
|
targetType: 'session',
|
|
targetId: sessionId,
|
|
status: 'started',
|
|
provider,
|
|
metadata: { count, refImages: refImages.length, style: style ?? null, promptLength: finalPrompt.length },
|
|
});
|
|
|
|
const savedRefUrls: string[] = [];
|
|
for (let i = 0; i < refImages.length; i++) {
|
|
const r = refImages[i];
|
|
if (r.startsWith('data:')) {
|
|
savedRefUrls.push(await saveRefImage(sessionId, i, r));
|
|
} else {
|
|
savedRefUrls.push(r);
|
|
}
|
|
}
|
|
|
|
let rawImages;
|
|
try {
|
|
rawImages = provider === 'gpt'
|
|
? await generateGptImages({ sessionId, prompt: finalPrompt, count, refImages: savedRefUrls })
|
|
: await generateMock({ sessionId, prompt: finalPrompt, count });
|
|
} catch (e) {
|
|
recordEvent({
|
|
action: 'idea.generate_failed',
|
|
sessionId,
|
|
targetType: 'session',
|
|
targetId: sessionId,
|
|
status: 'error',
|
|
provider,
|
|
message: String(e),
|
|
metadata: { count },
|
|
});
|
|
return NextResponse.json({ error: String(e) }, { status: 500 });
|
|
}
|
|
|
|
// 把 data URL 落盘成静态文件链接,便于后续选中复制
|
|
const images = await Promise.all(rawImages.map(async img => {
|
|
if (img.url.startsWith('data:')) {
|
|
const url = await saveGeneratedImage(sessionId, img.id, img.url);
|
|
return { ...img, url };
|
|
}
|
|
return img;
|
|
}));
|
|
|
|
const session: GenSession = {
|
|
id: sessionId,
|
|
createdAt: Date.now(),
|
|
prompt: finalPrompt,
|
|
refImages: savedRefUrls,
|
|
count,
|
|
images,
|
|
};
|
|
await saveSession(session);
|
|
recordEvent({
|
|
action: 'idea.generate_completed',
|
|
sessionId,
|
|
targetType: 'session',
|
|
targetId: sessionId,
|
|
status: 'ok',
|
|
provider,
|
|
metadata: { images: images.length },
|
|
});
|
|
|
|
const resp: GenerateResponse = { sessionId, images, provider };
|
|
return NextResponse.json(resp);
|
|
}
|