const view=document.querySelector('#view'), error=document.querySelector('#error'); let busy=false, refreshing=false; const esc=v=>String(v??'Nicht verfügbar').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const fmt=(v,unit='')=>v==null?'Nicht verfügbar':`${Number(v).toLocaleString('de-DE',{maximumFractionDigits:1})}${unit}`; const heading=(tag,title,desc)=>`
${tag}

${title}

${desc}

`; async function api(path,method='GET'){const r=await fetch('/api/v1/'+path,{method,headers:method==='POST'?{'X-Athena-Deck':'1'}:{},signal:AbortSignal.timeout(15000)});if(!r.ok)throw Error(`API-Anfrage fehlgeschlagen (${r.status})`);return r.json()} const metric=(title,value)=>`
${title}${value}
`; const bar=v=>v==null?'':``; const placeholders={chat:['Sprachmodelle / Chat','Modellprofile, Laufzeit und Chat','Hier werden später Modellprofile und native llama.cpp-Prozesse verwaltet.'],image:['Bildgenerierung','Worker und Aufträge','Hier entsteht später die Übersicht für Bild-Worker und deren Zustand.'],audio:['Audio','Spracheingabe und Sprachausgabe','Hier werden später getrennte Worker für Erkennung und Ausgabe eingebunden.'],services:['Weitere Dienste','Erweiterbare Dienste','Hier finden später zusätzliche Worker und ihre Schnittstellen Platz.']}; async function refresh(){if(refreshing)return;refreshing=true;const page=location.hash.slice(1)||'home';document.querySelectorAll('nav a').forEach(a=>a.classList.toggle('active',a.hash==='#'+page));try{error.textContent='';let html; if(page==='network'){await networkPage();return;} if(page==='home'){const s=await api('status'),d=s.demo;html=heading('DEIN KONTROLLZENTRUM','Alles beginnt mit einer klaren Basis.','Eine kleine Oberfläche für Status, Hardware und den ersten steuerbaren Dienst.')+`
Anwendung
Bereit

Athena Deck läuft auf ${esc(s.location)}.

Laufzeit ${fmt(s.uptime_seconds,' s')} · API v1
Betriebsmodus
Isoliert

Ein eigenständiger Prototyp mit lesendem Hardware-Zugriff auf Athena.

Keine Modell- oder Router-Steuerung angebunden

Demo-Dienst

LOKAL / CPU

Ein minimaler HTTP-Prozess zum Testen von Start, Stopp und Erreichbarkeit.

${metric('Prozess',d.state==='running'?'Läuft':'Gestoppt')}${metric('Health-Prüfung',d.reachable?'Erreichbar':'Nicht erreichbar')}${metric('Lokaler Port',d.port??'—')}
Lädt keine Modelle · endet beim Beenden von Athena Deck
`; }else if(page==='hardware'){const h=await api('hardware');html=heading('TELEMETRIE / ATHENA','Hardware im Blick.','Live-Messwerte vom Host 192.168.1.212. Aktualisierung alle fünf Sekunden.');if(!h.available)html+=`
Hardware nicht verfügbar

${esc(h.errors.join(' '))}

`;else{const c=h.cpu,r=h.ram;html+=`
CPU

${esc(c.name)}

${metric('Auslastung',fmt(c.percent,' %'))}${metric('Temperatur',fmt(c.temperature_c,' °C'))}
${bar(c.percent)}
System-RAM

Arbeitsspeicher

${metric('Belegt',fmt(r.used_bytes==null?null:r.used_bytes/2**30,' GiB'))}${metric('Gesamt',fmt(r.total_bytes==null?null:r.total_bytes/2**30,' GiB'))}
${bar(r.total_bytes&&r.used_bytes!=null?100*r.used_bytes/r.total_bytes:null)}
`;for(const g of h.gpus)html+=`
GPU ${g.index}${fmt(g.temperature_c,' °C')}

${esc(g.name)}

${metric('GPU-Auslastung',fmt(g.percent,' %'))}${metric('VRAM belegt / gesamt',fmt(g.used_mib==null?null:g.used_mib/1024)+' / '+fmt(g.total_mib==null?null:g.total_mib/1024,' GiB'))}
${bar(g.percent)}
`;if(h.gpus.length<2)html+='
Nicht alle zwei GPUs verfügbar.
';html+=`

${esc(h.source)} · Messung ${new Date(h.sampled_at*1000).toLocaleTimeString('de-DE')}
${esc(h.errors.join(' '))}

`;} }else{const p=placeholders[page]||placeholders.services;html=heading('ARBEITSBEREICH / VORBEREITET',p[0],p[1])+`
+

Platz für den nächsten Schritt.

${p[2]}

Platzhalter · noch keine Funktionen

Installation, Downloads, Presets und Modellwechsel sind in dieser Version nicht enthalten.

`;} if((location.hash.slice(1)||'home')===page){view.innerHTML=html;for(const action of ['start','stop'])document.getElementById(action)?.addEventListener('click',()=>control(action));} }catch(e){error.textContent=e.message;view.innerHTML=heading('VERBINDUNG','Status nicht verfügbar','Die API ist gerade nicht erreichbar. Angezeigte Messwerte wurden verworfen.');}finally{refreshing=false;}} async function control(action){busy=true;document.querySelectorAll('button').forEach(b=>b.disabled=true);try{await api('demo/'+action,'POST')}catch(e){error.textContent=e.message;busy=false;return}busy=false;await refresh()} window.addEventListener('hashchange',refresh);refresh();setInterval(refresh,5000);