base-plugin.tsā¢8.32 kB
/**
* Base Plugin Architecture for Content Elements
* Extensible system for adding new content element types
*/
import { ComposerWidgetUnion } from '../composer-widget-types.js';
export interface ContentElementPlugin {
type: string;
name: string;
description: string;
version: string;
// NLP Recognition
recognitionPatterns: RecognitionPattern[];
// Widget Generation
generateWidget(content: string, properties?: Record<string, any>): ComposerWidgetUnion;
// Validation
validateProperties(properties: Record<string, any>): ValidationResult;
// Educational Context
getEducationalValue(): EducationalMetadata;
}
export interface RecognitionPattern {
pattern: RegExp;
weight: number;
context?: string[];
examples?: string[];
}
export interface ValidationResult {
isValid: boolean;
errors: string[];
warnings: string[];
}
export interface EducationalMetadata {
learningTypes: ('memorization' | 'comprehension' | 'assessment' | 'demonstration' | 'exploration' | 'practice')[];
complexity: 'basic' | 'intermediate' | 'advanced';
interactivity: 'none' | 'low' | 'medium' | 'high';
timeEstimate: number; // minutes
prerequisites?: string[];
}
export abstract class BaseContentElementPlugin implements ContentElementPlugin {
abstract type: string;
abstract name: string;
abstract description: string;
abstract version: string;
abstract recognitionPatterns: RecognitionPattern[];
abstract generateWidget(content: string, properties?: Record<string, any>): ComposerWidgetUnion;
abstract getEducationalValue(): EducationalMetadata;
validateProperties(properties: Record<string, any>): ValidationResult {
// Default validation - can be overridden
return {
isValid: true,
errors: [],
warnings: [],
};
}
// Helper method for generating unique IDs
protected generateId(prefix?: string): string {
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 9);
return `${prefix || this.type}-${timestamp}-${random}`;
}
// Helper method for extracting content snippets
protected extractSnippet(content: string, maxLength: number = 100): string {
if (content.length <= maxLength) return content;
return content.substring(0, maxLength).trim() + '...';
}
// Helper method for cleaning HTML content
protected cleanContent(content: string): string {
return content
.replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/\s+/g, ' ') // Normalize whitespace
.trim();
}
// Helper method for pattern matching score
protected calculateMatchScore(content: string): number {
const text = content.toLowerCase();
let totalScore = 0;
let totalWeight = 0;
this.recognitionPatterns.forEach(pattern => {
const matches = text.match(pattern.pattern);
if (matches) {
totalScore += matches.length * pattern.weight;
}
totalWeight += pattern.weight;
});
return totalWeight > 0 ? Math.min(totalScore / totalWeight, 1.0) : 0;
}
}
export class PluginRegistry {
private plugins: Map<string, ContentElementPlugin> = new Map();
private loadedPlugins: string[] = [];
registerPlugin(plugin: ContentElementPlugin): void {
if (this.plugins.has(plugin.type)) {
throw new Error(`Plugin of type '${plugin.type}' is already registered`);
}
// Validate plugin
const validation = this.validatePlugin(plugin);
if (!validation.isValid) {
throw new Error(`Invalid plugin: ${validation.errors.join(', ')}`);
}
this.plugins.set(plugin.type, plugin);
this.loadedPlugins.push(plugin.type);
console.log(`ā
Registered content element plugin: ${plugin.name} (${plugin.type})`);
}
getPlugin(type: string): ContentElementPlugin | undefined {
return this.plugins.get(type);
}
getAllPlugins(): ContentElementPlugin[] {
return Array.from(this.plugins.values());
}
getPluginsByLearningType(learningType: EducationalMetadata['learningTypes'][0]): ContentElementPlugin[] {
return this.getAllPlugins().filter(plugin =>
plugin.getEducationalValue().learningTypes.includes(learningType)
);
}
findBestPlugin(content: string, intent?: string): { plugin: ContentElementPlugin; score: number } | null {
let bestMatch: { plugin: ContentElementPlugin; score: number } | null = null;
for (const plugin of this.plugins.values()) {
const score = this.calculatePluginScore(plugin, content, intent);
if (score > 0 && (!bestMatch || score > bestMatch.score)) {
bestMatch = { plugin, score };
}
}
return bestMatch;
}
private calculatePluginScore(plugin: ContentElementPlugin, content: string, intent?: string): number {
const text = content.toLowerCase();
let score = 0;
let totalWeight = 0;
plugin.recognitionPatterns.forEach(pattern => {
const matches = text.match(pattern.pattern);
if (matches) {
score += matches.length * pattern.weight;
// Bonus for context matching
if (intent && pattern.context?.includes(intent)) {
score += pattern.weight * 0.5;
}
}
totalWeight += pattern.weight;
});
return totalWeight > 0 ? Math.min(score / totalWeight, 1.0) : 0;
}
private validatePlugin(plugin: ContentElementPlugin): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Required fields validation
if (!plugin.type || typeof plugin.type !== 'string') {
errors.push('Plugin type is required and must be a string');
}
if (!plugin.name || typeof plugin.name !== 'string') {
errors.push('Plugin name is required and must be a string');
}
if (!plugin.description || typeof plugin.description !== 'string') {
errors.push('Plugin description is required and must be a string');
}
if (!plugin.version || typeof plugin.version !== 'string') {
errors.push('Plugin version is required and must be a string');
}
// Recognition patterns validation
if (!Array.isArray(plugin.recognitionPatterns) || plugin.recognitionPatterns.length === 0) {
errors.push('Plugin must have at least one recognition pattern');
} else {
plugin.recognitionPatterns.forEach((pattern, index) => {
if (!(pattern.pattern instanceof RegExp)) {
errors.push(`Recognition pattern ${index} must be a RegExp`);
}
if (typeof pattern.weight !== 'number' || pattern.weight <= 0) {
errors.push(`Recognition pattern ${index} weight must be a positive number`);
}
});
}
// Method validation
if (typeof plugin.generateWidget !== 'function') {
errors.push('Plugin must implement generateWidget method');
}
if (typeof plugin.getEducationalValue !== 'function') {
errors.push('Plugin must implement getEducationalValue method');
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
// Plugin management methods
unregisterPlugin(type: string): boolean {
const success = this.plugins.delete(type);
if (success) {
this.loadedPlugins = this.loadedPlugins.filter(t => t !== type);
console.log(`šļø Unregistered plugin: ${type}`);
}
return success;
}
getLoadedPluginTypes(): string[] {
return [...this.loadedPlugins];
}
getPluginStats(): {
totalPlugins: number;
pluginsByLearningType: Record<string, number>;
pluginsByComplexity: Record<string, number>;
} {
const plugins = this.getAllPlugins();
const stats = {
totalPlugins: plugins.length,
pluginsByLearningType: {} as Record<string, number>,
pluginsByComplexity: {} as Record<string, number>,
};
plugins.forEach(plugin => {
const eduValue = plugin.getEducationalValue();
// Count by learning types
eduValue.learningTypes.forEach(type => {
stats.pluginsByLearningType[type] = (stats.pluginsByLearningType[type] || 0) + 1;
});
// Count by complexity
stats.pluginsByComplexity[eduValue.complexity] = (stats.pluginsByComplexity[eduValue.complexity] || 0) + 1;
});
return stats;
}
}
// Global plugin registry instance
export const pluginRegistry = new PluginRegistry();