feat(models): Test-Knopf — zeigt, welches Modell wirklich geantwortet hat

Bisher liess sich nicht nachsehen, ob eine Modell-Einstellung ueberhaupt
greift. Der Proxy hat die Information sogar aktiv weggeworfen:
normalizeModelName() nahm den echten Namen aus modelUsage (z.B.
"claude-opus-5-5") und schrieb ihn auf "claude-opus-4" um. Das `model`-Feld
der Antwort log also — und damit war jede Ueberpruefung unmoeglich.

- normalizeModelName gibt den echten Namen durch. Im OpenAI-Protokoll soll
  `model` das Modell nennen, das geantwortet HAT; ein erfundener Name ist
  schlechter als keiner.
- Zusatzfelder aria_model / aria_error / aria_context_window / aria_max_output
  aus modelUsage.canonicalModel. Noetig, weil die CLI bei einem unbekannten
  Modell gar nicht bis zum Call kommt und ihre Begruendung einfach in den
  Antworttext legt — ohne Flag waere ein Fehlschlag nicht von einer Antwort
  zu unterscheiden.
- Knopf "🔬 Testen" neben Dropdown und Freitextfeld. Er schickt eine echte
  Mini-Anfrage durch den GANZEN Weg (Diagnostic → Proxy → extractModel →
  claude CLI → Anthropic).

Ausdruecklich NICHT gemacht: das Modell nach seiner eigenen Version fragen.
Darauf ist kein Verlass — Modelle kennen ihren Versionsstring oft nicht und
raten. Und den Dropdown-Wert zurueckzugeben waere sinnlos. Die Wahrheit kommt
aus modelUsage der CLI.

Vier unterscheidbare Ergebnisse:
  Tier-Alias      gruen  — zeigt die aufgeloeste Version ("opus" → Opus 5.5)
  exakte Version  gruen  — angefragt == geantwortet
  nicht erkannt   GELB   — der Proxy hat still "opus" eingesetzt; warnt
                           ausdruecklich davor, die ID so zu setzen
  ungueltig       ROT    — CLI-Begruendung im Klartext

Der gelbe Fall war wichtig: ein Name, der nicht wie ein Claude-Modell aussieht,
faellt auf "opus" zurueck und haette sonst als Erfolg gegolten — man setzt dann
eine ID, die nie benutzt wird, und merkt es nie.

Geprueft gegen ECHTE CLI-Ausgaben aus dem Proxy-Container (9 Faelle: Alias,
exakte Version, Tippfehler) plus 7 Faelle fuer die Anzeige-Logik. Nebenbefund:
`--model opus` loest derzeit auf claude-opus-5-5 auf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-03 10:54:22 +02:00
co-authored by Claude Opus 5
parent 09d06011d5
commit 752e2abc5f
3 changed files with 184 additions and 9 deletions
+78 -1
View File
@@ -632,17 +632,24 @@
<option value="">(lade Liste…)</option>
</select>
<button class="btn secondary" onclick="loadModelList(true)" title="Modell-Liste frisch von Anthropic holen (Modell-Katalog)" style="padding:4px 8px;font-size:10px;">↻ Aktualisieren</button>
<button class="btn secondary" onclick="testModel()" id="btn-test-model" title="Schickt eine echte Mini-Anfrage und zeigt, welches Modell geantwortet hat" style="padding:4px 8px;font-size:10px;">🔬 Testen</button>
<button class="btn" onclick="saveModel()" style="padding:4px 8px;font-size:10px;">Setzen</button>
</div>
<div id="model-desc" style="font-size:10px;color:#6a6a88;margin-bottom:6px;min-height:12px;"></div>
<div id="model-test-result" style="font-size:11px;color:#8888AA;margin-bottom:8px;padding:6px 8px;background:#12121E;border:1px solid #2A2A3E;border-radius:6px;display:none;white-space:pre-wrap;"></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;">
<input type="text" id="setting-model" placeholder="z.B. claude-opus-5-5" 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 secondary" onclick="testModel(true)" style="padding:4px 8px;font-size:10px;" title="Diese ID testen, bevor du sie setzt">🔬 Testen</button>
<button class="btn" onclick="saveModelFreeText()" style="padding:4px 8px;font-size:10px;">Setzen</button>
</div>
<div style="color:#555570;font-size:10px;margin-top:6px;">
Erst testen, dann setzen — ein Tippfehler im Namen fällt sonst erst
auf, wenn ARIA nicht mehr antwortet.
</div>
</details>
</div>
</div>
@@ -2716,6 +2723,49 @@
return;
}
if (msg.type === 'model_test_result') {
const box = document.getElementById('model-test-result');
const btn = document.getElementById('btn-test-model');
if (btn) { btn.disabled = false; btn.textContent = '🔬 Testen'; }
if (box) {
box.style.display = 'block';
const secs = msg.tookMs ? ' · ' + (msg.tookMs / 1000).toFixed(1) + 's' : '';
if (msg.error) {
box.textContent = '❌ "' + msg.requested + '" hat nicht funktioniert'
+ secs + '\n' + msg.error;
box.style.color = '#FF6B6B';
} else {
// Zeigt ausdruecklich BEIDES: was angefragt wurde und was
// tatsaechlich lief.
const TIERS = ['opus', 'sonnet', 'haiku', 'fable'];
const differs = msg.actual !== msg.requested;
const isTier = TIERS.includes(msg.requested);
const win = msg.contextWindow
? '\nKontext: ' + Math.round(msg.contextWindow / 1000) + 'k Token'
+ (msg.maxOutput ? ', max. Ausgabe ' + Math.round(msg.maxOutput / 1000) + 'k' : '')
: '';
const tail = win + (msg.reply ? '\nAntwort: ' + msg.reply : '');
if (differs && !isTier) {
// Ein nicht erkannter Name wird vom Proxy durch "opus" ersetzt.
// Das darf NICHT wie ein Erfolg aussehen — sonst setzt man eine
// Modell-ID, die nie benutzt wird, und merkt es nie.
box.textContent = '⚠ "' + msg.requested + '" wurde nicht erkannt' + secs
+ '\nStattdessen geantwortet hat: ' + msg.actual
+ '\nDiese ID so NICHT setzen — tippe den vollen Namen (z.B. '
+ 'claude-opus-5-5) oder nimm einen Eintrag aus der Liste.' + tail;
box.style.color = '#FFD60A';
} else {
box.textContent = '✅ Geantwortet hat: ' + msg.actual + secs
+ (differs ? '\n(angefragt war der Tier "' + msg.requested
+ '" — aufgelöst auf diese Version)' : '')
+ tail;
box.style.color = '#3FFF9F';
}
}
}
return;
}
if (msg.type === 'attachment_limit_info') {
const inp = document.getElementById('attachment-limit-input');
const st = document.getElementById('attachment-limit-status');
@@ -7969,6 +8019,33 @@
}
}
/**
* Testet ein Modell mit einer echten Mini-Anfrage.
* fromFreeText=true nimmt die handgetippte ID, sonst das Dropdown.
*
* Was hier NICHT passiert: den Dropdown-Wert zurueckgeben. Angezeigt wird,
* was die CLI als tatsaechlich benutztes Modell meldet — bei einem Alias
* also die aufgeloeste Version, bei einem Tippfehler der Fehler.
*/
function testModel(fromFreeText) {
const model = fromFreeText
? (document.getElementById('setting-model')?.value || '').trim()
: (document.getElementById('setting-model-select')?.value || '');
const box = document.getElementById('model-test-result');
if (!box) return;
box.style.display = 'block';
if (!model) {
box.textContent = 'Kein Modell ausgewählt.';
box.style.color = '#FF6B6B';
return;
}
box.textContent = `Teste "${model}" — schicke eine echte Anfrage …`;
box.style.color = '#8888AA';
const btn = document.getElementById('btn-test-model');
if (btn && !fromFreeText) { btn.disabled = true; btn.textContent = '🔬 läuft …'; }
send({ action: 'test_model', model });
}
function onModelSelectChange() {
const sel = document.getElementById('setting-model-select');
const desc = document.getElementById('model-desc');