UNPKG

agent-animate

Version:

AI-powered cinematic animations from workflow transcripts - Jony Ive precision meets Hans Zimmer timing

472 lines (393 loc) 16.7 kB
/** * SceneValidationAgent - AI agent that validates and optimizes scene creation * Works in collaboration with SceneCreationAgent for multi-agent reasoning */ class SceneValidationAgent { constructor() { this.validationCriteria = { visualClarity: 0.8, narrativeFlow: 0.85, timingBalance: 0.75, componentSpacing: 0.9, sceneTransitions: 0.8 }; this.reasoningLog = []; this.optimizationHistory = []; } /** * Main validation entry point - analyzes scenes created by SceneCreationAgent */ async validateScenes(scenes, originalTranscript, creationReasoning) { this.log("🔍 Starting scene validation process"); const validationResults = { overallScore: 0, detailedAnalysis: {}, recommendations: [], optimizedScenes: scenes, reasoning: [] }; // Step 1: Analyze narrative coherence const narrativeAnalysis = await this.validateNarrativeFlow(scenes, originalTranscript); validationResults.detailedAnalysis.narrative = narrativeAnalysis; // Step 2: Validate visual layout const layoutAnalysis = await this.validateLayoutOptimality(scenes); validationResults.detailedAnalysis.layout = layoutAnalysis; // Step 3: Check timing and pacing const timingAnalysis = await this.validateTimingBalance(scenes); validationResults.detailedAnalysis.timing = timingAnalysis; // Step 4: Validate component relationships const relationshipAnalysis = await this.validateComponentRelationships(scenes); validationResults.detailedAnalysis.relationships = relationshipAnalysis; // Step 5: Cross-reference with creation reasoning const reasoningValidation = await this.validateCreationReasoning(scenes, creationReasoning); validationResults.detailedAnalysis.reasoningValidation = reasoningValidation; // Step 6: Generate recommendations and optimizations const optimizations = await this.generateOptimizations(validationResults.detailedAnalysis); validationResults.recommendations = optimizations.recommendations; validationResults.optimizedScenes = await this.applyOptimizations(scenes, optimizations); // Calculate overall score validationResults.overallScore = this.calculateOverallScore(validationResults.detailedAnalysis); validationResults.reasoning = this.reasoningLog.slice(); this.log(`✅ Validation complete. Score: ${validationResults.overallScore}/1.0`); return validationResults; } async validateNarrativeFlow(scenes, originalTranscript) { this.log("📖 Validating narrative flow and coherence"); const analysis = { score: 0, issues: [], strengths: [], reasoning: [] }; // Check if scenes tell a coherent story const storyArc = this.analyzeStoryArc(scenes); if (storyArc.hasIntroduction && storyArc.hasBuildup && storyArc.hasClimax) { analysis.strengths.push("Complete story arc with intro, buildup, and climax"); analysis.score += 0.3; } else { analysis.issues.push("Missing elements in story arc"); } // Validate scene progression logic const progression = this.validateSceneProgression(scenes); if (progression.isLogical) { analysis.strengths.push("Logical scene progression"); analysis.score += 0.4; } else { analysis.issues.push("Scene progression could be improved"); analysis.reasoning.push(`Issue: ${progression.issues.join(', ')}`); } // Check alignment with original transcript const alignment = this.checkTranscriptAlignment(scenes, originalTranscript); analysis.score += alignment.score * 0.3; if (alignment.score > 0.8) { analysis.strengths.push("Strong alignment with original transcript"); } else { analysis.issues.push("Some misalignment with transcript narrative"); } this.log(`Narrative flow score: ${analysis.score}/1.0`); return analysis; } async validateLayoutOptimality(scenes) { this.log("📐 Validating layout and visual organization"); const analysis = { score: 0, issues: [], strengths: [], spatialIssues: [] }; for (const scene of scenes) { const sceneAnalysis = this.analyzeSceneLayout(scene); // Check for component overlaps if (sceneAnalysis.hasOverlaps) { analysis.issues.push(`Scene "${scene.name}" has overlapping components`); analysis.spatialIssues.push({ scene: scene.name, type: 'overlap', components: sceneAnalysis.overlappingComponents }); } else { analysis.score += 0.2; } // Validate spacing consistency if (sceneAnalysis.spacingConsistent) { analysis.score += 0.1; } else { analysis.issues.push(`Inconsistent spacing in scene "${scene.name}"`); } // Check visual hierarchy if (sceneAnalysis.hasGoodHierarchy) { analysis.strengths.push(`Good visual hierarchy in "${scene.name}"`); analysis.score += 0.1; } } // Normalize score analysis.score = Math.min(analysis.score, 1.0); this.log(`Layout optimality score: ${analysis.score}/1.0`); return analysis; } async validateTimingBalance(scenes) { this.log("⏱️ Validating timing and pacing"); const analysis = { score: 0, issues: [], strengths: [], timingRecommendations: [] }; const totalDuration = scenes.reduce((sum, scene) => sum + scene.duration, 0); const averageDuration = totalDuration / scenes.length; // Check for balanced scene durations const durationVariance = this.calculateDurationVariance(scenes, averageDuration); if (durationVariance < 0.3) { analysis.strengths.push("Well-balanced scene durations"); analysis.score += 0.4; } else { analysis.issues.push("Unbalanced scene durations"); analysis.timingRecommendations.push("Consider redistributing scene durations"); } // Validate pacing for complexity for (const scene of scenes) { const complexityScore = this.assessSceneComplexity(scene); const optimalDuration = this.calculateOptimalDuration(complexityScore); if (Math.abs(scene.duration - optimalDuration) < 1.0) { analysis.score += 0.1; } else { analysis.timingRecommendations.push( `Scene "${scene.name}" should be ${optimalDuration}s (currently ${scene.duration}s)` ); } } // Check for minimum viable scene duration const tooShortScenes = scenes.filter(s => s.duration < 1.5); if (tooShortScenes.length === 0) { analysis.score += 0.3; } else { analysis.issues.push(`${tooShortScenes.length} scenes are too short for comprehension`); } analysis.score = Math.min(analysis.score, 1.0); this.log(`Timing balance score: ${analysis.score}/1.0`); return analysis; } async validateComponentRelationships(scenes) { this.log("🔗 Validating component relationships and connections"); const analysis = { score: 0, issues: [], strengths: [], relationshipGaps: [] }; // Analyze component introduction order const introOrder = this.analyzeComponentIntroduction(scenes); if (introOrder.isLogical) { analysis.strengths.push("Logical component introduction order"); analysis.score += 0.3; } else { analysis.issues.push("Component introduction order could be improved"); } // Check for orphaned components const orphanedComponents = this.findOrphanedComponents(scenes); if (orphanedComponents.length === 0) { analysis.score += 0.3; } else { analysis.issues.push(`${orphanedComponents.length} components appear disconnected`); analysis.relationshipGaps = orphanedComponents; } // Validate connection patterns const connectionAnalysis = this.analyzeConnectionPatterns(scenes); analysis.score += connectionAnalysis.score * 0.4; if (connectionAnalysis.hasGoodFlow) { analysis.strengths.push("Good data flow patterns"); } this.log(`Component relationships score: ${analysis.score}/1.0`); return analysis; } async validateCreationReasoning(scenes, creationReasoning) { this.log("🧠 Cross-validating with creation agent reasoning"); const analysis = { score: 0, reasoningAlignment: 0, implementationFidelity: 0, unexpectedDeviations: [] }; // Check if final scenes match creation agent's intentions for (const scene of scenes) { if (scene.reasoning) { const alignmentScore = this.checkReasoningAlignment(scene, scene.reasoning); analysis.reasoningAlignment += alignmentScore; } } analysis.reasoningAlignment /= scenes.length; analysis.score = analysis.reasoningAlignment; if (analysis.score > 0.8) { this.log("Strong alignment between reasoning and implementation"); } else { this.log("Some deviation from creation reasoning detected"); } return analysis; } async generateOptimizations(detailedAnalysis) { this.log("🚀 Generating optimization recommendations"); const optimizations = { recommendations: [], layoutAdjustments: [], timingAdjustments: [], narrativeImprovements: [] }; // Layout optimizations if (detailedAnalysis.layout.spatialIssues.length > 0) { optimizations.layoutAdjustments.push({ type: 'spacing_increase', reason: 'Resolve component overlaps', impact: 'high', scenes: detailedAnalysis.layout.spatialIssues.map(i => i.scene) }); } // Timing optimizations if (detailedAnalysis.timing.timingRecommendations.length > 0) { optimizations.timingAdjustments = detailedAnalysis.timing.timingRecommendations.map(rec => ({ type: 'duration_adjustment', recommendation: rec, impact: 'medium' })); } // Narrative optimizations if (detailedAnalysis.narrative.score < 0.7) { optimizations.narrativeImprovements.push({ type: 'scene_reordering', reason: 'Improve narrative flow', impact: 'high' }); } // Compile final recommendations optimizations.recommendations = [ ...optimizations.layoutAdjustments.map(a => `Layout: ${a.reason}`), ...optimizations.timingAdjustments.map(a => `Timing: ${a.recommendation}`), ...optimizations.narrativeImprovements.map(a => `Narrative: ${a.reason}`) ]; this.log(`Generated ${optimizations.recommendations.length} optimization recommendations`); return optimizations; } async applyOptimizations(scenes, optimizations) { this.log("🔧 Applying optimizations to scenes"); let optimizedScenes = JSON.parse(JSON.stringify(scenes)); // Deep copy // Apply layout optimizations for (const adjustment of optimizations.layoutAdjustments) { if (adjustment.type === 'spacing_increase') { optimizedScenes = this.increaseComponentSpacing(optimizedScenes, adjustment.scenes); } } // Apply timing adjustments for (const adjustment of optimizations.timingAdjustments) { if (adjustment.type === 'duration_adjustment') { optimizedScenes = this.adjustSceneDurations(optimizedScenes, adjustment); } } this.log("✅ Optimizations applied successfully"); return optimizedScenes; } // Helper methods for validation analysis analyzeStoryArc(scenes) { return { hasIntroduction: scenes.some(s => s.name.includes('intro')), hasBuildup: scenes.length > 2, hasClimax: scenes.some(s => s.dataFlowActive) }; } validateSceneProgression(scenes) { const progression = { isLogical: true, issues: [] }; // Check for logical component count progression for (let i = 1; i < scenes.length; i++) { const prevCount = scenes[i-1].components.length; const currCount = scenes[i].components.length; if (currCount < prevCount && !scenes[i].name.includes('focus')) { progression.isLogical = false; progression.issues.push(`Scene ${i} reduces components unexpectedly`); } } return progression; } checkTranscriptAlignment(scenes, transcript) { // Simplified alignment check const transcriptKeywords = this.extractKeywords(transcript); const sceneKeywords = scenes.flatMap(s => [s.title, s.subtitle].join(' ')); const alignment = this.calculateSemanticSimilarity(transcriptKeywords, sceneKeywords); return { score: alignment }; } analyzeSceneLayout(scene) { return { hasOverlaps: false, // Simplified - would use actual component positions spacingConsistent: true, hasGoodHierarchy: scene.highlightComponents.length > 0, overlappingComponents: [] }; } calculateOverallScore(analysis) { const weights = { narrative: 0.25, layout: 0.25, timing: 0.25, relationships: 0.25 }; return Object.entries(weights).reduce((total, [key, weight]) => { return total + (analysis[key]?.score || 0) * weight; }, 0); } log(message) { console.log(`🔍 ValidationAgent: ${message}`); this.reasoningLog.push({ timestamp: Date.now(), message: message }); } // Additional helper methods (simplified implementations) calculateDurationVariance(scenes, average) { const variance = scenes.reduce((sum, scene) => sum + Math.pow(scene.duration - average, 2), 0) / scenes.length; return Math.sqrt(variance) / average; } assessSceneComplexity(scene) { return scene.components.length * 0.1 + (scene.connections?.length || 0) * 0.05; } calculateOptimalDuration(complexity) { return Math.max(1.5, Math.min(5.0, complexity * 3)); } analyzeComponentIntroduction(scenes) { return { isLogical: true }; // Simplified } findOrphanedComponents(scenes) { return []; // Simplified } analyzeConnectionPatterns(scenes) { return { score: 0.8, hasGoodFlow: true }; // Simplified } checkReasoningAlignment(scene, reasoning) { return 0.85; // Simplified } extractKeywords(text) { return text.toLowerCase().split(/\W+/).filter(w => w.length > 3); } calculateSemanticSimilarity(keywords1, keywords2) { const intersection = keywords1.filter(k => keywords2.includes(k)); return intersection.length / Math.max(keywords1.length, 1); } increaseComponentSpacing(scenes, targetScenes) { return scenes.map(scene => { if (targetScenes.includes(scene.name)) { return { ...scene, layout: { ...scene.layout, spacing: Math.max(scene.layout?.spacing || 150, 200) } }; } return scene; }); } adjustSceneDurations(scenes, adjustment) { // Simplified duration adjustment return scenes; } }