Clean repository: remove 42 debug, test and temporary files
- Remove all debug-*.js and test-*.* files from root directory - Remove temporary conversion and example files - Remove documentation files that cluttered root - Remove admin and start batch files - Keep only essential core files and organized directory structure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
dacc7e98a1
commit
cb0c3b01f8
@ -1,117 +0,0 @@
|
|||||||
# 🐉 ADVENTURE READER + DRAGON'S PEARL - CORRECTIONS
|
|
||||||
|
|
||||||
## 🔍 Problème Identifié
|
|
||||||
|
|
||||||
Adventure Reader ne pouvait pas utiliser le contenu de Dragon's Pearl car il cherchait le contenu dans une structure **ultra-modulaire** (`content.rawContent.texts[]`) alors que Dragon's Pearl utilise une structure **custom** (`content.story.chapters[].sentences[]`).
|
|
||||||
|
|
||||||
## 🔧 Corrections Apportées
|
|
||||||
|
|
||||||
### 1. Support Structure Dragon's Pearl
|
|
||||||
|
|
||||||
#### `extractSentences()` - Ligne 110-127
|
|
||||||
```javascript
|
|
||||||
// Support pour Dragon's Pearl structure: content.story.chapters[].sentences[]
|
|
||||||
if (content.story && content.story.chapters && Array.isArray(content.story.chapters)) {
|
|
||||||
content.story.chapters.forEach(chapter => {
|
|
||||||
if (chapter.sentences && Array.isArray(chapter.sentences)) {
|
|
||||||
chapter.sentences.forEach(sentence => {
|
|
||||||
if (sentence.original && sentence.translation) {
|
|
||||||
sentences.push({
|
|
||||||
original_language: sentence.original,
|
|
||||||
user_language: sentence.translation,
|
|
||||||
pronunciation: sentence.pronunciation || '',
|
|
||||||
chapter: chapter.title || '',
|
|
||||||
id: sentence.id || sentences.length
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `extractStories()` - Ligne 189-205
|
|
||||||
```javascript
|
|
||||||
// Support pour Dragon's Pearl structure
|
|
||||||
if (content.story && content.story.chapters && Array.isArray(content.story.chapters)) {
|
|
||||||
// Créer une histoire depuis les chapitres de Dragon's Pearl
|
|
||||||
stories.push({
|
|
||||||
title: content.story.title || content.name || "Dragon's Pearl",
|
|
||||||
original_language: content.story.chapters.map(ch =>
|
|
||||||
ch.sentences.map(s => s.original).join(' ')
|
|
||||||
).join('\n\n'),
|
|
||||||
user_language: content.story.chapters.map(ch =>
|
|
||||||
ch.sentences.map(s => s.translation).join(' ')
|
|
||||||
).join('\n\n'),
|
|
||||||
chapters: content.story.chapters.map(chapter => ({
|
|
||||||
title: chapter.title,
|
|
||||||
sentences: chapter.sentences
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `extractVocabulary()` - Ligne 78-100
|
|
||||||
```javascript
|
|
||||||
// Support pour Dragon's Pearl vocabulary structure
|
|
||||||
if (content.vocabulary && typeof content.vocabulary === 'object') {
|
|
||||||
vocabulary = Object.entries(content.vocabulary).map(([original_language, vocabData]) => {
|
|
||||||
if (typeof vocabData === 'string') {
|
|
||||||
// Simple format: "word": "translation"
|
|
||||||
return {
|
|
||||||
original_language: original_language,
|
|
||||||
user_language: vocabData,
|
|
||||||
type: 'unknown'
|
|
||||||
};
|
|
||||||
} else if (typeof vocabData === 'object') {
|
|
||||||
// Rich format: "word": { user_language: "translation", type: "noun", ... }
|
|
||||||
return {
|
|
||||||
original_language: original_language,
|
|
||||||
user_language: vocabData.user_language || vocabData.translation || 'No translation',
|
|
||||||
type: vocabData.type || 'unknown',
|
|
||||||
pronunciation: vocabData.pronunciation,
|
|
||||||
difficulty: vocabData.difficulty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}).filter(item => item !== null);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Résultats
|
|
||||||
|
|
||||||
Maintenant Adventure Reader peut :
|
|
||||||
|
|
||||||
### ✅ Extraire les Phrases de Dragon's Pearl
|
|
||||||
- **150+ phrases** des chapitres 1-4 de l'histoire
|
|
||||||
- **Chinois original + traduction anglaise**
|
|
||||||
- **Prononciation pinyin**
|
|
||||||
- **Organisation par chapitres**
|
|
||||||
|
|
||||||
### ✅ Utiliser le Vocabulaire
|
|
||||||
- **50+ mots chinois** avec traductions
|
|
||||||
- **Types grammaticaux** (noun, verb, adjective...)
|
|
||||||
- **Prononciation pinyin**
|
|
||||||
|
|
||||||
### ✅ Créer l'Histoire Complète
|
|
||||||
- **Titre** : "The Dragon's Pearl - 龙珠传说"
|
|
||||||
- **Chapitres structurés**
|
|
||||||
- **Texte complet** pour l'aventure
|
|
||||||
|
|
||||||
## 🎮 Fonctionnement en Jeu
|
|
||||||
|
|
||||||
Quand tu lances **Adventure Reader** avec **Dragon's Pearl** :
|
|
||||||
|
|
||||||
1. **Carte interactive** avec des pots 🏺 et ennemis 👹
|
|
||||||
2. **Clique sur les pots** → Affiche des **phrases chinoises** de l'histoire
|
|
||||||
3. **Combat les ennemis** → Teste ton **vocabulaire chinois**
|
|
||||||
4. **Modal de lecture** → Affiche **chinois + anglais + pinyin**
|
|
||||||
5. **Progression** → Traverse toute l'histoire de Li Ming et le Dragon
|
|
||||||
|
|
||||||
## 🔗 Compatibilité
|
|
||||||
|
|
||||||
✅ **Rétrocompatible** : Fonctionne toujours avec l'ancien format ultra-modulaire
|
|
||||||
✅ **Dragon's Pearl** : Support complet de la structure custom
|
|
||||||
✅ **Autres contenus** : Tous les autres modules continuent de fonctionner
|
|
||||||
|
|
||||||
Adventure Reader peut maintenant utiliser **toutes les phrases et tout le vocabulaire** de Dragon's Pearl pour créer une expérience d'apprentissage immersive ! 🐉✨
|
|
||||||
@ -1,240 +0,0 @@
|
|||||||
# 🎯 Système de Compatibilité Content-Game
|
|
||||||
|
|
||||||
## Vue d'Ensemble
|
|
||||||
|
|
||||||
Le système de compatibilité Content-Game analyse automatiquement le contenu éducatif et détermine quels jeux sont optimaux pour chaque type de contenu. Il fournit des scores de compatibilité, des recommandations visuelles, et des suggestions d'amélioration.
|
|
||||||
|
|
||||||
## 🔧 Architecture
|
|
||||||
|
|
||||||
### Composants Principaux
|
|
||||||
|
|
||||||
1. **`ContentGameCompatibility`** (`js/core/content-game-compatibility.js`)
|
|
||||||
- Moteur d'analyse de compatibilité
|
|
||||||
- Calculs de scores par type de jeu
|
|
||||||
- Système de suggestions d'amélioration
|
|
||||||
|
|
||||||
2. **Navigation Intelligente** (`js/core/navigation.js`)
|
|
||||||
- Intégration avec le système de compatibilité
|
|
||||||
- Affichage séparé des jeux compatibles/incompatibles
|
|
||||||
- Interface visuelle avec badges de compatibilité
|
|
||||||
|
|
||||||
3. **Interface Visuelle** (`css/main.css`)
|
|
||||||
- Styles pour les badges de compatibilité
|
|
||||||
- Couleurs et animations
|
|
||||||
- Modal d'aide pour jeux incompatibles
|
|
||||||
|
|
||||||
## 🎮 Fonctionnalités
|
|
||||||
|
|
||||||
### Analyse de Compatibilité
|
|
||||||
|
|
||||||
- **Scores automatiques** : 0-100% basés sur les besoins réels de chaque jeu
|
|
||||||
- **Seuils configurables** : Minimums personnalisables par jeu
|
|
||||||
- **Cache intelligent** : Optimisation des performances
|
|
||||||
|
|
||||||
### Interface Utilisateur
|
|
||||||
|
|
||||||
- **Badges visuels** :
|
|
||||||
- 🎯 **Excellent** (80%+) : Violet
|
|
||||||
- ✅ **Recommandé** (60-79%) : Vert
|
|
||||||
- 👍 **Compatible** (seuil-59%) : Bleu-vert
|
|
||||||
- ⚠️ **Limité** (<seuil) : Orange
|
|
||||||
|
|
||||||
- **Sections séparées** :
|
|
||||||
- "Jeux recommandés" : Compatibles, triés par score
|
|
||||||
- "Jeux avec limitations" : Incompatibles avec aide
|
|
||||||
|
|
||||||
- **Modal d'aide** : Suggestions d'amélioration pour jeux incompatibles
|
|
||||||
|
|
||||||
### Critères de Compatibilité par Jeu
|
|
||||||
|
|
||||||
#### Whack-a-Mole / Whack-a-Mole Hard
|
|
||||||
- **Minimum** : 5+ mots OU 3+ phrases
|
|
||||||
- **Idéal** : Vocabulaire varié + audio
|
|
||||||
- **Score** : Vocabulaire (40pts) + Phrases (30pts) + Audio (20pts)
|
|
||||||
|
|
||||||
#### Memory Match
|
|
||||||
- **Minimum** : 4+ paires vocabulaire-traduction
|
|
||||||
- **Idéal** : Avec images/audio
|
|
||||||
- **Score** : Vocabulaire (50pts) + Multimédia (30pts)
|
|
||||||
|
|
||||||
#### Quiz Game
|
|
||||||
- **Minimum** : Contenu de base (très flexible)
|
|
||||||
- **Idéal** : Exercices structurés
|
|
||||||
- **Score** : Vocabulaire (30pts) + Grammaire (25pts) + Exercices (45pts)
|
|
||||||
|
|
||||||
#### Fill the Blank
|
|
||||||
- **Minimum** : Phrases OU exercices à trous
|
|
||||||
- **Idéal** : Exercices dédiés fill-in-blanks
|
|
||||||
- **Score** : Exercices dédiés (70pts) OU Phrases adaptables (30pts)
|
|
||||||
|
|
||||||
#### Text Reader / Story Reader
|
|
||||||
- **Minimum** : 3+ phrases OU dialogues
|
|
||||||
- **Idéal** : Contenu narratif riche + audio
|
|
||||||
- **Score** : Phrases (40pts) + Dialogues (50pts) + Audio (10pts)
|
|
||||||
|
|
||||||
#### Adventure Reader
|
|
||||||
- **Minimum** : Dialogues + contenu narratif
|
|
||||||
- **Idéal** : Histoire cohérente complète
|
|
||||||
- **Score** : Dialogues (60pts) + Contenu riche (30pts) + Vocabulaire (10pts)
|
|
||||||
|
|
||||||
## 📊 Utilisation
|
|
||||||
|
|
||||||
### Intégration Automatique
|
|
||||||
|
|
||||||
Le système s'active automatiquement lors de la navigation :
|
|
||||||
|
|
||||||
1. L'utilisateur sélectionne un contenu
|
|
||||||
2. Le système analyse la compatibilité avec tous les jeux
|
|
||||||
3. Les jeux sont séparés et affichés avec leurs scores
|
|
||||||
4. L'interface guide vers les meilleurs choix
|
|
||||||
|
|
||||||
### Tests et Validation
|
|
||||||
|
|
||||||
#### Fichiers de Test Inclus
|
|
||||||
|
|
||||||
- **`test-minimal.js`** : Contenu minimal (2 mots) - démontre les limitations
|
|
||||||
- **`test-rich.js`** : Contenu riche complet - compatible avec tout
|
|
||||||
- **`test-final-compatibility.html`** : Interface de test complète
|
|
||||||
- **`test-node-compatibility.js`** : Tests unitaires Node.js
|
|
||||||
|
|
||||||
#### Commandes de Test
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test unitaire Node.js
|
|
||||||
node test-node-compatibility.js
|
|
||||||
|
|
||||||
# Test interface complète
|
|
||||||
http://localhost:8080/test-final-compatibility.html
|
|
||||||
|
|
||||||
# Validation application réelle
|
|
||||||
# (Charger verify-real-app.js dans la console)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 API Publique
|
|
||||||
|
|
||||||
### ContentGameCompatibility
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Initialisation
|
|
||||||
const checker = new ContentGameCompatibility();
|
|
||||||
|
|
||||||
// Vérifier compatibilité
|
|
||||||
const result = checker.checkCompatibility(content, gameType);
|
|
||||||
// result: { compatible, score, reason, requirements, details }
|
|
||||||
|
|
||||||
// Obtenir suggestions d'amélioration
|
|
||||||
const suggestions = checker.getImprovementSuggestions(content, gameType);
|
|
||||||
|
|
||||||
// Filtrer contenu compatible
|
|
||||||
const compatibleContent = checker.filterCompatibleContent(contentList, gameType);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Navigation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Navigation avec compatibilité automatique
|
|
||||||
AppNavigation.navigateTo('games', null, contentType);
|
|
||||||
|
|
||||||
// Le système affiche automatiquement:
|
|
||||||
// - Jeux compatibles en premier
|
|
||||||
// - Jeux incompatibles avec aide
|
|
||||||
// - Badges de compatibilité
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎨 Personnalisation
|
|
||||||
|
|
||||||
### Seuils de Compatibilité
|
|
||||||
|
|
||||||
Modifiez `minimumScores` dans `ContentGameCompatibility` :
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
this.minimumScores = {
|
|
||||||
'whack-a-mole': 40, // Seuil par défaut
|
|
||||||
'memory-match': 50,
|
|
||||||
'custom-game': 30 // Nouveau jeu
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nouveaux Types de Jeux
|
|
||||||
|
|
||||||
Ajoutez une fonction de calcul dans `ContentGameCompatibility` :
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
calculateCustomGameCompat(capabilities) {
|
|
||||||
let score = 0;
|
|
||||||
if (capabilities.hasVocabulary) score += 50;
|
|
||||||
if (capabilities.hasCustomFeature) score += 30;
|
|
||||||
|
|
||||||
return {
|
|
||||||
compatible: score >= 30,
|
|
||||||
score,
|
|
||||||
reason: `Compatible: ${reasons.join(', ')}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Styles Visuels
|
|
||||||
|
|
||||||
Modifiez les couleurs dans `css/main.css` :
|
|
||||||
|
|
||||||
```css
|
|
||||||
.compatibility-badge.excellent {
|
|
||||||
background: linear-gradient(135deg, #your-color, #your-color-2);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Déploiement
|
|
||||||
|
|
||||||
### Fichiers Requis
|
|
||||||
|
|
||||||
**Core System:**
|
|
||||||
- `js/core/content-game-compatibility.js`
|
|
||||||
- Modifications dans `js/core/navigation.js`
|
|
||||||
- Styles dans `css/main.css`
|
|
||||||
|
|
||||||
**Chargement HTML:**
|
|
||||||
```html
|
|
||||||
<script src="js/core/content-game-compatibility.js"></script>
|
|
||||||
<!-- Avant navigation.js -->
|
|
||||||
<script src="js/core/navigation.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Production
|
|
||||||
|
|
||||||
1. Le système fonctionne automatiquement une fois intégré
|
|
||||||
2. Aucune configuration supplémentaire requise
|
|
||||||
3. Compatible avec tous les contenus existants
|
|
||||||
4. Graceful fallback en cas d'erreur
|
|
||||||
|
|
||||||
## 🛠️ Maintenance
|
|
||||||
|
|
||||||
### Ajout de Nouveau Contenu
|
|
||||||
|
|
||||||
Le système détecte automatiquement les nouveaux modules. Aucune configuration requise.
|
|
||||||
|
|
||||||
### Débogage
|
|
||||||
|
|
||||||
Utilisez les logs intégrés :
|
|
||||||
```javascript
|
|
||||||
// Activer debug dans la console
|
|
||||||
window.AppNavigation.compatibilityChecker.clearCache();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Monitoring
|
|
||||||
|
|
||||||
Le système log automatiquement :
|
|
||||||
- Scores de compatibilité calculés
|
|
||||||
- Erreurs de chargement
|
|
||||||
- Suggestions générées
|
|
||||||
|
|
||||||
## ✅ Statut
|
|
||||||
|
|
||||||
**SYSTÈME COMPLET ET FONCTIONNEL**
|
|
||||||
|
|
||||||
- ✅ Analyse automatique de compatibilité
|
|
||||||
- ✅ Interface utilisateur intégrée
|
|
||||||
- ✅ Tests complets validés
|
|
||||||
- ✅ Documentation complète
|
|
||||||
- ✅ Prêt pour la production
|
|
||||||
|
|
||||||
Le système évite maintenant d'afficher des jeux incompatibles et guide l'utilisateur vers les meilleurs choix selon le contenu disponible!
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
|
|
||||||
🔧 CORRECTION APPLIQUÉE: CONVERSION HONNÊTE 🔧
|
|
||||||
================================================
|
|
||||||
|
|
||||||
❌ AVANT (SYSTÈME DÉFAILLANT):
|
|
||||||
- Inventait type, difficulty_level, semantic_category, usage_frequency
|
|
||||||
- Générais des examples et grammar_notes fantaisistes
|
|
||||||
- Hallucinait des métadonnées inexistantes
|
|
||||||
- Fichier: 9.8 KB avec des données inventées
|
|
||||||
|
|
||||||
✅ APRÈS (SYSTÈME CORRECT):
|
|
||||||
- Garde SEULEMENT les données réelles: mot → traduction
|
|
||||||
- Convertit le format mais n'invente RIEN
|
|
||||||
- Ajoute uniquement l'ID et la structure ultra-modulaire
|
|
||||||
- Fichier: 5.0 KB avec SEULEMENT des données vraies
|
|
||||||
|
|
||||||
🎯 DONNÉES CONSERVÉES (LÉGITIMES):
|
|
||||||
- vocabulary: central → 中心的;中央的 ✅
|
|
||||||
- sentences: anglais → chinois ✅
|
|
||||||
- difficulty_level: 7 (inféré du nom 'Level 7-8') ✅
|
|
||||||
- langues: détectées des données ✅
|
|
||||||
- tags: extraits du contenu réel ✅
|
|
||||||
|
|
||||||
🚫 DONNÉES SUPPRIMÉES (INVENTÉES):
|
|
||||||
- types de mots ❌
|
|
||||||
- niveaux de difficulté par mot ❌
|
|
||||||
- catégories sémantiques ❌
|
|
||||||
- fréquences d'usage ❌
|
|
||||||
- exemples artificiels ❌
|
|
||||||
- notes grammaticales ❌
|
|
||||||
- skills_covered, target_audience ❌
|
|
||||||
|
|
||||||
💡 PRINCIPE APPRIS:
|
|
||||||
Un bon système de conversion doit être HONNÊTE:
|
|
||||||
- Transformer le format ✅
|
|
||||||
- Préserver les données ✅
|
|
||||||
- JAMAIS inventer ❌
|
|
||||||
|
|
||||||
================================================
|
|
||||||
SYSTÈME CORRIGÉ: Conversion fidèle et transparente ✅
|
|
||||||
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
# 🎯 OPTIMISATION AFFICHAGE GRILLE DES JEUX
|
|
||||||
|
|
||||||
## ✅ Modifications Apportées
|
|
||||||
|
|
||||||
### 1. Taille Minimale des Colonnes Réduite
|
|
||||||
```css
|
|
||||||
/* AVANT */
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
|
|
||||||
/* APRÈS */
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Cartes Plus Compactes
|
|
||||||
- **Padding** : `30px 25px` → `20px 15px`
|
|
||||||
- **Min-height** : `180px` → `160px`
|
|
||||||
- **Gap** : `25px` → `20px`
|
|
||||||
|
|
||||||
### 3. Contenu des Cartes Optimisé
|
|
||||||
- **Icon** : `3rem` → `2.5rem`
|
|
||||||
- **Title** : `1.4rem` → `1.2rem`
|
|
||||||
- **Description** : `0.95rem` → `0.85rem`
|
|
||||||
- **Marges** réduites
|
|
||||||
|
|
||||||
### 4. Responsive Amélioré
|
|
||||||
|
|
||||||
#### Desktop (1200px+)
|
|
||||||
```css
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
min-height: 140px;
|
|
||||||
```
|
|
||||||
**→ Jusqu'à 6 jeux par ligne sur grand écran**
|
|
||||||
|
|
||||||
#### Tablette (768px)
|
|
||||||
```css
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
```
|
|
||||||
**→ 3-4 jeux par ligne sur tablette**
|
|
||||||
|
|
||||||
#### Mobile (480px)
|
|
||||||
```css
|
|
||||||
padding: 20px 15px;
|
|
||||||
```
|
|
||||||
**→ 2 jeux par ligne même sur mobile**
|
|
||||||
|
|
||||||
## 🎯 Résultats Attendus
|
|
||||||
|
|
||||||
### Avant (1 jeu par ligne) ❌
|
|
||||||
```
|
|
||||||
[ Whack-a-Mole ]
|
|
||||||
[ Memory Match ]
|
|
||||||
[ Quiz Game ]
|
|
||||||
[ Fill the Blank ]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Après (Plusieurs jeux par ligne) ✅
|
|
||||||
```
|
|
||||||
[ Whack-a-Mole ] [ Memory Match ] [ Quiz Game ]
|
|
||||||
[ Fill Blank ] [ Text Reader ] [ Adventure ]
|
|
||||||
[ Story Reader ] [ Chinese Game ] [ Quiz Hard ]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📱 Adaptabilité
|
|
||||||
|
|
||||||
**Large Desktop (1400px+)** : 6-7 jeux/ligne
|
|
||||||
**Desktop (1200px)** : 5-6 jeux/ligne
|
|
||||||
**Laptop (1024px)** : 4-5 jeux/ligne
|
|
||||||
**Tablette (768px)** : 3-4 jeux/ligne
|
|
||||||
**Mobile (480px)** : 2 jeux/ligne
|
|
||||||
|
|
||||||
Navigation **beaucoup plus rapide** et **vue d'ensemble** des options disponibles ! 🚀
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
|
|
||||||
🏆 PREUVE ABSOLUE: SYSTÈME ULTRA-MODULAIRE FONCTIONNEL 🏆
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
✅ CHALLENGE RÉUSSI:
|
|
||||||
- Données JS chargées: 27 mots vocabulaire + 4 phrases
|
|
||||||
- Analysées en mémoire avec notre ContentScanner
|
|
||||||
- Converties en JSON ultra-modulaire complet
|
|
||||||
- Fichier généré: sbs-level-7-8-GENERATED-from-js.json (9.8 KB)
|
|
||||||
|
|
||||||
🎯 CAPACITÉS DÉTECTÉES:
|
|
||||||
- Vocabulaire: ✅ (27 mots)
|
|
||||||
- Phrases: ✅ (4 phrases)
|
|
||||||
- Profondeur vocab: 1/6 (format simple détecté)
|
|
||||||
- Richesse contenu: 2.7/10
|
|
||||||
|
|
||||||
🎮 COMPATIBILITÉ JEUX (4/4 COMPATIBLES):
|
|
||||||
- whack-a-mole: ✅ 54% (vocabulaire disponible)
|
|
||||||
- memory-match: ✅ 40.5% (vocabulaire visuel)
|
|
||||||
- quiz-game: ✅ 42% (contenu polyvalent)
|
|
||||||
- text-reader: ✅ 40% (phrases disponibles)
|
|
||||||
|
|
||||||
📊 QUALITÉ FINALE: 91/100 ⭐⭐⭐⭐⭐
|
|
||||||
|
|
||||||
🔄 SYSTÈME PROUVÉ:
|
|
||||||
JS → Analyse → JSON Ultra-Modulaire → Validation ✅
|
|
||||||
|
|
||||||
================================================================
|
|
||||||
Le défi est RELEVÉ ! Le système fonctionne parfaitement ! 🚀
|
|
||||||
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
# 🎯 RAPPORT : SYSTÈME DE COMPATIBILITÉ RÉSOLU
|
|
||||||
|
|
||||||
## 📋 Problème Identifié
|
|
||||||
|
|
||||||
**Symptôme** : En cliquant sur Dragon's Pearl, l'interface affichait toujours les mêmes messages génériques au lieu d'analyser la compatibilité réelle du contenu avec les jeux.
|
|
||||||
|
|
||||||
## 🔍 Analyse des Causes
|
|
||||||
|
|
||||||
### 1. Architecture en Deux Systèmes Parallèles
|
|
||||||
|
|
||||||
L'application avait effectivement **deux systèmes de gestion du contenu** :
|
|
||||||
|
|
||||||
#### Système 1 : Configuration Statique ❌
|
|
||||||
- Fichier : `config/games-config.json` + `navigation.js` ligne 96-120
|
|
||||||
- Usage : Configuration fixe des contenus et jeux
|
|
||||||
- Problème : Pas de compatibilité dynamique
|
|
||||||
|
|
||||||
#### Système 2 : Scan Dynamique ✅
|
|
||||||
- Fichiers : `ContentScanner` + `ContentGameCompatibility`
|
|
||||||
- Usage : Détection automatique + analyse de compatibilité
|
|
||||||
- Problème : **Pas connecté au flux principal**
|
|
||||||
|
|
||||||
### 2. Bug de Connexion
|
|
||||||
|
|
||||||
**Flux cassé** :
|
|
||||||
```
|
|
||||||
renderLevelsGrid() → ContentScanner ✅ (Bon système)
|
|
||||||
↓ clic sur Dragon's Pearl
|
|
||||||
renderGamesGrid() → Config statique ❌ (Mauvais système)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Le problème exact** (ligne 424 dans `renderGamesGrid()`) :
|
|
||||||
```javascript
|
|
||||||
// ❌ AVANT : Utilisait les métadonnées du scan au lieu du module JS
|
|
||||||
compatibility = this.compatibilityChecker.checkCompatibility(contentInfo, key);
|
|
||||||
|
|
||||||
// ✅ APRÈS : Utilise le vrai module JavaScript
|
|
||||||
const actualContentModule = window.ContentModules?.[moduleName];
|
|
||||||
compatibility = this.compatibilityChecker.checkCompatibility(actualContentModule, key);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Solution Implémentée
|
|
||||||
|
|
||||||
### 1. Connexion des Systèmes
|
|
||||||
- **Forcé l'utilisation du scan dynamique** dans `renderGamesGrid()`
|
|
||||||
- **Ajout de `getModuleName()`** : convertit `"chinese-long-story"` → `"ChineseLongStory"`
|
|
||||||
- **Correction du bug de compatibilité** : utilise le module JS réel
|
|
||||||
|
|
||||||
### 2. Fonctions Ajoutées
|
|
||||||
|
|
||||||
#### `getModuleName(contentType)` - navigation.js:640
|
|
||||||
```javascript
|
|
||||||
getModuleName(contentType) {
|
|
||||||
const mapping = {
|
|
||||||
'chinese-long-story': 'ChineseLongStory',
|
|
||||||
'sbs-level-7-8-new': 'SBSLevel78New',
|
|
||||||
// ... autres mappings
|
|
||||||
};
|
|
||||||
return mapping[contentType] || this.toPascalCase(contentType);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `toPascalCase(str)` - navigation.js:653
|
|
||||||
Convertit `kebab-case` → `PascalCase`
|
|
||||||
|
|
||||||
### 3. Correction du Bug Principal - navigation.js:424-435
|
|
||||||
```javascript
|
|
||||||
// Récupérer le module JavaScript réel pour le test de compatibilité
|
|
||||||
const moduleName = this.getModuleName(contentType);
|
|
||||||
const actualContentModule = window.ContentModules?.[moduleName];
|
|
||||||
|
|
||||||
if (actualContentModule) {
|
|
||||||
compatibility = this.compatibilityChecker.checkCompatibility(actualContentModule, key);
|
|
||||||
logSh(`🎯 ${game.name} compatibility: ${compatibility.compatible ? '✅' : '❌'} (score: ${compatibility.score}%) - ${compatibility.reason}`, 'DEBUG');
|
|
||||||
} else {
|
|
||||||
logSh(`⚠️ Module JavaScript non trouvé: ${moduleName}`, 'WARN');
|
|
||||||
compatibility = { compatible: true, score: 50, reason: "Module not loaded - default compatibility" };
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Résultat Attendu
|
|
||||||
|
|
||||||
Maintenant quand l'utilisateur clique sur **Dragon's Pearl** :
|
|
||||||
|
|
||||||
### 1. Analyse Automatique ✅
|
|
||||||
- Le système utilise le **scan dynamique** qui a détecté le contenu
|
|
||||||
- Il récupère le **vrai module JavaScript** `window.ContentModules.ChineseLongStory`
|
|
||||||
- Il lance l'**analyse de compatibilité** avec chaque jeu
|
|
||||||
|
|
||||||
### 2. Affichage Adaptatif ✅
|
|
||||||
Dragon's Pearl contient une **longue histoire avec chapitres**, donc :
|
|
||||||
|
|
||||||
**🎯 Jeux Recommandés (score élevé)** :
|
|
||||||
- ✅ **Text Reader** (95%) - Parfait pour histoires longues
|
|
||||||
- ✅ **Story Reader** (95%) - Optimisé pour les histoires
|
|
||||||
- ✅ **Adventure Reader** (85%) - Mode RPG avec vocabulaire
|
|
||||||
|
|
||||||
**👍 Jeux Compatibles (score moyen)** :
|
|
||||||
- ✅ **Quiz Game** (70%) - Questions sur vocabulaire
|
|
||||||
- ✅ **Memory Match** (60%) - Basé sur vocabulaire
|
|
||||||
|
|
||||||
**⚠️ Jeux avec Limitations** :
|
|
||||||
- ⚠️ **Whack-a-Mole** (45%) - Besoin plus de vocabulaire isolé
|
|
||||||
- ⚠️ **Fill-the-Blank** (40%) - Format histoire pas optimal
|
|
||||||
|
|
||||||
## 🧪 Pour Tester
|
|
||||||
|
|
||||||
### 1. Test Manuel
|
|
||||||
1. Ouvrir `index.html` dans le navigateur
|
|
||||||
2. Cliquer sur **"LEVELS"** → Dragon's Pearl apparaît via scan dynamique ✅
|
|
||||||
3. Cliquer sur **Dragon's Pearl** → Analyse de compatibilité s'exécute ✅
|
|
||||||
4. Observer l'affichage par **sections** avec **badges de compatibilité** ✅
|
|
||||||
|
|
||||||
### 2. Test Console (Diagnostic)
|
|
||||||
Copier-coller `diagnostic-scan-dynamique.js` dans la console pour vérifier :
|
|
||||||
- ✅ ContentScanner chargé
|
|
||||||
- ✅ ContentGameCompatibility chargé
|
|
||||||
- ✅ Dragon's Pearl détecté dans le scan
|
|
||||||
- ✅ Module `ChineseLongStory` chargé
|
|
||||||
- ✅ Tests de compatibilité fonctionnels
|
|
||||||
|
|
||||||
## 📊 Bilan Final
|
|
||||||
|
|
||||||
### ✅ Problèmes Résolus
|
|
||||||
- **Deux systèmes parallèles** → Unifié sur scan dynamique
|
|
||||||
- **Messages génériques** → Analyse de compatibilité réelle
|
|
||||||
- **Bug de connexion** → Module JS réel utilisé
|
|
||||||
- **Mapping des modules** → Fonction `getModuleName()` ajoutée
|
|
||||||
|
|
||||||
### 🎯 Système Maintenant Opérationnel
|
|
||||||
- **Dragon's Pearl est correctement analysé**
|
|
||||||
- **Compatibilité calculée pour chaque jeu**
|
|
||||||
- **Affichage visuel avec badges et sections**
|
|
||||||
- **Dégradation gracieuse si module manquant**
|
|
||||||
|
|
||||||
Le système de compatibilité content-jeux est maintenant **100% fonctionnel** ! 🚀
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
# 📋 RÉSUMÉ DES MODIFICATIONS - Session Actuelle
|
|
||||||
|
|
||||||
## 🎯 Objectifs Accomplis
|
|
||||||
|
|
||||||
### 1. 🔧 Système de Compatibilité Content-Jeux
|
|
||||||
**Problème** : Les jeux s'affichaient même s'ils n'étaient pas compatibles avec le contenu sélectionné.
|
|
||||||
|
|
||||||
**Solution** : Système complet de compatibilité qui analyse le contenu et affiche seulement les jeux adaptés.
|
|
||||||
|
|
||||||
### 2. 🐉 Adventure Reader + Dragon's Pearl
|
|
||||||
**Problème** : Adventure Reader ne pouvait pas utiliser le contenu de Dragon's Pearl.
|
|
||||||
|
|
||||||
**Solution** : Support complet de la structure Dragon's Pearl avec extraction des phrases et prononciations.
|
|
||||||
|
|
||||||
### 3. 🎮 Interface Multi-Colonnes
|
|
||||||
**Problème** : Interface des jeux affichait un seul jeu par ligne, navigation lente.
|
|
||||||
|
|
||||||
**Solution** : Grille responsive avec 3-6 jeux par ligne selon la taille d'écran.
|
|
||||||
|
|
||||||
### 4. 🍞 Navigation Améliorée
|
|
||||||
**Problème** : Breadcrumb manquait le niveau "Levels", pas de boutons back.
|
|
||||||
|
|
||||||
**Solution** : Navigation complète avec breadcrumb correct et boutons back sur toutes les pages.
|
|
||||||
|
|
||||||
## 📊 Statistiques Git
|
|
||||||
```
|
|
||||||
29 fichiers modifiés
|
|
||||||
+5312 lignes ajoutées
|
|
||||||
-2097 lignes supprimées
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Fichiers Clés Modifiés
|
|
||||||
|
|
||||||
### Navigation & Interface
|
|
||||||
- **`js/core/navigation.js`** : Système de compatibilité intégré, breadcrumb corrigé
|
|
||||||
- **`css/main.css`** : Grille multi-colonnes, styles compatibilité, boutons back
|
|
||||||
- **`index.html`** : Boutons back ajoutés dans headers
|
|
||||||
|
|
||||||
### Système de Compatibilité
|
|
||||||
- **`js/core/content-game-compatibility.js`** ⭐ **NOUVEAU** : Classe principale de compatibilité
|
|
||||||
- **`css/main.css`** : Badges de compatibilité, sections visuelles
|
|
||||||
|
|
||||||
### Adventure Reader
|
|
||||||
- **`js/games/adventure-reader.js`** : Support Dragon's Pearl, extraction phrases, prononciations
|
|
||||||
- **`js/core/json-content-loader.js`** : Support structure `story.chapters[]`
|
|
||||||
- **`css/games.css`** : Styles prononciations vocabulaire et phrases
|
|
||||||
|
|
||||||
### Contenu
|
|
||||||
- **`js/content/chinese-long-story.js`** ⭐ **NOUVEAU** : Dragon's Pearl (150+ phrases chinoises)
|
|
||||||
|
|
||||||
## 🎯 Fonctionnalités Ajoutées
|
|
||||||
|
|
||||||
### 🔍 Système de Compatibilité Intelligent
|
|
||||||
- **Analyse automatique** du contenu (vocabulaire, phrases, dialogues, audio...)
|
|
||||||
- **Scores de compatibilité** 0-100% par jeu
|
|
||||||
- **Badges visuels** : Excellent (🎯), Recommandé (✅), Compatible (👍), Limité (⚠️)
|
|
||||||
- **Sections organisées** : Jeux recommandés vs jeux avec limitations
|
|
||||||
- **Messages d'aide** : Suggestions pour améliorer le contenu
|
|
||||||
|
|
||||||
### 🐉 Dragon's Pearl dans Adventure Reader
|
|
||||||
- **150+ phrases chinoises** avec traductions anglaises
|
|
||||||
- **Vocabulaire riche** avec prononciations pinyin
|
|
||||||
- **Structure par chapitres** : L'histoire de Li Ming et la perle du dragon
|
|
||||||
- **Ennemis générés** basés sur le nombre de phrases
|
|
||||||
- **Pots pour vocabulaire** avec prononciations
|
|
||||||
- **Modal de lecture** avec chinois + anglais + pinyin
|
|
||||||
|
|
||||||
### 🎮 Interface Multi-Colonnes
|
|
||||||
- **Desktop** : 4-6 jeux par ligne
|
|
||||||
- **Tablette** : 3-4 jeux par ligne
|
|
||||||
- **Mobile** : 2 jeux par ligne
|
|
||||||
- **Navigation rapide** : Vue d'ensemble de tous les jeux
|
|
||||||
|
|
||||||
### 🧭 Navigation Complète
|
|
||||||
- **Breadcrumb corrigé** : Home > Levels > Dragon's Pearl > Jeu
|
|
||||||
- **Boutons back** sur pages levels et games
|
|
||||||
- **Historique** de navigation avec goBack() fonctionnel
|
|
||||||
- **Styles responsive** pour tous les écrans
|
|
||||||
|
|
||||||
### 🗣️ Système de Prononciations
|
|
||||||
- **Vocabulaire** : Affichage pinyin dans popup (龙 → dragon → 🗣️ lóng)
|
|
||||||
- **Phrases** : Construction automatique des prononciations complètes
|
|
||||||
- **Styles visuels** : Fond coloré, italique, emoji 🗣️
|
|
||||||
|
|
||||||
## 🚀 Fichiers de Debug Créés
|
|
||||||
- `diagnostic-scan-dynamique.js` - Diagnostic système complet
|
|
||||||
- `debug-adventure-reader.js` - Debug Adventure Reader spécifique
|
|
||||||
- `test-extraction-direct.js` - Test extraction phrases Dragon's Pearl
|
|
||||||
|
|
||||||
## 📋 Rapports Générés
|
|
||||||
- `COMPATIBILITY-SYSTEM.md` - Documentation système compatibilité
|
|
||||||
- `ADVENTURE-READER-DRAGON-PEARL.md` - Guide Adventure Reader + Dragon's Pearl
|
|
||||||
- `RAPPORT-COMPATIBILITÉ-RÉSOLU.md` - Analyse problèmes/solutions
|
|
||||||
- `OPTIMISATION-GRILLE-JEUX.md` - Détails interface multi-colonnes
|
|
||||||
|
|
||||||
## ✅ Résultat Final
|
|
||||||
|
|
||||||
L'application est maintenant **beaucoup plus intelligente et utilisable** :
|
|
||||||
|
|
||||||
1. **Choix intelligent** des jeux selon le contenu
|
|
||||||
2. **Navigation rapide** avec vue d'ensemble
|
|
||||||
3. **Expérience immersive** avec Dragon's Pearl + Adventure Reader
|
|
||||||
4. **Apprentissage optimisé** avec prononciations chinoises
|
|
||||||
5. **Interface moderne** responsive et intuitive
|
|
||||||
|
|
||||||
**🎯 Mission accomplie !** Le système analyse automatiquement le contenu, recommande les meilleurs jeux, et offre une expérience d'apprentissage riche avec Dragon's Pearl ! 🐉✨
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
@echo off
|
|
||||||
title Class Generator - Startup
|
|
||||||
color 0A
|
|
||||||
|
|
||||||
echo ========================================
|
|
||||||
echo CLASS GENERATOR - DEMARRAGE
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
|
|
||||||
cd /d "%~dp0"
|
|
||||||
|
|
||||||
echo [1/6] Arret des instances precedentes...
|
|
||||||
echo ----------------------------------------
|
|
||||||
taskkill /F /IM node.exe >nul 2>&1
|
|
||||||
taskkill /F /IM python.exe >nul 2>&1
|
|
||||||
echo OK - Instances precedentes arretees
|
|
||||||
|
|
||||||
timeout /t 1 /nobreak > nul
|
|
||||||
|
|
||||||
echo [2/6] Demarrage du serveur WebSocket Logger (port 8082)...
|
|
||||||
echo ----------------------------------------
|
|
||||||
cd export_logger
|
|
||||||
start /B node websocket-server.js
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
timeout /t 2 /nobreak > nul
|
|
||||||
|
|
||||||
echo [3/6] Demarrage du serveur HTTP (port 8080)...
|
|
||||||
echo ----------------------------------------
|
|
||||||
start /B python -m http.server 8080
|
|
||||||
|
|
||||||
timeout /t 3 /nobreak > nul
|
|
||||||
|
|
||||||
echo [4/6] Verification des serveurs...
|
|
||||||
echo ----------------------------------------
|
|
||||||
echo OK - Serveur WebSocket demarre sur le port 8082
|
|
||||||
echo OK - Serveur HTTP demarre sur le port 8080
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo [5/6] Ouverture du logger en priorite...
|
|
||||||
echo ----------------------------------------
|
|
||||||
start "" "http://localhost:8080/export_logger/logs-viewer.html"
|
|
||||||
|
|
||||||
timeout /t 2 /nobreak > nul
|
|
||||||
|
|
||||||
echo [6/6] Ouverture de l'application principale...
|
|
||||||
echo ----------------------------------------
|
|
||||||
start "" "http://localhost:8080"
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ========================================
|
|
||||||
echo DEMARRAGE TERMINE AVEC SUCCES!
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
echo Serveur HTTP: http://localhost:8080
|
|
||||||
echo Serveur WebSocket: ws://localhost:8082
|
|
||||||
echo Application: Ouverte dans votre navigateur
|
|
||||||
echo Logger: Accessible via le bouton en haut a droite
|
|
||||||
echo.
|
|
||||||
echo Cette fenetre peut etre fermee.
|
|
||||||
echo Les serveurs continuent en arriere-plan.
|
|
||||||
echo.
|
|
||||||
echo Pour arreter les serveurs:
|
|
||||||
echo - Relancez ce script (il tue automatiquement les anciennes instances)
|
|
||||||
echo - Ou dans le gestionnaire de taches: Terminer node.exe et python.exe
|
|
||||||
echo.
|
|
||||||
timeout /t 5 /nobreak > nul
|
|
||||||
exit
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
@echo off
|
|
||||||
title Class Generator - HTTP Server
|
|
||||||
color 0A
|
|
||||||
|
|
||||||
echo ========================================
|
|
||||||
echo CLASS GENERATOR - SERVEUR HTTP
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
|
|
||||||
cd /d "%~dp0"
|
|
||||||
|
|
||||||
echo [1/3] Demarrage du serveur WebSocket Logger (port 8082)...
|
|
||||||
echo ----------------------------------------
|
|
||||||
cd export_logger
|
|
||||||
start /min cmd /c "node websocket-server.js"
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
timeout /t 2 /nobreak > nul
|
|
||||||
|
|
||||||
echo [2/3] Demarrage du serveur HTTP (port 8080)...
|
|
||||||
echo ----------------------------------------
|
|
||||||
start /min cmd /c "python -m http.server 8080"
|
|
||||||
|
|
||||||
timeout /t 3 /nobreak > nul
|
|
||||||
|
|
||||||
echo [3/3] Ouverture de l'application...
|
|
||||||
echo ----------------------------------------
|
|
||||||
start "" "http://localhost:8080"
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ========================================
|
|
||||||
echo DEMARRAGE TERMINE AVEC SUCCES!
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
echo Serveur HTTP: http://localhost:8080
|
|
||||||
echo Serveur WebSocket: ws://localhost:8082
|
|
||||||
echo Logger: Accessible via le bouton en haut a droite
|
|
||||||
echo.
|
|
||||||
echo Cette fenetre peut etre fermee.
|
|
||||||
echo Les serveurs continuent en arriere-plan.
|
|
||||||
echo.
|
|
||||||
timeout /t 5 /nobreak > nul
|
|
||||||
exit
|
|
||||||
@ -1,643 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>🚀 Admin: Ultra-Modular JSON System</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-header {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-header h1 {
|
|
||||||
color: #4338ca;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-container {
|
|
||||||
max-width: 1400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-panel {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 20px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
color: #4338ca;
|
|
||||||
border-bottom: 2px solid #e5e7eb;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-selector {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-card {
|
|
||||||
background: #f8fafc;
|
|
||||||
border: 2px solid #e2e8f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-card:hover {
|
|
||||||
border-color: #3b82f6;
|
|
||||||
background: #eff6ff;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-card.active {
|
|
||||||
border-color: #10b981;
|
|
||||||
background: #ecfdf5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-card h4 {
|
|
||||||
color: #1e293b;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-card p {
|
|
||||||
color: #64748b;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-results {
|
|
||||||
background: #f8fafc;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-score {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-circle {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-weight: bold;
|
|
||||||
color: white;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-excellent { background: #10b981; }
|
|
||||||
.score-good { background: #3b82f6; }
|
|
||||||
.score-fair { background: #f59e0b; }
|
|
||||||
.score-poor { background: #ef4444; }
|
|
||||||
|
|
||||||
.validation-section {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-section h4 {
|
|
||||||
color: #374151;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-list {
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-item {
|
|
||||||
background: white;
|
|
||||||
padding: 8px 12px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border-left: 3px solid #d1d5db;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-item.error {
|
|
||||||
border-left-color: #ef4444;
|
|
||||||
background: #fef2f2;
|
|
||||||
color: #dc2626;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-item.warning {
|
|
||||||
border-left-color: #f59e0b;
|
|
||||||
background: #fffbeb;
|
|
||||||
color: #d97706;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-item.suggestion {
|
|
||||||
border-left-color: #3b82f6;
|
|
||||||
background: #eff6ff;
|
|
||||||
color: #1d4ed8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.capabilities-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.capability-badge {
|
|
||||||
background: #f1f5f9;
|
|
||||||
border: 1px solid #cbd5e1;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px 10px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.capability-badge.active {
|
|
||||||
background: #dcfce7;
|
|
||||||
border-color: #16a34a;
|
|
||||||
color: #166534;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compatibility-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-compatibility {
|
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 12px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-compatibility.compatible {
|
|
||||||
border-color: #16a34a;
|
|
||||||
background: #f0fdf4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-compatibility.incompatible {
|
|
||||||
border-color: #dc2626;
|
|
||||||
background: #fef2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-score {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-reason {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #6b7280;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: #3b82f6;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-success {
|
|
||||||
background: #10b981;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-success:hover {
|
|
||||||
background: #059669;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-display {
|
|
||||||
background: #f8fafc;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 5px 0;
|
|
||||||
border-bottom: 1px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-row:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
color: #6b7280;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.admin-container {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="admin-header">
|
|
||||||
<h1>🚀 Admin: Ultra-Modular JSON System</h1>
|
|
||||||
<p>Validation et gestion des spécifications de contenu ultra-modulaires</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-container">
|
|
||||||
<!-- Panel de sélection et validation -->
|
|
||||||
<div class="admin-panel">
|
|
||||||
<div class="panel-header">
|
|
||||||
<span>🔍</span>
|
|
||||||
<h3>Validation de Spécifications</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4>Fichiers JSON Disponibles:</h4>
|
|
||||||
<div class="file-selector" id="file-selector">
|
|
||||||
<div class="loading">Chargement des fichiers...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="validation-results" style="display: none;">
|
|
||||||
<div class="validation-results">
|
|
||||||
<div class="validation-score">
|
|
||||||
<div>
|
|
||||||
<h4>Score de Qualité</h4>
|
|
||||||
<p id="selected-file-name">Aucun fichier sélectionné</p>
|
|
||||||
</div>
|
|
||||||
<div class="score-circle" id="quality-score">-</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="validation-section">
|
|
||||||
<h4>❌ Erreurs</h4>
|
|
||||||
<div class="validation-list" id="errors-list">
|
|
||||||
<div class="validation-item">Aucune erreur détectée</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="validation-section">
|
|
||||||
<h4>⚠️ Avertissements</h4>
|
|
||||||
<div class="validation-list" id="warnings-list">
|
|
||||||
<div class="validation-item">Aucun avertissement</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="validation-section">
|
|
||||||
<h4>💡 Suggestions</h4>
|
|
||||||
<div class="validation-list" id="suggestions-list">
|
|
||||||
<div class="validation-item">Aucune suggestion</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button class="btn btn-primary" onclick="validateSelected()">🔄 Revalider</button>
|
|
||||||
<button class="btn btn-success" onclick="convertToLegacy()">🔄 Convertir vers Legacy</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Panel d'analyse des capacités -->
|
|
||||||
<div class="admin-panel">
|
|
||||||
<div class="panel-header">
|
|
||||||
<span>📊</span>
|
|
||||||
<h3>Analyse des Capacités</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="capabilities-section" style="display: none;">
|
|
||||||
<div class="stats-display" id="content-stats">
|
|
||||||
<h4>📈 Statistiques du Contenu</h4>
|
|
||||||
<div id="stats-content"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4>🎯 Capacités Détectées</h4>
|
|
||||||
<div class="capabilities-grid" id="capabilities-grid"></div>
|
|
||||||
|
|
||||||
<h4>🎮 Compatibilité des Jeux</h4>
|
|
||||||
<div class="compatibility-grid" id="compatibility-grid"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</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/json-content-loader.js"></script>
|
|
||||||
<script src="js/core/content-scanner.js"></script>
|
|
||||||
<script src="js/tools/ultra-modular-validator.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
class UltraModularAdmin {
|
|
||||||
constructor() {
|
|
||||||
this.validator = new UltraModularValidator();
|
|
||||||
this.selectedFile = null;
|
|
||||||
this.validationResults = null;
|
|
||||||
this.availableFiles = [
|
|
||||||
{
|
|
||||||
filename: 'english_exemple.json',
|
|
||||||
name: 'English Example (Basic)',
|
|
||||||
description: 'Format JSON de base'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: 'english_exemple_fixed.json',
|
|
||||||
name: 'English Example (Modular)',
|
|
||||||
description: 'Format modulaire amélioré'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: 'english_exemple_ultra_commented.json',
|
|
||||||
name: 'English Example (Ultra-Modular)',
|
|
||||||
description: 'Spécification ultra-modulaire complète'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
this.renderFileSelector();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFileSelector() {
|
|
||||||
const fileSelector = document.getElementById('file-selector');
|
|
||||||
fileSelector.innerHTML = '';
|
|
||||||
|
|
||||||
this.availableFiles.forEach(file => {
|
|
||||||
const card = document.createElement('div');
|
|
||||||
card.className = 'file-card';
|
|
||||||
card.onclick = () => this.selectFile(file.filename);
|
|
||||||
card.innerHTML = `
|
|
||||||
<h4>${file.name}</h4>
|
|
||||||
<p>${file.description}</p>
|
|
||||||
`;
|
|
||||||
card.dataset.filename = file.filename;
|
|
||||||
fileSelector.appendChild(card);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectFile(filename) {
|
|
||||||
// Mise à jour visuelle
|
|
||||||
document.querySelectorAll('.file-card').forEach(card => {
|
|
||||||
card.classList.remove('active');
|
|
||||||
});
|
|
||||||
document.querySelector(`[data-filename="${filename}"]`).classList.add('active');
|
|
||||||
|
|
||||||
this.selectedFile = filename;
|
|
||||||
document.getElementById('selected-file-name').textContent =
|
|
||||||
this.availableFiles.find(f => f.filename === filename)?.name || filename;
|
|
||||||
|
|
||||||
// Validation automatique
|
|
||||||
await this.validateFile(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
async validateFile(filename) {
|
|
||||||
try {
|
|
||||||
document.querySelector('.loading').style.display = 'block';
|
|
||||||
|
|
||||||
const response = await fetch(filename);
|
|
||||||
const jsonContent = await response.json();
|
|
||||||
|
|
||||||
this.validationResults = await this.validator.validateSpecification(jsonContent);
|
|
||||||
|
|
||||||
this.renderValidationResults();
|
|
||||||
this.renderCapabilities();
|
|
||||||
|
|
||||||
document.getElementById('validation-results').style.display = 'block';
|
|
||||||
document.getElementById('capabilities-section').style.display = 'block';
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
alert(`Erreur lors du chargement: ${error.message}`);
|
|
||||||
} finally {
|
|
||||||
document.querySelector('.loading').style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderValidationResults() {
|
|
||||||
const results = this.validationResults;
|
|
||||||
|
|
||||||
// Score de qualité
|
|
||||||
const scoreCircle = document.getElementById('quality-score');
|
|
||||||
scoreCircle.textContent = results.score;
|
|
||||||
scoreCircle.className = 'score-circle ' + this.getScoreClass(results.score);
|
|
||||||
|
|
||||||
// Erreurs
|
|
||||||
this.renderValidationList('errors-list', results.errors, 'error');
|
|
||||||
|
|
||||||
// Avertissements
|
|
||||||
this.renderValidationList('warnings-list', results.warnings, 'warning');
|
|
||||||
|
|
||||||
// Suggestions
|
|
||||||
this.renderValidationList('suggestions-list', results.suggestions, 'suggestion');
|
|
||||||
}
|
|
||||||
|
|
||||||
renderValidationList(containerId, items, type) {
|
|
||||||
const container = document.getElementById(containerId);
|
|
||||||
container.innerHTML = '';
|
|
||||||
|
|
||||||
if (items.length === 0) {
|
|
||||||
const item = document.createElement('div');
|
|
||||||
item.className = 'validation-item';
|
|
||||||
item.textContent = type === 'error' ? 'Aucune erreur détectée' :
|
|
||||||
type === 'warning' ? 'Aucun avertissement' :
|
|
||||||
'Aucune suggestion';
|
|
||||||
container.appendChild(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
items.forEach(text => {
|
|
||||||
const item = document.createElement('div');
|
|
||||||
item.className = `validation-item ${type}`;
|
|
||||||
item.textContent = text;
|
|
||||||
container.appendChild(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCapabilities() {
|
|
||||||
const capabilities = this.validationResults.capabilities;
|
|
||||||
const compatibility = this.validationResults.compatibility;
|
|
||||||
|
|
||||||
// Statistiques
|
|
||||||
this.renderContentStats();
|
|
||||||
|
|
||||||
// Capacités
|
|
||||||
const capabilitiesGrid = document.getElementById('capabilities-grid');
|
|
||||||
capabilitiesGrid.innerHTML = '';
|
|
||||||
|
|
||||||
Object.entries(capabilities).forEach(([key, value]) => {
|
|
||||||
const badge = document.createElement('div');
|
|
||||||
badge.className = `capability-badge ${value ? 'active' : ''}`;
|
|
||||||
|
|
||||||
let displayText = key.replace(/([A-Z])/g, ' $1').toLowerCase();
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
displayText += `: ${value}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
badge.textContent = displayText;
|
|
||||||
capabilitiesGrid.appendChild(badge);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Compatibilité des jeux
|
|
||||||
const compatibilityGrid = document.getElementById('compatibility-grid');
|
|
||||||
compatibilityGrid.innerHTML = '';
|
|
||||||
|
|
||||||
Object.entries(compatibility).forEach(([game, compat]) => {
|
|
||||||
const gameCard = document.createElement('div');
|
|
||||||
gameCard.className = `game-compatibility ${compat.compatible ? 'compatible' : 'incompatible'}`;
|
|
||||||
gameCard.innerHTML = `
|
|
||||||
<div class="game-header">
|
|
||||||
<strong>${game}</strong>
|
|
||||||
<span class="game-score">${compat.score}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="game-reason">${compat.reason}</div>
|
|
||||||
`;
|
|
||||||
compatibilityGrid.appendChild(gameCard);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContentStats() {
|
|
||||||
// Simuler des statistiques basées sur les capacités
|
|
||||||
const caps = this.validationResults.capabilities;
|
|
||||||
const statsContent = document.getElementById('stats-content');
|
|
||||||
|
|
||||||
const stats = [
|
|
||||||
['Profondeur vocabulaire', `${caps.vocabularyDepth}/6`],
|
|
||||||
['Richesse contenu', `${caps.contentRichness.toFixed(1)}/10`],
|
|
||||||
['Audio présent', caps.hasAudioFiles ? '✅ Oui' : '❌ Non'],
|
|
||||||
['Exercices présents', caps.hasExercises ? '✅ Oui' : '❌ Non'],
|
|
||||||
['Contenu culturel', caps.hasCulture ? '✅ Oui' : '❌ Non'],
|
|
||||||
['Format multi-langue', caps.hasMultipleLanguages ? '✅ Oui' : '❌ Non']
|
|
||||||
];
|
|
||||||
|
|
||||||
statsContent.innerHTML = stats.map(([label, value]) => `
|
|
||||||
<div class="stats-row">
|
|
||||||
<span>${label}:</span>
|
|
||||||
<strong>${value}</strong>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
getScoreClass(score) {
|
|
||||||
if (score >= 90) return 'score-excellent';
|
|
||||||
if (score >= 70) return 'score-good';
|
|
||||||
if (score >= 50) return 'score-fair';
|
|
||||||
return 'score-poor';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
async validateSelected() {
|
|
||||||
if (this.selectedFile) {
|
|
||||||
await this.validateFile(this.selectedFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async convertToLegacy() {
|
|
||||||
if (!this.selectedFile || !this.validationResults) {
|
|
||||||
alert('Veuillez d\'abord sélectionner et valider un fichier');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(this.selectedFile);
|
|
||||||
const jsonContent = await response.json();
|
|
||||||
|
|
||||||
const conversion = await this.validator.convertToLegacy(jsonContent);
|
|
||||||
|
|
||||||
// Créer et télécharger le fichier JS
|
|
||||||
const blob = new Blob([conversion.jsModule], { type: 'text/javascript' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `${jsonContent.id || 'content'}.js`;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
|
|
||||||
alert('Module JavaScript généré et téléchargé avec succès !');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
alert(`Erreur lors de la conversion: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialiser l'admin au chargement
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
window.adminApp = new UltraModularAdmin();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fonctions globales pour les boutons
|
|
||||||
function validateSelected() {
|
|
||||||
window.adminApp.validateSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertToLegacy() {
|
|
||||||
window.adminApp.convertToLegacy();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
// Quick script to convert old vocabulary format to new language-agnostic format
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
function convertVocabularyFormat(inputFile, outputFile) {
|
|
||||||
let content = fs.readFileSync(inputFile, 'utf8');
|
|
||||||
|
|
||||||
// Pattern to match: word: "translation",
|
|
||||||
const pattern = /(\s+)(\w+|"[^"]*"): "([^"]*)",?/g;
|
|
||||||
|
|
||||||
content = content.replace(pattern, (match, indent, word, translation) => {
|
|
||||||
// Determine word type based on translation or word
|
|
||||||
let type = "noun"; // Default
|
|
||||||
if (translation.includes("的") && !translation.includes(";")) type = "adjective";
|
|
||||||
if (word.includes(" ")) type = "phrase";
|
|
||||||
|
|
||||||
return `${indent}${word}: { user_language: "${translation}", type: "${type}" },`;
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.writeFileSync(outputFile, content);
|
|
||||||
console.log(`Converted ${inputFile} to ${outputFile}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the sbs file
|
|
||||||
convertVocabularyFormat(
|
|
||||||
'js/content/sbs-level-7-8-new.js',
|
|
||||||
'js/content/sbs-level-7-8-new-converted.js'
|
|
||||||
);
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
// === DEBUG ADVENTURE READER + DRAGON'S PEARL ===
|
|
||||||
|
|
||||||
// Script à copier-coller dans la console pour déboguer
|
|
||||||
|
|
||||||
function debugAdventureReader() {
|
|
||||||
console.log('🐉 Debug Adventure Reader + Dragon\\'s Pearl');
|
|
||||||
|
|
||||||
// 1. Vérifier que le module Dragon's Pearl est chargé
|
|
||||||
console.log('\\n1️⃣ Module Dragon\\'s Pearl:');
|
|
||||||
const dragonModule = window.ContentModules?.ChineseLongStory;
|
|
||||||
console.log(' Module trouvé:', !!dragonModule);
|
|
||||||
|
|
||||||
if (dragonModule) {
|
|
||||||
console.log(' Nom:', dragonModule.name);
|
|
||||||
console.log(' Vocabulary entries:', Object.keys(dragonModule.vocabulary || {}).length);
|
|
||||||
console.log(' Story structure:', !!dragonModule.story);
|
|
||||||
|
|
||||||
if (dragonModule.story) {
|
|
||||||
console.log(' Story title:', dragonModule.story.title);
|
|
||||||
console.log(' Chapters:', dragonModule.story.chapters?.length || 0);
|
|
||||||
|
|
||||||
if (dragonModule.story.chapters?.[0]) {
|
|
||||||
console.log(' First chapter sentences:', dragonModule.story.chapters[0].sentences?.length || 0);
|
|
||||||
console.log(' Sample sentence:', dragonModule.story.chapters[0].sentences?.[0]?.original);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Tester l'extraction si possible
|
|
||||||
console.log('\\n2️⃣ Test d\\'extraction:');
|
|
||||||
if (dragonModule && window.AdventureReaderGame) {
|
|
||||||
// Créer une instance temporaire pour tester
|
|
||||||
const testReader = {
|
|
||||||
extractSentences: window.AdventureReaderGame.prototype.extractSentences,
|
|
||||||
extractVocabulary: window.AdventureReaderGame.prototype.extractVocabulary,
|
|
||||||
extractStories: window.AdventureReaderGame.prototype.extractStories
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sentences = testReader.extractSentences(dragonModule);
|
|
||||||
console.log(' Phrases extraites:', sentences.length);
|
|
||||||
if (sentences.length > 0) {
|
|
||||||
console.log(' Première phrase:', sentences[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const vocab = testReader.extractVocabulary(dragonModule);
|
|
||||||
console.log(' Vocabulaire extrait:', vocab.length);
|
|
||||||
if (vocab.length > 0) {
|
|
||||||
console.log(' Premier mot:', vocab[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const stories = testReader.extractStories(dragonModule);
|
|
||||||
console.log(' Histoires extraites:', stories.length);
|
|
||||||
if (stories.length > 0) {
|
|
||||||
console.log(' Première histoire:', stories[0].title);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(' ❌ Erreur d\\'extraction:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Vérifier l'instance active si elle existe
|
|
||||||
console.log('\\n3️⃣ Instance active:');
|
|
||||||
// Chercher dans le DOM s'il y a un jeu actif
|
|
||||||
const gameContainer = document.querySelector('.adventure-reader-wrapper');
|
|
||||||
console.log(' Jeu Adventure Reader actif:', !!gameContainer);
|
|
||||||
|
|
||||||
// Vérifier les éléments du DOM
|
|
||||||
const modal = document.getElementById('reading-modal');
|
|
||||||
console.log(' Modal de lecture:', !!modal);
|
|
||||||
|
|
||||||
const mapElement = document.querySelector('.game-map');
|
|
||||||
console.log(' Carte de jeu:', !!mapElement);
|
|
||||||
|
|
||||||
const pots = document.querySelectorAll('.pot');
|
|
||||||
const enemies = document.querySelectorAll('.enemy');
|
|
||||||
console.log(' Pots sur la carte:', pots.length);
|
|
||||||
console.log(' Ennemis sur la carte:', enemies.length);
|
|
||||||
|
|
||||||
console.log('\\n✅ Debug terminé');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-exécution
|
|
||||||
debugAdventureReader();
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
// === DEBUG SYSTÈME DE COMPATIBILITÉ ===
|
|
||||||
|
|
||||||
// Script à injecter dans la console pour débugger les problèmes
|
|
||||||
|
|
||||||
window.debugCompatibility = {
|
|
||||||
|
|
||||||
// Fonction pour logger les informations détaillées
|
|
||||||
logContentInfo: function(contentType) {
|
|
||||||
console.log('🔍 Debug Compatibilité pour:', contentType);
|
|
||||||
|
|
||||||
// 1. Vérifier si le contentScanner a ce contenu
|
|
||||||
if (window.AppNavigation && window.AppNavigation.scannedContent) {
|
|
||||||
const foundContent = window.AppNavigation.scannedContent.found.find(c => c.id === contentType);
|
|
||||||
console.log('📦 Contenu trouvé par scanner:', foundContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Vérifier dans ContentModules
|
|
||||||
const moduleName = this.getModuleName(contentType);
|
|
||||||
console.log('🏷️ Nom de module calculé:', moduleName);
|
|
||||||
|
|
||||||
if (window.ContentModules && window.ContentModules[moduleName]) {
|
|
||||||
const module = window.ContentModules[moduleName];
|
|
||||||
console.log('📋 Module dans ContentModules:', module);
|
|
||||||
|
|
||||||
// 3. Tester compatibilité directement
|
|
||||||
if (window.AppNavigation && window.AppNavigation.compatibilityChecker) {
|
|
||||||
const checker = window.AppNavigation.compatibilityChecker;
|
|
||||||
const games = ['whack-a-mole', 'memory-match', 'quiz-game', 'fill-the-blank', 'text-reader', 'adventure-reader'];
|
|
||||||
|
|
||||||
console.log('🎮 Tests de compatibilité:');
|
|
||||||
games.forEach(game => {
|
|
||||||
const result = checker.checkCompatibility(module, game);
|
|
||||||
console.log(` ${result.compatible ? '✅' : '❌'} ${game}: ${result.score}%`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('❌ Module non trouvé:', moduleName);
|
|
||||||
console.log('📋 Modules disponibles:', Object.keys(window.ContentModules || {}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Fonction pour convertir contentType vers moduleName (copie de la logique)
|
|
||||||
getModuleName: function(contentType) {
|
|
||||||
const mapping = {
|
|
||||||
'sbs-level-7-8-new': 'SBSLevel78New',
|
|
||||||
'basic-chinese': 'BasicChinese',
|
|
||||||
'english-class-demo': 'EnglishClassDemo',
|
|
||||||
'chinese-long-story': 'ChineseLongStory',
|
|
||||||
'test-minimal': 'TestMinimal',
|
|
||||||
'test-rich': 'TestRich'
|
|
||||||
};
|
|
||||||
return mapping[contentType] || this.toPascalCase(contentType);
|
|
||||||
},
|
|
||||||
|
|
||||||
toPascalCase: function(str) {
|
|
||||||
return str.split('-').map(word =>
|
|
||||||
word.charAt(0).toUpperCase() + word.slice(1)
|
|
||||||
).join('');
|
|
||||||
},
|
|
||||||
|
|
||||||
// Fonction pour tester avec Dragon's Pearl spécifiquement
|
|
||||||
testDragonsPearl: function() {
|
|
||||||
console.log('🐉 Test spécifique Dragon\'s Pearl');
|
|
||||||
this.logContentInfo('chinese-long-story');
|
|
||||||
},
|
|
||||||
|
|
||||||
// Fonction pour vérifier le contenu scanné
|
|
||||||
listScannedContent: function() {
|
|
||||||
if (window.AppNavigation && window.AppNavigation.scannedContent) {
|
|
||||||
console.log('📋 Contenu scanné:', window.AppNavigation.scannedContent.found.map(c => ({
|
|
||||||
id: c.id,
|
|
||||||
name: c.name,
|
|
||||||
filename: c.filename
|
|
||||||
})));
|
|
||||||
} else {
|
|
||||||
console.log('❌ Pas de contenu scanné trouvé');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('🔧 Script de debug chargé. Utilisez:');
|
|
||||||
console.log(' debugCompatibility.testDragonsPearl() - Test Dragon\'s Pearl');
|
|
||||||
console.log(' debugCompatibility.listScannedContent() - Liste le contenu scanné');
|
|
||||||
console.log(' debugCompatibility.logContentInfo("content-id") - Debug contenu spécifique');
|
|
||||||
@ -1,182 +0,0 @@
|
|||||||
// === DIAGNOSTIC COMPLET DU SCAN DYNAMIQUE ===
|
|
||||||
|
|
||||||
// Script de diagnostic à exécuter dans la console pour identifier TOUS les problèmes
|
|
||||||
|
|
||||||
function diagnosticComplet() {
|
|
||||||
console.log('🔍 DIAGNOSTIC COMPLET DU SCAN DYNAMIQUE\n');
|
|
||||||
console.log('=====================================\n');
|
|
||||||
|
|
||||||
const issues = [];
|
|
||||||
const success = [];
|
|
||||||
|
|
||||||
// 1. Vérification des classes de base
|
|
||||||
console.log('1️⃣ VÉRIFICATION DES CLASSES DE BASE');
|
|
||||||
console.log('-------------------------------------');
|
|
||||||
|
|
||||||
if (!window.ContentScanner) {
|
|
||||||
issues.push('❌ ContentScanner non chargé');
|
|
||||||
console.log('❌ ContentScanner: NON CHARGÉ');
|
|
||||||
} else {
|
|
||||||
success.push('✅ ContentScanner chargé');
|
|
||||||
console.log('✅ ContentScanner: CHARGÉ');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!window.ContentGameCompatibility) {
|
|
||||||
issues.push('❌ ContentGameCompatibility non chargé');
|
|
||||||
console.log('❌ ContentGameCompatibility: NON CHARGÉ');
|
|
||||||
} else {
|
|
||||||
success.push('✅ ContentGameCompatibility chargé');
|
|
||||||
console.log('✅ ContentGameCompatibility: CHARGÉ');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!window.AppNavigation) {
|
|
||||||
issues.push('❌ AppNavigation non chargé');
|
|
||||||
console.log('❌ AppNavigation: NON CHARGÉ');
|
|
||||||
return { issues, success };
|
|
||||||
} else {
|
|
||||||
success.push('✅ AppNavigation chargé');
|
|
||||||
console.log('✅ AppNavigation: CHARGÉ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Vérification de l'initialisation
|
|
||||||
console.log('\n2️⃣ VÉRIFICATION DE L\'INITIALISATION');
|
|
||||||
console.log('--------------------------------------');
|
|
||||||
|
|
||||||
if (!window.AppNavigation.contentScanner) {
|
|
||||||
issues.push('❌ ContentScanner non initialisé dans AppNavigation');
|
|
||||||
console.log('❌ AppNavigation.contentScanner: NON INITIALISÉ');
|
|
||||||
} else {
|
|
||||||
success.push('✅ ContentScanner initialisé');
|
|
||||||
console.log('✅ AppNavigation.contentScanner: INITIALISÉ');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!window.AppNavigation.compatibilityChecker) {
|
|
||||||
issues.push('❌ CompatibilityChecker non initialisé dans AppNavigation');
|
|
||||||
console.log('❌ AppNavigation.compatibilityChecker: NON INITIALISÉ');
|
|
||||||
} else {
|
|
||||||
success.push('✅ CompatibilityChecker initialisé');
|
|
||||||
console.log('✅ AppNavigation.compatibilityChecker: INITIALISÉ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Vérification du contenu scanné
|
|
||||||
console.log('\n3️⃣ VÉRIFICATION DU CONTENU SCANNÉ');
|
|
||||||
console.log('----------------------------------');
|
|
||||||
|
|
||||||
if (!window.AppNavigation.scannedContent) {
|
|
||||||
issues.push('❌ Aucun contenu scanné trouvé');
|
|
||||||
console.log('❌ scannedContent: VIDE');
|
|
||||||
} else {
|
|
||||||
const found = window.AppNavigation.scannedContent.found || [];
|
|
||||||
console.log(`✅ scannedContent: ${found.length} modules trouvés`);
|
|
||||||
|
|
||||||
if (found.length === 0) {
|
|
||||||
issues.push('❌ Scanner n\'a trouvé aucun module');
|
|
||||||
} else {
|
|
||||||
success.push(`✅ ${found.length} modules détectés`);
|
|
||||||
|
|
||||||
console.log('\n 📋 MODULES DÉTECTÉS:');
|
|
||||||
found.forEach((content, index) => {
|
|
||||||
console.log(` ${index + 1}. "${content.name}" (ID: ${content.id})`);
|
|
||||||
console.log(` 📁 Fichier: ${content.filename}`);
|
|
||||||
console.log(` 📊 Vocab: ${content.stats?.vocabularyCount || 0}, Phrases: ${content.stats?.sentenceCount || 0}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Chercher spécifiquement Dragon's Pearl
|
|
||||||
const dragonPearl = found.find(c =>
|
|
||||||
c.name.includes('Dragon') ||
|
|
||||||
c.id.includes('chinese-long-story') ||
|
|
||||||
c.filename.includes('chinese-long-story')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (dragonPearl) {
|
|
||||||
success.push('✅ Dragon\'s Pearl détecté dans le scan');
|
|
||||||
console.log(`\n 🐉 DRAGON'S PEARL TROUVÉ:`);
|
|
||||||
console.log(` Nom: "${dragonPearl.name}"`);
|
|
||||||
console.log(` ID: "${dragonPearl.id}"`);
|
|
||||||
console.log(` Fichier: "${dragonPearl.filename}"`);
|
|
||||||
} else {
|
|
||||||
issues.push('❌ Dragon\'s Pearl non trouvé dans le scan');
|
|
||||||
console.log('\n ❌ DRAGON\'S PEARL: NON TROUVÉ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Vérification des modules JavaScript
|
|
||||||
console.log('\n4️⃣ VÉRIFICATION DES MODULES JAVASCRIPT');
|
|
||||||
console.log('---------------------------------------');
|
|
||||||
|
|
||||||
if (!window.ContentModules) {
|
|
||||||
issues.push('❌ window.ContentModules non défini');
|
|
||||||
console.log('❌ window.ContentModules: NON DÉFINI');
|
|
||||||
} else {
|
|
||||||
const modules = Object.keys(window.ContentModules);
|
|
||||||
console.log(`✅ window.ContentModules: ${modules.length} modules`);
|
|
||||||
console.log(` Modules: ${modules.join(', ')}`);
|
|
||||||
|
|
||||||
if (window.ContentModules.ChineseLongStory) {
|
|
||||||
success.push('✅ Module ChineseLongStory chargé');
|
|
||||||
console.log('✅ ChineseLongStory: CHARGÉ');
|
|
||||||
|
|
||||||
const module = window.ContentModules.ChineseLongStory;
|
|
||||||
console.log(` Nom: "${module.name}"`);
|
|
||||||
console.log(` Vocabulaire: ${Object.keys(module.vocabulary || {}).length} mots`);
|
|
||||||
console.log(` Story: ${module.story ? 'OUI' : 'NON'}`);
|
|
||||||
} else {
|
|
||||||
issues.push('❌ Module ChineseLongStory non chargé');
|
|
||||||
console.log('❌ ChineseLongStory: NON CHARGÉ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Test de compatibilité
|
|
||||||
console.log('\n5️⃣ TEST DE COMPATIBILITÉ');
|
|
||||||
console.log('-------------------------');
|
|
||||||
|
|
||||||
if (window.AppNavigation?.compatibilityChecker && window.ContentModules?.ChineseLongStory) {
|
|
||||||
try {
|
|
||||||
const checker = window.AppNavigation.compatibilityChecker;
|
|
||||||
const content = window.ContentModules.ChineseLongStory;
|
|
||||||
|
|
||||||
console.log('✅ Test de compatibilité possible');
|
|
||||||
|
|
||||||
const games = ['whack-a-mole', 'memory-match', 'quiz-game', 'text-reader'];
|
|
||||||
games.forEach(game => {
|
|
||||||
const result = checker.checkCompatibility(content, game);
|
|
||||||
const status = result.compatible ? '✅' : '❌';
|
|
||||||
console.log(` ${status} ${game}: ${result.score}% - ${result.reason}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
success.push('✅ Système de compatibilité fonctionnel');
|
|
||||||
} catch (error) {
|
|
||||||
issues.push(`❌ Erreur test compatibilité: ${error.message}`);
|
|
||||||
console.log(`❌ ERREUR TEST: ${error.message}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
issues.push('❌ Test de compatibilité impossible');
|
|
||||||
console.log('❌ Test de compatibilité: IMPOSSIBLE');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Rapport final
|
|
||||||
console.log('\n📊 RAPPORT FINAL');
|
|
||||||
console.log('================');
|
|
||||||
console.log(`✅ Succès: ${success.length}`);
|
|
||||||
console.log(`❌ Problèmes: ${issues.length}`);
|
|
||||||
|
|
||||||
if (issues.length > 0) {
|
|
||||||
console.log('\n🚨 PROBLÈMES IDENTIFIÉS:');
|
|
||||||
issues.forEach((issue, index) => {
|
|
||||||
console.log(`${index + 1}. ${issue}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success.length > 0) {
|
|
||||||
console.log('\n🎉 ÉLÉMENTS FONCTIONNELS:');
|
|
||||||
success.forEach((item, index) => {
|
|
||||||
console.log(`${index + 1}. ${item}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { issues, success };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-exécution
|
|
||||||
diagnosticComplet();
|
|
||||||
@ -1,244 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "English Class Demo",
|
|
||||||
"description": "Complete example with all content types - vocabulary, grammar, audio, poems, exercises",
|
|
||||||
"difficulty": "mixed",
|
|
||||||
"language": "english",
|
|
||||||
"icon": "🇬🇧",
|
|
||||||
"vocabulary": {
|
|
||||||
"apple": {
|
|
||||||
"translation": "pomme",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "audio/apple.mp3",
|
|
||||||
"difficulty": "beginner",
|
|
||||||
"examples": ["I eat an apple every day", "The apple is red and sweet"],
|
|
||||||
"grammarNotes": "Count noun - can be singular or plural"
|
|
||||||
},
|
|
||||||
"run": {
|
|
||||||
"translation": "courir",
|
|
||||||
"type": "verb",
|
|
||||||
"pronunciation": "audio/run.mp3",
|
|
||||||
"difficulty": "beginner",
|
|
||||||
"examples": ["I run in the park", "She runs very fast"],
|
|
||||||
"grammarNotes": "Regular verb: run, runs, running, ran"
|
|
||||||
},
|
|
||||||
"beautiful": {
|
|
||||||
"translation": "beau/belle",
|
|
||||||
"type": "adjective",
|
|
||||||
"pronunciation": "audio/beautiful.mp3",
|
|
||||||
"difficulty": "intermediate",
|
|
||||||
"examples": ["The sunset is beautiful", "She has beautiful eyes"],
|
|
||||||
"grammarNotes": "Can be used before noun or after 'be'"
|
|
||||||
},
|
|
||||||
"hello": {
|
|
||||||
"translation": "bonjour",
|
|
||||||
"type": "greeting",
|
|
||||||
"pronunciation": "audio/hello.mp3",
|
|
||||||
"difficulty": "beginner",
|
|
||||||
"examples": ["Hello, how are you?", "Hello everyone!"],
|
|
||||||
"grammarNotes": "Common greeting used anytime"
|
|
||||||
},
|
|
||||||
"cat": "chat",
|
|
||||||
"dog": "chien",
|
|
||||||
"house": "maison"
|
|
||||||
},
|
|
||||||
"grammar": {
|
|
||||||
"presentSimple": {
|
|
||||||
"title": "Present Simple Tense",
|
|
||||||
"explanation": "Used for habits, facts, and general truths. Form: Subject + base verb (+ s for he/she/it)",
|
|
||||||
"examples": [
|
|
||||||
{ "english": "I walk to school", "french": "Je marche à l'école" },
|
|
||||||
{ "english": "She walks to school", "french": "Elle marche à l'école" },
|
|
||||||
{ "english": "They walk to school", "french": "Ils marchent à l'école" }
|
|
||||||
],
|
|
||||||
"exercises": [
|
|
||||||
"Complete: I _____ (play) tennis every Sunday",
|
|
||||||
"Transform: He walk to work → He _____ to work"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"articles": {
|
|
||||||
"title": "Articles: A, An, The",
|
|
||||||
"explanation": "A/An = indefinite articles (one of many). The = definite article (specific one)",
|
|
||||||
"examples": [
|
|
||||||
{ "english": "I see a cat", "french": "Je vois un chat" },
|
|
||||||
{ "english": "I see an elephant", "french": "Je vois un éléphant" },
|
|
||||||
{ "english": "I see the cat from yesterday", "french": "Je vois le chat d'hier" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"audio": {
|
|
||||||
"withText": [
|
|
||||||
{
|
|
||||||
"title": "Daily Routine Conversation",
|
|
||||||
"audioFile": "audio/daily_routine.mp3",
|
|
||||||
"transcript": "A: What time do you wake up? B: I usually wake up at 7 AM. A: That's early! I wake up at 8:30. B: I like to exercise before work. A: That's a good habit!",
|
|
||||||
"translation": "A: À quelle heure te réveilles-tu? B: Je me réveille habituellement à 7h. A: C'est tôt! Je me réveille à 8h30. B: J'aime faire de l'exercice avant le travail. A: C'est une bonne habitude!",
|
|
||||||
"timestamps": [
|
|
||||||
{ "time": 0.5, "text": "What time do you wake up?" },
|
|
||||||
{ "time": 3.2, "text": "I usually wake up at 7 AM" },
|
|
||||||
{ "time": 6.8, "text": "That's early! I wake up at 8:30" },
|
|
||||||
{ "time": 11.1, "text": "I like to exercise before work" },
|
|
||||||
{ "time": 14.5, "text": "That's a good habit!" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Weather Report",
|
|
||||||
"audioFile": "audio/weather.mp3",
|
|
||||||
"transcript": "Today's weather: It's sunny and warm with a high of 25 degrees. Light winds from the south. Perfect day for outdoor activities!",
|
|
||||||
"translation": "Météo d'aujourd'hui: Il fait ensoleillé et chaud avec un maximum de 25 degrés. Vents légers du sud. Journée parfaite pour les activités extérieures!"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"withoutText": [
|
|
||||||
{
|
|
||||||
"title": "Mystery Conversation",
|
|
||||||
"audioFile": "audio/mystery.mp3",
|
|
||||||
"questions": [
|
|
||||||
{ "question": "How many people are speaking?", "type": "ai_interpreted" },
|
|
||||||
{ "question": "What are they talking about?", "type": "ai_interpreted" },
|
|
||||||
{ "question": "What is the mood of the conversation?", "type": "ai_interpreted" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"poems": [
|
|
||||||
{
|
|
||||||
"title": "Roses Are Red",
|
|
||||||
"content": "Roses are red,\nViolets are blue,\nSugar is sweet,\nAnd so are you.",
|
|
||||||
"translation": "Les roses sont rouges,\nLes violettes sont bleues,\nLe sucre est doux,\nEt toi aussi.",
|
|
||||||
"audioFile": "audio/roses_poem.mp3",
|
|
||||||
"culturalContext": "Traditional English nursery rhyme pattern, often used to teach basic rhyming and poetry structure to children."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Twinkle, Twinkle",
|
|
||||||
"content": "Twinkle, twinkle, little star,\nHow I wonder what you are.\nUp above the world so high,\nLike a diamond in the sky.",
|
|
||||||
"audioFile": "audio/twinkle.mp3",
|
|
||||||
"culturalContext": "Famous children's lullaby, one of the most recognizable songs in English-speaking countries."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fillInBlanks": [
|
|
||||||
{
|
|
||||||
"sentence": "I _____ to school every day",
|
|
||||||
"options": ["go", "goes", "going", "went"],
|
|
||||||
"correctAnswer": "go",
|
|
||||||
"explanation": "Present simple with 'I' uses base form of verb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "She _____ a book right now",
|
|
||||||
"options": ["read", "reads", "reading", "is reading"],
|
|
||||||
"correctAnswer": "is reading",
|
|
||||||
"explanation": "Present continuous for actions happening now"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "The weather is _____ today",
|
|
||||||
"type": "open_ended",
|
|
||||||
"acceptedAnswers": ["nice", "good", "beautiful", "sunny", "warm", "pleasant", "lovely"],
|
|
||||||
"aiPrompt": "Evaluate if the answer is a positive adjective that could describe good weather"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "I feel _____ when I listen to music",
|
|
||||||
"type": "open_ended",
|
|
||||||
"acceptedAnswers": ["happy", "relaxed", "calm", "peaceful", "good", "better"],
|
|
||||||
"aiPrompt": "Check if the answer describes a positive emotion or feeling"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"corrections": [
|
|
||||||
{
|
|
||||||
"incorrect": "I are happy today",
|
|
||||||
"correct": "I am happy today",
|
|
||||||
"explanation": "Use 'am' with pronoun 'I', not 'are'",
|
|
||||||
"type": "grammar_correction"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"incorrect": "She don't like apples",
|
|
||||||
"correct": "She doesn't like apples",
|
|
||||||
"explanation": "Use 'doesn't' with he/she/it, not 'don't'",
|
|
||||||
"type": "grammar_correction"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"incorrect": "I can to swim",
|
|
||||||
"correct": "I can swim",
|
|
||||||
"explanation": "After modal verbs like 'can', use base form without 'to'",
|
|
||||||
"type": "grammar_correction"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"comprehension": [
|
|
||||||
{
|
|
||||||
"text": "Sarah is a 25-year-old teacher who lives in London. Every morning, she wakes up at 6:30 AM and goes for a jog in the park near her house. After jogging, she has breakfast and reads the news. She loves her job because she enjoys working with children and helping them learn. On weekends, Sarah likes to visit museums and try new restaurants with her friends.",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"question": "What is Sarah's profession?",
|
|
||||||
"type": "multiple_choice",
|
|
||||||
"options": ["Doctor", "Teacher", "Engineer", "Artist"],
|
|
||||||
"correctAnswer": "Teacher"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "What does Sarah do every morning?",
|
|
||||||
"type": "ai_interpreted",
|
|
||||||
"evaluationPrompt": "Check if answer mentions waking up early, jogging, and having breakfast"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "Why does Sarah love her job?",
|
|
||||||
"type": "ai_interpreted",
|
|
||||||
"evaluationPrompt": "Verify answer mentions working with children and helping them learn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "How would you describe Sarah's lifestyle?",
|
|
||||||
"type": "ai_interpreted",
|
|
||||||
"evaluationPrompt": "Accept answers mentioning active, healthy, social, or organized lifestyle"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"matching": [
|
|
||||||
{
|
|
||||||
"title": "Match Animals to Their Sounds",
|
|
||||||
"leftColumn": ["Cat", "Dog", "Cow", "Bird"],
|
|
||||||
"rightColumn": ["Woof", "Meow", "Tweet", "Moo"],
|
|
||||||
"correctPairs": [
|
|
||||||
{ "left": "Cat", "right": "Meow" },
|
|
||||||
{ "left": "Dog", "right": "Woof" },
|
|
||||||
{ "left": "Cow", "right": "Moo" },
|
|
||||||
{ "left": "Bird", "right": "Tweet" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Match Colors in English and French",
|
|
||||||
"leftColumn": ["Red", "Blue", "Green", "Yellow"],
|
|
||||||
"rightColumn": ["Bleu", "Vert", "Rouge", "Jaune"],
|
|
||||||
"correctPairs": [
|
|
||||||
{ "left": "Red", "right": "Rouge" },
|
|
||||||
{ "left": "Blue", "right": "Bleu" },
|
|
||||||
{ "left": "Green", "right": "Vert" },
|
|
||||||
{ "left": "Yellow", "right": "Jaune" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sentences": [
|
|
||||||
{ "english": "Hello, how are you?", "french": "Bonjour, comment allez-vous?" },
|
|
||||||
{ "english": "I like to read books", "french": "J'aime lire des livres" },
|
|
||||||
{ "english": "The weather is nice today", "french": "Il fait beau aujourd'hui" },
|
|
||||||
{ "english": "Can you help me please?", "french": "Pouvez-vous m'aider s'il vous plaît?" }
|
|
||||||
],
|
|
||||||
"texts": [
|
|
||||||
{
|
|
||||||
"title": "My Daily Routine",
|
|
||||||
"content": "I wake up at 7 AM every day. First, I brush my teeth and take a shower. Then I have breakfast with my family. After breakfast, I go to work by bus. I work from 9 AM to 5 PM. In the evening, I cook dinner and watch TV. I go to bed at 10 PM.",
|
|
||||||
"translation": "Je me réveille à 7h tous les jours. D'abord, je me brosse les dents et prends une douche. Ensuite je prends le petit déjeuner avec ma famille. Après le petit déjeuner, je vais au travail en bus. Je travaille de 9h à 17h. Le soir, je cuisine le dîner et regarde la télé. Je me couche à 22h."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "The Four Seasons",
|
|
||||||
"content": "There are four seasons in a year: spring, summer, autumn, and winter. Spring is warm and flowers bloom. Summer is hot and sunny. Autumn is cool and leaves change colors. Winter is cold and it sometimes snows.",
|
|
||||||
"translation": "Il y a quatre saisons dans une année: le printemps, l'été, l'automne et l'hiver. Le printemps est chaud et les fleurs fleurissent. L'été est chaud et ensoleillé. L'automne est frais et les feuilles changent de couleur. L'hiver est froid et il neige parfois."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dialogues": [
|
|
||||||
{
|
|
||||||
"title": "At the Restaurant",
|
|
||||||
"conversation": [
|
|
||||||
{ "speaker": "Waiter", "english": "Good evening! Welcome to our restaurant.", "french": "Bonsoir! Bienvenue dans notre restaurant." },
|
|
||||||
{ "speaker": "Customer", "english": "Thank you. Can I see the menu please?", "french": "Merci. Puis-je voir le menu s'il vous plaît?" },
|
|
||||||
{ "speaker": "Waiter", "english": "Of course! Here you are. What would you like to drink?", "french": "Bien sûr! Voici. Que voulez-vous boire?" },
|
|
||||||
{ "speaker": "Customer", "english": "I'll have a glass of water, please.", "french": "Je prendrai un verre d'eau, s'il vous plaît." }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,620 +0,0 @@
|
|||||||
{
|
|
||||||
// ========================================
|
|
||||||
// EDUCATIONAL CONTENT MODULE SPECIFICATION
|
|
||||||
// ========================================
|
|
||||||
// This JSON defines a complete content module for language learning applications.
|
|
||||||
// All fields are OPTIONAL unless explicitly marked as REQUIRED.
|
|
||||||
// Systems should gracefully handle missing fields and adapt functionality accordingly.
|
|
||||||
|
|
||||||
// === CORE METADATA (REQUIRED) ===
|
|
||||||
// Essential identification and configuration data
|
|
||||||
"id": "english_class_demo_complete_example", // REQUIRED: Unique identifier (lowercase, underscores)
|
|
||||||
"name": "English Class Demo - Complete Example", // REQUIRED: Human-readable display name
|
|
||||||
"description": "Comprehensive example showcasing all content structure possibilities", // REQUIRED: Brief description
|
|
||||||
"difficulty": 6, // REQUIRED: Integer 1-10 (1=absolute beginner, 10=native level)
|
|
||||||
"original_lang": "english", // REQUIRED: ISO language code of source content
|
|
||||||
"user_lang": "french", // REQUIRED: ISO language code for translations
|
|
||||||
"icon": "assets/icons/uk-flag.svg", // OPTIONAL: Path to icon file (relative or absolute)
|
|
||||||
"icon_fallback": "🇬🇧", // REQUIRED if icon specified: Emoji fallback for missing files
|
|
||||||
|
|
||||||
// === CLASSIFICATION SYSTEM ===
|
|
||||||
// Used for content discovery, filtering, and game compatibility assessment
|
|
||||||
"tags": [
|
|
||||||
// Content characteristics - systems can filter/search using these
|
|
||||||
"beginner-friendly", // Suitable for beginners (difficulty ≤ 4)
|
|
||||||
"audio-rich", // Contains significant audio content (>50% items have audio)
|
|
||||||
"grammar-focus", // Emphasizes grammar learning (has grammar section)
|
|
||||||
"cultural-content", // Includes cultural context (has cultural section)
|
|
||||||
"interactive", // Has interactive exercises (matching, corrections, etc.)
|
|
||||||
"daily-life", // Real-world applicable content
|
|
||||||
"conversation", // Dialogue-based learning (has dialogues section)
|
|
||||||
"multimedia" // Multiple content types (text, audio, visual)
|
|
||||||
],
|
|
||||||
|
|
||||||
// === LEARNING OBJECTIVES ===
|
|
||||||
// Skills and competencies this module addresses - used for progress tracking
|
|
||||||
"skills_covered": [
|
|
||||||
"vocabulary_recognition", // Identifying and understanding words
|
|
||||||
"vocabulary_production", // Using words correctly in context
|
|
||||||
"listening_comprehension", // Understanding spoken language
|
|
||||||
"reading_comprehension", // Understanding written text
|
|
||||||
"pronunciation", // Correct sound production
|
|
||||||
"grammar_application", // Using grammar rules correctly
|
|
||||||
"cultural_awareness", // Understanding cultural context
|
|
||||||
"conversation_skills", // Interactive dialogue ability
|
|
||||||
"translation", // Converting between languages
|
|
||||||
"pattern_recognition", // Identifying linguistic patterns
|
|
||||||
"error_correction", // Identifying and fixing mistakes
|
|
||||||
"audio_discrimination" // Distinguishing between sounds
|
|
||||||
],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// VOCABULARY SECTION - PROGRESSIVE COMPLEXITY
|
|
||||||
// ========================================
|
|
||||||
// Word definitions with varying levels of detail. Systems should adapt based on available data.
|
|
||||||
"vocabulary": {
|
|
||||||
|
|
||||||
// LEVEL 1: MINIMAL (Translation only)
|
|
||||||
// Simplest form - just word and translation string
|
|
||||||
// Usage: Basic vocabulary games, simple matching
|
|
||||||
"cat": "chat",
|
|
||||||
"dog": "chien",
|
|
||||||
"house": "maison",
|
|
||||||
|
|
||||||
// LEVEL 2: BASIC (Essential data)
|
|
||||||
// Translation + grammatical type
|
|
||||||
// Usage: Games that need word categorization
|
|
||||||
"book": {
|
|
||||||
"translation": "livre",
|
|
||||||
"type": "noun" // Values: noun, verb, adjective, adverb, greeting, number, etc.
|
|
||||||
},
|
|
||||||
|
|
||||||
"read": {
|
|
||||||
"translation": "lire",
|
|
||||||
"type": "verb"
|
|
||||||
},
|
|
||||||
|
|
||||||
// LEVEL 3: MEDIUM (Enhanced with media OR pronunciation)
|
|
||||||
// Add ONE media type: pronunciation OR image
|
|
||||||
// Usage: Games with visual or audio components
|
|
||||||
"apple": {
|
|
||||||
"translation": "pomme",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "/ˈæp.əl/", // IPA phonetic notation (recommended format)
|
|
||||||
"image": "images/vocabulary/apple.jpg" // Path to image file
|
|
||||||
},
|
|
||||||
|
|
||||||
"beautiful": {
|
|
||||||
"translation": "beau/belle",
|
|
||||||
"type": "adjective",
|
|
||||||
"pronunciation": "/ˈbjuː.tɪ.fəl/",
|
|
||||||
"examples": ["The sunset is beautiful"] // Array of usage examples
|
|
||||||
},
|
|
||||||
|
|
||||||
// LEVEL 4: RICH (Multi-media)
|
|
||||||
// Multiple media types for enhanced learning
|
|
||||||
// Usage: Advanced vocabulary games, multimedia lessons
|
|
||||||
"elephant": {
|
|
||||||
"translation": "éléphant",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "/ˈel.ɪ.fənt/",
|
|
||||||
"audio": "audio/vocabulary/elephant.mp3", // Audio pronunciation file
|
|
||||||
"image": "images/vocabulary/elephant.jpg", // Visual representation
|
|
||||||
"examples": [
|
|
||||||
"The elephant is huge",
|
|
||||||
"Elephants have good memory"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// LEVEL 5: COMPREHENSIVE (Full linguistic data)
|
|
||||||
// Maximum detail for advanced language learning
|
|
||||||
// Usage: Professional language learning, detailed grammar games
|
|
||||||
"run": {
|
|
||||||
"translation": "courir",
|
|
||||||
"type": "verb",
|
|
||||||
"pronunciation": "/rʌn/",
|
|
||||||
"audio": "audio/vocabulary/run.mp3",
|
|
||||||
"image": "images/vocabulary/run_action.gif", // Can be GIF for actions
|
|
||||||
"examples": [
|
|
||||||
"I run in the park every morning",
|
|
||||||
"She runs faster than me",
|
|
||||||
"They ran to catch the bus"
|
|
||||||
],
|
|
||||||
"grammarNotes": "Irregular verb: run/runs/running/ran/run", // Teaching notes
|
|
||||||
"conjugation": { // Verb forms for grammar games
|
|
||||||
"present": ["run", "runs"],
|
|
||||||
"past": "ran",
|
|
||||||
"participle": "run",
|
|
||||||
"continuous": "running"
|
|
||||||
},
|
|
||||||
"difficulty_context": "Physical action verb - easy to demonstrate" // Teaching hints
|
|
||||||
},
|
|
||||||
|
|
||||||
// LEVEL 6: ADVANCED (Cultural context)
|
|
||||||
// Includes cultural and contextual information
|
|
||||||
// Usage: Cultural awareness games, advanced language courses
|
|
||||||
"breakfast": {
|
|
||||||
"translation": "petit-déjeuner",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "/ˈbrek.fəst/",
|
|
||||||
"audio": "audio/vocabulary/breakfast.mp3",
|
|
||||||
"image": "images/vocabulary/breakfast.jpg",
|
|
||||||
"examples": [
|
|
||||||
"I have breakfast at 7 AM",
|
|
||||||
"What did you have for breakfast?",
|
|
||||||
"Breakfast is the most important meal"
|
|
||||||
],
|
|
||||||
"cultural_note": "Traditional English breakfast includes eggs, bacon, beans, and toast"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// GRAMMAR SYSTEM - STRUCTURED LESSONS
|
|
||||||
// ========================================
|
|
||||||
// Step-by-step grammar instruction with alternating explanations and exercises
|
|
||||||
// NOTE: This system is for dedicated grammar games/apps, not general content games
|
|
||||||
"grammar": {
|
|
||||||
"present_simple_be": { // Lesson identifier (must be unique)
|
|
||||||
"id": "present_simple_be", // Same as key for validation
|
|
||||||
"title": "Present Simple - Verb 'to be'", // Display title
|
|
||||||
"difficulty": 3, // 1-10 scale
|
|
||||||
"prerequisite": null, // null = no prerequisite, or reference another lesson id
|
|
||||||
"estimated_time": 15, // Estimated minutes to complete
|
|
||||||
"learning_objectives": [ // Clear, measurable goals
|
|
||||||
"Conjugate 'to be' in present tense",
|
|
||||||
"Use 'to be' in affirmative sentences",
|
|
||||||
"Form questions with 'to be'"
|
|
||||||
],
|
|
||||||
|
|
||||||
// Sequential learning steps - MUST be in order
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "explanation", // Types: "explanation" or "exercise"
|
|
||||||
"order": 1, // Integer ordering (must be sequential)
|
|
||||||
"title": "Introduction to 'be'",
|
|
||||||
"content": "The verb 'to be' is the most important verb in English. It has three forms in present tense.",
|
|
||||||
"translation": "Le verbe 'être' est le verbe le plus important en anglais. Il a trois formes au présent.",
|
|
||||||
"examples": [ // Array of example objects
|
|
||||||
{ "original": "I am happy", "userLanguage": "Je suis heureux" },
|
|
||||||
{ "original": "You are smart", "userLanguage": "Tu es intelligent" },
|
|
||||||
{ "original": "She is tall", "userLanguage": "Elle est grande" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "exercise",
|
|
||||||
"order": 2,
|
|
||||||
"exercise_type": "multiple_choice", // Exercise types: multiple_choice, transformation, fill_blanks, classification, conjugation
|
|
||||||
"title": "Choose the correct form",
|
|
||||||
"questions": [ // Array of question objects
|
|
||||||
{
|
|
||||||
"question": "I ___ a student",
|
|
||||||
"options": ["am", "is", "are"], // Array of choices
|
|
||||||
"correct": "am", // Single correct answer
|
|
||||||
"explanation": "Use 'am' with 'I'" // Feedback for learner
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "She ___ my friend",
|
|
||||||
"options": ["am", "is", "are"],
|
|
||||||
"correct": "is",
|
|
||||||
"explanation": "Use 'is' with 'she', 'he', 'it'"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "explanation",
|
|
||||||
"order": 3,
|
|
||||||
"title": "Negative forms",
|
|
||||||
"content": "To make negative sentences with 'be', add 'not' after the verb. We often use contractions.",
|
|
||||||
"translation": "Pour faire des phrases négatives avec 'être', ajoutez 'not' après le verbe. On utilise souvent des contractions.",
|
|
||||||
"examples": [
|
|
||||||
{
|
|
||||||
"original": "I am not tired",
|
|
||||||
"userLanguage": "Je ne suis pas fatigué",
|
|
||||||
"contraction": "I'm not tired" // Optional contraction form
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "exercise",
|
|
||||||
"order": 4,
|
|
||||||
"exercise_type": "transformation",
|
|
||||||
"title": "Make these sentences negative",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"original": "I am busy", // Source sentence
|
|
||||||
"correct": "I am not busy", // Primary correct answer
|
|
||||||
"alternative_correct": ["I'm not busy"] // Array of acceptable alternatives
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Lesson summary and reinforcement
|
|
||||||
"summary": {
|
|
||||||
"key_points": [ // Main takeaways
|
|
||||||
"I am, you are, he/she/it is, we are, they are",
|
|
||||||
"Negative: add 'not' after 'be'",
|
|
||||||
"Questions: put 'be' before subject"
|
|
||||||
],
|
|
||||||
"common_mistakes": [ // Frequent errors with corrections
|
|
||||||
{
|
|
||||||
"incorrect": "I are happy",
|
|
||||||
"correct": "I am happy",
|
|
||||||
"explanation": "Use 'am' with 'I'"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// TEXT CONTENT - FLEXIBLE USAGE
|
|
||||||
// ========================================
|
|
||||||
// Text-based content that can optionally include exercises
|
|
||||||
"texts": [
|
|
||||||
{
|
|
||||||
"title": "My Daily Routine", // Display title
|
|
||||||
"content": "I wake up at 7 AM every day. First, I brush my teeth and take a shower...", // Main text
|
|
||||||
"translation": "Je me réveille à 7h tous les jours. D'abord, je me brosse les dents...", // Full translation
|
|
||||||
|
|
||||||
// OPTIONAL: Add comprehension questions to any text
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"question": "What time does the person wake up?",
|
|
||||||
"type": "multiple_choice", // Types: multiple_choice, ai_interpreted
|
|
||||||
"options": ["6 AM", "7 AM", "8 AM", "9 AM"],
|
|
||||||
"correctAnswer": "7 AM"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "Describe the person's evening routine",
|
|
||||||
"type": "ai_interpreted", // Requires AI evaluation
|
|
||||||
"evaluationPrompt": "Check if answer mentions cooking dinner and watching TV" // Prompt for AI evaluator
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// OPTIONAL: Add fill-in-the-blank exercises to any text
|
|
||||||
"fillInBlanks": [
|
|
||||||
{
|
|
||||||
"sentence": "I wake up ___ 7 AM every day",
|
|
||||||
"options": ["at", "in", "on"], // Multiple choice options
|
|
||||||
"correctAnswer": "at",
|
|
||||||
"explanation": "Use 'at' with specific times"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "The routine is very ___",
|
|
||||||
"type": "open_ended", // Open-ended answer
|
|
||||||
"acceptedAnswers": ["good", "nice", "healthy", "regular"], // Acceptable answers
|
|
||||||
"aiPrompt": "Check if answer describes routine positively" // AI evaluation prompt
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// AUDIO-ONLY CONTENT
|
|
||||||
// ========================================
|
|
||||||
// Pure listening exercises WITHOUT transcripts
|
|
||||||
// NOTE: Audio WITH text should go in texts[] or dialogues[] sections
|
|
||||||
"audio": [
|
|
||||||
{
|
|
||||||
"title": "Mystery Conversation - Restaurant",
|
|
||||||
"audioFile": "audio/listening/restaurant_sounds.mp3", // Audio file path
|
|
||||||
"type": "ambient_listening", // Types: ambient_listening, sound_identification, pronunciation_exercise
|
|
||||||
"description": "Listen to the ambient sounds and conversation", // Optional description
|
|
||||||
"questions": [ // Listening comprehension questions
|
|
||||||
{
|
|
||||||
"question": "Where does this conversation take place?",
|
|
||||||
"type": "multiple_choice",
|
|
||||||
"options": ["Restaurant", "Office", "School", "Park"],
|
|
||||||
"correctAnswer": "Restaurant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "How many people are speaking?",
|
|
||||||
"type": "ai_interpreted",
|
|
||||||
"evaluationPrompt": "Accept numeric answers indicating number of distinct voices"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Pronunciation Discrimination",
|
|
||||||
"audioFile": "audio/pronunciation/minimal_pairs.mp3",
|
|
||||||
"type": "pronunciation_exercise",
|
|
||||||
"description": "Listen to similar sounding words and identify differences",
|
|
||||||
"word_pairs": [ // For pronunciation exercises
|
|
||||||
["ship", "sheep"],
|
|
||||||
["live", "leave"],
|
|
||||||
["cat", "cut"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// CULTURAL CONTENT - RICH CONTEXT
|
|
||||||
// ========================================
|
|
||||||
// Cultural material with educational value and context
|
|
||||||
"cultural": {
|
|
||||||
// Poetry and literary works
|
|
||||||
"poems": [
|
|
||||||
{
|
|
||||||
"title": "Roses Are Red",
|
|
||||||
"content": "Roses are red,\nViolets are blue,\nSugar is sweet,\nAnd so are you.",
|
|
||||||
"translation": "Les roses sont rouges,\nLes violettes sont bleues...",
|
|
||||||
"audio": "audio/poems/roses.mp3", // Optional audio recitation
|
|
||||||
"image": "images/cultural/roses_poem_illustration.jpg", // Optional illustration
|
|
||||||
"type": "nursery_rhyme", // Types: nursery_rhyme, classic_poem, song, etc.
|
|
||||||
"cultural_context": "Traditional English nursery rhyme pattern...",
|
|
||||||
"learning_focus": ["rhyme_patterns", "basic_vocabulary", "rhythm"] // Educational objectives
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Proverbs and sayings
|
|
||||||
"proverbs": [
|
|
||||||
{
|
|
||||||
"original": "The early bird catches the worm",
|
|
||||||
"userLanguage": "L'avenir appartient à ceux qui se lèvent tôt",
|
|
||||||
"meaning": "People who wake up early and start working have better chances of success",
|
|
||||||
"image": "images/cultural/early_bird_illustration.jpg",
|
|
||||||
"cultural_context": "Common English saying emphasizing the value of being proactive...",
|
|
||||||
"equivalent_proverbs": { // Cross-cultural equivalents
|
|
||||||
"french": "L'avenir appartient à ceux qui se lèvent tôt",
|
|
||||||
"literal": "Le premier oiseau attrape le ver"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Unified cultural facts system
|
|
||||||
"culture_facts": [
|
|
||||||
{
|
|
||||||
"name": "Tea Time",
|
|
||||||
"category": "tradition", // Categories: tradition, holiday, food, custom, etc.
|
|
||||||
"description": "Traditional British custom of drinking tea in the afternoon, usually around 4 PM",
|
|
||||||
"translation": "L'heure du thé - tradition britannique...",
|
|
||||||
"image": "images/cultural/tea_time.jpg",
|
|
||||||
"cultural_significance": "Social ritual that brings people together...",
|
|
||||||
"vocabulary_related": ["tea", "biscuit", "afternoon", "tradition", "social"], // Related vocabulary
|
|
||||||
"region": "uk" // Geographic association (uk, us, global, etc.)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Christmas",
|
|
||||||
"category": "holiday",
|
|
||||||
"date": "December 25th", // Optional date field for holidays
|
|
||||||
"description": "Major Christian holiday celebrating the birth of Jesus Christ",
|
|
||||||
"translation": "Noël - grande fête chrétienne...",
|
|
||||||
"image": "images/cultural/christmas_celebration.jpg",
|
|
||||||
"customs": ["gift_giving", "family_gatherings", "christmas_tree", "caroling"], // Associated customs
|
|
||||||
"vocabulary_related": ["Christmas", "gift", "tree", "family", "celebration"],
|
|
||||||
"region": "global"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Fish and Chips",
|
|
||||||
"category": "food",
|
|
||||||
"description": "Traditional British dish of battered fish with fried potatoes",
|
|
||||||
"translation": "Poisson-frites - plat britannique traditionnel...",
|
|
||||||
"image": "images/cultural/fish_and_chips.jpg",
|
|
||||||
"cultural_context": "Popular working-class meal, often served in newspaper wrapping",
|
|
||||||
"vocabulary_related": ["fish", "chips", "batter", "traditional", "popular"],
|
|
||||||
"region": "uk",
|
|
||||||
"ingredients": ["fish", "potatoes", "batter", "oil"], // For food items
|
|
||||||
"typical_sides": ["mushy_peas", "tartar_sauce", "malt_vinegar"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// SENTENCES WITH PARAMETERS
|
|
||||||
// ========================================
|
|
||||||
// Generic sentence structure used for various exercises including corrections
|
|
||||||
"sentences": [
|
|
||||||
{
|
|
||||||
"original": "Hello, how are you?", // Source language sentence
|
|
||||||
"userLanguage": "Bonjour, comment allez-vous?", // Target language translation
|
|
||||||
"type": "greeting", // Sentence classification
|
|
||||||
"formality": "neutral" // Additional parameter
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"original": "I like to read books",
|
|
||||||
"userLanguage": "J'aime lire des livres",
|
|
||||||
"type": "preference_statement",
|
|
||||||
"tense": "present_simple"
|
|
||||||
},
|
|
||||||
|
|
||||||
// Sentences with correction exercise data
|
|
||||||
{
|
|
||||||
"original": "I am happy today", // CORRECT version
|
|
||||||
"userLanguage": "Je suis heureux aujourd'hui",
|
|
||||||
"type": "correction_target", // Indicates this is for correction exercises
|
|
||||||
"correction_data": {
|
|
||||||
"incorrect_versions": [ // Array of common mistakes
|
|
||||||
{
|
|
||||||
"text": "I are happy today", // Incorrect version
|
|
||||||
"error_type": "subject_verb_agreement", // Classification of error
|
|
||||||
"explanation": "Use 'am' with pronoun 'I', not 'are'",
|
|
||||||
"difficulty": 2 // Difficulty of catching this error (1-10)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "I is happy today",
|
|
||||||
"error_type": "subject_verb_agreement",
|
|
||||||
"explanation": "Use 'am' with pronoun 'I', not 'is'",
|
|
||||||
"difficulty": 1 // Easier to spot
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammar_focus": "be_verb_conjugation", // Grammar topic
|
|
||||||
"common_mistake": true // Boolean: is this a frequent error?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// MATCHING EXERCISES - DUAL SYSTEM
|
|
||||||
// ========================================
|
|
||||||
// Two types of matching exercises: traditional 2-column and flexible multi-column
|
|
||||||
"matching": [
|
|
||||||
|
|
||||||
// TYPE 1: Traditional two-column matching (backward compatibility)
|
|
||||||
{
|
|
||||||
"title": "Match Animals to Their Sounds",
|
|
||||||
"type": "two_column_matching", // Explicit type declaration
|
|
||||||
"leftColumn": ["Cat", "Dog", "Cow", "Bird"], // Left side items
|
|
||||||
"rightColumn": ["Woof", "Meow", "Tweet", "Moo"], // Right side items
|
|
||||||
"correctPairs": [ // Valid connections
|
|
||||||
{ "left": "Cat", "right": "Meow" },
|
|
||||||
{ "left": "Dog", "right": "Woof" },
|
|
||||||
{ "left": "Cow", "right": "Moo" },
|
|
||||||
{ "left": "Bird", "right": "Tweet" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// TYPE 2: Multi-column matching (new flexible system)
|
|
||||||
{
|
|
||||||
"title": "Build Correct Sentences",
|
|
||||||
"type": "multi_column_matching", // Flexible N-column system
|
|
||||||
"columns": [ // Array of columns (minimum 2, no maximum)
|
|
||||||
{
|
|
||||||
"id": 1, // REQUIRED: Numeric identifier for referencing
|
|
||||||
"name": "Subject", // OPTIONAL: Display label (can be omitted)
|
|
||||||
"items": ["I", "She", "They", "We"] // REQUIRED: Items in this column
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": "Verb",
|
|
||||||
"items": ["am", "is", "are", "are"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"name": "Complement",
|
|
||||||
"items": ["happy", "a teacher", "students", "friends"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"valid_combinations": [ // Valid combinations using column IDs
|
|
||||||
{"1": "I", "2": "am", "3": "happy"}, // References by column ID
|
|
||||||
{"1": "I", "2": "am", "3": "a teacher"},
|
|
||||||
{"1": "She", "2": "is", "3": "happy"},
|
|
||||||
{"1": "She", "2": "is", "3": "a teacher"},
|
|
||||||
{"1": "They", "2": "are", "3": "students"},
|
|
||||||
{"1": "They", "2": "are", "3": "friends"},
|
|
||||||
{"1": "We", "2": "are", "3": "students"},
|
|
||||||
{"1": "We", "2": "are", "3": "friends"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// TYPE 2 Example without column names (demonstrates flexibility)
|
|
||||||
{
|
|
||||||
"title": "Match Country Information",
|
|
||||||
"type": "multi_column_matching",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "Country",
|
|
||||||
"items": ["France", "Spain", "Italy", "Germany"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": "Capital",
|
|
||||||
"items": ["Paris", "Madrid", "Rome", "Berlin"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
// NO "name" field - demonstrates optional nature
|
|
||||||
"items": ["French", "Spanish", "Italian", "German"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"valid_combinations": [
|
|
||||||
{"1": "France", "2": "Paris", "3": "French"},
|
|
||||||
{"1": "Spain", "2": "Madrid", "3": "Spanish"},
|
|
||||||
{"1": "Italy", "2": "Rome", "3": "Italian"},
|
|
||||||
{"1": "Germany", "2": "Berlin", "3": "German"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// DIALOGUES - CONVERSATIONAL CONTENT
|
|
||||||
// ========================================
|
|
||||||
// Structured conversations between speakers
|
|
||||||
// NOTE: Audio WITH text belongs here, not in audio[] section
|
|
||||||
"dialogues": [
|
|
||||||
{
|
|
||||||
"title": "At the Restaurant", // Dialogue title
|
|
||||||
"conversation": [ // Array of turns in conversation
|
|
||||||
{
|
|
||||||
"speaker": "Waiter", // Speaker identification
|
|
||||||
"original": "Good evening! Welcome to our restaurant.",
|
|
||||||
"userLanguage": "Bonsoir! Bienvenue dans notre restaurant."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"speaker": "Customer",
|
|
||||||
"original": "Thank you. Can I see the menu please?",
|
|
||||||
"userLanguage": "Merci. Puis-je voir le menu s'il vous plaît?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// Dialogue with synchronized audio
|
|
||||||
{
|
|
||||||
"title": "Daily Routine Conversation",
|
|
||||||
"audio": "audio/conversations/daily_routine.mp3", // Audio file with spoken dialogue
|
|
||||||
"conversation": [
|
|
||||||
{
|
|
||||||
"speaker": "A",
|
|
||||||
"original": "What time do you wake up?",
|
|
||||||
"userLanguage": "À quelle heure te réveilles-tu?",
|
|
||||||
"timestamp": 0.5 // OPTIONAL: Time in audio file (seconds)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"speaker": "B",
|
|
||||||
"original": "I usually wake up at 7 AM.",
|
|
||||||
"userLanguage": "Je me réveille habituellement à 7h.",
|
|
||||||
"timestamp": 3.2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// IMPLEMENTATION NOTES FOR AI SYSTEMS
|
|
||||||
// ========================================
|
|
||||||
/*
|
|
||||||
FIELD OPTIONALITY:
|
|
||||||
- All fields are optional unless marked REQUIRED
|
|
||||||
- Systems should gracefully handle missing fields
|
|
||||||
- Adapt functionality based on available data richness
|
|
||||||
|
|
||||||
VOCABULARY LEVELS:
|
|
||||||
- Level 1 (string): Basic games only
|
|
||||||
- Level 2-3 (basic object): Standard games
|
|
||||||
- Level 4-6 (rich object): Advanced features
|
|
||||||
|
|
||||||
EXERCISE TYPES:
|
|
||||||
- multiple_choice: Fixed options with single correct answer
|
|
||||||
- transformation: Convert one form to another
|
|
||||||
- fill_blanks: Complete sentences with missing words
|
|
||||||
- classification: Categorize items into groups
|
|
||||||
- conjugation: Verb form exercises
|
|
||||||
- ai_interpreted: Requires AI evaluation with prompt
|
|
||||||
|
|
||||||
AUDIO PLACEMENT:
|
|
||||||
- audio[]: ONLY for content without transcripts
|
|
||||||
- texts[]/dialogues[]: For content WITH transcripts
|
|
||||||
- vocabulary.*.audio: Individual word pronunciation
|
|
||||||
|
|
||||||
MATCHING SYSTEMS:
|
|
||||||
- two_column_matching: Traditional left-right matching
|
|
||||||
- multi_column_matching: Flexible N-column system with numeric IDs
|
|
||||||
|
|
||||||
CULTURAL CONTENT:
|
|
||||||
- poems[]: Literary works with cultural context
|
|
||||||
- proverbs[]: Sayings with cross-cultural equivalents
|
|
||||||
- culture_facts[]: Unified system for traditions, holidays, food, customs
|
|
||||||
|
|
||||||
ERROR HANDLING:
|
|
||||||
- Missing files (audio, images): Use fallbacks or skip features
|
|
||||||
- Invalid references: Ignore and continue with available content
|
|
||||||
- Malformed data: Log errors but don't crash, use defaults
|
|
||||||
|
|
||||||
EXTENSIBILITY:
|
|
||||||
- New exercise types can be added without breaking existing systems
|
|
||||||
- Additional fields can be added to any section
|
|
||||||
- New sections can be added at root level
|
|
||||||
- Systems should ignore unknown fields gracefully
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@ -1,828 +0,0 @@
|
|||||||
{
|
|
||||||
// === METADATA SECTION ===
|
|
||||||
// Core module identification and language configuration
|
|
||||||
"id": "english_class_demo_complete_example", // Unique ID based on name (lowercase, underscores)
|
|
||||||
"name": "English Class Demo - Complete Example",
|
|
||||||
"description": "Comprehensive example showcasing all content structure possibilities",
|
|
||||||
"difficulty": 6, // Scale 1-10: 1=absolute beginner, 10=native level
|
|
||||||
"original_lang": "english", // Language of the original content
|
|
||||||
"user_lang": "french", // User's native language for translations
|
|
||||||
"icon": "assets/icons/uk-flag.svg", // Path to icon file
|
|
||||||
"icon_fallback": "🇬🇧", // Emoji fallback if file missing
|
|
||||||
|
|
||||||
// === SYSTEM TAGS ===
|
|
||||||
// Categorization for content discovery and filtering
|
|
||||||
"tags": [
|
|
||||||
"beginner-friendly", // Suitable for beginners
|
|
||||||
"audio-rich", // Contains significant audio content
|
|
||||||
"grammar-focus", // Emphasizes grammar learning
|
|
||||||
"cultural-content", // Includes cultural context
|
|
||||||
"interactive", // Has interactive exercises
|
|
||||||
"daily-life", // Real-world applicable content
|
|
||||||
"conversation", // Dialogue-based learning
|
|
||||||
"multimedia" // Multiple content types (text, audio, visual)
|
|
||||||
],
|
|
||||||
|
|
||||||
// === SKILLS COVERED ===
|
|
||||||
// Learning objectives and competencies addressed
|
|
||||||
"skills_covered": [
|
|
||||||
"vocabulary_recognition", // Identifying and understanding words
|
|
||||||
"vocabulary_production", // Using words correctly in context
|
|
||||||
"listening_comprehension", // Understanding spoken language
|
|
||||||
"reading_comprehension", // Understanding written text
|
|
||||||
"pronunciation", // Correct sound production
|
|
||||||
"grammar_application", // Using grammar rules correctly
|
|
||||||
"cultural_awareness", // Understanding cultural context
|
|
||||||
"conversation_skills", // Interactive dialogue ability
|
|
||||||
"translation", // Converting between languages
|
|
||||||
"pattern_recognition", // Identifying linguistic patterns
|
|
||||||
"error_correction", // Identifying and fixing mistakes
|
|
||||||
"audio_discrimination" // Distinguishing between sounds
|
|
||||||
],
|
|
||||||
|
|
||||||
// === VOCABULARY SECTION ===
|
|
||||||
// Multiple levels of vocabulary completion from minimal to comprehensive
|
|
||||||
"vocabulary": {
|
|
||||||
|
|
||||||
// === MINIMAL LEVEL (Translation only) ===
|
|
||||||
// Simplest form - just word and translation
|
|
||||||
"cat": "chat",
|
|
||||||
"dog": "chien",
|
|
||||||
"house": "maison",
|
|
||||||
|
|
||||||
// === BASIC LEVEL (Essential data) ===
|
|
||||||
// Translation + type
|
|
||||||
"book": {
|
|
||||||
"translation": "livre",
|
|
||||||
"type": "noun"
|
|
||||||
},
|
|
||||||
|
|
||||||
"read": {
|
|
||||||
"translation": "lire",
|
|
||||||
"type": "verb"
|
|
||||||
},
|
|
||||||
|
|
||||||
// === MEDIUM LEVEL (Common teaching elements) ===
|
|
||||||
// Translation + type + pronunciation OR image
|
|
||||||
"apple": {
|
|
||||||
"translation": "pomme",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "/ˈæp.əl/",
|
|
||||||
"image": "images/vocabulary/apple.jpg" // Visual representation
|
|
||||||
},
|
|
||||||
|
|
||||||
"beautiful": {
|
|
||||||
"translation": "beau/belle",
|
|
||||||
"type": "adjective",
|
|
||||||
"pronunciation": "/ˈbjuː.tɪ.fəl/",
|
|
||||||
"examples": ["The sunset is beautiful"] // Single example
|
|
||||||
},
|
|
||||||
|
|
||||||
// === RICH LEVEL (Audio + visual) ===
|
|
||||||
// Multiple media types for enhanced learning
|
|
||||||
"elephant": {
|
|
||||||
"translation": "éléphant",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "/ˈel.ɪ.fənt/",
|
|
||||||
"audio": "audio/vocabulary/elephant.mp3",
|
|
||||||
"image": "images/vocabulary/elephant.jpg",
|
|
||||||
"examples": [
|
|
||||||
"The elephant is huge",
|
|
||||||
"Elephants have good memory"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// === COMPREHENSIVE LEVEL (Full linguistic data) ===
|
|
||||||
// All possible fields for maximum educational value
|
|
||||||
"run": {
|
|
||||||
"translation": "courir",
|
|
||||||
"type": "verb",
|
|
||||||
"pronunciation": "/rʌn/",
|
|
||||||
"audio": "audio/vocabulary/run.mp3",
|
|
||||||
"image": "images/vocabulary/run_action.gif", // Can be GIF for actions
|
|
||||||
"examples": [
|
|
||||||
"I run in the park every morning",
|
|
||||||
"She runs faster than me",
|
|
||||||
"They ran to catch the bus"
|
|
||||||
],
|
|
||||||
"grammarNotes": "Irregular verb: run/runs/running/ran/run",
|
|
||||||
"conjugation": {
|
|
||||||
"present": ["run", "runs"],
|
|
||||||
"past": "ran",
|
|
||||||
"participle": "run",
|
|
||||||
"continuous": "running"
|
|
||||||
},
|
|
||||||
"difficulty_context": "Physical action verb - easy to demonstrate"
|
|
||||||
},
|
|
||||||
|
|
||||||
// === ADVANCED LEVEL (Cultural and contextual) ===
|
|
||||||
"breakfast": {
|
|
||||||
"translation": "petit-déjeuner",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "/ˈbrek.fəst/",
|
|
||||||
"audio": "audio/vocabulary/breakfast.mp3",
|
|
||||||
"image": "images/vocabulary/breakfast.jpg",
|
|
||||||
"examples": [
|
|
||||||
"I have breakfast at 7 AM",
|
|
||||||
"What did you have for breakfast?",
|
|
||||||
"Breakfast is the most important meal"
|
|
||||||
],
|
|
||||||
"cultural_note": "Traditional English breakfast includes eggs, bacon, beans, and toast"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// === GRAMMAR SYSTEM ===
|
|
||||||
// Step-by-step grammar lessons with explanation -> exercise -> explanation -> exercise flow
|
|
||||||
"grammar": {
|
|
||||||
"present_simple_be": {
|
|
||||||
"id": "present_simple_be",
|
|
||||||
"title": "Present Simple - Verb 'to be'",
|
|
||||||
"difficulty": 3,
|
|
||||||
"prerequisite": null, // No prerequisite - foundational lesson
|
|
||||||
"estimated_time": 15, // minutes
|
|
||||||
"learning_objectives": [
|
|
||||||
"Conjugate 'to be' in present tense",
|
|
||||||
"Use 'to be' in affirmative sentences",
|
|
||||||
"Form questions with 'to be'"
|
|
||||||
],
|
|
||||||
|
|
||||||
// Sequential steps: explanation -> exercise -> explanation -> exercise
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "explanation",
|
|
||||||
"order": 1,
|
|
||||||
"title": "Introduction to 'be'",
|
|
||||||
"content": "The verb 'to be' is the most important verb in English. It has three forms in present tense.",
|
|
||||||
"translation": "Le verbe 'être' est le verbe le plus important en anglais. Il a trois formes au présent.",
|
|
||||||
"examples": [
|
|
||||||
{ "original": "I am happy", "userLanguage": "Je suis heureux" },
|
|
||||||
{ "original": "You are smart", "userLanguage": "Tu es intelligent" },
|
|
||||||
{ "original": "She is tall", "userLanguage": "Elle est grande" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "exercise",
|
|
||||||
"order": 2,
|
|
||||||
"exercise_type": "multiple_choice",
|
|
||||||
"title": "Choose the correct form",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"question": "I ___ a student",
|
|
||||||
"options": ["am", "is", "are"],
|
|
||||||
"correct": "am",
|
|
||||||
"explanation": "Use 'am' with 'I'"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "She ___ my friend",
|
|
||||||
"options": ["am", "is", "are"],
|
|
||||||
"correct": "is",
|
|
||||||
"explanation": "Use 'is' with 'she', 'he', 'it'"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "They ___ teachers",
|
|
||||||
"options": ["am", "is", "are"],
|
|
||||||
"correct": "are",
|
|
||||||
"explanation": "Use 'are' with 'they', 'we', 'you'"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "explanation",
|
|
||||||
"order": 3,
|
|
||||||
"title": "Negative forms",
|
|
||||||
"content": "To make negative sentences with 'be', add 'not' after the verb. We often use contractions.",
|
|
||||||
"translation": "Pour faire des phrases négatives avec 'être', ajoutez 'not' après le verbe. On utilise souvent des contractions.",
|
|
||||||
"examples": [
|
|
||||||
{ "original": "I am not tired", "userLanguage": "Je ne suis pas fatigué", "contraction": "I'm not tired" },
|
|
||||||
{ "original": "He is not here", "userLanguage": "Il n'est pas ici", "contraction": "He isn't here" },
|
|
||||||
{ "original": "We are not ready", "userLanguage": "Nous ne sommes pas prêts", "contraction": "We aren't ready" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "exercise",
|
|
||||||
"order": 4,
|
|
||||||
"exercise_type": "transformation",
|
|
||||||
"title": "Make these sentences negative",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"original": "I am busy",
|
|
||||||
"correct": "I am not busy",
|
|
||||||
"alternative_correct": ["I'm not busy"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"original": "She is happy",
|
|
||||||
"correct": "She is not happy",
|
|
||||||
"alternative_correct": ["She isn't happy"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"original": "They are at home",
|
|
||||||
"correct": "They are not at home",
|
|
||||||
"alternative_correct": ["They aren't at home"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "explanation",
|
|
||||||
"order": 5,
|
|
||||||
"title": "Questions with 'be'",
|
|
||||||
"content": "To make questions, put the 'be' verb before the subject. The word order changes.",
|
|
||||||
"translation": "Pour faire des questions, mettez le verbe 'être' avant le sujet. L'ordre des mots change.",
|
|
||||||
"pattern": {
|
|
||||||
"statement": "Subject + be + complement",
|
|
||||||
"question": "Be + subject + complement + ?"
|
|
||||||
},
|
|
||||||
"examples": [
|
|
||||||
{
|
|
||||||
"statement": "You are ready",
|
|
||||||
"question": "Are you ready?",
|
|
||||||
"answer": "Yes, I am / No, I'm not"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"statement": "He is a doctor",
|
|
||||||
"question": "Is he a doctor?",
|
|
||||||
"answer": "Yes, he is / No, he isn't"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "exercise",
|
|
||||||
"order": 6,
|
|
||||||
"exercise_type": "transformation",
|
|
||||||
"title": "Transform to questions",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"original": "You are tired",
|
|
||||||
"correct": "Are you tired?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"original": "She is a teacher",
|
|
||||||
"correct": "Is she a teacher?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"original": "They are students",
|
|
||||||
"correct": "Are they students?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "exercise",
|
|
||||||
"order": 7,
|
|
||||||
"exercise_type": "fill_blanks",
|
|
||||||
"title": "Complete the conversation",
|
|
||||||
"context": "A conversation between two people meeting for the first time",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"sentence": "Hi! ___ you a new student?",
|
|
||||||
"correct": "Are",
|
|
||||||
"options": ["Are", "Is", "Am"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "Yes, I ___. My name ___ Sarah.",
|
|
||||||
"correct": ["am", "is"],
|
|
||||||
"options": ["am/is", "is/am", "are/are"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "___ you from France?",
|
|
||||||
"correct": "Are",
|
|
||||||
"options": ["Are", "Is", "Am"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Summary and reinforcement
|
|
||||||
"summary": {
|
|
||||||
"key_points": [
|
|
||||||
"I am, you are, he/she/it is, we are, they are",
|
|
||||||
"Negative: add 'not' after 'be'",
|
|
||||||
"Questions: put 'be' before subject"
|
|
||||||
],
|
|
||||||
"common_mistakes": [
|
|
||||||
{
|
|
||||||
"incorrect": "I are happy",
|
|
||||||
"correct": "I am happy",
|
|
||||||
"explanation": "Use 'am' with 'I'"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"incorrect": "She am tired",
|
|
||||||
"correct": "She is tired",
|
|
||||||
"explanation": "Use 'is' with 'she'"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"present_simple_verbs": {
|
|
||||||
"id": "present_simple_verbs",
|
|
||||||
"title": "Present Simple - Regular Verbs",
|
|
||||||
"difficulty": 4,
|
|
||||||
"prerequisite": "present_simple_be", // Must complete 'be' lesson first
|
|
||||||
"estimated_time": 20,
|
|
||||||
"learning_objectives": [
|
|
||||||
"Use present simple for habits and facts",
|
|
||||||
"Add 's' for he/she/it",
|
|
||||||
"Form negatives with don't/doesn't"
|
|
||||||
],
|
|
||||||
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "explanation",
|
|
||||||
"order": 1,
|
|
||||||
"title": "Present Simple usage",
|
|
||||||
"content": "We use present simple for habits, routines, facts, and things that are always true.",
|
|
||||||
"translation": "Nous utilisons le présent simple pour les habitudes, routines, faits, et choses toujours vraies.",
|
|
||||||
"examples": [
|
|
||||||
{ "original": "I work every day", "userLanguage": "Je travaille tous les jours", "usage": "routine" },
|
|
||||||
{ "original": "The sun rises in the east", "userLanguage": "Le soleil se lève à l'est", "usage": "fact" },
|
|
||||||
{ "original": "She likes coffee", "userLanguage": "Elle aime le café", "usage": "preference" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "exercise",
|
|
||||||
"order": 2,
|
|
||||||
"exercise_type": "classification",
|
|
||||||
"title": "Is it a habit, fact, or preference?",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"sentence": "I eat breakfast at 8 AM",
|
|
||||||
"correct": "habit",
|
|
||||||
"options": ["habit", "fact", "preference"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "Water boils at 100°C",
|
|
||||||
"correct": "fact",
|
|
||||||
"options": ["habit", "fact", "preference"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "He loves music",
|
|
||||||
"correct": "preference",
|
|
||||||
"options": ["habit", "fact", "preference"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "explanation",
|
|
||||||
"order": 3,
|
|
||||||
"title": "Third person singular (he/she/it + s)",
|
|
||||||
"content": "With he, she, it, add 's' to the verb. This is very important in English!",
|
|
||||||
"translation": "Avec he, she, it, ajoutez 's' au verbe. C'est très important en anglais!",
|
|
||||||
"rules": [
|
|
||||||
{ "rule": "Normal verbs: add 's'", "example": "work → works, play → plays" },
|
|
||||||
{ "rule": "Verbs ending in s,x,ch,sh: add 'es'", "example": "watch → watches, fix → fixes" },
|
|
||||||
{ "rule": "Verbs ending in consonant + y: change y to ies", "example": "study → studies, try → tries" }
|
|
||||||
],
|
|
||||||
"examples": [
|
|
||||||
{ "original": "I work in London", "userLanguage": "Je travaille à Londres" },
|
|
||||||
{ "original": "She works in London", "userLanguage": "Elle travaille à Londres" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "exercise",
|
|
||||||
"order": 4,
|
|
||||||
"exercise_type": "conjugation",
|
|
||||||
"title": "Add 's' when needed",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"sentence": "He ___ (play) football",
|
|
||||||
"correct": "plays",
|
|
||||||
"base_verb": "play"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "She ___ (watch) TV",
|
|
||||||
"correct": "watches",
|
|
||||||
"base_verb": "watch"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "It ___ (fly) in the sky",
|
|
||||||
"correct": "flies",
|
|
||||||
"base_verb": "fly"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
"summary": {
|
|
||||||
"key_points": [
|
|
||||||
"Present simple = habits, facts, preferences",
|
|
||||||
"He/she/it + verb + s",
|
|
||||||
"I/you/we/they + base verb"
|
|
||||||
],
|
|
||||||
"common_mistakes": [
|
|
||||||
{
|
|
||||||
"incorrect": "She work here",
|
|
||||||
"correct": "She works here",
|
|
||||||
"explanation": "Add 's' with he/she/it"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// === TEXT CONTENT SECTION ===
|
|
||||||
// All text-based content with optional question/exercise metadata
|
|
||||||
"texts": [
|
|
||||||
{
|
|
||||||
"title": "My Daily Routine",
|
|
||||||
"content": "I wake up at 7 AM every day. First, I brush my teeth and take a shower. Then I have breakfast with my family. After breakfast, I go to work by bus. I work from 9 AM to 5 PM. In the evening, I cook dinner and watch TV. I go to bed at 10 PM.",
|
|
||||||
"translation": "Je me réveille à 7h tous les jours. D'abord, je me brosse les dents et prends une douche. Ensuite je prends le petit déjeuner avec ma famille. Après le petit déjeuner, je vais au travail en bus. Je travaille de 9h à 17h. Le soir, je cuisine le dîner et regarde la télé. Je me couche à 22h.",
|
|
||||||
// Optional: Add comprehension questions to any text
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"question": "What time does the person wake up?",
|
|
||||||
"type": "multiple_choice",
|
|
||||||
"options": ["6 AM", "7 AM", "8 AM", "9 AM"],
|
|
||||||
"correctAnswer": "7 AM"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "Describe the person's evening routine",
|
|
||||||
"type": "ai_interpreted",
|
|
||||||
"evaluationPrompt": "Check if answer mentions cooking dinner and watching TV"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"title": "The Four Seasons",
|
|
||||||
"content": "There are four seasons in a year: spring, summer, autumn, and winter. Spring is warm and flowers bloom. Summer is hot and sunny. Autumn is cool and leaves change colors. Winter is cold and it sometimes snows.",
|
|
||||||
"translation": "Il y a quatre saisons dans une année: le printemps, l'été, l'automne et l'hiver. Le printemps est chaud et les fleurs fleurissent. L'été est chaud et ensoleillé. L'automne est frais et les feuilles changent de couleur. L'hiver est froid et il neige parfois.",
|
|
||||||
// Optional: Add fill-in-the-blank exercises to any text
|
|
||||||
"fillInBlanks": [
|
|
||||||
{
|
|
||||||
"sentence": "There are _____ seasons in a year",
|
|
||||||
"options": ["three", "four", "five", "six"],
|
|
||||||
"correctAnswer": "four",
|
|
||||||
"explanation": "Spring, summer, autumn, and winter make four seasons"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sentence": "Spring is _____ and flowers bloom",
|
|
||||||
"type": "open_ended",
|
|
||||||
"acceptedAnswers": ["warm", "nice", "pleasant", "mild"],
|
|
||||||
"aiPrompt": "Check if answer describes spring weather positively"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// === AUDIO-ONLY CONTENT ===
|
|
||||||
// Pure listening exercises WITHOUT text/transcript (text-based audio goes in texts/dialogues)
|
|
||||||
"audio": [
|
|
||||||
{
|
|
||||||
"title": "Mystery Conversation - Restaurant",
|
|
||||||
"audioFile": "audio/listening/restaurant_sounds.mp3",
|
|
||||||
"type": "ambient_listening",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"question": "Where does this conversation take place?",
|
|
||||||
"type": "multiple_choice",
|
|
||||||
"options": ["Restaurant", "Office", "School", "Park"],
|
|
||||||
"correctAnswer": "Restaurant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"question": "How many people are speaking?",
|
|
||||||
"type": "ai_interpreted",
|
|
||||||
"evaluationPrompt": "Accept numeric answers indicating number of distinct voices"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Sound Recognition",
|
|
||||||
"audioFile": "audio/sounds/morning_routine.mp3",
|
|
||||||
"type": "sound_identification",
|
|
||||||
"description": "Listen and identify the morning routine sounds",
|
|
||||||
"questions": [
|
|
||||||
{
|
|
||||||
"question": "What sounds did you hear?",
|
|
||||||
"type": "ai_interpreted",
|
|
||||||
"evaluationPrompt": "Check for mentions of alarm, shower, coffee, breakfast sounds"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Pronunciation Discrimination",
|
|
||||||
"audioFile": "audio/pronunciation/minimal_pairs.mp3",
|
|
||||||
"type": "pronunciation_exercise",
|
|
||||||
"description": "Listen to similar sounding words and identify differences",
|
|
||||||
"word_pairs": [
|
|
||||||
["ship", "sheep"],
|
|
||||||
["live", "leave"],
|
|
||||||
["cat", "cut"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// === CULTURAL CONTENT ===
|
|
||||||
// Rich cultural material with context and educational value
|
|
||||||
"cultural": {
|
|
||||||
// Poetry and literary works
|
|
||||||
"poems": [
|
|
||||||
{
|
|
||||||
"title": "Roses Are Red",
|
|
||||||
"content": "Roses are red,\nViolets are blue,\nSugar is sweet,\nAnd so are you.",
|
|
||||||
"translation": "Les roses sont rouges,\nLes violettes sont bleues,\nLe sucre est doux,\nEt toi aussi.",
|
|
||||||
"audio": "audio/poems/roses.mp3",
|
|
||||||
"image": "images/cultural/roses_poem_illustration.jpg",
|
|
||||||
"type": "nursery_rhyme",
|
|
||||||
"cultural_context": "Traditional English nursery rhyme pattern, often used to teach basic rhyming and poetry structure to children.",
|
|
||||||
"learning_focus": ["rhyme_patterns", "basic_vocabulary", "rhythm"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Proverbs and sayings
|
|
||||||
"proverbs": [
|
|
||||||
{
|
|
||||||
"original": "The early bird catches the worm",
|
|
||||||
"userLanguage": "L'avenir appartient à ceux qui se lèvent tôt",
|
|
||||||
"meaning": "People who wake up early and start working have better chances of success",
|
|
||||||
"image": "images/cultural/early_bird_illustration.jpg",
|
|
||||||
"cultural_context": "Common English saying emphasizing the value of being proactive and starting early",
|
|
||||||
"equivalent_proverbs": {
|
|
||||||
"french": "L'avenir appartient à ceux qui se lèvent tôt",
|
|
||||||
"literal": "Le premier oiseau attrape le ver"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Cultural facts - unified system for traditions, holidays, food, etc.
|
|
||||||
"culture_facts": [
|
|
||||||
{
|
|
||||||
"name": "Tea Time",
|
|
||||||
"category": "tradition",
|
|
||||||
"description": "Traditional British custom of drinking tea in the afternoon, usually around 4 PM",
|
|
||||||
"translation": "L'heure du thé - tradition britannique de boire le thé l'après-midi, généralement vers 16h",
|
|
||||||
"image": "images/cultural/tea_time.jpg",
|
|
||||||
"cultural_significance": "Social ritual that brings people together, often includes biscuits or small cakes",
|
|
||||||
"vocabulary_related": ["tea", "biscuit", "afternoon", "tradition", "social"],
|
|
||||||
"region": "uk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Christmas",
|
|
||||||
"category": "holiday",
|
|
||||||
"date": "December 25th",
|
|
||||||
"description": "Major Christian holiday celebrating the birth of Jesus Christ",
|
|
||||||
"translation": "Noël - grande fête chrétienne célébrant la naissance de Jésus-Christ",
|
|
||||||
"image": "images/cultural/christmas_celebration.jpg",
|
|
||||||
"customs": ["gift_giving", "family_gatherings", "christmas_tree", "caroling"],
|
|
||||||
"vocabulary_related": ["Christmas", "gift", "tree", "family", "celebration"],
|
|
||||||
"region": "global"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Fish and Chips",
|
|
||||||
"category": "food",
|
|
||||||
"description": "Traditional British dish of battered fish with fried potatoes",
|
|
||||||
"translation": "Poisson-frites - plat britannique traditionnel de poisson en pâte avec des pommes de terre frites",
|
|
||||||
"image": "images/cultural/fish_and_chips.jpg",
|
|
||||||
"cultural_context": "Popular working-class meal, often served in newspaper wrapping",
|
|
||||||
"vocabulary_related": ["fish", "chips", "batter", "traditional", "popular"],
|
|
||||||
"region": "uk",
|
|
||||||
"ingredients": ["fish", "potatoes", "batter", "oil"],
|
|
||||||
"typical_sides": ["mushy_peas", "tartar_sauce", "malt_vinegar"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Thanksgiving",
|
|
||||||
"category": "holiday",
|
|
||||||
"date": "Fourth Thursday in November",
|
|
||||||
"description": "American holiday celebrating gratitude and harvest",
|
|
||||||
"translation": "Action de grâce - fête américaine célébrant la gratitude et la récolte",
|
|
||||||
"image": "images/cultural/thanksgiving_dinner.jpg",
|
|
||||||
"customs": ["family_dinner", "turkey_meal", "gratitude_sharing", "football_watching"],
|
|
||||||
"vocabulary_related": ["thanksgiving", "turkey", "grateful", "harvest", "family"],
|
|
||||||
"region": "us"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// === SENTENCES WITH CORRECTION PARAMETERS ===
|
|
||||||
// Generic sentence structure that can be used for various exercises including corrections
|
|
||||||
"sentences": [
|
|
||||||
{
|
|
||||||
"original": "Hello, how are you?",
|
|
||||||
"userLanguage": "Bonjour, comment allez-vous?",
|
|
||||||
"type": "greeting",
|
|
||||||
"formality": "neutral"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"original": "I like to read books",
|
|
||||||
"userLanguage": "J'aime lire des livres",
|
|
||||||
"type": "preference_statement",
|
|
||||||
"tense": "present_simple"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"original": "The weather is nice today",
|
|
||||||
"userLanguage": "Il fait beau aujourd'hui",
|
|
||||||
"type": "observation",
|
|
||||||
"topic": "weather"
|
|
||||||
},
|
|
||||||
// Sentences specifically for correction exercises
|
|
||||||
{
|
|
||||||
"original": "I am happy today", // Correct version
|
|
||||||
"userLanguage": "Je suis heureux aujourd'hui",
|
|
||||||
"type": "correction_target",
|
|
||||||
"correction_data": {
|
|
||||||
"incorrect_versions": [
|
|
||||||
{
|
|
||||||
"text": "I are happy today",
|
|
||||||
"error_type": "subject_verb_agreement",
|
|
||||||
"explanation": "Use 'am' with pronoun 'I', not 'are'",
|
|
||||||
"difficulty": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "I is happy today",
|
|
||||||
"error_type": "subject_verb_agreement",
|
|
||||||
"explanation": "Use 'am' with pronoun 'I', not 'is'",
|
|
||||||
"difficulty": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammar_focus": "be_verb_conjugation",
|
|
||||||
"common_mistake": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"original": "She doesn't like apples", // Correct version
|
|
||||||
"userLanguage": "Elle n'aime pas les pommes",
|
|
||||||
"type": "correction_target",
|
|
||||||
"correction_data": {
|
|
||||||
"incorrect_versions": [
|
|
||||||
{
|
|
||||||
"text": "She don't like apples",
|
|
||||||
"error_type": "subject_verb_agreement",
|
|
||||||
"explanation": "Use 'doesn't' with he/she/it, not 'don't'",
|
|
||||||
"difficulty": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "She not like apples",
|
|
||||||
"error_type": "auxiliary_verb_missing",
|
|
||||||
"explanation": "Need auxiliary verb 'doesn't' for negative statements",
|
|
||||||
"difficulty": 4
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammar_focus": "negative_present_simple",
|
|
||||||
"common_mistake": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"original": "I can swim", // Correct version
|
|
||||||
"userLanguage": "Je sais nager",
|
|
||||||
"type": "correction_target",
|
|
||||||
"correction_data": {
|
|
||||||
"incorrect_versions": [
|
|
||||||
{
|
|
||||||
"text": "I can to swim",
|
|
||||||
"error_type": "infinitive_after_modal",
|
|
||||||
"explanation": "After modal verbs like 'can', use base form without 'to'",
|
|
||||||
"difficulty": 5
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammar_focus": "modal_verbs",
|
|
||||||
"common_mistake": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// === MATCHING EXERCISES ===
|
|
||||||
// Connect-the-lines style exercises
|
|
||||||
"matching": [
|
|
||||||
{
|
|
||||||
"title": "Match Animals to Their Sounds",
|
|
||||||
"type": "two_column_matching",
|
|
||||||
"leftColumn": ["Cat", "Dog", "Cow", "Bird"],
|
|
||||||
"rightColumn": ["Woof", "Meow", "Tweet", "Moo"],
|
|
||||||
"correctPairs": [
|
|
||||||
{ "left": "Cat", "right": "Meow" },
|
|
||||||
{ "left": "Dog", "right": "Woof" },
|
|
||||||
{ "left": "Cow", "right": "Moo" },
|
|
||||||
{ "left": "Bird", "right": "Tweet" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Match Colors in English and French",
|
|
||||||
"type": "two_column_matching",
|
|
||||||
"leftColumn": ["Red", "Blue", "Green", "Yellow"],
|
|
||||||
"rightColumn": ["Bleu", "Vert", "Rouge", "Jaune"],
|
|
||||||
"correctPairs": [
|
|
||||||
{ "left": "Red", "right": "Rouge" },
|
|
||||||
{ "left": "Blue", "right": "Bleu" },
|
|
||||||
{ "left": "Green", "right": "Vert" },
|
|
||||||
{ "left": "Yellow", "right": "Jaune" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Build Correct Sentences",
|
|
||||||
"type": "multi_column_matching",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "Subject",
|
|
||||||
"items": ["I", "She", "They", "We"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": "Verb",
|
|
||||||
"items": ["am", "is", "are", "are"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"name": "Complement",
|
|
||||||
"items": ["happy", "a teacher", "students", "friends"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"valid_combinations": [
|
|
||||||
{"1": "I", "2": "am", "3": "happy"},
|
|
||||||
{"1": "I", "2": "am", "3": "a teacher"},
|
|
||||||
{"1": "She", "2": "is", "3": "happy"},
|
|
||||||
{"1": "She", "2": "is", "3": "a teacher"},
|
|
||||||
{"1": "They", "2": "are", "3": "students"},
|
|
||||||
{"1": "They", "2": "are", "3": "friends"},
|
|
||||||
{"1": "We", "2": "are", "3": "students"},
|
|
||||||
{"1": "We", "2": "are", "3": "friends"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Match Country Information",
|
|
||||||
"type": "multi_column_matching",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "Country",
|
|
||||||
"items": ["France", "Spain", "Italy", "Germany"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": "Capital",
|
|
||||||
"items": ["Paris", "Madrid", "Rome", "Berlin"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"items": ["French", "Spanish", "Italian", "German"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"valid_combinations": [
|
|
||||||
{"1": "France", "2": "Paris", "3": "French"},
|
|
||||||
{"1": "Spain", "2": "Madrid", "3": "Spanish"},
|
|
||||||
{"1": "Italy", "2": "Rome", "3": "Italian"},
|
|
||||||
{"1": "Germany", "2": "Berlin", "3": "German"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
|
|
||||||
// === DIALOGUES ===
|
|
||||||
// Structured conversations between speakers (text with audio goes here, not in audio section)
|
|
||||||
"dialogues": [
|
|
||||||
{
|
|
||||||
"title": "At the Restaurant",
|
|
||||||
"conversation": [
|
|
||||||
{
|
|
||||||
"speaker": "Waiter",
|
|
||||||
"original": "Good evening! Welcome to our restaurant.",
|
|
||||||
"userLanguage": "Bonsoir! Bienvenue dans notre restaurant."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"speaker": "Customer",
|
|
||||||
"original": "Thank you. Can I see the menu please?",
|
|
||||||
"userLanguage": "Merci. Puis-je voir le menu s'il vous plaît?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"speaker": "Waiter",
|
|
||||||
"original": "Of course! Here you are. What would you like to drink?",
|
|
||||||
"userLanguage": "Bien sûr! Voici. Que voulez-vous boire?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Daily Routine Conversation",
|
|
||||||
"audio": "audio/conversations/daily_routine.mp3", // Audio WITH text belongs here
|
|
||||||
"conversation": [
|
|
||||||
{
|
|
||||||
"speaker": "A",
|
|
||||||
"original": "What time do you wake up?",
|
|
||||||
"userLanguage": "À quelle heure te réveilles-tu?",
|
|
||||||
"timestamp": 0.5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"speaker": "B",
|
|
||||||
"original": "I usually wake up at 7 AM.",
|
|
||||||
"userLanguage": "Je me réveille habituellement à 7h.",
|
|
||||||
"timestamp": 3.2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"speaker": "A",
|
|
||||||
"original": "That's early! I wake up at 8:30.",
|
|
||||||
"userLanguage": "C'est tôt! Je me réveille à 8h30.",
|
|
||||||
"timestamp": 6.8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"speaker": "B",
|
|
||||||
"original": "I like to exercise before work.",
|
|
||||||
"userLanguage": "J'aime faire de l'exercice avant le travail.",
|
|
||||||
"timestamp": 11.1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"speaker": "A",
|
|
||||||
"original": "That's a good habit!",
|
|
||||||
"userLanguage": "C'est une bonne habitude!",
|
|
||||||
"timestamp": 14.5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,114 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
// Script de migration des console.log vers logSh
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// Fonction pour parcourir récursivement les fichiers
|
|
||||||
function walkSync(dir, filelist = []) {
|
|
||||||
const files = fs.readdirSync(dir);
|
|
||||||
|
|
||||||
files.forEach(file => {
|
|
||||||
const filepath = path.join(dir, file);
|
|
||||||
const stat = fs.statSync(filepath);
|
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
// Skip certains dossiers
|
|
||||||
if (!['node_modules', '.git', 'export_logger'].includes(file)) {
|
|
||||||
filelist = walkSync(filepath, filelist);
|
|
||||||
}
|
|
||||||
} else if (file.endsWith('.js') && !file.startsWith('migrate-')) {
|
|
||||||
filelist.push(filepath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return filelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonction pour migrer un fichier
|
|
||||||
function migrateFile(filepath) {
|
|
||||||
console.log(`🔄 Migration: ${filepath}`);
|
|
||||||
|
|
||||||
let content = fs.readFileSync(filepath, 'utf8');
|
|
||||||
let modified = false;
|
|
||||||
|
|
||||||
// Remplacements avec mapping vers logSh
|
|
||||||
const replacements = [
|
|
||||||
// console.log -> logSh avec niveau INFO
|
|
||||||
{
|
|
||||||
pattern: /console\.log\((.*?)\);?/g,
|
|
||||||
replacement: (match, args) => {
|
|
||||||
// Si c'est déjà un template string avec des variables, le garder tel quel
|
|
||||||
if (args.includes('`') || args.includes('${')) {
|
|
||||||
return `logSh(${args}, 'INFO');`;
|
|
||||||
}
|
|
||||||
// Sinon, traiter normalement
|
|
||||||
return `logSh(${args}, 'INFO');`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// console.warn -> logSh avec niveau WARN
|
|
||||||
{
|
|
||||||
pattern: /console\.warn\((.*?)\);?/g,
|
|
||||||
replacement: (match, args) => {
|
|
||||||
return `logSh(${args}, 'WARN');`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// console.error -> logSh avec niveau ERROR
|
|
||||||
{
|
|
||||||
pattern: /console\.error\((.*?)\);?/g,
|
|
||||||
replacement: (match, args) => {
|
|
||||||
return `logSh(${args}, 'ERROR');`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Appliquer les remplacements
|
|
||||||
replacements.forEach(({ pattern, replacement }) => {
|
|
||||||
const originalContent = content;
|
|
||||||
content = content.replace(pattern, replacement);
|
|
||||||
if (content !== originalContent) {
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sauvegarder si modifié
|
|
||||||
if (modified) {
|
|
||||||
fs.writeFileSync(filepath, content, 'utf8');
|
|
||||||
console.log(`✅ Migré: ${filepath}`);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
console.log(`⏸️ Pas de changement: ${filepath}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Script principal
|
|
||||||
function main() {
|
|
||||||
console.log('🚀 Démarrage migration console vers logSh...');
|
|
||||||
|
|
||||||
const projectRoot = __dirname;
|
|
||||||
const jsFiles = walkSync(projectRoot);
|
|
||||||
|
|
||||||
console.log(`📁 Trouvé ${jsFiles.length} fichiers JavaScript`);
|
|
||||||
|
|
||||||
let migratedCount = 0;
|
|
||||||
|
|
||||||
jsFiles.forEach(filepath => {
|
|
||||||
if (migrateFile(filepath)) {
|
|
||||||
migratedCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`\n🎉 Migration terminée!`);
|
|
||||||
console.log(`📊 ${migratedCount} fichiers modifiés sur ${jsFiles.length}`);
|
|
||||||
console.log(`\n🔍 Pour vérifier, lance:`);
|
|
||||||
console.log(`grep -r "console\\." --include="*.js" . || echo "✅ Plus de console dans le code!"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lancer le script
|
|
||||||
if (require.main === module) {
|
|
||||||
main();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { migrateFile, walkSync };
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
// === TEST RAPIDE À COPIER-COLLER DANS LA CONSOLE ===
|
|
||||||
|
|
||||||
// Copie-colle ça dans la console du navigateur pour déboguer
|
|
||||||
function quickDebug() {
|
|
||||||
console.log('🔧 Quick Debug du système de compatibilité\n');
|
|
||||||
|
|
||||||
// 1. Vérifier que les classes existent
|
|
||||||
console.log('1️⃣ Classes disponibles:');
|
|
||||||
console.log(' ContentGameCompatibility:', !!window.ContentGameCompatibility);
|
|
||||||
console.log(' ContentScanner:', !!window.ContentScanner);
|
|
||||||
console.log(' AppNavigation:', !!window.AppNavigation);
|
|
||||||
|
|
||||||
// 2. Vérifier l'initialisation
|
|
||||||
console.log('\n2️⃣ Initialisation AppNavigation:');
|
|
||||||
if (window.AppNavigation) {
|
|
||||||
console.log(' compatibilityChecker:', !!window.AppNavigation.compatibilityChecker);
|
|
||||||
console.log(' contentScanner:', !!window.AppNavigation.contentScanner);
|
|
||||||
console.log(' scannedContent:', !!window.AppNavigation.scannedContent);
|
|
||||||
|
|
||||||
if (window.AppNavigation.scannedContent) {
|
|
||||||
console.log(' Contenu trouvé:', window.AppNavigation.scannedContent.found?.length || 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Vérifier les modules chargés
|
|
||||||
console.log('\n3️⃣ Modules de contenu:');
|
|
||||||
if (window.ContentModules) {
|
|
||||||
const modules = Object.keys(window.ContentModules);
|
|
||||||
console.log(' Modules:', modules);
|
|
||||||
|
|
||||||
if (window.ContentModules.ChineseLongStory) {
|
|
||||||
console.log(' ✅ ChineseLongStory trouvé');
|
|
||||||
} else {
|
|
||||||
console.log(' ❌ ChineseLongStory manquant');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(' ❌ window.ContentModules manquant');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Test de compatibilité direct
|
|
||||||
console.log('\n4️⃣ Test de compatibilité direct:');
|
|
||||||
if (window.AppNavigation?.compatibilityChecker && window.ContentModules?.ChineseLongStory) {
|
|
||||||
const checker = window.AppNavigation.compatibilityChecker;
|
|
||||||
const content = window.ContentModules.ChineseLongStory;
|
|
||||||
|
|
||||||
const result = checker.checkCompatibility(content, 'whack-a-mole');
|
|
||||||
console.log(' Test whack-a-mole:', result.compatible, `(${result.score}%)`);
|
|
||||||
} else {
|
|
||||||
console.log(' ❌ Impossible - composants manquants');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n✅ Debug terminé');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-exécution
|
|
||||||
quickDebug();
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "sbs_level_7_8_converted_from_js",
|
|
||||||
"name": "SBS Level 7-8 (Converted from JavaScript)",
|
|
||||||
"description": "English learning content covering housing and clothing vocabulary - automatically converted from legacy JavaScript format to ultra-modular JSON specification",
|
|
||||||
"difficulty_level": 7,
|
|
||||||
"original_lang": "english",
|
|
||||||
"user_lang": "chinese",
|
|
||||||
"icon": "🏠",
|
|
||||||
"tags": [
|
|
||||||
"vocabulary",
|
|
||||||
"intermediate",
|
|
||||||
"places",
|
|
||||||
"housing",
|
|
||||||
"clothing"
|
|
||||||
],
|
|
||||||
"vocabulary": {
|
|
||||||
"central": {
|
|
||||||
"user_language": "中心的;中央的",
|
|
||||||
"original_language": "central"
|
|
||||||
},
|
|
||||||
"avenue": {
|
|
||||||
"user_language": "大街;林荫道",
|
|
||||||
"original_language": "avenue"
|
|
||||||
},
|
|
||||||
"refrigerator": {
|
|
||||||
"user_language": "冰箱",
|
|
||||||
"original_language": "refrigerator"
|
|
||||||
},
|
|
||||||
"closet": {
|
|
||||||
"user_language": "衣柜;壁橱",
|
|
||||||
"original_language": "closet"
|
|
||||||
},
|
|
||||||
"elevator": {
|
|
||||||
"user_language": "电梯",
|
|
||||||
"original_language": "elevator"
|
|
||||||
},
|
|
||||||
"building": {
|
|
||||||
"user_language": "建筑物;大楼",
|
|
||||||
"original_language": "building"
|
|
||||||
},
|
|
||||||
"air conditioner": {
|
|
||||||
"user_language": "空调",
|
|
||||||
"original_language": "air conditioner"
|
|
||||||
},
|
|
||||||
"superintendent": {
|
|
||||||
"user_language": "主管;负责人",
|
|
||||||
"original_language": "superintendent"
|
|
||||||
},
|
|
||||||
"bus stop": {
|
|
||||||
"user_language": "公交车站",
|
|
||||||
"original_language": "bus stop"
|
|
||||||
},
|
|
||||||
"jacuzzi": {
|
|
||||||
"user_language": "按摩浴缸",
|
|
||||||
"original_language": "jacuzzi"
|
|
||||||
},
|
|
||||||
"shirt": {
|
|
||||||
"user_language": "衬衫",
|
|
||||||
"original_language": "shirt"
|
|
||||||
},
|
|
||||||
"coat": {
|
|
||||||
"user_language": "外套、大衣",
|
|
||||||
"original_language": "coat"
|
|
||||||
},
|
|
||||||
"dress": {
|
|
||||||
"user_language": "连衣裙",
|
|
||||||
"original_language": "dress"
|
|
||||||
},
|
|
||||||
"skirt": {
|
|
||||||
"user_language": "短裙",
|
|
||||||
"original_language": "skirt"
|
|
||||||
},
|
|
||||||
"blouse": {
|
|
||||||
"user_language": "女式衬衫",
|
|
||||||
"original_language": "blouse"
|
|
||||||
},
|
|
||||||
"jacket": {
|
|
||||||
"user_language": "夹克、短外套",
|
|
||||||
"original_language": "jacket"
|
|
||||||
},
|
|
||||||
"sweater": {
|
|
||||||
"user_language": "毛衣、针织衫",
|
|
||||||
"original_language": "sweater"
|
|
||||||
},
|
|
||||||
"suit": {
|
|
||||||
"user_language": "套装、西装",
|
|
||||||
"original_language": "suit"
|
|
||||||
},
|
|
||||||
"tie": {
|
|
||||||
"user_language": "领带",
|
|
||||||
"original_language": "tie"
|
|
||||||
},
|
|
||||||
"pants": {
|
|
||||||
"user_language": "裤子",
|
|
||||||
"original_language": "pants"
|
|
||||||
},
|
|
||||||
"jeans": {
|
|
||||||
"user_language": "牛仔裤",
|
|
||||||
"original_language": "jeans"
|
|
||||||
},
|
|
||||||
"belt": {
|
|
||||||
"user_language": "腰带、皮带",
|
|
||||||
"original_language": "belt"
|
|
||||||
},
|
|
||||||
"hat": {
|
|
||||||
"user_language": "帽子",
|
|
||||||
"original_language": "hat"
|
|
||||||
},
|
|
||||||
"glove": {
|
|
||||||
"user_language": "手套",
|
|
||||||
"original_language": "glove"
|
|
||||||
},
|
|
||||||
"glasses": {
|
|
||||||
"user_language": "眼镜",
|
|
||||||
"original_language": "glasses"
|
|
||||||
},
|
|
||||||
"pajamas": {
|
|
||||||
"user_language": "睡衣",
|
|
||||||
"original_language": "pajamas"
|
|
||||||
},
|
|
||||||
"shoes": {
|
|
||||||
"user_language": "鞋子",
|
|
||||||
"original_language": "shoes"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sentences": [
|
|
||||||
{
|
|
||||||
"id": "sentence_1",
|
|
||||||
"original_language": "Amy's apartment building is in the center of town.",
|
|
||||||
"user_language": "艾米的公寓楼在城镇中心。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sentence_2",
|
|
||||||
"original_language": "There's a lot of noise near Amy's apartment building.",
|
|
||||||
"user_language": "艾米的公寓楼附近很吵。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sentence_3",
|
|
||||||
"original_language": "The superintendent is very helpful.",
|
|
||||||
"user_language": "管理员非常乐于助人。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sentence_4",
|
|
||||||
"original_language": "I need to buy new clothes for winter.",
|
|
||||||
"user_language": "我需要为冬天买新衣服。"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"conversion_metadata": {
|
|
||||||
"converted_from": "legacy_javascript_module",
|
|
||||||
"conversion_timestamp": "2025-09-16T11:50:26.158Z",
|
|
||||||
"conversion_system": "ultra_modular_converter_v1.0",
|
|
||||||
"original_format": "js_content_module",
|
|
||||||
"target_format": "ultra_modular_json_v2.0",
|
|
||||||
"original_stats": {
|
|
||||||
"vocabulary_count": 27,
|
|
||||||
"sentence_count": 4,
|
|
||||||
"has_complex_phrases": true
|
|
||||||
},
|
|
||||||
"detected_capabilities": {
|
|
||||||
"hasVocabulary": true,
|
|
||||||
"hasSentences": true,
|
|
||||||
"hasGrammar": false,
|
|
||||||
"hasAudio": false,
|
|
||||||
"hasDialogues": false,
|
|
||||||
"hasExercises": false,
|
|
||||||
"hasMatching": false,
|
|
||||||
"hasCulture": false,
|
|
||||||
"vocabularyDepth": 1,
|
|
||||||
"contentRichness": 2.7,
|
|
||||||
"vocabularyCount": 27,
|
|
||||||
"sentenceCount": 4,
|
|
||||||
"complexPhrases": 2
|
|
||||||
},
|
|
||||||
"game_compatibility": {
|
|
||||||
"whack-a-mole": {
|
|
||||||
"compatible": true,
|
|
||||||
"score": 54,
|
|
||||||
"reason": "Nécessite vocabulaire"
|
|
||||||
},
|
|
||||||
"memory-match": {
|
|
||||||
"compatible": true,
|
|
||||||
"score": 40.5,
|
|
||||||
"reason": "Optimal pour vocabulaire visuel"
|
|
||||||
},
|
|
||||||
"quiz-game": {
|
|
||||||
"compatible": true,
|
|
||||||
"score": 42,
|
|
||||||
"reason": "Fonctionne avec tout contenu"
|
|
||||||
},
|
|
||||||
"text-reader": {
|
|
||||||
"compatible": true,
|
|
||||||
"score": 40,
|
|
||||||
"reason": "Nécessite phrases à lire"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"quality_score": 91
|
|
||||||
},
|
|
||||||
"system_validation": {
|
|
||||||
"format_version": "2.0",
|
|
||||||
"specification": "ultra_modular",
|
|
||||||
"backwards_compatible": true,
|
|
||||||
"memory_stored": true,
|
|
||||||
"conversion_verified": true,
|
|
||||||
"ready_for_games": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
@echo off
|
|
||||||
echo ========================================
|
|
||||||
echo WebSocket Logger Server
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
|
|
||||||
cd /d "%~dp0\export_logger"
|
|
||||||
|
|
||||||
echo Demarrage du serveur WebSocket sur le port 8082...
|
|
||||||
node websocket-server.js
|
|
||||||
|
|
||||||
pause
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "========================================"
|
|
||||||
echo " WebSocket Logger Server"
|
|
||||||
echo "========================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Se déplacer dans le dossier export_logger
|
|
||||||
cd "$(dirname "$0")/export_logger"
|
|
||||||
|
|
||||||
echo "Démarrage du serveur WebSocket sur le port 8082..."
|
|
||||||
node websocket-server.js
|
|
||||||
@ -1,147 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// Test de signature AWS pour DigitalOcean Spaces
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
// Configuration avec ta clé read-only
|
|
||||||
const config = {
|
|
||||||
DO_ACCESS_KEY: 'DO801XTYPE968NZGAQM3',
|
|
||||||
DO_SECRET_KEY: 'rfKPjampdpUCYhn02XrKg6IWKmqebjg9HQTGxNLzJQY',
|
|
||||||
DO_REGION: 'fra1',
|
|
||||||
DO_ENDPOINT: 'https://autocollant.fra1.digitaloceanspaces.com',
|
|
||||||
DO_CONTENT_PATH: 'Class_generator/ContentMe'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fonction de hash SHA256
|
|
||||||
function sha256(message) {
|
|
||||||
return crypto.createHash('sha256').update(message, 'utf8').digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonction HMAC SHA256
|
|
||||||
function hmacSha256(key, message) {
|
|
||||||
return crypto.createHmac('sha256', key).update(message, 'utf8').digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Génération de la signature AWS V4
|
|
||||||
async function generateAWSSignature(method, url) {
|
|
||||||
const accessKey = config.DO_ACCESS_KEY;
|
|
||||||
const secretKey = config.DO_SECRET_KEY;
|
|
||||||
const region = config.DO_REGION;
|
|
||||||
const service = 's3';
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
||||||
const timeStamp = now.toISOString().slice(0, 19).replace(/[-:]/g, '') + 'Z';
|
|
||||||
|
|
||||||
console.log(`🕐 Timestamp: ${timeStamp}`);
|
|
||||||
console.log(`📅 Date: ${dateStamp}`);
|
|
||||||
|
|
||||||
// Parse URL
|
|
||||||
const urlObj = new URL(url);
|
|
||||||
const host = urlObj.hostname;
|
|
||||||
const canonicalUri = urlObj.pathname || '/';
|
|
||||||
const canonicalQueryString = urlObj.search ? urlObj.search.slice(1) : '';
|
|
||||||
|
|
||||||
console.log(`🌐 Host: ${host}`);
|
|
||||||
console.log(`📍 URI: ${canonicalUri}`);
|
|
||||||
console.log(`❓ Query: ${canonicalQueryString}`);
|
|
||||||
|
|
||||||
// Canonical headers
|
|
||||||
const canonicalHeaders = `host:${host}\nx-amz-date:${timeStamp}\n`;
|
|
||||||
const signedHeaders = 'host;x-amz-date';
|
|
||||||
|
|
||||||
console.log(`📝 Canonical headers:\n${canonicalHeaders}`);
|
|
||||||
|
|
||||||
// Create canonical request
|
|
||||||
const payloadHash = method === 'GET' ? sha256('') : 'UNSIGNED-PAYLOAD';
|
|
||||||
const canonicalRequest = [
|
|
||||||
method,
|
|
||||||
canonicalUri,
|
|
||||||
canonicalQueryString,
|
|
||||||
canonicalHeaders,
|
|
||||||
signedHeaders,
|
|
||||||
payloadHash
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
console.log(`📋 Canonical request:\n${canonicalRequest}`);
|
|
||||||
console.log(`🔢 Payload hash: ${payloadHash}`);
|
|
||||||
|
|
||||||
// Create string to sign
|
|
||||||
const algorithm = 'AWS4-HMAC-SHA256';
|
|
||||||
const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
|
|
||||||
const canonicalRequestHash = sha256(canonicalRequest);
|
|
||||||
const stringToSign = [
|
|
||||||
algorithm,
|
|
||||||
timeStamp,
|
|
||||||
credentialScope,
|
|
||||||
canonicalRequestHash
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
console.log(`🔐 String to sign:\n${stringToSign}`);
|
|
||||||
console.log(`🔗 Canonical request hash: ${canonicalRequestHash}`);
|
|
||||||
|
|
||||||
// Calculate signature
|
|
||||||
const kDate = hmacSha256('AWS4' + secretKey, dateStamp);
|
|
||||||
const kRegion = hmacSha256(kDate, region);
|
|
||||||
const kService = hmacSha256(kRegion, service);
|
|
||||||
const kSigning = hmacSha256(kService, 'aws4_request');
|
|
||||||
const signature = hmacSha256(kSigning, stringToSign).toString('hex');
|
|
||||||
|
|
||||||
console.log(`✍️ Signature: ${signature}`);
|
|
||||||
|
|
||||||
// Create authorization header
|
|
||||||
const authorization = `${algorithm} Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
||||||
|
|
||||||
console.log(`🔑 Authorization: ${authorization}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
'Authorization': authorization,
|
|
||||||
'X-Amz-Date': timeStamp,
|
|
||||||
'X-Amz-Content-Sha256': payloadHash
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test avec fetch
|
|
||||||
async function testDigitalOceanAccess() {
|
|
||||||
console.log('🚀 Test d\'accès DigitalOcean Spaces avec signature AWS\n');
|
|
||||||
|
|
||||||
const testUrl = `${config.DO_ENDPOINT}/${config.DO_CONTENT_PATH}/greetings-basic.json`;
|
|
||||||
console.log(`🎯 URL de test: ${testUrl}\n`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const headers = await generateAWSSignature('GET', testUrl);
|
|
||||||
|
|
||||||
console.log('\n📦 Headers finaux:');
|
|
||||||
console.log(JSON.stringify(headers, null, 2));
|
|
||||||
|
|
||||||
// Test avec node-fetch si disponible
|
|
||||||
try {
|
|
||||||
const fetch = require('node-fetch');
|
|
||||||
console.log('\n🌐 Test avec node-fetch...');
|
|
||||||
|
|
||||||
const response = await fetch(testUrl, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: headers
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`📡 Status: ${response.status} ${response.statusText}`);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const content = await response.text();
|
|
||||||
console.log(`✅ Succès ! Contenu (${content.length} chars): ${content.substring(0, 200)}...`);
|
|
||||||
} else {
|
|
||||||
const errorText = await response.text();
|
|
||||||
console.log(`❌ Erreur: ${errorText}`);
|
|
||||||
}
|
|
||||||
} catch (fetchError) {
|
|
||||||
console.log('⚠️ node-fetch non disponible, test signature seulement');
|
|
||||||
console.log('✅ Signature générée avec succès !');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ Erreur: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lancer le test
|
|
||||||
testDigitalOceanAccess();
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Test Chinese Vocabulary",
|
|
||||||
"description": "Basic Chinese characters for testing",
|
|
||||||
"difficulty": "beginner",
|
|
||||||
"language": "chinese",
|
|
||||||
"vocabulary": {
|
|
||||||
"你好": {
|
|
||||||
"user_language": "bonjour",
|
|
||||||
"type": "greeting",
|
|
||||||
"pronunciation": "nǐ hǎo",
|
|
||||||
"hskLevel": "HSK1",
|
|
||||||
"examples": ["你好,欢迎!", "你好吗?"]
|
|
||||||
},
|
|
||||||
"谢谢": {
|
|
||||||
"user_language": "merci",
|
|
||||||
"type": "greeting",
|
|
||||||
"pronunciation": "xiè xiè",
|
|
||||||
"hskLevel": "HSK1"
|
|
||||||
},
|
|
||||||
"猫": {
|
|
||||||
"user_language": "chat",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "māo",
|
|
||||||
"hskLevel": "HSK1",
|
|
||||||
"examples": ["我有一只猫"]
|
|
||||||
},
|
|
||||||
"狗": {
|
|
||||||
"user_language": "chien",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "gǒu",
|
|
||||||
"hskLevel": "HSK1"
|
|
||||||
},
|
|
||||||
"水": {
|
|
||||||
"user_language": "eau",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "shuǐ",
|
|
||||||
"hskLevel": "HSK1"
|
|
||||||
},
|
|
||||||
"学习": {
|
|
||||||
"user_language": "étudier",
|
|
||||||
"type": "verb",
|
|
||||||
"pronunciation": "xué xí",
|
|
||||||
"hskLevel": "HSK2"
|
|
||||||
},
|
|
||||||
"老师": {
|
|
||||||
"user_language": "professeur",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "lǎo shī",
|
|
||||||
"hskLevel": "HSK2"
|
|
||||||
},
|
|
||||||
"学生": {
|
|
||||||
"user_language": "étudiant",
|
|
||||||
"type": "noun",
|
|
||||||
"pronunciation": "xué shēng",
|
|
||||||
"hskLevel": "HSK2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,222 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Test Système de Compatibilité</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
.test-container {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.test-result {
|
|
||||||
padding: 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.compatible {
|
|
||||||
background: #d4edda;
|
|
||||||
border: 1px solid #c3e6cb;
|
|
||||||
color: #155724;
|
|
||||||
}
|
|
||||||
.incompatible {
|
|
||||||
background: #f8d7da;
|
|
||||||
border: 1px solid #f5c6cb;
|
|
||||||
color: #721c24;
|
|
||||||
}
|
|
||||||
.score {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background: #0056b3;
|
|
||||||
}
|
|
||||||
.log {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
font-family: monospace;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>🎯 Test du Système de Compatibilité Content-Game</h1>
|
|
||||||
|
|
||||||
<div class="test-container">
|
|
||||||
<h2>Chargement des modules</h2>
|
|
||||||
<button onclick="loadModules()">Charger les modules</button>
|
|
||||||
<div id="loading-status"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-container">
|
|
||||||
<h2>Tests de compatibilité</h2>
|
|
||||||
<button onclick="runCompatibilityTests()">Lancer les tests</button>
|
|
||||||
<div id="test-results"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-container">
|
|
||||||
<h2>Log des opérations</h2>
|
|
||||||
<button onclick="clearLog()">Effacer le log</button>
|
|
||||||
<div id="log" class="log"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Scripts nécessaires -->
|
|
||||||
<script src="js/core/utils.js"></script>
|
|
||||||
<script src="js/core/content-scanner.js"></script>
|
|
||||||
<script src="js/core/content-game-compatibility.js"></script>
|
|
||||||
<script src="js/content/test-compatibility.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let contentScanner;
|
|
||||||
let compatibilityChecker;
|
|
||||||
let testContent = [];
|
|
||||||
|
|
||||||
// Logger simple pour les tests
|
|
||||||
function logSh(message, level = 'INFO') {
|
|
||||||
const logDiv = document.getElementById('log');
|
|
||||||
const timestamp = new Date().toLocaleTimeString();
|
|
||||||
logDiv.textContent += `[${timestamp}] ${level}: ${message}\n`;
|
|
||||||
logDiv.scrollTop = logDiv.scrollHeight;
|
|
||||||
console.log(`[${level}] ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLog() {
|
|
||||||
document.getElementById('log').textContent = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadModules() {
|
|
||||||
const statusDiv = document.getElementById('loading-status');
|
|
||||||
statusDiv.innerHTML = '⏳ Chargement en cours...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Initialiser le scanner de contenu
|
|
||||||
contentScanner = new ContentScanner();
|
|
||||||
logSh('✅ ContentScanner initialisé');
|
|
||||||
|
|
||||||
// Initialiser le vérificateur de compatibilité
|
|
||||||
compatibilityChecker = new ContentGameCompatibility();
|
|
||||||
logSh('✅ ContentGameCompatibility initialisé');
|
|
||||||
|
|
||||||
// Scanner le contenu (nos modules de test devraient être trouvés)
|
|
||||||
const scannedContent = await contentScanner.scanAllContent();
|
|
||||||
logSh(`📦 ${scannedContent.found.length} modules de contenu trouvés`);
|
|
||||||
|
|
||||||
// Récupérer nos modules de test spécifiquement
|
|
||||||
testContent = scannedContent.found.filter(content =>
|
|
||||||
content.id.includes('test-') || content.name.includes('Test')
|
|
||||||
);
|
|
||||||
|
|
||||||
statusDiv.innerHTML = `
|
|
||||||
<div style="color: green;">
|
|
||||||
✅ Modules chargés avec succès<br>
|
|
||||||
📊 ${scannedContent.found.length} modules total<br>
|
|
||||||
🧪 ${testContent.length} modules de test trouvés
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
logSh(`🧪 Modules de test: ${testContent.map(c => c.name).join(', ')}`);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
statusDiv.innerHTML = `<div style="color: red;">❌ Erreur: ${error.message}</div>`;
|
|
||||||
logSh(`❌ Erreur lors du chargement: ${error.message}`, 'ERROR');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runCompatibilityTests() {
|
|
||||||
const resultsDiv = document.getElementById('test-results');
|
|
||||||
|
|
||||||
if (!compatibilityChecker || testContent.length === 0) {
|
|
||||||
resultsDiv.innerHTML = '<div style="color: red;">❌ Modules non chargés. Cliquez d\'abord sur "Charger les modules"</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logSh('🧪 Début des tests de compatibilité');
|
|
||||||
|
|
||||||
const games = [
|
|
||||||
'whack-a-mole',
|
|
||||||
'whack-a-mole-hard',
|
|
||||||
'memory-match',
|
|
||||||
'quiz-game',
|
|
||||||
'fill-the-blank',
|
|
||||||
'text-reader',
|
|
||||||
'adventure-reader'
|
|
||||||
];
|
|
||||||
|
|
||||||
let resultsHTML = '<h3>Résultats des tests</h3>';
|
|
||||||
|
|
||||||
testContent.forEach(content => {
|
|
||||||
resultsHTML += `<h4>📦 ${content.name}</h4>`;
|
|
||||||
|
|
||||||
games.forEach(game => {
|
|
||||||
const compatibility = compatibilityChecker.checkCompatibility(content, game);
|
|
||||||
const cssClass = compatibility.compatible ? 'compatible' : 'incompatible';
|
|
||||||
const icon = compatibility.compatible ? '✅' : '❌';
|
|
||||||
|
|
||||||
resultsHTML += `
|
|
||||||
<div class="test-result ${cssClass}">
|
|
||||||
<strong>${icon} ${game}</strong><br>
|
|
||||||
<span class="score">Score: ${compatibility.score}%</span><br>
|
|
||||||
Raison: ${compatibility.reason}<br>
|
|
||||||
Recommandation: ${compatibility.details?.recommendation || 'N/A'}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
logSh(`🎮 ${content.name} → ${game}: ${compatibility.compatible ? 'COMPATIBLE' : 'INCOMPATIBLE'} (${compatibility.score}%)`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
resultsDiv.innerHTML = resultsHTML;
|
|
||||||
logSh('✅ Tests de compatibilité terminés');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonctions utilitaires manquantes pour les tests
|
|
||||||
window.Utils = {
|
|
||||||
storage: {
|
|
||||||
get: (key, defaultValue) => {
|
|
||||||
try {
|
|
||||||
const value = localStorage.getItem(key);
|
|
||||||
return value ? JSON.parse(value) : defaultValue;
|
|
||||||
} catch {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set: (key, value) => {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Cannot save to localStorage:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Auto-charger au démarrage
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
logSh('🚀 Page de test chargée');
|
|
||||||
loadModules();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Test WebSocket Connection</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Test WebSocket Connection</h1>
|
|
||||||
<div id="status">Connecting...</div>
|
|
||||||
<div id="messages"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const statusDiv = document.getElementById('status');
|
|
||||||
const messagesDiv = document.getElementById('messages');
|
|
||||||
|
|
||||||
console.log('🔌 Tentative de connexion WebSocket...');
|
|
||||||
const ws = new WebSocket('ws://localhost:8082');
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
console.log('✅ WebSocket connecté !');
|
|
||||||
statusDiv.textContent = 'Connecté !';
|
|
||||||
statusDiv.style.color = 'green';
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
|
||||||
console.log('📨 Message reçu:', event.data);
|
|
||||||
const messageDiv = document.createElement('div');
|
|
||||||
messageDiv.textContent = `Reçu: ${event.data}`;
|
|
||||||
messagesDiv.appendChild(messageDiv);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
console.log('❌ Connexion fermée');
|
|
||||||
statusDiv.textContent = 'Déconnecté';
|
|
||||||
statusDiv.style.color = 'red';
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = (error) => {
|
|
||||||
console.log('❌ Erreur WebSocket:', error);
|
|
||||||
statusDiv.textContent = 'Erreur de connexion';
|
|
||||||
statusDiv.style.color = 'red';
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Test Content Loading</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Content Loading Test</h1>
|
|
||||||
<div id="results"></div>
|
|
||||||
|
|
||||||
<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-scanner.js"></script>
|
|
||||||
<script src="js/content/sbs-level-7-8-new.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
async function testContentLoading() {
|
|
||||||
const resultsDiv = document.getElementById('results');
|
|
||||||
|
|
||||||
// Test 1: Check if module is loaded
|
|
||||||
resultsDiv.innerHTML += '<h2>Test 1: Module Loading</h2>';
|
|
||||||
if (window.ContentModules && window.ContentModules.SBSLevel78New) {
|
|
||||||
resultsDiv.innerHTML += '✅ Module SBSLevel78New loaded<br>';
|
|
||||||
resultsDiv.innerHTML += `📊 Vocabulary count: ${Object.keys(window.ContentModules.SBSLevel78New.vocabulary).length}<br>`;
|
|
||||||
resultsDiv.innerHTML += `📝 Name: ${window.ContentModules.SBSLevel78New.name}<br>`;
|
|
||||||
} else {
|
|
||||||
resultsDiv.innerHTML += '❌ Module SBSLevel78New NOT loaded<br>';
|
|
||||||
resultsDiv.innerHTML += `Available modules: ${Object.keys(window.ContentModules || {}).join(', ')}<br>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 2: Content Scanner
|
|
||||||
resultsDiv.innerHTML += '<h2>Test 2: Content Scanner</h2>';
|
|
||||||
try {
|
|
||||||
const scanner = new ContentScanner();
|
|
||||||
const result = await scanner.scanAllContent();
|
|
||||||
|
|
||||||
resultsDiv.innerHTML += `✅ Scanner completed<br>`;
|
|
||||||
resultsDiv.innerHTML += `📁 Found ${result.found.length} modules<br>`;
|
|
||||||
resultsDiv.innerHTML += `❌ Errors: ${result.errors.length}<br>`;
|
|
||||||
|
|
||||||
result.found.forEach(content => {
|
|
||||||
resultsDiv.innerHTML += ` - ${content.name} (${content.id})<br>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.errors.length > 0) {
|
|
||||||
resultsDiv.innerHTML += '<h3>Errors:</h3>';
|
|
||||||
result.errors.forEach(error => {
|
|
||||||
resultsDiv.innerHTML += ` - ${error.filename || 'Unknown'}: ${error.error}<br>`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
resultsDiv.innerHTML += `❌ Scanner failed: ${error.message}<br>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run test when page loads
|
|
||||||
document.addEventListener('DOMContentLoaded', testContentLoading);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,321 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// Test de conversion: english-exemple-commented.js → JSON Ultra-Modulaire
|
|
||||||
console.log('🚀 Conversion english-exemple-commented.js → JSON Ultra-Modulaire');
|
|
||||||
console.log('================================================================');
|
|
||||||
|
|
||||||
// Simuler l'environnement browser
|
|
||||||
global.window = {
|
|
||||||
ContentModules: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Charger le module JS
|
|
||||||
require('./js/content/english-exemple-commented.js');
|
|
||||||
|
|
||||||
// Récupérer le module depuis l'objet global
|
|
||||||
const englishModule = global.window?.ContentModules?.EnglishExempleCommented;
|
|
||||||
|
|
||||||
if (!englishModule) {
|
|
||||||
console.error('❌ Erreur: Module EnglishExempleCommented non trouvé');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('✅ Module JS chargé:');
|
|
||||||
console.log(` - ${Object.keys(englishModule.vocabulary).length} mots de vocabulaire`);
|
|
||||||
console.log(` - ${englishModule.sentences?.length || 0} phrases`);
|
|
||||||
console.log(` - ${englishModule.texts?.length || 0} textes`);
|
|
||||||
console.log(` - ${englishModule.dialogues?.length || 0} dialogues`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
// Fonction de conversion COMPLÈTE (toutes les données JS)
|
|
||||||
function convertToUltraModular(jsModule) {
|
|
||||||
const ultraModular = {
|
|
||||||
// ========================================
|
|
||||||
// CORE METADATA - Conversion honnête
|
|
||||||
// ========================================
|
|
||||||
id: "english_exemple_commented_from_js",
|
|
||||||
name: jsModule.name || "English Example Commented",
|
|
||||||
description: jsModule.description || "Converted from JavaScript module",
|
|
||||||
|
|
||||||
// Difficulté: on peut l'inférer du champ difficulty s'il existe
|
|
||||||
difficulty_level: jsModule.difficulty === 'intermediate' ? 5 :
|
|
||||||
jsModule.difficulty === 'easy' ? 3 :
|
|
||||||
jsModule.difficulty === 'hard' ? 7 : 4,
|
|
||||||
|
|
||||||
// Langues détectées
|
|
||||||
original_lang: jsModule.language || "english",
|
|
||||||
user_lang: "french", // Détecté depuis les traductions
|
|
||||||
|
|
||||||
// Icon simple
|
|
||||||
icon: "📚",
|
|
||||||
|
|
||||||
// Tags basés sur le contenu réel
|
|
||||||
tags: [],
|
|
||||||
|
|
||||||
// Skills covered si disponible
|
|
||||||
skills_covered: jsModule.skills_covered || [],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// VOCABULARY - Conversion fidèle avec tous les niveaux
|
|
||||||
// ========================================
|
|
||||||
vocabulary: {},
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// GRAMMAR - Système de grammaire complet
|
|
||||||
// ========================================
|
|
||||||
grammar: {},
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// SENTENCES - Conversion fidèle
|
|
||||||
// ========================================
|
|
||||||
sentences: [],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// TEXTS - Conversion fidèle
|
|
||||||
// ========================================
|
|
||||||
texts: [],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// DIALOGUES - Conversion fidèle
|
|
||||||
// ========================================
|
|
||||||
dialogues: [],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// AUDIO - Contenu audio
|
|
||||||
// ========================================
|
|
||||||
audio: [],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// CORRECTIONS - Exercices de correction
|
|
||||||
// ========================================
|
|
||||||
corrections: [],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// FILL-IN-BLANKS - Exercices fill-in-blanks
|
|
||||||
// ========================================
|
|
||||||
fillInBlanks: [],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// CULTURAL - Contenu culturel
|
|
||||||
// ========================================
|
|
||||||
cultural: {},
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// MATCHING - Exercices de matching
|
|
||||||
// ========================================
|
|
||||||
matching: [],
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// METADATA - Pour tracer la conversion
|
|
||||||
// ========================================
|
|
||||||
conversion_metadata: {
|
|
||||||
converted_from: "javascript_module",
|
|
||||||
conversion_timestamp: new Date().toISOString(),
|
|
||||||
source_file: "english-exemple-commented.js",
|
|
||||||
conversion_system: "honest_converter_v3.0_complete",
|
|
||||||
conversion_notes: "Conversion complète de toutes les données JS"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// VOCABULARY - Conversion complète avec tous les niveaux
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.vocabulary) {
|
|
||||||
for (const [word, translation] of Object.entries(jsModule.vocabulary)) {
|
|
||||||
if (typeof translation === 'string') {
|
|
||||||
// Format simple: garder tel quel
|
|
||||||
ultraModular.vocabulary[word] = {
|
|
||||||
user_language: translation,
|
|
||||||
original_language: word
|
|
||||||
};
|
|
||||||
} else if (typeof translation === 'object') {
|
|
||||||
// Objet complet: préserver toutes les propriétés
|
|
||||||
ultraModular.vocabulary[word] = {
|
|
||||||
user_language: translation.translation || translation.french || translation,
|
|
||||||
original_language: word
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ajouter toutes les propriétés qui existent
|
|
||||||
if (translation.type) ultraModular.vocabulary[word].type = translation.type;
|
|
||||||
if (translation.pronunciation) ultraModular.vocabulary[word].pronunciation = translation.pronunciation;
|
|
||||||
if (translation.audio) ultraModular.vocabulary[word].audio = translation.audio;
|
|
||||||
if (translation.image) ultraModular.vocabulary[word].image = translation.image;
|
|
||||||
if (translation.examples) ultraModular.vocabulary[word].examples = translation.examples;
|
|
||||||
if (translation.grammarNotes) ultraModular.vocabulary[word].grammarNotes = translation.grammarNotes;
|
|
||||||
if (translation.conjugation) ultraModular.vocabulary[word].conjugation = translation.conjugation;
|
|
||||||
if (translation.difficulty_context) ultraModular.vocabulary[word].difficulty_context = translation.difficulty_context;
|
|
||||||
if (translation.cultural_note) ultraModular.vocabulary[word].cultural_note = translation.cultural_note;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// GRAMMAR - Conversion complète
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.grammar) {
|
|
||||||
ultraModular.grammar = jsModule.grammar;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// SENTENCES - Conversion fidèle
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.sentences && Array.isArray(jsModule.sentences)) {
|
|
||||||
ultraModular.sentences = jsModule.sentences.map((sentence, idx) => ({
|
|
||||||
id: `sentence_${idx + 1}`,
|
|
||||||
original_language: sentence.english || sentence.original,
|
|
||||||
user_language: sentence.french || sentence.translation
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// TEXTS - Conversion complète avec exercices
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.texts && Array.isArray(jsModule.texts)) {
|
|
||||||
ultraModular.texts = jsModule.texts.map((text, idx) => {
|
|
||||||
const convertedText = {
|
|
||||||
id: `text_${idx + 1}`,
|
|
||||||
title: text.title,
|
|
||||||
original_language: text.content || text.english,
|
|
||||||
user_language: text.translation || text.french
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ajouter les exercices s'ils existent
|
|
||||||
if (text.questions) convertedText.questions = text.questions;
|
|
||||||
if (text.fillInBlanks) convertedText.fillInBlanks = text.fillInBlanks;
|
|
||||||
|
|
||||||
return convertedText;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// DIALOGUES - Conversion fidèle
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.dialogues && Array.isArray(jsModule.dialogues)) {
|
|
||||||
ultraModular.dialogues = jsModule.dialogues.map((dialogue, idx) => ({
|
|
||||||
id: `dialogue_${idx + 1}`,
|
|
||||||
title: dialogue.title,
|
|
||||||
conversation: dialogue.conversation.map((line, lineIdx) => ({
|
|
||||||
id: `line_${lineIdx + 1}`,
|
|
||||||
speaker: line.speaker,
|
|
||||||
original_language: line.english,
|
|
||||||
user_language: line.french
|
|
||||||
}))
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// AUDIO - Conversion complète
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.audio && Array.isArray(jsModule.audio)) {
|
|
||||||
ultraModular.audio = jsModule.audio;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// CORRECTIONS - Exercices de correction
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.corrections && Array.isArray(jsModule.corrections)) {
|
|
||||||
ultraModular.corrections = jsModule.corrections;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// FILL-IN-BLANKS - Exercices fill-in-blanks
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.fillInBlanks && Array.isArray(jsModule.fillInBlanks)) {
|
|
||||||
ultraModular.fillInBlanks = jsModule.fillInBlanks;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// CULTURAL - Contenu culturel complet
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.cultural) {
|
|
||||||
ultraModular.cultural = jsModule.cultural;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// MATCHING - Exercices de matching
|
|
||||||
// ========================================
|
|
||||||
if (jsModule.matching && Array.isArray(jsModule.matching)) {
|
|
||||||
ultraModular.matching = jsModule.matching;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// TAGS basés sur le contenu RÉEL
|
|
||||||
// ========================================
|
|
||||||
if (Object.keys(ultraModular.vocabulary).length > 0) ultraModular.tags.push("vocabulary");
|
|
||||||
if (ultraModular.sentences.length > 0) ultraModular.tags.push("sentences");
|
|
||||||
if (ultraModular.texts.length > 0) ultraModular.tags.push("texts");
|
|
||||||
if (ultraModular.dialogues.length > 0) ultraModular.tags.push("dialogues");
|
|
||||||
if (ultraModular.grammar && Object.keys(ultraModular.grammar).length > 0) ultraModular.tags.push("grammar");
|
|
||||||
if (ultraModular.audio && ultraModular.audio.length > 0) ultraModular.tags.push("audio");
|
|
||||||
if (ultraModular.corrections && ultraModular.corrections.length > 0) ultraModular.tags.push("corrections");
|
|
||||||
if (ultraModular.fillInBlanks && ultraModular.fillInBlanks.length > 0) ultraModular.tags.push("fillInBlanks");
|
|
||||||
if (ultraModular.cultural && Object.keys(ultraModular.cultural).length > 0) ultraModular.tags.push("cultural");
|
|
||||||
if (ultraModular.matching && ultraModular.matching.length > 0) ultraModular.tags.push("matching");
|
|
||||||
ultraModular.tags.push("converted_from_js");
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// STATS COMPLÈTES de conversion
|
|
||||||
// ========================================
|
|
||||||
ultraModular.conversion_metadata.stats = {
|
|
||||||
vocabulary_count: Object.keys(ultraModular.vocabulary).length,
|
|
||||||
sentence_count: ultraModular.sentences.length,
|
|
||||||
text_count: ultraModular.texts.length,
|
|
||||||
dialogue_count: ultraModular.dialogues.length,
|
|
||||||
grammar_count: ultraModular.grammar ? Object.keys(ultraModular.grammar).length : 0,
|
|
||||||
audio_count: ultraModular.audio ? ultraModular.audio.length : 0,
|
|
||||||
corrections_count: ultraModular.corrections ? ultraModular.corrections.length : 0,
|
|
||||||
fillInBlanks_count: ultraModular.fillInBlanks ? ultraModular.fillInBlanks.length : 0,
|
|
||||||
cultural_sections_count: ultraModular.cultural ? Object.keys(ultraModular.cultural).length : 0,
|
|
||||||
matching_count: ultraModular.matching ? ultraModular.matching.length : 0,
|
|
||||||
skills_covered_count: ultraModular.skills_covered ? ultraModular.skills_covered.length : 0
|
|
||||||
};
|
|
||||||
|
|
||||||
return ultraModular;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Effectuer la conversion
|
|
||||||
console.log('🔄 Conversion en cours...');
|
|
||||||
const ultraModularJSON = convertToUltraModular(englishModule);
|
|
||||||
|
|
||||||
console.log('✅ Conversion COMPLÈTE terminée!');
|
|
||||||
console.log(` - ${Object.keys(ultraModularJSON.vocabulary).length} mots convertis`);
|
|
||||||
console.log(` - ${ultraModularJSON.sentences.length} phrases converties`);
|
|
||||||
console.log(` - ${ultraModularJSON.texts.length} textes convertis`);
|
|
||||||
console.log(` - ${ultraModularJSON.dialogues.length} dialogues convertis`);
|
|
||||||
console.log(` - ${ultraModularJSON.conversion_metadata.stats.grammar_count} leçons de grammaire`);
|
|
||||||
console.log(` - ${ultraModularJSON.conversion_metadata.stats.audio_count} contenus audio`);
|
|
||||||
console.log(` - ${ultraModularJSON.conversion_metadata.stats.corrections_count} exercices de correction`);
|
|
||||||
console.log(` - ${ultraModularJSON.conversion_metadata.stats.fillInBlanks_count} exercices fill-in-blanks`);
|
|
||||||
console.log(` - ${ultraModularJSON.conversion_metadata.stats.cultural_sections_count} sections culturelles`);
|
|
||||||
console.log(` - ${ultraModularJSON.conversion_metadata.stats.matching_count} exercices de matching`);
|
|
||||||
console.log(` - ${ultraModularJSON.conversion_metadata.stats.skills_covered_count} compétences couvertes`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
// Sauvegarder le fichier JSON
|
|
||||||
const fs = require('fs');
|
|
||||||
const outputFile = 'english-exemple-commented-GENERATED.json';
|
|
||||||
const jsonContent = JSON.stringify(ultraModularJSON, null, 2);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(outputFile, jsonContent, 'utf8');
|
|
||||||
console.log(`📁 Fichier JSON généré: ${outputFile}`);
|
|
||||||
console.log(`📏 Taille: ${(jsonContent.length / 1024).toFixed(1)} KB`);
|
|
||||||
console.log(`📄 Lignes: ${jsonContent.split('\n').length}`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
// Validation finale
|
|
||||||
const reloaded = JSON.parse(jsonContent);
|
|
||||||
console.log('✅ VALIDATION:');
|
|
||||||
console.log(` - JSON valide: ✅`);
|
|
||||||
console.log(` - ID: ${reloaded.id}`);
|
|
||||||
console.log(` - Nom: ${reloaded.name}`);
|
|
||||||
console.log(` - Tags: ${reloaded.tags.join(', ')}`);
|
|
||||||
console.log(` - Conversion honnête: ✅ (pas de données inventées)`);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Erreur:', error.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('================================================================');
|
|
||||||
console.log('🎉 Conversion JS → JSON Ultra-Modulaire RÉUSSIE (honnête)!');
|
|
||||||
@ -1,368 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// Test de conversion JS → JSON Ultra-Modulaire
|
|
||||||
// Prouve que notre système fonctionne en mémoire
|
|
||||||
|
|
||||||
console.log('🚀 Démarrage du test de conversion JS → JSON Ultra-Modulaire');
|
|
||||||
console.log('========================================================');
|
|
||||||
|
|
||||||
// Simuler l'environnement browser avec les logs
|
|
||||||
global.logSh = function(message, level = 'INFO') {
|
|
||||||
console.log(`[${level}] ${message}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Simuler window.ContentModules
|
|
||||||
global.window = {
|
|
||||||
ContentModules: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Charger le contenu JavaScript (simulation du module SBS)
|
|
||||||
const sbsContent = {
|
|
||||||
name: "SBS Level 7-8",
|
|
||||||
difficulty: "intermediate",
|
|
||||||
vocabulary: {
|
|
||||||
// Housing and Places
|
|
||||||
central: "中心的;中央的",
|
|
||||||
avenue: "大街;林荫道",
|
|
||||||
refrigerator: "冰箱",
|
|
||||||
closet: "衣柜;壁橱",
|
|
||||||
elevator: "电梯",
|
|
||||||
building: "建筑物;大楼",
|
|
||||||
"air conditioner": "空调",
|
|
||||||
superintendent: "主管;负责人",
|
|
||||||
"bus stop": "公交车站",
|
|
||||||
jacuzzi: "按摩浴缸",
|
|
||||||
|
|
||||||
// Clothing and Accessories
|
|
||||||
shirt: "衬衫",
|
|
||||||
coat: "外套、大衣",
|
|
||||||
dress: "连衣裙",
|
|
||||||
skirt: "短裙",
|
|
||||||
blouse: "女式衬衫",
|
|
||||||
jacket: "夹克、短外套",
|
|
||||||
sweater: "毛衣、针织衫",
|
|
||||||
suit: "套装、西装",
|
|
||||||
tie: "领带",
|
|
||||||
pants: "裤子",
|
|
||||||
jeans: "牛仔裤",
|
|
||||||
belt: "腰带、皮带",
|
|
||||||
hat: "帽子",
|
|
||||||
glove: "手套",
|
|
||||||
glasses: "眼镜",
|
|
||||||
pajamas: "睡衣",
|
|
||||||
shoes: "鞋子"
|
|
||||||
},
|
|
||||||
|
|
||||||
sentences: [
|
|
||||||
{
|
|
||||||
english: "Amy's apartment building is in the center of town.",
|
|
||||||
chinese: "艾米的公寓楼在城镇中心。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
english: "There's a lot of noise near Amy's apartment building.",
|
|
||||||
chinese: "艾米的公寓楼附近很吵。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
english: "The superintendent is very helpful.",
|
|
||||||
chinese: "管理员非常乐于助人。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
english: "I need to buy new clothes for winter.",
|
|
||||||
chinese: "我需要为冬天买新衣服。"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ajouter aux modules globaux
|
|
||||||
global.window.ContentModules.SBSLevel78New = sbsContent;
|
|
||||||
|
|
||||||
console.log('✅ Module JavaScript chargé en mémoire:');
|
|
||||||
console.log(` - ${Object.keys(sbsContent.vocabulary).length} mots de vocabulaire`);
|
|
||||||
console.log(` - ${sbsContent.sentences.length} phrases d'exemple`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// ANALYSEUR DE CAPACITÉS SIMPLIFIÉ
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
class SimpleContentAnalyzer {
|
|
||||||
analyzeCapabilities(module) {
|
|
||||||
const vocab = module.vocabulary || {};
|
|
||||||
const sentences = module.sentences || [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasVocabulary: Object.keys(vocab).length > 0,
|
|
||||||
hasSentences: sentences.length > 0,
|
|
||||||
hasGrammar: false,
|
|
||||||
hasAudio: false,
|
|
||||||
hasDialogues: false,
|
|
||||||
hasExercises: false,
|
|
||||||
hasMatching: false,
|
|
||||||
hasCulture: false,
|
|
||||||
|
|
||||||
vocabularyDepth: this.analyzeVocabularyDepth(vocab),
|
|
||||||
contentRichness: Object.keys(vocab).length / 10,
|
|
||||||
|
|
||||||
// Stats
|
|
||||||
vocabularyCount: Object.keys(vocab).length,
|
|
||||||
sentenceCount: sentences.length,
|
|
||||||
complexPhrases: Object.keys(vocab).filter(word => word.includes(' ')).length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
analyzeVocabularyDepth(vocabulary) {
|
|
||||||
let maxDepth = 1; // Simple strings
|
|
||||||
|
|
||||||
for (const [word, translation] of Object.entries(vocabulary)) {
|
|
||||||
if (typeof translation === 'string') {
|
|
||||||
maxDepth = Math.max(maxDepth, 1);
|
|
||||||
} else if (typeof translation === 'object') {
|
|
||||||
maxDepth = Math.max(maxDepth, 2);
|
|
||||||
// Pourrait analyser plus profondément ici
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateGameCompatibility(capabilities) {
|
|
||||||
return {
|
|
||||||
'whack-a-mole': {
|
|
||||||
compatible: capabilities.hasVocabulary,
|
|
||||||
score: capabilities.vocabularyCount * 2,
|
|
||||||
reason: 'Nécessite vocabulaire'
|
|
||||||
},
|
|
||||||
'memory-match': {
|
|
||||||
compatible: capabilities.hasVocabulary,
|
|
||||||
score: capabilities.vocabularyCount * 1.5,
|
|
||||||
reason: 'Optimal pour vocabulaire visuel'
|
|
||||||
},
|
|
||||||
'quiz-game': {
|
|
||||||
compatible: capabilities.hasVocabulary || capabilities.hasSentences,
|
|
||||||
score: (capabilities.vocabularyCount + capabilities.sentenceCount * 2) * 1.2,
|
|
||||||
reason: 'Fonctionne avec tout contenu'
|
|
||||||
},
|
|
||||||
'text-reader': {
|
|
||||||
compatible: capabilities.hasSentences,
|
|
||||||
score: capabilities.sentenceCount * 10,
|
|
||||||
reason: 'Nécessite phrases à lire'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// CONVERTISSEUR VERS ULTRA-MODULAIRE
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
class JSToUltraModularConverter {
|
|
||||||
convert(jsModule) {
|
|
||||||
const analyzer = new SimpleContentAnalyzer();
|
|
||||||
const capabilities = analyzer.analyzeCapabilities(jsModule);
|
|
||||||
const compatibility = analyzer.calculateGameCompatibility(capabilities);
|
|
||||||
|
|
||||||
console.log('🔍 Analyse des capacités:');
|
|
||||||
console.log(` - Vocabulaire: ${capabilities.vocabularyCount} mots`);
|
|
||||||
console.log(` - Phrases: ${capabilities.sentenceCount}`);
|
|
||||||
console.log(` - Profondeur vocab: ${capabilities.vocabularyDepth}/6`);
|
|
||||||
console.log(` - Richesse: ${capabilities.contentRichness.toFixed(1)}/10`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
const ultraModularSpec = {
|
|
||||||
// ========================================================================================================
|
|
||||||
// CORE METADATA SECTION - GENERATED FROM JS MODULE
|
|
||||||
// ========================================================================================================
|
|
||||||
id: "sbs_level_7_8_converted_from_js",
|
|
||||||
name: "SBS Level 7-8 (Converted from JavaScript)",
|
|
||||||
description: "English learning content covering housing and clothing vocabulary - automatically converted from legacy JavaScript format to ultra-modular JSON specification",
|
|
||||||
|
|
||||||
// CORRECTION: Inférence raisonnable basée sur les données existantes
|
|
||||||
// Difficulty: on peut l'inférer du nom "Level 7-8"
|
|
||||||
difficulty_level: 7, // Basé sur "SBS Level 7-8"
|
|
||||||
|
|
||||||
// Langues: on peut les détecter des données (english → chinese)
|
|
||||||
original_lang: "english", // Détecté des clés du vocabulaire
|
|
||||||
user_lang: "chinese", // Détecté des valeurs du vocabulaire
|
|
||||||
|
|
||||||
// Icon: on peut faire une inférence basée sur le contenu détecté
|
|
||||||
icon: "🏠", // Basé sur la prédominance du vocabulaire housing
|
|
||||||
|
|
||||||
// CORRECTION: On peut extraire les tags du contenu existant (ça c'est de l'analyse, pas de l'invention)
|
|
||||||
tags: this.extractTags(jsModule.vocabulary),
|
|
||||||
|
|
||||||
// SUPPRIMÉ: skills_covered, target_audience, estimated_duration
|
|
||||||
// On n'a PAS ces infos dans le JS original, donc on n'invente pas !
|
|
||||||
|
|
||||||
// ========================================================================================================
|
|
||||||
// VOCABULARY SECTION - ENHANCED FROM ORIGINAL
|
|
||||||
// ========================================================================================================
|
|
||||||
vocabulary: this.enhanceVocabulary(jsModule.vocabulary || {}),
|
|
||||||
|
|
||||||
// ========================================================================================================
|
|
||||||
// SENTENCES SECTION - ENHANCED FROM ORIGINAL
|
|
||||||
// ========================================================================================================
|
|
||||||
sentences: this.enhanceSentences(jsModule.sentences || []),
|
|
||||||
|
|
||||||
// ========================================================================================================
|
|
||||||
// CONVERSION METADATA - PROOF OF SYSTEM FUNCTIONALITY
|
|
||||||
// ========================================================================================================
|
|
||||||
conversion_metadata: {
|
|
||||||
converted_from: "legacy_javascript_module",
|
|
||||||
conversion_timestamp: new Date().toISOString(),
|
|
||||||
conversion_system: "ultra_modular_converter_v1.0",
|
|
||||||
original_format: "js_content_module",
|
|
||||||
target_format: "ultra_modular_json_v2.0",
|
|
||||||
|
|
||||||
// Original module stats
|
|
||||||
original_stats: {
|
|
||||||
vocabulary_count: capabilities.vocabularyCount,
|
|
||||||
sentence_count: capabilities.sentenceCount,
|
|
||||||
has_complex_phrases: capabilities.complexPhrases > 0
|
|
||||||
},
|
|
||||||
|
|
||||||
// Detected capabilities
|
|
||||||
detected_capabilities: capabilities,
|
|
||||||
|
|
||||||
// Game compatibility analysis
|
|
||||||
game_compatibility: compatibility,
|
|
||||||
|
|
||||||
// Quality metrics
|
|
||||||
quality_score: this.calculateQualityScore(capabilities, compatibility)
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================================================================
|
|
||||||
// SYSTEM VALIDATION - PROVES THE SYSTEM WORKS
|
|
||||||
// ========================================================================================================
|
|
||||||
system_validation: {
|
|
||||||
format_version: "2.0",
|
|
||||||
specification: "ultra_modular",
|
|
||||||
backwards_compatible: true,
|
|
||||||
memory_stored: true,
|
|
||||||
conversion_verified: true,
|
|
||||||
ready_for_games: Object.values(compatibility).some(game => game.compatible)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return ultraModularSpec;
|
|
||||||
}
|
|
||||||
|
|
||||||
extractTags(vocabulary) {
|
|
||||||
const tags = new Set(['vocabulary', 'intermediate']);
|
|
||||||
|
|
||||||
for (const word of Object.keys(vocabulary)) {
|
|
||||||
if (word.match(/shirt|coat|dress|pants|jacket|shoes|clothing/)) {
|
|
||||||
tags.add('clothing');
|
|
||||||
}
|
|
||||||
if (word.match(/building|apartment|house|room|elevator|housing/)) {
|
|
||||||
tags.add('housing');
|
|
||||||
}
|
|
||||||
if (word.match(/street|town|center|avenue|places/)) {
|
|
||||||
tags.add('places');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
enhanceVocabulary(originalVocab) {
|
|
||||||
const enhanced = {};
|
|
||||||
|
|
||||||
for (const [word, translation] of Object.entries(originalVocab)) {
|
|
||||||
// CORRECTION: Ne pas inventer de données inexistantes!
|
|
||||||
// Conversion minimaliste: garder SEULEMENT ce qu'on a
|
|
||||||
enhanced[word] = {
|
|
||||||
user_language: translation,
|
|
||||||
original_language: word
|
|
||||||
// FINI. Pas d'invention de type, difficulté, catégorie, etc.
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return enhanced;
|
|
||||||
}
|
|
||||||
|
|
||||||
enhanceSentences(originalSentences) {
|
|
||||||
return originalSentences.map((sentence, index) => ({
|
|
||||||
id: `sentence_${index + 1}`,
|
|
||||||
original_language: sentence.english,
|
|
||||||
user_language: sentence.chinese
|
|
||||||
// FINI. Pas d'invention de grammatical_focus, topic, etc.
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// CORRECTION: Helper methods - SEULEMENT pour l'analyse des données existantes, pas l'invention
|
|
||||||
|
|
||||||
calculateQualityScore(capabilities, compatibility) {
|
|
||||||
let score = 0;
|
|
||||||
|
|
||||||
// Base content score
|
|
||||||
if (capabilities.hasVocabulary) score += 30;
|
|
||||||
if (capabilities.hasSentences) score += 20;
|
|
||||||
|
|
||||||
// Volume bonus
|
|
||||||
score += Math.min(25, capabilities.vocabularyCount);
|
|
||||||
score += Math.min(15, capabilities.sentenceCount * 2);
|
|
||||||
|
|
||||||
// Compatibility bonus
|
|
||||||
const compatibleGames = Object.values(compatibility).filter(g => g.compatible).length;
|
|
||||||
score += compatibleGames * 2;
|
|
||||||
|
|
||||||
return Math.min(100, score);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// EXÉCUTION DU TEST
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
console.log('🔄 Conversion en cours...');
|
|
||||||
|
|
||||||
const converter = new JSToUltraModularConverter();
|
|
||||||
const ultraModularJSON = converter.convert(sbsContent);
|
|
||||||
|
|
||||||
console.log('✅ Conversion terminée avec succès!');
|
|
||||||
console.log(`📊 Score de qualité: ${ultraModularJSON.conversion_metadata.quality_score}/100`);
|
|
||||||
console.log(`🎮 Jeux compatibles: ${Object.values(ultraModularJSON.conversion_metadata.game_compatibility).filter(g => g.compatible).length}/4`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// GÉNÉRATION DU FICHIER JSON
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const outputFile = 'sbs-level-7-8-GENERATED-from-js.json';
|
|
||||||
const jsonContent = JSON.stringify(ultraModularJSON, null, 2);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(outputFile, jsonContent, 'utf8');
|
|
||||||
console.log(`📁 Fichier JSON généré: ${outputFile}`);
|
|
||||||
console.log(`📏 Taille: ${(jsonContent.length / 1024).toFixed(1)} KB`);
|
|
||||||
console.log(`📄 Lignes: ${jsonContent.split('\n').length}`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
// Validation finale
|
|
||||||
const reloaded = JSON.parse(jsonContent);
|
|
||||||
console.log('✅ VALIDATION FINALE:');
|
|
||||||
console.log(` - JSON valide: ✅`);
|
|
||||||
console.log(` - ID: ${reloaded.id}`);
|
|
||||||
console.log(` - Nom: ${reloaded.name}`);
|
|
||||||
console.log(` - Vocabulaire: ${Object.keys(reloaded.vocabulary).length} mots`);
|
|
||||||
console.log(` - Phrases: ${reloaded.sentences.length}`);
|
|
||||||
console.log(` - Métadonnées conversion: ✅`);
|
|
||||||
console.log(` - Score qualité: ${reloaded.conversion_metadata.quality_score}/100`);
|
|
||||||
console.log('');
|
|
||||||
console.log('🎉 PREUVE ÉTABLIE: Le système fonctionne parfaitement!');
|
|
||||||
console.log(' - Données JS chargées en mémoire ✅');
|
|
||||||
console.log(' - Analyse des capacités ✅');
|
|
||||||
console.log(' - Conversion ultra-modulaire ✅');
|
|
||||||
console.log(' - Génération JSON ✅');
|
|
||||||
console.log(' - Validation finale ✅');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Erreur lors de l\'écriture du fichier:', error.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('========================================================');
|
|
||||||
console.log('✅ Test de conversion JS → JSON Ultra-Modulaire RÉUSSI');
|
|
||||||
102
test-curl.js
102
test-curl.js
@ -1,102 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const { spawn } = require('child_process');
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
const config = {
|
|
||||||
DO_ACCESS_KEY: 'DO801MU8BZBB89LLK4FN',
|
|
||||||
DO_SECRET_KEY: 'rfKPjampdpUCYhn02XrKg6IWKmqebjg9HQTGxNLzJQY',
|
|
||||||
DO_REGION: 'fra1'
|
|
||||||
};
|
|
||||||
|
|
||||||
function sha256(message) {
|
|
||||||
return crypto.createHash('sha256').update(message, 'utf8').digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hmacSha256(key, message) {
|
|
||||||
return crypto.createHmac('sha256', key).update(message, 'utf8').digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateSignatureAndTest() {
|
|
||||||
const testUrl = 'https://autocollant.fra1.digitaloceanspaces.com/Class_generator/ContentMe/greetings-basic.json';
|
|
||||||
const method = 'GET';
|
|
||||||
|
|
||||||
// Timestamp actuel
|
|
||||||
const now = new Date();
|
|
||||||
const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
||||||
const timeStamp = now.toISOString().slice(0, 19).replace(/[-:]/g, '') + 'Z';
|
|
||||||
|
|
||||||
console.log(`🕐 Génération signature pour: ${timeStamp}`);
|
|
||||||
|
|
||||||
// Parse URL
|
|
||||||
const urlObj = new URL(testUrl);
|
|
||||||
const host = urlObj.hostname;
|
|
||||||
const canonicalUri = urlObj.pathname;
|
|
||||||
|
|
||||||
// Headers canoniques
|
|
||||||
const canonicalHeaders = `host:${host}\nx-amz-date:${timeStamp}\n`;
|
|
||||||
const signedHeaders = 'host;x-amz-date';
|
|
||||||
|
|
||||||
// Requête canonique
|
|
||||||
const payloadHash = sha256('');
|
|
||||||
const canonicalRequest = [
|
|
||||||
method,
|
|
||||||
canonicalUri,
|
|
||||||
'', // query string
|
|
||||||
canonicalHeaders,
|
|
||||||
signedHeaders,
|
|
||||||
payloadHash
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
// String to sign
|
|
||||||
const algorithm = 'AWS4-HMAC-SHA256';
|
|
||||||
const credentialScope = `${dateStamp}/${config.DO_REGION}/s3/aws4_request`;
|
|
||||||
const canonicalRequestHash = sha256(canonicalRequest);
|
|
||||||
const stringToSign = [
|
|
||||||
algorithm,
|
|
||||||
timeStamp,
|
|
||||||
credentialScope,
|
|
||||||
canonicalRequestHash
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
// Signature
|
|
||||||
const kDate = hmacSha256('AWS4' + config.DO_SECRET_KEY, dateStamp);
|
|
||||||
const kRegion = hmacSha256(kDate, config.DO_REGION);
|
|
||||||
const kService = hmacSha256(kRegion, 's3');
|
|
||||||
const kSigning = hmacSha256(kService, 'aws4_request');
|
|
||||||
const signature = hmacSha256(kSigning, stringToSign).toString('hex');
|
|
||||||
|
|
||||||
const authorization = `${algorithm} Credential=${config.DO_ACCESS_KEY}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
||||||
|
|
||||||
console.log(`🔑 Authorization: ${authorization}`);
|
|
||||||
|
|
||||||
// Test avec curl
|
|
||||||
const curlArgs = [
|
|
||||||
'-X', 'GET',
|
|
||||||
testUrl,
|
|
||||||
'-H', `Authorization: ${authorization}`,
|
|
||||||
'-H', `X-Amz-Date: ${timeStamp}`,
|
|
||||||
'-H', `X-Amz-Content-Sha256: ${payloadHash}`,
|
|
||||||
'-H', 'User-Agent: test-client',
|
|
||||||
'-i'
|
|
||||||
];
|
|
||||||
|
|
||||||
console.log('🌐 Test curl...');
|
|
||||||
|
|
||||||
const curl = spawn('curl', curlArgs);
|
|
||||||
|
|
||||||
curl.stdout.on('data', (data) => {
|
|
||||||
console.log(data.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
curl.stderr.on('data', (data) => {
|
|
||||||
console.error(data.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
curl.on('close', (code) => {
|
|
||||||
console.log(`✅ Curl terminé avec code: ${code}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
generateSignatureAndTest();
|
|
||||||
@ -1,374 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Test DigitalOcean Spaces Connection</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
background: white;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 30px;
|
|
||||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
border-bottom: 3px solid #667eea;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
.test-section {
|
|
||||||
margin: 20px 0;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 8px;
|
|
||||||
border-left: 4px solid #667eea;
|
|
||||||
}
|
|
||||||
.test-result {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
.success {
|
|
||||||
background: #d4edda;
|
|
||||||
border: 1px solid #c3e6cb;
|
|
||||||
color: #155724;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
background: #f8d7da;
|
|
||||||
border: 1px solid #f5c6cb;
|
|
||||||
color: #721c24;
|
|
||||||
}
|
|
||||||
.warning {
|
|
||||||
background: #fff3cd;
|
|
||||||
border: 1px solid #ffeeba;
|
|
||||||
color: #856404;
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
background: #d1ecf1;
|
|
||||||
border: 1px solid #bee5eb;
|
|
||||||
color: #0c5460;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 5px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background: #5a67d8;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
||||||
}
|
|
||||||
button:disabled {
|
|
||||||
background: #ccc;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
.spinner {
|
|
||||||
display: inline-block;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border: 3px solid rgba(255,255,255,.3);
|
|
||||||
border-radius: 50%;
|
|
||||||
border-top-color: white;
|
|
||||||
animation: spin 1s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.config-display {
|
|
||||||
background: #2d3748;
|
|
||||||
color: #48bb78;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin: 10px 0;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
}
|
|
||||||
.test-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
.test-card {
|
|
||||||
background: white;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.test-card h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
color: #4a5568;
|
|
||||||
}
|
|
||||||
.status-indicator {
|
|
||||||
display: inline-block;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
.status-indicator.success { background: #48bb78; }
|
|
||||||
.status-indicator.error { background: #f56565; }
|
|
||||||
.status-indicator.pending { background: #ed8936; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>🔧 Test de Connexion DigitalOcean Spaces</h1>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>📋 Configuration Actuelle</h2>
|
|
||||||
<div id="config" class="config-display">Chargement...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>🧪 Tests de Connexion</h2>
|
|
||||||
<div>
|
|
||||||
<button onclick="runAllTests()">🚀 Lancer Tous les Tests</button>
|
|
||||||
<button onclick="testPublicAccess()">🌐 Test Accès Public</button>
|
|
||||||
<button onclick="testWithAuth()">🔐 Test avec Authentification</button>
|
|
||||||
<button onclick="testCORS()">🔄 Test CORS</button>
|
|
||||||
<button onclick="clearResults()">🗑️ Effacer Résultats</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-grid" id="testGrid"></div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>📊 Résultats Détaillés</h2>
|
|
||||||
<div id="results"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>💡 Recommandations</h2>
|
|
||||||
<div id="recommendations" class="info">
|
|
||||||
Les recommandations apparaîtront après les tests...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Charger les scripts nécessaires -->
|
|
||||||
<script src="js/core/env-config.js"></script>
|
|
||||||
<script>
|
|
||||||
// Désactiver temporairement les logs pour éviter les erreurs
|
|
||||||
if (typeof logSh === 'undefined') {
|
|
||||||
window.logSh = function(msg, level) {
|
|
||||||
console.log(`[${level}] ${msg}`);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Afficher la configuration
|
|
||||||
function displayConfig() {
|
|
||||||
const config = window.envConfig.getDiagnostics();
|
|
||||||
document.getElementById('config').innerHTML = JSON.stringify(config, null, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonction pour ajouter un résultat
|
|
||||||
function addResult(message, type = 'info') {
|
|
||||||
const resultsDiv = document.getElementById('results');
|
|
||||||
const resultDiv = document.createElement('div');
|
|
||||||
resultDiv.className = `test-result ${type}`;
|
|
||||||
resultDiv.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
||||||
resultsDiv.appendChild(resultDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonction pour ajouter une carte de test
|
|
||||||
function addTestCard(title, status, details) {
|
|
||||||
const grid = document.getElementById('testGrid');
|
|
||||||
const card = document.createElement('div');
|
|
||||||
card.className = 'test-card';
|
|
||||||
card.innerHTML = `
|
|
||||||
<h3><span class="status-indicator ${status}"></span>${title}</h3>
|
|
||||||
<div>${details}</div>
|
|
||||||
`;
|
|
||||||
grid.appendChild(card);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test d'accès public (sans auth)
|
|
||||||
async function testPublicAccess() {
|
|
||||||
addResult('Test d\'accès public sans authentification...', 'info');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = window.envConfig.getRemoteContentUrl() + 'test.json';
|
|
||||||
addResult(`URL testée: ${url}`, 'info');
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'HEAD',
|
|
||||||
mode: 'cors'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
addResult('✅ Accès public réussi!', 'success');
|
|
||||||
addTestCard('Accès Public', 'success', 'Le bucket est accessible publiquement');
|
|
||||||
} else if (response.status === 403) {
|
|
||||||
addResult('🔒 Accès refusé (403) - Le bucket est privé', 'warning');
|
|
||||||
addTestCard('Accès Public', 'error', 'Bucket privé - Authentification requise');
|
|
||||||
} else {
|
|
||||||
addResult(`❌ Erreur: Status ${response.status}`, 'error');
|
|
||||||
addTestCard('Accès Public', 'error', `Status HTTP: ${response.status}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
addResult(`❌ Erreur réseau: ${error.message}`, 'error');
|
|
||||||
|
|
||||||
if (error.message.includes('CORS')) {
|
|
||||||
addResult('⚠️ Problème CORS détecté - Vérifiez la configuration CORS du bucket', 'warning');
|
|
||||||
addTestCard('Accès Public', 'error', 'Erreur CORS');
|
|
||||||
} else {
|
|
||||||
addTestCard('Accès Public', 'error', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test avec authentification
|
|
||||||
async function testWithAuth() {
|
|
||||||
addResult('Test avec authentification AWS Signature v4...', 'info');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const testUrl = window.envConfig.getRemoteContentUrl() + 'test.json';
|
|
||||||
const authHeaders = await window.envConfig.getAuthHeaders('GET', testUrl);
|
|
||||||
|
|
||||||
addResult('Headers d\'authentification générés:', 'info');
|
|
||||||
addResult(JSON.stringify(authHeaders, null, 2), 'info');
|
|
||||||
|
|
||||||
const response = await fetch(testUrl, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: authHeaders,
|
|
||||||
mode: 'cors'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
addResult('✅ Authentification réussie!', 'success');
|
|
||||||
const data = await response.text();
|
|
||||||
addResult(`Contenu reçu: ${data.substring(0, 200)}...`, 'success');
|
|
||||||
addTestCard('Authentification', 'success', 'Connexion authentifiée réussie');
|
|
||||||
} else {
|
|
||||||
addResult(`❌ Authentification échouée: Status ${response.status}`, 'error');
|
|
||||||
addResult(`Message: ${response.statusText}`, 'error');
|
|
||||||
addTestCard('Authentification', 'error', `Status: ${response.status}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
addResult(`❌ Erreur: ${error.message}`, 'error');
|
|
||||||
addTestCard('Authentification', 'error', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test CORS
|
|
||||||
async function testCORS() {
|
|
||||||
addResult('Test de la configuration CORS...', 'info');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = window.envConfig.getRemoteContentUrl();
|
|
||||||
|
|
||||||
// Test OPTIONS (preflight)
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'OPTIONS',
|
|
||||||
headers: {
|
|
||||||
'Origin': window.location.origin,
|
|
||||||
'Access-Control-Request-Method': 'GET',
|
|
||||||
'Access-Control-Request-Headers': 'authorization'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const corsHeaders = {
|
|
||||||
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
|
|
||||||
'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
|
|
||||||
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers')
|
|
||||||
};
|
|
||||||
|
|
||||||
addResult('Headers CORS reçus:', 'info');
|
|
||||||
addResult(JSON.stringify(corsHeaders, null, 2), 'info');
|
|
||||||
|
|
||||||
if (corsHeaders['Access-Control-Allow-Origin']) {
|
|
||||||
addResult('✅ CORS configuré', 'success');
|
|
||||||
addTestCard('Configuration CORS', 'success', 'Headers CORS présents');
|
|
||||||
} else {
|
|
||||||
addResult('⚠️ Headers CORS manquants', 'warning');
|
|
||||||
addTestCard('Configuration CORS', 'error', 'Headers CORS non configurés');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
addResult(`❌ Test CORS échoué: ${error.message}`, 'error');
|
|
||||||
addTestCard('Configuration CORS', 'error', 'Test échoué');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lancer tous les tests
|
|
||||||
async function runAllTests() {
|
|
||||||
clearResults();
|
|
||||||
addResult('🚀 Démarrage de la suite de tests complète...', 'info');
|
|
||||||
|
|
||||||
await testPublicAccess();
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
|
|
||||||
await testWithAuth();
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
|
|
||||||
await testCORS();
|
|
||||||
|
|
||||||
// Test de connexion via EnvConfig
|
|
||||||
addResult('Test via EnvConfig.testRemoteConnection()...', 'info');
|
|
||||||
const result = await window.envConfig.testRemoteConnection();
|
|
||||||
addResult(`Résultat: ${JSON.stringify(result, null, 2)}`, result.success ? 'success' : 'error');
|
|
||||||
|
|
||||||
generateRecommendations();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Générer des recommandations
|
|
||||||
function generateRecommendations() {
|
|
||||||
const recoDiv = document.getElementById('recommendations');
|
|
||||||
let recommendations = [];
|
|
||||||
|
|
||||||
const results = document.getElementById('results').textContent;
|
|
||||||
|
|
||||||
if (results.includes('403')) {
|
|
||||||
recommendations.push('🔐 Le bucket est privé. Assurez-vous que les clés d\'API sont correctes.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.includes('CORS')) {
|
|
||||||
recommendations.push('🔄 Configurez CORS sur votre bucket DigitalOcean Spaces:');
|
|
||||||
recommendations.push(' - Allez dans les paramètres du bucket');
|
|
||||||
recommendations.push(' - Ajoutez une règle CORS pour autoriser votre origine');
|
|
||||||
recommendations.push(' - Origine: * ou file:// pour les fichiers locaux');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.includes('Authentification échouée')) {
|
|
||||||
recommendations.push('🔑 Vérifiez vos clés d\'accès DigitalOcean dans env-config.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recommendations.length === 0) {
|
|
||||||
recommendations.push('✅ Tout semble fonctionner correctement!');
|
|
||||||
}
|
|
||||||
|
|
||||||
recoDiv.innerHTML = recommendations.join('<br>');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Effacer les résultats
|
|
||||||
function clearResults() {
|
|
||||||
document.getElementById('results').innerHTML = '';
|
|
||||||
document.getElementById('testGrid').innerHTML = '';
|
|
||||||
document.getElementById('recommendations').innerHTML = 'Les recommandations apparaîtront après les tests...';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialisation
|
|
||||||
displayConfig();
|
|
||||||
addResult('Page de test prête. Cliquez sur "Lancer Tous les Tests" pour commencer.', 'info');
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Test DigitalOcean Auth</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: monospace; padding: 20px; background: #f0f0f0; }
|
|
||||||
.result { margin: 10px 0; padding: 10px; border-radius: 5px; }
|
|
||||||
.success { background: #d4edda; color: #155724; }
|
|
||||||
.error { background: #f8d7da; color: #721c24; }
|
|
||||||
.info { background: #d1ecf1; color: #0c5460; }
|
|
||||||
button { padding: 10px 20px; margin: 5px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>🔧 Test Authentification DigitalOcean</h1>
|
|
||||||
<button onclick="testAuth()">Tester l'authentification</button>
|
|
||||||
<button onclick="testListFiles()">Lister les fichiers</button>
|
|
||||||
<div id="results"></div>
|
|
||||||
|
|
||||||
<script src="js/core/env-config.js"></script>
|
|
||||||
<script>
|
|
||||||
// Mock logSh si pas défini
|
|
||||||
if (typeof logSh === 'undefined') {
|
|
||||||
window.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addResult(message, type = 'info') {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = `result ${type}`;
|
|
||||||
div.innerHTML = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
||||||
document.getElementById('results').appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testAuth() {
|
|
||||||
addResult('🚀 Test d\'authentification avec la nouvelle clé...', 'info');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Test avec un fichier qui devrait exister
|
|
||||||
const testUrl = 'https://autocollant.fra1.digitaloceanspaces.com/Class_generator/ContentMe/greetings-basic.json';
|
|
||||||
|
|
||||||
addResult(`URL testée: ${testUrl}`, 'info');
|
|
||||||
|
|
||||||
// Générer les headers d'auth
|
|
||||||
const authHeaders = await window.envConfig.getAuthHeaders('GET', testUrl);
|
|
||||||
addResult('Headers générés: ' + JSON.stringify(authHeaders, null, 2), 'info');
|
|
||||||
|
|
||||||
// Faire la requête
|
|
||||||
const response = await fetch(testUrl, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: authHeaders
|
|
||||||
});
|
|
||||||
|
|
||||||
addResult(`Status: ${response.status} ${response.statusText}`, response.ok ? 'success' : 'error');
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const content = await response.text();
|
|
||||||
addResult(`✅ Contenu reçu (${content.length} caractères): ${content.substring(0, 200)}...`, 'success');
|
|
||||||
} else {
|
|
||||||
const errorText = await response.text();
|
|
||||||
addResult(`❌ Erreur: ${errorText}`, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
addResult(`❌ Erreur: ${error.message}`, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testListFiles() {
|
|
||||||
addResult('📂 Test de listage des fichiers...', 'info');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const listUrl = 'https://autocollant.fra1.digitaloceanspaces.com/Class_generator/ContentMe/';
|
|
||||||
|
|
||||||
const authHeaders = await window.envConfig.getAuthHeaders('GET', listUrl);
|
|
||||||
|
|
||||||
const response = await fetch(listUrl, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: authHeaders
|
|
||||||
});
|
|
||||||
|
|
||||||
addResult(`Status listage: ${response.status}`, response.ok ? 'success' : 'error');
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const content = await response.text();
|
|
||||||
addResult(`📋 Contenu du dossier: ${content.substring(0, 500)}...`, 'success');
|
|
||||||
} else {
|
|
||||||
addResult(`❌ Impossible de lister: ${response.statusText}`, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
addResult(`❌ Erreur listage: ${error.message}`, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test automatique au chargement
|
|
||||||
addResult('🔧 Page de test chargée. Clique sur les boutons pour tester.', 'info');
|
|
||||||
addResult(`Configuration: ${window.envConfig.getRemoteContentUrl()}`, 'info');
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
// === TEST AVEC DRAGON'S PEARL ===
|
|
||||||
|
|
||||||
// Simuler l'environnement browser
|
|
||||||
global.window = {};
|
|
||||||
global.document = {};
|
|
||||||
global.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
|
|
||||||
|
|
||||||
// Charger les modules
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// Charger le système de compatibilité
|
|
||||||
const compatibilityCode = fs.readFileSync(path.join(__dirname, 'js/core/content-game-compatibility.js'), 'utf8');
|
|
||||||
eval(compatibilityCode);
|
|
||||||
|
|
||||||
// Charger le contenu Dragon's Pearl
|
|
||||||
const dragonPearlCode = fs.readFileSync(path.join(__dirname, 'js/content/chinese-long-story.js'), 'utf8');
|
|
||||||
eval(dragonPearlCode);
|
|
||||||
|
|
||||||
console.log('🐉 Test avec Dragon\'s Pearl\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const checker = new window.ContentGameCompatibility();
|
|
||||||
const dragonPearlContent = window.ContentModules.ChineseLongStory;
|
|
||||||
|
|
||||||
console.log('📦 Contenu Dragon\'s Pearl:');
|
|
||||||
console.log(` Nom: ${dragonPearlContent.name}`);
|
|
||||||
console.log(` Description: ${dragonPearlContent.description}`);
|
|
||||||
console.log(` Difficulté: ${dragonPearlContent.difficulty}`);
|
|
||||||
console.log(` Vocabulaire: ${Object.keys(dragonPearlContent.vocabulary || {}).length} mots`);
|
|
||||||
console.log(` Story: ${dragonPearlContent.story ? 'Oui' : 'Non'}`);
|
|
||||||
|
|
||||||
console.log('\n🎮 Tests de compatibilité:');
|
|
||||||
const games = ['whack-a-mole', 'memory-match', 'quiz-game', 'fill-the-blank', 'text-reader', 'adventure-reader'];
|
|
||||||
|
|
||||||
games.forEach(game => {
|
|
||||||
const result = checker.checkCompatibility(dragonPearlContent, game);
|
|
||||||
const status = result.compatible ? '✅' : '❌';
|
|
||||||
console.log(` ${status} ${game}: ${result.score}% - ${result.reason}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n💡 Suggestions pour jeux incompatibles:');
|
|
||||||
games.forEach(game => {
|
|
||||||
const result = checker.checkCompatibility(dragonPearlContent, game);
|
|
||||||
if (!result.compatible) {
|
|
||||||
const suggestions = checker.getImprovementSuggestions(dragonPearlContent, game);
|
|
||||||
if (suggestions.length > 0) {
|
|
||||||
console.log(` 📝 ${game}:`);
|
|
||||||
suggestions.forEach(suggestion => {
|
|
||||||
console.log(` 💡 ${suggestion}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Erreur:', error.message);
|
|
||||||
console.error(error.stack);
|
|
||||||
}
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
// === TEST EXTRACTION DIRECT ===
|
|
||||||
|
|
||||||
// Script pour tester l'extraction des phrases de Dragon's Pearl
|
|
||||||
// À copier-coller dans la console
|
|
||||||
|
|
||||||
function testExtractionDirect() {
|
|
||||||
console.log('🔬 Test extraction direct Dragon\\'s Pearl');
|
|
||||||
|
|
||||||
// 1. Récupérer le module
|
|
||||||
const dragonModule = window.ContentModules?.ChineseLongStory;
|
|
||||||
console.log('\\n📦 Module Dragon\\'s Pearl:', !!dragonModule);
|
|
||||||
|
|
||||||
if (!dragonModule) {
|
|
||||||
console.error('❌ Module non trouvé !');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Tester la structure
|
|
||||||
console.log('\\n🔍 Structure du module:');
|
|
||||||
console.log(' story:', !!dragonModule.story);
|
|
||||||
console.log(' story.chapters:', !!dragonModule.story?.chapters);
|
|
||||||
console.log(' Nombre de chapitres:', dragonModule.story?.chapters?.length || 0);
|
|
||||||
|
|
||||||
if (dragonModule.story?.chapters?.[0]) {
|
|
||||||
const firstChapter = dragonModule.story.chapters[0];
|
|
||||||
console.log('\\n📖 Premier chapitre:');
|
|
||||||
console.log(' Titre:', firstChapter.title);
|
|
||||||
console.log(' Sentences:', !!firstChapter.sentences);
|
|
||||||
console.log(' Nombre phrases:', firstChapter.sentences?.length || 0);
|
|
||||||
|
|
||||||
if (firstChapter.sentences?.[0]) {
|
|
||||||
const firstSentence = firstChapter.sentences[0];
|
|
||||||
console.log('\\n💬 Première phrase:');
|
|
||||||
console.log(' original:', firstSentence.original);
|
|
||||||
console.log(' translation:', firstSentence.translation);
|
|
||||||
console.log(' id:', firstSentence.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Tester l'extraction manuellement
|
|
||||||
console.log('\\n🧪 Test extraction manuelle:');
|
|
||||||
|
|
||||||
const sentences = [];
|
|
||||||
|
|
||||||
if (dragonModule.story && dragonModule.story.chapters && Array.isArray(dragonModule.story.chapters)) {
|
|
||||||
dragonModule.story.chapters.forEach(chapter => {
|
|
||||||
console.log(` Traitement chapitre: ${chapter.title}`);
|
|
||||||
|
|
||||||
if (chapter.sentences && Array.isArray(chapter.sentences)) {
|
|
||||||
console.log(` ${chapter.sentences.length} phrases dans ce chapitre`);
|
|
||||||
|
|
||||||
chapter.sentences.forEach(sentence => {
|
|
||||||
if (sentence.original && sentence.translation) {
|
|
||||||
sentences.push({
|
|
||||||
original_language: sentence.original,
|
|
||||||
user_language: sentence.translation,
|
|
||||||
pronunciation: sentence.pronunciation || '',
|
|
||||||
chapter: chapter.title || '',
|
|
||||||
id: sentence.id || sentences.length
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.warn(' ⚠️ Phrase ignorée:', sentence);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.warn(` ⚠️ Pas de sentences dans ${chapter.title}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error(' ❌ Structure story.chapters introuvable');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\\n📊 Résultat extraction:');
|
|
||||||
console.log(' Phrases extraites:', sentences.length);
|
|
||||||
|
|
||||||
if (sentences.length > 0) {
|
|
||||||
console.log(' Première phrase extraite:', sentences[0]);
|
|
||||||
console.log(' Dernière phrase extraite:', sentences[sentences.length - 1]);
|
|
||||||
} else {
|
|
||||||
console.error(' ❌ Aucune phrase extraite !');
|
|
||||||
}
|
|
||||||
|
|
||||||
return sentences;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-exécution
|
|
||||||
const results = testExtractionDirect();
|
|
||||||
@ -1,333 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Test Final - Système de Compatibilité</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f5f7fa;
|
|
||||||
}
|
|
||||||
.test-section {
|
|
||||||
background: white;
|
|
||||||
padding: 25px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.test-result {
|
|
||||||
padding: 12px;
|
|
||||||
margin: 8px 0;
|
|
||||||
border-radius: 6px;
|
|
||||||
border-left: 4px solid #ddd;
|
|
||||||
}
|
|
||||||
.success {
|
|
||||||
background: #d4edda;
|
|
||||||
border-left-color: #28a745;
|
|
||||||
color: #155724;
|
|
||||||
}
|
|
||||||
.warning {
|
|
||||||
background: #fff3cd;
|
|
||||||
border-left-color: #ffc107;
|
|
||||||
color: #856404;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
background: #f8d7da;
|
|
||||||
border-left-color: #dc3545;
|
|
||||||
color: #721c24;
|
|
||||||
}
|
|
||||||
.compatible {
|
|
||||||
background: #e8f5e8;
|
|
||||||
border-left-color: #28a745;
|
|
||||||
}
|
|
||||||
.incompatible {
|
|
||||||
background: #fff8e1;
|
|
||||||
border-left-color: #ff9800;
|
|
||||||
}
|
|
||||||
.score {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
margin: 8px 4px;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
.log {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
.stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
.stat-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.stat-number {
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #667eea;
|
|
||||||
}
|
|
||||||
.content-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
.compatibility-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>🎯 Test Final - Système de Compatibilité Content-Game</h1>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>📊 Vue d'ensemble</h2>
|
|
||||||
<div class="stats" id="stats">
|
|
||||||
<!-- Stats will be populated here -->
|
|
||||||
</div>
|
|
||||||
<button onclick="runFullTest()">🚀 Lancer Test Complet</button>
|
|
||||||
<button onclick="clearLog()">🗑️ Effacer Log</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>📦 Contenus Détectés</h2>
|
|
||||||
<div id="content-list"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>🎮 Matrice de Compatibilité</h2>
|
|
||||||
<div id="compatibility-matrix"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>📋 Log d'Exécution</h2>
|
|
||||||
<div id="log" class="log"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Scripts nécessaires -->
|
|
||||||
<script src="js/core/utils.js"></script>
|
|
||||||
<script src="js/core/content-scanner.js"></script>
|
|
||||||
<script src="js/core/content-game-compatibility.js"></script>
|
|
||||||
<script src="js/content/test-minimal.js"></script>
|
|
||||||
<script src="js/content/test-rich.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let contentScanner;
|
|
||||||
let compatibilityChecker;
|
|
||||||
let allContent = [];
|
|
||||||
|
|
||||||
// Logger
|
|
||||||
function logSh(message, level = 'INFO') {
|
|
||||||
const logDiv = document.getElementById('log');
|
|
||||||
const timestamp = new Date().toLocaleTimeString();
|
|
||||||
logDiv.textContent += `[${timestamp}] ${level}: ${message}\n`;
|
|
||||||
logDiv.scrollTop = logDiv.scrollHeight;
|
|
||||||
console.log(`[${level}] ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLog() {
|
|
||||||
document.getElementById('log').textContent = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utils nécessaires
|
|
||||||
window.Utils = {
|
|
||||||
storage: {
|
|
||||||
get: (key, defaultValue) => {
|
|
||||||
try {
|
|
||||||
const value = localStorage.getItem(key);
|
|
||||||
return value ? JSON.parse(value) : defaultValue;
|
|
||||||
} catch {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set: (key, value) => {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Cannot save to localStorage:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialisation
|
|
||||||
async function init() {
|
|
||||||
logSh('🚀 Initialisation du test de compatibilité');
|
|
||||||
|
|
||||||
try {
|
|
||||||
contentScanner = new ContentScanner();
|
|
||||||
compatibilityChecker = new ContentGameCompatibility();
|
|
||||||
logSh('✅ Modules initialisés');
|
|
||||||
|
|
||||||
await loadContent();
|
|
||||||
updateStats();
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logSh(`❌ Erreur d'initialisation: ${error.message}`, 'ERROR');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadContent() {
|
|
||||||
logSh('📦 Chargement du contenu...');
|
|
||||||
const results = await contentScanner.scanAllContent();
|
|
||||||
allContent = results.found;
|
|
||||||
logSh(`✅ ${allContent.length} modules de contenu chargés`);
|
|
||||||
|
|
||||||
displayContentList();
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayContentList() {
|
|
||||||
const contentList = document.getElementById('content-list');
|
|
||||||
contentList.innerHTML = '';
|
|
||||||
|
|
||||||
allContent.forEach(content => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'content-card';
|
|
||||||
div.innerHTML = `
|
|
||||||
<h4>${content.name}</h4>
|
|
||||||
<p>${content.description}</p>
|
|
||||||
<p><strong>Stats:</strong>
|
|
||||||
${content.stats?.vocabularyCount || 0} mots,
|
|
||||||
${content.stats?.sentenceCount || 0} phrases,
|
|
||||||
${content.stats?.dialogueCount || 0} dialogues
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
contentList.appendChild(div);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStats() {
|
|
||||||
const statsDiv = document.getElementById('stats');
|
|
||||||
const games = ['whack-a-mole', 'memory-match', 'quiz-game', 'fill-the-blank', 'text-reader', 'adventure-reader'];
|
|
||||||
|
|
||||||
let totalCompatible = 0;
|
|
||||||
let totalTests = 0;
|
|
||||||
|
|
||||||
allContent.forEach(content => {
|
|
||||||
games.forEach(game => {
|
|
||||||
const compatibility = compatibilityChecker.checkCompatibility(content, game);
|
|
||||||
if (compatibility.compatible) totalCompatible++;
|
|
||||||
totalTests++;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
statsDiv.innerHTML = `
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${allContent.length}</div>
|
|
||||||
<div>Modules de Contenu</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${games.length}</div>
|
|
||||||
<div>Types de Jeux</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${totalTests}</div>
|
|
||||||
<div>Tests Effectués</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${Math.round(totalCompatible/totalTests*100)}%</div>
|
|
||||||
<div>Compatibilité Globale</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runFullTest() {
|
|
||||||
logSh('🧪 Lancement du test complet de compatibilité');
|
|
||||||
|
|
||||||
const games = ['whack-a-mole', 'memory-match', 'quiz-game', 'fill-the-blank', 'text-reader', 'adventure-reader'];
|
|
||||||
const matrixDiv = document.getElementById('compatibility-matrix');
|
|
||||||
|
|
||||||
let matrixHTML = `
|
|
||||||
<table style="width:100%; border-collapse: collapse;">
|
|
||||||
<thead>
|
|
||||||
<tr style="background: #f8f9fa;">
|
|
||||||
<th style="padding: 10px; border: 1px solid #ddd;">Contenu</th>
|
|
||||||
${games.map(game => `<th style="padding: 10px; border: 1px solid #ddd;">${game}</th>`).join('')}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
`;
|
|
||||||
|
|
||||||
allContent.forEach(content => {
|
|
||||||
matrixHTML += `<tr><td style="padding: 10px; border: 1px solid #ddd; font-weight: bold;">${content.name}</td>`;
|
|
||||||
|
|
||||||
games.forEach(game => {
|
|
||||||
const compatibility = compatibilityChecker.checkCompatibility(content, game);
|
|
||||||
const bgColor = compatibility.compatible ? '#e8f5e8' : '#fff8e1';
|
|
||||||
const textColor = compatibility.compatible ? '#2e7d32' : '#f57c00';
|
|
||||||
const icon = compatibility.compatible ? '✅' : '⚠️';
|
|
||||||
|
|
||||||
matrixHTML += `
|
|
||||||
<td style="padding: 10px; border: 1px solid #ddd; background: ${bgColor}; color: ${textColor}; text-align: center;">
|
|
||||||
${icon}<br>
|
|
||||||
<strong>${compatibility.score}%</strong><br>
|
|
||||||
<small>${compatibility.details?.recommendation || 'N/A'}</small>
|
|
||||||
</td>
|
|
||||||
`;
|
|
||||||
|
|
||||||
logSh(`🎯 ${content.name} → ${game}: ${compatibility.compatible ? 'COMPATIBLE' : 'INCOMPATIBLE'} (${compatibility.score}%)`);
|
|
||||||
});
|
|
||||||
|
|
||||||
matrixHTML += '</tr>';
|
|
||||||
});
|
|
||||||
|
|
||||||
matrixHTML += '</tbody></table>';
|
|
||||||
matrixDiv.innerHTML = matrixHTML;
|
|
||||||
|
|
||||||
// Test des suggestions d'amélioration
|
|
||||||
logSh('\n💡 Test des suggestions d\'amélioration:');
|
|
||||||
allContent.forEach(content => {
|
|
||||||
games.forEach(game => {
|
|
||||||
const compatibility = compatibilityChecker.checkCompatibility(content, game);
|
|
||||||
if (!compatibility.compatible) {
|
|
||||||
const suggestions = compatibilityChecker.getImprovementSuggestions(content, game);
|
|
||||||
if (suggestions.length > 0) {
|
|
||||||
logSh(` 📝 ${content.name} + ${game}:`);
|
|
||||||
suggestions.forEach(suggestion => {
|
|
||||||
logSh(` 💡 ${suggestion}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
updateStats();
|
|
||||||
logSh('🎉 Test complet terminé!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-init
|
|
||||||
window.addEventListener('load', init);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
# 🔄 TEST DU FLUX BOUTON BACK
|
|
||||||
|
|
||||||
## ✅ Ancienne Interface Supprimée
|
|
||||||
|
|
||||||
La fonction `showGamesPageFallback()` a été **complètement supprimée** de `navigation.js`.
|
|
||||||
|
|
||||||
## 🎯 Nouveau Flux Unique
|
|
||||||
|
|
||||||
### 1. Depuis Niveau → Jeux ✅
|
|
||||||
```
|
|
||||||
Levels → Clic Dragon's Pearl → showGamesPage(content) → Interface avec compatibilité
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Depuis Bouton Back ✅
|
|
||||||
```
|
|
||||||
Jeux → Bouton Back → goBack() → Récupère content depuis URL → showGamesPage(content) → Interface avec compatibilité
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Si Pas de Content (Fallback) ✅
|
|
||||||
```
|
|
||||||
Jeux → Bouton Back → goBack() → Pas de content → Retour aux Levels
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Modifications Apportées
|
|
||||||
|
|
||||||
### 1. Supprimé `showGamesPageFallback()`
|
|
||||||
- ❌ **SUPPRIMÉ** : L'ancienne interface sans compatibilité
|
|
||||||
- ✅ **GARDÉ** : `showGamesPage()` + `renderGamesGrid()` avec compatibilité
|
|
||||||
|
|
||||||
### 2. Modifié `navigateTo()` case 'games'
|
|
||||||
```javascript
|
|
||||||
case 'games':
|
|
||||||
if (!content) {
|
|
||||||
// Retour aux levels si pas de content
|
|
||||||
this.showLevelsPage();
|
|
||||||
} else {
|
|
||||||
// Interface avec compatibilité
|
|
||||||
this.showGamesPage(content)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Modifié `goBack()` pour games
|
|
||||||
```javascript
|
|
||||||
} else if (previousPage === 'games') {
|
|
||||||
const urlContent = params.content;
|
|
||||||
if (urlContent) {
|
|
||||||
this.navigateTo('games', null, urlContent); // ✅ Avec content
|
|
||||||
} else {
|
|
||||||
this.navigateTo('levels'); // ✅ Fallback vers levels
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Résultat
|
|
||||||
|
|
||||||
Maintenant il n'y a plus qu'**UNE SEULE interface** pour choisir les jeux :
|
|
||||||
- ✅ **Toujours** avec analyse de compatibilité
|
|
||||||
- ✅ **Toujours** avec badges et sections
|
|
||||||
- ✅ **Toujours** avec le scan dynamique
|
|
||||||
|
|
||||||
**Peu importe** comment tu arrives sur la page des jeux (depuis niveau ou bouton back), c'est **toujours la même interface** avec compatibilité ! 🚀
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Test Logs Interface Direct</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: monospace; background: #1e1e1e; color: white; padding: 20px; }
|
|
||||||
.log { margin: 2px 0; padding: 4px; border-left: 3px solid #007bff; }
|
|
||||||
.ERROR { border-color: #dc3545; }
|
|
||||||
.WARN { border-color: #ffc107; }
|
|
||||||
.DEBUG { border-color: #6c757d; }
|
|
||||||
.INFO { border-color: #17a2b8; }
|
|
||||||
#status {
|
|
||||||
padding: 10px;
|
|
||||||
background: #333;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Test Direct Interface de Logs</h2>
|
|
||||||
<div id="status">En attente...</div>
|
|
||||||
<div id="logs"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const statusDiv = document.getElementById('status');
|
|
||||||
const logsDiv = document.getElementById('logs');
|
|
||||||
let messageCount = 0;
|
|
||||||
|
|
||||||
console.log('🔌 Tentative connexion WebSocket ws://localhost:8082');
|
|
||||||
const ws = new WebSocket('ws://localhost:8082');
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
console.log('✅ WebSocket connecté !');
|
|
||||||
statusDiv.innerHTML = '<span style="color: green;">✅ WebSocket connecté - En attente des logs...</span>';
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
|
||||||
messageCount++;
|
|
||||||
console.log(`📨 Message #${messageCount} reçu:`, event.data);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const logData = JSON.parse(event.data);
|
|
||||||
const logDiv = document.createElement('div');
|
|
||||||
logDiv.className = `log ${logData.level}`;
|
|
||||||
|
|
||||||
const time = new Date(logData.timestamp).toLocaleTimeString();
|
|
||||||
logDiv.innerHTML = `<strong>[${time}] ${logData.level}:</strong> ${logData.message}`;
|
|
||||||
|
|
||||||
logsDiv.appendChild(logDiv);
|
|
||||||
|
|
||||||
// Auto-scroll
|
|
||||||
logDiv.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
||||||
|
|
||||||
// Update status
|
|
||||||
statusDiv.innerHTML = `<span style="color: green;">✅ Connecté - ${messageCount} messages reçus</span>`;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log('❌ Erreur parsing:', error);
|
|
||||||
const errorDiv = document.createElement('div');
|
|
||||||
errorDiv.className = 'log ERROR';
|
|
||||||
errorDiv.textContent = `Erreur parsing: ${event.data}`;
|
|
||||||
logsDiv.appendChild(errorDiv);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
console.log('❌ Connexion fermée');
|
|
||||||
statusDiv.innerHTML = `<span style="color: red;">❌ Déconnecté (${messageCount} messages reçus)</span>`;
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = (error) => {
|
|
||||||
console.log('❌ Erreur WebSocket:', error);
|
|
||||||
statusDiv.innerHTML = '<span style="color: red;">❌ Erreur de connexion</span>';
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
// === TEST NODE.JS DU SYSTÈME DE COMPATIBILITÉ ===
|
|
||||||
|
|
||||||
// Simuler l'environnement browser
|
|
||||||
global.window = {};
|
|
||||||
global.document = {};
|
|
||||||
global.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
|
|
||||||
|
|
||||||
// Charger les modules
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// Charger le système de compatibilité
|
|
||||||
const compatibilityCode = fs.readFileSync(path.join(__dirname, 'js/core/content-game-compatibility.js'), 'utf8');
|
|
||||||
eval(compatibilityCode);
|
|
||||||
|
|
||||||
// Créer les contenus de test
|
|
||||||
const testMinimalContent = {
|
|
||||||
id: "test-minimal",
|
|
||||||
name: "Test Minimal",
|
|
||||||
vocabulary: {
|
|
||||||
"hello": "bonjour",
|
|
||||||
"world": "monde"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const testRichContent = {
|
|
||||||
id: "test-rich",
|
|
||||||
name: "Test Riche",
|
|
||||||
vocabulary: {
|
|
||||||
"apple": {
|
|
||||||
translation: "pomme",
|
|
||||||
prononciation: "apple",
|
|
||||||
type: "noun"
|
|
||||||
},
|
|
||||||
"book": {
|
|
||||||
translation: "livre",
|
|
||||||
prononciation: "book",
|
|
||||||
type: "noun"
|
|
||||||
},
|
|
||||||
"car": {
|
|
||||||
translation: "voiture",
|
|
||||||
prononciation: "car",
|
|
||||||
type: "noun"
|
|
||||||
},
|
|
||||||
"dog": {
|
|
||||||
translation: "chien",
|
|
||||||
prononciation: "dog",
|
|
||||||
type: "noun"
|
|
||||||
},
|
|
||||||
"eat": {
|
|
||||||
translation: "manger",
|
|
||||||
prononciation: "eat",
|
|
||||||
type: "verb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sentences: [
|
|
||||||
{ english: "I have an apple", french: "J'ai une pomme" },
|
|
||||||
{ english: "The dog is good", french: "Le chien est bon" },
|
|
||||||
{ english: "I like books", french: "J'aime les livres" }
|
|
||||||
],
|
|
||||||
dialogues: [
|
|
||||||
{
|
|
||||||
title: "Test dialogue",
|
|
||||||
conversation: [
|
|
||||||
{ speaker: "A", english: "Hello", french: "Bonjour" },
|
|
||||||
{ speaker: "B", english: "Hi there", french: "Salut" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tester le système
|
|
||||||
console.log('🧪 Test du système de compatibilité Content-Game\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const checker = new window.ContentGameCompatibility();
|
|
||||||
console.log('✅ ContentGameCompatibility initialisé\n');
|
|
||||||
|
|
||||||
const games = ['whack-a-mole', 'memory-match', 'quiz-game', 'fill-the-blank', 'text-reader', 'adventure-reader'];
|
|
||||||
|
|
||||||
console.log('📦 Test avec contenu minimal (2 mots):');
|
|
||||||
games.forEach(game => {
|
|
||||||
const result = checker.checkCompatibility(testMinimalContent, game);
|
|
||||||
const status = result.compatible ? '✅' : '❌';
|
|
||||||
console.log(` ${status} ${game}: ${result.score}% - ${result.reason}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n📦 Test avec contenu riche:');
|
|
||||||
games.forEach(game => {
|
|
||||||
const result = checker.checkCompatibility(testRichContent, game);
|
|
||||||
const status = result.compatible ? '✅' : '❌';
|
|
||||||
console.log(` ${status} ${game}: ${result.score}% - ${result.reason}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n🧪 Test des suggestions d\'amélioration:');
|
|
||||||
const suggestions = checker.getImprovementSuggestions(testMinimalContent, 'whack-a-mole');
|
|
||||||
console.log(` Suggestions pour "whack-a-mole":`);
|
|
||||||
suggestions.forEach(suggestion => {
|
|
||||||
console.log(` 💡 ${suggestion}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n🎉 Tous les tests réussis!');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Erreur:', error.message);
|
|
||||||
console.error(error.stack);
|
|
||||||
}
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
// === TEST DU FLUX COMPLET ===
|
|
||||||
|
|
||||||
// Test qui simule exactement le flux réel de l'application
|
|
||||||
async function testRealFlow() {
|
|
||||||
console.log('🔄 Test du flux complet de l\'application\n');
|
|
||||||
|
|
||||||
// Simuler l'environnement browser
|
|
||||||
global.window = {};
|
|
||||||
global.document = {};
|
|
||||||
global.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
|
|
||||||
|
|
||||||
// Charger tous les modules
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// 1. Charger ContentScanner
|
|
||||||
const scannerCode = fs.readFileSync(path.join(__dirname, 'js/core/content-scanner.js'), 'utf8');
|
|
||||||
eval(scannerCode);
|
|
||||||
|
|
||||||
// 2. Charger ContentGameCompatibility
|
|
||||||
const compatibilityCode = fs.readFileSync(path.join(__dirname, 'js/core/content-game-compatibility.js'), 'utf8');
|
|
||||||
eval(compatibilityCode);
|
|
||||||
|
|
||||||
// 3. Charger le contenu Dragon's Pearl
|
|
||||||
const dragonPearlCode = fs.readFileSync(path.join(__dirname, 'js/content/chinese-long-story.js'), 'utf8');
|
|
||||||
eval(dragonPearlCode);
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('🚀 1. Initialisation du système...');
|
|
||||||
const scanner = new window.ContentScanner();
|
|
||||||
const checker = new window.ContentGameCompatibility();
|
|
||||||
|
|
||||||
console.log('📦 2. Scan du contenu...');
|
|
||||||
const results = await scanner.scanAllContent();
|
|
||||||
console.log(` Trouvé: ${results.found.length} modules`);
|
|
||||||
|
|
||||||
// 3. Rechercher Dragon's Pearl dans les résultats
|
|
||||||
console.log('🐉 3. Recherche Dragon\'s Pearl...');
|
|
||||||
const dragonPearl = results.found.find(content =>
|
|
||||||
content.name.includes('Dragon') || content.id.includes('chinese-long-story')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (dragonPearl) {
|
|
||||||
console.log(` ✅ Trouvé: ${dragonPearl.name} (ID: ${dragonPearl.id})`);
|
|
||||||
|
|
||||||
// 4. Test de compatibilité comme dans l'interface
|
|
||||||
console.log('🎮 4. Test de compatibilité des jeux...');
|
|
||||||
const games = ['whack-a-mole', 'memory-match', 'quiz-game', 'fill-the-blank', 'text-reader', 'adventure-reader'];
|
|
||||||
|
|
||||||
const compatibleGames = [];
|
|
||||||
const incompatibleGames = [];
|
|
||||||
|
|
||||||
games.forEach(game => {
|
|
||||||
const compatibility = checker.checkCompatibility(dragonPearl, game);
|
|
||||||
const gameData = { game, compatibility };
|
|
||||||
|
|
||||||
if (compatibility.compatible) {
|
|
||||||
compatibleGames.push(gameData);
|
|
||||||
} else {
|
|
||||||
incompatibleGames.push(gameData);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(` ${compatibility.compatible ? '✅' : '❌'} ${game}: ${compatibility.score}%`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`\n📊 Résultat final:`);
|
|
||||||
console.log(` 🎯 Jeux compatibles: ${compatibleGames.length}`);
|
|
||||||
console.log(` ⚠️ Jeux incompatibles: ${incompatibleGames.length}`);
|
|
||||||
|
|
||||||
if (compatibleGames.length > 0) {
|
|
||||||
console.log(`\n🎉 SUCCESS: Dragon's Pearl devrait maintenant afficher ${compatibleGames.length} jeux compatibles`);
|
|
||||||
|
|
||||||
// Simuler l'affichage comme dans l'interface
|
|
||||||
console.log('\n🎯 Jeux recommandés:');
|
|
||||||
compatibleGames
|
|
||||||
.sort((a, b) => b.compatibility.score - a.compatibility.score)
|
|
||||||
.forEach(({ game, compatibility }) => {
|
|
||||||
const badge = compatibility.score >= 80 ? '🎯 Excellent' :
|
|
||||||
compatibility.score >= 60 ? '✅ Recommandé' : '👍 Compatible';
|
|
||||||
console.log(` ${badge} ${game} (${compatibility.score}%)`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (incompatibleGames.length > 0) {
|
|
||||||
console.log('\n⚠️ Jeux avec limitations:');
|
|
||||||
incompatibleGames.forEach(({ game, compatibility }) => {
|
|
||||||
console.log(` ⚠️ Limité ${game} (${compatibility.score}%) - ${compatibility.reason}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('❌ PROBLÈME: Aucun jeu compatible détecté');
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log('❌ Dragon\'s Pearl non trouvé dans les résultats');
|
|
||||||
console.log(' Contenus trouvés:');
|
|
||||||
results.found.forEach(content => {
|
|
||||||
console.log(` - ${content.name} (ID: ${content.id})`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Erreur:', error.message);
|
|
||||||
console.error(error.stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utils pour le scanner
|
|
||||||
global.window = global.window || {};
|
|
||||||
global.window.Utils = {
|
|
||||||
storage: {
|
|
||||||
get: (key, defaultValue) => defaultValue,
|
|
||||||
set: (key, value) => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
testRealFlow();
|
|
||||||
@ -1,457 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>🔄 Test: JS → JSON Ultra-Modulaire</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
.test-section {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
border-left: 4px solid #3B82F6;
|
|
||||||
}
|
|
||||||
.success { border-left-color: #10B981; }
|
|
||||||
.error { border-left-color: #EF4444; }
|
|
||||||
pre {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow-x: auto;
|
|
||||||
max-height: 500px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
margin: 15px 0;
|
|
||||||
}
|
|
||||||
.stat-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.stat-number {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #3B82F6;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
background: #3B82F6;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 10px 5px;
|
|
||||||
}
|
|
||||||
.btn:hover {
|
|
||||||
background: #2563eb;
|
|
||||||
}
|
|
||||||
.capabilities {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
margin: 15px 0;
|
|
||||||
}
|
|
||||||
.capability {
|
|
||||||
background: #f0fdf4;
|
|
||||||
border: 1px solid #16a34a;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>🔄 Test: Conversion JS → JSON Ultra-Modulaire</h1>
|
|
||||||
<p>Démonstration complète du système : partir d'un module JS existant et générer un JSON ultra-modulaire</p>
|
|
||||||
|
|
||||||
<div id="results"></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/json-content-loader.js"></script>
|
|
||||||
<script src="js/core/content-scanner.js"></script>
|
|
||||||
<script src="js/tools/ultra-modular-validator.js"></script>
|
|
||||||
<script src="js/content/sbs-level-7-8-new.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
class ReverseConverter {
|
|
||||||
constructor() {
|
|
||||||
this.results = document.getElementById('results');
|
|
||||||
this.contentScanner = new ContentScanner();
|
|
||||||
this.validator = new UltraModularValidator();
|
|
||||||
}
|
|
||||||
|
|
||||||
addResult(title, content, type = 'info') {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = `test-section ${type}`;
|
|
||||||
div.innerHTML = `<h3>${title}</h3>${content}`;
|
|
||||||
this.results.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
async runConversion() {
|
|
||||||
this.addResult('🚀 Démarrage de la conversion inverse', 'Conversion JS → JSON Ultra-Modulaire...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Récupérer le module JS chargé
|
|
||||||
const jsModule = window.ContentModules?.SBSLevel78New;
|
|
||||||
if (!jsModule) {
|
|
||||||
throw new Error('Module SBSLevel78New non trouvé - vérifiez le chargement du script');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addResult('✅ Module JS Chargé',
|
|
||||||
`<p>Module trouvé: <strong>SBSLevel78New</strong></p>
|
|
||||||
<p>Contient: ${Object.keys(jsModule).join(', ')}</p>`,
|
|
||||||
'success');
|
|
||||||
|
|
||||||
// 2. Analyser les capacités du module existant
|
|
||||||
const capabilities = this.contentScanner.analyzeContentCapabilities(jsModule);
|
|
||||||
const compatibility = this.contentScanner.calculateGameCompatibility(capabilities);
|
|
||||||
|
|
||||||
this.displayCapabilities(capabilities, compatibility);
|
|
||||||
|
|
||||||
// 3. Créer la spécification JSON ultra-modulaire
|
|
||||||
const ultraModularSpec = this.convertToUltraModular(jsModule, capabilities, compatibility);
|
|
||||||
|
|
||||||
this.addResult('🛠️ Spécification Ultra-Modulaire Générée',
|
|
||||||
`<p>Conversion réussie vers le format ultra-modulaire complet!</p>
|
|
||||||
<pre>${JSON.stringify(ultraModularSpec, null, 2)}</pre>`,
|
|
||||||
'success');
|
|
||||||
|
|
||||||
// 4. Valider la spécification générée
|
|
||||||
const validation = await this.validator.validateSpecification(ultraModularSpec);
|
|
||||||
|
|
||||||
this.addResult('🔍 Validation de la Spécification Générée',
|
|
||||||
`<p><strong>Score de Qualité:</strong> ${validation.score}/100</p>
|
|
||||||
<p><strong>Valide:</strong> ${validation.valid ? '✅ Oui' : '❌ Non'}</p>
|
|
||||||
<p><strong>Erreurs:</strong> ${validation.errors.length}</p>
|
|
||||||
<p><strong>Avertissements:</strong> ${validation.warnings.length}</p>
|
|
||||||
<p><strong>Suggestions:</strong> ${validation.suggestions.length}</p>`,
|
|
||||||
validation.valid ? 'success' : 'error');
|
|
||||||
|
|
||||||
// 5. Créer le fichier JSON
|
|
||||||
this.generateJSONFile(ultraModularSpec);
|
|
||||||
|
|
||||||
this.addResult('✅ Test Complet Réussi',
|
|
||||||
'<p>La conversion JS → JSON Ultra-Modulaire fonctionne parfaitement!</p>' +
|
|
||||||
'<button class="btn" onclick="downloadJSON()">📥 Télécharger le JSON</button>',
|
|
||||||
'success');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.addResult('❌ Erreur de Conversion', `Erreur: ${error.message}`, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
displayCapabilities(capabilities, compatibility) {
|
|
||||||
const activeCapabilities = Object.entries(capabilities)
|
|
||||||
.filter(([key, value]) => value === true || (typeof value === 'number' && value > 0))
|
|
||||||
.map(([key, value]) => typeof value === 'number' ? `${key}: ${value}` : key);
|
|
||||||
|
|
||||||
const compatibleGames = Object.entries(compatibility)
|
|
||||||
.filter(([game, compat]) => compat.compatible)
|
|
||||||
.map(([game, compat]) => `${game} (${compat.score}%)`);
|
|
||||||
|
|
||||||
this.addResult('📊 Analyse du Module JS',
|
|
||||||
`<div class="stats">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${Object.keys(window.ContentModules.SBSLevel78New.vocabulary || {}).length}</div>
|
|
||||||
<div>Mots de vocabulaire</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${capabilities.vocabularyDepth}</div>
|
|
||||||
<div>Profondeur vocab (1-6)</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${compatibleGames.length}</div>
|
|
||||||
<div>Jeux compatibles</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${capabilities.contentRichness.toFixed(1)}</div>
|
|
||||||
<div>Richesse contenu</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h4>🎯 Capacités Détectées:</h4>
|
|
||||||
<div class="capabilities">
|
|
||||||
${activeCapabilities.map(cap => `<div class="capability">${cap}</div>`).join('')}
|
|
||||||
</div>
|
|
||||||
<h4>🎮 Jeux Compatibles:</h4>
|
|
||||||
<div class="capabilities">
|
|
||||||
${compatibleGames.map(game => `<div class="capability">${game}</div>`).join('')}
|
|
||||||
</div>`,
|
|
||||||
'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
convertToUltraModular(jsModule, capabilities, compatibility) {
|
|
||||||
// Générer l'ID basé sur le nom du module
|
|
||||||
const moduleId = 'sbs_level_7_8_converted_from_js';
|
|
||||||
|
|
||||||
// Analyser le vocabulaire pour détecter la complexité
|
|
||||||
const vocabularyAnalysis = this.analyzeVocabularyComplexity(jsModule.vocabulary || {});
|
|
||||||
|
|
||||||
// Créer la spécification ultra-modulaire complète
|
|
||||||
const ultraModularSpec = {
|
|
||||||
// ========================================================================================================
|
|
||||||
// CORE METADATA SECTION
|
|
||||||
// ========================================================================================================
|
|
||||||
id: moduleId,
|
|
||||||
name: "SBS Level 7-8 (Converted from JS)",
|
|
||||||
description: "Comprehensive English learning content covering housing, clothing, and cultural topics - converted from legacy JavaScript format to ultra-modular specification",
|
|
||||||
|
|
||||||
// Difficulty system (1-10 scale)
|
|
||||||
difficulty_level: 6, // Intermediate level based on content analysis
|
|
||||||
|
|
||||||
// Language configuration (original_lang/user_lang pattern)
|
|
||||||
original_lang: "english",
|
|
||||||
user_lang: "chinese",
|
|
||||||
|
|
||||||
// Icon system with fallback
|
|
||||||
icon: {
|
|
||||||
primary: "🏠",
|
|
||||||
fallback: "📚",
|
|
||||||
emoji: "🏠"
|
|
||||||
},
|
|
||||||
|
|
||||||
// Enhanced metadata
|
|
||||||
tags: ["housing", "clothing", "daily_life", "vocabulary", "sbs_textbook", "intermediate"],
|
|
||||||
skills_covered: ["vocabulary_acquisition", "reading_comprehension", "cultural_awareness", "daily_communication"],
|
|
||||||
target_audience: {
|
|
||||||
age_range: [12, 16],
|
|
||||||
proficiency_level: "intermediate",
|
|
||||||
learning_goals: ["daily_vocabulary", "cultural_understanding"]
|
|
||||||
},
|
|
||||||
estimated_duration: 45,
|
|
||||||
pedagogical_approach: "communicative_language_teaching",
|
|
||||||
|
|
||||||
// ========================================================================================================
|
|
||||||
// VOCABULARY SECTION (Progressive Enhancement)
|
|
||||||
// ========================================================================================================
|
|
||||||
vocabulary: this.enhanceVocabulary(jsModule.vocabulary || {}, vocabularyAnalysis),
|
|
||||||
|
|
||||||
// ========================================================================================================
|
|
||||||
// SENTENCES SECTION
|
|
||||||
// ========================================================================================================
|
|
||||||
sentences: this.enhanceSentences(jsModule.sentences || []),
|
|
||||||
|
|
||||||
// ========================================================================================================
|
|
||||||
// METADATA AND ANALYTICS
|
|
||||||
// ========================================================================================================
|
|
||||||
content_analytics: {
|
|
||||||
vocabulary_count: Object.keys(jsModule.vocabulary || {}).length,
|
|
||||||
sentence_count: (jsModule.sentences || []).length,
|
|
||||||
difficulty_distribution: vocabularyAnalysis.difficultyDistribution,
|
|
||||||
topic_coverage: this.extractTopics(jsModule.vocabulary || {}),
|
|
||||||
converted_from: "legacy_javascript",
|
|
||||||
conversion_timestamp: new Date().toISOString(),
|
|
||||||
detected_capabilities: capabilities,
|
|
||||||
game_compatibility: compatibility
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================================================================
|
|
||||||
// SYSTEM METADATA
|
|
||||||
// ========================================================================================================
|
|
||||||
system_metadata: {
|
|
||||||
format_version: "2.0",
|
|
||||||
specification: "ultra_modular",
|
|
||||||
backwards_compatible: true,
|
|
||||||
generated_by: "reverse_converter",
|
|
||||||
validation_required: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Stocker globalement pour l'export
|
|
||||||
window.generatedUltraModularSpec = ultraModularSpec;
|
|
||||||
|
|
||||||
return ultraModularSpec;
|
|
||||||
}
|
|
||||||
|
|
||||||
analyzeVocabularyComplexity(vocabulary) {
|
|
||||||
const analysis = {
|
|
||||||
totalWords: Object.keys(vocabulary).length,
|
|
||||||
simpleTranslations: 0,
|
|
||||||
complexPhrases: 0,
|
|
||||||
categories: new Set(),
|
|
||||||
difficultyDistribution: { basic: 0, intermediate: 0, advanced: 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [word, translation] of Object.entries(vocabulary)) {
|
|
||||||
// Analyser la complexité
|
|
||||||
if (word.includes(' ') || word.includes('/')) {
|
|
||||||
analysis.complexPhrases++;
|
|
||||||
analysis.difficultyDistribution.intermediate++;
|
|
||||||
} else if (word.length > 10) {
|
|
||||||
analysis.difficultyDistribution.advanced++;
|
|
||||||
} else {
|
|
||||||
analysis.simpleTranslations++;
|
|
||||||
analysis.difficultyDistribution.basic++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extraire les catégories
|
|
||||||
if (word.includes('shirt') || word.includes('coat') || word.includes('dress')) {
|
|
||||||
analysis.categories.add('clothing');
|
|
||||||
} else if (word.includes('building') || word.includes('apartment')) {
|
|
||||||
analysis.categories.add('housing');
|
|
||||||
} else if (word.includes('street') || word.includes('town')) {
|
|
||||||
analysis.categories.add('places');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
analysis.categories = Array.from(analysis.categories);
|
|
||||||
return analysis;
|
|
||||||
}
|
|
||||||
|
|
||||||
enhanceVocabulary(originalVocab, analysis) {
|
|
||||||
const enhanced = {};
|
|
||||||
|
|
||||||
for (const [word, translation] of Object.entries(originalVocab)) {
|
|
||||||
// Créer des objets enrichis de niveau 2-3
|
|
||||||
enhanced[word] = {
|
|
||||||
user_language: translation,
|
|
||||||
original_language: word,
|
|
||||||
type: this.detectWordType(word),
|
|
||||||
|
|
||||||
// Ajouter des contextes spécifiques selon le type
|
|
||||||
usage_context: this.getUsageContext(word),
|
|
||||||
|
|
||||||
// Niveau de difficulté basé sur l'analyse
|
|
||||||
difficulty_level: this.getWordDifficulty(word),
|
|
||||||
|
|
||||||
// Catégorie sémantique
|
|
||||||
semantic_category: this.getSemanticCategory(word)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return enhanced;
|
|
||||||
}
|
|
||||||
|
|
||||||
enhanceSentences(originalSentences) {
|
|
||||||
return originalSentences.map((sentence, index) => ({
|
|
||||||
id: `sentence_${index + 1}`,
|
|
||||||
original_language: sentence.english,
|
|
||||||
user_language: sentence.chinese,
|
|
||||||
difficulty_level: sentence.english.split(' ').length > 8 ? 7 : 5,
|
|
||||||
grammatical_focus: this.detectGrammarFocus(sentence.english),
|
|
||||||
communicative_function: this.detectCommunicativeFunction(sentence.english),
|
|
||||||
cultural_context: this.detectCulturalContext(sentence.english)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper methods
|
|
||||||
detectWordType(word) {
|
|
||||||
if (word.endsWith('ing')) return 'verb_gerund';
|
|
||||||
if (word.endsWith('ed')) return 'verb_past';
|
|
||||||
if (word.includes(' ')) return 'phrase';
|
|
||||||
if (word.endsWith('s') && !word.endsWith('ss')) return 'noun_plural';
|
|
||||||
return 'noun';
|
|
||||||
}
|
|
||||||
|
|
||||||
getUsageContext(word) {
|
|
||||||
if (word.includes('apartment') || word.includes('building')) return 'housing_description';
|
|
||||||
if (word.includes('shirt') || word.includes('coat')) return 'clothing_description';
|
|
||||||
if (word.includes('street') || word.includes('town')) return 'location_description';
|
|
||||||
return 'general_communication';
|
|
||||||
}
|
|
||||||
|
|
||||||
getWordDifficulty(word) {
|
|
||||||
if (word.length <= 5) return 3;
|
|
||||||
if (word.length <= 8) return 5;
|
|
||||||
if (word.includes(' ') || word.includes('/')) return 6;
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSemanticCategory(word) {
|
|
||||||
if (word.match(/shirt|coat|dress|pants|jacket|shoes/)) return 'clothing';
|
|
||||||
if (word.match(/building|apartment|house|room|elevator/)) return 'housing';
|
|
||||||
if (word.match(/street|town|center|avenue|sidewalk/)) return 'locations';
|
|
||||||
if (word.match(/noise|convenient|upset|central/)) return 'descriptive';
|
|
||||||
return 'general';
|
|
||||||
}
|
|
||||||
|
|
||||||
detectGrammarFocus(sentence) {
|
|
||||||
if (sentence.includes('is in') || sentence.includes('are in')) return 'location_prepositions';
|
|
||||||
if (sentence.includes("'s")) return 'possessive_case';
|
|
||||||
if (sentence.includes('There\'s') || sentence.includes('There are')) return 'existential_there';
|
|
||||||
return 'declarative_statement';
|
|
||||||
}
|
|
||||||
|
|
||||||
detectCommunicativeFunction(sentence) {
|
|
||||||
if (sentence.includes('?')) return 'questioning';
|
|
||||||
if (sentence.includes('!')) return 'exclamation';
|
|
||||||
if (sentence.toLowerCase().includes('please')) return 'request';
|
|
||||||
return 'description';
|
|
||||||
}
|
|
||||||
|
|
||||||
detectCulturalContext(sentence) {
|
|
||||||
if (sentence.includes('apartment building')) return 'urban_living';
|
|
||||||
if (sentence.includes('center of town')) return 'city_geography';
|
|
||||||
if (sentence.includes('noise')) return 'urban_challenges';
|
|
||||||
return 'daily_life';
|
|
||||||
}
|
|
||||||
|
|
||||||
extractTopics(vocabulary) {
|
|
||||||
const topics = new Set();
|
|
||||||
for (const word of Object.keys(vocabulary)) {
|
|
||||||
if (word.match(/shirt|coat|dress|pants|jacket|shoes|clothing/)) topics.add('clothing');
|
|
||||||
if (word.match(/building|apartment|house|room|elevator|housing/)) topics.add('housing');
|
|
||||||
if (word.match(/street|town|center|avenue|places/)) topics.add('places');
|
|
||||||
}
|
|
||||||
return Array.from(topics);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateJSONFile(spec) {
|
|
||||||
// Créer le contenu JSON avec beautification
|
|
||||||
const jsonContent = JSON.stringify(spec, null, 2);
|
|
||||||
|
|
||||||
// Stocker pour le téléchargement
|
|
||||||
window.downloadableJSON = jsonContent;
|
|
||||||
|
|
||||||
this.addResult('📁 Fichier JSON Généré',
|
|
||||||
`<p>Fichier JSON ultra-modulaire créé avec succès!</p>
|
|
||||||
<p><strong>Taille:</strong> ${(jsonContent.length / 1024).toFixed(1)} KB</p>
|
|
||||||
<p><strong>Lignes:</strong> ${jsonContent.split('\\n').length}</p>
|
|
||||||
<button class="btn" onclick="downloadJSON()">📥 Télécharger JSON</button>
|
|
||||||
<button class="btn" onclick="copyToClipboard()">📋 Copier JSON</button>`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonctions globales
|
|
||||||
function downloadJSON() {
|
|
||||||
const content = window.downloadableJSON;
|
|
||||||
const blob = new Blob([content], { type: 'application/json' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = 'sbs-level-7-8-ultra-modular.json';
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyToClipboard() {
|
|
||||||
navigator.clipboard.writeText(window.downloadableJSON).then(() => {
|
|
||||||
alert('JSON copié dans le presse-papier !');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Démarrer le test
|
|
||||||
document.addEventListener('DOMContentLoaded', async function() {
|
|
||||||
const converter = new ReverseConverter();
|
|
||||||
await converter.runConversion();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,332 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Test Ultra-Modular JSON System</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
.test-section {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
border-left: 4px solid #3B82F6;
|
|
||||||
}
|
|
||||||
.success { border-left-color: #10B981; }
|
|
||||||
.error { border-left-color: #EF4444; }
|
|
||||||
.warning { border-left-color: #F59E0B; }
|
|
||||||
pre {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow-x: auto;
|
|
||||||
max-height: 300px;
|
|
||||||
}
|
|
||||||
.capabilities {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
.capability {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-left: 3px solid #10B981;
|
|
||||||
}
|
|
||||||
.compatibility {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.game-compat {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.compatible { border-left: 3px solid #10B981; }
|
|
||||||
.incompatible { border-left: 3px solid #EF4444; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>🧪 Test du Système Ultra-Modulaire</h1>
|
|
||||||
<p>Test de compatibilité avec les nouvelles spécifications JSON</p>
|
|
||||||
|
|
||||||
<div id="results"></div>
|
|
||||||
|
|
||||||
<!-- Scripts Core -->
|
|
||||||
<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/json-content-loader.js"></script>
|
|
||||||
<script src="js/core/content-scanner.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
class UltraModularTester {
|
|
||||||
constructor() {
|
|
||||||
this.results = document.getElementById('results');
|
|
||||||
this.jsonLoader = new JSONContentLoader();
|
|
||||||
this.contentScanner = new ContentScanner();
|
|
||||||
this.tests = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
addResult(title, content, type = 'info') {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = `test-section ${type}`;
|
|
||||||
div.innerHTML = `<h3>${title}</h3>${content}`;
|
|
||||||
this.results.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
async runTests() {
|
|
||||||
this.addResult('🚀 Démarrage des tests', 'Test du système de données ultra-modulaire...');
|
|
||||||
|
|
||||||
await this.testJSONLoader();
|
|
||||||
await this.testContentScanner();
|
|
||||||
await this.testCapabilityAnalysis();
|
|
||||||
await this.testGameCompatibility();
|
|
||||||
|
|
||||||
this.addResult('✅ Tests terminés', `${this.tests.length} tests exécutés avec succès`, 'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
async testJSONLoader() {
|
|
||||||
try {
|
|
||||||
// Test de chargement du fichier ultra-commenté
|
|
||||||
const response = await fetch('english_exemple_ultra_commented.json');
|
|
||||||
const jsonContent = await response.json();
|
|
||||||
|
|
||||||
this.addResult('📋 Chargement JSON', 'Fichier ultra-commenté chargé avec succès', 'success');
|
|
||||||
|
|
||||||
// Test de l'adaptation
|
|
||||||
const adaptedContent = this.jsonLoader.adapt(jsonContent);
|
|
||||||
|
|
||||||
this.addResult('🔄 Adaptation JSON → Legacy',
|
|
||||||
`<p>Contenu adapté avec succès!</p>
|
|
||||||
<p><strong>ID:</strong> ${adaptedContent.id}</p>
|
|
||||||
<p><strong>Nom:</strong> ${adaptedContent.name}</p>
|
|
||||||
<p><strong>Difficulté:</strong> ${adaptedContent.difficulty} (niveau ${adaptedContent.difficulty_level})</p>
|
|
||||||
<p><strong>Langues:</strong> ${adaptedContent.original_lang} → ${adaptedContent.user_lang}</p>
|
|
||||||
<p><strong>Vocabulaire:</strong> ${Object.keys(adaptedContent.vocabulary || {}).length} mots</p>
|
|
||||||
<p><strong>Tags:</strong> ${(adaptedContent.tags || []).join(', ')}</p>`,
|
|
||||||
'success');
|
|
||||||
|
|
||||||
this.tests.push({ name: 'JSON Loader', status: 'success' });
|
|
||||||
return adaptedContent;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.addResult('❌ Erreur JSON Loader', `Erreur: ${error.message}`, 'error');
|
|
||||||
this.tests.push({ name: 'JSON Loader', status: 'error', error: error.message });
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async testContentScanner() {
|
|
||||||
try {
|
|
||||||
// Simuler un module de contenu ultra-modulaire
|
|
||||||
const mockModule = {
|
|
||||||
id: 'test_ultra_module',
|
|
||||||
name: 'Test Ultra Module',
|
|
||||||
difficulty_level: 7,
|
|
||||||
original_lang: 'english',
|
|
||||||
user_lang: 'french',
|
|
||||||
tags: ['advanced', 'grammar', 'culture'],
|
|
||||||
skills_covered: ['listening', 'speaking', 'cultural_awareness'],
|
|
||||||
vocabulary: {
|
|
||||||
'sophisticated': {
|
|
||||||
user_language: 'sophistiqué',
|
|
||||||
type: 'adjective',
|
|
||||||
ipa: '/səˈfɪstɪkeɪtɪd/',
|
|
||||||
audio_file: 'audio/sophisticated.mp3',
|
|
||||||
examples: ['She has sophisticated taste in art'],
|
|
||||||
etymology: 'From Latin sophisticatus',
|
|
||||||
cultural_significance: 'Often used in academic contexts',
|
|
||||||
memory_techniques: ['Visualize a person in elegant clothing']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grammar: {
|
|
||||||
subjunctive: {
|
|
||||||
title: 'Subjunctive Mood',
|
|
||||||
explanation: 'Used for hypothetical situations',
|
|
||||||
examples: [
|
|
||||||
{ english: 'If I were rich...', french: 'Si j\'étais riche...' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
culture: {
|
|
||||||
traditions: {
|
|
||||||
afternoon_tea: {
|
|
||||||
description: 'British afternoon tea tradition',
|
|
||||||
cultural_importance: 'High',
|
|
||||||
modern_relevance: 'Still practiced in formal settings'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
matching: [
|
|
||||||
{
|
|
||||||
title: 'Multi-column Grammar Matching',
|
|
||||||
type: 'multi_column',
|
|
||||||
columns: [
|
|
||||||
{ id: 1, name: 'Subject', items: ['I', 'You', 'He'] },
|
|
||||||
{ id: 2, name: 'Verb', items: ['am', 'are', 'is'] },
|
|
||||||
{ id: 3, name: 'Complement', items: ['happy', 'tired', 'busy'] }
|
|
||||||
],
|
|
||||||
correct_matches: [
|
|
||||||
{ matches: [{ column: 1, item: 'I' }, { column: 2, item: 'am' }, { column: 3, item: 'happy' }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Analyser les capacités
|
|
||||||
const capabilities = this.contentScanner.analyzeContentCapabilities(mockModule);
|
|
||||||
const compatibility = this.contentScanner.calculateGameCompatibility(capabilities);
|
|
||||||
|
|
||||||
this.addResult('🔍 Analyse des Capacités',
|
|
||||||
`<div class="capabilities">
|
|
||||||
${Object.entries(capabilities).map(([key, value]) => {
|
|
||||||
if (typeof value === 'boolean') {
|
|
||||||
return value ? `<div class="capability">✅ ${key}</div>` : '';
|
|
||||||
} else if (typeof value === 'number') {
|
|
||||||
return `<div class="capability">📊 ${key}: ${value}</div>`;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}).join('')}
|
|
||||||
</div>`,
|
|
||||||
'success');
|
|
||||||
|
|
||||||
this.addResult('🎮 Compatibilité des Jeux',
|
|
||||||
`<div class="compatibility">
|
|
||||||
${Object.entries(compatibility).map(([game, compat]) =>
|
|
||||||
`<div class="game-compat ${compat.compatible ? 'compatible' : 'incompatible'}">
|
|
||||||
<strong>${game}</strong><br>
|
|
||||||
Score: ${compat.score}%<br>
|
|
||||||
${compat.compatible ? '✅' : '❌'} ${compat.reason}
|
|
||||||
</div>`
|
|
||||||
).join('')}
|
|
||||||
</div>`,
|
|
||||||
'success');
|
|
||||||
|
|
||||||
this.tests.push({ name: 'Content Scanner', status: 'success' });
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.addResult('❌ Erreur Content Scanner', `Erreur: ${error.message}`, 'error');
|
|
||||||
this.tests.push({ name: 'Content Scanner', status: 'error', error: error.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async testCapabilityAnalysis() {
|
|
||||||
try {
|
|
||||||
// Test de l'analyse de profondeur du vocabulaire
|
|
||||||
const testVocab = {
|
|
||||||
// Niveau 1: string simple
|
|
||||||
'cat': 'chat',
|
|
||||||
// Niveau 2: objet de base
|
|
||||||
'dog': { user_language: 'chien', type: 'noun' },
|
|
||||||
// Niveau 3: avec exemples
|
|
||||||
'house': {
|
|
||||||
user_language: 'maison',
|
|
||||||
type: 'noun',
|
|
||||||
examples: ['I live in a big house'],
|
|
||||||
grammar_notes: 'Count noun'
|
|
||||||
},
|
|
||||||
// Niveau 6: tout inclus
|
|
||||||
'serendipity': {
|
|
||||||
user_language: 'sérendipité',
|
|
||||||
type: 'noun',
|
|
||||||
ipa: '/ˌsɛrənˈdɪpɪti/',
|
|
||||||
examples: ['What a wonderful serendipity!'],
|
|
||||||
grammar_notes: 'Abstract noun, uncountable',
|
|
||||||
etymology: 'Coined by Horace Walpole in 1754',
|
|
||||||
word_family: ['serendipitous', 'serendipitously'],
|
|
||||||
cultural_significance: 'Popular concept in Western philosophy',
|
|
||||||
memory_techniques: ['Think of happy accidents'],
|
|
||||||
visual_associations: ['Four-leaf clover', 'shooting star']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const depth = this.contentScanner.analyzeVocabularyDepth({ vocabulary: testVocab });
|
|
||||||
|
|
||||||
this.addResult('📊 Analyse de Profondeur',
|
|
||||||
`<p>Profondeur détectée: <strong>Niveau ${depth}/6</strong></p>
|
|
||||||
<p>Le système détecte correctement les 6 niveaux de complexité du vocabulaire.</p>
|
|
||||||
<pre>${JSON.stringify(testVocab, null, 2)}</pre>`,
|
|
||||||
'success');
|
|
||||||
|
|
||||||
this.tests.push({ name: 'Capability Analysis', status: 'success' });
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.addResult('❌ Erreur Analyse Capacités', `Erreur: ${error.message}`, 'error');
|
|
||||||
this.tests.push({ name: 'Capability Analysis', status: 'error', error: error.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async testGameCompatibility() {
|
|
||||||
try {
|
|
||||||
// Test des différents types de compatibilité
|
|
||||||
const testCases = [
|
|
||||||
{
|
|
||||||
name: 'Contenu Vocabulaire Riche',
|
|
||||||
capabilities: {
|
|
||||||
hasVocabulary: true,
|
|
||||||
hasAudioFiles: true,
|
|
||||||
vocabularyDepth: 4,
|
|
||||||
contentRichness: 8
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Contenu Audio/Prononciation',
|
|
||||||
capabilities: {
|
|
||||||
hasVocabulary: true,
|
|
||||||
hasAudioFiles: true,
|
|
||||||
hasIPA: true,
|
|
||||||
vocabularyDepth: 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Contenu Culturel',
|
|
||||||
capabilities: {
|
|
||||||
hasCulture: true,
|
|
||||||
hasPoems: true,
|
|
||||||
hasCulturalContext: true,
|
|
||||||
contentRichness: 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
const compatibility = this.contentScanner.calculateGameCompatibility(testCase.capabilities);
|
|
||||||
|
|
||||||
this.addResult(`🎯 Test: ${testCase.name}`,
|
|
||||||
`<div class="compatibility">
|
|
||||||
${Object.entries(compatibility).map(([game, compat]) =>
|
|
||||||
`<div class="game-compat ${compat.compatible ? 'compatible' : 'incompatible'}">
|
|
||||||
<strong>${game}</strong><br>
|
|
||||||
Score: ${compat.score}%<br>
|
|
||||||
${compat.compatible ? '✅' : '❌'} ${compat.reason}
|
|
||||||
</div>`
|
|
||||||
).join('')}
|
|
||||||
</div>`,
|
|
||||||
'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tests.push({ name: 'Game Compatibility', status: 'success' });
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.addResult('❌ Erreur Compatibilité Jeux', `Erreur: ${error.message}`, 'error');
|
|
||||||
this.tests.push({ name: 'Game Compatibility', status: 'error', error: error.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Démarrer les tests quand la page est chargée
|
|
||||||
document.addEventListener('DOMContentLoaded', async function() {
|
|
||||||
const tester = new UltraModularTester();
|
|
||||||
await tester.runTests();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Test WebSocket Simple</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: monospace; background: #1e1e1e; color: white; }
|
|
||||||
.log { margin: 2px 0; padding: 4px; border-left: 3px solid #007bff; }
|
|
||||||
.ERROR { border-color: #dc3545; }
|
|
||||||
.WARN { border-color: #ffc107; }
|
|
||||||
.DEBUG { border-color: #6c757d; }
|
|
||||||
.INFO { border-color: #17a2b8; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Test WebSocket Simple</h2>
|
|
||||||
<div id="status">En attente...</div>
|
|
||||||
<div id="logs"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const statusDiv = document.getElementById('status');
|
|
||||||
const logsDiv = document.getElementById('logs');
|
|
||||||
let messageCount = 0;
|
|
||||||
|
|
||||||
console.log('🔌 Tentative connexion WebSocket ws://localhost:8082');
|
|
||||||
const ws = new WebSocket('ws://localhost:8082');
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
console.log('✅ WebSocket connecté !');
|
|
||||||
statusDiv.textContent = 'Connecté !';
|
|
||||||
statusDiv.style.color = 'green';
|
|
||||||
|
|
||||||
// Clear initial content
|
|
||||||
logsDiv.innerHTML = '<div style="color: green;">✅ WebSocket connecté - En attente des logs...</div>';
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
|
||||||
messageCount++;
|
|
||||||
console.log(`📨 Message #${messageCount} reçu:`, event.data);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const logData = JSON.parse(event.data);
|
|
||||||
const logDiv = document.createElement('div');
|
|
||||||
logDiv.className = `log ${logData.level}`;
|
|
||||||
|
|
||||||
const time = new Date(logData.timestamp).toLocaleTimeString();
|
|
||||||
logDiv.innerHTML = `<strong>[${time}] ${logData.level}:</strong> ${logData.message}`;
|
|
||||||
|
|
||||||
logsDiv.appendChild(logDiv);
|
|
||||||
|
|
||||||
// Auto-scroll
|
|
||||||
logDiv.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log('❌ Erreur parsing:', error);
|
|
||||||
const errorDiv = document.createElement('div');
|
|
||||||
errorDiv.className = 'log ERROR';
|
|
||||||
errorDiv.textContent = `Erreur parsing: ${event.data}`;
|
|
||||||
logsDiv.appendChild(errorDiv);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
console.log('❌ Connexion fermée');
|
|
||||||
statusDiv.textContent = `Déconnecté (${messageCount} messages reçus)`;
|
|
||||||
statusDiv.style.color = 'red';
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = (error) => {
|
|
||||||
console.log('❌ Erreur WebSocket:', error);
|
|
||||||
statusDiv.textContent = 'Erreur de connexion';
|
|
||||||
statusDiv.style.color = 'red';
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
158
todotemp.md
158
todotemp.md
@ -1,158 +0,0 @@
|
|||||||
# TODO List - Développement Technique
|
|
||||||
|
|
||||||
## 🚨 URGENT - DEMAIN
|
|
||||||
|
|
||||||
### Core Navigation System
|
|
||||||
- [ ] Create index.html with 3-level navigation
|
|
||||||
- [ ] Implement URL routing with params (?page=games&game=whack&content=sbs8)
|
|
||||||
- [ ] Build game-selector.html with clickable cards
|
|
||||||
- [ ] Build level-selector.html with dynamic content loading
|
|
||||||
- [ ] Create game.html generic page with dynamic module loading
|
|
||||||
|
|
||||||
### Game Modules
|
|
||||||
- [ ] Refactor existing whack-a-mole.js into proper module format
|
|
||||||
- [ ] Refactor existing fill-the-blank.js into proper module format
|
|
||||||
- [ ] Implement game loader system (js/core/game-loader.js)
|
|
||||||
- [ ] Create base GameEngine class for inheritance
|
|
||||||
|
|
||||||
### Content System
|
|
||||||
- [ ] Convert sbs-level-8.js to new unified format
|
|
||||||
- [ ] Implement content loader system
|
|
||||||
- [ ] Create content validation functions
|
|
||||||
- [ ] Add error handling for missing content
|
|
||||||
|
|
||||||
### New Games (Pick 3-4)
|
|
||||||
- [ ] simon-says.js - digital "Touch the X" game
|
|
||||||
- [ ] speed-categories.js - rapid category clicking
|
|
||||||
- [ ] true-false.js - rapid true/false with images
|
|
||||||
- [ ] memory-pairs.js - classic memory game
|
|
||||||
- [ ] sound-match.js - audio to image matching
|
|
||||||
- [ ] catch-words.js - flying words to catch
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 CONTENT EXPANSION
|
|
||||||
|
|
||||||
### Lesson Introduction Module
|
|
||||||
- [ ] Create lesson-intro.js for vocabulary presentation
|
|
||||||
- [ ] Add context presentation before games
|
|
||||||
- [ ] Implement guided repetition system
|
|
||||||
- [ ] Add audio playback for pronunciation
|
|
||||||
|
|
||||||
### Additional Content Modules
|
|
||||||
- [ ] animals.js vocabulary set
|
|
||||||
- [ ] colors.js vocabulary set
|
|
||||||
- [ ] family-extended.js more family vocabulary
|
|
||||||
- [ ] actions.js verbs and actions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 TECHNICAL IMPROVEMENTS
|
|
||||||
|
|
||||||
### Core System
|
|
||||||
- [ ] navigation.js - handle URL routing and back buttons
|
|
||||||
- [ ] utils.js - shared utility functions
|
|
||||||
- [ ] audio-manager.js - handle sound loading/playing
|
|
||||||
- [ ] progress-tracker.js - basic score tracking
|
|
||||||
|
|
||||||
### Game Engine Enhancements
|
|
||||||
- [ ] Standardize game initialization pattern
|
|
||||||
- [ ] Add game state management (start/pause/stop/reset)
|
|
||||||
- [ ] Implement scoring system interface
|
|
||||||
- [ ] Add game configuration loading
|
|
||||||
|
|
||||||
### Performance & UX
|
|
||||||
- [ ] Lazy loading for game modules
|
|
||||||
- [ ] Preload critical assets
|
|
||||||
- [ ] Add loading states and spinners
|
|
||||||
- [ ] Implement keyboard shortcuts (ESC = back)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 MODULAR ARCHITECTURE
|
|
||||||
|
|
||||||
### File Structure Implementation
|
|
||||||
```
|
|
||||||
├── index.html
|
|
||||||
├── css/
|
|
||||||
│ ├── main.css
|
|
||||||
│ ├── games.css
|
|
||||||
│ └── navigation.css
|
|
||||||
├── js/
|
|
||||||
│ ├── core/
|
|
||||||
│ │ ├── navigation.js
|
|
||||||
│ │ ├── game-loader.js
|
|
||||||
│ │ ├── content-loader.js
|
|
||||||
│ │ └── utils.js
|
|
||||||
│ ├── games/
|
|
||||||
│ │ ├── base-game.js
|
|
||||||
│ │ ├── whack-a-mole.js
|
|
||||||
│ │ ├── fill-blank.js
|
|
||||||
│ │ ├── simon-says.js
|
|
||||||
│ │ ├── speed-categories.js
|
|
||||||
│ │ └── temp-games.js
|
|
||||||
│ └── content/
|
|
||||||
│ ├── sbs-level-8.js
|
|
||||||
│ ├── animals.js
|
|
||||||
│ └── colors.js
|
|
||||||
```
|
|
||||||
|
|
||||||
### Module Standards
|
|
||||||
- [ ] Define base game class interface
|
|
||||||
- [ ] Standardize content module format
|
|
||||||
- [ ] Create module registration system
|
|
||||||
- [ ] Implement dependency loading
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🌟 FUTURE TECHNICAL FEATURES
|
|
||||||
|
|
||||||
### Advanced Game Types
|
|
||||||
- [ ] drag-drop.js - sentence building
|
|
||||||
- [ ] story-builder.js - narrative construction
|
|
||||||
- [ ] voice-game.js - speech recognition
|
|
||||||
- [ ] drawing-game.js - character tracing
|
|
||||||
|
|
||||||
### Content Management
|
|
||||||
- [ ] JSON-based content configuration
|
|
||||||
- [ ] Content validation schemas
|
|
||||||
- [ ] Dynamic content generation helpers
|
|
||||||
- [ ] Content import/export utilities
|
|
||||||
|
|
||||||
### System Extensions
|
|
||||||
- [ ] Plugin architecture for third-party games
|
|
||||||
- [ ] API for external content sources
|
|
||||||
- [ ] Offline caching system
|
|
||||||
- [ ] Multi-language UI support
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 CHINESE VERSION PREP
|
|
||||||
|
|
||||||
### Architecture Adaptation
|
|
||||||
- [ ] Extend content format for Chinese specifics
|
|
||||||
- [ ] Add tone support in audio system
|
|
||||||
- [ ] Implement character stroke order
|
|
||||||
- [ ] Add pinyin display system
|
|
||||||
|
|
||||||
### Chinese-Specific Games
|
|
||||||
- [ ] stroke-order.js - character writing
|
|
||||||
- [ ] tone-practice.js - tone recognition
|
|
||||||
- [ ] radical-builder.js - character composition
|
|
||||||
- [ ] pinyin-typing.js - romanization practice
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤖 AI INTEGRATION PREP
|
|
||||||
|
|
||||||
### API Integration Points
|
|
||||||
- [ ] content-generator.js - AI content creation
|
|
||||||
- [ ] response-validator.js - AI answer checking
|
|
||||||
- [ ] difficulty-adapter.js - AI difficulty adjustment
|
|
||||||
- [ ] feedback-generator.js - AI personalized feedback
|
|
||||||
|
|
||||||
### Data Collection
|
|
||||||
- [ ] User interaction logging
|
|
||||||
- [ ] Performance metrics collection
|
|
||||||
- [ ] Error pattern tracking
|
|
||||||
- [ ] Learning progress data structure
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
// === SCRIPT DE VALIDATION DU SYSTÈME DE COMPATIBILITÉ ===
|
|
||||||
|
|
||||||
// Fonction pour tester le chargement des modules
|
|
||||||
async function validateCompatibilitySystem() {
|
|
||||||
console.log('🧪 Validation du système de compatibilité...');
|
|
||||||
|
|
||||||
// Test 1: Vérifier que les classes globales existent
|
|
||||||
console.log('\n1️⃣ Test des classes globales:');
|
|
||||||
const requiredClasses = [
|
|
||||||
'ContentScanner',
|
|
||||||
'ContentGameCompatibility'
|
|
||||||
];
|
|
||||||
|
|
||||||
requiredClasses.forEach(className => {
|
|
||||||
if (window[className]) {
|
|
||||||
console.log(`✅ ${className} est disponible`);
|
|
||||||
} else {
|
|
||||||
console.error(`❌ ${className} manquant!`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test 2: Initialiser les systèmes
|
|
||||||
console.log('\n2️⃣ Test d\'initialisation:');
|
|
||||||
try {
|
|
||||||
const scanner = new window.ContentScanner();
|
|
||||||
console.log('✅ ContentScanner initialisé');
|
|
||||||
|
|
||||||
const checker = new window.ContentGameCompatibility();
|
|
||||||
console.log('✅ ContentGameCompatibility initialisé');
|
|
||||||
|
|
||||||
// Test 3: Scanner le contenu
|
|
||||||
console.log('\n3️⃣ Test du scan de contenu:');
|
|
||||||
const results = await scanner.scanAllContent();
|
|
||||||
console.log(`✅ Scan terminé: ${results.found.length} modules trouvés`);
|
|
||||||
|
|
||||||
// Test 4: Test de compatibilité
|
|
||||||
console.log('\n4️⃣ Test de compatibilité:');
|
|
||||||
if (results.found.length > 0) {
|
|
||||||
const testContent = results.found[0];
|
|
||||||
const compatibility = checker.checkCompatibility(testContent, 'whack-a-mole');
|
|
||||||
console.log(`✅ Test compatibilité: ${testContent.name} → whack-a-mole = ${compatibility.compatible ? 'Compatible' : 'Incompatible'} (${compatibility.score}%)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 5: Vérifier AppNavigation
|
|
||||||
console.log('\n5️⃣ Test de l\'intégration navigation:');
|
|
||||||
if (window.AppNavigation && window.AppNavigation.compatibilityChecker) {
|
|
||||||
console.log('✅ AppNavigation a le système de compatibilité intégré');
|
|
||||||
} else {
|
|
||||||
console.log('⚠️ AppNavigation n\'a pas le système de compatibilité');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n🎉 Tous les tests passés!');
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ Erreur pendant les tests: ${error.message}`);
|
|
||||||
console.error(error.stack);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-exécution si ce script est chargé directement
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
// Attendre que tous les scripts soient chargés
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
setTimeout(validateCompatibilitySystem, 1000);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setTimeout(validateCompatibilitySystem, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export pour utilisation manuelle
|
|
||||||
window.validateCompatibilitySystem = validateCompatibilitySystem;
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
// === SCRIPT DE VÉRIFICATION APPLICATION RÉELLE ===
|
|
||||||
|
|
||||||
// Test que tous les composants fonctionnent ensemble dans l'app réelle
|
|
||||||
async function verifyRealApplication() {
|
|
||||||
console.log('🔍 Vérification de l\'application réelle...\n');
|
|
||||||
|
|
||||||
const tests = [];
|
|
||||||
|
|
||||||
// Test 1: Vérifier que AppNavigation existe et est initialisé
|
|
||||||
tests.push({
|
|
||||||
name: 'AppNavigation disponible',
|
|
||||||
test: () => window.AppNavigation && typeof window.AppNavigation.init === 'function',
|
|
||||||
critical: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test 2: Vérifier que le système de compatibilité est intégré
|
|
||||||
tests.push({
|
|
||||||
name: 'Système de compatibilité intégré',
|
|
||||||
test: () => window.AppNavigation && window.AppNavigation.compatibilityChecker,
|
|
||||||
critical: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test 3: Vérifier que ContentScanner est opérationnel
|
|
||||||
tests.push({
|
|
||||||
name: 'ContentScanner opérationnel',
|
|
||||||
test: () => window.AppNavigation && window.AppNavigation.contentScanner,
|
|
||||||
critical: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test 4: Vérifier que GameLoader existe
|
|
||||||
tests.push({
|
|
||||||
name: 'GameLoader disponible',
|
|
||||||
test: () => window.GameLoader && typeof window.GameLoader.loadGame === 'function',
|
|
||||||
critical: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test 5: Vérifier que les modules de jeu sont chargés
|
|
||||||
tests.push({
|
|
||||||
name: 'Modules de jeu chargés',
|
|
||||||
test: () => window.GameModules && Object.keys(window.GameModules).length > 0,
|
|
||||||
critical: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test 6: Vérifier que les modules de contenu sont chargés
|
|
||||||
tests.push({
|
|
||||||
name: 'Modules de contenu chargés',
|
|
||||||
test: () => window.ContentModules && Object.keys(window.ContentModules).length > 0,
|
|
||||||
critical: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test 7: Vérifier que showGamesPage est async
|
|
||||||
tests.push({
|
|
||||||
name: 'showGamesPage est async',
|
|
||||||
test: () => {
|
|
||||||
const fn = window.AppNavigation.showGamesPage;
|
|
||||||
return fn && fn.constructor.name === 'AsyncFunction';
|
|
||||||
},
|
|
||||||
critical: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test 8: Vérifier les CSS de compatibilité
|
|
||||||
tests.push({
|
|
||||||
name: 'CSS de compatibilité chargé',
|
|
||||||
test: () => {
|
|
||||||
const sheets = Array.from(document.styleSheets);
|
|
||||||
return sheets.some(sheet => {
|
|
||||||
try {
|
|
||||||
const rules = Array.from(sheet.cssRules || []);
|
|
||||||
return rules.some(rule => rule.selectorText && rule.selectorText.includes('compatibility-badge'));
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
critical: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Exécuter tous les tests
|
|
||||||
let passedTests = 0;
|
|
||||||
let criticalFailed = 0;
|
|
||||||
|
|
||||||
console.log('📋 Résultats des tests:\n');
|
|
||||||
|
|
||||||
for (const test of tests) {
|
|
||||||
try {
|
|
||||||
const result = test.test();
|
|
||||||
if (result) {
|
|
||||||
console.log(`✅ ${test.name}`);
|
|
||||||
passedTests++;
|
|
||||||
} else {
|
|
||||||
const level = test.critical ? 'CRITIQUE' : 'OPTIONNEL';
|
|
||||||
console.log(`❌ ${test.name} (${level})`);
|
|
||||||
if (test.critical) criticalFailed++;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const level = test.critical ? 'CRITIQUE' : 'OPTIONNEL';
|
|
||||||
console.log(`🚨 ${test.name} - ERREUR: ${error.message} (${level})`);
|
|
||||||
if (test.critical) criticalFailed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n📊 Résultat: ${passedTests}/${tests.length} tests réussis`);
|
|
||||||
|
|
||||||
if (criticalFailed === 0) {
|
|
||||||
console.log('🎉 Tous les tests critiques passent! L\'application est prête.');
|
|
||||||
|
|
||||||
// Test bonus: essayer de charger du contenu
|
|
||||||
if (window.AppNavigation && window.AppNavigation.contentScanner) {
|
|
||||||
console.log('\n🔍 Test bonus: chargement du contenu...');
|
|
||||||
try {
|
|
||||||
const results = await window.AppNavigation.contentScanner.scanAllContent();
|
|
||||||
console.log(`✅ ${results.found.length} modules de contenu détectés`);
|
|
||||||
|
|
||||||
// Test de compatibilité sur contenu réel
|
|
||||||
if (results.found.length > 0 && window.AppNavigation.compatibilityChecker) {
|
|
||||||
const testContent = results.found[0];
|
|
||||||
const compatibility = window.AppNavigation.compatibilityChecker.checkCompatibility(testContent, 'whack-a-mole');
|
|
||||||
console.log(`✅ Test compatibilité: ${compatibility.compatible ? 'COMPATIBLE' : 'INCOMPATIBLE'} (${compatibility.score}%)`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`⚠️ Erreur lors du test bonus: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
console.log(`❌ ${criticalFailed} tests critiques ont échoué. Vérification nécessaire.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonction d'auto-test
|
|
||||||
function autoVerify() {
|
|
||||||
// Attendre que l'app soit complètement chargée
|
|
||||||
if (document.readyState !== 'complete') {
|
|
||||||
window.addEventListener('load', () => setTimeout(verifyRealApplication, 2000));
|
|
||||||
} else {
|
|
||||||
setTimeout(verifyRealApplication, 2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export pour utilisation manuelle
|
|
||||||
window.verifyRealApplication = verifyRealApplication;
|
|
||||||
|
|
||||||
// Auto-vérification si ce script est chargé dans l'app réelle
|
|
||||||
if (typeof window !== 'undefined' && window.location.pathname === '/') {
|
|
||||||
autoVerify();
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user