// Configuration const API_BASE_URL = window.location.origin; // Use same origin as the UI const REFRESH_INTERVAL = 10000; // 10 seconds (increased for demo) const MAX_LOGS = 100; // Global state let refreshInterval; let autoScrollEnabled = true; let currentFiles = []; let selectedFile = null; let apiConnected = false; // DOM Elements const elements = { statusIndicator: document.getElementById('statusIndicator'), totalFiles: document.getElementById('totalFiles'), processedFiles: document.getElementById('processedFiles'), extractedCourses: document.getElementById('extractedCourses'), extractedVideos: document.getElementById('extractedVideos'), extractedFrames: document.getElementById('extractedFrames'), trackedCursors: document.getElementById('trackedCursors'), currentFile: document.getElementById('currentFile'), progressFill: document.getElementById('progressFill'), progressText: document.getElementById('progressText'), startIndex: document.getElementById('startIndex'), startProcessing: document.getElementById('startProcessing'), stopProcessing: document.getElementById('stopProcessing'), refreshBtn: document.getElementById('refreshBtn'), themeToggle: document.getElementById('themeToggle'), fileCount: document.getElementById('fileCount'), filesGrid: document.getElementById('filesGrid'), logsContainer: document.getElementById('logsContainer'), clearLogs: document.getElementById('clearLogs'), autoScroll: document.getElementById('autoScroll'), fileModal: document.getElementById('fileModal'), modalTitle: document.getElementById('modalTitle'), modalBody: document.getElementById('modalBody'), modalClose: document.getElementById('modalClose'), downloadFile: document.getElementById('downloadFile'), viewFrames: document.getElementById('viewFrames'), loadingOverlay: document.getElementById('loadingOverlay'), toastContainer: document.getElementById('toastContainer') }; // Initialize the application document.addEventListener('DOMContentLoaded', function() { initializeTheme(); setupEventListeners(); startAutoRefresh(); fetchInitialData(); }); // Theme Management function initializeTheme() { const savedTheme = localStorage.getItem('theme') || 'light'; document.documentElement.setAttribute('data-theme', savedTheme); updateThemeIcon(savedTheme); } function toggleTheme() { const currentTheme = document.documentElement.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme); updateThemeIcon(newTheme); } function updateThemeIcon(theme) { const icon = elements.themeToggle.querySelector('i'); icon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; } // Event Listeners function setupEventListeners() { elements.themeToggle.addEventListener('click', toggleTheme); elements.refreshBtn.addEventListener('click', () => { showToast('Refreshing data...', 'info'); fetchAllData(); }); elements.startProcessing.addEventListener('click', startProcessing); elements.stopProcessing.addEventListener('click', stopProcessing); elements.clearLogs.addEventListener('click', clearLogs); elements.autoScroll.addEventListener('click', toggleAutoScroll); elements.modalClose.addEventListener('click', closeModal); elements.fileModal.addEventListener('click', (e) => { if (e.target === elements.fileModal) closeModal(); }); elements.downloadFile.addEventListener('click', downloadSelectedFile); elements.viewFrames.addEventListener('click', viewFrames); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); if (e.key === 'F5') { e.preventDefault(); fetchAllData(); } }); } // API Functions async function apiRequest(endpoint, options = {}) { try { showLoading(); const response = await fetch(`${API_BASE_URL}${endpoint}`, { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } apiConnected = true; return await response.json(); } catch (error) { console.error('API request failed:', error); apiConnected = false; // Show different messages based on error type if (error.name === 'TypeError' && error.message.includes('fetch')) { showToast('API server not running. Please start the cursor tracking API on port 8000.', 'warning'); } else { showToast(`API Error: ${error.message}`, 'error'); } throw error; } finally { hideLoading(); } } async function fetchStatus() { try { const data = await apiRequest('/status'); updateStatusDisplay(data.processing_status); return data; } catch (error) { // Show demo data when API is not connected const demoStatus = { is_running: false, total_files: 150, processed_files: 45, extracted_courses: 12, extracted_videos: 89, extracted_frames_count: 15420, tracked_cursors_count: 8934, current_file: null, logs: [ '[Demo Mode] API server not connected', '[Demo Mode] This is a demonstration of the UI', '[Demo Mode] Start the API server on port 8000 to see real data' ] }; updateStatusDisplay(demoStatus); } } async function fetchCursorData() { try { const data = await apiRequest('/cursor-data'); currentFiles = data.files || []; updateFilesDisplay(currentFiles); return data; } catch (error) { // Show demo files when API is not connected const demoFiles = [ { filename: 'course_1_video_1_mp4_cursor_data.json', size_bytes: 45678, modified_time: 'Sun Jul 13 19:30:15 2025' }, { filename: 'course_2_video_3_mp4_cursor_data.json', size_bytes: 67890, modified_time: 'Sun Jul 13 18:45:22 2025' }, { filename: 'course_3_video_2_mp4_cursor_data.json', size_bytes: 34567, modified_time: 'Sun Jul 13 17:20:10 2025' } ]; currentFiles = demoFiles; updateFilesDisplay(demoFiles); } } async function fetchFileDetails(filename) { try { const data = await apiRequest(`/cursor-data/${filename}/summary`); return data; } catch (error) { showToast(`Failed to fetch details for ${filename}`, 'error'); return null; } } async function startProcessing() { try { const startIndex = parseInt(elements.startIndex.value) || 0; const data = await apiRequest('/start-processing', { method: 'POST', body: JSON.stringify({ start_index: startIndex }) }); showToast(data.message, data.status === 'started' ? 'success' : 'info'); if (data.status === 'started') { elements.startProcessing.disabled = true; elements.stopProcessing.disabled = false; } } catch (error) { showToast('Failed to start processing', 'error'); } } async function stopProcessing() { try { const data = await apiRequest('/stop-processing', { method: 'POST' }); showToast(data.message, 'info'); elements.startProcessing.disabled = false; elements.stopProcessing.disabled = true; } catch (error) { showToast('Failed to stop processing', 'error'); } } // Display Update Functions function updateStatusDisplay(status) { // Update status indicator const statusDot = elements.statusIndicator.querySelector('.status-dot'); const statusText = elements.statusIndicator.querySelector('.status-text'); if (status.is_running) { statusDot.className = 'status-dot running'; statusText.textContent = 'Processing'; elements.startProcessing.disabled = true; elements.stopProcessing.disabled = false; } else { statusDot.className = 'status-dot stopped'; statusText.textContent = 'Idle'; elements.startProcessing.disabled = false; elements.stopProcessing.disabled = true; } // Update statistics elements.totalFiles.textContent = status.total_files || 0; elements.processedFiles.textContent = status.processed_files || 0; elements.extractedCourses.textContent = status.extracted_courses || 0; elements.extractedVideos.textContent = status.extracted_videos || 0; elements.extractedFrames.textContent = status.extracted_frames_count || 0; elements.trackedCursors.textContent = status.tracked_cursors_count || 0; // Update current file and progress const currentFile = status.current_file || 'None'; elements.currentFile.textContent = currentFile; const progress = status.total_files > 0 ? Math.round((status.processed_files / status.total_files) * 100) : 0; elements.progressFill.style.width = `${progress}%`; elements.progressText.textContent = `${progress}%`; // Update logs if (status.logs && status.logs.length > 0) { updateLogs(status.logs); } } function updateFilesDisplay(files) { elements.fileCount.textContent = `${files.length} files`; if (files.length === 0) { elements.filesGrid.innerHTML = `
No cursor tracking files found yet.
Files will appear here after processing completes.
Failed to load file details.
'; } } function showModal() { elements.fileModal.classList.add('show'); document.body.style.overflow = 'hidden'; } function closeModal() { elements.fileModal.classList.remove('show'); document.body.style.overflow = ''; selectedFile = null; } // File Operations async function downloadFile(filename) { try { const response = await fetch(`${API_BASE_URL}/cursor-data/${filename}`); if (!response.ok) throw new Error('Download failed'); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); showToast(`Downloaded ${filename}`, 'success'); } catch (error) { showToast(`Failed to download ${filename}`, 'error'); } } function downloadSelectedFile() { if (selectedFile) { downloadFile(selectedFile); } } function viewFrames() { if (selectedFile) { showToast('Frame viewer feature coming soon!', 'info'); } } // Utility Functions function formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } function formatDate(dateString) { try { return new Date(dateString).toLocaleDateString(); } catch { return dateString; } } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Log Management function clearLogs() { elements.logsContainer.innerHTML = '