@@ -31,6 +31,9 @@ SEPARATOR_UI_URL = os.getenv("SEPARATOR_UI_URL", "http://192.168.1.212:8007/")
VOICE_UI_URL = os . getenv ( " VOICE_UI_URL " , " http://192.168.1.212:8008/ " )
VOICE_CHANGE_UI_URL = os . getenv ( " VOICE_CHANGE_UI_URL " , " http://192.168.1.212:8009/ " )
APPLIO_UI_URL = os . getenv ( " APPLIO_UI_URL " , " http://192.168.1.212:8011/ " )
MIKES_APPLIO_UI_URL = os . getenv (
" MIKES_APPLIO_UI_URL " , " http://192.168.1.212:8012/ "
)
HOST_PROC = Path ( os . getenv ( " HOST_PROC " , " /host/proc " ) )
HOST_DATA = os . getenv ( " HOST_DATA " , " /host/data " )
HOST_MODELS = Path ( os . getenv ( " HOST_MODELS " , " /host/models " ) )
@@ -638,7 +641,7 @@ HTML = r'''<!doctype html>
</style></head><body><main>
<div class= " top " ><div><div class= " eyebrow " >Mike AI · Live Telemetry</div><h1>Athena llama.cpp Dashboard</h1></div><div class= " live " ><span class= " dot " id= " dot " ></span><span id= " updated " >verbinde …</span></div></div>
<section class= " grid " >
<article class= " card span12 " ><div class= " mode-row " ><div><div class= " label " >Athena Betriebsmodus</div><div class= " value " id= " operatingMode " >– </div><div class= " sub " id= " modeStatus " >Status wird geladen …</div></div><div class= " mode-buttons " ><button id= " llmMode " onclick= " setMode( ' llm ' ) " >LLM-Betrieb</button><button id= " musicMode " onclick= " setMode( ' music ' ) " >Musikstudio</button><button id= " separationMode " onclick= " setMode( ' separation ' ) " >Audio trennen</button><button id= " voiceMode " onclick= " setMode( ' voice ' ) " >Voice Studio</button><button id= " voiceChangeMode " onclick= " setMode( ' voicechange ' ) " >X-VC</button><button id= " applioMode " onclick= " setMode( ' applio ' ) " >Applio / RVC · Modell nötig</button><span id= " musicOpen " hidden><a class= " stable " href= " __MUSIC_ORIGINAL_UI_URL__ " target= " _blank " rel= " noopener " >Original UI · stabil</a><a class= " experimental " href= " __MUSIC_COMMUNITY_UI_URL__ " target= " _blank " rel= " noopener " >Community UI · experimentell</a></span><span id= " separatorOpen " hidden><a class= " stable " href= " __SEPARATOR_UI_URL__ " target= " _blank " rel= " noopener " >Separator öffnen</a></span><span id= " voiceOpen " hidden><a class= " stable " href= " __VOICE_UI_URL__ " target= " _blank " rel= " noopener " >Voice Studio öffnen</a></span><span id= " voiceChangeOpen " hidden><a class= " experimental " href= " __VOICE_CHANGE_UI_URL__ " target= " _blank " rel= " noopener " >X-VC öffnen</a></span><span id= " applioOpen " hidden><a class= " experimental " href= " __APPLIO_UI_URL__ " target= " _blank " rel= " noopener " >Applio öffnen · RVC-Modell erforderlich </a></span></div></div></article>
<article class= " card span12 " ><div class= " mode-row " ><div><div class= " label " >Athena Betriebsmodus</div><div class= " value " id= " operatingMode " >– </div><div class= " sub " id= " modeStatus " >Status wird geladen …</div></div><div class= " mode-buttons " ><button id= " llmMode " onclick= " setMode( ' llm ' ) " >LLM-Betrieb</button><button id= " musicMode " onclick= " setMode( ' music ' ) " >Musikstudio</button><button id= " separationMode " onclick= " setMode( ' separation ' ) " >Audio trennen</button><button id= " voiceMode " onclick= " setMode( ' voice ' ) " >Voice Studio</button><button id= " voiceChangeMode " onclick= " setMode( ' voicechange ' ) " >X-VC</button><button id= " applioMode " onclick= " setMode( ' applio ' ) " >Applio / RVC · Modell nötig</button><span id= " musicOpen " hidden><a class= " stable " href= " __MUSIC_ORIGINAL_UI_URL__ " target= " _blank " rel= " noopener " >Original UI · stabil</a><a class= " experimental " href= " __MUSIC_COMMUNITY_UI_URL__ " target= " _blank " rel= " noopener " >Community UI · experimentell</a></span><span id= " separatorOpen " hidden><a class= " stable " href= " __SEPARATOR_UI_URL__ " target= " _blank " rel= " noopener " >Separator öffnen</a></span><span id= " voiceOpen " hidden><a class= " stable " href= " __VOICE_UI_URL__ " target= " _blank " rel= " noopener " >Voice Studio öffnen</a></span><span id= " voiceChangeOpen " hidden><a class= " experimental " href= " __VOICE_CHANGE_UI_URL__ " target= " _blank " rel= " noopener " >X-VC öffnen</a></span><span id= " applioOpen " hidden><a class= " stable " href= " __APPLIO_UI_URL__ " target= " _blank " rel= " noopener " >Original Applio UI</a><a class= " stable " href= " __MIKES_APPLIO_UI_URL__ " target= " _blank " rel= " noopener " >Mikes Applio UI </a></span></div></div></article>
<article class= " card span3 " ><div class= " label " >Aktives Profil</div><div class= " value " id= " profile " >– </div><div class= " sub " id= " profileSub " >Router wird abgefragt</div></article>
<article class= " card span3 " ><div class= " label " >Modell</div><div class= " value " id= " model " >– </div><div class= " sub " id= " modelSub " >– </div></article>
<article class= " card span3 " ><div class= " label " >CPU</div><div class= " value " id= " cpu " >– </div><div class= " bar " ><div class= " fill " id= " cpuBar " ></div></div><div class= " sub " id= " load " >– </div></article>
@@ -678,30 +681,15 @@ function gpuCard(g){let total=g.memory_total_mib||0,used=g.memory_used_mib||0,p=
const imagePhaseLabel=p=>( { " stopping-qwen " : " Qwen wird entladen " , " loading-image " : " Bildmodell wird geladen " , " generating " : " Bild wird generiert " , " unloading-image " : " Bildmodell wird entladen " , " restoring-qwen " : " Qwen wird wiederhergestellt " }[p]||p|| ' bereit ' );
let modeBusy=false;
async function setMode(mode) { if(modeBusy)return;modeBusy=true;for(const id of [ ' llmMode ' , ' musicMode ' , ' separationMode ' , ' voiceMode ' , ' voiceChangeMode ' , ' applioMode ' ])$(id).disabled=true;$( ' modeStatus ' ).textContent= ' Umschaltung angefordert … ' ;try { let r=await fetch( ' /api/mode ' , { method: ' POST ' ,headers: { ' Content-Type ' : ' application/json ' },body:JSON.stringify( {mode} )});let d=await r.json();if(!r.ok)throw Error(d?.error?.message||d?.error||`HTTP $ {r.status} `);$( ' modeStatus ' ).textContent= ' Umschaltung läuft … ' }catch(e) { $( ' modeStatus ' ).textContent=e.message;$( ' modeStatus ' ).classList.add( ' mode-error ' )}finally { modeBusy=false;setTimeout(refresh,250)}}
async function refresh() { try { let r=await fetch( ' /api/status ' , { cache: ' no-store ' });if(!r.ok)throw Error(`HTTP $ {r.status} `);let d=await r.json(),c=d.cpu|| {} ,m=c.memory|| {} ,rt=d.router|| {} ,up=rt.upstream|| {} ,q=rt.qwen|| {} ,lr=d.llama_runtime|| {} ,img=rt.image|| {} ,imageActive=img.phase&&img.phase!== ' idle ' ;let md=rt.mode|| {} ,switchingMode=md.phase&&md.phase!== ' ready ' ,modeName=md.active=== ' music ' ? ' Musikstudio ' :md.active=== ' separation ' ? ' Stimmtrennung ' :md.active=== ' voice ' ? ' Voice Stud io' : ' LLM-Betrieb ' ;$( ' operatingMode ' ).textContent=modeName;$( ' modeStatus ' ).textContent=switchingMode?`Umschaltung: $ {md.phase} `:(md.last_error||`Musik: $ { md.music_worker|| ' – ' } · Separator: $ { md.separator_worker|| ' – ' } · Voice: $ { md.voice_worker|| ' – ' }$ { md.return_profile?` · Rückkehr zu $ {md.return_profile} `: ' ' }`);$( ' modeStatus ' ).classList.toggle( ' mode-error ' ,!!md.last_error);$( ' llmMode ' ).classList.toggle( ' active ' ,md.active=== ' llm ' );$( ' musicMode ' ).classList.toggle( ' active ' ,md.active=== ' music ' );$( ' separationMode ' ).classList.toggle( ' active ' ,md.active=== ' separation ' );$( ' voiceMode ' ).classList.toggle( ' active ' ,md.active=== ' voice ' );$( ' llmMode ' ).disabled= $( ' musicMode ' ).disabled=$ ( ' separationMode ' ).disabled=$( ' voiceMode ' ).disabled =modeBusy||switchingMode||!md.enabled;$( ' musicOpen ' ).hidden=md.active!== ' music ' ;$( ' separatorOpen ' ).hidden=md.active!== ' separation ' ;$( ' voiceOpen ' ).hidden=md.active!== ' voice ' ;$( ' profile ' ).textContent=imageActive? ' Bildgenerierung ' :(rt.current_profile|| ' nicht geladen ' );$( ' profileSub ' ).textContent=imageActive?imagePhaseLabel(img.phase):(rt.switching?`Wechsel zu $ {rt.switching} `:`Kontext: $ { up.ctx?up.ctx.toLocaleString( ' de-DE ' ): ' – ' } Token`);$( ' model ' ).textContent=imageActive?(img.model|| ' Bildmodell ' ):(up.model|| ' – ' );$( ' modelSub ' ).textContent=imageActive?`$ { img.model_loaded? ' geladen ' : ' wird vorbereitet ' } · Worker $ { img.worker|| ' – ' }`:(lr.model_file|| (up.reachable? ' llama.cpp erreichbar ' : ' llama.cpp nicht erreichbar ' ));$( ' cpu ' ).textContent=pct(c.usage_percent);$( ' cpuBar ' ).style.width=`$ { c.usage_percent||0} % `;$( ' load ' ).textContent=`$ { c.logical_cpus|| ' – ' } Threads · Load $ { (c.load||[]).join( ' / ' )}`;let rp=m.total?m.used/m.total*100:0;$( ' ram ' ).textContent=pct(rp);$( ' ramBar ' ).style.width=`$ {rp} % `;$( ' ramSub ' ).textContent=`$ { gib(m.used)} / $ { gib(m.total)}`;$( ' gpuCards ' ).innerHTML=(d.gpus||[]).map(gpuCard).join( ' ' )|| ' <article class= " card span12 error " >Keine GPU-Daten verfügbar</article> ' ;$( ' availability ' ).textContent=imageActive?imagePhaseLabel(img.phase):(q.available? ' bereit ' : ' nicht bereit ' );$( ' availability ' ).className=`value status $ { (imageActive||q.available)? ' ' : ' bad ' }`;$( ' activeChats ' ).textContent=q.active_chats?? ' – ' ;$( ' routerUptime ' ).textContent=dur(rt.uptime_seconds);$( ' switching ' ).textContent=imageActive?imagePhaseLabel(img.phase):(rt.switching|| ' nein ' );let disk=c.disk_data|| {} ,dp=disk.total?disk.used/disk.total*100:null;$( ' dataDisk ' ).textContent=pct(dp);$( ' processes ' ).innerHTML=(d.gpu_processes||[]).map(p=>`<tr><td>$ { (d.gpus||[]).find(g=>g.uuid===p.gpu_uuid)?.index?? ' – ' }</td><td>$ {p.name} </td><td>$ {p.pid} </td><td>$ { p.memory_mib?? ' – ' } MiB</td></tr>`).join( ' ' )|| ' <tr><td colspan= " 4 " >Keine Compute-Prozesse gemeldet</td></tr> ' ;let runtime=[[ ' Modell-Datei ' ,lr.model_file],[ ' PID ' ,lr.pid],[ ' Kontext ' ,lr.context_size?lr.context_size.toLocaleString( ' de-DE ' ): ' – ' ],[ ' Batch / µBatch ' ,`$ { lr.batch_size?? ' – ' } / $ { lr.ubatch_size?? ' – ' }`],[ ' Parallel ' ,lr.parallel],[ ' Threads ' ,`$ { lr.threads?? ' – ' } / $ { lr.threads_batch?? ' – ' }`],[ ' Geräte ' ,lr.device],[ ' Tensor-Split ' ,lr.tensor_split],[ ' KV-Cache ' ,`$ { lr.cache_k?? ' – ' } / $ { lr.cache_v?? ' – ' }`],[ ' Flash Attention ' ,lr.flash_attention? ' an ' : ' aus ' ],[ ' Prompt-Cache ' ,lr.prompt_cache? ' an ' : ' aus ' ],[ ' MTP Draft ' ,lr.mtp_draft_tokens]];$( ' runtime ' ).innerHTML=runtime.map(([k,v])=>`<div class= " metric " ><b>$ { v?? ' – ' }</b><span>$ {k} </span></div>`).join( ' ' );let es=Object.entries(d.errors|| {} ).filter(([,v])=>v);$( ' errors ' ).hidden=!es.length;$( ' errors ' ).textContent=es.map(([k,v])=>`$ {k} : $ {v} `).join( ' \ n ' );$( ' updated ' ).textContent=`Live · $ { new Date(d.timestamp*1000).toLocaleTimeString( ' de-DE ' )}`;$( ' dot ' ).style.background= ' var(--green) ' }catch(e) { $( ' updated ' ).textContent=`Verbindung gestört: $ {e.message} `;$( ' dot ' ).style.background= ' var(--red) ' }}refresh();setInterval(refresh,1000);
</script><script>
const baseSetMode=setMode;
setMode=async function(mode) {
if(modeBusy)return;
for(const id of [ ' llmMode ' , ' musicMode ' , ' separationMode ' , ' voiceMode ' , ' voiceChangeMode ' , ' applioMode ' ])$(id).disabled=true;
await baseSetMode(mode);
};
async function refreshVoiceChange() {
try {
const response=await fetch( ' /api/status ' , { cache: ' no-store ' }); if(!response.ok)return;
const data=await response.json(),mode=data.router?.mode|| {} ,busy=modeBusy||(mode.phase&&mode.phase!== ' ready ' )||!mode.enabled;
const modes=[[ ' voicechange ' , ' voiceChangeMode ' , ' voiceChangeOpen ' , ' X-VC Voice Changer ' ],[ ' applio ' , ' applioMode ' , ' applioOpen ' , ' Applio / RVC ' ]];
for(const [name,button,link,label] of modes) { const active=mode.active===name;$(button).classList.toggle( ' active ' ,active);$(button).disabled=busy;$(link).hidden=!active;if(active)$( ' operatingMode ' ).textContent=label;}
}catch(_error) {}
}
setTimeout(()=> { refreshVoiceChange();setInterval(refreshVoiceChange,1000)},150);
async function refresh() { try { let r=await fetch( ' /api/status ' , { cache: ' no-store ' });if(!r.ok)throw Error(`HTTP $ {r.status} `);let d=await r.json(),c=d.cpu|| {} ,m=c.memory|| {} ,rt=d.router|| {} ,up=rt.upstream|| {} ,q=rt.qwen|| {} ,lr=d.llama_runtime|| {} ,img=rt.image|| {} ,imageActive=img.phase&&img.phase!== ' idle ' ;let md=rt.mode|| {} ,switchingMode=md.phase&&md.phase!== ' ready ' ,modeName=( { llm: ' LLM-Betrieb ' ,music: ' Musikstudio ' ,separation: ' Stimmtrennung ' ,voice: ' Voice Studio ' ,voicechange: ' X-VC Voice Changer ' ,appl io: ' Applio / RVC ' })[md.active]|| ' Unbekannt ' ;$( ' operatingMode ' ).textContent=modeName;$( ' modeStatus ' ).textContent=switchingMode?`Umschaltung: $ {md.phase} `:(md.last_error||`Musik: $ { md.music_worker|| ' – ' } · Separator: $ { md.separator_worker|| ' – ' } · Voice: $ { md.voice_worker|| ' – ' }$ { md.return_profile?` · Rückkehr zu $ {md.return_profile} `: ' ' }`);$( ' modeStatus ' ).classList.toggle( ' mode-error ' ,!!md.last_error);$( ' llmMode ' ).classList.toggle( ' active ' ,md.active=== ' llm ' );$( ' musicMode ' ).classList.toggle( ' active ' ,md.active=== ' music ' );$( ' separationMode ' ).classList.toggle( ' active ' ,md.active=== ' separation ' );$( ' voiceMode ' ).classList.toggle( ' active ' ,md.active=== ' voice ' );$( ' voiceChangeMode ' ).classList.toggle( ' active ' ,md.active=== ' voicechange ' ); $(' applioMode ' ).classList.toggle ( ' active ' ,md.active=== ' applio ' );let modeControlsBusy =modeBusy||switchingMode||!md.enabled;for(const id of [ ' llmMode ' , ' musicMode ' , ' separationMode ' , ' voiceMode ' , ' voiceChangeMode ' , ' applioMode ' ])$(id).disabled=modeControlsBusy; $(' musicOpen ' ).hidden=md.active!== ' music ' ;$( ' separatorOpen ' ).hidden=md.active!== ' separation ' ;$( ' voiceOpen ' ).hidden=md.active!== ' voice ' ;$( ' voiceChangeOpen ' ).hidden=md.active!== ' voicechange ' ;$( ' applioOpen ' ).hidden=md.active!== ' applio ' ;$( ' profile ' ).textContent=imageActive? ' Bildgenerierung ' :(rt.current_profile|| ' nicht geladen ' );$( ' profileSub ' ).textContent=imageActive?imagePhaseLabel(img.phase):(rt.switching?`Wechsel zu $ {rt.switching} `:`Kontext: $ { up.ctx?up.ctx.toLocaleString( ' de-DE ' ): ' – ' } Token`);$( ' model ' ).textContent=imageActive?(img.model|| ' Bildmodell ' ):(up.model|| ' – ' );$( ' modelSub ' ).textContent=imageActive?`$ { img.model_loaded? ' geladen ' : ' wird vorbereitet ' } · Worker $ { img.worker|| ' – ' }`:(lr.model_file|| (up.reachable? ' llama.cpp erreichbar ' : ' llama.cpp nicht erreichbar ' ));$( ' cpu ' ).textContent=pct(c.usage_percent);$( ' cpuBar ' ).style.width=`$ { c.usage_percent||0} % `;$( ' load ' ).textContent=`$ { c.logical_cpus|| ' – ' } Threads · Load $ { (c.load||[]).join( ' / ' )}`;let rp=m.total?m.used/m.total*100:0;$( ' ram ' ).textContent=pct(rp);$( ' ramBar ' ).style.width=`$ {rp} % `;$( ' ramSub ' ).textContent=`$ { gib(m.used)} / $ { gib(m.total)}`;$( ' gpuCards ' ).innerHTML=(d.gpus||[]).map(gpuCard).join( ' ' )|| ' <article class= " card span12 error " >Keine GPU-Daten verfügbar</article> ' ;$( ' availability ' ).textContent=imageActive?imagePhaseLabel(img.phase):(q.available? ' bereit ' : ' nicht bereit ' );$( ' availability ' ).className=`value status $ { (imageActive||q.available)? ' ' : ' bad ' }`;$( ' activeChats ' ).textContent=q.active_chats?? ' – ' ;$( ' routerUptime ' ).textContent=dur(rt.uptime_seconds);$( ' switching ' ).textContent=imageActive?imagePhaseLabel(img.phase):(rt.switching|| ' nein ' );let disk=c.disk_data|| {} ,dp=disk.total?disk.used/disk.total*100:null;$( ' dataDisk ' ).textContent=pct(dp);$( ' processes ' ).innerHTML=(d.gpu_processes||[]).map(p=>`<tr><td>$ { (d.gpus||[]).find(g=>g.uuid===p.gpu_uuid)?.index?? ' – ' }</td><td>$ {p.name} </td><td>$ {p.pid} </td><td>$ { p.memory_mib?? ' – ' } MiB</td></tr>`).join( ' ' )|| ' <tr><td colspan= " 4 " >Keine Compute-Prozesse gemeldet</td></tr> ' ;let runtime=[[ ' Modell-Datei ' ,lr.model_file],[ ' PID ' ,lr.pid],[ ' Kontext ' ,lr.context_size?lr.context_size.toLocaleString( ' de-DE ' ): ' – ' ],[ ' Batch / µBatch ' ,`$ { lr.batch_size?? ' – ' } / $ { lr.ubatch_size?? ' – ' }`],[ ' Parallel ' ,lr.parallel],[ ' Threads ' ,`$ { lr.threads?? ' – ' } / $ { lr.threads_batch?? ' – ' }`],[ ' Geräte ' ,lr.device],[ ' Tensor-Split ' ,lr.tensor_split],[ ' KV-Cache ' ,`$ { lr.cache_k?? ' – ' } / $ { lr.cache_v?? ' – ' }`],[ ' Flash Attention ' ,lr.flash_attention? ' an ' : ' aus ' ],[ ' Prompt-Cache ' ,lr.prompt_cache? ' an ' : ' aus ' ],[ ' MTP Draft ' ,lr.mtp_draft_tokens]];$( ' runtime ' ).innerHTML=runtime.map(([k,v])=>`<div class= " metric " ><b>$ { v?? ' – ' }</b><span>$ {k} </span></div>`).join( ' ' );let es=Object.entries(d.errors|| {} ).filter(([,v])=>v);$( ' errors ' ).hidden=!es.length;$( ' errors ' ).textContent=es.map(([k,v])=>`$ {k} : $ {v} `).join( ' \ n ' );$( ' updated ' ).textContent=`Live · $ { new Date(d.timestamp*1000).toLocaleTimeString( ' de-DE ' )}`;$( ' dot ' ).style.background= ' var(--green) ' }catch(e) { $( ' updated ' ).textContent=`Verbindung gestört: $ {e.message} `;$( ' dot ' ).style.background= ' var(--red) ' }}refresh();setInterval(refresh,1000);
</script><script src= " /full.js " ></script><script src= " /history.js " ></script></body></html> ''' . replace (
" __MUSIC_ORIGINAL_UI_URL__ " , MUSIC_ORIGINAL_UI_URL
) . replace ( " __MUSIC_COMMUNITY_UI_URL__ " , MUSIC_COMMUNITY_UI_URL
) . replace ( " __SEPARATOR_UI_URL__ " , SEPARATOR_UI_URL
) . replace ( " __VOICE_UI_URL__ " , VOICE_UI_URL
) . replace ( " __VOICE_CHANGE_UI_URL__ " , VOICE_CHANGE_UI_URL
) . replace ( " __APPLIO_UI_URL__ " , APPLIO_UI_URL )
) . replace ( " __APPLIO_UI_URL__ " , APPLIO_UI_URL
) . replace ( " __MIKES_APPLIO_UI_URL__ " , MIKES_APPLIO_UI_URL )
FULL_JS = r '''