window.VideoUI=(()=>{ const e=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const labels={llm:'LLM',video:'Video',switching:'Wechsel läuft',idle:'Bereit für LLM',ready:'Videoprofil bereit',failed:'Fehlgeschlagen',running:'Video wird erzeugt',complete:'Video fertig'}; const elapsed=t=>{const seconds=Math.max(0,Math.floor(Date.now()/1000-t));return `${Math.floor(seconds/60)} Min. ${seconds%60} Sek.`;}; const busy=(title,detail='')=>`
${e(detail)}
GPU-Modus: ${e(labels[s.mode]||s.mode)}${s.profile_name?' · Geladen: '+e(s.profile_name):''}
${s.error?`${e(s.error)}
`:''}Noch kein Video-Test gestartet.
'; if(job.state==='running')return busy('Video-Test läuft',`${job.phase||'Auftrag wird vorbereitet'} · Laufzeit ${elapsed(job.created_at)}. BF16 mit Disk-Streaming kann mehrere Minuten benötigen. Eine verlässliche Prozentanzeige liefert der Worker noch nicht.`); return `Das Ergebnis steht unten zur Vorschau und zum Download bereit.
':''}${job.error?`${e(job.error)}
`:''}Freigegebenes Videoprofil: ${e(profiles.find(p=>p.id===s.profile_id)?.name||'Keines')} · Unter Video → Profile freigeben
${e(s.memory_policy)}
Video beendet laufende Deck-GPU-Aufträge und sperrt Chat, Bild und TTS. CPU-STT bleibt verfügbar.
Eigene LTX-Python-Umgebung mit CUDA und Disk-Streaming auf der RTX 5080.
':''}${test?`Kurzer Text-zu-Video-Test mit dem freigegebenen Profil. Werte gelten nur für diesen Auftrag. Noch kein Bild-/Audio-Upload.
Video → Testen bietet einen kurzen Funktionstest; Video → Laufend zeigt den Worker und den aktuellen Auftrag.
'}${e(r.installed?'Installiert':r.job?.phase||'Noch nicht installiert')}
`;root.querySelector('#video-install').disabled=pending||r.installed||r.job?.state==='running';root.querySelector('#video-cancel').disabled=pending||r.job?.state!=='running';} } async function action(path,data){ if(pending)return; if(path==='video/generate'&&(!current||current.mode!=='video'||current.state!=='ready')){msg.innerHTML='Falscher Modus oder Video noch nicht bereit. Bitte zuerst in der Übersicht auf Video umschalten.
';return;} pending=true;revision++;root.querySelectorAll('button').forEach(b=>b.disabled=true); msg.innerHTML=busy(path==='video/generate'?'Video-Test wird gestartet …':'Anfrage wird verarbeitet …'); try{const value=await api(path,data);if(!root.isConnected)return; if(path==='video/generate'){current={...current,job:value};msg.textContent='Test gestartet. Der Fortschritt steht unter Auftragsstatus.';root.querySelector('#video-result').replaceChildren();draw(current);}else {current={...current,runtime:value};msg.textContent='Auftrag angenommen.';} }catch(x){if(root.isConnected)msg.innerHTML=`${e(x.message)}
`;} finally{pending=false;revision++;if(root.isConnected&¤t)draw(current);} } root.querySelector('#video-install')?.addEventListener('click',()=>action('video-runtime/install',{})); root.querySelector('#video-cancel')?.addEventListener('click',()=>action('video-runtime/cancel',{})); if(form)form.onsubmit=ev=>{ev.preventDefault();const d=Object.fromEntries(new FormData(form));for(const k of ['width','height','frames','fps','seed'])d[k]=Number(d[k]);action('video/generate',d);}; async function poll(){if(!root.isConnected)return;const version=revision; try{const s=await api('video');if(root.isConnected&&!pending&&version===revision){if(pollError){msg.textContent='Verbindung wiederhergestellt.';pollError=false;}draw(s);}} catch(x){if(root.isConnected&&!pending&&version===revision){pollError=true;msg.innerHTML=`Status momentan nicht erreichbar: ${e(x.message)}. Ein laufender Auftrag kann weiterlaufen.
`;current=null;if(form)form.querySelector('button').disabled=true;}} if(root.isConnected)setTimeout(poll,1500); }poll(); } return {mode,runtime,html,bind}; })();