feat(local-llm): B0.5-2 — Live-Lade-Status des lokalen Modells in Diagnostic

Adapter meldet bei Modellwechsel/Erst-Load service_status (service=llm):
loading -> ready (mit loadSeconds; freshlyDownloaded 🎉 bei langem Erst-Load)
oder error. Laeuft ueber den vorhandenen Pfad: Adapter -> RVS -> Diagnostic
(RVS-Client) -> Browser -> updateServiceStatus (generisch; nur Label 'Lokales
LLM' ergaenzt). Kein Bridge-/server.js-Change noetig.

Download-% gibt llama-swap nicht her — daher Zustands-Status (laedt/bereit/
Fehler), im gemeinsamen Service-Banner wie whisper/flux.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 14:29:18 +02:00
co-authored by Claude Opus 4.8
parent 8ae20a9bd8
commit 2dfe6fd9c3
2 changed files with 35 additions and 1 deletions
+1 -1
View File
@@ -2641,7 +2641,7 @@
// Liste neu aufbauen // Liste neu aufbauen
list.innerHTML = ''; list.innerHTML = '';
let anyLoading = false, anyError = false; let anyLoading = false, anyError = false;
const labels = { f5tts: 'F5-TTS', whisper: 'Whisper STT', flux: 'FLUX Image-Gen' }; const labels = { f5tts: 'F5-TTS', whisper: 'Whisper STT', flux: 'FLUX Image-Gen', llm: 'Lokales LLM' };
for (const [s, info] of Object.entries(_serviceState)) { for (const [s, info] of Object.entries(_serviceState)) {
const row = document.createElement('div'); const row = document.createElement('div');
row.style.cssText = 'display:flex;align-items:center;gap:6px;'; row.style.cssText = 'display:flex;align-items:center;gap:6px;';
+34
View File
@@ -110,7 +110,22 @@ async def _call_llama(messages: list, *, max_tokens: int, temperature: float,
return {"ok": False, "content": "", "error": str(e)[:300]} return {"ok": False, "content": "", "error": str(e)[:300]}
# B0.5-2: Lade-Status ans Diagnostic (service_status, service="llm"). Wir kennen
# den Download-Fortschritt nicht (llama-swap gibt ihn nicht her), aber wir melden
# den Zustand bei Modellwechsel: loading -> ready/error. _last_model = aktuell
# geladenes; _ready_models = in dieser Session schon einmal bereit gewesene
# (fuer den "frisch geladen"-Hinweis 🎉 bei langem Erst-Load).
_last_model = None
_ready_models: set = set()
async def _emit_llm_status(ws, state: str, model: str, **extra) -> None:
await _send(ws, "service_status",
{"service": "llm", "state": state, "model": model, **extra})
async def _handle_llm_request(ws, payload: dict) -> None: async def _handle_llm_request(ws, payload: dict) -> None:
global _last_model
req_id = payload.get("requestId", "") req_id = payload.get("requestId", "")
messages = payload.get("messages") or [] messages = payload.get("messages") or []
if not isinstance(messages, list) or not messages: if not isinstance(messages, list) or not messages:
@@ -123,11 +138,30 @@ async def _handle_llm_request(ws, payload: dict) -> None:
stop = payload.get("stop") stop = payload.get("stop")
tools = payload.get("tools") or None tools = payload.get("tools") or None
model = (payload.get("model") or "").strip() or None model = (payload.get("model") or "").strip() or None
eff_model = model or LLM_MODEL
# Modellwechsel (oder erster Request) → llama-swap laedt/swappt: Status melden.
switching = eff_model != _last_model
if switching:
await _emit_llm_status(ws, "loading", eff_model)
t0 = time.time() t0 = time.time()
res = await _call_llama(messages, max_tokens=max_tokens, res = await _call_llama(messages, max_tokens=max_tokens,
temperature=temperature, stop=stop, tools=tools, temperature=temperature, stop=stop, tools=tools,
model=model) model=model)
dt = time.time() - t0 dt = time.time() - t0
if switching:
if res.get("ok"):
fresh = (eff_model not in _ready_models) and dt > 25
_ready_models.add(eff_model)
_last_model = eff_model
await _emit_llm_status(ws, "ready", eff_model,
loadSeconds=round(dt, 1), freshlyDownloaded=fresh)
else:
# bei Fehler _last_model NICHT setzen → naechster Versuch meldet erneut loading
await _emit_llm_status(ws, "error", eff_model,
error=(res.get("error") or "")[:120])
tc = res.get("tool_calls") tc = res.get("tool_calls")
logger.info("llm_request id=%s model=%s -> ok=%s %.2fs content_len=%d tool_calls=%d", logger.info("llm_request id=%s model=%s -> ok=%s %.2fs content_len=%d tool_calls=%d",
(req_id[:8] if req_id else "?"), model or LLM_MODEL, res.get("ok"), dt, (req_id[:8] if req_id else "?"), model or LLM_MODEL, res.get("ok"), dt,