Class_generator/js/core/content-generators.js
StillHammer b004382cee Initial commit: Interactive English Learning Platform
- Complete SPA architecture with dynamic module loading
- 9 different educational games (whack-a-mole, memory, quiz, etc.)
- Rich content system supporting multimedia (audio, images, video)
- Chinese study mode with character recognition
- Adaptive game system based on available content
- Content types: vocabulary, grammar, poems, fill-blanks, corrections
- AI-powered text evaluation for open-ended answers
- Flexible content schema with backward compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-15 14:25:13 +08:00

864 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// === GÉNÉRATEURS AUTOMATIQUES D'EXERCICES ===
// === GÉNÉRATEUR DE VOCABULAIRE ===
class VocabularyGenerator {
async generate(parsedContent, options = {}) {
console.log('📚 VocabularyGenerator - Génération exercices vocabulaire');
const exercises = [];
const vocabulary = parsedContent.vocabulary || [];
vocabulary.forEach((word, index) => {
const exercise = this.createVocabularyExercise(word, index, options);
exercises.push(exercise);
});
// Générer des exercices supplémentaires si demandé
if (options.generateVariations) {
exercises.push(...this.generateVariations(vocabulary, options));
}
return exercises;
}
createVocabularyExercise(word, index, options) {
return {
id: `vocab_${index + 1}`,
type: 'vocabulary',
difficulty: this.inferDifficulty(word),
category: word.category || 'general',
content: {
english: word.english,
french: word.french,
phonetic: word.phonetic || this.generatePhonetic(word.english),
context: word.context || word.category || '',
tags: this.generateTags(word)
},
media: {
image: word.image || null,
audio: word.audio || null,
icon: this.getIconForWord(word.english, word.category)
},
pedagogy: {
learningObjective: `Apprendre le mot "${word.english}"`,
prerequisites: [],
followUp: this.suggestFollowUp(word),
grammarFocus: 'vocabulary'
},
interaction: {
type: options.interactionType || this.selectBestInteraction(word),
validation: 'exact',
hints: this.generateHints(word),
feedback: {
correct: `Parfait ! "${word.english}" = "${word.french}" ${this.getIconForWord(word.english)}`,
incorrect: `Non, "${word.english}" signifie "${word.french}"`
},
alternatives: this.generateAlternatives(word)
}
};
}
inferDifficulty(word) {
const length = word.english.length;
const complexity = this.calculateComplexity(word.english);
if (length <= 4 && complexity < 2) return 'easy';
if (length <= 8 && complexity < 3) return 'medium';
return 'hard';
}
calculateComplexity(word) {
let complexity = 0;
if (word.includes('th')) complexity++;
if (word.includes('gh')) complexity++;
if (word.match(/[aeiou]{2,}/)) complexity++;
if (word.split('').some(c => 'xyz'.includes(c))) complexity++;
return complexity;
}
generateTags(word) {
const tags = [word.category || 'general'];
// Tags basés sur la longueur
if (word.english.length <= 4) tags.push('short');
else if (word.english.length >= 8) tags.push('long');
// Tags basés sur la catégorie
if (word.category) {
tags.push(word.category);
if (['cat', 'dog', 'bird'].includes(word.english)) tags.push('pet');
if (['red', 'blue', 'green'].includes(word.english)) tags.push('color');
}
return tags;
}
getIconForWord(word, category) {
const wordIcons = {
cat: '🐱', dog: '🐕', bird: '🐦', fish: '🐠',
apple: '🍎', banana: '🍌', orange: '🍊',
car: '🚗', house: '🏠', school: '🏫',
book: '📚', pen: '✏️', pencil: '✏️'
};
if (wordIcons[word.toLowerCase()]) {
return wordIcons[word.toLowerCase()];
}
const categoryIcons = {
animals: '🐾', food: '🍎', transport: '🚗',
family: '👨‍👩‍👧‍👦', colors: '🎨', numbers: '🔢'
};
return categoryIcons[category] || '📝';
}
selectBestInteraction(word) {
// Sélection intelligente du type d'interaction
if (word.category === 'actions') return 'gesture';
if (word.english.length > 8) return 'type';
if (word.image) return 'drag_drop';
return 'click';
}
generateHints(word) {
const hints = [];
// Hint basé sur la catégorie
if (word.category === 'animals') hints.push("C'est un animal");
if (word.category === 'food') hints.push("On peut le manger");
if (word.category === 'colors') hints.push("C'est une couleur");
// Hint basé sur la longueur
hints.push(`Ce mot a ${word.english.length} lettres`);
// Hint basé sur la première lettre
hints.push(`Commence par la lettre "${word.english[0].toUpperCase()}"`);
return hints.slice(0, 3); // Max 3 hints
}
generateAlternatives(word) {
// Générer des alternatives plausibles pour QCM
const alternatives = [];
// Pour l'instant, alternatives génériques
// Dans une vraie implémentation, on utiliserait une base de données
const commonWords = {
animals: ['chien', 'oiseau', 'poisson', 'lapin'],
food: ['pomme', 'banane', 'orange', 'pain'],
colors: ['rouge', 'bleu', 'vert', 'jaune'],
family: ['mère', 'père', 'frère', 'sœur']
};
const categoryAlts = commonWords[word.category] || ['mot1', 'mot2', 'mot3'];
// Prendre 3 alternatives qui ne sont pas la bonne réponse
return categoryAlts.filter(alt => alt !== word.french).slice(0, 3);
}
suggestFollowUp(word) {
const followUp = [];
if (word.category === 'animals') {
followUp.push('pets', 'farm_animals', 'wild_animals');
} else if (word.category === 'family') {
followUp.push('family_relationships', 'family_activities');
}
return followUp;
}
generateVariations(vocabulary, options) {
const variations = [];
// Générer des exercices de synonymes/antonymes
// Générer des exercices de catégorisation
// Générer des exercices de construction de phrases
return variations; // Pour l'instant vide
}
generatePhonetic(word) {
// Génération basique de phonétique
// Dans une vraie implémentation, utiliser une API de phonétique
const basicPhonetic = {
cat: '/kæt/', dog: '/dɔg/', bird: '/bɜrd/',
apple: '/ˈæpəl/', house: '/haʊs/', book: '/bʊk/'
};
return basicPhonetic[word.toLowerCase()] || `/${word}/`;
}
}
// === GÉNÉRATEUR DE PHRASES ===
class SentenceGenerator {
async generate(parsedContent, options = {}) {
console.log('📖 SentenceGenerator - Génération exercices phrases');
const exercises = [];
const sentences = parsedContent.sentences || [];
sentences.forEach((sentence, index) => {
const exercise = this.createSentenceExercise(sentence, index, options);
exercises.push(exercise);
});
return exercises;
}
createSentenceExercise(sentence, index, options) {
return {
id: `sent_${index + 1}`,
type: 'sentence',
difficulty: this.inferSentenceDifficulty(sentence),
category: sentence.category || 'general',
content: {
english: sentence.english,
french: sentence.french || this.suggestTranslation(sentence.english),
structure: this.analyzeSentenceStructure(sentence.english),
context: sentence.context || 'general',
tags: this.generateSentenceTags(sentence)
},
media: {
icon: this.getSentenceIcon(sentence)
},
pedagogy: {
learningObjective: `Comprendre et construire la phrase "${sentence.english}"`,
prerequisites: this.extractPrerequisites(sentence.english),
followUp: ['complex_sentences', 'questions'],
grammarFocus: this.identifyGrammarFocus(sentence.english)
},
interaction: {
type: 'build_sentence',
validation: 'word_order',
hints: this.generateSentenceHints(sentence),
feedback: {
correct: `Excellent ! Tu as construit la phrase correctement ! ✨`,
incorrect: `Essaie de remettre les mots dans le bon ordre`
}
}
};
}
inferSentenceDifficulty(sentence) {
const wordCount = sentence.english.split(' ').length;
const structure = sentence.structure || {};
if (wordCount <= 4 && !structure.hasQuestion) return 'easy';
if (wordCount <= 8) return 'medium';
return 'hard';
}
analyzeSentenceStructure(sentence) {
return {
words: sentence.split(' '),
wordCount: sentence.split(' ').length,
hasQuestion: sentence.includes('?'),
hasExclamation: sentence.includes('!'),
hasComma: sentence.includes(','),
startsWithCapital: /^[A-Z]/.test(sentence),
tense: this.detectTense(sentence)
};
}
detectTense(sentence) {
if (sentence.includes(' am ') || sentence.includes(' is ') || sentence.includes(' are ')) {
if (sentence.includes('ing')) return 'present_continuous';
return 'present_simple';
}
if (sentence.includes(' was ') || sentence.includes(' were ')) return 'past_simple';
if (sentence.includes(' will ')) return 'future_simple';
return 'present_simple';
}
extractPrerequisites(sentence) {
const prerequisites = [];
const words = sentence.toLowerCase().split(' ');
// Extraire les mots de vocabulaire importants
const importantWords = words.filter(word =>
word.length > 3 &&
!['this', 'that', 'with', 'from', 'they', 'have', 'been'].includes(word)
);
return importantWords.slice(0, 3);
}
identifyGrammarFocus(sentence) {
if (sentence.includes('?')) return 'questions';
if (sentence.includes(' my ') || sentence.includes(' your ')) return 'possessives';
if (sentence.includes(' is ') || sentence.includes(' are ')) return 'be_verb';
if (sentence.includes('ing')) return 'present_continuous';
return 'sentence_structure';
}
generateSentenceTags(sentence) {
const tags = ['sentence'];
if (sentence.english.includes('?')) tags.push('question');
if (sentence.english.includes('!')) tags.push('exclamation');
if (sentence.english.toLowerCase().includes('hello')) tags.push('greeting');
if (sentence.english.toLowerCase().includes('my name')) tags.push('introduction');
return tags;
}
getSentenceIcon(sentence) {
if (sentence.english.includes('?')) return '❓';
if (sentence.english.includes('!')) return '❗';
if (sentence.english.toLowerCase().includes('hello')) return '👋';
return '💬';
}
generateSentenceHints(sentence) {
const hints = [];
hints.push(`La phrase a ${sentence.english.split(' ').length} mots`);
if (sentence.english.includes('?')) {
hints.push("C'est une question");
}
const firstWord = sentence.english.split(' ')[0];
hints.push(`Commence par "${firstWord}"`);
return hints;
}
suggestTranslation(english) {
// Traduction basique pour exemples
const basicTranslations = {
'Hello': 'Bonjour',
'My name is': 'Je m\'appelle',
'I am': 'Je suis',
'How are you?': 'Comment allez-vous ?',
'Thank you': 'Merci'
};
for (const [en, fr] of Object.entries(basicTranslations)) {
if (english.includes(en)) {
return english.replace(en, fr);
}
}
return ''; // À traduire manuellement
}
}
// === GÉNÉRATEUR DE DIALOGUES ===
class DialogueGenerator {
async generate(parsedContent, options = {}) {
console.log('💬 DialogueGenerator - Génération exercices dialogues');
const exercises = [];
if (parsedContent.dialogue) {
const exercise = this.createDialogueExercise(parsedContent.dialogue, 0, options);
exercises.push(exercise);
}
if (parsedContent.conversations) {
parsedContent.conversations.forEach((conversation, index) => {
const exercise = this.createConversationExercise(conversation, index, options);
exercises.push(exercise);
});
}
return exercises;
}
createDialogueExercise(dialogue, index, options) {
return {
id: `dial_${index + 1}`,
type: 'dialogue',
difficulty: this.inferDialogueDifficulty(dialogue),
category: dialogue.scenario || 'conversation',
content: {
scenario: dialogue.scenario || 'conversation',
english: this.extractDialogueTitle(dialogue),
french: this.translateScenario(dialogue.scenario),
conversation: dialogue.conversation.map(line => ({
speaker: line.speaker,
english: line.english || line.text,
french: line.french || this.suggestTranslation(line.english || line.text),
role: this.identifyRole(line)
})),
context: dialogue.scenario || 'general',
tags: this.generateDialogueTags(dialogue)
},
media: {
icon: this.getDialogueIcon(dialogue.scenario)
},
pedagogy: {
learningObjective: `Participer à un dialogue : ${dialogue.scenario}`,
prerequisites: this.extractDialoguePrerequisites(dialogue),
followUp: ['advanced_dialogue', 'role_play'],
grammarFocus: 'conversation_patterns'
},
interaction: {
type: 'role_play',
validation: 'dialogue_flow',
userRole: this.selectUserRole(dialogue),
hints: this.generateDialogueHints(dialogue),
feedback: {
correct: `Parfait ! Tu maîtrises ce type de conversation ! 🎭`,
incorrect: `Essaie de suivre le flow naturel de la conversation`
}
}
};
}
createConversationExercise(conversation, index, options) {
return {
id: `conv_${index + 1}`,
type: 'dialogue',
difficulty: 'medium',
category: conversation.scene || 'conversation',
content: {
scenario: conversation.scene || `Conversation ${index + 1}`,
english: `${conversation.speaker}: ${conversation.english}`,
french: `${conversation.speaker}: ${conversation.french}`,
conversation: [{
speaker: conversation.speaker,
english: conversation.english,
french: conversation.french,
role: 'statement'
}],
tags: ['conversation', 'dialogue']
},
interaction: {
type: 'click',
validation: 'exact'
}
};
}
inferDialogueDifficulty(dialogue) {
const conversationLength = dialogue.conversation?.length || 1;
const averageWordsPerLine = dialogue.conversation?.reduce((sum, line) =>
sum + (line.english || line.text || '').split(' ').length, 0) / conversationLength || 5;
if (conversationLength <= 2 && averageWordsPerLine <= 5) return 'easy';
if (conversationLength <= 4 && averageWordsPerLine <= 8) return 'medium';
return 'hard';
}
extractDialogueTitle(dialogue) {
if (dialogue.title) return dialogue.title;
if (dialogue.scenario) return this.beautifyScenario(dialogue.scenario);
return 'Dialogue';
}
beautifyScenario(scenario) {
const scenarios = {
'greeting': 'Salutations',
'restaurant': 'Au Restaurant',
'shopping': 'Faire les Courses',
'school': 'À l\'École',
'family': 'En Famille'
};
return scenarios[scenario] || scenario;
}
translateScenario(scenario) {
const translations = {
'greeting': 'Se saluer',
'restaurant': 'Commander au restaurant',
'shopping': 'Acheter quelque chose',
'school': 'Parler à l\'école',
'family': 'Conversation familiale'
};
return translations[scenario] || scenario;
}
identifyRole(line) {
const text = (line.english || line.text || '').toLowerCase();
if (text.includes('?')) return 'question';
if (text.includes('hello') || text.includes('hi')) return 'greeting';
if (text.includes('thank')) return 'thanks';
if (text.includes('goodbye') || text.includes('bye')) return 'farewell';
return 'statement';
}
generateDialogueTags(dialogue) {
const tags = ['dialogue', 'conversation'];
if (dialogue.scenario) tags.push(dialogue.scenario);
const hasQuestion = dialogue.conversation?.some(line =>
(line.english || line.text || '').includes('?'));
if (hasQuestion) tags.push('questions');
return tags;
}
getDialogueIcon(scenario) {
const icons = {
'greeting': '👋',
'restaurant': '🍽️',
'shopping': '🛒',
'school': '🎒',
'family': '👨‍👩‍👧‍👦',
'friends': '👫'
};
return icons[scenario] || '💬';
}
selectUserRole(dialogue) {
// Sélectionner le rôle que l'utilisateur va jouer
const speakers = dialogue.conversation?.map(line => line.speaker) || [];
const uniqueSpeakers = [...new Set(speakers)];
// Privilégier certains rôles
if (uniqueSpeakers.includes('student')) return 'student';
if (uniqueSpeakers.includes('child')) return 'child';
if (uniqueSpeakers.includes('customer')) return 'customer';
// Sinon, prendre le premier
return uniqueSpeakers[0] || 'person1';
}
extractDialoguePrerequisites(dialogue) {
const prerequisites = new Set();
dialogue.conversation?.forEach(line => {
const text = line.english || line.text || '';
const words = text.toLowerCase().split(' ');
// Extraire mots importants
words.forEach(word => {
if (word.length > 3 && !['this', 'that', 'with', 'they', 'have'].includes(word)) {
prerequisites.add(word);
}
});
});
return Array.from(prerequisites).slice(0, 5);
}
generateDialogueHints(dialogue) {
const hints = [];
hints.push(`Cette conversation a ${dialogue.conversation?.length || 1} répliques`);
if (dialogue.scenario) {
hints.push(`Le contexte est : ${dialogue.scenario}`);
}
const hasQuestions = dialogue.conversation?.some(line =>
(line.english || line.text || '').includes('?'));
if (hasQuestions) {
hints.push('Il y a des questions dans cette conversation');
}
return hints;
}
suggestTranslation(english) {
// Utiliser le même système que SentenceGenerator
return new SentenceGenerator().suggestTranslation(english);
}
}
// === GÉNÉRATEUR DE SÉQUENCES ===
class SequenceGenerator {
async generate(parsedContent, options = {}) {
console.log('📋 SequenceGenerator - Génération exercices séquences');
const exercises = [];
if (parsedContent.sequence) {
const exercise = this.createSequenceExercise(parsedContent.sequence, 0, options);
exercises.push(exercise);
}
return exercises;
}
createSequenceExercise(sequence, index, options) {
return {
id: `seq_${index + 1}`,
type: 'sequence',
difficulty: this.inferSequenceDifficulty(sequence),
category: this.inferSequenceCategory(sequence),
content: {
english: sequence.title || 'Sequence',
french: this.translateSequenceTitle(sequence.title),
title: sequence.title || 'Sequence',
steps: sequence.steps.map(step => ({
order: step.order,
english: step.english,
french: step.french || this.suggestStepTranslation(step.english),
icon: this.getStepIcon(step.english),
time: this.extractTime(step)
})),
context: this.inferContext(sequence),
tags: this.generateSequenceTags(sequence)
},
media: {
icon: this.getSequenceIcon(sequence)
},
pedagogy: {
learningObjective: `Comprendre l'ordre chronologique : ${sequence.title}`,
prerequisites: this.extractSequencePrerequisites(sequence),
followUp: ['time_expressions', 'daily_routine'],
grammarFocus: 'sequence_connectors'
},
interaction: {
type: 'chronological_order',
validation: 'sequence_correct',
hints: this.generateSequenceHints(sequence),
feedback: {
correct: `Parfait ! Tu connais l'ordre correct ! ⏰`,
incorrect: `Réfléchis à l'ordre logique des étapes`
}
}
};
}
inferSequenceDifficulty(sequence) {
const stepCount = sequence.steps?.length || 1;
const avgWordsPerStep = sequence.steps?.reduce((sum, step) =>
sum + step.english.split(' ').length, 0) / stepCount || 3;
if (stepCount <= 3 && avgWordsPerStep <= 4) return 'easy';
if (stepCount <= 5 && avgWordsPerStep <= 6) return 'medium';
return 'hard';
}
inferSequenceCategory(sequence) {
const title = sequence.title?.toLowerCase() || '';
const allText = sequence.steps?.map(s => s.english.toLowerCase()).join(' ') || '';
if (title.includes('morning') || allText.includes('wake up') || allText.includes('breakfast')) {
return 'daily_routine';
}
if (title.includes('recipe') || allText.includes('cook') || allText.includes('mix')) {
return 'cooking';
}
if (title.includes('story') || allText.includes('once upon')) {
return 'story';
}
return 'sequence';
}
translateSequenceTitle(title) {
const translations = {
'Morning Routine': 'Routine du Matin',
'Getting Ready': 'Se Préparer',
'Cooking Recipe': 'Recette de Cuisine',
'Story': 'Histoire'
};
return translations[title] || title;
}
suggestStepTranslation(english) {
const stepTranslations = {
'Wake up': 'Se réveiller',
'Get dressed': 'S\'habiller',
'Eat breakfast': 'Prendre le petit-déjeuner',
'Go to school': 'Aller à l\'école',
'Brush teeth': 'Se brosser les dents'
};
return stepTranslations[english] || '';
}
getStepIcon(stepText) {
const icons = {
'wake up': '⏰', 'get dressed': '👕', 'breakfast': '🥞',
'school': '🎒', 'brush': '🪥', 'wash': '🧼',
'cook': '🍳', 'mix': '🥄', 'bake': '🔥'
};
const lowerText = stepText.toLowerCase();
for (const [key, icon] of Object.entries(icons)) {
if (lowerText.includes(key)) return icon;
}
return '📝';
}
extractTime(step) {
// Extraire indication de temps du texte
const timeMatch = step.english.match(/\d{1,2}:\d{2}|\d{1,2}(am|pm)/i);
return timeMatch ? timeMatch[0] : null;
}
inferContext(sequence) {
const category = this.inferSequenceCategory(sequence);
const contexts = {
'daily_routine': 'routine quotidienne',
'cooking': 'cuisine',
'story': 'histoire',
'sequence': 'étapes'
};
return contexts[category] || 'séquence';
}
generateSequenceTags(sequence) {
const tags = ['sequence', 'chronological'];
const category = this.inferSequenceCategory(sequence);
tags.push(category);
if (sequence.steps?.some(step => this.extractTime(step))) {
tags.push('time');
}
return tags;
}
getSequenceIcon(sequence) {
const category = this.inferSequenceCategory(sequence);
const icons = {
'daily_routine': '🌅',
'cooking': '👨‍🍳',
'story': '📖',
'sequence': '📋'
};
return icons[category] || '📋';
}
extractSequencePrerequisites(sequence) {
const prerequisites = new Set();
sequence.steps?.forEach(step => {
const words = step.english.toLowerCase().split(' ');
// Verbes d'action importants
const actionVerbs = words.filter(word =>
['wake', 'get', 'eat', 'go', 'brush', 'wash', 'cook', 'mix'].some(verb =>
word.includes(verb)
)
);
actionVerbs.forEach(verb => prerequisites.add(verb));
});
return Array.from(prerequisites).slice(0, 4);
}
generateSequenceHints(sequence) {
const hints = [];
hints.push(`Cette séquence a ${sequence.steps?.length || 0} étapes`);
const category = this.inferSequenceCategory(sequence);
if (category === 'daily_routine') {
hints.push('Pense à ton ordre habituel du matin');
} else if (category === 'cooking') {
hints.push('Quelle est la logique de la recette ?');
}
const firstStep = sequence.steps?.[0];
if (firstStep) {
hints.push(`La première étape est : "${firstStep.english}"`);
}
return hints;
}
}
// === GÉNÉRATEUR AUTOMATIQUE ===
class AutoGenerator {
async generate(parsedContent, options = {}) {
console.log('🤖 AutoGenerator - Génération automatique intelligente');
const exercises = [];
// Détecter et générer selon ce qui est disponible
if (parsedContent.vocabulary?.length > 0) {
const vocabGen = new VocabularyGenerator();
const vocabExercises = await vocabGen.generate(parsedContent, options);
exercises.push(...vocabExercises);
}
if (parsedContent.sentences?.length > 0) {
const sentGen = new SentenceGenerator();
const sentExercises = await sentGen.generate(parsedContent, options);
exercises.push(...sentExercises);
}
if (parsedContent.dialogue) {
const dialogGen = new DialogueGenerator();
const dialogExercises = await dialogGen.generate(parsedContent, options);
exercises.push(...dialogExercises);
}
if (parsedContent.sequence) {
const seqGen = new SequenceGenerator();
const seqExercises = await seqGen.generate(parsedContent, options);
exercises.push(...seqExercises);
}
// Si rien de spécifique n'a été détecté, créer du contenu basique
if (exercises.length === 0) {
exercises.push(this.createFallbackExercise(parsedContent, options));
}
return exercises;
}
createFallbackExercise(parsedContent, options) {
return {
id: 'auto_001',
type: 'vocabulary',
difficulty: 'easy',
category: 'general',
content: {
english: 'text',
french: 'texte',
context: 'Contenu généré automatiquement',
tags: ['auto-generated']
},
interaction: {
type: 'click',
validation: 'exact',
feedback: {
correct: 'Bonne réponse !',
incorrect: 'Essaie encore'
}
}
};
}
}
// Export global
window.VocabularyGenerator = VocabularyGenerator;
window.SentenceGenerator = SentenceGenerator;
window.DialogueGenerator = DialogueGenerator;
window.SequenceGenerator = SequenceGenerator;
window.AutoGenerator = AutoGenerator;