// === CONTENT FACTORY - GÉNÉRATEUR UNIVERSEL DE CONTENU === class ContentFactory { constructor() { this.parsers = new Map(); this.generators = new Map(); this.templates = new Map(); this.mediaProcessor = new MediaProcessor(); this.validator = new ContentValidator(); this.initializeParsers(); this.initializeGenerators(); this.initializeTemplates(); } // === PARSERS - ANALYSE DE CONTENU BRUT === initializeParsers() { // Parser pour texte libre this.parsers.set('text', new TextParser()); // Parser pour fichiers CSV this.parsers.set('csv', new CSVParser()); // Parser pour JSON structuré this.parsers.set('json', new JSONParser()); // Parser pour dialogue/script this.parsers.set('dialogue', new DialogueParser()); // Parser pour séquences/histoires this.parsers.set('sequence', new SequenceParser()); // Parser pour média (audio/image) this.parsers.set('media', new MediaParser()); } // === GÉNÉRATEURS - CRÉATION D'EXERCICES === initializeGenerators() { // Générateur de vocabulaire this.generators.set('vocabulary', new VocabularyGenerator()); // Générateur de phrases this.generators.set('sentence', new SentenceGenerator()); // Générateur de dialogues this.generators.set('dialogue', new DialogueGenerator()); // Générateur de séquences this.generators.set('sequence', new SequenceGenerator()); // Générateur de scénarios this.generators.set('scenario', new ScenarioGenerator()); // Générateur automatique (détection de type) this.generators.set('auto', new AutoGenerator()); } // === TEMPLATES - MODÈLES DE CONTENU === initializeTemplates() { this.templates.set('vocabulary_simple', { name: 'Vocabulaire Simple', description: 'Mots avec traduction', requiredFields: ['english', 'french'], optionalFields: ['image', 'audio', 'phonetic', 'category'], interactions: ['click', 'drag_drop', 'type'], games: ['whack-a-mole', 'memory-game', 'temp-games'] }); this.templates.set('dialogue_conversation', { name: 'Dialogue Conversationnel', description: 'Conversation entre personnages', requiredFields: ['speakers', 'conversation'], optionalFields: ['scenario', 'context', 'audio_files'], interactions: ['role_play', 'click', 'build_sentence'], games: ['story-builder', 'temp-games'] }); this.templates.set('sequence_story', { name: 'Histoire Séquentielle', description: 'Étapes chronologiques', requiredFields: ['title', 'steps'], optionalFields: ['images', 'times', 'context'], interactions: ['chronological_order', 'drag_drop'], games: ['story-builder', 'memory-game'] }); this.templates.set('scenario_context', { name: 'Scénario Contextuel', description: 'Situation réelle complexe', requiredFields: ['setting', 'vocabulary', 'phrases'], optionalFields: ['roles', 'objectives', 'media'], interactions: ['simulation', 'role_play', 'click'], games: ['story-builder', 'temp-games'] }); } // === MÉTHODE PRINCIPALE - CRÉATION DE CONTENU === async createContent(input, options = {}) { try { console.log('🏭 Content Factory - Début création contenu'); // 1. Analyser l'input const parsedContent = await this.parseInput(input, options); // 2. Détecter le type de contenu const contentType = this.detectContentType(parsedContent, options); // 3. Générer les exercices const exercises = await this.generateExercises(parsedContent, contentType, options); // 4. Traiter les médias const processedMedia = await this.processMedia(parsedContent.media || [], options); // 5. Assembler le module final const contentModule = this.assembleModule(exercises, processedMedia, options); // 6. Valider le résultat if (!this.validator.validate(contentModule)) { throw new Error('Contenu généré invalide'); } console.log('✅ Content Factory - Contenu créé avec succès'); return contentModule; } catch (error) { console.error('❌ Content Factory - Erreur:', error); throw error; } } // === PARSING - ANALYSE DES INPUTS === async parseInput(input, options) { const inputType = this.detectInputType(input, options); const parser = this.parsers.get(inputType); if (!parser) { throw new Error(`Parser non trouvé pour le type: ${inputType}`); } return await parser.parse(input, options); } detectInputType(input, options) { // Type explicite fourni if (options.inputType) { return options.inputType; } // Détection automatique if (typeof input === 'string') { if (input.includes(',') && input.includes('=')) { return 'csv'; } if (input.includes(':') && (input.includes('A:') || input.includes('B:'))) { return 'dialogue'; } if (input.includes('1.') || input.includes('First') || input.includes('Then')) { return 'sequence'; } return 'text'; } if (typeof input === 'object') { if (input.contentItems) return 'json'; if (input.conversation) return 'dialogue'; if (input.steps) return 'sequence'; } if (Array.isArray(input)) { if (input[0]?.english && input[0]?.french) return 'vocabulary'; if (input[0]?.speaker) return 'dialogue'; if (input[0]?.order || input[0]?.step) return 'sequence'; } return 'text'; // Fallback } detectContentType(parsedContent, options) { // Type explicite if (options.contentType) { return options.contentType; } // Détection basée sur la structure if (parsedContent.vocabulary && parsedContent.vocabulary.length > 0) { return 'vocabulary'; } if (parsedContent.conversation || parsedContent.dialogue) { return 'dialogue'; } if (parsedContent.steps || parsedContent.sequence) { return 'sequence'; } if (parsedContent.scenario || parsedContent.setting) { return 'scenario'; } if (parsedContent.sentences) { return 'sentence'; } return 'auto'; // Génération automatique } // === GÉNÉRATION D'EXERCICES === async generateExercises(parsedContent, contentType, options) { const generator = this.generators.get(contentType); if (!generator) { throw new Error(`Générateur non trouvé pour le type: ${contentType}`); } return await generator.generate(parsedContent, options); } // === ASSEMBLAGE DU MODULE === assembleModule(exercises, media, options) { const moduleId = options.id || this.generateId(); const moduleName = options.name || 'Contenu Généré'; return { id: moduleId, name: moduleName, description: options.description || `Contenu généré automatiquement - ${new Date().toLocaleDateString()}`, version: "2.0", format: "unified", difficulty: options.difficulty || this.inferDifficulty(exercises), metadata: { totalItems: exercises.length, categories: this.extractCategories(exercises), contentTypes: this.extractContentTypes(exercises), generatedAt: new Date().toISOString(), sourceType: options.inputType || 'auto-detected' }, config: { defaultInteraction: options.defaultInteraction || "click", supportedGames: options.supportedGames || ["whack-a-mole", "memory-game", "temp-games", "story-builder"], adaptiveEnabled: true, difficultyProgression: true }, contentItems: exercises, media: media, categories: this.generateCategories(exercises), gameSettings: this.generateGameSettings(exercises, options), // Métadonnées de génération generation: { timestamp: Date.now(), factory_version: "1.0", options: options, stats: { parsing_time: 0, // À implémenter generation_time: 0, total_time: 0 } } }; } // === UTILITAIRES === generateId() { return 'generated_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5); } inferDifficulty(exercises) { // Analyser la complexité des exercices const complexities = exercises.map(ex => { if (ex.type === 'vocabulary') return 1; if (ex.type === 'sentence') return 2; if (ex.type === 'dialogue') return 3; if (ex.type === 'scenario') return 4; return 2; }); const avgComplexity = complexities.reduce((a, b) => a + b, 0) / complexities.length; if (avgComplexity <= 1.5) return 'easy'; if (avgComplexity <= 2.5) return 'medium'; return 'hard'; } extractCategories(exercises) { const categories = new Set(); exercises.forEach(ex => { if (ex.category) categories.add(ex.category); if (ex.content?.tags) { ex.content.tags.forEach(tag => categories.add(tag)); } }); return Array.from(categories); } extractContentTypes(exercises) { const types = new Set(); exercises.forEach(ex => types.add(ex.type)); return Array.from(types); } generateCategories(exercises) { const categories = {}; const categoryGroups = this.groupByCategory(exercises); Object.keys(categoryGroups).forEach(cat => { categories[cat] = { name: this.beautifyCategory(cat), icon: this.getCategoryIcon(cat), description: `Contenu généré pour ${cat}`, difficulty: this.inferCategoryDifficulty(categoryGroups[cat]), estimatedTime: Math.ceil(categoryGroups[cat].length * 1.5) }; }); return categories; } generateGameSettings(exercises, options) { const types = this.extractContentTypes(exercises); return { whackAMole: { recommendedWords: Math.min(15, exercises.length), timeLimit: 60, maxErrors: 5, supportedTypes: types.filter(t => ['vocabulary', 'sentence'].includes(t)) }, memoryGame: { recommendedPairs: Math.min(8, Math.floor(exercises.length / 2)), timeLimit: 120, maxFlips: 30, supportedTypes: types }, storyBuilder: { recommendedScenes: Math.min(6, exercises.length), timeLimit: 180, supportedTypes: types.filter(t => ['dialogue', 'sequence', 'scenario'].includes(t)) }, tempGames: { recommendedItems: Math.min(10, exercises.length), timeLimit: 90, supportedTypes: types } }; } // === API PUBLIQUE SIMPLIFIÉE === // Créer contenu depuis texte libre async fromText(text, options = {}) { return this.createContent(text, { ...options, inputType: 'text' }); } // Créer contenu depuis liste vocabulaire async fromVocabulary(words, options = {}) { return this.createContent(words, { ...options, contentType: 'vocabulary' }); } // Créer contenu depuis dialogue async fromDialogue(dialogue, options = {}) { return this.createContent(dialogue, { ...options, contentType: 'dialogue' }); } // Créer contenu depuis séquence async fromSequence(steps, options = {}) { return this.createContent(steps, { ...options, contentType: 'sequence' }); } // Créer contenu avec médias async fromMediaBundle(content, mediaFiles, options = {}) { return this.createContent(content, { ...options, media: mediaFiles, processMedia: true }); } // === TEMPLATE HELPERS === getAvailableTemplates() { return Array.from(this.templates.entries()).map(([key, template]) => ({ id: key, ...template })); } async fromTemplate(templateId, data, options = {}) { const template = this.templates.get(templateId); if (!template) { throw new Error(`Template non trouvé: ${templateId}`); } // Valider les données selon le template this.validateTemplateData(data, template); return this.createContent(data, { ...options, template: template, requiredFields: template.requiredFields }); } validateTemplateData(data, template) { for (const field of template.requiredFields) { if (!data[field]) { throw new Error(`Champ requis manquant: ${field}`); } } } // === MÉTHODES UTILITAIRES === groupByCategory(exercises) { const groups = {}; exercises.forEach(ex => { const cat = ex.category || 'general'; if (!groups[cat]) groups[cat] = []; groups[cat].push(ex); }); return groups; } beautifyCategory(category) { const beautified = { 'family': 'Famille', 'animals': 'Animaux', 'colors': 'Couleurs', 'numbers': 'Nombres', 'food': 'Nourriture', 'school': 'École', 'daily': 'Quotidien', 'greetings': 'Salutations' }; return beautified[category] || category.charAt(0).toUpperCase() + category.slice(1); } getCategoryIcon(category) { const icons = { 'family': '👨‍👩‍👧‍👦', 'animals': '🐱', 'colors': '🎨', 'numbers': '🔢', 'food': '🍎', 'school': '📚', 'daily': '🌅', 'greetings': '👋', 'general': '📝' }; return icons[category] || '📝'; } inferCategoryDifficulty(exercises) { const difficulties = exercises.map(ex => { switch(ex.difficulty) { case 'easy': return 1; case 'medium': return 2; case 'hard': return 3; default: return 2; } }); const avg = difficulties.reduce((a, b) => a + b, 0) / difficulties.length; if (avg <= 1.3) return 'easy'; if (avg <= 2.3) return 'medium'; return 'hard'; } } // === PROCESSEUR DE MÉDIAS === class MediaProcessor { constructor() { this.supportedAudioFormats = ['mp3', 'wav', 'ogg']; this.supportedImageFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp']; } async processMedia(mediaFiles, options = {}) { const processedMedia = { audio: {}, images: {}, metadata: { totalFiles: mediaFiles.length, processedAt: new Date().toISOString() } }; for (const file of mediaFiles) { try { const processed = await this.processFile(file, options); if (this.isAudioFile(file.name)) { processedMedia.audio[file.id || file.name] = processed; } else if (this.isImageFile(file.name)) { processedMedia.images[file.id || file.name] = processed; } } catch (error) { console.warn(`Erreur traitement fichier ${file.name}:`, error); } } return processedMedia; } async processFile(file, options) { // Dans un environnement réel, ici on ferait : // - Validation du format // - Optimisation (compression, resize) // - Upload vers CDN // - Génération de thumbnails // - Extraction de métadonnées return { originalName: file.name, path: file.path || `assets/${this.getFileCategory(file.name)}/${file.name}`, size: file.size, type: file.type, processedAt: new Date().toISOString() }; } isAudioFile(filename) { const ext = filename.split('.').pop().toLowerCase(); return this.supportedAudioFormats.includes(ext); } isImageFile(filename) { const ext = filename.split('.').pop().toLowerCase(); return this.supportedImageFormats.includes(ext); } getFileCategory(filename) { return this.isAudioFile(filename) ? 'sounds' : 'images'; } } // Export global window.ContentFactory = ContentFactory; window.MediaProcessor = MediaProcessor;