Skip to main content
Glama
enhanced-nlp-widget-parser.ts19.4 kB
/** * Enhanced NLP Widget Parser with Plugin System Integration * Intelligent content analysis with educational best practices */ import { CompositionWidget } from './nlp-widget-parser.js'; import { ComposerWidgetUnion, Head1Widget } from './composer-widget-types.js'; import { EducationalContentAnalyzer, ContentChunk, EducationalContext } from './educational-content-analyzer.js'; import { pluginRegistry, ContentElementPlugin } from './content-element-plugins/index.js'; export interface EnhancedParsingResult { widgets: ComposerWidgetUnion[]; educationalContext: EducationalContext; recommendations: string[]; learningSequence: string[]; contentInsights: ContentInsight[]; } export interface ContentInsight { type: 'suggestion' | 'warning' | 'optimization' | 'best-practice'; message: string; reasoning: string; impact: 'low' | 'medium' | 'high'; } export interface ParseOptions { enablePluginSystem: boolean; applyEducationalBestPractices: boolean; optimizeEngagement: boolean; maxWidgets?: number; targetComplexity?: 'basic' | 'intermediate' | 'advanced'; prioritizeInteractivity?: boolean; } export class EnhancedNLPWidgetParser { private educationalAnalyzer: EducationalContentAnalyzer; constructor() { this.educationalAnalyzer = new EducationalContentAnalyzer(); } public parseContentIntelligently( prompt: string, title?: string, options: ParseOptions = { enablePluginSystem: true, applyEducationalBestPractices: true, optimizeEngagement: true, prioritizeInteractivity: false, } ): EnhancedParsingResult { console.log('🧠 Starting intelligent content analysis...'); // Step 1: Analyze educational context and content structure (Brazilian-aware) const analysis = this.educationalAnalyzer.analyzeContent(prompt, title); console.log(`📊 Educational Context Analysis: - Learning Objectives: ${analysis.overallContext.learningObjectives.length} - Target Audience: ${analysis.overallContext.targetAudience} - Content Complexity: ${analysis.overallContext.contentComplexity} - Engagement Level: ${analysis.overallContext.engagementLevel} - Estimated Duration: ${analysis.overallContext.estimatedDuration} minutes - Content Chunks: ${analysis.chunks.length} ${analysis.brazilianContext ? `- Brazilian Grade Level: ${analysis.brazilianContext.gradeLevel}` : ''} ${analysis.brazilianContext?.subject ? `- Subject: ${analysis.brazilianContext.subject}` : ''}`); // Step 2: Generate widgets using intelligent content selection const widgets = this.generateIntelligentWidgets(analysis, options); // Step 3: Apply educational best practices const optimizedWidgets = options.applyEducationalBestPractices ? this.applyEducationalOptimizations(widgets, analysis.overallContext) : widgets; // Step 4: Generate insights and recommendations const insights = this.generateContentInsights(analysis, optimizedWidgets, options); const recommendations = this.generateRecommendations(analysis, optimizedWidgets); console.log(`✅ Generated ${optimizedWidgets.length} widgets with ${insights.length} insights`); return { widgets: optimizedWidgets, educationalContext: analysis.overallContext, recommendations, learningSequence: analysis.recommendedSequence, contentInsights: insights, }; } private generateIntelligentWidgets( analysis: { chunks: ContentChunk[]; overallContext: EducationalContext }, options: ParseOptions ): ComposerWidgetUnion[] { const widgets: ComposerWidgetUnion[] = []; // Always start with a header widget widgets.push(this.createHeaderWidget(analysis.overallContext.learningObjectives[0] || 'Learning Content')); // Process each content chunk intelligently analysis.chunks.forEach((chunk, index) => { const chunkWidgets = this.processChunkIntelligently(chunk, analysis.overallContext, options); widgets.push(...chunkWidgets); // Insert engagement elements at optimal intervals if (options.optimizeEngagement && index > 0 && index % 2 === 0) { const engagementWidget = this.insertEngagementElement(chunk, analysis.overallContext); if (engagementWidget) { widgets.push(engagementWidget); } } }); // Apply content chunking for long content if (analysis.overallContext.estimatedDuration > 20) { return this.applyContentChunking(widgets, analysis.overallContext); } // Limit widgets if specified if (options.maxWidgets && widgets.length > options.maxWidgets) { return this.prioritizeWidgets(widgets, options.maxWidgets, options); } return widgets; } private processChunkIntelligently( chunk: ContentChunk, context: EducationalContext, options: ParseOptions ): ComposerWidgetUnion[] { const widgets: ComposerWidgetUnion[] = []; if (options.enablePluginSystem) { // Use plugin system for intelligent widget selection const selectedWidgets = this.selectWidgetsViaPlugins(chunk, context, options); widgets.push(...selectedWidgets); } else { // Fallback to basic widget creation const basicWidget = this.createBasicWidget(chunk); widgets.push(basicWidget); } return widgets; } private selectWidgetsViaPlugins( chunk: ContentChunk, context: EducationalContext, options: ParseOptions ): ComposerWidgetUnion[] { const widgets: ComposerWidgetUnion[] = []; // Get plugins that match the content intent const relevantPlugins = pluginRegistry.getPluginsByLearningType(chunk.intent.type); if (relevantPlugins.length === 0) { // No specific plugins, use best match const bestMatch = pluginRegistry.findBestPlugin(chunk.content, chunk.intent.type); if (bestMatch && bestMatch.score > 0.3) { const widget = bestMatch.plugin.generateWidget(chunk.content); widgets.push(widget); } else { // Fallback to basic text widget widgets.push(this.createBasicWidget(chunk)); } } else { // Use the most appropriate plugin based on content analysis const bestPlugin = this.selectBestPlugin(relevantPlugins, chunk, context, options); if (bestPlugin) { const widget = bestPlugin.generateWidget(chunk.content); widgets.push(widget); } } return widgets; } private selectBestPlugin( plugins: ContentElementPlugin[], chunk: ContentChunk, context: EducationalContext, options: ParseOptions ): ContentElementPlugin | null { // Score plugins based on multiple factors const scoredPlugins = plugins.map(plugin => { let score = 0; const eduValue = plugin.getEducationalValue(); // Match complexity if (eduValue.complexity === context.contentComplexity) { score += 0.3; } // Match interactivity preference if (options.prioritizeInteractivity && eduValue.interactivity === 'high') { score += 0.4; } // Match learning types if (eduValue.learningTypes.includes(chunk.intent.type)) { score += 0.5; } // Calculate content match score const contentMatch = this.calculateContentMatchScore(plugin, chunk.content); score += contentMatch * 0.4; return { plugin, score }; }); // Return the highest scoring plugin const bestMatch = scoredPlugins.sort((a, b) => b.score - a.score)[0]; return bestMatch && bestMatch.score > 0.3 ? bestMatch.plugin : null; } private calculateContentMatchScore(plugin: ContentElementPlugin, content: string): number { const lowerContent = content.toLowerCase(); let totalScore = 0; let totalWeight = 0; plugin.recognitionPatterns.forEach(pattern => { const matches = lowerContent.match(pattern.pattern); if (matches) { totalScore += matches.length * pattern.weight; } totalWeight += pattern.weight; }); return totalWeight > 0 ? Math.min(totalScore / totalWeight, 1.0) : 0; } private applyEducationalOptimizations( widgets: ComposerWidgetUnion[], context: EducationalContext ): ComposerWidgetUnion[] { const optimizedWidgets: ComposerWidgetUnion[] = [...widgets]; // Apply spaced learning principles if (context.estimatedDuration > 15) { this.insertSpacedLearningElements(optimizedWidgets, context); } // Apply active learning techniques this.enhanceWithActiveLearning(optimizedWidgets, context); // Apply cognitive load management this.optimizeCognitiveLoad(optimizedWidgets, context); return optimizedWidgets; } private insertSpacedLearningElements( widgets: ComposerWidgetUnion[], context: EducationalContext ): void { // Insert review elements at strategic points for (let i = 3; i < widgets.length; i += 4) { const reviewQuiz = this.createReviewQuiz(widgets.slice(Math.max(0, i - 3), i)); widgets.splice(i, 0, reviewQuiz); } } private enhanceWithActiveLearning( widgets: ComposerWidgetUnion[], context: EducationalContext ): void { // Ensure interactive elements are present const interactiveCount = widgets.filter(w => ['quiz-1', 'flashcards-1', 'hotspot-1'].includes(w.type) ).length; const totalWidgets = widgets.length; const interactiveRatio = interactiveCount / totalWidgets; // Target: 30% interactive elements for high engagement if (context.engagementLevel === 'high' && interactiveRatio < 0.3) { this.addInteractiveElements(widgets, context); } } private optimizeCognitiveLoad( widgets: ComposerWidgetUnion[], context: EducationalContext ): void { // Break up long text sections for (let i = 0; i < widgets.length; i++) { if (widgets[i].type === 'text-1' && widgets[i].content && widgets[i].content!.length > 500) { const chunks = this.breakUpLongText(widgets[i]); widgets.splice(i, 1, ...chunks); i += chunks.length - 1; } } } private insertEngagementElement( chunk: ContentChunk, context: EducationalContext ): ComposerWidgetUnion | null { // Select engagement element based on context if (context.engagementLevel === 'high') { // Create an interactive hotspot or quick quiz const quizPlugin = pluginRegistry.getPlugin('quiz-1'); if (quizPlugin) { return quizPlugin.generateWidget(`Quick check: ${chunk.content.substring(0, 100)}`); } } return null; } private applyContentChunking( widgets: ComposerWidgetUnion[], context: EducationalContext ): ComposerWidgetUnion[] { const chunkedWidgets: ComposerWidgetUnion[] = []; const chunkSize = this.calculateOptimalChunkSize(context); for (let i = 0; i < widgets.length; i += chunkSize) { const chunk = widgets.slice(i, i + chunkSize); chunkedWidgets.push(...chunk); // Add section break if not at the end if (i + chunkSize < widgets.length) { chunkedWidgets.push(this.createSectionBreak(i / chunkSize + 1)); } } return chunkedWidgets; } private calculateOptimalChunkSize(context: EducationalContext): number { // Adjust chunk size based on complexity and audience const baseSize = { elementary: 3, middle: 4, high: 5, college: 6, adult: 6, professional: 7, }; const complexityModifier = { basic: 1, intermediate: 0.8, advanced: 0.6, }; return Math.ceil(baseSize[context.targetAudience] * complexityModifier[context.contentComplexity]); } private prioritizeWidgets( widgets: ComposerWidgetUnion[], maxWidgets: number, options: ParseOptions ): ComposerWidgetUnion[] { // Score widgets by importance const scoredWidgets = widgets.map((widget, index) => { let score = 0; // Header widgets are essential if (widget.type.includes('head')) score += 100; // Interactive widgets get priority if requested if (options.prioritizeInteractivity && ['quiz-1', 'flashcards-1', 'hotspot-1'].includes(widget.type)) { score += 50; } // Early widgets are more important score += (widgets.length - index) * 2; // Educational value const plugin = pluginRegistry.getPlugin(widget.type); if (plugin) { const eduValue = plugin.getEducationalValue(); score += eduValue.timeEstimate; // More comprehensive content scores higher } return { widget, score, index }; }); // Return top-scoring widgets return scoredWidgets .sort((a, b) => b.score - a.score) .slice(0, maxWidgets) .sort((a, b) => a.index - b.index) // Restore original order .map(item => item.widget); } private generateContentInsights( analysis: { chunks: ContentChunk[]; overallContext: EducationalContext }, widgets: ComposerWidgetUnion[], options: ParseOptions ): ContentInsight[] { const insights: ContentInsight[] = []; // Analyze widget distribution const widgetTypes = widgets.map(w => w.type); const uniqueTypes = new Set(widgetTypes); // Check for educational best practices if (uniqueTypes.size < 3 && widgets.length > 5) { insights.push({ type: 'suggestion', message: 'Consider adding more variety in content types', reasoning: 'Diverse content types improve engagement and learning outcomes', impact: 'medium', }); } // Check for assessment elements const hasAssessment = widgets.some(w => w.type === 'quiz-1'); if (!hasAssessment && analysis.overallContext.estimatedDuration > 10) { insights.push({ type: 'best-practice', message: 'Add knowledge check questions', reasoning: 'Regular assessment improves retention and identifies learning gaps', impact: 'high', }); } // Check for multimedia content const hasVideo = widgets.some(w => w.type === 'video-1'); const hasComplexConcepts = analysis.chunks.some(c => c.intent.type === 'comprehension'); if (!hasVideo && hasComplexConcepts) { insights.push({ type: 'suggestion', message: 'Consider adding video explanations for complex concepts', reasoning: 'Visual demonstrations enhance understanding of complex topics', impact: 'medium', }); } // Check engagement ratio const interactiveWidgets = widgets.filter(w => ['quiz-1', 'flashcards-1', 'hotspot-1'].includes(w.type) ).length; const engagementRatio = interactiveWidgets / widgets.length; if (engagementRatio < 0.2 && analysis.overallContext.engagementLevel === 'high') { insights.push({ type: 'optimization', message: 'Increase interactive content for better engagement', reasoning: 'Interactive elements maintain attention and improve learning outcomes', impact: 'high', }); } return insights; } private generateRecommendations( analysis: { chunks: ContentChunk[]; overallContext: EducationalContext }, widgets: CompositionWidget[] ): string[] { const recommendations: string[] = []; // Duration-based recommendations if (analysis.overallContext.estimatedDuration > 30) { recommendations.push('Consider breaking this content into multiple shorter sessions'); } // Complexity-based recommendations if (analysis.overallContext.contentComplexity === 'advanced' && analysis.overallContext.targetAudience === 'elementary') { recommendations.push('Content complexity may be too high for target audience'); } // Widget-specific recommendations const videoCount = widgets.filter(w => w.type === 'video-1').length; if (videoCount > 3) { recommendations.push('Consider balancing video content with other interactive elements'); } // Learning objectives recommendations if (analysis.overallContext.learningObjectives.length === 0) { recommendations.push('Define clear learning objectives for better content structure'); } return recommendations; } // Helper methods for widget creation private createHeaderWidget(title: string): Head1Widget { return { id: `header-${Date.now()}`, type: 'head-1', content_title: null, primary_color: '#FFFFFF', secondary_color: '#007bff', category: `<p>${title}</p>`, background_image: 'https://pocs.digitalpages.com.br/rdpcomposer/media/head-1/background.png', avatar: 'https://pocs.digitalpages.com.br/rdpcomposer/media/head-1/avatar.png', avatar_border_color: '#00643e', author_name: '<p>Educational Content</p>', author_office: '<p>AI Generated</p>', show_category: true, show_author_name: true, show_divider: true, }; } private createBasicWidget(chunk: ContentChunk): ComposerWidgetUnion { return { id: `text-${Date.now()}`, type: 'text-1', content: `<p>${chunk.content}</p>`, }; } private createReviewQuiz(previousWidgets: CompositionWidget[]): ComposerWidgetUnion { const quizPlugin = pluginRegistry.getPlugin('quiz-1'); if (quizPlugin) { const content = previousWidgets .filter(w => w.content || w.category) .map(w => w.content || w.category) .join(' '); return quizPlugin.generateWidget(`Review: ${content.substring(0, 200)}`); } return this.createBasicWidget({ id: 'review', content: 'Review the previous concepts', intent: { type: 'assessment', confidence: 0.8, indicators: [], suggestedElements: [] }, suggestedElements: [], position: 0, }); } private addInteractiveElements(widgets: CompositionWidget[], context: EducationalContext): void { // Add flashcards for memorization content const textWidgets = widgets.filter(w => w.type === 'text-1'); if (textWidgets.length > 0) { const flashcardsPlugin = pluginRegistry.getPlugin('flashcards-1'); if (flashcardsPlugin) { const content = textWidgets.map(w => w.content).join(' '); const flashcardsWidget = flashcardsPlugin.generateWidget(content); widgets.push(flashcardsWidget); } } } private breakUpLongText(widget: CompositionWidget): ComposerWidgetUnion[] { if (!widget.content) return [widget]; const chunks = widget.content.split('</p><p>').map(chunk => chunk.replace(/<\/?p>/g, '')); return chunks.map((chunk, index) => ({ ...widget, id: `${widget.id}-chunk-${index}`, content: `<p>${chunk}</p>`, })); } private createSectionBreak(sectionNumber: number): ComposerWidgetUnion { return { id: `section-${sectionNumber}-${Date.now()}`, type: 'head-1', content_title: null, primary_color: '#f8f9fa', secondary_color: '#6c757d', category: `<p>Section ${sectionNumber}</p>`, background_image: 'https://pocs.digitalpages.com.br/rdpcomposer/media/head-1/background.png', avatar: 'https://pocs.digitalpages.com.br/rdpcomposer/media/head-1/avatar.png', avatar_border_color: '#6c757d', author_name: '<p>Continue Learning</p>', author_office: '<p>Next Section</p>', show_category: true, show_author_name: false, show_divider: true, }; } }

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