Skip to main content
Glama
phase3-widget-orchestrator.ts20.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); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rkm097git/euconquisto-composer-mcp-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server