| |
| |
| |
| |
|
|
| import { generateTwoStageAIManimCode } from '../../../services/concept-designer' |
| import { ensureJobNotCancelled } from '../../../services/job-cancel' |
| import { createLogger } from '../../../utils/logger' |
| import type { CustomApiConfig, OutputMode, PromptOverrides, ReferenceImage } from '../../../types' |
|
|
| const logger = createLogger('AnalysisStep') |
|
|
| |
| |
| |
| export interface AnalysisResult { |
| analysisType: 'latex' | 'template' | 'ai' | 'fallback' |
| code: string | null |
| needsAI: boolean |
| } |
|
|
| |
| |
| |
| export interface GenerationResult { |
| code: string |
| usedAI: boolean |
| generationType: string |
| sceneDesign?: string |
| } |
|
|
| |
| |
| |
| export async function analyzeConcept( |
| jobId: string, |
| concept: string, |
| _quality: string, |
| outputMode: OutputMode |
| ): Promise<AnalysisResult> { |
| logger.info('Analyzing concept', { jobId, concept, outputMode }) |
|
|
| logger.info('Using AI for all concepts (template shortcuts disabled)', { jobId, outputMode }) |
| return { |
| analysisType: 'ai', |
| code: null, |
| needsAI: true |
| } |
| } |
|
|
| |
| |
| |
| export async function generateCode( |
| jobId: string, |
| concept: string, |
| _quality: string, |
| outputMode: OutputMode, |
| analyzeResult: AnalysisResult, |
| customApiConfig?: CustomApiConfig, |
| promptOverrides?: PromptOverrides, |
| referenceImages?: ReferenceImage[] |
| ): Promise<GenerationResult> { |
| const { analysisType, code, needsAI } = analyzeResult |
| logger.info('Generating code', { jobId, outputMode, needsAI, analysisType, hasImages: !!referenceImages?.length }) |
|
|
| if (needsAI) { |
| try { |
| logger.info('Using two-stage AI generation', { jobId, hasImages: !!referenceImages?.length }) |
| const result = await generateTwoStageAIManimCode( |
| concept, |
| outputMode, |
| customApiConfig, |
| promptOverrides, |
| referenceImages, |
| () => ensureJobNotCancelled(jobId) |
| ) |
| if (result.code && result.code.length > 0) { |
| logger.info('Two-stage AI code generation succeeded', { jobId, length: result.code.length, hasSceneDesign: !!result.sceneDesign }) |
| return { |
| code: result.code, |
| usedAI: true, |
| generationType: 'two-stage-ai', |
| sceneDesign: result.sceneDesign |
| } |
| } |
| throw new Error('Two-stage AI returned an empty code result') |
| } catch (error) { |
| logger.error('AI generation failed', { jobId, error: String(error) }) |
| throw error |
| } |
| } |
|
|
| if (code) { |
| logger.info('Using pre-generated code', { jobId, length: code.length }) |
| return { code, usedAI: false, generationType: analysisType } |
| } |
|
|
| throw new Error('No renderable code was generated') |
| } |
|
|
| |
| |
| |
| export async function analyzeAndGenerate( |
| jobId: string, |
| concept: string, |
| quality: string, |
| outputMode: OutputMode, |
| _timings: Record<string, number>, |
| customApiConfig?: CustomApiConfig, |
| promptOverrides?: PromptOverrides, |
| referenceImages?: ReferenceImage[] |
| ): Promise<GenerationResult> { |
| const analysisResult = await analyzeConcept(jobId, concept, quality, outputMode) |
| return generateCode( |
| jobId, |
| concept, |
| quality, |
| outputMode, |
| analysisResult, |
| customApiConfig, |
| promptOverrides, |
| referenceImages |
| ) |
| }
|
|
|