diff --git a/src/sidepanel/app.ts b/src/sidepanel/app.ts index 9293229..413d1bc 100644 --- a/src/sidepanel/app.ts +++ b/src/sidepanel/app.ts @@ -73,6 +73,7 @@ const usageBarLabel = need('usageBarLabel'); const mcpStatus = need('mcpStatus'); const targetTabEl = need('targetTab'); const modeBadge = need('modeBadge'); +const headerModelSel = need('headerModel'); const modeOffBtn = need('modeOff'); const modeConfirmBtn = need('modeConfirm'); const settingsPanel = need('settingsPanel'); @@ -386,6 +387,7 @@ function applyConfigToFields(cfg: ExtensionConfig): void { setFieldValue(systemPromptInput, cfg.systemPrompt); setFieldValue(nexusApiKeyInput, cfg.nexusApiKey); applyNexusUi(cfg); + refreshHeaderModel(cfg); if (document.activeElement !== memoryEnabledInput) memoryEnabledInput.checked = cfg.memoryEnabled; setFieldValue(memoryUrlInput, cfg.memoryUrl); @@ -453,6 +455,49 @@ function applyNexusUi(cfg: ExtensionConfig): void { : 'GPT läuft über den Azure-OpenAI-Pfad und streamt.'; } +/** + * Modellwahl in der Kopfzeile. + * + * Das Modell ist der Schalter, den man täglich anfasst — er lag bisher am Ende + * einer 18-zeiligen Einstellungsliste. Die Liste hängt vom Antrieb ab: im + * direct-Modus mit Anbieter 'nexus' die geprüften Gateway-Modelle, im + * Broker-Modus die vom Broker gemeldeten, sonst nur der eingetragene Freitext. + */ +function refreshHeaderModel(cfg: ExtensionConfig): void { + const options: { id: string; label: string }[] = []; + + if (cfg.driveMode === 'direct') { + if (cfg.provider === 'nexus') { + for (const m of NEXUS_MODELS) options.push({ id: m.id, label: m.label }); + } + if (cfg.model && !options.some(o => o.id === cfg.model)) { + options.unshift({ id: cfg.model, label: cfg.model }); + } + } else { + for (const m of nexusModels) options.push({ id: m.id, label: m.label ?? m.id }); + } + + // Nichts zu wählen: den Schalter ausblenden statt eine leere Liste zeigen. + headerModelSel.hidden = options.length < 1; + if (headerModelSel.hidden) return; + + const current = cfg.driveMode === 'direct' ? cfg.model : (nexusModelSel.value || cfg.model); + const same = headerModelSel.options.length === options.length + && options.every((o, i) => headerModelSel.options[i]?.value === o.id); + if (!same) { + headerModelSel.textContent = ''; + for (const o of options) { + const el = document.createElement('option'); + el.value = o.id; + el.textContent = o.label; + headerModelSel.append(el); + } + } + if (document.activeElement !== headerModelSel && headerModelSel.value !== current) { + headerModelSel.value = current; + } +} + function applyRiskMode(mode: RiskMode): void { const asking = mode === 'confirm'; modeOffBtn.setAttribute('aria-pressed', String(!asking)); @@ -783,6 +828,7 @@ async function loadNexusModels(silent = true): Promise { const data = await res.json(); nexusModels = Array.isArray(data.models) ? data.models : []; renderNexusModels(); + refreshHeaderModel(config); } catch (e: unknown) { nexusModels = []; renderNexusModels(); @@ -869,6 +915,19 @@ function syncNexusModelGroup(): void { function wireNexusModels(): void { nexusModelSel.addEventListener('change', () => setNexusModel(nexusModelSel.value)); + + headerModelSel.addEventListener('change', () => { + const id = headerModelSel.value; + if (!id) return; + if (config.driveMode === 'direct') { + patchConfigValue({ model: id }); + setFieldValue(modelInput, id); + applyNexusUi(config); + } else { + // Im Broker-Modus entscheidet der Broker — dasselbe wie im Einstellungs-Dropdown. + setNexusModel(id); + } + }); nexusModelRefresh.addEventListener('click', () => loadNexusModels(false)); nexusModelProbe.addEventListener('click', probeNexusModels); } diff --git a/src/sidepanel/index.html b/src/sidepanel/index.html index 6974127..17f70d6 100644 --- a/src/sidepanel/index.html +++ b/src/sidepanel/index.html @@ -234,6 +234,54 @@ .settings[open] > summary::before { content: '▾ '; } .settings > summary:hover { color: var(--text); } .settings-body { padding: 2px 10px 10px; } + + /* Klappbare Abschnitte: 18 Zeilen auf einer Ebene sind keine Bedienung, + sondern eine Liste. Was man taeglich anfasst, steht offen; der Rest + liegt zugeklappt darunter. */ + .sgroup { border-top: 1px solid var(--border); margin-top: 6px; } + .sgroup > summary { + padding: 6px 0 5px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.5px; + text-transform: uppercase; + color: var(--text-mute); + cursor: pointer; + user-select: none; + list-style: none; + outline: none; + display: flex; + align-items: center; + gap: 5px; + } + .sgroup > summary::-webkit-details-marker { display: none; } + .sgroup > summary::before { + content: ''; + width: 0; height: 0; + border-left: 4px solid currentColor; + border-top: 3px solid transparent; + border-bottom: 3px solid transparent; + transition: transform 0.15s; + flex: 0 0 auto; + } + .sgroup[open] > summary::before { transform: rotate(90deg); } + .sgroup > summary:hover { color: var(--text); } + .sgroup-body { padding: 1px 0 7px; } + + /* Modellwahl in der Kopfzeile — der am haeufigsten benutzte Schalter + gehoert nicht ans Ende einer Einstellungsliste. */ + #headerModel { + font-size: 10px; + max-width: 116px; + padding: 2px 4px; + background: var(--surface2); + color: var(--text-dim); + border: 1px solid var(--border-lt); + border-radius: 4px; + font-family: inherit; + cursor: pointer; + } + #headerModel:hover { color: var(--text); border-color: var(--accent); } .row { display: flex; align-items: center; @@ -896,6 +944,7 @@ + getrennt @@ -980,6 +1029,8 @@
Einstellungen
+ +
Verbindung & Antrieb
@@ -1013,6 +1064,9 @@
+
+ +
Verhalten
+
+ +
Anzeige & Sicherheit
@@ -1085,7 +1144,9 @@
-
Memory (memory.cnull.net)
+
+ +
Memory (memory.cnull.net)
@@ -1128,6 +1189,8 @@
Änderungen werden sofort gespeichert.
+
+