From e8971fbf0c8e373bb31b4c93dc22358108044bda Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Tue, 23 Jun 2026 18:19:01 +0200 Subject: [PATCH] =?UTF-8?q?fix(v8.1):=20Kritische=20Bugs=20behoben=20?= =?UTF-8?q?=E2=80=94=20system=5Fstatus,=20model-routing,=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0: - hermes_agent: system_status Key-Mismatch behoben (cpu_pct→cpu.percent, ram_used_gb→ram.used/1024³, gtt analog) — zeigt jetzt echte Werte statt '?' - guides-data: & in 4 Titel-Strings → & (Svelte text-node rendert keine HTML-Entities) P1: - hermes_agent: choose_model() um recherchier/such/vergleich/etc. erweitert; kein Downgrade wenn komplexes Modell bereits geladen - routers/hermes: asyncio.get_event_loop() → get_running_loop() (Whisper+TTS) - maintenance: llama-swap Update akzeptiert optionales sudo-PW via stdin; import os hinzugefügt (fix NameError in updates()); UpdateReq-Model - ServerPanel: Engine-Update fragt jetzt nach sudo-PW (analog OS-Update) - HermesPanel: Chat-History in sessionStorage (überlebt Reload); Stopp-Button während Hermes antwortet; Verlauf-löschen-Button - ConnectPanel: opencode.json → opencode.jsonc; provider → providers; LAN-IP Override (localStorage) für NPM-Proxy-Setups Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/panels/ConnectPanel.svelte | 35 +++++++-- frontend/src/panels/HermesPanel.svelte | 65 ++++++++++++--- frontend/src/panels/ServerPanel.svelte | 4 +- frontend/src/panels/guides-data.ts | 8 +- hermes_agent.py | 58 +++++++++++--- routers/hermes.py | 4 +- routers/maintenance.py | 19 ++++- static/dist/main.js | 100 ++++++++++++------------ 8 files changed, 204 insertions(+), 89 deletions(-) diff --git a/frontend/src/panels/ConnectPanel.svelte b/frontend/src/panels/ConnectPanel.svelte index 9c5468d..57ddf52 100644 --- a/frontend/src/panels/ConnectPanel.svelte +++ b/frontend/src/panels/ConnectPanel.svelte @@ -6,17 +6,29 @@ const s = $derived(statusStore.value) const allModels = $derived(((s?.models || []) as any[]).filter((m: any) => !m.incomplete)) - const url = $derived(baseUrl()) let selectedTool = $state('zed') let selectedModelIds = $state([]) let testResult = $state('') let testOk = $state(false) let selectedMemTool = $state('cline') - const memBlock = $derived(memMcpBlock(selectedMemTool)) + let ipOverride = $state(localStorage.getItem('mc_ip_override') || '') - function baseUrl() { return `http://${location.hostname}:8080/v1` } function isLanIp(h: string) { return /^\d{1,3}(\.\d{1,3}){3}$/.test(h) } function behindProxy() { return location.protocol === 'https:' || !isLanIp(location.hostname) } + function baseUrl() { + const host = ipOverride.trim() || location.hostname + return `http://${host}:8080/v1` + } + + const url = $derived(baseUrl()) + + function saveIpOverride() { + const v = ipOverride.trim() + if (v) localStorage.setItem('mc_ip_override', v) + else localStorage.removeItem('mc_ip_override') + } + + const memBlock = $derived(memMcpBlock(selectedMemTool)) const activeIds = $derived( selectedModelIds.length ? selectedModelIds : allModels.map((m: any) => m.name) @@ -42,7 +54,7 @@ const ctx = m.meta?.ctx || 131072 return ` "${m.name}": { "name": "${m.name}", "contextWindowSize": ${ctx} }` }).join(',\n') - return `"provider": {\n "llama-swap": {\n "npm": "@ai-sdk/openai-compatible",\n "name": "Bosgame (llama-swap)",\n "options": {\n "baseURL": "${url}",\n "apiKey": "local"\n },\n "models": {\n${modelMap}\n }\n }\n}` + return `"providers": {\n "llama-swap": {\n "npm": "@ai-sdk/openai-compatible",\n "name": "Bosgame (llama-swap)",\n "options": {\n "baseURL": "${url}",\n "apiKey": "local"\n },\n "models": {\n${modelMap}\n }\n }\n}` } function continueBlock(url: string, ids: string[]) { @@ -97,10 +109,16 @@

1 · Verbindung prüfen

Stelle sicher, dass die LLM-Engine erreichbar ist, bevor du Tools konfigurierst.
-
+
+
+ +
{#if behindProxy()}
@@ -254,14 +272,15 @@
Danach im Agent-Panel das Modell bosgame / {firstModel} auswählen.
{:else if selectedTool === 'opencode'} {@const block = openCodeBlock(url, activeIds)} -

Datei anlegen oder öffnen:

-

Windows: %USERPROFILE%\.config\opencode\opencode.json

-
Nur den "provider"-Block einfügen. Falls bereits ein "llama-swap"-Eintrag vorhanden: vorher löschen, sonst erscheinen Modelle doppelt.
+

Datei anlegen oder öffnen (OpenCode Desktop nutzt .jsonc — JSON mit Kommentaren):

+

Windows: %USERPROFILE%\.config\opencode\opencode.jsonc

+
Den "providers"-Block in die bestehende opencode.jsonc einfügen (nicht die ganze Datei ersetzen). Falls bereits ein "llama-swap"-Eintrag existiert: vorher löschen.
{block}

Danach in OpenCode /modelsllama-swap/{firstModel} wählen.

+
OpenCode Desktop: opencode.ai
{:else if selectedTool === 'cline'}

In Cline/Cursor den Provider „OpenAI Compatible" wählen:

diff --git a/frontend/src/panels/HermesPanel.svelte b/frontend/src/panels/HermesPanel.svelte index 433fd67..5501a65 100644 --- a/frontend/src/panels/HermesPanel.svelte +++ b/frontend/src/panels/HermesPanel.svelte @@ -6,10 +6,15 @@ type MsgRole = 'user' | 'hermes' | 'info' | 'tool' | 'error' type Msg = { role: MsgRole; content: string; tool?: string; args?: any } + const SESSION_KEY = 'hermes_chat_history' + // ---- State ---- - let messages = $state([]) + let messages = $state((() => { + try { return JSON.parse(sessionStorage.getItem(SESSION_KEY) || '[]') } catch { return [] } + })()) let input = $state('') let thinking = $state(false) + let stopping = $state(false) let voiceOn = $state(false) let recording = $state(false) let status = $state(null) @@ -18,6 +23,11 @@ let ws = $state(null) let currentBot = $state('') // aktuell gestreamter Hermes-Token + // Nachrichten in sessionStorage sichern (lebt nur für aktive Browser-Tab-Session) + $effect(() => { + try { sessionStorage.setItem(SESSION_KEY, JSON.stringify(messages)) } catch {} + }) + // ---- WebSocket ---- function connect() { const proto = location.protocol === 'https:' ? 'wss:' : 'ws:' @@ -56,6 +66,7 @@ messages = [...messages, { role: 'error', content: m.content }] } else if (m.type === 'done') { thinking = false + stopping = false currentBot = '' if (voiceOn) speakLast() } @@ -69,6 +80,18 @@ ws = sock } + // ---- Stopp ---- + function stopGeneration() { + stopping = true + thinking = false + if (currentBot) { + messages = [...messages.slice(0, -1), + { role: 'hermes', content: currentBot + ' ✋' }] + } + currentBot = '' + ws?.close() // onclose reconnects nach 3s automatisch + } + // ---- Senden ---- async function send() { const text = input.trim() @@ -76,9 +99,17 @@ messages = [...messages, { role: 'user', content: text }] input = '' currentBot = '' + stopping = false ws.send(JSON.stringify({ message: text })) } + // ---- Verlauf löschen ---- + function clearHistory() { + messages = [] + currentBot = '' + try { sessionStorage.removeItem(SESSION_KEY) } catch {} + } + // ---- Voice: Aufnahme ---- let mediaRecorder: MediaRecorder | null = null let audioChunks: Blob[] = [] @@ -433,15 +464,31 @@ {recording ? '⏹' : '🎤'} - - + {#if thinking || stopping} + + + {:else} + + + {/if}
+ + + {#if messages.length > 0 && !thinking} +
+ +
+ {/if}