| |
| |
| |
| |
| |
| |
| |
|
|
| import { NextRequest } from 'next/server'; |
| import { callLLM } from '@/lib/ai/llm'; |
| import { |
| applyOutlineFallbacks, |
| generateSceneContent, |
| buildVisionUserContent, |
| } from '@/lib/generation/generation-pipeline'; |
| import type { AgentInfo } from '@/lib/generation/generation-pipeline'; |
| import type { SceneOutline, PdfImage, ImageMapping } from '@/lib/types/generation'; |
| import { createLogger } from '@/lib/logger'; |
| import { apiError, apiSuccess } from '@/lib/server/api-response'; |
| import { resolveModelFromRequest } from '@/lib/server/resolve-model'; |
|
|
| const log = createLogger('Scene Content API'); |
|
|
| export const maxDuration = 300; |
|
|
| export async function POST(req: NextRequest) { |
| let outlineTitle: string | undefined; |
| let resolvedModelString: string | undefined; |
| try { |
| const body = await req.json(); |
| const { |
| outline: rawOutline, |
| allOutlines, |
| pdfImages, |
| imageMapping, |
| stageInfo: _stageInfo, |
| stageId, |
| agents, |
| languageDirective, |
| } = body as { |
| outline: SceneOutline; |
| allOutlines: SceneOutline[]; |
| pdfImages?: PdfImage[]; |
| imageMapping?: ImageMapping; |
| stageInfo: { |
| name: string; |
| description?: string; |
| style?: string; |
| }; |
| stageId: string; |
| agents?: AgentInfo[]; |
| languageDirective?: string; |
| }; |
|
|
| |
| if (!rawOutline) { |
| return apiError('MISSING_REQUIRED_FIELD', 400, 'outline is required'); |
| } |
| if (!allOutlines || allOutlines.length === 0) { |
| return apiError( |
| 'MISSING_REQUIRED_FIELD', |
| 400, |
| 'allOutlines is required and must not be empty', |
| ); |
| } |
| if (!stageId) { |
| return apiError('MISSING_REQUIRED_FIELD', 400, 'stageId is required'); |
| } |
|
|
| const outline: SceneOutline = { ...rawOutline }; |
|
|
| |
| const { |
| model: languageModel, |
| modelInfo, |
| modelString, |
| thinkingConfig, |
| } = await resolveModelFromRequest(req, body); |
| outlineTitle = rawOutline?.title; |
| resolvedModelString = modelString; |
|
|
| |
| const hasVision = !!modelInfo?.capabilities?.vision; |
|
|
| |
| const aiCall = async ( |
| systemPrompt: string, |
| userPrompt: string, |
| images?: Array<{ id: string; src: string }>, |
| ): Promise<string> => { |
| if (images?.length && hasVision) { |
| const result = await callLLM( |
| { |
| model: languageModel, |
| system: systemPrompt, |
| messages: [ |
| { |
| role: 'user' as const, |
| content: buildVisionUserContent(userPrompt, images), |
| }, |
| ], |
| maxOutputTokens: modelInfo?.outputWindow, |
| }, |
| 'scene-content', |
| undefined, |
| thinkingConfig, |
| ); |
| return result.text; |
| } |
| const result = await callLLM( |
| { |
| model: languageModel, |
| system: systemPrompt, |
| prompt: userPrompt, |
| maxOutputTokens: modelInfo?.outputWindow, |
| }, |
| 'scene-content', |
| undefined, |
| thinkingConfig, |
| ); |
| return result.text; |
| }; |
|
|
| |
| const effectiveOutline = applyOutlineFallbacks(outline, !!languageModel); |
|
|
| |
| let assignedImages: PdfImage[] | undefined; |
| if ( |
| pdfImages && |
| pdfImages.length > 0 && |
| effectiveOutline.suggestedImageIds && |
| effectiveOutline.suggestedImageIds.length > 0 |
| ) { |
| const suggestedIds = new Set(effectiveOutline.suggestedImageIds); |
| assignedImages = pdfImages.filter((img) => suggestedIds.has(img.id)); |
| } |
|
|
| |
| |
| |
| const generatedMediaMapping: ImageMapping = {}; |
|
|
| |
| log.info( |
| `Generating content: "${effectiveOutline.title}" (${effectiveOutline.type}) [model=${modelString}]`, |
| ); |
|
|
| const content = await generateSceneContent(effectiveOutline, aiCall, { |
| assignedImages, |
| imageMapping, |
| languageModel: effectiveOutline.type === 'pbl' ? languageModel : undefined, |
| visionEnabled: hasVision, |
| generatedMediaMapping, |
| agents, |
| languageDirective, |
| thinkingConfig, |
| }); |
|
|
| if (!content) { |
| log.error(`Failed to generate content for: "${effectiveOutline.title}"`); |
|
|
| return apiError( |
| 'GENERATION_FAILED', |
| 500, |
| `Failed to generate content: ${effectiveOutline.title}`, |
| ); |
| } |
|
|
| log.info(`Content generated successfully: "${effectiveOutline.title}"`); |
|
|
| return apiSuccess({ content, effectiveOutline }); |
| } catch (error) { |
| log.error( |
| `Scene content generation failed [scene="${outlineTitle ?? 'unknown'}", model=${resolvedModelString ?? 'unknown'}]:`, |
| error, |
| ); |
| return apiError('INTERNAL_ERROR', 500, error instanceof Error ? error.message : String(error)); |
| } |
| } |
|
|