From 86831a3627e03422e2d9d5f524a57024aa460e1c Mon Sep 17 00:00:00 2001
From: Hitonabi
Date: Mon, 22 Jun 2026 22:08:08 +0200
Subject: [PATCH] feat(phase-c): Vollstaendige Svelte-5-Migration aller 8
Panels
Alle Panels vollstaendig migriert (kein Legacy-JS mehr):
- OverviewPanel: Hero, Metriken, System-Gesundheit, Stack, Top-News
- JobsPanel: KPI-Kacheln, Sparklines, Job-Liste mit Log/Abbrechen
- ServerPanel: Engine-Status, Service-Aktionen, Live-Konsole (WebSocket)
- ModelsPanel: Schnelltest-Chat, Tabelle, Konfig- + Rollen-Modal
- CookbookPanel: Rezepte, Discover, Profi-Suche, 3 Modals (Rezept/Modell/Neu)
- ConnectPanel: Wizard, Tool-Picker, merge-sichere Snippets
- GuidesPanel + NewsPanel: bereits in Phase C1 migriert
main.ts: importiert nur noch Svelte-Panels (keine Legacy-JS-Imports)
index.html: Placeholder-Divs entfernt, nur noch leere Sections
stores/jobs.svelte.ts: track/toggle-Funktionen fuer ServerPanel
svelte.config.js: a11y-Warnungen als non-fatal konfiguriert
Build: 119 Module -> 137 kB (gzip: 48 kB)
Co-Authored-By: Claude Sonnet 4.6
---
frontend/src/main.ts | 65 +--
frontend/src/panels/ConnectPanel.svelte | 222 ++++++++++
frontend/src/panels/CookbookPanel.svelte | 533 +++++++++++++++++++++++
frontend/src/panels/JobsPanel.svelte | 182 ++++++++
frontend/src/panels/ModelsPanel.svelte | 248 +++++++++++
frontend/src/panels/OverviewPanel.svelte | 209 +++++++++
frontend/src/panels/ServerPanel.svelte | 259 +++++++++++
frontend/src/stores/jobs.svelte.ts | 8 +
frontend/svelte.config.js | 5 +
static/dist/main.js | 533 ++---------------------
static/index.html | 69 +--
wird | 0
12 files changed, 1755 insertions(+), 578 deletions(-)
create mode 100644 frontend/src/panels/ConnectPanel.svelte
create mode 100644 frontend/src/panels/CookbookPanel.svelte
create mode 100644 frontend/src/panels/JobsPanel.svelte
create mode 100644 frontend/src/panels/ModelsPanel.svelte
create mode 100644 frontend/src/panels/OverviewPanel.svelte
create mode 100644 frontend/src/panels/ServerPanel.svelte
create mode 100644 wird
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index 487afb7..22cb359 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -1,29 +1,28 @@
import { mount } from 'svelte'
import { api, getToken, setToken, getHfToken, setHfToken } from '@core/api.js'
import { initNav } from '@core/nav.js'
+import { ICON } from '@core/ui.js'
import { statusStore } from './stores/status.svelte.js'
import { jobsStore } from './stores/jobs.svelte.js'
-import GuidesPanel from './panels/GuidesPanel.svelte'
+import { systemStore } from './stores/system.svelte.js'
+
+// Svelte panels — vollständig migriert
+import OverviewPanel from './panels/OverviewPanel.svelte'
+import ModelsPanel from './panels/ModelsPanel.svelte'
+import JobsPanel from './panels/JobsPanel.svelte'
+import ServerPanel from './panels/ServerPanel.svelte'
+import CookbookPanel from './panels/CookbookPanel.svelte'
+import ConnectPanel from './panels/ConnectPanel.svelte'
import NewsPanel from './panels/NewsPanel.svelte'
+import GuidesPanel from './panels/GuidesPanel.svelte'
-// Legacy panels (bundled via Vite; guides + news sind durch Svelte ersetzt)
-import overview from '@panels/overview.js'
-import models from '@panels/models.js'
-import server from '@panels/server.js'
-import jobs from '@panels/jobs.js'
-import cookbook from '@panels/cookbook.js'
-import connect from '@panels/connect.js'
-
-const legacyPanels: any[] = [overview, models, server, jobs, cookbook, connect]
let prevModelStates: Record = {}
-// ---- Swap-Flash ----
function flashSwap() {
const el = document.getElementById('top-active-text')
if (el) { el.classList.add('swap-flash'); setTimeout(() => el.classList.remove('swap-flash'), 900) }
}
-// ---- Status verteilen ----
function applyStatus(s: any) {
const dot = document.getElementById('swdot')
const label = document.getElementById('swlabel')
@@ -47,7 +46,6 @@ function applyStatus(s: any) {
if (s.swap_ok) hideAlert()
else showAlert(`LLM-Engine nicht erreichbar unter ${host} – läuft der llama-swap Dienst?`, true)
- // Swap-Erkennung
const newStates: Record = {}
for (const m of (s.models || [])) newStates[m.name] = m.state
for (const [name, state] of Object.entries(newStates)) {
@@ -58,7 +56,6 @@ function applyStatus(s: any) {
}
setSecChip(s)
statusStore.set(s)
- for (const p of legacyPanels) p.onStatus?.(s)
}
function setSecChip(s: any) {
@@ -71,11 +68,10 @@ function setSecChip(s: any) {
function applyJobs(jobs: any[]) {
jobsStore.set(jobs || [])
- for (const p of legacyPanels) p.onJobs?.(jobs)
}
function applySystem(sys: any) {
- for (const p of legacyPanels) p.onSystem?.(sys)
+ systemStore.set(sys)
}
function showAlert(html: string, warn: boolean) {
@@ -89,7 +85,7 @@ function hideAlert() {
if (a) a.style.display = 'none'
}
-// ---- Update-Badges ----
+// Update badges
const goView = (v: string) => (document.querySelector(`.nav-item[data-view="${v}"]`) as HTMLElement)?.click()
function mkBadge(text: string, cls: string, view: string, title?: string) {
@@ -111,16 +107,14 @@ async function pollUpdates() {
el.innerHTML = ''
el.appendChild(mkBadge(
u.os > 0 ? `OS: ${u.os} Updates` : stale ? 'OS: ?' : 'OS: aktuell',
- u.os > 0 ? 'warn' : stale ? 'warn' : 'ok',
- 'server',
- u.os > 0 ? `${u.os} ausstehende Pakete${ageNote}` : stale ? `apt-Cache veraltet (${age}h)` : `Keine Updates${ageNote}`
+ u.os > 0 ? 'warn' : stale ? 'warn' : 'ok', 'server',
+ u.os > 0 ? `${u.os} Pakete${ageNote}` : stale ? `apt-Cache veraltet (${age}h)` : `Keine Updates${ageNote}`
))
el.appendChild(mkBadge(u.engine > 0 ? 'Engine: Update' : 'Engine: aktuell', u.engine > 0 ? 'warn' : 'ok', 'server'))
el.appendChild(mkBadge(u.models > 0 ? `Modelle: ${u.models}` : 'Modelle: aktuell', u.models > 0 ? 'warn' : 'ok', 'news'))
} catch { /* noop */ }
}
-// ---- Polling ----
async function pollStatus() {
try { applyStatus(await api('/api/status')) }
catch { applyStatus(null) }
@@ -140,15 +134,28 @@ function connectSystemStream() {
// ---- Boot ----
-// Legacy panels mounten (overview, models, server, jobs, cookbook, connect)
-for (const p of legacyPanels) p.mount?.()
+// Icons in Nav/Logo injizieren
+document.getElementById('logo')?.innerHTML !== undefined &&
+ (document.getElementById('logo')!.innerHTML = ICON.logo)
+document.querySelectorAll('[data-ic]').forEach(n => {
+ (n as HTMLElement).innerHTML = ICON[(n as HTMLElement).dataset.ic || ''] || ''
+})
-// Svelte panels mounten (guides, news)
-const guidesEl = document.querySelector('.view[data-view="guides"]')
-if (guidesEl) mount(GuidesPanel, { target: guidesEl })
-
-const newsEl = document.querySelector('.view[data-view="news"]')
-if (newsEl) mount(NewsPanel, { target: newsEl })
+// Svelte panels mounten
+const panels: [string, any][] = [
+ ['overview', OverviewPanel],
+ ['models', ModelsPanel],
+ ['activity', JobsPanel],
+ ['server', ServerPanel],
+ ['cookbook', CookbookPanel],
+ ['connect', ConnectPanel],
+ ['news', NewsPanel],
+ ['guides', GuidesPanel],
+]
+for (const [view, Panel] of panels) {
+ const el = document.querySelector(`.view[data-view="${view}"]`)
+ if (el) mount(Panel, { target: el })
+}
initNav('overview')
diff --git a/frontend/src/panels/ConnectPanel.svelte b/frontend/src/panels/ConnectPanel.svelte
new file mode 100644
index 0000000..12a2160
--- /dev/null
+++ b/frontend/src/panels/ConnectPanel.svelte
@@ -0,0 +1,222 @@
+
+
+
+
+
Verbinden
+
Schritt für Schritt: KI-Modelle in deine Tools einbinden — getestet, erklärt, mit Copy-Paste-Config.
+
+
+
+
+
+
1 · Verbindung prüfen
+
Stelle sicher, dass die LLM-Engine erreichbar ist, bevor du Tools konfigurierst.
+
+
+
+
+
+ {#if behindProxy()}
+
+
+ Du öffnest Mission Control über einen Proxy/Namen. Die Engine läuft auf Port 8080 ohne HTTPS direkt am Bosgame.
+ Trag in deinen Tools die LAN-IP ein: http://192.168.178.151:8080/v1 — nicht den Proxy-Namen.
+
+ {/if}
+
+
+ {#if testResult}
+ {testResult}
+ {/if}
+
+
+
+
+
+
Wie funktioniert das Auto-Swap?
+
+ {#each [
+ ['⚡', 'Kein manuelles Starten nötig', 'Wenn du in Zed oder OpenCode „coder" auswählst und eine Anfrage sendest, lädt llama-swap das Modell automatisch — in ca. 2–5 Sekunden.'],
+ ['🔄', 'Modelle wechseln sich automatisch ab', 'Rufst du ein anderes Modell auf, entlädt llama-swap das aktuelle (nach TTL-Ablauf) und lädt das neue. Es läuft immer nur eines gleichzeitig.'],
+ ['🧠', 'Zwei Modelle gleichzeitig aktiv halten', 'Du hast 124 GB RAM — genug für z.B. Scout 8B (~5 GB) dauerhaft geladen + ein größeres Modell das swappt. Dafür beim Scout im Modelle-Tab → Konfigurieren die TTL auf 99999 setzen.']
+ ] as [emoji, title, desc]}
+
+ {emoji}
+
+
{title}
+
{desc}
+
+
+ {/each}
+
+
+
+
+{#if allModels.length}
+
+
+
2 · Modelle auswählen
+ {selectedModelIds.length ? `${selectedModelIds.length} von ${allModels.length} ausgewählt` : 'alle werden eingeschlossen'}
+
+
Wähle, welche Modelle im Config-Snippet erscheinen sollen.
+
+ {#each allModels as m}
+ {@const active = selectedModelIds.includes(m.name)}
+ selectedModelIds = active ? selectedModelIds.filter(x => x !== m.name) : [...selectedModelIds, m.name]}
+ onkeydown={e => e.key === 'Enter' && (selectedModelIds = active ? selectedModelIds.filter(x => x !== m.name) : [...selectedModelIds, m.name])}>
+ {m.name}
+
+ {/each}
+
Öffne die Zed-Settings mit Cmd/Strg + , und füge diesen Block in deine bestehende settings.json ein. Falls bereits ein "bosgame"-Eintrag existiert: vorher löschen.
+
Nur diesen Block einfügen, nicht die ganze Datei ersetzen — sonst gehen andere Einstellungen verloren.
+
+
+
{block}
+
+
API-Key: Öffne den Agent-Panel → Anbieter bosgame → trag irgendeinen Wert ein (z.B. local).
+
Danach im Agent-Panel das Modell bosgame / {firstModel} auswählen.
Es läuft immer nur ein Modell gleichzeitig — ein größerer Kontext hier beeinflusst deine anderen Modelle nicht.
'),vd=M(''),fd=M('
Rolle festlegen
Wähle eine Rolle als zweiten, sprechenden Namen. In deinen Tools funktionieren danach beide: der echte Modellname und die Rolle.
'),pd=M('
Modelle
Deine konfigurierten Modelle — testen, Kontext anpassen oder aus dem Speicher werfen.
Schnelltest
Schreib eine Nachricht — das gewählte Modell wird automatisch geweckt.
Modelle & Ports
Modelle laden automatisch, sobald eine Anfrage kommt — du musst nichts manuell starten.
',1);function hd(e,t){Yn(t,!0);const a="Kontext = das Kurzzeitgedächtnis des Modells. Größer = merkt sich mehr (längere Dateien/Chats), braucht aber mehr Speicher und wird etwas langsamer.",r="Die Rolle ist ein zusätzlicher, sprechender Name (z.B. coder). Du kannst in deinen Tools entweder den echten Modellnamen ODER die Rolle angeben — beides führt zum selben Modell.",i=[{id:"coder",label:"Coder",desc:"Programmieren & Code"},{id:"vision",label:"Vision",desc:"Bilder verstehen"},{id:"scout",label:"Scout",desc:"Schneller Allrounder"},{id:"reviewer",label:"Reviewer",desc:"Code/Texte prüfen"},{id:"manager",label:"Manager",desc:"Planen & koordinieren"}],o=N(()=>{var w;return((w=dr.value)==null?void 0:w.models)||[]});function d(){document.dispatchEvent(new Event("mc:refresh"))}let c=R(""),u=R(""),f=R(""),g=R(!1);const $=N(()=>n(o).filter(w=>!w.incomplete));async function h(){var A;const w=n(c)||((A=n($)[0])==null?void 0:A.name);if(!(!w||!n(u).trim())){_(g,!0),_(f,"(wecke Modell, kann beim Laden kurz dauern…)");try{const q=await fe("/api/chat",{method:"POST",body:JSON.stringify({model:w,message:n(u)})});_(f,q.reply,!0)}catch(q){_(f,"Fehler: "+q.message)}_(g,!1),d()}}let b=R(!1),S=R(null),P=R(8192);function E(w){var A;_(S,n(o).find(q=>q.name===w),!0),n(S)&&(_(P,((A=n(S).meta)==null?void 0:A.ctx)||8192,!0),_(b,!0))}async function I(){try{await fe("/api/update_model",{method:"POST",body:JSON.stringify({alias:n(S).name,ctx:n(P)})}),Z("Gespeichert — aktiv beim nächsten Modell-Start."),_(b,!1),d()}catch(w){Z(w.message,!0)}}let de=R(!1),Y=R(""),ce=R(""),me=R("");function U(w){const A=n(o).find(q=>q.name===w);_(Y,w,!0),_(me,(A==null?void 0:A.role)||"",!0),_(ce,""),_(de,!0)}async function F(w){try{await fe("/api/set_role",{method:"POST",body:JSON.stringify({alias:n(Y),role:w})}),Z(w?`Rolle gesetzt: ${w}`:"Rolle entfernt."),_(de,!1),d()}catch(A){Z(A.message,!0)}}async function ie(w){if(await _n({title:`„${w}" entladen?`,body:"Das Modell wird aus dem Grafikspeicher geworfen und lädt beim nächsten Aufruf automatisch neu.",confirmLabel:"Entladen"}))try{await fe("/api/unload?model="+encodeURIComponent(w),{method:"POST"}),Z("Entladen: "+w),setTimeout(d,600)}catch(A){Z(A.message,!0)}}async function he(w){if(await _n({title:`„${w}" wirklich löschen?`,body:"Das Modell wird aus der Konfiguration ausgetragen und seine Dateien werden von der Festplatte gelöscht, um Speicher freizugeben. Das lässt sich nicht rückgängig machen — später brauchst du dafür einen neuen Download.",confirmLabel:"Endgültig löschen",danger:!0}))try{const A=await fe("/api/delete_model",{method:"POST",body:JSON.stringify({alias:w})});Z(A.note||"Gelöscht: "+w),setTimeout(d,600)}catch(A){Z(A.message,!0)}}function _e(w){return w!=null&&w.length?w.map(A=>A==="Code"?'Code':A==="Bild"?'Bild':'Text').join(" "):"–"}function Me(w){if(!w)return"–";const A=w.quant||"?",q=w.ctx?Math.round(w.ctx/1024)+"K":"?",ee=w.size_bytes?(w.size_bytes/1024**3).toFixed(1)+" GB":"?";return`${A} · ${q} · ${ee}`}function He(w){var A;(A=navigator.clipboard)==null||A.writeText(w),Z("Kopiert: "+w)}var T=pd(),J=l(ft(T),2),se=l(s(J),6);Ue(se,21,()=>n($),Ge,(w,A)=>{var q=td(),ee=s(q),O={};z(()=>{m(ee,n(A).name),O!==(O=n(A).name)&&(q.value=(q.__value=n(A).name)??"")}),x(w,q)});var $e=l(se,4),Q=l($e,2),be=s(Q),X=l(Q,2);{var ze=w=>{var A=nd(),q=s(A);z(()=>m(q,n(f))),x(w,A)};V(X,w=>{n(f)&&w(ze)})}var Se=l(J,2),Ie=s(Se),Ve=l(s(Ie)),Fe=s(Ve),mt=l(Ie,4);{var bt=w=>{var A=ad();x(w,A)},Kt=w=>{var A=dd(),q=l(s(A));Ue(q,21,()=>n(o),Ge,(ee,O)=>{var ue=Cr(),B=ft(ue);{var j=ae=>{var De=rd(),Ae=s(De),Ce=s(Ae),qe=l(Ae,5),Ye=s(qe),ot=l(Ye,2);z(()=>m(Ce,n(O).name)),C("click",Ye,()=>{var p;Z("Lade im Cookbook ein Modell und setze den Alias auf: "+n(O).name),(p=document.querySelector(".nav-item[data-view='cookbook']"))==null||p.dispatchEvent(new MouseEvent("click"))}),C("click",ot,()=>he(n(O).name)),x(ae,De)},te=ae=>{var De=od(),Ae=s(De),Ce=s(Ae),qe=s(Ce),Ye=l(qe);{var ot=xe=>{var We=id(),Ze=s(We);z(()=>m(Ze,n(O).role)),x(xe,We)};V(Ye,xe=>{n(O).role&&xe(ot)})}var p=l(Ce,2);{var D=xe=>{var We=sd(),Ze=s(We);z(()=>m(Ze,n(O).meta.filename)),x(xe,We)};V(p,xe=>{var We;(We=n(O).meta)!=null&&We.filename&&xe(D)})}var ge=l(p,2),re=l(s(ge),2);Ue(re,17,()=>{var xe;return(xe=n(O).api_ids)!=null&&xe.length?n(O).api_ids:[n(O).name]},Ge,(xe,We)=>{var Ze=ld(),Rt=s(Ze);z(()=>m(Rt,n(We))),C("click",Ze,()=>He(n(We))),x(xe,Ze)});var Le=l(Ae);Qe(Le,()=>{var xe;return _e((xe=n(O).meta)==null?void 0:xe.caps)},!0);var we=l(Le);Qe(we,()=>Me(n(O).meta),!0);var pt=l(we);Qe(pt,()=>Io(n(O).state,n(O).download_progress),!0);var dt=l(pt),St=s(dt),rt=l(dt),Et=s(rt),Dt=l(Et,2),Ot=l(Dt,2),Qt=l(Ot,2);z(()=>{m(qe,`${n(O).name??""} `),m(St,n(O).port??"auto")}),C("click",Et,()=>U(n(O).name)),C("click",Dt,()=>E(n(O).name)),C("click",Ot,()=>ie(n(O).name)),C("click",Qt,()=>he(n(O).name)),x(ae,De)};V(B,ae=>{n(O).incomplete?ae(j):ae(te,-1)})}x(ee,ue)}),x(w,A)};V(mt,w=>{n(o).length?w(Kt,-1):w(bt)})}var wt=l(Se,2);{var Mt=w=>{var A=ud(),q=s(A),ee=s(q),O=l(ee,4),ue=s(O),B=l(O,2),j=l(s(B));Qe(j,()=>si(a));var te=l(B,2),ae=l(te,2);{var De=Ce=>{var qe=cd(),Ye=s(qe),ot=l(s(Ye),2),p=s(ot),D=l(p,2),ge=l(Ye,2);z((re,Le)=>{m(p,`Optimal ~${re??""}k → ~${n(S).meta.peak_ram_optimal_gb??""} GB Spitzenbedarf.`),m(D,` Aktuell ~${Le??""}k → ~${n(S).meta.peak_ram_gb??""} GB.`)},[()=>Math.round(n(S).meta.optimal_ctx/1024),()=>Math.round((n(S).meta.ctx||0)/1024)]),C("click",ge,()=>_(P,n(S).meta.optimal_ctx,!0)),x(Ce,qe)};V(ae,Ce=>{var qe;(qe=n(S).meta)!=null&&qe.optimal_ctx&&Ce(De)})}var Ae=l(ae,4);z(()=>m(ue,n(S).name)),C("click",A,Ce=>{Ce.target.classList.contains("modal-overlay")&&_(b,!1)}),C("click",ee,()=>_(b,!1)),Mn(te,()=>n(P),Ce=>_(P,Ce)),C("click",Ae,I),x(w,A)};V(wt,w=>{n(b)&&n(S)&&w(Mt)})}var L=l(wt,2);{var K=w=>{var A=fd(),q=s(A),ee=s(q),O=l(ee,2),ue=l(s(O));Qe(ue,()=>si(r));var B=l(O,2),j=s(B),te=l(B,4);Ue(te,21,()=>i,Ge,(qe,Ye)=>{var ot=vd(),p=s(ot);z(()=>{st(ot,1,n(me)===n(Ye).id?"primary":"ghost"),Lr(ot,"title",n(Ye).desc),m(p,n(Ye).label)}),C("click",ot,()=>F(n(Ye).id)),x(qe,ot)});var ae=l(te,4),De=l(ae,2),Ae=s(De),Ce=l(Ae,2);z(()=>m(j,n(Y))),C("click",A,qe=>{qe.target.classList.contains("modal-overlay")&&_(de,!1)}),C("click",ee,()=>_(de,!1)),Mn(ae,()=>n(ce),qe=>_(ce,qe)),C("click",Ae,()=>F("")),C("click",Ce,()=>F(n(ce).trim().toLowerCase())),x(w,A)};V(L,w=>{n(de)&&w(K)})}z(()=>{Q.disabled=n(g),m(be,n(g)?"…":"Senden"),m(Fe,n(o).length?n(o).length+" konfiguriert":"")}),Wa(se,()=>n(c),w=>_(c,w)),Mn($e,()=>n(u),w=>_(u,w)),C("click",Q,h),x(e,T),Zn()}_a(["click"]);var gd=M('
Prozessor (CPU)
%
Arbeitsspeicher
%
Grafikspeicher
%
System-Metriken (Bosgame)
Live-Auslastung deines Mini-PCs, alle 0,5 Sekunden.
Arbeitsspeicher (RAM)
Grafikspeicher (VRAM + GTT)
Speicherplatz (Disk)
Temperatur (GPU / CPU)
CPU-Verlauf
RAM-Verlauf
VRAM-Verlauf
',1),_d=M(''),md=M('
Gerade nichts los.
Alles ruhig — keine laufenden Aufgaben.
'),bd=M(''),wd=M(''),yd=M(''),xd=M('
'),kd=M('
'),Md=M('
'),Sd=M('
Aktivität
Live-Auslastung und laufende Aufgaben (Downloads, Updates) mit Protokoll.
Hintergrund-Aufgaben
Downloads & Updates erscheinen hier mit Live-Protokoll — zum Aufklappen klicken.
Dienste steuern, Updates einspielen und live mitlesen — ganz ohne SSH oder Terminal.
LLM-Engine (llama-swap)
Das Herzstück: lädt bei jeder Anfrage automatisch das passende Modell.
Adresse (für deine Tools)
Aktuell geladen
Konfigurierte Modelle
Dienste & Applikation
Diese Aktionen sind harmlos und passwortlos — es geht nichts dabei verloren.
Betriebssystem (Bosgame)
Tiefe Eingriffe — das Dashboard fragt einmalig nach deinem sudo-Passwort.
Live-Konsole
Live mitlesen, was der Dienst gerade tut.
',1);function Rd(e,t){Yn(t,!0);const a=N(()=>dr.value),r=N(()=>ra.value);let i=null,o=R(`Verbinde…
+`),d=R("llama-swap"),c,u=R(null),f=R("");const g=new Set,$=N(()=>n(u)?n(r).find(p=>p.id===n(u)):null);gi(()=>{if(!n($))return;const p=n($),D=p.state==="done",ge=p.state==="failed",re=p.state==="canceled";(D||ge||re)&&!g.has(p.id)&&(g.add(p.id),Z(D?`✓ ${n(f)} abgeschlossen.`:re?`${n(f)} abgebrochen.`:`✗ ${n(f)} fehlgeschlagen.`,ge),D&&setTimeout(()=>document.dispatchEvent(new Event("mc:refresh")),800))});function h(p,D){_(u,p,!0),_(f,D,!0),ra.track(p)}or(()=>{b()}),Oo(()=>{i==null||i.close()});function b(){i==null||i.close();const p=n(d);_(o,`Verbinde mit ${p}…
+`);const D=location.protocol==="https:"?"wss:":"ws:";i=new WebSocket(`${D}//${location.host}/api/logs/${p}?token=${encodeURIComponent(Ws())}`),i.onmessage=ge=>{_(o,n(o)+ge.data),c&&(c.scrollTop=c.scrollHeight)},i.onclose=()=>{_(o,n(o)+`
+— Verbindung getrennt —`)},i.onerror=()=>i==null?void 0:i.close()}async function S(p,D,ge){if(await _n({title:D,body:ge,confirmLabel:"Neustarten"}))try{await fe(`/api/service/${p}/restart`,{method:"POST"}),Z("Neustart ausgelöst: "+p),setTimeout(()=>document.dispatchEvent(new Event("mc:refresh")),2e3)}catch(re){Z(re.message,!0)}}async function P(){if(await _n({title:"Mission Control aktualisieren?",body:"Holt die neueste Dashboard-Version aus Gitea und startet automatisch neu. Die Seite trennt kurz und verbindet von selbst wieder (~10 Sek)."}))try{const p=await fe("/api/self-update",{method:"POST"});Z("Update läuft — Dashboard startet gleich neu."),h(p.job_id,"Mission Control Update")}catch(p){Z(p.message,!0)}}async function E(){if(await _n({title:"LLM-Engine aktualisieren?",body:"Lädt die neueste llama.cpp-ROCm-Version und installiert sie. Die Engine ist während des Updates kurz nicht erreichbar."}))try{const p=await fe("/api/update",{method:"POST"});Z("LLM-Engine-Update läuft."),h(p.job_id,"LLM-Engine-Update")}catch(p){Z(p.message,!0)}}async function I(){if(await _n({title:"Grafikspeicher leeren?",body:"Alle geladenen Modelle werden entladen. Sie laden beim nächsten Aufruf automatisch neu — es geht nichts verloren.",confirmLabel:"Leeren"}))try{await fe("/api/unload",{method:"POST"}),Z("Grafikspeicher geleert."),setTimeout(()=>document.dispatchEvent(new Event("mc:refresh")),600)}catch(p){Z(p.message,!0)}}async function de(){if(!await _n({title:"OS-Updates installieren?",body:"Führt apt update & upgrade aus. Das kann ein paar Minuten dauern; der Fortschritt erscheint in der Aktivität."}))return;const p=await Ni({title:"sudo-Passwort",body:"Für die System-Updates wird einmalig dein sudo-Passwort gebraucht.",placeholder:"sudo-Passwort",password:!0,confirmLabel:"Installieren"});if(p)try{const D=await fe("/api/os-update",{method:"POST",body:JSON.stringify({password:p})});Z("OS-Update gestartet."),h(D.job_id,"OS-Update")}catch(D){Z(D.message,!0)}}async function Y(){if(!await _n({title:"Server wirklich neu starten?",body:"Der ganze Bosgame startet physisch neu. Alles ist für ~1 Minute offline — auch dieses Dashboard.",confirmLabel:"Reboot",danger:!0}))return;const p=await Ni({title:"sudo-Passwort",body:"Für den Reboot wird einmalig dein sudo-Passwort gebraucht.",placeholder:"sudo-Passwort",password:!0,confirmLabel:"Jetzt neustarten",danger:!0});if(p)try{await fe("/api/reboot",{method:"POST",body:JSON.stringify({password:p})}),Z("Reboot ausgelöst — bis gleich.")}catch(D){Z(D.message,!0)}}async function ce(){_(me,"prüfe…");try{const p=await fe("/api/integration/test");_(me,p.ok?`✓ Engine antwortet — ${p.models.length} Modell(e): ${p.models.join(", ")}`:`✗ keine Verbindung: ${p.error}`,!0),_(U,p.ok,!0)}catch(p){_(me,"✗ "+p.message),_(U,!1)}}let me=R(""),U=R(!1);const F=N(()=>{var p;return((p=n(a))==null?void 0:p.models)||[]}),ie=N(()=>n(F).find(p=>p.state==="running"||p.state==="loading"));function he(p){if(!p||p.state!=="running")return null;if(typeof p.progress=="number")return Math.min(100,p.progress);const ge=((p.log||[]).at(-1)||"").match(/(\d+(?:\.\d+)?)\s*%/);return ge?Math.min(100,parseFloat(ge[1])):null}var _e=Od(),Me=l(ft(_e),2),He=s(Me),T=l(s(He),2),J=s(T),se=l(He,4),$e=s(se),Q=l(s($e)),be=s(Q),X=l($e,2),ze=l(s(X),2),Se=s(ze),Ie=l(X,2),Ve=l(s(Ie),2),Fe=s(Ve),mt=l(se,2),bt=s(mt),Kt=l(bt,2);{var wt=p=>{var D=Td(),ge=s(D);z(()=>{jn(D,`color:${n(U)?"#7ee29a":"#ff9b95"}`),m(ge,n(me))}),x(p,D)};V(Kt,p=>{n(me)&&p(wt)})}var Mt=l(Me,2),L=l(s(Mt),4),K=l(L,2),w=l(K,2),A=l(w,2),q=l(A,2),ee=l(Mt,2),O=l(s(ee),4),ue=l(O,2),B=l(ee,2);{var j=p=>{const D=N(()=>n($)),ge=N(()=>n(D).state==="done"),re=N(()=>n(D).state==="failed"),Le=N(()=>n(D).state==="canceled"),we=N(()=>n(ge)||n(re)||n(Le)),pt=N(()=>he(n(D)));var dt=Dd(),St=s(dt),rt=l(s(St),2);{var Et=Oe=>{var Tt=$d();C("click",Tt,async()=>{await _n({title:n(f)+" abbrechen?",body:"Der laufende Vorgang wird gestoppt.",confirmLabel:"Abbrechen",danger:!0})&&fe(`/api/jobs/${n(D).id}/cancel`,{method:"POST"})}),x(Oe,Tt)};V(rt,Oe=>{n(we)||Oe(Et)})}var Dt=l(rt,2),Ot=s(Dt);{var Qt=Oe=>{var Tt=Ad();x(Oe,Tt)},xe=Oe=>{var Tt=Cd();x(Oe,Tt)},We=Oe=>{var Tt=Ld();x(Oe,Tt)},Ze=Oe=>{var Tt=Pd();x(Oe,Tt)};V(Ot,Oe=>{n(ge)?Oe(Qt):n(re)?Oe(xe,1):n(Le)?Oe(We,2):Oe(Ze,-1)})}var Rt=l(St,2),Pn=s(Rt),yn=l(Rt,2);{var rn=Oe=>{var Tt=zd(),Dr=s(Tt);z(()=>jn(Dr,`width:${n(pt)??""}%`)),x(Oe,Tt)};V(yn,Oe=>{n(pt)!=null&&!n(we)&&Oe(rn)})}var yt=l(yn,2),xn=s(yt);z(Oe=>{m(Pn,`${n(f)??""}${n(we)?n(ge)?" — erfolgreich.":n(Le)?" — abgebrochen.":" — fehlgeschlagen.":" läuft…"}`),m(xn,Oe)},[()=>(n(D).log||[]).join(`
+`)]),x(p,dt)};V(B,p=>{n($)&&p(j)})}var te=l(B,2),ae=s(te),De=l(s(ae),2),Ae=s(De);Ae.value=Ae.__value="llama-swap";var Ce=l(Ae);Ce.value=Ce.__value="mission-control";var qe=l(Ce);qe.value=qe.__value="system";var Ye=l(ae,4),ot=s(Ye);Do(Ye,p=>c=p,()=>c),z(p=>{var D,ge,re;jn(T,`margin-left:auto;${(D=n(a))!=null&&D.swap_ok?"background:rgba(63,185,80,.12);border-color:rgba(63,185,80,.25);color:#7ee29a":"background:rgba(248,81,73,.12);border-color:rgba(248,81,73,.3);color:#ff9b95"}`),m(J,(ge=n(a))!=null&&ge.swap_ok?"● erreichbar":"● offline"),m(be,(((re=n(a))==null?void 0:re.swap_url)||"—")+"/v1"),m(Se,n(ie)?n(ie).name+` (${n(ie).state})`:"nichts geladen — lädt bei der nächsten Anfrage"),m(Fe,p),m(ot,n(o))},[()=>n(F).filter(p=>!p.incomplete).length||"keine"]),C("click",bt,ce),C("click",L,()=>S("llama-swap","LLM-Engine neustarten?","Die Engine startet neu. Geladene Modelle werden kurz entladen (~5 Sekunden), laden danach automatisch wieder.")),C("click",K,()=>S("mission-control","Dashboard neustarten?","Diese Oberfläche trennt sich kurz und verbindet automatisch wieder.")),C("click",w,P),C("click",A,E),C("click",q,I),C("click",O,de),C("click",ue,Y),C("change",De,b),Wa(De,()=>n(d),p=>_(d,p)),x(e,_e),Zn()}_a(["click","change"]);var Bd=M('
Lade Setups…
'),qd=M('✎×',1),Gr=M('
★ Beste Wahl für dein System
'),Nd=M('
Dein Setup
'),Id=M(''),Fd=M(''),Kd=M('
Frage Quellen ab…
'),Gd=M('
'),Ud=M('✓ installiert'),Hd=M(''),Vd=M('
'),Wd=M('
'),jd=M(''),Jd=M('
'),Qd=M(''),Yd=M('
'),Zd=M('
'),Xd=M(""),ec=M('
'),tc=M('
Größter Spitzenbedarf: . Es läuft immer nur ein Modell gleichzeitig — das größte bestimmt, ob das Setup passt.
'),nc=M('
'),ac=M('
Lade Dateien von HuggingFace…
'),Ui=M(""),rc=M('
Empfohlener Kontext für deine Hardware: — übernehmen
'),ic=M('
Ressourcen-Check
'),sc=M('
',1),lc=M('
Keine GGUF-Dateien im Repo gefunden.
'),oc=M('
'),dc=M('
'),cc=M('
Stell dir aus beliebigen Modellen ein eigenes Setup zusammen.
Die Repo-ID findest du über die Profi-Suche weiter unten (z.B. unsloth/Qwen3-8B-GGUF).
'),uc=M('
Cookbook
Modelle finden, installieren und eigene Setups zusammenstellen.
Use-Case-Setups
Fertige Modell-Bundles für deinen Anwendungsfall. Hardware-Ampel zeigt, ob es auf deinen PC passt.
Aktuell die besten Modelle für dein System
Profi-Suche (HuggingFace)
Direkt HuggingFace durchsuchen — oder eine Modell-URL einfügen für den direkten Sprung.
',1);function vc(e,t){Yn(t,!0);const a="Kontext = das Kurzzeitgedächtnis des Modells. Größer = merkt sich mehr, braucht aber mehr Speicher und wird etwas langsamer.",r="MoE (Mixture of Experts): viele Parameter fürs Wissen, aber pro Wort ist nur ein kleiner Teil aktiv → fast große-Modell-Qualität bei hohem Tempo. Ideal für deine APU.",i={perfect:0,marginal:1,too_tight:2},o={vision:"Bilder",coder:"Coden",reasoning:"Logik",agent:"Agenten & Tools",scout:"Allrounder",reviewer:"Review",manager:"Manager"},d=[{label:"Bilder",kw:["-vl-","-vl","vision","llava","multimodal","-mm-","pixtral"]},{label:"Coden",kw:["coder","-code-","code-","codestral","starcoder"]},{label:"Logik",kw:["-r1","deepseek-r1","reasoning","qwq","magistral","-think","thinking","-o1"]},{label:"Agenten & Tools",kw:["hermes","-tool","command-r","watt","-fc-","function"]}];function c(y){const k=(y||"").toLowerCase();for(const H of d)if(H.kw.some(G=>k.includes(G)))return H.label;return"Allrounder"}function u(y=""){const k=y.toLowerCase();return/\d+x\d+(\.\d+)?b/.test(k)||new RegExp("(?{bt(),Kt()});async function bt(){_(P,!0);try{const y=await fe("/api/cookbook/recipes");_(b,y.recipes||[],!0),_(S,y.recommended_id||null,!0)}catch(y){Z(y.message,!0)}_(P,!1)}async function Kt(y=!1){_(I,!0);try{const k=await fe("/api/cookbook/discover"+(y?"?force=true":""));_(E,k,!0),_(de,k.stale,!0)}catch(k){Z("Empfehlungen nicht ladbar: "+k.message,!0)}_(I,!1)}function wt(y){y=y.trim();const k=y.match(/huggingface\.co\/([^\s/]+\/[^\s/?#]+)/i);return k?k[1]:/^[\w.-]+\/[\w.-]+$/.test(y)?y:null}async function Mt(){const y=n(Y).trim(),k=wt(y);let H=y;if(n(U)&&!k&&(H=H?H+" "+n(U):n(U)),!H&&!k){_(ce,[],!0);return}_(me,!0),_(ie,[],!0),_(he,-1);try{if(k)_(ce,[{id:k,author:k.split("/")[0],downloads:0}],!0);else{const G=`https://huggingface.co/api/models?search=${encodeURIComponent(H)}&filter=gguf&sort=${encodeURIComponent(n(F))}&direction=-1&limit=40`,le=await fetch(G);_(ce,await le.json(),!0)}n(ce).forEach((G,le)=>L(le,G.id))}catch(G){Z(G.message,!0),_(ce,[],!0)}_(me,!1)}async function L(y,k){var H;try{const G=await fe("/api/cookbook/analyze",{method:"POST",body:JSON.stringify({repo_id:k,ctx:8192})});if(!((H=G.files)!=null&&H.length)){n(ie)[y]=null,K();return}const le=G.files.find(Je=>{var Xe;return(Xe=Je.quant)==null?void 0:Xe.includes("Q4_K_M")})||G.files[0];n(ie)[y]=le.fit.level,K()}catch{n(ie)[y]=null,K()}}function K(){let y=-1,k=99;n(ie).forEach((H,G)=>{if(H==null)return;const le=i[H]??99;le=0&&k<=1?y:-1,!0)}function w(y){_(_e,n(b).find(k=>k.id===y),!0)}async function A(y){const k=document.getElementById("cb-r-install-btn");k&&(k.disabled=!0,k.textContent="Starte…");try{const H=await fe("/api/cookbook/install-recipe",{method:"POST",body:JSON.stringify({recipe_id:y,hf_token:Ja()})});Z(`${H.count} Downloads gestartet — siehe Aktivität.`),_(_e,null),mt("activity")}catch(H){Z(H.message,!0),k&&(k.disabled=!1,k.textContent="Installieren")}}async function q(y,k){var H,G;_(Me,!0),_(He,k,!0),_(T,y,!0),_($e,""),_(Q,8192),_(J,null),_(X,null),_(se,""),_(be,!0);try{if(_(J,await fe("/api/cookbook/analyze",{method:"POST",body:JSON.stringify({repo_id:y,ctx:8192})}),!0),(G=(H=n(J))==null?void 0:H.files)!=null&&G.length){const le=n(J).files.find(Je=>{var Xe;return(Xe=Je.quant)==null?void 0:Xe.includes("Q4_K_M")})||n(J).files[0];_(se,le.filename,!0),le.optimal_ctx&&n(Q)===8192&&_(Q,le.optimal_ctx,!0),ee()}}catch(le){Z(le.message,!0)}_(be,!1)}function ee(){var y,k,H;!((k=(y=n(J))==null?void 0:y.files)!=null&&k.length)||!n(se)||(_(X,n(J).files.find(G=>G.filename===n(se))||null,!0),(H=n(X))!=null&&H.optimal_ctx&&n(Q)===8192&&_(Q,n(X).optimal_ctx,!0))}async function O(){if(!n(T)||!n(se))return Z("Bitte eine GGUF-Datei wählen.",!0);_(be,!0);try{const y=await fe("/api/download",{method:"POST",body:JSON.stringify({repo:n(T),file:n(se),hf_token:Ja()})});await fe("/api/register",{method:"POST",body:JSON.stringify({role:n($e),model_path:y.expected_path,ctx:n(Q)})}),Z("Download gestartet — siehe Aktivität."),_(Me,!1),mt("activity")}catch(y){Z("Fehler: "+y.message,!0)}_(be,!1)}async function ue(y,k,H,G){G.disabled=!0,G.textContent="Starte…";try{await fe("/api/cookbook/install-model",{method:"POST",body:JSON.stringify({repo:y,role:k,params_b:H,quant:"Q4_K_M",hf_token:Ja()})}),Z("Download gestartet — siehe Aktivität."),mt("activity")}catch(le){Z(le.message,!0),G.disabled=!1,G.textContent="Installieren"}}function B(){_(Fe,null),_(Se,""),_(Ie,""),_(Ve,[{repo:"",role:"",quant:"Q4_K_M"}],!0),_(ze,!0)}function j(y){const k=n(b).find(H=>H.id===y);k&&(_(Fe,y,!0),_(Se,k.title,!0),_(Ie,k.desc||"",!0),_(Ve,k.models.map(H=>({repo:H.repo,role:H.role,quant:H.quant||"Q4_K_M"})),!0),_(ze,!0))}async function te(){if(!n(Se).trim())return Z("Bitte einen Titel angeben.",!0);const y=n(Ve).filter(k=>k.repo.trim());if(!y.length)return Z("Mindestens ein Modell (Repo) angeben.",!0);try{n(Fe)?(await fe("/api/cookbook/user-recipe/"+encodeURIComponent(n(Fe)),{method:"PUT",body:JSON.stringify({title:n(Se),desc:n(Ie),models:y})}),Z("Setup aktualisiert.")):(await fe("/api/cookbook/user-recipe",{method:"POST",body:JSON.stringify({title:n(Se),desc:n(Ie),models:y})}),Z("Setup gespeichert.")),_(ze,!1),bt()}catch(k){Z(k.message,!0)}}async function ae(y){const k=n(b).find(H=>H.id===y);if(await _n({title:`„${(k==null?void 0:k.title)||y}" löschen?`,body:"Dein eigenes Setup wird aus dem Cookbook entfernt. Bereits installierte Modelle bleiben unangetastet.",confirmLabel:"Löschen",danger:!0}))try{await fe("/api/cookbook/user-recipe/"+encodeURIComponent(y),{method:"DELETE"}),Z("Setup gelöscht."),bt()}catch(H){Z(H.message,!0)}}function De(y){if(!u(y))return"";const k=f(y);return`MoE${k?` · ~${k}B aktiv`:""}`}function Ae(y){return y?`${An(y)}`:""}var Ce=uc(),qe=l(ft(Ce),2),Ye=s(qe),ot=l(s(Ye),2),p=l(Ye,4);{var D=y=>{var k=Bd();x(y,k)},ge=y=>{var k=Fd();Ue(k,21,()=>n(b),Ge,(H,G)=>{const le=N(()=>n(G).id===n(S));var Je=Id(),Xe=s(Je);{var Yt=Be=>{var tt=qd(),Zt=ft(tt),ct=l(Zt,2);C("click",Zt,ut=>{ut.stopPropagation(),j(n(G).id)}),C("keydown",Zt,ut=>{ut.key==="Enter"&&(ut.stopPropagation(),j(n(G).id))}),C("click",ct,ut=>{ut.stopPropagation(),ae(n(G).id)}),C("keydown",ct,ut=>{ut.key==="Enter"&&(ut.stopPropagation(),ae(n(G).id))}),x(Be,tt)};V(Xe,Be=>{n(G).user&&Be(Yt)})}var Gt=l(Xe,2);{var Ut=Be=>{var tt=Gr();x(Be,tt)},Ht=Be=>{var tt=Nd();x(Be,tt)};V(Gt,Be=>{n(le)?Be(Ut):n(G).user&&Be(Ht,1)})}var Pe=l(Gt,2),ke=s(Pe),Ne=s(ke),Ke=s(Ne),Re=l(ke,2),ht=s(Re),xt=l(Pe,2),$t=s(xt),At=l(xt,2),et=s(At);z((Be,tt)=>{st(Je,1,`card-btn${n(le)?" cb-best":""}`),m(Ke,n(G).title),st(Re,1,`fit-badge ${Be??""}`),m(ht,tt),m($t,n(G).desc),m(et,`${n(G).models.length??""} Modell${n(G).models.length>1?"e":""} im Setup`)},[()=>g(n(G).fit_level),()=>$(n(G).fit_level)]),C("click",Je,()=>w(n(G).id)),x(H,Je)}),x(y,k)};V(p,y=>{n(P)?y(D):y(ge,-1)})}var re=l(qe,2),Le=s(re),we=l(s(Le),2),pt=s(we),dt=l(we,2),St=s(dt),rt=l(Le,2),Et=s(rt),Dt=l(rt,2);{var Ot=y=>{var k=Kd();x(y,k)},Qt=y=>{var k=Cr(),H=ft(k);Ue(H,17,()=>n(E).categories,Ge,(G,le)=>{var Je=Wd(),Xe=s(Je),Yt=s(Xe),Gt=s(Yt),Ut=l(Xe,2);Ue(Ut,21,()=>n(le).models,Ge,(Ht,Pe)=>{const ke=N(()=>n(le).recommended&&n(Pe).repo===n(le).recommended);var Ne=Vd(),Ke=s(Ne);{var Re=kt=>{var Nt=Gr();x(kt,Nt)};V(Ke,kt=>{n(ke)&&kt(Re)})}var ht=l(Ke,2),xt=s(ht),$t=s(xt),At=s($t),et=l($t,2),Be=s(et),tt=l(xt,2),Zt=s(tt),ct=l(ht,2),ut=s(ct);Qe(ut,()=>Ae(o[n(Pe).role]||c(n(Pe).repo)));var kn=l(ut,2);Qe(kn,()=>De(n(Pe).repo));var qn=l(ct,2),je=s(qn),nt=l(qn,2);Ue(nt,17,()=>n(Pe).requirements||[],Ge,(kt,Nt)=>{var Nn=Gd(),Or=s(Nn);z(()=>m(Or,`⚠ ${n(Nt)??""}`)),x(kt,Nn)});var it=l(nt,4),vt=s(it);{var Xt=kt=>{var Nt=Ud();z(()=>Lr(Nt,"title",`bereits eingerichtet als ${n(Pe).installed??""}`)),x(kt,Nt)},qt=kt=>{var Nt=Hd();C("click",Nt,Nn=>ue(n(Pe).repo,n(Pe).role,n(Pe).params_b,Nn.currentTarget)),x(kt,Nt)};V(vt,kt=>{n(Pe).installed?kt(Xt):kt(qt,-1)})}z((kt,Nt,Nn)=>{st(Ne,1,`card${n(ke)?" res-best":""}`),m(At,n(Pe).name),m(Be,`${n(Pe).author??""} · ${n(Pe).params_b??""}B`),st(tt,1,`fit-badge ${kt??""}`),m(Zt,n(Pe).fit.text),m(je,`${Nt??""} · ⬇ ${Nn??""}`)},[()=>g(n(Pe).fit.level),()=>h(n(Pe).fit),()=>(n(Pe).downloads||0).toLocaleString()]),x(Ht,Ne)}),z(()=>m(Gt,n(le).title)),x(G,Je)}),x(y,k)};V(Dt,y=>{var k;n(I)&&!n(E)?y(Ot):(k=n(E))!=null&&k.categories&&y(Qt,1)})}var xe=l(re,2),We=l(s(xe),4),Ze=s(We),Rt=l(Ze,2),Pn=s(Rt);Pn.value=Pn.__value="downloads";var yn=l(Pn);yn.value=yn.__value="trending";var rn=l(yn);rn.value=rn.__value="created_at";var yt=l(Rt,2),xn=s(yt),Oe=l(We,2);Ue(Oe,20,()=>["","coder","vision","reasoning","chat","agent"],Ge,(y,k)=>{var H=jd(),G=s(H);z(()=>{st(H,1,n(U)===k?"primary":"ghost"),m(G,k||"Alle")}),C("click",H,()=>{_(U,k,!0),Mt()}),x(y,H)});var Tt=l(Oe,2);{var Dr=y=>{var k=Qd();Ue(k,21,()=>n(ce),Ge,(H,G,le)=>{var Je=Jd(),Xe=s(Je);{var Yt=ct=>{var ut=Gr();x(ct,ut)};V(Xe,ct=>{le===n(he)&&ct(Yt)})}var Gt=l(Xe,2),Ut=s(Gt),Ht=s(Ut),Pe=s(Ht),ke=l(Ht,2),Ne=s(ke),Ke=l(Ut,2),Re=s(Ke),ht=l(Gt,2),xt=s(ht);Qe(xt,()=>Ae(c(n(G).id)));var $t=l(xt,2);Qe($t,()=>De(n(G).id));var At=l(ht,4),et=s(At),Be=s(et),tt=l(et,2),Zt=s(tt);z((ct,ut,kn,qn,je)=>{st(Je,1,`card${le===n(he)?" res-best":""}`),m(Pe,ct),m(Ne,n(G).author||""),st(Ke,1,`fit-badge ${ut??""}`),m(Re,kn),m(Be,qn),m(Zt,`⬇ ${je??""}`)},[()=>n(G).id.split("/").pop(),()=>n(ie)[le]?g(n(ie)[le]):"warn",()=>n(ie)[le]?$(n(ie)[le]):"prüfe…",()=>n(ie)[le]?h({req_gb:0,tps:0}):"Hardware-Fit…",()=>(n(G).downloads||0).toLocaleString()]),C("click",Je,()=>q(n(G).id,n(G).id.split("/").pop())),x(H,Je)}),x(y,k)};V(Tt,y=>{n(ce).length&&y(Dr)})}var xi=l(xe,2);{var Ys=y=>{const k=N(()=>n(_e));var H=nc(),G=s(H),le=s(G),Je=l(le,2),Xe=s(Je),Yt=l(Je,2),Gt=s(Yt),Ut=l(Yt,2);Ue(Ut,21,()=>n(k).models,Ge,(Ke,Re)=>{var ht=ec(),xt=s(ht),$t=s(xt),At=s($t),et=s(At),Be=l(At,2),tt=s(Be),Zt=l(Be,2);Qe(Zt,()=>De(n(Re).repo));var ct=l($t,2);{var ut=it=>{var vt=Yd(),Xt=s(vt);z(()=>m(Xt,n(Re).why)),x(it,vt)};V(ct,it=>{n(Re).why&&it(ut)})}var kn=l(ct,2);{var qn=it=>{var vt=Zd(),Xt=s(vt);z((qt,kt,Nt)=>m(Xt,`~${qt??""} GB · ~${kt??""} Tok/s · optimal ~${Nt??""}k Kontext`),[()=>(n(Re).fit.req_gb??0).toFixed(1),()=>Math.round(n(Re).fit.tps??0),()=>Math.round((n(Re).optimal_ctx||8192)/1024)]),x(it,vt)};V(kn,it=>{n(Re).fit&&it(qn)})}var je=l(xt,2);{var nt=it=>{var vt=Xd(),Xt=s(vt);z(qt=>{st(vt,1,`fit-badge ${qt??""}`),m(Xt,n(Re).fit.text)},[()=>g(n(Re).fit.level)]),x(it,vt)};V(je,it=>{n(Re).fit&&it(nt)})}z(()=>{m(et,n(Re).name),m(tt,n(Re).role)}),x(Ke,ht)});var Ht=l(Ut,2);{var Pe=Ke=>{const Re=N(()=>Math.max(...n(k).models.map(At=>{var et;return((et=At.fit)==null?void 0:et.req_gb)??0})));var ht=tc(),xt=l(s(ht)),$t=s(xt);z(At=>m($t,`~${At??""} GB`),[()=>n(Re).toFixed(1)]),x(Ke,ht)};V(Ht,Ke=>{n(k).models.length&&n(k).models[0].fit&&Ke(Pe)})}var ke=l(Ht,2),Ne=s(ke);z(()=>{m(Xe,n(k).title),m(Gt,n(k).desc),st(ke,1,`primary${n(k).fit_level==="too_tight"?" warn":""}`),m(Ne,n(k).fit_level==="too_tight"?"Trotzdem installieren (zu groß)":"Komplettes Setup installieren")}),C("click",H,Ke=>{Ke.target.classList.contains("modal-overlay")&&_(_e,null)}),C("click",le,()=>_(_e,null)),C("click",ke,()=>A(n(k).id)),x(y,H)};V(xi,y=>{n(_e)&&y(Ys)})}var ki=l(xi,2);{var Zs=y=>{var k=oc(),H=s(k),G=s(H),le=l(G,2),Je=s(le),Xe=l(le,2),Yt=s(Xe),Gt=l(Xe,2);{var Ut=ke=>{var Ne=ac();x(ke,Ne)},Ht=ke=>{var Ne=sc(),Ke=l(ft(Ne),2);Ue(Ke,21,()=>n(J).files,Ge,(je,nt)=>{const it=N(()=>n(nt).fit.level==="perfect"?"●":n(nt).fit.level==="marginal"?"◐":"○");var vt=Ui(),Xt=s(vt),qt={};z(()=>{m(Xt,`${n(it)??""} ${n(nt).filename??""}`),qt!==(qt=n(nt).filename)&&(vt.value=(vt.__value=n(nt).filename)??"")}),x(je,vt)});var Re=l(Ke,2),ht=s(Re),xt=l(s(ht),2),$t=l(ht,2),At=s($t),et=l(s(At));Qe(et,()=>si(a));var Be=l(At,2),tt=l(Re,2);{var Zt=je=>{var nt=rc(),it=l(s(nt)),vt=s(it),Xt=l(it,2);z(qt=>m(vt,`~${qt??""}k`),[()=>Math.round(n(X).optimal_ctx/1024)]),C("click",Xt,qt=>{qt.preventDefault(),_(Q,n(X).optimal_ctx,!0)}),x(je,nt)};V(tt,je=>{var nt;(nt=n(X))!=null&&nt.optimal_ctx&&je(Zt)})}var ct=l(tt,2);{var ut=je=>{var nt=ic(),it=s(nt),vt=l(s(it),2),Xt=s(vt),qt=l(it,2),kt=s(qt);z((Nt,Nn,Or)=>{m(Xt,`~${Nt??""} GB · ~${Nn??""} Tok/s · ${(n(X).quant||"GGUF")??""}`),st(qt,1,`fit-badge ${Or??""}`),m(kt,n(X).fit.text)},[()=>(n(X).fit.req_gb??0).toFixed(1),()=>Math.round(n(X).fit.tps??0),()=>g(n(X).fit.level)]),x(je,nt)};V(ct,je=>{n(X)&&je(ut)})}var kn=l(ct,2),qn=s(kn);z(()=>{var je,nt;st(kn,1,`primary${((je=n(X))==null?void 0:je.fit.level)==="too_tight"?" warn":""}`),kn.disabled=n(be),m(qn,n(be)?"Starte…":((nt=n(X))==null?void 0:nt.fit.level)==="too_tight"?"Trotzdem holen (zu groß)":"Herunterladen & Einpflegen")}),C("change",Ke,ee),Wa(Ke,()=>n(se),je=>_(se,je)),Mn(xt,()=>n($e),je=>_($e,je)),Mn(Be,()=>n(Q),je=>_(Q,je)),C("click",kn,O),x(ke,Ne)},Pe=ke=>{var Ne=lc();x(ke,Ne)};V(Gt,ke=>{var Ne,Ke;n(be)&&!n(J)?ke(Ut):(Ke=(Ne=n(J))==null?void 0:Ne.files)!=null&&Ke.length?ke(Ht,1):n(be)||ke(Pe,2)})}z(()=>{m(Je,n(He)),m(Yt,n(T))}),C("click",k,ke=>{ke.target.classList.contains("modal-overlay")&&_(Me,!1)}),C("click",G,()=>_(Me,!1)),x(y,k)};V(ki,y=>{n(Me)&&y(Zs)})}var Xs=l(ki,2);{var el=y=>{var k=cc(),H=s(k),G=s(H),le=l(G,2),Je=s(le),Xe=l(le,6),Yt=l(Xe,4),Gt=l(Yt,4);Ue(Gt,17,()=>n(Ve),Ge,(ke,Ne,Ke)=>{var Re=dc(),ht=s(Re),xt=l(ht,2),$t=l(xt,2);Ue($t,20,()=>["Q4_K_M","Q5_K_M","Q6_K","Q8_0","Q3_K_M"],Ge,(et,Be)=>{var tt=Ui(),Zt=s(tt),ct={};z(()=>{m(Zt,Be),ct!==(ct=Be)&&(tt.__value=Be)}),x(et,tt)});var At=l($t,2);Mn(ht,()=>n(Ne).repo,et=>n(Ne).repo=et),Mn(xt,()=>n(Ne).role,et=>n(Ne).role=et),Wa($t,()=>n(Ne).quant,et=>n(Ne).quant=et),C("click",At,()=>_(Ve,n(Ve).filter((et,Be)=>Be!==Ke),!0)),x(ke,Re)});var Ut=l(Gt,2),Ht=l(Ut,4),Pe=s(Ht);z(()=>{m(Je,n(Fe)?"Setup bearbeiten":"Eigenes Setup erstellen"),m(Pe,n(Fe)?"Änderungen speichern":"Setup speichern")}),C("click",k,ke=>{ke.target.classList.contains("modal-overlay")&&_(ze,!1)}),C("click",G,()=>_(ze,!1)),Mn(Xe,()=>n(Se),ke=>_(Se,ke)),Mn(Yt,()=>n(Ie),ke=>_(Ie,ke)),C("click",Ut,()=>_(Ve,[...n(Ve),{repo:"",role:"",quant:"Q4_K_M"}],!0)),C("click",Ht,te),x(y,k)};V(Xs,y=>{n(ze)&&y(el)})}z(()=>{m(pt,n(E)?"aktuell":""),dt.disabled=n(I),m(St,n(I)?"Suche…":"Aktualisieren"),m(Et,`Täglich aktualisierte Top-Modelle je Kategorie — Hardware-Fit berechnet für dein System. ${n(de)?"⚠ Cache veraltet.":""}`),yt.disabled=n(me),m(xn,n(me)?"Lade…":"Suchen")}),C("click",ot,B),C("click",dt,()=>Kt(!0)),C("keydown",Ze,y=>y.key==="Enter"&&Mt()),Mn(Ze,()=>n(Y),y=>_(Y,y)),Wa(Rt,()=>n(F),y=>_(F,y)),C("click",yt,Mt),x(e,Ce),Zn()}_a(["click","keydown","change"]);var fc=M(`
Du öffnest Mission Control über einen Proxy/Namen. Die Engine läuft auf Port 8080 ohne HTTPS direkt am Bosgame.
+ Trag in deinen Tools die LAN-IP ein: http://192.168.178.151:8080/v1 — nicht den Proxy-Namen.
Wähle, welche Modelle im Config-Snippet erscheinen sollen.
'),bc=M(''),wc=M('
Öffne die Zed-Settings mit Cmd/Strg + , und füge diesen Block in deine bestehende settings.json ein. Falls bereits ein "bosgame"-Eintrag existiert: vorher löschen.
Nur diesen Block einfügen, nicht die ganze Datei ersetzen — sonst gehen andere Einstellungen verloren.
API-Key: Öffne den Agent-Panel → Anbieter bosgame → trag irgendeinen Wert ein (z.B. local).
Nur den "provider"-Block einfügen. Falls bereits ein "llama-swap"-Eintrag vorhanden: vorher löschen, sonst erscheinen Modelle doppelt.
Danach in OpenCode /models → wählen.
',1),xc=M('
In Cline/Cursor den Provider „OpenAI Compatible" wählen:
API-Key: ein beliebiger Wert (z.B. local).
',1),kc=M('
Settings → Admin Panel → Connections → neue OpenAI-Verbindung:
',1),Mc=M('
Bilder einbinden (Vision)
Mit Vision-Modellen kannst du Screenshots oder Fehlerbilder direkt mitschicken.
'),Sc=M('
Verbinden
Schritt für Schritt: KI-Modelle in deine Tools einbinden — getestet, erklärt, mit Copy-Paste-Config.
1 · Verbindung prüfen
Stelle sicher, dass die LLM-Engine erreichbar ist, bevor du Tools konfigurierst.
Wie funktioniert das Auto-Swap?
3 · Tool wählen
Für welches Werkzeug brauchst du die Konfiguration?
4 · Config-Snippet
',1);function Ec(e,t){Yn(t,!0);const a=N(()=>dr.value),r=N(()=>{var L;return(((L=n(a))==null?void 0:L.models)||[]).filter(K=>!K.incomplete)}),i=N(f);let o=R("zed"),d=R(Bt([])),c=R(""),u=R(!1);function f(){return`http://${location.hostname}:8080/v1`}function g(L){return/^\d{1,3}(\.\d{1,3}){3}$/.test(L)}function $(){return location.protocol==="https:"||!g(location.hostname)}const h=N(()=>n(d).length?n(d):n(r).map(L=>L.name)),b=N(()=>n(h)[0]||"coder"),S=N(()=>{var L;return(L=n(r).find(K=>{var w,A;return(A=(w=K.meta)==null?void 0:w.caps)==null?void 0:A.includes("Bild")}))==null?void 0:L.name});function P(L,K){const A=(K.length?n(r).filter(q=>K.includes(q.name)):n(r).length?n(r):[{name:"coder",meta:{}}]).map(q=>{var O;const ee=(((O=q.meta)==null?void 0:O.caps)||[]).some(ue=>/Bild|Vision/i.test(ue));return` { "name": "${q.name}", "display_name": "${q.name}", "max_tokens": 32768${ee?', "capabilities": { "tools": true, "images": true }':""} }`}).join(`,
+`);return`"language_models": {
+ "openai_compatible": {
+ "bosgame": {
+ "api_url": "${L}",
+ "available_models": [
+${A}
+ ]
+ }
+ }
+}`}function E(L,K){const A=(K.length?K:n(r).length?n(r).map(q=>q.name):["coder"]).map(q=>` "${q}": { "name": "${q}" }`).join(`,
+`);return`"provider": {
+ "llama-swap": {
+ "npm": "@ai-sdk/openai-compatible",
+ "name": "Bosgame (llama-swap)",
+ "options": {
+ "baseURL": "${L}",
+ "apiKey": "local"
+ },
+ "models": {
+${A}
+ }
+ }
+}`}async function I(){_(c,"Teste…");try{const L=await fe("/api/integration/test");_(u,L.ok,!0),_(c,L.ok?`✓ Verbunden — ${L.models.length} Modell(e): ${L.models.join(", ")}`:`✗ Keine Verbindung: ${L.error}`,!0)}catch(L){_(u,!1),_(c,"✗ "+L.message)}}function de(L){var w;const K=L.querySelector("code");K&&((w=navigator.clipboard)==null||w.writeText(K.textContent||""))}function Y(L){var K;(K=navigator.clipboard)==null||K.writeText(L.value)}var ce=Sc(),me=l(ft(ce),2),U=l(s(me),6),F=s(U),ie=l(F,2),he=l(U,2);{var _e=L=>{var K=fc();x(L,K)},Me=N(()=>$());V(he,L=>{n(Me)&&L(_e)})}var He=l(he,2),T=s(He),J=l(T,2);{var se=L=>{var K=pc(),w=s(K);z(()=>{jn(K,`color:${n(u)?"#7ee29a":"#ff9b95"}`),m(w,n(c))}),x(L,K)};V(J,L=>{n(c)&&L(se)})}var $e=l(me,2),Q=l(s($e),2);Ue(Q,20,()=>[["⚡","Kein manuelles Starten nötig",'Wenn du in Zed oder OpenCode „coder" auswählst und eine Anfrage sendest, lädt llama-swap das Modell automatisch — in ca. 2–5 Sekunden.'],["🔄","Modelle wechseln sich automatisch ab","Rufst du ein anderes Modell auf, entlädt llama-swap das aktuelle (nach TTL-Ablauf) und lädt das neue. Es läuft immer nur eines gleichzeitig."],["🧠","Zwei Modelle gleichzeitig aktiv halten","Du hast 124 GB RAM — genug für z.B. Scout 8B (~5 GB) dauerhaft geladen + ein größeres Modell das swappt. Dafür beim Scout im Modelle-Tab → Konfigurieren die TTL auf 99999 setzen."]],Ge,(L,K)=>{var w=N(()=>Qa(K,3));let A=()=>n(w)[0],q=()=>n(w)[1],ee=()=>n(w)[2];var O=hc(),ue=s(O),B=s(ue),j=l(ue,2),te=s(j),ae=s(te),De=l(te,2),Ae=s(De);z(()=>{m(B,A()),m(ae,q()),m(Ae,ee())}),x(L,O)});var be=l($e,2);{var X=L=>{var K=mc(),w=s(K),A=l(s(w),2),q=s(A),ee=l(w,4);Ue(ee,21,()=>n(r),Ge,(B,j)=>{const te=N(()=>n(d).includes(n(j).name));var ae=gc(),De=s(ae);z(()=>{jn(ae,`cursor:pointer;${n(te)?"background:rgba(45,212,191,.2);border-color:var(--accent);color:var(--accent)":""}`),m(De,n(j).name)}),C("click",ae,()=>_(d,n(te)?n(d).filter(Ae=>Ae!==n(j).name):[...n(d),n(j).name],!0)),C("keydown",ae,Ae=>Ae.key==="Enter"&&_(d,n(te)?n(d).filter(Ce=>Ce!==n(j).name):[...n(d),n(j).name],!0)),x(B,ae)});var O=l(ee,2);{var ue=B=>{var j=_c(),te=s(j);C("click",te,ae=>{ae.preventDefault(),_(d,[],!0)}),x(B,j)};V(O,B=>{n(d).length&&B(ue)})}z(()=>m(q,n(d).length?`${n(d).length} von ${n(r).length} ausgewählt`:"alle werden eingeschlossen")),x(L,K)};V(be,L=>{n(r).length&&L(X)})}var ze=l(be,2),Se=l(s(ze),4);Ue(Se,20,()=>[["zed","Zed","Schneller nativer Editor — Empfohlen"],["opencode","OpenCode","Open-Source Coding-Agent (Terminal/Desktop)"],["cline","Cline / Cursor","VS Code Extension, Bring-Your-Own-Key"],["openwebui","OpenWebUI","Browser-Chat-Interface"]],Ge,(L,K)=>{var w=N(()=>Qa(K,3));let A=()=>n(w)[0],q=()=>n(w)[1],ee=()=>n(w)[2];var O=bc(),ue=s(O),B=s(ue),j=l(ue,2),te=s(j);z(()=>{st(O,1,`card-btn${n(o)===A()?" cb-best":""}`),m(B,q()),m(te,ee())}),C("click",O,()=>_(o,A(),!0)),x(L,O)});var Ie=l(ze,2),Ve=l(s(Ie),2);{var Fe=L=>{const K=N(()=>P(n(i),n(h)));var w=wc(),A=l(ft(w),4),q=s(A),ee=l(q,2),O=s(ee),ue=s(O),B=l(A,4),j=l(s(B)),te=s(j);z(()=>{m(ue,n(K)),m(te,`bosgame / ${n(b)??""}`)}),C("click",q,ae=>de(ae.currentTarget.parentElement)),x(L,w)},mt=L=>{const K=N(()=>E(n(i),n(h)));var w=yc(),A=l(ft(w),6),q=s(A),ee=l(q,2),O=s(ee),ue=s(O),B=l(A,2),j=l(s(B),3),te=s(j);z(()=>{m(ue,n(K)),m(te,`llama-swap/${n(b)??""}`)}),C("click",q,ae=>de(ae.currentTarget.parentElement)),x(L,w)},bt=L=>{var K=xc(),w=l(ft(K),4),A=s(w),q=l(A,2),ee=l(w,4),O=s(ee),ue=l(O,2);z(()=>{ur(A,n(i)),ur(O,n(b))}),C("click",q,B=>Y(B.currentTarget.previousElementSibling)),C("click",ue,B=>Y(B.currentTarget.previousElementSibling)),x(L,K)},Kt=L=>{var K=kc(),w=l(ft(K),4),A=s(w),q=l(A,2),ee=l(w,4),O=s(ee),ue=l(O,2);z(()=>ur(A,n(i))),C("click",q,B=>Y(B.currentTarget.previousElementSibling)),C("click",ue,B=>Y(B.currentTarget.previousElementSibling)),x(L,K)};V(Ve,L=>{n(o)==="zed"?L(Fe):n(o)==="opencode"?L(mt,1):n(o)==="cline"?L(bt,2):n(o)==="openwebui"&&L(Kt,3)})}var wt=l(Ie,2);{var Mt=L=>{const K=N(()=>`curl ${n(i)}/chat/completions \\
+ -H "Content-Type: application/json" \\
+ -d '{
+ "model": "${n(S)}",
+ "messages": [{"role":"user","content":[{"type":"text","text":"Was ist auf dem Bild falsch?"},{"type":"image_url","image_url":{"url":"data:image/png;base64,"}}]}]
+}'`);var w=Mc(),A=s(w),q=l(s(A),2),ee=s(q);Qe(ee,()=>ea("check"));var O=l(ee),ue=l(A,4),B=s(ue),j=l(B,2),te=s(j),ae=s(te);z(()=>{m(O,` „${n(S)??""}" bereit`),m(ae,n(K))}),C("click",B,De=>de(De.currentTarget.parentElement)),x(L,w)};V(wt,L=>{n(S)&&L(Mt)})}z(()=>ur(F,n(i))),C("click",ie,L=>{const K=L.currentTarget.previousElementSibling;Y(K)}),C("click",T,I),x(e,ce),Zn()}_a(["click","keydown"]);var Tc=M('
'),$c=M('
Für dich: Upgrades
Neuere Modelle, die ein installiertes ersetzen können.
'),Ac=M('
Lade News… (erstes Laden kann kurz dauern)
'),Cc=M('
'),Lc=M('
Keine News geladen
Die Quellen sind gerade nicht erreichbar — später nochmal probieren.
'),Pc=M('für dich'),zc=M('
'),Dc=M(''),Oc=M('
News
Aktuelles aus der Local-LLM-Welt — aus kuratierten Quellen.
',1);function Rc(e,t){Yn(t,!0);let a=R(Bt([])),r=R(Bt([])),i=R(!0),o=R("");const d=U=>U==="perfect"?"ok":U==="marginal"?"warn":"bad";function c(U){const F=Math.max(0,Date.now()/1e3-U);return F<60?F+"s":F<3600?Math.floor(F/60)+"m":F<86400?Math.floor(F/3600)+"h":Math.floor(F/86400)+"d"}async function u(){_(i,!0),_(o,"");try{_(a,(await fe("/api/news")).items||[],!0)}catch(U){_(o,U.message||"Fehler beim Laden",!0)}finally{_(i,!1)}}async function f(){try{_(r,(await fe("/api/cookbook/upgrades")).upgrades||[],!0)}catch{_(r,[],!0)}}async function g(U,F){var ie;F.disabled=!0,F.textContent="Starte…";try{await fe("/api/cookbook/install-model",{method:"POST",body:JSON.stringify({repo:U.repo,role:U.role,params_b:U.params_b,quant:U.quant,hf_token:Ja()})}),(ie=document.querySelector(".nav-item[data-view='activity']"))==null||ie.dispatchEvent(new MouseEvent("click"))}catch(he){F.disabled=!1,F.textContent="Installieren",alert("Fehler: "+he.message)}}function $(){u(),f()}or(()=>{u(),f()});var h=Oc(),b=ft(h),S=l(s(b),2),P=l(b,2);{var E=U=>{var F=$c(),ie=s(F),he=l(s(ie),2),_e=s(he),Me=l(ie,4);Ue(Me,21,()=>n(r),Ge,(He,T)=>{var J=Tc(),se=s(J),$e=s(se),Q=s($e),be=s(Q),X=l(Q,2),ze=s(X),Se=l($e,2),Ie=s(Se),Ve=l(se,2);z(Fe=>{m(be,n(T).name),st(X,1,`fit-badge ${Fe??""}`),m(ze,n(T).fit.text),m(Ie,`Ersetzt dein „${n(T).old??""}". ${n(T).why??""}`)},[()=>d(n(T).fit.level)]),C("click",Ve,Fe=>g(n(T),Fe.currentTarget)),x(He,J)}),z(()=>m(_e,n(r).length)),x(U,F)};V(P,U=>{n(r).length&&U(E)})}var I=l(P,2);{var de=U=>{var F=Ac();x(U,F)},Y=U=>{var F=Cc(),ie=s(F);z(()=>m(ie,n(o))),x(U,F)},ce=U=>{var F=Lc();x(U,F)},me=U=>{var F=Dc();Ue(F,21,()=>n(a),Ge,(ie,he,_e)=>{var Me=zc();st(Me,1,`news-card${_e===0?" featured":""}`);var He=s(Me),T=s(He),J=s(T),se=l(T,2);{var $e=Se=>{var Ie=Pc();x(Se,Ie)};V(se,Se=>{n(he).relevant&&Se($e)})}var Q=l(se,2),be=s(Q),X=l(He,2),ze=s(X);z(Se=>{Lr(Me,"href",n(he).link),m(J,n(he).source),m(be,Se),m(ze,n(he).title)},[()=>n(he).ts?c(n(he).ts):""]),x(ie,Me)}),x(U,F)};V(I,U=>{n(i)?U(de):n(o)?U(Y,1):n(a).length?U(me,-1):U(ce,2)})}C("click",S,$),x(e,h),Zn()}_a(["click"]);const Bc=[["Dein erster Workflow — in 3 Schritten",`
Modell holen: Tab Cookbook → wähle deinen Anwendungsfall (z.B. „Coden") →
Komplettes Setup installieren. Der Download läuft im Tab Aktivität.
@@ -75,483 +109,4 @@ var ir=Object.defineProperty;var ns=e=>{throw TypeError(e)};var sr=(e,t,n)=>t in
OpenWebUI: Klassische Chat-Oberfläche für Fragen/Texte.
Tipp: starkes Modell (coder) für schwere Aufgaben, schnelles
- (scout) für einfache — du wählst es einfach im Tool aus.
`]],Gl=[["LLM / Modell","Die KI selbst — versteht und schreibt Text (wie ChatGPT, nur lokal auf deinem PC). Ein Modell ist eine konkrete Datei davon, oft auf etwas spezialisiert (Code, Bilder, Chat)."],["GGUF","Das lokal lauffähige Dateiformat. Unsere Engine lädt nur GGUF — Repos ohne GGUF kann sie nicht nutzen."],["Quantisierung (Q4_K_M)","Verkleinert ein Modell (16-Bit → 4-Bit), damit es in den Speicher passt — bei kaum Qualitätsverlust. Q4_K_M ist der beliebte Sweetspot."],["Kontextfenster","Das Kurzzeitgedächtnis: wie viel Text sich das Modell gleichzeitig merkt. Größer = mehr Überblick, mehr Speicher, etwas langsamer."],["Auto-Swap","llama-swap lädt/entlädt Modelle automatisch — immer nur eins gleichzeitig. Du managst nichts."],["Agent","Ein Programm (OpenCode, Cline, Continue), das das Modell handeln lässt — Dateien lesen/ändern, Befehle ausführen, planen, im Loop bis die Aufgabe erledigt ist."],["MCP","Model Context Protocol — der Anschluss, über den ein Agent zusätzliche Werkzeuge bekommt (Web, Dateien, Datenbank). MCP = Werkzeuge."],["Skills","Verpackte Anleitung für wiederkehrende Aufgaben — der Agent liest sie und macht es jedes Mal wie du willst. Skills = Anleitungen."],["Rules / Commands","Die konkrete Form von Skills je Tool: AGENTS.md (OpenCode), .clinerules (Cline), Rules/Prompts (Continue)."]];var Kl=be(''),ql=be('
'),Ul=be('
Guide für Einsteiger
Praktische Workflows + die Local-LLM-Welt in Klartext — ohne Vorwissen.
Hol dir unter „Cookbook" ein Modell, das auf deine Hardware passt.
`)}function co(){Vi(),Wi(),Ra(),Fa(),Na()}function uo(){Fa(),co(),oo()}function fo(e){Ge=e,Vi(),Wi(),Na()}function po(e){ne=e,Vi(),Wi(),Ra()}const ho={id:"overview",mount:uo,onStatus:fo,onSystem:po};let _s=[];const vo="Kontext = das Kurzzeitgedächtnis des Modells. Größer = merkt sich mehr (längere Dateien/Chats), braucht aber mehr Speicher und wird etwas langsamer.",mo="Die Rolle ist ein zusätzlicher, sprechender Name (z.B. coder). Du kannst in deinen Tools entweder den echten Modellnamen ODER die Rolle angeben — beides führt zum selben Modell.",go=[{id:"coder",label:"Coder",desc:"Programmieren & Code"},{id:"vision",label:"Vision",desc:"Bilder verstehen"},{id:"scout",label:"Scout",desc:"Schneller Allrounder"},{id:"reviewer",label:"Reviewer",desc:"Code/Texte prüfen"},{id:"manager",label:"Manager",desc:"Planen & koordinieren"}];let je=[];function kn(){document.dispatchEvent(new Event("mc:refresh"))}function bo(e){return Bl(e.state,e.download_progress)}function wo(e){return e?`${g(e)}`:""}function yo(e){return`
Deine konfigurierten Modelle — testen, Kontext anpassen oder aus dem Speicher werfen.
`,l("#m-chat").innerHTML=`
-
Schnelltest
-
Schreib eine Nachricht — das gewählte Modell wird automatisch geweckt.
-
-
-
-
-
- `,l("#m-table").innerHTML=`
-
Modelle & Ports
-
Modelle laden automatisch, sobald eine Anfrage kommt — du musst nichts manuell starten.
-
-
Modell
Kann
Details
Status
Port
Aktionen
-
-
-
-
Noch keine Modelle konfiguriert
-
Hol dir unter „Cookbook" ein passendes Modell.
-
-
-
-
-
-
Modell konfigurieren
-
-
-
-
-
Empfohlen für deine Hardware
-
-
-
-
Es läuft immer nur ein Modell gleichzeitig — ein größerer Kontext hier beeinflusst deine anderen Modelle nicht.
-
-
-
-
-
-
-
-
Rolle festlegen ${Jn(mo)}
-
-
Wähle eine Rolle als zweiten, sprechenden Namen. In deinen Tools funktionieren danach beide: der echte Modellname und die Rolle.
-
-
-
-
-
-
-
-
-
`,l("#chat-btn").addEventListener("click",Ao),l("#cfg-close").addEventListener("click",()=>l("#cfg-modal").style.display="none"),l("#cfg-modal").addEventListener("click",e=>{e.target.id==="cfg-modal"&&(l("#cfg-modal").style.display="none")}),l("#cfg-save").addEventListener("click",Co),l("#role-close").addEventListener("click",()=>l("#role-modal").style.display="none"),l("#role-modal").addEventListener("click",e=>{e.target.id==="role-modal"&&(l("#role-modal").style.display="none")}),l("#role-save").addEventListener("click",()=>Pi(l("#role-custom").value.trim().toLowerCase())),l("#role-clear").addEventListener("click",()=>Pi("")),l("#m-table").addEventListener("click",e=>{var s,a;const t=e.target.closest("[data-action]");if(!t)return;const n=t.getAttribute("data-name"),i=t.getAttribute("data-action");n&&(i==="cfg"?To(n):i==="role"?Mo(n):i==="unload"?Lo(n):i==="del"?Eo(n):i==="copy"?((s=navigator.clipboard)==null||s.writeText(n),w("Kopiert: "+n)):i==="assign"&&(w("Lade im Cookbook ein Modell und setze den Alias auf: "+n),(a=document.querySelector(".nav-item[data-view='cookbook']"))==null||a.click()))})}let Di=null;function Mo(e){Di=e;const t=je.find(n=>n.name===e);l("#role-model-name").textContent=e,l("#role-custom").value="",l("#role-chips").innerHTML=go.map(n=>``).join(""),l("#role-chips").querySelectorAll("[data-rolepick]").forEach(n=>n.addEventListener("click",()=>Pi(n.getAttribute("data-rolepick")))),l("#role-modal").style.display="flex"}async function Pi(e){if(Di)try{await $("/api/set_role",{method:"POST",body:JSON.stringify({alias:Di,role:e})}),w(e?`Rolle gesetzt: ${e}`:"Rolle entfernt."),l("#role-modal").style.display="none",kn()}catch(t){w(t.message,!0)}}function Ia(){const e=l("#models");if(!e)return;l("#models-empty").style.display=je.length?"none":"flex",l("#m-count").textContent=je.length?je.length+" konfiguriert":"";const t=l("#chat-model"),n=t.value;t.innerHTML="",e.innerHTML=je.map(i=>{var a,r;if(i.incomplete)return`
-
${g(i.name)}
kein Modell hinterlegt
-
—
-
leere Rolle
-
leer
-
—
-
-
-
-
`;const s=(a=i.meta)!=null&&a.filename?`
${g(i.meta.filename)}
`:"";return`
-
-
${g(i.name)}${wo(i.role)}
- ${s}${yo(i)}
-
${xo((r=i.meta)==null?void 0:r.caps)}
-
${_o(i.meta)}
-
${bo(i)}
-
${i.port??"auto"}
-
-
-
-
-
-
`}).join("");for(const i of je)i.incomplete||t.insertAdjacentHTML("beforeend",``);n&&(t.value=n)}function So(e){je=(e==null?void 0:e.models)||[],Ia()}function $o(e){_s=e||[],_s.some(n=>(n.state==="running"||n.state==="queued")&&/^download\b/i.test(n.label))&&Ia()}async function Eo(e){if(await Ee({title:`„${e}" wirklich löschen?`,body:"Das Modell wird aus der Konfiguration ausgetragen und seine Dateien werden von der Festplatte gelöscht, um Speicher freizugeben. Das lässt sich nicht rückgängig machen — später brauchst du dafür einen neuen Download.",confirmLabel:"Endgültig löschen",danger:!0}))try{const n=await $("/api/delete_model",{method:"POST",body:JSON.stringify({alias:e})});w(n.note||"Gelöscht: "+e),setTimeout(kn,600)}catch(n){w(n.message,!0)}}function To(e){var s,a,r,o,c;const t=je.find(u=>u.name===e);if(!t)return;l("#cfg-model-name").textContent=t.name,l("#cfg-ctx").value=((s=t.meta)==null?void 0:s.ctx)||8192;const n=(a=t.meta)==null?void 0:a.optimal_ctx,i=l("#cfg-rec");if(n){const u=((r=t.meta)==null?void 0:r.ctx)||0,p=(o=t.meta)==null?void 0:o.peak_ram_gb,v=(c=t.meta)==null?void 0:c.peak_ram_optimal_gb;l("#cfg-rec-note").innerHTML=`Optimal ~${Math.round(n/1024)}k → ~${v} GB Spitzenbedarf. Aktuell ~${Math.round(u/1024)}k → ~${p} GB.`,l("#cfg-rec-apply").onclick=()=>{l("#cfg-ctx").value=n},i.style.display="flex"}else i.style.display="none";l("#cfg-modal").style.display="flex"}async function Co(){const e=l("#cfg-model-name").textContent,t=parseInt(l("#cfg-ctx").value)||8192;l("#cfg-save").disabled=!0;try{await $("/api/update_model",{method:"POST",body:JSON.stringify({alias:e,ctx:t})}),w("Gespeichert — aktiv beim nächsten Modell-Start."),l("#cfg-modal").style.display="none",kn()}catch(n){w(n.message,!0)}l("#cfg-save").disabled=!1}async function Lo(e){if(await Ee({title:`„${e}" entladen?`,body:"Das Modell wird aus dem Grafikspeicher geworfen und lädt beim nächsten Aufruf automatisch neu.",confirmLabel:"Entladen"}))try{await $("/api/unload?model="+encodeURIComponent(e),{method:"POST"}),w("Entladen: "+e),setTimeout(kn,600)}catch(t){w(t.message,!0)}}async function Ao(){const e=l("#chat-model").value,t=l("#chat-msg").value.trim();if(!e)return w("Kein Modell vorhanden.",!0);if(!t)return;const n=l("#chat-btn");n.disabled=!0,n.textContent="…";const i=l("#chat-reply");i.style.display="block",i.textContent="(wecke Modell, kann beim Laden kurz dauern…)";try{const s=await $("/api/chat",{method:"POST",body:JSON.stringify({model:e,message:t})});i.textContent=s.reply}catch(s){i.textContent="Fehler: "+s.message}n.disabled=!1,n.textContent="Senden",kn()}const zo={id:"models",mount:ko,onStatus:So,onJobs:$o},dn=new Set;let lt=[],B=null;function Ji(e){dn.add(e),Vt()}const Ue={cpu:[],ram:[],gpu:[]},Do=60;function Po(e){return e==="done"?'fertig':e==="failed"?'fehler':e==="canceled"?'abgebrochen':'läuft…'}function Oo(e){return e==="done"?"on":e==="failed"||e==="canceled"?"":"load"}function Bo(e){if(e.state!=="running")return null;if(typeof e.progress=="number")return Math.min(100,e.progress);const n=((e.log||[])[e.log.length-1]||"").match(/(\d+(?:\.\d+)?)\s*%/);return n?Math.min(100,parseFloat(n[1])):null}function Ho(e){return e==null||e<0?"":e>=90?`noch ~${Math.round(e/60)} min`:`noch ~${Math.max(1,Math.round(e))} s`}async function Ro(e){try{await $(`/api/jobs/${e}/cancel`,{method:"POST"}),w("Abbruch angefordert…"),Vt()}catch(t){w(t.message,!0)}}async function Fo(e){try{await $(`/api/jobs/${e}`,{method:"DELETE"}),Vt()}catch(t){w(t.message,!0)}}async function No(){try{const e=await $("/api/jobs/clear",{method:"POST"});w(`${e.removed} Einträge entfernt.`),Vt()}catch(e){w(e.message,!0)}}function mi(e,t,n){return`
Dienste steuern, Updates einspielen und live mitlesen — ganz ohne SSH oder Terminal.
-
-
-
-
LLM-Engine (llama-swap)
- prüfe…
-
Das Herzstück: llama-swap lädt bei jeder Anfrage automatisch das passende Modell und entlädt die anderen — es läuft immer nur eines gleichzeitig.
-
-
Adresse (für deine Tools)—
-
Aktuell geladen—
-
Konfigurierte Modelle—
-
-
Wie ansteuern? In deinem Tool (Zed/OpenCode) die obige Adresse als OpenAI-Basis-URL eintragen und einen Modellnamen oder eine Rolle angeben (siehe „Verbinden"). Eine einzige Anfrage genügt — das Modell wird automatisch geweckt.
-
-
-
-
-
Dienste & Applikation
-
Diese Aktionen sind harmlos und passwortlos — es geht nichts dabei verloren.
- ${at("w-restart-swap","refresh","teal","LLM-Engine neustarten","Startet llama-swap neu. Geladene Modelle werden kurz entladen (~5 Sek), laden danach automatisch wieder.")}
- ${at("w-restart-mc","refresh","blue","Dashboard neustarten","Startet diese Oberfläche neu. Sie trennt kurz und verbindet automatisch wieder.")}
- ${at("w-self-update","download","teal","Mission Control aktualisieren","Holt die neueste Dashboard-Version aus Gitea und startet automatisch neu (~10 Sek).")}
- ${at("w-update","bolt","blue","LLM-Engine aktualisieren","Lädt die neueste llama.cpp-Version (ROCm) für deine GPU und installiert sie.")}
- ${at("w-unload","database","amber","Grafikspeicher leeren","Wirft alle aktuell geladenen Modelle aus dem VRAM. Fragt vorher nach.")}
-
-
-
-
Betriebssystem (Bosgame)
-
Tiefe Eingriffe — das Dashboard fragt einmalig nach deinem sudo-Passwort.
- ${at("w-os-update","refresh","amber","OS-Updates installieren","Führt apt update & upgrade aus. Kann ein paar Minuten dauern — Fortschritt in der Aktivität.")}
- ${at("w-reboot","bolt","red","Server neustarten (Reboot)","Bootet den ganzen Bosgame neu. Alles ist für ~1 Minute offline, auch dieses Dashboard.")}
-
-
-
-
Aktueller Vorgang
-
-
-
—
-
-
-
-
-
-
Live-Konsole
-
-
-
Live mitlesen, was der Dienst gerade tut.
-
Verbinde…
-
-
`,l("#w-restart-swap").addEventListener("click",()=>Ss("llama-swap","LLM-Engine neustarten?","Die Engine startet neu. Geladene Modelle werden kurz entladen (~5 Sekunden), laden danach automatisch wieder.")),l("#w-restart-mc").addEventListener("click",()=>Ss("mission-control","Dashboard neustarten?","Diese Oberfläche trennt sich kurz und verbindet automatisch wieder. Laufende Downloads laufen weiter.")),l("#w-self-update").addEventListener("click",Qo),l("#w-update").addEventListener("click",Zo),l("#w-unload").addEventListener("click",Jo),l("#w-os-update").addEventListener("click",Xo),l("#w-reboot").addEventListener("click",ed),l("#w-console-sel").addEventListener("change",Ms),l("#w-update-cancel").addEventListener("click",Wo),l("#w-swap-check").addEventListener("click",jo),Oi&&Ka(Oi),Ms()}let Oi=null;function Ka(e){const t=l("#w-swap-state");if(!t)return;const n=!!(e!=null&&e.swap_ok);t.textContent=n?"● erreichbar":"● offline",t.style.cssText="margin-left:auto;"+(n?"background:rgba(63,185,80,.12);border-color:rgba(63,185,80,.25);color:#7ee29a":"background:rgba(248,81,73,.12);border-color:rgba(248,81,73,.3);color:#ff9b95"),l("#w-swap-url").textContent=((e==null?void 0:e.swap_url)||"—")+"/v1";const i=(e==null?void 0:e.models)||[],s=i.find(r=>r.state==="running"||r.state==="loading");l("#w-swap-loaded").innerHTML=s?`${g(s.name)}(${g(s.state)})`:'nichts geladen — lädt bei der nächsten Anfrage';const a=i.filter(r=>!r.incomplete).length;l("#w-swap-count").textContent=a?`${a} bereit`:"keine"}async function jo(){const e=l("#w-swap-check-res");e.textContent="prüfe…",e.style.color="var(--mut)";try{const t=await $("/api/integration/test");t.ok?(e.style.color="#7ee29a",e.textContent=`✓ Engine antwortet — ${t.models.length} Modell(e): ${t.models.join(", ")}`):(e.style.color="#ff9b95",e.textContent="✗ keine Verbindung: "+t.error)}catch(t){e.style.color="#ff9b95",e.textContent="✗ "+t.message}}function Vo(e){Oi=e,Ka(e)}async function Wo(){if(Le&&await Ee({title:Le.label+" abbrechen?",body:"Der laufende Vorgang wird gestoppt. Bei einem Download bleibt eine unvollständige Datei zurück (kannst du später neu starten).",confirmLabel:"Abbrechen",danger:!0}))try{await $(`/api/jobs/${Le.id}/cancel`,{method:"POST"}),w("Abbruch angefordert…")}catch(e){w(e.message,!0)}}let St=null;function Ms(){St&&(St.close(),St=null);const e=l("#w-console-sel").value,t=l("#w-console");t.textContent="Verbinde mit "+e+`…
-`;const n=location.protocol==="https:"?"wss:":"ws:";St=new WebSocket(`${n}//${location.host}/api/logs/${e}?token=${encodeURIComponent(Da())}`),St.onmessage=i=>{t.textContent+=i.data,t.scrollTop=t.scrollHeight},St.onclose=()=>{t.textContent+=`
-— Verbindung getrennt —`}}async function Ss(e,t,n){if(await Ee({title:t,body:n,confirmLabel:"Neustarten"}))try{await $(`/api/service/${e}/restart`,{method:"POST"}),w("Neustart ausgelöst: "+e),setTimeout(Qi,2e3)}catch(i){w(i.message,!0)}}async function Jo(){if(await Ee({title:"Grafikspeicher leeren?",body:"Alle geladenen Modelle werden entladen. Sie laden beim nächsten Aufruf automatisch neu — es geht nichts verloren.",confirmLabel:"Leeren"}))try{await $("/api/unload",{method:"POST"}),w("Grafikspeicher geleert."),setTimeout(Qi,600)}catch(e){w(e.message,!0)}}async function Qo(){if(await Ee({title:"Mission Control aktualisieren?",body:"Holt die neueste Dashboard-Version aus Gitea und startet automatisch neu. Die Seite trennt kurz und verbindet von selbst wieder (~10 Sek)."}))try{const e=await $("/api/self-update",{method:"POST"});w("Update läuft — Dashboard startet gleich neu."),Ji(e.job_id)}catch(e){w(e.message,!0)}}let Le=null;const $s=new Set;function qa(e,t){Le={id:e,label:t};const n=l("#w-update-card");n&&(n.style.display="",l("#w-update-label").textContent=t+" läuft…",l("#w-update-badge").innerHTML='läuft…',l("#w-update-log").textContent="Starte…",n.scrollIntoView({behavior:"smooth",block:"nearest"}))}function Yo(e){if(!Le)return;const t=(e||[]).find(v=>v.id===Le.id),n=l("#w-update-card");if(!t||!n)return;n.style.display="";const i=l("#w-update-log");i&&(i.textContent=(t.log||[]).join(`
-`),i.scrollTop=i.scrollHeight);const s=t.state==="done",a=t.state==="failed",r=t.state==="canceled",o=s||a||r,c=l("#w-update-badge");c&&(c.innerHTML=s?'✓ fertig':a?'✗ fehlgeschlagen':r?'abgebrochen':'läuft…');let u=null;if(!o)if(typeof t.progress=="number")u=t.progress;else{const f=((t.log||[])[t.log.length-1]||"").match(/(\d+(?:\.\d+)?)\s*%/);f&&(u=parseFloat(f[1]))}const p=l("#w-update-bar");p&&(u!=null?(p.style.display="",p.querySelector("i").style.width=Math.min(100,u)+"%"):p.style.display="none"),l("#w-update-cancel").style.display=o?"none":"",o&&(l("#w-update-label").textContent=Le.label+(s?" — erfolgreich abgeschlossen.":r?" — abgebrochen.":" — fehlgeschlagen (siehe Protokoll)."),$s.has(t.id)||($s.add(t.id),w(s?"✓ "+Le.label+" abgeschlossen.":r?Le.label+" abgebrochen.":"✗ "+Le.label+" fehlgeschlagen.",a),s&&setTimeout(Qi,800)))}async function Zo(){if(await Ee({title:"LLM-Engine aktualisieren?",body:"Lädt die neueste llama.cpp-ROCm-Version und installiert sie. Die Engine ist während des Updates kurz nicht erreichbar."}))try{const e=await $("/api/update",{method:"POST"});w("LLM-Engine-Update läuft."),Ji(e.job_id),qa(e.job_id,"LLM-Engine-Update")}catch(e){w(e.message,!0)}}async function Xo(){if(!await Ee({title:"OS-Updates installieren?",body:"Führt apt update & upgrade aus. Das kann ein paar Minuten dauern; der Fortschritt erscheint in der Aktivität."}))return;const e=await Oa({title:"sudo-Passwort",body:"Für die System-Updates wird einmalig dein sudo-Passwort gebraucht.",placeholder:"sudo-Passwort",password:!0,confirmLabel:"Installieren"});if(e)try{const t=await $("/api/os-update",{method:"POST",body:JSON.stringify({password:e})});w("OS-Update gestartet."),Ji(t.job_id),qa(t.job_id,"OS-Update")}catch(t){w(t.message,!0)}}async function ed(){if(!await Ee({title:"Server wirklich neu starten?",body:"Der ganze Bosgame startet physisch neu. Alles ist für ~1 Minute offline — auch dieses Dashboard.",confirmLabel:"Reboot",danger:!0}))return;const e=await Oa({title:"sudo-Passwort",body:"Für den Reboot wird einmalig dein sudo-Passwort gebraucht.",placeholder:"sudo-Passwort",password:!0,confirmLabel:"Jetzt neustarten",danger:!0});if(e)try{await $("/api/reboot",{method:"POST",body:JSON.stringify({password:e})}),w("Reboot ausgelöst — bis gleich.")}catch(t){w(t.message,!0)}}const td={id:"server",mount:Uo,onJobs:Yo,onStatus:Vo},nd="Kontext = das Kurzzeitgedächtnis des Modells. Größer = merkt sich mehr, braucht aber mehr Speicher und wird etwas langsamer.",Ua="GGUF ist das lokal lauffähige Dateiformat. Unsere Engine lädt nur GGUF — Repos ohne GGUF-Datei kann sie nicht nutzen.",id="MoE (Mixture of Experts): viele Parameter fürs Wissen, aber pro Wort ist nur ein kleiner Teil aktiv → fast große-Modell-Qualität bei hohem Tempo. Ideal für deine APU.",sd={vision:"Bilder",coder:"Coden",reasoning:"Logik",agent:"Agenten & Tools",scout:"Allrounder",reviewer:"Review",manager:"Manager"},ad=[{label:"Bilder",kw:["-vl-","-vl","vision","llava","multimodal","-mm-","pixtral"]},{label:"Coden",kw:["coder","-code-","code-","codestral","starcoder"]},{label:"Logik",kw:["-r1","deepseek-r1","reasoning","qwq","magistral","-think","thinking","-o1"]},{label:"Agenten & Tools",kw:["hermes","-tool","command-r","watt","-fc-","function"]}];function ja(e){const t=(e||"").toLowerCase();for(const n of ad)if(n.kw.some(i=>t.includes(i)))return n.label;return"Allrounder"}function rd(e=""){const t=e.toLowerCase();return/\d+x\d+(\.\d+)?b/.test(t)||new RegExp("(?MoE${t?` · ~${t}B aktiv`:""}`}function Va(e){return e?`${g(e)}`:""}const od=[{id:"",label:"Alle"},{id:"coder",label:"Coder"},{id:"scout",label:"Scout"},{id:"vision",label:"Vision"},{id:"manager",label:"Manager"},{id:"reviewer",label:"Reviewer"}];let nn=null,sn=[],Ae=null,Dt="",Mn=[],Wa=null,cn=[];const dd={perfect:0,marginal:1,too_tight:2},Sn=e=>e==="perfect"?"ok":e==="marginal"?"warn":"bad",cd=e=>e==="perfect"?"Passt locker":e==="marginal"?"Wird knapp":"Zu groß";function ud(){const e=l(".view[data-view='cookbook']");e.innerHTML=`
-
-
Modell-Cookbook
-
Wofür willst du es nutzen? Wir empfehlen das passende Setup für deine Hardware.
-
-
-
Wofür möchtest du es nutzen?
- deine Hardware
-
-
-
Lade Setups…
-
-
-
Aktuell beste Modelle für dein System
-
-
-
Automatisch aus vertrauenswürdigen Quellen (HuggingFace) — laufend aktuell, gefiltert auf das, was auf deine Hardware passt.
-
Lade aktuelle Empfehlungen…
-
-
- Profi-Modus: HuggingFace direkt durchsuchen
-
-
-
-
-
-
-
-
-
-
- passt locker
- läuft, aber knapp
- zu groß für deinen Speicher
-
-
Es werden nur Modelle im GGUF-Format ${Jn(Ua)} gezeigt — nur die laufen lokal.
-
-
-
-
-
-
-
-
Setup
-
-
-
-
-
Lädt alle Modelle herunter und pflegt sie mit optimalem Kontext ein. Fortschritt in der Aktivität.
-
-
-
-
-
-
-
Eigenes Setup erstellen
-
Stell dir aus beliebigen Modellen ein eigenes Setup zusammen — es erscheint danach mit Hardware-Ampel hier im Cookbook.
-
-
-
-
-
-
-
-
Die Repo-ID findest du über die Profi-Suche weiter unten (z.B. unsloth/Qwen3-8B-GGUF). Die Modellgröße erkennen wir automatisch aus dem Namen.
Stelle sicher, dass die LLM-Engine erreichbar ist, bevor du Tools konfigurierst.
- ${an("Engine-Adresse (für alle Tools)",e)}
- ${Dd()?`
- Du öffnest Mission Control über einen Proxy/Namen (HTTPS o.ä.). Die Engine läuft auf Port 8080 ohne HTTPS direkt am Bosgame.
- Trag in deinen Tools die LAN-IP ein: http://192.168.178.151:8080/v1 — nicht den Proxy-Namen.
`:""}
-
-
-
-
-
`}function Rd(){return`
-
Wie funktioniert das Auto-Swap?
-
-
- ⚡
-
-
Kein manuelles Starten nötig
-
Wenn du in Zed oder OpenCode „coder" auswählst und eine Anfrage sendest,
- lädt llama-swap das Modell automatisch — in ca. 2–5 Sekunden. Du siehst es hier im Dashboard unter „Aktives Modell".
-
-
-
- 🔄
-
-
Modelle wechseln sich automatisch ab
-
Rufst du ein anderes Modell auf, entlädt llama-swap das aktuelle (nach TTL-Ablauf)
- und lädt das neue. Es läuft immer nur eines gleichzeitig — llama-swap verwaltet das komplett selbst.
-
-
-
- 🧠
-
-
Zwei Modelle gleichzeitig aktiv halten
-
Du hast 124 GB RAM — genug für z.B. Scout 8B (~5 GB) dauerhaft geladen + ein
- größeres Modell das swappt. Dafür beim Scout im Modelle-Tab → Konfigurieren die TTL auf 99999 setzen
- (= nie automatisch entladen). Das kleine Modell beantwortet schnelle Fragen sofort, ohne jede Wartezeit.
Öffne die Zed-Settings mit Cmd/Strg + , und füge diesen Block in deine bestehende
- settings.json ein. Falls bereits ein "bosgame"-Eintrag existiert: vorher löschen.
-
- Nur diesen Block einfügen, nicht die ganze Datei ersetzen — sonst gehen andere Einstellungen verloren.
-
-
-
-
${g(s)}
-
-
API-Key: Öffne den Agent-Panel → Anbieter bosgame → trag irgendeinen Wert ein (z.B. local).
- Alternativ BOSGAME_API_KEY=local als Umgebungsvariable.
-
Danach im Agent-Panel das Modell bosgame / ${g(n)} auswählen. Das Auto-Swap lädt es beim ersten Request.
`}else if(Lt==="opencode"){const s=Bd(e,t);i=`
-
Datei anlegen oder öffnen:
-
Windows: %USERPROFILE%\\.config\\opencode\\opencode.json
- Linux/Mac: ~/.config/opencode/opencode.json
- Oder im Projektordner: opencode.json
-
- Nur den "provider"-Block einfügen oder mergen.
- Falls bereits ein "llama-swap"-Eintrag vorhanden ist: vorher löschen, sonst erscheinen Modelle doppelt.
-
-
-
-
${g(s)}
-
-
Danach in OpenCode /models → llama-swap/${g(n)} wählen.
-
Statt alles zu ersetzen: nur den provider-Block in deine bestehende Config einfügen — so bleiben andere Anbieter erhalten.
`}else Lt==="cline"?i=`
-
In Cline/Cursor den Provider „OpenAI Compatible" wählen und folgende Felder eintragen:
Schritt für Schritt: KI-Modelle in deine Tools einbinden — getestet, erklärt, mit Copy-Paste-Config.
-
-
- ${Hd(t)}
- ${Rd()}
- ${Fd()}
- ${Nd()}
- ${Id(t)}
- ${Gd(t)}`,(n=l("#cn-test"))==null||n.addEventListener("click",Kd)}function ts(){if(!l(".view[data-view='connect'] .card:nth-of-type(3)")){un();return}const t=l(".view[data-view='connect']"),n=t==null?void 0:t.querySelectorAll(".card");if(!n||n.length<3){un();return}un()}async function Kd(){const e=l("#cn-test-result");e.innerHTML='Teste…';try{const t=await $("/api/integration/test");e.innerHTML=t.ok?Za(`Verbunden — ${t.models.length} Modell(e): ${t.models.join(", ")}`):`✗ Keine Verbindung: ${g(t.error)}`}catch(t){e.innerHTML=`✗ ${g(t.message)}`}}function qd(){un()}function Ud(e){Fn=e;const t=((e==null?void 0:e.models)||[]).map(n=>n.name).join(",")+"|"+!!e;t!==Ls&&(Ls=t,un())}const jd={id:"connect",mount:qd,onStatus:Ud},ni=[ho,zo,td,qo,Ld,jd];let As={};function Vd(){const e=document.getElementById("top-active-text");e&&(e.classList.add("swap-flash"),setTimeout(()=>e.classList.remove("swap-flash"),900))}function zs(e){var s;const t=document.getElementById("swdot"),n=document.getElementById("swlabel"),i=document.getElementById("top-active-text");if(!e)t&&(t.className="dot off"),n&&(n.textContent="Backend nicht erreichbar"),i&&(i.textContent="Backend offline"),Ds("Backend nicht erreichbar – läuft uvicorn?",!1);else{const a=(e.swap_url||"").replace(/^https?:\/\//,"");t&&(t.className="dot "+(e.swap_ok?"on":"off")),n&&(n.textContent=(e.swap_ok?"LLM-Engine: Online – ":"LLM-Engine: Offline – ")+a);const r=(e.models||[]).find(c=>c.state==="running");i&&(i.innerHTML=r?`Geladen: ${r.name}`:"Kein Modell im VRAM"),e.swap_ok?Yd():Ds(`LLM-Engine nicht erreichbar unter ${a} – läuft der llama-swap Dienst?`,!0);const o={};for(const c of e.models||[])o[c.name]=c.state;for(const[c,u]of Object.entries(o)){const p=As[c];if(p&&p!==u&&(u==="running"||u==="loading")){Vd();break}}As=o}Wd(e),Rl.set(e);for(const a of ni)(s=a.onStatus)==null||s.call(a,e)}function Wd(e){const t=document.getElementById("sec-chip"),n=document.getElementById("sec-chip-tx");!t||!n||(t.classList.toggle("ok",!!(e!=null&&e.secured)),n.textContent=e!=null&&e.secured?"Geschützt · Heimnetz":"Nur im Heimnetz (offen)")}function Jd(e){var t;Fl.set(e||[]);for(const n of ni)(t=n.onJobs)==null||t.call(n,e)}function Qd(e){var t;for(const n of ni)(t=n.onSystem)==null||t.call(n,e)}function Ds(e,t){const n=document.getElementById("alert");n.className="alert"+(t?" warn":""),n.innerHTML=`${e}`,n.style.display="flex"}function Yd(){const e=document.getElementById("alert");e&&(e.style.display="none")}const Zd=e=>{var t;return(t=document.querySelector(`.nav-item[data-view="${e}"]`))==null?void 0:t.click()};function wi(e,t,n,i){const s=document.createElement("span");return s.className="upd-badge"+(t?" "+t:""),s.textContent=e,i&&(s.title=i),s.onclick=()=>Zd(n),s}async function Xa(){try{const e=await $("/api/updates"),t=document.getElementById("update-badges");if(!t)return;const n=e.apt_cache_age_h,i=n!=null&&n>24,s=n!=null?` · apt-Cache: vor ${n}h`:"";t.innerHTML="",t.appendChild(wi(e.os>0?`OS: ${e.os} Updates`:i?"OS: ?":"OS: aktuell",e.os>0||i?"warn":"ok","server",e.os>0?`${e.os} ausstehende Pakete${s}`:i?`apt-Cache veraltet (${n}h)`:`Keine Updates${s}`)),t.appendChild(wi(e.engine>0?"Engine: Update":"Engine: aktuell",e.engine>0?"warn":"ok","server")),t.appendChild(wi(e.models>0?`Modelle: ${e.models}`:"Modelle: aktuell",e.models>0?"warn":"ok","news"))}catch{}}async function ii(){try{zs(await $("/api/status"))}catch{zs(null)}}async function er(){try{Jd(await $("/api/jobs"))}catch{}}function tr(){const e=window.location.protocol==="https:"?"wss:":"ws:",t=new WebSocket(`${e}//${window.location.host}/api/system/stream`);t.onmessage=n=>{try{Qd(JSON.parse(n.data))}catch{}},t.onclose=()=>{setTimeout(tr,3e3)},t.onerror=()=>t.close()}var Ns;for(const e of ni)(Ns=e.mount)==null||Ns.call(e);const Ps=document.querySelector('.view[data-view="guides"]');Ps&&za(jl,{target:Ps});const Os=document.querySelector('.view[data-view="news"]');Os&&za(no,{target:Os});Hl("overview");const yi=document.getElementById("token");yi&&(yi.value=Da(),yi.addEventListener("change",e=>{Dl(e.target.value),ii()}));const xi=document.getElementById("hf-token");xi&&(xi.value=_n(),xi.addEventListener("change",e=>Ol(e.target.value)));const Bs=document.getElementById("nav-settings"),_i=document.getElementById("settings-modal"),Hs=document.getElementById("sm-close");Bs&&_i&&Hs&&(Bs.addEventListener("click",()=>_i.style.display="flex"),Hs.addEventListener("click",()=>_i.style.display="none"));function Xd(){const e=document.getElementById("clock");e&&(e.textContent=new Date().toTimeString().slice(0,5))}document.addEventListener("mc:refresh",ii);ii();er();tr();setInterval(Xd,1e3);setInterval(ii,3e3);setInterval(er,1500);Xa();setInterval(Xa,6e4);
+ (scout) für einfache — du wählst es einfach im Tool aus.
`]],qc=[["LLM / Modell","Die KI selbst — versteht und schreibt Text (wie ChatGPT, nur lokal auf deinem PC). Ein Modell ist eine konkrete Datei davon, oft auf etwas spezialisiert (Code, Bilder, Chat)."],["GGUF","Das lokal lauffähige Dateiformat. Unsere Engine lädt nur GGUF — Repos ohne GGUF kann sie nicht nutzen."],["Quantisierung (Q4_K_M)","Verkleinert ein Modell (16-Bit → 4-Bit), damit es in den Speicher passt — bei kaum Qualitätsverlust. Q4_K_M ist der beliebte Sweetspot."],["Kontextfenster","Das Kurzzeitgedächtnis: wie viel Text sich das Modell gleichzeitig merkt. Größer = mehr Überblick, mehr Speicher, etwas langsamer."],["Auto-Swap","llama-swap lädt/entlädt Modelle automatisch — immer nur eins gleichzeitig. Du managst nichts."],["Agent","Ein Programm (OpenCode, Cline, Continue), das das Modell handeln lässt — Dateien lesen/ändern, Befehle ausführen, planen, im Loop bis die Aufgabe erledigt ist."],["MCP","Model Context Protocol — der Anschluss, über den ein Agent zusätzliche Werkzeuge bekommt (Web, Dateien, Datenbank). MCP = Werkzeuge."],["Skills","Verpackte Anleitung für wiederkehrende Aufgaben — der Agent liest sie und macht es jedes Mal wie du willst. Skills = Anleitungen."],["Rules / Commands","Die konkrete Form von Skills je Tool: AGENTS.md (OpenCode), .clinerules (Cline), Rules/Prompts (Continue)."]];var Nc=M(''),Ic=M('
'),Fc=M('
Guide für Einsteiger
Praktische Workflows + die Local-LLM-Welt in Klartext — ohne Vorwissen.
Tutorials & Workflows
Konzepte kurz erklärt
',1);function Kc(e){var t=Fc(),a=l(ft(t),4);Ue(a,21,()=>Bc,Ge,(i,o,d)=>{var c=N(()=>Qa(n(o),2));let u=()=>n(c)[0],f=()=>n(c)[1];var g=Nc();g.open=d===0;var $=s(g),h=s($),b=l($,2);Qe(b,f,!0),z(()=>m(h,u())),x(i,g)});var r=l(a,4);Ue(r,21,()=>qc,Ge,(i,o)=>{var d=N(()=>Qa(n(o),2));let c=()=>n(d)[0],u=()=>n(d)[1];var f=Ic(),g=s(f),$=s(g),h=l(g,2),b=s(h);z(()=>{m($,c()),m(b,u())}),x(i,f)}),x(e,t)}let Hi={};function Gc(){const e=document.getElementById("top-active-text");e&&(e.classList.add("swap-flash"),setTimeout(()=>e.classList.remove("swap-flash"),900))}function Vi(e){const t=document.getElementById("swdot"),a=document.getElementById("swlabel"),r=document.getElementById("top-active-text");if(!e)t&&(t.className="dot off"),a&&(a.textContent="Backend nicht erreichbar"),r&&(r.textContent="Backend offline"),Wi("Backend nicht erreichbar – läuft uvicorn?",!1);else{const i=(e.swap_url||"").replace(/^https?:\/\//,"");t&&(t.className="dot "+(e.swap_ok?"on":"off")),a&&(a.textContent=(e.swap_ok?"LLM-Engine: Online – ":"LLM-Engine: Offline – ")+i);const o=(e.models||[]).find(c=>c.state==="running");r&&(r.innerHTML=o?`Geladen: ${o.name}`:"Kein Modell im VRAM"),e.swap_ok?Wc():Wi(`LLM-Engine nicht erreichbar unter ${i} – läuft der llama-swap Dienst?`,!0);const d={};for(const c of e.models||[])d[c.name]=c.state;for(const[c,u]of Object.entries(d)){const f=Hi[c];if(f&&f!==u&&(u==="running"||u==="loading")){Gc();break}}Hi=d}Uc(e),dr.set(e)}function Uc(e){const t=document.getElementById("sec-chip"),a=document.getElementById("sec-chip-tx");!t||!a||(t.classList.toggle("ok",!!(e!=null&&e.secured)),a.textContent=e!=null&&e.secured?"Geschützt · Heimnetz":"Nur im Heimnetz (offen)")}function Hc(e){ra.set(e||[])}function Vc(e){yi.set(e)}function Wi(e,t){const a=document.getElementById("alert");a.className="alert"+(t?" warn":""),a.innerHTML=`${e}`,a.style.display="flex"}function Wc(){const e=document.getElementById("alert");e&&(e.style.display="none")}const jc=e=>{var t;return(t=document.querySelector(`.nav-item[data-view="${e}"]`))==null?void 0:t.click()};function Ur(e,t,a,r){const i=document.createElement("span");return i.className="upd-badge"+(t?" "+t:""),i.textContent=e,r&&(i.title=r),i.onclick=()=>jc(a),i}async function js(){try{const e=await fe("/api/updates"),t=document.getElementById("update-badges");if(!t)return;const a=e.apt_cache_age_h,r=a!=null&&a>24,i=a!=null?` · apt-Cache: vor ${a}h`:"";t.innerHTML="",t.appendChild(Ur(e.os>0?`OS: ${e.os} Updates`:r?"OS: ?":"OS: aktuell",e.os>0||r?"warn":"ok","server",e.os>0?`${e.os} Pakete${i}`:r?`apt-Cache veraltet (${a}h)`:`Keine Updates${i}`)),t.appendChild(Ur(e.engine>0?"Engine: Update":"Engine: aktuell",e.engine>0?"warn":"ok","server")),t.appendChild(Ur(e.models>0?`Modelle: ${e.models}`:"Modelle: aktuell",e.models>0?"warn":"ok","news"))}catch{}}async function zr(){try{Vi(await fe("/api/status"))}catch{Vi(null)}}async function Js(){try{Hc(await fe("/api/jobs"))}catch{}}function Qs(){const e=window.location.protocol==="https:"?"wss:":"ws:",t=new WebSocket(`${e}//${window.location.host}/api/system/stream`);t.onmessage=a=>{try{Vc(JSON.parse(a.data))}catch{}},t.onclose=()=>{setTimeout(Qs,3e3)},t.onerror=()=>t.close()}var Xi;((Xi=document.getElementById("logo"))==null?void 0:Xi.innerHTML)!==void 0&&(document.getElementById("logo").innerHTML=Pr.logo);document.querySelectorAll("[data-ic]").forEach(e=>{e.innerHTML=Pr[e.dataset.ic||""]||""});const Jc=[["overview",ed],["models",hd],["activity",Ed],["server",Rd],["cookbook",vc],["connect",Ec],["news",Rc],["guides",Kc]];for(const[e,t]of Jc){const a=document.querySelector(`.view[data-view="${e}"]`);a&&wo(t,{target:a})}Fo("overview");const Hr=document.getElementById("token");Hr&&(Hr.value=Ws(),Hr.addEventListener("change",e=>{Ro(e.target.value),zr()}));const Vr=document.getElementById("hf-token");Vr&&(Vr.value=Ja(),Vr.addEventListener("change",e=>qo(e.target.value)));const ji=document.getElementById("nav-settings"),Wr=document.getElementById("settings-modal"),Ji=document.getElementById("sm-close");ji&&Wr&&Ji&&(ji.addEventListener("click",()=>Wr.style.display="flex"),Ji.addEventListener("click",()=>Wr.style.display="none"));function Qc(){const e=document.getElementById("clock");e&&(e.textContent=new Date().toTimeString().slice(0,5))}document.addEventListener("mc:refresh",zr);zr();Js();Qs();setInterval(Qc,1e3);setInterval(zr,3e3);setInterval(Js,1500);js();setInterval(js,6e4);
diff --git a/static/index.html b/static/index.html
index 11ae286..a09b333 100644
--- a/static/index.html
+++ b/static/index.html
@@ -11,7 +11,6 @@
-
-
verbinde…
@@ -39,63 +37,21 @@
Nur im Heimnetz
-
-
-
-
-
-
-