phase3-widget-orchestrator.ts•20.2 kB
/**
* Phase 3 Widget Orchestrator
* Task 3.3: Educational Flow Optimization Integration
*
* Orchestrates all Phase 3 components to create optimized Composer widgets:
* - Content-to-widget mapping (Task 3.1)
* - Context-aware image selection (Task 3.2)
* - Educational flow optimization (Task 3.3)
* - Integration with Phase 2 content generation
* - Complete Composer JSON structure generation
*/
import { ImageSelectionService } from '../services/image-selection-service';
import { EducationalImageMapper } from '../widget-mappers/educational-image-mapper';
import { EducationalFlowOptimizer } from '../services/educational-flow-optimizer';
interface ComposerWidget {
id: string;
type: string;
content_title?: string;
padding_top?: number;
padding_bottom?: number;
background_color?: string;
primary_color?: string;
secondary_color?: string;
[key: string]: any;
}
interface ComposerComposition {
composition: {
id: string;
title: string;
description: string;
author: string;
created: string;
version: string;
metadata: {
disciplina: string;
serie: string;
duracao_estimada: string;
tags: string[];
};
elements: ComposerWidget[];
};
}
interface Phase2Output {
baseContent: any;
assessmentEngine: any;
subjectAdapters: any;
}
interface Phase3Configuration {
subject: string;
grade_level: string;
topic: string;
learning_objectives: string[];
author: string;
target_duration: number;
}
export class Phase3WidgetOrchestrator {
private imageService: ImageSelectionService;
private imageMapper: EducationalImageMapper;
private flowOptimizer: EducationalFlowOptimizer;
constructor() {
this.imageService = new ImageSelectionService();
this.imageMapper = new EducationalImageMapper();
this.flowOptimizer = new EducationalFlowOptimizer();
}
/**
* Orchestrate complete Phase 3 transformation: Content → Optimized Composer Widgets
*/
public async orchestratePhase3Transformation(
phase2Output: Phase2Output,
configuration: Phase3Configuration
): Promise<ComposerComposition> {
console.log('🎼 Starting Phase 3 Widget Orchestration...');
// Step 1: Optimize educational flow
console.log('📊 Step 1: Optimizing educational flow...');
const flowOptimization = this.flowOptimizer.integrateWithContentGeneration(
phase2Output.baseContent,
phase2Output.assessmentEngine,
{}, // Image output will be generated per widget
{
subject: configuration.subject,
grade_level: configuration.grade_level,
topic: configuration.topic,
learning_objectives: configuration.learning_objectives,
content_complexity: this.determineContentComplexity(configuration.grade_level),
student_attention_span: this.getAttentionSpan(configuration.grade_level)
}
);
console.log(`✅ Flow optimized: ${flowOptimization.optimized_flow.segments.length} segments, ${flowOptimization.optimized_flow.total_duration} minutes`);
// Step 2: Transform each segment into Composer widgets
console.log('🔄 Step 2: Transforming segments to Composer widgets...');
const composerWidgets: ComposerWidget[] = [];
for (const segment of flowOptimization.optimized_flow.segments) {
const widget = await this.transformSegmentToWidget(segment, configuration);
composerWidgets.push(widget);
}
console.log(`✅ Transformed ${composerWidgets.length} widgets`);
// Step 3: Apply styling and branding
console.log('🎨 Step 3: Applying educational styling...');
const styledWidgets = this.applyEducationalStyling(composerWidgets, configuration);
// Step 4: Generate final Composer composition
console.log('📦 Step 4: Generating final composition...');
const composition = this.generateComposerComposition(styledWidgets, configuration, flowOptimization);
console.log('🎉 Phase 3 Widget Orchestration Complete!');
return composition;
}
/**
* Transform educational segment into Composer widget
*/
private async transformSegmentToWidget(
segment: any,
configuration: Phase3Configuration
): Promise<ComposerWidget> {
// Get context-aware images for this segment
const imageMapping = this.imageMapper.mapContentToImages({
content: this.extractContentText(segment.content),
subject: configuration.subject,
grade_level: configuration.grade_level,
widget_type: segment.widget_type,
learning_intent: this.mapSegmentTypeToLearningIntent(segment.type)
});
// Base widget structure
const baseWidget: ComposerWidget = {
id: segment.id,
type: segment.widget_type,
content_title: this.generateContentTitle(segment, configuration),
padding_top: this.calculatePadding(segment, 'top'),
padding_bottom: this.calculatePadding(segment, 'bottom'),
background_color: this.getSubjectColor(configuration.subject),
primary_color: this.getSubjectColor(configuration.subject),
secondary_color: this.getSubjectSecondaryColor(configuration.subject)
};
// Widget-specific content transformation
switch (segment.widget_type) {
case 'head-1':
return this.transformToHeaderWidget(baseWidget, segment, configuration, imageMapping);
case 'text-1':
return this.transformToTextWidget(baseWidget, segment, configuration, imageMapping);
case 'quiz-1':
return this.transformToQuizWidget(baseWidget, segment, configuration, imageMapping);
case 'flashcards-1':
return this.transformToFlashcardsWidget(baseWidget, segment, configuration, imageMapping);
case 'list-1':
return this.transformToListWidget(baseWidget, segment, configuration, imageMapping);
case 'image-1':
return this.transformToImageWidget(baseWidget, segment, configuration, imageMapping);
case 'hotspots-1':
return this.transformToHotspotsWidget(baseWidget, segment, configuration, imageMapping);
case 'gallery-1':
return this.transformToGalleryWidget(baseWidget, segment, configuration, imageMapping);
default:
return this.transformToGenericWidget(baseWidget, segment, configuration, imageMapping);
}
}
/**
* Transform to header widget (head-1)
*/
private transformToHeaderWidget(
baseWidget: ComposerWidget,
segment: any,
configuration: Phase3Configuration,
imageMapping: any
): ComposerWidget {
const backgroundImage = imageMapping.image_properties.background_image;
const avatarImage = imageMapping.image_properties.avatar;
return {
...baseWidget,
category: `🎓 ${configuration.subject.toUpperCase()} - ${this.getGradeDisplayName(configuration.grade_level)}`,
show_category: true,
show_author_name: true,
author_name: configuration.author,
author_office: `Professor de ${configuration.subject}`,
avatar: avatarImage?.url || 'https://ui-avatars.com/api/?name=Professor&background=2563eb',
avatar_border_color: this.getSubjectSecondaryColor(configuration.subject),
background_image: backgroundImage?.url,
show_divider: true,
progress_tracking: true,
font_family: 'Lato'
};
}
/**
* Transform to text widget (text-1)
*/
private transformToTextWidget(
baseWidget: ComposerWidget,
segment: any,
configuration: Phase3Configuration,
imageMapping: any
): ComposerWidget {
const inlineImage = imageMapping.image_properties.inline_image;
return {
...baseWidget,
content: this.formatTextContent(segment.content),
text_size: this.getTextSizeForGrade(configuration.grade_level),
line_height: 1.6,
text_align: 'left',
font_family: 'Open Sans',
include_image: !!inlineImage,
image_url: inlineImage?.url,
image_caption: inlineImage?.caption,
image_alt: inlineImage?.alt_text
};
}
/**
* Transform to quiz widget (quiz-1)
*/
private transformToQuizWidget(
baseWidget: ComposerWidget,
segment: any,
configuration: Phase3Configuration,
imageMapping: any
): ComposerWidget {
const questionImages = imageMapping.image_properties.question_images;
const feedbackImages = imageMapping.image_properties;
return {
...baseWidget,
remake: 'enable',
max_attempts: this.getMaxAttemptsForGrade(configuration.grade_level),
utilization: { enabled: true, percentage: 70 },
feedback: { type: 'custom' },
questions: this.formatQuizQuestions(segment.content, questionImages),
feedback_correct: {
type: 'custom',
title: 'Parabéns!',
message: 'Resposta correta! Continue assim.',
image: feedbackImages.feedback_correct?.url
},
feedback_incorrect: {
type: 'custom',
title: 'Não desanime!',
message: 'Revise o conteúdo e tente novamente.',
image: feedbackImages.feedback_incorrect?.url
}
};
}
/**
* Transform to flashcards widget (flashcards-1)
*/
private transformToFlashcardsWidget(
baseWidget: ComposerWidget,
segment: any,
configuration: Phase3Configuration,
imageMapping: any
): ComposerWidget {
const cardImages = imageMapping.image_properties.card_images;
return {
...baseWidget,
card_height: 300,
card_width: 400,
border_color: this.getSubjectColor(configuration.subject),
items: this.formatFlashcardItems(segment.content, cardImages)
};
}
/**
* Transform to list widget (list-1)
*/
private transformToListWidget(
baseWidget: ComposerWidget,
segment: any,
configuration: Phase3Configuration,
imageMapping: any
): ComposerWidget {
return {
...baseWidget,
list_type: 'numbered',
items: this.formatListItems(segment.content),
text_size: this.getTextSizeForGrade(configuration.grade_level),
font_family: 'Open Sans'
};
}
/**
* Transform to image widget (image-1)
*/
private transformToImageWidget(
baseWidget: ComposerWidget,
segment: any,
configuration: Phase3Configuration,
imageMapping: any
): ComposerWidget {
const primaryImage = imageMapping.image_properties.primary_image;
return {
...baseWidget,
imagem_fundo: primaryImage?.url,
legenda_texto: primaryImage?.caption,
zoom: 'Habilitado',
width: 760,
full_device_width: false
};
}
/**
* Transform to hotspots widget (hotspots-1)
*/
private transformToHotspotsWidget(
baseWidget: ComposerWidget,
segment: any,
configuration: Phase3Configuration,
imageMapping: any
): ComposerWidget {
const backgroundImage = imageMapping.image_properties.background_image;
const hotspotMarkers = imageMapping.image_properties.hotspot_markers;
return {
...baseWidget,
imagem_fundo: backgroundImage?.url,
marcadores: this.formatHotspotMarkers(hotspotMarkers),
interaction_type: 'click'
};
}
/**
* Transform to gallery widget (gallery-1)
*/
private transformToGalleryWidget(
baseWidget: ComposerWidget,
segment: any,
configuration: Phase3Configuration,
imageMapping: any
): ComposerWidget {
const slides = imageMapping.image_properties.slides;
return {
...baseWidget,
slides: this.formatGallerySlides(slides),
autoplay: false,
show_navigation: true,
show_thumbnails: true
};
}
/**
* Transform to generic widget
*/
private transformToGenericWidget(
baseWidget: ComposerWidget,
segment: any,
configuration: Phase3Configuration,
imageMapping: any
): ComposerWidget {
return {
...baseWidget,
content: this.formatGenericContent(segment.content),
educational_metadata: imageMapping.educational_metadata
};
}
/**
* Apply educational styling and branding
*/
private applyEducationalStyling(
widgets: ComposerWidget[],
configuration: Phase3Configuration
): ComposerWidget[] {
const subjectTheme = this.getSubjectTheme(configuration.subject);
return widgets.map(widget => ({
...widget,
font_family: widget.font_family || subjectTheme.font_family,
primary_color: widget.primary_color || subjectTheme.primary_color,
secondary_color: widget.secondary_color || subjectTheme.secondary_color,
background_color: widget.background_color || subjectTheme.background_color
}));
}
/**
* Generate final Composer composition
*/
private generateComposerComposition(
widgets: ComposerWidget[],
configuration: Phase3Configuration,
flowOptimization: any
): ComposerComposition {
const compositionId = `${configuration.topic.toLowerCase().replace(/\s+/g, '-')}-${configuration.grade_level}-${Date.now()}`;
return {
composition: {
id: compositionId,
title: `${configuration.topic} - ${this.getGradeDisplayName(configuration.grade_level)}`,
description: `Composição educacional interativa sobre ${configuration.topic} otimizada para aprendizagem efetiva`,
author: configuration.author,
created: new Date().toISOString().split('T')[0],
version: '3.0.0',
metadata: {
disciplina: this.getSubjectDisplayName(configuration.subject),
serie: this.getGradeDisplayName(configuration.grade_level),
duracao_estimada: `${flowOptimization.optimized_flow.total_duration} minutos`,
tags: [
configuration.topic.toLowerCase(),
configuration.subject,
configuration.grade_level,
'educação',
'interativo'
]
},
elements: widgets
}
};
}
/**
* Helper methods for content formatting and styling
*/
private extractContentText(content: any): string {
if (typeof content === 'string') return content;
if (content?.content) return content.content;
if (content?.text) return content.text;
if (content?.title) return content.title;
return JSON.stringify(content);
}
private mapSegmentTypeToLearningIntent(segmentType: string): string {
const mapping = {
'introduction': 'introduction',
'concept': 'comprehension',
'example': 'demonstration',
'practice': 'practice',
'assessment': 'assessment',
'summary': 'memorization'
};
return mapping[segmentType] || 'comprehension';
}
private generateContentTitle(segment: any, configuration: Phase3Configuration): string {
const titles = {
'introduction': `Introdução: ${configuration.topic}`,
'concept': 'Conceitos Fundamentais',
'example': 'Exemplos Práticos',
'practice': 'Atividade Prática',
'assessment': 'Verificação de Aprendizagem',
'summary': 'Resumo e Conclusões'
};
return titles[segment.type] || 'Conteúdo Educacional';
}
private calculatePadding(segment: any, position: 'top' | 'bottom'): number {
const basePadding = { top: 20, bottom: 30 };
const visualBreakPadding = { top: 40, bottom: 40 };
return segment.visual_break ? visualBreakPadding[position] : basePadding[position];
}
private getSubjectColor(subject: string): string {
const colors = {
'ciências': '#228B22',
'física': '#4169E1',
'química': '#FF6347',
'história': '#8B4513',
'matemática': '#9370DB',
'português': '#DC143C'
};
return colors[subject] || '#2563eb';
}
private getSubjectSecondaryColor(subject: string): string {
const colors = {
'ciências': '#32CD32',
'física': '#87CEEB',
'química': '#FFA07A',
'história': '#DEB887',
'matemática': '#DDA0DD',
'português': '#F08080'
};
return colors[subject] || '#60a5fa';
}
private getSubjectTheme(subject: string) {
return {
font_family: 'Open Sans',
primary_color: this.getSubjectColor(subject),
secondary_color: this.getSubjectSecondaryColor(subject),
background_color: '#ffffff'
};
}
private getGradeDisplayName(gradeLevel: string): string {
const mapping = {
'fundamental': 'Ensino Fundamental',
'médio': 'Ensino Médio',
'superior': 'Ensino Superior'
};
return mapping[gradeLevel] || gradeLevel;
}
private getSubjectDisplayName(subject: string): string {
const mapping = {
'ciências': 'Ciências Naturais',
'física': 'Física',
'química': 'Química',
'história': 'História',
'matemática': 'Matemática',
'português': 'Língua Portuguesa'
};
return mapping[subject] || subject;
}
private determineContentComplexity(gradeLevel: string): 'basic' | 'intermediate' | 'advanced' {
const mapping = {
'fundamental': 'basic' as const,
'médio': 'intermediate' as const,
'superior': 'advanced' as const
};
return mapping[gradeLevel] || 'basic';
}
private getAttentionSpan(gradeLevel: string): number {
const spans = {
'fundamental': 15,
'médio': 20,
'superior': 25
};
return spans[gradeLevel] || 15;
}
private getTextSizeForGrade(gradeLevel: string): number {
const sizes = {
'fundamental': 16,
'médio': 14,
'superior': 14
};
return sizes[gradeLevel] || 14;
}
private getMaxAttemptsForGrade(gradeLevel: string): number {
const attempts = {
'fundamental': 3,
'médio': 2,
'superior': 2
};
return attempts[gradeLevel] || 2;
}
private formatTextContent(content: any): string {
if (typeof content === 'string') return `<p>${content}</p>`;
if (content?.content) return `<p>${content.content}</p>`;
return '<p>Conteúdo educacional</p>';
}
private formatQuizQuestions(content: any, questionImages: any): any[] {
// Simplified quiz formatting - would integrate with Phase 2 QuizGenerator output
if (content?.questions) return content.questions;
return [{
id: 1,
question: '<p>Pergunta sobre o conteúdo apresentado</p>',
choices: [
{ id: 'a', correct: true, text: '<p>Resposta correta</p>' },
{ id: 'b', correct: false, text: '<p>Resposta incorreta 1</p>' },
{ id: 'c', correct: false, text: '<p>Resposta incorreta 2</p>' },
{ id: 'd', correct: false, text: '<p>Resposta incorreta 3</p>' }
]
}];
}
private formatFlashcardItems(content: any, cardImages: any): any[] {
// Simplified flashcard formatting - would integrate with Phase 2 FlashcardGenerator output
if (content?.flashcards) return content.flashcards;
return [{
id: 1,
front_card: {
text: 'Conceito',
centered_image: null,
fullscreen_image: null
},
back_card: {
text: 'Definição do conceito',
centered_image: null,
fullscreen_image: null
},
opened: false
}];
}
private formatListItems(content: any): string[] {
if (Array.isArray(content)) return content;
if (content?.examples) return content.examples;
return ['Item de exemplo 1', 'Item de exemplo 2', 'Item de exemplo 3'];
}
private formatHotspotMarkers(hotspotMarkers: any): any[] {
// Simplified hotspot formatting
return [{
id: 1,
x: 50,
y: 50,
icon: 'info',
title: 'Ponto de interesse',
description: 'Informação adicional sobre este elemento'
}];
}
private formatGallerySlides(slides: any): any[] {
// Simplified gallery formatting
if (Array.isArray(slides)) {
return slides.map((slide, index) => ({
id: index + 1,
image: slide.url || slide,
caption: slide.caption || `Slide ${index + 1}`
}));
}
return [{
id: 1,
image: 'https://cdn.digitalpages.com.br/education/generic/slide-1.jpg',
caption: 'Imagem educacional 1'
}];
}
private formatGenericContent(content: any): string {
return this.extractContentText(content);
}
}