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 = '
No tasks at the moment
'; return; } Object.entries(filteredData).forEach(([taskType, tasks]) => { // Calculate task statistics for this type let runningCount = 0; let completedCount = 0; let errorCount = 0; tasks.forEach(task => { if (task.status.status === 'Running' || task.status.status === 'Preparing' || task.status.status === 'Initializing') { runningCount++; } else if (task.status.status === 'Done' || task.status.status === 'Done (Message Exit)' || task.status.status === 'Done (Max Steps)' || task.status.status === 'Done (Thought Exit)') { completedCount++; } else if (task.status.status === 'Error') { errorCount++; } }); // Create the task type card const typeSection = document.createElement('div'); typeSection.className = 'task-type'; // Create header with task type name and statistics const typeHeader = document.createElement('div'); typeHeader.className = 'task-type-header'; typeHeader.innerHTML = ` ${taskType}
${errorCount > 0 ? ` ${errorCount} error` : ''} ${tasks.length} total ${runningCount} active ${completedCount} completed
`; typeSection.appendChild(typeHeader); // Create container for task cards const tasksContainer = document.createElement('div'); tasksContainer.className = 'tasks-container'; // Set default collapsed state typeSection.classList.add('collapsed'); tasksContainer.setAttribute('aria-hidden', 'true'); if (tasks.length === 0) { const noTasks = document.createElement('div'); noTasks.className = 'no-tasks'; noTasks.innerHTML = ' No Tasks Available'; tasksContainer.appendChild(noTasks); } else { // Add scrolling for large task lists if (tasks.length > 10) { tasksContainer.style.maxHeight = '600px'; tasksContainer.style.overflowY = 'auto'; } tasks.forEach(task => { const taskCard = document.createElement('div'); taskCard.className = 'task-card'; // Add data attributes for later updates taskCard.setAttribute('data-task-id', task.id); taskCard.setAttribute('data-task-type', taskType); const taskHeader = document.createElement('div'); taskHeader.className = 'task-header'; const taskTitle = document.createElement('div'); taskTitle.className = 'task-title'; taskTitle.innerHTML = ` Task ID: ${task.id}`; taskHeader.appendChild(taskTitle); const taskStatus = document.createElement('div'); taskStatus.className = 'task-status'; 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; } taskStatus.classList.add(statusClass); taskStatus.innerHTML = ` ${task.status.status}`; taskHeader.appendChild(taskStatus); taskCard.appendChild(taskHeader); const taskInstruction = document.createElement('div'); taskInstruction.className = 'task-instruction'; taskInstruction.innerHTML = ` Instruction: ${task.instruction}`; taskCard.appendChild(taskInstruction); const taskProgress = document.createElement('div'); taskProgress.className = 'task-details'; if (task.status.progress > 0) { const progressText = document.createElement('div'); progressText.innerHTML = ` Progress: ${task.status.progress}/${task.status.max_steps} step(s)`; taskProgress.appendChild(progressText); const progressBar = document.createElement('div'); progressBar.className = 'progress-bar'; const progressFill = document.createElement('div'); progressFill.className = 'progress-fill'; const percentage = (task.status.progress / task.status.max_steps) * 100; progressFill.style.width = `${percentage}%`; progressBar.appendChild(progressFill); taskProgress.appendChild(progressBar); const progressPercentage = document.createElement('div'); progressPercentage.className = 'progress-percentage'; progressPercentage.textContent = `${Math.round(percentage)}%`; taskProgress.appendChild(progressPercentage); } if (task.status.last_update) { const timestamp = document.createElement('div'); timestamp.className = 'timestamp'; timestamp.innerHTML = ` Last Update: ${task.status.last_update}`; taskProgress.appendChild(timestamp); } if (task.status.result) { const resultDiv = document.createElement('div'); resultDiv.className = 'task-result'; resultDiv.innerHTML = ` Result: ${task.status.result}`; taskProgress.appendChild(resultDiv); } taskCard.appendChild(taskProgress); if (task.status.status !== 'Not Started') { taskCard.style.cursor = 'pointer'; taskCard.addEventListener('click', () => { window.location.href = `/task/${taskType}/${task.id}`; }); } tasksContainer.appendChild(taskCard); }); } typeSection.appendChild(tasksContainer); // Toggle collapse when clicking on the header typeHeader.addEventListener('click', (event) => { // Prevent toggling when clicking task cards if (!event.target.closest('.task-card')) { typeSection.classList.toggle('collapsed'); // Set appropriate aria attributes for accessibility const isCollapsed = typeSection.classList.contains('collapsed'); tasksContainer.setAttribute('aria-hidden', isCollapsed); // Update session storage with current expanded state 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); } }); sessionStorage.setItem('expandedTaskTypes', JSON.stringify(expandedTaskTypes)); } }); // Check if this task type was expanded before refresh const expandedTaskTypes = JSON.parse(sessionStorage.getItem('expandedTaskTypes') || '[]'); if (expandedTaskTypes.includes(taskType)) { typeSection.classList.remove('collapsed'); tasksContainer.setAttribute('aria-hidden', 'false'); } container.appendChild(typeSection); }); } // add auto-refresh with time interval 10 seconds setInterval(() => { refreshPage(); }, 10000); // 10 seconds interval