Class_generator/index.html
StillHammer f5cef0c913 Add comprehensive testing suite with UI/UX and E2E integration tests
- 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>
2025-09-28 23:04:38 +08:00

2707 lines
123 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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">&times;</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>