/** * Reddit Video Maker Bot - Progress GUI JavaScript * Real-time progress tracking via WebSocket */ class ProgressTracker { constructor() { this.socket = null; this.connected = false; this.currentJob = null; this.jobHistory = []; // DOM elements this.elements = { noJob: document.getElementById('no-job'), jobInfo: document.getElementById('job-info'), jobSubreddit: document.getElementById('job-subreddit'), jobTitle: document.getElementById('job-title'), jobStatusBadge: document.getElementById('job-status-badge'), overallProgressFill: document.getElementById('overall-progress-fill'), overallProgressText: document.getElementById('overall-progress-text'), stepsContainer: document.getElementById('steps-container'), previewContainer: document.getElementById('preview-container'), historyList: document.getElementById('history-list'), connectionDot: document.getElementById('connection-dot'), connectionText: document.getElementById('connection-text'), }; this.stepIcons = { pending: '○', in_progress: '◐', completed: '✓', failed: '✗', skipped: '⊘', }; this.init(); } init() { this.connectWebSocket(); this.fetchInitialStatus(); } connectWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}`; this.socket = io(wsUrl + '/progress', { transports: ['websocket', 'polling'], reconnection: true, reconnectionDelay: 1000, reconnectionAttempts: Infinity, }); this.socket.on('connect', () => { this.connected = true; this.updateConnectionStatus(true); console.log('Connected to progress server'); }); this.socket.on('disconnect', () => { this.connected = false; this.updateConnectionStatus(false); console.log('Disconnected from progress server'); }); this.socket.on('progress_update', (data) => { this.handleProgressUpdate(data); }); this.socket.on('connect_error', (error) => { console.error('Connection error:', error); this.updateConnectionStatus(false); }); } fetchInitialStatus() { fetch('/api/status') .then(response => response.json()) .then(data => this.handleProgressUpdate(data)) .catch(error => console.error('Error fetching status:', error)); } updateConnectionStatus(connected) { const { connectionDot, connectionText } = this.elements; if (connected) { connectionDot.classList.add('connected'); connectionDot.classList.remove('disconnected'); connectionText.textContent = 'Connected'; } else { connectionDot.classList.remove('connected'); connectionDot.classList.add('disconnected'); connectionText.textContent = 'Disconnected - Reconnecting...'; } } handleProgressUpdate(data) { this.currentJob = data.current_job; this.jobHistory = data.job_history || []; this.renderCurrentJob(); this.renderHistory(); } renderCurrentJob() { const { noJob, jobInfo, jobSubreddit, jobTitle, jobStatusBadge, overallProgressFill, overallProgressText, stepsContainer, previewContainer } = this.elements; if (!this.currentJob) { noJob.classList.remove('hidden'); jobInfo.classList.add('hidden'); return; } noJob.classList.add('hidden'); jobInfo.classList.remove('hidden'); // Update job info jobSubreddit.textContent = `r/${this.currentJob.subreddit}`; jobTitle.textContent = this.currentJob.title; // Update status badge jobStatusBadge.textContent = this.formatStatus(this.currentJob.status); jobStatusBadge.className = `status-badge ${this.currentJob.status}`; // Update overall progress const progress = this.currentJob.overall_progress || 0; overallProgressFill.style.width = `${progress}%`; overallProgressText.textContent = `${Math.round(progress)}%`; // Render steps this.renderSteps(stepsContainer, this.currentJob.steps); // Update preview this.updatePreview(previewContainer, this.currentJob.steps); } renderSteps(container, steps) { container.innerHTML = ''; steps.forEach((step, index) => { const stepEl = document.createElement('div'); stepEl.className = `step ${this.getStepClass(step.status)}`; const icon = this.getStepIcon(step.status); const isActive = step.status === 'in_progress'; stepEl.innerHTML = `
Preview will appear here during processing
No completed jobs yet
'; return; } historyList.innerHTML = ''; // Show most recent first const sortedHistory = [...this.jobHistory].reverse(); sortedHistory.forEach(job => { const item = document.createElement('div'); item.className = 'history-item'; const duration = job.completed_at && job.created_at ? this.formatDuration(job.completed_at - job.created_at) : 'N/A'; const statusClass = job.status === 'completed' ? 'completed' : 'failed'; item.innerHTML = `