26 lines
7.9 KiB
JavaScript
26 lines
7.9 KiB
JavaScript
window.VideoUI=(()=>{
|
|
const e=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
async function api(path,data){const r=await fetch('/api/v1/'+path,data?{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(data)}:{});const s=await r.json();if(!r.ok)throw Error(s.error||'Anfrage fehlgeschlagen');return s;}
|
|
function mode(root,s,profiles=[],port=null){
|
|
if(root.dataset.busy==='1')return;
|
|
root.innerHTML=`<section class="card"><h2>GPU-Modus</h2><p>Gemeinsamer API-Port: <strong>${e(port||"siehe Endpunkt")}</strong> · LLM-Modus: OpenAI-API · Video-Modus: ${s.runtime_kind==='original'?'originale LTX-API':'ComfyUI-API'}</p><button data-mode="llm" ${s.mode==='llm'||s.state==='switching'?'disabled':''}>LLM</button><button data-mode="video" ${!s.service?.id||s.mode==='video'||s.state==='switching'?'disabled':''}>Video</button><button data-mode="music" ${!s.music_profiles?.length||s.mode==='music'||s.state==='switching'?'disabled':''}>Musik</button><h3>${e(s.mode==='music'?s.music_profiles?.map(p=>p.name).join(', '):s.service?.name||'Kein Videodienst eingerichtet')}</h3>${s.state==='switching'?`<div class="video-activity" role="status"><strong>${e(s.switch_phase)}</strong><progress></progress></div>`:`<p>${s.mode==='music'?'Musik bereit · Gewichte laden bei Anfrage':s.mode==='llm'?'LLM-Betrieb · Videodienst gestoppt':s.service?.ready?'Video-API bereit':s.service?.running?'Videodienst läuft · API noch nicht bereit':'Dienststatus nicht prüfbar · GPU-Aufträge gesperrt'}</p>`}${s.mode!=='music'&&s.service?.api_url?`<p>Backend: <code>${e(s.service.api_url)}</code></p><p>${e(s.service.message)}</p>${s.service.ready&&port?`<a class="link" target="_blank" rel="noopener" href="${e(location.protocol+"//"+location.hostname+":"+(s.runtime_kind==='original'?8118:port)+"/")}">${s.runtime_kind==='original'?'LTX DeskWEB öffnen':'ComfyUI öffnen'} →</a>`:""}`:''}${s.error?`<p class="video-error" role="alert">${e(s.error)}</p>`:''}<p>Modelle, Auflösung, Dauer und Generierung werden in der jeweiligen Video-Anwendung verwaltet. Video beendet Deck-GPU-Aufträge; fremde Dienste werden nicht beendet.</p><a href="#video">Aktiven Videodienst im Bereich Video wählen</a>${!s.music_profiles?.length?'<p>Für Musik zuerst unter <a href="#music">Musik → Profile</a> ein ausführbares Profil am API-Endpunkt freigeben.</p>':''}<div class="video-message" role="status"></div></section>`;
|
|
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').textContent='Moduswechsel wird angefordert …';try{const next=await api('video/mode',{mode:b.dataset.mode});root.dataset.busy='0';mode(root,next,profiles,port);}catch(x){root.querySelector('.video-message').textContent=x.message;root.dataset.busy='0';}});
|
|
}
|
|
function services(root){
|
|
if(!root||root.dataset.bound)return;root.dataset.bound='1';
|
|
async function poll(){if(!root.isConnected)return;try{const s=await api('video');if(root.dataset.busy!=='1'){
|
|
root.innerHTML=`<section class="card"><h2>Video-Laufzeit</h2><label>Laufzeit auswählen<select id="video-runtime-choice" ${s.mode!=='llm'?'disabled':''}><option value="comfy" ${s.runtime_kind!=='original'?'selected':''}>ComfyUI · gemeinsam mit Bildgenerierung</option><option value="original" ${s.runtime_kind==='original'?'selected':''}>LTX Original · LTX DeskWEB</option></select></label><p>${s.runtime?.installed?'Vorhanden · bereit':s.runtime_kind==='original'?'Nicht installiert · unter Einstellungen → LTX Original einrichten':'Nicht installiert · unter Einstellungen → Bildgenerierung einrichten'}</p><p>5080: Diffusionsmodell und VAE. 3060: Textencoder. Große BF16-Dateien nutzen zusätzlich CPU-Auslagerung. Gewichte laden erst bei einer Anfrage.</p><a href="#ltx-original-runtime">LTX Original installieren / Status →</a></section>${(s.models||[]).map(x=>`<section class="card"><h3>${e(x.model.file)}</h3><strong>${e(x.label)}</strong><button class="secondary" data-components-model="${e(x.model.id)}">Komponenten prüfen / nachladen</button>${x.blockers.map(b=>`<p class="warning">${e(b)}</p>`).join('')}<p>${Object.keys(x.components).length} Komponenten geprüft · gleiche Quellversion</p><button data-select="${e(x.model.id)}" ${s.mode!=='llm'||x.model.id===s.selected?'disabled':''}>${x.model.id===s.selected?'Aktives Videomodell':'Als Videomodell aktivieren'}</button>${x.model.id===s.selected&&s.mode==='llm'?'<button data-select="">Auswahl aufheben</button>':''}</section>`).join('')||'<p>Kein unterstütztes LTX-Modell in der Bibliothek. Unter Entdecken herunterladen.</p>'}<p class="video-message" role="status"></p>`;
|
|
root.querySelector('#video-runtime-choice').onchange=async event=>{root.dataset.busy='1';try{await api('video/runtime',{runtime:event.target.value});}catch(x){root.querySelector('.video-message').textContent=x.message;}finally{root.dataset.busy='0';poll();}};
|
|
root.querySelectorAll('[data-components-model]').forEach(b=>b.onclick=async()=>{root.dataset.busy='1';try{const data=await api('profiles/components?'+new URLSearchParams({model_id:b.dataset.componentsModel}));root.innerHTML=`<section class="card"><h2>LTX-Komponenten</h2><p>${e(data.message)}</p>${data.requirements.map((r,i)=>`<p><strong>${e(r.label)}</strong> · ${r.available.length?'Bereits heruntergeladen':r.candidates.length?'Fehlt':'In dieser Quellversion nicht gefunden'}</p>${!r.available.length?r.candidates.map((c,j)=>`<button data-download-component="${i}:${j}">${e(c.name)} herunterladen</button>`).join(''):''}`).join('')}<p class="video-message"></p><button data-close-components>Zurück</button></section>`;root.querySelector('[data-close-components]').onclick=()=>{root.dataset.busy='0';poll();};root.querySelectorAll('[data-download-component]').forEach(d=>d.onclick=async()=>{const [i,j]=d.dataset.downloadComponent.split(':');const f=data.requirements[i].candidates[j];d.disabled=true;try{await api('catalog/download',{repo:f.repo,filename:f.name,revision:f.revision,kind:'video'});root.querySelector('.video-message').textContent='Download eingeplant. Fortschritt unter Downloads. Nach Abschluss werden die Komponenten automatisch erkannt.';}catch(x){root.querySelector('.video-message').textContent=x.message;d.disabled=false;}});}catch(x){root.dataset.busy='0';root.querySelector('.video-message').textContent=x.message;}});
|
|
root.querySelectorAll('[data-select]').forEach(b=>b.onclick=async()=>{root.dataset.busy='1';try{await api('video/service',{id:b.dataset.select});root.querySelector('.video-message').textContent='Videomodell ausgewählt. Start über die Übersicht.';}catch(x){root.querySelector('.video-message').textContent=x.message;}finally{root.dataset.busy='0';}});
|
|
}}catch(x){root.textContent=x.message;}if(root.isConnected)setTimeout(poll,5000);}poll();
|
|
}
|
|
function runtime(){
|
|
const view=document.querySelector('#view');if(view.querySelector('#video-service-page'))return;
|
|
view.innerHTML='<section id="video-service-page"><h1>Video</h1><p>LTX Original mit LTX DeskWEB oder die vorhandene ComfyUI-Laufzeit auswählen. Deck verwaltet Modelle, Komponenten und den GPU-Modus; Generierungsparameter gehören in die jeweilige Video-Oberfläche.</p><div class="studio-tabs"><button data-video-tab="runtime">Laufzeit / Aktives Modell</button><button data-video-tab="discover">Entdecken</button><button data-video-tab="library">Bibliothek</button><button data-video-tab="downloads">Downloads</button></div><div id="video-content"></div></section>';
|
|
function show(tab){const root=view.querySelector('#video-content');if(tab==='runtime'){root.innerHTML='<div id="video-services"></div>';services(root.firstChild);}else{root.innerHTML=CatalogUI.html(tab==='library',tab==='downloads');CatalogUI.bind('video',tab==='library',tab==='downloads');}}
|
|
view.querySelectorAll('[data-video-tab]').forEach(b=>b.onclick=()=>show(b.dataset.videoTab));show('runtime');
|
|
}
|
|
return {mode,services,runtime};
|
|
})();
|