diff --git a/admin/ia/stt.md b/admin/ia/stt.md index c02b024..3dffa00 100644 --- a/admin/ia/stt.md +++ b/admin/ia/stt.md @@ -212,7 +212,8 @@ au prompt système (`STT_DISABLE_THINKING`, défaut `true` ; inoffensif hors Qwe | — ASR | `stt/client/stt/voice/asr/` | backend enfichable : `whisper` (faster-whisper CPU, défaut) \| `onnx` (Parakeet/Canary/Nemotron via onnx-asr, multilingue, streaming-ready). Choisi par `[voice] asr_engine` | | — api | `stt/client/stt/api.py` | `ServerClient` → `POST /v1/ask` | | — UI/HUD | `stt/client/stt/ui/` + `hud/` | HTTP statique + websocket bidirectionnel (états → HUD ; réglages **et messages texte** → backend) ; HUD embarqué dans le package | -| — portail | `stt/client/stt/portal/` | `registry.py` (services config-driven `[[services]]`) + `health.py` (StatusPoller). Panneau « Services » du HUD : tuiles + **pages détail** (description, **santé live**, composants, alertes, bouton Ouvrir). Santé = probe HTTP + **Prometheus** `up{}`/kube-state-metrics + **Alertmanager**. Ouvre l'URL dans le navigateur normal | +| — portail | `stt/client/stt/portal/` | `registry.py` (services config-driven `[[services]]`) + `health.py` (StatusPoller). Panneau « Services » du HUD : tuiles + **pages détail** (description, **santé live**, composants, alertes, **aperçu enrichi**, bouton Ouvrir). Santé = probe HTTP + **Prometheus** `up{}`/kube-state-metrics + **Alertmanager**. Ouvre l'URL dans le navigateur normal | +| — aperçu enrichi (≥ 0.18.0) | `hud/index.html` (`renderServiceData`) + `ui/app.py` (`service-detail`) | Aperçu live spécifique au service dans sa page détail : **Ghostfolio** → valeur + performance + positions (alloc % et P/L par ligne, via `POST /v1/portfolio/details` du serveur, jeton client) ; **Alertmanager** → **toutes** les alertes actives (rendu HUD à partir de `all_alerts` poussé par le poller, auto-rafraîchi). Extensible : `DETAIL_PROVIDERS` (`server` = requête backend \| `alerts` = liste locale) | | — intentions vocales | `stt/client/stt/portal/intents.py` + `ghostfolio.py` | lecture seule, court-circuitent le LLM (interceptées dans `engine._respond`) : « ouvre <service> », « état du cluster/services » (résumé santé parlé), « combien sur mon ghostfolio » (API Ghostfolio, `[ghostfolio] access_token`) | | — contexte HUD | `stt/client/stt/ui/app.py` + `hud/` | sélecteur de contexte (Réglages) envoyé par requête + **visualiseur** (drawer) affichant le contexte assemblé renvoyé par le serveur ; affichage de la version installée | | STT-server | `stt/server/` (conteneur) | FastAPI : `/healthz`, `/v1/ask` ; `brain.py` → LiteLLM | diff --git a/stt/client/pyproject.toml b/stt/client/pyproject.toml index 653b575..830ea2a 100644 --- a/stt/client/pyproject.toml +++ b/stt/client/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "stt" -version = "0.17.0" +version = "0.18.0" description = "STT — client vocal Jarvis du homelab Funk (voix + HUD, parle au STT-server)" requires-python = ">=3.11" readme = "README.md" diff --git a/stt/client/stt/api.py b/stt/client/stt/api.py index f5e3af5..bc82956 100644 --- a/stt/client/stt/api.py +++ b/stt/client/stt/api.py @@ -68,6 +68,27 @@ class ServerClient: except requests.RequestException: return "Je n'arrive pas à joindre le serveur pour Ghostfolio." + def portfolio_details(self) -> dict: + """Aperçu structuré du portefeuille (page détail du portail HUD). + + {value, currency, performance_pct, holdings:[…]} ou {error: …}. Dégrade en + clair si le serveur est antérieur (pas d'endpoint) ou injoignable. + """ + import requests + + try: + r = requests.post( + f"{self.url}/v1/portfolio/details", + json={"token": self.secrets.get("ghostfolio_token")}, + timeout=self.timeout, + ) + if r.status_code == 404: + return {"error": "Aperçu indisponible (STT-server antérieur)."} + r.raise_for_status() + return r.json() + except requests.RequestException: + return {"error": "Je n'arrive pas à joindre le serveur pour Ghostfolio."} + def contexts(self) -> dict | None: """{'default':…, 'contexts':[{id,label,icon,description}]} ; None si indisponible. diff --git a/stt/client/stt/hud/index.html b/stt/client/stt/hud/index.html index 38c4c7c..f3f2128 100644 --- a/stt/client/stt/hud/index.html +++ b/stt/client/stt/hud/index.html @@ -751,6 +751,36 @@ .svc-info a { color: var(--accent-active); text-decoration: none; } .svc-open-btn { margin-top: 18px; } + /* Aperçu enrichi (page détail) : cartes de stats + lignes de données */ + .svc-stats { display: flex; flex-wrap: wrap; gap: 10px; } + .svc-stats:empty { display: none; } + .svc-stat { + flex: 1 1 120px; min-width: 0; + padding: 12px 14px; border-radius: 12px; + background: var(--bg-soft); border: 1px solid var(--line); + } + .svc-stat .svc-stat-label { + display: block; font-size: 11px; font-weight: 700; text-transform: uppercase; + letter-spacing: .04em; color: var(--ink-soft); margin-bottom: 6px; + } + .svc-stat .svc-stat-value { font-size: 20px; font-weight: 650; letter-spacing: -.01em; } + .svc-stat[data-tone="good"] .svc-stat-value { color: #2bb673; } + .svc-stat[data-tone="bad"] .svc-stat-value { color: #d23a52; } + .svc-data-rows { margin-top: 12px; } + .svc-data-rows:empty { display: none; } + .svc-data-row { + display: flex; align-items: baseline; gap: 10px; + padding: 8px 0; font-size: 13.5px; border-top: 1px solid var(--line); + } + .svc-data-row:first-child { border-top: none; } + .svc-data-row .svc-data-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .svc-data-row .svc-data-sub { font-size: 11.5px; color: var(--ink-soft); margin-left: 6px; } + .svc-data-row .svc-data-val { font-weight: 600; flex: none; } + .svc-data-row .svc-data-val[data-tone="good"] { color: #2bb673; } + .svc-data-row .svc-data-val[data-tone="bad"] { color: #d23a52; } + .svc-data-row .svc-data-val[data-tone="warn"] { color: #c5841f; } + .svc-data-empty { font-size: 13px; color: var(--ink-soft); margin: 4px 0 0; } + /* Badge alertes global (en tête de la liste) */ .svc-alert-badge { display: flex; align-items: center; gap: 8px; @@ -1090,6 +1120,15 @@

+ + +