feat(diagnostic): Sprachmodell per Tier-Dropdown waehlen (Opus/Sonnet/Haiku)

ARIA laeuft ueber das Claude-Max-Abo via CLI — waehlbar ist der Tier, nicht
eine feste Modellversion (die CLI nimmt automatisch das aktuelle Modell des
Tiers). Kein API-Key → keine echte Anthropic-Models-API; daher kuratierte Liste.

- proxy routes.js handleModels: kuratierte /v1/models mit tier/display_name/
  description (ids bleiben MODEL_MAP-kompatibel: claude-sonnet-4/opus-4/haiku-4).
- diagnostic server.js: neuer GET /api/models-list — holt die Liste vom Proxy
  (Frontend erreicht den Proxy nicht direkt).
- Frontend: Dropdown statt Freitext + „↻ Aktualisieren"-Button; markiert das
  aktive brainModel (get_model), zeigt Beschreibung, „Setzen" schreibt brainModel
  und weist auf Brain-Neustart hin. Freitext-Override bleibt unter „Erweitert".
  Liste wird bei WS-Connect automatisch geladen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 22:13:17 +02:00
co-authored by Claude Opus 4.8
parent 1b48307907
commit 44f4067834
3 changed files with 118 additions and 18 deletions
+78 -6
View File
@@ -878,20 +878,32 @@
<h2>Sprachmodell (Brain)</h2>
<div class="card" style="max-width:500px;">
<div style="font-size:11px;color:#8888AA;margin-bottom:10px;line-height:1.5;">
Welches Claude-Model nutzt das Brain pro Anfrage. Wert wird in
Welches Claude-Model das Brain pro Anfrage nutzt. Wert wird in
<code>/shared/config/runtime.json</code> als <code>brainModel</code> persistiert.
Bei Aenderung: <strong>aria-brain restarten</strong> (Reparatur-Section oben), damit's greift.
<br><br>
Verfuegbar via Proxy: <code>claude-sonnet-4</code> (Default — schnell, gut),
<code>claude-opus-4</code> (langsam, smarter), <code>claude-haiku-4-5</code> (sehr schnell, kleiner Kontext).
ARIA laeuft ueber dein Claude-Max-Abo (CLI) — waehlbar ist der <strong>Tier</strong>
(Opus/Sonnet/Haiku), nicht eine feste Version. Die CLI nimmt automatisch das
jeweils aktuelle Modell des Tiers.
</div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
<span style="font-size:12px;color:#8888AA;white-space:nowrap;">Aktives Model:</span>
<input type="text" id="setting-model" placeholder="claude-sonnet-4" style="flex:1;background:#1E1E2E;border:1px solid #333;border-radius:4px;padding:6px 8px;color:#E0E0F0;font-family:inherit;font-size:12px;">
<button class="btn secondary" onclick="loadModel()" style="padding:4px 8px;font-size:10px;">Laden</button>
<select id="setting-model-select" onchange="onModelSelectChange()" style="flex:1;background:#1E1E2E;border:1px solid #333;border-radius:4px;padding:6px 8px;color:#E0E0F0;font-family:inherit;font-size:12px;">
<option value="">(lade Liste…)</option>
</select>
<button class="btn secondary" onclick="loadModelList()" title="Liste vom Proxy neu holen" style="padding:4px 8px;font-size:10px;">↻ Aktualisieren</button>
<button class="btn" onclick="saveModel()" style="padding:4px 8px;font-size:10px;">Setzen</button>
</div>
<div id="model-status" style="font-size:10px;color:#8888AA;"></div>
<div id="model-desc" style="font-size:10px;color:#6a6a88;margin-bottom:6px;min-height:12px;"></div>
<div id="model-status" style="font-size:10px;color:#8888AA;margin-bottom:8px;"></div>
<details style="font-size:11px;color:#8888AA;">
<summary style="cursor:pointer;">Erweitert: freie Model-ID</summary>
<div style="display:flex;align-items:center;gap:8px;margin-top:6px;">
<input type="text" id="setting-model" placeholder="z.B. claude-opus-4" style="flex:1;background:#1E1E2E;border:1px solid #333;border-radius:4px;padding:6px 8px;color:#E0E0F0;font-family:inherit;font-size:12px;">
<button class="btn secondary" onclick="loadModel()" style="padding:4px 8px;font-size:10px;">Laden</button>
<button class="btn" onclick="saveModelFreeText()" style="padding:4px 8px;font-size:10px;">Setzen</button>
</div>
</details>
</div>
</div>
@@ -1504,6 +1516,8 @@
send({ action: 'load_chat_history' });
// Brain-Card initial laden (sonst zeigt sie "Lade...")
try { loadBrainStatus(); } catch {}
// Sprachmodell-Dropdown befuellen (kuratierte Tier-Liste vom Proxy)
try { loadModelList(); } catch {}
};
// Brain-Status periodisch refreshen damit die Card live bleibt
@@ -1966,8 +1980,14 @@
// session_restarted / openclaw_config WS-Events entfernt — aria-core ist raus.
if (msg.type === 'model_info') {
const el = document.getElementById('setting-model');
const sel = document.getElementById('setting-model-select');
const st = document.getElementById('model-status');
if (el && msg.model) el.value = msg.model;
// Dropdown auf das aktuelle Model stellen (falls in der Liste).
if (sel && msg.model) {
const has = Array.from(sel.options).some(o => o.value === msg.model);
if (has) { sel.value = msg.model; onModelSelectChange(); }
}
if (st) {
st.textContent = msg.info || msg.error || '';
st.style.color = msg.error ? '#FF6B6B' : '#34C759';
@@ -6062,14 +6082,66 @@
// ── Einstellungen: Model ────────────────────────────────
let _modelListCache = [];
function loadModel() {
send({ action: 'get_model' });
}
// Kuratierte Tier-Liste vom Proxy holen (via Diagnostic-Server) und ins
// Dropdown fuellen. Danach get_model, damit das aktive Model markiert wird.
async function loadModelList() {
const sel = document.getElementById('setting-model-select');
const st = document.getElementById('model-status');
if (!sel) return;
try {
const r = await fetch('/api/models-list');
const d = await r.json();
if (!d.ok) throw new Error(d.error || 'Fehler');
_modelListCache = d.models || [];
sel.innerHTML = '';
for (const m of _modelListCache) {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.displayName || m.id;
sel.appendChild(opt);
}
if (!_modelListCache.length) {
const opt = document.createElement('option');
opt.value = ''; opt.textContent = '(keine Modelle vom Proxy)';
sel.appendChild(opt);
}
onModelSelectChange();
// Aktuelles Model vom Brain holen → markiert die richtige Option
loadModel();
} catch (e) {
if (st) { st.textContent = 'Model-Liste laden fehlgeschlagen: ' + e.message; st.style.color = '#FF6B6B'; }
}
}
function onModelSelectChange() {
const sel = document.getElementById('setting-model-select');
const desc = document.getElementById('model-desc');
if (!sel || !desc) return;
const m = _modelListCache.find(x => x.id === sel.value);
desc.textContent = m && m.description ? m.description : '';
}
function saveModel() {
const sel = document.getElementById('setting-model-select');
const model = sel && sel.value ? sel.value : '';
if (!model) return;
send({ action: 'set_model', model });
const st = document.getElementById('model-status');
if (st) { st.textContent = 'Gesetzt — aria-brain neu starten (Reparatur oben), damit es greift.'; st.style.color = '#FFD60A'; }
}
function saveModelFreeText() {
const model = document.getElementById('setting-model').value.trim();
if (!model) return;
send({ action: 'set_model', model });
const st = document.getElementById('model-status');
if (st) { st.textContent = 'Gesetzt (Freitext) — aria-brain neu starten, damit es greift.'; st.style.color = '#FFD60A'; }
}
// ── Einstellungen: OpenClaw Config ──────────────────────
+22
View File
@@ -1625,6 +1625,28 @@ const server = http.createServer((req, res) => {
res.end(JSON.stringify({ ok: false, error: err.message }));
}
return;
} else if (req.url === "/api/models-list" && req.method === "GET") {
// Kuratierte Model-Liste vom Proxy (/v1/models) — Tier-Auswahl fuers
// Sprachmodell-Dropdown. ARIA laeuft ueber das Max-Abo/CLI, waehlbar ist
// der Tier (opus/sonnet/haiku), keine feste Version.
(async () => {
try {
const r = await fetch(`${PROXY_URL}/v1/models`);
const d = await r.json();
const models = (d.data || []).map(m => ({
id: m.id,
tier: m.tier || m.id,
displayName: m.display_name || m.id,
description: m.description || "",
}));
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, models }));
} catch (err) {
res.writeHead(502, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: false, error: String(err && err.message || err) }));
}
})();
return;
} else if (req.url === "/api/files-list" && req.method === "GET") {
// Liste alle Dateien in /shared/uploads/ — die kommen entweder vom User
// (Upload aus App/Diagnostic) oder von ARIA (aria_<name>.<ext> Pattern).