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

12 lines
4.4 KiB
JavaScript

window.AutoTestUI=(()=>{
const e=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
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)});const v=await r.json();if(!r.ok)throw Error(v.error||'Anfrage fehlgeschlagen');return v;}
function html(){return `<section id="auto-test"><h2>Automatische Einpassung & Leistungstest</h2><p>Priorität: RTX 5080 allein → möglichst kleiner Anteil auf RTX 3060 → erst zuletzt CPU-Auslagerung. Synthetische Tests belegen die Deck-Laufzeit exklusiv; API-Anfragen warten. Fremde GPU-Dienste werden nicht beendet.</p><form id="auto-form" class="card"><label>Heruntergeladenes Sprachmodell<select id="auto-model" required></select></label><label>Bis zu diesem Kontext testen<select id="auto-context">${[4096,8192,16384,32768,65536,131072,160000,192000,262144].map(x=>`<option ${x===16384?'selected':''}>${x}</option>`).join('')}</select></label><label><input id="auto-mtp" type="checkbox"> MTP testen (2 Draft-Token, p-min 0,05; kompatible Gewichte erforderlich)</label><p>Ein Slot, Batch 2048, Microbatch 128 / 64 / 256. Kontextstufen ab 2048; pro Kandidat werden 75 % des Kontextes tatsächlich gefüllt und 64 Token erzeugt. Höchstens 48 Kandidaten bzw. zwei Stunden zwischen Kandidaten. Kein absichtliches Übergehen der Speicherreserve.</p><button id="auto-start">Auto-Test starten</button><button id="auto-stop" type="button" class="secondary">Abbrechen</button></form><p id="auto-message" role="alert"></p><p id="auto-phase" role="status"></p><p>Dies ist eine begrenzte Suche, kein Beweis des absoluten Optimums oder der Stabilität bei vollem Kontext und paralleler Last. Erfolgreiche Profile werden nur auf Wunsch gespeichert, nicht am Endpunkt freigegeben.</p><label>Name für das zu speichernde Profil<input id="auto-name" value="auto-llm" pattern="[a-zA-Z0-9][a-zA-Z0-9_-]{1,63}" maxlength="64"></label><div id="auto-results"></div></section>`;}
function bind(){const root=document.querySelector('#auto-test'),el=x=>root.querySelector('#'+x);let job=null,last='';const msg=x=>{if(root.isConnected)el('auto-message').textContent=x;};
api('catalog').then(s=>{if(root.isConnected)el('auto-model').innerHTML=s.entries.filter(x=>x.kind==='chat'&&x.profile_eligible&&x.file.endsWith('.gguf')).map(x=>`<option value="${e(x.id)}">${e(x.file)}</option>`).join('');}).catch(x=>msg(x.message));
async function refresh(){try{const s=await api('auto-tests');if(!root.isConnected)return;job=s.job;const running=job?.state==='running';el('auto-start').disabled=running;el('auto-stop').disabled=!running;el('auto-phase').textContent=job?`${job.state} · ${job.phase} · ${job.attempts} Kandidaten${job.error?' · '+job.error:''}`:'Noch kein Auto-Test.';const rows=job?.results||[],signature=JSON.stringify(rows);if(last!==signature){last=signature;const best=rows.reduce((a,r,i)=>r.success&&(!a||r.timings.predicted_per_second>a.speed)?{i,speed:r.timings.predicted_per_second}:a,null);el('auto-results').innerHTML=rows.map((r,i)=>`<article class="card"><h3>${r.context.toLocaleString('de-DE')} Kontext · ${e(r.tier)} ${best?.i===i?'· schnellster gemessener Kandidat':''}</h3><p>Verteilung ${e(r.parameters.tensor_split.join(':')||'nur 5080')} · Microbatch ${r.parameters.ubatch}</p>${r.success?`<p>Prefill ${Number(r.timings.prompt_per_second).toFixed(1)} Token/s · Ausgabe ${Number(r.timings.predicted_per_second).toFixed(1)} Token/s · ${r.timings.prompt_n} Eingabe-Token geprüft</p><p>GPU-Layerlimit: ${r.memory_plan.gpu_layers===999?'alle':r.memory_plan.gpu_layers}</p><button data-save="${i}">Als neues Profil speichern</button>`:`<p>${e(r.error)}</p>`}</article>`).join('');root.querySelectorAll('[data-save]').forEach(b=>b.onclick=async()=>{try{await api('auto-tests/save',{job_id:job.id,index:Number(b.dataset.save),name:el('auto-name').value});msg('Profil gespeichert.');}catch(x){msg(x.message);}});}}catch(x){msg(x.message);}finally{if(root.isConnected)setTimeout(refresh,2000);}}
el('auto-form').onsubmit=async ev=>{ev.preventDefault();try{await api('auto-tests/start',{model_id:el('auto-model').value,max_context:Number(el('auto-context').value),mtp:el('auto-mtp').checked});msg('Auto-Test gestartet.');}catch(x){msg(x.message);}};
el('auto-stop').onclick=()=>api('auto-tests/cancel',{}).then(()=>msg('Abbruch angefordert.')).catch(x=>msg(x.message));refresh();
}return {html,bind};
})();