Skip to main content
Glama
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();

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