document.addEventListener('DOMContentLoaded', () => { fetchTasks(); // Bind filter functionality document.getElementById('total-tasks').parentElement.addEventListener('click', () => setTaskFilter('all')); document.getElementById('active-tasks').parentElement.addEventListener('click', () => setTaskFilter('active')); document.getElementById('completed-tasks').parentElement.addEventListener('click', () => setTaskFilter('completed')); document.getElementById('error-tasks').parentElement.addEventListener('click', () => setTaskFilter('error')); }); let allTaskData = null; let currentFilter = 'all'; function refreshPage() { // Save expanded state before refresh const expandedTaskTypes = []; document.querySelectorAll('.task-type').forEach(section => { if (!section.classList.contains('collapsed')) { const typeName = section.querySelector('.task-type-name').textContent.trim(); expandedTaskTypes.push(typeName); } }); // Store in sessionStorage sessionStorage.setItem('expandedTaskTypes', JSON.stringify(expandedTaskTypes)); // Only fetch brief data for update to improve refresh speed fetchTasksForRefresh(); } function fetchTasksForRefresh() { fetch('/api/tasks/brief') .then(response => response.json()) .then(data => { // Update stored data allTaskData = data; // Only update statistics and task status, do not fully re-render updateStatistics(data); updateTaskStatus(data); }) .catch(error => console.error('Error refreshing tasks:', error)); } // New function: only update task status, do not re-render the entire list function updateTaskStatus(data) { // Add pulse animation to score banner when refreshing const scoreBanner = document.querySelector('.score-banner'); if (scoreBanner) { scoreBanner.classList.add('refreshing'); setTimeout(() => { scoreBanner.classList.remove('refreshing'); }, 1000); } // Update the status display of each task Object.entries(data).forEach(([taskType, tasks]) => { tasks.forEach(task => { // Find the corresponding task card const taskCard = document.querySelector(`.task-card[data-task-id="${task.id}"][data-task-type="${taskType}"]`); if (!taskCard) return; // Update status display const statusElement = taskCard.querySelector('.task-status'); if (statusElement) { // Remove all status classes statusElement.classList.remove('status-not-started', 'status-preparing', 'status-running', 'status-completed', 'status-error', 'status-unknown'); // Set new status class and icon let statusClass = ''; let statusIcon = ''; switch(task.status.status) { case 'Not Started': statusClass = 'status-not-started'; statusIcon = 'fa-hourglass-start'; break; case 'Preparing': case 'Initializing': statusClass = 'status-preparing'; statusIcon = 'fa-spinner fa-pulse'; break; case 'Running': statusClass = 'status-running'; statusIcon = 'fa-running'; break; case 'Done': case 'Done (Message Exit)': case 'Done (Max Steps)': case 'Done (Thought Exit)': statusClass = 'status-completed'; statusIcon = 'fa-check-circle'; break; case 'Error': statusClass = 'status-error'; statusIcon = 'fa-exclamation-circle'; break; default: statusClass = 'status-unknown'; statusIcon = 'fa-question-circle'; break; } statusElement.classList.add(statusClass); statusElement.innerHTML = ` ${task.status.status}`; } // Update progress bar if (task.status.progress > 0) { const progressText = taskCard.querySelector('.task-details div:first-child'); if (progressText) { progressText.innerHTML = ` Progress: ${task.status.progress}/${task.status.max_steps} step(s)`; } const progressFill = taskCard.querySelector('.progress-fill'); if (progressFill) { const percentage = (task.status.progress / task.status.max_steps) * 100; progressFill.style.width = `${percentage}%`; } const progressPercentage = taskCard.querySelector('.progress-percentage'); if (progressPercentage) { const percentage = (task.status.progress / task.status.max_steps) * 100; progressPercentage.textContent = `${Math.round(percentage)}%`; } } // Update last update time const timestamp = taskCard.querySelector('.timestamp'); if (timestamp && task.status.last_update) { timestamp.innerHTML = ` Last Update: ${task.status.last_update}`; } // Update result info if (task.status.result) { let resultDiv = taskCard.querySelector('.task-result'); if (!resultDiv) { resultDiv = document.createElement('div'); resultDiv.className = 'task-result'; taskCard.querySelector('.task-details').appendChild(resultDiv); } resultDiv.innerHTML = ` Result: ${task.status.result}`; } }); }); } function fetchTasks() { fetch('/api/tasks/brief') .then(response => response.json()) .then(data => { allTaskData = data; renderTasks(data); updateStatistics(data); }) .catch(error => console.error('Error fetching tasks:', error)); } function setTaskFilter(filter) { currentFilter = filter; if (!allTaskData) return; renderTasks(allTaskData); // Highlight selected card document.querySelectorAll('.stat-card').forEach(card => card.classList.remove('selected')); if (filter === 'all') { document.getElementById('total-tasks').parentElement.classList.add('selected'); } else if (filter === 'active') { document.getElementById('active-tasks').parentElement.classList.add('selected'); } else if (filter === 'completed') { document.getElementById('completed-tasks').parentElement.classList.add('selected'); } else if (filter === 'error') { document.getElementById('error-tasks').parentElement.classList.add('selected'); } } // Update statistics info function updateStatistics(data) { let totalTasks = 0; let activeTasks = 0; let completedTasks = 0; let errorTasks = 0; let totalScore = 0; Object.entries(data).forEach(([taskType, tasks]) => { totalTasks += tasks.length; tasks.forEach(task => { if (task.status.status === 'Running' || task.status.status === 'Preparing' || task.status.status === 'Initializing') { activeTasks++; } else if (task.status.status === 'Done' || task.status.status === 'Done (Message Exit)' || task.status.status === 'Done (Max Steps)' || task.status.status === 'Done (Thought Exit)') { completedTasks++; // Calculate score if task is completed if (task.status.result) { try { const score = parseFloat(task.status.result); if (!isNaN(score) && score >= 0 && score <= 1) { totalScore += score; } } catch (e) { console.log(`Could not parse score for task: ${task.id}`); } } } else if (task.status.status === 'Error') { errorTasks++; } }); }); document.getElementById('total-tasks').textContent = totalTasks; document.getElementById('active-tasks').textContent = activeTasks; document.getElementById('completed-tasks').textContent = completedTasks; document.getElementById('error-tasks').textContent = errorTasks; // Update score display with formatted score const scoreDisplay = document.getElementById('score-display'); if (completedTasks > 0) { const scoreFormatted = totalScore.toFixed(2); scoreDisplay.innerHTML = `${scoreFormatted} / ${completedTasks}`; } else { scoreDisplay.innerHTML = '0.00 / 0'; } // Highlight the currently selected statistics card document.querySelectorAll('.stat-card').forEach(card => card.classList.remove('selected')); if (currentFilter === 'all') { document.getElementById('total-tasks').parentElement.classList.add('selected'); } else if (currentFilter === 'active') { document.getElementById('active-tasks').parentElement.classList.add('selected'); } else if (currentFilter === 'completed') { document.getElementById('completed-tasks').parentElement.classList.add('selected'); } else if (currentFilter === 'error') { document.getElementById('error-tasks').parentElement.classList.add('selected'); } } function renderTasks(data) { const container = document.getElementById('task-container'); container.innerHTML = ''; let filteredData = {}; if (currentFilter === 'all') { filteredData = data; } else { Object.entries(data).forEach(([taskType, tasks]) => { let filteredTasks = []; if (currentFilter === 'active') { filteredTasks = tasks.filter(task => ['Running', 'Preparing', 'Initializing'].includes(task.status.status)); } else if (currentFilter === 'completed') { filteredTasks = tasks.filter(task => task.status.status === 'Done' || task.status.status === 'Done (Message Exit)' || task.status.status === 'Done (Max Steps)'|| task.status.status === 'Done (Thought Exit)'); } else if (currentFilter === 'error') { filteredTasks = tasks.filter(task => task.status.status === 'Error'); } if (filteredTasks.length > 0) { filteredData[taskType] = filteredTasks; } }); } if (Object.keys(filteredData).length === 0) { container.innerHTML = '