Skip to main content
Glama
educational-content-analyzer.ts24.6 kB
/** * Educational Content Analyzer * AI-driven content analysis with educational best practices */ import { BrazilianEducationalAnalyzer, BrazilianEducationalContext } from './brazilian-educational-analyzer.js'; export interface ContentIntent { type: 'memorization' | 'comprehension' | 'assessment' | 'demonstration' | 'exploration' | 'practice'; confidence: number; indicators: string[]; suggestedElements: string[]; } export interface EducationalContext { learningObjectives: string[]; targetAudience: 'elementary' | 'middle' | 'high' | 'college' | 'adult' | 'professional'; contentComplexity: 'basic' | 'intermediate' | 'advanced'; engagementLevel: 'low' | 'medium' | 'high'; estimatedDuration: number; // minutes } export interface ContentChunk { id: string; content: string; intent: ContentIntent; suggestedElements: ElementSuggestion[]; position: number; } export interface ElementSuggestion { type: string; confidence: number; reasoning: string; properties: Record<string, any>; } export class EducationalContentAnalyzer { private brazilianAnalyzer: BrazilianEducationalAnalyzer; constructor() { this.brazilianAnalyzer = new BrazilianEducationalAnalyzer(); } private memorializationPatterns = [ /\b(memorize|remember|recall|learn by heart|commit to memory)\b/i, /\b(vocabulary|terms|definitions|formulas|facts)\b/i, /\b(study cards|flash cards|drill|repetition)\b/i, // Brazilian Portuguese patterns /\b(memorizar|lembrar|recordar|decorar|gravar)\b/i, /\b(vocabulário|termos|definições|fórmulas|fatos)\b/i, /\b(cartões de estudo|repetição|treino|exercitar)\b/i, ]; private comprehensionPatterns = [ /\b(understand|explain|comprehend|grasp|interpret)\b/i, /\b(concept|theory|principle|idea|how it works)\b/i, /\b(demonstrate|show|illustrate|example)\b/i, // Brazilian Portuguese patterns /\b(entender|explicar|compreender|interpretar|captar)\b/i, /\b(conceito|teoria|princípio|ideia|como funciona)\b/i, /\b(demonstrar|mostrar|ilustrar|exemplo|exemplificar)\b/i, ]; private assessmentPatterns = [ /\b(test|quiz|assess|evaluate|check|verify)\b/i, /\b(knowledge|understanding|learning|mastery)\b/i, /\b(exam|assessment|evaluation|grade)\b/i, /\btest\s+knowledge\s+of\b/i, /\bassess\s+understanding\b/i, /\bcheck\s+if\s+students?\s+learned?\b/i, /\bverify\s+comprehension\b/i, /\bmeasure\s+progress\b/i, /\bevaluate\s+learning\b/i, /\bquiz\s+on\b/i, /\btest\s+your\s+knowledge\b/i, /\bhow\s+well\s+do\s+you\s+know\b/i, /\bcan\s+you\s+answer\b/i, /\bmultiple\s+choice\b/i, /\bselect\s+the\s+correct\b/i, /\bchoose\s+the\s+best\b/i, /\bfinal\s+exam\b/i, /\bmidterm\s+test\b/i, /\bpop\s+quiz\b/i, /\bdiagnostic\s+assessment\b/i, ]; private demonstrationPatterns = [ /\b(show|demonstrate|display|present|exhibit)\b/i, /\b(video|visual|watch|see|observe)\b/i, /\b(process|procedure|steps|how to)\b/i, /\bshow\s+a\s+video\s+about\b/i, /\bdemonstrate\s+the\s+process\b/i, /\bwatch\s+this\s+video\b/i, /\bplay\s+the\s+video\b/i, /\bvideo\s+explanation\b/i, /\bvisual\s+demonstration\b/i, /\bsee\s+how\s+it\s+works\b/i, /\bobserve\s+the\s+technique\b/i, /\bvideo\s+tutorial\b/i, /\bscreen\s+recording\b/i, /\banimated\s+explanation\b/i, /\bstep-by-step\s+video\b/i, /\bwalkthrough\s+video\b/i, /\binstructional\s+video\b/i, /\beducational\s+video\b/i, ]; private explorationPatterns = [ /\b(explore|discover|investigate|research)\b/i, /\b(interactive|hands-on|experiment|try)\b/i, /\b(click|navigate|browse|search)\b/i, ]; private practicePatterns = [ /\b(practice|exercise|drill|apply|try out)\b/i, /\b(simulation|scenario|case study)\b/i, /\b(hands-on|interactive practice)\b/i, ]; public analyzeContent(prompt: string, title?: string): { chunks: ContentChunk[]; overallContext: EducationalContext; recommendedSequence: string[]; brazilianContext?: BrazilianEducationalContext; } { // Analyze Brazilian educational context first const brazilianContext = this.brazilianAnalyzer.analyzeBrazilianContext(prompt, title); // Analyze overall educational context with Brazilian input const context = this.analyzeEducationalContext(prompt, title, brazilianContext); // Break content into meaningful chunks const chunks = this.chunkContent(prompt); // Analyze each chunk for learning intent const analyzedChunks = chunks.map((chunk, index) => this.analyzeChunk(chunk, index, context) ); // Generate recommended learning sequence const sequence = this.generateLearningSequence(analyzedChunks, context); return { chunks: analyzedChunks, overallContext: context, recommendedSequence: sequence, brazilianContext, }; } private analyzeEducationalContext(prompt: string, title?: string, brazilianContext?: BrazilianEducationalContext): EducationalContext { const fullText = `${title || ''} ${prompt}`.toLowerCase(); // Use Brazilian context if available, otherwise fallback to generic analysis const targetAudience = brazilianContext?.gradeLevel ? this.brazilianAnalyzer.mapToInternationalAudience(brazilianContext.gradeLevel) : this.determineTargetAudience(fullText); // Assess content complexity (use Brazilian duration mapping if available) const contentComplexity = brazilianContext?.duration ? this.brazilianAnalyzer.mapComplexityFromDuration(brazilianContext.duration) : this.assessComplexity(fullText); // Extract learning objectives const learningObjectives = this.extractLearningObjectives(prompt); // Estimate engagement level needed const engagementLevel = this.assessEngagementNeeds(fullText); // Estimate duration (use Brazilian context if available) const estimatedDuration = brazilianContext?.duration || this.estimateContentDuration(prompt); return { learningObjectives, targetAudience, contentComplexity, engagementLevel, estimatedDuration, }; } private analyzeChunk(content: string, position: number, context: EducationalContext): ContentChunk { const intent = this.detectContentIntent(content); const suggestedElements = this.suggestElementsForChunk(content, intent, context); return { id: `chunk-${position}`, content, intent, suggestedElements, position, }; } private detectContentIntent(content: string): ContentIntent { const text = content.toLowerCase(); const intents: Array<{type: ContentIntent['type'], score: number, indicators: string[]}> = []; // Check for memorization intent const memorizationScore = this.calculatePatternScore(text, this.memorializationPatterns); if (memorizationScore > 0) { intents.push({ type: 'memorization', score: memorizationScore, indicators: this.findMatchingPatterns(text, this.memorializationPatterns) }); } // Check for comprehension intent const comprehensionScore = this.calculatePatternScore(text, this.comprehensionPatterns); if (comprehensionScore > 0) { intents.push({ type: 'comprehension', score: comprehensionScore, indicators: this.findMatchingPatterns(text, this.comprehensionPatterns) }); } // Check for assessment intent const assessmentScore = this.calculatePatternScore(text, this.assessmentPatterns); if (assessmentScore > 0) { intents.push({ type: 'assessment', score: assessmentScore, indicators: this.findMatchingPatterns(text, this.assessmentPatterns) }); } // Check for demonstration intent const demonstrationScore = this.calculatePatternScore(text, this.demonstrationPatterns); if (demonstrationScore > 0) { intents.push({ type: 'demonstration', score: demonstrationScore, indicators: this.findMatchingPatterns(text, this.demonstrationPatterns) }); } // Check for exploration intent const explorationScore = this.calculatePatternScore(text, this.explorationPatterns); if (explorationScore > 0) { intents.push({ type: 'exploration', score: explorationScore, indicators: this.findMatchingPatterns(text, this.explorationPatterns) }); } // Check for practice intent const practiceScore = this.calculatePatternScore(text, this.practicePatterns); if (practiceScore > 0) { intents.push({ type: 'practice', score: practiceScore, indicators: this.findMatchingPatterns(text, this.practicePatterns) }); } // Return highest scoring intent, or default to comprehension if (intents.length === 0) { return { type: 'comprehension', confidence: 0.3, indicators: ['default content type'], suggestedElements: ['text-1', 'head-1'] }; } const topIntent = intents.sort((a, b) => b.score - a.score)[0]; return { type: topIntent.type, confidence: Math.min(topIntent.score, 1.0), indicators: topIntent.indicators, suggestedElements: this.getElementsForIntent(topIntent.type) }; } private suggestElementsForChunk( content: string, intent: ContentIntent, context: EducationalContext ): ElementSuggestion[] { const suggestions: ElementSuggestion[] = []; switch (intent.type) { case 'memorization': suggestions.push({ type: 'flashcards-1', confidence: 0.9, reasoning: 'Flashcards are optimal for memorization and recall practice', properties: this.generateFlashcardProperties(content) }); // Add spaced repetition if content is complex if (context.contentComplexity !== 'basic') { suggestions.push({ type: 'quiz-1', confidence: 0.7, reasoning: 'Quiz reinforces memorization through active recall', properties: this.generateQuizProperties(content, 'memorization') }); } break; case 'comprehension': // Start with content presentation suggestions.push({ type: 'text-1', confidence: 0.8, reasoning: 'Clear text explanation establishes foundational understanding', properties: this.generateTextProperties(content) }); // Add visual demonstration if complex if (context.contentComplexity !== 'basic' || content.includes('process') || content.includes('how')) { suggestions.push({ type: 'video-1', confidence: 0.85, reasoning: 'Video demonstration enhances conceptual understanding', properties: this.generateVideoProperties(content) }); } // Add interactive exploration for engagement if (context.engagementLevel === 'high') { suggestions.push({ type: 'hotspot-1', confidence: 0.6, reasoning: 'Interactive elements maintain engagement during concept learning', properties: this.generateHotspotProperties(content) }); } break; case 'assessment': suggestions.push({ type: 'quiz-1', confidence: 0.95, reasoning: 'Multiple choice quiz directly addresses assessment needs', properties: this.generateQuizProperties(content, 'assessment') }); break; case 'demonstration': suggestions.push({ type: 'video-1', confidence: 0.9, reasoning: 'Video is ideal for demonstrating processes and procedures', properties: this.generateVideoProperties(content) }); // Add step-by-step breakdown if complex if (content.includes('step') || content.includes('process')) { suggestions.push({ type: 'list-1', confidence: 0.7, reasoning: 'Structured list breaks down complex procedures', properties: this.generateListProperties(content) }); } break; case 'exploration': suggestions.push({ type: 'hotspot-1', confidence: 0.8, reasoning: 'Interactive hotspots enable exploratory learning', properties: this.generateHotspotProperties(content) }); break; case 'practice': suggestions.push({ type: 'flashcards-1', confidence: 0.75, reasoning: 'Flashcards provide structured practice opportunities', properties: this.generateFlashcardProperties(content) }); suggestions.push({ type: 'quiz-1', confidence: 0.8, reasoning: 'Quiz provides practice with immediate feedback', properties: this.generateQuizProperties(content, 'practice') }); break; } return suggestions.sort((a, b) => b.confidence - a.confidence); } private generateLearningSequence(chunks: ContentChunk[], context: EducationalContext): string[] { const sequence: string[] = []; // Apply educational best practices for sequencing for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; const isLast = i === chunks.length - 1; // Always start with introduction/overview if (i === 0) { sequence.push('head-1'); // Header } // Main content based on intent switch (chunk.intent.type) { case 'comprehension': sequence.push('text-1'); if (context.contentComplexity !== 'basic') { sequence.push('video-1'); } break; case 'memorization': sequence.push('text-1'); // Brief explanation sequence.push('flashcards-1'); // Main memorization tool break; case 'demonstration': sequence.push('video-1'); sequence.push('text-1'); // Supporting explanation break; case 'assessment': sequence.push('quiz-1'); break; case 'exploration': sequence.push('text-1'); sequence.push('hotspot-1'); break; case 'practice': sequence.push('flashcards-1'); sequence.push('quiz-1'); break; } // Add engagement elements at optimal intervals if (i > 0 && i % 3 === 0 && context.engagementLevel === 'high') { sequence.push('hotspot-1'); // Break up content with interaction } // Assessment after learning sequences if (!isLast && (chunk.intent.type === 'comprehension' || chunk.intent.type === 'memorization')) { const nextChunk = chunks[i + 1]; if (nextChunk.intent.type !== 'assessment') { sequence.push('quiz-1'); // Knowledge check } } } // Final assessment if not already included const lastElement = sequence[sequence.length - 1]; if (lastElement !== 'quiz-1' && context.learningObjectives.length > 0) { sequence.push('quiz-1'); } return sequence; } // Helper methods for pattern matching and scoring private calculatePatternScore(text: string, patterns: RegExp[]): number { let score = 0; patterns.forEach(pattern => { const matches = text.match(pattern); if (matches) { score += matches.length * 0.3; } }); return Math.min(score, 1.0); } private findMatchingPatterns(text: string, patterns: RegExp[]): string[] { const matches: string[] = []; patterns.forEach(pattern => { const found = text.match(pattern); if (found) { matches.push(...found); } }); return matches; } private getElementsForIntent(intent: ContentIntent['type']): string[] { const mapping = { memorization: ['flashcards-1', 'quiz-1'], comprehension: ['text-1', 'video-1', 'hotspot-1'], assessment: ['quiz-1'], demonstration: ['video-1', 'list-1'], exploration: ['hotspot-1', 'gallery-1'], practice: ['flashcards-1', 'quiz-1'], }; return mapping[intent] || ['text-1']; } // Content chunking methods private chunkContent(prompt: string): string[] { // Split by paragraphs, sentences, or logical breaks const chunks = prompt .split(/\n\s*\n+/) // Double newlines .filter(chunk => chunk.trim().length > 20) // Minimum chunk size .map(chunk => chunk.trim()); // If no clear paragraphs, split by sentences for very long content if (chunks.length === 1 && chunks[0].length > 500) { return chunks[0] .split(/[.!?]+/) .filter(sentence => sentence.trim().length > 30) .map(sentence => sentence.trim() + '.'); } return chunks.length > 0 ? chunks : [prompt]; } // Context analysis helper methods private determineTargetAudience(text: string): EducationalContext['targetAudience'] { if (text.includes('elementary') || text.includes('kids') || text.includes('children')) { return 'elementary'; } if (text.includes('middle school') || text.includes('junior')) { return 'middle'; } if (text.includes('high school') || text.includes('teenager')) { return 'high'; } if (text.includes('college') || text.includes('university') || text.includes('student')) { return 'college'; } if (text.includes('professional') || text.includes('workplace') || text.includes('corporate')) { return 'professional'; } return 'adult'; // Default } private assessComplexity(text: string): EducationalContext['contentComplexity'] { const complexityIndicators = { basic: ['simple', 'basic', 'introduction', 'overview', 'beginner'], intermediate: ['intermediate', 'detailed', 'comprehensive', 'analysis'], advanced: ['advanced', 'complex', 'sophisticated', 'expert', 'research'], }; for (const [level, indicators] of Object.entries(complexityIndicators)) { if (indicators.some(indicator => text.includes(indicator))) { return level as EducationalContext['contentComplexity']; } } // Assess by content length and vocabulary if (text.length > 1000) return 'advanced'; if (text.length > 300) return 'intermediate'; return 'basic'; } private extractLearningObjectives(prompt: string): string[] { const objectives: string[] = []; // Look for explicit objectives const objectivePatterns = [ /students?\s+(?:will|should|must|need to)\s+([^.!?]+)/gi, /(?:learn|understand|master|know)\s+([^.!?]+)/gi, /objective[s]?[:]\s*([^.!?]+)/gi, ]; objectivePatterns.forEach(pattern => { const matches = prompt.match(pattern); if (matches) { objectives.push(...matches.map(match => match.trim())); } }); return objectives.length > 0 ? objectives : ['Complete the learning content']; } private assessEngagementNeeds(text: string): EducationalContext['engagementLevel'] { const highEngagementIndicators = ['interactive', 'engaging', 'fun', 'exciting', 'hands-on']; const lowEngagementIndicators = ['formal', 'lecture', 'presentation', 'reading']; if (highEngagementIndicators.some(indicator => text.includes(indicator))) { return 'high'; } if (lowEngagementIndicators.some(indicator => text.includes(indicator))) { return 'low'; } return 'medium'; } private estimateContentDuration(prompt: string): number { // Rough estimation: 200 words per minute reading, plus interaction time const wordCount = prompt.split(/\s+/).length; const baseTime = Math.ceil(wordCount / 200); // Add time for interactive elements const interactiveBonus = prompt.toLowerCase().includes('interactive') ? 10 : 0; return Math.max(5, baseTime + interactiveBonus); // Minimum 5 minutes } // Property generation methods for different element types private generateFlashcardProperties(content: string): Record<string, any> { return { cards: this.extractFlashcardPairs(content), shuffle: true, showProgress: true, autoAdvance: false, }; } private generateQuizProperties(content: string, purpose: string): Record<string, any> { return { questions: this.extractQuizQuestions(content), showFeedback: true, randomizeOrder: purpose === 'practice', allowRetries: purpose !== 'assessment', passingScore: purpose === 'assessment' ? 80 : 60, }; } private generateVideoProperties(content: string): Record<string, any> { return { title: this.extractVideoTitle(content), description: content.substring(0, 200) + '...', placeholder: true, // User will provide actual video URL autoplay: false, controls: true, }; } private generateTextProperties(content: string): Record<string, any> { return { content: `<p>${content}</p>`, formatting: 'paragraph', readingLevel: 'standard', }; } private generateHotspotProperties(content: string): Record<string, any> { return { hotspots: this.extractInteractivePoints(content), backgroundImage: 'placeholder.jpg', title: 'Interactive Exploration', }; } private generateListProperties(content: string): Record<string, any> { return { items: this.extractListItems(content), type: 'numbered', style: 'standard', }; } // Content extraction helper methods private extractFlashcardPairs(content: string): Array<{front: string, back: string}> { // Simple extraction - can be enhanced with more sophisticated NLP const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 10); const pairs: Array<{front: string, back: string}> = []; for (let i = 0; i < sentences.length - 1; i += 2) { if (sentences[i + 1]) { pairs.push({ front: sentences[i].trim(), back: sentences[i + 1].trim(), }); } } return pairs.length > 0 ? pairs : [{ front: 'Key Concept', back: content.substring(0, 100) + '...', }]; } private extractQuizQuestions(content: string): Array<{question: string, options: string[], correct: number}> { // Generate questions based on content - simplified implementation const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 20); const questions: Array<{question: string, options: string[], correct: number}> = []; sentences.slice(0, 3).forEach((sentence, index) => { questions.push({ question: `What is the main point about: ${sentence.trim()}?`, options: [ 'Option A (correct)', 'Option B', 'Option C', 'Option D', ], correct: 0, }); }); return questions.length > 0 ? questions : [{ question: 'What is the main topic of this content?', options: ['Topic A', 'Topic B', 'Topic C', 'Topic D'], correct: 0, }]; } private extractVideoTitle(content: string): string { // Extract likely video title from content const firstSentence = content.split(/[.!?]/)[0]; return firstSentence.length > 50 ? firstSentence.substring(0, 50) + '...' : firstSentence; } private extractInteractivePoints(content: string): Array<{x: number, y: number, title: string, content: string}> { const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 15); const points: Array<{x: number, y: number, title: string, content: string}> = []; sentences.slice(0, 3).forEach((sentence, index) => { points.push({ x: 20 + (index * 25), // Distribute across image y: 30 + (index * 20), title: `Point ${index + 1}`, content: sentence.trim(), }); }); return points; } private extractListItems(content: string): string[] { // Look for existing list items or create from sentences const listPattern = /^[-•*]\s*(.+)$/gm; const matches = content.match(listPattern); if (matches) { return matches.map(item => item.replace(/^[-•*]\s*/, '').trim()); } // Create list from sentences if no explicit list found return content .split(/[.!?]+/) .filter(s => s.trim().length > 10) .slice(0, 5) .map(s => s.trim()); } }

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