ManimCat / src /queues /processors /steps /analysis-step.ts
Bin29's picture
Sync from main: 68df783 feat: support multimodal studio reference images
d47b053
/**
* 概念分析步骤
* 分析用户输入,决定生成策略
*/
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
)
}