Skip to main content
Glama
analyze-content-for-widgets-v1.1.0.js27.5 kB
#!/usr/bin/env node /** * Content Analysis for Widgets Tool v1.1.0 - RELIABILITY FOCUSED * Dynamically adapts to any educational content while ensuring robust structure preservation * @version 1.1.0 (January 20, 2025) * @status DEVELOPMENT - Improved reliability with fail-fast validation * @reference JIT workflow step 2 of 7 * @enhancement Dynamic adaptation + robust error handling + content preservation */ export class ContentWidgetAnalyzerV110 { constructor() { this.processingStartTime = null; this.validationErrors = []; this.contentTypes = this.initializeContentTypes(); this.widgetMappings = this.initializeWidgetMappings(); } /** * Main content analysis entry point - v1.1.0 RELIABLE VERSION * @param {Object} input - Contains claudeContent (natural educational content) * @returns {Object} Widget mapping recommendations with educational flow analysis */ async analyzeContent(input) { this.processingStartTime = new Date(); this.validationErrors = []; try { // FAIL-FAST VALIDATION PHASE const validationResult = this.validateAndNormalizeInput(input); if (!validationResult.valid) { throw new Error(`Content validation failed:\n${validationResult.errors.join('\n')}\n\nReceived input structure: ${JSON.stringify(Object.keys(input), null, 2)}`); } const { claudeContent, normalizedContent } = validationResult; console.error('[CONTENT_ANALYZER_V110] Content validated successfully'); console.error(`[CONTENT_ANALYZER_V110] Processing ${normalizedContent.sections.length} sections with ${normalizedContent.totalCharacters} characters`); // DYNAMIC CONTENT ANALYSIS PHASE const contentStructure = this.analyzeContentStructure(normalizedContent); // WIDGET MAPPING PHASE const widgetRecommendations = this.generateWidgetRecommendations(contentStructure); // EDUCATIONAL FLOW ANALYSIS PHASE const educationalFlow = this.analyzeEducationalFlow(widgetRecommendations); // OPTIMIZATION PHASE const optimizations = this.generateOptimizations(educationalFlow, widgetRecommendations); // FINAL WIDGET SELECTION PHASE const selectedWidgetTypes = this.createWidgetSelection(widgetRecommendations); // SUCCESS RESPONSE return { success: true, data: { contentStructure: { originalFormat: normalizedContent.detectedFormat, processedSections: contentStructure.sections.length, totalContent: normalizedContent.totalCharacters, dynamicAdaptations: normalizedContent.adaptations }, widgetRecommendations: widgetRecommendations, educationalFlow: educationalFlow, optimizations: optimizations, selectedWidgetTypes: selectedWidgetTypes, mappingRationale: { totalWidgets: widgetRecommendations.length, widgetTypes: [...new Set(widgetRecommendations.map(w => w.widgetType))], averageConfidence: this.calculateAverageConfidence(widgetRecommendations), mappingStrategy: "Dynamic educational adaptation with structure preservation" } }, metadata: { version: "1.1.0", timestamp: new Date().toISOString(), processingTime: Date.now() - this.processingStartTime.getTime(), inputFormat: normalizedContent.detectedFormat, sectionsProcessed: normalizedContent.sections.length } }; } catch (error) { console.error('[CONTENT_ANALYZER_V110] FAIL-FAST ERROR:', error.message); // FAIL-FAST ERROR RESPONSE return { success: false, error: { code: 'CONTENT_ANALYSIS_FAILED', message: error.message, timestamp: new Date().toISOString(), processingTime: Date.now() - this.processingStartTime.getTime(), failFast: true, developmentMode: true, troubleshooting: { commonIssues: [ "Content structure not recognized - check if claudeContent is properly structured", "Missing required properties - ensure content has analyzable structure", "Type mismatch - verify input is an object with educational content" ], debugSteps: [ "1. Check input.claudeContent exists and is an object", "2. Verify content has title, sections, or other educational structure", "3. Ensure content is not empty or malformed" ] } } }; } } /** * FAIL-FAST VALIDATION with dynamic format detection * Key innovation: Adapts to ANY content format while ensuring structural integrity */ validateAndNormalizeInput(input) { const errors = []; // Basic input validation if (!input || typeof input !== 'object') { errors.push("Input must be an object containing claudeContent"); return { valid: false, errors }; } const { claudeContent } = input; if (!claudeContent) { errors.push("claudeContent is required"); return { valid: false, errors }; } if (typeof claudeContent !== 'object') { errors.push(`claudeContent must be an object, received: ${typeof claudeContent}`); return { valid: false, errors }; } // DYNAMIC FORMAT DETECTION const formatDetection = this.detectContentFormat(claudeContent); if (!formatDetection.recognized) { errors.push(`Content format not recognized. Detected keys: [${Object.keys(claudeContent).join(', ')}]. Expected educational content with structure like: title, sections, content, etc.`); return { valid: false, errors }; } console.error(`[CONTENT_ANALYZER_V110] Detected format: ${formatDetection.format}`); console.error(`[CONTENT_ANALYZER_V110] Format confidence: ${formatDetection.confidence}`); // DYNAMIC NORMALIZATION based on detected format const normalizedContent = this.normalizeContentDynamically(claudeContent, formatDetection); // STRUCTURE VALIDATION if (normalizedContent.sections.length === 0) { errors.push("No analyzable content sections found. Content appears to be empty or unstructured."); return { valid: false, errors }; } if (normalizedContent.totalCharacters < 50) { errors.push(`Content too short for analysis. Found ${normalizedContent.totalCharacters} characters, minimum 50 required.`); return { valid: false, errors }; } console.error(`[CONTENT_ANALYZER_V110] Validation passed: ${normalizedContent.sections.length} sections, ${normalizedContent.totalCharacters} characters`); return { valid: true, claudeContent, normalizedContent, errors: [] }; } /** * DYNAMIC FORMAT DETECTION - Adapts to any educational content structure * This preserves the strategic differential while ensuring reliability */ detectContentFormat(claudeContent) { const keys = Object.keys(claudeContent); let format = 'unknown'; let confidence = 0; let recognized = false; // Structured sections format (most common from Claude Desktop) if (claudeContent.sections && Array.isArray(claudeContent.sections)) { format = 'structured_sections'; confidence = 0.9; recognized = true; } // Legacy contentSections format else if (claudeContent.contentSections && Array.isArray(claudeContent.contentSections)) { format = 'content_sections'; confidence = 0.8; recognized = true; } // Object-based content format else if (claudeContent.content && typeof claudeContent.content === 'object') { format = 'object_content'; confidence = 0.7; recognized = true; } // String-based content format else if (claudeContent.content && typeof claudeContent.content === 'string') { format = 'string_content'; confidence = 0.6; recognized = true; } // Flat educational object (title + direct properties) else if (claudeContent.title && Object.keys(claudeContent).length > 2) { format = 'flat_educational'; confidence = 0.5; recognized = true; } // Minimal content (just text fields) else if (keys.some(key => typeof claudeContent[key] === 'string' && claudeContent[key].length > 20)) { format = 'minimal_text'; confidence = 0.4; recognized = true; } return { format, confidence, recognized, keys }; } /** * DYNAMIC CONTENT NORMALIZATION - Adapts to detected format * Preserves all content while creating consistent internal structure */ normalizeContentDynamically(claudeContent, formatDetection) { const { format } = formatDetection; const sections = []; const adaptations = []; let totalCharacters = 0; console.error(`[CONTENT_ANALYZER_V110] Normalizing format: ${format}`); switch (format) { case 'structured_sections': this.normalizeStructuredSections(claudeContent, sections, adaptations); break; case 'content_sections': this.normalizeContentSections(claudeContent, sections, adaptations); break; case 'object_content': this.normalizeObjectContent(claudeContent, sections, adaptations); break; case 'string_content': this.normalizeStringContent(claudeContent, sections, adaptations); break; case 'flat_educational': this.normalizeFlatEducational(claudeContent, sections, adaptations); break; case 'minimal_text': this.normalizeMinimalText(claudeContent, sections, adaptations); break; default: throw new Error(`Unsupported content format: ${format}`); } // Calculate total characters totalCharacters = sections.reduce((sum, section) => sum + section.content.length, 0); // Add metadata sections if present this.addMetadataSections(claudeContent, sections, adaptations); return { sections, adaptations, totalCharacters, detectedFormat: format }; } /** * Normalize structured sections format (Claude Desktop standard) */ normalizeStructuredSections(claudeContent, sections, adaptations) { adaptations.push("Processed structured sections array"); claudeContent.sections.forEach((section, index) => { const sectionContent = this.extractSectionContent(section); const sectionType = this.detectSectionType(section); sections.push({ id: `section_${index}`, title: section.title || `Section ${index + 1}`, content: sectionContent, type: sectionType, originalData: section, position: index }); }); } /** * Normalize content sections format (legacy) */ normalizeContentSections(claudeContent, sections, adaptations) { adaptations.push("Processed legacy contentSections array"); claudeContent.contentSections.forEach((section, index) => { const sectionContent = this.extractSectionContent(section); const sectionType = this.detectSectionType(section); sections.push({ id: `content_section_${index}`, title: section.title || `Content Section ${index + 1}`, content: sectionContent, type: sectionType, originalData: section, position: index }); }); } /** * Normalize object-based content */ normalizeObjectContent(claudeContent, sections, adaptations) { adaptations.push("Processed structured object content"); const objectContent = claudeContent.content; let sectionIndex = 0; // Process main content object Object.entries(objectContent).forEach(([key, value]) => { if (this.isContentProperty(key, value)) { const content = this.extractValueContent(value); if (content.trim().length > 0) { sections.push({ id: `obj_section_${sectionIndex++}`, title: this.formatPropertyTitle(key), content: content, type: this.inferTypeFromKey(key), originalData: { key, value }, position: sectionIndex }); } } }); } /** * Normalize string-based content */ normalizeStringContent(claudeContent, sections, adaptations) { adaptations.push("Processed string content with markdown parsing"); const content = claudeContent.content; const parsedSections = this.parseMarkdownToSections(content); parsedSections.forEach((section, index) => { sections.push({ id: `string_section_${index}`, title: section.title, content: section.content, type: section.type, originalData: section, position: index }); }); } /** * Normalize flat educational object */ normalizeFlatEducational(claudeContent, sections, adaptations) { adaptations.push("Processed flat educational object"); let sectionIndex = 0; Object.entries(claudeContent).forEach(([key, value]) => { if (key !== 'title' && this.isContentProperty(key, value)) { const content = this.extractValueContent(value); if (content.trim().length > 0) { sections.push({ id: `flat_section_${sectionIndex++}`, title: this.formatPropertyTitle(key), content: content, type: this.inferTypeFromKey(key), originalData: { key, value }, position: sectionIndex }); } } }); } /** * Normalize minimal text content */ normalizeMinimalText(claudeContent, sections, adaptations) { adaptations.push("Processed minimal text content"); let sectionIndex = 0; Object.entries(claudeContent).forEach(([key, value]) => { if (typeof value === 'string' && value.length > 20) { sections.push({ id: `minimal_section_${sectionIndex++}`, title: this.formatPropertyTitle(key), content: value, type: this.inferTypeFromKey(key), originalData: { key, value }, position: sectionIndex }); } }); } /** * Extract content from section object */ extractSectionContent(section) { let content = ''; // Direct content property if (section.content) { if (typeof section.content === 'string') { content += section.content; } else if (typeof section.content === 'object') { content += this.extractValueContent(section.content); } } // Add vocabulary terms if (section.terms && Array.isArray(section.terms)) { section.terms.forEach(term => { content += `\n${term.term}: ${term.definition}`; }); } // Add questions if (section.questions && Array.isArray(section.questions)) { section.questions.forEach(q => { content += `\n${q.question}`; if (q.options) { q.options.forEach(opt => content += `\n- ${opt}`); } }); } return content.trim(); } /** * Detect section type from section data */ detectSectionType(section) { // Direct type property if (section.type) { return this.mapSectionTypeToContentType(section.type); } // Detect from data structures if (section.terms && Array.isArray(section.terms)) { return 'vocabulary'; } if (section.questions && Array.isArray(section.questions)) { return 'assessment'; } // Detect from title/content const title = (section.title || '').toLowerCase(); const content = (section.content || '').toLowerCase(); if (title.includes('quiz') || title.includes('avaliação') || content.includes('questão')) { return 'assessment'; } if (title.includes('vocabulário') || title.includes('terms')) { return 'vocabulary'; } if (title.includes('introdução') || title.includes('objetivo')) { return 'introduction'; } return 'explanatory'; } /** * Check if property contains educational content */ isContentProperty(key, value) { if (typeof value !== 'string' && typeof value !== 'object') { return false; } if (typeof value === 'string' && value.length < 10) { return false; } // Skip metadata properties const metadataKeys = ['id', 'timestamp', 'version', 'metadata', 'config']; if (metadataKeys.includes(key.toLowerCase())) { return false; } return true; } /** * Extract text content from various value types */ extractValueContent(value) { if (typeof value === 'string') { return value; } if (Array.isArray(value)) { return value.map(item => this.extractValueContent(item)).join('\n'); } if (typeof value === 'object' && value !== null) { return Object.values(value).map(item => this.extractValueContent(item)).join('\n'); } return String(value); } /** * Format property key as section title */ formatPropertyTitle(key) { return key .replace(/([A-Z])/g, ' $1') .replace(/^./, str => str.toUpperCase()) .trim(); } /** * Infer content type from property key */ inferTypeFromKey(key) { const keyLower = key.toLowerCase(); if (keyLower.includes('intro') || keyLower.includes('objetivo')) { return 'introduction'; } if (keyLower.includes('vocab') || keyLower.includes('terms')) { return 'vocabulary'; } if (keyLower.includes('quiz') || keyLower.includes('assessment') || keyLower.includes('questions')) { return 'assessment'; } if (keyLower.includes('conclusion') || keyLower.includes('summary')) { return 'conclusion'; } return 'explanatory'; } /** * Parse markdown content into sections */ parseMarkdownToSections(content) { const sections = []; const lines = content.split('\n'); let currentSection = null; let sectionIndex = 0; lines.forEach(line => { const trimmed = line.trim(); if (this.isMarkdownHeader(trimmed)) { // Save previous section if (currentSection && currentSection.content.trim()) { sections.push(currentSection); } // Start new section currentSection = { title: trimmed.replace(/^#+\s*/, ''), content: '', type: 'explanatory' }; } else if (currentSection) { currentSection.content += line + '\n'; } else if (trimmed) { // No section started, create default currentSection = { title: 'Introduction', content: line + '\n', type: 'introduction' }; } }); // Don't forget last section if (currentSection && currentSection.content.trim()) { sections.push(currentSection); } return sections; } /** * Check if line is markdown header */ isMarkdownHeader(line) { return line.match(/^#+\s+/) !== null; } /** * Add metadata sections from title, objectives, etc. */ addMetadataSections(claudeContent, sections, adaptations) { // Add title as header if present if (claudeContent.title) { sections.unshift({ id: 'metadata_title', title: claudeContent.title, content: claudeContent.title, type: 'header', originalData: { source: 'title' }, position: -1 }); adaptations.push("Added title as header section"); } } /** * Analyze content structure (simplified for v1.1.0) */ analyzeContentStructure(normalizedContent) { const sections = normalizedContent.sections.map(section => ({ id: section.id, content: section.content, type: section.type, position: section.position, length: section.content.length, title: section.title })); return { sections }; } /** * Generate widget recommendations based on content analysis */ generateWidgetRecommendations(contentStructure) { const recommendations = []; // Always add header recommendations.push({ widgetType: "head-1", confidence: 1.0, contentSegments: [], content: "Professional lesson header", rationale: "Professional lesson header required", educationalGoal: "organization", priority: 0 }); // Analyze each section for widget recommendations contentStructure.sections.forEach((section, index) => { const widgetType = this.mapContentToWidget(section.type, section.content); if (widgetType) { recommendations.push({ widgetType: widgetType, confidence: this.calculateConfidence(section.type, section.content), contentSegments: [section.id], content: section.content, rationale: this.getRationale(widgetType, section.type), educationalGoal: this.getEducationalGoal(widgetType), priority: index + 1 }); } }); console.error(`[CONTENT_ANALYZER_V110] Generated ${recommendations.length} widget recommendations`); return recommendations; } /** * Map content type to appropriate widget */ mapContentToWidget(contentType, content) { const mapping = { 'header': 'head-1', 'introduction': 'text-1', 'explanatory': 'text-1', 'vocabulary': 'flashcards-1', 'assessment': 'quiz-1', 'conclusion': 'text-1', 'list': 'list-1' }; return mapping[contentType] || 'text-1'; } /** * Calculate confidence score for widget recommendation */ calculateConfidence(contentType, content) { const baseConfidence = { 'header': 1.0, 'introduction': 0.9, 'explanatory': 0.8, 'vocabulary': 0.9, 'assessment': 0.95, 'conclusion': 0.85, 'list': 0.9 }; let confidence = baseConfidence[contentType] || 0.75; // Adjust based on content characteristics if (content.length < 50) confidence -= 0.1; if (content.length > 300) confidence += 0.05; return Math.max(0.5, Math.min(1.0, confidence)); } /** * Get rationale for widget selection */ getRationale(widgetType, contentType) { const rationales = { 'head-1': 'Professional lesson header required', 'text-1': 'Text content detected - suitable for reading and explanation', 'flashcards-1': 'Vocabulary content detected - perfect for memorization', 'quiz-1': 'Assessment content detected - ideal for knowledge testing', 'list-1': 'List content detected - structured presentation needed' }; return rationales[widgetType] || 'Content analysis suggests this widget type'; } /** * Get educational goal for widget type */ getEducationalGoal(widgetType) { const goals = { 'head-1': 'organization', 'text-1': 'knowledge_transfer', 'flashcards-1': 'concept_memorization', 'quiz-1': 'assessment', 'list-1': 'information_organization' }; return goals[widgetType] || 'learning_support'; } /** * Analyze educational flow (simplified for v1.1.0) */ analyzeEducationalFlow(recommendations) { const widgetTypes = recommendations.map(r => r.widgetType); return { hasIntroduction: widgetTypes.includes('head-1'), hasMainContent: widgetTypes.includes('text-1'), hasInteractivity: widgetTypes.includes('flashcards-1') || widgetTypes.includes('quiz-1'), hasAssessment: widgetTypes.includes('quiz-1'), score: widgetTypes.length * 20, // Simple scoring recommendations: [] }; } /** * Generate optimization suggestions (simplified for v1.1.0) */ generateOptimizations(educationalFlow, recommendations) { const optimizations = []; if (!educationalFlow.hasInteractivity) { optimizations.push({ type: 'interactivity', message: 'Consider adding interactive elements for better engagement', severity: 'suggestion' }); } return optimizations; } /** * Create final widget type selection summary */ createWidgetSelection(recommendations) { const widgetCounts = {}; const widgetInfo = {}; recommendations.forEach(rec => { const type = rec.widgetType; widgetCounts[type] = (widgetCounts[type] || 0) + 1; if (!widgetInfo[type]) { widgetInfo[type] = { confidence: rec.confidence, rationale: rec.rationale }; } }); return Object.keys(widgetCounts).map(type => ({ type: type, count: widgetCounts[type], confidence: widgetInfo[type].confidence, usageRationale: widgetInfo[type].rationale })); } /** * Calculate average confidence across recommendations */ calculateAverageConfidence(recommendations) { if (recommendations.length === 0) return 0; const sum = recommendations.reduce((acc, rec) => acc + rec.confidence, 0); return sum / recommendations.length; } /** * Map section types to content types */ mapSectionTypeToContentType(sectionType) { const typeMap = { 'introduction': 'introduction', 'concept_explanation': 'explanatory', 'main_content': 'explanatory', 'vocabulary': 'vocabulary', 'assessment': 'assessment', 'quiz': 'assessment', 'conclusion': 'conclusion' }; return typeMap[sectionType] || 'explanatory'; } /** * Initialize content type patterns */ initializeContentTypes() { return { introduction: ['introdução', 'objetivo', 'vamos', 'começar'], vocabulary: ['vocabulário', 'termos', 'definições'], assessment: ['quiz', 'avaliação', 'questões', 'perguntas'], explanatory: ['explicação', 'conceito', 'processo'], conclusion: ['conclusão', 'resumo', 'fechamento'] }; } /** * Initialize widget mapping rules */ initializeWidgetMappings() { return { introduction: { primary: 'text-1', alternatives: ['head-1'] }, vocabulary: { primary: 'flashcards-1', alternatives: ['text-1'] }, assessment: { primary: 'quiz-1', alternatives: ['text-1'] }, explanatory: { primary: 'text-1', alternatives: [] }, conclusion: { primary: 'text-1', alternatives: [] } }; } } /** * Create and export the tool instance for JIT server integration */ export function createContentWidgetAnalyzerV110() { const analyzer = new ContentWidgetAnalyzerV110(); return { name: 'analyze_content_for_widgets_v110', description: 'STEP 2 v1.1.0: Reliably analyze any educational content format with fail-fast validation and dynamic adaptation. Preserves strategic differential while ensuring robust structure handling.', inputSchema: { type: 'object', properties: { claudeContent: { type: 'object', description: 'Natural educational content in any format - dynamically adapts to structure' } }, required: ['claudeContent'] }, handler: async (input) => { return await analyzer.analyzeContent(input); } }; }

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