const ProgressPanel = { manifest: null, wsConnection: null, startTime: null, runtimeInterval: null, init(taskId) { this.taskId = taskId; this.startTime = Date.now(); this.startRuntimeCounter(); this.connectWebSocket(taskId); }, connectWebSocket(taskId) { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/task-progress/${taskId}`; this.wsConnection = new WebSocket(wsUrl); this.wsConnection.onopen = () => { console.log('Progress panel WebSocket connected'); }; this.wsConnection.onmessage = (event) => { const data = JSON.parse(event.data); this.handleProgressUpdate(data); }; this.wsConnection.onclose = () => { console.log('Progress panel WebSocket closed'); setTimeout(() => this.connectWebSocket(taskId), 3000); }; this.wsConnection.onerror = (error) => { console.error('Progress panel WebSocket error:', error); }; }, handleProgressUpdate(data) { if (data.type === 'manifest_update') { this.manifest = data.manifest; this.render(); } else if (data.type === 'section_update') { this.updateSection(data.section_id, data.status, data.progress); } else if (data.type === 'item_update') { this.updateItem(data.section_id, data.item_id, data.status, data.duration); } else if (data.type === 'terminal_line') { this.addTerminalLine(data.content, data.line_type); } else if (data.type === 'stats_update') { this.updateStats(data.stats); } else if (data.type === 'task_progress') { this.handleTaskProgress(data); } }, handleTaskProgress(data) { if (data.activity && data.activity.manifest) { this.manifest = data.activity.manifest; this.render(); } if (data.step) { this.updateCurrentAction(data.message || data.step); } if (data.details) { this.addTerminalLine(data.details, 'info'); } }, startRuntimeCounter() { this.runtimeInterval = setInterval(() => { const elapsed = Math.floor((Date.now() - this.startTime) / 1000); const runtimeEl = document.getElementById('status-runtime'); if (runtimeEl) { runtimeEl.textContent = this.formatDuration(elapsed); } }, 1000); }, stopRuntimeCounter() { if (this.runtimeInterval) { clearInterval(this.runtimeInterval); this.runtimeInterval = null; } }, formatDuration(seconds) { if (seconds < 60) { return `${seconds} sec`; } else if (seconds < 3600) { const mins = Math.floor(seconds / 60); return `${mins} min`; } else { const hours = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); return `${hours} hr ${mins} min`; } }, render() { if (!this.manifest) return; this.renderStatus(); this.renderProgressLog(); this.renderTerminal(); }, renderStatus() { const titleEl = document.getElementById('status-title'); if (titleEl) { titleEl.textContent = this.manifest.description || this.manifest.app_name; } const estimatedEl = document.getElementById('estimated-time'); if (estimatedEl && this.manifest.estimated_seconds) { estimatedEl.textContent = this.formatDuration(this.manifest.estimated_seconds); } const currentAction = this.getCurrentAction(); const actionEl = document.getElementById('current-action'); if (actionEl && currentAction) { actionEl.textContent = currentAction; } this.updateDecisionPoint(); }, getCurrentAction() { if (!this.manifest || !this.manifest.sections) return null; for (const section of this.manifest.sections) { if (section.status === 'Running') { for (const child of section.children || []) { if (child.status === 'Running') { for (const item of child.items || []) { if (item.status === 'Running') { return item.name; } } return child.name; } } return section.name; } } return null; }, updateCurrentAction(action) { const actionEl = document.getElementById('current-action'); if (actionEl) { actionEl.textContent = action; } }, updateDecisionPoint() { const decisionStepEl = document.getElementById('decision-step'); const decisionTotalEl = document.getElementById('decision-total'); if (decisionStepEl && this.manifest) { decisionStepEl.textContent = this.manifest.completed_steps || 0; } if (decisionTotalEl && this.manifest) { decisionTotalEl.textContent = this.manifest.total_steps || 0; } }, renderProgressLog() { const container = document.getElementById('progress-log-content'); if (!container || !this.manifest || !this.manifest.sections) return; container.innerHTML = ''; for (const section of this.manifest.sections) { const sectionEl = this.createSectionElement(section); container.appendChild(sectionEl); } }, createSectionElement(section) { const sectionDiv = document.createElement('div'); sectionDiv.className = 'log-section'; sectionDiv.dataset.sectionId = section.id; if (section.status === 'Running' || section.status === 'Completed') { sectionDiv.classList.add('expanded'); } const statusClass = section.status.toLowerCase(); const stepCurrent = section.current_step || 0; const stepTotal = section.total_steps || 0; sectionDiv.innerHTML = `
${this.escapeHtml(section.name)} View Details ▸ Step ${stepCurrent}/${stepTotal} ${section.status}
`; const childrenContainer = sectionDiv.querySelector('.log-children'); for (const child of section.children || []) { const childEl = this.createChildElement(child, section.id); childrenContainer.appendChild(childEl); } if (section.items && section.items.length > 0 && (!section.children || section.children.length === 0)) { for (const item of section.items) { const itemEl = this.createItemElement(item); childrenContainer.appendChild(itemEl); } } return sectionDiv; }, createChildElement(child, parentId) { const childDiv = document.createElement('div'); childDiv.className = 'log-child'; childDiv.dataset.childId = child.id; if (child.status === 'Running' || child.status === 'Completed') { childDiv.classList.add('expanded'); } const statusClass = child.status.toLowerCase(); const stepCurrent = child.current_step || 0; const stepTotal = child.total_steps || 0; const duration = child.duration_seconds ? this.formatDuration(child.duration_seconds) : ''; childDiv.innerHTML = `
${this.escapeHtml(child.name)} View Details ▸ Step ${stepCurrent}/${stepTotal} ${child.status}
`; const itemsContainer = childDiv.querySelector('.log-items'); for (const item of child.items || []) { const itemEl = this.createItemElement(item); itemsContainer.appendChild(itemEl); } return childDiv; }, createItemElement(item) { const itemDiv = document.createElement('div'); itemDiv.className = 'log-item'; itemDiv.dataset.itemId = item.id; const statusClass = item.status.toLowerCase(); const duration = item.duration_seconds ? `Duration: ${this.formatDuration(item.duration_seconds)}` : ''; const checkIcon = item.status === 'Completed' ? '✓' : (item.status === 'Running' ? '◎' : '○'); itemDiv.innerHTML = ` ${this.escapeHtml(item.name)}${item.details ? ` - ${this.escapeHtml(item.details)}` : ''}
${duration} ${checkIcon}
`; return itemDiv; }, renderTerminal() { if (!this.manifest || !this.manifest.terminal_output) return; const container = document.getElementById('terminal-content'); if (!container) return; container.innerHTML = ''; for (const line of this.manifest.terminal_output.slice(-50)) { this.appendTerminalLine(container, line.content, line.line_type || 'info'); } container.scrollTop = container.scrollHeight; }, addTerminalLine(content, lineType) { const container = document.getElementById('terminal-content'); if (!container) return; this.appendTerminalLine(container, content, lineType); container.scrollTop = container.scrollHeight; this.incrementProcessedCount(); }, appendTerminalLine(container, content, lineType) { const lineDiv = document.createElement('div'); lineDiv.className = `terminal-line ${lineType || 'info'}`; lineDiv.textContent = content; container.appendChild(lineDiv); }, incrementProcessedCount() { const processedEl = document.getElementById('terminal-processed'); if (processedEl) { const current = parseInt(processedEl.textContent, 10) || 0; processedEl.textContent = current + 1; } }, updateStats(stats) { const processedEl = document.getElementById('terminal-processed'); if (processedEl && stats.data_points_processed !== undefined) { processedEl.textContent = stats.data_points_processed; } const speedEl = document.getElementById('terminal-speed'); if (speedEl && stats.sources_per_min !== undefined) { speedEl.textContent = `~${stats.sources_per_min.toFixed(1)} sources/min`; } const etaEl = document.getElementById('terminal-eta'); if (etaEl && stats.estimated_remaining_seconds !== undefined) { etaEl.textContent = this.formatDuration(stats.estimated_remaining_seconds); } }, updateSection(sectionId, status, progress) { const sectionEl = document.querySelector(`[data-section-id="${sectionId}"]`); if (!sectionEl) return; const indicator = sectionEl.querySelector('.section-indicator'); const statusBadge = sectionEl.querySelector('.section-status-badge'); const stepBadge = sectionEl.querySelector('.section-step-badge'); if (indicator) { indicator.className = `section-indicator ${status.toLowerCase()}`; } if (statusBadge) { statusBadge.className = `section-status-badge ${status.toLowerCase()}`; statusBadge.textContent = status; } if (stepBadge && progress) { stepBadge.textContent = `Step ${progress.current}/${progress.total}`; } if (status === 'Running' || status === 'Completed') { sectionEl.classList.add('expanded'); } }, updateItem(sectionId, itemId, status, duration) { const itemEl = document.querySelector(`[data-item-id="${itemId}"]`); if (!itemEl) return; const dot = itemEl.querySelector('.item-dot'); const check = itemEl.querySelector('.item-check'); const durationEl = itemEl.querySelector('.item-duration'); const statusClass = status.toLowerCase(); if (dot) { dot.className = `item-dot ${statusClass}`; } if (check) { check.className = `item-check ${statusClass}`; check.textContent = status === 'Completed' ? '✓' : (status === 'Running' ? '◎' : '○'); } if (durationEl && duration) { durationEl.textContent = `Duration: ${this.formatDuration(duration)}`; } }, toggleSection(sectionId) { const sectionEl = document.querySelector(`[data-section-id="${sectionId}"]`); if (sectionEl) { sectionEl.classList.toggle('expanded'); } }, toggleChild(childId) { const childEl = document.querySelector(`[data-child-id="${childId}"]`); if (childEl) { childEl.classList.toggle('expanded'); } }, viewDetails(sectionId) { console.log('View details for section:', sectionId); }, viewChildDetails(childId) { console.log('View details for child:', childId); }, escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, loadManifest(taskId) { fetch(`/api/autotask/${taskId}/manifest`) .then(response => response.json()) .then(data => { if (data.success && data.manifest) { this.manifest = data.manifest; this.render(); } }) .catch(error => { console.error('Failed to load manifest:', error); }); }, destroy() { this.stopRuntimeCounter(); if (this.wsConnection) { this.wsConnection.close(); this.wsConnection = null; } } }; function toggleLogSection(header) { const section = header.closest('.log-section'); if (section) { section.classList.toggle('expanded'); } } function toggleLogChild(header) { const child = header.closest('.log-child'); if (child) { child.classList.toggle('expanded'); } } function viewSectionDetails(sectionId) { ProgressPanel.viewDetails(sectionId); } function viewChildDetails(childId) { ProgressPanel.viewChildDetails(childId); } window.ProgressPanel = ProgressPanel;