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(title)}

${e(detail)}

`; async function api(path,data){const r=await fetch('/api/v1/'+path,data!==undefined?{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(data)}:{}),s=await r.json();if(!r.ok)throw Error(s.error||'Anfrage fehlgeschlagen');return s;} function workerState(s){ if(s.state==='switching')return busy(`Wechsel zu ${labels[s.target_mode]||'anderem Modus'}`,`${s.switch_phase||'Laufende Aufträge beenden und Speicher freigeben'}${s.switch_started_at?' · '+elapsed(s.switch_started_at):''}`); return `
${e(labels[s.state]||s.state)}

GPU-Modus: ${e(labels[s.mode]||s.mode)}${s.profile_name?' · Geladen: '+e(s.profile_name):''}

${s.error?`

${e(s.error)}

`:''}
`; } function jobState(job){ if(!job)return '

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 `
${e(labels[job.state]||job.state)}${job.state==='complete'?'

Das Ergebnis steht unten zur Vorschau und zum Download bereit.

':''}${job.error?`

${e(job.error)}

`:''}
`; } function mode(root,s,profiles){ if(root.dataset.busy==='1')return; const html=`

GPU-Modus

${workerState(s)}

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.

`; if(root._html===html)return;root._html=html;root.innerHTML=html; root.querySelectorAll('[data-mode]').forEach(b=>b.onclick=async()=>{ root.dataset.busy='1';root.querySelectorAll('button').forEach(x=>x.disabled=true); root.querySelector('.video-message').innerHTML=busy('Moduswechsel wird angefordert …'); let next; try{next=await api('video/mode',{mode:b.dataset.mode});}catch(x){root.querySelector('.video-message').textContent=x.message;root.querySelectorAll('button').forEach(x=>x.disabled=false);} finally{root.dataset.busy='0';root._html='';} if(next&&root.isConnected)mode(root,next,profiles); }); } function html(view='test'){ const test=view==='test',runtime=view==='runtime'; return `

${runtime?'LTX-Video-Laufzeit':test?'Video testen':'Video · Laufend'}

${runtime?'

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.

${[['width','Breite',256,256,1920,64],['height','Höhe',256,256,1088,64],['frames','Bilder (8n+1)',9,9,241,8],['fps','FPS',24,1,60,1],['seed','Seed',42,-1,2147483647,1]].map(([n,l,v,min,max,step])=>``).join('')}
`:''}${!runtime?'

Auftragsstatus

':'

Video → Testen bietet einen kurzen Funktionstest; Video → Laufend zeigt den Worker und den aktuellen Auftrag.

'}
`; } function runtime(){const view=document.querySelector('#view');if(view.querySelector('[data-video-view="runtime"]'))return;view.innerHTML=html('runtime');bind(view.firstElementChild);} function bind(root=document.querySelector('#video-runtime-page')){ if(!root)return; const msg=root.querySelector('#video-message'),form=root.querySelector('#video-test');let current=null,pending=false,revision=0,pollError=false; function draw(s){ current=s; const worker=root.querySelector('#video-worker-state');if(worker)worker.innerHTML=workerState(s); const ready=s.mode==='video'&&s.state==='ready',running=s.job?.state==='running'; if(form){ const hint=root.querySelector('#video-mode-hint');hint.innerHTML=ready?'Videomodus bereit. Du kannst einen Test starten.':'Für diesen Test muss der Video-Modus bereit sein. Bitte in der Übersicht auf Video umschalten.'; form.querySelector('button').disabled=pending||running||s.state==='switching'; form.querySelector('button').textContent=pending?'Wird gestartet …':running?'Test läuft …':'Video erzeugen'; form.setAttribute('aria-busy',String(pending||running)); } const job=root.querySelector('#video-job');if(job)job.innerHTML=jobState(s.job); const result=root.querySelector('#video-result'); if(result&&s.job?.state==='complete'&&result.dataset.id!==s.job.id){result.dataset.id=s.job.id;result.innerHTML=`Video herunterladen`;} else if(result&&s.job?.state!=='complete'){result.replaceChildren();delete result.dataset.id;} const installation=root.querySelector('#video-install-status'); if(installation){const r=s.runtime;installation.innerHTML=r.job?.state==='running'?busy('Video-Laufzeit wird installiert',r.job.phase):`

${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=``;} 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}; })();