Skip to main content
Glama
flashcard-generator.js21.8 kB
/** * Intelligent Flashcard Generator * @module content-generation/flashcard-generator * @description Generates contextually relevant flashcards from educational content * @version 5.0.0-alpha */ export class FlashcardGenerator { constructor(config = {}) { this.config = { minFlashcards: 8, maxFlashcards: 25, targetFlashcards: 15, difficultyBalance: { easy: 0.4, medium: 0.4, hard: 0.2 }, typeBalance: { concept: 0.4, example: 0.3, application: 0.2, relationship: 0.1 }, ...config }; // Flashcard type templates this.flashcardTypes = { concept: { template: 'definition', difficulty: 'easy', examples: ['Conceito → Definição', 'Termo → Explicação'] }, example: { template: 'illustration', difficulty: 'medium', examples: ['Conceito → Exemplo prático', 'Princípio → Aplicação'] }, application: { template: 'scenario', difficulty: 'medium', examples: ['Situação → Solução', 'Problema → Método'] }, relationship: { template: 'connection', difficulty: 'hard', examples: ['Causa → Efeito', 'Conceito A → Relação → Conceito B'] } }; } /** * Generate comprehensive flashcard set from content * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @returns {Promise<Array>} Generated flashcards */ async generateFlashcards(content, topicAnalysis) { console.log('[FLASHCARD-GENERATOR] Generating flashcards for:', topicAnalysis.subject); try { const flashcards = []; // Extract concept flashcards flashcards.push(...await this.extractConceptCards(content, topicAnalysis)); // Generate example flashcards flashcards.push(...await this.generateExampleCards(content, topicAnalysis)); // Create application flashcards flashcards.push(...await this.createApplicationCards(content, topicAnalysis)); // Build relationship flashcards flashcards.push(...await this.buildRelationshipCards(content, topicAnalysis)); // Optimize flashcard set const optimized = this.optimizeFlashcardSet(flashcards); // Add metadata const withMetadata = this.addFlashcardMetadata(optimized, topicAnalysis); console.log(`[FLASHCARD-GENERATOR] Generated ${withMetadata.length} flashcards`); return withMetadata; } catch (error) { console.error('[FLASHCARD-GENERATOR] Flashcard generation failed:', error); throw new Error(`Flashcard generation failed: ${error.message}`); } } /** * Extract concept flashcards from content * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @returns {Promise<Array>} Concept flashcards */ async extractConceptCards(content, topicAnalysis) { const conceptCards = []; const { keywords, concepts } = topicAnalysis; // Generate concept definition cards const allConcepts = [...new Set([...keywords, ...concepts])]; const targetCount = Math.floor(this.config.targetFlashcards * this.config.typeBalance.concept); for (let i = 0; i < Math.min(targetCount, allConcepts.length); i++) { const concept = allConcepts[i]; const definition = this.generateConceptDefinition(concept, topicAnalysis); conceptCards.push({ id: `concept-${i}-${Date.now()}`, type: 'concept', cardType: 'definition', front: `O que é ${concept}?`, back: definition, concept: concept, difficulty: 'easy', tags: [concept, 'conceito', 'definição'], source: 'content_extraction' }); } // Generate key term cards from content if (content.components.explanation) { const keyTerms = this.extractKeyTerms(content.components.explanation.content); keyTerms.slice(0, 3).forEach((term, index) => { conceptCards.push({ id: `term-${index}-${Date.now()}`, type: 'concept', cardType: 'term', front: `Defina: ${term}`, back: this.generateTermDefinition(term, content.components.explanation.content), concept: term, difficulty: 'easy', tags: [term, 'termo', 'vocabulário'], source: 'term_extraction' }); }); } return conceptCards; } /** * Generate example flashcards * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @returns {Promise<Array>} Example flashcards */ async generateExampleCards(content, topicAnalysis) { const exampleCards = []; const targetCount = Math.floor(this.config.targetFlashcards * this.config.typeBalance.example); // Use examples from content if (content.components.examples && content.components.examples.examples) { const examples = content.components.examples.examples; examples.slice(0, targetCount).forEach((example, index) => { exampleCards.push({ id: `example-${index}-${Date.now()}`, type: 'example', cardType: 'illustration', front: `Dê um exemplo de ${topicAnalysis.subject}`, back: example, difficulty: 'medium', tags: ['exemplo', 'prático', topicAnalysis.subject], source: 'content_examples' }); }); } // Generate subject-specific examples const subjectExamples = this.generateSubjectExamples(topicAnalysis); subjectExamples.slice(0, Math.max(0, targetCount - exampleCards.length)).forEach((example, index) => { exampleCards.push({ id: `subject-example-${index}-${Date.now()}`, type: 'example', cardType: 'subject_specific', front: example.front, back: example.back, difficulty: 'medium', tags: ['exemplo', topicAnalysis.subject, 'específico'], source: 'subject_generation' }); }); return exampleCards; } /** * Create application flashcards * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @returns {Promise<Array>} Application flashcards */ async createApplicationCards(content, topicAnalysis) { const applicationCards = []; const targetCount = Math.floor(this.config.targetFlashcards * this.config.typeBalance.application); // Generate problem-solution cards const problems = this.generateProblemScenarios(topicAnalysis); problems.slice(0, targetCount).forEach((problem, index) => { applicationCards.push({ id: `application-${index}-${Date.now()}`, type: 'application', cardType: 'scenario', front: problem.scenario, back: problem.solution, difficulty: 'medium', tags: ['aplicação', 'problema', 'solução'], source: 'scenario_generation' }); }); return applicationCards; } /** * Build relationship flashcards * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @returns {Promise<Array>} Relationship flashcards */ async buildRelationshipCards(content, topicAnalysis) { const relationshipCards = []; const targetCount = Math.floor(this.config.targetFlashcards * this.config.typeBalance.relationship); // Generate concept relationship cards const relationships = this.identifyConceptRelationships(topicAnalysis); relationships.slice(0, targetCount).forEach((relationship, index) => { relationshipCards.push({ id: `relationship-${index}-${Date.now()}`, type: 'relationship', cardType: 'connection', front: `Como ${relationship.concept1} se relaciona com ${relationship.concept2}?`, back: relationship.explanation, difficulty: 'hard', tags: ['relação', 'conexão', relationship.concept1, relationship.concept2], source: 'relationship_analysis' }); }); return relationshipCards; } /** * Generate concept definition * @param {string} concept - Concept to define * @param {Object} topicAnalysis - Topic analysis * @returns {string} Concept definition */ generateConceptDefinition(concept, topicAnalysis) { const { subject } = topicAnalysis; const definitions = { física: `${concept} é um conceito fundamental da física que descreve propriedades ou fenômenos relacionados à matéria e energia.`, química: `${concept} refere-se a aspectos químicos relacionados à estrutura, propriedades e transformações da matéria.`, história: `${concept} é um elemento histórico importante para compreender eventos, processos e transformações sociais.`, matemática: `${concept} é um conceito matemático que representa relações quantitativas e estruturas lógicas.`, geral: `${concept} é um conceito importante para compreender os aspectos fundamentais do tópico estudado.` }; return definitions[subject] || definitions.geral; } /** * Generate term definition from content * @param {string} term - Term to define * @param {string} content - Content text * @returns {string} Term definition */ generateTermDefinition(term, content) { // Simple extraction: find sentence containing the term const sentences = content.split(/[.!?]+/); const definitionSentence = sentences.find(sentence => sentence.toLowerCase().includes(term.toLowerCase()) ); if (definitionSentence) { return definitionSentence.trim() + '.'; } return `${term} é um termo importante relacionado ao tópico estudado.`; } /** * Extract key terms from content * @param {string} content - Content text * @returns {Array} Key terms */ extractKeyTerms(content) { const text = content.toLowerCase(); // Simple term extraction based on capitalized words and important patterns const terms = []; // Look for definitions patterns const definitionPatterns = [ /(\w+)\s+é\s+/g, /(\w+)\s+refere-se\s+/g, /(\w+)\s+representa\s+/g, /(\w+)\s+significa\s+/g ]; definitionPatterns.forEach(pattern => { let match; while ((match = pattern.exec(text)) !== null) { if (match[1].length > 3) { terms.push(match[1]); } } }); return [...new Set(terms)]; // Remove duplicates } /** * Generate subject-specific examples * @param {Object} topicAnalysis - Topic analysis * @returns {Array} Subject examples */ generateSubjectExamples(topicAnalysis) { const { subject } = topicAnalysis; const examples = { física: [ { front: 'Exemplo de força no cotidiano', back: 'Empurrar uma porta, puxar uma gaveta, peso dos objetos' }, { front: 'Exemplo de energia cinética', back: 'Carro em movimento, bola sendo arremessada, pessoa correndo' } ], química: [ { front: 'Exemplo de reação química cotidiana', back: 'Combustão do gás de cozinha, digestão dos alimentos, ferrugem' }, { front: 'Exemplo de mistura homogênea', back: 'Água e sal dissolvido, ar atmosférico, álcool e água' } ], história: [ { front: 'Exemplo de causa histórica', back: 'Crise econômica levando a mudanças políticas' }, { front: 'Exemplo de fonte primária', back: 'Documento da época, fotografia histórica, relato de testemunha' } ], geral: [ { front: 'Exemplo prático do conceito', back: 'Aplicação do conceito em situação real do cotidiano' } ] }; return examples[subject] || examples.geral; } /** * Generate problem scenarios * @param {Object} topicAnalysis - Topic analysis * @returns {Array} Problem scenarios */ generateProblemScenarios(topicAnalysis) { const { subject } = topicAnalysis; const scenarios = { física: [ { scenario: 'Como calcular a velocidade de um objeto em queda livre?', solution: 'Use v = gt, onde g = 9,8 m/s² e t é o tempo de queda' }, { scenario: 'Como determinar a força necessária para mover um objeto?', solution: 'Use F = ma, considerando massa e aceleração desejada' } ], química: [ { scenario: 'Como balancear uma equação química?', solution: 'Ajuste coeficientes para igualar átomos de cada elemento' }, { scenario: 'Como calcular concentração molar?', solution: 'Use M = n/V, onde n = mols de soluto e V = volume em litros' } ], história: [ { scenario: 'Como analisar um documento histórico?', solution: 'Identifique autor, data, contexto e objetivo do documento' }, { scenario: 'Como estabelecer relação de causa e efeito?', solution: 'Identifique eventos anteriores e suas consequências diretas' } ], geral: [ { scenario: 'Como aplicar este conceito na prática?', solution: 'Identifique situações relevantes e aplique os princípios aprendidos' } ] }; return scenarios[subject] || scenarios.geral; } /** * Identify concept relationships * @param {Object} topicAnalysis - Topic analysis * @returns {Array} Concept relationships */ identifyConceptRelationships(topicAnalysis) { const { concepts, keywords } = topicAnalysis; const relationships = []; // Generate relationships between concepts for (let i = 0; i < concepts.length && i < 3; i++) { for (let j = i + 1; j < concepts.length && j < 3; j++) { relationships.push({ concept1: concepts[i], concept2: concepts[j], explanation: this.generateRelationshipExplanation(concepts[i], concepts[j], topicAnalysis) }); } } return relationships; } /** * Generate relationship explanation * @param {string} concept1 - First concept * @param {string} concept2 - Second concept * @param {Object} topicAnalysis - Topic analysis * @returns {string} Relationship explanation */ generateRelationshipExplanation(concept1, concept2, topicAnalysis) { const { subject } = topicAnalysis; const templates = { física: `${concept1} e ${concept2} estão relacionados através de princípios físicos fundamentais.`, química: `${concept1} influencia ${concept2} através de processos químicos específicos.`, história: `${concept1} teve impacto direto em ${concept2} no contexto histórico estudado.`, geral: `${concept1} está conectado a ${concept2} dentro do tópico abordado.` }; return templates[subject] || templates.geral; } /** * Optimize flashcard set for quality and balance * @param {Array} flashcards - Raw flashcards * @returns {Array} Optimized flashcards */ optimizeFlashcardSet(flashcards) { // Remove duplicates const unique = this.removeDuplicates(flashcards); // Balance difficulty levels const balanced = this.balanceDifficulty(unique); // Ensure target count const sized = this.adjustToTargetSize(balanced); // Sort by importance const sorted = this.sortByImportance(sized); return sorted; } /** * Remove duplicate flashcards * @param {Array} flashcards - Flashcards array * @returns {Array} Unique flashcards */ removeDuplicates(flashcards) { const seen = new Set(); return flashcards.filter(card => { const key = `${card.front}|${card.back}`; if (seen.has(key)) { return false; } seen.add(key); return true; }); } /** * Balance difficulty levels * @param {Array} flashcards - Flashcards array * @returns {Array} Balanced flashcards */ balanceDifficulty(flashcards) { const byDifficulty = { easy: flashcards.filter(card => card.difficulty === 'easy'), medium: flashcards.filter(card => card.difficulty === 'medium'), hard: flashcards.filter(card => card.difficulty === 'hard') }; const target = this.config.targetFlashcards; const balanced = []; // Add cards according to balance configuration const easyCount = Math.floor(target * this.config.difficultyBalance.easy); const mediumCount = Math.floor(target * this.config.difficultyBalance.medium); const hardCount = Math.floor(target * this.config.difficultyBalance.hard); balanced.push(...byDifficulty.easy.slice(0, easyCount)); balanced.push(...byDifficulty.medium.slice(0, mediumCount)); balanced.push(...byDifficulty.hard.slice(0, hardCount)); return balanced; } /** * Adjust to target size * @param {Array} flashcards - Flashcards array * @returns {Array} Size-adjusted flashcards */ adjustToTargetSize(flashcards) { if (flashcards.length < this.config.minFlashcards) { // Add more cards if below minimum const needed = this.config.minFlashcards - flashcards.length; const additional = this.generateAdditionalCards(needed); return [...flashcards, ...additional]; } if (flashcards.length > this.config.maxFlashcards) { // Trim if above maximum return flashcards.slice(0, this.config.maxFlashcards); } return flashcards; } /** * Generate additional cards when needed * @param {number} count - Number of cards needed * @returns {Array} Additional flashcards */ generateAdditionalCards(count) { const additional = []; for (let i = 0; i < count; i++) { additional.push({ id: `additional-${i}-${Date.now()}`, type: 'concept', cardType: 'general', front: 'Conceito importante do tópico', back: 'Definição ou explicação relevante para o aprendizado', difficulty: 'easy', tags: ['adicional', 'conceito'], source: 'gap_filling' }); } return additional; } /** * Sort flashcards by importance * @param {Array} flashcards - Flashcards array * @returns {Array} Sorted flashcards */ sortByImportance(flashcards) { return flashcards.sort((a, b) => { // Priority order: concept > example > application > relationship const typeOrder = { concept: 0, example: 1, application: 2, relationship: 3 }; const aOrder = typeOrder[a.type] || 4; const bOrder = typeOrder[b.type] || 4; if (aOrder !== bOrder) { return aOrder - bOrder; } // Secondary sort by difficulty (easy first) const difficultyOrder = { easy: 0, medium: 1, hard: 2 }; return (difficultyOrder[a.difficulty] || 1) - (difficultyOrder[b.difficulty] || 1); }); } /** * Add metadata to flashcards * @param {Array} flashcards - Flashcards array * @param {Object} topicAnalysis - Topic analysis * @returns {Array} Flashcards with metadata */ addFlashcardMetadata(flashcards, topicAnalysis) { return flashcards.map((card, index) => ({ ...card, metadata: { position: index + 1, totalCards: flashcards.length, subject: topicAnalysis.subject, gradeLevel: topicAnalysis.gradeLevel, generatedAt: new Date().toISOString(), version: '5.0.0-alpha' } })); } /** * Calculate flashcard set statistics * @param {Array} flashcards - Flashcards array * @returns {Object} Statistics */ calculateStatistics(flashcards) { const stats = { totalCards: flashcards.length, typeDistribution: {}, difficultyDistribution: {}, averageLength: 0, qualityScore: 0 }; // Calculate distributions flashcards.forEach(card => { stats.typeDistribution[card.type] = (stats.typeDistribution[card.type] || 0) + 1; stats.difficultyDistribution[card.difficulty] = (stats.difficultyDistribution[card.difficulty] || 0) + 1; }); // Calculate average length const totalLength = flashcards.reduce((sum, card) => sum + card.front.length + card.back.length, 0 ); stats.averageLength = Math.round(totalLength / flashcards.length); // Calculate quality score stats.qualityScore = this.calculateQualityScore(flashcards); return stats; } /** * Calculate quality score for flashcard set * @param {Array} flashcards - Flashcards array * @returns {number} Quality score (0-100) */ calculateQualityScore(flashcards) { let score = 100; // Deduct for insufficient cards if (flashcards.length < this.config.minFlashcards) { score -= 30; } // Deduct for poor balance const typeCount = Object.keys( flashcards.reduce((acc, card) => ({ ...acc, [card.type]: true }), {}) ).length; if (typeCount < 2) { score -= 20; // Lack of variety } // Add for good difficulty distribution const difficultyCount = Object.keys( flashcards.reduce((acc, card) => ({ ...acc, [card.difficulty]: true }), {}) ).length; score += difficultyCount * 5; // Bonus for difficulty variety return Math.max(0, Math.min(100, score)); } } // Factory function for creating FlashcardGenerator instances export function createFlashcardGenerator(config = {}) { return new FlashcardGenerator(config); }

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