- Create complete integration test system (test-integration.js) - Add UI/UX interaction testing with real event simulation (test-uiux-integration.js) - Implement end-to-end scenario testing for user journeys (test-e2e-scenarios.js) - Add console testing commands for rapid development testing (test-console-commands.js) - Create comprehensive test guide documentation (TEST-GUIDE.md) - Integrate test buttons in debug panel (F12 → 3 test types) - Add vocabulary modal two-progress-bar system integration - Fix flashcard retry system for "don't know" cards - Update IntelligentSequencer for task distribution validation 🧪 Testing Coverage: - 35+ integration tests (architecture/modules) - 20+ UI/UX tests (real user interactions) - 5 E2E scenarios (complete user journeys) - Console commands for rapid testing - Debug panel integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
2707 lines
123 KiB
HTML
2707 lines
123 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Class Generator - Educational Games Platform</title>
|
||
|
||
<!-- Styles -->
|
||
<link rel="stylesheet" href="src/styles/base.css?v=3">
|
||
<link rel="stylesheet" href="src/styles/components.css?v=17">
|
||
|
||
<!-- Favicon -->
|
||
<link rel="icon" type="image/x-icon" href="assets/favicon.ico">
|
||
</head>
|
||
<body>
|
||
<!-- Application Root -->
|
||
<div id="app">
|
||
<!-- Loading screen shown until app initializes -->
|
||
<div id="loading-screen" class="loading-screen">
|
||
<div class="loading-spinner"></div>
|
||
<h2>Loading Class Generator...</h2>
|
||
<p>Initializing educational games platform</p>
|
||
</div>
|
||
|
||
<!-- Main application container -->
|
||
<div id="app-container" class="app-container" style="display: none;">
|
||
<!-- Top navigation bar -->
|
||
<header id="app-header" class="app-header">
|
||
<div class="header-content">
|
||
<div id="connection-status" class="connection-status">
|
||
<span class="status-indicator"></span>
|
||
<span class="status-text">Online</span>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main content area -->
|
||
<main id="app-main" class="app-main">
|
||
<!-- Content will be rendered here by modules -->
|
||
</main>
|
||
|
||
<!-- Debug panel (only shown in debug mode) -->
|
||
<div id="debug-panel" class="debug-panel" style="display: none;">
|
||
<div class="debug-header">
|
||
<h3>Debug Panel</h3>
|
||
<button id="debug-toggle" class="debug-toggle">×</button>
|
||
</div>
|
||
<div class="debug-content">
|
||
<div id="debug-status"></div>
|
||
<div id="debug-events"></div>
|
||
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #ccc;">
|
||
<button id="run-integration-tests" onclick="runDRSTests()"
|
||
style="background: #28a745; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; width: 100%; margin-bottom: 8px;">
|
||
🧪 Run Integration Tests
|
||
</button>
|
||
<button id="run-uiux-tests" onclick="runUIUXTests()"
|
||
style="background: #6f42c1; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; width: 100%; margin-bottom: 8px;">
|
||
🎨 Run UI/UX Tests
|
||
</button>
|
||
<button id="run-e2e-tests" onclick="runE2ETests()"
|
||
style="background: #fd7e14; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; width: 100%;">
|
||
🎬 Run E2E Scenarios
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Application Bootstrap -->
|
||
<script type="module">
|
||
// Import and start the application
|
||
import app from './src/Application.js';
|
||
import ContentLoader from './src/utils/ContentLoader.js';
|
||
import SmartPreviewOrchestrator from './src/DRS/SmartPreviewOrchestrator.js';
|
||
|
||
// Global navigation state
|
||
let currentBookId = null;
|
||
let currentChapterId = null;
|
||
|
||
// Initialize ContentLoader and make it global
|
||
const contentLoader = new ContentLoader();
|
||
window.contentLoader = contentLoader;
|
||
|
||
// Smart Preview Orchestrator will be initialized automatically by Application.js
|
||
|
||
// Wait for both DOM and application to be ready
|
||
let appReady = false;
|
||
let domReady = false;
|
||
|
||
function checkReadiness() {
|
||
if (appReady && domReady) {
|
||
setupEventListeners();
|
||
}
|
||
}
|
||
|
||
// DOM ready check
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
domReady = true;
|
||
checkReadiness();
|
||
});
|
||
} else {
|
||
domReady = true;
|
||
}
|
||
|
||
// App ready check with retry
|
||
function waitForApp() {
|
||
try {
|
||
// Try to access EventBus - if this works, app is ready
|
||
const { eventBus } = app.getCore();
|
||
if (eventBus && eventBus.getRegisteredModules().includes('Bootstrap')) {
|
||
appReady = true;
|
||
checkReadiness();
|
||
} else {
|
||
setTimeout(waitForApp, 50);
|
||
}
|
||
} catch (error) {
|
||
setTimeout(waitForApp, 50);
|
||
}
|
||
}
|
||
|
||
waitForApp();
|
||
|
||
function setupEventListeners() {
|
||
const { eventBus } = app.getCore();
|
||
|
||
// Hide tooltip on navigation
|
||
eventBus.on('router:route-changed', () => {
|
||
window.hideTooltip();
|
||
}, 'Bootstrap');
|
||
|
||
// Handle app ready event
|
||
eventBus.on('app:ready', async (event) => {
|
||
console.log('🎉 Application is ready!');
|
||
|
||
// Hide loading screen
|
||
const loadingScreen = document.getElementById('loading-screen');
|
||
const appContainer = document.getElementById('app-container');
|
||
|
||
if (loadingScreen) loadingScreen.style.display = 'none';
|
||
if (appContainer) appContainer.style.display = 'block';
|
||
|
||
// Smart Preview Orchestrator is automatically initialized by Application.js
|
||
|
||
// Show debug panel if enabled
|
||
const status = app.getStatus();
|
||
if (status.config.enableDebug) {
|
||
const debugPanel = document.getElementById('debug-panel');
|
||
if (debugPanel) debugPanel.style.display = 'block';
|
||
updateDebugInfo();
|
||
}
|
||
}, 'Bootstrap');
|
||
|
||
// Handle navigation events
|
||
eventBus.on('navigation:home', () => {
|
||
updateMainContent(`
|
||
<div class="hero-container">
|
||
<div class="hero-background"></div>
|
||
<div class="hero-content">
|
||
<div class="hero-icon">🎓</div>
|
||
<h1 class="hero-title">Class Generator</h1>
|
||
<p class="hero-subtitle">Interactive Educational Learning Platform</p>
|
||
<p class="hero-description">
|
||
Develop your language skills with captivating and adaptive learning experiences
|
||
</p>
|
||
|
||
<div class="action-buttons">
|
||
<button class="btn btn-primary" onclick="navigateToBooks()">
|
||
<span class="btn-icon">📚</span>
|
||
<span class="btn-text">Explore Books</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="secondary-buttons">
|
||
<button class="btn btn-outline" onclick="navigateToDynamicRevision()">
|
||
<span class="btn-icon">🧠</span>
|
||
<span class="btn-text">Dynamic Revision System</span>
|
||
</button>
|
||
<button class="btn btn-outline" onclick="openPlaceholder()">
|
||
<span class="btn-icon">🎮</span>
|
||
<span class="btn-text">Quick Play</span>
|
||
</button>
|
||
<button class="btn btn-outline" onclick="openSettings()">
|
||
<span class="btn-icon">⚙️</span>
|
||
<span class="btn-text">Settings</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`);
|
||
}, 'Bootstrap');
|
||
|
||
eventBus.on('navigation:books', async () => {
|
||
try {
|
||
const response = await fetch('/api/books');
|
||
const books = await response.json();
|
||
|
||
const languages = [...new Set(books.map(book => book.language))];
|
||
const languageOptions = languages.map(lang =>
|
||
`<option value="${lang}">${lang === 'en-US' ? 'English' : lang}</option>`
|
||
).join('');
|
||
|
||
const booksHTML = books.map(book => {
|
||
const bookCover = book.id === 'sbs'
|
||
? `<img src="assets/SBSBook.jpg" alt="${book.name} Book Cover" />`
|
||
: `<div class="book-cover-placeholder">📚</div>`;
|
||
|
||
return `
|
||
<div class="book-card" data-language="${book.language}" onclick="openBook('${book.id}')">
|
||
<div class="book-cover">
|
||
${bookCover}
|
||
</div>
|
||
<div class="book-info">
|
||
<h3>${book.name}</h3>
|
||
<p class="book-language">${book.language === 'en-US' ? 'English' : book.language}</p>
|
||
<p class="book-description">${book.description}</p>
|
||
<span class="book-difficulty">${book.difficulty}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
updateMainContent(`
|
||
<div class="books-container">
|
||
<div class="books-header">
|
||
<button class="btn btn-outline btn-sm" onclick="navigateToHome()">
|
||
<span class="btn-icon">🏠</span>
|
||
<span class="btn-text">Back to Home</span>
|
||
</button>
|
||
<h2>Available Books</h2>
|
||
<div class="language-selector">
|
||
<label for="language-select">Language:</label>
|
||
<select id="language-select" class="form-select" onchange="filterBooksByLanguage(this.value)">
|
||
<option value="all">All Languages</option>
|
||
${languageOptions}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="books-grid" id="books-grid">
|
||
${booksHTML}
|
||
</div>
|
||
</div>
|
||
`);
|
||
} catch (error) {
|
||
console.error('Error loading books:', error);
|
||
updateMainContent(`
|
||
<div class="error-message">
|
||
<h2>Error Loading Books</h2>
|
||
<p>Could not load available books. Please try again.</p>
|
||
<button onclick="navigateToBooks()">Retry</button>
|
||
</div>
|
||
`);
|
||
}
|
||
}, 'Bootstrap');
|
||
|
||
eventBus.on('navigation:chapters', async (event) => {
|
||
const bookId = event.data.path.split('/')[2];
|
||
currentBookId = bookId;
|
||
|
||
try {
|
||
// Load content using ContentLoader to get reports
|
||
const contentData = await contentLoader.loadContent(bookId);
|
||
|
||
updateMainContent(`
|
||
<div class="chapters-container">
|
||
<div class="chapters-header">
|
||
<button class="btn btn-outline btn-sm" onclick="navigateToHome()">
|
||
<span class="btn-icon">🏠</span>
|
||
<span class="btn-text">Back to Home</span>
|
||
</button>
|
||
<button class="btn btn-outline btn-sm" onclick="navigateToBooks()">
|
||
<span class="btn-icon">←</span>
|
||
<span class="btn-text">Back to Books</span>
|
||
</button>
|
||
<h2>${contentData.name}</h2>
|
||
</div>
|
||
|
||
<div class="chapters-grid">
|
||
<div class="chapter-card" onclick="navigateToGames('${bookId}')"
|
||
onmouseenter="showTooltip(event, '${bookId}')"
|
||
onmouseleave="hideTooltip()">
|
||
<div class="chapter-number">7-8</div>
|
||
<div class="chapter-info">
|
||
<h3>${contentData.name}</h3>
|
||
<p class="chapter-description">${contentData.description}</p>
|
||
<div class="chapter-meta">
|
||
<span class="difficulty-badge difficulty-${contentData.difficulty}">${contentData.difficulty}</span>
|
||
<span class="language-badge">${contentData.language}</span>
|
||
</div>
|
||
<div class="chapter-progress">
|
||
<div class="progress">
|
||
<div class="progress-bar" style="width: 0%"></div>
|
||
</div>
|
||
<span class="progress-text">0% Complete</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`);
|
||
} catch (error) {
|
||
console.error('Error loading chapter data:', error);
|
||
updateMainContent(`
|
||
<div class="chapters-container">
|
||
<div class="chapters-header">
|
||
<button class="btn btn-outline btn-sm" onclick="navigateToBooks()">
|
||
<span class="btn-icon">←</span>
|
||
<span class="btn-text">Back to Books</span>
|
||
</button>
|
||
<h2>Chapters</h2>
|
||
</div>
|
||
<div class="error-message">
|
||
<p>Error loading chapter data. Please try again.</p>
|
||
</div>
|
||
</div>
|
||
`);
|
||
}
|
||
}, 'Bootstrap');
|
||
|
||
eventBus.on('navigation:games', async (event) => {
|
||
const chapterId = event.data.path.split('/')[2];
|
||
currentChapterId = chapterId;
|
||
|
||
try {
|
||
// Get GameLoader instance
|
||
const gameLoader = app.getModule('gameLoader');
|
||
if (!gameLoader) {
|
||
throw new Error('GameLoader not available');
|
||
}
|
||
|
||
// Load current content for compatibility scoring
|
||
const content = contentLoader.getContent(chapterId);
|
||
if (content) {
|
||
eventBus.emit('content:loaded', { content }, 'Bootstrap');
|
||
|
||
// Wait a bit for the event to be processed, then get games
|
||
setTimeout(() => {
|
||
window._renderGamesInterface(gameLoader, chapterId);
|
||
}, 50);
|
||
return;
|
||
}
|
||
|
||
// If no content, render immediately with no games
|
||
window._renderGamesInterface(gameLoader, chapterId);
|
||
} catch (error) {
|
||
console.error('Error loading games:', error);
|
||
updateMainContent(`
|
||
<div class="games-container">
|
||
<div class="games-header">
|
||
<button class="btn btn-outline btn-sm" onclick="navigateToChapters('${chapterId}')">
|
||
<span class="btn-icon">←</span>
|
||
<span class="btn-text">Back to Chapters</span>
|
||
</button>
|
||
<h2>Games</h2>
|
||
</div>
|
||
<div class="error-message">
|
||
<p>Error loading games. Please try again.</p>
|
||
</div>
|
||
</div>
|
||
`);
|
||
}
|
||
}, 'Bootstrap');
|
||
|
||
// Helper function to render games interface
|
||
window._renderGamesInterface = function(gameLoader, chapterId) {
|
||
// Get compatible games
|
||
const availableGames = gameLoader.getAvailableGames();
|
||
console.log('🎮 Available games:', availableGames);
|
||
|
||
const compatibleGames = gameLoader.getCompatibleGames(0.1); // Low threshold to show most games
|
||
console.log('✅ Compatible games:', compatibleGames);
|
||
|
||
updateMainContent(`
|
||
<div class="games-container">
|
||
<div class="games-header">
|
||
<button class="btn btn-outline btn-sm" onclick="navigateToChapters('${chapterId}')">
|
||
<span class="btn-icon">←</span>
|
||
<span class="btn-text">Back to Chapters</span>
|
||
</button>
|
||
<h2>${chapterId} - Games</h2>
|
||
<p class="games-subtitle">${compatibleGames.length} compatible games found</p>
|
||
</div>
|
||
|
||
${compatibleGames.length > 0 ? `
|
||
<div class="games-grid">
|
||
${compatibleGames.map(game => `
|
||
<div class="game-card ${game.compatibility.score < 0.3 ? 'low-compatibility' : ''}"
|
||
onclick="launchGame('${game.id}')">
|
||
<div class="game-icon">🎮</div>
|
||
<div class="game-info">
|
||
<h3>${game.metadata.name}</h3>
|
||
<p class="game-description">${game.metadata.description}</p>
|
||
<div class="game-meta">
|
||
<span class="difficulty-badge difficulty-${game.metadata.difficulty}">
|
||
${game.metadata.difficulty}
|
||
</span>
|
||
<span class="compatibility-score ${game.compatibility.score >= 0.7 ? 'high' : game.compatibility.score >= 0.3 ? 'medium' : 'low'}">
|
||
${Math.round(game.compatibility.score * 100)}% compatible
|
||
</span>
|
||
</div>
|
||
<div class="compatibility-details">
|
||
${game.compatibility.reason}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
` : `
|
||
<div class="no-games-container">
|
||
<div class="no-games-icon">🎮</div>
|
||
<h3>No Compatible Games</h3>
|
||
<p>No games are compatible with the current content.</p>
|
||
<div class="suggestions">
|
||
<p>Games typically require:</p>
|
||
<ul>
|
||
<li>At least 8 vocabulary words for Memory Match</li>
|
||
<li>Proper content structure</li>
|
||
<li>Translation data</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
`}
|
||
</div>
|
||
`);
|
||
};
|
||
|
||
// Handle dynamic revision navigation
|
||
eventBus.on('navigation:dynamic-revision', async () => {
|
||
try {
|
||
// Load books using ContentLoader
|
||
await contentLoader.loadBooks();
|
||
const books = contentLoader.getBooks();
|
||
|
||
updateMainContent(`
|
||
<div class="dynamic-revision-container">
|
||
<div class="revision-header">
|
||
<button class="btn btn-outline btn-sm" onclick="navigateToHome()">
|
||
<span class="btn-icon">🏠</span>
|
||
<span class="btn-text">Back to Home</span>
|
||
</button>
|
||
<h2>🧠 Dynamic Revision System</h2>
|
||
<p class="revision-subtitle">Intelligent course preparation with adaptive exercises</p>
|
||
</div>
|
||
|
||
<div class="revision-controls">
|
||
<div class="dropdown-container">
|
||
<div class="dropdown-group">
|
||
<label for="book-select">Select Book:</label>
|
||
<select id="book-select" class="form-select" onchange="onBookSelected(this.value)">
|
||
<option value="">Choose a book...</option>
|
||
${books.map(book =>
|
||
`<option value="${book.id}">${book.name}</option>`
|
||
).join('')}
|
||
</select>
|
||
</div>
|
||
|
||
<div class="dropdown-group">
|
||
<label for="chapter-select">Select Chapter:</label>
|
||
<select id="chapter-select" class="form-select" disabled onchange="onChapterSelected(this.value)">
|
||
<option value="">Choose a chapter...</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="revision-start">
|
||
<button id="smart-guide-btn" class="btn btn-primary btn-lg btn-disabled" disabled onclick="startSmartGuide()">
|
||
<span class="btn-icon">🧠</span>
|
||
<span class="btn-text">Start Smart Guide</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="manual-mode">
|
||
<details class="manual-controls">
|
||
<summary>Advanced Manual Mode</summary>
|
||
<div class="manual-content">
|
||
<div class="exercise-type-selection">
|
||
<h5>Exercise Type:</h5>
|
||
<div class="exercise-types">
|
||
<button class="exercise-type-btn active" data-type="text">
|
||
<span class="type-icon">📚</span>
|
||
<span class="type-name">Reading</span>
|
||
</button>
|
||
<button class="exercise-type-btn" data-type="audio">
|
||
<span class="type-icon">🎵</span>
|
||
<span class="type-name">Audio</span>
|
||
</button>
|
||
<button class="exercise-type-btn" data-type="image">
|
||
<span class="type-icon">🖼️</span>
|
||
<span class="type-name">Image</span>
|
||
</button>
|
||
<button class="exercise-type-btn" data-type="grammar">
|
||
<span class="type-icon">📝</span>
|
||
<span class="type-name">Grammar</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="difficulty-selection">
|
||
<h5>Difficulty:</h5>
|
||
<div class="difficulty-buttons">
|
||
<button class="difficulty-btn" data-difficulty="easy">Easy</button>
|
||
<button class="difficulty-btn active" data-difficulty="medium">Medium</button>
|
||
<button class="difficulty-btn" data-difficulty="hard">Hard</button>
|
||
</div>
|
||
</div>
|
||
|
||
<button id="start-revision-btn" class="btn btn-outline btn-disabled" disabled onclick="startRevision()">
|
||
<span class="btn-icon">🚀</span>
|
||
<span class="btn-text">Start Manual Revision</span>
|
||
</button>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="revision-viewport">
|
||
<div class="viewport-placeholder">
|
||
<div class="placeholder-icon">🧠</div>
|
||
<h3>Smart Preview Exercises</h3>
|
||
<p>Select a book and chapter to see available exercises</p>
|
||
<div class="exercise-types-preview">
|
||
<div class="exercise-type">📚 Vocabulary (groups of 5)</div>
|
||
<div class="exercise-type">💬 Phrase comprehension</div>
|
||
<div class="exercise-type">📖 Text processing</div>
|
||
<div class="exercise-type">🎵 Audio comprehension</div>
|
||
<div class="exercise-type">🖼️ Image description</div>
|
||
<div class="exercise-type">📝 Grammar exercises</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`);
|
||
|
||
// After loading the interface, try to load last used selection
|
||
await loadLastUsedSelection();
|
||
|
||
// Setup exercise type selection handlers
|
||
setupExerciseTypeHandlers();
|
||
setupDifficultyHandlers();
|
||
|
||
} catch (error) {
|
||
console.error('Error loading dynamic revision:', error);
|
||
updateMainContent(`
|
||
<div class="dynamic-revision-container">
|
||
<div class="revision-header">
|
||
<button class="btn btn-outline btn-sm" onclick="navigateToHome()">
|
||
<span class="btn-icon">🏠</span>
|
||
<span class="btn-text">Back to Home</span>
|
||
</button>
|
||
<h2>Dynamic Revision System</h2>
|
||
</div>
|
||
<div class="error-message">
|
||
<p>Error loading revision system. Please try again.</p>
|
||
</div>
|
||
</div>
|
||
`);
|
||
}
|
||
}, 'Bootstrap');
|
||
|
||
// Handle errors
|
||
eventBus.on('app:error', (event) => {
|
||
console.error('Application error:', event.data);
|
||
showError('An error occurred. Check console for details.');
|
||
}, 'Bootstrap');
|
||
|
||
// Set up keyboard shortcuts after app is ready
|
||
setupKeyboardShortcuts();
|
||
|
||
// Set up debug panel after app is ready
|
||
setupDebugPanel();
|
||
}
|
||
|
||
// Tooltip functions - Make them global
|
||
window.showTooltip = function(event, bookId) {
|
||
|
||
// Remove any existing tooltip
|
||
window.hideTooltip();
|
||
|
||
// Get tooltip content from ContentLoader
|
||
const tooltipHTML = contentLoader.generateTooltipHTML(bookId);
|
||
|
||
// Create tooltip element
|
||
const tooltip = document.createElement('div');
|
||
tooltip.className = 'tooltip';
|
||
tooltip.innerHTML = tooltipHTML;
|
||
|
||
// Position au centre de l'écran - simple et efficace
|
||
tooltip.style.position = 'fixed';
|
||
tooltip.style.left = '50%';
|
||
tooltip.style.top = '50%';
|
||
tooltip.style.transform = 'translate(-50%, -50%)';
|
||
tooltip.style.zIndex = '1000';
|
||
tooltip.style.maxWidth = '400px';
|
||
|
||
// Add to DOM
|
||
document.body.appendChild(tooltip);
|
||
};
|
||
|
||
window.hideTooltip = function() {
|
||
const existingTooltip = document.querySelector('.tooltip');
|
||
if (existingTooltip) {
|
||
existingTooltip.remove();
|
||
}
|
||
};
|
||
|
||
// Utility functions
|
||
function updateMainContent(html) {
|
||
const mainElement = document.getElementById('app-main');
|
||
if (mainElement) {
|
||
mainElement.innerHTML = html;
|
||
}
|
||
}
|
||
|
||
function showError(message) {
|
||
const mainElement = document.getElementById('app-main');
|
||
if (mainElement) {
|
||
mainElement.innerHTML = `
|
||
<div class="error-message">
|
||
<h2>Error</h2>
|
||
<p>${message}</p>
|
||
<button onclick="window.location.reload()">Reload Page</button>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
function updateDebugInfo() {
|
||
const debugStatus = document.getElementById('debug-status');
|
||
if (debugStatus && app) {
|
||
const status = app.getStatus();
|
||
debugStatus.innerHTML = `
|
||
<h4>System Status</h4>
|
||
<ul>
|
||
<li>Running: ${status.isRunning}</li>
|
||
<li>Uptime: ${Math.round(status.uptime / 1000)}s</li>
|
||
<li>Loaded Modules: ${status.modules.loaded.length}</li>
|
||
</ul>
|
||
`;
|
||
}
|
||
}
|
||
|
||
function setupDebugPanel() {
|
||
const debugToggle = document.getElementById('debug-toggle');
|
||
const debugPanel = document.getElementById('debug-panel');
|
||
|
||
if (debugToggle && debugPanel) {
|
||
debugToggle.addEventListener('click', () => {
|
||
debugPanel.style.display = 'none';
|
||
});
|
||
}
|
||
}
|
||
|
||
function setupKeyboardShortcuts() {
|
||
document.addEventListener('keydown', (event) => {
|
||
try {
|
||
// ESC to go back
|
||
if (event.key === 'Escape') {
|
||
const { router } = app.getCore();
|
||
if (router && !router.isCurrentRoute('/')) {
|
||
router.back();
|
||
}
|
||
}
|
||
|
||
} catch (error) {
|
||
// Ignore errors in keyboard shortcuts to prevent crashes
|
||
console.log('Keyboard shortcut error (safe to ignore):', error.message);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Connection status monitoring
|
||
function updateConnectionStatus() {
|
||
const statusIndicator = document.querySelector('.status-indicator');
|
||
const statusText = document.querySelector('.status-text');
|
||
|
||
if (navigator.onLine) {
|
||
if (statusIndicator) statusIndicator.className = 'status-indicator online';
|
||
if (statusText) statusText.textContent = 'Online';
|
||
} else {
|
||
if (statusIndicator) statusIndicator.className = 'status-indicator offline';
|
||
if (statusText) statusText.textContent = 'Offline';
|
||
}
|
||
}
|
||
|
||
// Monitor connection changes
|
||
window.addEventListener('online', updateConnectionStatus);
|
||
window.addEventListener('offline', updateConnectionStatus);
|
||
updateConnectionStatus(); // Initial check
|
||
|
||
// Export app globally for console access
|
||
window.app = app;
|
||
|
||
// Global navigation functions for buttons
|
||
window.navigateToHome = function() {
|
||
try {
|
||
const { router } = app.getCore();
|
||
router.navigate('/');
|
||
} catch (error) {
|
||
console.error('Navigation error:', error);
|
||
}
|
||
};
|
||
|
||
window.navigateToBooks = function() {
|
||
try {
|
||
const { router } = app.getCore();
|
||
router.navigate('/books');
|
||
} catch (error) {
|
||
console.error('Navigation error:', error);
|
||
}
|
||
};
|
||
|
||
window.navigateToChapters = function(bookId) {
|
||
try {
|
||
const { router } = app.getCore();
|
||
router.navigate(`/chapters/${bookId || ''}`);
|
||
} catch (error) {
|
||
console.error('Navigation error:', error);
|
||
}
|
||
};
|
||
|
||
window.navigateToGames = function(chapterId) {
|
||
try {
|
||
const { router } = app.getCore();
|
||
router.navigate(`/games/${chapterId || ''}`);
|
||
} catch (error) {
|
||
console.error('Navigation error:', error);
|
||
}
|
||
};
|
||
|
||
window.openPlaceholder = function() {
|
||
alert('Quick Play feature coming soon!');
|
||
};
|
||
|
||
window.openSettings = function() {
|
||
alert('Settings panel coming soon!');
|
||
};
|
||
|
||
window.navigateToDynamicRevision = function() {
|
||
try {
|
||
const { router } = app.getCore();
|
||
router.navigate('/dynamic-revision');
|
||
} catch (error) {
|
||
console.error('Navigation error:', error);
|
||
alert('Dynamic Revision System coming soon!');
|
||
}
|
||
};
|
||
|
||
window.filterBooksByLanguage = function(language) {
|
||
const books = document.querySelectorAll('.book-card');
|
||
books.forEach(book => {
|
||
const bookLanguage = book.getAttribute('data-language');
|
||
if (language === 'all' || bookLanguage === language) {
|
||
book.style.display = 'block';
|
||
} else {
|
||
book.style.display = 'none';
|
||
}
|
||
});
|
||
};
|
||
|
||
window.openBook = function(bookId) {
|
||
try {
|
||
const { router } = app.getCore();
|
||
router.navigate(`/chapters/${bookId}`);
|
||
} catch (error) {
|
||
console.error('Navigation error:', error);
|
||
}
|
||
};
|
||
|
||
window.launchGame = async function(gameId) {
|
||
try {
|
||
console.log('🎮 Launching game:', gameId);
|
||
|
||
// Get GameLoader instance
|
||
const gameLoader = app.getModule('gameLoader');
|
||
if (!gameLoader) {
|
||
throw new Error('GameLoader not available');
|
||
}
|
||
|
||
// Get current content
|
||
const content = contentLoader.getContent(currentChapterId);
|
||
if (!content) {
|
||
throw new Error('No content loaded for current chapter');
|
||
}
|
||
|
||
// Prepare game container
|
||
updateMainContent(`
|
||
<div class="game-container">
|
||
<div class="game-header">
|
||
<button class="btn btn-outline btn-sm" onclick="exitGame()">
|
||
<span class="btn-icon">←</span>
|
||
<span class="btn-text">Exit Game</span>
|
||
</button>
|
||
<h2>${gameId.replace(/[-_]/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}</h2>
|
||
<div class="game-loading">
|
||
<div class="loading-spinner">⚡</div>
|
||
<span>Loading game...</span>
|
||
</div>
|
||
</div>
|
||
<div id="game-area" class="game-area">
|
||
<!-- Le jeu sera rendu ici -->
|
||
</div>
|
||
</div>
|
||
`);
|
||
|
||
// Launch game through GameLoader
|
||
const instanceId = await gameLoader.launchGame(gameId, {
|
||
container: document.getElementById('game-area'),
|
||
config: {
|
||
container: document.getElementById('game-area')
|
||
},
|
||
dependencies: {
|
||
content: content
|
||
}
|
||
});
|
||
|
||
// Store instance ID for cleanup
|
||
window.currentGameInstance = instanceId;
|
||
|
||
// Hide loading indicator
|
||
const loadingElement = document.querySelector('.game-loading');
|
||
if (loadingElement) {
|
||
loadingElement.style.display = 'none';
|
||
}
|
||
|
||
console.log('✅ Game launched successfully:', instanceId);
|
||
|
||
} catch (error) {
|
||
console.error('Error launching game:', error);
|
||
updateMainContent(`
|
||
<div class="game-container">
|
||
<div class="game-header">
|
||
<button class="btn btn-outline btn-sm" onclick="navigateToGames(currentChapterId || 'unknown')">
|
||
<span class="btn-icon">←</span>
|
||
<span class="btn-text">Back to Games</span>
|
||
</button>
|
||
<h2>Game Error</h2>
|
||
</div>
|
||
<div class="error-message">
|
||
<p>Error loading game: ${gameId}</p>
|
||
<p>Error: ${error.message}</p>
|
||
</div>
|
||
</div>
|
||
`);
|
||
}
|
||
};
|
||
|
||
window.exitGame = function() {
|
||
try {
|
||
// Clean up current game instance
|
||
if (window.currentGameInstance) {
|
||
const gameLoader = app.getModule('gameLoader');
|
||
if (gameLoader) {
|
||
gameLoader.exitGame(window.currentGameInstance);
|
||
}
|
||
window.currentGameInstance = null;
|
||
}
|
||
|
||
// Navigate back to games
|
||
const { router } = app.getCore();
|
||
router.navigate(`/games/${currentChapterId || 'unknown'}`);
|
||
} catch (error) {
|
||
console.error('Navigation error:', error);
|
||
}
|
||
};
|
||
|
||
// Dynamic Revision System Functions
|
||
window.onBookSelected = async function(bookId) {
|
||
const chapterSelect = document.getElementById('chapter-select');
|
||
const startBtn = document.getElementById('start-revision-btn');
|
||
|
||
if (!bookId) {
|
||
chapterSelect.disabled = true;
|
||
chapterSelect.innerHTML = '<option value="">Choose a chapter...</option>';
|
||
startBtn.disabled = true;
|
||
startBtn.classList.add('btn-disabled');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Load chapters using ContentLoader
|
||
const chapters = contentLoader.getBookChapters(bookId);
|
||
|
||
chapterSelect.disabled = false;
|
||
chapterSelect.innerHTML = `
|
||
<option value="">Choose a chapter...</option>
|
||
${chapters.map(chapter =>
|
||
`<option value="${chapter.id}">${chapter.name} (${chapter.chapter_number})</option>`
|
||
).join('')}
|
||
`;
|
||
|
||
console.log(`📚 Loaded ${chapters.length} chapters for book ${bookId}`);
|
||
} catch (error) {
|
||
console.error('Error loading chapters:', error);
|
||
chapterSelect.innerHTML = '<option value="">Error loading chapters</option>';
|
||
}
|
||
};
|
||
|
||
window.onChapterSelected = async function(chapterId) {
|
||
const startBtn = document.getElementById('start-revision-btn');
|
||
const smartGuideBtn = document.getElementById('smart-guide-btn');
|
||
|
||
if (!chapterId) {
|
||
if (startBtn) {
|
||
startBtn.disabled = true;
|
||
startBtn.classList.add('btn-disabled');
|
||
}
|
||
if (smartGuideBtn) {
|
||
smartGuideBtn.disabled = true;
|
||
smartGuideBtn.classList.add('btn-disabled');
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Smart Guide is the primary button
|
||
if (smartGuideBtn) {
|
||
smartGuideBtn.disabled = false;
|
||
smartGuideBtn.classList.remove('btn-disabled');
|
||
}
|
||
// Manual revision is secondary
|
||
if (startBtn) {
|
||
startBtn.disabled = false;
|
||
startBtn.classList.remove('btn-disabled');
|
||
}
|
||
|
||
try {
|
||
// Load chapter content using ContentLoader
|
||
const chapterData = await contentLoader.loadContent(chapterId);
|
||
const stats = chapterData.statistics || {};
|
||
|
||
// Load progress information
|
||
const bookSelect = document.getElementById('book-select');
|
||
const bookId = bookSelect.value;
|
||
const progress = await getChapterProgress(bookId, chapterId);
|
||
const isCompleted = progress.completed;
|
||
const masteryCount = progress.masteryCount || 0;
|
||
|
||
// Count mastered items (handle both old string format and new object format)
|
||
const countMasteredItems = (items) => items ? items.length : 0;
|
||
const getLatestActivity = (items) => {
|
||
if (!items || items.length === 0) return null;
|
||
const latestEntry = items
|
||
.filter(entry => typeof entry === 'object' && entry.masteredAt)
|
||
.sort((a, b) => new Date(b.masteredAt) - new Date(a.masteredAt))[0];
|
||
return latestEntry ? latestEntry.masteredAt : null;
|
||
};
|
||
|
||
const vocabCount = countMasteredItems(progress.masteredVocabulary);
|
||
const phraseCount = countMasteredItems(progress.masteredPhrases);
|
||
const totalMastered = vocabCount + phraseCount;
|
||
const lastActivity = getLatestActivity([
|
||
...(progress.masteredVocabulary || []),
|
||
...(progress.masteredPhrases || [])
|
||
]);
|
||
|
||
// Create mastery badge and reset button
|
||
const masteryBadge = masteryCount > 0 ? `<span class="mastery-badge">🏆 Mastered ${masteryCount}x</span>` : '';
|
||
const completionBadge = isCompleted ? `<span class="completion-badge">✅ Completed</span>` : '';
|
||
const resetButton = isCompleted ? `
|
||
<button class="btn btn-warning btn-sm" onclick="resetChapterProgressUI('${bookId}', '${chapterId}')">
|
||
<span class="btn-icon">🔄</span>
|
||
<span class="btn-text">Reset & Master Again</span>
|
||
</button>
|
||
` : '';
|
||
|
||
// Update viewport with chapter preview and actual statistics
|
||
const viewport = document.querySelector('.revision-viewport');
|
||
if (viewport) {
|
||
viewport.innerHTML = `
|
||
<div class="chapter-preview">
|
||
<div class="preview-header">
|
||
<div class="chapter-title-row">
|
||
<h3>📚 Chapter: ${chapterData.name || chapterId}</h3>
|
||
<div class="chapter-badges">
|
||
${completionBadge}
|
||
${masteryBadge}
|
||
</div>
|
||
</div>
|
||
<p>Ready to start intelligent revision</p>
|
||
<div class="chapter-stats">
|
||
<span class="stat">📚 ${stats.vocabulary_count || 0} words (${vocabCount} mastered)</span>
|
||
<span class="stat">💬 ${stats.phrases_count || 0} phrases (${phraseCount} mastered)</span>
|
||
<span class="stat">📖 ${stats.dialogs_count || 0} dialogs</span>
|
||
<span class="stat">⏱️ ~${stats.estimated_completion_time || 25}min</span>
|
||
<span class="stat">📈 Total: ${totalMastered} items mastered</span>
|
||
${lastActivity ? `<span class="stat">🕐 Last activity: ${new Date(lastActivity).toLocaleDateString()}</span>` : ''}
|
||
</div>
|
||
<div class="chapter-actions">
|
||
${resetButton}
|
||
<button class="btn btn-outline btn-sm" onclick="showDataSyncUI('${bookId}', '${chapterId}')">
|
||
<span class="btn-icon">🔄</span>
|
||
<span class="btn-text">Sync Data</span>
|
||
</button>
|
||
<button class="btn btn-outline btn-sm" onclick="exportProgressData('${bookId}', '${chapterId}')">
|
||
<span class="btn-icon">💾</span>
|
||
<span class="btn-text">Export</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="exercise-preview-grid">
|
||
<div class="exercise-preview-card">
|
||
<div class="exercise-icon">📚</div>
|
||
<h4>Vocabulary</h4>
|
||
<p>${stats.vocabulary_count || 0} words in groups of 5</p>
|
||
</div>
|
||
<div class="exercise-preview-card">
|
||
<div class="exercise-icon">💬</div>
|
||
<h4>Phrases</h4>
|
||
<p>${stats.phrases_count || 0} phrases with context</p>
|
||
</div>
|
||
<div class="exercise-preview-card">
|
||
<div class="exercise-icon">📖</div>
|
||
<h4>Texts</h4>
|
||
<p>${stats.dialogs_count || 0} dialogs sentence-by-sentence</p>
|
||
</div>
|
||
<div class="exercise-preview-card">
|
||
<div class="exercise-icon">🎵</div>
|
||
<h4>Audio</h4>
|
||
<p>Listening comprehension</p>
|
||
</div>
|
||
<div class="exercise-preview-card">
|
||
<div class="exercise-icon">🖼️</div>
|
||
<h4>Images</h4>
|
||
<p>Description exercises</p>
|
||
</div>
|
||
<div class="exercise-preview-card">
|
||
<div class="exercise-icon">📝</div>
|
||
<h4>Grammar</h4>
|
||
<p>Construction and validation</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
console.log(`📖 Loaded chapter data for ${chapterId}:`, stats);
|
||
} catch (error) {
|
||
console.error('Error loading chapter data:', error);
|
||
|
||
// Fallback to basic preview
|
||
const viewport = document.querySelector('.revision-viewport');
|
||
if (viewport) {
|
||
viewport.innerHTML = `
|
||
<div class="chapter-preview">
|
||
<div class="preview-header">
|
||
<h3>📚 Chapter: ${chapterId}</h3>
|
||
<p>Ready to start intelligent revision</p>
|
||
<div class="error-message">Could not load chapter statistics</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
};
|
||
|
||
window.startRevision = async function() {
|
||
const bookSelect = document.getElementById('book-select');
|
||
const chapterSelect = document.getElementById('chapter-select');
|
||
|
||
const bookId = bookSelect.value;
|
||
const chapterId = chapterSelect.value;
|
||
|
||
if (!bookId || !chapterId) {
|
||
alert('Please select both a book and a chapter first.');
|
||
return;
|
||
}
|
||
|
||
// Get selected exercise type and difficulty
|
||
const selectedTypeBtn = document.querySelector('.exercise-type-btn.active');
|
||
const selectedDifficultyBtn = document.querySelector('.difficulty-btn.active');
|
||
|
||
const exerciseType = selectedTypeBtn ? selectedTypeBtn.dataset.type : 'text';
|
||
const difficulty = selectedDifficultyBtn ? selectedDifficultyBtn.dataset.difficulty : 'medium';
|
||
|
||
// Get UnifiedDRS from the application
|
||
const moduleLoader = window.app.getCore().moduleLoader;
|
||
const unifiedDRS = moduleLoader.getModule('unifiedDRS');
|
||
|
||
if (!unifiedDRS) {
|
||
alert('UnifiedDRS is not initialized. Please refresh the page.');
|
||
return;
|
||
}
|
||
|
||
// Show loading state
|
||
const viewport = document.querySelector('.revision-viewport');
|
||
if (viewport) {
|
||
viewport.innerHTML = `
|
||
<div class="revision-active">
|
||
<div class="revision-status">
|
||
<h3>🚀 Starting Dynamic Revision...</h3>
|
||
<p><strong>Book:</strong> ${bookSelect.options[bookSelect.selectedIndex].text}</p>
|
||
<p><strong>Chapter:</strong> ${chapterSelect.options[chapterSelect.selectedIndex].text}</p>
|
||
<p><strong>Exercise:</strong> ${exerciseType.charAt(0).toUpperCase() + exerciseType.slice(1)} (${difficulty})</p>
|
||
<div class="loading-spinner">🧠</div>
|
||
<p>Initializing Unified DRS System...</p>
|
||
</div>
|
||
<div id="drs-exercise-container">
|
||
<!-- Exercise modules will be loaded here -->
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
try {
|
||
// Save the selection as last used
|
||
saveLastUsedSelection(bookId, chapterId);
|
||
|
||
// Start revision session with UnifiedDRS
|
||
const exerciseContainer = document.getElementById('drs-exercise-container');
|
||
|
||
console.log(`🎯 Starting ${exerciseType} exercise (${difficulty}) for ${bookId}/${chapterId}`);
|
||
|
||
await unifiedDRS.start(exerciseContainer, {
|
||
type: exerciseType,
|
||
difficulty: difficulty,
|
||
bookId: bookId,
|
||
chapterId: chapterId,
|
||
context: 'dynamic-revision'
|
||
});
|
||
|
||
// Hide loading, show exercise container
|
||
const loadingDiv = viewport.querySelector('.revision-status');
|
||
if (loadingDiv) {
|
||
loadingDiv.style.display = 'none';
|
||
}
|
||
|
||
console.log('✅ Dynamic Revision Session started with UnifiedDRS');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error starting revision session:', error);
|
||
|
||
// Show error state
|
||
if (viewport) {
|
||
viewport.innerHTML = `
|
||
<div class="revision-error">
|
||
<div class="error-icon">❌</div>
|
||
<h3>Failed to Start Revision Session</h3>
|
||
<p><strong>Error:</strong> ${error.message}</p>
|
||
<p><strong>Type:</strong> ${exerciseType} (${difficulty})</p>
|
||
<button class="btn btn-outline" onclick="location.reload()">
|
||
<span class="btn-icon">🔄</span>
|
||
<span class="btn-text">Reload Page</span>
|
||
</button>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
};
|
||
|
||
// Exercise type and difficulty selection handlers
|
||
window.setupExerciseTypeHandlers = function() {
|
||
const exerciseTypeButtons = document.querySelectorAll('.exercise-type-btn');
|
||
|
||
exerciseTypeButtons.forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
// Remove active class from all buttons
|
||
exerciseTypeButtons.forEach(b => b.classList.remove('active'));
|
||
|
||
// Add active class to clicked button
|
||
btn.classList.add('active');
|
||
|
||
console.log(`📋 Selected exercise type: ${btn.dataset.type}`);
|
||
});
|
||
});
|
||
};
|
||
|
||
window.setupDifficultyHandlers = function() {
|
||
const difficultyButtons = document.querySelectorAll('.difficulty-btn');
|
||
|
||
difficultyButtons.forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
// Remove active class from all buttons
|
||
difficultyButtons.forEach(b => b.classList.remove('active'));
|
||
|
||
// Add active class to clicked button
|
||
btn.classList.add('active');
|
||
|
||
console.log(`🎯 Selected difficulty: ${btn.dataset.difficulty}`);
|
||
});
|
||
});
|
||
};
|
||
|
||
// Last Use functionality for DRS
|
||
window.saveLastUsedSelection = function(bookId, chapterId) {
|
||
const lastUse = {
|
||
bookId: bookId,
|
||
chapterId: chapterId,
|
||
timestamp: new Date().toISOString()
|
||
};
|
||
localStorage.setItem('drs-last-use', JSON.stringify(lastUse));
|
||
console.log('💾 Saved last used selection:', lastUse);
|
||
};
|
||
|
||
window.getLastUsedSelection = function() {
|
||
try {
|
||
const lastUse = localStorage.getItem('drs-last-use');
|
||
return lastUse ? JSON.parse(lastUse) : null;
|
||
} catch (error) {
|
||
console.error('Error loading last use:', error);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
// Test timestamped progress tracking
|
||
window.testTimestamps = async function() {
|
||
console.log('🕐 Testing timestamped progress tracking...');
|
||
|
||
try {
|
||
const bookId = 'test-book';
|
||
const chapterId = 'test-chapter';
|
||
|
||
// Test adding items with timestamps and metadata
|
||
console.log('📤 Adding vocabulary item...');
|
||
await addMasteredItem(bookId, chapterId, 'vocabulary', 'hello', {
|
||
difficulty: 'easy',
|
||
responseTime: 1200
|
||
});
|
||
|
||
console.log('📤 Adding phrase item...');
|
||
await addMasteredItem(bookId, chapterId, 'phrases', 'how are you', {
|
||
difficulty: 'medium',
|
||
aiScore: 95,
|
||
responseTime: 3400
|
||
});
|
||
|
||
// Test updating existing item
|
||
console.log('🔄 Re-adding same vocabulary (should update)...');
|
||
await addMasteredItem(bookId, chapterId, 'vocabulary', 'hello', {
|
||
difficulty: 'easy',
|
||
responseTime: 800
|
||
});
|
||
|
||
// Load and display
|
||
console.log('📥 Loading timestamped progress...');
|
||
const progress = await getChapterProgress(bookId, chapterId);
|
||
|
||
console.log('✅ Timestamped progress loaded:', progress);
|
||
|
||
// Clean up
|
||
setTimeout(async () => {
|
||
try {
|
||
await fetch('/api/progress/save', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
system: 'drs',
|
||
bookId,
|
||
chapterId,
|
||
progressData: { masteredVocabulary: [], masteredPhrases: [], masteredGrammar: [] }
|
||
})
|
||
});
|
||
console.log('🧹 Test data cleaned');
|
||
} catch (e) { console.log('Clean up failed:', e); }
|
||
}, 5000);
|
||
|
||
return true;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Timestamp test failed:', error);
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// Test progress API functionality
|
||
window.testProgressAPI = async function() {
|
||
console.log('🧪 Testing Progress API...');
|
||
|
||
try {
|
||
// Test save
|
||
const testData = {
|
||
masteredVocabulary: ['hello', 'world', 'test'],
|
||
masteredPhrases: ['phrase1', 'phrase2'],
|
||
masteryCount: 3,
|
||
completed: true
|
||
};
|
||
|
||
console.log('📤 Testing save...');
|
||
await saveChapterProgress('test-book', 'test-chapter', testData);
|
||
|
||
// Test load
|
||
console.log('📥 Testing load...');
|
||
const loadedData = await getChapterProgress('test-book', 'test-chapter');
|
||
|
||
console.log('✅ Progress API test successful!', loadedData);
|
||
return true;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Progress API test failed:', error);
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// Test localStorage functionality
|
||
window.testLocalStorage = function() {
|
||
try {
|
||
// Test write
|
||
const testData = {
|
||
vocabulary: ['hello', 'world', 'test'],
|
||
phrases: ['phrase1', 'phrase2'],
|
||
masteryCount: 3,
|
||
completed: true,
|
||
timestamp: new Date().toISOString()
|
||
};
|
||
|
||
localStorage.setItem('drs-test', JSON.stringify(testData));
|
||
console.log('✅ localStorage WRITE successful:', testData);
|
||
|
||
// Test read
|
||
const readData = JSON.parse(localStorage.getItem('drs-test'));
|
||
console.log('✅ localStorage READ successful:', readData);
|
||
|
||
// Test existence check
|
||
const exists = localStorage.getItem('drs-test') !== null;
|
||
console.log('✅ localStorage EXISTS check:', exists);
|
||
|
||
// Cleanup
|
||
localStorage.removeItem('drs-test');
|
||
console.log('✅ localStorage DELETE successful');
|
||
|
||
return true;
|
||
} catch (error) {
|
||
console.error('❌ localStorage test failed:', error);
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// DRS Progress Management Functions (Server-side storage)
|
||
window.saveChapterProgress = async function(bookId, chapterId, progressData) {
|
||
try {
|
||
const response = await fetch('/api/progress/save', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
system: 'drs',
|
||
bookId,
|
||
chapterId,
|
||
progressData
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
console.log(`💾 Saved progress to file: ${result.filename}`, progressData);
|
||
return progressData;
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Failed to save progress for ${bookId}/${chapterId}:`, error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
window.getChapterProgress = async function(bookId, chapterId) {
|
||
try {
|
||
const response = await fetch(`/api/progress/load/drs/${bookId}/${chapterId}`);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const progress = await response.json();
|
||
console.log(`📁 Loaded progress from file: ${bookId}/${chapterId}`, progress);
|
||
return progress;
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Failed to load progress for ${bookId}/${chapterId}:`, error);
|
||
|
||
// Return empty progress on error
|
||
return {
|
||
masteredVocabulary: [],
|
||
masteredPhrases: [],
|
||
masteredGrammar: [],
|
||
completed: false,
|
||
masteryCount: 0,
|
||
system: 'drs',
|
||
bookId,
|
||
chapterId
|
||
};
|
||
}
|
||
};
|
||
|
||
window.isChapterCompleted = async function(bookId, chapterId) {
|
||
const progress = await getChapterProgress(bookId, chapterId);
|
||
return progress && progress.completed === true;
|
||
};
|
||
|
||
window.getChapterMasteryCount = async function(bookId, chapterId) {
|
||
const progress = await getChapterProgress(bookId, chapterId);
|
||
return progress ? progress.masteryCount || 0 : 0;
|
||
};
|
||
|
||
window.resetChapterProgress = async function(bookId, chapterId) {
|
||
const isCompleted = await isChapterCompleted(bookId, chapterId);
|
||
if (!isCompleted) {
|
||
throw new Error("Cannot reset: Chapter not completed yet");
|
||
}
|
||
|
||
const progress = await getChapterProgress(bookId, chapterId);
|
||
const resetProgress = {
|
||
masteredVocabulary: [],
|
||
masteredPhrases: [],
|
||
masteredGrammar: [],
|
||
completed: false,
|
||
masteryCount: (progress.masteryCount || 0) + 1,
|
||
resetAt: new Date().toISOString(),
|
||
createdAt: progress.createdAt,
|
||
previousCompletions: progress.previousCompletions || []
|
||
};
|
||
|
||
// Add completion to history
|
||
if (progress.completedAt) {
|
||
resetProgress.previousCompletions.push({
|
||
completedAt: progress.completedAt,
|
||
masteryNumber: progress.masteryCount || 0
|
||
});
|
||
}
|
||
|
||
await saveChapterProgress(bookId, chapterId, resetProgress);
|
||
console.log(`🔄 Reset progress for ${bookId}/${chapterId} - Mastery count: ${resetProgress.masteryCount}`);
|
||
return resetProgress;
|
||
};
|
||
|
||
window.markChapterCompleted = async function(bookId, chapterId) {
|
||
const progress = await getChapterProgress(bookId, chapterId);
|
||
const completedProgress = {
|
||
...progress,
|
||
completed: true,
|
||
completedAt: new Date().toISOString()
|
||
};
|
||
|
||
return await saveChapterProgress(bookId, chapterId, completedProgress);
|
||
};
|
||
|
||
window.addMasteredItem = async function(bookId, chapterId, type, item, metadata = {}) {
|
||
const progress = await getChapterProgress(bookId, chapterId);
|
||
const key = `mastered${type.charAt(0).toUpperCase() + type.slice(1)}`;
|
||
|
||
// Convert old format (strings) to new format (objects) if needed
|
||
if (!Array.isArray(progress[key])) {
|
||
progress[key] = [];
|
||
}
|
||
|
||
// Check if item exists (handle both old string format and new object format)
|
||
const existingItem = progress[key].find(entry => {
|
||
if (typeof entry === 'string') return entry === item;
|
||
return entry.item === item;
|
||
});
|
||
|
||
if (!existingItem) {
|
||
const masteredEntry = {
|
||
item: item,
|
||
masteredAt: new Date().toISOString(),
|
||
attempts: 1,
|
||
...metadata
|
||
};
|
||
|
||
progress[key].push(masteredEntry);
|
||
await saveChapterProgress(bookId, chapterId, progress);
|
||
console.log(`✅ Added ${type}: ${item} to ${bookId}/${chapterId} at ${masteredEntry.masteredAt}`);
|
||
} else if (typeof existingItem === 'object') {
|
||
// Update existing entry with new attempt
|
||
existingItem.lastReviewAt = new Date().toISOString();
|
||
existingItem.attempts = (existingItem.attempts || 1) + 1;
|
||
await saveChapterProgress(bookId, chapterId, progress);
|
||
console.log(`🔄 Updated ${type}: ${item} - attempt #${existingItem.attempts}`);
|
||
}
|
||
};
|
||
|
||
window.loadLastUsedSelection = async function() {
|
||
const lastUse = getLastUsedSelection();
|
||
if (!lastUse) {
|
||
console.log('📋 No last used selection found');
|
||
return;
|
||
}
|
||
|
||
console.log('🔄 Loading last used selection:', lastUse);
|
||
|
||
const bookSelect = document.getElementById('book-select');
|
||
const chapterSelect = document.getElementById('chapter-select');
|
||
|
||
if (!bookSelect || !chapterSelect) {
|
||
console.log('⚠️ Selection elements not found, skipping auto-load');
|
||
return;
|
||
}
|
||
|
||
// Set book selection
|
||
bookSelect.value = lastUse.bookId;
|
||
|
||
// Trigger book selection to load chapters
|
||
await onBookSelected(lastUse.bookId);
|
||
|
||
// Wait a moment for chapters to load, then set chapter
|
||
setTimeout(() => {
|
||
chapterSelect.value = lastUse.chapterId;
|
||
// Trigger chapter selection to load preview
|
||
onChapterSelected(lastUse.chapterId);
|
||
}, 100);
|
||
|
||
console.log('✅ Last used selection loaded successfully');
|
||
};
|
||
|
||
// Reset chapter progress with confirmation and UI update
|
||
window.resetChapterProgressUI = async function(bookId, chapterId) {
|
||
const progress = await getChapterProgress(bookId, chapterId);
|
||
|
||
if (!progress.completed) {
|
||
alert('This chapter is not completed yet. You can only reset completed chapters.');
|
||
return;
|
||
}
|
||
|
||
const confirmation = confirm(
|
||
`Are you sure you want to reset the progress for this chapter?\n\n` +
|
||
`This will:\n` +
|
||
`• Clear all mastered vocabulary (${progress.masteredVocabulary.length} items)\n` +
|
||
`• Clear all mastered phrases (${progress.masteredPhrases.length} items)\n` +
|
||
`• Increment mastery counter to ${(progress.masteryCount || 0) + 1}\n` +
|
||
`• Allow you to practice this chapter again\n\n` +
|
||
`This action cannot be undone.`
|
||
);
|
||
|
||
if (confirmation) {
|
||
try {
|
||
const resetResult = await resetChapterProgress(bookId, chapterId);
|
||
|
||
// Show success message
|
||
alert(`✅ Chapter progress reset successfully!\n\nMastery Count: ${resetResult.masteryCount}\nYou can now practice this chapter again.`);
|
||
|
||
// Refresh the chapter preview
|
||
await onChapterSelected(chapterId);
|
||
|
||
} catch (error) {
|
||
alert(`❌ Failed to reset progress: ${error.message}`);
|
||
console.error('Reset failed:', error);
|
||
}
|
||
}
|
||
};
|
||
|
||
// Data merge functions for combining local and external progress
|
||
window.mergeProgressData = async function(bookId, chapterId, externalData, mergeStrategy = 'timestamp') {
|
||
try {
|
||
console.log(`🔄 Merging progress data for ${bookId}/${chapterId} with strategy: ${mergeStrategy}`);
|
||
|
||
// Get current local data
|
||
const localData = await getChapterProgress(bookId, chapterId);
|
||
|
||
// Call merge API
|
||
const response = await fetch('/api/progress/merge', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
system: 'drs',
|
||
bookId,
|
||
chapterId,
|
||
localData,
|
||
externalData,
|
||
mergeStrategy
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`Merge failed: ${response.statusText}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (!result.success) {
|
||
throw new Error(result.error || 'Merge operation failed');
|
||
}
|
||
|
||
console.log('✅ Data merge completed:', result.mergeInfo);
|
||
return result;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Data merge failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
window.getSyncStatus = async function() {
|
||
try {
|
||
const response = await fetch('/api/progress/sync-status');
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`Sync status failed: ${response.statusText}`);
|
||
}
|
||
|
||
const status = await response.json();
|
||
console.log('📊 Sync status:', status);
|
||
return status;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Sync status failed:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// Import external progress data (from file, URL, or text)
|
||
window.importExternalProgress = async function(bookId, chapterId, source, sourceData) {
|
||
try {
|
||
console.log(`📥 Importing external progress from ${source}`);
|
||
|
||
let externalData;
|
||
|
||
// Handle different source types
|
||
switch (source) {
|
||
case 'file':
|
||
// sourceData is File object
|
||
const fileContent = await sourceData.text();
|
||
externalData = JSON.parse(fileContent);
|
||
break;
|
||
|
||
case 'url':
|
||
// sourceData is URL string
|
||
const response = await fetch(sourceData);
|
||
if (!response.ok) throw new Error('Failed to fetch from URL');
|
||
externalData = await response.json();
|
||
break;
|
||
|
||
case 'text':
|
||
// sourceData is JSON string
|
||
externalData = JSON.parse(sourceData);
|
||
break;
|
||
|
||
default:
|
||
throw new Error('Invalid source type. Use: file, url, or text');
|
||
}
|
||
|
||
// Validate external data
|
||
if (!externalData || typeof externalData !== 'object') {
|
||
throw new Error('Invalid external data format');
|
||
}
|
||
|
||
// Show merge preview
|
||
const localData = await getChapterProgress(bookId, chapterId);
|
||
const mergePreview = {
|
||
local: {
|
||
vocab: localData.masteredVocabulary?.length || 0,
|
||
phrases: localData.masteredPhrases?.length || 0,
|
||
grammar: localData.masteredGrammar?.length || 0
|
||
},
|
||
external: {
|
||
vocab: externalData.masteredVocabulary?.length || 0,
|
||
phrases: externalData.masteredPhrases?.length || 0,
|
||
grammar: externalData.masteredGrammar?.length || 0
|
||
}
|
||
};
|
||
|
||
console.log('📋 Merge preview:', mergePreview);
|
||
|
||
// Confirm merge operation
|
||
const confirmation = confirm(
|
||
`Merge External Progress?\n\n` +
|
||
`Local Data:\n` +
|
||
`• ${mergePreview.local.vocab} vocabulary items\n` +
|
||
`• ${mergePreview.local.phrases} phrases\n` +
|
||
`• ${mergePreview.local.grammar} grammar concepts\n\n` +
|
||
`External Data:\n` +
|
||
`• ${mergePreview.external.vocab} vocabulary items\n` +
|
||
`• ${mergePreview.external.phrases} phrases\n` +
|
||
`• ${mergePreview.external.grammar} grammar concepts\n\n` +
|
||
`Merge strategy: Newest timestamps win\n` +
|
||
`Conflicts will be resolved automatically.\n\n` +
|
||
`Continue with merge?`
|
||
);
|
||
|
||
if (!confirmation) {
|
||
console.log('📋 Merge cancelled by user');
|
||
return { cancelled: true };
|
||
}
|
||
|
||
// Perform merge
|
||
const mergeResult = await mergeProgressData(bookId, chapterId, externalData, 'timestamp');
|
||
|
||
// Show merge results
|
||
const message = `✅ Merge Completed!\n\n` +
|
||
`Total Items: ${mergeResult.mergeInfo.totalItems}\n` +
|
||
`Conflicts Resolved: ${mergeResult.mergeInfo.conflicts.length}\n` +
|
||
`Merge Strategy: ${mergeResult.mergeInfo.strategy}\n\n` +
|
||
`Your progress has been updated with the merged data.`;
|
||
|
||
alert(message);
|
||
|
||
return mergeResult;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Import failed:', error);
|
||
alert(`❌ Import Failed: ${error.message}`);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// Export current progress for backup or sharing
|
||
window.exportProgressData = async function(bookId, chapterId) {
|
||
try {
|
||
const progressData = await getChapterProgress(bookId, chapterId);
|
||
|
||
// Add export metadata
|
||
const exportData = {
|
||
...progressData,
|
||
exportedAt: new Date().toISOString(),
|
||
exportVersion: '1.0',
|
||
source: 'class-generator-drs'
|
||
};
|
||
|
||
// Create downloadable file
|
||
const dataStr = JSON.stringify(exportData, null, 2);
|
||
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
||
|
||
// Create download link
|
||
const url = URL.createObjectURL(dataBlob);
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.download = `drs-progress-${bookId}-${chapterId}.json`;
|
||
|
||
// Trigger download
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
URL.revokeObjectURL(url);
|
||
|
||
console.log('✅ Progress exported successfully');
|
||
return exportData;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Export failed:', error);
|
||
alert(`❌ Export Failed: ${error.message}`);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// Show data sync UI modal
|
||
window.showDataSyncUI = async function(bookId, chapterId) {
|
||
try {
|
||
// Get sync status and current progress
|
||
const syncStatus = await getSyncStatus();
|
||
const currentProgress = await getChapterProgress(bookId, chapterId);
|
||
|
||
// Create modal HTML
|
||
const modalHTML = `
|
||
<div id="sync-modal" class="sync-modal" onclick="if(event.target === this) closeSyncModal()">
|
||
<div class="sync-modal-content">
|
||
<div class="sync-modal-header">
|
||
<h3>🔄 Data Synchronization</h3>
|
||
<button onclick="closeSyncModal()" class="close-btn">×</button>
|
||
</div>
|
||
|
||
<div class="sync-tabs">
|
||
<button class="sync-tab active" onclick="showSyncTab('import')">📥 Import</button>
|
||
<button class="sync-tab" onclick="showSyncTab('export')">📤 Export</button>
|
||
<button class="sync-tab" onclick="showSyncTab('status')">📊 Status</button>
|
||
</div>
|
||
|
||
<div id="import-tab" class="sync-tab-content">
|
||
<h4>Import External Progress</h4>
|
||
<p>Merge progress data from another device or backup:</p>
|
||
|
||
<div class="import-options">
|
||
<div class="import-option">
|
||
<h5>📁 From File</h5>
|
||
<input type="file" id="file-input" accept=".json" style="display: none;" onchange="handleFileImport(event, '${bookId}', '${chapterId}')">
|
||
<button class="btn btn-primary" onclick="document.getElementById('file-input').click()">
|
||
Choose File
|
||
</button>
|
||
</div>
|
||
|
||
<div class="import-option">
|
||
<h5>🌐 From URL</h5>
|
||
<input type="url" id="url-input" placeholder="https://example.com/progress.json" style="width: 100%; margin-bottom: 10px;">
|
||
<button class="btn btn-primary" onclick="handleUrlImport('${bookId}', '${chapterId}')">
|
||
Import from URL
|
||
</button>
|
||
</div>
|
||
|
||
<div class="import-option">
|
||
<h5>📝 From Text</h5>
|
||
<textarea id="text-input" placeholder="Paste JSON progress data here..." rows="4" style="width: 100%; margin-bottom: 10px;"></textarea>
|
||
<button class="btn btn-primary" onclick="handleTextImport('${bookId}', '${chapterId}')">
|
||
Import from Text
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="export-tab" class="sync-tab-content" style="display: none;">
|
||
<h4>Export Current Progress</h4>
|
||
<div class="current-progress-info">
|
||
<p><strong>Current Progress:</strong></p>
|
||
<ul>
|
||
<li>📚 ${currentProgress.masteredVocabulary?.length || 0} vocabulary items</li>
|
||
<li>💬 ${currentProgress.masteredPhrases?.length || 0} phrases</li>
|
||
<li>📝 ${currentProgress.masteredGrammar?.length || 0} grammar concepts</li>
|
||
<li>🏆 Mastery count: ${currentProgress.masteryCount || 0}</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<button class="btn btn-primary" onclick="exportProgressData('${bookId}', '${chapterId}'); closeSyncModal();">
|
||
💾 Download Progress File
|
||
</button>
|
||
|
||
<div class="export-note">
|
||
<p><em>This will download a JSON file with all your progress data that can be imported on another device.</em></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="status-tab" class="sync-tab-content" style="display: none;">
|
||
<h4>Synchronization Status</h4>
|
||
|
||
<div class="status-overview">
|
||
<p><strong>Local Files:</strong> ${syncStatus.totalFiles} saved progress files</p>
|
||
<p><strong>Last Activity:</strong> ${syncStatus.lastSync ? new Date(syncStatus.lastSync).toLocaleString() : 'Never'}</p>
|
||
</div>
|
||
|
||
<div class="saved-files-list">
|
||
<h5>Saved Progress Files:</h5>
|
||
${syncStatus.savedFiles.length > 0 ?
|
||
syncStatus.savedFiles.map(file => `
|
||
<div class="saved-file-item">
|
||
<div class="file-info">
|
||
<strong>${file.filename}</strong>
|
||
<span class="file-meta">
|
||
${file.itemCount} items |
|
||
${file.hasTimestamps ? '🕐 Timestamped' : '📝 Basic'} |
|
||
${new Date(file.savedAt).toLocaleDateString()}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
`).join('') :
|
||
'<p><em>No saved files found</em></p>'
|
||
}
|
||
</div>
|
||
|
||
<button class="btn btn-secondary" onclick="testDataMerge()">
|
||
🧪 Test Merge Function
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Add modal to page
|
||
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
||
|
||
console.log('📱 Data sync UI opened');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Failed to show sync UI:', error);
|
||
alert(`Failed to show sync interface: ${error.message}`);
|
||
}
|
||
};
|
||
|
||
// Close sync modal
|
||
window.closeSyncModal = function() {
|
||
const modal = document.getElementById('sync-modal');
|
||
if (modal) {
|
||
modal.remove();
|
||
}
|
||
};
|
||
|
||
// Switch sync tabs
|
||
window.showSyncTab = function(tabName) {
|
||
// Update tab buttons
|
||
document.querySelectorAll('.sync-tab').forEach(tab => {
|
||
tab.classList.remove('active');
|
||
});
|
||
event.target.classList.add('active');
|
||
|
||
// Update tab content
|
||
document.querySelectorAll('.sync-tab-content').forEach(content => {
|
||
content.style.display = 'none';
|
||
});
|
||
document.getElementById(`${tabName}-tab`).style.display = 'block';
|
||
};
|
||
|
||
// Import handlers
|
||
window.handleFileImport = async function(event, bookId, chapterId) {
|
||
try {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
const result = await importExternalProgress(bookId, chapterId, 'file', file);
|
||
|
||
if (!result.cancelled) {
|
||
closeSyncModal();
|
||
// Refresh chapter preview
|
||
await onChapterSelected(chapterId);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ File import failed:', error);
|
||
}
|
||
};
|
||
|
||
window.handleUrlImport = async function(bookId, chapterId) {
|
||
try {
|
||
const url = document.getElementById('url-input').value.trim();
|
||
if (!url) {
|
||
alert('Please enter a URL');
|
||
return;
|
||
}
|
||
|
||
const result = await importExternalProgress(bookId, chapterId, 'url', url);
|
||
|
||
if (!result.cancelled) {
|
||
closeSyncModal();
|
||
await onChapterSelected(chapterId);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ URL import failed:', error);
|
||
}
|
||
};
|
||
|
||
window.handleTextImport = async function(bookId, chapterId) {
|
||
try {
|
||
const text = document.getElementById('text-input').value.trim();
|
||
if (!text) {
|
||
alert('Please paste JSON data');
|
||
return;
|
||
}
|
||
|
||
const result = await importExternalProgress(bookId, chapterId, 'text', text);
|
||
|
||
if (!result.cancelled) {
|
||
closeSyncModal();
|
||
await onChapterSelected(chapterId);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Text import failed:', error);
|
||
}
|
||
};
|
||
|
||
// Test AI provider connectivity
|
||
window.testAIConnectivity = async function() {
|
||
try {
|
||
console.log('🔍 Testing AI provider connectivity...');
|
||
|
||
// Try to get the IAEngine instance from the DRS system
|
||
let iaEngine = null;
|
||
|
||
// Test if the DRS SmartPreviewOrchestrator has been created
|
||
if (window.drsOrchestrator && window.drsOrchestrator.llmValidator && window.drsOrchestrator.llmValidator.iaEngine) {
|
||
iaEngine = window.drsOrchestrator.llmValidator.iaEngine;
|
||
} else {
|
||
console.log('⚠️ DRS not initialized, creating temporary IAEngine for testing...');
|
||
// Dynamically import and create IAEngine for testing
|
||
const IAEngineModule = await import('./src/DRS/services/IAEngine.js');
|
||
const IAEngine = IAEngineModule.default;
|
||
iaEngine = new IAEngine({ debug: true });
|
||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for API keys
|
||
}
|
||
|
||
// Test all providers
|
||
const results = await iaEngine.testAllProvidersConnectivity();
|
||
|
||
console.log('🧪 AI Connectivity Test Results:');
|
||
console.log('================================');
|
||
console.log(`AI System Status: ${results.aiDisabled ? '🚫 DISABLED' : '✅ ACTIVE'}`);
|
||
if (results.disableReason) {
|
||
console.log(`Disable Reason: ${results.disableReason}`);
|
||
}
|
||
console.log(`Available Providers: ${results.availableProviders.join(', ') || 'None'}`);
|
||
console.log('\nProvider Details:');
|
||
|
||
Object.entries(results.results).forEach(([provider, result]) => {
|
||
console.log(` ${provider.toUpperCase()}: ${result.success ? '✅ SUCCESS' : '❌ FAILED'} (${result.duration || 0}ms)`);
|
||
if (!result.success) {
|
||
console.log(` Error: ${result.error}`);
|
||
}
|
||
});
|
||
|
||
console.log('\nProvider Status:');
|
||
Object.entries(results.providerStatus).forEach(([provider, status]) => {
|
||
console.log(` ${provider.toUpperCase()}:`);
|
||
console.log(` Available: ${status.available !== null ? (status.available ? '✅ Yes' : '❌ No') : '❓ Unknown'}`);
|
||
console.log(` Last Tested: ${status.lastTested || 'Never'}`);
|
||
console.log(` Consecutive Failures: ${status.consecutiveFailures || 0}`);
|
||
if (status.lastError) {
|
||
console.log(` Last Error: ${status.lastError}`);
|
||
}
|
||
});
|
||
|
||
// Show user-friendly alert
|
||
const message = results.aiDisabled
|
||
? `🚫 AI System Disabled\n\nReason: ${results.disableReason}\n\nAll AI features will use mock mode.`
|
||
: `✅ AI System Active\n\nAvailable Providers:\n${results.availableProviders.map(p => `• ${p.toUpperCase()}`).join('\n')}\n\nAI validation will work normally.`;
|
||
|
||
alert(message);
|
||
|
||
return results;
|
||
|
||
} catch (error) {
|
||
console.error('❌ AI connectivity test failed:', error);
|
||
alert(`❌ AI Test Failed\n\nError: ${error.message}\n\nPlease check the console for more details.`);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// Test data merge functionality
|
||
window.testDataMerge = async function() {
|
||
try {
|
||
const bookId = 'test-book';
|
||
const chapterId = 'test-chapter';
|
||
|
||
console.log('🧪 Testing data merge functionality...');
|
||
|
||
// Create test local data
|
||
await addMasteredItem(bookId, chapterId, 'vocabulary', 'local-word', {
|
||
sessionId: 'local-session',
|
||
difficulty: 'easy'
|
||
});
|
||
|
||
// Create test external data
|
||
const externalData = {
|
||
masteredVocabulary: [
|
||
{
|
||
item: 'external-word',
|
||
masteredAt: new Date().toISOString(),
|
||
attempts: 1,
|
||
sessionId: 'external-session',
|
||
difficulty: 'hard'
|
||
},
|
||
{
|
||
item: 'local-word', // Same word as local - will create conflict
|
||
masteredAt: new Date(Date.now() + 60000).toISOString(), // Newer timestamp
|
||
attempts: 2,
|
||
sessionId: 'external-session',
|
||
difficulty: 'good'
|
||
}
|
||
],
|
||
masteredPhrases: [
|
||
{
|
||
item: 'external phrase',
|
||
masteredAt: new Date().toISOString(),
|
||
attempts: 1,
|
||
moduleType: 'phrase'
|
||
}
|
||
],
|
||
masteredGrammar: [],
|
||
completed: false,
|
||
masteryCount: 0,
|
||
system: 'drs',
|
||
bookId,
|
||
chapterId
|
||
};
|
||
|
||
// Test merge
|
||
const mergeResult = await mergeProgressData(bookId, chapterId, externalData, 'timestamp');
|
||
|
||
console.log('✅ Data merge test completed:', mergeResult.mergeInfo);
|
||
alert(`✅ Data Merge Test Completed!\n\nTotal Items: ${mergeResult.mergeInfo.totalItems}\nConflicts: ${mergeResult.mergeInfo.conflicts.length}`);
|
||
|
||
return mergeResult;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Data merge test failed:', error);
|
||
alert(`❌ Test Failed: ${error.message}`);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// Smart Guide functionality - Intelligent exercise sequencing
|
||
let currentGuidedSession = null;
|
||
|
||
window.startSmartGuide = async function() {
|
||
const bookSelect = document.getElementById('book-select');
|
||
const chapterSelect = document.getElementById('chapter-select');
|
||
|
||
const bookId = bookSelect.value;
|
||
const chapterId = chapterSelect.value;
|
||
|
||
if (!bookId || !chapterId) {
|
||
alert('Please select both a book and a chapter first.');
|
||
return;
|
||
}
|
||
|
||
// Get IntelligentSequencer from the application
|
||
const moduleLoader = window.app.getCore().moduleLoader;
|
||
const intelligentSequencer = moduleLoader.getModule('intelligentSequencer');
|
||
|
||
if (!intelligentSequencer) {
|
||
alert('IntelligentSequencer is not initialized. Please refresh the page.');
|
||
return;
|
||
}
|
||
|
||
// Show Smart Guide mode in viewport
|
||
const viewport = document.querySelector('.revision-viewport');
|
||
if (viewport) {
|
||
viewport.innerHTML = `
|
||
<div class="smart-guide-active">
|
||
<div class="guide-header">
|
||
<div class="guide-title">
|
||
<h3>🧠 Smart Guide Active</h3>
|
||
<div class="guide-actions">
|
||
<button class="btn btn-info btn-sm" onclick="showVocabularyKnowledge()">
|
||
<span class="btn-icon">📖</span>
|
||
<span class="btn-text">Vocabulary Knowledge</span>
|
||
</button>
|
||
<button class="btn btn-warning btn-sm" onclick="stopSmartGuide()">
|
||
<span class="btn-icon">⏹️</span>
|
||
<span class="btn-text">Stop Guide</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="guide-status" id="guide-status">
|
||
Analyzing performance and preparing intelligent sequence...
|
||
</div>
|
||
</div>
|
||
|
||
<div class="guide-controls">
|
||
<div class="session-progress" id="session-progress">
|
||
<div class="progress-bar-container">
|
||
<div class="progress-bar" id="guide-progress-bar" style="width: 0%"></div>
|
||
</div>
|
||
<div class="progress-text" id="guide-progress-text">Preparing session...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="current-exercise-info" id="current-exercise-info" style="display: none;">
|
||
<h4>Current Exercise</h4>
|
||
<div class="exercise-details" id="exercise-details"></div>
|
||
<div class="exercise-reasoning" id="exercise-reasoning"></div>
|
||
</div>
|
||
|
||
<div id="guide-exercise-container">
|
||
<!-- Exercise modules will be loaded here -->
|
||
</div>
|
||
|
||
<div class="guide-insights" id="guide-insights" style="display: none;">
|
||
<h4>🎯 Performance Insights</h4>
|
||
<div id="insights-content"></div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
try {
|
||
// Start guided session
|
||
console.log('🧠 Starting Smart Guide session...');
|
||
currentGuidedSession = intelligentSequencer.startGuidedSession({
|
||
bookId: bookId,
|
||
chapterId: chapterId,
|
||
targetLength: 8 // Default 8 exercises per session
|
||
});
|
||
|
||
updateGuideStatus(`✅ Session started: ${currentGuidedSession.id}`);
|
||
updateProgressBar(0, currentGuidedSession.targetLength);
|
||
|
||
// Get first recommended exercise
|
||
const firstExercise = await intelligentSequencer.getNextExercise();
|
||
|
||
if (firstExercise) {
|
||
await startGuidedExercise(firstExercise);
|
||
} else {
|
||
// Session completed immediately (shouldn't happen)
|
||
completeGuidedSession();
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error starting Smart Guide:', error);
|
||
updateGuideStatus(`❌ Error: ${error.message}`);
|
||
setTimeout(() => stopSmartGuide(), 3000);
|
||
}
|
||
};
|
||
|
||
window.stopSmartGuide = function() {
|
||
if (currentGuidedSession) {
|
||
const moduleLoader = window.app.getCore().moduleLoader;
|
||
const intelligentSequencer = moduleLoader.getModule('intelligentSequencer');
|
||
|
||
if (intelligentSequencer && intelligentSequencer.isGuiding()) {
|
||
intelligentSequencer.stopGuidedSession();
|
||
}
|
||
|
||
currentGuidedSession = null;
|
||
console.log('🛑 Smart Guide session stopped');
|
||
}
|
||
|
||
// Return to normal revision interface
|
||
const chapterSelect = document.getElementById('chapter-select');
|
||
if (chapterSelect && chapterSelect.value) {
|
||
onChapterSelected(chapterSelect.value);
|
||
}
|
||
};
|
||
|
||
// Helper function to get current book ID from chapter ID
|
||
window.getCurrentBookId = function() {
|
||
const chapterId = window.currentChapterId || 'sbs-7-8';
|
||
// Parse chapter ID to extract book (e.g., "sbs-7-8" -> "sbs")
|
||
const parts = chapterId.split('-');
|
||
return parts[0] || 'sbs';
|
||
};
|
||
|
||
// Helper function to load persisted vocabulary data
|
||
window.loadPersistedVocabularyData = async function(chapterId) {
|
||
try {
|
||
const bookId = getCurrentBookId();
|
||
console.log(`📁 Loading persisted data for ${bookId}/${chapterId}`);
|
||
|
||
// Load from API server progress
|
||
const serverProgress = await getChapterProgress(bookId, chapterId);
|
||
console.log('Server progress:', serverProgress);
|
||
|
||
// Load from localStorage FlashcardLearning
|
||
const flashcardProgress = JSON.parse(localStorage.getItem('flashcard_progress') || '{}');
|
||
console.log('Flashcard progress:', flashcardProgress);
|
||
|
||
// Extract mastered words from flashcard data
|
||
const flashcardMasteredWords = Object.keys(flashcardProgress).filter(cardId => {
|
||
const progress = flashcardProgress[cardId];
|
||
return progress.masteryLevel === 'mastered';
|
||
}).map(cardId => {
|
||
// Extract word from cardId (format: "vocab_word" or "sentence_index")
|
||
const progress = flashcardProgress[cardId];
|
||
return progress.word || cardId.replace('vocab_', '').replace(/_/g, ' ');
|
||
});
|
||
|
||
// Combine discovered and mastered words from server
|
||
const serverDiscoveredWords = serverProgress.masteredVocabulary || [];
|
||
|
||
return {
|
||
serverDiscovered: serverDiscoveredWords,
|
||
flashcardMastered: flashcardMasteredWords,
|
||
serverData: serverProgress,
|
||
flashcardData: flashcardProgress
|
||
};
|
||
|
||
} catch (error) {
|
||
console.warn('Failed to load persisted vocabulary data:', error);
|
||
return {
|
||
serverDiscovered: [],
|
||
flashcardMastered: [],
|
||
serverData: {},
|
||
flashcardData: {}
|
||
};
|
||
}
|
||
};
|
||
|
||
// Helper function to calculate combined vocabulary progress
|
||
window.calculateVocabularyProgress = async function(chapterId, persistedData, prerequisiteEngine) {
|
||
try {
|
||
console.log('🧮 Calculating combined vocabulary progress...');
|
||
|
||
// Get chapter content to know total vocabulary
|
||
const moduleLoader = window.app.getCore().moduleLoader;
|
||
const contentLoader = moduleLoader.getModule('contentLoader');
|
||
|
||
if (!contentLoader) {
|
||
throw new Error('ContentLoader not available');
|
||
}
|
||
|
||
const content = await contentLoader.getContent(chapterId);
|
||
if (!content?.vocabulary) {
|
||
throw new Error('No vocabulary content found');
|
||
}
|
||
|
||
const allWords = Object.keys(content.vocabulary);
|
||
const vocabCount = allWords.length;
|
||
|
||
// Combine all data sources
|
||
const combinedDiscovered = new Set([
|
||
...persistedData.serverDiscovered,
|
||
...persistedData.flashcardMastered
|
||
]);
|
||
|
||
const combinedMastered = new Set([
|
||
...persistedData.flashcardMastered
|
||
]);
|
||
|
||
// Add current session data if prerequisiteEngine is available
|
||
if (prerequisiteEngine) {
|
||
allWords.forEach(word => {
|
||
if (prerequisiteEngine.isDiscovered(word)) {
|
||
combinedDiscovered.add(word);
|
||
}
|
||
if (prerequisiteEngine.isMastered(word)) {
|
||
combinedMastered.add(word);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Count words that are in the current chapter vocabulary
|
||
const discoveredCount = allWords.filter(word => combinedDiscovered.has(word)).length;
|
||
const masteredCount = allWords.filter(word => combinedMastered.has(word)).length;
|
||
const masteredWordsList = allWords.filter(word => combinedMastered.has(word));
|
||
|
||
const result = {
|
||
total: vocabCount,
|
||
discovered: discoveredCount,
|
||
mastered: masteredCount,
|
||
discoveredPercentage: vocabCount > 0 ? Math.round((discoveredCount / vocabCount) * 100) : 0,
|
||
masteredPercentage: vocabCount > 0 ? Math.round((masteredCount / vocabCount) * 100) : 0,
|
||
masteredWordsList: masteredWordsList
|
||
};
|
||
|
||
console.log('✅ Combined progress calculated:', result);
|
||
return result;
|
||
|
||
} catch (error) {
|
||
console.warn('Failed to calculate vocabulary progress:', error);
|
||
return {
|
||
total: 0,
|
||
discovered: 0,
|
||
mastered: 0,
|
||
discoveredPercentage: 0,
|
||
masteredPercentage: 0,
|
||
masteredWordsList: []
|
||
};
|
||
}
|
||
};
|
||
|
||
window.showVocabularyKnowledge = async function() {
|
||
try {
|
||
console.log('📖 Refreshing vocabulary knowledge with latest data...');
|
||
|
||
// Close any existing modal first
|
||
const existingModal = document.querySelector('.vocabulary-knowledge-modal');
|
||
if (existingModal) {
|
||
existingModal.remove();
|
||
console.log('🗑️ Removed existing modal');
|
||
}
|
||
|
||
// Always start fresh - reload all data on every opening
|
||
const currentChapterId = window.currentChapterId || 'sbs-7-8';
|
||
console.log('🔄 Reloading data for chapter:', currentChapterId);
|
||
|
||
// Load fresh persisted data
|
||
const persistedData = await loadPersistedVocabularyData(currentChapterId);
|
||
console.log('📁 Fresh persisted data loaded:', persistedData);
|
||
|
||
// Get prerequisite engine from current session
|
||
let prerequisiteEngine = null;
|
||
try {
|
||
const moduleLoader = window.app.getCore().moduleLoader;
|
||
const orchestrator = moduleLoader.getModule('smartPreviewOrchestrator');
|
||
if (orchestrator) {
|
||
prerequisiteEngine = orchestrator.sharedServices?.prerequisiteEngine || orchestrator.prerequisiteEngine;
|
||
console.log('🔗 PrerequisiteEngine connected:', !!prerequisiteEngine);
|
||
}
|
||
} catch (error) {
|
||
console.log('Could not connect to PrerequisiteEngine:', error);
|
||
}
|
||
|
||
// Calculate fresh combined progress
|
||
const vocabularyProgress = await calculateVocabularyProgress(currentChapterId, persistedData, prerequisiteEngine);
|
||
console.log('📊 Fresh vocabulary progress:', vocabularyProgress);
|
||
|
||
// Prepare data for modal
|
||
const progress = {
|
||
vocabulary: vocabularyProgress
|
||
};
|
||
const masteredWords = vocabularyProgress.masteredWordsList;
|
||
|
||
|
||
// Create vocabulary knowledge modal
|
||
const modal = document.createElement('div');
|
||
modal.className = 'vocabulary-knowledge-modal';
|
||
modal.innerHTML = `
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>📖 Vocabulary Knowledge</h2>
|
||
<button class="modal-close" onclick="closeVocabularyModal()">✕</button>
|
||
</div>
|
||
|
||
<div class="knowledge-stats">
|
||
<div class="stat-card">
|
||
<div class="stat-number">${progress.vocabulary?.discovered || 0}</div>
|
||
<div class="stat-label">Words Discovered</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-number">${progress.vocabulary?.mastered || 0}</div>
|
||
<div class="stat-label">Words Mastered</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-number">${progress.vocabulary?.total || 0}</div>
|
||
<div class="stat-label">Total Words</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="learning-details">
|
||
<h3>📚 Learning Progress Details</h3>
|
||
<div class="progress-sections">
|
||
<div class="progress-section">
|
||
<h4>👁️ Words Discovered</h4>
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" style="width: ${progress.vocabulary?.discoveredPercentage || 0}%; background: linear-gradient(90deg, #3498db, #2980b9);"></div>
|
||
</div>
|
||
<span>${progress.vocabulary?.discovered || 0}/${progress.vocabulary?.total || 0} words (${progress.vocabulary?.discoveredPercentage || 0}%)</span>
|
||
</div>
|
||
<div class="progress-section">
|
||
<h4>🎯 Words Mastered</h4>
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" style="width: ${progress.vocabulary?.masteredPercentage || 0}%; background: linear-gradient(90deg, #27ae60, #229954);"></div>
|
||
</div>
|
||
<span>${progress.vocabulary?.mastered || 0}/${progress.vocabulary?.total || 0} words (${progress.vocabulary?.masteredPercentage || 0}%)</span>
|
||
</div>
|
||
<div class="progress-section">
|
||
<h4>Phrases</h4>
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" style="width: ${progress.phrases?.percentage || 0}%"></div>
|
||
</div>
|
||
<span>${progress.phrases?.mastered || 0}/${progress.phrases?.total || 0} phrases</span>
|
||
</div>
|
||
<div class="progress-section">
|
||
<h4>Grammar</h4>
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" style="width: ${progress.grammar?.percentage || 0}%"></div>
|
||
</div>
|
||
<span>${progress.grammar?.mastered || 0}/${progress.grammar?.total || 0} concepts</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mastered-words">
|
||
<h3>✅ Mastered Words</h3>
|
||
<div class="words-grid">
|
||
${masteredWords.length > 0
|
||
? masteredWords.map(word => `<span class="mastered-word">${word}</span>`).join('')
|
||
: '<p class="no-words">No words mastered yet. Start with flashcards to build your vocabulary!</p>'
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(modal);
|
||
console.log('Modal added to DOM:', modal);
|
||
|
||
// Force show modal with inline styles for debugging
|
||
modal.style.display = 'flex';
|
||
modal.style.position = 'fixed';
|
||
modal.style.top = '0';
|
||
modal.style.left = '0';
|
||
modal.style.width = '100%';
|
||
modal.style.height = '100%';
|
||
modal.style.zIndex = '999999';
|
||
|
||
} catch (error) {
|
||
console.error('Error showing vocabulary knowledge:', error);
|
||
alert('Error loading vocabulary knowledge. Please try again.');
|
||
}
|
||
};
|
||
|
||
window.closeVocabularyModal = function() {
|
||
const modal = document.querySelector('.vocabulary-knowledge-modal');
|
||
if (modal) {
|
||
modal.remove();
|
||
console.log('🗑️ Vocabulary modal closed and removed');
|
||
}
|
||
};
|
||
|
||
// Helper function to refresh modal if it's already open
|
||
window.refreshVocabularyModalIfOpen = function() {
|
||
const existingModal = document.querySelector('.vocabulary-knowledge-modal');
|
||
if (existingModal) {
|
||
console.log('🔄 Modal is open, refreshing with latest data...');
|
||
showVocabularyKnowledge(); // This will close the old one and create a new one
|
||
}
|
||
};
|
||
|
||
window.startGuidedExercise = async function(exerciseRecommendation) {
|
||
const moduleLoader = window.app.getCore().moduleLoader;
|
||
const unifiedDRS = moduleLoader.getModule('unifiedDRS');
|
||
|
||
if (!unifiedDRS) {
|
||
throw new Error('UnifiedDRS not available');
|
||
}
|
||
|
||
// Update UI with current exercise info
|
||
updateCurrentExerciseInfo(exerciseRecommendation);
|
||
updateGuideStatus(`🎯 Starting: ${exerciseRecommendation.type} (${exerciseRecommendation.difficulty})`);
|
||
|
||
const exerciseContainer = document.getElementById('guide-exercise-container');
|
||
|
||
console.log('🎯 Starting guided exercise:', exerciseRecommendation);
|
||
|
||
// Start the exercise
|
||
await unifiedDRS.start(exerciseContainer, {
|
||
type: exerciseRecommendation.type,
|
||
difficulty: exerciseRecommendation.difficulty,
|
||
bookId: exerciseRecommendation.bookId,
|
||
chapterId: exerciseRecommendation.chapterId,
|
||
context: 'smart-guide',
|
||
sessionId: currentGuidedSession.id
|
||
});
|
||
|
||
// Update progress
|
||
updateProgressBar(exerciseRecommendation.sessionPosition, exerciseRecommendation.totalInSession);
|
||
};
|
||
|
||
window.updateGuideStatus = function(message) {
|
||
const statusElement = document.getElementById('guide-status');
|
||
if (statusElement) {
|
||
statusElement.textContent = message;
|
||
}
|
||
};
|
||
|
||
window.updateProgressBar = function(current, total) {
|
||
const progressBar = document.getElementById('guide-progress-bar');
|
||
const progressText = document.getElementById('guide-progress-text');
|
||
|
||
if (progressBar) {
|
||
const percentage = total > 0 ? (current / total) * 100 : 0;
|
||
progressBar.style.width = percentage + '%';
|
||
}
|
||
|
||
if (progressText) {
|
||
const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
|
||
progressText.textContent = `${percentage}% Content Coverage`;
|
||
}
|
||
};
|
||
|
||
window.updateCurrentExerciseInfo = function(exercise) {
|
||
const infoContainer = document.getElementById('current-exercise-info');
|
||
const detailsElement = document.getElementById('exercise-details');
|
||
const reasoningElement = document.getElementById('exercise-reasoning');
|
||
|
||
if (infoContainer && detailsElement && reasoningElement) {
|
||
infoContainer.style.display = 'block';
|
||
|
||
detailsElement.innerHTML = `
|
||
<div class="exercise-detail">
|
||
<strong>Type:</strong> ${exercise.type.charAt(0).toUpperCase() + exercise.type.slice(1)}
|
||
</div>
|
||
<div class="exercise-detail">
|
||
<strong>Difficulty:</strong> ${exercise.difficulty.charAt(0).toUpperCase() + exercise.difficulty.slice(1)}
|
||
</div>
|
||
<div class="exercise-detail">
|
||
<strong>Position:</strong> ${exercise.sessionPosition}/${exercise.totalInSession}
|
||
</div>
|
||
`;
|
||
|
||
reasoningElement.innerHTML = `
|
||
<div class="reasoning-box">
|
||
<strong>Why this exercise?</strong><br>
|
||
${exercise.reasoning}
|
||
</div>
|
||
`;
|
||
}
|
||
};
|
||
|
||
window.completeGuidedSession = function() {
|
||
updateGuideStatus('🎉 Smart Guide session completed!');
|
||
|
||
// Get performance insights
|
||
const moduleLoader = window.app.getCore().moduleLoader;
|
||
const intelligentSequencer = moduleLoader.getModule('intelligentSequencer');
|
||
|
||
if (intelligentSequencer) {
|
||
const insights = intelligentSequencer.getPerformanceInsights();
|
||
showPerformanceInsights(insights);
|
||
}
|
||
|
||
// Reset session
|
||
setTimeout(() => {
|
||
currentGuidedSession = null;
|
||
}, 5000);
|
||
};
|
||
|
||
window.showPerformanceInsights = function(insights) {
|
||
const insightsContainer = document.getElementById('guide-insights');
|
||
const insightsContent = document.getElementById('insights-content');
|
||
|
||
if (insightsContainer && insightsContent) {
|
||
insightsContainer.style.display = 'block';
|
||
|
||
const overallStats = insights.overallStats;
|
||
const recommendations = insights.recommendations || [];
|
||
const recentTrends = insights.recentTrends;
|
||
|
||
insightsContent.innerHTML = `
|
||
<div class="insights-grid">
|
||
<div class="insight-card">
|
||
<h5>📊 Overall Performance</h5>
|
||
<ul>
|
||
<li>Total Exercises: ${overallStats.totalExercises}</li>
|
||
<li>Average Accuracy: ${Math.round(overallStats.averageAccuracy * 100)}%</li>
|
||
<li>Completion Rate: ${Math.round(overallStats.completionRate * 100)}%</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="insight-card">
|
||
<h5>📈 Recent Trend</h5>
|
||
<p>
|
||
${recentTrends.trend === 'improving' ? '📈 Improving' :
|
||
recentTrends.trend === 'declining' ? '📉 Declining' :
|
||
recentTrends.trend === 'stable' ? '📊 Stable' : '❓ Insufficient data'}
|
||
</p>
|
||
${recentTrends.recentAccuracy ?
|
||
`<p>Recent accuracy: ${Math.round(recentTrends.recentAccuracy * 100)}%</p>` : ''}
|
||
</div>
|
||
|
||
${recommendations.length > 0 ? `
|
||
<div class="insight-card">
|
||
<h5>💡 Recommendations</h5>
|
||
<ul>
|
||
${recommendations.slice(0, 3).map(rec =>
|
||
`<li><strong>${rec.type}:</strong> ${rec.message}</li>`
|
||
).join('')}
|
||
</ul>
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
|
||
<div class="insights-actions">
|
||
<button class="btn btn-primary" onclick="startSmartGuide()">
|
||
🧠 Start Another Session
|
||
</button>
|
||
<button class="btn btn-outline" onclick="stopSmartGuide()">
|
||
📖 Return to Manual Mode
|
||
</button>
|
||
</div>
|
||
`;
|
||
}
|
||
};
|
||
|
||
// Set up Smart Guide event listeners when app is ready
|
||
function setupSmartGuideEventListeners() {
|
||
if (!window.app || !window.app.getCore) {
|
||
setTimeout(setupSmartGuideEventListeners, 100);
|
||
return;
|
||
}
|
||
|
||
const { eventBus } = window.app.getCore();
|
||
|
||
// Register SmartGuide module for event listening
|
||
eventBus.registerModule({ name: 'SmartGuide' });
|
||
|
||
console.log('🎧 Setting up Smart Guide event listeners...');
|
||
|
||
// Set up event listeners for guided sessions
|
||
eventBus.on('drs:completed', (event) => {
|
||
if (!currentGuidedSession) {
|
||
console.log('📢 DRS completed but no guided session active');
|
||
return;
|
||
}
|
||
|
||
const data = event.data;
|
||
console.log('🎯 Exercise completed in guided session:', data);
|
||
|
||
// Record completion in sequencer
|
||
const moduleLoader = window.app.getCore().moduleLoader;
|
||
const intelligentSequencer = moduleLoader.getModule('intelligentSequencer');
|
||
|
||
if (intelligentSequencer && intelligentSequencer.isGuiding()) {
|
||
console.log('🔄 Getting next exercise recommendation...');
|
||
|
||
// Get next exercise recommendation
|
||
setTimeout(async () => {
|
||
try {
|
||
const nextExercise = await intelligentSequencer.getNextExercise();
|
||
|
||
if (nextExercise) {
|
||
updateGuideStatus('🔄 Preparing next exercise...');
|
||
console.log('🎯 Next exercise recommended:', nextExercise);
|
||
setTimeout(() => startGuidedExercise(nextExercise), 1500);
|
||
} else {
|
||
// Session completed
|
||
console.log('✅ No more exercises - session complete');
|
||
completeGuidedSession();
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error getting next exercise:', error);
|
||
updateGuideStatus(`❌ Error: ${error.message}`);
|
||
}
|
||
}, 500);
|
||
} else {
|
||
console.warn('⚠️ IntelligentSequencer not guiding or not available');
|
||
}
|
||
}, 'SmartGuide');
|
||
|
||
eventBus.on('sequencer:session-completed', (event) => {
|
||
console.log('🎉 Guided session completed via sequencer:', event.data);
|
||
completeGuidedSession();
|
||
}, 'SmartGuide');
|
||
|
||
eventBus.on('drs:step-completed', (event) => {
|
||
if (!currentGuidedSession) return;
|
||
console.log('📝 Step completed in guided session:', event.data);
|
||
// Update progress for individual steps within an exercise
|
||
updateProgressBar(
|
||
currentGuidedSession.completed + (event.data.step + 1) / event.data.total,
|
||
currentGuidedSession.targetLength
|
||
);
|
||
}, 'SmartGuide');
|
||
|
||
console.log('✅ Smart Guide event listeners configured');
|
||
}
|
||
|
||
// Initialize event listeners when app is ready
|
||
setupSmartGuideEventListeners();
|
||
|
||
</script>
|
||
<!-- Integration Test Suite -->
|
||
<script src="test-integration.js"></script>
|
||
<script src="test-console-commands.js"></script>
|
||
<script src="test-uiux-integration.js"></script>
|
||
<script src="test-e2e-scenarios.js"></script>
|
||
</body>
|
||
</html> |