diff --git a/diagnostic/index.html b/diagnostic/index.html index 4287dd3..75ae9e9 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -910,6 +910,35 @@ +
+

Lokales LLM (schnelle Antworten)

+
+
+ Ein schnelles lokales Modell (Qwen3 auf der Gamebox) beantwortet einfache + Plauder-Turns in unter 1 Sekunde; alles Schwere/Werkzeug-artige geht + weiter an Claude. Schreibt /shared/config/local_llm.json — + der Router im Brain liest sie live (kein Neustart noetig). +
+ + +
+ Tool-Umfang: + + +
+
+
+
+ @@ -1521,6 +1550,8 @@ try { loadBrainStatus(); } catch {} // Sprachmodell-Dropdown befuellen (kuratierte Tier-Liste vom Proxy) try { loadModelList(); } catch {} + // Lokales-LLM-Schalter aus /shared/config/local_llm.json laden + try { loadLocalLlmConfig(); } catch {} }; // Brain-Status periodisch refreshen damit die Card live bleibt @@ -5856,6 +5887,28 @@ // Vor-definierte Info-Blocks const INFO_TEXTS = { + 'local-llm': { + title: 'Lokales LLM — schnelle Antworten', + html: ` +

Ein kleines, schnelles Modell (Qwen3 8B) laeuft auf deiner Gamebox-GPU und beantwortet einfache Plauder-Turns in <1 s. Alles Schwere, Technische oder Werkzeug-artige (Wetter, Timer, Musik, Bilder, Gedaechtnis, Code) reicht ein Router automatisch an Claude weiter.

+

Lokales LLM nutzen — Master-Schalter. Aus = alle Anfragen laufen wie bisher ueber Claude.

+

Nur lokales LLM — erzwingt lokal, KEIN Claude-Fallback. Zum Ausprobieren, wie stark das lokale Modell allein ist. Achtung: Werkzeug-Turns (Wetter/Timer/…) funktionieren dann nicht — das lokale Tier hat keine Tools.

+

Die Antwortzeit haengt an deinem Heim-Internet (Gamebox @home, ARIA @RZ). Ist die Gamebox aus/nicht erreichbar, faellt ARIA automatisch auf Claude zurueck.

+ `, + }, + 'local-llm-tools': { + title: 'Tool-Umfang: Abgespeckt vs. Voll', + html: ` +

Abgespeckt — das lokale Modell bekommt eine kleine, kuratierte Tool-Auswahl (Wetter, Zeit, Gedaechtnis-Suche, Timer, Spotify, Licht). Der Rest laeuft ueber Claude. Passt in den VRAM einer 1×RTX 3060 (12 GB).

+

Voll — das lokale Modell soll ARIAs komplettes Arsenal kennen. Das sind ~15-20 K Tokens Tool-Schemas → passt nicht in ein 8-K-Kontextfenster. Groesseres Fenster kostet VRAM:

+ +

Deshalb ist „Voll" hier deaktiviert, bis die Hardware (2. Karte) und die lokale Tool-Loop (B1b) da sind.

+ `, + }, 'brain-status': { title: 'Gehirn — Status', html: ` @@ -6147,6 +6200,52 @@ if (st) { st.textContent = 'Gesetzt (Freitext) — aria-brain neu starten, damit es greift.'; st.style.color = '#FFD60A'; } } + // ── Einstellungen: Lokales LLM (Plan B) ───────────────── + function setLocalLlmStatus(c) { + const el = document.getElementById('local-llm-status'); + if (!el) return; + if (!c || !c.enabled) { + el.textContent = 'Aus — alle Anfragen laufen ueber Claude.'; + el.style.color = '#6a6a88'; + return; + } + if (c.localOnly) { + el.textContent = 'NUR lokal — kein Claude-Fallback (Testmodus). Werkzeug-Turns funktionieren hier nicht.'; + el.style.color = '#FFD60A'; + } else { + el.textContent = 'Aktiv — einfache Turns lokal (<1s), Rest → Claude.'; + el.style.color = '#4ADE80'; + } + } + async function loadLocalLlmConfig() { + try { + const r = await fetch('/api/local-llm-config'); + const c = await r.json(); + const en = document.getElementById('local-llm-enabled'); + const ol = document.getElementById('local-llm-onlylocal'); + const tv = document.getElementById('local-llm-toolvariant'); + if (en) en.checked = !!c.enabled; + if (ol) ol.checked = !!c.localOnly; + if (tv) tv.value = (c.toolVariant === 'full') ? 'full' : 'slim'; + setLocalLlmStatus(c); + } catch (e) { /* still */ } + } + async function saveLocalLlmConfig() { + const body = { + enabled: document.getElementById('local-llm-enabled').checked, + localOnly: document.getElementById('local-llm-onlylocal').checked, + toolVariant: document.getElementById('local-llm-toolvariant').value, + }; + try { + const r = await fetch('/api/local-llm-config', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + const j = await r.json(); + if (j.ok) setLocalLlmStatus(j.config); + } catch (e) { /* still */ } + } + // ── Einstellungen: OpenClaw Config ────────────────────── // loadOpenClawConfig entfernt — aria-core ist raus. diff --git a/diagnostic/server.js b/diagnostic/server.js index fb1fbd7..108e5dd 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -297,6 +297,35 @@ function writeRuntimeConfig(patch) { } // Atomic write: temp-file + rename, laute Logs bei Fehler. + +// ── Local-LLM-Config: /shared/config/local_llm.json ───────────────── +// Der Router im Brain (router.py) liest diese Datei pro Request. Wir schreiben +// sie hier aus den Diagnostic-Schaltern. Default = alles aus (nur Claude). +const LOCAL_LLM_CONFIG_FILE = "/shared/config/local_llm.json"; +function readLocalLlmConfig() { + try { + const p = JSON.parse(fs.readFileSync(LOCAL_LLM_CONFIG_FILE, "utf-8")); + return { + enabled: !!p.enabled, + localOnly: !!p.localOnly, + toolVariant: p.toolVariant === "full" ? "full" : "slim", + }; + } catch { + return { enabled: false, localOnly: false, toolVariant: "slim" }; + } +} +function writeLocalLlmConfig(patch) { + const cur = readLocalLlmConfig(); + if (typeof patch.enabled === "boolean") cur.enabled = patch.enabled; + if (typeof patch.localOnly === "boolean") cur.localOnly = patch.localOnly; + if (patch.toolVariant === "slim" || patch.toolVariant === "full") cur.toolVariant = patch.toolVariant; + fs.mkdirSync("/shared/config", { recursive: true }); + const tmp = LOCAL_LLM_CONFIG_FILE + ".tmp"; + fs.writeFileSync(tmp, JSON.stringify(cur, null, 2)); + fs.renameSync(tmp, LOCAL_LLM_CONFIG_FILE); + return cur; +} + // ── File-Project-Manifest ─────────────────────────────────────────── // Jeder Eintrag map[absoluter_pfad] = project_id (leer = Hauptchat). // Wird vom files-list-Endpoint + files-set-project gepflegt. @@ -1573,6 +1602,24 @@ const server = http.createServer((req, res) => { } }); return; + } else if (req.url === "/api/local-llm-config" && req.method === "GET") { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify(readLocalLlmConfig())); + } else if (req.url === "/api/local-llm-config" && req.method === "POST") { + let body = ""; + req.on("data", chunk => { body += chunk; if (body.length > 8192) req.destroy(); }); + req.on("end", () => { + try { + const cfg = writeLocalLlmConfig(JSON.parse(body)); + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ ok: true, config: cfg })); + log("info", "server", `Local-LLM-Config: enabled=${cfg.enabled} localOnly=${cfg.localOnly} tools=${cfg.toolVariant}`); + } catch (err) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ ok: false, error: err.message })); + } + }); + return; } else if (req.url === "/api/onboarding") { // RVS-Credentials fuer QR-Code App-Onboarding res.writeHead(200, { "Content-Type": "application/json" });