Saturday, October 4, 2025

Ai

Khalil VideoGen — Frontend

Khalil VideoGen

Text → short stylized video (local)

Ready.
Make sure your backend (FastAPI) is running and `ffmpeg` is installed.
/* style.css - minimal clean UI */ :root{ --bg:#0f1720; --card:#0b1220; --accent:#4ad2a6; --muted:#b8c2c9; --panel:#081018; --radius:10px; } *{box-sizing:border-box} html,body{height:100%;margin:0;font-family:Inter,ui-sans-serif,system-ui,Segoe UI,Helvetica,Arial;color:#e6eef6;background:linear-gradient(180deg,#071129 0%, #07132a 100%);-webkit-font-smoothing:antialiased} .container{max-width:900px;margin:36px auto;padding:20px} header h1{margin:0;font-size:1.8rem} .muted{color:var(--muted);margin-top:6px} .card{background:rgba(255,255,255,0.02);padding:18px;border-radius:var(--radius);box-shadow:0 6px 20px rgba(2,6,23,0.6);margin-top:16px} label{display:block;font-size:.9rem;color:var(--muted);margin-bottom:6px} textarea{width:100%;min-height:120px;padding:12px;border-radius:8px;border:1px solid rgba(255,255,255,0.04);background:#06101a;color:#e6eef6;resize:vertical} input[type="text"],input[type="number"]{width:100%;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.04);background:#06101a;color:#e6eef6} .row{display:flex;gap:12px} .col{flex:1} .actions{display:flex;gap:10px;margin-top:12px} button{background:linear-gradient(90deg,var(--accent),#2bc6a6);border:0;padding:10px 14px;border-radius:10px;color:#032 ;font-weight:600;cursor:pointer} button:disabled{opacity:.6;cursor:not-allowed} .btn-ghost{background:transparent;border:1px solid rgba(255,255,255,0.06);color:var(--muted)} .status{display:flex;align-items:center;gap:12px;margin-top:12px;color:var(--muted)} .spinner{width:20px;height:20px;border-radius:50%;border:3px solid rgba(255,255,255,0.06);border-top-color:var(--accent);animation:spin 1s linear infinite} .hidden{display:none} .output-row{display:flex;gap:12px;align-items:center} .links a{display:inline-block;padding:8px 10px;background:rgba(255,255,255,0.03);border-radius:8px;color:var(--accent);text-decoration:none;margin-right:8px} .preview{width:100%;max-height:480px;margin-top:12px;border-radius:8px;background:#000} .log{background:#051018;color:var(--muted);padding:10px;border-radius:8px;margin-top:12px;white-space:pre-wrap} .small{font-size:.85rem} @keyframes spin{to{transform:rotate(360deg)}} // script.js - handles UI and requests (() => { const form = document.getElementById('genForm'); const genBtn = document.getElementById('genBtn'); const cancelBtn = document.getElementById('cancelBtn'); const statusText = document.getElementById('statusText'); const spinner = document.getElementById('spinner'); const outputSection = document.getElementById('output'); const linksDiv = document.getElementById('links'); const previewVideo = document.getElementById('preview'); const logPre = document.getElementById('log'); let controller = null; function setBusy(yes, text = '') { if (yes) { spinner.classList.remove('hidden'); genBtn.disabled = true; cancelBtn.hidden = false; statusText.textContent = text || 'Generating — this may take a few minutes (model download on first run)...'; } else { spinner.classList.add('hidden'); genBtn.disabled = false; cancelBtn.hidden = true; statusText.textContent = text || 'Ready.'; } } cancelBtn.addEventListener('click', () => { if (controller) { controller.abort(); setBusy(false, 'Generation cancelled.'); controller = null; } }); form.addEventListener('submit', async (ev) => { ev.preventDefault(); outputSection.classList.add('hidden'); linksDiv.innerHTML = ''; previewVideo.src = ''; logPre.textContent = ''; const prompt = document.getElementById('prompt').value.trim(); const duration = document.getElementById('duration').value; const voice_text = document.getElementById('voice_text').value.trim(); if (!prompt) { statusText.textContent = 'Please enter a prompt.'; return; } const formData = new FormData(); formData.append('prompt', prompt); formData.append('duration', duration); formData.append('voice_text', voice_text); controller = new AbortController(); setBusy(true, 'Sending request to server...'); try { const resp = await fetch('/generate', { method: 'POST', body: formData, signal: controller.signal, }); if (!resp.ok) { const err = await resp.json().catch(() => ({error: 'Unknown error'})); setBusy(false, `Server error: ${err.error || resp.statusText}`); logPre.textContent = JSON.stringify(err, null, 2); return; } const data = await resp.json(); setBusy(false, 'Generation finished.'); // Show download link and video preview const dl = data.download_url; const job = data.job_id; const file = data.file; linksDiv.innerHTML = `Download ${file} (or path: outputs/${job}/${file})`; previewVideo.src = dl; previewVideo.classList.remove('hidden'); previewVideo.load(); outputSection.classList.remove('hidden'); logPre.textContent = `Job: ${job}\nFile: ${file}\nDownload URL: ${dl}`; } catch (err) { if (err.name === 'AbortError') { setBusy(false, 'Request aborted.'); logPre.textContent = 'Generation aborted by user.'; } else { setBusy(false, 'Request failed.'); logPre.textContent = String(err); } } finally { controller = null; } }); })();

No comments:

Post a Comment

Ai

Khalil VideoGen — Frontend Khalil VideoGen Text → short stylized video (local) Promp...