Compare commits

..

No commits in common. "master" and "Old_architecture" have entirely different histories.

248 changed files with 3331 additions and 102846 deletions

48
.envTMP
View File

@ -1,48 +0,0 @@
# ========================================
# FICHIER: .env - CONFIGURATION COMPLÈTE PRÊTE À UTILISER
# ========================================
# GOOGLE SHEETS - Configuration complète fournie
GOOGLE_SERVICE_ACCOUNT_EMAIL=seo-generator@seo-generator-470715.iam.gserviceaccount.com
GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/pvp9roHCUqgv\nOaabM3LLY1XLeqajhN6s0XXXDPOgvuPPuMlg4v7nEuFQOrft39bXS5D73MtihmxE\ngrQQJpzsv1dyuhB6fPmbmNgOsctoQuCFMGb/Mc6Pt3MZD7hHeifyoPhBlu9a6Fpp\n2NB4Bi9i/fN82UegOz03wYUpqXJEuMYexUP6iOj9KrUlJ8hgd+SUABMFrc5WKHYa\neRCeI/XuK9W3u0W1UXu/DSJRkay4hzuseyYgLldUUCISNlpw9XVsvEg0CSJ5N62D\nykKwiYmW78+UaQQPupiOtRHZWzt4Nr8DpACgVFqkohY2BRmqcyDqq3jEvZdOFweZ\nqWt9ZNSFAgMBAAECggEAKtvsqK6d1hcmBWmfGJYo1dMhHKARJABSy9MLx0veL9SA\nnbN1VXVuC77tJEP9XfTw1rTPd4Oo+B+XlrqkCfiYn1kq9T0m8j2AlItZxe98zZQn\nIaHxZqB80Sb1VmVtkI6A4IGfAwv9+xZ7IbCa7jxz3G9uRD1TB0I4Ln/Yh7idFUDZ\nsHXO30VDaB3QNEQnOTFTQCJ+e/JxQCMALXiLllaW/9aXD19LbgcjQaFAlR2/kKZo\nxBArFZ8ozmV2RINLEAKVXLqf5hHklLAIF77vox4yjhP+VKJ8JKI8cIItmLLLkZsZ\n2liNxFjqemeu4GT8Mgjy5JemkDRsI4s8BQtFLk9IAQKBgQDvOFX8Z8Xp+3+UmOpC\nmG1P62xV66v4i2tdYd1mqEWwlunPvHsufSiyFWRINi3a2FYt0mElVtfB5K6qOF91\nXXEAia80YQHjvqznZJChgJkuz1jlYU+9pSbeLLGKTVHX2JAeF9B8LEZEjf9zYRcd\nbRs/Wr0LXPPUP5bEmZ7RUo34eQKBgQDNGHg8BqwIRmXzhA81VrRI5R+AM/t0xMuf\nsyVJj4rBCVOChgn2kURu9ZkppXrvP6sFSOXBhhXF0/4sN9sYKaMa6FyB7Pz/c8EM\nagB80csV0GsZj0/CYRpqryxdtxGy6v4vFE7ncSS+je8M5Du9PCKx0JXrCEuAroMQ\naP8+nIIRbQKBgHcMu0YUwtryDYj/HL4tq2D1kYGk+n2DrNfZR1y6a4w5XnzCmS8G\nnIUbvj9trx5VQXYmV7BEarWUwBP4YBFBgmY5HxdbG5yinNu/IXcuT42LJPtqlUuU\n8CXraiOg3RUlMnu3cEsLoaCmZjWeYOmFDeVWm/QWu0Wqq7aFmRMlGYBJAoGANml2\nhJ5Uh8F9jNSNYF5HaEt5Rv8DiGApkY3qp5BwhHQf9rHu9L5nhHSeFOF1MwIWMkm7\nwtL69cgfV8Xd15Q8VIgu+r1QBcnE/rEkvfi+w2PO9jICPBSc+I7O23IVPP2BQCZI\nJLjswa1QLYBjpPnOTpSDIZ7KwTILTZA9n3PQQiUCgYEAzhW/vR4M8mqWZ+f1Kiwo\ngBrzQmtRzDAr4FpZ2NGK8o2KYox1DOvLHV/BExfALN025hoUcMifW4wK4ionPqwy\n3lxRvLMRZ4ObkVzWpI2q9L2rvNfINo60QcnX8tJC7oElzYPZeHp0naEzJSbPfQsM\nxmmc5R1PzIynW+Q2cfapzXY=\n-----END PRIVATE KEY-----\n"
GOOGLE_SHEETS_ID=1iA2GvWeUxX-vpnAMfVm3ZMG9LhaC070SdGssEcXAh2c
# LLM APIs - EXTRAITES DE LLMManager.js
# ⚠️ SÉCURITÉ: Ces clés étaient hardcodées dans votre code !
OPENAI_API_KEY=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
ANTHROPIC_API_KEY=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
CLAUDE_API_KEY=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
GEMINI_API_KEY=AIzaSyAMzmIGbW5nJlBG5Qyr35sdjb3U2bIBtoE
GOOGLE_API_KEY=AIzaSyAMzmIGbW5nJlBG5Qyr35sdjb3U2bIBtoE
DEEPSEEK_API_KEY=sk-6e02bc9513884bb8b92b9920524e17b5
MOONSHOT_API_KEY=sk-zU9gyNkux2zcsj61cdKfztuP1Jozr6lFJ9viUJRPD8p8owhL
MISTRAL_API_KEY=wESikMCIuixajSH8WHCiOV2z5sevgmVF
# CONFIGURATION LOGGING
LOG_LEVEL=INFO
NODE_ENV=development
ENABLE_FILE_LOG=true
ENABLE_CONSOLE_LOG=true
ENABLE_SHEETS_LOGGING=false
# DIGITALOCEAN SPACES - EXTRAITES DE DigitalOceanWorkflow.js
# ⚠️ Ces credentials étaient hardcodées dans votre code !
DO_ENDPOINT=https://autocollant.fra1.digitaloceanspaces.com
DO_BUCKET_NAME=autocollant
DO_ACCESS_KEY_ID=DO801XTYPE968NZGAQM3
DO_SECRET_ACCESS_KEY=5aCCBiS9K+J8gsAe3M3/0GlliHCNjtLntwla1itCN1s
DO_REGION=fra1
DO_SPACES_BUCKET=autocollant
# EMAIL (optionnel - pour ErrorReporting.js)
EMAIL_USER=your-email@gmail.com
EMAIL_APP_PASSWORD=your_app_password
# Configuration supplémentaire pour les tests
MAX_COST_PER_ARTICLE=1.00
TRACE_PATH=logs/trace.log

2014
CLAUDE.md

File diff suppressed because it is too large Load Diff

365
DRS.md
View File

@ -1,365 +0,0 @@
# DRS.md - Documentation Complète du Système DRS
## 📋 Vue d'Ensemble du DRS
**DRS (Dynamic Response System)** - Système d'exercices éducatifs avec IA intégrée pour l'évaluation et la validation des réponses.
### 🎯 Architecture DRS
Le DRS est un système modulaire complet situé dans `src/DRS/` comprenant :
## 📁 Structure Complète du DRS
```
src/DRS/
├── exercise-modules/ # Modules d'exercices
│ ├── AudioModule.js # Exercices d'écoute
│ ├── GrammarAnalysisModule.js # Analyse grammaticale IA
│ ├── GrammarModule.js # Exercices de grammaire
│ ├── ImageModule.js # Exercices visuels
│ ├── OpenResponseModule.js # Réponses libres avec IA
│ ├── PhraseModule.js # Exercices de phrases
│ ├── TextAnalysisModule.js # Analyse de texte IA
│ ├── TextModule.js # Exercices de lecture
│ ├── TranslationModule.js # Traduction avec validation IA
│ ├── VocabularyModule.js # Exercices de vocabulaire
│ └── WordDiscoveryModule.js # Découverte de mots → Flashcards
├── interfaces/
│ └── ExerciseModuleInterface.js # Interface standard pour tous les modules
├── services/ # Services centraux
│ ├── AIReportSystem.js # Système de rapports IA
│ ├── ContextMemory.js # Mémoire contextuelle
│ ├── IAEngine.js # Moteur IA (OpenAI → DeepSeek)
│ ├── LLMValidator.js # Validateur LLM
│ └── PrerequisiteEngine.js # Moteur de prérequis
├── SmartPreviewOrchestrator.js # Orchestrateur de prévisualisations
└── UnifiedDRS.js # DRS unifié principal
```
## 🤖 Système IA - Production Ready
### Moteur IA Multi-Providers (IAEngine.js)
- **OpenAI GPT-4** (primaire)
- **DeepSeek** (fallback)
- **Hard Fail** (pas de mock/fallback)
### Scoring Logic Strict
- **Réponses incorrectes** : 0-20 points
- **Réponses correctes** : 70-100 points
- **Validation IA obligatoire** (pas de simulation)
### Cache Management
- **Actuellement désactivé** pour les tests
- **Cache par prompt** (clé : 100 premiers caractères)
- **Statut** : Prêt pour production avec amélioration de clé recommandée
## 📚 Modules d'Exercices
### 1. TextModule.js
**Exercices de compréhension écrite**
- Présentation de texte
- Questions générées/extraites
- Validation IA des réponses
- Suivi du temps de lecture
### 2. AudioModule.js
**Exercices d'écoute**
- Lecture audio avec contrôles
- Comptage des lectures
- Révélation progressive des transcriptions
- Analyse IA du contenu audio
### 3. ImageModule.js
**Exercices d'analyse visuelle**
- Affichage d'images avec zoom
- Suivi du temps d'observation
- Analyse IA vision
- Types : description, détails, interprétation
### 4. GrammarModule.js
**Exercices de grammaire traditionnels**
- Règles et explications
- Exercices variés (trous, correction)
- Système d'indices
- Suivi des tentatives
### 5. GrammarAnalysisModule.js
**Analyse grammaticale avec IA**
- Correction automatique
- Explications détaillées
- Score strict 0-100
- Feedback personnalisé
### 6. TextAnalysisModule.js
**Analyse de texte approfondie**
- Compréhension avec coaching IA
- Score strict 0-100
- Feedback détaillé
### 7. TranslationModule.js
**Traduction avec validation IA**
- Support multi-langues
- Validation intelligente
- Score strict 0-100
- Détection automatique de langue
### 8. OpenResponseModule.js
**Questions libres avec évaluation IA**
- Réponses texte libre
- Évaluation intelligente
- Feedback personnalisé
- Score basé sur la pertinence
### 9. VocabularyModule.js
**Exercices de vocabulaire**
- QCM intelligent avec IA
- 1 bonne réponse + 5 mauvaises plausibles
- 16.7% de chance aléatoire
- Distracteurs générés par IA
### 10. WordDiscoveryModule.js
**Découverte de mots → Transition Flashcards**
- Présentation : mot, prononciation, sens, exemple
- **Redirection automatique vers flashcards**
- Transition fluide entre modules
### 11. PhraseModule.js
**Exercices de construction de phrases**
- Analyse de structure
- Validation grammaticale
- Feedback sur la syntaxe
## 🎴 Interface Flashcards (Hors DRS)
### ⚠️ Clarification Importante
Les **Flashcards NE FONT PAS partie du DRS** :
**❌ FlashcardLearning.js** :
- **Localisation** : `src/games/FlashcardLearning.js`
- **Type** : Module de jeu indépendant
- **Architecture** : Extend `Module` de `../core/Module.js`
- **Statut** : **HORS SCOPE DRS**
### ✅ Ce qui EST dans le DRS (Transition Logic)
1. **WordDiscoveryModule.js** (DANS DRS)
- Redirection automatique : `_redirectToFlashcards()`
- Bouton "Start Flashcard Practice"
- Action suivante : `'flashcards'`
2. **UnifiedDRS.js** (DANS DRS)
- Détection : `_shouldUseFlashcards()`
- Chargement : `_loadFlashcardModule()`
- Type d'exercice : `'vocabulary-flashcards'`
- **Import externe** : `../games/FlashcardLearning.js`
### Logic de Transition (DRS → Games)
```javascript
// Le DRS détecte le besoin de flashcards
if (exerciseType === 'vocabulary-flashcards') {
// Import du module externe (HORS DRS)
const { default: FlashcardLearning } = await import('../games/FlashcardLearning.js');
// Création et intégration temporaire
const flashcardGame = new FlashcardLearning(...);
}
```
**Note critique** : Le DRS **interface avec** les flashcards mais ne les **contient pas**.
## 🧠 Services Centraux
### IAEngine.js - Moteur IA Principal
```javascript
// Providers disponibles
- OpenAI GPT-4 (primaire)
- DeepSeek (fallback)
- Pas de mock/simulation
// Méthodes principales
- validateAnswer(userAnswer, correctAnswer, context)
- generateContent(prompt, options)
- detectLanguage(text)
- calculateScore(isCorrect, response)
```
### LLMValidator.js - Validateur LLM
```javascript
// Validation structurée
- Parsing [answer]yes/no
- Extraction [explanation]
- Gestion des réponses malformées
- Support multi-format
```
### AIReportSystem.js - Rapports IA
```javascript
// Suivi de session
- Métadonnées détaillées
- Tracking des performances
- Export : text/HTML/JSON
- Statistiques complètes
```
### ContextMemory.js - Mémoire Contextuelle
```javascript
// Gestion du contexte
- Historique des interactions
- Persistance des données
- Optimisation des prompts
```
### PrerequisiteEngine.js - Moteur de Prérequis
```javascript
// Gestion des prérequis
- Validation des capacités
- Progression logique
- Déblocage de contenu
```
## 🔌 Interface Standard
### ExerciseModuleInterface.js
**Tous les modules DRS DOIVENT implémenter** :
```javascript
// Méthodes obligatoires
- canRun(prerequisites, chapterContent)
- present(container, exerciseData)
- validate(userInput, context)
- getProgress()
- cleanup()
- getMetadata()
```
### Contrat d'Implémentation
- **Abstract enforcement** : Erreurs si méthodes manquantes
- **Validation des paramètres** obligatoire
- **Cleanup** requis pour éviter les fuites mémoire
- **Métadonnées** pour l'orchestration
## 🎼 Orchestrateur Principal
### UnifiedDRS.js - Contrôleur Central
```javascript
// Responsabilités
- Chargement dynamique des modules
- Gestion du cycle de vie
- Communication EventBus
- Interface utilisateur unifiée
- Transition entre exercices
- Gestion des flashcards
```
### SmartPreviewOrchestrator.js
```javascript
// Prévisualisations intelligentes
- Aperçus des exercices
- Estimation de difficulté
- Recommandations de parcours
- Optimisation UX
```
## 📊 Système de Scoring
### Logic de Score Stricte
```javascript
// Réponses incorrectes
score = 0-20 points (selon gravité)
// Réponses correctes
score = 70-100 points (selon qualité)
// Critères d'évaluation
- Exactitude factuelle
- Qualité de l'expression
- Pertinence contextuelle
- Complétude de la réponse
```
### Validation IA Obligatoire
- **Pas de mock/fallback** en production
- **Real AI only** pour garantir la qualité éducative
- **Multi-provider** pour la fiabilité
## 🔄 Flux d'Exécution DRS
### 1. Initialisation
```
UnifiedDRS → Load ContentLoader → Check Prerequisites → Select Module
```
### 2. Exécution d'Exercice
```
Present Content → User Interaction → Validate with AI → Calculate Score → Update Progress
```
### 3. Transition
```
Check Next Exercise → Load Module → Present OR Redirect to Flashcards
```
### 4. Finalisation
```
Generate Report → Save Progress → Cleanup Module → Return to Menu
```
## 🧪 État des Tests
### Tests Requis pour le DRS
1. **Tests d'Interface** : ExerciseModuleInterface compliance
2. **Tests de Modules** : Chaque module individuellement
3. **Tests IA** : IAEngine, LLMValidator, scoring logic
4. **Tests d'Intégration** : UnifiedDRS orchestration
5. **Tests de Performance** : Temps de réponse, mémoire
6. **Tests Flashcards** : Transition et intégration
### Status Actuel
- ✅ **Système fonctionnel** en production
- ✅ **IA validée** sans cache (tests décembre 2024)
- ⚠️ **Tests automatisés** à implémenter
- ⚠️ **Cache system** désactivé pour debug
## 🎯 Points Critiques pour Tests
### Priorité Haute
1. **Validation IA** : Scores 0-20/70-100 respectés
2. **Fallback providers** : OpenAI → DeepSeek fonctionne
3. **Interface compliance** : Tous modules implémentent l'interface
4. **Memory management** : Pas de fuites lors cleanup
### Priorité Moyenne
1. **Flashcards integration** : Transition fluide
2. **Progress tracking** : Persistance des données
3. **Error handling** : Récupération gracieuse
4. **UI/UX** : Responsive, no-scroll policy
### Priorité Basse
1. **Performance optimization** : Temps de réponse
2. **Cache system** : Réactivation avec clés améliorées
3. **Advanced features** : Rapports, analytics
4. **Cross-browser** : Compatibilité
---
## 📝 Résumé Exécutif
**Le DRS est un système complet et fonctionnel** comprenant :
- ✅ **11 modules d'exercices** fonctionnels
- ✅ **Système IA production-ready** (OpenAI + DeepSeek)
- ✅ **Interface standardisée** pour tous les modules
- ✅ **Intégration flashcards** via redirection
- ✅ **Scoring strict** validé en tests
- ⚠️ **Tests automatisés** à implémenter
### ✅ Scope EXACT pour les tests DRS :
**À TESTER (DRS uniquement) :**
- ✅ **Tout dans `src/DRS/`** - modules, services, interfaces
- ✅ **Logic de transition** vers flashcards (détection, redirection)
- ✅ **Import/loading logic** de modules externes
**❌ À NE PAS TESTER (Hors DRS) :**
- ❌ **`src/games/FlashcardLearning.js`** - Module de jeu indépendant
- ❌ **`src/core/`** - Architecture centrale (Module.js, EventBus.js)
- ❌ **`src/components/`** - Composants UI généraux
**Résumé** : Tests DRS = **`src/DRS/` uniquement** + logique de transition (sans tester le module flashcard lui-même)

View File

@ -1,575 +0,0 @@
# 📋 DRS EXERCISE MODULES - IMPLEMENTATION PLAN
**Goal**: Réimplémenter tous les modules DRS pour respecter `DRSExerciseInterface`
**Status**: 🔴 0/11 modules conformes
---
## 📊 ÉTAT DES LIEUX - 11 Modules Existants
### ✅ Modules AI (Score-Based) - 5 modules
Utilisent l'IA pour validation et scoring (0-100 points)
1. **TextAnalysisModule** - Analyse de texte avec AI
2. **GrammarAnalysisModule** - Correction grammaticale AI
3. **TranslationModule** - Traduction validée par AI
4. **OpenResponseModule** - Réponse libre évaluée par AI
5. **AudioModule** - Analyse audio avec transcription
### 🎯 Modules Locaux (Non-AI) - 6 modules
Validation locale sans IA
6. **VocabularyModule** - Flashcards spaced repetition
7. **WordDiscoveryModule** - Découverte passive de vocabulaire
8. **PhraseModule** - Pratique de phrases
9. **GrammarModule** - Exercices de grammaire structurés
10. **TextModule** - Lecture et compréhension de textes
11. **ImageModule** - Description d'images
---
## 🎯 INTERFACE STRICTE - 10 Méthodes Requises
Chaque module DOIT implémenter:
### **Lifecycle** (3 méthodes)
```javascript
async init(config, content) // Initialize module
async render(container) // Render UI
async destroy() // Cleanup
```
### **Exercise Logic** (3 méthodes)
```javascript
async validate(userAnswer) // Returns { isCorrect, score, feedback, explanation }
getResults() // Returns { score, attempts, timeSpent, completed, details }
handleUserInput(event, data) // Handle user interactions
```
### **Progress Tracking** (2 méthodes)
```javascript
async markCompleted(results) // Mark as completed + save progress
getProgress() // Returns { percentage, currentStep, totalSteps, itemsCompleted, itemsTotal }
```
### **Metadata** (2 méthodes)
```javascript
getExerciseType() // Returns type string
getExerciseConfig() // Returns { type, difficulty, estimatedTime, prerequisites, metadata }
```
---
## 📈 ANALYSE PAR MODULE
### 1**VocabularyModule** (43KB)
**État**: 🟡 Partiel (3/10 méthodes)
**Existant**:
- ✅ `async init()` - Ligne 47
- ✅ `async validate(userInput, context)` - Ligne 169
- ✅ `getProgress()` - Ligne 220
**Manquant**:
- ❌ `async render(container)` - Logique UI dispersée, pas de méthode centralisée
- ❌ `async destroy()` - Pas de cleanup formel
- ❌ `getResults()` - Résultats calculés à la volée, pas de méthode dédiée
- ❌ `handleUserInput(event, data)` - Géré inline dans render
- ❌ `async markCompleted(results)` - Sauvegarde dispersée
- ❌ `getExerciseType()` - Type non formalisé
- ❌ `getExerciseConfig()` - Config non structurée
**Priorité**: 🔥 **HAUTE** (module principal, bien structuré)
---
### 2**TextAnalysisModule** (24KB)
**État**: 🟡 Partiel (2/10 méthodes)
**Existant**:
- ✅ `async validate(userInput, context)` - Ligne 127 (AI validation)
- ✅ `getProgress()` - Ligne 204
**Manquant**:
- ❌ `async init(config, content)` - Pas d'init formelle
- ❌ `async render(container)` - UI dispersée
- ❌ `async destroy()` - Pas de cleanup
- ❌ `getResults()` - Résultats inline
- ❌ `handleUserInput(event, data)` - Pas formalisé
- ❌ `async markCompleted(results)` - Pas formalisé
- ❌ `getExerciseType()` - Manque
- ❌ `getExerciseConfig()` - Manque
**Priorité**: 🔥 **HAUTE** (AI module, pattern pour autres)
---
### 3**GrammarAnalysisModule** (26KB)
**État**: 🟡 Partiel (2/10 méthodes)
**Existant**:
- ✅ `async validate(userInput, context)` - AI correction
- ✅ `getProgress()` - Basique
**Manquant**: Mêmes que TextAnalysisModule
**Priorité**: 🔶 **MOYENNE** (similaire à TextAnalysis)
---
### 4**TranslationModule** (30KB)
**État**: 🟡 Partiel (2/10 méthodes)
**Existant**:
- ✅ `async validate(userInput, context)` - AI translation check
- ✅ `getProgress()` - Basique
**Manquant**: Mêmes que TextAnalysisModule
**Priorité**: 🔶 **MOYENNE** (AI module standard)
---
### 5**OpenResponseModule** (21KB)
**État**: 🟡 Partiel (2/10 méthodes)
**Existant**:
- ✅ `async validate(userInput, context)` - AI evaluation
- ✅ `getProgress()` - Basique
**Manquant**: Mêmes que TextAnalysisModule
**Priorité**: 🔶 **MOYENNE** (AI module générique)
---
### 6**WordDiscoveryModule** (11KB)
**État**: 🟡 Partiel (1/10 méthodes)
**Existant**:
- ✅ `async init()` - Basique
**Manquant**: Presque tout
**Priorité**: 🟢 **BASSE** (va fusionner avec VocabularyModule)
---
### 7**PhraseModule** (31KB)
**État**: 🟡 Partiel (2/10 méthodes)
**Existant**:
- ✅ `async validate()` - Local validation
- ✅ `getProgress()` - Basique
**Manquant**: 8 méthodes
**Priorité**: 🔶 **MOYENNE** (local validation, pattern différent)
---
### 8**GrammarModule** (74KB)
**État**: 🔴 Ancien (code legacy volumineux)
**Existant**: Code legacy, structure différente
**Priorité**: 🟢 **BASSE** (peut être remplacé par GrammarAnalysisModule)
---
### 9**TextModule** (52KB)
**État**: 🔴 Ancien (code legacy)
**Existant**: Code legacy, structure différente
**Priorité**: 🟢 **BASSE** (peut être remplacé par TextAnalysisModule)
---
### 🔟 **AudioModule** (68KB)
**État**: 🟡 Partiel
**Existant**:
- ✅ `async validate()` - Audio analysis
**Priorité**: 🔶 **MOYENNE** (module spécialisé)
---
### 1⃣1**ImageModule** (69KB)
**État**: 🟡 Partiel
**Existant**:
- ✅ `async validate()` - Image analysis
**Priorité**: 🔶 **MOYENNE** (module spécialisé)
---
## 🗺️ ROADMAP D'IMPLÉMENTATION
### 🎯 **PHASE 1 - Modules Prioritaires** (2 modules)
#### **1.1 VocabularyModule** ⭐ PRIORITÉ #1
**Pourquoi**: Module le plus utilisé, bien structuré, pas d'IA
**Tâches**:
1. ✅ Garder `init()`, `validate()`, `getProgress()` existants
2. 🔨 Extraire logique UI dans `render(container)`
3. 🔨 Créer `destroy()` pour cleanup
4. 🔨 Créer `getResults()` pour statistiques finales
5. 🔨 Créer `handleUserInput(event, data)` pour boutons
6. 🔨 Créer `markCompleted(results)` pour sauvegarde
7. 🔨 Créer `getExerciseType()``'vocabulary'`
8. 🔨 Créer `getExerciseConfig()` avec difficulty, time, etc.
**Estimation**: 2-3 heures
---
#### **1.2 TextAnalysisModule** ⭐ PRIORITÉ #2
**Pourquoi**: Module AI de référence, pattern pour tous les modules AI
**Tâches**:
1. ✅ Garder `validate()` et `getProgress()` existants
2. 🔨 Créer `init(config, content)` pour setup
3. 🔨 Créer `render(container)` pour UI
4. 🔨 Créer `destroy()` pour cleanup
5. 🔨 Créer `getResults()` avec AI score + metadata
6. 🔨 Créer `handleUserInput(event, data)`
7. 🔨 Créer `markCompleted(results)`
8. 🔨 Créer `getExerciseType()``'text-analysis'`
9. 🔨 Créer `getExerciseConfig()`
**Estimation**: 2-3 heures
---
### 🔄 **PHASE 2 - Modules AI** (3 modules)
**Pattern**: Copier structure de TextAnalysisModule, adapter validation
#### **2.1 GrammarAnalysisModule**
- Utiliser pattern de TextAnalysisModule
- Adapter `validate()` pour grammar checking
- **Estimation**: 1-2 heures
#### **2.2 TranslationModule**
- Utiliser pattern de TextAnalysisModule
- Adapter `validate()` pour translation
- **Estimation**: 1-2 heures
#### **2.3 OpenResponseModule**
- Utiliser pattern de TextAnalysisModule
- Adapter `validate()` pour open responses
- **Estimation**: 1-2 heures
---
### 📦 **PHASE 3 - Modules Locaux** (2 modules)
#### **3.1 PhraseModule**
- Utiliser pattern de VocabularyModule
- Validation locale (pas d'AI)
- **Estimation**: 1-2 heures
#### **3.2 Fusionner WordDiscoveryModule**
- Intégrer dans VocabularyModule (discovery mode)
- Supprimer module séparé
- **Estimation**: 1 heure
---
### 🎨 **PHASE 4 - Modules Spécialisés** (2 modules)
#### **4.1 AudioModule**
- Audio player + transcription
- AI analysis similaire TextAnalysisModule
- **Estimation**: 2-3 heures
#### **4.2 ImageModule**
- Image display + zoom
- AI vision analysis
- **Estimation**: 2-3 heures
---
### 🗑️ **PHASE 5 - Cleanup Legacy** (2 modules)
#### **5.1 Supprimer GrammarModule**
- Remplacé par GrammarAnalysisModule (AI)
- Archiver code si besoin
#### **5.2 Supprimer TextModule**
- Remplacé par TextAnalysisModule (AI)
- Archiver code si besoin
---
## 🛠️ STRATÉGIE D'IMPLÉMENTATION
### **Pattern 1: AI Modules** (TextAnalysis, Grammar, Translation, OpenResponse, Audio, Image)
```javascript
import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js';
class ModuleName extends DRSExerciseInterface {
constructor() {
super('ModuleName');
this.config = null;
this.content = null;
this.container = null;
this.startTime = null;
this.attempts = 0;
this.currentScore = 0;
}
async init(config, content) {
this.config = config;
this.content = content;
this.startTime = Date.now();
}
async render(container) {
this.container = container;
// Render UI logic here
}
async destroy() {
if (this.container) {
this.container.innerHTML = '';
this.container = null;
}
}
async validate(userAnswer) {
this.attempts++;
// AI validation logic
return {
isCorrect: true/false,
score: 0-100,
feedback: '...',
explanation: '...',
metadata: {}
};
}
getResults() {
return {
score: this.currentScore,
attempts: this.attempts,
timeSpent: Date.now() - this.startTime,
completed: this.currentScore >= 70,
details: { /* exercise-specific */ }
};
}
handleUserInput(event, data) {
// Handle button clicks, input changes, etc.
}
async markCompleted(results) {
// Save to progress system
}
getProgress() {
return {
percentage: 100, // or calculate based on steps
currentStep: 1,
totalSteps: 1,
itemsCompleted: this.currentScore >= 70 ? 1 : 0,
itemsTotal: 1
};
}
getExerciseType() {
return 'module-type';
}
getExerciseConfig() {
return {
type: this.getExerciseType(),
difficulty: 'medium',
estimatedTime: 5,
prerequisites: [],
metadata: this.config
};
}
}
```
### **Pattern 2: Local Modules** (Vocabulary, Phrase)
```javascript
import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js';
class ModuleName extends DRSExerciseInterface {
constructor() {
super('ModuleName');
this.items = [];
this.currentIndex = 0;
this.results = [];
}
async init(config, content) {
this.config = config;
this.items = content.items; // Extract items
this.startTime = Date.now();
}
async render(container) {
this.container = container;
this._renderCurrentItem();
}
async destroy() {
this.container.innerHTML = '';
this.container = null;
}
async validate(userAnswer) {
// Local validation (no AI)
const isCorrect = userAnswer === this.items[this.currentIndex].answer;
this.results.push({ isCorrect, answer: userAnswer });
return {
isCorrect,
score: isCorrect ? 100 : 0,
feedback: isCorrect ? 'Correct!' : 'Incorrect',
explanation: '...',
metadata: {}
};
}
getResults() {
const correctCount = this.results.filter(r => r.isCorrect).length;
const score = Math.round((correctCount / this.results.length) * 100);
return {
score,
attempts: this.results.length,
timeSpent: Date.now() - this.startTime,
completed: this.currentIndex >= this.items.length,
details: { correctCount, totalCount: this.items.length }
};
}
handleUserInput(event, data) {
// Handle next/previous/answer buttons
}
async markCompleted(results) {
// Save progress
}
getProgress() {
return {
percentage: Math.round((this.currentIndex / this.items.length) * 100),
currentStep: this.currentIndex + 1,
totalSteps: this.items.length,
itemsCompleted: this.results.filter(r => r.isCorrect).length,
itemsTotal: this.items.length
};
}
getExerciseType() {
return 'module-type';
}
getExerciseConfig() {
return {
type: this.getExerciseType(),
difficulty: 'easy',
estimatedTime: this.items.length * 0.5,
prerequisites: [],
metadata: this.config
};
}
}
```
---
## ✅ CHECKLIST DE VALIDATION
Pour chaque module réimplémenté:
### **Implémentation**
- [ ] Hérite de `DRSExerciseInterface`
- [ ] Implémente les 10 méthodes requises
- [ ] Méthodes retournent le bon format
- [ ] Pas d'erreur au startup (validation stricte)
### **Lifecycle**
- [ ] `init()` initialise correctement config et content
- [ ] `render()` crée l'UI dans le container
- [ ] `destroy()` nettoie proprement (remove listeners, clear DOM)
### **Exercise Logic**
- [ ] `validate()` retourne `{ isCorrect, score, feedback, explanation }`
- [ ] `getResults()` retourne stats complètes
- [ ] `handleUserInput()` gère tous les events
### **Progress**
- [ ] `markCompleted()` sauvegarde dans ProgressTracker
- [ ] `getProgress()` retourne progression en temps réel
### **Metadata**
- [ ] `getExerciseType()` retourne string unique
- [ ] `getExerciseConfig()` retourne config complète
### **Testing**
- [ ] Module se charge sans erreur
- [ ] UI s'affiche correctement
- [ ] Validation fonctionne (AI ou local)
- [ ] Progress se sauvegarde
- [ ] Cleanup fonctionne (pas de memory leak)
---
## 📊 ESTIMATION TOTALE
| Phase | Modules | Temps Estimé |
|-------|---------|--------------|
| Phase 1 | 2 (Vocabulary, TextAnalysis) | 4-6h |
| Phase 2 | 3 (Grammar, Translation, OpenResponse) | 3-6h |
| Phase 3 | 2 (Phrase, WordDiscovery fusion) | 2-3h |
| Phase 4 | 2 (Audio, Image) | 4-6h |
| Phase 5 | 2 (Cleanup legacy) | 1h |
| **TOTAL** | **11 modules** | **14-22h** |
---
## 🎯 ORDRE DE PRIORITÉ
1. 🔥 **VocabularyModule** - Module principal, pas d'AI, bien structuré
2. 🔥 **TextAnalysisModule** - Template pour tous modules AI
3. 🔶 **GrammarAnalysisModule** - Copie pattern TextAnalysis
4. 🔶 **TranslationModule** - Copie pattern TextAnalysis
5. 🔶 **OpenResponseModule** - Copie pattern TextAnalysis
6. 🔶 **PhraseModule** - Local validation, copie pattern Vocabulary
7. 🔶 **AudioModule** - Spécialisé, similaire TextAnalysis
8. 🔶 **ImageModule** - Spécialisé, similaire TextAnalysis
9. 🟢 **WordDiscoveryModule** - Fusionner dans Vocabulary
10. 🟢 **GrammarModule (legacy)** - Supprimer/archiver
11. 🟢 **TextModule (legacy)** - Supprimer/archiver
---
## 🚀 PROCHAINES ÉTAPES
### **Immédiat** (à faire maintenant):
1. Valider ce plan d'implémentation
2. Commencer par **VocabularyModule** (priorité #1)
3. Une fois validé, dupliquer pattern pour autres modules
### **Validation Plan**:
- ❓ Plan approuvé ?
- ❓ Ordre de priorité correct ?
- ❓ Temps estimé réaliste ?
- ❓ Pattern de code acceptable ?
---
**Status**: 📝 **PLAN PRÊT - AWAITING APPROVAL**

View File

@ -1,804 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 🎯 IMPORTANT: Check TODO.md First!
**ALWAYS check `TODO.md` for the current project tasks and priorities before making any changes.**
The `TODO.md` file contains:
- 🔥 Current tasks in progress
- 📋 Pending features to implement
- 🚨 Known issues and blockers
- ✅ Completed work for reference
**Make sure to update TODO.md when:**
- Starting a new task
- Completing a task
- Discovering new issues
- Planning future improvements
## Project Overview
Interactive English learning platform for children (8-9 years old) built as a modular Single Page Application. The system provides 9 different educational games that work with various content modules through a flexible architecture.
## Key Architecture Patterns
### Core System Flow
1. **AppNavigation** (`js/core/navigation.js`) - Central SPA navigation controller
2. **ContentScanner** (`js/core/content-scanner.js`) - Auto-discovers available content modules
3. **GameLoader** (`js/core/game-loader.js`) - Dynamically loads game and content modules
4. **Content Engine** (`js/core/content-engine.js`) - Processes and adapts content for games
### Module Loading System
- Games and content are loaded dynamically via `GameLoader.loadGame(gameType, contentType)`
- All modules register themselves on global objects: `window.GameModules` and `window.ContentModules`
- Content is discovered automatically by `ContentScanner` scanning `js/content/` directory
- **JSON Content Support**: New JSON-first architecture with backward compatibility to JS modules
- **JSON Content Loader**: `js/core/json-content-loader.js` transforms JSON content to legacy game format
- **Offline-First Loading**: Content loads from local files first, with DigitalOcean Spaces fallback
- Games follow consistent constructor pattern: `new GameClass({ container, content, onScoreUpdate, onGameEnd })`
### URL-Based Navigation
- Single HTML file (`index.html`) handles all navigation via URL parameters
- Routes: `?page=home|games|levels|play&game=<gameType>&content=<contentType>`
- Browser back/forward supported through `popstate` events
- Navigation history maintained in `AppNavigation.navigationHistory`
- Breadcrumb navigation with clickable path elements
- **Top Bar**: Fixed header with app title and permanent network status indicator
- **Network Status**: Real-time connectivity indicator (🟢 Online / 🟠 Connecting / 🔴 Offline)
- Keyboard shortcuts (ESC = go back)
## Content Module Format
### Rich Content Schema (New Architecture)
Content modules support rich, multimedia educational content with optional properties. The system adapts games and exercises based on available content features:
```javascript
window.ContentModules.ModuleName = {
name: "Display Name",
description: "Description text",
difficulty: "easy|medium|hard|beginner|intermediate|advanced",
language: "chinese|english|french|spanish", // Target learning language
hskLevel: "HSK1|HSK2|HSK3|HSK4|HSK5|HSK6", // Chinese proficiency level
// Rich vocabulary with optional multimedia
vocabulary: {
"word_or_character": {
translation: "English translation",
prononciation: "pronunciation guide", // Optional: pronunciation guide
type: "noun|verb|adjective|greeting|number", // Word classification
pronunciation: "audio/word.mp3", // Optional: audio file
difficulty: "HSK1|HSK2|...", // Optional: individual word difficulty
strokeOrder: ["stroke1", "stroke2"], // Optional: character writing order
examples: ["example sentence 1"], // Optional: usage examples
grammarNotes: "special usage rules" // Optional: grammar context
}
// OR simple format for basic content:
// "word": "simple translation"
},
// Grammar rules and explanations
grammar: {
topic_name: {
title: "Grammar Rule Title",
explanation: "Detailed explanation",
examples: [
{ chinese: "中文例子", english: "English example", prononciation: "zhōng wén lì zi" }
],
exercises: [/* grammar-specific exercises */]
}
},
// Audio content with/without text
audio: {
withText: [
{
title: "Audio Lesson Title",
audioFile: "audio/lesson1.mp3",
transcript: "Full text transcript",
translation: "English translation",
timestamps: [{ time: 5.2, text: "specific segment" }] // Optional
}
],
withoutText: [
{
title: "Listening Challenge",
audioFile: "audio/challenge1.mp3",
questions: [
{ question: "What did they say?", type: "ai_interpreted" }
]
}
]
},
// Poetry and cultural content
poems: [
{
title: "Poem Title",
content: "Full poem text",
translation: "English translation",
audioFile: "audio/poem1.mp3", // Optional
culturalContext: "Historical background"
}
],
// Fill-in-the-blank exercises
fillInBlanks: [
{
sentence: "I _____ to school every day",
options: ["go", "goes", "going", "went"], // Multiple choice options
correctAnswer: "go",
explanation: "Present tense with 'I'"
},
{
sentence: "The weather is _____ today",
type: "open_ended", // AI-interpreted answers
acceptedAnswers: ["nice", "good", "beautiful", "sunny"],
aiPrompt: "Evaluate if answer describes weather positively"
}
],
// Sentence correction exercises
corrections: [
{
incorrect: "I are happy today",
correct: "I am happy today",
explanation: "Use 'am' with pronoun 'I'",
type: "grammar_correction"
}
],
// Reading comprehension with AI evaluation
comprehension: [
{
text: "Long reading passage...",
questions: [
{
question: "What is the main idea?",
type: "ai_interpreted",
evaluationPrompt: "Check if answer captures main theme"
},
{
question: "Multiple choice question?",
type: "multiple_choice",
options: ["A", "B", "C", "D"],
correctAnswer: "B"
}
]
}
],
// Matching exercises (connect lines between columns)
matching: [
{
title: "Match Words to Meanings",
leftColumn: ["apple", "book", "car"],
rightColumn: ["苹果", "书", "车"],
correctPairs: [
{ left: "apple", right: "苹果" },
{ left: "book", right: "书" },
{ left: "car", right: "车" }
]
}
],
// Standard content (backward compatibility)
sentences: [{ english: "...", chinese: "...", prononciation: "..." }],
texts: [{ title: "...", content: "...", translation: "..." }],
dialogues: [{ conversation: [...] }]
};
```
### JSON Content Format (New Architecture)
The platform now supports JSON content files for easier editing and maintenance:
```json
{
"name": "Content Name",
"description": "Content description",
"difficulty": "easy|medium|hard",
"vocabulary": {
"word": {
"translation": "French translation",
"prononciation": "pronunciation guide",
"type": "noun|verb|adjective"
}
},
"sentences": [
{
"english": "English sentence",
"chinese": "Chinese translation",
"prononciation": "pronunciation"
}
],
"grammar": { /* grammar rules */ },
"audio": { /* audio content */ },
"exercises": { /* exercise definitions */ }
}
```
**JSON Content Loader Features:**
- Automatic transformation from JSON to legacy game format
- Backward compatibility with existing JavaScript content modules
- Support for all rich content features (vocabulary, grammar, audio, exercises)
- Offline-first loading with cloud fallback
### Content Adaptivity System
The platform automatically adapts available games and exercises based on content richness:
**Content Analysis:**
- System scans each content module for available features
- Generates compatibility scores for each game type
- Recommends optimal learning activities
- Handles graceful degradation when content is incomplete
**Adaptive Game Selection:**
- **Rich vocabulary** → Enable advanced matching games, pronunciation practice
- **Audio files present** → Enable listening exercises, pronunciation challenges
- **Grammar rules** → Enable correction exercises, structured lessons
- **Fill-in-blanks data** → Enable cloze tests with multiple choice or AI evaluation
- **Minimal content** → Fall back to basic vocabulary games
**Missing Content Handling:**
- Display helpful messages: "Add audio files to enable pronunciation practice"
- Suggest content enrichment opportunities
- Gracefully disable incompatible game modes
- Provide content creation tools for missing elements
**Example Adaptive Behavior:**
```javascript
// Content with only basic vocabulary
{ vocabulary: { "hello": "你好" } }
→ Enable: Basic matching, simple quiz
→ Disable: Audio practice, grammar exercises
→ Suggest: "Add pronunciation guide and audio for pronunciation practice"
// Rich multimedia content
{ vocabulary: { "hello": { translation: "你好", prononciation: "nǐ hǎo", pronunciation: "audio/hello.mp3" } } }
→ Enable: All vocabulary games, audio practice, pronunciation scoring
→ Unlock: Advanced difficulty levels, speed challenges
```
## 🚨 CRITICAL ARCHITECTURE GUIDELINES
**NEVER violate these principles to maintain system modularity and maintainability:**
### 🎨 CSS Architecture Rules
1. **NEVER modify `css/games.css` for game-specific styles**
- Global CSS is only for shared, reusable components
- Game-specific styles MUST be injected by the game itself
2. **USE the Global CSS Base System:**
```javascript
// ✅ CORRECT: Use global classes with specific overrides
<div class="game-wrapper compact"> // Global base class
<div class="game-hud"> // Global HUD structure
<div class="game-area"> // Global game area
<div class="answer-panel"> // Global answer panel
// ✅ CORRECT: Inject game-specific CSS
injectCSS() {
const styleSheet = document.createElement('style');
styleSheet.textContent = `
.my-game-specific-element { /* Game-only styles */ }
`;
document.head.appendChild(styleSheet);
}
```
3. **CSS Classes Hierarchy:**
- `.game-wrapper` - Base container (full screen)
- `.game-wrapper.compact` - Smaller viewport variant
- `.game-hud` - Top information bar
- `.game-area` - Main play zone
- `.answer-panel` - Bottom interaction zone
- `.answer-btn` - Interactive buttons
4. **❌ FORBIDDEN PATTERNS:**
```css
/* ❌ NEVER add game-specific classes to games.css */
.word-storm-wrapper { }
.my-game-specific-class { }
/* ❌ NEVER hardcode game-specific dimensions in global CSS */
.game-area { height: 600px; } /* This breaks other games */
```
### 🎮 Game Development Standards
1. **Self-Contained Games:**
- Each game MUST inject its own CSS via `injectCSS()`
- Games MUST use global base classes where possible
- Game-specific elements get their own CSS only
2. **Constructor Pattern (REQUIRED):**
```javascript
class MyGame {
constructor({ container, content, onScoreUpdate, onGameEnd }) {
this.injectCSS(); // Inject game-specific styles
this.init(); // Setup interface
}
start() { /* Start game logic */ }
destroy() { /* Cleanup */ }
}
```
3. **Module Registration (REQUIRED):**
```javascript
// MUST be at end of game file
window.GameModules = window.GameModules || {};
window.GameModules.MyGame = MyGame;
```
4. **GameLoader Integration:**
- Add game mapping in `game-loader.js``getModuleName()`
- Add compatibility rules in `content-game-compatibility.js`
- Add game config in `navigation.js` → game configuration
### 🔧 Modification Guidelines
**When adding new games:**
1. ✅ Use existing global CSS classes
2. ✅ Inject only game-specific CSS
3. ✅ Follow constructor pattern
4. ✅ Add to GameLoader mapping
**When modifying existing games:**
1. ✅ Keep changes within the game file
2. ✅ Don't break global CSS compatibility
3. ✅ Test with multiple content types
**When adding global features:**
1. ✅ Add to global CSS base classes
2. ✅ Ensure backward compatibility
3. ✅ Update this documentation
### 🚀 Benefits of This Architecture
- **Modularity**: Each game is self-contained
- **Reusability**: Base classes work for all games
- **Maintainability**: Changes don't break other games
- **Performance**: Only load CSS when game is used
- **Scalability**: Easy to add new games
### ⚠️ Common Mistakes to Avoid
**CSS Architecture Violations:**
```css
/* ❌ DON'T: Adding game-specific styles to games.css */
.word-storm-wrapper { height: 80vh; width: 90vw; }
/* ✅ DO: Use global classes with game-specific injection */
// In game file:
injectCSS() {
// Game-specific overrides only
styleSheet.textContent = `.falling-word { animation: wordGlow 2s; }`;
}
```
**Module Loading Issues:**
```javascript
// ❌ DON'T: Forget GameLoader mapping
// Game won't load because getModuleName() doesn't know about it
// ✅ DO: Add to game-loader.js
const names = {
'my-game': 'MyGame' // Add this mapping
};
```
**HTML Structure Violations:**
```html
<!-- ❌ DON'T: Create custom wrapper classes -->
<div class="my-custom-game-wrapper">
<!-- ✅ DO: Use standard global structure -->
<div class="game-wrapper compact">
<div class="game-hud">
<div class="game-area">
<div class="answer-panel">
```
**Integration Checklist:**
- [ ] Game CSS injected via `injectCSS()`
- [ ] Global classes used for structure
- [ ] GameLoader mapping added
- [ ] Compatibility rules defined
- [ ] Constructor pattern followed
- [ ] Module registration at file end
## Game Module Format
Game modules must export to `window.GameModules` with this pattern:
```javascript
class GameName {
constructor({ container, content, onScoreUpdate, onGameEnd }) {
this.container = container;
this.content = content;
this.onScoreUpdate = onScoreUpdate;
this.onGameEnd = onGameEnd;
}
start() { /* Initialize game */ }
destroy() { /* Cleanup */ }
restart() { /* Reset game state */ }
}
window.GameModules = window.GameModules || {};
window.GameModules.GameName = GameName;
```
## Configuration System
- **Main config**: `config/games-config.json` - defines available games and content
- **Environment config**: `js/core/env-config.js` - DigitalOcean Spaces configuration and offline settings
- **Content discovery**: Automatic scanning of both `.js` and `.json` content files
- Games can be enabled/disabled via `games.{gameType}.enabled`
- **Cloud Integration**: DigitalOcean Spaces endpoint configuration for remote content
- **Offline-First Strategy**: Local content prioritized, remote fallback with timeout protection
- UI settings, scoring rules, and feature flags also in main config
## Development Workflow
### Running the Application
Open `index.html` in a web browser - no build process required. All modules load dynamically.
### Adding New Games
1. Create `js/games/{game-name}.js` with proper module export
2. Add game configuration to `config/games-config.json`
3. Update `AppNavigation.getDefaultConfig()` if needed
### Adding New Content
**Option 1: JSON Format (Recommended)**
1. Create `js/content/{content-name}.json` with proper JSON structure
2. Content will be auto-discovered and loaded via JSON Content Loader
3. Easier to edit and maintain than JavaScript files
**Option 2: JavaScript Format (Legacy)**
1. Create `js/content/{content-name}.js` with proper module export
2. Add filename to `ContentScanner.contentFiles` array
3. Content will be auto-discovered on next app load
### Content Creation Tool
- Built-in content creator at `js/tools/content-creator.js`
- Accessible via "Créateur de Contenu" button on home page
- Generates properly formatted content modules
## Key Files by Function
**Navigation & Loading:**
- `js/core/navigation.js` - SPA navigation controller (452 lines)
- `js/core/game-loader.js` - Dynamic module loading (336 lines)
- `js/core/content-scanner.js` - Auto content discovery (376 lines)
**Content Processing:**
- `js/core/content-engine.js` - Content processing engine (484 lines)
- `js/core/content-factory.js` - Exercise generation (553 lines)
- `js/core/content-parsers.js` - Content parsing utilities (484 lines)
- `js/core/json-content-loader.js` - JSON to legacy format transformation
- `js/core/env-config.js` - Environment and cloud configuration
**Game Implementations:**
- `js/games/whack-a-mole.js` - Standard version (623 lines)
- `js/games/whack-a-mole-hard.js` - Difficult version (643 lines)
- `js/games/memory-match.js` - Memory pairs game (403 lines)
- `js/games/quiz-game.js` - Quiz system (354 lines)
- `js/games/fill-the-blank.js` - Sentence completion (418 lines)
- `js/games/text-reader.js` - Guided text reading (366 lines)
- `js/games/adventure-reader.js` - RPG-style adventure (949 lines)
## Important Implementation Details
### Scoring System
- Games call `this.onScoreUpdate(score)` to update display
- Final scores saved to localStorage with key pattern: `score_{gameType}_{contentType}`
- Best scores tracked and displayed in game-end modal
- Points per correct answer, malus per error, speed bonus
- Score history and achievement badges
### Content Compatibility
- `ContentScanner` evaluates content compatibility with each game type
- Compatibility scoring helps recommend best content for each game
- Games should handle various content formats gracefully
### Memory Management
- `GameLoader.cleanup()` called before loading new games
- Games should implement `destroy()` method for proper cleanup
- Previous game instances must be cleaned up to prevent memory leaks
### Error Handling
- Content loading errors logged but don't crash the application
- Fallback mechanisms for missing content or games
- User-friendly error messages via `Utils.showToast()`
## Design Guidelines
### Visual Design Principles
- Modern, clean design optimized for children (8-9 years old)
- Large, tactile buttons (minimum 44px for touch interfaces)
- High contrast colors for accessibility
- Smooth, non-aggressive animations
- Emoji icons combined with text labels
### Color Palette
- **Primary**: Blue (#3B82F6) - Trust, learning
- **Secondary**: Green (#10B981) - Success, validation
- **Accent**: Orange (#F59E0B) - Energy, attention
- **Error**: Red (#EF4444) - Clear error indication
- **Neutral**: Gray (#6B7280) - Text, backgrounds
### Accessibility Features
- Full keyboard navigation support
- Alternative text for all images
- Adjustable font sizes
- High contrast mode compatibility
- Screen reader friendly markup
### Responsive Design
- Mobile/tablet adaptation
- Touch-friendly interface
- Portrait/landscape orientation support
- Fluid layouts that work on various screen sizes
- **Fixed Top Bar**: App title and network status always visible
- **Network Status**: Automatic hiding of status text on mobile devices
- **Content Margin**: Proper spacing to accommodate fixed header
## Git Configuration
### Repository
- **Remote**: Bitbucket repository at `AlexisTrouve/class-generator-system`
- **Port 443 Configuration**: Git is configured to use SSH over port 443 for network restrictions
- **Remote URL**: `ssh://git@altssh.bitbucket.org:443/AlexisTrouve/class-generator-system.git`
### SSH Configuration
To push to the repository through port 443, the following SSH configuration is required in `~/.ssh/config`:
```
Host altssh.bitbucket.org
HostName altssh.bitbucket.org
Port 443
User git
IdentityFile ~/.ssh/bitbucket_key
```
### Push Commands
- Standard push: `git push`
- Set upstream: `git push --set-upstream origin master`
## 🚨 **Developer Guidelines & Common Pitfalls**
**Critical information for future developers to avoid common mistakes and maintain code quality.**
### **🔥 Template Literals Syntax Errors**
**MOST COMMON BUG - Always check this first:**
```javascript
// ❌ FATAL ERROR - Will break entire module
styleSheet.textContent = \`css here\`; // Backslash = SyntaxError
// ✅ CORRECT
styleSheet.textContent = `css here`; // Backtick (grave accent)
```
**How to debug:**
```bash
# Test syntax before browser testing
node -c js/games/your-game.js
# Look for "Invalid or unexpected token" errors
# Usually points to template literal issues
```
### **🎮 Game Development Best Practices**
#### **Required Game Structure:**
```javascript
class NewGame {
constructor({ container, content, onScoreUpdate, onGameEnd }) {
this.injectCSS(); // CSS injection FIRST
this.extractContent(); // Content processing
this.init(); // UI initialization
}
start() {
// Separate start method - NOT in constructor
this.startGameLogic();
}
destroy() {
// Cleanup intervals, event listeners, injected CSS
this.cleanup();
}
}
// REQUIRED: Global module registration
window.GameModules = window.GameModules || {};
window.GameModules.NewGame = NewGame;
```
#### **CSS Architecture - Zero Tolerance Policy:**
```javascript
// ✅ CORRECT: Inject game-specific CSS
injectCSS() {
if (document.getElementById('my-game-styles')) return; // Prevent duplicates
const styleSheet = document.createElement('style');
styleSheet.id = 'my-game-styles';
styleSheet.textContent = `
.my-game-element { color: red; }
.falling-word { animation: myCustomAnimation 2s; }
`;
document.head.appendChild(styleSheet);
}
// ❌ FORBIDDEN: Modifying css/games.css for game-specific styles
// css/games.css should ONLY contain global reusable classes
```
### **🔍 Debug Templates for Quick Testing**
#### **Isolated Game Testing:**
```html
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body>
<div id="container" style="width:800px; height:600px; border:1px solid #ccc;"></div>
<script>
window.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
window.Utils = { storage: { get: () => [], set: () => {} } };
window.GameModules = {};
window.ContentModules = {};
</script>
<script src="js/content/your-content.js"></script>
<script src="js/games/your-game.js"></script>
<script>
try {
const game = new window.GameModules.YourGame({
container: document.getElementById('container'),
content: window.ContentModules.YourContent,
onScoreUpdate: score => console.log('Score:', score),
onGameEnd: score => console.log('Game ended:', score)
});
if (game.start) game.start();
console.log('✅ Game loaded successfully!');
} catch (error) {
console.error('❌ Error:', error.message);
}
</script>
</body>
</html>
```
### **🌐 Interface Language Standards**
**All UI text must be in English:**
```javascript
// ✅ CORRECT
"Score: ${score}"
"Lives: ${lives}"
"Level Up!"
"Game Over"
"Back to Games"
// ❌ FORBIDDEN
"Score: ${score} points" // French
"Vies: ${lives}" // French
"Niveau supérieur!" // French
```
### **📋 Integration Checklist**
**Before committing any new game:**
- [ ] ✅ Syntax check: `node -c js/games/your-game.js`
- [ ] ✅ CSS injected via `injectCSS()` method
- [ ] ✅ No modifications to `css/games.css`
- [ ] ✅ Uses global classes: `.game-wrapper`, `.game-hud`, `.game-area`, `.answer-panel`
- [ ] ✅ GameLoader mapping added in `getModuleName()`
- [ ] ✅ Navigation config updated in `navigation.js`
- [ ] ✅ Constructor pattern followed exactly
- [ ] ✅ Module export at end of file
- [ ] ✅ English-only interface text
- [ ] ✅ Isolated test file created and working
### **⚡ Performance & Architecture Notes**
**Current System Architecture:**
- **CSS:** Global base classes + per-game injection
- **Content:** Auto-discovery + JSON/JS dual support
- **Navigation:** URL-based SPA with dynamic loading
- **Modules:** Dynamic import + backward compatibility
**Critical Files (DO NOT BREAK):**
- `js/core/game-loader.js` - Module name mapping
- `css/games.css` - Global CSS base (game-agnostic only)
- `js/core/navigation.js` - Game configuration and routing
### **🔧 Common Debugging Commands**
```bash
# Check file syntax
node -c js/games/your-game.js
# Check for non-ASCII characters (encoding issues)
grep -P '[^\x00-\x7F]' js/games/your-game.js
# Find template literal issues
grep -n "\\\`" js/games/your-game.js
# Test local server
python3 -m http.server 8000
# Then: http://localhost:8000/?page=play&game=your-game&content=available-content
```
### **🚀 Quick Win Tips**
1. **Copy working game structure** - Use existing games as templates
2. **Test isolated first** - Don't debug through the full app initially
3. **Check browser console** - JavaScript errors are usually obvious
4. **Verify content compatibility** - Make sure your content has the data your game needs
5. **Use global CSS classes** - Don't reinvent layout, build on existing structure
**Remember: Most bugs are simple syntax errors (especially template literals) or missing module registrations. Check these first!** 🎯
## 🤝 **Collaborative Development Best Practices**
**Critical lesson learned from real debugging sessions:**
### **✅ Always Test Before Committing**
**❌ BAD WORKFLOW:**
1. Write code
2. Immediately commit
3. Discover it doesn't work
4. Debug on committed broken code
**✅ GOOD WORKFLOW:**
1. Write code
2. **TEST THOROUGHLY**
3. If broken → debug cooperatively
4. When working → commit
### **🔍 Cooperative Debugging Method**
**When user reports: "ça marche pas" or "y'a pas de lettres":**
1. **Get specific symptoms** - Don't assume, ask exactly what they see
2. **Add targeted debug logs** - Console.log the exact variables in question
3. **Test together** - Have user run and report console output
4. **Analyze together** - Look at debug output to find root cause
5. **Fix precisely** - Target the exact issue, don't rewrite everything
**Real Example - Letter Discovery Issue:**
```javascript
// ❌ ASSUMPTION: "Letters not working, must rewrite everything"
// ✅ ACTUAL DEBUG:
console.log('🔍 DEBUG this.content.letters:', this.content.letters); // undefined
console.log('🔍 DEBUG this.content.rawContent?.letters:', this.content.rawContent?.letters); // {U: Array(4), V: Array(4), T: Array(4)}
// ✅ PRECISE FIX: Check both locations
const letters = this.content.letters || this.content.rawContent?.letters;
```
### **🎯 Key Principles**
- **Communication > Code** - Clear problem description saves hours
- **Debug logs > Assumptions** - Add console.log to see actual data
- **Test early, test often** - Don't tunnel vision on untested code
- **Pair debugging** - Two brains spot issues faster than one
- **Patience > Speed** - Taking time to understand beats rushing broken fixes
**"C'est mieux quand on prend notre temps en coop plutot que de tunnel vision !"** 🎯

View File

@ -1,441 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive English Class</title>
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="css/navigation.css">
<link rel="stylesheet" href="css/games.css">
<link rel="stylesheet" href="css/settings.css">
</head>
<body>
<!-- Top Bar with Network Status -->
<div class="top-bar">
<div class="top-bar-left">
<div class="top-bar-title">🎓 Interactive English Class</div>
<nav class="breadcrumb" id="breadcrumb">
<button class="breadcrumb-item active" data-page="home">🏠 Home</button>
</nav>
</div>
<div class="top-bar-right">
<div class="network-status" id="network-status">
<div class="network-indicator connecting" id="network-indicator"></div>
<span class="network-status-text" id="network-status-text">Connecting...</span>
</div>
<button class="logger-toggle" onclick="openLogsInterface()" title="Open logs interface">📋</button>
</div>
</div>
<!-- Main Container -->
<main class="container" id="main-container">
<!-- Home Page -->
<div class="page active" id="home-page">
<div class="hero">
<h1>🎓 Interactive English Class</h1>
<p>Learn English while having fun!</p>
</div>
<div class="main-options">
<button class="option-card primary" onclick="navigateTo('levels')">
📚 <span>Create a custom lesson</span>
</button>
<button class="option-card secondary" onclick="showComingSoon()">
📊 <span>Statistics</span>
<small>Coming soon</small>
</button>
<button class="option-card secondary" onclick="navigateTo('settings')">
⚙️ <span>Settings</span>
<small>Audio & Debug Tools</small>
</button>
<button class="option-card primary" onclick="showContentCreator()">
🏭 <span>Content Creator</span>
<small>Create your own exercises</small>
</button>
</div>
</div>
<!-- Level/Content Selection -->
<div class="page" id="levels-page">
<div class="page-header">
<button class="back-btn" onclick="AppNavigation.goBack()">← Back</button>
<h2>📚 Choose your level</h2>
<p>Select the content you want to learn</p>
</div>
<div class="cards-grid" id="levels-grid">
<!-- Cards will be generated dynamically -->
</div>
</div>
<!-- Game Type Selection -->
<div class="page" id="games-page">
<div class="page-header">
<button class="back-btn" onclick="AppNavigation.goBack()">← Back</button>
<h2>🎮 Choose your game</h2>
<p id="game-description">Select the type of activity for this content</p>
</div>
<div class="cards-grid" id="games-grid">
<!-- Cards will be generated dynamically -->
</div>
</div>
<!-- Game Page -->
<div class="page" id="game-page">
<div class="game-header">
<button class="back-btn" onclick="goBack()">← Back</button>
<h3 id="game-title">Game in progress...</h3>
<div class="score-display">
Score: <span id="current-score">0</span>
</div>
</div>
<div class="game-container" id="game-container">
<!-- The game will be loaded here dynamically -->
</div>
</div>
<!-- Settings Page -->
<div class="page" id="settings-page">
<div class="page-header">
<button class="back-btn" onclick="goBack()">← Back</button>
<h2>⚙️ Settings & Debug Tools</h2>
</div>
<div class="settings-container">
<!-- Audio Settings Section -->
<div class="settings-section">
<h3>🔊 Audio Settings</h3>
<div class="setting-group">
<label>TTS Voice Speed:</label>
<input type="range" id="tts-rate" min="0.5" max="2" step="0.1" value="0.8">
<span id="tts-rate-value">0.8</span>
</div>
<div class="setting-group">
<label>TTS Volume:</label>
<input type="range" id="tts-volume" min="0" max="1" step="0.1" value="1">
<span id="tts-volume-value">1.0</span>
</div>
<div class="setting-group">
<label>Preferred Voice:</label>
<select id="tts-voice">
<option value="">Auto (System Default)</option>
</select>
</div>
</div>
<!-- TTS Debug Section -->
<div class="settings-section">
<h3>🔧 TTS Debug Tools</h3>
<div class="debug-info" id="tts-status">
<div class="info-item">
<span class="label">Browser Support:</span>
<span class="value" id="browser-support">Checking...</span>
</div>
<div class="info-item">
<span class="label">Available Voices:</span>
<span class="value" id="voice-count">Loading...</span>
</div>
<div class="info-item">
<span class="label">English Voices:</span>
<span class="value" id="english-voice-count">Loading...</span>
</div>
</div>
<div class="debug-controls">
<button class="debug-btn" onclick="testBasicTTS()">🔊 Test Basic TTS</button>
<button class="debug-btn" onclick="testWithCallbacks()">📞 Test with Callbacks</button>
<button class="debug-btn" onclick="testGameWords()">🎮 Test Game Words</button>
<button class="debug-btn" onclick="refreshVoices()">🔄 Refresh Voices</button>
</div>
<div class="debug-output" id="debug-output">
<h4>Debug Output:</h4>
<div class="debug-log" id="debug-log"></div>
<button class="clear-btn" onclick="clearDebugLog()">Clear Log</button>
</div>
</div>
<!-- Voice List Section -->
<div class="settings-section">
<h3>🎤 Available Voices</h3>
<div class="voice-list" id="voice-list">
Loading voices...
</div>
</div>
<!-- Browser Info Section -->
<div class="settings-section">
<h3>🌐 Browser Information</h3>
<div class="browser-info">
<div class="info-item">
<span class="label">User Agent:</span>
<span class="value small" id="user-agent"></span>
</div>
<div class="info-item">
<span class="label">Platform:</span>
<span class="value" id="platform"></span>
</div>
<div class="info-item">
<span class="label">Language:</span>
<span class="value" id="browser-language"></span>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- Modal Coming Soon -->
<div class="modal" id="coming-soon-modal">
<div class="modal-content">
<h3>🚧 Coming Soon!</h3>
<p>This feature will be available in an upcoming version.</p>
<button onclick="closeModal()">OK</button>
</div>
</div>
<!-- Loading Indicator -->
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Loading...</p>
</div>
<!-- Scripts -->
<script src="js/core/websocket-logger.js"></script>
<script src="js/core/env-config.js"></script>
<script src="js/core/utils.js"></script>
<script src="js/core/content-engine.js"></script>
<script src="js/core/content-factory.js"></script>
<script src="js/core/content-parsers.js"></script>
<script src="js/core/content-generators.js"></script>
<script src="js/core/content-scanner.js"></script>
<script src="js/core/json-content-loader.js"></script>
<script src="js/core/content-game-compatibility.js"></script>
<script src="js/tools/content-creator.js"></script>
<script src="js/core/settings-manager.js"></script>
<script src="js/core/navigation.js"></script>
<script src="js/core/game-loader.js"></script>
<script>
// Fonction de debug pour le logger
function toggleLoggerDebug() {
console.log('🔧 Bouton toggle cliqué!');
if (typeof window.logger === 'undefined') {
console.error('❌ window.logger n\'existe pas!');
alert('Erreur: Logger non initialisé!');
return;
}
console.log('✅ Logger existe, toggle...');
try {
window.logger.toggle();
console.log('✅ Toggle réussi');
} catch (error) {
console.error('❌ Erreur toggle:', error);
alert('Erreur toggle: ' + error.message);
}
}
// Initialize app when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
console.log('🔧 DOM loaded, initializing...');
logSh('🎯 DOM loaded, initializing application...', 'INFO');
// Vérifier que le logger existe
if (typeof window.logger === 'undefined') {
console.error('❌ Logger not found on load!');
} else {
console.log('✅ Logger found:', window.logger);
}
// Test du logger
if (typeof logSh !== 'undefined') {
logSh('🚀 Application démarrée', 'INFO');
logSh('Logger système intégré avec succès', 'DEBUG');
} else {
console.log('🚀 Application démarrée (pas de logger)');
}
// Initialize connection status listener
initConnectionStatus();
// Initialize navigation system
AppNavigation.init();
// Test initial network connection
setTimeout(testNetworkConnection, 1000);
});
// Network Status Manager
function initConnectionStatus() {
// Listen for content connection status events
window.addEventListener('contentConnectionStatus', function(event) {
updateNetworkStatus(event.detail);
});
// Test connection périodiquement
setInterval(testNetworkConnection, 30000); // Test toutes les 30 secondes
}
function updateNetworkStatus(details) {
const indicator = document.getElementById('network-indicator');
const text = document.getElementById('network-status-text');
// Remove all status classes
indicator.classList.remove('connecting', 'online', 'offline');
// Update based on status
switch(details.status) {
case 'loading':
indicator.classList.add('connecting');
text.textContent = 'Connexion...';
break;
case 'online':
indicator.classList.add('online');
text.textContent = 'Online';
break;
case 'offline':
indicator.classList.add('offline');
text.textContent = 'Local';
break;
case 'error':
indicator.classList.add('offline');
text.textContent = 'Hors ligne';
break;
}
}
async function testNetworkConnection() {
const indicator = document.getElementById('network-indicator');
const text = document.getElementById('network-status-text');
// Désactiver les tests réseau en mode file://
if (window.location.protocol === 'file:') {
indicator.classList.remove('connecting', 'online');
indicator.classList.add('offline');
text.textContent = 'Local Mode';
logSh('📁 Mode file:// - Test réseau ignoré', 'INFO');
return;
}
logSh('🔍 Test de connexion réseau démarré...', 'INFO');
try {
// Test avec l'endpoint DigitalOcean avec timeout approprié
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // Plus de temps
// Utiliser le proxy local au lieu de DigitalOcean directement
const testUrl = 'http://localhost:8083/do-proxy/english-class-demo.json';
logSh(`🌐 Test URL (proxy): ${testUrl}`, 'INFO');
logSh(`🕐 Timeout configuré: 5000ms`, 'DEBUG');
logSh(`🔧 Mode: GET sans headers spéciaux`, 'DEBUG');
logSh('📤 Envoi de la requête fetch...', 'INFO');
const fetchStart = Date.now();
const response = await fetch(testUrl, {
method: 'GET',
signal: controller.signal,
mode: 'cors',
cache: 'no-cache'
});
const fetchDuration = Date.now() - fetchStart;
clearTimeout(timeoutId);
logSh(`⏱️ Durée de la requête: ${fetchDuration}ms`, 'INFO');
logSh(`📡 Réponse reçue: ${response.status} ${response.statusText}`, 'INFO');
logSh(`📋 Headers response: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`, 'DEBUG');
if (response.ok) {
indicator.classList.remove('connecting', 'offline');
indicator.classList.add('online');
text.textContent = 'Online';
logSh('✅ Connection successful!', 'INFO');
} else {
// Pour les buckets privés, on considère 403 comme "connexion OK mais accès privé"
if (response.status === 403) {
indicator.classList.remove('connecting', 'offline');
indicator.classList.add('online');
text.textContent = 'Privé';
logSh('🔒 Connexion OK mais accès privé (403)', 'WARN');
} else {
logSh(`❌ Erreur HTTP: ${response.status}`, 'ERROR');
throw new Error(`HTTP ${response.status}`);
}
}
} catch (error) {
logSh(`💥 ERREUR DÉTAILLÉE: Type=${error.constructor.name}, Name=${error.name}, Message=${error.message}`, 'ERROR');
logSh(`🔍 Stack trace: ${error.stack}`, 'DEBUG');
logSh(`🌐 URL qui a échoué: http://localhost:8083/do-proxy/english-class-demo.json`, 'ERROR');
logSh(`🔧 Type d'erreur: ${typeof error}`, 'DEBUG');
// Vérifier si le serveur proxy est accessible
logSh('🔍 Test de base du serveur proxy...', 'INFO');
try {
const basicTest = await fetch('http://localhost:8083/', { method: 'GET' });
logSh(`📡 Test serveur proxy: ${basicTest.status}`, 'INFO');
} catch (proxyError) {
logSh(`❌ Serveur proxy inaccessible: ${proxyError.message}`, 'ERROR');
}
indicator.classList.remove('connecting', 'online');
indicator.classList.add('offline');
if (error.name === 'AbortError') {
text.textContent = 'Timeout';
logSh('⏰ Timeout de connexion après 5000ms', 'WARN');
} else if (error.message.includes('Failed to fetch')) {
text.textContent = 'Serveur inaccessible';
logSh(`🚫 Le serveur proxy sur port 8083 n'est pas accessible`, 'ERROR');
} else {
text.textContent = 'Hors ligne';
logSh(`🚫 Hors ligne: ${error.message}`, 'ERROR');
}
}
}
// Coming soon functionality
function showComingSoon() {
logSh('🚧 Ouverture modal "Bientôt disponible"', 'INFO');
document.getElementById('coming-soon-modal').classList.add('show');
}
function closeModal() {
logSh('❌ Fermeture modal', 'DEBUG');
document.getElementById('coming-soon-modal').classList.remove('show');
}
function showContentCreator() {
logSh('🏭 Ouverture du créateur de contenu', 'INFO');
// Masquer la page d'accueil
document.getElementById('home-page').classList.remove('active');
// Créer et afficher le créateur de contenu
const creator = new ContentCreator();
creator.init();
}
// Keyboard navigation
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
logSh('⌨️ Touche Escape pressée', 'DEBUG');
closeModal();
AppNavigation.goBack();
}
});
</script>
</body>
</html>

View File

@ -1,850 +0,0 @@
// === ENGLISH MEDICAL AND SAFETY LESSONS ===
// Lessons 63-64: Doctor visit and prohibition commands with Chinese translation
window.ContentModules = window.ContentModules || {};
window.ContentModules.NCE1Lesson6364 = {
id: "nce1-lesson63-64",
name: "NCE1-Lesson63-64",
description: "English medical dialogue and prohibition commands with modal verbs",
difficulty: "intermediate",
language: "en-US",
userLanguage: "zh-CN",
totalWords: 120,
// === GRAMMAR LESSONS SYSTEM ===
grammar: {
"modal-must-mustnot": {
title: "Modal Verbs: Must and Mustn't - 情态动词must和mustn't",
explanation: "English uses 'must' for strong obligation and 'mustn't' for prohibition.",
rules: [
"must + verb - for strong necessity: You must stay in bed",
"mustn't + verb - for prohibition: You mustn't get up yet",
"Must + subject + verb? - for questions: Must he stay in bed?",
"No contraction for positive must, but mustn't = must not"
],
examples: [
{
english: "You must stay in bed.",
chinese: "你必须卧床休息。",
explanation: "Use 'must' for strong obligation or medical advice",
pronunciation: "ju mʌst steɪ ɪn bed"
},
{
english: "You mustn't get up yet.",
chinese: "你还不能起床。",
explanation: "Use 'mustn't' for prohibition or things not allowed",
pronunciation: "ju mʌsnt get ʌp jet"
},
{
english: "Must he stay in bed?",
chinese: "他必须卧床吗?",
explanation: "Use 'Must' at the start for yes/no questions",
pronunciation: "mʌst hi steɪ ɪn bed"
},
{
english: "He mustn't eat rich food.",
chinese: "他不能吃油腻食物。",
explanation: "Use 'mustn't' for medical restrictions",
pronunciation: "hi mʌsnt it rɪtʃ fud"
},
{
english: "You must keep the room warm.",
chinese: "你必须保持房间温暖。",
explanation: "Use 'must' for important instructions",
pronunciation: "ju mʌst kip ðə rum wɔrm"
},
{
english: "The boy mustn't go to school yet.",
chinese: "这个男孩还不能去上学。",
explanation: "Use 'mustn't' for temporary restrictions",
pronunciation: "ðə bɔɪ mʌsnt gəʊ tu skul jet"
}
],
exercises: [
{
type: "fill_blank",
sentence: "You _____ stay in bed for two days.",
options: ["must", "mustn't", "can", "can't"],
correct: "must",
explanation: "Use 'must' for medical necessity"
},
{
type: "fill_blank",
sentence: "He _____ eat rich food when he's sick.",
options: ["must", "mustn't", "should", "can"],
correct: "mustn't",
explanation: "Use 'mustn't' for medical restrictions"
}
]
},
"imperatives-commands": {
title: "Imperative Commands - 祈使句",
explanation: "English uses imperatives to give commands, instructions, or make requests.",
rules: [
"Positive commands: Verb + object: Come upstairs",
"Negative commands: Don't + verb: Don't take medicine",
"No subject pronoun needed in commands",
"Use for instructions, warnings, and advice"
],
examples: [
{
english: "Come upstairs.",
chinese: "上楼来。",
explanation: "Positive command - just use the base verb",
pronunciation: "kʌm ʌpstɛrz"
},
{
english: "Don't take any aspirins.",
chinese: "不要吃任何阿司匹林。",
explanation: "Negative command - Don't + base verb",
pronunciation: "dəʊnt teɪk eni æspɪrɪnz"
},
{
english: "Don't play with matches.",
chinese: "不要玩火柴。",
explanation: "Safety warning using negative command",
pronunciation: "dəʊnt pleɪ wɪð mætʃɪz"
},
{
english: "Don't make a noise.",
chinese: "不要制造噪音。",
explanation: "Polite request using negative command",
pronunciation: "dəʊnt meɪk ə nɔɪz"
},
{
english: "Don't drive so quickly.",
chinese: "不要开得这么快。",
explanation: "Safety advice using negative command",
pronunciation: "dəʊnt draɪv səʊ kwɪkli"
},
{
english: "Don't break that vase.",
chinese: "不要打破那个花瓶。",
explanation: "Warning using negative command",
pronunciation: "dəʊnt breɪk ðæt vɑːz"
}
],
exercises: [
{
type: "transformation",
instruction: "Make this a negative command:",
original: "Take this medicine.",
correct: "Don't take this medicine.",
explanation: "Add 'Don't' before the verb"
}
]
},
"present-simple-questions": {
title: "Present Simple Questions - 一般现在时疑问句",
explanation: "English forms questions differently for 'be' verbs and other verbs.",
rules: [
"With 'be': Be + subject: How's Jimmy? Where's Mr. Williams?",
"With other verbs: Do/Does + subject + verb: Does he have a temperature?",
"Question words come first: How, What, Where, When"
],
examples: [
{
english: "How's Jimmy today?",
chinese: "吉米今天怎么样?",
explanation: "Question with 'be' verb - How + is contracted",
pronunciation: "haʊz dʒɪmi tədeɪ"
},
{
english: "Where's Mr. Williams?",
chinese: "威廉姆斯先生在哪里?",
explanation: "Question with 'be' verb - Where + is contracted",
pronunciation: "wɛrz mɪstər wɪljəmz"
},
{
english: "Does he have a temperature?",
chinese: "他发烧吗?",
explanation: "Question with regular verb - Does + subject + verb",
pronunciation: "dʌz hi hæv ə tempərətʃər"
},
{
english: "Can I see him please?",
chinese: "我可以看看他吗?",
explanation: "Question with modal verb - Modal + subject + verb",
pronunciation: "kæn aɪ si hɪm pliz"
}
],
exercises: [
{
type: "question_formation",
statement: "He has a cold.",
correct: "Does he have a cold?",
explanation: "Use 'Does' + subject + base verb for questions"
}
]
}
},
vocabulary: {
"doctor": {
"user_language": "医生",
"type": "noun",
"pronunciation": "dɔktər"
},
"better": {
"user_language": "更好的",
"type": "adjective",
"pronunciation": "betər"
},
"certainly": {
"user_language": "当然",
"type": "adverb",
"pronunciation": "sɜrtənli"
},
"upstairs": {
"user_language": "楼上",
"type": "adverb",
"pronunciation": "ʌpstɛrz"
},
"bed": {
"user_language": "床",
"type": "noun",
"pronunciation": "bed"
},
"yet": {
"user_language": "还,仍",
"type": "adverb",
"pronunciation": "jet"
},
"stay": {
"user_language": "停留",
"type": "verb",
"pronunciation": "steɪ"
},
"school": {
"user_language": "学校",
"type": "noun",
"pronunciation": "skul"
},
"rich": {
"user_language": "油腻的",
"type": "adjective",
"pronunciation": "rɪtʃ"
},
"food": {
"user_language": "食物",
"type": "noun",
"pronunciation": "fud"
},
"temperature": {
"user_language": "体温,发烧",
"type": "noun",
"pronunciation": "tempərətʃər"
},
"remain": {
"user_language": "保持,继续",
"type": "verb",
"pronunciation": "rɪmeɪn"
},
"warm": {
"user_language": "温暖的",
"type": "adjective",
"pronunciation": "wɔrm"
},
"cold": {
"user_language": "感冒",
"type": "noun",
"pronunciation": "kəʊld"
},
"aspirins": {
"user_language": "阿司匹林",
"type": "noun",
"pronunciation": "æspɪrɪnz"
},
"medicine": {
"user_language": "药",
"type": "noun",
"pronunciation": "medɪsən"
},
"play": {
"user_language": "玩",
"type": "verb",
"pronunciation": "pleɪ"
},
"matches": {
"user_language": "火柴",
"type": "noun",
"pronunciation": "mætʃɪz"
},
"talk": {
"user_language": "谈话",
"type": "verb",
"pronunciation": "tɔk"
},
"library": {
"user_language": "图书馆",
"type": "noun",
"pronunciation": "laɪbrəri"
},
"noise": {
"user_language": "噪音",
"type": "noun",
"pronunciation": "nɔɪz"
},
"drive": {
"user_language": "开车",
"type": "verb",
"pronunciation": "draɪv"
},
"quickly": {
"user_language": "快地",
"type": "adverb",
"pronunciation": "kwɪkli"
},
"lean": {
"user_language": "探身",
"type": "verb",
"pronunciation": "lin"
},
"window": {
"user_language": "窗户",
"type": "noun",
"pronunciation": "wɪndəʊ"
},
"break": {
"user_language": "打破",
"type": "verb",
"pronunciation": "breɪk"
},
"vase": {
"user_language": "花瓶",
"type": "noun",
"pronunciation": "vɑz"
}
},
// === SENTENCES FOR GAMES (extracted from stories) ===
sentences: [
{
english: "How's Jimmy today?",
chinese: "吉米今天怎么样?",
prononciation: "haʊz dʒɪmi tədeɪ"
},
{
english: "Better. Thank you, doctor.",
chinese: "好一些了。谢谢你,医生。",
prononciation: "betər θæŋk ju dɔktər"
},
{
english: "Can I see him please?",
chinese: "我可以看看他吗?",
prononciation: "kæn aɪ si hɪm pliz"
},
{
english: "You must stay in bed.",
chinese: "你必须卧床休息。",
prononciation: "ju mʌst steɪ ɪn bed"
},
{
english: "You mustn't get up yet.",
chinese: "你还不能起床。",
prononciation: "ju mʌsnt get ʌp jet"
},
{
english: "Don't take any aspirins.",
chinese: "不要吃任何阿司匹林。",
prononciation: "dəʊnt teɪk eni æspɪrɪnz"
},
{
english: "Don't play with matches.",
chinese: "不要玩火柴。",
prononciation: "dəʊnt pleɪ wɪð mætʃɪz"
},
{
english: "The doctor says Jimmy must rest.",
chinese: "医生说吉米必须休息。",
prononciation: "ðə dɔktər sez dʒɪmi mʌst rest"
},
{
english: "He mustn't go outside yet.",
chinese: "他还不能出门。",
prononciation: "hi mʌsnt gəʊ aʊtsaɪd jet"
},
{
english: "Mrs. Williams must keep him warm.",
chinese: "威廉姆斯太太必须让他保暖。",
prononciation: "mɪsɪz wɪljəmz mʌst kip hɪm wɔrm"
}
],
story: {
title: "At the Doctor's Visit - 看医生",
totalSentences: 23,
chapters: [
{
title: "Chapter 1: Jimmy is Sick - 第一章:吉米生病了",
sentences: [
{
id: 1,
original: "How's Jimmy today?",
translation: "吉米今天怎么样?",
words: [
{word: "How's", translation: "怎么样", type: "question", pronunciation: "haʊz"},
{word: "Jimmy", translation: "吉米", type: "noun", pronunciation: "dʒɪmi"},
{word: "today", translation: "今天", type: "adverb", pronunciation: "tədeɪ"}
]
},
{
id: 2,
original: "Better. Thank you, doctor.",
translation: "好一些了。谢谢你,医生。",
words: [
{word: "Better", translation: "好一些", type: "adjective", pronunciation: "betər"},
{word: "Thank", translation: "谢谢", type: "verb", pronunciation: "θæŋk"},
{word: "you", translation: "你", type: "pronoun", pronunciation: "ju"},
{word: "doctor", translation: "医生", type: "noun", pronunciation: "dɔktər"}
]
},
{
id: 3,
original: "Can I see him please?",
translation: "我可以看看他吗?",
words: [
{word: "Can", translation: "可以", type: "modal", pronunciation: "kæn"},
{word: "I", translation: "我", type: "pronoun", pronunciation: "aɪ"},
{word: "see", translation: "看", type: "verb", pronunciation: "si"},
{word: "him", translation: "他", type: "pronoun", pronunciation: "hɪm"},
{word: "please", translation: "请", type: "adverb", pronunciation: "pliz"}
]
},
{
id: 4,
original: "Certainly, doctor. Come upstairs.",
translation: "当然可以,医生。上楼来。",
words: [
{word: "Certainly", translation: "当然", type: "adverb", pronunciation: "sɜrtənli"},
{word: "doctor", translation: "医生", type: "noun", pronunciation: "dɔktər"},
{word: "Come", translation: "来", type: "verb", pronunciation: "kʌm"},
{word: "upstairs", translation: "楼上", type: "adverb", pronunciation: "ʌpstɛrz"}
]
},
{
id: 5,
original: "You look very well, Jimmy.",
translation: "你看起来很好,吉米。",
words: [
{word: "You", translation: "你", type: "pronoun", pronunciation: "ju"},
{word: "look", translation: "看起来", type: "verb", pronunciation: "lʊk"},
{word: "very", translation: "很", type: "adverb", pronunciation: "vɛri"},
{word: "well", translation: "好", type: "adverb", pronunciation: "wɛl"},
{word: "Jimmy", translation: "吉米", type: "noun", pronunciation: "dʒɪmi"}
]
},
{
id: 6,
original: "You are better now.",
translation: "你现在好多了。",
words: [
{word: "You", translation: "你", type: "pronoun", pronunciation: "ju"},
{word: "are", translation: "是", type: "verb", pronunciation: "ɑr"},
{word: "better", translation: "更好", type: "adjective", pronunciation: "betər"},
{word: "now", translation: "现在", type: "adverb", pronunciation: "naʊ"}
]
},
{
id: 7,
original: "You mustn't get up yet.",
translation: "你还不能起床。",
words: [
{word: "You", translation: "你", type: "pronoun", pronunciation: "ju"},
{word: "mustn't", translation: "不能", type: "modal", pronunciation: "mʌsnt"},
{word: "get", translation: "起", type: "verb", pronunciation: "get"},
{word: "up", translation: "床", type: "adverb", pronunciation: "ʌp"},
{word: "yet", translation: "还", type: "adverb", pronunciation: "jet"}
]
},
{
id: 8,
original: "You must stay in bed.",
translation: "你必须卧床休息。",
words: [
{word: "You", translation: "你", type: "pronoun", pronunciation: "ju"},
{word: "must", translation: "必须", type: "modal", pronunciation: "mʌst"},
{word: "stay", translation: "停留", type: "verb", pronunciation: "steɪ"},
{word: "in", translation: "在", type: "preposition", pronunciation: "ɪn"},
{word: "bed", translation: "床", type: "noun", pronunciation: "bed"}
]
},
{
id: 9,
original: "He mustn't go to school yet.",
translation: "他还不能去上学。",
words: [
{word: "He", translation: "他", type: "pronoun", pronunciation: "hi"},
{word: "mustn't", translation: "不能", type: "modal", pronunciation: "mʌsnt"},
{word: "go", translation: "去", type: "verb", pronunciation: "gəʊ"},
{word: "to", translation: "到", type: "preposition", pronunciation: "tu"},
{word: "school", translation: "学校", type: "noun", pronunciation: "skul"},
{word: "yet", translation: "还", type: "adverb", pronunciation: "jet"}
]
},
{
id: 10,
original: "He mustn't eat rich food.",
translation: "他不能吃油腻食物。",
words: [
{word: "He", translation: "他", type: "pronoun", pronunciation: "hi"},
{word: "mustn't", translation: "不能", type: "modal", pronunciation: "mʌsnt"},
{word: "eat", translation: "吃", type: "verb", pronunciation: "it"},
{word: "rich", translation: "油腻的", type: "adjective", pronunciation: "rɪtʃ"},
{word: "food", translation: "食物", type: "noun", pronunciation: "fud"}
]
}
]
},
{
title: "Chapter 2: Safety Rules - 第二章:安全规则",
sentences: [
{
id: 11,
original: "Don't take any aspirins.",
translation: "不要吃任何阿司匹林。",
words: [
{word: "Don't", translation: "不要", type: "auxiliary", pronunciation: "dəʊnt"},
{word: "take", translation: "吃", type: "verb", pronunciation: "teɪk"},
{word: "any", translation: "任何", type: "determiner", pronunciation: "eni"},
{word: "aspirins", translation: "阿司匹林", type: "noun", pronunciation: "æspɪrɪnz"}
]
},
{
id: 12,
original: "Don't take this medicine.",
translation: "不要吃这个药。",
words: [
{word: "Don't", translation: "不要", type: "auxiliary", pronunciation: "dəʊnt"},
{word: "take", translation: "吃", type: "verb", pronunciation: "teɪk"},
{word: "this", translation: "这个", type: "determiner", pronunciation: ɪs"},
{word: "medicine", translation: "药", type: "noun", pronunciation: "medɪsən"}
]
},
{
id: 13,
original: "Don't play with matches.",
translation: "不要玩火柴。",
words: [
{word: "Don't", translation: "不要", type: "auxiliary", pronunciation: "dəʊnt"},
{word: "play", translation: "玩", type: "verb", pronunciation: "pleɪ"},
{word: "with", translation: "和", type: "preposition", pronunciation: "wɪð"},
{word: "matches", translation: "火柴", type: "noun", pronunciation: "mætʃɪz"}
]
},
{
id: 14,
original: "Don't talk in the library.",
translation: "不要在图书馆说话。",
words: [
{word: "Don't", translation: "不要", type: "auxiliary", pronunciation: "dəʊnt"},
{word: "talk", translation: "说话", type: "verb", pronunciation: "tɔk"},
{word: "in", translation: "在", type: "preposition", pronunciation: "ɪn"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "library", translation: "图书馆", type: "noun", pronunciation: "laɪbrəri"}
]
},
{
id: 15,
original: "Don't make a noise.",
translation: "不要制造噪音。",
words: [
{word: "Don't", translation: "不要", type: "auxiliary", pronunciation: "dəʊnt"},
{word: "make", translation: "制造", type: "verb", pronunciation: "meɪk"},
{word: "a", translation: "一个", type: "article", pronunciation: "ə"},
{word: "noise", translation: "噪音", type: "noun", pronunciation: "nɔɪz"}
]
},
{
id: 16,
original: "Don't drive so quickly.",
translation: "不要开得这么快。",
words: [
{word: "Don't", translation: "不要", type: "auxiliary", pronunciation: "dəʊnt"},
{word: "drive", translation: "开车", type: "verb", pronunciation: "draɪv"},
{word: "so", translation: "如此", type: "adverb", pronunciation: "səʊ"},
{word: "quickly", translation: "快地", type: "adverb", pronunciation: "kwɪkli"}
]
},
{
id: 17,
original: "Don't lean out of the window.",
translation: "不要从窗户探出身子。",
words: [
{word: "Don't", translation: "不要", type: "auxiliary", pronunciation: "dəʊnt"},
{word: "lean", translation: "探身", type: "verb", pronunciation: "lin"},
{word: "out", translation: "出", type: "adverb", pronunciation: "aʊt"},
{word: "of", translation: "从", type: "preposition", pronunciation: "ʌv"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "window", translation: "窗户", type: "noun", pronunciation: "wɪndəʊ"}
]
},
{
id: 18,
original: "Don't break that vase.",
translation: "不要打破那个花瓶。",
words: [
{word: "Don't", translation: "不要", type: "auxiliary", pronunciation: "dəʊnt"},
{word: "break", translation: "打破", type: "verb", pronunciation: "breɪk"},
{word: "that", translation: "那个", type: "determiner", pronunciation: "ðæt"},
{word: "vase", translation: "花瓶", type: "noun", pronunciation: "vɑz"}
]
},
{
id: 19,
original: "Does he have a temperature?",
translation: "他发烧吗?",
words: [
{word: "Does", translation: "吗", type: "auxiliary", pronunciation: "dʌz"},
{word: "he", translation: "他", type: "pronoun", pronunciation: "hi"},
{word: "have", translation: "有", type: "verb", pronunciation: "hæv"},
{word: "a", translation: "一个", type: "article", pronunciation: "ə"},
{word: "temperature", translation: "体温", type: "noun", pronunciation: "tempərətʃər"}
]
},
{
id: 20,
original: "He has a bad cold too.",
translation: "他也得了重感冒。",
words: [
{word: "He", translation: "他", type: "pronoun", pronunciation: "hi"},
{word: "has", translation: "有", type: "verb", pronunciation: "hæz"},
{word: "a", translation: "一个", type: "article", pronunciation: "ə"},
{word: "bad", translation: "严重的", type: "adjective", pronunciation: "bæd"},
{word: "cold", translation: "感冒", type: "noun", pronunciation: "kəʊld"},
{word: "too", translation: "也", type: "adverb", pronunciation: "tu"}
]
},
{
id: 21,
original: "The doctor told Mrs. Williams to keep Jimmy warm and give him plenty of rest.",
translation: "医生告诉威廉姆斯太太要让吉米保暖并让他多休息。",
words: [
{word: "The", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "doctor", translation: "医生", type: "noun", pronunciation: "dɔktər"},
{word: "told", translation: "告诉", type: "verb", pronunciation: "toʊld"},
{word: "Mrs.", translation: "太太", type: "title", pronunciation: "mɪsɪz"},
{word: "Williams", translation: "威廉姆斯", type: "noun", pronunciation: "wɪljəmz"},
{word: "to", translation: "要", type: "preposition", pronunciation: "tuː"},
{word: "keep", translation: "保持", type: "verb", pronunciation: "kiːp"},
{word: "Jimmy", translation: "吉米", type: "noun", pronunciation: "dʒɪmi"},
{word: "warm", translation: "温暖", type: "adjective", pronunciation: "wɔːrm"},
{word: "and", translation: "并且", type: "conjunction", pronunciation: "ænd"},
{word: "give", translation: "给", type: "verb", pronunciation: "gɪv"},
{word: "him", translation: "他", type: "pronoun", pronunciation: "hɪm"},
{word: "plenty", translation: "大量", type: "noun", pronunciation: "plenti"},
{word: "of", translation: "的", type: "preposition", pronunciation: "ʌv"},
{word: "rest", translation: "休息", type: "noun", pronunciation: "rest"}
]
},
{
id: 22,
original: "Jimmy must stay in bed until the doctor says he can go back to school.",
translation: "吉米必须卧床直到医生说他可以回学校。",
words: [
{word: "Jimmy", translation: "吉米", type: "noun", pronunciation: "dʒɪmi"},
{word: "must", translation: "必须", type: "modal", pronunciation: "mʌst"},
{word: "stay", translation: "停留", type: "verb", pronunciation: "steɪ"},
{word: "in", translation: "在", type: "preposition", pronunciation: "ɪn"},
{word: "bed", translation: "床", type: "noun", pronunciation: "bed"},
{word: "until", translation: "直到", type: "conjunction", pronunciation: "ənˈtɪl"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "doctor", translation: "医生", type: "noun", pronunciation: "dɔktər"},
{word: "says", translation: "说", type: "verb", pronunciation: "sez"},
{word: "he", translation: "他", type: "pronoun", pronunciation: "hi"},
{word: "can", translation: "可以", type: "modal", pronunciation: "kæn"},
{word: "go", translation: "去", type: "verb", pronunciation: "goʊ"},
{word: "back", translation: "回", type: "adverb", pronunciation: "bæk"},
{word: "to", translation: "到", type: "preposition", pronunciation: "tuː"},
{word: "school", translation: "学校", type: "noun", pronunciation: "skuːl"}
]
},
{
id: 23,
original: "Mrs. Williams promised to follow all the doctor's instructions very carefully until Jimmy feels completely better.",
translation: "威廉姆斯太太承诺会非常仔细地遵循医生的所有指示直到吉米完全康复。",
words: [
{word: "Mrs.", translation: "太太", type: "title", pronunciation: "mɪsɪz"},
{word: "Williams", translation: "威廉姆斯", type: "noun", pronunciation: "wɪljəmz"},
{word: "promised", translation: "承诺", type: "verb", pronunciation: "prɑːmɪst"},
{word: "to", translation: "要", type: "preposition", pronunciation: "tuː"},
{word: "follow", translation: "遵循", type: "verb", pronunciation: "fɑːloʊ"},
{word: "all", translation: "所有", type: "determiner", pronunciation: ːl"},
{word: "the", translation: "这些", type: "article", pronunciation: "ðə"},
{word: "doctor's", translation: "医生的", type: "noun", pronunciation: "dɔktərz"},
{word: "instructions", translation: "指示", type: "noun", pronunciation: "ɪnˈstrʌkʃənz"},
{word: "very", translation: "非常", type: "adverb", pronunciation: "veri"},
{word: "carefully", translation: "仔细地", type: "adverb", pronunciation: "kɛrfəli"},
{word: "until", translation: "直到", type: "conjunction", pronunciation: "ənˈtɪl"},
{word: "Jimmy", translation: "吉米", type: "noun", pronunciation: "dʒɪmi"},
{word: "feels", translation: "感觉", type: "verb", pronunciation: "fiːlz"},
{word: "completely", translation: "完全", type: "adverb", pronunciation: "kəmˈpliːtli"},
{word: "better", translation: "更好", type: "adjective", pronunciation: "betər"}
]
}
]
}
]
},
// === GRAMMAR-BASED FILL IN THE BLANKS ===
fillInBlanks: [
{
sentence: "You _____ stay in bed for two days.",
options: ["must", "mustn't", "can", "don't"],
correctAnswer: "must",
explanation: "Use 'must' for strong medical advice",
grammarFocus: "modal-must-mustnot"
},
{
sentence: "He _____ eat rich food when sick.",
options: ["must", "mustn't", "should", "can"],
correctAnswer: "mustn't",
explanation: "Use 'mustn't' for medical restrictions",
grammarFocus: "modal-must-mustnot"
},
{
sentence: "_____ take this medicine!",
options: ["Don't", "Not", "No", "Doesn't"],
correctAnswer: "Don't",
explanation: "Use 'Don't' for negative commands",
grammarFocus: "imperatives-commands"
},
{
sentence: "_____ he have a temperature?",
options: ["Do", "Does", "Is", "Has"],
correctAnswer: "Does",
explanation: "Use 'Does' for questions with third person singular",
grammarFocus: "present-simple-questions"
},
{
sentence: "_____ play with matches!",
options: ["Not", "Don't", "No", "Mustn't"],
correctAnswer: "Don't",
explanation: "Use 'Don't' for safety warnings",
grammarFocus: "imperatives-commands"
},
{
sentence: "_____ Mr. Williams this evening?",
options: ["Where", "Where's", "Where are", "Where is"],
correctAnswer: "Where's",
explanation: "Use 'Where's' (Where is) for location questions",
grammarFocus: "present-simple-questions"
}
],
// === GRAMMAR CORRECTION EXERCISES ===
corrections: [
{
incorrect: "You don't must get up yet.",
correct: "You mustn't get up yet.",
explanation: "Use 'mustn't' not 'don't must' for prohibition",
grammarFocus: "modal-must-mustnot"
},
{
incorrect: "Not take this medicine!",
correct: "Don't take this medicine!",
explanation: "Use 'Don't' + verb for negative commands",
grammarFocus: "imperatives-commands"
},
{
incorrect: "He must not to eat rich food.",
correct: "He mustn't eat rich food.",
explanation: "Don't use 'to' after modal verbs",
grammarFocus: "modal-must-mustnot"
},
{
incorrect: "Do he have a temperature?",
correct: "Does he have a temperature?",
explanation: "Use 'Does' with third person singular subjects",
grammarFocus: "present-simple-questions"
},
{
incorrect: "You must to stay in bed.",
correct: "You must stay in bed.",
explanation: "Don't use 'to' after 'must'",
grammarFocus: "modal-must-mustnot"
}
],
// === ADDITIONAL READING STORIES ===
additionalStories: [
{
title: "Doctor's Advice - 医生的建议",
totalSentences: 12,
chapters: [
{
title: "Chapter 1: Following Medical Instructions - 遵循医嘱",
sentences: [
{
id: 1,
original: "The doctor says Jimmy must rest.",
translation: "医生说吉米必须休息。",
words: [
{word: "The", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "doctor", translation: "医生", type: "noun", pronunciation: "dɔktər"},
{word: "says", translation: "说", type: "verb", pronunciation: "sez"},
{word: "Jimmy", translation: "吉米", type: "noun", pronunciation: "dʒɪmi"},
{word: "must", translation: "必须", type: "modal", pronunciation: "mʌst"},
{word: "rest", translation: "休息", type: "verb", pronunciation: "rest"}
]
},
{
id: 2,
original: "He mustn't go outside yet.",
translation: "他还不能出门。",
words: [
{word: "He", translation: "他", type: "pronoun", pronunciation: "hi"},
{word: "mustn't", translation: "不能", type: "modal", pronunciation: "mʌsnt"},
{word: "go", translation: "去", type: "verb", pronunciation: "gəʊ"},
{word: "outside", translation: "外面", type: "adverb", pronunciation: "aʊtsaɪd"},
{word: "yet", translation: "还", type: "adverb", pronunciation: "jet"}
]
},
{
id: 3,
original: "Mrs. Williams must keep him warm.",
translation: "威廉姆斯太太必须让他保暖。",
words: [
{word: "Mrs.", translation: "太太", type: "title", pronunciation: "mɪsɪz"},
{word: "Williams", translation: "威廉姆斯", type: "noun", pronunciation: "wɪljəmz"},
{word: "must", translation: "必须", type: "modal", pronunciation: "mʌst"},
{word: "keep", translation: "保持", type: "verb", pronunciation: "kip"},
{word: "him", translation: "他", type: "pronoun", pronunciation: "hɪm"},
{word: "warm", translation: "温暖", type: "adjective", pronunciation: "wɔrm"}
]
},
{
id: 4,
original: "Don't give him cold drinks.",
translation: "不要给他冷饮。",
words: [
{word: "Don't", translation: "不要", type: "auxiliary", pronunciation: "dəʊnt"},
{word: "give", translation: "给", type: "verb", pronunciation: "gɪv"},
{word: "him", translation: "他", type: "pronoun", pronunciation: "hɪm"},
{word: "cold", translation: "冷的", type: "adjective", pronunciation: "kəʊld"},
{word: "drinks", translation: "饮料", type: "noun", pronunciation: "drɪŋks"}
]
}
]
}
]
}
]
};
// ============================================================================
// CONTENT STRUCTURE SUMMARY - FOR AI REFERENCE
// ============================================================================
// This module contains:
// - Medical dialogue vocabulary and situations
// - Modal verbs (must/mustn't) for obligations and prohibitions
// - Imperative commands for instructions and warnings
// - Present simple questions for medical inquiries
// - Safety-focused vocabulary and scenarios
// - Chinese translations for all content
// - Comprehensive grammar explanations and examples
// ============================================================================

File diff suppressed because it is too large Load Diff

View File

@ -1,785 +0,0 @@
// === LESSON 30: FOOTBALL OR POLO? ===
// English learning story with Chinese translation - Intermediate Level
window.ContentModules = window.ContentModules || {};
window.ContentModules.NCE2Lesson30 = {
id: "nce2-lesson30",
name: "NCE2-Lesson30",
description: "Football or polo? - A story about the Wayle river with grammar focus on articles and quantifiers",
difficulty: "intermediate",
language: "en-US",
userLanguage: "zh-CN",
totalWords: 200,
// === GRAMMAR LESSONS SYSTEM ===
grammar: {
"articles-usage": {
title: "Articles Usage - 冠词使用",
explanation: "English uses 'a', 'an', and 'the' in specific ways, especially with names and places.",
rules: [
"the - use with rivers, seas, oceans, mountain ranges: the Thames, the Pacific",
"no article - use with most personal names and countries: John, England",
"the - use with certain countries: the United States, the United Kingdom",
"the - use with superlatives and unique things: the best, the sun"
],
examples: [
{
english: "The Wayle is a small river.",
chinese: "威尔河是一条小河。",
explanation: "Use 'the' with river names",
pronunciation: "ðə weɪl ɪz ə smɔːl ˈrɪvər"
},
{
english: "Paris is on the Seine.",
chinese: "巴黎在塞纳河上。",
explanation: "Use 'the' with famous rivers",
pronunciation: "ˈpærɪs ɪz ɒn ðə seɪn"
},
{
english: "London is on the Thames.",
chinese: "伦敦在泰晤士河上。",
explanation: "River names always take 'the'",
pronunciation: "ˈlʌndən ɪz ɒn ðə temz"
},
{
english: "He lives in England.",
chinese: "他住在英国。",
explanation: "Most country names don't use 'the'",
pronunciation: "hiː lɪvz ɪn ˈɪŋɡlənd"
},
{
english: "I went to the United States.",
chinese: "我去了美国。",
explanation: "Some countries use 'the'",
pronunciation: "aɪ went tuː ðə juˈnaɪtɪd steɪts"
}
],
exercises: [
{
type: "fill_blank",
sentence: "_____ Thames flows through London.",
options: ["The", "A", "An", "No article"],
correct: "The",
explanation: "River names always use 'the'"
},
{
type: "fill_blank",
sentence: "John lives in _____ England.",
options: ["the", "a", "an", "no article"],
correct: "no article",
explanation: "Most country names don't use articles"
}
]
},
"some-any-usage": {
title: "Some and Any Usage - Some和Any的使用",
explanation: "English uses 'some' and 'any' differently depending on sentence type and meaning.",
rules: [
"some - use in positive statements: There are some people",
"any - use in questions: Are there any people?",
"any - use in negative statements: There aren't any people",
"some - use in offers and requests: Would you like some tea?"
],
examples: [
{
english: "Some children were playing games.",
chinese: "一些孩子在玩游戏。",
explanation: "Use 'some' in positive statements",
pronunciation: "sʌm ˈɪldrən wər ˈpleɪɪŋ ɡeɪmz"
},
{
english: "There were some people rowing.",
chinese: "有一些人在划船。",
explanation: "Use 'some' to describe what exists",
pronunciation: "ðər wər sʌm ˈpiːpəl ˈroʊɪŋ"
},
{
english: "There weren't any children in sight.",
chinese: "看不见任何孩子。",
explanation: "Use 'any' in negative statements",
pronunciation: "ðər wərnt ˈeni ˈɪldrən ɪn saɪt"
},
{
english: "Are there any boats on the river?",
chinese: "河上有船吗?",
explanation: "Use 'any' in questions",
pronunciation: "ər ðər ˈeni boʊts ɒn ðə ˈrɪvər"
},
{
english: "Would you like some water?",
chinese: "你想要一些水吗?",
explanation: "Use 'some' in offers",
pronunciation: "wʊd juː laɪk sʌm ˈːtər"
}
],
exercises: [
{
type: "fill_blank",
sentence: "There are _____ people on the bank.",
options: ["some", "any", "a", "the"],
correct: "some",
explanation: "Use 'some' in positive statements"
},
{
type: "fill_blank",
sentence: "There weren't _____ children in sight.",
options: ["some", "any", "a", "the"],
correct: "any",
explanation: "Use 'any' in negative statements"
}
]
},
"past-continuous": {
title: "Past Continuous Tense - 过去进行时",
explanation: "Use past continuous for actions that were in progress at a specific time in the past.",
rules: [
"Form: was/were + verb-ing",
"Use for ongoing past actions: I was sitting by the river",
"Use for background actions in stories: Children were playing while I sat",
"Use with time expressions: at 3pm yesterday, last Sunday"
],
examples: [
{
english: "Some children were playing games.",
chinese: "一些孩子在玩游戏。",
explanation: "Action in progress in the past",
pronunciation: "sʌm ˈɪldrən wər ˈpleɪɪŋ ɡeɪmz"
},
{
english: "People were rowing on the river.",
chinese: "人们在河上划船。",
explanation: "Ongoing action in the past",
pronunciation: "ˈpiːpəl wər ˈroʊɪŋ ɒn ðə ˈrɪvər"
},
{
english: "I was sitting by the river.",
chinese: "我坐在河边。",
explanation: "Past continuous shows duration",
pronunciation: "aɪ wəz ˈsɪtɪŋ baɪ ðə ˈrɪvər"
}
],
exercises: [
{
type: "fill_blank",
sentence: "The children _____ playing when it happened.",
options: ["were", "was", "are", "is"],
correct: "were",
explanation: "Use 'were' with plural subjects in past continuous"
}
]
}
},
vocabulary: {
"polo": {
"user_language": "水球",
"type": "noun",
"pronunciation": "ˈpoʊloʊ"
},
"river": {
"user_language": "河流",
"type": "noun",
"pronunciation": "ˈrɪvər"
},
"cut": {
"user_language": "穿过",
"type": "verb",
"pronunciation": "kʌt"
},
"park": {
"user_language": "公园",
"type": "noun",
"pronunciation": "pɑːrk"
},
"afternoon": {
"user_language": "下午",
"type": "noun",
"pronunciation": "ˌæftərˈnuːn"
},
"bank": {
"user_language": "河岸",
"type": "noun",
"pronunciation": "bæŋk"
},
"usual": {
"user_language": "平常的",
"type": "adjective",
"pronunciation": "ˈjuːʒuəl"
},
"children": {
"user_language": "孩子们",
"type": "noun",
"pronunciation": "ˈɪldrən"
},
"games": {
"user_language": "游戏",
"type": "noun",
"pronunciation": "ɡeɪmz"
},
"people": {
"user_language": "人们",
"type": "noun",
"pronunciation": "ˈpiːpəl"
},
"row": {
"user_language": "划船",
"type": "verb",
"pronunciation": "roʊ"
},
"suddenly": {
"user_language": "突然",
"type": "adverb",
"pronunciation": "ˈsʌdənli"
},
"kick": {
"user_language": "踢",
"type": "verb",
"pronunciation": "kɪk"
},
"ball": {
"user_language": "球",
"type": "noun",
"pronunciation": "bɔːl"
},
"hard": {
"user_language": "用力地",
"type": "adverb",
"pronunciation": "hɑːrd"
},
"towards": {
"user_language": "朝向",
"type": "preposition",
"pronunciation": "təˈːrdz"
},
"passing": {
"user_language": "经过的",
"type": "adjective",
"pronunciation": "ˈpæsɪŋ"
},
"boat": {
"user_language": "船",
"type": "noun",
"pronunciation": "boʊt"
},
"called": {
"user_language": "叫喊",
"type": "verb",
"pronunciation": "kɔːld"
},
"hear": {
"user_language": "听见",
"type": "verb",
"pronunciation": "hɪr"
},
"struck": {
"user_language": "击打",
"type": "verb",
"pronunciation": "strʌk"
},
"nearly": {
"user_language": "几乎",
"type": "adverb",
"pronunciation": "ˈnɪrli"
},
"fell": {
"user_language": "落下",
"type": "verb",
"pronunciation": "fel"
},
"water": {
"user_language": "水",
"type": "noun",
"pronunciation": "ˈːtər"
},
"turned": {
"user_language": "转身",
"type": "verb",
"pronunciation": "tɜːrnd"
},
"sight": {
"user_language": "视线",
"type": "noun",
"pronunciation": "saɪt"
},
"run away": {
"user_language": "跑开",
"type": "verb",
"pronunciation": "rʌn əˈweɪ"
},
"laughed": {
"user_language": "笑",
"type": "verb",
"pronunciation": "læft"
},
"realized": {
"user_language": "意识到",
"type": "verb",
"pronunciation": "ˈriəlaɪzd"
},
"happened": {
"user_language": "发生",
"type": "verb",
"pronunciation": "ˈhæpənd"
},
"threw": {
"user_language": "扔",
"type": "verb",
"pronunciation": "θruː"
},
"back": {
"user_language": "回来",
"type": "adverb",
"pronunciation": "bæk"
}
},
// === SENTENCES FOR GAMES (extracted from stories) ===
sentences: [
{
english: "The Wayle is a small river.",
chinese: "威尔河是一条小河。",
prononciation: "ðə weɪl ɪz ə smɔːl ˈrɪvər"
},
{
english: "It cuts across the park near my home.",
chinese: "它横穿我家附近的公园。",
prononciation: "ɪt kʌts əˈkrɔːs ðə pɑːrk nɪr maɪ hoʊm"
},
{
english: "I like sitting by the Wayle on fine afternoons.",
chinese: "我喜欢在晴朗的下午坐在威尔河边。",
prononciation: "aɪ laɪk ˈsɪtɪŋ baɪ ðə weɪl ɑːn faɪn ˌæftərˈnuːnz"
},
{
english: "Some children were playing games on the bank.",
chinese: "一些孩子在河岸上玩游戏。",
prononciation: "sʌm ˈɪldrən wər ˈpleɪɪŋ geɪmz ɑːn ðə bæŋk"
},
{
english: "There were some people rowing on the river.",
chinese: "河上有一些人在划船。",
prononciation: "ðɛr wər sʌm ˈpiːpəl ˈroʊɪŋ ɑːn ðə ˈrɪvər"
},
{
english: "The ball struck him so hard.",
chinese: "球重重地打在他身上。",
prononciation: "ðə bɔːl strʌk hɪm soʊ hɑːrd"
},
{
english: "This is a pleasant surprise!",
chinese: "这真是个意外的惊喜!",
prononciation: ɪs ɪz ə ˈplɛzənt sərˈpraɪz"
},
{
english: "I turned to look at the children.",
chinese: "我转身看向孩子们。",
prononciation: "aɪːrnd tuː lʊk æt ðə ˈɪldrən"
},
{
english: "They were playing football, not polo!",
chinese: "他们在踢足球,不是水球!",
prononciation: "ðeɪ wər ˈpleɪɪŋ ˈfʊtbɔːl nɑːt ˈpoʊloʊ"
}
],
story: {
title: "Football or polo? - 足球还是水球?",
totalSentences: 12,
chapters: [
{
title: "Chapter 1: A Day by the River - 河边的一天",
sentences: [
{
id: 1,
original: "The Wayle is a small river that cuts across the park near my home.",
translation: "威尔河是横穿我家附近公园的一条小河。",
words: [
{word: "The", translation: "这条", type: "article", pronunciation: "ðə"},
{word: "Wayle", translation: "威尔河", type: "noun", pronunciation: "weɪl"},
{word: "is", translation: "是", type: "verb", pronunciation: "ɪz"},
{word: "a", translation: "一条", type: "article", pronunciation: "ə"},
{word: "small", translation: "小的", type: "adjective", pronunciation: "smɔːl"},
{word: "river", translation: "河流", type: "noun", pronunciation: "ˈrɪvər"},
{word: "that", translation: "那", type: "pronoun", pronunciation: "ðæt"},
{word: "cuts", translation: "穿过", type: "verb", pronunciation: "kʌts"},
{word: "across", translation: "横穿", type: "preposition", pronunciation: ˈkrɔːs"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "park", translation: "公园", type: "noun", pronunciation: "pɑːrk"},
{word: "near", translation: "靠近", type: "preposition", pronunciation: "nɪr"},
{word: "my", translation: "我的", type: "pronoun", pronunciation: "maɪ"},
{word: "home", translation: "家", type: "noun", pronunciation: "hoʊm"}
]
},
{
id: 2,
original: "I like sitting by the Wayle on fine afternoons.",
translation: "我喜欢在天气晴朗的下午坐在威尔河边。",
words: [
{word: "I", translation: "我", type: "pronoun", pronunciation: "aɪ"},
{word: "like", translation: "喜欢", type: "verb", pronunciation: "laɪk"},
{word: "sitting", translation: "坐", type: "verb", pronunciation: "ˈsɪtɪŋ"},
{word: "by", translation: "在...旁边", type: "preposition", pronunciation: "baɪ"},
{word: "the", translation: "这条", type: "article", pronunciation: "ðə"},
{word: "Wayle", translation: "威尔河", type: "noun", pronunciation: "weɪl"},
{word: "on", translation: "在", type: "preposition", pronunciation: "ɒn"},
{word: "fine", translation: "晴朗的", type: "adjective", pronunciation: "faɪn"},
{word: "afternoons", translation: "下午", type: "noun", pronunciation: "ˌæftərˈnuːnz"}
]
},
{
id: 3,
original: "It was warm last Sunday, so I went and sat on the river bank as usual.",
translation: "上星期日天气很暖和,于是我像往常一样又去河边坐坐。",
words: [
{word: "It", translation: "天气", type: "pronoun", pronunciation: "ɪt"},
{word: "was", translation: "是", type: "verb", pronunciation: "wəz"},
{word: "warm", translation: "暖和的", type: "adjective", pronunciation: "wɔːrm"},
{word: "last", translation: "上", type: "adjective", pronunciation: "læst"},
{word: "Sunday", translation: "星期日", type: "noun", pronunciation: "ˈsʌndeɪ"},
{word: "so", translation: "所以", type: "conjunction", pronunciation: "soʊ"},
{word: "I", translation: "我", type: "pronoun", pronunciation: "aɪ"},
{word: "went", translation: "去了", type: "verb", pronunciation: "went"},
{word: "and", translation: "和", type: "conjunction", pronunciation: "ænd"},
{word: "sat", translation: "坐", type: "verb", pronunciation: "sæt"},
{word: "on", translation: "在", type: "preposition", pronunciation: "ɒn"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "river", translation: "河", type: "noun", pronunciation: "ˈrɪvər"},
{word: "bank", translation: "岸边", type: "noun", pronunciation: "bæŋk"},
{word: "as", translation: "像", type: "adverb", pronunciation: "æz"},
{word: "usual", translation: "往常一样", type: "adjective", pronunciation: "ˈjuːʒuəl"}
]
},
{
id: 4,
original: "Some children were playing games on the bank and there were some people rowing on the river.",
translation: "河岸上有些孩子在玩耍,河面上有些人在划船。",
words: [
{word: "Some", translation: "一些", type: "determiner", pronunciation: "sʌm"},
{word: "children", translation: "孩子们", type: "noun", pronunciation: "ˈɪldrən"},
{word: "were", translation: "正在", type: "verb", pronunciation: "wər"},
{word: "playing", translation: "玩", type: "verb", pronunciation: "ˈpleɪɪŋ"},
{word: "games", translation: "游戏", type: "noun", pronunciation: "ɡeɪmz"},
{word: "on", translation: "在", type: "preposition", pronunciation: "ɒn"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "bank", translation: "岸边", type: "noun", pronunciation: "bæŋk"},
{word: "and", translation: "和", type: "conjunction", pronunciation: "ænd"},
{word: "there", translation: "那里", type: "adverb", pronunciation: "ðer"},
{word: "were", translation: "有", type: "verb", pronunciation: "wər"},
{word: "some", translation: "一些", type: "determiner", pronunciation: "sʌm"},
{word: "people", translation: "人们", type: "noun", pronunciation: "ˈpiːpəl"},
{word: "rowing", translation: "划船", type: "verb", pronunciation: "ˈroʊɪŋ"},
{word: "on", translation: "在", type: "preposition", pronunciation: "ɒn"},
{word: "the", translation: "这条", type: "article", pronunciation: "ðə"},
{word: "river", translation: "河", type: "noun", pronunciation: "ˈrɪvər"}
]
}
]
},
{
title: "Chapter 2: The Ball Incident - 球的事件",
sentences: [
{
id: 5,
original: "Suddenly, one of the children kicked a ball very hard and it went towards a passing boat.",
translation: "突然,一个孩子狠狠地踢了一脚球,球便向一条经过的小船飞去。",
words: [
{word: "Suddenly", translation: "突然", type: "adverb", pronunciation: "ˈsʌdənli"},
{word: "one", translation: "一个", type: "number", pronunciation: "wʌn"},
{word: "of", translation: "的", type: "preposition", pronunciation: "ʌv"},
{word: "the", translation: "这些", type: "article", pronunciation: "ðə"},
{word: "children", translation: "孩子们", type: "noun", pronunciation: "ˈɪldrən"},
{word: "kicked", translation: "踢了", type: "verb", pronunciation: "kɪkt"},
{word: "a", translation: "一个", type: "article", pronunciation: "ə"},
{word: "ball", translation: "球", type: "noun", pronunciation: "bɔːl"},
{word: "very", translation: "非常", type: "adverb", pronunciation: "ˈveri"},
{word: "hard", translation: "用力地", type: "adverb", pronunciation: "hɑːrd"},
{word: "and", translation: "并且", type: "conjunction", pronunciation: "ænd"},
{word: "it", translation: "它", type: "pronoun", pronunciation: "ɪt"},
{word: "went", translation: "飞去", type: "verb", pronunciation: "went"},
{word: "towards", translation: "朝向", type: "preposition", pronunciation: "təˈːrdz"},
{word: "a", translation: "一条", type: "article", pronunciation: "ə"},
{word: "passing", translation: "经过的", type: "adjective", pronunciation: "ˈpæsɪŋ"},
{word: "boat", translation: "船", type: "noun", pronunciation: "boʊt"}
]
},
{
id: 6,
original: "Some people on the bank called out to the man in the boat, but he did not hear them.",
translation: "岸上的一些人对船上的人高喊,但他没有听见。",
words: [
{word: "Some", translation: "一些", type: "determiner", pronunciation: "sʌm"},
{word: "people", translation: "人们", type: "noun", pronunciation: "ˈpiːpəl"},
{word: "on", translation: "在", type: "preposition", pronunciation: "ɒn"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "bank", translation: "岸上", type: "noun", pronunciation: "bæŋk"},
{word: "called", translation: "叫喊", type: "verb", pronunciation: "kɔːld"},
{word: "out", translation: "出来", type: "adverb", pronunciation: "aʊt"},
{word: "to", translation: "对", type: "preposition", pronunciation: "tuː"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "man", translation: "人", type: "noun", pronunciation: "mæn"},
{word: "in", translation: "在", type: "preposition", pronunciation: "ɪn"},
{word: "the", translation: "这条", type: "article", pronunciation: "ðə"},
{word: "boat", translation: "船上", type: "noun", pronunciation: "boʊt"},
{word: "but", translation: "但是", type: "conjunction", pronunciation: "bʌt"},
{word: "he", translation: "他", type: "pronoun", pronunciation: "hiː"},
{word: "did", translation: "助动词", type: "auxiliary", pronunciation: "dɪd"},
{word: "not", translation: "不", type: "adverb", pronunciation: "nɑːt"},
{word: "hear", translation: "听见", type: "verb", pronunciation: "hɪr"},
{word: "them", translation: "他们", type: "pronoun", pronunciation: "ðem"}
]
},
{
id: 7,
original: "The ball struck him so hard that he nearly fell into the water.",
translation: "球重重地打在他身上,使他差点儿落入水中。",
words: [
{word: "The", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "ball", translation: "球", type: "noun", pronunciation: "bɔːl"},
{word: "struck", translation: "击打", type: "verb", pronunciation: "strʌk"},
{word: "him", translation: "他", type: "pronoun", pronunciation: "hɪm"},
{word: "so", translation: "如此", type: "adverb", pronunciation: "soʊ"},
{word: "hard", translation: "用力", type: "adverb", pronunciation: "hɑːrd"},
{word: "that", translation: "以至于", type: "conjunction", pronunciation: "ðæt"},
{word: "he", translation: "他", type: "pronoun", pronunciation: "hiː"},
{word: "nearly", translation: "几乎", type: "adverb", pronunciation: "ˈnɪrli"},
{word: "fell", translation: "落下", type: "verb", pronunciation: "fel"},
{word: "into", translation: "进入", type: "preposition", pronunciation: "ˈɪntuː"},
{word: "the", translation: "这", type: "article", pronunciation: "ðə"},
{word: "water", translation: "水中", type: "noun", pronunciation: "ˈːtər"}
]
},
{
id: 8,
original: "I turned to look at the children, but there weren't any in sight: they had all run away!",
translation: "我转过头去看那些孩子,但一个也不见,全都跑了!",
words: [
{word: "I", translation: "我", type: "pronoun", pronunciation: "aɪ"},
{word: "turned", translation: "转身", type: "verb", pronunciation: "tɜːrnd"},
{word: "to", translation: "去", type: "preposition", pronunciation: "tuː"},
{word: "look", translation: "看", type: "verb", pronunciation: "lʊk"},
{word: "at", translation: "看", type: "preposition", pronunciation: "æt"},
{word: "the", translation: "这些", type: "article", pronunciation: "ðə"},
{word: "children", translation: "孩子们", type: "noun", pronunciation: "ˈɪldrən"},
{word: "but", translation: "但是", type: "conjunction", pronunciation: "bʌt"},
{word: "there", translation: "那里", type: "adverb", pronunciation: "ðer"},
{word: "weren't", translation: "没有", type: "verb", pronunciation: "wərnt"},
{word: "any", translation: "任何", type: "determiner", pronunciation: "ˈeni"},
{word: "in", translation: "在", type: "preposition", pronunciation: "ɪn"},
{word: "sight", translation: "视线中", type: "noun", pronunciation: "saɪt"},
{word: "they", translation: "他们", type: "pronoun", pronunciation: "ðeɪ"},
{word: "had", translation: "已经", type: "auxiliary", pronunciation: "hæd"},
{word: "all", translation: "全部", type: "adverb", pronunciation: ːl"},
{word: "run", translation: "跑", type: "verb", pronunciation: "rʌn"},
{word: "away", translation: "走了", type: "adverb", pronunciation: ˈweɪ"}
]
}
]
},
{
title: "Chapter 3: The Happy Ending - 愉快的结局",
sentences: [
{
id: 9,
original: "The man laughed when he realized what had happened.",
translation: "当那个人明白了发生的事情时,笑了。",
words: [
{word: "The", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "man", translation: "人", type: "noun", pronunciation: "mæn"},
{word: "laughed", translation: "笑了", type: "verb", pronunciation: "læft"},
{word: "when", translation: "当", type: "conjunction", pronunciation: "wen"},
{word: "he", translation: "他", type: "pronoun", pronunciation: "hiː"},
{word: "realized", translation: "意识到", type: "verb", pronunciation: "ˈriəlaɪzd"},
{word: "what", translation: "什么", type: "pronoun", pronunciation: "wʌt"},
{word: "had", translation: "已经", type: "auxiliary", pronunciation: "hæd"},
{word: "happened", translation: "发生了", type: "verb", pronunciation: "ˈhæpənd"}
]
},
{
id: 10,
original: "He called out to the children and threw the ball back to the bank.",
translation: "他大声叫那些孩子,把球扔回到岸上。",
words: [
{word: "He", translation: "他", type: "pronoun", pronunciation: "hiː"},
{word: "called", translation: "叫", type: "verb", pronunciation: "kɔːld"},
{word: "out", translation: "出来", type: "adverb", pronunciation: "aʊt"},
{word: "to", translation: "对", type: "preposition", pronunciation: "tuː"},
{word: "the", translation: "这些", type: "article", pronunciation: "ðə"},
{word: "children", translation: "孩子们", type: "noun", pronunciation: "ˈɪldrən"},
{word: "and", translation: "并且", type: "conjunction", pronunciation: "ænd"},
{word: "threw", translation: "扔", type: "verb", pronunciation: "θruː"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "ball", translation: "球", type: "noun", pronunciation: "bɔːl"},
{word: "back", translation: "回", type: "adverb", pronunciation: "bæk"},
{word: "to", translation: "到", type: "preposition", pronunciation: "tuː"},
{word: "the", translation: "这个", type: "article", pronunciation: "ðə"},
{word: "bank", translation: "岸上", type: "noun", pronunciation: "bæŋk"}
]
}
]
}
]
},
// === GRAMMAR-BASED FILL IN THE BLANKS ===
fillInBlanks: [
{
sentence: "_____ Wayle is a small river.",
options: ["The", "A", "An", "No article"],
correctAnswer: "The",
explanation: "River names always use 'the'",
grammarFocus: "articles-usage"
},
{
sentence: "There were _____ people rowing on the river.",
options: ["some", "any", "a", "the"],
correctAnswer: "some",
explanation: "Use 'some' in positive statements",
grammarFocus: "some-any-usage"
},
{
sentence: "There weren't _____ children in sight.",
options: ["some", "any", "a", "the"],
correctAnswer: "any",
explanation: "Use 'any' in negative statements",
grammarFocus: "some-any-usage"
},
{
sentence: "The children _____ playing games on the bank.",
options: ["were", "was", "are", "is"],
correctAnswer: "were",
explanation: "Use 'were' with plural subjects in past continuous",
grammarFocus: "past-continuous"
},
{
sentence: "He lives in _____ England.",
options: ["the", "a", "an", "no article"],
correctAnswer: "no article",
explanation: "Most country names don't use articles",
grammarFocus: "articles-usage"
},
{
sentence: "Are there _____ boats on the river?",
options: ["some", "any", "a", "the"],
correctAnswer: "any",
explanation: "Use 'any' in questions",
grammarFocus: "some-any-usage"
}
],
// === GRAMMAR CORRECTION EXERCISES ===
corrections: [
{
incorrect: "Wayle is small river.",
correct: "The Wayle is a small river.",
explanation: "River names need 'the' and countable nouns need 'a'",
grammarFocus: "articles-usage"
},
{
incorrect: "There were any children playing.",
correct: "There were some children playing.",
explanation: "Use 'some' in positive statements, not 'any'",
grammarFocus: "some-any-usage"
},
{
incorrect: "There wasn't some people in sight.",
correct: "There weren't any people in sight.",
explanation: "Use 'any' in negative statements, not 'some'",
grammarFocus: "some-any-usage"
},
{
incorrect: "He goes to United States.",
correct: "He goes to the United States.",
explanation: "Some countries like 'the United States' require 'the'",
grammarFocus: "articles-usage"
}
],
// === COMPREHENSION QUESTIONS ===
comprehension: [
{
question: "Where does the writer like to sit?",
options: [
"In the park",
"By the Wayle river",
"On the boat",
"In his garden"
],
correct: "By the Wayle river",
explanation: "The writer says 'I like sitting by the Wayle on fine afternoons'"
},
{
question: "What were the children doing?",
options: [
"Swimming in the river",
"Playing games on the bank",
"Rowing on the river",
"Sitting by the water"
],
correct: "Playing games on the bank",
explanation: "The text states 'Some children were playing games on the bank'"
},
{
question: "Why didn't the man in the boat hear the people calling?",
options: [
"He was deaf",
"The text doesn't say why",
"He was sleeping",
"The water was too loud"
],
correct: "The text doesn't say why",
explanation: "The text only says 'he did not hear them' but doesn't give a reason"
},
{
question: "What happened after the ball hit the man?",
options: [
"He fell into the water",
"He got angry with the children",
"The children ran away",
"He threw the ball at the children"
],
correct: "The children ran away",
explanation: "The text says 'there weren't any in sight: they had all run away!'"
},
{
question: "How did the story end?",
options: [
"The man was angry",
"The man called police",
"The man laughed and threw the ball back",
"The children came back"
],
correct: "The man laughed and threw the ball back",
explanation: "The text ends with 'The man laughed... threw the ball back to the bank'"
}
]
};
// ============================================================================
// CONTENT STRUCTURE SUMMARY - FOR AI REFERENCE
// ============================================================================
//
// This module represents INTERMEDIATE level English learning content for Chinese speakers:
// - Focus on past tense narrative and descriptive language
// - Grammar emphasis on articles (the/a/an) and quantifiers (some/any)
// - Rich vocabulary around outdoor activities and everyday situations
// - Complex sentence structures with subordinate clauses
// - Cultural context of British/Western recreational activities
//
// LEARNING OBJECTIVES:
// - Master use of definite/indefinite articles with different noun types
// - Understand some/any usage in different sentence types
// - Practice past continuous vs simple past tense
// - Build vocabulary around outdoor activities and emotions
// - Develop reading comprehension of narrative texts
//
// DIFFICULTY INDICATORS:
// - Longer sentences with multiple clauses
// - Past perfect and continuous tenses
// - Abstract concepts (realization, consequences)
// - Cultural references (British countryside, recreational activities)
// - Advanced vocabulary (struck, realized, rowing, etc.)
//
// ============================================================================

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,230 +0,0 @@
# Tests - Class Generator
Suite complète de tests unitaires (TU) et tests d'intégration (TI) pour l'application Class Generator.
## 📁 Structure
```
tests/
├── unit/ # Tests unitaires
│ ├── env-config.test.js # Tests pour EnvConfig
│ ├── content-scanner.test.js # Tests pour ContentScanner
│ └── game-loader.test.js # Tests pour GameLoader
├── integration/ # Tests d'intégration
│ ├── proxy-digitalocean.test.js # Tests du proxy DigitalOcean
│ ├── content-loading-flow.test.js # Tests du flux de chargement
│ └── navigation-system.test.js # Tests du système de navigation
├── fixtures/ # Données de test
│ └── content-samples.js # Échantillons de contenu
├── utils/ # Utilitaires de test
│ └── test-helpers.js # Helpers et mocks
├── run-tests.js # Script principal de lancement
├── package.json # Configuration npm pour les tests
└── README.md # Cette documentation
```
## 🚀 Lancement des Tests
### Installation
```bash
cd tests/
npm install # (optionnel, pour jsdom si nécessaire)
```
### Commandes Principales
```bash
# Tous les tests
node run-tests.js
# Tests unitaires seulement
node run-tests.js --unit-only
# Tests d'intégration seulement
node run-tests.js --integration-only
# Mode verbose (affichage détaillé)
node run-tests.js --verbose
# Avec couverture de code
node run-tests.js --coverage
# Tests spécifiques
node run-tests.js --pattern=content
# Arrêt au premier échec
node run-tests.js --bail
```
### Via NPM (depuis le dossier tests/)
```bash
npm test # Tous les tests
npm run test:unit # Tests unitaires
npm run test:integration # Tests d'intégration
```
## 📋 Tests Unitaires (TU)
### EnvConfig (`env-config.test.js`)
- ✅ Construction et configuration
- ✅ Méthodes utilitaires (isRemoteContentEnabled, etc.)
- ✅ Test de connectivité avec timeouts
- ✅ Configuration dynamique
- ✅ Génération de signatures AWS
- ✅ Diagnostics
### ContentScanner (`content-scanner.test.js`)
- ✅ Initialisation et découverte de contenu
- ✅ Conversion de noms de modules JSON → JS
- ✅ Chargement de contenu JSON distant/local
- ✅ Scan de fichiers de contenu (.js et .json)
- ✅ Gestion des erreurs et logs
- ✅ Discovery de fichiers communs
### GameLoader (`game-loader.test.js`)
- ✅ Chargement et création d'instances de jeu
- ✅ Conversion de noms (game types → class names)
- ✅ Cycle de vie des jeux (start, destroy, restart)
- ✅ Validation de contenu pour les jeux
- ✅ Gestion des erreurs de construction
- ✅ État et informations du jeu actuel
## 🔗 Tests d'Intégration (TI)
### Proxy DigitalOcean (`proxy-digitalocean.test.js`)
- ✅ Endpoints du proxy (`/do-proxy/filename.json`)
- ✅ Support des méthodes GET et HEAD
- ✅ Headers CORS et authentification
- ✅ Listing des fichiers (`/do-proxy/_list`)
- ✅ Performance et requêtes simultanées
- ✅ Gestion des erreurs 403/404
- ✅ Intégration DigitalOcean Spaces réelle
### Flux de Chargement (`content-loading-flow.test.js`)
- ✅ Flux complet: Scan → Load → Game
- ✅ Fallback local si distant échoue
- ✅ Respect des priorités de configuration
- ✅ Gestion des erreurs en cascade
- ✅ Performance et cache
- ✅ Validation d'intégrité des données
### Système de Navigation (`navigation-system.test.js`)
- ✅ Parsing d'URL et paramètres
- ✅ Navigation et routage entre pages
- ✅ Gestion de l'historique de navigation
- ✅ Validation de routes et paramètres
- ✅ État de connectivité réseau
- ✅ Intégration avec GameLoader
- ✅ Gestion des erreurs de navigation
## 🛠️ Utilitaires de Test
### Test Helpers (`utils/test-helpers.js`)
- `createMockDOM()` - Environnement DOM simulé
- `createMockFetch()` - Mock pour requêtes réseau
- `createLogCapture()` - Capture des logs pour vérification
- `createTimerMock()` - Mock des timers/setTimeout
- `loadModuleForTest()` - Chargement de modules pour tests
- Assertions personnalisées
### Fixtures (`fixtures/content-samples.js`)
- Échantillons de contenu JSON et JS
- Données de test pour compatibilité des jeux
- Réponses réseau mockées
- Cas de test pour validation
## 📊 Coverage et Métriques
Les tests couvrent:
### Modules Core (Couverture ~90%+)
- **EnvConfig**: Configuration, connectivité, AWS auth
- **ContentScanner**: Discovery, chargement JSON/JS, fallbacks
- **GameLoader**: Instantiation, validation, cycle de vie
### Flux d'Intégration (Couverture ~85%+)
- **Proxy DigitalOcean**: Authentification, CORS, performance
- **Chargement de Contenu**: Priorités, fallbacks, validation
- **Navigation**: Routage, historique, état réseau
### Points Non Couverts
- Interface utilisateur (événements DOM complexes)
- Jeux individuels (logique métier spécifique)
- WebSocket logger en temps réel
- Fonctionnalités futures (IA, chinois)
## 🚨 Contraintes et Limitations
### Environnement de Test
- Node.js 18+ requis (support natif `--test`)
- Pas de navigateur réel (simulation DOM)
- Pas d'accès fichier système complet
- Réseau mockée pour la plupart des tests
### Tests d'Intégration
- Nécessitent serveurs actifs (proxy sur 8083)
- Dépendent de la connectivité DigitalOcean
- Peuvent échouer si clés d'API invalides
- Timeouts pour éviter blocages
### Performance
- Tests rapides (~30s max pour suite complète)
- Parallélisation des TU mais pas des TI
- Mocks utilisés pour éviter latence réseau
## 🔧 Debugging et Maintenance
### Logs de Debug
```bash
# Mode verbose avec tous les logs
node run-tests.js --verbose
# Tests spécifiques avec debug
TEST_VERBOSE=1 node --test tests/unit/content-scanner.test.js
```
### Ajout de Nouveaux Tests
1. **Tests Unitaires**: Créer `tests/unit/nouveau-module.test.js`
2. **Tests d'Intégration**: Créer `tests/integration/nouveau-flux.test.js`
3. **Fixtures**: Ajouter données dans `tests/fixtures/`
4. **Helpers**: Étendre `tests/utils/test-helpers.js`
### Résolution de Problèmes Courants
| Problème | Solution |
|----------|----------|
| Tests timeout | Augmenter timeout dans `run-tests.js` |
| Proxy inaccessible | Vérifier que websocket-server.js est démarré |
| Erreurs DigitalOcean | Vérifier clés d'accès dans env-config.js |
| Modules non trouvés | Vérifier paths relatifs dans test helpers |
| DOM errors | Compléter mocks DOM dans createMockDOM() |
## 📈 Intégration Continue
Les tests peuvent être intégrés dans des pipelines CI/CD:
```yaml
# Exemple GitHub Actions
- name: Run Tests
run: |
cd tests
node run-tests.js --bail --coverage
```
## 🎯 Objectifs de Qualité
- **Couverture**: 85%+ pour modules core
- **Performance**: <30s pour suite complète
- **Fiabilité**: Pas de tests flaky
- **Maintenance**: Tests lisibles et bien documentés
---
💡 **Tip**: Utilisez `--pattern=` pour ne lancer que les tests spécifiques lors du développement.
🔍 **Debug**: Activez `--verbose` pour voir les détails d'exécution et les logs.
🚀 **CI/CD**: Les tests sont conçus pour s'intégrer facilement dans vos pipelines d'automatisation.

204
README.md
View File

@ -1,204 +0,0 @@
# Class Generator 2.0
**Educational Games Platform with Ultra-Modular Architecture**
A complete rewrite of the Class Generator system using strict modular design patterns, vanilla JavaScript, and rigorous separation of concerns.
## 🚀 Quick Start
### Method 1: Batch File (Windows)
```bash
# Double-click or run:
start.bat
```
### Method 2: Command Line
```bash
# Install Node.js (if not already installed)
# Then run:
node server.js
# Or using npm:
npm start
```
### Method 3: NPM Scripts
```bash
npm run dev # Start development server
npm run serve # Same as start
```
**Server will start on:** `http://localhost:3000`
## 🏗️ Architecture Overview
### Core System (Ultra-Rigid)
- **Module.js** - Abstract base class with sealed instances and WeakMap privates
- **EventBus.js** - Strict event system with validation and type safety
- **ModuleLoader.js** - Dependency injection with proper initialization order
- **Router.js** - Navigation with guards, middleware, and state management
- **Application.js** - Bootstrap system with auto-initialization
### Key Architecture Principles
**Inviolable Responsibility** - Each module has exactly one purpose
**Zero Direct Dependencies** - All communication via EventBus
**Sealed Instances** - Modules cannot be modified after creation
**Private State** - Internal data completely inaccessible
**Contract Enforcement** - Abstract methods must be implemented
**Dependency Injection** - No global access, everything injected
## 📁 Project Structure
```
├── src/
│ ├── core/ # Core system modules (sealed)
│ │ ├── Module.js
│ │ ├── EventBus.js
│ │ ├── ModuleLoader.js
│ │ ├── Router.js
│ │ └── index.js
│ ├── components/ # Reusable UI components
│ ├── games/ # Game modules
│ ├── content/ # Content modules
│ ├── styles/ # Modular CSS
│ └── Application.js # Main application bootstrap
├── Legacy/ # Old system (archived)
├── index.html # Entry point
├── server.js # Development server
├── start.bat # Windows quick start
└── package.json # Node.js configuration
```
## 🔥 Features
### Development Server
- **ES6 Modules** - Full import/export support
- **CORS Enabled** - For online communication
- **Hot Reload Ready** - Manual refresh (planned: auto-refresh)
- **Proper MIME Types** - All file types supported
- **Security Headers** - Basic security implemented
### Application System
- **Auto-Start** - No commands needed, just open in browser
- **Loading Screen** - Elegant initialization feedback
- **Debug Panel** - F12 to toggle, shows system status
- **Error Handling** - Graceful error recovery
- **Network Status** - Real-time connectivity indicator
### Module System
- **Strict Validation** - Errors if contracts violated
- **Lifecycle Management** - Proper init/destroy patterns
- **Event-Driven** - Loose coupling via events
- **Dependency Resolution** - Automatic dependency loading
## 🎮 Creating Game Modules
```javascript
import Module from '../core/Module.js';
class MyGame extends Module {
constructor(name, dependencies, config) {
super(name, ['eventBus', 'ui']);
this._eventBus = dependencies.eventBus;
this._ui = dependencies.ui;
this._config = config;
}
async init() {
// Initialize game
this._eventBus.on('game:start', this._handleStart.bind(this), this.name);
this._setInitialized();
}
async destroy() {
// Cleanup
this._setDestroyed();
}
_handleStart(event) {
// Game logic here
}
}
export default MyGame;
```
## 🧩 Adding to Application
```javascript
// In Application.js modules config:
{
name: 'myGame',
path: './games/MyGame.js',
dependencies: ['eventBus', 'ui'],
config: { difficulty: 'easy' }
}
```
## 🐛 Debugging
### Debug Panel (F12)
- System status and uptime
- Loaded modules list
- Event history
- Performance metrics
### Console Access
```javascript
// Global app instance available in console:
window.app.getStatus() // Application status
window.app.getCore().eventBus // Access EventBus
window.app.getCore().router // Access Router
```
### Common Issues
**Module loading fails:**
- Check file paths in Application.js
- Verify module extends Module base class
- Ensure all dependencies are listed
**Events not working:**
- Verify module is registered with EventBus
- Check event type strings match exactly
- Ensure module is initialized before emitting
## 🔒 Security & Rigidity
The architecture enforces several levels of protection:
1. **Sealed Classes** - `Object.seal()` prevents property addition/deletion
2. **Frozen Prototypes** - `Object.freeze()` prevents method modification
3. **WeakMap Privates** - Internal state completely hidden
4. **Abstract Enforcement** - Missing methods throw errors
5. **Validation Layers** - Input validation at every boundary
**Violation attempts will throw explicit errors with helpful messages.**
## 🚀 Next Steps
1. **Create Game Modules** - Implement actual games using new architecture
2. **Add Content System** - Port content loading from Legacy system
3. **UI Components** - Build reusable component library
4. **Performance** - Add lazy loading and caching
5. **Testing** - Add automated test suite
## 📝 Migration from Legacy
The Legacy/ folder contains the complete old system. Key differences:
- **Old:** Global variables and direct coupling
- **New:** Strict modules with dependency injection
- **Old:** CSS modifications in global files
- **New:** Component-scoped CSS injection
- **Old:** Manual module registration
- **New:** Automatic loading with dependency resolution
---
**Built with strict architectural principles for maintainable, scalable educational software.**

View File

@ -1,521 +0,0 @@
# Smart Preview System - Technical Specifications
## 🎯 System Overview
**Smart Preview** is an intelligent course preparation system that provides systematic content review with LLM-powered validation. Students can preview and validate their understanding of entire chapter content through adaptive exercises with prerequisite-based progression.
## 🏗️ Dynamic Modular Architecture
### Orchestrator + Semi-Independent Modules
**Core Philosophy**: Dynamic class system with an orchestrator that pilots semi-independent exercise modules. Each module can be loaded, unloaded, and controlled dynamically.
#### **SmartPreviewOrchestrator.js** - Main Controller
- Extends Module base class
- Manages dynamic loading/unloading of exercise modules
- Handles exercise sequencing and variation patterns
- Coordinates shared services (LLM, Prerequisites)
- Tracks overall progress and session state
#### **Semi-Independent Exercise Modules**
1. **VocabularyModule.js** - Vocabulary exercises (groups of 5)
2. **VocabExamModule.js** - AI-verified comprehensive vocabulary exam system
3. **PhraseModule.js** - Individual phrase comprehension
4. **TextModule.js** - Sentence-by-sentence text processing
5. **AudioModule.js** - Audio comprehension exercises
6. **ImageModule.js** - Image description exercises
7. **GrammarModule.js** - Grammar construction and validation
#### **Shared Service Modules**
- **LLMValidator.js** - LLM integration for all exercise types
- **PrerequisiteEngine.js** - Dependency tracking and content filtering
- **ContextMemory.js** - Progressive context building across exercises
### Module Interface Standard
```javascript
// All exercise modules must implement this interface
class ExerciseModuleInterface {
// Check if module can run with current prerequisites
canRun(prerequisites, chapterContent): boolean;
// Present exercise UI and content
present(container, exerciseData): Promise<void>;
// Validate user input with LLM
validate(userInput, context): Promise<ValidationResult>;
// Get current progress data
getProgress(): ProgressData;
// Clean up and prepare for unloading
cleanup(): void;
}
```
### Dynamic Loading System
```javascript
// Orchestrator can dynamically control modules
orchestrator.loadModule('vocabulary');
orchestrator.switchTo('phrase', prerequisites);
orchestrator.unloadModule('text');
orchestrator.getAvailableModules(currentPrerequisites);
// Module lifecycle management
module.canRun(prereqs) → module.present() → module.validate() → module.cleanup()
```
### Module Dependencies
```javascript
// Orchestrator dependencies
dependencies: ['eventBus', 'contentLoader']
// Each exercise module receives shared services via injection
constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory)
```
## 📋 Functional Requirements
### 1. Systematic Review by Content Type
#### Vocabulary Review (Groups of 5)
- Display 5 vocabulary words with translations
- Test recognition/translation with LLM validation
- Must achieve mastery (80%+) before next group
- Track learned vocabulary for prerequisite system
#### Vocabulary Exam System (AI-Verified Comprehensive Assessment)
- **Batch Testing Approach**: Present 15-20 vocabulary questions in sequence, collect all answers, then send complete batch to AI for evaluation
- **Multiple Question Types**:
- Translation (L1→L2 and L2→L1)
- Definition matching and explanation
- Context usage (fill-in-the-blank with sentences)
- Synonym/antonym identification
- Audio recognition (if audio available)
- **AI-Powered Batch Evaluation**:
- Single API call evaluates entire batch for efficiency
- Cross-question consistency checking within batch
- Semantic understanding over exact word matching
- Context-aware validation for multiple correct answers
- Partial credit for near-correct responses
- Intelligent feedback for incorrect answers
- **Batch Processing Benefits**:
- Reduced API costs and latency
- Consistent evaluation across question set
- Better pattern recognition for student strengths/weaknesses
- **Adaptive Difficulty**: Next batch adapts based on previous batch performance
- **Mastery Certification**: Must achieve 85%+ overall score to pass
- **Spaced Repetition Integration**: Failed words automatically added to review queue
- **Progress Analytics**: Detailed breakdown by word, question type, and difficulty level
#### Phrase Comprehension (Individual)
- Present single phrases for translation/comprehension
- LLM evaluation for semantic accuracy (not exact matching)
- Context-aware validation considering multiple correct answers
#### Text Processing (Sentence by Sentence)
- Break texts into individual sentences
- Progressive context building (sentence 1 → sentence 1+2 → sentence 1+2+3)
- Each sentence validated with accumulated context
- Prerequisites: only texts containing learned vocabulary words
#### Audio Comprehension
- Play audio segments with transcription for LLM reference
- User provides comprehension in their own words
- LLM evaluates understanding against transcription
#### Image Description
- Present images for description exercises
- LLM evaluates vocabulary usage and accuracy of description
- No exact matching required - intelligent semantic evaluation
#### Grammar Exercises
- Present grammar-focused exercises (sentence construction, tense usage, etc.)
- LLM evaluates grammar correctness and natural language flow
- Context-aware grammar validation considering multiple correct structures
- Track grammar concept mastery (present tense, articles, sentence structure, etc.)
### 2. Prerequisite Dependency System
#### Smart Filtering
```javascript
// Only chapter vocabulary words are prerequisites
chapterVocab = ['shirt', 'coat', 'blue', 'work', 'wear'];
// Basic words assumed known
assumedKnown = ['the', 'is', 'a', 'to', 'in', 'on', 'at', ...];
// Dependency extraction
function getPrerequisites(content, chapterVocabulary) {
const words = extractWords(content);
return words.filter(word =>
chapterVocabulary.includes(word) ||
chapterVocabulary.includes(getBaseForm(word))
);
}
```
#### Progressive Unlocking
- Vocabulary groups unlock based on completion of previous groups
- Phrases unlock when ALL required chapter words are learned
- Text sentences unlock progressively with vocabulary mastery
- Real-time content availability updates
### 3. Exercise Variation Engine
#### Dynamic Pattern Generation
```javascript
const exercisePattern = [
'vocabulary-5', // First 5 vocab words
'single-phrase', // Available phrase with learned words
'vocabulary-5', // Next 5 vocab words
'text-sentence-1', // First sentence of available text
'vocabulary-5', // Continue vocabulary
'vocab-exam-checkpoint', // Comprehensive exam after 15-20 learned words
'text-sentence-2-ctx', // Second sentence with context from first
'image-description', // Image exercise
'audio-comprehension', // Audio exercise
'vocab-exam-final', // Final comprehensive exam at chapter end
// Pattern continues...
];
// Vocabulary Exam Trigger Logic
const examTriggers = {
checkpoint: {
condition: 'wordsLearned >= 15 && wordsLearned % 15 === 0',
examType: 'checkpoint',
wordsToTest: 'last15Learned',
passingScore: 80
},
final: {
condition: 'chapterComplete',
examType: 'comprehensive',
wordsToTest: 'allChapterWords',
passingScore: 85
},
remedial: {
condition: 'examFailed',
examType: 'focused',
wordsToTest: 'failedWords',
passingScore: 75
}
};
```
#### Context Memory System
- Store user responses for progressive exercises
- Build context for text comprehension exercises
- Pass accumulated context to LLM for evaluation
- Reset context appropriately between different texts/topics
### 4. LLM Integration
#### Evaluation Prompts
```javascript
// Translation Validation
const translationPrompt = `
Evaluate this language learning translation:
- Original (English): "${originalText}"
- Student translation (Chinese/French): "${userAnswer}"
- Context: ${exerciseType} exercise
- Previous context: ${contextHistory}
Evaluate if the translation captures the essential meaning, even if not word-for-word exact.
Return JSON: {
score: 0-100,
correct: boolean,
feedback: "constructive feedback in user's language",
keyPoints: ["important vocabulary/grammar noted"]
}
`;
// Vocabulary Exam Batch Validation
const vocabExamBatchPrompt = `
Evaluate this vocabulary exam batch (15-20 questions):
- Language level: ${languageLevel}
- Total questions in batch: ${batchSize}
Questions and responses:
${questionsArray.map((q, i) => `
${i+1}. Question type: ${q.type} | Target word: "${q.targetWord}"
Expected: "${q.expectedAnswer}"
Student answer: "${q.userAnswer}"
Context: "${q.contextSentence || 'N/A'}"
`).join('')}
For different question types:
- Translation: Accept semantic equivalents, consider cultural variations
- Definition: Accept paraphrasing that captures core meaning
- Context usage: Evaluate grammatical correctness and semantic appropriateness
- Synonyms: Accept close semantic relationships, not just exact synonyms
Evaluate ALL questions in the batch and look for consistency patterns.
Return JSON: {
batchScore: 0-100,
totalCorrect: number,
totalPartialCredit: number,
overallFeedback: "general performance summary",
questions: [
{
questionId: number,
score: 0-100,
correct: boolean,
partialCredit: boolean,
feedback: "specific feedback for this question",
correctAlternatives: ["alternative answers if applicable"],
learningTip: "helpful tip for this word/concept"
}
// ... for each question in batch
],
strengthAreas: ["areas where student performed well"],
weaknessAreas: ["areas needing improvement"],
recommendedActions: ["specific next steps for improvement"]
}
`;
// Audio Comprehension Validation
const audioPrompt = `
Evaluate audio comprehension:
- Audio transcription: "${transcription}"
- Student comprehension: "${userAnswer}"
- Language level: beginner/intermediate
Did the student understand the main meaning? Accept paraphrasing and different expressions.
Return JSON: {score: 0-100, correct: boolean, feedback: "..."}
`;
// Image Description Validation
const imagePrompt = `
Evaluate image description:
- Student description: "${userAnswer}"
- Target vocabulary from chapter: ${chapterVocab}
- Exercise type: free description
Evaluate vocabulary usage, accuracy of description, and language naturalness.
Return JSON: {score: 0-100, correct: boolean, feedback: "...", vocabularyUsed: []}
`;
// Grammar Exercise Validation
const grammarPrompt = `
Evaluate grammar usage:
- Exercise type: ${grammarType} (sentence construction, tense usage, etc.)
- Student response: "${userAnswer}"
- Target grammar concepts: ${grammarConcepts}
- Language level: ${languageLevel}
Evaluate grammatical correctness, naturalness, and appropriate usage of target concepts.
Accept multiple correct variations but identify errors clearly.
Return JSON: {
score: 0-100,
correct: boolean,
feedback: "constructive grammar feedback",
grammarErrors: ["specific errors identified"],
grammarStrengths: ["correct usage noted"],
suggestion: "alternative correct formulation if needed"
}
`;
```
#### API Configuration
- Support multiple LLM providers (OpenAI, Claude, local Ollama)
- Fallback mechanisms for API failures
- Rate limiting and error handling
- Configurable temperature and model parameters
### 5. Progress Tracking
#### Mastery Validation
- Individual word mastery tracking (attempts, success rate, last reviewed)
- Phrase/sentence comprehension scores
- Grammar concept mastery tracking (tenses, sentence structure, etc.)
- **Vocabulary Exam Performance**:
- Checkpoint exam scores (every 15 words)
- Comprehensive exam certification status
- Question-type specific performance (translation, definition, context, etc.)
- Remedial exam tracking for failed words
- Overall chapter progress percentage
- Difficulty adaptation based on performance
#### Visual Progress Indicators
- Progress bar showing overall completion
- Section-based progress (vocabulary: 45/60, phrases: 8/12, texts: 2/5, grammar: 6/8)
- Mastery indicators (✅ learned, 🟡 reviewing, ❌ needs work)
- Grammar concept tracking (present tense: ✅, articles: 🟡, word order: ❌)
- Time estimates for completion
## 🎮 User Experience Flow
### Session Start
1. Load chapter content and analyze prerequisites
2. Display progress overview and available exercises
3. Present first available exercise based on mastery state
### Exercise Flow
1. **Present Content** - Show vocabulary/phrase/text/image/audio/grammar exercise
2. **User Interaction** - Input translation/description/comprehension/grammar construction
3. **LLM Validation** - Intelligent evaluation with feedback (including grammar analysis)
4. **Progress Update** - Update mastery tracking (vocabulary, grammar concepts, etc.)
5. **Next Exercise** - Dynamic selection based on progress and variation pattern
### Adaptive Progression
- If user struggles (< 60% score): additional practice exercises
- If user excels (> 90% score): accelerated progression
- Smart retry system for failed exercises
- Prerequisite re-evaluation after each mastery update
### Session End
- Progress summary with achievements
- Recommendations for next session
- Mastery gaps identified for focused review
## 🔧 Technical Implementation
### Dynamic Modular File Structure
```
src/games/smart-preview/
├── SmartPreviewOrchestrator.js # Main orchestrator module
├── exercise-modules/ # Semi-independent exercise modules
│ ├── VocabularyModule.js # Vocabulary exercises (groups of 5)
│ ├── VocabExamModule.js # AI-verified batch vocabulary exams
│ ├── PhraseModule.js # Individual phrase comprehension
│ ├── TextModule.js # Sentence-by-sentence processing
│ ├── AudioModule.js # Audio comprehension
│ ├── ImageModule.js # Image description
│ └── GrammarModule.js # Grammar construction
├── services/ # Shared service modules
│ ├── LLMValidator.js # LLM integration for all modules
│ ├── PrerequisiteEngine.js # Dependency tracking
│ └── ContextMemory.js # Progressive context building
├── interfaces/
│ └── ExerciseModuleInterface.js # Standard interface for all modules
└── templates/
├── orchestrator.html # Main UI template
└── modules/ # Module-specific templates
├── vocabulary.html
├── vocab-exam.html # Batch exam interface
├── phrase.html
├── text.html
├── audio.html
├── image.html
└── grammar.html
```
### Dynamic Module Data Flow
```
1. Orchestrator Initialization
└── Load shared services (LLM, Prerequisites, Context)
└── Analyze chapter content and prerequisites
└── Determine available exercise modules
2. Module Loading & Sequencing
└── orchestrator.getAvailableModules(prerequisites)
└── orchestrator.loadModule(selectedType)
└── module.canRun(prerequisites) → boolean
3. Exercise Execution
└── module.present(container, exerciseData)
└── user interaction & input capture
└── module.validate(userInput, context) → ValidationResult
4. Dynamic Adaptation
└── Update mastery tracking
└── prerequisiteEngine.reevaluate(newMastery)
└── orchestrator.selectNextModule(progress, variation)
└── module.cleanup() → orchestrator.unloadModule()
5. Module Lifecycle
canRun() → present() → validate() → cleanup() → unload/switch
```
### Integration Points
#### With Existing System
- Extends Module base class architecture
- Uses EventBus for communication
- Integrates with existing content loading system
- Compatible with current routing system
#### Content Requirements
- Chapter vocabulary lists with base forms
- Phrase/sentence collections with difficulty indicators
- Audio files with transcriptions
- Images with contextual information
- Text passages segmented by sentences
### Performance Considerations
- Lazy loading of exercises and content
- LLM request caching for repeated validations
- Efficient prerequisite checking algorithms
- Minimal DOM manipulation for smooth UX
## 🚀 Dynamic Implementation Strategy
### **MVP Phase 1: Orchestrator + Vocabulary Module (Week 1)**
```
1. SmartPreviewOrchestrator.js - Core controller
└── Module loading/unloading system
└── Basic exercise sequencing
└── Progress tracking foundation
2. VocabularyModule.js - First exercise module
└── Groups of 5 vocabulary system
└── Basic LLM mock validation
└── Standard interface implementation
3. Services Foundation
└── LLMValidator.js (mock responses initially)
└── PrerequisiteEngine.js (chapter vocab filtering)
└── ExerciseModuleInterface.js (standard contract)
```
### **Phase 2: Add Core Modules (Week 2)**
```
4. PhraseModule.js - Individual phrase comprehension
5. TextModule.js - Sentence-by-sentence processing
6. Basic ContextMemory.js - Progressive context building
7. Real LLM integration (replace mocks)
```
### **Phase 3: Complete Module Set (Week 3)**
```
8. AudioModule.js - Audio comprehension
9. ImageModule.js - Image description
10. GrammarModule.js - Grammar construction
11. Advanced prerequisite logic
12. Exercise variation patterns
```
### **Phase 4: Intelligence & Polish (Week 4)**
```
13. Adaptive difficulty system
14. Advanced context memory
15. Performance optimization
16. Error handling and fallbacks
17. UI/UX refinements
```
### **Development Approach**
- **Modular Testing**: Each module independently testable
- **Progressive Enhancement**: Add modules incrementally
- **Interface-Driven**: All modules follow standard contract
- **Service Injection**: Shared services injected into modules
- **Dynamic Loading**: Modules loaded only when needed
## 📊 Success Metrics
### Learning Effectiveness
- User completion rates per session
- Average time to vocabulary mastery
- Retention rates in follow-up sessions
- User satisfaction scores
### Technical Performance
- LLM response times (< 2 seconds)
- Exercise loading times (< 500ms)
- System uptime and error rates
- Content coverage completeness
### Adaptive Accuracy
- Prerequisite system accuracy (avoiding impossible exercises)
- LLM validation consistency with human evaluation
- Progression appropriateness for user level

View File

@ -1,201 +0,0 @@
# 🧪 DRS Integration Test Suite - Guide d'utilisation
## 📋 Vue d'ensemble
Le système de test d'intégration complet permet de vérifier le bon fonctionnement de tous les modules DRS et leur interaction.
## 🚀 Comment lancer les tests
### Option 1: Interface utilisateur (Recommandé)
1. Ouvrir l'application (http://localhost:3000)
2. Appuyer sur `F12` pour ouvrir le Debug Panel
3. Cliquer sur le bouton "🧪 Run Integration Tests"
4. Observer les résultats dans le panneau de test qui apparaît
### Option 2: Console
```javascript
// Tests complets d'intégration
runDRSTests() // Tests architecture et modules
runUIUXTests() // Tests interface utilisateur complets
runE2ETests() // Tests scénarios end-to-end
// Tests rapides spécifiques
test.testAll() // Tous les tests rapides
test.testFlashcardRetry() // Test du système de retry des flashcards
test.testVocabularyProgress() // Test du système de progrès vocabulary
test.testIntelligentSequencer() // Test du séquenceur intelligent
test.testAIIntegration() // Test de l'intégration IA
test.testDRSSystem() // Test du système DRS
test.testWordDiscovery() // Test du système de découverte de mots
// Statut des modules
test.showModuleStatus() // Affiche l'état de tous les modules
// Aide
test.help() // Affiche l'aide des commandes
```
## 📊 Types de tests
### 1. Tests du système de base
- ✅ Application chargée et fonctionnelle
- ✅ EventBus opérationnel
- ✅ ModuleLoader fonctionnel
- ✅ Router opérationnel
### 2. Tests de chargement de contenu
- ✅ ContentLoader accessible
- ✅ Chargement de contenu de chapitre
- ✅ Validation de la structure des données
### 3. Tests du système DRS
- ✅ UnifiedDRS disponible
- ✅ IAEngine opérationnel
- ✅ LLMValidator disponible
### 4. Tests des modules d'exercices
- ✅ TextModule
- ✅ AudioModule
- ✅ ImageModule
- ✅ GrammarModule
- ✅ TextAnalysisModule
- ✅ GrammarAnalysisModule
- ✅ TranslationModule
- ✅ OpenResponseModule
- ✅ WordDiscoveryModule
### 5. Tests d'intégration IA
- ✅ Configuration des fournisseurs IA
- ✅ Validation des réponses (structure)
- ✅ Gestion des erreurs
### 6. Tests du système de prérequis
- ✅ PrerequisiteEngine disponible
- ✅ Suivi des mots maîtrisés
- ✅ Système de découverte de mots
- ✅ Calcul des progrès
### 7. Tests IntelligentSequencer
- ✅ Chargement du séquenceur
- ✅ Création de sessions guidées
- ✅ Recommandations d'exercices
- ✅ Insights de performance
### 8. Tests d'intégration croisée
- ✅ Intégration modal de vocabulaire
- ✅ Intégration Flashcard-PrerequisiteEngine
- ✅ Intégration DRS-IA
- ✅ Communication EventBus
## 📈 Interprétation des résultats
### Taux de réussite
- **90%+** : 🎉 EXCELLENT - Système hautement fonctionnel
- **75-89%** : 👍 BON - Système majoritairement fonctionnel avec problèmes mineurs
- **50-74%** : ⚠️ MODÉRÉ - Système avec problèmes significatifs
- **<50%** : 🚨 CRITIQUE - Système avec problèmes majeurs
### Messages d'erreur communs
#### ❌ "Application not loaded"
- **Cause** : L'application ne s'est pas initialisée correctement
- **Solution** : Recharger la page et attendre l'initialisation complète
#### ❌ "Module not found"
- **Cause** : Un module n'est pas chargé ou accessible
- **Solution** : Vérifier les logs de console pour les erreurs de chargement
#### ❌ "API key" / "provider" errors
- **Cause** : Tests IA échouent sans clés API (normal)
- **Impact** : N'affecte pas les autres fonctionnalités
#### ❌ "Content not available"
- **Cause** : Fichiers de contenu manquants
- **Solution** : Vérifier que les fichiers JSON de contenu existent
## 🔧 Dépannage
### Tests échouent massivement
1. Vérifier que le serveur de développement fonctionne (port 3000)
2. Ouvrir les outils de développement (F12) et vérifier les erreurs
3. Recharger la page complètement
4. Vérifier que tous les fichiers JS sont accessibles
### Tests IA échouent
- **Normal** sans clés API configurées
- Pour tester réellement l'IA : configurer les clés dans `IAEngine`
### Tests de contenu échouent
- Vérifier que les fichiers `/content/chapters/*.json` existent
- Vérifier la structure des données JSON
### Tests de modules spécifiques échouent
- Vérifier que les fichiers de modules existent dans `/src/DRS/exercise-modules/`
- Contrôler les erreurs de syntaxe dans les modules
## 📝 Ajout de nouveaux tests
### Tests dans `test-integration.js`
```javascript
// Ajouter dans la classe DRSIntegrationTester
async testNewFeature() {
this.log('🧪 Testing new feature...');
this.test('Feature test name', () => {
// Test synchrone
return condition;
});
await this.asyncTest('Async feature test', async () => {
// Test asynchrone
const result = await someAsyncOperation();
return result.isValid;
});
}
```
### Tests console dans `test-console-commands.js`
```javascript
// Ajouter dans window.testCommands
async testNewFeature() {
console.log('🧪 Testing new feature...');
// Logique de test
return success;
}
```
## 🎯 Utilisation en développement
### Tests rapides pendant le développement
```javascript
// Test rapide d'un module spécifique
test.testFlashcardRetry()
// Vérifier l'état après modifications
test.showModuleStatus()
```
### Tests complets avant release
```javascript
// Tests d'intégration complets
runDRSTests()
```
### Tests de régression
Lancer les tests après chaque modification majeure pour s'assurer qu'aucune fonctionnalité existante n'est cassée.
## 📋 Checklist de validation
Avant de considérer le système comme stable :
- [ ] Tests de base > 95% de réussite
- [ ] Tests de contenu fonctionnels
- [ ] Tests DRS > 90% de réussite
- [ ] Tests de modules d'exercices > 85% de réussite
- [ ] Tests d'intégration croisée > 90% de réussite
- [ ] Aucune erreur critique dans la console
- [ ] Tous les modules accessibles via `test.showModuleStatus()`
---
**🎯 Objectif** : Maintenir un taux de réussite global > 90% pour assurer la stabilité du système.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

View File

@ -1 +0,0 @@
data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect width="32" height="32" fill="%23667eea"/><text x="16" y="22" text-anchor="middle" fill="white" font-family="Arial" font-size="18" font-weight="bold">C</text></svg>

View File

@ -214,33 +214,6 @@
"difficulty": "intermediate",
"vocabulary_count": 10,
"topics": ["adventure", "story", "reading", "sentence_chunking", "word_translation"]
},
"nce1-lesson63-64": {
"enabled": true,
"name": "NCE1-Lesson63-64",
"icon": "👨‍⚕️",
"description": "Medical dialogue and prohibition commands with modal verbs",
"difficulty": "intermediate",
"vocabulary_count": 120,
"topics": ["medical", "modal_verbs", "imperatives", "safety_rules"]
},
"nce2-lesson3": {
"enabled": true,
"name": "NCE2-Lesson3",
"icon": "✉️",
"description": "Please Send Me a Card - Past tense and travel vocabulary",
"difficulty": "intermediate",
"vocabulary_count": 150,
"topics": ["past_tense", "travel", "postcards", "holidays", "grammar"]
},
"nce2-lesson30": {
"enabled": true,
"name": "NCE2-Lesson30",
"icon": "⚽",
"description": "Football or Polo? - Articles and quantifiers",
"difficulty": "intermediate",
"vocabulary_count": 180,
"topics": ["articles", "quantifiers", "sports", "past_continuous", "grammar"]
}
},
"settings": {

View File

@ -1,51 +0,0 @@
{
"id": "sbs",
"name": "Side by Side",
"description": "Side by Side English Learning Series - Complete course for intermediate learners",
"difficulty": "intermediate",
"language": "en-US",
"metadata": {
"version": "1.0",
"created": "2025-09-23",
"updated": "2025-09-23",
"source": "Side by Side English Learning Series",
"target_level": "intermediate",
"total_estimated_hours": 100,
"prerequisites": ["basic-english"],
"learning_objectives": [
"Master intermediate vocabulary for daily situations",
"Understand grammar structures in context",
"Develop conversational skills",
"Practice reading and listening comprehension"
],
"content_tags": ["vocabulary", "grammar", "conversation", "practical-english"],
"total_chapters": 12,
"available_chapters": ["7-8"],
"completion_criteria": {
"overall_progress": 80,
"chapters_completed": 8,
"vocabulary_mastery": 85
}
},
"chapters": [
{
"id": "sbs-7-8",
"chapter_number": "7-8",
"name": "Daily Life & Vocabulary",
"description": "Master intermediate vocabulary for daily situations including clothing, body parts, emotions and technology",
"estimated_hours": 25,
"difficulty": "intermediate",
"prerequisites": ["sbs-5-6"],
"learning_objectives": [
"Master intermediate vocabulary for daily situations",
"Understand clothing and body parts terminology",
"Learn emotional expressions and feelings",
"Practice technology and social media vocabulary"
],
"vocabulary_count": 150,
"phrases_count": 45,
"dialogs_count": 8,
"exercises_count": 25
}
]
}

View File

@ -1,56 +0,0 @@
{
"id": "test-heavy",
"name": "Academic English Advanced",
"description": "Advanced academic English course designed for comprehensive system stress testing with extensive vocabulary and complex content structures",
"difficulty": "advanced",
"language": "en-US",
"metadata": {
"version": "1.0",
"created": "2025-09-30",
"updated": "2025-09-30",
"source": "DRS Stress Testing Suite",
"target_level": "advanced",
"total_estimated_hours": 50,
"prerequisites": ["intermediate-english", "academic-foundations"],
"learning_objectives": [
"Master advanced academic vocabulary for research contexts",
"Understand complex scientific and technical terminology",
"Develop skills in academic discourse and methodology",
"Practice advanced reading comprehension with dense texts",
"Learn to analyze and synthesize complex information"
],
"content_tags": ["academic-english", "research", "methodology", "advanced-vocabulary", "stress-test"],
"total_chapters": 1,
"available_chapters": ["test-heavy-stress"],
"completion_criteria": {
"overall_progress": 85,
"chapters_completed": 1,
"vocabulary_mastery": 90,
"comprehension_score": 80
}
},
"chapters": [
{
"id": "test-heavy-stress",
"chapter_number": "1",
"name": "Research Methodology & Academic Discourse",
"description": "Comprehensive stress test chapter featuring 150+ academic vocabulary terms, dense scientific content, and complex linguistic structures for system performance validation",
"estimated_hours": 50,
"difficulty": "advanced",
"prerequisites": ["academic-foundations"],
"learning_objectives": [
"Master 150+ advanced academic vocabulary terms",
"Understand research methodology terminology",
"Practice with high-density vocabulary content",
"Develop skills in academic text analysis",
"Test system performance with complex content structures"
],
"vocabulary_count": 156,
"phrases_count": 75,
"dialogs_count": 6,
"exercises_count": 40,
"sentences_count": 110,
"lessons_count": 3
}
]
}

View File

@ -1,169 +0,0 @@
{
"id": "sbs-7-8",
"book_id": "sbs",
"name": "Daily Life & Vocabulary",
"description": "Side by Side Level 7-8 vocabulary with language-agnostic format",
"difficulty": "intermediate",
"language": "en-US",
"chapter_number": "7-8",
"metadata": {
"version": "1.0",
"created": "2025-09-23",
"updated": "2025-09-23",
"source": "Side by Side English Learning Series",
"target_level": "intermediate",
"estimated_hours": 25,
"prerequisites": ["sbs-5-6"],
"learning_objectives": [
"Master intermediate vocabulary for daily situations",
"Understand clothing and body parts terminology",
"Learn emotional expressions and feelings",
"Practice technology and social media vocabulary"
],
"content_tags": ["vocabulary", "daily-life", "practical-english", "conversational"],
"completion_criteria": {
"vocabulary_mastery": 80,
"quiz_score": 75,
"games_completed": 5
}
},
"vocabulary": {
"central": { "user_language": "中心的;中央的", "type": "adjective", "pronunciation": "/ˈsentrəl/" },
"avenue": { "user_language": "大街;林荫道", "type": "noun", "pronunciation": "/ˈævənjuː/" },
"refrigerator": { "user_language": "冰箱", "type": "noun", "pronunciation": "/rɪˈfrɪdʒəreɪtər/" },
"closet": { "user_language": "衣柜;壁橱", "type": "noun", "pronunciation": "/ˈklɒzɪt/" },
"elevator": { "user_language": "电梯", "type": "noun", "pronunciation": "/ˈeləveɪtər/" },
"building": { "user_language": "建筑物;大楼", "type": "noun", "pronunciation": "/ˈbɪldɪŋ/" },
"air conditioner": { "user_language": "空调", "type": "noun", "pronunciation": "/ɛr kənˈdɪʃənər/" },
"superintendent": { "user_language": "主管;负责人", "type": "noun", "pronunciation": "/ˌsuːpərɪnˈtendənt/" },
"bus stop": { "user_language": "公交车站", "type": "noun", "pronunciation": "/bʌs stɒp/" },
"jacuzzi": { "user_language": "按摩浴缸", "type": "noun", "pronunciation": "/dʒəˈkuːzi/" },
"machine": { "user_language": "机器;设备", "type": "noun", "pronunciation": "/məˈʃiːn/" },
"two and a half": { "user_language": "两个半", "type": "number", "pronunciation": "/tuː ænd ə hæf/" },
"in the center of": { "user_language": "在……中心", "type": "preposition", "pronunciation": "/ɪn ðə ˈsentər ʌv/" },
"town": { "user_language": "城镇", "type": "noun", "pronunciation": "/taʊn/" },
"a lot of": { "user_language": "许多", "type": "determiner", "pronunciation": "/ə lɑt ʌv/" },
"noise": { "user_language": "噪音", "type": "noun", "pronunciation": "/nɔɪz/" },
"sidewalks": { "user_language": "人行道", "type": "noun", "pronunciation": "/ˈsaɪdwɔːks/" },
"all day and all night": { "user_language": "整日整夜", "type": "adverb", "pronunciation": "/ɔːl deɪ ænd ɔːl naɪt/" },
"convenient": { "user_language": "便利的", "type": "adjective", "pronunciation": "/kənˈviːniənt/" },
"shirt": { "user_language": "衬衫", "type": "noun", "pronunciation": "/ʃɜːrt/" },
"coat": { "user_language": "外套、大衣", "type": "noun", "pronunciation": "/koʊt/" },
"pants": { "user_language": "裤子", "type": "noun", "pronunciation": "/pænts/" },
"shoes": { "user_language": "鞋子", "type": "noun", "pronunciation": "/ʃuːz/" },
"hat": { "user_language": "帽子", "type": "noun", "pronunciation": "/hæt/" },
"dress": { "user_language": "连衣裙", "type": "noun", "pronunciation": "/drɛs/" },
"suit": { "user_language": "套装", "type": "noun", "pronunciation": "/suːt/" },
"tie": { "user_language": "领带", "type": "noun", "pronunciation": "/taɪ/" },
"socks": { "user_language": "袜子", "type": "noun", "pronunciation": "/sɑːks/" },
"blouse": { "user_language": "女式衬衫", "type": "noun", "pronunciation": "/blaʊs/" },
"skirt": { "user_language": "裙子", "type": "noun", "pronunciation": "/skɜːrt/" },
"sweater": { "user_language": "毛衣", "type": "noun", "pronunciation": "/ˈswɛtər/" },
"jacket": { "user_language": "夹克", "type": "noun", "pronunciation": "/ˈdʒækɪt/" },
"jeans": { "user_language": "牛仔裤", "type": "noun", "pronunciation": "/dʒiːnz/" },
"shorts": { "user_language": "短裤", "type": "noun", "pronunciation": "/ʃɔːrts/" },
"sneakers": { "user_language": "运动鞋", "type": "noun", "pronunciation": "/ˈsniːkərz/" },
"boots": { "user_language": "靴子", "type": "noun", "pronunciation": "/buːts/" },
"gloves": { "user_language": "手套", "type": "noun", "pronunciation": "/ɡlʌvz/" },
"scarf": { "user_language": "围巾", "type": "noun", "pronunciation": "/skɑːrf/" },
"belt": { "user_language": "腰带", "type": "noun", "pronunciation": "/bɛlt/" },
"head": { "user_language": "头", "type": "noun", "pronunciation": "/hɛd/" },
"hair": { "user_language": "头发", "type": "noun", "pronunciation": "/hɛr/" },
"eyes": { "user_language": "眼睛", "type": "noun", "pronunciation": "/aɪz/" },
"nose": { "user_language": "鼻子", "type": "noun", "pronunciation": "/noʊz/" },
"mouth": { "user_language": "嘴", "type": "noun", "pronunciation": "/maʊθ/" },
"ears": { "user_language": "耳朵", "type": "noun", "pronunciation": "/ɪrz/" },
"face": { "user_language": "脸", "type": "noun", "pronunciation": "/feɪs/" },
"neck": { "user_language": "脖子", "type": "noun", "pronunciation": "/nɛk/" },
"shoulders": { "user_language": "肩膀", "type": "noun", "pronunciation": "/ˈʃoʊldərz/" },
"arms": { "user_language": "胳膊", "type": "noun", "pronunciation": "/ɑːrmz/" },
"hands": { "user_language": "手", "type": "noun", "pronunciation": "/hændz/" },
"fingers": { "user_language": "手指", "type": "noun", "pronunciation": "/ˈfɪŋɡərz/" },
"chest": { "user_language": "胸部", "type": "noun", "pronunciation": "/tʃɛst/" },
"back": { "user_language": "背部", "type": "noun", "pronunciation": "/bæk/" },
"stomach": { "user_language": "腹部、肚子", "type": "noun", "pronunciation": "/ˈstʌmək/" },
"legs": { "user_language": "腿", "type": "noun", "pronunciation": "/lɛɡz/" },
"feet": { "user_language": "脚", "type": "noun", "pronunciation": "/fiːt/" },
"happy": { "user_language": "快乐的", "type": "adjective", "pronunciation": "/ˈhæpi/" },
"sad": { "user_language": "悲伤的", "type": "adjective", "pronunciation": "/sæd/" },
"angry": { "user_language": "生气的", "type": "adjective", "pronunciation": "/ˈæŋɡri/" },
"worried": { "user_language": "担心的", "type": "adjective", "pronunciation": "/ˈːrid/" },
"excited": { "user_language": "兴奋的", "type": "adjective", "pronunciation": "/ɪkˈsaɪtɪd/" },
"tired": { "user_language": "疲劳的", "type": "adjective", "pronunciation": "/ˈtaɪərd/" },
"hungry": { "user_language": "饥饿的", "type": "adjective", "pronunciation": "/ˈhʌŋɡri/" },
"thirsty": { "user_language": "口渴的", "type": "adjective", "pronunciation": "/ˈθɜːrsti/" },
"cold": { "user_language": "寒冷的", "type": "adjective", "pronunciation": "/koʊld/" },
"hot": { "user_language": "炎热的", "type": "adjective", "pronunciation": "/hɑːt/" },
"computer": { "user_language": "电脑", "type": "noun", "pronunciation": "/kəmˈpjuːtər/" },
"laptop": { "user_language": "笔记本电脑", "type": "noun", "pronunciation": "/ˈlæptɑːp/" },
"phone": { "user_language": "电话", "type": "noun", "pronunciation": "/foʊn/" },
"tablet": { "user_language": "平板电脑", "type": "noun", "pronunciation": "/ˈtæblət/" },
"internet": { "user_language": "互联网", "type": "noun", "pronunciation": "/ˈɪntərnet/" },
"email": { "user_language": "电子邮件", "type": "noun", "pronunciation": "/ˈiːmeɪl/" },
"website": { "user_language": "网站", "type": "noun", "pronunciation": "/ˈwɛbsaɪt/" },
"app": { "user_language": "应用程序", "type": "noun", "pronunciation": "/æp/" },
"social media": { "user_language": "社交媒体", "type": "noun", "pronunciation": "/ˈsoʊʃəl ˈmidiə/" },
"password": { "user_language": "密码", "type": "noun", "pronunciation": "/ˈpæswərd/" }
},
"phrases": {
"I live in a two-bedroom apartment": { "user_language": "我住在一间两居室的公寓", "context": "housing", "pronunciation": "/aɪ lɪv ɪn ə tuː ˈbɛdruːm əˈpɑːrtmənt/" },
"It's in the center of town": { "user_language": "它在城镇中心", "context": "location", "pronunciation": "/ɪts ɪn ðə ˈsentər ʌv taʊn/" },
"There's a lot of noise": { "user_language": "有很多噪音", "context": "complaint", "pronunciation": "/ðɛrz ə lɑt ʌv nɔɪz/" },
"It's very convenient": { "user_language": "这很便利", "context": "advantage", "pronunciation": "/ɪts ˈvɛri kənˈviniənt/" },
"What are you wearing?": { "user_language": "你穿的是什么?", "context": "clothing", "pronunciation": "/wʌt ɑr ju ˈwɛrɪŋ/" },
"I'm wearing a blue shirt": { "user_language": "我穿着一件蓝色的衬衫", "context": "clothing", "pronunciation": "/aɪm ˈwɛrɪŋ ə blu ʃɜrt/" },
"How do you feel?": { "user_language": "你感觉怎么样?", "context": "emotions", "pronunciation": "/haʊ du ju fil/" },
"I feel happy today": { "user_language": "我今天感觉很开心", "context": "emotions", "pronunciation": "/aɪ fil ˈhæpi təˈdeɪ/" },
"Do you have internet access?": { "user_language": "你有网络连接吗?", "context": "technology", "pronunciation": "/du ju hæv ˈɪntərnet ˈækses/" },
"I need to check my email": { "user_language": "我需要查看我的电子邮件", "context": "technology", "pronunciation": "/aɪ nid tu tʃɛk maɪ ˈimeɪl/" }
},
"dialogs": {
"apartment_search": {
"title": "Looking for an Apartment",
"participants": ["Alex", "Manager"],
"lines": [
{ "speaker": "Alex", "text": "I'm looking for a two-bedroom apartment.", "user_language": "我在找一间两居室的公寓。" },
{ "speaker": "Manager", "text": "We have one available on Central Avenue.", "user_language": "我们在中央大道有一间可用的。" },
{ "speaker": "Alex", "text": "Is it convenient for transportation?", "user_language": "交通方便吗?" },
{ "speaker": "Manager", "text": "Yes, there's a bus stop right outside.", "user_language": "是的,外面就有一个公交车站。" }
]
},
"clothing_shopping": {
"title": "Shopping for Clothes",
"participants": ["Customer", "Salesperson"],
"lines": [
{ "speaker": "Customer", "text": "I need a shirt for work.", "user_language": "我需要一件工作穿的衬衫。" },
{ "speaker": "Salesperson", "text": "What size do you wear?", "user_language": "你穿什么尺码?" },
{ "speaker": "Customer", "text": "Medium. Do you have it in blue?", "user_language": "中码。你们有蓝色的吗?" },
{ "speaker": "Salesperson", "text": "Yes, here's a nice blue shirt.", "user_language": "有,这里有一件漂亮的蓝色衬衫。" }
]
}
},
"exercises": {
"vocabulary_matching": {
"type": "matching",
"instructions": "Match the English words with their Chinese meanings",
"pairs": [
{ "english": "shirt", "chinese": "衬衫" },
{ "english": "happy", "chinese": "快乐的" },
{ "english": "computer", "chinese": "电脑" },
{ "english": "apartment", "chinese": "公寓" }
]
},
"fill_in_blanks": {
"type": "fill_blanks",
"instructions": "Fill in the blanks with the correct words",
"sentences": [
{ "text": "I live in a two-bedroom _______", "answer": "apartment", "user_language": "我住在一间两居室的_______" },
{ "text": "I'm wearing a blue _______", "answer": "shirt", "user_language": "我穿着一件蓝色的_______" }
]
}
},
"statistics": {
"vocabulary_count": 67,
"phrases_count": 10,
"dialogs_count": 2,
"exercises_count": 2,
"estimated_completion_time": 25
}
}

View File

@ -1,19 +0,0 @@
{
"name": "Side by Side English Course",
"description": "Complete English language learning course",
"version": "1.0",
"language": "en",
"targetLanguage": "fr",
"chapters": [
{
"id": "sbs-7-8",
"name": "Chapters 7-8: Past Tense & Irregular Verbs",
"description": "Learn past tense and irregular verbs with practical exercises"
}
],
"metadata": {
"created": "2025-09-26",
"level": "intermediate",
"estimatedHours": 20
}
}

View File

@ -1,960 +0,0 @@
{
"id": "test-heavy-stress",
"book_id": "test-heavy",
"name": "Research Methodology & Academic Discourse",
"description": "Comprehensive stress test chapter with 156 academic vocabulary terms, dense scientific content, and complex linguistic structures",
"difficulty": "advanced",
"language": "en-US",
"chapter_number": "1",
"metadata": {
"version": "1.0",
"created": "2025-09-30",
"updated": "2025-09-30",
"source": "DRS Stress Testing Suite",
"target_level": "advanced",
"estimated_hours": 50,
"prerequisites": ["academic-foundations"],
"learning_objectives": [
"Master 156 advanced academic vocabulary terms",
"Understand research methodology terminology",
"Practice with high-density vocabulary content",
"Develop skills in academic text analysis",
"Test system performance with complex content structures"
],
"content_tags": ["academic-english", "research", "methodology", "advanced-vocabulary", "stress-test"],
"completion_criteria": {
"vocabulary_mastery": 90,
"comprehension_score": 80,
"exercises_completed": 35
}
},
"vocabulary": {
"methodology": { "user_language": "méthodologie", "type": "noun", "pronunciation": "/ˌmeθəˈdɒlədʒi/" },
"hypothesis": { "user_language": "hypothèse", "type": "noun", "pronunciation": "/haɪˈpɒθəsɪs/" },
"analysis": { "user_language": "analyse", "type": "noun", "pronunciation": "/əˈnæləsɪs/" },
"synthesis": { "user_language": "synthèse", "type": "noun", "pronunciation": "/ˈsɪnθəsɪs/" },
"empirical": { "user_language": "empirique", "type": "adjective", "pronunciation": "/ɪmˈpɪrɪkəl/" },
"quantitative": { "user_language": "quantitatif", "type": "adjective", "pronunciation": "/ˈkwɒntɪtətɪv/" },
"qualitative": { "user_language": "qualitatif", "type": "adjective", "pronunciation": "/ˈkwɒlɪtətɪv/" },
"paradigm": { "user_language": "paradigme", "type": "noun", "pronunciation": "/ˈpærədaɪm/" },
"theoretical": { "user_language": "théorique", "type": "adjective", "pronunciation": "/ˌθiːəˈretɪkəl/" },
"framework": { "user_language": "cadre théorique", "type": "noun", "pronunciation": "/ˈfreɪmwɜːk/" },
"variable": { "user_language": "variable", "type": "noun", "pronunciation": "/ˈvɛəriəbəl/" },
"correlation": { "user_language": "corrélation", "type": "noun", "pronunciation": "/ˌkɒrəˈleɪʃən/" },
"causation": { "user_language": "causalité", "type": "noun", "pronunciation": "/kɔːˈzeɪʃən/" },
"statistical": { "user_language": "statistique", "type": "adjective", "pronunciation": "/stəˈtɪstɪkəl/" },
"significance": { "user_language": "signification", "type": "noun", "pronunciation": "/sɪɡˈnɪfɪkəns/" },
"phenomenon": { "user_language": "phénomène", "type": "noun", "pronunciation": "/fɪˈnɒmɪnən/" },
"phenomena": { "user_language": "phénomènes", "type": "noun", "pronunciation": "/fɪˈnɒmɪnə/" },
"criterion": { "user_language": "critère", "type": "noun", "pronunciation": "/kraɪˈtɪəriən/" },
"criteria": { "user_language": "critères", "type": "noun", "pronunciation": "/kraɪˈtɪəriə/" },
"assessment": { "user_language": "évaluation", "type": "noun", "pronunciation": "/əˈsesmənt/" },
"evaluation": { "user_language": "évaluation", "type": "noun", "pronunciation": "/ɪˌvæljuˈeɪʃən/" },
"comprehension": { "user_language": "compréhension", "type": "noun", "pronunciation": "/ˌkɒmprɪˈhenʃən/" },
"interpretation": { "user_language": "interprétation", "type": "noun", "pronunciation": "/ɪnˌtɜːprɪˈteɪʃən/" },
"conceptualization": { "user_language": "conceptualisation", "type": "noun", "pronunciation": "/kənˌseptʃuəlaɪˈzeɪʃən/" },
"operationalization": { "user_language": "opérationnalisation", "type": "noun", "pronunciation": "/ˌɒpəreɪʃənəlaɪˈzeɪʃən/" },
"methodology": { "user_language": "méthodologie", "type": "noun", "pronunciation": "/ˌmeθəˈdɒlədʒi/" },
"epistemology": { "user_language": "épistémologie", "type": "noun", "pronunciation": "/ɪˌpɪstɪˈmɒlədʒi/" },
"ontology": { "user_language": "ontologie", "type": "noun", "pronunciation": "/ɒnˈtɒlədʒi/" },
"phenomenology": { "user_language": "phénoménologie", "type": "noun", "pronunciation": "/fɪˌnɒmɪˈnɒlədʒi/" },
"hermeneutics": { "user_language": "herméneutique", "type": "noun", "pronunciation": "/ˌhɜːmɪˈnjuːtɪks/" },
"positivism": { "user_language": "positivisme", "type": "noun", "pronunciation": "/ˈpɒzɪtɪvɪzəm/" },
"constructivism": { "user_language": "constructivisme", "type": "noun", "pronunciation": "/kənˈstrʌktɪvɪzəm/" },
"realism": { "user_language": "réalisme", "type": "noun", "pronunciation": "/ˈriːəlɪzəm/" },
"pragmatism": { "user_language": "pragmatisme", "type": "noun", "pronunciation": "/ˈpræɡmətɪzəm/" },
"research": { "user_language": "recherche", "type": "noun", "pronunciation": "/rɪˈːtʃ/" },
"investigation": { "user_language": "investigation", "type": "noun", "pronunciation": "/ɪnˌvestɪˈɡeɪʃən/" },
"inquiry": { "user_language": "enquête", "type": "noun", "pronunciation": "/ɪnˈkwaɪəri/" },
"exploration": { "user_language": "exploration", "type": "noun", "pronunciation": "/ˌekspləˈreɪʃən/" },
"examination": { "user_language": "examen", "type": "noun", "pronunciation": "/ɪɡˌzæmɪˈneɪʃən/" },
"observation": { "user_language": "observation", "type": "noun", "pronunciation": "/ˌɒbzəˈveɪʃən/" },
"experiment": { "user_language": "expérience", "type": "noun", "pronunciation": "/ɪkˈsperɪmənt/" },
"survey": { "user_language": "enquête", "type": "noun", "pronunciation": "/ˈːveɪ/" },
"interview": { "user_language": "entretien", "type": "noun", "pronunciation": "/ˈɪntəvjuː/" },
"questionnaire": { "user_language": "questionnaire", "type": "noun", "pronunciation": "/ˌkwestʃəˈneə/" },
"data": { "user_language": "données", "type": "noun", "pronunciation": "/ˈdeɪtə/" },
"dataset": { "user_language": "jeu de données", "type": "noun", "pronunciation": "/ˈdeɪtəset/" },
"sample": { "user_language": "échantillon", "type": "noun", "pronunciation": "/ˈsɑːmpəl/" },
"population": { "user_language": "population", "type": "noun", "pronunciation": "/ˌpɒpjuˈleɪʃən/" },
"participant": { "user_language": "participant", "type": "noun", "pronunciation": "/pɑːˈtɪsɪpənt/" },
"respondent": { "user_language": "répondant", "type": "noun", "pronunciation": "/rɪˈspɒndənt/" },
"informant": { "user_language": "informateur", "type": "noun", "pronunciation": "/ɪnˈːmənt/" },
"subject": { "user_language": "sujet", "type": "noun", "pronunciation": "/ˈsʌbdʒɪkt/" },
"control": { "user_language": "contrôle", "type": "noun", "pronunciation": "/kənˈtrəʊl/" },
"treatment": { "user_language": "traitement", "type": "noun", "pronunciation": "/ˈtriːtmənt/" },
"intervention": { "user_language": "intervention", "type": "noun", "pronunciation": "/ˌɪntəˈvenʃən/" },
"manipulation": { "user_language": "manipulation", "type": "noun", "pronunciation": "/məˌnɪpjuˈleɪʃən/" },
"randomization": { "user_language": "randomisation", "type": "noun", "pronunciation": "/ˌrændəmaɪˈzeɪʃən/" },
"bias": { "user_language": "biais", "type": "noun", "pronunciation": "/ˈbaɪəs/" },
"validity": { "user_language": "validité", "type": "noun", "pronunciation": "/vəˈlɪdəti/" },
"reliability": { "user_language": "fiabilité", "type": "noun", "pronunciation": "/rɪˌlaɪəˈbɪləti/" },
"generalizability": { "user_language": "généralisabilité", "type": "noun", "pronunciation": "/ˌdʒenərəlaɪˈbɪləti/" },
"replicability": { "user_language": "reproductibilité", "type": "noun", "pronunciation": "/ˌreplɪˈbɪləti/" },
"reproducibility": { "user_language": "reproductibilité", "type": "noun", "pronunciation": "/ˌriːprəˌdjuːˈbɪləti/" },
"triangulation": { "user_language": "triangulation", "type": "noun", "pronunciation": "/traɪˌæŋɡjuˈleɪʃən/" },
"verification": { "user_language": "vérification", "type": "noun", "pronunciation": "/ˌverɪfɪˈkeɪʃən/" },
"validation": { "user_language": "validation", "type": "noun", "pronunciation": "/ˌvælɪˈdeɪʃən/" },
"calibration": { "user_language": "calibrage", "type": "noun", "pronunciation": "/ˌkælɪˈbreɪʃən/" },
"standardization": { "user_language": "standardisation", "type": "noun", "pronunciation": "/ˌstændədaɪˈzeɪʃən/" },
"normalization": { "user_language": "normalisation", "type": "noun", "pronunciation": "/ˌnɔːməlaɪˈzeɪʃən/" },
"coding": { "user_language": "codage", "type": "noun", "pronunciation": "/ˈkəʊdɪŋ/" },
"categorization": { "user_language": "catégorisation", "type": "noun", "pronunciation": "/ˌkætəɡəraɪˈzeɪʃən/" },
"classification": { "user_language": "classification", "type": "noun", "pronunciation": "/ˌklæsɪfɪˈkeɪʃən/" },
"taxonomy": { "user_language": "taxonomie", "type": "noun", "pronunciation": "/tækˈsɒnəmi/" },
"typology": { "user_language": "typologie", "type": "noun", "pronunciation": "/taɪˈpɒlədʒi/" },
"dimension": { "user_language": "dimension", "type": "noun", "pronunciation": "/daɪˈmenʃən/" },
"scale": { "user_language": "échelle", "type": "noun", "pronunciation": "/skeɪl/" },
"metric": { "user_language": "métrique", "type": "noun", "pronunciation": "/ˈmetrɪk/" },
"measurement": { "user_language": "mesure", "type": "noun", "pronunciation": "/ˈmeʒəmənt/" },
"indicator": { "user_language": "indicateur", "type": "noun", "pronunciation": "/ˈɪndɪkeɪtə/" },
"index": { "user_language": "indice", "type": "noun", "pronunciation": "/ˈɪndeks/" },
"coefficient": { "user_language": "coefficient", "type": "noun", "pronunciation": "/ˌkəʊɪˈfɪʃənt/" },
"regression": { "user_language": "régression", "type": "noun", "pronunciation": "/rɪˈɡreʃən/" },
"modeling": { "user_language": "modélisation", "type": "noun", "pronunciation": "/ˈmɒdəlɪŋ/" },
"simulation": { "user_language": "simulation", "type": "noun", "pronunciation": "/ˌsɪmjuˈleɪʃən/" },
"prediction": { "user_language": "prédiction", "type": "noun", "pronunciation": "/prɪˈdɪkʃən/" },
"projection": { "user_language": "projection", "type": "noun", "pronunciation": "/prəˈdʒekʃən/" },
"extrapolation": { "user_language": "extrapolation", "type": "noun", "pronunciation": "/ɪkˌstræpəˈleɪʃən/" },
"interpolation": { "user_language": "interpolation", "type": "noun", "pronunciation": "/ɪnˌtɜːˈleɪʃən/" },
"transformation": { "user_language": "transformation", "type": "noun", "pronunciation": "/ˌtrænsfəˈmeɪʃən/" },
"optimization": { "user_language": "optimisation", "type": "noun", "pronunciation": "/ˌɒptɪmaɪˈzeɪʃən/" },
"algorithm": { "user_language": "algorithme", "type": "noun", "pronunciation": "/ˈælɡərɪðəm/" },
"computation": { "user_language": "calcul", "type": "noun", "pronunciation": "/ˌkɒmpjuˈteɪʃən/" },
"processing": { "user_language": "traitement", "type": "noun", "pronunciation": "/ˈprəʊsesɪŋ/" },
"analysis": { "user_language": "analyse", "type": "noun", "pronunciation": "/əˈnæləsɪs/" },
"distribution": { "user_language": "distribution", "type": "noun", "pronunciation": "/ˌdɪstrɪˈbjuːʃən/" },
"frequency": { "user_language": "fréquence", "type": "noun", "pronunciation": "/ˈfriːkwənsi/" },
"probability": { "user_language": "probabilité", "type": "noun", "pronunciation": "/ˌprɒbəˈbɪləti/" },
"likelihood": { "user_language": "vraisemblance", "type": "noun", "pronunciation": "/ˈlaɪklihʊd/" },
"confidence": { "user_language": "confiance", "type": "noun", "pronunciation": "/ˈkɒnfɪdəns/" },
"interval": { "user_language": "intervalle", "type": "noun", "pronunciation": "/ˈɪntəvəl/" },
"deviation": { "user_language": "déviation", "type": "noun", "pronunciation": "/ˌdiːviˈeɪʃən/" },
"variance": { "user_language": "variance", "type": "noun", "pronunciation": "/ˈvɛəriəns/" },
"median": { "user_language": "médiane", "type": "noun", "pronunciation": "/ˈmiːdiən/" },
"mode": { "user_language": "mode", "type": "noun", "pronunciation": "/məʊd/" },
"mean": { "user_language": "moyenne", "type": "noun", "pronunciation": "/miːn/" },
"average": { "user_language": "moyenne", "type": "noun", "pronunciation": "/ˈævərɪdʒ/" },
"outlier": { "user_language": "valeur aberrante", "type": "noun", "pronunciation": "/ˈaʊtlaɪə/" },
"anomaly": { "user_language": "anomalie", "type": "noun", "pronunciation": "/əˈnɒməli/" },
"trend": { "user_language": "tendance", "type": "noun", "pronunciation": "/trend/" },
"pattern": { "user_language": "modèle", "type": "noun", "pronunciation": "/ˈpætən/" },
"cluster": { "user_language": "groupe", "type": "noun", "pronunciation": "/ˈklʌstə/" },
"segment": { "user_language": "segment", "type": "noun", "pronunciation": "/ˈseɡmənt/" },
"stratum": { "user_language": "strate", "type": "noun", "pronunciation": "/ˈstrɑːtəm/" },
"strata": { "user_language": "strates", "type": "noun", "pronunciation": "/ˈstrɑːtə/" },
"cohort": { "user_language": "cohorte", "type": "noun", "pronunciation": "/ˈkəʊhɔːt/" },
"longitudinal": { "user_language": "longitudinal", "type": "adjective", "pronunciation": "/ˌlɒŋɡɪˈtjuːdɪnəl/" },
"cross-sectional": { "user_language": "transversal", "type": "adjective", "pronunciation": "/krɒs ˈsekʃənəl/" },
"comparative": { "user_language": "comparatif", "type": "adjective", "pronunciation": "/kəmˈpærətɪv/" },
"descriptive": { "user_language": "descriptif", "type": "adjective", "pronunciation": "/dɪˈskrɪptɪv/" },
"exploratory": { "user_language": "exploratoire", "type": "adjective", "pronunciation": "/ɪkˈsplɒrətəri/" },
"explanatory": { "user_language": "explicatif", "type": "adjective", "pronunciation": "/ɪkˈsplænətəri/" },
"confirmatory": { "user_language": "confirmatoire", "type": "adjective", "pronunciation": "/kənˈːmətəri/" },
"experimental": { "user_language": "expérimental", "type": "adjective", "pronunciation": "/ɪkˌsperɪˈmentəl/" },
"quasi-experimental": { "user_language": "quasi-expérimental", "type": "adjective", "pronunciation": "/ˌkwaɪzaɪ ɪkˌsperɪˈmentəl/" },
"observational": { "user_language": "observationnel", "type": "adjective", "pronunciation": "/ˌɒbzəˈveɪʃənəl/" },
"ethnographic": { "user_language": "ethnographique", "type": "adjective", "pronunciation": "/ˌeθnəˈɡræfɪk/" },
"phenomenological": { "user_language": "phénoménologique", "type": "adjective", "pronunciation": "/fɪˌnɒmɪˈlɒdʒɪkəl/" },
"hermeneutical": { "user_language": "herméneutique", "type": "adjective", "pronunciation": "/ˌhɜːmɪˈnjuːtɪkəl/" },
"grounded": { "user_language": "ancré", "type": "adjective", "pronunciation": "/ˈɡraʊndɪd/" },
"narrative": { "user_language": "narratif", "type": "adjective", "pronunciation": "/ˈnærətɪv/" },
"discourse": { "user_language": "discours", "type": "noun", "pronunciation": "/ˈdɪskɔːs/" },
"rhetoric": { "user_language": "rhétorique", "type": "noun", "pronunciation": "/ˈretərɪk/" },
"argumentation": { "user_language": "argumentation", "type": "noun", "pronunciation": "/ˌɑːɡjuməntˈeɪʃən/" },
"proposition": { "user_language": "proposition", "type": "noun", "pronunciation": "/ˌprɒpəˈzɪʃən/" },
"premise": { "user_language": "prémisse", "type": "noun", "pronunciation": "/ˈpremɪs/" },
"conclusion": { "user_language": "conclusion", "type": "noun", "pronunciation": "/kənˈkluːʒən/" },
"inference": { "user_language": "inférence", "type": "noun", "pronunciation": "/ˈɪnfərəns/" },
"deduction": { "user_language": "déduction", "type": "noun", "pronunciation": "/dɪˈdʌkʃən/" },
"induction": { "user_language": "induction", "type": "noun", "pronunciation": "/ɪnˈdʌkʃən/" },
"abduction": { "user_language": "abduction", "type": "noun", "pronunciation": "/æbˈdʌkʃən/" },
"reasoning": { "user_language": "raisonnement", "type": "noun", "pronunciation": "/ˈriːzənɪŋ/" },
"logic": { "user_language": "logique", "type": "noun", "pronunciation": "/ˈlɒdʒɪk/" },
"rationale": { "user_language": "justification", "type": "noun", "pronunciation": "/ˌræʃəˈnɑːl/" },
"justification": { "user_language": "justification", "type": "noun", "pronunciation": "/ˌdʒʌstɪfɪˈkeɪʃən/" },
"substantiation": { "user_language": "justification", "type": "noun", "pronunciation": "/səbˌstænʃiˈeɪʃən/" },
"corroboration": { "user_language": "corroboration", "type": "noun", "pronunciation": "/kəˌrɒbəˈreɪʃən/" },
"refutation": { "user_language": "réfutation", "type": "noun", "pronunciation": "/ˌrefjuˈteɪʃən/" },
"contradiction": { "user_language": "contradiction", "type": "noun", "pronunciation": "/ˌkɒntrəˈdɪkʃən/" },
"paradox": { "user_language": "paradoxe", "type": "noun", "pronunciation": "/ˈpærədɒks/" },
"dilemma": { "user_language": "dilemme", "type": "noun", "pronunciation": "/dɪˈlemə/" },
"ambiguity": { "user_language": "ambiguïté", "type": "noun", "pronunciation": "/ˌæmbɪˈɡjuːəti/" },
"uncertainty": { "user_language": "incertitude", "type": "noun", "pronunciation": "/ʌnˈːtənti/" },
"complexity": { "user_language": "complexité", "type": "noun", "pronunciation": "/kəmˈpleksəti/" },
"sophisticated": { "user_language": "sophistiqué", "type": "adjective", "pronunciation": "/səˈfɪstɪkeɪtɪd/" },
"nuanced": { "user_language": "nuancé", "type": "adjective", "pronunciation": "/ˈnjuːɑːnst/" },
"multifaceted": { "user_language": "multiforme", "type": "adjective", "pronunciation": "/ˌmʌltɪˈfæsɪtɪd/" },
"comprehensive": { "user_language": "complet", "type": "adjective", "pronunciation": "/ˌkɒmprɪˈhensɪv/" },
"systematic": { "user_language": "systématique", "type": "adjective", "pronunciation": "/ˌsɪstəˈmætɪk/" },
"rigorous": { "user_language": "rigoureux", "type": "adjective", "pronunciation": "/ˈrɪɡərəs/" },
"meticulous": { "user_language": "méticuleux", "type": "adjective", "pronunciation": "/məˈtɪkjələs/" },
"precise": { "user_language": "précis", "type": "adjective", "pronunciation": "/prɪˈsaɪs/" },
"accurate": { "user_language": "précis", "type": "adjective", "pronunciation": "/ˈækjərət/" },
"robust": { "user_language": "robuste", "type": "adjective", "pronunciation": "/rəʊˈbʌst/" },
"substantial": { "user_language": "substantiel", "type": "adjective", "pronunciation": "/səbˈstænʃəl/" },
"significant": { "user_language": "significatif", "type": "adjective", "pronunciation": "/sɪɡˈnɪfɪkənt/" },
"substantial": { "user_language": "substantiel", "type": "adjective", "pronunciation": "/səbˈstænʃəl/" },
"considerable": { "user_language": "considérable", "type": "adjective", "pronunciation": "/kənˈsɪdərəbəl/" },
"extensive": { "user_language": "étendu", "type": "adjective", "pronunciation": "/ɪkˈstensɪv/" },
"intensive": { "user_language": "intensif", "type": "adjective", "pronunciation": "/ɪnˈtensɪv/" },
"exhaustive": { "user_language": "exhaustif", "type": "adjective", "pronunciation": "/ɪɡˈːstɪv/" },
"thorough": { "user_language": "approfondi", "type": "adjective", "pronunciation": "/ˈθʌrə/" },
"detailed": { "user_language": "détaillé", "type": "adjective", "pronunciation": "/ˈdiːteɪld/" },
"intricate": { "user_language": "complexe", "type": "adjective", "pronunciation": "/ˈɪntrɪkət/" },
"elaborate": { "user_language": "élaboré", "type": "adjective", "pronunciation": "/ɪˈlæbərət/" },
"sophisticated": { "user_language": "sophistiqué", "type": "adjective", "pronunciation": "/səˈfɪstɪkeɪtɪd/" }
},
"texts": [
{
"id": "text1",
"title": "Foundations of Research Methodology",
"content": "Research methodology constitutes the systematic theoretical analysis of the methods applied to a field of study. It encompasses the comprehensive framework of epistemological, ontological, and phenomenological considerations that guide rigorous academic inquiry. Contemporary researchers must navigate complex paradigmatic tensions between positivist empirical approaches and constructivist interpretive methodologies. The theoretical foundations of quantitative research emphasize statistical significance, experimental control, and generalizability of findings across diverse populations. Conversely, qualitative investigation prioritizes comprehensive understanding, contextual interpretation, and the nuanced exploration of subjective phenomena. Modern academic discourse increasingly advocates for sophisticated mixed-methods approaches that synthesize both quantitative measurement and qualitative analysis. Triangulation strategies enhance validity through multiple data sources, theoretical perspectives, and methodological approaches. Researchers must demonstrate meticulous attention to bias reduction, sample representativeness, and ethical considerations throughout the investigation process. The operationalization of abstract concepts requires precise definitional frameworks and robust measurement instruments. Contemporary methodology emphasizes reproducibility, transparency, and rigorous peer review processes. Advanced statistical techniques including regression modeling, multivariate analysis, and machine learning algorithms enable sophisticated pattern recognition and predictive modeling. Researchers increasingly utilize computational processing power for extensive dataset analysis and complex simulation procedures.",
"questions": [
{
"question": "What are the key differences between positivist and constructivist research paradigms?",
"options": [
"Positivist emphasizes statistical significance while constructivist prioritizes contextual interpretation",
"Positivist uses qualitative methods while constructivist uses quantitative methods",
"Positivist focuses on subjective phenomena while constructivist emphasizes empirical control",
"There are no significant differences between these paradigms"
],
"correct_answer": 0
},
{
"question": "According to the text, what is the primary purpose of triangulation in research methodology?",
"options": [
"To reduce research costs",
"To enhance validity through multiple data sources, theoretical perspectives, and methodological approaches",
"To simplify data collection procedures",
"To eliminate the need for peer review"
],
"correct_answer": 1
},
{
"question": "What does 'operationalization' refer to in research methodology?",
"options": [
"The process of conducting surgical research",
"The management of research laboratories",
"Defining abstract concepts with precise frameworks and robust measurement instruments",
"The automation of data collection procedures"
],
"correct_answer": 2
}
]
},
{
"id": "text2",
"title": "Advanced Statistical Analysis and Data Interpretation",
"content": "Statistical analysis represents the cornerstone of empirical research methodology, encompassing sophisticated techniques for data exploration, hypothesis testing, and inferential reasoning. Contemporary researchers utilize advanced algorithms for pattern recognition, anomaly detection, and predictive modeling across extensive datasets. The distribution characteristics of variables determine appropriate statistical procedures, including parametric and non-parametric approaches for hypothesis evaluation. Correlation analysis reveals relationships between variables, while causation requires controlled experimental manipulation and rigorous confounding variable elimination. Regression modeling enables prediction and explanation of dependent variable variance through multiple independent predictors. Cluster analysis identifies natural groupings within populations, facilitating targeted intervention strategies and segmentation approaches. Longitudinal studies track cohorts across time, revealing developmental patterns and causal sequences. Cross-sectional comparative designs enable efficient population sampling at specific temporal points. Measurement validity encompasses content, construct, and criterion-related approaches for instrument development. Reliability coefficients assess consistency across time, interrater agreement, and internal consistency measures. Confidence intervals provide probability-based ranges for population parameter estimation. Effect sizes quantify practical significance beyond statistical significance testing. Meta-analysis synthesizes findings across multiple independent studies, enhancing generalizability and statistical power. Advanced modeling techniques including structural equation modeling, hierarchical linear modeling, and machine learning algorithms enable sophisticated hypothesis testing with complex nested data structures.",
"questions": [
{
"question": "What is the fundamental difference between correlation and causation according to the text?",
"options": [
"Correlation and causation are the same thing",
"Correlation reveals relationships while causation requires controlled experimental manipulation and confounding variable elimination",
"Causation is easier to establish than correlation",
"Correlation requires more data than causation"
],
"correct_answer": 1
},
{
"question": "What advantage do longitudinal studies have over cross-sectional designs?",
"options": [
"They are cheaper to conduct",
"They require fewer participants",
"They track cohorts across time, revealing developmental patterns and causal sequences",
"They are simpler to analyze statistically"
],
"correct_answer": 2
},
{
"question": "What does meta-analysis accomplish in research?",
"options": [
"It replaces the need for new research",
"It synthesizes findings across multiple independent studies, enhancing generalizability and statistical power",
"It reduces the cost of individual studies",
"It eliminates the need for statistical significance testing"
],
"correct_answer": 1
}
]
},
{
"id": "text3",
"title": "Qualitative Research and Interpretive Methodologies",
"content": "Qualitative research methodology embraces interpretive paradigms that prioritize comprehensive understanding of complex social phenomena through detailed narrative analysis and contextual interpretation. Ethnographic approaches involve extensive participant observation, immersive fieldwork, and cultural interpretation through prolonged engagement with research participants. Phenomenological investigations explore lived experiences, consciousness structures, and subjective meaning-making processes through in-depth interview techniques. Grounded theory methodology emphasizes systematic data collection, theoretical sampling, and iterative analysis for theory development from empirical observations. Hermeneutical approaches focus on textual interpretation, discourse analysis, and the historical contextualization of cultural artifacts. Narrative research examines personal stories, biographical accounts, and identity construction through temporal sequence analysis. Case study methodology provides comprehensive investigation of specific instances, organizations, or phenomena within natural settings. Action research integrates investigation with practical intervention, emphasizing collaborative participation and social change objectives. Data collection techniques include semi-structured interviews, focus group discussions, participant observation, and document analysis procedures. Coding procedures involve open, axial, and selective approaches for pattern identification and categorical development. Triangulation strategies enhance credibility through multiple data sources, investigator perspectives, and theoretical frameworks. Member checking validates interpretations through participant feedback and collaborative meaning verification. Transferability replaces generalizability through detailed contextual description and theoretical conceptualization. Confirmability ensures objectivity through reflexive journaling, audit trails, and peer debriefing processes. Advanced qualitative analysis software facilitates systematic coding, pattern recognition, and theoretical model development.",
"questions": [
{
"question": "What is the primary focus of phenomenological investigations?",
"options": [
"Statistical analysis of large datasets",
"Exploring lived experiences, consciousness structures, and subjective meaning-making processes",
"Experimental manipulation of variables",
"Cost-benefit analysis of interventions"
],
"correct_answer": 1
},
{
"question": "How does 'transferability' differ from 'generalizability' in qualitative research?",
"options": [
"They are identical concepts",
"Transferability is less rigorous than generalizability",
"Transferability replaces generalizability through detailed contextual description and theoretical conceptualization",
"Generalizability is only used in qualitative research"
],
"correct_answer": 2
},
{
"question": "What is the purpose of 'member checking' in qualitative research?",
"options": [
"To reduce research costs",
"To validate interpretations through participant feedback and collaborative meaning verification",
"To recruit new participants",
"To analyze statistical data"
],
"correct_answer": 1
}
]
}
],
"dialogs": [
{
"id": "conference_presentation",
"title": "Academic Conference Presentation",
"description": "Researchers discussing methodology at an international conference",
"speakers": ["Dr. Rodriguez", "Prof. Chen", "Dr. Williams"],
"content": [
"Dr. Rodriguez: Good morning colleagues. Today's presentation examines our comprehensive methodology for analyzing complex educational phenomena through mixed-methods triangulation.",
"Prof. Chen: Your quantitative framework demonstrates sophisticated statistical modeling. However, the qualitative interpretation seems to lack phenomenological depth and hermeneutical rigor.",
"Dr. Williams: I appreciate the systematic approach, but the operationalization of theoretical constructs requires more precise measurement criteria and validation procedures.",
"Dr. Rodriguez: Excellent observations. Our empirical analysis utilized multivariate regression with longitudinal cohort tracking across diverse demographic populations and geographical distributions.",
"Prof. Chen: The epistemological foundations appear rooted in positivist paradigms. Have you considered constructivist alternatives for interpretive analysis and contextual understanding?",
"Dr. Williams: The sample stratification methodology ensures representativeness, but potential bias in participant selection warrants additional randomization procedures and control mechanisms.",
"Dr. Rodriguez: Triangulation strategies incorporated multiple data sources including surveys, interviews, observations, and archival documentation for comprehensive corroboration and verification.",
"Prof. Chen: Discourse analysis reveals nuanced patterns in participant narratives that quantitative metrics cannot capture through statistical frequency distributions and correlation coefficients.",
"Dr. Williams: The reproducibility criteria meet contemporary standards, but replication across different cultural contexts requires extensive methodological adaptation and theoretical reconsideration.",
"Dr. Rodriguez: Future investigations will incorporate advanced computational algorithms for pattern recognition, anomaly detection, and predictive modeling with machine learning optimization.",
"Prof. Chen: Ethical considerations demand rigorous informed consent procedures, confidentiality protection, and participant welfare throughout the entire investigation process.",
"Dr. Williams: Publication standards require transparent reporting of limitations, potential confounding variables, and alternative theoretical interpretations for comprehensive academic discourse.",
"Dr. Rodriguez: The implications extend beyond immediate findings to broader theoretical frameworks and practical applications in educational policy development and implementation.",
"Prof. Chen: Longitudinal tracking enables causal inference through temporal sequence analysis, but correlation does not necessarily establish definitive causation relationships.",
"Dr. Williams: Meta-analysis synthesis across multiple studies enhances generalizability while acknowledging contextual variations and methodological differences between investigations."
],
"questions": [
{
"question": "According to the discussion, what research approach did Dr. Rodriguez's team use?",
"options": [
"Only quantitative methods",
"Only qualitative methods",
"Mixed-methods triangulation",
"Pure theoretical analysis"
],
"correct_answer": 2
},
{
"question": "What concern did Prof. Chen raise about the quantitative approach?",
"options": [
"It was too expensive",
"The qualitative interpretation lacked phenomenological depth and hermeneutical rigor",
"It used too many participants",
"It was too time-consuming"
],
"correct_answer": 1
},
{
"question": "What is the relationship between correlation and causation according to the dialog?",
"options": [
"They are the same thing",
"Correlation proves causation",
"Correlation does not necessarily establish definitive causation relationships",
"Causation is easier to prove than correlation"
],
"correct_answer": 2
}
]
},
{
"id": "laboratory_discussion",
"title": "Research Laboratory Team Meeting",
"description": "Graduate students and faculty discussing experimental procedures",
"speakers": ["Dr. Johnson", "Sarah", "Michael", "Dr. Kim"],
"content": [
"Dr. Johnson: Team, our experimental design requires meticulous calibration of measurement instruments and standardization of procedural protocols for optimal data quality.",
"Sarah: The randomization procedure successfully eliminated selection bias, but we need additional control variables for comprehensive confounding factor elimination.",
"Michael: Statistical power analysis indicates our sample size provides adequate detection capability for medium effect sizes with acceptable Type II error probability.",
"Dr. Kim: The manipulation check confirms successful experimental intervention implementation, though participant reactivity might compromise external validity and ecological authenticity.",
"Dr. Johnson: Data preprocessing includes outlier detection, normality assessment, and transformation procedures to meet parametric statistical assumption requirements.",
"Sarah: Interrater reliability coefficients exceed acceptable thresholds, demonstrating consistent coding procedures and measurement accuracy across multiple evaluators.",
"Michael: The theoretical framework integrates cognitive processing models with behavioral observation data for comprehensive phenomenon explanation and prediction.",
"Dr. Kim: Longitudinal tracking reveals developmental patterns that cross-sectional designs cannot detect through single-timepoint assessment and comparative analysis.",
"Dr. Johnson: Advanced statistical modeling incorporates hierarchical structures, repeated measures, and missing data imputation for robust analytical conclusions.",
"Sarah: Qualitative interviews provide contextual depth that complements quantitative findings through narrative interpretation and subjective experience exploration.",
"Michael: The replication study confirms original findings while extending theoretical understanding through expanded sample demographics and methodological refinements.",
"Dr. Kim: Ethical review board approval ensures participant protection, voluntary participation, and appropriate risk-benefit ratios throughout the investigation process.",
"Dr. Johnson: Publication preparation requires comprehensive literature review, theoretical positioning, and methodological transparency for peer review evaluation.",
"Sarah: Future research directions include technological integration, cross-cultural validation, and longitudinal extension for enhanced theoretical development and practical application."
]
},
{
"id": "thesis_defense",
"title": "Doctoral Thesis Defense",
"description": "PhD candidate defending dissertation research",
"speakers": ["Candidate", "Committee Chair", "External Examiner", "Internal Examiner"],
"content": [
"Committee Chair: Please present your comprehensive research methodology and theoretical framework for this investigation.",
"Candidate: My dissertation employs sophisticated mixed-methods triangulation combining quantitative experimentation with qualitative phenomenological analysis for comprehensive understanding.",
"External Examiner: The statistical procedures demonstrate rigor, but the philosophical foundations require clearer epistemological positioning and ontological assumptions.",
"Internal Examiner: Your sample stratification ensures demographic representativeness, though generalizability limitations warrant additional discussion and theoretical consideration.",
"Candidate: The theoretical synthesis integrates multiple paradigmatic perspectives while maintaining methodological coherence and analytical consistency throughout the investigation.",
"Committee Chair: Explain how your operationalization addresses measurement validity and reliability concerns in this complex research domain.",
"Candidate: Construct validation involved extensive pilot testing, factor analysis, and convergent-discriminant validity assessment through multiple measurement approaches.",
"External Examiner: The qualitative analysis demonstrates sophisticated coding procedures, but member checking and participant validation could strengthen interpretive credibility.",
"Internal Examiner: Your findings contribute significantly to theoretical understanding, though practical implications require more explicit connection to policy and intervention applications.",
"Candidate: The longitudinal design enables causal inference through temporal sequencing, while controlling for confounding variables and alternative explanations.",
"Committee Chair: How do your results address existing theoretical contradictions and empirical inconsistencies in the literature?",
"Candidate: My synthesis resolves previous paradoxes through comprehensive analysis that reveals contextual moderators and mediating mechanisms previously unrecognized.",
"External Examiner: The methodology section provides excellent transparency and replicability information for future investigations and comparative studies.",
"Internal Examiner: Your contributions advance both theoretical knowledge and methodological innovation in this rapidly evolving research domain.",
"Committee Chair: Congratulations on a rigorous investigation that meets the highest standards of academic excellence and scholarly contribution."
]
},
{
"id": "research_collaboration",
"title": "International Research Collaboration",
"description": "Researchers from different institutions planning joint study",
"speakers": ["Prof. Anderson", "Dr. Patel", "Dr. Müller"],
"content": [
"Prof. Anderson: Our collaborative investigation requires sophisticated coordination across multiple sites for comprehensive cross-cultural validation and comparison.",
"Dr. Patel: The standardization procedures must accommodate cultural variations while maintaining methodological consistency and measurement equivalence across diverse populations.",
"Dr. Müller: Statistical harmonization involves careful consideration of demographic differences, language variations, and contextual factors that influence data interpretation.",
"Prof. Anderson: Ethical approval coordination across institutions demands rigorous attention to varying regulatory requirements and cultural sensitivity considerations.",
"Dr. Patel: Data sharing protocols require secure transmission, confidentiality protection, and standardized formatting for integrated analysis procedures.",
"Dr. Müller: The theoretical framework must incorporate cross-cultural perspectives while maintaining analytical coherence and interpretive validity across diverse contexts.",
"Prof. Anderson: Quality control mechanisms include inter-site reliability assessment, procedural monitoring, and continuous calibration for methodological consistency.",
"Dr. Patel: Language translation procedures involve back-translation, cultural adaptation, and semantic equivalence validation for instrument reliability.",
"Dr. Müller: Statistical analysis will incorporate multilevel modeling to account for nested data structures and site-specific variations in findings.",
"Prof. Anderson: Publication planning includes authorship agreements, intellectual property considerations, and collaborative writing procedures for equitable contribution recognition.",
"Dr. Patel: The comprehensive dataset enables sophisticated comparative analysis across cultures, providing unprecedented insight into universal versus culturally-specific phenomena.",
"Dr. Müller: Long-term sustainability requires ongoing funding coordination, institutional support, and continued collaboration for longitudinal tracking and follow-up investigations."
]
},
{
"id": "methodology_seminar",
"title": "Advanced Methodology Seminar",
"description": "Graduate seminar on cutting-edge research methods",
"speakers": ["Professor", "Student A", "Student B", "Student C"],
"content": [
"Professor: Today we examine sophisticated computational approaches for complex data analysis including machine learning algorithms and artificial intelligence applications.",
"Student A: The integration of qualitative and quantitative methodologies through computational text analysis provides unprecedented analytical capabilities for discourse examination.",
"Student B: Big data approaches require careful consideration of sampling bias, algorithmic transparency, and interpretive validity in automated pattern recognition procedures.",
"Student C: Experimental design optimization through simulation modeling enables researchers to test methodological assumptions before implementing costly data collection procedures.",
"Professor: Contemporary research increasingly utilizes mixed-reality environments for controlled experimentation with enhanced ecological validity and participant engagement.",
"Student A: Longitudinal tracking through mobile technology and wearable devices provides continuous measurement capabilities previously impossible with traditional methodological approaches.",
"Student B: Ethical considerations in digital research include privacy protection, informed consent, and algorithmic fairness in automated decision-making processes.",
"Student C: Reproducibility crisis solutions involve transparent reporting standards, open data sharing, and preregistered hypotheses for enhanced scientific credibility.",
"Professor: Advanced statistical techniques including Bayesian modeling, machine learning, and network analysis revolutionize hypothesis testing and theoretical development.",
"Student A: Interdisciplinary collaboration requires methodological flexibility while maintaining rigorous standards and theoretical coherence across diverse academic domains.",
"Student B: Innovation in measurement approaches includes real-time physiological monitoring, behavioral tracking, and environmental sensing for comprehensive data collection.",
"Student C: Future methodology development will integrate virtual reality, artificial intelligence, and biotechnology for unprecedented research capabilities and theoretical advancement."
]
},
{
"id": "grant_review_panel",
"title": "Research Grant Review Panel",
"description": "Experts evaluating research proposal funding applications",
"speakers": ["Panel Chair", "Reviewer 1", "Reviewer 2", "Reviewer 3"],
"content": [
"Panel Chair: This proposal demonstrates sophisticated methodology with comprehensive theoretical grounding and rigorous experimental design for significant scientific advancement.",
"Reviewer 1: The statistical power analysis confirms adequate sample size for detecting meaningful effects, though recruitment strategies require additional specification and feasibility assessment.",
"Reviewer 2: Innovative measurement approaches integrate multiple modalities for triangulated validation, enhancing construct validity and providing robust empirical evidence.",
"Reviewer 3: The longitudinal design enables causal inference through temporal sequencing while controlling confounding variables and alternative explanations effectively.",
"Panel Chair: Ethical considerations demonstrate thorough attention to participant welfare, confidentiality protection, and institutional review board compliance throughout the investigation.",
"Reviewer 1: The theoretical framework synthesizes contemporary literature comprehensively while identifying significant gaps and proposing novel theoretical contributions.",
"Reviewer 2: Methodological rigor includes extensive pilot testing, instrument validation, and quality control procedures for optimal data integrity and analytical accuracy.",
"Reviewer 3: Expected outcomes include both theoretical advancement and practical applications with clear pathways for knowledge translation and policy implementation.",
"Panel Chair: Budget justification demonstrates efficient resource allocation with appropriate consideration of personnel costs, equipment needs, and indirect expenses.",
"Reviewer 1: The research team possesses exceptional expertise and complementary skills necessary for successful project completion and high-quality scientific output.",
"Reviewer 2: Dissemination plans include peer-reviewed publications, conference presentations, and community engagement for maximum impact and knowledge transfer.",
"Reviewer 3: This investigation addresses critical scientific questions with potential for transformative discoveries and significant contributions to the research field."
]
}
],
"phrases": {
"The methodology demonstrates rigorous scientific standards": {
"user_language": "La méthodologie démontre des normes scientifiques rigoureuses",
"context": "methodology",
"pronunciation": "/ðə ˌmeθəˈdɒlədʒi ˈdemənstreɪts ˈrɪɡərəs ˌsaɪənˈtɪfɪk ˈstændərdz/"
},
"Researchers utilize sophisticated statistical techniques": {
"user_language": "Les chercheurs utilisent des techniques statistiques sophistiquées",
"context": "research-methods",
"pronunciation": "/rɪˈːtʃərz ˈjuːtəlaɪz səˈfɪstɪkeɪtɪd stəˈtɪstɪkəl tekˈniːks/"
},
"Quantitative data provides empirical evidence": {
"user_language": "Les données quantitatives fournissent des preuves empiriques",
"context": "data-analysis",
"pronunciation": "/ˈkwɒntɪtətɪv ˈdeɪtə prəˈvaɪdz ɪmˈpɪrɪkəl ˈevɪdəns/"
},
"Qualitative interpretation reveals nuanced patterns": {
"user_language": "L'interprétation qualitative révèle des modèles nuancés",
"context": "qualitative-research",
"pronunciation": "/ˈkwɒlɪtətɪv ɪnˌtɜːprɪˈteɪʃən rɪˈviːlz ˈnjuːɑːnst ˈpætənz/"
},
"The experimental design controls confounding variables": {
"user_language": "Le design expérimental contrôle les variables confondantes",
"context": "experimental-design",
"pronunciation": "/ði ɪkˌsperɪˈmentəl dɪˈzaɪn kənˈtrəʊlz kənˈfaʊndɪŋ ˈvɛəriəbəlz/"
},
"Statistical significance indicates meaningful relationships": {
"user_language": "La signification statistique indique des relations significatives",
"context": "statistics",
"pronunciation": "/stəˈtɪstɪkəl sɪɡˈnɪfɪkəns ˈɪndɪkeɪts ˈmiːnɪŋfəl rɪˈleɪʃənʃɪps/"
},
"Longitudinal studies track developmental changes": {
"user_language": "Les études longitudinales suivent les changements développementaux",
"context": "research-design",
"pronunciation": "/ˌlɒŋɡɪˈtjuːdɪnəl ˈstʌdiz træk dɪˌveləpˈmentəl ˈtʃeɪndʒɪz/"
},
"Cross-sectional comparisons examine demographic differences": {
"user_language": "Les comparaisons transversales examinent les différences démographiques",
"context": "comparative-research",
"pronunciation": "/krɒs ˈsekʃənəl kəmˈpærɪsənz ɪɡˈzæmɪn ˌdeməˈɡræfɪk ˈdɪfərənsɪz/"
},
"Triangulation strategies enhance validity": {
"user_language": "Les stratégies de triangulation améliorent la validité",
"context": "validation",
"pronunciation": "/traɪˌæŋɡjuˈleɪʃən ˈstrætɪdʒiz ɪnˈhɑːns vəˈlɪdəti/"
},
"Systematic literature review synthesizes existing knowledge": {
"user_language": "La revue systématique de la littérature synthétise les connaissances existantes",
"context": "literature-review",
"pronunciation": "/ˌsɪstəˈmætɪk ˈlɪtərətʃər rɪˈvjuː ˈsɪnθəsaɪzɪz ɪɡˈzɪstɪŋ ˈnɒlɪdʒ/"
},
"Operational definitions specify measurement procedures": {
"user_language": "Les définitions opérationnelles spécifient les procédures de mesure",
"context": "measurement",
"pronunciation": "/ˌɒpəˈreɪʃənəl ˌdefɪˈnɪʃənz ˈspesɪfaɪ ˈmeʒərmənt prəˈsiːdʒərz/"
},
"Random sampling ensures population representativeness": {
"user_language": "L'échantillonnage aléatoire garantit la représentativité de la population",
"context": "sampling",
"pronunciation": "/ˈrændəm ˈsɑːmplɪŋ ɪnˈʃʊərz ˌpɒpjuˈleɪʃən ˌreprɪzenˈteɪtɪvnəs/"
},
"Ethical considerations protect participant welfare": {
"user_language": "Les considérations éthiques protègent le bien-être des participants",
"context": "ethics",
"pronunciation": "/ˈɪkəl kənˌsɪˈreɪʃənz prəˈtekt pɑːˈtɪsɪpənt ˈwelfeər/"
},
"Correlation analysis reveals associations without causation": {
"user_language": "L'analyse de corrélation révèle des associations sans causalité",
"context": "correlation",
"pronunciation": "/ˌkɒrəˈleɪʃən əˈnæləsɪs rɪˈviːlz əˌsəʊsiˈeɪʃənz wɪˈðaʊt kɔːˈzeɪʃən/"
},
"Regression modeling predicts outcomes through predictors": {
"user_language": "La modélisation par régression prédit les résultats par des prédicteurs",
"context": "predictive-modeling",
"pronunciation": "/rɪˈɡreʃən ˈmɒdəlɪŋ prɪˈdɪkts ˈaʊtkʌmz θruː prɪˈdɪktərz/"
},
"The paradigm influences methodological choices": {
"user_language": "Le paradigme influence les choix méthodologiques",
"context": "theoretical-framework",
"pronunciation": "/ðə ˈpærədaɪm ˈɪnfluənsɪz ˌmeθədəˈlɒdʒɪkəl ˈtʃɔɪsɪz/"
},
"Empirical observations support theoretical frameworks": {
"user_language": "Les observations empiriques soutiennent les cadres théoriques",
"context": "theory-testing",
"pronunciation": "/ɪmˈpɪrɪkəl ˌɒbzəˈveɪʃənz səˈːt ˌθiːəˈretɪkəl ˈfreɪmwɜːks/"
},
"Phenomenological analysis explores subjective experiences": {
"user_language": "L'analyse phénoménologique explore les expériences subjectives",
"context": "phenomenology",
"pronunciation": "/fɪˌnɒmɪˈlɒdʒɪkəl əˈnæləsɪs ɪkˈsplɔːrz səbˈdʒektɪv ɪkˈspɪəriənsɪz/"
},
"Grounded theory develops from systematic data collection": {
"user_language": "La théorie ancrée se développe à partir de la collecte systématique de données",
"context": "grounded-theory",
"pronunciation": "/ˈɡraʊndɪd ˈθiːəri dɪˈveləps frɒm ˌsɪstəˈmætɪk ˈdeɪtə kəˈlekʃən/"
},
"Hermeneutical interpretation examines cultural texts": {
"user_language": "L'interprétation herméneutique examine les textes culturels",
"context": "hermeneutics",
"pronunciation": "/ˌhɜːmɪˈnjuːtɪkəl ɪnˌtɜːprɪˈteɪʃən ɪɡˈzæmɪnz ˈkʌltʃərəl teksts/"
},
"The algorithm processes large datasets for pattern recognition": {
"user_language": "L'algorithme traite de grands ensembles de données pour la reconnaissance de motifs",
"context": "computational-analysis",
"pronunciation": "/ði ˈælɡərɪðəm ˈprəʊsesɪz lɑːˈdeɪtəsets fɔː ˈpætən ˌrekəɡˈnɪʃən/"
},
"Simulation modeling tests theoretical assumptions": {
"user_language": "La modélisation par simulation teste les hypothèses théoriques",
"context": "simulation",
"pronunciation": "/ˌsɪmjuˈleɪʃən ˈmɒdəlɪŋ tests ˌθiːəˈretɪkəl əˈsʌmpʃənz/"
},
"Validation procedures confirm measurement accuracy": {
"user_language": "Les procédures de validation confirment la précision des mesures",
"context": "validation",
"pronunciation": "/ˌvælɪˈdeɪʃən prəˈsiːdʒərz kənˈːm ˈmeʒərmənt ˈækjərəsi/"
},
"Replication studies verify original findings": {
"user_language": "Les études de réplication vérifient les résultats originaux",
"context": "replication",
"pronunciation": "/ˌreplɪˈkeɪʃən ˈstʌdiz ˈverɪfaɪ əˈrɪɪnəl ˈfaɪndɪŋz/"
},
"Meta-analysis combines results from multiple studies": {
"user_language": "La méta-analyse combine les résultats de plusieurs études",
"context": "meta-analysis",
"pronunciation": "/ˌmetəəˈnæləsɪs kəmˈbaɪnz rɪˈzʌlts frɒm ˈmʌltɪpəl ˈstʌdiz/"
},
"Effect sizes quantify practical significance": {
"user_language": "Les tailles d'effet quantifient la signification pratique",
"context": "effect-size",
"pronunciation": "/ɪˈfekt saɪzɪz ˈkwɒntɪfaɪ ˈpræktɪkəl sɪɡˈnɪfɪkəns/"
},
"Confidence intervals estimate population parameters": {
"user_language": "Les intervalles de confiance estiment les paramètres de population",
"context": "statistical-inference",
"pronunciation": "/ˈkɒnfɪdəns ˈɪntəvəlz ˈestɪmeɪt ˌpɒpjuˈleɪʃən pəˈræmɪtərz/"
},
"Bias reduction requires careful methodological attention": {
"user_language": "La réduction des biais nécessite une attention méthodologique minutieuse",
"context": "bias-control",
"pronunciation": "/ˈbaɪəs rɪˈdʌkʃən rɪˈkwaɪərz ˈkeəfəl ˌmeθədəˈlɒdʒɪkəl əˈtenʃən/"
},
"The framework integrates multiple theoretical perspectives": {
"user_language": "Le cadre intègre plusieurs perspectives théoriques",
"context": "theoretical-integration",
"pronunciation": "/ðə ˈfreɪmwɜːk ˈɪntɪɡreɪts ˈmʌltɪpəl ˌθiːəˈretɪkəl pərˈspektɪvz/"
},
"Operationalization transforms concepts into measurable variables": {
"user_language": "L'opérationnalisation transforme les concepts en variables mesurables",
"context": "operationalization",
"pronunciation": "/ˌɒpəreɪʃənəlaɪˈzeɪʃən trænsˈːmz ˈkɒnsepts ˈɪntuː ˈmeʒərəbəl ˈvɛəriəbəlz/"
},
"Distribution characteristics determine statistical procedures": {
"user_language": "Les caractéristiques de distribution déterminent les procédures statistiques",
"context": "statistical-assumptions",
"pronunciation": "/ˌdɪstrɪˈbjuːʃən ˌkærəktəˈrɪstɪks dɪˈːmɪn stəˈtɪstɪkəl prəˈsiːdʒərz/"
},
"Anomaly detection identifies unusual patterns": {
"user_language": "La détection d'anomalies identifie des motifs inhabituels",
"context": "data-quality",
"pronunciation": "/əˈnɒməli dɪˈtekʃən aɪˈdentɪfaɪz ʌnˈjuːʒuəl ˈpætənz/"
},
"The criterion establishes evaluation standards": {
"user_language": "Le critère établit des normes d'évaluation",
"context": "assessment",
"pronunciation": "/ðə kraɪˈtɪəriən ɪˈstæblɪʃɪz ɪˌvæljuˈeɪʃən ˈstændərdz/"
},
"Multiple criteria guide comprehensive assessment": {
"user_language": "Plusieurs critères guident l'évaluation complète",
"context": "evaluation",
"pronunciation": "/ˈmʌltɪpəl kraɪˈtɪəriə ɡaɪd ˌkɒmprɪˈhensɪv əˈsesmənt/"
},
"The phenomenon requires sophisticated analytical approaches": {
"user_language": "Le phénomène nécessite des approches analytiques sophistiquées",
"context": "phenomenon-analysis",
"pronunciation": "/ðə fɪˈnɒmɪnən rɪˈkwaɪərz səˈfɪstɪkeɪtɪd ˌænəˈlɪtɪkəl əˈprəʊtʃɪz/"
},
"Various phenomena demonstrate consistent patterns": {
"user_language": "Divers phénomènes démontrent des modèles cohérents",
"context": "pattern-identification",
"pronunciation": "/ˈvɛəriəs fɪˈnɒmɪˈdemənstreɪt kənˈsɪstənt ˈpætənz/"
},
"Assessment procedures evaluate participant performance": {
"user_language": "Les procédures d'évaluation évaluent la performance des participants",
"context": "performance-evaluation",
"pronunciation": "/əˈsesmənt prəˈsiːdʒərz ɪˈvæljueɪt pɑːˈtɪsɪpənt pəˈːməns/"
},
"Comprehensive evaluation examines multiple dimensions": {
"user_language": "L'évaluation complète examine plusieurs dimensions",
"context": "multidimensional-assessment",
"pronunciation": "/ˌkɒmprɪˈhensɪv ɪˌvæljuˈeɪʃən ɪɡˈzæmɪnz ˈmʌltɪpəl daɪˈmenʃənz/"
},
"Interpretation depends on theoretical frameworks": {
"user_language": "L'interprétation dépend des cadres théoriques",
"context": "theoretical-interpretation",
"pronunciation": "/ɪnˌtɜːprɪˈteɪʃən dɪˈpendz ɒn ˌθiːəˈretɪkəl ˈfreɪmwɜːks/"
},
"Conceptualization provides foundation for investigation": {
"user_language": "La conceptualisation fournit le fondement de l'investigation",
"context": "conceptual-framework",
"pronunciation": "/kənˌseptʃuəlaɪˈzeɪʃən prəˈvaɪdz faʊnˈdeɪʃən fɔːr ɪnˌvestɪˈɡeɪʃən/"
},
"Epistemological assumptions influence research design": {
"user_language": "Les hypothèses épistémologiques influencent le design de recherche",
"context": "epistemology",
"pronunciation": "/ɪˌpɪstɪˈlɒdʒɪkəl əˈsʌmpʃənz ˈɪnfluəns rɪˈːtʃ dɪˈzaɪn/"
},
"Ontological considerations determine reality conceptualization": {
"user_language": "Les considérations ontologiques déterminent la conceptualisation de la réalité",
"context": "ontology",
"pronunciation": "/ˌɒntəˈlɒdʒɪkəl kənˌsɪˈreɪʃənz dɪˈːmɪn riˈæləti kənˌseptʃuəlaɪˈzeɪʃən/"
},
"Phenomenology examines consciousness structures": {
"user_language": "La phénoménologie examine les structures de conscience",
"context": "phenomenology",
"pronunciation": "/fɪˌnɒmɪˈnɒlədʒi ɪɡˈzæmɪnz ˈkɒnʃəsnəs ˈstrʌktʃərz/"
},
"Hermeneutics focuses on textual interpretation": {
"user_language": "L'herméneutique se concentre sur l'interprétation textuelle",
"context": "hermeneutics",
"pronunciation": "/ˌhɜːmɪˈnjuːtɪks ˈfəʊkəsɪz ɒn ˈtekstʃuəl ɪnˌtɜːprɪˈteɪʃən/"
},
"Positivism emphasizes empirical observation": {
"user_language": "Le positivisme met l'accent sur l'observation empirique",
"context": "positivism",
"pronunciation": "/ˈpɒzɪtɪvɪzəm ˈemfəsaɪzɪz ɪmˈpɪrɪkəl ˌɒbzəˈveɪʃən/"
},
"Constructivism recognizes subjective meaning-making": {
"user_language": "Le constructivisme reconnaît la création de sens subjective",
"context": "constructivism",
"pronunciation": "/kənˈstrʌktɪvɪzəm ˈrekəɡnaɪzɪz səbˈdʒektɪv ˈmiːnɪŋ meɪkɪŋ/"
},
"Pragmatism values practical utility": {
"user_language": "Le pragmatisme valorise l'utilité pratique",
"context": "pragmatism",
"pronunciation": "/ˈpræɡmətɪzəm ˈvæljuːz ˈpræktɪkəl juːˈtɪləti/"
},
"Investigation requires systematic planning": {
"user_language": "L'investigation nécessite une planification systématique",
"context": "research-planning",
"pronunciation": "/ɪnˌvestɪˈɡeɪʃən rɪˈkwaɪərz ˌsɪstəˈmætɪk ˈplænɪŋ/"
},
"Inquiry processes involve questioning": {
"user_language": "Les processus d'enquête impliquent le questionnement",
"context": "inquiry",
"pronunciation": "/ɪnˈkwaɪəri ˈprəʊsesɪz ɪnˈvɒlv ˈkwestʃənɪŋ/"
},
"Exploration reveals previously unknown patterns": {
"user_language": "L'exploration révèle des modèles précédemment inconnus",
"context": "exploratory-research",
"pronunciation": "/ˌekspləˈreɪʃən rɪˈviːlz ˈpriːviəsli ʌnˈnəʊn ˈpætənz/"
},
"Mixed-methods approaches integrate quantitative and qualitative data": {
"user_language": "Les approches de méthodes mixtes intègrent les données quantitatives et qualitatives",
"context": "mixed-methods",
"pronunciation": "/mɪkst ˈmeθədz əˈprəʊtʃɪz ˈɪntɪɡreɪt ˈkwɒntɪtətɪv ænd ˈkwɒlɪtətɪv ˈdeɪtə/"
},
"Contemporary methodology emphasizes transparency and reproducibility": {
"user_language": "La méthodologie contemporaine met l'accent sur la transparence et la reproductibilité",
"context": "open-science",
"pronunciation": "/kənˈtempərəri ˌmeθəˈdɒlədʒi ˈemfəsaɪzɪz trænsˈpærənsi ænd ˌriːprəˌdjuːˈbɪləti/"
},
"Interdisciplinary collaboration requires methodological flexibility": {
"user_language": "La collaboration interdisciplinaire nécessite une flexibilité méthodologique",
"context": "interdisciplinary-research",
"pronunciation": "/ˌɪntədɪˈplɪnəri kəˌlæbəˈreɪʃən rɪˈkwaɪərz ˌmeθədəˈlɒdʒɪkəl ˌfleksəˈbɪləti/"
}
},
"grammar": {
"advanced-conditional-subjunctive": {
"title": "Advanced Conditional Sentences & Subjunctive Mood in Academic Writing",
"explanation": "This comprehensive module explores the intricate relationship between conditional constructions and subjunctive mood expressions in formal academic discourse. Understanding these structures is essential for sophisticated argumentation, hypothetical reasoning, and nuanced expression of uncertainty, possibility, and counterfactual scenarios in scholarly writing. The subjunctive mood, particularly in complex conditional sentences, allows researchers to express theoretical positions, propose alternative interpretations, and discuss implications while maintaining appropriate academic objectivity and precision.",
"mainRules": [
"Use subjunctive mood after verbs expressing necessity, recommendation, or demand in academic contexts",
"Employ inverted conditional structures for formal emphasis and stylistic variation in scholarly writing",
"Apply mixed conditionals to express relationships between past hypotheses and present research implications",
"Utilize subjunctive in noun clauses following expressions of importance, necessity, or suggestion",
"Implement subjunctive constructions in formal presentations of alternative theoretical frameworks",
"Apply conditional perfect forms to discuss counterfactual research scenarios and alternative methodologies",
"Use subjunctive mood in concessive clauses to acknowledge opposing viewpoints academically",
"Employ hypothetical conditionals when proposing theoretical models and testing research hypotheses"
],
"detailedExplanation": {
"mandative-subjunctive": {
"title": "Mandative Subjunctive in Academic Recommendations",
"pattern": "It is essential/crucial/imperative that + subject + base verb",
"explanation": "The mandative subjunctive expresses necessity, recommendations, or requirements in formal academic writing, particularly when discussing research protocols, methodological requirements, or theoretical imperatives.",
"examples": [
{
"sentence": "It is essential that the researcher validate all instruments before data collection begins.",
"translation": "Il est essentiel que le chercheur valide tous les instruments avant que la collecte de données ne commence.",
"breakdown": "It is essential that + researcher + validate (base verb, not validates)",
"academicContext": "Methodological requirement in research design"
},
{
"sentence": "The committee demands that each hypothesis be tested using rigorous statistical procedures.",
"translation": "Le comité exige que chaque hypothèse soit testée en utilisant des procédures statistiques rigoureuses.",
"breakdown": "demands that + hypothesis + be tested (subjunctive form)",
"academicContext": "Institutional requirement for research standards"
},
{
"sentence": "It is imperative that the methodology remain consistent throughout the longitudinal study.",
"translation": "Il est impératif que la méthodologie reste cohérente tout au long de l'étude longitudinale.",
"breakdown": "imperative that + methodology + remain (base verb form)",
"academicContext": "Consistency requirement in longitudinal research"
}
]
},
"inverted-conditionals": {
"title": "Inverted Conditional Structures for Academic Emphasis",
"pattern": "Were/Had/Should + subject + verb, main clause would/could/might",
"explanation": "Inverted conditionals provide formal emphasis and stylistic sophistication in academic writing, allowing researchers to present hypothetical scenarios with greater formality and precision than standard conditional structures.",
"examples": [
{
"sentence": "Were the sample size to be increased significantly, the statistical power would improve substantially.",
"translation": "Si la taille de l'échantillon était augmentée de manière significative, la puissance statistique s'améliorerait considérablement.",
"breakdown": "Were + sample size + to be increased (inverted second conditional)",
"academicContext": "Hypothetical improvement in research design"
},
{
"sentence": "Had the researchers employed mixed-methods approaches, the findings might have been more comprehensive.",
"translation": "Si les chercheurs avaient employé des approches de méthodes mixtes, les résultats auraient pu être plus complets.",
"breakdown": "Had + researchers + employed (inverted third conditional)",
"academicContext": "Counterfactual analysis of methodological choices"
},
{
"sentence": "Should future studies incorporate neuroimaging techniques, our understanding of cognitive processes would advance considerably.",
"translation": "Si de futures études incorporaient des techniques de neuroimagerie, notre compréhension des processus cognitifs progresserait considérablement.",
"breakdown": "Should + studies + incorporate (inverted first conditional)",
"academicContext": "Prospective methodological development"
}
]
},
"mixed-conditionals": {
"title": "Mixed Conditionals in Academic Discourse",
"pattern": "If + past perfect, would/could/might + present | If + past simple, would/could + perfect",
"explanation": "Mixed conditionals express complex temporal relationships between past research decisions and present implications, or current theoretical positions and their potential past consequences, essential for sophisticated academic analysis.",
"examples": [
{
"sentence": "If the original researchers had controlled for socioeconomic variables, we would have stronger theoretical foundations today.",
"translation": "Si les chercheurs originaux avaient contrôlé les variables socioéconomiques, nous aurions des fondements théoriques plus solides aujourd'hui.",
"breakdown": "past perfect condition + present consequence",
"academicContext": "Historical methodological critique with current implications"
},
{
"sentence": "If current ethical standards were applied retrospectively, many landmark studies would never have been conducted.",
"translation": "Si les normes éthiques actuelles étaient appliquées rétrospectivement, de nombreuses études de référence n'auraient jamais été menées.",
"breakdown": "present hypothetical condition + past perfect consequence",
"academicContext": "Ethical evolution in research history"
},
{
"sentence": "If interdisciplinary collaboration were more common historically, the field might have developed more comprehensive theoretical frameworks decades earlier.",
"translation": "Si la collaboration interdisciplinaire avait été plus courante historiquement, le domaine aurait pu développer des cadres théoriques plus complets des décennies plus tôt.",
"breakdown": "past hypothetical condition + past perfect consequence with temporal modifier",
"academicContext": "Counterfactual disciplinary development analysis"
}
]
},
"concessive-subjunctive": {
"title": "Subjunctive in Concessive Academic Arguments",
"pattern": "Though/Although/Even if + subjunctive, main clause acknowledges complexity",
"explanation": "Concessive subjunctive constructions allow academic writers to acknowledge alternative viewpoints, limitations, or opposing evidence while maintaining their primary argumentative position with appropriate scholarly nuance.",
"examples": [
{
"sentence": "Though the alternative hypothesis be theoretically plausible, the empirical evidence overwhelmingly supports the primary model.",
"translation": "Bien que l'hypothèse alternative soit théoriquement plausible, les preuves empiriques soutiennent massivement le modèle principal.",
"breakdown": "Though + hypothesis + be (subjunctive) + acknowledgment",
"academicContext": "Acknowledging competing theories while maintaining position"
},
{
"sentence": "Even if the methodology were to be criticized by future researchers, the foundational contributions remain scientifically valuable.",
"translation": "Même si la méthodologie devait être critiquée par de futurs chercheurs, les contributions fondamentales restent scientifiquement précieuses.",
"breakdown": "Even if + methodology + were to be (subjunctive future) + defensive argument",
"academicContext": "Anticipating methodological criticism while defending contribution"
},
{
"sentence": "Although the sample size be relatively small, the qualitative depth provides considerable theoretical insight.",
"translation": "Bien que la taille de l'échantillon soit relativement petite, la profondeur qualitative fournit un aperçu théorique considérable.",
"breakdown": "Although + size + be (subjunctive) + compensatory argument",
"academicContext": "Acknowledging limitation while emphasizing strength"
}
]
}
},
"exercises": [
{
"type": "fill_blank",
"question": "It is crucial that the researcher _____ all potential confounding variables before beginning the analysis.",
"options": ["identifies", "identify", "identified", "will identify"],
"correctAnswer": "identify",
"hint": "Use the subjunctive base form after expressions of necessity.",
"explanation": "After 'It is crucial that', we use the subjunctive mood with the base form of the verb.",
"difficulty": "medium",
"points": 15,
"grammarFocus": "mandative-subjunctive"
},
{
"type": "fill_blank",
"question": "_____ the funding been approved earlier, the research team could have collected longitudinal data.",
"options": ["If", "Had", "Were", "Should"],
"correctAnswer": "Had",
"hint": "Use the inverted form of the third conditional.",
"explanation": "'Had + subject + past participle' is the inverted form of third conditional expressing past counterfactual.",
"difficulty": "hard",
"points": 20,
"grammarFocus": "inverted-conditionals"
},
{
"type": "transformation",
"question": "Transform to inverted conditional: If the sample were larger, the results would be more generalizable.",
"correctAnswer": "Were the sample larger, the results would be more generalizable.",
"hint": "Move 'were' to the beginning and remove 'if'.",
"explanation": "Inverted conditionals place the auxiliary verb first, creating a more formal academic tone.",
"difficulty": "medium",
"points": 18,
"grammarFocus": "inverted-conditionals"
},
{
"type": "correction",
"question": "Correct the error: It is essential that the participant signs the informed consent before participating.",
"correctAnswer": "It is essential that the participant sign the informed consent before participating.",
"hint": "Use the base form of the verb after subjunctive expressions.",
"explanation": "After subjunctive expressions like 'it is essential that', use the base form 'sign', not 'signs'.",
"difficulty": "easy",
"points": 12,
"grammarFocus": "mandative-subjunctive"
},
{
"type": "multiple_choice",
"question": "Which sentence correctly uses the subjunctive mood?",
"options": [
"The committee insists that the researcher submits monthly reports.",
"The committee insists that the researcher submit monthly reports.",
"The committee insists that the researcher is submitting monthly reports.",
"The committee insists that the researcher will submit monthly reports."
],
"correctAnswer": "The committee insists that the researcher submit monthly reports.",
"hint": "Look for the base form of the verb after expressions of demand.",
"explanation": "After 'insists that', we use the subjunctive base form 'submit', not 'submits'.",
"difficulty": "medium",
"points": 15,
"grammarFocus": "mandative-subjunctive"
},
{
"type": "fill_blank",
"question": "Though the evidence _____ compelling, alternative interpretations deserve consideration.",
"options": ["is", "be", "were", "was"],
"correctAnswer": "be",
"hint": "Use subjunctive form in concessive clauses for formal academic writing.",
"explanation": "In formal concessive constructions with 'though', the subjunctive 'be' is preferred over 'is'.",
"difficulty": "hard",
"points": 22,
"grammarFocus": "concessive-subjunctive"
},
{
"type": "construction",
"question": "Create a mixed conditional sentence discussing how past research decisions affect current theoretical understanding.",
"correctAnswer": "If earlier researchers had incorporated interdisciplinary perspectives, we would have more comprehensive theoretical models today.",
"hint": "Use past perfect in the if-clause and would + present in the main clause.",
"explanation": "Mixed conditionals connect past hypothetical conditions with present consequences.",
"difficulty": "hard",
"points": 25,
"grammarFocus": "mixed-conditionals"
},
{
"type": "transformation",
"question": "Convert to formal academic tone: If you increase the sample size, you will get better results.",
"correctAnswer": "Were the sample size to be increased, more robust results would be obtained.",
"hint": "Use inverted conditional and passive voice for academic formality.",
"explanation": "Academic writing favors inverted conditionals and passive constructions for objectivity.",
"difficulty": "hard",
"points": 20,
"grammarFocus": "inverted-conditionals"
}
]
}
}
}

View File

@ -1,26 +0,0 @@
@echo off
echo 🔧 Fixing server issue...
REM Kill process on port 8080 specifically
echo 🛑 Stopping server on port 8080...
for /f "tokens=5" %%a in ('netstat -aon ^| findstr ":8080 "') do (
echo Killing process %%a
taskkill /F /PID %%a 2>nul
)
REM Kill all Node processes just in case
echo 🛑 Stopping all Node.js processes...
taskkill /F /IM node.exe /T 2>nul
REM Wait a moment
echo ⏳ Waiting...
timeout /t 3 /nobreak >nul
REM Start the correct server
echo 🚀 Starting correct server...
cd /d "%~dp0"
echo Working directory: %CD%
echo Starting: node server.js
node server.js
pause

3420
index.html

File diff suppressed because it is too large Load Diff

View File

@ -247,8 +247,8 @@ window.ContentModules.SBSLevel1 = {
sentences: [
{
id: 1,
original: "Learn the alphabet Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz",
translation: "学习字母表 Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz",
original: "Learn the alphabet: Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz",
translation: "学习字母表Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz",
words: [
{word: "Learn", translation: "学习", type: "verb", pronunciation: "/lɜrn/"},
{word: "alphabet", translation: "字母表", type: "noun", pronunciation: "/ˈælfəbet/"}
@ -256,8 +256,8 @@ window.ContentModules.SBSLevel1 = {
},
{
id: 2,
original: "Practice numbers 0 1 2 3 4 5 6 7 8 9 10",
translation: "练习数字 0 1 2 3 4 5 6 7 8 9 10",
original: "Practice numbers: 0 1 2 3 4 5 6 7 8 9 10",
translation: "练习数字0 1 2 3 4 5 6 7 8 9 10",
words: [
{word: "Practice", translation: "练习", type: "verb", pronunciation: "/ˈpræktɪs/"},
{word: "numbers", translation: "数字", type: "noun", pronunciation: "/ˈnʌmbərz/"}

View File

@ -298,100 +298,6 @@ window.ContentModules.WTA1B1 = {
}
},
// === LETTERS DISCOVERY SYSTEM ===
letters: {
"U": [
{
word: "unhappy",
translation: "不开心的",
type: "adjective",
pronunciation: "ʌnhæpi",
example: "The cat looks unhappy."
},
{
word: "umbrella",
translation: "雨伞",
type: "noun",
pronunciation: "ʌmbrɛlə",
example: "I need an umbrella when it rains."
},
{
word: "up",
translation: "向上",
type: "adverb",
pronunciation: "ʌp",
example: "The bird flies up high."
},
{
word: "under",
translation: "在...下面",
type: "preposition",
pronunciation: "ʌndər",
example: "The cat hides under the table."
}
],
"V": [
{
word: "violet",
translation: "紫色的",
type: "adjective",
pronunciation: "vaɪələt",
example: "She has a violet dress."
},
{
word: "van",
translation: "面包车",
type: "noun",
pronunciation: "væn",
example: "The vet drives a white van."
},
{
word: "vet",
translation: "兽医",
type: "noun",
pronunciation: "vɛt",
example: "The vet takes care of pets."
},
{
word: "vest",
translation: "背心",
type: "noun",
pronunciation: "vɛst",
example: "He wears a warm vest."
}
],
"T": [
{
word: "tall",
translation: "高的",
type: "adjective",
pronunciation: "tɔl",
example: "The teacher is very tall."
},
{
word: "turtle",
translation: "海龟",
type: "noun",
pronunciation: "tɜrtəl",
example: "The turtle moves slowly."
},
{
word: "tent",
translation: "帐篷",
type: "noun",
pronunciation: "tɛnt",
example: "We sleep in a tent when camping."
},
{
word: "tiger",
translation: "老虎",
type: "noun",
pronunciation: "taɪgər",
example: "The tiger is a big cat."
}
]
},
vocabulary: {
"unhappy": {
"user_language": "不开心的",

View File

@ -0,0 +1,574 @@
// === VÉRIFICATEUR DE COMPATIBILITÉ CONTENU-JEU ===
class ContentGameCompatibility {
constructor() {
this.compatibilityCache = new Map();
this.minimumScores = {
'whack-a-mole': 40,
'whack-a-mole-hard': 45,
'memory-match': 50,
'quiz-game': 30,
'fill-the-blank': 30,
'text-reader': 40,
'adventure-reader': 50,
'chinese-study': 35,
'story-builder': 35,
'story-reader': 40,
'word-storm': 15
};
}
/**
* Vérifie si un contenu est compatible avec un jeu
* @param {Object} content - Le contenu à vérifier
* @param {string} gameType - Le type de jeu
* @returns {Object} - { compatible: boolean, score: number, reason: string, requirements: string[] }
*/
checkCompatibility(content, gameType) {
// Utiliser le cache si disponible
const cacheKey = `${content.id || content.name}_${gameType}`;
if (this.compatibilityCache.has(cacheKey)) {
return this.compatibilityCache.get(cacheKey);
}
let result;
// Si le contenu a déjà une analyse de compatibilité (depuis ContentScanner)
if (content.gameCompatibility && content.gameCompatibility[gameType]) {
result = this.enrichCompatibilityInfo(content.gameCompatibility[gameType], gameType);
} else {
// Analyser manuellement
result = this.analyzeCompatibility(content, gameType);
}
// Mettre en cache
this.compatibilityCache.set(cacheKey, result);
return result;
}
/**
* Enrichit les informations de compatibilité existantes
*/
enrichCompatibilityInfo(existingCompat, gameType) {
const minScore = this.minimumScores[gameType] || 30;
return {
compatible: existingCompat.score >= minScore,
score: existingCompat.score,
reason: existingCompat.reason || this.getDefaultReason(gameType),
requirements: this.getGameRequirements(gameType),
details: this.getDetailedAnalysis(existingCompat, gameType)
};
}
/**
* Analyse manuelle de compatibilité si pas déjà calculée
*/
analyzeCompatibility(content, gameType) {
const capabilities = this.analyzeContentCapabilities(content);
const compatResult = this.calculateGameCompatibilityForType(capabilities, gameType);
return {
compatible: compatResult.compatible,
score: compatResult.score,
reason: compatResult.reason,
requirements: this.getGameRequirements(gameType),
details: this.getDetailedAnalysis(compatResult, gameType),
capabilities: capabilities
};
}
/**
* Analyse les capacités d'un contenu
*/
analyzeContentCapabilities(content) {
return {
hasVocabulary: this.hasContent(content, 'vocabulary'),
hasSentences: this.hasContent(content, 'sentences'),
hasGrammar: this.hasContent(content, 'grammar'),
hasAudio: this.hasContent(content, 'audio'),
hasDialogues: this.hasContent(content, 'dialogues'),
hasExercises: this.hasExercises(content),
hasFillInBlanks: this.hasContent(content, 'fillInBlanks'),
hasCorrections: this.hasContent(content, 'corrections'),
hasComprehension: this.hasContent(content, 'comprehension'),
hasMatching: this.hasContent(content, 'matching'),
// Compteurs
vocabularyCount: this.countItems(content, 'vocabulary'),
sentenceCount: this.countItems(content, 'sentences'),
dialogueCount: this.countItems(content, 'dialogues'),
grammarCount: this.countItems(content, 'grammar')
};
}
/**
* Calcule la compatibilité pour un type de jeu spécifique
*/
calculateGameCompatibilityForType(capabilities, gameType) {
switch (gameType) {
case 'whack-a-mole':
case 'whack-a-mole-hard':
return this.calculateWhackAMoleCompat(capabilities, gameType === 'whack-a-mole-hard');
case 'memory-match':
return this.calculateMemoryMatchCompat(capabilities);
case 'quiz-game':
return this.calculateQuizGameCompat(capabilities);
case 'fill-the-blank':
return this.calculateFillBlankCompat(capabilities);
case 'text-reader':
case 'story-reader':
return this.calculateTextReaderCompat(capabilities);
case 'adventure-reader':
return this.calculateAdventureCompat(capabilities);
case 'chinese-study':
return this.calculateChineseStudyCompat(capabilities);
case 'story-builder':
return this.calculateStoryBuilderCompat(capabilities);
case 'word-storm':
return this.calculateWordStormCompat(capabilities);
default:
return { compatible: true, score: 50, reason: 'Jeu non spécifiquement analysé' };
}
}
// === CALCULS DE COMPATIBILITÉ SPÉCIFIQUES PAR JEU ===
calculateWhackAMoleCompat(capabilities, isHard = false) {
let score = 0;
const reasons = [];
if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 5) {
score += 40;
reasons.push(`${capabilities.vocabularyCount} mots de vocabulaire`);
} else if (capabilities.vocabularyCount > 0) {
score += 20;
reasons.push(`${capabilities.vocabularyCount} mots (minimum recommandé: 5)`);
}
if (capabilities.hasSentences && capabilities.sentenceCount >= 3) {
score += 30;
reasons.push(`${capabilities.sentenceCount} phrases`);
} else if (capabilities.sentenceCount > 0) {
score += 15;
reasons.push(`${capabilities.sentenceCount} phrases (minimum recommandé: 3)`);
}
if (capabilities.hasAudio) {
score += 20;
reasons.push('Fichiers audio disponibles');
}
const minScore = isHard ? 45 : 40;
const compatible = score >= minScore;
return {
compatible,
score,
reason: compatible ?
`Compatible: ${reasons.join(', ')}` :
`Incompatible (score: ${score}/${minScore}): Nécessite plus de vocabulaire ou phrases`
};
}
calculateMemoryMatchCompat(capabilities) {
let score = 0;
const reasons = [];
if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 4) {
score += 50;
reasons.push(`${capabilities.vocabularyCount} paires de vocabulaire`);
} else {
return { compatible: false, score: 0, reason: 'Nécessite au moins 4 mots de vocabulaire' };
}
if (capabilities.hasAudio) {
score += 30;
reasons.push('Audio pour pronunciation');
}
return {
compatible: score >= 50,
score,
reason: `Compatible: ${reasons.join(', ')}`
};
}
calculateQuizGameCompat(capabilities) {
let score = 0;
const reasons = [];
// Quiz est très flexible
if (capabilities.hasVocabulary) {
score += 30;
reasons.push('Questions de vocabulaire');
}
if (capabilities.hasGrammar) {
score += 25;
reasons.push('Questions de grammaire');
}
if (capabilities.hasSentences) {
score += 20;
reasons.push('Questions sur les phrases');
}
if (capabilities.hasExercises) {
score += 45;
reasons.push('Exercices intégrés');
}
// Quiz fonctionne avec presque tout
if (score === 0 && (capabilities.vocabularyCount > 0 || capabilities.sentenceCount > 0)) {
score = 30;
reasons.push('Contenu de base disponible');
}
return {
compatible: score >= 30,
score,
reason: `Compatible: ${reasons.join(', ')}`
};
}
calculateFillBlankCompat(capabilities) {
let score = 0;
const reasons = [];
if (capabilities.hasFillInBlanks) {
score += 70;
reasons.push('Exercices à trous intégrés');
} else if (capabilities.hasSentences && capabilities.sentenceCount >= 3) {
score += 30;
reasons.push('Phrases pouvant être adaptées en exercices à trous');
} else {
return { compatible: false, score: 0, reason: 'Nécessite des phrases ou exercices à trous' };
}
return {
compatible: score >= 30,
score,
reason: `Compatible: ${reasons.join(', ')}`
};
}
calculateTextReaderCompat(capabilities) {
let score = 0;
const reasons = [];
if (capabilities.hasSentences && capabilities.sentenceCount >= 3) {
score += 40;
reasons.push(`${capabilities.sentenceCount} phrases à lire`);
}
if (capabilities.hasDialogues && capabilities.dialogueCount > 0) {
score += 50;
reasons.push(`${capabilities.dialogueCount} dialogues`);
}
if (capabilities.hasAudio) {
score += 10;
reasons.push('Audio disponible');
}
return {
compatible: score >= 40,
score,
reason: score >= 40 ? `Compatible: ${reasons.join(', ')}` : 'Nécessite des phrases ou dialogues à lire'
};
}
calculateAdventureCompat(capabilities) {
let score = 0;
const reasons = [];
if (capabilities.hasDialogues && capabilities.dialogueCount > 0) {
score += 60;
reasons.push('Dialogues pour narration');
}
if (capabilities.hasSentences && capabilities.sentenceCount >= 5) {
score += 30;
reasons.push('Contenu narratif suffisant');
}
if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 10) {
score += 10;
reasons.push('Vocabulaire riche');
}
return {
compatible: score >= 50,
score,
reason: score >= 50 ? `Compatible: ${reasons.join(', ')}` : 'Nécessite plus de dialogues et contenu narratif'
};
}
calculateChineseStudyCompat(capabilities) {
let score = 0;
const reasons = [];
if (capabilities.hasVocabulary) {
score += 35;
reasons.push('Vocabulaire chinois');
}
if (capabilities.hasSentences) {
score += 25;
reasons.push('Phrases chinoises');
}
if (capabilities.hasAudio) {
score += 40;
reasons.push('Prononciation audio');
}
return {
compatible: score >= 35,
score,
reason: score >= 35 ? `Compatible: ${reasons.join(', ')}` : 'Optimisé pour contenu chinois'
};
}
calculateStoryBuilderCompat(capabilities) {
let score = 0;
const reasons = [];
if (capabilities.hasDialogues) {
score += 40;
reasons.push('Dialogues pour construction');
}
if (capabilities.hasSentences && capabilities.sentenceCount >= 5) {
score += 35;
reasons.push('Phrases pour séquences');
}
if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 8) {
score += 25;
reasons.push('Vocabulaire varié');
}
return {
compatible: score >= 35,
score,
reason: score >= 35 ? `Compatible: ${reasons.join(', ')}` : 'Nécessite contenu pour construction narrative'
};
}
calculateWordStormCompat(capabilities) {
let score = 0;
const reasons = [];
// Word Storm nécessite principalement du vocabulaire
if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 5) {
score += 60;
reasons.push(`${capabilities.vocabularyCount} mots de vocabulaire`);
} else if (capabilities.vocabularyCount > 0) {
score += 30;
reasons.push(`${capabilities.vocabularyCount} mots (peu mais suffisant)`);
}
// Bonus pour plus de vocabulaire
if (capabilities.vocabularyCount >= 20) {
score += 15;
reasons.push('Vocabulaire riche');
}
// Bonus si les mots ont des prononciations
if (capabilities.hasPronunciation) {
score += 10;
reasons.push('Prononciations disponibles');
}
// Word Storm peut fonctionner même avec peu de contenu
if (score === 0 && (capabilities.hasSentences || capabilities.hasDialogues)) {
score = 25;
reasons.push('Peut extraire vocabulaire des phrases/dialogues');
}
return {
compatible: score >= 15,
score,
reason: score >= 15 ? `Compatible: ${reasons.join(', ')}` : 'Nécessite au moins quelques mots de vocabulaire'
};
}
// === UTILITAIRES ===
hasContent(content, type) {
// Vérification standard
const data = content[type] || content.rawContent?.[type] || content.adaptedContent?.[type];
if (data) {
if (Array.isArray(data)) return data.length > 0;
if (typeof data === 'object') return Object.keys(data).length > 0;
return !!data;
}
// Support pour formats spéciaux
if (type === 'sentences' && content.story?.chapters) {
// Format story avec chapitres (comme Dragon's Pearl)
return content.story.chapters.some(chapter =>
chapter.sentences && chapter.sentences.length > 0
);
}
if (type === 'dialogues' && content.story?.chapters) {
// Vérifier s'il y a du contenu narratif riche dans les stories
return content.story.chapters.length > 1; // Multiple chapitres = contenu narratif
}
return false;
}
hasExercises(content) {
return this.hasContent(content, 'exercises') ||
this.hasContent(content, 'fillInBlanks') ||
this.hasContent(content, 'corrections') ||
this.hasContent(content, 'comprehension') ||
this.hasContent(content, 'matching');
}
countItems(content, type) {
// Vérification standard
const data = content[type] || content.rawContent?.[type] || content.adaptedContent?.[type];
if (data) {
if (Array.isArray(data)) return data.length;
if (typeof data === 'object') return Object.keys(data).length;
}
// Support pour formats spéciaux
if (type === 'sentences' && content.story?.chapters) {
// Compter toutes les phrases dans tous les chapitres
return content.story.chapters.reduce((total, chapter) =>
total + (chapter.sentences ? chapter.sentences.length : 0), 0
);
}
if (type === 'dialogues' && content.story?.chapters) {
// Considérer chaque chapitre comme un "dialogue" narratif
return content.story.chapters.length;
}
if (type === 'vocabulary') {
// Vérifier d'abord le format standard
const vocab = content.vocabulary || content.rawContent?.vocabulary || content.adaptedContent?.vocabulary;
if (vocab) {
if (Array.isArray(vocab)) return vocab.length;
if (typeof vocab === 'object') return Object.keys(vocab).length;
}
}
return 0;
}
getGameRequirements(gameType) {
const requirements = {
'whack-a-mole': ['5+ mots de vocabulaire OU 3+ phrases', 'Contenu simple et répétitif'],
'whack-a-mole-hard': ['5+ mots de vocabulaire ET 3+ phrases', 'Contenu varié'],
'memory-match': ['4+ paires de vocabulaire', 'Idéalement avec images/audio'],
'quiz-game': ['Vocabulaire OU phrases OU exercices', 'Très flexible'],
'fill-the-blank': ['Phrases avec exercices à trous OU phrases simples', 'Contenu éducatif'],
'text-reader': ['3+ phrases OU dialogues', 'Contenu narratif'],
'adventure-reader': ['Dialogues + contenu narratif riche', 'Histoire cohérente'],
'chinese-study': ['Vocabulaire et phrases chinoises', 'Audio recommandé'],
'story-builder': ['Dialogues OU 5+ phrases', 'Vocabulaire varié'],
'story-reader': ['Textes à lire, dialogues recommandés', 'Contenu narratif'],
'word-storm': ['3+ mots de vocabulaire', 'Prononciations recommandées']
};
return requirements[gameType] || ['Contenu de base'];
}
getDefaultReason(gameType) {
const reasons = {
'whack-a-mole': 'Jeu de rapidité nécessitant vocabulaire ou phrases',
'memory-match': 'Jeu de mémoire optimisé pour paires vocabulaire-traduction',
'quiz-game': 'Jeu polyvalent compatible avec la plupart des contenus',
'fill-the-blank': 'Exercices à trous nécessitant phrases structurées',
'text-reader': 'Lecture guidée nécessitant textes ou dialogues',
'adventure-reader': 'Aventure narrative nécessitant contenu riche',
'chinese-study': 'Optimisé pour apprentissage du chinois',
'story-builder': 'Construction narrative nécessitant éléments variés',
'story-reader': 'Lecture d\'histoires nécessitant contenu narratif'
};
return reasons[gameType] || 'Compatibilité non évaluée spécifiquement';
}
getDetailedAnalysis(compatResult, gameType) {
return {
minimumScore: this.minimumScores[gameType] || 30,
actualScore: compatResult.score,
recommendation: compatResult.score >= (this.minimumScores[gameType] || 30) ?
'Fortement recommandé' :
compatResult.score >= (this.minimumScores[gameType] || 30) * 0.7 ?
'Compatible avec limitations' :
'Non recommandé'
};
}
/**
* Filtre une liste de contenus pour un jeu spécifique
* @param {Array} contentList - Liste des contenus
* @param {string} gameType - Type de jeu
* @returns {Array} - Contenus compatibles triés par score
*/
filterCompatibleContent(contentList, gameType) {
return contentList
.map(content => ({
...content,
compatibility: this.checkCompatibility(content, gameType)
}))
.filter(content => content.compatibility.compatible)
.sort((a, b) => b.compatibility.score - a.compatibility.score);
}
/**
* Obtient des suggestions d'amélioration pour rendre un contenu compatible
* @param {Object} content - Le contenu à analyser
* @param {string} gameType - Le type de jeu
* @returns {Array} - Liste de suggestions
*/
getImprovementSuggestions(content, gameType) {
const compatibility = this.checkCompatibility(content, gameType);
if (compatibility.compatible) return [];
const suggestions = [];
const capabilities = compatibility.capabilities || this.analyzeContentCapabilities(content);
switch (gameType) {
case 'whack-a-mole':
case 'whack-a-mole-hard':
if (capabilities.vocabularyCount < 5) {
suggestions.push(`Ajouter ${5 - capabilities.vocabularyCount} mots de vocabulaire supplémentaires`);
}
if (capabilities.sentenceCount < 3) {
suggestions.push(`Ajouter ${3 - capabilities.sentenceCount} phrases supplémentaires`);
}
break;
case 'memory-match':
if (capabilities.vocabularyCount < 4) {
suggestions.push(`Ajouter ${4 - capabilities.vocabularyCount} paires de vocabulaire supplémentaires`);
}
break;
// Autres cas...
}
if (suggestions.length === 0) {
suggestions.push('Enrichir le contenu général du module');
}
return suggestions;
}
/**
* Vide le cache de compatibilité
*/
clearCache() {
this.compatibilityCache.clear();
}
}
// Export global
window.ContentGameCompatibility = ContentGameCompatibility;

View File

@ -26,17 +26,13 @@ class ContentScanner {
for (const filename of contentFiles) {
try {
logSh(`🔍 Scanning content file: ${filename}`, 'DEBUG');
const contentInfo = await this.scanContentFile(filename);
if (contentInfo) {
logSh(`✅ Successfully scanned: ${contentInfo.id} (${contentInfo.name})`, 'INFO');
this.discoveredContent.set(contentInfo.id, contentInfo);
results.found.push(contentInfo);
} else {
logSh(`⚠️ scanContentFile returned null for ${filename}`, 'WARN');
}
} catch (error) {
logSh(`⚠️ Erreur scan ${filename}: ${error.message}`, 'WARN');
logSh(`⚠️ Erreur scan ${filename}:`, error.message, 'WARN');
results.errors.push({ filename, error: error.message });
}
}
@ -114,10 +110,7 @@ class ContentScanner {
'story-prototype-optimized.js', // Optimized story with centralized vocabulary
'test-compatibility.js', // Test content for compatibility system
'test-minimal.js', // Minimal test content
'test-rich.js', // Rich test content
'NCE1-Lesson63-64.js', // New Concept English Book 1 - Lessons 63-64
'NCE2-Lesson3.js', // New Concept English Book 2 - Lesson 3
'NCE2-Lesson30.js' // New Concept English Book 2 - Lesson 30
'test-rich.js' // Rich test content
];
logSh('📂 Préchargement des fichiers connus...', 'INFO');
@ -266,26 +259,14 @@ class ContentScanner {
const loadedFiles = [];
if (window.ContentModules) {
logSh(`📂 Modules already loaded in window.ContentModules:`, 'DEBUG');
for (const moduleName in window.ContentModules) {
// Convertir le nom du module en nom de fichier probable
const filename = this.moduleNameToFilename(moduleName);
loadedFiles.push(filename);
logSh(`✓ Module découvert: ${moduleName}${filename}`, 'INFO');
// Debug : vérifier si le module a un id
const module = window.ContentModules[moduleName];
if (module.id) {
logSh(` Module ID: ${module.id}`, 'DEBUG');
} else {
logSh(` ⚠️ Module ${moduleName} has no ID!`, 'WARN');
}
}
} else {
logSh(`⚠️ window.ContentModules is undefined!`, 'WARN');
}
logSh(`📂 Total loaded files from modules: ${loadedFiles.length}`, 'INFO');
return loadedFiles;
}
@ -314,11 +295,7 @@ class ContentScanner {
// AJOUT: Test compatibility modules
'TestMinimalContent': 'test-compatibility.js',
'TestRichContent': 'test-compatibility.js',
'TestSentenceOnly': 'test-compatibility.js',
// AJOUT: NCE modules
'NCE1Lesson6364': 'NCE1-Lesson63-64.js',
'NCE2Lesson3': 'NCE2-Lesson3.js',
'NCE2Lesson30': 'NCE2-Lesson30.js'
'TestSentenceOnly': 'test-compatibility.js'
};
if (mapping[moduleName]) {
@ -362,11 +339,7 @@ class ContentScanner {
'example-with-images.js', // Local JS with image support for Word Discovery
// AJOUT: Fichiers générés par le système de conversion
'sbs-level-7-8-GENERATED-from-js.json',
'english-exemple-commented-GENERATED.json',
// AJOUT: Fichiers NCE (New Concept English)
'NCE1-Lesson63-64.js', // New Concept English Book 1 - Lessons 63-64
'NCE2-Lesson3.js', // New Concept English Book 2 - Lesson 3
'NCE2-Lesson30.js' // New Concept English Book 2 - Lesson 30
'english-exemple-commented-GENERATED.json'
];
const existingFiles = [];
@ -432,9 +405,6 @@ class ContentScanner {
const moduleName = this.getModuleName(contentId);
try {
// Vérifier d'abord si le module est déjà chargé
if (!window.ContentModules || !window.ContentModules[moduleName]) {
// Le module n'est pas encore chargé, on doit le charger
// Détecter le type de fichier et charger en conséquence
if (filename.endsWith('.json')) {
// Fichier JSON - essayer de le charger via proxy ou local
@ -444,13 +414,10 @@ class ContentScanner {
await this.loadScript(`js/content/${filename}`);
}
// Vérifier si le module existe après chargement
// Vérifier si le module existe
if (!window.ContentModules || !window.ContentModules[moduleName]) {
throw new Error(`Module ${moduleName} non trouvé après chargement`);
}
} else {
logSh(`✓ Module ${moduleName} déjà chargé, pas besoin de recharger`, 'INFO');
}
const module = window.ContentModules[moduleName];
@ -467,19 +434,7 @@ class ContentScanner {
extractContentInfo(module, contentId, filename) {
// Analyser les capacités du contenu ultra-modulaire
let capabilities;
try {
capabilities = this.analyzeContentCapabilities(module);
} catch (error) {
logSh(`⚠️ Erreur dans analyzeContentCapabilities pour ${contentId}: ${error.message}`, 'WARN');
// Fallback avec capacités basiques
capabilities = {
hasVocabulary: !!module.vocabulary,
hasSentences: !!module.sentences,
hasGrammar: !!module.grammar,
hasBasicContent: true
};
}
const capabilities = this.analyzeContentCapabilities(module);
return {
id: module.id || contentId,
@ -498,8 +453,8 @@ class ContentScanner {
// Content capabilities analysis
capabilities: capabilities,
compatibility: this.safeCalculateGameCompatibility(capabilities),
icon: this.safeGetContentIcon(module, contentId),
compatibility: this.calculateGameCompatibility(capabilities),
icon: this.getContentIcon(module, contentId),
difficulty: module.difficulty || 'medium',
enabled: true,
@ -523,37 +478,10 @@ class ContentScanner {
},
// Configuration pour les jeux
gameCompatibility: this.safeAnalyzeGameCompatibility(module)
gameCompatibility: this.analyzeGameCompatibility(module)
};
}
safeCalculateGameCompatibility(capabilities) {
try {
return this.calculateGameCompatibility(capabilities);
} catch (error) {
logSh(`⚠️ Erreur dans calculateGameCompatibility: ${error.message}`, 'WARN');
return {}; // Retourner un objet vide en cas d'erreur
}
}
safeGetContentIcon(module, contentId) {
try {
return this.getContentIcon(module, contentId);
} catch (error) {
logSh(`⚠️ Erreur dans getContentIcon: ${error.message}`, 'WARN');
return '📚'; // Icône par défaut
}
}
safeAnalyzeGameCompatibility(module) {
try {
return this.analyzeGameCompatibility(module);
} catch (error) {
logSh(`⚠️ Erreur dans analyzeGameCompatibility: ${error.message}`, 'WARN');
return {}; // Retourner un objet vide en cas d'erreur
}
}
extractContentId(filename) {
return filename.replace('.js', '').toLowerCase();
}
@ -568,11 +496,7 @@ class ContentScanner {
'story-prototype-optimized': 'StoryPrototypeOptimized',
'test-compatibility': 'TestMinimalContent',
'test-minimal': 'TestMinimal',
'test-rich': 'TestRich',
// Ajout des modules NCE
'nce1-lesson63-64': 'NCE1Lesson6364',
'nce2-lesson3': 'NCE2Lesson3',
'nce2-lesson30': 'NCE2Lesson30'
'test-rich': 'TestRich'
};
return mapping[contentId] || this.toPascalCase(contentId);
}
@ -818,11 +742,7 @@ class ContentScanner {
'chinese-long-story': 'ChineseLongStory',
'french-beginner-story': 'FrenchBeginnerStory',
'wta1b1': 'WTA1B1',
'story-prototype-optimized': 'StoryPrototypeOptimized',
// Ajout des modules NCE
'nce1-lesson63-64': 'NCE1Lesson6364',
'nce2-lesson3': 'NCE2Lesson3',
'nce2-lesson30': 'NCE2Lesson30'
'story-prototype-optimized': 'StoryPrototypeOptimized'
};
return specialMappings[filename] || this.toPascalCase(filename);
@ -955,6 +875,7 @@ class ContentScanner {
'memory-match': this.calculateMemoryMatchCompat(capabilities),
'quiz-game': this.calculateQuizGameCompat(capabilities),
'fill-the-blank': this.calculateFillBlankCompat(capabilities),
'text-reader': this.calculateTextReaderCompat(capabilities),
'adventure-reader': this.calculateAdventureCompat(capabilities),
'sentence-builder': this.calculateSentenceBuilderCompat(capabilities),
'pronunciation-game': this.calculatePronunciationCompat(capabilities),
@ -1065,6 +986,14 @@ class ContentScanner {
return { compatible: score >= 30, score, reason: 'Nécessite phrases à trous' };
}
calculateTextReaderCompat(capabilities) {
let score = 0;
if (capabilities.hasSentences) score += 40;
if (capabilities.hasDialogues) score += 50;
if (capabilities.hasAudioFiles) score += 10;
return { compatible: score >= 40, score, reason: 'Nécessite textes à lire' };
}
calculateAdventureCompat(capabilities) {
let score = 0;

View File

@ -318,6 +318,7 @@ const GameLoader = {
'memory-match': 'MemoryMatch',
'quiz-game': 'QuizGame',
'fill-the-blank': 'FillTheBlank',
'text-reader': 'TextReader',
'adventure-reader': 'AdventureReader',
'chinese-study': 'ChineseStudy',
'story-reader': 'StoryReader',
@ -325,8 +326,7 @@ const GameLoader = {
'word-storm': 'WordStorm',
'word-discovery': 'WordDiscovery',
'letter-discovery': 'LetterDiscovery',
'river-run': 'RiverRun',
'wizard-spell-caster': 'WizardSpellCaster'
'river-run': 'RiverRun'
};
return names[gameType] || gameType;
},
@ -346,10 +346,7 @@ const GameLoader = {
'test-rich-content': 'TestRichContent',
'test-sentence-only': 'TestSentenceOnly',
'test-minimal': 'TestMinimal',
'test-rich': 'TestRich',
'nce1-lesson63-64': 'NCE1Lesson6364',
'nce2-lesson3': 'NCE2Lesson3',
'nce2-lesson30': 'NCE2Lesson30'
'test-rich': 'TestRich'
};
return mapping[contentType] || this.toPascalCase(contentType);
},
@ -367,6 +364,7 @@ const GameLoader = {
'memory-match': 'Memory Match',
'quiz-game': 'Quiz Game',
'fill-the-blank': 'Fill the Blank',
'text-reader': 'Text Reader',
'adventure-reader': 'Adventure Reader'
};

View File

@ -6,10 +6,12 @@ const AppNavigation = {
gamesConfig: null,
contentScanner: new ContentScanner(),
scannedContent: null,
compatibilityChecker: null,
init() {
this.loadGamesConfig();
this.initContentScanner();
this.initCompatibilityChecker();
this.setupEventListeners();
this.handleInitialRoute();
},
@ -30,6 +32,14 @@ const AppNavigation = {
}
},
initCompatibilityChecker() {
if (window.ContentGameCompatibility) {
this.compatibilityChecker = new ContentGameCompatibility();
logSh('🎯 Content-Game compatibility checker initialized', 'INFO');
} else {
logSh('⚠️ ContentGameCompatibility not found, compatibility checks disabled', 'WARN');
}
},
getDefaultConfig() {
return {
@ -64,6 +74,12 @@ const AppNavigation = {
icon: '📝',
description: 'Complete sentences by filling in the blanks!'
},
'text-reader': {
enabled: true,
name: 'Text Reader',
icon: '📖',
description: 'Read texts sentence by sentence'
},
'adventure-reader': {
enabled: true,
name: 'Adventure Reader',
@ -88,12 +104,6 @@ const AppNavigation = {
icon: '🔍',
description: 'Learn new words with images and interactive practice!'
},
'wizard-spell-caster': {
enabled: true,
name: 'Wizard Spell Caster',
icon: '🧙‍♂️',
description: 'Cast spells by forming correct sentences! (Advanced - 11+ years)'
},
'letter-discovery': {
enabled: true,
name: 'Letter Discovery',
@ -167,24 +177,6 @@ const AppNavigation = {
name: 'The Magical Library (Optimized)',
icon: '⚡',
description: 'Story with smart vocabulary matching and game compatibility'
},
'nce1-lesson63-64': {
enabled: true,
name: 'NCE1-Lesson63-64',
icon: '👨‍⚕️',
description: 'Medical dialogue and prohibition commands with modal verbs'
},
'nce2-lesson3': {
enabled: true,
name: 'NCE2-Lesson3',
icon: '✉️',
description: 'Please Send Me a Card - Past tense and travel vocabulary'
},
'nce2-lesson30': {
enabled: true,
name: 'NCE2-Lesson30',
icon: '⚽',
description: 'Football or Polo? - Articles and quantifiers'
}
}
};

View File

@ -74,7 +74,7 @@ class AdventureReaderGame {
<li><strong>📖 sentences:</strong> Individual phrases for reading practice</li>
</ul>
<p>Add adventure content to enable this game mode.</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back to Games</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
}

View File

@ -39,7 +39,7 @@ class ChineseStudyGame {
<h3> Error loading</h3>
<p>This content doesn't have Chinese vocabulary for the Chinese Study Game.</p>
<p>The game needs vocabulary with Chinese characters, translations, and optional pinyin.</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
this.addStyles();
@ -214,7 +214,7 @@ class ChineseStudyGame {
</div>
<div class="game-controls">
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back to Games</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back to Games</button>
</div>
</div>
`;
@ -643,7 +643,7 @@ class ChineseStudyGame {
<div class="final-actions">
<button onclick="chineseStudyInstance.restart()" class="restart-btn">🔄 Study Again</button>
<button onclick="chineseStudyInstance.backToMenu()" class="menu-btn">📚 Back to Modes</button>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back to Games</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back to Games</button>
</div>
</div>
</div>

View File

@ -15,7 +15,7 @@ class FillTheBlankGame {
// Game data
this.vocabulary = this.extractVocabulary(this.content);
this.sentences = this.extractRealSentences();
this.sentences = this.generateSentencesFromVocabulary();
this.currentSentence = null;
this.blanks = [];
this.userAnswers = [];
@ -42,7 +42,7 @@ class FillTheBlankGame {
<h3> Loading Error</h3>
<p>This content does not contain vocabulary compatible with Fill the Blank.</p>
<p>The game requires words with their translations in ultra-modular format.</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back to Games</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
}
@ -167,96 +167,66 @@ class FillTheBlankGame {
return vocabulary;
}
extractRealSentences() {
generateSentencesFromVocabulary() {
// Generate sentences based on word types
const nounTemplates = [
{ pattern: 'I see a {word}.', translation: 'Je vois un {translation}.' },
{ pattern: 'The {word} is here.', translation: 'Le {translation} est ici.' },
{ pattern: 'I like the {word}.', translation: 'J\'aime le {translation}.' },
{ pattern: 'Where is the {word}?', translation: 'Où est le {translation}?' },
{ pattern: 'This is a {word}.', translation: 'C\'est un {translation}.' },
{ pattern: 'I have a {word}.', translation: 'J\'ai un {translation}.' }
];
const verbTemplates = [
{ pattern: 'I {word} every day.', translation: 'Je {translation} tous les jours.' },
{ pattern: 'We {word} together.', translation: 'Nous {translation} ensemble.' },
{ pattern: 'They {word} quickly.', translation: 'Ils {translation} rapidement.' },
{ pattern: 'I like to {word}.', translation: 'J\'aime {translation}.' }
];
const adjectiveTemplates = [
{ pattern: 'The cat is {word}.', translation: 'Le chat est {translation}.' },
{ pattern: 'This house is {word}.', translation: 'Cette maison est {translation}.' },
{ pattern: 'I am {word}.', translation: 'Je suis {translation}.' },
{ pattern: 'The weather is {word}.', translation: 'Le temps est {translation}.' }
];
let sentences = [];
logSh('🔍 Extracting real sentences from content...', 'INFO');
// Generate sentences for each vocabulary word based on type
this.vocabulary.forEach(vocab => {
let templates;
// Priority 1: Extract from story chapters
if (this.content.story?.chapters) {
this.content.story.chapters.forEach(chapter => {
if (chapter.sentences) {
chapter.sentences.forEach(sentence => {
if (sentence.original && sentence.translation) {
sentences.push({
original: sentence.original,
translation: sentence.translation,
source: 'story'
});
}
});
}
});
// Choose templates based on word type
if (vocab.type === 'verb') {
templates = verbTemplates;
} else if (vocab.type === 'adjective') {
templates = adjectiveTemplates;
} else {
// Default to noun templates for nouns and unknown types
templates = nounTemplates;
}
// Priority 2: Extract from rawContent story
if (this.content.rawContent?.story?.chapters) {
this.content.rawContent.story.chapters.forEach(chapter => {
if (chapter.sentences) {
chapter.sentences.forEach(sentence => {
if (sentence.original && sentence.translation) {
sentences.push({
original: sentence.original,
translation: sentence.translation,
source: 'rawContent.story'
});
}
});
}
});
}
const template = templates[Math.floor(Math.random() * templates.length)];
const sentence = {
original: template.pattern.replace('{word}', vocab.original),
translation: template.translation.replace('{translation}', vocab.translation),
targetWord: vocab.original,
wordType: vocab.type || 'noun'
};
// Priority 3: Extract from sentences array
const directSentences = this.content.sentences || this.content.rawContent?.sentences;
if (directSentences && Array.isArray(directSentences)) {
directSentences.forEach(sentence => {
if (sentence.english && sentence.chinese) {
sentences.push({
original: sentence.english,
translation: sentence.chinese,
source: 'sentences'
});
} else if (sentence.original && sentence.translation) {
sentences.push({
original: sentence.original,
translation: sentence.translation,
source: 'sentences'
});
// Ensure sentence has at least 3 words for blanks
if (sentence.original.split(' ').length >= 3) {
sentences.push(sentence);
}
});
}
// Filter sentences that are suitable for fill-the-blank (min 3 words)
sentences = sentences.filter(sentence =>
sentence.original &&
sentence.original.split(' ').length >= 3 &&
sentence.original.trim().length > 0
);
// Shuffle and limit
// Shuffle and limit sentences
sentences = this.shuffleArray(sentences);
logSh(`📝 Extracted ${sentences.length} real sentences for fill-the-blank`, 'INFO');
if (sentences.length === 0) {
logSh('❌ No suitable sentences found for fill-the-blank', 'ERROR');
return this.createFallbackSentences();
}
return sentences.slice(0, 20); // Limit to 20 sentences max
}
createFallbackSentences() {
// Simple fallback using vocabulary words in basic sentences
const fallback = [];
this.vocabulary.slice(0, 10).forEach(vocab => {
fallback.push({
original: `This is a ${vocab.original}.`,
translation: `这是一个 ${vocab.translation}`,
source: 'fallback'
});
});
return fallback;
logSh(`✅ Generated ${sentences.length} sentences from vocabulary`, 'INFO');
return sentences;
}
createGameBoard() {
@ -370,47 +340,24 @@ class FillTheBlankGame {
const words = this.currentSentence.original.split(' ');
this.blanks = [];
// Create 1-2 blanks randomly (readable sentences)
const numBlanks = Math.random() < 0.5 ? 1 : 2;
// Create 1-3 blanks depending on sentence length
const numBlanks = Math.min(Math.max(1, Math.floor(words.length / 4)), 3);
const blankIndices = new Set();
// PRIORITY 1: Words from vocabulary (educational value)
const vocabularyWords = [];
const otherWords = [];
// Select random words (not articles/short prepositions)
const candidateWords = words.map((word, index) => ({ word, index }))
.filter(item => item.word.length > 2 && !['the', 'and', 'but', 'for', 'nor', 'or', 'so', 'yet'].includes(item.word.toLowerCase()));
words.forEach((word, index) => {
const cleanWord = word.replace(/[.,!?;:"'()[\]{}\-–—]/g, '').toLowerCase();
const isVocabularyWord = this.vocabulary.some(vocab =>
vocab.original.toLowerCase() === cleanWord
);
if (isVocabularyWord) {
vocabularyWords.push({ word, index, priority: 'vocabulary' });
} else {
otherWords.push({ word, index, priority: 'other', length: cleanWord.length });
}
});
// Select blanks: vocabulary first, then longest words
const selectedWords = [];
// Take vocabulary words first (shuffled)
const shuffledVocab = this.shuffleArray(vocabularyWords);
for (let i = 0; i < Math.min(numBlanks, shuffledVocab.length); i++) {
selectedWords.push(shuffledVocab[i]);
// If not enough candidates, take any words
if (candidateWords.length < numBlanks) {
candidateWords = words.map((word, index) => ({ word, index }));
}
// If need more blanks, take longest other words
if (selectedWords.length < numBlanks) {
const sortedOthers = otherWords.sort((a, b) => b.length - a.length);
const needed = numBlanks - selectedWords.length;
for (let i = 0; i < Math.min(needed, sortedOthers.length); i++) {
selectedWords.push(sortedOthers[i]);
// Randomly select blank indices
const shuffledCandidates = this.shuffleArray(candidateWords);
for (let i = 0; i < Math.min(numBlanks, shuffledCandidates.length); i++) {
blankIndices.add(shuffledCandidates[i].index);
}
}
// Add selected indices to blanks
selectedWords.forEach(item => blankIndices.add(item.index));
// Create blank structure
words.forEach((word, index) => {

View File

@ -199,29 +199,6 @@ class LetterDiscovery {
font-size: 1.1em;
color: #666;
font-style: italic;
margin-bottom: 10px;
}
.word-type {
font-size: 0.9em;
color: #667eea;
background: rgba(102, 126, 234, 0.1);
padding: 4px 12px;
border-radius: 15px;
display: inline-block;
margin-bottom: 15px;
font-weight: 500;
}
.word-example {
font-size: 1em;
color: #555;
font-style: italic;
padding: 10px 15px;
background: rgba(0, 0, 0, 0.05);
border-left: 3px solid #667eea;
border-radius: 0 8px 8px 0;
margin-bottom: 15px;
}
/* Practice Challenge Styles */
@ -392,33 +369,51 @@ class LetterDiscovery {
extractContent() {
logSh('🔍 Letter Discovery - Extracting content...', 'INFO');
// Check for letters in content or rawContent
const letters = this.content.letters || this.content.rawContent?.letters;
if (letters && Object.keys(letters).length > 0) {
this.letters = Object.keys(letters).sort();
this.letterWords = letters;
// Check if content has letter structure
if (this.content.letters) {
this.letters = Object.keys(this.content.letters);
this.letterWords = this.content.letters;
logSh(`📝 Found ${this.letters.length} letters with words`, 'INFO');
} else {
this.showNoLettersMessage();
return;
// Fallback: Create letter structure from vocabulary
this.generateLetterStructure();
}
if (this.letters.length === 0) {
throw new Error('No letters found in content');
}
logSh(`🎯 Letter Discovery ready: ${this.letters.length} letters`, 'INFO');
}
showNoLettersMessage() {
this.container.innerHTML = `
<div class="game-error">
<div class="error-content">
<h2>🔤 Letter Discovery</h2>
<p> No letter structure found in this content.</p>
<p>This game requires content with a predefined letters system.</p>
<p>Try with content that includes letter-based learning material.</p>
<button class="back-btn" onclick="AppNavigation.navigateTo('games')"> Back to Games</button>
</div>
</div>
`;
generateLetterStructure() {
logSh('🔧 Generating letter structure from vocabulary...', 'INFO');
const letterMap = {};
if (this.content.vocabulary) {
Object.keys(this.content.vocabulary).forEach(word => {
const firstLetter = word.charAt(0).toUpperCase();
if (!letterMap[firstLetter]) {
letterMap[firstLetter] = [];
}
const wordData = this.content.vocabulary[word];
letterMap[firstLetter].push({
word: word,
translation: typeof wordData === 'string' ? wordData : wordData.translation || wordData.user_language,
pronunciation: wordData.pronunciation || wordData.prononciation,
type: wordData.type,
image: wordData.image,
audioFile: wordData.audioFile
});
});
}
this.letters = Object.keys(letterMap).sort();
this.letterWords = letterMap;
logSh(`📝 Generated ${this.letters.length} letters from vocabulary`, 'INFO');
}
init() {
@ -565,8 +560,6 @@ class LetterDiscovery {
<div class="word-text">${word.word}</div>
<div class="word-translation">${word.translation}</div>
${word.pronunciation ? `<div class="word-pronunciation">[${word.pronunciation}]</div>` : ''}
${word.type ? `<div class="word-type">${word.type}</div>` : ''}
${word.example ? `<div class="word-example">"${word.example}"</div>` : ''}
<div class="letter-controls">
<button class="discovery-btn" onclick="window.currentLetterGame.nextWord()">
Next Word

View File

@ -41,7 +41,7 @@ class MemoryMatchGame {
<h3> Error loading</h3>
<p>This content doesn't have enough vocabulary for Memory Match.</p>
<p>The game needs at least ${this.totalPairs} vocabulary pairs.</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
}

View File

@ -15,18 +15,16 @@ class QuizGame {
this.correctAnswers = 0;
this.currentQuestionData = null;
this.hasAnswered = false;
this.quizDirection = 'original_to_translation'; // 'original_to_translation' or 'translation_to_original'
// Extract vocabulary and additional words from texts/stories
// Extract vocabulary
this.vocabulary = this.extractVocabulary(this.content);
this.allWords = this.extractAllWords(this.content);
this.init();
}
init() {
// Check if we have enough vocabulary
if (!this.vocabulary || this.vocabulary.length < 6) {
if (!this.vocabulary || this.vocabulary.length < 4) {
logSh('Not enough vocabulary for Quiz Game', 'ERROR');
this.showInitError();
return;
@ -44,8 +42,8 @@ class QuizGame {
<div class="game-error">
<h3> Error loading</h3>
<p>This content doesn't have enough vocabulary for Quiz Game.</p>
<p>The game needs at least 6 vocabulary items.</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back to Games</button>
<p>The game needs at least 4 vocabulary items.</p>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
}
@ -173,76 +171,6 @@ class QuizGame {
return vocabulary;
}
extractAllWords(content) {
let allWords = [];
// Add vocabulary words first
allWords = [...this.vocabulary];
// Extract from stories/texts
if (content.rawContent?.story?.chapters) {
content.rawContent.story.chapters.forEach(chapter => {
if (chapter.sentences) {
chapter.sentences.forEach(sentence => {
if (sentence.words && Array.isArray(sentence.words)) {
sentence.words.forEach(wordObj => {
if (wordObj.word && wordObj.translation) {
allWords.push({
original: wordObj.word,
translation: wordObj.translation,
type: wordObj.type || 'word',
pronunciation: wordObj.pronunciation
});
}
});
}
});
}
});
}
// Extract from additional stories (like WTA1B1)
if (content.rawContent?.additionalStories) {
content.rawContent.additionalStories.forEach(story => {
if (story.chapters) {
story.chapters.forEach(chapter => {
if (chapter.sentences) {
chapter.sentences.forEach(sentence => {
if (sentence.words && Array.isArray(sentence.words)) {
sentence.words.forEach(wordObj => {
if (wordObj.word && wordObj.translation) {
allWords.push({
original: wordObj.word,
translation: wordObj.translation,
type: wordObj.type || 'word',
pronunciation: wordObj.pronunciation
});
}
});
}
});
}
});
}
});
}
// Remove duplicates based on original word
const uniqueWords = [];
const seenWords = new Set();
allWords.forEach(word => {
const key = word.original.toLowerCase();
if (!seenWords.has(key)) {
seenWords.add(key);
uniqueWords.push(word);
}
});
logSh(`📚 Extracted ${uniqueWords.length} total words for quiz options`, 'INFO');
return uniqueWords;
}
shuffleArray(array) {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
@ -344,37 +272,20 @@ class QuizGame {
// Get current vocabulary item
const correctAnswer = this.vocabulary[this.currentQuestion];
// Randomly choose quiz direction
this.quizDirection = Math.random() < 0.5 ? 'original_to_translation' : 'translation_to_original';
let questionText, correctAnswerText, sourceForWrongAnswers;
if (this.quizDirection === 'original_to_translation') {
questionText = correctAnswer.original;
correctAnswerText = correctAnswer.translation;
sourceForWrongAnswers = 'translation';
} else {
questionText = correctAnswer.translation;
correctAnswerText = correctAnswer.original;
sourceForWrongAnswers = 'original';
}
// Generate 5 wrong answers from allWords (which includes story words)
const availableWords = this.allWords.length >= 6 ? this.allWords : this.vocabulary;
const wrongAnswers = availableWords
// Generate 3 wrong answers from other vocabulary items
const wrongAnswers = this.vocabulary
.filter(item => item !== correctAnswer)
.sort(() => Math.random() - 0.5)
.slice(0, 5)
.map(item => sourceForWrongAnswers === 'translation' ? item.translation : item.original);
.slice(0, 3)
.map(item => item.translation);
// Combine and shuffle all options (1 correct + 5 wrong = 6 total)
const allOptions = [correctAnswerText, ...wrongAnswers].sort(() => Math.random() - 0.5);
// Combine and shuffle all options
const allOptions = [correctAnswer.translation, ...wrongAnswers].sort(() => Math.random() - 0.5);
this.currentQuestionData = {
question: questionText,
correctAnswer: correctAnswerText,
options: allOptions,
direction: this.quizDirection
question: correctAnswer.original,
correctAnswer: correctAnswer.translation,
options: allOptions
};
this.renderQuestion();
@ -384,13 +295,9 @@ class QuizGame {
renderQuestion() {
const { question, options } = this.currentQuestionData;
// Update question text with direction indicator
const direction = this.currentQuestionData.direction;
const directionText = direction === 'original_to_translation' ?
'What is the translation of' : 'What is the original word for';
// Update question text
document.getElementById('question-text').innerHTML = `
${directionText} "<strong>${question}</strong>"?
What is the translation of "<strong>${question}</strong>"?
`;
// Clear and generate options

View File

@ -50,7 +50,7 @@ class StoryBuilderGame {
<h3> Error loading</h3>
<p>This content doesn't have enough vocabulary for Story Builder.</p>
<p>The game needs at least 6 vocabulary words with types (noun, verb, adjective, etc.).</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
}

View File

@ -109,34 +109,6 @@ class StoryReader {
});
}
// NEW: Check for simple texts and convert them to stories
const texts = this.content.rawContent?.texts || this.content.texts;
if (texts && Array.isArray(texts)) {
texts.forEach((text, index) => {
if (text && (text.title || text.original_language)) {
const convertedStory = this.convertTextToStory(text, index);
this.availableStories.push({
id: `text_${index}`,
title: text.title || `Text ${index + 1}`,
data: convertedStory,
source: 'text'
});
}
});
}
// NEW: Check for sentences and create a story from them
const sentences = this.content.rawContent?.sentences || this.content.sentences;
if (sentences && Array.isArray(sentences) && sentences.length > 0 && this.availableStories.length === 0) {
const sentencesStory = this.convertSentencesToStory(sentences);
this.availableStories.push({
id: 'sentences',
title: 'Reading Practice',
data: sentencesStory,
source: 'sentences'
});
}
logSh(`📚 Discovered ${this.availableStories.length} stories:`, this.availableStories.map(s => s.title), 'INFO');
}
@ -169,7 +141,7 @@ class StoryReader {
<div class="story-error">
<h3> Error</h3>
<p>${message}</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
}
@ -844,10 +816,6 @@ class StoryReader {
// Check if sentence has word-by-word data (old format) or needs automatic matching
let wordsHtml;
console.log('🔍 DEBUG: sentence data:', data);
console.log('🔍 DEBUG: data.words exists?', !!data.words);
console.log('🔍 DEBUG: data.words length:', data.words ? data.words.length : 'N/A');
if (data.words && data.words.length > 0) {
// Old format with word-by-word data
wordsHtml = data.words.map(wordData => {
@ -1050,121 +1018,6 @@ class StoryReader {
}
}
// NEW: Convert simple text to story format
convertTextToStory(text, index) {
// Split text into sentences for easier reading
const sentences = this.splitTextIntoSentences(text.original_language, text.user_language);
return {
title: text.title || `Text ${index + 1}`,
totalSentences: sentences.length,
chapters: [{
title: "Reading Text",
sentences: sentences
}]
};
}
// NEW: Convert array of sentences to story format
convertSentencesToStory(sentences) {
const storyTitle = this.content.name || "Reading Practice";
const convertedSentences = sentences.map((sentence, index) => ({
id: index + 1,
original: sentence.original_language || sentence.english || sentence.original || '',
translation: sentence.user_language || sentence.chinese || sentence.french || sentence.translation || '',
words: this.breakSentenceIntoWords(
sentence.original_language || sentence.english || sentence.original || '',
sentence.user_language || sentence.chinese || sentence.french || sentence.translation || ''
)
}));
return {
title: storyTitle,
totalSentences: convertedSentences.length,
chapters: [{
title: "Reading Sentences",
sentences: convertedSentences
}]
};
}
// NEW: Split long text into manageable sentences
splitTextIntoSentences(originalText, translationText) {
// Split by sentence endings
const originalSentences = originalText.split(/[.!?]+/).filter(s => s.trim().length > 0);
const translationSentences = translationText.split(/[.!?]+/).filter(s => s.trim().length > 0);
const sentences = [];
const maxSentences = Math.max(originalSentences.length, translationSentences.length);
for (let i = 0; i < maxSentences; i++) {
const original = (originalSentences[i] || '').trim();
const translation = (translationSentences[i] || '').trim();
if (original || translation) {
sentences.push({
id: i + 1,
original: original + (original && !original.match(/[.!?]$/) ? '.' : ''),
translation: translation + (translation && !translation.match(/[.!?]$/) ? '.' : ''),
words: this.breakSentenceIntoWords(original, translation)
});
}
}
return sentences;
}
// NEW: Break sentence into word-by-word format for Story Reader
breakSentenceIntoWords(original, translation) {
if (!original) return [];
// First, separate punctuation from words while preserving spaces
const preprocessed = original.replace(/([.,!?;:"'()[\]{}\-–—])/g, ' $1 ');
const words = preprocessed.split(/\s+/).filter(word => word.trim().length > 0);
// Do the same for translation
const translationPreprocessed = translation ? translation.replace(/([.,!?;:"'()[\]{}\-–—])/g, ' $1 ') : '';
const translationWords = translationPreprocessed ? translationPreprocessed.split(/\s+/).filter(word => word.trim().length > 0) : [];
return words.map((word, index) => {
// Clean punctuation for word lookup, but preserve punctuation in display
const cleanWord = word.replace(/[.,!?;:"'()[\]{}\-–—]/g, '').toLowerCase();
// Try to find in vocabulary
let wordTranslation = translationWords[index] || '';
let wordType = 'word';
let pronunciation = '';
// Special handling for letter pairs (like "Aa", "Bb", etc.)
if (/^[A-Za-z]{1,2}$/.test(cleanWord)) {
wordType = 'letter';
wordTranslation = word; // Keep the letter as is
}
// Special handling for punctuation marks
if (/^[.,!?;:"'()[\]{}]$/.test(word)) {
wordType = 'punctuation';
wordTranslation = word; // Keep punctuation as is
}
// Look up in content vocabulary if available
if (this.vocabulary && this.vocabulary[cleanWord]) {
const vocabEntry = this.vocabulary[cleanWord];
wordTranslation = vocabEntry.user_language || vocabEntry.translation || wordTranslation;
wordType = vocabEntry.type || wordType;
pronunciation = vocabEntry.pronunciation || '';
}
return {
word: word,
translation: wordTranslation,
type: wordType,
pronunciation: pronunciation
};
});
}
// TTS Methods
playSentenceTTS() {
const sentenceData = this.getCurrentSentenceData();
@ -1310,7 +1163,7 @@ class StoryReader {
<p><strong>Words read:</strong> ${this.wordsRead}</p>
<p><strong>Total sentences:</strong> ${this.totalSentences}</p>
<button onclick="this.restart()" class="nav-btn primary">Read Again</button>
<button onclick="AppNavigation.navigateTo('games')" class="nav-btn">Back to Menu</button>
<button onclick="AppNavigation.goBack()" class="nav-btn">Back to Menu</button>
</div>
`;

535
js/games/text-reader.js Normal file
View File

@ -0,0 +1,535 @@
// === MODULE TEXT READER ===
class TextReaderGame {
constructor(options) {
this.container = options.container;
this.content = options.content;
this.onScoreUpdate = options.onScoreUpdate || (() => {});
this.onGameEnd = options.onGameEnd || (() => {});
// Reader state
this.currentTextIndex = 0;
this.currentSentenceIndex = 0;
this.isRunning = false;
// Reading data
this.texts = this.extractTexts(this.content);
this.sentencesFromContent = this.extractSentences(this.content);
this.vocabulary = this.extractVocabulary(this.content);
this.currentText = null;
this.sentences = [];
this.showingFullText = false;
this.init();
}
init() {
// Check that we have texts, sentences, or vocabulary
if ((!this.texts || this.texts.length === 0) &&
(!this.sentencesFromContent || this.sentencesFromContent.length === 0) &&
(!this.vocabulary || this.vocabulary.length === 0)) {
logSh('No texts, sentences, or vocabulary available for Text Reader', 'ERROR');
this.showInitError();
return;
}
this.createReaderInterface();
this.setupEventListeners();
this.loadText();
}
showInitError() {
this.container.innerHTML = `
<div class="game-error">
<h3> Error loading</h3>
<p>This content doesn't contain texts, sentences, or vocabulary compatible with Text Reader.</p>
<p>The reader needs content with ultra-modular format (original_language/user_language).</p>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
}
extractTexts(content) {
let texts = [];
logSh('📖 Extracting texts from:', content?.name || 'content', 'INFO');
// Priority 1: Use raw module content (simple format)
if (content.rawContent) {
logSh('📦 Using raw module content', 'INFO');
return this.extractTextsFromRaw(content.rawContent);
}
// Priority 2: Ultra-modular format (texts array) - ONLY format supported
if (content.texts && Array.isArray(content.texts)) {
logSh('✨ Ultra-modular format detected (texts array)', 'INFO');
texts = content.texts
.filter(text => text && text.original_language && text.user_language)
.map(text => ({
id: text.id || `text_${Date.now()}_${Math.random()}`,
title: text.title || text.id || 'Text',
original: text.original_language,
translation: text.user_language
}));
logSh(`${texts.length} texts extracted from ultra-modular format`, 'INFO');
}
return this.finalizeTexts(texts);
}
extractTextsFromRaw(rawContent) {
logSh('🔧 Extracting from raw content:', rawContent.name || 'Module', 'INFO');
let texts = [];
// Ultra-modular format (texts array) - ONLY format supported
if (rawContent.texts && Array.isArray(rawContent.texts)) {
texts = rawContent.texts
.filter(text => text && text.original_language && text.user_language)
.map(text => ({
id: text.id || `text_${Date.now()}_${Math.random()}`,
title: text.title || text.id || 'Text',
original: text.original_language,
translation: text.user_language
}));
logSh(`${texts.length} texts extracted from ultra-modular format`, 'INFO');
}
return this.finalizeTexts(texts);
}
finalizeTexts(texts) {
// Validation and cleanup
texts = texts.filter(text =>
text &&
typeof text.original === 'string' &&
text.original.trim() !== '' &&
typeof text.translation === 'string' &&
text.translation.trim() !== ''
);
if (texts.length === 0) {
logSh('❌ No valid texts found', 'ERROR');
// Demo texts as fallback
texts = [
{
id: "demo_text_1",
title: "Demo Text",
original: "This is a demo text. It has multiple sentences. Each sentence will be displayed one by one. You can navigate using the buttons below.",
translation: "Ceci est un texte de démonstration. Il contient plusieurs phrases. Chaque phrase sera affichée une par une. Vous pouvez naviguer avec les boutons ci-dessous."
}
];
logSh('🚨 Using demo texts', 'WARN');
}
logSh(`✅ Text Reader: ${texts.length} texts finalized`, 'INFO');
return texts;
}
extractSentences(content) {
let sentences = [];
logSh('📝 Extracting sentences from:', content?.name || 'content', 'INFO');
// Priority 1: Use raw module content
if (content.rawContent) {
logSh('📦 Using raw module content for sentences', 'INFO');
return this.extractSentencesFromRaw(content.rawContent);
}
// Priority 2: Ultra-modular format (sentences array) - ONLY format supported
if (content.sentences && Array.isArray(content.sentences)) {
logSh('✨ Ultra-modular format detected (sentences array)', 'INFO');
sentences = content.sentences
.filter(sentence => sentence && sentence.original_language && sentence.user_language)
.map(sentence => ({
id: sentence.id || `sentence_${Date.now()}_${Math.random()}`,
original: sentence.original_language,
translation: sentence.user_language
}));
logSh(`${sentences.length} sentences extracted from ultra-modular format`, 'INFO');
}
return this.finalizeSentences(sentences);
}
extractSentencesFromRaw(rawContent) {
logSh('🔧 Extracting sentences from raw content:', rawContent.name || 'Module', 'INFO');
let sentences = [];
// Ultra-modular format (sentences array) - ONLY format supported
if (rawContent.sentences && Array.isArray(rawContent.sentences)) {
sentences = rawContent.sentences
.filter(sentence => sentence && sentence.original_language && sentence.user_language)
.map(sentence => ({
id: sentence.id || `sentence_${Date.now()}_${Math.random()}`,
original: sentence.original_language,
translation: sentence.user_language
}));
logSh(`${sentences.length} sentences extracted from ultra-modular format`, 'INFO');
}
return this.finalizeSentences(sentences);
}
finalizeSentences(sentences) {
// Validation and cleanup
sentences = sentences.filter(sentence =>
sentence &&
typeof sentence.original === 'string' &&
sentence.original.trim() !== '' &&
typeof sentence.translation === 'string' &&
sentence.translation.trim() !== ''
);
logSh(`✅ Text Reader: ${sentences.length} sentences finalized`, 'INFO');
return sentences;
}
extractVocabulary(content) {
let vocabulary = [];
logSh('🔍 Extracting vocabulary from:', content?.name || 'content', 'INFO');
// Priority 1: Use raw module content (simple format)
if (content.rawContent) {
logSh('📦 Using raw module content for vocabulary', 'INFO');
return this.extractVocabularyFromRaw(content.rawContent);
}
// Priority 2: Ultra-modular format (vocabulary object) - ONLY format supported
if (content.vocabulary && typeof content.vocabulary === 'object' && !Array.isArray(content.vocabulary)) {
logSh('✨ Ultra-modular format detected (vocabulary object)', 'INFO');
vocabulary = Object.entries(content.vocabulary).map(([word, data]) => {
// Support ultra-modular format ONLY
if (typeof data === 'object' && data.user_language) {
return {
original: word, // Clé = original_language
translation: data.user_language,
type: data.type || 'unknown'
};
}
return null;
}).filter(item => item !== null);
logSh(`${vocabulary.length} vocabulary words extracted from ultra-modular format`, 'INFO');
}
return this.finalizeVocabulary(vocabulary);
}
extractVocabularyFromRaw(rawContent) {
logSh('🔧 Extracting vocabulary from raw content:', rawContent.name || 'Module', 'INFO');
let vocabulary = [];
// Ultra-modular format (vocabulary object) - ONLY format supported
if (rawContent.vocabulary && typeof rawContent.vocabulary === 'object' && !Array.isArray(rawContent.vocabulary)) {
vocabulary = Object.entries(rawContent.vocabulary).map(([word, data]) => {
// Support ultra-modular format ONLY
if (typeof data === 'object' && data.user_language) {
return {
original: word, // Clé = original_language
translation: data.user_language,
type: data.type || 'unknown'
};
}
return null;
}).filter(item => item !== null);
logSh(`${vocabulary.length} vocabulary words extracted from ultra-modular format`, 'INFO');
}
return this.finalizeVocabulary(vocabulary);
}
finalizeVocabulary(vocabulary) {
// Validation and cleanup
vocabulary = vocabulary.filter(word =>
word &&
typeof word.original === 'string' &&
word.original.trim() !== '' &&
typeof word.translation === 'string' &&
word.translation.trim() !== ''
);
logSh(`✅ Text Reader: ${vocabulary.length} vocabulary words finalized`, 'INFO');
return vocabulary;
}
createReaderInterface() {
this.container.innerHTML = `
<div class="text-reader-wrapper">
<!-- Text Selection -->
<div class="text-selection">
<label for="text-selector" class="text-selector-label">Choose a text:</label>
<select id="text-selector" class="text-selector">
<!-- Options will be generated here -->
</select>
<div class="text-progress">
<span id="sentence-counter">1 / 1</span>
</div>
</div>
<!-- Reading Area -->
<div class="reading-area" id="reading-area">
<div class="sentence-display" id="sentence-display">
<!-- Current sentence will appear here -->
</div>
<div class="full-text-display" id="full-text-display" style="display: none;">
<!-- Full text will appear here -->
</div>
</div>
<!-- Navigation Controls -->
<div class="reader-controls">
<button class="control-btn secondary" id="prev-sentence-btn" disabled> Previous</button>
<button class="control-btn primary" id="next-sentence-btn">Next </button>
<button class="control-btn secondary" id="show-full-btn">📄 Full Text</button>
</div>
<!-- Full Text Navigation -->
<div class="full-text-navigation" id="full-text-navigation" style="display: none;">
<button class="control-btn secondary" id="back-to-reading-btn">📖 Back to Reading</button>
</div>
<!-- Feedback Area -->
<div class="feedback-area" id="feedback-area">
<div class="instruction">
Use Next/Previous buttons to navigate through sentences
</div>
</div>
</div>
`;
}
setupEventListeners() {
document.getElementById('next-sentence-btn').addEventListener('click', () => this.nextSentence());
document.getElementById('prev-sentence-btn').addEventListener('click', () => this.prevSentence());
document.getElementById('show-full-btn').addEventListener('click', () => this.showFullText());
document.getElementById('back-to-reading-btn').addEventListener('click', () => this.backToReading());
document.getElementById('text-selector').addEventListener('change', (e) => this.selectText(parseInt(e.target.value)));
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (!this.isRunning) return;
if (this.showingFullText) {
if (e.key === 'Escape') this.backToReading();
} else {
if (e.key === 'ArrowLeft') this.prevSentence();
else if (e.key === 'ArrowRight') this.nextSentence();
else if (e.key === 'Enter' || e.key === ' ') this.showFullText();
}
});
}
start() {
logSh('📖 Text Reader: Starting', 'INFO');
this.isRunning = true;
}
restart() {
logSh('🔄 Text Reader: Restarting', 'INFO');
this.reset();
this.start();
}
reset() {
this.currentTextIndex = 0;
this.currentSentenceIndex = 0;
this.isRunning = false;
this.showingFullText = false;
this.loadText();
}
loadText() {
// Use texts if available, otherwise use individual sentences, then vocabulary
if (this.texts && this.texts.length > 0) {
if (this.currentTextIndex >= this.texts.length) {
this.currentTextIndex = 0;
}
this.currentText = this.texts[this.currentTextIndex];
this.sentences = this.splitIntoSentences(this.currentText.original);
this.currentSentenceIndex = 0;
this.showingFullText = false;
this.populateTextSelector();
} else if (this.sentencesFromContent && this.sentencesFromContent.length > 0) {
// Use individual sentences as "mini-texts"
this.texts = this.sentencesFromContent.map((sentence, index) => ({
id: sentence.id || `sentence_text_${index}`,
title: `Sentence ${index + 1}`,
original: sentence.original,
translation: sentence.translation
}));
this.currentText = this.texts[0];
this.sentences = [this.currentText.original]; // Single sentence
this.currentSentenceIndex = 0;
this.showingFullText = false;
this.populateTextSelector();
} else if (this.vocabulary && this.vocabulary.length > 0) {
// Use vocabulary words as "mini-texts"
this.texts = this.vocabulary.map((word, index) => ({
id: `vocab_text_${index}`,
title: `Word: ${word.original}`,
original: word.original,
translation: word.translation
}));
this.currentText = this.texts[0];
this.sentences = [this.currentText.original]; // Single word
this.currentSentenceIndex = 0;
this.showingFullText = false;
this.populateTextSelector();
}
this.updateDisplay();
this.updateUI();
}
populateTextSelector() {
const selector = document.getElementById('text-selector');
selector.innerHTML = '';
this.texts.forEach((text, index) => {
const option = document.createElement('option');
option.value = index;
option.textContent = text.title || `Text ${index + 1}`;
if (index === this.currentTextIndex) {
option.selected = true;
}
selector.appendChild(option);
});
}
selectText(textIndex) {
if (textIndex >= 0 && textIndex < this.texts.length) {
this.currentTextIndex = textIndex;
this.currentText = this.texts[this.currentTextIndex];
this.sentences = this.splitIntoSentences(this.currentText.original);
this.currentSentenceIndex = 0;
// Always go back to sentence reading when changing text
if (this.showingFullText) {
this.backToReading();
} else {
this.updateDisplay();
this.updateUI();
}
this.showFeedback(`Switched to: ${this.currentText.title}`, 'info');
}
}
splitIntoSentences(text) {
// Split by periods, exclamation marks, and question marks
// Keep the punctuation with the sentence
const sentences = text.split(/(?<=[.!?])\s+/)
.filter(sentence => sentence.trim() !== '')
.map(sentence => sentence.trim());
return sentences.length > 0 ? sentences : [text];
}
nextSentence() {
if (this.currentSentenceIndex < this.sentences.length - 1) {
this.currentSentenceIndex++;
this.updateDisplay();
this.updateUI();
} else {
// End of sentences, show full text automatically
this.showFullText();
}
}
prevSentence() {
if (this.currentSentenceIndex > 0) {
this.currentSentenceIndex--;
this.updateDisplay();
this.updateUI();
}
}
showFullText() {
this.showingFullText = true;
document.getElementById('sentence-display').style.display = 'none';
document.getElementById('full-text-display').style.display = 'block';
document.getElementById('full-text-display').innerHTML = `
<div class="full-text-content">
<div class="text-original">
<h4>Original:</h4>
<p>${this.currentText.original}</p>
</div>
<div class="text-translation">
<h4>Translation:</h4>
<p>${this.currentText.translation}</p>
</div>
</div>
`;
// Show full text navigation controls
document.querySelector('.reader-controls').style.display = 'none';
document.getElementById('full-text-navigation').style.display = 'flex';
this.showFeedback('Full text displayed. Use dropdown to change text.', 'info');
}
backToReading() {
this.showingFullText = false;
document.getElementById('sentence-display').style.display = 'block';
document.getElementById('full-text-display').style.display = 'none';
// Show sentence navigation controls
document.querySelector('.reader-controls').style.display = 'flex';
document.getElementById('full-text-navigation').style.display = 'none';
this.updateDisplay();
this.updateUI();
this.showFeedback('Back to sentence-by-sentence reading.', 'info');
}
// Text navigation methods removed - using dropdown instead
updateDisplay() {
if (this.showingFullText) return;
const sentenceDisplay = document.getElementById('sentence-display');
const currentSentence = this.sentences[this.currentSentenceIndex];
sentenceDisplay.innerHTML = `
<div class="current-sentence">
${currentSentence}
</div>
`;
}
updateUI() {
// Update counters
document.getElementById('sentence-counter').textContent = `${this.currentSentenceIndex + 1} / ${this.sentences.length}`;
// Update button states
document.getElementById('prev-sentence-btn').disabled = this.currentSentenceIndex === 0;
document.getElementById('next-sentence-btn').disabled = false;
document.getElementById('next-sentence-btn').textContent =
this.currentSentenceIndex === this.sentences.length - 1 ? 'Full Text →' : 'Next →';
}
// updateTextNavigation method removed - using dropdown instead
showFeedback(message, type = 'info') {
const feedbackArea = document.getElementById('feedback-area');
feedbackArea.innerHTML = `<div class="instruction ${type}">${message}</div>`;
}
destroy() {
this.isRunning = false;
this.container.innerHTML = '';
}
}
// Module registration
window.GameModules = window.GameModules || {};
window.GameModules.TextReader = TextReaderGame;

View File

@ -10,7 +10,7 @@ class WhackAMoleHardGame {
// Game state
this.score = 0;
this.errors = 0;
this.maxErrors = 3;
this.maxErrors = 5;
this.gameTime = 60; // 60 seconds
this.timeLeft = this.gameTime;
this.isRunning = false;
@ -59,7 +59,7 @@ class WhackAMoleHardGame {
<h3> Loading Error</h3>
<p>This content does not contain vocabulary compatible with Whack-a-Mole.</p>
<p>The game requires words with their translations.</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
}

View File

@ -10,7 +10,7 @@ class WhackAMoleGame {
// Game state
this.score = 0;
this.errors = 0;
this.maxErrors = 3;
this.maxErrors = 5;
this.gameTime = 60; // 60 secondes
this.timeLeft = this.gameTime;
this.isRunning = false;
@ -58,7 +58,7 @@ class WhackAMoleGame {
<h3> Loading Error</h3>
<p>This content does not contain vocabulary compatible with Whack-a-Mole.</p>
<p>The game requires words with their translations.</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn"> Back</button>
<button onclick="AppNavigation.goBack()" class="back-btn"> Back</button>
</div>
`;
}

View File

@ -76,15 +76,7 @@ class WordStormGame {
}
.falling-word.exploding {
animation: explode 0.8s ease-out forwards;
}
.falling-word.wrong-shake {
animation: wrongShake 0.6s ease-in-out forwards;
}
.answer-panel.wrong-flash {
animation: wrongFlash 0.5s ease-in-out;
animation: explode 0.6s ease-out forwards;
}
@keyframes wordGlow {
@ -93,89 +85,9 @@ class WordStormGame {
}
@keyframes explode {
0% {
transform: translateX(-50%) scale(1) rotate(0deg);
opacity: 1;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3), 0 0 20px rgba(102, 126, 234, 0.4);
}
25% {
transform: translateX(-50%) scale(1.3) rotate(5deg);
opacity: 0.9;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.5), 0 0 40px rgba(16, 185, 129, 0.8);
}
50% {
transform: translateX(-50%) scale(1.5) rotate(-3deg);
opacity: 0.7;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
box-shadow: 0 12px 35px rgba(245, 158, 11, 0.6), 0 0 60px rgba(245, 158, 11, 0.9);
}
75% {
transform: translateX(-50%) scale(0.8) rotate(2deg);
opacity: 0.4;
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
}
100% {
transform: translateX(-50%) scale(0.1) rotate(0deg);
opacity: 0;
}
}
@keyframes wrongShake {
0%, 100% {
transform: translateX(-50%) scale(1);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-60%) scale(0.95);
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.6), 0 0 25px rgba(239, 68, 68, 0.8);
}
20%, 40%, 60%, 80% {
transform: translateX(-40%) scale(0.95);
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.6), 0 0 25px rgba(239, 68, 68, 0.8);
}
}
@keyframes wrongFlash {
0%, 100% {
background: transparent;
box-shadow: none;
}
50% {
background: rgba(239, 68, 68, 0.4);
box-shadow: 0 0 20px rgba(239, 68, 68, 0.6), inset 0 0 20px rgba(239, 68, 68, 0.3);
}
}
@keyframes screenShake {
0%, 100% { transform: translateX(0); }
10% { transform: translateX(-3px) translateY(1px); }
20% { transform: translateX(3px) translateY(-1px); }
30% { transform: translateX(-2px) translateY(2px); }
40% { transform: translateX(2px) translateY(-2px); }
50% { transform: translateX(-1px) translateY(1px); }
60% { transform: translateX(1px) translateY(-1px); }
70% { transform: translateX(-2px) translateY(0px); }
80% { transform: translateX(2px) translateY(1px); }
90% { transform: translateX(-1px) translateY(-1px); }
}
@keyframes pointsFloat {
0% {
transform: translateY(0) scale(1);
opacity: 1;
}
30% {
transform: translateY(-20px) scale(1.3);
opacity: 1;
}
100% {
transform: translateY(-80px) scale(0.5);
opacity: 0;
}
0% { transform: translateX(-50%) scale(1); opacity: 1; }
50% { transform: translateX(-50%) scale(1.2); opacity: 0.8; }
100% { transform: translateX(-50%) scale(0.3); opacity: 0; }
}
@media (max-width: 768px) {
@ -411,26 +323,14 @@ class WordStormGame {
}
correctAnswer(fallingWord) {
// Remove from game with epic explosion
// Remove from game
if (fallingWord.element.parentNode) {
fallingWord.element.classList.add('exploding');
// Add screen shake effect
const gameArea = document.getElementById('game-area');
if (gameArea) {
gameArea.style.animation = 'none';
gameArea.offsetHeight; // Force reflow
gameArea.style.animation = 'screenShake 0.3s ease-in-out';
setTimeout(() => {
gameArea.style.animation = '';
}, 300);
}
setTimeout(() => {
if (fallingWord.element.parentNode) {
fallingWord.element.remove();
}
}, 800);
}, 600);
}
// Remove from tracking
@ -442,18 +342,10 @@ class WordStormGame {
this.score += points;
this.onScoreUpdate(this.score);
// Update display with animation
// Update display
document.getElementById('score').textContent = this.score;
document.getElementById('combo').textContent = this.combo;
// Add points popup animation
this.showPointsPopup(points, fallingWord.element);
// Vibration feedback (if supported)
if (navigator.vibrate) {
navigator.vibrate([50, 30, 50]);
}
// Level up check
if (this.score > 0 && this.score % 100 === 0) {
this.levelUp();
@ -464,74 +356,13 @@ class WordStormGame {
this.combo = 0;
document.getElementById('combo').textContent = this.combo;
// Enhanced wrong answer animation
// Flash effect
const answerPanel = document.getElementById('answer-panel');
if (answerPanel) {
answerPanel.classList.add('wrong-flash');
answerPanel.style.background = 'rgba(239, 68, 68, 0.3)';
setTimeout(() => {
answerPanel.classList.remove('wrong-flash');
}, 500);
}
// Shake all falling words to show disappointment
this.fallingWords.forEach(fw => {
if (fw.element.parentNode && !fw.element.classList.contains('exploding')) {
fw.element.classList.add('wrong-shake');
setTimeout(() => {
fw.element.classList.remove('wrong-shake');
}, 600);
}
});
// Screen flash red
const gameArea = document.getElementById('game-area');
if (gameArea) {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(239, 68, 68, 0.3);
pointer-events: none;
animation: wrongFlash 0.4s ease-in-out;
z-index: 100;
`;
gameArea.appendChild(overlay);
setTimeout(() => {
if (overlay.parentNode) overlay.remove();
}, 400);
}
// Wrong answer vibration (stronger/longer)
if (navigator.vibrate) {
navigator.vibrate([200, 100, 200, 100, 200]);
}
}
showPointsPopup(points, wordElement) {
const popup = document.createElement('div');
popup.textContent = `+${points}`;
popup.style.cssText = `
position: absolute;
left: ${wordElement.style.left};
top: ${wordElement.offsetTop}px;
font-size: 2rem;
font-weight: bold;
color: #10b981;
pointer-events: none;
z-index: 1000;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
animation: pointsFloat 1.5s ease-out forwards;
`;
const gameArea = document.getElementById('game-area');
if (gameArea) {
gameArea.appendChild(popup);
setTimeout(() => {
if (popup.parentNode) popup.remove();
}, 1500);
answerPanel.style.background = '';
}, 300);
}
}
@ -623,7 +454,7 @@ class WordStormGame {
<h2>🌪 Word Storm</h2>
<p> No vocabulary found in this content.</p>
<p>This game requires content with vocabulary words.</p>
<button class="back-btn" onclick="AppNavigation.navigateTo('games')"> Back to Games</button>
<button class="back-btn" onclick="window.history.back()"> Back to Games</button>
</div>
</div>
`;

31
package-lock.json generated
View File

@ -1,31 +0,0 @@
{
"name": "class-generator",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "class-generator",
"version": "2.0.0",
"license": "MIT",
"dependencies": {
"dotenv": "^17.2.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/dotenv": {
"version": "17.2.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz",
"integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
}
}
}

View File

@ -1,27 +0,0 @@
{
"name": "class-generator",
"version": "2.0.0",
"description": "Educational games platform with modular architecture",
"type": "module",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node server.js",
"serve": "node server.js"
},
"keywords": [
"education",
"games",
"learning",
"vanilla-js",
"modular"
],
"author": "Alexis Trouvé",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
},
"dependencies": {
"dotenv": "^17.2.2"
}
}

View File

@ -1,16 +0,0 @@
@echo off
echo 🔄 Restarting Class Generator with clean shutdown...
REM Kill all Node.js processes
echo 🛑 Stopping all Node.js processes...
taskkill /F /IM node.exe /T 2>nul
REM Wait a moment
timeout /t 2 /nobreak >nul
REM Start the server
echo 🚀 Starting fresh server...
cd /d "%~dp0"
node server.js
pause

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

807
server.js
View File

@ -1,807 +0,0 @@
/**
* Development Server - Simple HTTP server for local development
* Handles static files, CORS, and development features
*/
import { createServer } from 'http';
import { readFile, writeFile, stat, readdir, mkdir } from 'fs/promises';
import { join, extname } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { existsSync } from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const PORT = process.env.PORT || 8080;
const HOST = process.env.HOST || 'localhost';
// MIME types for different file extensions
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.mp3': 'audio/mpeg',
'.wav': 'audio/wav',
'.mp4': 'video/mp4'
};
const server = createServer(async (req, res) => {
try {
// Parse URL and remove query parameters
const urlPath = new URL(req.url, `http://${req.headers.host}`).pathname;
// Default to index.html for root requests
const filePath = urlPath === '/' ? 'index.html' : urlPath.slice(1);
const fullPath = join(__dirname, filePath);
console.log(`${new Date().toISOString()} - ${req.method} ${urlPath}`);
// Legacy API endpoint to get all available books (deprecated - use ContentLoader)
if (urlPath === '/api/books') {
console.warn('⚠️ /api/books is deprecated. Use ContentLoader.loadBooks() instead');
return await handleBooksAPI(res);
}
// API endpoint for LLM configuration (IAEngine)
if (urlPath === '/api/llm-config') {
return await handleLLMConfigAPI(req, res);
}
// Progress API endpoints (DRS and Flashcards only)
if (urlPath === '/api/progress/save') {
return await handleProgressSave(req, res);
}
// Data merge endpoint for combining local and external sources
if (urlPath === '/api/progress/merge') {
return await handleProgressMerge(req, res);
}
// Sync status endpoint
if (urlPath === '/api/progress/sync-status') {
return await handleSyncStatus(req, res);
}
// DRS progress load: /api/progress/load/drs/bookId/chapterId
const drsLoadMatch = urlPath.match(/^\/api\/progress\/load\/drs\/([^\/]+)\/([^\/]+)$/);
if (drsLoadMatch) {
const [, bookId, chapterId] = drsLoadMatch;
return await handleProgressLoad(req, res, 'drs', bookId, chapterId);
}
// Flashcards progress load: /api/progress/load/flashcards
if (urlPath === '/api/progress/load/flashcards') {
return await handleProgressLoad(req, res, 'flashcards');
}
// Set CORS headers for all requests
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Disable caching completely for development
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
res.setHeader('Surrogate-Control', 'no-store');
// Handle preflight requests
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Check if file exists
try {
const stats = await stat(fullPath);
if (stats.isDirectory()) {
// Try to serve index.html from directory
const indexPath = join(fullPath, 'index.html');
try {
await stat(indexPath);
return serveFile(indexPath, res);
} catch {
return send404(res, `Directory listing not allowed for ${urlPath}`);
}
}
return serveFile(fullPath, res);
} catch (error) {
if (error.code === 'ENOENT') {
return send404(res, `File not found: ${urlPath}`);
}
throw error;
}
} catch (error) {
console.error('Server error:', error);
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Internal Server Error');
}
});
async function handleBooksAPI(res) {
try {
const booksDir = join(__dirname, 'content', 'books');
const files = await readdir(booksDir);
const jsonFiles = files.filter(file => file.endsWith('.json'));
const books = [];
for (const file of jsonFiles) {
try {
const filePath = join(booksDir, file);
const content = await readFile(filePath, 'utf8');
const data = JSON.parse(content);
books.push({
id: data.id,
name: data.name,
description: data.description,
difficulty: data.difficulty,
language: data.language,
chapters: data.chapters || []
});
} catch (error) {
console.error(`Error reading ${file}:`, error);
}
}
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200);
res.end(JSON.stringify(books, null, 2));
console.log(` ✅ Served API books list (${books.length} books)`);
} catch (error) {
console.error('Error in books API:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Failed to load books' }));
}
}
async function handleChaptersAPI(res, bookId) {
try {
const booksDir = join(__dirname, 'content', 'books');
const bookPath = join(booksDir, `${bookId}.json`);
const content = await readFile(bookPath, 'utf8');
const bookData = JSON.parse(content);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200);
res.end(JSON.stringify(bookData.chapters || [], null, 2));
console.log(` ✅ Served API chapters for book ${bookId} (${bookData.chapters?.length || 0} chapters)`);
} catch (error) {
console.error(`Error in chapters API for ${bookId}:`, error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Failed to load chapters' }));
}
}
async function handleChapterContentAPI(res, chapterId) {
try {
const chaptersDir = join(__dirname, 'content', 'chapters');
const chapterPath = join(chaptersDir, `${chapterId}.json`);
const content = await readFile(chapterPath, 'utf8');
const chapterData = JSON.parse(content);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200);
res.end(JSON.stringify(chapterData, null, 2));
console.log(` ✅ Served API content for chapter ${chapterId}`);
} catch (error) {
console.error(`Error in chapter content API for ${chapterId}:`, error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Failed to load chapter content' }));
}
}
async function serveFile(filePath, res) {
try {
const ext = extname(filePath).toLowerCase();
const mimeType = MIME_TYPES[ext] || 'application/octet-stream';
// Set content type
res.setHeader('Content-Type', mimeType);
// Set cache headers for static assets
if (['.css', '.png', '.jpg', '.gif', '.svg', '.ico', '.woff', '.woff2'].includes(ext)) {
res.setHeader('Cache-Control', 'public, max-age=3600'); // 1 hour
} else {
res.setHeader('Cache-Control', 'no-cache'); // No cache for HTML and JS files (development)
}
// Add security headers
if (ext === '.js') {
res.setHeader('X-Content-Type-Options', 'nosniff');
}
// Read and serve file
const content = await readFile(filePath);
res.writeHead(200);
res.end(content);
console.log(` ✅ Served ${filePath} (${content.length} bytes, ${mimeType})`);
} catch (error) {
console.error(`Error serving file ${filePath}:`, error);
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Error reading file');
}
}
function send404(res, message = 'Not Found') {
const html404 = `
<!DOCTYPE html>
<html>
<head>
<title>404 - Not Found</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
}
h1 { font-size: 3rem; margin-bottom: 1rem; }
p { font-size: 1.2rem; opacity: 0.8; }
a { color: white; text-decoration: underline; }
</style>
</head>
<body>
<h1>404</h1>
<p>${message}</p>
<p><a href="/"> Back to Class Generator</a></p>
</body>
</html>
`;
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end(html404);
console.log(` ❌ 404: ${message}`);
}
// Progress storage functions for DRS and Flashcards
async function handleProgressSave(req, res) {
try {
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Method not allowed' }));
return;
}
// Read request body
let body = '';
req.on('data', chunk => body += chunk.toString());
req.on('end', async () => {
try {
const { system, bookId, chapterId, progressData } = JSON.parse(body);
// Validate system (only DRS and Flashcards allowed)
if (!['drs', 'flashcards'].includes(system)) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid system. Only "drs" and "flashcards" allowed' }));
return;
}
// Create saves directory if it doesn't exist
const savesDir = join(__dirname, 'saves');
if (!existsSync(savesDir)) {
await mkdir(savesDir, { recursive: true });
}
// Create filename based on system and identifiers
const filename = system === 'drs'
? `${system}-progress-${bookId}-${chapterId}.json`
: `${system}-progress.json`;
const filePath = join(savesDir, filename);
// Add metadata
const saveData = {
...progressData,
system,
bookId: system === 'drs' ? bookId : undefined,
chapterId: system === 'drs' ? chapterId : undefined,
savedAt: new Date().toISOString(),
version: '1.0'
};
await writeFile(filePath, JSON.stringify(saveData, null, 2));
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200);
res.end(JSON.stringify({
success: true,
filename,
savedAt: saveData.savedAt
}));
console.log(` ✅ Saved ${system} progress: ${filename}`);
} catch (parseError) {
console.error('Error parsing progress save request:', parseError);
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON data' }));
}
});
} catch (error) {
console.error('Error in progress save API:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Failed to save progress' }));
}
}
async function handleProgressLoad(req, res, system, bookId, chapterId) {
try {
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Method not allowed' }));
return;
}
// Validate system
if (!['drs', 'flashcards'].includes(system)) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid system' }));
return;
}
const filename = system === 'drs'
? `${system}-progress-${bookId}-${chapterId}.json`
: `${system}-progress.json`;
const filePath = join(__dirname, 'saves', filename);
try {
const content = await readFile(filePath, 'utf8');
const progressData = JSON.parse(content);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200);
res.end(JSON.stringify(progressData));
console.log(` ✅ Loaded ${system} progress: ${filename}`);
} catch (fileError) {
// File doesn't exist - return empty progress
const emptyProgress = system === 'drs' ? {
masteredVocabulary: [],
masteredPhrases: [],
masteredGrammar: [],
completed: false,
masteryCount: 0,
system,
bookId,
chapterId
} : {
system: 'flashcards',
progress: {},
stats: {}
};
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200);
res.end(JSON.stringify(emptyProgress));
console.log(` No saved progress found for ${system}, returning empty`);
}
} catch (error) {
console.error('Error in progress load API:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Failed to load progress' }));
}
}
// API handler for LLM configuration
async function handleLLMConfigAPI(req, res) {
try {
// Only allow GET requests
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Method not allowed' }));
return;
}
// Load environment variables
const { config } = await import('dotenv');
config();
// Extract only the LLM API keys
const llmConfig = {
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
MISTRAL_API_KEY: process.env.MISTRAL_API_KEY,
GEMINI_API_KEY: process.env.GEMINI_API_KEY
};
// Filter out undefined keys
const validKeys = Object.fromEntries(
Object.entries(llmConfig).filter(([key, value]) => value && value.length > 0)
);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200);
res.end(JSON.stringify(validKeys));
console.log(` ✅ Served LLM config with ${Object.keys(validKeys).length} API keys`);
} catch (error) {
console.error('Error in LLM config API:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Failed to load LLM configuration' }));
}
}
// Data merge handler for combining local and external progress
async function handleProgressMerge(req, res) {
try {
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Method not allowed' }));
return;
}
// Read request body
let body = '';
req.on('data', chunk => body += chunk.toString());
req.on('end', async () => {
try {
const { system, bookId, chapterId, localData, externalData, mergeStrategy = 'timestamp' } = JSON.parse(body);
// Validate system
if (!['drs', 'flashcards'].includes(system)) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid system. Only "drs" and "flashcards" allowed' }));
return;
}
// Perform data merge
const mergedData = await mergeProgressData(localData, externalData, mergeStrategy);
// Add merge metadata
mergedData.mergeInfo = {
strategy: mergeStrategy,
mergedAt: new Date().toISOString(),
localItems: countProgressItems(localData),
externalItems: countProgressItems(externalData),
totalItems: countProgressItems(mergedData),
conflicts: mergedData.conflicts || []
};
// Save merged data
const savesDir = join(__dirname, 'saves');
if (!existsSync(savesDir)) {
await mkdir(savesDir, { recursive: true });
}
const filename = system === 'drs'
? `${system}-progress-${bookId}-${chapterId}.json`
: `${system}-progress.json`;
const filePath = join(savesDir, filename);
await writeFile(filePath, JSON.stringify(mergedData, null, 2));
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200);
res.end(JSON.stringify({
success: true,
mergedData,
mergeInfo: mergedData.mergeInfo
}));
console.log(` ✅ Merged ${system} progress: ${mergedData.mergeInfo.totalItems} total items`);
} catch (parseError) {
console.error('Error parsing progress merge request:', parseError);
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON data' }));
}
});
} catch (error) {
console.error('Error in progress merge API:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Failed to merge progress' }));
}
}
// Sync status handler
async function handleSyncStatus(req, res) {
try {
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Method not allowed' }));
return;
}
const savesDir = join(__dirname, 'saves');
const syncStatus = {
savesDirectory: savesDir,
lastSync: null,
savedFiles: [],
totalFiles: 0
};
try {
if (existsSync(savesDir)) {
const files = await readdir(savesDir);
const jsonFiles = files.filter(file => file.endsWith('.json'));
syncStatus.totalFiles = jsonFiles.length;
for (const file of jsonFiles) {
try {
const filePath = join(savesDir, file);
const stats = await stat(filePath);
const content = await readFile(filePath, 'utf8');
const data = JSON.parse(content);
syncStatus.savedFiles.push({
filename: file,
lastModified: stats.mtime.toISOString(),
savedAt: data.savedAt || stats.mtime.toISOString(),
system: data.system || 'unknown',
bookId: data.bookId,
chapterId: data.chapterId,
hasTimestamps: hasTimestampData(data),
itemCount: countProgressItems(data)
});
// Update last sync time to most recent file
if (!syncStatus.lastSync || stats.mtime > new Date(syncStatus.lastSync)) {
syncStatus.lastSync = stats.mtime.toISOString();
}
} catch (fileError) {
console.warn(`Error reading file ${file}:`, fileError);
}
}
}
} catch (dirError) {
console.warn('Saves directory not accessible:', dirError);
}
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200);
res.end(JSON.stringify(syncStatus));
console.log(` ✅ Sync status: ${syncStatus.totalFiles} files`);
} catch (error) {
console.error('Error in sync status API:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Failed to get sync status' }));
}
}
// Data merge utilities
async function mergeProgressData(localData, externalData, strategy = 'timestamp') {
const merged = {
masteredVocabulary: [],
masteredPhrases: [],
masteredGrammar: [],
completed: false,
masteryCount: 0,
conflicts: []
};
// Copy metadata from most recent source
const localTimestamp = localData?.savedAt || localData?.lastModified || '1970-01-01T00:00:00.000Z';
const externalTimestamp = externalData?.savedAt || externalData?.lastModified || '1970-01-01T00:00:00.000Z';
const useExternal = new Date(externalTimestamp) > new Date(localTimestamp);
const primarySource = useExternal ? externalData : localData;
const secondarySource = useExternal ? localData : externalData;
// Copy basic properties
merged.system = primarySource.system || localData.system || 'drs';
merged.bookId = primarySource.bookId || localData.bookId;
merged.chapterId = primarySource.chapterId || localData.chapterId;
merged.completed = primarySource.completed || secondarySource.completed || false;
merged.savedAt = new Date().toISOString();
// Merge each category
const categories = ['masteredVocabulary', 'masteredPhrases', 'masteredGrammar'];
for (const category of categories) {
const localItems = localData[category] || [];
const externalItems = externalData[category] || [];
const mergeResult = mergeItemArrays(localItems, externalItems, strategy);
merged[category] = mergeResult.items;
merged.conflicts.push(...mergeResult.conflicts.map(c => ({ ...c, category })));
}
// Calculate mastery count (max of both sources plus any new merged items)
merged.masteryCount = Math.max(
localData.masteryCount || 0,
externalData.masteryCount || 0,
merged.masteredVocabulary.length + merged.masteredPhrases.length + merged.masteredGrammar.length
);
return merged;
}
function mergeItemArrays(localItems, externalItems, strategy) {
const result = {
items: [],
conflicts: []
};
const itemMap = new Map();
// Add all items to map, handling conflicts
const addToMap = (items, source) => {
items.forEach(item => {
const itemKey = typeof item === 'string' ? item : item.item;
const itemData = typeof item === 'string' ? { item, masteredAt: '1970-01-01T00:00:00.000Z', source } : { ...item, source };
if (itemMap.has(itemKey)) {
const existing = itemMap.get(itemKey);
const conflict = {
item: itemKey,
local: source === 'local' ? itemData : existing,
external: source === 'external' ? itemData : existing,
resolution: 'pending'
};
// Resolve conflict based on strategy
let resolvedItem;
switch (strategy) {
case 'timestamp':
const existingTime = new Date(existing.masteredAt || existing.lastReviewAt || '1970-01-01');
const newTime = new Date(itemData.masteredAt || itemData.lastReviewAt || '1970-01-01');
if (newTime > existingTime) {
resolvedItem = { ...itemData, attempts: (existing.attempts || 1) + (itemData.attempts || 1) };
conflict.resolution = `newer_timestamp_from_${source}`;
} else {
resolvedItem = { ...existing, attempts: (existing.attempts || 1) + (itemData.attempts || 1) };
conflict.resolution = 'kept_existing_newer_timestamp';
}
break;
case 'attempts':
resolvedItem = {
...existing,
attempts: (existing.attempts || 1) + (itemData.attempts || 1),
lastReviewAt: new Date().toISOString()
};
conflict.resolution = 'merged_attempts';
break;
case 'prefer_local':
resolvedItem = source === 'local' ? itemData : existing;
conflict.resolution = 'preferred_local';
break;
case 'prefer_external':
resolvedItem = source === 'external' ? itemData : existing;
conflict.resolution = 'preferred_external';
break;
default:
resolvedItem = existing;
conflict.resolution = 'kept_existing_default';
}
itemMap.set(itemKey, resolvedItem);
result.conflicts.push(conflict);
} else {
itemMap.set(itemKey, itemData);
}
});
};
// Process both arrays
addToMap(localItems, 'local');
addToMap(externalItems, 'external');
// Convert map back to array, removing source metadata
result.items = Array.from(itemMap.values()).map(item => {
const { source, ...cleanItem } = item;
return cleanItem;
});
return result;
}
function countProgressItems(data) {
if (!data) return 0;
const vocab = data.masteredVocabulary?.length || 0;
const phrases = data.masteredPhrases?.length || 0;
const grammar = data.masteredGrammar?.length || 0;
return vocab + phrases + grammar;
}
function hasTimestampData(data) {
if (!data) return false;
const checkArray = (arr) => {
return arr && arr.length > 0 && arr.some(item =>
typeof item === 'object' && (item.masteredAt || item.lastReviewAt)
);
};
return checkArray(data.masteredVocabulary) ||
checkArray(data.masteredPhrases) ||
checkArray(data.masteredGrammar);
}
// Start server
server.listen(PORT, HOST, () => {
console.log('\n🚀 Class Generator Development Server');
console.log('=====================================');
console.log(`📍 Local: http://${HOST}:${PORT}/`);
console.log(`🌐 Network: http://localhost:${PORT}/`);
console.log('📁 Serving files from:', __dirname);
console.log('\n✨ Features:');
console.log(' • ES6 modules support');
console.log(' • CORS enabled');
console.log(' • Static file serving');
console.log(' • Development-friendly caching');
console.log('\n🔥 Ready for development!');
console.log('Press Ctrl+C to stop\n');
});
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\n👋 Shutting down server...');
server.close(() => {
console.log('✅ Server stopped');
process.exit(0);
});
});
process.on('SIGTERM', () => {
console.log('\n👋 Received SIGTERM, shutting down...');
server.close(() => {
console.log('✅ Server stopped');
process.exit(0);
});
});

View File

@ -1,389 +0,0 @@
/**
* Application - Main application bootstrap and lifecycle manager
* Auto-initializes the entire system without external commands
*/
import { EventBus, ModuleLoader, Router } from './core/index.js';
class Application {
constructor(config = {}) {
// Application state
this._isRunning = false;
this._isInitialized = false;
this._startTime = null;
// Core system instances
this._eventBus = new EventBus();
this._moduleLoader = new ModuleLoader(this._eventBus);
this._router = null;
// Register core modules immediately for early access
this._registerCoreModules();
// Configuration
this._config = {
autoStart: config.autoStart !== false, // Default true
defaultRoute: config.defaultRoute || '/',
enableDebug: config.enableDebug || false,
modules: config.modules || [],
...config
};
// Auto-start if enabled
if (this._config.autoStart) {
this._autoStart();
}
// Seal to prevent modification
Object.seal(this);
}
/**
* Initialize and start the application
*/
async start() {
if (this._isRunning) {
console.warn('Application is already running');
return;
}
try {
this._startTime = Date.now();
console.log('🚀 Starting Class Generator Application...');
// Validate all progress item implementations (STRICT MODE)
console.log('🔍 Validating progress item implementations...');
const { default: ImplementationValidator } = await import('./DRS/services/ImplementationValidator.js');
const isValid = await ImplementationValidator.validateAll();
if (!isValid) {
throw new Error('❌ Implementation validation failed - check console for details');
}
// Initialize core systems
await this._initializeCore();
// Load and initialize modules
await this._loadModules();
// Start routing
await this._startRouting();
// Set up global error handling
this._setupErrorHandling();
this._isRunning = true;
this._isInitialized = true;
const startupTime = Date.now() - this._startTime;
console.log(`✅ Application started successfully in ${startupTime}ms`);
// Emit application ready event
this._eventBus.emit('app:ready', {
startupTime,
modules: this._moduleLoader.getStatus()
}, 'Application');
} catch (error) {
console.error('❌ Failed to start application:', error);
throw error;
}
}
/**
* Stop the application and clean up
*/
async stop() {
if (!this._isRunning) {
return;
}
try {
console.log('🛑 Stopping application...');
// Emit application stopping event
this._eventBus.emit('app:stopping', {}, 'Application');
// Destroy all modules
const status = this._moduleLoader.getStatus();
for (const moduleName of status.loaded) {
await this._moduleLoader.destroy(moduleName);
}
// Destroy router
if (this._router) {
await this._moduleLoader.destroy('router');
}
this._isRunning = false;
this._isInitialized = false;
console.log('✅ Application stopped successfully');
} catch (error) {
console.error('❌ Error stopping application:', error);
}
}
/**
* Get application status
*/
getStatus() {
return {
isRunning: this._isRunning,
isInitialized: this._isInitialized,
startTime: this._startTime,
uptime: this._startTime ? Date.now() - this._startTime : 0,
modules: this._moduleLoader.getStatus(),
config: { ...this._config }
};
}
/**
* Get core system instances (for advanced usage)
*/
getCore() {
return {
eventBus: this._eventBus,
moduleLoader: this._moduleLoader,
router: this._router
};
}
/**
* Get a loaded module by name
* @param {string} moduleName - Name of the module to get
* @returns {Object|null} Module instance or null if not found
*/
getModule(moduleName) {
return this._moduleLoader ? this._moduleLoader.getModule(moduleName) : null;
}
// Private methods
async _autoStart() {
// Wait for DOM to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.start());
} else {
// DOM is already loaded
setTimeout(() => this.start(), 0);
}
}
_registerCoreModules() {
// Register bootstrap module to allow HTML script to use EventBus
this._eventBus.registerModule({ name: 'Bootstrap' });
// Register Application itself as a module
this._eventBus.registerModule({ name: 'Application' });
// Register ModuleLoader as a module
this._eventBus.registerModule({ name: 'ModuleLoader' });
// Register core dependencies by storing them directly in ModuleLoader
// This allows other modules to depend on 'eventBus'
this._moduleLoader._loadedModules.set('eventBus', this._eventBus);
this._moduleLoader._modules.set('eventBus', {
name: 'eventBus',
instance: this._eventBus,
loaded: true,
initialized: true
});
}
async _initializeCore() {
// Register router as a module
this._moduleLoader.register('router', Router, ['eventBus']);
// Create and initialize router
this._router = await this._moduleLoader.loadAndInitialize('router', {
defaultRoute: this._config.defaultRoute,
maxHistorySize: 100
});
if (this._config.enableDebug) {
console.log('🔧 Core systems initialized');
}
}
async _loadModules() {
console.log(`🔄 Loading ${this._config.modules.length} modules...`);
// PHASE 1: Register all modules first
console.log('📋 PHASE 1: Registering all modules...');
for (const moduleConfig of this._config.modules) {
try {
const { name, path, dependencies = [], config = {} } = moduleConfig;
console.log(`📝 Registering module: ${name} from ${path}`);
// Dynamically import module
const moduleModule = await import(path);
const ModuleClass = moduleModule.default;
if (!ModuleClass) {
throw new Error(`Module ${name} does not export a default class`);
}
// Register only
this._moduleLoader.register(name, ModuleClass, dependencies);
console.log(`✅ Registered module: ${name} with dependencies: [${dependencies.join(', ')}]`);
} catch (error) {
console.error(`❌ Failed to register module ${moduleConfig.name}:`, error);
console.error(`❌ Module config was:`, moduleConfig);
console.error(`❌ Full error stack:`, error.stack);
// Emit module load error
this._eventBus.emit('app:module-error', {
module: moduleConfig.name,
error: error.message
}, 'Application');
}
}
// Show registration status
const status = this._moduleLoader.getStatus();
console.log(`📊 Registration complete. Registered modules: [${status.registered.join(', ')}]`);
// PHASE 2: Load and initialize all modules
console.log('🚀 PHASE 2: Loading and initializing modules...');
for (const moduleConfig of this._config.modules) {
try {
const { name, config = {} } = moduleConfig;
console.log(`🔄 Loading and initializing module: ${name}`);
await this._moduleLoader.loadAndInitialize(name, config);
console.log(`✅ Module ${name} loaded and initialized successfully`);
} catch (error) {
console.error(`❌ Failed to load/initialize module ${moduleConfig.name}:`, error);
console.error(`❌ Module config was:`, moduleConfig);
console.error(`❌ Full error stack:`, error.stack);
// Emit module load error
this._eventBus.emit('app:module-error', {
module: moduleConfig.name,
error: error.message
}, 'Application');
}
}
// Final status
const finalStatus = this._moduleLoader.getStatus();
console.log(`🎯 Module loading complete. Initialized modules: [${finalStatus.initialized.join(', ')}]`);
}
async _startRouting() {
// Register generic routes with dynamic loading
this._router.register('/', this._handleHomeRoute.bind(this));
this._router.register('/books', this._handleBooksRoute.bind(this));
this._router.register('/chapters', this._handleChaptersRoute.bind(this), { exact: false });
this._router.register('/games', this._handleGamesRoute.bind(this), { exact: false });
this._router.register('/dynamic-revision', this._handleDynamicRevisionRoute.bind(this));
this._router.register('/settings', this._handleSettingsRoute.bind(this));
// Now that routes are registered, handle the current route
console.log('🛣️ Routes registered, handling initial route...');
this._router._handleCurrentRoute();
if (this._config.enableDebug) {
console.log('🛣️ Routing system started');
}
}
_setupErrorHandling() {
// Global error handler
window.addEventListener('error', (event) => {
console.error('Global error:', event.error);
this._eventBus.emit('app:error', {
message: event.error.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno
}, 'Application');
});
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
this._eventBus.emit('app:promise-rejection', {
reason: event.reason
}, 'Application');
});
if (this._config.enableDebug) {
console.log('🛡️ Error handling setup complete');
}
}
// Default route handlers
async _handleHomeRoute(path, state) {
this._eventBus.emit('navigation:home', { path, state }, 'Application');
}
async _handleBooksRoute(path, state) {
this._eventBus.emit('navigation:books', { path, state }, 'Application');
}
async _handleChaptersRoute(path, state) {
this._eventBus.emit('navigation:chapters', { path, state }, 'Application');
}
async _handleGamesRoute(path, state) {
this._eventBus.emit('navigation:games', { path, state }, 'Application');
// Simple approach: Force re-render by emitting the chapter navigation event
console.log('🔄 Games route - path:', path, 'state:', state);
// Extract chapter ID from path or use current one
const pathParts = path.split('/');
let chapterId = pathParts[2] || window.currentChapterId || 'sbs';
console.log('🔄 Games route - using chapterId:', chapterId);
// Make sure currentChapterId is set
if (!window.currentChapterId) {
window.currentChapterId = chapterId;
}
// Force navigation to the chapter games - this will trigger the content loading
setTimeout(() => {
console.log('🔄 Games route - forcing navigation event');
this._eventBus.emit('navigation:games', {
path: `/games/${chapterId}`,
data: { path: `/games/${chapterId}` }
}, 'Application');
}, 100);
}
async _handleDynamicRevisionRoute(path, state) {
this._eventBus.emit('navigation:dynamic-revision', { path, state }, 'Application');
}
async _handleSettingsRoute(path, state) {
this._eventBus.emit('navigation:settings', { path, state }, 'Application');
}
}
// Create global application instance
const app = new Application({
enableDebug: true,
modules: [
// Core system modules
{ name: 'contentLoader', path: './core/ContentLoader.js', dependencies: ['eventBus'] },
{ name: 'gameLoader', path: './core/GameLoader.js', dependencies: ['eventBus'] },
{ name: 'intelligentSequencer', path: './core/IntelligentSequencer.js', dependencies: ['eventBus'] },
// DRS system
{ name: 'unifiedDRS', path: './DRS/UnifiedDRS.js', dependencies: ['eventBus', 'contentLoader'] },
{ name: 'smartPreviewOrchestrator', path: './DRS/SmartPreviewOrchestrator.js', dependencies: ['eventBus', 'contentLoader'] },
// UI components
{ name: 'settingsDebug', path: './components/SettingsDebug.js', dependencies: ['eventBus', 'router'] }
]
});
// Export for manual control if needed
export default app;
// Auto-start is handled by the constructor

View File

@ -1,715 +0,0 @@
/**
* DRSTestRunner - Interface de tests DRS intégrée dans l'application
* Accessible via l'interface web pour tester le système DRS en temps réel
*/
class DRSTestRunner {
constructor() {
this.tests = {
passed: 0,
failed: 0,
total: 0,
failures: [],
results: []
};
this.container = null;
this.isRunning = false;
}
/**
* Initialiser l'interface de tests
*/
init(container) {
this.container = container;
this.renderTestInterface();
}
/**
* Créer l'interface utilisateur des tests
*/
renderTestInterface() {
this.container.innerHTML = `
<div class="drs-test-runner">
<div class="test-header">
<h2>🧪 DRS Test Suite</h2>
<p>Tests spécifiques au système DRS (src/DRS/ uniquement)</p>
<div class="test-controls">
<button id="run-drs-tests" class="btn-primary">
🚀 Lancer les Tests DRS
</button>
<button id="clear-results" class="btn-secondary">
🗑 Effacer les Résultats
</button>
</div>
</div>
<div class="test-progress" id="test-progress" style="display: none;">
<div class="progress-bar">
<div class="progress-fill" id="progress-fill"></div>
</div>
<div class="progress-text" id="progress-text">Initialisation...</div>
</div>
<div class="test-results" id="test-results">
<div class="welcome-message">
<h3>👋 Bienvenue dans l'interface de tests DRS</h3>
<p>Cliquez sur "Lancer les Tests DRS" pour commencer la validation du système.</p>
<div class="test-scope">
<h4>🎯 Scope des tests :</h4>
<ul>
<li> Imports et structure des modules DRS</li>
<li> Compliance avec ExerciseModuleInterface</li>
<li> Services DRS (IAEngine, LLMValidator, etc.)</li>
<li> VocabularyModule (système flashcards intégré)</li>
<li> Séparation DRS vs Games</li>
<li> Transitions entre modules</li>
</ul>
</div>
</div>
</div>
<div class="test-summary" id="test-summary" style="display: none;">
<!-- Résumé affiché à la fin -->
</div>
</div>
`;
// Ajouter les styles
this.addTestStyles();
// Ajouter les event listeners
document.getElementById('run-drs-tests').onclick = () => this.runAllTests();
document.getElementById('clear-results').onclick = () => this.clearResults();
}
/**
* Lancer tous les tests DRS
*/
async runAllTests() {
if (this.isRunning) return;
this.isRunning = true;
this.resetTests();
this.showProgress();
const resultsContainer = document.getElementById('test-results');
resultsContainer.innerHTML = '<div class="test-log"></div>';
try {
await this.runTestSuite();
} catch (error) {
this.logError(`Erreur critique: ${error.message}`);
}
this.showSummary();
this.hideProgress();
this.isRunning = false;
}
/**
* Exécuter la suite de tests
*/
async runTestSuite() {
this.logSection('📁 Testing DRS Structure & Imports...');
await this.testDRSStructure();
this.logSection('🎮 Testing DRS Exercise Modules...');
await this.testExerciseModules();
this.logSection('🏗️ Testing DRS Architecture...');
await this.testDRSArchitecture();
this.logSection('🔒 Testing DRS Interface Compliance...');
await this.testInterfaceCompliance();
this.logSection('🚫 Testing DRS/Games Separation...');
await this.testDRSGamesSeparation();
this.logSection('📚 Testing VocabularyModule (Flashcard System)...');
await this.testVocabularyModule();
this.logSection('🔄 Testing WordDiscovery → Vocabulary Transition...');
await this.testWordDiscoveryTransition();
}
/**
* Test structure et imports DRS
*/
async testDRSStructure() {
const modules = [
{ name: 'ExerciseModuleInterface', path: './interfaces/ExerciseModuleInterface.js' },
{ name: 'IAEngine', path: './services/IAEngine.js' },
{ name: 'LLMValidator', path: './services/LLMValidator.js' },
{ name: 'AIReportSystem', path: './services/AIReportSystem.js' },
{ name: 'ContextMemory', path: './services/ContextMemory.js' },
{ name: 'PrerequisiteEngine', path: './services/PrerequisiteEngine.js' }
];
for (const module of modules) {
await this.test(`${module.name} imports correctly`, async () => {
const imported = await import(module.path);
return imported.default !== undefined;
});
}
}
/**
* Test modules d'exercices
*/
async testExerciseModules() {
const exerciseModules = [
'AudioModule', 'GrammarAnalysisModule', 'GrammarModule', 'ImageModule',
'OpenResponseModule', 'PhraseModule', 'TextAnalysisModule', 'TextModule',
'TranslationModule', 'VocabularyModule', 'WordDiscoveryModule'
];
for (const moduleName of exerciseModules) {
await this.test(`${moduleName} imports correctly`, async () => {
const module = await import(`./exercise-modules/${moduleName}.js`);
return module.default !== undefined;
});
}
}
/**
* Test architecture DRS
*/
async testDRSArchitecture() {
await this.test('UnifiedDRS imports correctly', async () => {
const module = await import('./UnifiedDRS.js');
return module.default !== undefined;
});
await this.test('SmartPreviewOrchestrator imports correctly', async () => {
const module = await import('./SmartPreviewOrchestrator.js');
return module.default !== undefined;
});
}
/**
* Test compliance interface
*/
async testInterfaceCompliance() {
await this.test('VocabularyModule extends ExerciseModuleInterface', async () => {
const { default: VocabularyModule } = await import('./exercise-modules/VocabularyModule.js');
// Créer des mocks complets
const mockOrchestrator = {
_eventBus: { emit: () => {} },
sessionId: 'test-session',
bookId: 'test-book',
chapterId: 'test-chapter'
};
const mockLLMValidator = { validate: () => Promise.resolve({ score: 100, correct: true }) };
const mockPrerequisiteEngine = { markWordMastered: () => {} };
const mockContextMemory = { recordInteraction: () => {} };
const instance = new VocabularyModule(mockOrchestrator, mockLLMValidator, mockPrerequisiteEngine, mockContextMemory);
// Vérifier que toutes les méthodes requises existent
const requiredMethods = ['canRun', 'present', 'validate', 'getProgress', 'cleanup', 'getMetadata'];
for (const method of requiredMethods) {
if (typeof instance[method] !== 'function') {
throw new Error(`Missing method: ${method}`);
}
}
return true;
});
}
/**
* Test séparation DRS/Games
*/
async testDRSGamesSeparation() {
this.test('No FlashcardLearning imports in DRS', () => {
// Test symbolique - nous avons déjà nettoyé les imports
return true;
});
this.test('No ../games/ imports in DRS', () => {
// Test symbolique - nous avons déjà nettoyé les imports
return true;
});
}
/**
* Test VocabularyModule spécifique
*/
async testVocabularyModule() {
await this.test('VocabularyModule has spaced repetition logic', async () => {
const { default: VocabularyModule } = await import('./exercise-modules/VocabularyModule.js');
const mockOrchestrator = { _eventBus: { emit: () => {} } };
const mockLLMValidator = { validate: () => Promise.resolve({ score: 100, correct: true }) };
const mockPrerequisiteEngine = { markWordMastered: () => {} };
const mockContextMemory = { recordInteraction: () => {} };
const instance = new VocabularyModule(mockOrchestrator, mockLLMValidator, mockPrerequisiteEngine, mockContextMemory);
return typeof instance._handleDifficultySelection === 'function';
});
await this.test('VocabularyModule uses local validation (no AI)', async () => {
const { default: VocabularyModule } = await import('./exercise-modules/VocabularyModule.js');
const mockOrchestrator = { _eventBus: { emit: () => {} } };
const mockLLMValidator = { validate: () => Promise.resolve({ score: 100, correct: true }) };
const mockPrerequisiteEngine = { markWordMastered: () => {} };
const mockContextMemory = { recordInteraction: () => {} };
const instance = new VocabularyModule(mockOrchestrator, mockLLMValidator, mockPrerequisiteEngine, mockContextMemory);
// Initialiser avec des données test
instance.currentVocabularyGroup = [{ word: 'test', translation: 'test' }];
instance.currentWordIndex = 0;
// Tester validation locale
const result = await instance.validate('test', {});
return result && typeof result.score === 'number' && result.provider === 'local';
});
}
/**
* Test transition WordDiscovery
*/
async testWordDiscoveryTransition() {
await this.test('WordDiscoveryModule redirects to vocabulary-flashcards', async () => {
const { default: WordDiscoveryModule } = await import('./exercise-modules/WordDiscoveryModule.js');
let emittedEvent = null;
const mockOrchestrator = {
_eventBus: {
emit: (eventName, data) => {
emittedEvent = { eventName, data };
}
}
};
const instance = new WordDiscoveryModule(mockOrchestrator, null, null, null);
instance.currentWords = [{ word: 'test' }];
// Simuler la redirection
instance._redirectToFlashcards();
return emittedEvent &&
emittedEvent.data.nextAction === 'vocabulary-flashcards' &&
emittedEvent.data.nextExerciseType === 'vocabulary-flashcards';
});
}
/**
* Exécuter un test individuel
*/
async test(name, testFn) {
this.tests.total++;
this.updateProgress();
try {
const result = await testFn();
if (result === true || result === undefined) {
this.logSuccess(name);
this.tests.passed++;
} else {
this.logFailure(name, result);
this.tests.failed++;
this.tests.failures.push(name);
}
} catch (error) {
this.logFailure(name, error.message);
this.tests.failed++;
this.tests.failures.push(`${name}: ${error.message}`);
}
this.tests.results.push({
name,
passed: this.tests.results.length < this.tests.passed + 1,
error: this.tests.failures.length > 0 ? this.tests.failures[this.tests.failures.length - 1] : null
});
}
/**
* Logging methods
*/
logSection(title) {
const log = document.querySelector('.test-log');
log.innerHTML += `<div class="test-section">${title}</div>`;
log.scrollTop = log.scrollHeight;
}
logSuccess(name) {
const log = document.querySelector('.test-log');
log.innerHTML += `<div class="test-result success">✅ ${name}</div>`;
log.scrollTop = log.scrollHeight;
}
logFailure(name, error) {
const log = document.querySelector('.test-log');
log.innerHTML += `<div class="test-result failure">❌ ${name}: ${error}</div>`;
log.scrollTop = log.scrollHeight;
}
logError(message) {
const log = document.querySelector('.test-log');
log.innerHTML += `<div class="test-result error">💥 ${message}</div>`;
log.scrollTop = log.scrollHeight;
}
/**
* Gestion de la progression
*/
showProgress() {
document.getElementById('test-progress').style.display = 'block';
document.querySelector('.welcome-message').style.display = 'none';
}
hideProgress() {
document.getElementById('test-progress').style.display = 'none';
}
updateProgress() {
const progress = (this.tests.passed + this.tests.failed) / Math.max(this.tests.total, 1) * 100;
document.getElementById('progress-fill').style.width = `${progress}%`;
document.getElementById('progress-text').textContent =
`Tests: ${this.tests.passed + this.tests.failed}/${this.tests.total} - ${this.tests.passed}${this.tests.failed}`;
}
/**
* Afficher le résumé final
*/
showSummary() {
const successRate = Math.round((this.tests.passed / this.tests.total) * 100);
let status = '🎉 EXCELLENT';
let statusClass = 'excellent';
if (this.tests.failed > 0) {
if (this.tests.failed < this.tests.total / 2) {
status = '✅ BON';
statusClass = 'good';
} else {
status = '⚠️ PROBLÈMES';
statusClass = 'problems';
}
}
const summaryContainer = document.getElementById('test-summary');
summaryContainer.innerHTML = `
<div class="summary-content ${statusClass}">
<h3>📊 Résultats des Tests DRS</h3>
<div class="summary-stats">
<div class="stat">
<span class="stat-number">${this.tests.total}</span>
<span class="stat-label">Total</span>
</div>
<div class="stat success">
<span class="stat-number">${this.tests.passed}</span>
<span class="stat-label">Réussis</span>
</div>
<div class="stat failure">
<span class="stat-number">${this.tests.failed}</span>
<span class="stat-label">Échecs</span>
</div>
<div class="stat rate">
<span class="stat-number">${successRate}%</span>
<span class="stat-label">Taux de réussite</span>
</div>
</div>
<div class="summary-status">
<h4>${status}</h4>
${this.tests.failed === 0 ?
'<p>🎯 Tous les tests DRS sont passés ! Le système fonctionne parfaitement.</p>' :
`<p>⚠️ ${this.tests.failed} test(s) en échec. Vérifiez les détails ci-dessus.</p>`
}
</div>
</div>
`;
summaryContainer.style.display = 'block';
}
/**
* Réinitialiser les tests
*/
resetTests() {
this.tests = {
passed: 0,
failed: 0,
total: 0,
failures: [],
results: []
};
document.getElementById('test-summary').style.display = 'none';
}
/**
* Effacer les résultats
*/
clearResults() {
this.resetTests();
this.hideProgress();
document.getElementById('test-results').innerHTML = `
<div class="welcome-message">
<h3>👋 Bienvenue dans l'interface de tests DRS</h3>
<p>Cliquez sur "Lancer les Tests DRS" pour commencer la validation du système.</p>
<div class="test-scope">
<h4>🎯 Scope des tests :</h4>
<ul>
<li> Imports et structure des modules DRS</li>
<li> Compliance avec ExerciseModuleInterface</li>
<li> Services DRS (IAEngine, LLMValidator, etc.)</li>
<li> VocabularyModule (système flashcards intégré)</li>
<li> Séparation DRS vs Games</li>
<li> Transitions entre modules</li>
</ul>
</div>
</div>
`;
document.getElementById('test-summary').style.display = 'none';
}
/**
* Ajouter les styles CSS
*/
addTestStyles() {
if (document.getElementById('drs-test-styles')) return;
const styles = document.createElement('style');
styles.id = 'drs-test-styles';
styles.textContent = `
.drs-test-runner {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.test-header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border-radius: 12px;
}
.test-header h2 {
margin: 0 0 10px 0;
font-size: 2em;
}
.test-controls {
margin-top: 20px;
display: flex;
gap: 15px;
justify-content: center;
}
.btn-primary {
background: white;
color: #667eea;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.btn-secondary {
background: rgba(255,255,255,0.2);
color: white;
border: 1px solid white;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-secondary:hover {
background: rgba(255,255,255,0.3);
}
.test-progress {
margin-bottom: 20px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.progress-bar {
width: 100%;
height: 20px;
background: #f0f0f0;
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.3s ease;
width: 0%;
}
.progress-text {
text-align: center;
font-weight: bold;
color: #666;
}
.test-results {
background: white;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
min-height: 400px;
}
.welcome-message {
padding: 40px;
text-align: center;
}
.welcome-message h3 {
color: #667eea;
margin-bottom: 15px;
}
.test-scope {
margin-top: 30px;
text-align: left;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
.test-scope ul {
list-style: none;
padding: 0;
}
.test-scope li {
padding: 5px 0;
border-bottom: 1px solid #f0f0f0;
}
.test-log {
padding: 20px;
max-height: 500px;
overflow-y: auto;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 14px;
}
.test-section {
font-weight: bold;
color: #667eea;
margin: 20px 0 10px 0;
padding: 10px 0;
border-bottom: 2px solid #f0f0f0;
}
.test-result {
padding: 8px 0;
border-left: 3px solid transparent;
padding-left: 15px;
margin: 5px 0;
}
.test-result.success {
border-left-color: #28a745;
background: rgba(40, 167, 69, 0.1);
}
.test-result.failure {
border-left-color: #dc3545;
background: rgba(220, 53, 69, 0.1);
}
.test-result.error {
border-left-color: #fd7e14;
background: rgba(253, 126, 20, 0.1);
font-weight: bold;
}
.test-summary {
margin-top: 20px;
padding: 30px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.summary-content.excellent {
border-left: 5px solid #28a745;
}
.summary-content.good {
border-left: 5px solid #ffc107;
}
.summary-content.problems {
border-left: 5px solid #dc3545;
}
.summary-stats {
display: flex;
justify-content: space-around;
margin: 20px 0;
flex-wrap: wrap;
gap: 20px;
}
.stat {
text-align: center;
padding: 20px;
border-radius: 8px;
background: #f8f9fa;
min-width: 100px;
}
.stat.success {
background: rgba(40, 167, 69, 0.1);
}
.stat.failure {
background: rgba(220, 53, 69, 0.1);
}
.stat.rate {
background: rgba(102, 126, 234, 0.1);
}
.stat-number {
display: block;
font-size: 2em;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
color: #666;
font-size: 0.9em;
}
.summary-status {
text-align: center;
margin-top: 20px;
}
.summary-status h4 {
font-size: 1.5em;
margin-bottom: 10px;
}
`;
document.head.appendChild(styles);
}
}
export default DRSTestRunner;

View File

@ -1,785 +0,0 @@
/**
* SmartPreviewOrchestrator - Main controller for Dynamic Revision System
* Manages dynamic loading/unloading of exercise modules and coordinates shared services
*/
import Module from '../core/Module.js';
const privateData = new WeakMap();
class SmartPreviewOrchestrator extends Module {
constructor(name, dependencies, config) {
super(name, ['eventBus', 'contentLoader']);
// Validate dependencies
if (!dependencies.eventBus) {
throw new Error('SmartPreviewOrchestrator requires EventBus dependency');
}
if (!dependencies.contentLoader) {
throw new Error('SmartPreviewOrchestrator requires ContentLoader dependency');
}
// Store dependencies and configuration
this._eventBus = dependencies.eventBus;
this._contentLoader = dependencies.contentLoader;
this._config = config || {};
// Initialize private data
privateData.set(this, {
loadedModules: new Map(),
availableModules: new Map(),
currentModule: null,
sharedServices: {
llmValidator: null,
prerequisiteEngine: null,
contextMemory: null,
aiReportInterface: null
},
sessionState: {
currentChapter: null,
chapterContent: null,
masteredVocabulary: new Set(),
masteredPhrases: new Set(),
masteredGrammar: new Set(),
sessionProgress: {},
exerciseSequence: [],
sequenceIndex: 0
},
moduleRegistry: {
'vocabulary': './exercise-modules/VocabularyModule.js',
'phrase': './exercise-modules/PhraseModule.js',
'text': './exercise-modules/TextModule.js',
'text-analysis': './exercise-modules/TextAnalysisModule.js',
'audio': './exercise-modules/AudioModule.js',
'image': './exercise-modules/ImageModule.js',
'grammar': './exercise-modules/GrammarModule.js',
'grammar-analysis': './exercise-modules/GrammarAnalysisModule.js',
'translation': './exercise-modules/TranslationModule.js'
}
});
Object.seal(this);
}
async init() {
this._validateNotDestroyed();
try {
console.log('🎯 Initializing Smart Preview Orchestrator...');
// Initialize shared services
await this._initializeSharedServices();
// Set up event listeners
this._setupEventListeners();
// Register available module types
this._registerModuleTypes();
this._setInitialized();
console.log('✅ Smart Preview Orchestrator initialized successfully');
} catch (error) {
console.error('❌ SmartPreviewOrchestrator initialization failed:', error);
throw error;
}
}
async destroy() {
this._validateNotDestroyed();
try {
console.log('🧹 Cleaning up Smart Preview Orchestrator...');
// Unload all loaded modules
await this._unloadAllModules();
// Cleanup shared services
await this._cleanupSharedServices();
// Remove event listeners
this._eventBus.off('drs:startSession', this._handleStartSession, this.name);
this._eventBus.off('drs:switchModule', this._handleSwitchModule, this.name);
this._eventBus.off('drs:updateProgress', this._handleUpdateProgress, this.name);
this._setDestroyed();
console.log('✅ Smart Preview Orchestrator destroyed successfully');
} catch (error) {
console.error('❌ SmartPreviewOrchestrator cleanup failed:', error);
throw error;
}
}
// Public API Methods
/**
* Start a new revision session for a chapter
* @param {string} bookId - Book identifier
* @param {string} chapterId - Chapter identifier
* @returns {Promise<boolean>} - Success status
*/
async startRevisionSession(bookId, chapterId) {
this._validateInitialized();
try {
console.log(`🚀 Starting revision session: ${bookId} - ${chapterId}`);
// Load chapter content
const chapterContent = await this._contentLoader.loadContent(chapterId);
const data = privateData.get(this);
data.sessionState.currentChapter = { bookId, chapterId };
data.sessionState.chapterContent = chapterContent;
// Load existing progress from files
if (window.getChapterProgress) {
try {
const savedProgress = await window.getChapterProgress(bookId, chapterId);
// Populate session state with saved progress (handle both old and new format)
if (savedProgress.masteredVocabulary) {
const vocabItems = savedProgress.masteredVocabulary.map(entry => {
return typeof entry === 'string' ? entry : entry.item;
});
data.sessionState.masteredVocabulary = new Set(vocabItems);
}
if (savedProgress.masteredPhrases) {
const phraseItems = savedProgress.masteredPhrases.map(entry => {
return typeof entry === 'string' ? entry : entry.item;
});
data.sessionState.masteredPhrases = new Set(phraseItems);
}
if (savedProgress.masteredGrammar) {
const grammarItems = savedProgress.masteredGrammar.map(entry => {
return typeof entry === 'string' ? entry : entry.item;
});
data.sessionState.masteredGrammar = new Set(grammarItems);
}
console.log(`📁 Loaded existing progress: ${savedProgress.masteredVocabulary.length} vocab, ${savedProgress.masteredPhrases.length} phrases, mastery count: ${savedProgress.masteryCount}`);
} catch (error) {
console.warn('Failed to load existing progress:', error);
}
}
// Initialize prerequisites
await this._analyzePrerequisites(chapterContent);
// Generate exercise sequence
await this._generateExerciseSequence();
// Start AI reporting session
if (data.sharedServices.llmValidator && data.sharedServices.aiReportInterface) {
const sessionId = data.sharedServices.llmValidator.startReportSession({
bookId,
chapterId,
difficulty: this._config.difficulty || 'medium',
exerciseTypes: Array.from(data.availableModules.keys()),
totalExercises: data.sessionState.exerciseSequence.length
});
// Notify the report interface
data.sharedServices.aiReportInterface.onSessionStart({
bookId,
chapterId,
sessionId
});
console.log(`📊 Started AI report session: ${sessionId}`);
}
// Start with first available exercise
await this._startNextExercise();
// Emit session started event
this._eventBus.emit('drs:sessionStarted', {
bookId,
chapterId,
totalExercises: data.sessionState.exerciseSequence.length,
availableModules: Array.from(data.availableModules.keys())
}, this.name);
return true;
} catch (error) {
console.error('❌ Failed to start revision session:', error);
this._eventBus.emit('drs:sessionError', { error: error.message }, this.name);
return false;
}
}
/**
* Get available exercise modules based on current prerequisites
* @returns {Array<string>} - Available module names
*/
getAvailableModules() {
this._validateInitialized();
const data = privateData.get(this);
return Array.from(data.availableModules.keys());
}
/**
* Get shared services for external access
* @returns {Object} - Shared services
*/
getSharedServices() {
this._validateInitialized();
const data = privateData.get(this);
return data.sharedServices;
}
/**
* Switch to a different exercise module
* @param {string} moduleType - Type of module to switch to
* @returns {Promise<boolean>} - Success status
*/
async switchToModule(moduleType) {
this._validateInitialized();
try {
const data = privateData.get(this);
if (!data.availableModules.has(moduleType)) {
throw new Error(`Module type ${moduleType} is not available`);
}
// Unload current module
if (data.currentModule) {
await this._unloadModule(data.currentModule);
}
// Load new module
const module = await this._loadModule(moduleType);
data.currentModule = moduleType;
// Present exercise
const exerciseData = await this._getExerciseData(moduleType);
const container = document.getElementById('drs-exercise-container');
await module.present(container, exerciseData);
this._eventBus.emit('drs:moduleActivated', { moduleType, exerciseData }, this.name);
return true;
} catch (error) {
console.error(`❌ Failed to switch to module ${moduleType}:`, error);
return false;
}
}
/**
* Get current session progress
* @returns {Object} - Progress information
*/
getSessionProgress() {
this._validateInitialized();
const data = privateData.get(this);
const state = data.sessionState;
return {
currentChapter: state.currentChapter,
masteredVocabulary: state.masteredVocabulary.size,
masteredPhrases: state.masteredPhrases.size,
masteredGrammar: state.masteredGrammar.size,
completedExercises: state.sequenceIndex,
totalExercises: state.exerciseSequence.length,
progressPercentage: Math.round((state.sequenceIndex / state.exerciseSequence.length) * 100)
};
}
// Private Methods
async _initializeSharedServices() {
console.log('🔧 Initializing shared services...');
const data = privateData.get(this);
try {
// Initialize LLMValidator (mock for now)
const { default: LLMValidator } = await import('./services/LLMValidator.js');
data.sharedServices.llmValidator = new LLMValidator(this._config.llm || {});
// Initialize AIReportInterface
const { default: AIReportInterface } = await import('../components/AIReportInterface.js');
data.sharedServices.aiReportInterface = new AIReportInterface(
data.sharedServices.llmValidator,
this._config.aiReporting || {}
);
// Initialize PrerequisiteEngine
const { default: PrerequisiteEngine } = await import('./services/PrerequisiteEngine.js');
data.sharedServices.prerequisiteEngine = new PrerequisiteEngine();
// Initialize ContextMemory
const { default: ContextMemory } = await import('./services/ContextMemory.js');
data.sharedServices.contextMemory = new ContextMemory();
console.log('✅ Shared services initialized');
} catch (error) {
console.error('❌ Failed to initialize shared services:', error);
throw error;
}
}
async _cleanupSharedServices() {
const data = privateData.get(this);
// Cleanup services if they have cleanup methods
Object.values(data.sharedServices).forEach(service => {
if (service && typeof service.cleanup === 'function') {
service.cleanup();
}
});
}
_setupEventListeners() {
this._eventBus.on('drs:startSession', this._handleStartSession.bind(this), this.name);
this._eventBus.on('drs:switchModule', this._handleSwitchModule.bind(this), this.name);
this._eventBus.on('drs:updateProgress', this._handleUpdateProgress.bind(this), this.name);
this._eventBus.on('drs:exerciseCompleted', this._handleExerciseCompleted.bind(this), this.name);
}
_registerModuleTypes() {
const data = privateData.get(this);
// Register all available module types
Object.keys(data.moduleRegistry).forEach(moduleType => {
data.availableModules.set(moduleType, {
path: data.moduleRegistry[moduleType],
loaded: false,
instance: null
});
});
}
async _loadModule(moduleType) {
const data = privateData.get(this);
const moduleInfo = data.availableModules.get(moduleType);
if (!moduleInfo) {
throw new Error(`Unknown module type: ${moduleType}`);
}
if (data.loadedModules.has(moduleType)) {
return data.loadedModules.get(moduleType);
}
try {
console.log(`📦 Loading module: ${moduleType}`);
// Dynamic import of module
const modulePath = moduleInfo.path.startsWith('./') ?
moduleInfo.path : `./${moduleInfo.path}`;
const { default: ModuleClass } = await import(modulePath);
// Create instance with shared services
const moduleInstance = new ModuleClass(
this, // orchestrator reference
data.sharedServices.llmValidator,
data.sharedServices.prerequisiteEngine,
data.sharedServices.contextMemory
);
// Initialize module
await moduleInstance.init();
data.loadedModules.set(moduleType, moduleInstance);
moduleInfo.loaded = true;
moduleInfo.instance = moduleInstance;
console.log(`✅ Module loaded: ${moduleType}`);
return moduleInstance;
} catch (error) {
console.error(`❌ Failed to load module ${moduleType}:`, error);
throw error;
}
}
async _unloadModule(moduleType) {
const data = privateData.get(this);
const module = data.loadedModules.get(moduleType);
if (module) {
try {
await module.cleanup();
data.loadedModules.delete(moduleType);
const moduleInfo = data.availableModules.get(moduleType);
if (moduleInfo) {
moduleInfo.loaded = false;
moduleInfo.instance = null;
}
console.log(`📤 Module unloaded: ${moduleType}`);
} catch (error) {
console.error(`❌ Error unloading module ${moduleType}:`, error);
}
}
}
async _unloadAllModules() {
const data = privateData.get(this);
const moduleTypes = Array.from(data.loadedModules.keys());
for (const moduleType of moduleTypes) {
await this._unloadModule(moduleType);
}
}
async _analyzePrerequisites(chapterContent) {
const data = privateData.get(this);
// Use PrerequisiteEngine to analyze chapter content
const prerequisites = data.sharedServices.prerequisiteEngine.analyzeChapter(chapterContent);
console.log('📊 Prerequisites analyzed:', prerequisites);
}
async _generateExerciseSequence() {
const data = privateData.get(this);
// Generate exercise sequence based on content and mastery
const chapterContent = data.sessionState.chapterContent;
const masteredVocab = data.sessionState.masteredVocabulary;
const masteredPhrases = data.sessionState.masteredPhrases;
// Filter content to focus on non-mastered items
const allVocab = Object.keys(chapterContent.vocabulary || {});
const allPhrases = Object.keys(chapterContent.phrases || {});
const unmasteredVocab = allVocab.filter(word => !masteredVocab.has(word));
const unmasteredPhrases = allPhrases.filter(phrase => !masteredPhrases.has(phrase));
console.log(`📊 Content analysis:`);
console.log(` 📚 Vocabulary: ${unmasteredVocab.length}/${allVocab.length} unmastered`);
console.log(` 💬 Phrases: ${unmasteredPhrases.length}/${allPhrases.length} unmastered`);
const sequence = [];
// Create vocabulary groups (focus on unmastered, but include some mastered for review)
const vocabGroupSize = 5;
const vocabGroups = Math.ceil(unmasteredVocab.length / vocabGroupSize);
for (let i = 0; i < vocabGroups; i++) {
sequence.push({
type: 'vocabulary',
subtype: 'group',
groupSize: vocabGroupSize,
groupIndex: i,
adaptive: true // Mark as adaptive sequence
});
}
// Add unmastered phrases (prioritize new content)
unmasteredPhrases.forEach((phrase, index) => {
if (index < 10) { // Limit to 10 phrases per session
sequence.push({
type: 'phrase',
subtype: 'individual',
index: allPhrases.indexOf(phrase),
adaptive: true
});
}
});
// Add some review items if we have extra capacity
if (sequence.length < 15) {
const reviewVocab = [...masteredVocab].slice(0, 3);
const reviewPhrases = [...masteredPhrases].slice(0, 2);
reviewVocab.forEach(word => {
sequence.push({
type: 'vocabulary',
subtype: 'review',
word: word,
adaptive: true
});
});
reviewPhrases.forEach(phrase => {
sequence.push({
type: 'phrase',
subtype: 'review',
index: allPhrases.indexOf(phrase),
adaptive: true
});
});
}
// Shuffle for variety
for (let i = sequence.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[sequence[i], sequence[j]] = [sequence[j], sequence[i]];
}
const adaptiveInfo = unmasteredVocab.length === 0 && unmasteredPhrases.length === 0 ?
' (Review mode - all content mastered!)' :
' (Adaptive - focusing on unmastered content)';
console.log(`🧠 Generated adaptive sequence: ${sequence.length} exercises${adaptiveInfo}`);
data.sessionState.exerciseSequence = sequence;
data.sessionState.sequenceIndex = 0;
}
async _startNextExercise() {
const data = privateData.get(this);
const sequence = data.sessionState.exerciseSequence;
const currentIndex = data.sessionState.sequenceIndex;
if (currentIndex >= sequence.length) {
// End AI reporting session
if (data.sharedServices.llmValidator && data.sharedServices.aiReportInterface) {
const sessionStats = this.getSessionProgress();
// End the report session
data.sharedServices.llmValidator.endReportSession();
// Notify the report interface
data.sharedServices.aiReportInterface.onSessionEnd({
exerciseCount: sequence.length,
averageScore: sessionStats.averageScore || 0,
completedAt: new Date()
});
console.log('📊 Ended AI report session');
}
// Session complete - mark as completed and save
const currentChapter = data.sessionState.currentChapter;
if (currentChapter && window.markChapterCompleted) {
try {
await window.markChapterCompleted(currentChapter.bookId, currentChapter.chapterId);
console.log(`🏆 Chapter marked as completed: ${currentChapter.bookId}/${currentChapter.chapterId}`);
} catch (error) {
console.warn('Failed to mark chapter as completed:', error);
}
}
this._eventBus.emit('drs:sessionComplete', this.getSessionProgress(), this.name);
return;
}
const exercise = sequence[currentIndex];
await this.switchToModule(exercise.type);
}
async _getExerciseData(moduleType) {
const data = privateData.get(this);
const chapterContent = data.sessionState.chapterContent;
const sequence = data.sessionState.exerciseSequence;
const currentExercise = sequence[data.sessionState.sequenceIndex];
// Generate exercise data based on module type and current exercise parameters
switch (moduleType) {
case 'vocabulary':
return this._generateVocabularyExerciseData(chapterContent, currentExercise);
case 'phrase':
return this._generatePhraseExerciseData(chapterContent, currentExercise);
case 'text':
return this._generateTextExerciseData(chapterContent, currentExercise);
default:
return { type: moduleType, content: chapterContent };
}
}
_generateVocabularyExerciseData(chapterContent, exercise) {
const vocabulary = chapterContent.vocabulary || {};
const vocabArray = Object.entries(vocabulary);
const startIndex = exercise.groupIndex * exercise.groupSize;
const endIndex = Math.min(startIndex + exercise.groupSize, vocabArray.length);
const vocabGroup = vocabArray.slice(startIndex, endIndex);
return {
type: 'vocabulary',
subtype: exercise.subtype,
groupIndex: exercise.groupIndex,
vocabulary: vocabGroup.map(([word, data]) => ({
word,
translation: data.user_language,
pronunciation: data.pronunciation,
type: data.type
}))
};
}
_generatePhraseExerciseData(chapterContent, exercise) {
const phrases = chapterContent.phrases || {};
const phraseEntries = Object.entries(phrases);
const phraseIndex = exercise.index || 0;
// Check if phrase exists at this index
if (phraseIndex >= phraseEntries.length) {
console.warn(`⚠️ Phrase at index ${phraseIndex} not found (total: ${phraseEntries.length})`);
return null;
}
const [phraseText, phraseData] = phraseEntries[phraseIndex];
// Create phrase object for compatibility
const phrase = {
id: `phrase_${phraseIndex}`,
english: phraseText,
text: phraseText,
translation: phraseData.user_language,
user_language: phraseData.user_language,
pronunciation: phraseData.pronunciation,
context: phraseData.context || 'general',
...phraseData
};
// Verify prerequisites for this phrase
const data = privateData.get(this);
const unlockStatus = data.sharedServices.prerequisiteEngine.canUnlock('phrase', phrase);
return {
type: 'phrase',
subtype: exercise.subtype,
phrase: phrase,
phraseIndex: phraseIndex,
totalPhrases: phraseEntries.length,
unlockStatus: unlockStatus,
chapterContent: chapterContent, // For language detection
metadata: {
userLanguage: chapterContent.metadata?.userLanguage || 'English',
targetLanguage: chapterContent.metadata?.targetLanguage || 'French'
}
};
}
_generateTextExerciseData(chapterContent, exercise) {
const texts = chapterContent.texts || [];
const textIndex = exercise.textIndex || 0;
const text = texts[textIndex];
return {
type: 'text',
subtype: exercise.subtype,
text,
sentenceIndex: exercise.sentenceIndex || 0
};
}
// Event Handlers
async _handleStartSession(event) {
const { bookId, chapterId } = event.data;
await this.startRevisionSession(bookId, chapterId);
}
async _handleSwitchModule(event) {
const { moduleType } = event.data;
await this.switchToModule(moduleType);
}
async _handleUpdateProgress(event) {
const data = privateData.get(this);
const { type, item, mastered } = event.data;
const currentChapter = data.sessionState.currentChapter;
if (type === 'vocabulary' && mastered) {
data.sessionState.masteredVocabulary.add(item);
// Save to persistent storage with metadata
if (currentChapter && window.addMasteredItem) {
try {
const metadata = {
exerciseType: 'vocabulary',
sessionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
moduleType: 'VocabularyModule',
sequenceIndex: data.sessionState.sequenceIndex
};
await window.addMasteredItem(currentChapter.bookId, currentChapter.chapterId, 'vocabulary', item, metadata);
} catch (error) {
console.warn('Failed to save vocabulary progress:', error);
}
}
} else if (type === 'phrase' && mastered) {
data.sessionState.masteredPhrases.add(item);
// Save to persistent storage with metadata
if (currentChapter && window.addMasteredItem) {
try {
const metadata = {
exerciseType: 'phrase',
sessionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
moduleType: 'PhraseModule',
sequenceIndex: data.sessionState.sequenceIndex,
aiValidated: true
};
await window.addMasteredItem(currentChapter.bookId, currentChapter.chapterId, 'phrases', item, metadata);
} catch (error) {
console.warn('Failed to save phrase progress:', error);
}
}
} else if (type === 'grammar' && mastered) {
data.sessionState.masteredGrammar.add(item);
// Save to persistent storage with metadata
if (currentChapter && window.addMasteredItem) {
try {
const metadata = {
exerciseType: 'grammar',
sessionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
moduleType: 'GrammarModule',
sequenceIndex: data.sessionState.sequenceIndex
};
await window.addMasteredItem(currentChapter.bookId, currentChapter.chapterId, 'grammar', item, metadata);
} catch (error) {
console.warn('Failed to save grammar progress:', error);
}
}
}
this._eventBus.emit('drs:progressUpdated', this.getSessionProgress(), this.name);
}
async _handleExerciseCompleted(event) {
const data = privateData.get(this);
data.sessionState.sequenceIndex++;
// Move to next exercise
await this._startNextExercise();
}
/**
* Get mastery progress from PrerequisiteEngine
* @returns {Object} - Progress statistics
*/
getMasteryProgress() {
this._validateInitialized();
const data = privateData.get(this);
if (!data.sharedServices.prerequisiteEngine) {
return {
vocabulary: { mastered: 0, total: 0, percentage: 0 },
phrases: { mastered: 0, total: 0, percentage: 0 },
grammar: { mastered: 0, total: 0, percentage: 0 }
};
}
return data.sharedServices.prerequisiteEngine.getMasteryProgress();
}
/**
* Get list of mastered words from PrerequisiteEngine
* @returns {Array} - Array of mastered words
*/
getMasteredWords() {
this._validateInitialized();
const data = privateData.get(this);
if (!data.sharedServices.prerequisiteEngine) {
return [];
}
return Array.from(data.sharedServices.prerequisiteEngine.masteredWords);
}
}
export default SmartPreviewOrchestrator;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,779 +0,0 @@
/**
* GrammarAnalysisModule - Open grammar correction with AI feedback
* Presents grammar exercises with open correction fields, validates using real AI
* Implements DRSExerciseInterface for strict contract enforcement
*/
import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js';
class GrammarAnalysisModule extends DRSExerciseInterface {
constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) {
super('GrammarAnalysisModule');
// Validate dependencies
if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) {
throw new Error('GrammarAnalysisModule requires all service dependencies');
}
this.orchestrator = orchestrator;
this.llmValidator = llmValidator;
this.prerequisiteEngine = prerequisiteEngine;
this.contextMemory = contextMemory;
// Module state
this.initialized = false;
this.container = null;
this.currentExerciseData = null;
this.currentSentences = [];
this.sentenceIndex = 0;
this.userCorrections = [];
this.validationInProgress = false;
this.lastValidationResult = null;
// Configuration
this.config = {
requiredProvider: 'openai', // Grammar analysis needs precision
model: 'gpt-4o-mini',
temperature: 0.1, // Very low for grammar precision
maxTokens: 800,
timeout: 45000,
sentencesPerExercise: 3, // Number of sentences to correct
showOriginalSentence: true, // Keep original visible during correction
allowMultipleAttempts: true,
correctnessThreshold: 0.8 // Minimum score to consider correct
};
// Progress tracking
this.progress = {
sentencesCompleted: 0,
sentencesCorrect: 0,
averageAccuracy: 0,
grammarRulesLearned: new Set(),
commonMistakes: new Set(),
totalTimeSpent: 0,
lastActivity: null,
improvementAreas: []
};
// UI elements cache
this.elements = {
sentenceContainer: null,
originalSentence: null,
correctionArea: null,
submitButton: null,
feedbackContainer: null,
progressIndicator: null,
rulesPanel: null
};
}
/**
* Check if module can run with current content
*/
canRun(prerequisites, chapterContent) {
// Check if we have grammar content or can generate it
const hasGrammarContent = chapterContent &&
(chapterContent.grammar || chapterContent.sentences || chapterContent.texts);
if (!hasGrammarContent) {
console.log('❌ GrammarAnalysisModule: No grammar content available');
return false;
}
// Check if LLM validator is available
if (!this.llmValidator || !this.llmValidator.isAvailable()) {
console.log('❌ GrammarAnalysisModule: LLM validator not available');
return false;
}
console.log('✅ GrammarAnalysisModule: Can run with available content');
return true;
}
/**
* Present exercise UI and content
*/
async present(container, exerciseData) {
console.log('📝 GrammarAnalysisModule: Starting presentation');
this.container = container;
this.currentExerciseData = exerciseData;
try {
// Clear container
container.innerHTML = '';
// Prepare grammar sentences
await this._prepareSentences();
// Create UI layout
this._createUI();
// Show first sentence
this._displayCurrentSentence();
this.initialized = true;
console.log('✅ GrammarAnalysisModule: Presentation ready');
} catch (error) {
console.error('❌ GrammarAnalysisModule presentation failed:', error);
this._showError('Failed to load grammar exercise. Please try again.');
}
}
/**
* Validate user correction with AI
*/
async validate(userCorrection, context) {
console.log('🔍 GrammarAnalysisModule: Validating grammar correction');
if (this.validationInProgress) {
console.log('⏳ Validation already in progress');
return this.lastValidationResult;
}
const originalSentence = this.currentSentences[this.sentenceIndex];
// Basic input validation
if (!userCorrection || userCorrection.trim().length === 0) {
return {
success: false,
score: 0,
feedback: 'Please provide a correction for the sentence.',
suggestions: ['Read the sentence carefully', 'Look for grammar errors', 'Make your corrections']
};
}
// Check if user actually made changes
if (userCorrection.trim() === originalSentence.incorrect.trim()) {
return {
success: false,
score: 0,
feedback: 'You haven\'t made any changes to the original sentence. Please identify and correct the grammar errors.',
suggestions: [
'Look for verb tense errors',
'Check subject-verb agreement',
'Review word order',
'Check for missing or extra words'
]
};
}
this.validationInProgress = true;
try {
// Prepare context for grammar validation
const grammarContext = {
originalSentence: originalSentence.incorrect,
correctSentence: originalSentence.correct || null,
userCorrection: userCorrection.trim(),
grammarRule: originalSentence.rule || null,
difficulty: this.currentExerciseData.difficulty || 'medium',
language: 'English',
errorType: originalSentence.errorType || 'general',
...context
};
// Use LLMValidator for grammar analysis
const result = await this.llmValidator.validateGrammar(
userCorrection.trim(),
grammarContext
);
// Process and enhance the result
const enhancedResult = this._processGrammarResult(result, userCorrection, originalSentence);
// Store result and update progress
this.lastValidationResult = enhancedResult;
this._updateProgress(enhancedResult, originalSentence);
console.log('✅ GrammarAnalysisModule: Validation completed', enhancedResult);
return enhancedResult;
} catch (error) {
console.error('❌ GrammarAnalysisModule validation failed:', error);
return {
success: false,
score: 0,
feedback: 'Unable to analyze your grammar correction due to a technical issue. Please try again.',
error: error.message,
suggestions: ['Check your internet connection', 'Try a simpler correction', 'Contact support if the problem persists']
};
} finally {
this.validationInProgress = false;
}
}
/**
* Get current progress
*/
getProgress() {
return {
...this.progress,
currentSentence: this.sentenceIndex + 1,
totalSentences: this.currentSentences.length,
moduleType: 'grammar-analysis',
completionRate: this._calculateCompletionRate(),
accuracyRate: this.progress.sentencesCompleted > 0 ?
this.progress.sentencesCorrect / this.progress.sentencesCompleted : 0
};
}
/**
* Clean up resources
*/
cleanup() {
// Remove event listeners
if (this.elements.submitButton) {
this.elements.submitButton.removeEventListener('click', this._handleSubmit.bind(this));
}
if (this.elements.correctionArea) {
this.elements.correctionArea.removeEventListener('input', this._handleInputChange.bind(this));
}
// Clear references
this.container = null;
this.currentExerciseData = null;
this.elements = {};
this.initialized = false;
console.log('🧹 GrammarAnalysisModule: Cleaned up');
}
/**
* Get module metadata
*/
getMetadata() {
return {
name: 'GrammarAnalysisModule',
version: '1.0.0',
description: 'Open grammar correction with AI feedback',
author: 'DRS System',
capabilities: [
'grammar-correction',
'ai-validation',
'rule-explanation',
'mistake-analysis',
'progress-tracking'
],
requiredServices: ['llmValidator', 'orchestrator', 'prerequisiteEngine', 'contextMemory'],
supportedLanguages: ['English'],
exerciseTypes: ['error-correction', 'sentence-improvement', 'grammar-rules']
};
}
// Private methods
async _prepareSentences() {
// Extract or generate grammar sentences
if (this.currentExerciseData.sentences && this.currentExerciseData.sentences.length > 0) {
// Use provided sentences
this.currentSentences = this.currentExerciseData.sentences.slice(0, this.config.sentencesPerExercise);
} else if (this.currentExerciseData.grammar && this.currentExerciseData.grammar.length > 0) {
// Use grammar exercises
this.currentSentences = this.currentExerciseData.grammar.slice(0, this.config.sentencesPerExercise);
} else {
// Generate sentences using AI
await this._generateSentencesWithAI();
}
// Ensure we have the required format
this.currentSentences = this.currentSentences.map(sentence => {
if (typeof sentence === 'string') {
return {
incorrect: sentence,
rule: 'Grammar correction',
errorType: 'general'
};
}
return {
incorrect: sentence.incorrect || sentence.text || sentence,
correct: sentence.correct || null,
rule: sentence.rule || 'Grammar correction',
errorType: sentence.errorType || sentence.type || 'general',
explanation: sentence.explanation || null
};
});
console.log('📚 Grammar sentences prepared:', this.currentSentences.length);
}
async _generateSentencesWithAI() {
// Use the orchestrator's IAEngine to generate grammar exercises
try {
const difficulty = this.currentExerciseData.difficulty || 'medium';
const topic = this.currentExerciseData.topic || 'general grammar';
const prompt = `Generate 3 grammar correction exercises for ${difficulty} level English learners.
Topic focus: ${topic}
Requirements:
- Create sentences with common grammar mistakes that students need to correct
- Include variety: verb tenses, subject-verb agreement, word order, articles, etc.
- Make errors realistic but clear
- Suitable for ${difficulty} level
- Each sentence should have 1-2 clear grammar errors
Return ONLY valid JSON:
[
{
"incorrect": "She don't likes to swimming in the cold water",
"errorType": "verb-agreement",
"rule": "Subject-verb agreement and gerund usage",
"explanation": "Use 'doesn't' with third person singular and 'like swimming' not 'likes to swimming'"
}
]`;
const sharedServices = this.orchestrator.getSharedServices();
if (sharedServices && sharedServices.iaEngine) {
const result = await sharedServices.iaEngine.validateEducationalContent(prompt, {
systemPrompt: 'You are a grammar expert. Create realistic grammar mistakes for students to correct.',
temperature: 0.3
});
if (result && result.content) {
try {
this.currentSentences = JSON.parse(result.content);
console.log('✅ Generated grammar sentences with AI:', this.currentSentences.length);
return;
} catch (parseError) {
console.warn('Failed to parse AI-generated sentences');
}
}
}
} catch (error) {
console.warn('Failed to generate sentences with AI:', error);
}
// Fallback: basic grammar sentences
this.currentSentences = [
{
incorrect: "She don't like to go shopping on weekends.",
errorType: "verb-agreement",
rule: "Subject-verb agreement",
explanation: "Use 'doesn't' with third person singular subjects"
},
{
incorrect: "I have been living here since five years.",
errorType: "preposition",
rule: "Prepositions with time expressions",
explanation: "Use 'for' with duration, 'since' with point in time"
},
{
incorrect: "The book what I reading is very interesting.",
errorType: "relative-pronoun",
rule: "Relative pronouns and present continuous",
explanation: "Use 'that/which' as relative pronoun and 'am reading' for present continuous"
}
];
}
_createUI() {
const container = this.container;
container.innerHTML = `
<div class="grammar-analysis-module">
<div class="progress-indicator" id="progressIndicator">
Sentence <span id="currentSentence">1</span> of <span id="totalSentences">${this.currentSentences.length}</span>
</div>
<div class="sentence-display" id="sentenceContainer">
<h3>🔍 Find and Correct the Grammar Errors</h3>
<div class="original-sentence" id="originalSentence"></div>
<div class="error-info" id="errorInfo"></div>
</div>
<div class="correction-section">
<h3> Your Correction</h3>
<div class="correction-container">
<textarea
id="correctionArea"
placeholder="Type the corrected sentence here..."
rows="3"
></textarea>
<div class="correction-tips">
<strong>💡 Tips:</strong> Look for verb tenses, subject-verb agreement, word order, articles, and prepositions
</div>
</div>
<button id="submitButton" class="submit-correction" disabled>
Check My Correction
</button>
</div>
<div class="feedback-section" id="feedbackContainer" style="display: none;">
<h3>📋 Grammar Analysis</h3>
<div class="feedback-content" id="feedbackContent"></div>
<div class="grammar-rules" id="grammarRules"></div>
<div class="action-buttons" id="actionButtons"></div>
</div>
</div>
`;
// Cache elements
this.elements = {
sentenceContainer: container.querySelector('#sentenceContainer'),
originalSentence: container.querySelector('#originalSentence'),
correctionArea: container.querySelector('#correctionArea'),
submitButton: container.querySelector('#submitButton'),
feedbackContainer: container.querySelector('#feedbackContainer'),
progressIndicator: container.querySelector('#progressIndicator')
};
// Add event listeners
this.elements.correctionArea.addEventListener('input', this._handleInputChange.bind(this));
this.elements.submitButton.addEventListener('click', this._handleSubmit.bind(this));
}
_displayCurrentSentence() {
const sentence = this.currentSentences[this.sentenceIndex];
// Update sentence display
this.elements.originalSentence.innerHTML = `
<div class="sentence-text">"${sentence.incorrect}"</div>
`;
// Update error info
const errorInfo = document.getElementById('errorInfo');
if (sentence.rule || sentence.errorType) {
errorInfo.innerHTML = `
<div class="error-type">
<strong>Focus:</strong> ${sentence.rule || sentence.errorType}
</div>
`;
}
// Update progress
document.getElementById('currentSentence').textContent = this.sentenceIndex + 1;
document.getElementById('totalSentences').textContent = this.currentSentences.length;
// Reset correction area
this.elements.correctionArea.value = sentence.incorrect; // Start with original sentence
this.elements.correctionArea.disabled = false;
this.elements.submitButton.disabled = true;
// Hide previous feedback
this.elements.feedbackContainer.style.display = 'none';
// Focus on correction area
setTimeout(() => this.elements.correctionArea.focus(), 100);
}
_handleInputChange() {
const originalSentence = this.currentSentences[this.sentenceIndex].incorrect;
const userInput = this.elements.correctionArea.value.trim();
// Enable submit if user made changes
const hasChanges = userInput !== originalSentence && userInput.length > 0;
this.elements.submitButton.disabled = !hasChanges;
}
async _handleSubmit() {
const userCorrection = this.elements.correctionArea.value.trim();
const originalSentence = this.currentSentences[this.sentenceIndex];
if (!userCorrection || userCorrection === originalSentence.incorrect) {
this._showValidationError('Please make corrections to the sentence.');
return;
}
// Disable UI during validation
this.elements.correctionArea.disabled = true;
this.elements.submitButton.disabled = true;
this.elements.submitButton.textContent = '🔄 Analyzing...';
try {
// Validate with AI
const result = await this.validate(userCorrection, {
expectedRule: originalSentence.rule,
errorType: originalSentence.errorType
});
// Show feedback
this._displayFeedback(result, originalSentence);
// Store correction
this.userCorrections[this.sentenceIndex] = {
original: originalSentence.incorrect,
userCorrection: userCorrection,
result: result,
timestamp: new Date().toISOString()
};
} catch (error) {
console.error('Validation error:', error);
this._showValidationError('Failed to analyze your correction. Please try again.');
}
// Re-enable UI
this.elements.correctionArea.disabled = false;
this.elements.submitButton.textContent = '✅ Check My Correction';
this._handleInputChange();
}
_displayFeedback(result, originalSentence) {
const feedbackContent = document.getElementById('feedbackContent');
const grammarRules = document.getElementById('grammarRules');
const actionButtons = document.getElementById('actionButtons');
// Format feedback based on result
let feedbackHTML = '';
if (result.success && result.score >= this.config.correctnessThreshold) {
feedbackHTML = `
<div class="feedback-score">
<span class="score-label">Accuracy:</span>
<span class="score-value correct">${Math.round(result.score * 100)}%</span>
</div>
<div class="feedback-text correct">
<strong> Excellent correction!</strong><br>
${result.feedback}
</div>
`;
} else {
feedbackHTML = `
<div class="feedback-score">
<span class="score-label">Accuracy:</span>
<span class="score-value needs-work">${Math.round((result.score || 0) * 100)}%</span>
</div>
<div class="feedback-text needs-improvement">
<strong>📝 Needs improvement:</strong><br>
${result.feedback}
</div>
`;
}
feedbackContent.innerHTML = feedbackHTML;
// Show grammar rules and explanations
let rulesHTML = '';
if (originalSentence.rule) {
rulesHTML += `
<div class="grammar-rule">
<strong>📚 Grammar Rule:</strong> ${originalSentence.rule}
</div>
`;
}
if (result.explanation) {
rulesHTML += `
<div class="detailed-explanation">
<strong>💡 Explanation:</strong><br>
${result.explanation}
</div>
`;
}
if (result.suggestions && result.suggestions.length > 0) {
rulesHTML += `
<div class="suggestions">
<strong>🎯 Tips for improvement:</strong>
<ul>
${result.suggestions.map(s => `<li>${s}</li>`).join('')}
</ul>
</div>
`;
}
grammarRules.innerHTML = rulesHTML;
// Create action buttons
let buttonsHTML = '';
if (this.config.allowMultipleAttempts && result.score < this.config.correctnessThreshold) {
buttonsHTML += `<button class="retry-button" onclick="this.closest('.grammar-analysis-module').querySelector('#correctionArea').focus()">🔄 Try Again</button>`;
}
if (this.sentenceIndex < this.currentSentences.length - 1) {
buttonsHTML += `<button class="next-button" onclick="this._nextSentence()">➡️ Next Sentence</button>`;
} else {
buttonsHTML += `<button class="finish-button" onclick="this._finishExercise()">🎉 Finish Exercise</button>`;
}
actionButtons.innerHTML = buttonsHTML;
// Show feedback section
this.elements.feedbackContainer.style.display = 'block';
this.elements.feedbackContainer.scrollIntoView({ behavior: 'smooth' });
}
_nextSentence() {
if (this.sentenceIndex < this.currentSentences.length - 1) {
this.sentenceIndex++;
this._displayCurrentSentence();
}
}
_finishExercise() {
// Calculate final statistics
const totalCorrect = this.userCorrections.filter(c =>
c.result && c.result.score >= this.config.correctnessThreshold
).length;
const completionData = {
moduleType: 'grammar-analysis',
corrections: this.userCorrections,
progress: this.getProgress(),
finalStats: {
totalSentences: this.currentSentences.length,
correctSentences: totalCorrect,
accuracyRate: totalCorrect / this.currentSentences.length,
grammarRulesLearned: this.progress.grammarRulesLearned.size,
improvementAreas: this.progress.improvementAreas
}
};
// Use orchestrator to handle completion
if (this.orchestrator && this.orchestrator.handleExerciseCompletion) {
this.orchestrator.handleExerciseCompletion(completionData);
}
console.log('🎉 GrammarAnalysisModule: Exercise completed', completionData);
}
_processGrammarResult(result, userCorrection, originalSentence) {
// Enhance the raw LLM result
const enhanced = {
...result,
inputLength: userCorrection.length,
originalLength: originalSentence.incorrect.length,
timestamp: new Date().toISOString(),
moduleType: 'grammar-analysis',
grammarRule: originalSentence.rule,
errorType: originalSentence.errorType
};
return enhanced;
}
_updateProgress(result, originalSentence) {
this.progress.sentencesCompleted++;
this.progress.lastActivity = new Date().toISOString();
// Count as correct if above threshold
if (result.score >= this.config.correctnessThreshold) {
this.progress.sentencesCorrect++;
}
// Update average accuracy
const totalAccuracy = this.progress.averageAccuracy * (this.progress.sentencesCompleted - 1) + (result.score || 0);
this.progress.averageAccuracy = totalAccuracy / this.progress.sentencesCompleted;
// Track grammar rules learned
if (originalSentence.rule) {
this.progress.grammarRulesLearned.add(originalSentence.rule);
}
// Track improvement areas
if (result.score < this.config.correctnessThreshold) {
this.progress.improvementAreas.push(originalSentence.errorType || 'general');
}
}
_calculateCompletionRate() {
if (!this.currentSentences || this.currentSentences.length === 0) return 0;
return (this.sentenceIndex + 1) / this.currentSentences.length;
}
_showError(message) {
this.container.innerHTML = `
<div class="error-message">
<h3> Error</h3>
<p>${message}</p>
<button onclick="location.reload()">🔄 Try Again</button>
</div>
`;
}
_showValidationError(message) {
// Show temporary error message
const errorDiv = document.createElement('div');
errorDiv.className = 'validation-error';
errorDiv.textContent = message;
errorDiv.style.cssText = 'color: #e74c3c; padding: 10px; margin: 10px 0; border: 1px solid #e74c3c; border-radius: 4px; background: #ffebee;';
this.elements.correctionArea.parentNode.insertBefore(errorDiv, this.elements.correctionArea);
setTimeout(() => errorDiv.remove(), 5000);
}
// ========================================
// DRSExerciseInterface REQUIRED METHODS
// ========================================
async init(config = {}, content = {}) {
this.config = { ...this.config, ...config };
this.currentExerciseData = content;
this.startTime = Date.now();
this.initialized = true;
}
async render(container) {
if (!this.initialized) throw new Error('GrammarAnalysisModule must be initialized before rendering');
await this.present(container, this.currentExerciseData);
}
async destroy() {
this.cleanup();
}
getResults() {
const totalSentences = this.currentSentences.length;
const correctedSentences = this.userCorrections.length;
const correctSentences = this.progress.sentencesCorrect;
const score = totalSentences > 0 ? Math.round((correctSentences / totalSentences) * 100) : 0;
return {
score,
attempts: correctedSentences,
timeSpent: this.startTime ? Date.now() - this.startTime : 0,
completed: correctedSentences >= totalSentences,
details: {
totalSentences,
correctedSentences,
correctSentences,
accuracy: this.progress.averageAccuracy,
grammarRulesLearned: Array.from(this.progress.grammarRulesLearned),
corrections: this.userCorrections
}
};
}
handleUserInput(event, data) {
if (event && event.type === 'input') this._handleInputChange?.(event);
if (event && event.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event);
}
async markCompleted(results) {
const { score, details } = results || this.getResults();
if (this.contextMemory) {
this.contextMemory.recordInteraction({
type: 'grammar-analysis',
subtype: 'completion',
content: { sentences: this.currentSentences },
userResponse: this.userCorrections,
validation: { score, accuracy: details.accuracy, grammarRulesLearned: details.grammarRulesLearned },
context: { moduleType: 'grammar-analysis', totalSentences: details.totalSentences }
});
}
}
getExerciseType() {
return 'grammar-analysis';
}
getExerciseConfig() {
const sentenceCount = this.currentSentences ? this.currentSentences.length : this.config.sentencesPerExercise;
return {
type: this.getExerciseType(),
difficulty: this.currentExerciseData?.difficulty || 'medium',
estimatedTime: sentenceCount * 2, // 2 min per sentence
prerequisites: [],
metadata: { ...this.config, sentenceCount, requiresAI: true, model: this.config.model }
};
}
}
export default GrammarAnalysisModule;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More