enhanced-nlp-widget-parser.ts•19.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,
};
}
}