agent-animate
Version:
AI-powered cinematic animations from workflow transcripts - Jony Ive precision meets Hans Zimmer timing
472 lines (393 loc) • 16.7 kB
JavaScript
/**
* 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;
}
}