Class_generator/js/core/content-engine.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

485 lines
15 KiB
JavaScript

// === MOTEUR DE CONTENU FLEXIBLE ===
class ContentEngine {
constructor() {
this.loadedContent = {};
this.migrator = new ContentMigrator();
this.validator = new ContentValidator();
}
// Charger et traiter un module de contenu
async loadContent(contentId) {
if (this.loadedContent[contentId]) {
return this.loadedContent[contentId];
}
try {
// Charger le contenu brut
const rawContent = await this.loadRawContent(contentId);
// Valider et migrer si nécessaire
const processedContent = this.processContent(rawContent);
// Mettre en cache
this.loadedContent[contentId] = processedContent;
return processedContent;
} catch (error) {
console.error(`Erreur chargement contenu ${contentId}:`, error);
throw error;
}
}
async loadRawContent(contentId) {
// Charger depuis le module existant
const moduleName = this.getModuleName(contentId);
if (window.ContentModules && window.ContentModules[moduleName]) {
return window.ContentModules[moduleName];
}
// Charger dynamiquement le script
await this.loadScript(`js/content/${contentId}.js`);
return window.ContentModules[moduleName];
}
processContent(rawContent) {
// Vérifier le format
if (this.isOldFormat(rawContent)) {
console.log('Migration ancien format vers nouveau format...');
return this.migrator.migrateToNewFormat(rawContent);
}
// Valider le nouveau format
if (!this.validator.validate(rawContent)) {
throw new Error('Format de contenu invalide');
}
return rawContent;
}
isOldFormat(content) {
// Détecter l'ancien format (simple array vocabulary)
return content.vocabulary && Array.isArray(content.vocabulary) &&
!content.contentItems && !content.version;
}
// Filtrer le contenu par critères
filterContent(content, filters = {}) {
if (!content.contentItems) return content;
let filtered = [...content.contentItems];
// Filtrer par type
if (filters.type) {
filtered = filtered.filter(item =>
Array.isArray(filters.type) ?
filters.type.includes(item.type) :
item.type === filters.type
);
}
// Filtrer par difficulté
if (filters.difficulty) {
filtered = filtered.filter(item => item.difficulty === filters.difficulty);
}
// Filtrer par catégorie
if (filters.category) {
filtered = filtered.filter(item => item.category === filters.category);
}
// Filtrer par tags
if (filters.tags) {
filtered = filtered.filter(item =>
item.content.tags &&
filters.tags.some(tag => item.content.tags.includes(tag))
);
}
return { ...content, contentItems: filtered };
}
// Adapter le contenu pour un jeu spécifique
adaptForGame(content, gameType) {
const adapter = new GameContentAdapter(gameType);
return adapter.adapt(content);
}
// Obtenir des éléments aléatoires
getRandomItems(content, count = 10, filters = {}) {
const filtered = this.filterContent(content, filters);
const items = filtered.contentItems || [];
const shuffled = [...items].sort(() => Math.random() - 0.5);
return shuffled.slice(0, count);
}
// Obtenir des éléments par progression
getItemsByProgression(content, userLevel = 1, count = 10) {
const items = content.contentItems || [];
// Calculer la difficulté appropriée
const targetDifficulties = this.getDifficultiesForLevel(userLevel);
const appropriateItems = items.filter(item =>
targetDifficulties.includes(item.difficulty)
);
return this.shuffleArray(appropriateItems).slice(0, count);
}
getDifficultiesForLevel(level) {
if (level <= 2) return ['easy'];
if (level <= 4) return ['easy', 'medium'];
return ['easy', 'medium', 'hard'];
}
// Utilitaires
getModuleName(contentId) {
const names = {
'sbs-level-8': 'SBSLevel8',
'animals': 'Animals',
'colors': 'Colors',
'family': 'Family',
'food': 'Food',
'house': 'House'
};
return names[contentId] || contentId;
}
async loadScript(src) {
return new Promise((resolve, reject) => {
const existingScript = document.querySelector(`script[src="${src}"]`);
if (existingScript) {
resolve();
return;
}
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = () => reject(new Error(`Impossible de charger ${src}`));
document.head.appendChild(script);
});
}
shuffleArray(array) {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
}
// === MIGRATEUR DE CONTENU ===
class ContentMigrator {
migrateToNewFormat(oldContent) {
const newContent = {
id: oldContent.name || 'migrated-content',
name: oldContent.name || 'Contenu Migré',
description: oldContent.description || '',
version: '2.0',
format: 'unified',
difficulty: oldContent.difficulty || 'intermediate',
// Métadonnées
metadata: {
totalItems: oldContent.vocabulary ? oldContent.vocabulary.length : 0,
categories: this.extractCategories(oldContent),
contentTypes: ['vocabulary'],
migrationDate: new Date().toISOString()
},
// Configuration
config: {
defaultInteraction: 'click',
supportedGames: ['whack-a-mole', 'memory-game', 'temp-games'],
adaptiveEnabled: true
},
// Contenu principal
contentItems: []
};
// Migrer le vocabulaire
if (oldContent.vocabulary) {
oldContent.vocabulary.forEach((word, index) => {
newContent.contentItems.push(this.migrateVocabularyItem(word, index));
});
}
// Migrer les phrases si elles existent
if (oldContent.phrases) {
oldContent.phrases.forEach((phrase, index) => {
newContent.contentItems.push(this.migratePhraseItem(phrase, index));
});
}
return newContent;
}
migrateVocabularyItem(oldWord, index) {
return {
id: `vocab_${index + 1}`,
type: 'vocabulary',
difficulty: this.inferDifficulty(oldWord.english),
category: oldWord.category || 'general',
content: {
english: oldWord.english,
french: oldWord.french,
context: oldWord.category || '',
tags: oldWord.category ? [oldWord.category] : []
},
media: {
image: oldWord.image || null,
audio: null,
icon: this.getIconForCategory(oldWord.category)
},
pedagogy: {
learningObjective: `Apprendre le mot "${oldWord.english}"`,
prerequisites: [],
followUp: [],
grammarFocus: 'vocabulary'
},
interaction: {
type: 'click',
validation: 'exact',
hints: [oldWord.french],
feedback: {
correct: `Bien joué ! "${oldWord.english}" = "${oldWord.french}"`,
incorrect: `Non, "${oldWord.english}" = "${oldWord.french}"`
},
alternatives: [] // Sera rempli dynamiquement
}
};
}
migratePhraseItem(oldPhrase, index) {
return {
id: `phrase_${index + 1}`,
type: 'sentence',
difficulty: 'medium',
category: oldPhrase.category || 'general',
content: {
english: oldPhrase.english,
french: oldPhrase.french,
context: oldPhrase.category || '',
tags: [oldPhrase.category || 'phrase']
},
media: {
image: null,
audio: null,
icon: '💬'
},
pedagogy: {
learningObjective: `Comprendre la phrase "${oldPhrase.english}"`,
prerequisites: [],
followUp: [],
grammarFocus: 'sentence_structure'
},
interaction: {
type: 'click',
validation: 'exact',
hints: [oldPhrase.french],
feedback: {
correct: `Parfait ! Cette phrase signifie "${oldPhrase.french}"`,
incorrect: `Cette phrase signifie "${oldPhrase.french}"`
}
}
};
}
inferDifficulty(englishWord) {
if (englishWord.length <= 4) return 'easy';
if (englishWord.length <= 8) return 'medium';
return 'hard';
}
getIconForCategory(category) {
const icons = {
family: '👨‍👩‍👧‍👦',
animals: '🐱',
colors: '🎨',
food: '🍎',
school_objects: '📚',
daily_activities: '🌅',
numbers: '🔢'
};
return icons[category] || '📝';
}
extractCategories(oldContent) {
const categories = new Set();
if (oldContent.vocabulary) {
oldContent.vocabulary.forEach(word => {
if (word.category) categories.add(word.category);
});
}
if (oldContent.categories) {
Object.keys(oldContent.categories).forEach(cat => categories.add(cat));
}
return Array.from(categories);
}
}
// === VALIDATEUR DE CONTENU ===
class ContentValidator {
validate(content) {
try {
// Vérifications de base
if (!content.id || !content.name) {
console.warn('Contenu manque ID ou nom');
return false;
}
if (!content.contentItems || !Array.isArray(content.contentItems)) {
console.warn('contentItems manquant ou invalide');
return false;
}
// Valider chaque élément
for (let item of content.contentItems) {
if (!this.validateContentItem(item)) {
return false;
}
}
return true;
} catch (error) {
console.error('Erreur validation:', error);
return false;
}
}
validateContentItem(item) {
// Champs requis
const requiredFields = ['id', 'type', 'content'];
for (let field of requiredFields) {
if (!item[field]) {
console.warn(`Champ requis manquant: ${field}`);
return false;
}
}
// Valider le contenu selon le type
switch (item.type) {
case 'vocabulary':
return this.validateVocabulary(item);
case 'sentence':
return this.validateSentence(item);
case 'dialogue':
return this.validateDialogue(item);
default:
console.warn(`Type de contenu inconnu: ${item.type}`);
return true; // Permettre types inconnus pour extensibilité
}
}
validateVocabulary(item) {
return item.content.english && item.content.french;
}
validateSentence(item) {
return item.content.english && item.content.french;
}
validateDialogue(item) {
return item.content.conversation && Array.isArray(item.content.conversation);
}
}
// === ADAPTATEUR CONTENU/JEU ===
class GameContentAdapter {
constructor(gameType) {
this.gameType = gameType;
}
adapt(content) {
switch (this.gameType) {
case 'whack-a-mole':
return this.adaptForWhackAMole(content);
case 'memory-game':
return this.adaptForMemoryGame(content);
case 'temp-games':
return this.adaptForTempGames(content);
default:
return content;
}
}
adaptForWhackAMole(content) {
// Convertir vers format attendu par Whack-a-Mole
const vocabulary = content.contentItems
.filter(item => item.type === 'vocabulary' || item.type === 'sentence')
.map(item => ({
english: item.content.english,
french: item.content.french,
image: item.media?.image,
category: item.category,
difficulty: item.difficulty,
interaction: item.interaction
}));
return {
...content,
vocabulary: vocabulary,
// Maintenir la compatibilité
gameSettings: {
whackAMole: {
recommendedWords: Math.min(15, vocabulary.length),
timeLimit: 60,
maxErrors: 5
}
}
};
}
adaptForMemoryGame(content) {
const pairs = content.contentItems
.filter(item => ['vocabulary', 'sentence'].includes(item.type))
.map(item => ({
english: item.content.english,
french: item.content.french,
image: item.media?.image,
type: item.type
}));
return { ...content, pairs: pairs };
}
adaptForTempGames(content) {
// Format flexible pour les mini-jeux
return {
...content,
vocabulary: content.contentItems.map(item => ({
english: item.content.english,
french: item.content.french,
type: item.type,
difficulty: item.difficulty
}))
};
}
}
// Export global
window.ContentEngine = ContentEngine;
window.ContentMigrator = ContentMigrator;
window.ContentValidator = ContentValidator;
window.GameContentAdapter = GameContentAdapter;