Aktivierte Profile werden angeboten. Erst eine Anfrage lädt das Modell. Wechsel warten auf laufende Antworten; Bildaufträge entladen vorher das eigene Sprachmodell.
${[['llm','Sprachmodelle'],['image','Bildgenerierung'],['tts','Text → Sprache (TTS)'],['stt','Sprache → Text (STT)'],['music','Musik']].map(([key,label])=>{const c=s.counts[key];return `
${label}
${c.supported?`${c.enabled} Profile aktiviert · ${c.available} ausführbar`:'Noch keine eigene Laufzeit eingerichtet'}
${c.loaded?'AKTIV IM SPEICHER':c.available?'BEI ANFRAGE LADEN':'NICHT AKTIV'}`}).join('')}
`;}
const modeLabels={llm:'LLM',video:'Video',music:'Musik',separator:'Audio-Trennung'};
function control(s,root){
const v=s.video||{},active=v.mode||'llm';
if(!selectedMode)selectedMode=active==='switching'?(v.target_mode||'llm'):active;
const selected=selectedMode,label=modeLabels[selected],switching=v.state==='switching',busy=pending||switching||s.state==='stopping';
const current=selected===active,available=selected==='video'?Boolean(v.service?.id):selected==='separator'?Boolean(v.separator?.installed&&v.separator.models?.some(m=>m.installed)):Boolean(s.counts[selected]?.available);
const ready=current&&(selected==='separator'||s.reachable)&&!switching&&(selected!=='video'||v.service?.ready);
const loading=current&&selected==='llm'&&s.worker.state==='loading';
const address=location.protocol+'//'+location.hostname+':'+s.port;
const routes=selected==='llm'?[['Chat',address+'/v1/chat/completions'],['Modelle',address+'/v1/models']]:selected==='separator'?[['Trennauftrag',location.protocol+'//'+location.hostname+':8108/api/v1/separator-jobs/start'],['Status',location.protocol+'//'+location.hostname+':8108/api/v1/separator-tests']]:selected==='music'?[['Musik erzeugen',address+'/v1/audio/music'],['Musikmodelle',address+'/v1/audio/music/models']]:[['Video-API',address+'/']];
const reason=available?'':selected==='video'?'Wähle zuerst ein eingerichtetes Videomodell.':selected==='separator'?'Installiere zuerst die Laufzeit und ein Trennmodellpaket.':'Gib zuerst ein ausführbares Profil am API-Endpunkt frei.';
const state=switching?'Wechsel läuft':loading?'Modell wird geladen':ready?'Bereit':current&&s.state==='stopping'?'Wird gestoppt':current&&s.reachable?'Startet':current?'Gestoppt':'Nicht aktiv';
put(root.querySelector('#control-modes'),`
${e(switching?v.switch_phase:loading?s.worker.phase||'Modell wird geladen':s.state==='stopping'?'Laufende Aufträge werden beendet':'Start wird angefordert')}
Der Zugriff verwendet den API-Token aus Zugang & API-Token. Verwaltungsoberfläche und API haben getrennte Ports.
Am Endpunkt angebotene Profile
Nur explizit aktivierte, ausführbare Profile erscheinen in GET /v1/models. Aktivieren lädt noch kein Modell.
API-Routen
POST /v1/chat/completions · Text und Streaming POST /v1/images/generations · Qwen-Image-Rezept, ein Bild als b64_json POST /v1/audio/speech und POST /v1/audio/transcriptions · noch nicht eingerichtet (501)
Alle Routen nutzen denselben Port und Bearer-Token. Bei Bildaufträgen bestimmt das Profil Auflösung, Schritte, Guidance und Seed. Noch keine Bildbearbeitung oder Vision-Eingaben.
':''}`;
root=view.querySelector('#endpoint-page');
if(settings)for(const action of ['start','stop'])root.querySelector('#endpoint-'+action).onclick=()=>mutate('/'+action,{});
root.querySelector('#endpoint-port-form')?.addEventListener('submit',event=>{event.preventDefault();mutate('/config',{port:Number(root.querySelector('#endpoint-port').value)});});
}
if(pending)return;const id=sequence;
try{const s=await api();if(!root.isConnected||id!==sequence)return;
if(!settings){control(s,root);clearTimeout(controlTimer);if(s.video?.state==='switching'||s.worker.state==='loading'||s.state==='stopping')controlTimer=setTimeout(()=>{if(root.isConnected)render(false);},1000);return;}
put(root.querySelector('#endpoint-summary'),summary(s));
root.querySelector('#endpoint-start').disabled=s.state!=='stopped';root.querySelector('#endpoint-stop').disabled=s.state!=='running';
if(settings){const port=root.querySelector('#endpoint-port');if(document.activeElement!==port)port.value=s.port;port.disabled=s.state!=='stopped';root.querySelector('#endpoint-port-save').disabled=s.state!=='stopped';
root.querySelector('#endpoint-port-help').textContent=s.allowed_ports.length?'Docker-Testinstallation: veröffentlichte Loopback-Ports '+s.allowed_ports.join(', ')+'. Portwechsel nur bei gestopptem Endpunkt. Der SSH-Tunnel muss denselben Port weiterleiten.':'Nativer Dienst: freie Ports zwischen 1024 und 65535, nur bei gestopptem Endpunkt.';
put(root.querySelector('#endpoint-profiles'),s.profiles.map(p=>`
${e(s.worker.error||'Aktivierte Profile werden bei einer API-Anfrage automatisch geladen.')}
Endpunkt und Profile verwalten →`;}catch(error){if(root.isConnected)root.textContent=error.message;}if(root.isConnected)setTimeout(()=>{if(root.isConnected)bindRunning();},3000);}
return {render,runningHTML,bindRunning};
})();