Class_generator/js/core/content-factory.js
StillHammer 1f8688c4aa Fix WebSocket logging system and add comprehensive network features
- Fix WebSocket server to properly broadcast logs to all connected clients
- Integrate professional logging system with real-time WebSocket interface
- Add network status indicator with DigitalOcean Spaces connectivity
- Implement AWS Signature V4 authentication for private bucket access
- Add JSON content loader with backward compatibility to JS modules
- Restore navigation breadcrumb system with comprehensive logging
- Add multiple content formats: JSON + JS with automatic discovery
- Enhance top bar with logger toggle and network status indicator
- Remove deprecated temp-games module and clean up unused files

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

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

554 lines
18 KiB
JavaScript

// === 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']
});
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']
});
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']
});
}
// === MÉTHODE PRINCIPALE - CRÉATION DE CONTENU ===
async createContent(input, options = {}) {
try {
logSh('🏭 Content Factory - Début création contenu', 'INFO');
// 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');
}
logSh('✅ Content Factory - Contenu créé avec succès', 'INFO');
return contentModule;
} catch (error) {
logSh('❌ Content Factory - Erreur:', error, '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", "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) {
logSh(`Erreur traitement fichier ${file.name}:`, error, 'WARN');
}
}
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;