Spaces:
Running
Running
| document.addEventListener('DOMContentLoaded', () => { | |
| const form = document.getElementById('genForm'); | |
| const runBtn = document.getElementById('runBtn'); | |
| const fileInput = document.getElementById('image'); | |
| const dropZone = document.getElementById('dropZone'); | |
| const fileNameDisplay = document.getElementById('fileName'); | |
| const jobsList = document.getElementById('jobsList'); | |
| const refreshBtn = document.getElementById('refreshBtn'); | |
| // File Upload Drag & Drop | |
| dropZone.addEventListener('click', () => fileInput.click()); | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = '#8a2be2'; | |
| dropZone.style.background = 'rgba(138, 43, 226, 0.15)'; | |
| }); | |
| dropZone.addEventListener('dragleave', () => { | |
| dropZone.style.borderColor = 'rgba(255, 255, 255, 0.2)'; | |
| dropZone.style.background = 'rgba(15, 23, 42, 0.4)'; | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = 'rgba(255, 255, 255, 0.2)'; | |
| dropZone.style.background = 'rgba(15, 23, 42, 0.4)'; | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| updateFileName(); | |
| } | |
| }); | |
| fileInput.addEventListener('change', updateFileName); | |
| function updateFileName() { | |
| if (fileInput.files.length > 0) { | |
| fileNameDisplay.textContent = fileInput.files[0].name; | |
| fileNameDisplay.style.color = '#34d399'; | |
| } else { | |
| fileNameDisplay.textContent = 'No file chosen'; | |
| fileNameDisplay.style.color = '#00d2ff'; | |
| } | |
| } | |
| // Form Submission | |
| form.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| runBtn.disabled = true; | |
| runBtn.innerHTML = `<span class="btn-icon">⏳</span><span class="btn-text">DISPATCHING TO KAGGLE...</span>`; | |
| const formData = new FormData(form); | |
| try { | |
| const response = await fetch('/api/run', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| alert(`🎉 Job successfully dispatched to Kaggle!\nJob ID: ${data.job_id}\n\nYou can safely close this browser or go offline. Your video is being generated in the cloud!`); | |
| form.reset(); | |
| fileNameDisplay.textContent = 'No file chosen'; | |
| fileNameDisplay.style.color = '#00d2ff'; | |
| fetchJobs(); | |
| } else { | |
| alert(`❌ Error sending job to Kaggle:\n${data.error || 'Unknown error'}`); | |
| } | |
| } catch (err) { | |
| alert(`❌ Network error:\n${err.message}`); | |
| } finally { | |
| runBtn.disabled = false; | |
| runBtn.innerHTML = `<span class="btn-icon">🚀</span><span class="btn-text">RUN CLOUD GENERATION</span>`; | |
| } | |
| }); | |
| // Fetch & Render Jobs | |
| async function fetchJobs() { | |
| refreshBtn.innerHTML = '⏳ Checking...'; | |
| try { | |
| const res = await fetch('/api/jobs'); | |
| const jobs = await res.json(); | |
| renderJobs(jobs); | |
| } catch (err) { | |
| jobsList.innerHTML = `<div class="empty-state">Failed to load jobs. Cloud backend unreachable.</div>`; | |
| } finally { | |
| refreshBtn.innerHTML = '🔄 Refresh'; | |
| } | |
| } | |
| refreshBtn.addEventListener('click', fetchJobs); | |
| function renderJobs(jobs) { | |
| if (!jobs || jobs.length === 0) { | |
| jobsList.innerHTML = ` | |
| <div class="empty-state"> | |
| <div class="empty-icon">📂</div> | |
| <h3>No Generations Yet</h3> | |
| <p style="margin-top: 0.5rem; font-size: 0.85rem;">Submit your first script above to start generating videos on Kaggle.</p> | |
| </div>`; | |
| return; | |
| } | |
| // Sort reverse chronological | |
| jobs.reverse(); | |
| jobsList.innerHTML = jobs.map(job => { | |
| let statusClass = 'status-running'; | |
| let statusIcon = '⚙️'; | |
| if (job.status === 'Completed') { | |
| statusClass = 'status-completed'; | |
| statusIcon = '✅'; | |
| } else if (job.status === 'Failed') { | |
| statusClass = 'status-failed'; | |
| statusIcon = '❌'; | |
| } | |
| const downloadSection = job.status === 'Completed' ? ` | |
| <a href="/api/download/${job.id}" class="download-btn" target="_blank" download> | |
| <span>⬇️ Download Stitched Video (.mp4)</span> | |
| </a> | |
| ` : (job.status === 'Failed' ? ` | |
| <div style="color: #ef4444; font-size: 0.8rem; text-align: center; padding: 0.5rem; background: rgba(239,68,68,0.1); border-radius: 8px;"> | |
| ⚠️ Kaggle kernel failed or timed out. Check Kaggle logs. | |
| </div> | |
| ` : ` | |
| <div style="color: #fbbf24; font-size: 0.85rem; text-align: center; padding: 0.8rem; background: rgba(245,158,11,0.1); border-radius: 10px;"> | |
| 🔄 Processing on Kaggle GPU... You can close this tab! | |
| </div> | |
| `); | |
| return ` | |
| <div class="job-card"> | |
| <div class="job-top"> | |
| <div> | |
| <div class="job-id">🎬 ${job.id}</div> | |
| <div class="job-time">${job.created_at || 'Recently'}</div> | |
| </div> | |
| <span class="job-status ${statusClass}">${statusIcon} ${job.status}</span> | |
| </div> | |
| <div class="job-preview">"${job.script_preview || 'No script text'}"</div> | |
| ${downloadSection} | |
| </div> | |
| `; | |
| }).join(''); | |
| } | |
| // Auto load on startup and poll every 30s | |
| fetchJobs(); | |
| setInterval(fetchJobs, 30000); | |
| }); | |