Files
Athena-Deck/image-test-ui.js
T

25 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
window.ImageTestUI=(()=>{
const e=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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 v=await r.json();if(!r.ok)throw Error(v.error||'Anfrage fehlgeschlagen');return v;}
function html(readonly=false){return `<section id="image-test"><div class="section-heading"><div><h2>${readonly?'Bildlaufzeit':'Bildmodell testen'}</h2><p>Ein Bild mit deinem gespeicherten Profil erzeugen. Der eigene Worker beendet sich nach dem Auftrag.</p></div></div>${readonly?'':`<form id="image-test-form" class="card"><label>Bildprofil<select name="profile_id" id="image-test-profile" required><option value="">Profile werden geladen …</option></select></label><p id="image-profile-info"></p><label>Prompt<textarea name="prompt" maxlength="4000" rows="5" required placeholder="Beschreibe das gewünschte Bild …"></textarea></label><button id="image-generate" disabled>Bild generieren</button><p class="small">Auflösung, Schritte, Guidance und Seed stammen aus dem Profil. Maximal 1024 × 1024 Pixel. Nur eine freie GPU wird genutzt; Deck stoppt keine produktiven Modelle. Der erste Start kann einige Minuten dauern.</p></form>`}<p class="note">Textencoder: CPU / System-RAM · Diffusionsmodell und VAE: GPU / VRAM. GPU-Last entsteht nicht während aller Verarbeitungsschritte. <a class="link" href="#hardware">Aktuelle Hardware-Messwerte →</a></p><p id="image-test-message" role="status"></p><section id="image-test-state" class="card"></section><section id="image-test-result"></section></section>`;}
function bind(readonly=false){
const root=document.querySelector('#image-test'),el=id=>root.querySelector('#'+id);let profiles=[],status=null,lastJob='',timer,submitting=false;
const message=t=>{if(root.isConnected)el('image-test-message').textContent=t;};
function selection(){if(readonly)return;const p=profiles.find(p=>p.id===el('image-test-profile').value);el('image-profile-info').textContent=p?`${p.parameters.width} × ${p.parameters.height} · ${p.parameters.steps} Schritte · Guidance ${p.parameters.guidance} · Seed ${p.parameters.seed}${p.blockers.length?' · '+p.blockers.join(' '):''}`:'';el('image-generate').disabled=submitting||!p?.runnable||!status?.runtime_installed||status?.job?.state==='running';}
async function refresh(){try{status=await api('image-tests');if(!root.isConnected)return;const j=status.job,signature=JSON.stringify(j);if(signature!==lastJob){lastJob=signature;el('image-test-state').innerHTML=j?`<span class="pill">${e({running:'LÄUFT',complete:'FERTIG',failed:'FEHLGESCHLAGEN',cancelled:'ABGEBROCHEN',interrupted:'UNTERBROCHEN'}[j.state]||j.state)}</span><h3>${e(j.profile_name)}</h3><p>${e(j.phase)}</p><p class="small">${e(j.gpu)} · Seed ${e(j.seed)}</p>${j.state==='running'?'<button class="secondary" id="image-cancel">Bildtest abbrechen</button>':''}`:`<h3>${status.runtime_installed?'Bildlaufzeit ist installiert':'Bildlaufzeit noch nicht installiert'}</h3><p>Noch kein Bildtest gestartet.</p>`;
el('image-test-result').innerHTML=j?.state==='complete'?`<figure class="card"><img class="generated-image" src="/api/v1/image-tests/image?id=${encodeURIComponent(j.id)}" alt="Ergebnis des Bildtests"><figcaption><a class="link" href="/api/v1/image-tests/image?id=${encodeURIComponent(j.id)}" download="athena-${e(j.id)}.png">PNG herunterladen</a></figcaption></figure>`:'';
el('image-cancel')?.addEventListener('click',async event=>{event.target.disabled=true;try{await api('image-tests/cancel',{});message('Abbruch angefordert; der eigene Bildworker wird beendet.');}catch(error){message(error.message);}});
}selection();}catch(error){message(error.message);}finally{if(root.isConnected)timer=setTimeout(refresh,2000);}}
if(!readonly){api('profiles').then(v=>{if(!root.isConnected)return;profiles=v.profiles.filter(p=>p.kind==='image');el('image-test-profile').innerHTML=profiles.map(p=>`<option value="${p.id}">${e(p.name)}${p.runnable?'':' · Komponenten / Laufzeit prüfen'}</option>`).join('')||'<option value="">Zuerst ein Bildprofil anlegen</option>';selection();}).catch(error=>message(error.message));el('image-test-profile').onchange=selection;
el('image-test-form').onsubmit=async event=>{event.preventDefault();submitting=true;selection();try{await api('image-tests/start',Object.fromEntries(new FormData(event.target)));message('Bildtest gestartet.');}catch(error){message(error.message);}finally{submitting=false;selection();}};
}
refresh();
}
function bindInstaller(){
const root=document.querySelector('#image-runtime-install');let pending=false;
async function refresh(){try{const s=await api('image-runtime');if(!root.isConnected)return;const running=s.job?.state==='running';root.innerHTML=`<p><strong>${s.installed?'Installiert · '+e(s.origin):'Noch nicht installiert'}</strong></p><p class="small">ComfyUI ${e(s.comfy_revision.slice(0,12))} · GGUF ${e(s.gguf_revision.slice(0,12))}</p><p>${e(s.job?.phase||'Installation richtet eine eigene Python-Umgebung mit ComfyUI, GGUF-Unterstützung und CUDA-PyTorch ein. Mindestens 25 GiB freier Speicher und Internetzugang erforderlich.')}</p><button id="image-runtime-action" ${pending||s.installed?'disabled':''}>${s.installed?'Bereits installiert':running?'Installation abbrechen':'Bildlaufzeit installieren'}</button><p class="small">Systemvoraussetzungen: Python mit venv, Git und kompatibler NVIDIA-Treiber. Die Installation ändert keine Host-Pakete oder anderen Dienste.</p>`;root.querySelector('button').onclick=async()=>{pending=true;root.querySelector('button').disabled=true;try{await api('image-runtime/'+(running?'cancel':'install'),{});}catch(error){alert(error.message);}finally{pending=false;}};}catch(error){if(root.isConnected)root.textContent=error.message;}finally{if(root.isConnected)setTimeout(refresh,2000);}}
refresh();
}
return {html,bind,bindInstaller};
})();