Files
Athena-Deck/app.js
T

41 lines
4.8 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.
const view=document.querySelector('#view'), error=document.querySelector('#error');
let refreshing=false;
const esc=v=>String(v??'Nicht verfügbar').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const fmt=(v,unit='')=>v==null?'Nicht verfügbar':`${Number(v).toLocaleString('de-DE',{maximumFractionDigits:1})}${unit}`;
const heading=(tag,title,desc)=>`<div class="kicker">${tag}</div><h1>${title}</h1><p>${desc}</p>`;
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.status===401){location.reload();throw Error('Bitte erneut anmelden.')}if(!r.ok)throw Error(`API-Anfrage fehlgeschlagen (${r.status})`);return r.json()}
const metric=(title,value)=>`<div><small>${title}</small><strong>${value}</strong></div>`;
const bar=v=>v==null?'':`<progress max="100" value="${Number(v)}" aria-label="Auslastung"></progress>`;
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.']};
const DECK_THEME_KEY='athena-deck-theme';
const DECK_THEMES=new Set(['deck','athena-classic']);
function applyDeckTheme(theme){
const selected=DECK_THEMES.has(theme)?theme:'deck';
document.documentElement.dataset.theme=selected;
try{localStorage.setItem(DECK_THEME_KEY,selected)}catch(_){}
return selected;
}
try{applyDeckTheme(localStorage.getItem(DECK_THEME_KEY))}catch(_){applyDeckTheme('deck')}
function appearancePage(){
if(view.querySelector('#appearance-page'))return;
view.innerHTML=`<div id="appearance-page"><div class="kicker">EINSTELLUNGEN / DARSTELLUNG</div><h1>Darstellung</h1><p>Wähle die Farben für Athena Deck. Die Auswahl gilt in diesem Browser und wird sofort angewendet.</p><div class="theme-options" role="group" aria-label="Farbschema"><button type="button" class="theme-choice" data-theme-choice="deck" aria-pressed="false"><span class="theme-preview deck-preview"><i></i><i></i><i></i></span><strong>Deck Standard</strong><small>Anthrazit, Salbeigrün und helle Akzente</small></button><button type="button" class="theme-choice" data-theme-choice="athena-classic" aria-pressed="false"><span class="theme-preview classic-preview"><i></i><i></i><i></i></span><strong>Athena Classic</strong><small>Dunkelblau, Cyan und Mint wie im alten Dashboard</small></button></div></div>`;
const sync=()=>view.querySelectorAll('[data-theme-choice]').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.themeChoice===document.documentElement.dataset.theme)));
view.querySelectorAll('[data-theme-choice]').forEach(b=>b.addEventListener('click',()=>{applyDeckTheme(b.dataset.themeChoice);sync()}));
sync();
}
async function refresh(){if(refreshing)return;refreshing=true;const page=location.hash.slice(1)||'dashboard';document.querySelectorAll('nav a').forEach(a=>a.classList.toggle('active',a.hash==='#'+page));try{error.textContent='';let html;
if(page==='audio-cpp-runtime'){AudioCppUI.render();return;}
if(page==='ltx-original-runtime'){LTXOriginalUI.render();return;}
if(['video','video-runtime'].includes(page)){VideoUI.runtime();return;}
if(['chat','image','audio','stt','music','voice','video','runtime','runtimes','image-runtime','stt-runtime','tts-runtime','docker-runtime','services'].includes(page)){Studio.render(page);return;}
if(page==='backup'){BackupUI.render();return;}
if(page==='access'){await accessPage();return;}
if(page==='appearance'){appearancePage();return;}
if(page==='network'){await networkPage();return;}
if(page==='dashboard'){DashboardUI.render();return;}
if(page==='home'||page==='endpoint'){await EndpointUI.render(page==='endpoint');return;
}else{const p=placeholders[page]||placeholders.services;html=heading('ARBEITSBEREICH / VORBEREITET',p[0],p[1])+`<section class="empty"><div class="symbol">+</div><h2>Platz für den nächsten Schritt.</h2><p>${p[2]}</p><span class="pill">Platzhalter · noch keine Funktionen</span><p class="note">Installation, Downloads, Presets und Modellwechsel sind in dieser Version nicht enthalten.</p></section>`;}
if((location.hash.slice(1)||'dashboard')===page){view.innerHTML=html;}
}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;}}
window.addEventListener('hashchange',refresh);refresh();setInterval(refresh,5000);