// Auto-extracted from tasks-manager.js (verbatim) — augments TasksManager.prototype. Loaded after the base. Object.assign(TasksManager.prototype, { toggleTaskDetails(taskId) { const details = document.getElementById(`details-${taskId}`); const toggleBtn = document.querySelector(`.task-btn.toggle-details[onclick*="toggleTaskDetails('${taskId}')"]`); if (details) { const isOpen = details.style.display === 'block'; // Close all other task details and reset their buttons if (isOpen) { document.querySelectorAll('.task-details').forEach(otherDetails => { if (otherDetails.id !== `details-${taskId}`) { otherDetails.style.display = 'none'; otherDetails.classList.remove('task-details-open'); } }); document.querySelectorAll('.task-btn.toggle-details').forEach(otherBtn => { if (!otherBtn.getAttribute('onclick').includes(taskId)) { otherBtn.classList.remove('expanded'); } }); // Close current details.style.display = 'none'; details.classList.remove('task-details-open'); if (toggleBtn) toggleBtn.classList.remove('expanded'); } else { // Open current details.style.display = 'block'; details.classList.add('task-details-open'); if (toggleBtn) toggleBtn.classList.add('expanded'); // Auto-load logs when opened. For active tasks, hand off to the live // streamer so SSE chunks keep updating the panel; for terminal tasks // a one-shot snapshot is enough. const t = this.tasks.find(x => x.id === taskId); if (t && (t.status === 'running' || t.status === 'queued' || t.status === 'pending')) { this.startLogStreaming(taskId, t); } else { this.loadTaskLogs(taskId); } // Scroll to task const taskElement = document.querySelector(`[data-task-id="${taskId}"]`); if (taskElement) { taskElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } // Update URL to include task parameter this.updateURL(this.currentCategory, isOpen ? null : taskId); this.highlightedTaskId = isOpen ? null : taskId; } else { // Remove task parameter from URL this.updateURL(this.currentCategory); this.highlightedTaskId = null; } }, // Monitor a specific task. State changes now arrive via SSE through // TaskEventBus, so this is mostly a hook for the UI to: // - auto-expand the task if it's the one we just started // - start log streaming when the task transitions to running // - clean up local intervals when it terminates // No more polling; the only fetches happen on-demand via TaskManager. monitorTask(taskId, appName, action) { if (!this.highlightedTaskId || this.highlightedTaskId === taskId) { setTimeout(() => this.autoExpandTask(taskId), 1500); } let statusUpdateInterval = null; const onUpdate = (event) => { const t = event.detail && event.detail.task; if (!t || t.id !== taskId) return; if (t.status === 'running') { this.updateTaskStructure(taskId, t); this.startLogStreaming(taskId, t); if (this.highlightedTaskId === taskId && !statusUpdateInterval) { statusUpdateInterval = setInterval(() => this.updateHighlightedTaskStatus(taskId), 2000); } } else { this.updateTaskDisplay(t); } }; const onComplete = (event) => { if (!event.detail || event.detail.taskId !== taskId) return; if (statusUpdateInterval) { clearInterval(statusUpdateInterval); statusUpdateInterval = null; } this.stopLogStreaming(taskId); window.removeEventListener('taskUpdated', onUpdate); window.removeEventListener('taskCompleted', onComplete); }; window.addEventListener('taskUpdated', onUpdate); window.addEventListener('taskCompleted', onComplete); }, // Auto-expand a task when it's created async autoExpandTask(taskId) { // Wait for task to be rendered let attempts = 0; const maxAttempts = 10; const tryExpand = async () => { attempts++; // Check if task element exists const taskElement = document.querySelector(`[data-task-id="${taskId}"]`); if (!taskElement) { if (attempts < maxAttempts) { setTimeout(tryExpand, 500); // Try again in 500ms } else { console.warn(`⚠️ Could not find task element for ${taskId} after ${maxAttempts} attempts`); } return; } // Get the details element const details = document.getElementById(`details-${taskId}`); if (!details) { if (attempts < maxAttempts) { setTimeout(tryExpand, 500); } else { console.warn(`⚠️ Could not find details element for ${taskId}`); } return; } // Expand the task details details.style.display = 'block'; details.classList.add('task-details-open'); // Update toggle button const toggleBtn = document.querySelector(`.task-btn.toggle-details[onclick*="toggleTaskDetails('${taskId}')"]`); if (toggleBtn) { toggleBtn.classList.add('expanded'); } // Scroll to the task taskElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); // Load the output if task is completed const task = await this.taskManager.getTask(taskId); if (task && (task.status === 'completed' || task.status === 'failed')) { setTimeout(() => { this.loadTaskOutput(taskId); }, 1000); } }; tryExpand(); }, });