muthuk1's picture
Convert OpenMAIC from Next.js to React (Vite)
f56a29b verified
/**
* Image Generation API
*
* Generates an image from a text prompt using the specified provider.
* Called by the client during media generation after slides are produced.
*
* POST /api/generate/image
*
* Headers:
* x-image-provider: ImageProviderId (default: 'seedream')
* x-api-key: string (optional, server fallback)
* x-base-url: string (optional, server fallback)
*
* Body: { prompt, negativePrompt?, width?, height?, aspectRatio?, style? }
* Response: { success: boolean, result?: ImageGenerationResult, error?: string }
*/
import { NextRequest } from 'next/server';
import { generateImage, aspectRatioToDimensions } from '@/lib/media/image-providers';
import { resolveImageApiKey, resolveImageBaseUrl } from '@/lib/server/provider-config';
import type { ImageProviderId, ImageGenerationOptions } from '@/lib/media/types';
import { createLogger } from '@/lib/logger';
import { apiError, apiSuccess } from '@/lib/server/api-response';
import { validateUrlForSSRF } from '@/lib/server/ssrf-guard';
const log = createLogger('ImageGeneration API');
export const maxDuration = 60;
export async function POST(request: NextRequest) {
try {
const body = (await request.json()) as ImageGenerationOptions;
if (!body.prompt) {
return apiError('MISSING_REQUIRED_FIELD', 400, 'Missing prompt');
}
const providerId = (request.headers.get('x-image-provider') || 'seedream') as ImageProviderId;
const clientApiKey = request.headers.get('x-api-key') || undefined;
const clientBaseUrl = request.headers.get('x-base-url') || undefined;
const clientModel = request.headers.get('x-image-model') || undefined;
if (clientBaseUrl && process.env.NODE_ENV === 'production') {
const ssrfError = await validateUrlForSSRF(clientBaseUrl);
if (ssrfError) {
return apiError('INVALID_URL', 403, ssrfError);
}
}
const apiKey = clientBaseUrl
? clientApiKey || ''
: resolveImageApiKey(providerId, clientApiKey);
if (!apiKey) {
return apiError(
'MISSING_API_KEY',
401,
`No API key configured for image provider: ${providerId}`,
);
}
const baseUrl = clientBaseUrl ? clientBaseUrl : resolveImageBaseUrl(providerId, clientBaseUrl);
// Resolve dimensions from aspect ratio if not explicitly set
if (!body.width && !body.height && body.aspectRatio) {
const dims = aspectRatioToDimensions(body.aspectRatio);
body.width = dims.width;
body.height = dims.height;
}
log.info(
`Generating image: provider=${providerId}, model=${clientModel || 'default'}, ` +
`prompt="${body.prompt.slice(0, 80)}...", size=${body.width ?? 'auto'}x${body.height ?? 'auto'}`,
);
const result = await generateImage({ providerId, apiKey, baseUrl, model: clientModel }, body);
return apiSuccess({ result });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
// Detect content safety filter rejections (e.g. Seedream OutputImageSensitiveContentDetected)
if (message.includes('SensitiveContent') || message.includes('sensitive information')) {
log.warn(`Image blocked by content safety filter: ${message}`);
return apiError('CONTENT_SENSITIVE', 400, message);
}
log.error(
`Image generation failed [provider=${request.headers.get('x-image-provider') ?? 'seedream'}, model=${request.headers.get('x-image-model') ?? 'default'}]:`,
error,
);
return apiError('INTERNAL_ERROR', 500, message);
}
}