feat(diagnostic): B1a-2 — Schalter fuer lokales LLM (Master/Nur-lokal/Tool-Umfang)
Neuer Settings-Block "Lokales LLM (schnelle Antworten)": - Master-Checkbox "Lokales LLM nutzen" (aus = alles Claude) - "Nur lokales LLM (kein Claude-Fallback)" — Eval-Haken - "Tool-Umfang: Abgespeckt / Voll" — Voll deaktiviert (gated) + ⓘ mit VRAM-Erklaerung - ⓘ-Haupt-Info erklaert Router/Latenz/Heim-Internet-Abhaengigkeit server.js: GET/POST /api/local-llm-config <-> /shared/config/local_llm.json (read/write, spiegelt runtime-config-Muster). Genau die Datei, die router.py im Brain live liest. Onchange-Autosave, Status-Zeile, Laden bei ws.onopen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -910,6 +910,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h2>Lokales LLM (schnelle Antworten) <button class="info-btn" onclick="showInfo('local-llm')" title="Wie funktioniert das?">ℹ</button></h2>
|
||||
<div class="card" style="max-width:500px;">
|
||||
<div style="font-size:11px;color:#8888AA;margin-bottom:10px;line-height:1.5;">
|
||||
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 <code>/shared/config/local_llm.json</code> —
|
||||
der Router im Brain liest sie live (kein Neustart noetig).
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:8px;margin-bottom:8px;font-size:12px;color:#E0E0F0;cursor:pointer;">
|
||||
<input type="checkbox" id="local-llm-enabled" onchange="saveLocalLlmConfig()" style="margin:0;">
|
||||
<span>Lokales LLM nutzen <span style="color:#8888AA;">(aus = alles ueber Claude)</span></span>
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:8px;margin-bottom:10px;font-size:12px;color:#E0E0F0;cursor:pointer;">
|
||||
<input type="checkbox" id="local-llm-onlylocal" onchange="saveLocalLlmConfig()" style="margin:0;">
|
||||
<span>Nur lokales LLM — kein Claude-Fallback <span style="color:#8888AA;">(zum Testen)</span></span>
|
||||
</label>
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
||||
<span style="font-size:12px;color:#8888AA;">Tool-Umfang:</span>
|
||||
<select id="local-llm-toolvariant" onchange="saveLocalLlmConfig()" style="background:#1E1E2E;border:1px solid #333;border-radius:4px;padding:6px 8px;color:#E0E0F0;font-family:inherit;font-size:12px;">
|
||||
<option value="slim">Abgespeckt (kuratierte Tools)</option>
|
||||
<option value="full" disabled>Voll (braucht mehr VRAM)</option>
|
||||
</select>
|
||||
<button class="info-btn" onclick="showInfo('local-llm-tools')" title="Voll: wie viel VRAM?">ℹ</button>
|
||||
</div>
|
||||
<div id="local-llm-status" style="font-size:10px;color:#6a6a88;margin-top:6px;min-height:12px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenClaw-Config-Sektion entfernt — aria-core ist raus. -->
|
||||
|
||||
</div><!-- /tab-settings -->
|
||||
@@ -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: `
|
||||
<p>Ein kleines, schnelles Modell (<strong>Qwen3 8B</strong>) laeuft auf deiner Gamebox-GPU und beantwortet <strong>einfache Plauder-Turns in <1 s</strong>. Alles Schwere, Technische oder Werkzeug-artige (Wetter, Timer, Musik, Bilder, Gedaechtnis, Code) reicht ein Router automatisch an <strong>Claude</strong> weiter.</p>
|
||||
<p><strong>Lokales LLM nutzen</strong> — Master-Schalter. Aus = alle Anfragen laufen wie bisher ueber Claude.</p>
|
||||
<p><strong>Nur lokales LLM</strong> — 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.</p>
|
||||
<p>Die Antwortzeit haengt an deinem Heim-Internet (Gamebox @home, ARIA @RZ). Ist die Gamebox aus/nicht erreichbar, faellt ARIA automatisch auf Claude zurueck.</p>
|
||||
`,
|
||||
},
|
||||
'local-llm-tools': {
|
||||
title: 'Tool-Umfang: Abgespeckt vs. Voll',
|
||||
html: `
|
||||
<p><strong>Abgespeckt</strong> — 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 <strong>1×RTX 3060 (12 GB)</strong>.</p>
|
||||
<p><strong>Voll</strong> — das lokale Modell soll ARIAs komplettes Arsenal kennen. Das sind ~15-20 K Tokens Tool-Schemas → passt <em>nicht</em> in ein 8-K-Kontextfenster. Groesseres Fenster kostet VRAM:</p>
|
||||
<ul>
|
||||
<li><strong>12 GB (1×3060):</strong> nur „Abgespeckt".</li>
|
||||
<li><strong>24 GB (2×3060, eine Box):</strong> Qwen mit grossem Kontext = volles Schema, oder ein groesseres Modell.</li>
|
||||
<li><strong>Cluster:</strong> weitere GPU-Hosts, jeder ein Modell-Server ueber RVS.</li>
|
||||
</ul>
|
||||
<p>Deshalb ist „Voll" hier deaktiviert, bis die Hardware (2. Karte) und die lokale Tool-Loop (B1b) da sind.</p>
|
||||
`,
|
||||
},
|
||||
'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.
|
||||
|
||||
@@ -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" });
|
||||
|
||||
Reference in New Issue
Block a user