Funk-lab/admin/ia/stt.md
alkatrazz 0f732f0631 feat(stt-client): page détail des services en pleine page + métriques live
La page détail d'un service (panneau « Services » du HUD) passe en PLEINE
PAGE : le drawer prend tout l'écran, le titre devient le nom du service, et
le contenu s'organise en 2 colonnes sur grand écran (données à gauche, état
+ actions à droite).

Nouvelle section « Métriques » par service, config-driven (comme `components`) :
- `[[services.metrics]]` → {label, prom, unit, hide_if_empty} évalué par le
  StatusPoller (dédup + parallèle avec les composants).
- Défauts via `config._pod_metrics(ns, pod_re)` pour les 7 services : redémarrages
  + uptime (kube-state-metrics, toujours dispo) ; mémoire + CPU (cAdvisor,
  `hide_if_empty` → carte masquée si non scrappé).
- Formatage HUD par unité : duration (8 j 9 h), bytes (497 Mo), cpu (30 mcœur),
  count.

Client 0.19.0. PromQL vérifié live (les 4 métriques renvoient des valeurs réelles
sur les 7 services). Rendu pleine page + formats validés via Playwright.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 00:01:38 +02:00

30 KiB
Raw Blame History

STT — Assistant vocal "Jarvis" du homelab Funk

STT est l'interface vocale et graphique de Funk : un assistant type Jarvis / Iron Man qui écoute, parle et affiche un HUD animé. Architecture client-serveur : un client stt sur le poste (voix + HUD) qui interroge un STT-server in-cluster (orchestration AI). Asa est désormais agentique : le STT-server fait du function calling (contexte asa) — elle sait (doc + état live), cherche (web SearXNG) et agit (Hermes via hermes-exec, avec confirmation).

Nom de code : STT. À l'origine « Speech-To-Text », mais le projet couvre toute la chaîne voix → cerveau → voix + interface + mémoire. Renommable plus tard sans impact technique (FRIDAY, etc.) — c'est juste un identifiant de répertoire/commande.

2026-06-17 — déployé in-cluster (ArgoCD), mémoire court-terme (5a) + long-terme (5b) validées sur cible, embeddings dédiés nomic :1238 (5d) + fix thinking Qwen3 /no_think (5e). 2026-06-18 — HUD avancé (4) + auto-start (6). HUD : design Claude Design (avatar portrait réactif, transcript à bulles, drawer Réglages câblé au backend : reset / mode cerveau / mot de réveil à chaud). Auto-start : stt --install-service (systemd --user). App de bureau : stt --install-desktop (fenêtre type Discord) ; auto-update : stt --update. 2026-06-21 — transcription partielle live + veille/réveil vocal (« Asa » / « Asa stop »). 2026-06-22Asa agentique : contexte asa par défaut (function calling local), outils doc + état live enrichi + web (SearXNG) + actions Hermes (admin_action, confirmation). Vérifié en prod. Voir docs/stt-presentation.md (+ schémas) et docs/asa-commandes.md. 2026-06-23TTS enfichable (Piper|Kokoro) + stt --voices / --install-voice / --install-kokoro. Reste : portraits d'avatar réels (le HUD montre le repli « あ »).


Principe directeur : réutiliser, ne pas réinventer

~70 % du backend existe déjà dans Funk. STT n'ajoute que le visage (HUD), la personnalisation, la mémoire multi-tiers et le packaging en commande.

Besoin Jarvis Brique Funk réutilisée État
Modèle local ou Claude LiteLLM :4000 + hermes-default + hermes-switch qwen|claude opérationnel
Agir sur le homelab Hermes Agent (:8080, profils funk-ai/system/monitor/brain) opérationnel
Voix (STT + TTS + wake word) tools/hermes-voice/ — faster-whisper + Piper + webrtcvad existe (CLI only)
Démarrage auto au boot stt --install-service (systemd --user) — repris du pattern tools/hermes-voice/
Mémoire sémantique Qdrant :6333 + RAG (rag-query/rag-ingest) sur s01 Qdrant réparé 17/06, RAG funk-docs re-ingéré (436 chunks, nomic dim 768)

Maillon réellement manquant : l'interface graphique HUD + le packaging + la mémoire perso.


Décisions d'architecture (verrouillées 2026-06-17)

Décision Choix retenu
Architecture Client-serveur : client sur le poste, STT-server in-cluster (révise le tout-local)
Interface HUD web sur-mesure côté client (canvas/WebGL)
STT / TTS Local sur le poste (faster-whisper CPU + Piper) — le serveur ne touche pas à l'audio
Packaging Client : commande stt via pipx (#subdirectory=stt/client). Serveur : conteneur (ghcr) déployé par ArgoCD
Cerveau Côté serveur : route vers LiteLLM :4000 (Qwen3 / Claude) + boucle d'outils (function calling : doc, état live, web, actions Hermes)
Client → serveur Client serveur-only (pas de repli Claude). URL serveur paramétrable
Mémoire Côté serveur (futur) : Qdrant s01 + distillée GitHub. Client : cache local SQLite

Pivot 2026-06-17 (post-test) : on est passé du tout-local à un modèle client-serveur. Le « cerveau » (ex-routeur 3 modes côté client) a migré côté serveur.


Architecture

                       LAN / *.lab.local
   ┌─ POSTE — client `stt` (pipx) ─┐        ┌─ CLUSTER k8s (namespace ai) ─────────┐
   │  • micro + VAD + wake word     │  HTTP  │  STT-server (Deployment + Service)    │
   │  • faster-whisper (STT)        │ ─────▶ │   GET  /healthz                        │
   │  • Piper (TTS)                 │ POST   │   POST /v1/ask {text} → {reply}        │
   │  • HUD web (ui/ + hud/)        │ /v1/ask│   brain → LiteLLM (httpx)              │
   │  • api.py → ServerClient       │ ◀───── │  IngressRoute : stt.lab.local         │
   └────────────────────────────────┘  reply │  → LiteLLM s01 192.168.10.1:4000      │
                                              └──────────────┬─────────────────────────┘
                                                             ▼
                                                LiteLLM :4000 (storage-01, hors cluster)
                                                → Qwen3 (g01) / Claude (hermes-default)

Le cerveau — côté serveur

Le STT-server appelle LiteLLM :4000 (OpenAI-compatible), joint depuis le cluster en IP directe 192.168.10.1:4000 (même pattern qu'open-webui). LiteLLM route lui-même vers Qwen3 (g01) ou Claude selon l'alias hermes-default / hermes-switch.

« Agir sur Funk » est désormais en place (2026-06-22) : pas via le gateway Hermes :8080 (jamais spécifié) mais via hermes-exec (storage-01 :9096, jeton) appelé par l'outil admin_action du contexte asa, avec confirmation vocale. Détails plus bas.

Choix du modèle : le client envoie un model (alias LiteLLM) par requête à /v1/ask ; le serveur le valide contre STT_ALLOWED_MODELS et le passe à LiteLLM. Pas de switch global type hermes-switch (pas de restart, chaque client choisit le sien). GET /v1/models liste le défaut + les alias autorisés. Noms courts client : hermes (=hermes-default), qwen, claude (=claude-sonnet-4-6), opus.

Contextes présélectionnables (visualiseur + presets live) : le client envoie un context par requête à /v1/ask (GET /v1/contexts liste les profils). Chaque profil (contexts.py) a son system prompt et ses sources live injectées dans le contexte (sources.py, best-effort, env-config) :

  • asaboucle d'outils (function calling), défaut prod (STT_DEFAULT_CONTEXT=asa). Voir ci-dessous.
  • funk — grounding strict + RAG funk-docs injecté d'office (comportement historique, sélectionnable)
  • ghostfolio — valeur/positions du portefeuille (API Ghostfolio, STT_GHOSTFOLIO_TOKEN)
  • grafana — métriques clés (Prometheus)
  • alerting — alertes actives (Alertmanager, hors Watchdog)
  • cluster — état pods/nœuds (Prometheus/kube-state-metrics) + RAG doc

Contexte « asa » — boucle d'outils (function calling, local) : au lieu d'un appel LLM unique sur un contexte figé, Asa décide elle-même quels outils appeler (tools.py) puis répond à partir de leurs résultats (brain.ask_with_tools, boucle bornée par STT_TOOL_MAX_ITERS=4 ; au-delà, réponse forcée sans outils). C'est ce qui débloque les questions à état live sans présélection — ex. « gpu-01 tourne bien ? » → l'outil host_health est appelé → réponse réelle (avant : le RAG seul disait « la doc ne le précise pas »). Outils LECTURE SEULE (tools.py) : search_docs (RAG funk-docs), host_health(gpu-01|storage-01) (up/charge/RAM + llama-server, Prometheus), cluster_status (nœuds/pods), prometheus_query(expr) (PromQL arbitraire), et web_search(query) (recherche INTERNET via SearXNG, Phase 2). Modèle local Qwen3-8B (le tool-calling natif de llama.cpp fonctionne, même avec /no_think — vérifié). Le trace des outils appelés est renvoyé dans context → visualiseur HUD (un bloc par appel).

Outil d'écriture admin_action (Phase 3) : pour une demande explicite d'AGIR, le LLM appelle admin_action(description) qui n'exécute rien — la boucle s'arrête (confirm_tools dans ask_with_tools), Asa propose l'action et stocke un pending par session. Le tour suivant, si l'utilisateur dit « confirme » (mots de agent.is_confirmation), _handle_agentic exécute via agent.run_actionhermes-exec (hermes -z --yolo) ; « annule » l'oublie. Réutilise le handshake + le jeton du contexte agent. Exposé seulement si _actions_available() (opt-in STT_ACTIONS_ENABLED + jeton) : sinon admin_action est retiré des schémas. La confirmation protège des erreurs de transcription ; le jeton, des déclenchements non autorisés.

cluster_status filtre les pods terminés (CronJob Succeeded/Failed) via un join kube_pod_status_phase → ne signale comme « non prêts » que les pods réellement actifs (sinon les pods cron finis, ex. sacrifice-assign-renfort, déclenchaient une fausse alarme).

SearXNG (recherche web in-cluster, Phase 2) : méta-moteur self-host, manifests k8s/apps/searxng/ (Deployment + Service + ConfigMap settings.yml + IngressRoute searxng.lab.local), Application ArgoCD searxng. Namespace ai, interne (l'outil tape http://searxng:8080). Points clés : use_default_settings: true (hérite des moteurs), search.formats inclut json (sinon l'API JSON renvoie 403), limiter:false/image_proxy:false (usage interne), image pinnée, conf copiée dans un emptyDir via initContainer (évite le mount RO). Outil = STT_SEARXNG_URL (tools._web_search).

La réponse /v1/ask renvoie le contexte assemblé (context: system prompt + blocs live + extraits RAG + mémoire) → alimente le visualiseur du HUD (« voir ce qu'on envoie à Asa »). URLs in-cluster dans k8s/apps/stt/deployment.yaml (Prometheus/Alertmanager monitoring, Ghostfolio ai). Jeton Ghostfolio = secret optionnel stt-server-secrets/ghostfolio-token.

Contexte « agent » — actions via Hermes (OPT-IN) : sélectionner le contexte agent fait agir Asa sur le homelab. Flux dédié (agent.py, court-circuite le LLM) : la demande est confirmée (handshake 2 temps « tu veux que… ? » → « confirme »/« annule »), puis exécutée par le vrai agent Hermes via hermes-exec (storage-01:9096 → hermes -z "<prompt>" --yolo, profil défaut, tous outils). Désactivé par défaut (STT_ACTIONS_ENABLED=false) ; le contexte n'apparaît dans /v1/contexts que si activé et jeton présent. Jeton = stt-server-secrets/hermes-exec-token côté serveur = vault_hermes_exec_token côté Ansible (rôle hermes_exec). La confirmation protège des erreurs de transcription ; le jeton empêche tout déclenchement non autorisé depuis le cluster.

Caveat Qwen3 — mode « thinking » (corrigé) : Qwen3 est un modèle de raisonnement. Sans précaution, il dépense tout le budget max_tokens (200) dans reasoning_contentcontent vide, ou réfléchit trop longtemps → timeout 502 (upstream LiteLLM : au message vide — signature d'un timeout httpx). Diagnostiqué via appel direct LiteLLM : réponse en 0.87s mais content="" et tout dans reasoning_content. Fix : brain.py ajoute le token /no_think au prompt système (STT_DISABLE_THINKING, défaut true ; inoffensif hors Qwen) et récupère reasoning_content en filet de sécurité si content est vide. Caveat partagé : admin/ia/llama_server.md.

La mémoire — côté serveur

  • Court-terme (5a, fait) : le serveur garde l'historique par session_id (en mémoire, borné max_turns + TTL) et l'injecte dans l'appel LLM. /v1/ask {session_id} + /v1/reset. Le client génère un session_id par run. Deployment en 1 worker (cohérence mémoire process).
  • Long-terme (5b, validé 17/06) : collection Qdrant dédiée stt-memory (indépendante du funk-docs du RAG), embeddings via nomic-embed-text :1238 (dim 768, Cosine), souvenirs pertinents (top-k) injectés au prompt. Qdrant en IP directe 192.168.10.1:6333. Dégrade proprement si Qdrant/embeddings injoignables (la mémoire court-terme tient).
    • Validé bout-en-bout : « mon chat s'appelle Felix » (session A) rappelé dans une nouvelle session B (« comment s'appelle mon chat ? » → « Felix »). points_count confirmé via /v1/memory/health.
    • upsert ?wait=true : l'écriture Qdrant est synchrone, donc un souvenir est immédiatement cherchable (sans wait, l'écriture est mise en file → un rappel cross-session immédiat pouvait la rater).
    • GET /v1/memory/health : sonde active embed + Qdrant + collection, expose les erreurs (recall/remember dégradent en silence → indébogables sans cet endpoint). Sert au diagnostic.
  • Embeddings dédiés (5d, fait) : passage de Qwen3 (chat réutilisé, dim 4096, cosinus uniformément hauts → peu discriminant) à nomic-embed-text (modèle spécialisé, dim 768), servi par une instance llama-server dédiée sur gpu-01 :1238 (GPU), gérée par le rôle llama_server (llama_embed_enabled). STT : STT_EMBED_URL=…:1238, STT_EMBED_MODEL=nomic-embed-text.
    • Migration auto de la collection : _ensure_collection détecte le changement de dimension (4096 → 768) et recrée stt-memory (les anciens vecteurs sont incomparables dans le nouvel espace). Aucun drop manuel — la collection est reconstruite à la première requête après bascule.
  • Réparation Qdrant (5c, fait) : crash-loop depuis 05/06 (segment funk-docs corrompu). Réparé le 17/06 sur s01 : systemctl stop qdrant && rm -rf /srv/data/qdrant/storage/collections/funk-docs && systemctl start qdrant.
  • RAG documentaire / grounding (5f, fait) : le cerveau interroge aussi la collection funk-docs (la doc admin/ indexée) et injecte les passages pertinents au prompt → il répond à partir de la vraie doc du homelab au lieu d'halluciner des généralités (« stockage décentralisé », « worker nodes »…). Module stt_server/knowledge.py, activé par STT_DOCS_RAG.
    • Coût ~nul : funk-docs et stt-memory partagent le même embedder (nomic-embed-text :1238, dim 768) → le vecteur de requête calculé pour le recall mémoire est réutilisé pour la recherche doc (une seule embed, deux recherches Qdrant). Dégrade en silence (réponse sans doc si Qdrant/embed down).
    • Réglages : STT_DOCS_TOPK (6), STT_DOCS_MIN_SCORE (0.45), STT_DOCS_TIMEOUT (4 s), STT_DOCS_COLLECTION (funk-docs). Le funk-docs doit être ingéré (rag-ingest) avec le même modèle nomic — sinon dimensions incompatibles (cf. rôle rag).
    • Validé : « c'est quoi le nom des nœuds ? » → sans RAG « worker/master nodes » (inventé) ; avec RAG « Kubernetes ; nœuds storage-01, compute-01, compute-02 » (tiré de la doc). Le log /v1/ask expose docs=<n> (nb d'extraits injectés).

⚠️ Vie privée : seule la mémoire distillée serait committée. Repo privé impératif.


Composants

Composant Emplacement Description
Client stt stt/client/ (pipx) stt (voix+HUD), stt --text (chat texte), stt --setup, stt --server <url>, stt --model <hermes|claude|qwen|opus>
— voix stt/client/stt/voice/ wake word, ASR enfichable (voice/asr/), TTS Piper
— 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 ServerClientPOST /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, 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) ; Alertmanagertoutes 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)
— page détail pleine page + métriques (≥ 0.19.0) hud/index.html (renderMetrics, is-detail) + registry.Metric + health.py La page détail passe en pleine page (le drawer prend tout l'écran, 2 colonnes sur grand écran : données ⟂ état). Section Métriques par service (config [[services.metrics]]{label, prom, unit, hide_if_empty}) évaluée par le StatusPoller : redémarrages, uptime (kube-state-metrics) + mémoire, CPU (cAdvisor, masqués si absents). Défauts via config._pod_metrics(ns, pod_re). Formatage HUD par unité (duration/bytes/cpu/count)
— 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
Image ghcr.io/alkatrazz24/funk-stt-server construite par .github/workflows/build-stt-server.yml
Manifests k8s/apps/stt/ Deployment, Service, IngressRoute (stt.lab.local)
Application ArgoCD k8s/apps-of-apps/apps/stt.yaml déploie depuis main

Interaction vocale — veille/réveil & transcription live (client ≥ 0.14.0)

Machine à états du moteur vocal (stt/client/stt/voice/engine.py) :

  • Veille (asleep) — état par défaut. Asa transcrit chaque énoncé uniquement pour y repérer le wake word ; elle ne répond à rien d'autre (HUD : portrait atténué, « SOMMEIL »).
  • Réveil — dire « Asa » la passe en éveil. « Asa, » exécute directement la question. ⚠️ L'ASR (Parakeet FR) ne transcrit pas « Asa » comme un mot : il l'entend « a so » / « à ça » / « ah ça »… La détection joint les 1-2 premiers mots (déaccentués, sans espace) et compare en égalité exacte à [voice].wake_aliases (formes compactes : asa, aso, aca, ahca…). Ajuster cette liste si l'ASR rend encore autrement — le log [stt.voice] (veille) entendu: '…' (journalctl, cf. STT_VOICE_LOG) montre la transcription réelle.
  • Éveil (listening) — elle répond à tout. « Asa stop » (ou « stop », « au revoir », « dors »… — [voice].sleep_words) la remet en veille ; un timeout d'inactivité (chat_timeout_sec) aussi.
  • Transcription partielle live — en éveil, le buffer en cours est re-transcrit toutes les partial_interval_sec et poussé au HUD (partial) → le texte se forme en direct, puis la bulle finale le remplace. Auto-activée seulement avec l'ASR onnx (Parakeet, rapide en CPU) ; forçable via [voice].partial_transcription = true|false. Whisper large-v3 CPU est trop lent.

Le wake word par défaut est désormais asa (était hermes). Les configs existantes (~/.config/stt/stt.toml) gardent leur valeur — changer [voice].wake_word ou via le HUD (réglage « mot de réveil ») / la commande /wake.


Personnalisation (« l'image, le design, la voix »)

Côté client, pilotable depuis stt/client/config/ + l'écran de réglages du HUD :

  • Avatar / image (config/avatars/), thème (config/themes/), voix, wake word, URL serveur.

Moteur TTS — [voice].tts_engine : piper (défaut, rapide, voix .onnx dans ~/.local/share/piper/) ou kokoro (nettement plus naturel, ONNX/CPU). Pour Kokoro : stt --install-kokoro (télécharge kokoro-v1.0.onnx + voices-v1.0.bin dans ~/.local/share/kokoro/), pipx inject stt kokoro-onnx, espeak-ng (phonémisation FR), puis tts_engine = "kokoro" (voix FR kokoro_voice = "ff_siwis"). Le client précharge Kokoro au démarrage et retombe en silence proprement si le modèle/paquet manque (engine._synth_kokoro). La synthèse est enfichable (engine._synthesize) — Piper reste le repli.

Volume de la voix : réglable à chaud dans le HUD (Réglages → « Volume de la voix », 0200 %) ou via [voice].tts_volume (0.0 muet … 2.0 amplifié). Gain appliqué au WAV avant lecture, commun aux deux moteurs (engine._apply_gain) ; persisté côté HUD (localStorage).

Fenêtrage : [ui].window_mode = "app" (fenêtre déplaçable/redimensionnable, type Discord) | "kiosk" (plein écran) | "none". stt --window app persiste désormais le choix (relu par le service au prochain stt --restart).

Changer de voix Piper (homme/femme) : stt --voices liste les voix FR ; stt --install-voice fr_FR-tom-medium télécharge la voix (.onnx + .onnx.json depuis HuggingFace rhasspy/piper-voices) et la sélectionne (tts_engine=piper + piper_voice écrits dans stt.toml). Puis stt --restart.

⚠️ Changer la voix nécessite d'écrire tts_engine/piper_voice dans le stt.toml existant : un simple stt --update ne bascule rien (la config perso prime sur le défaut) — d'où ces commandes.


Prérequis / dépendances

  • Poste : micro, Python 3.11+, pipx, navigateur. Piper + voix dans ~/.local/share/piper/, aplay.
  • Cluster : image poussée sur ghcr ; ArgoCD déploie depuis main (donc merge requis) ; ghcr privé ⇒ éventuel imagePullSecret.
  • LiteLLM joignable depuis le cluster (192.168.10.1:4000).

Auto-start (service systemd --user)

Lancer le HUD + voix automatiquement à l'ouverture de session (écran « Jarvis ») :

stt --install-service     # écrit ~/.config/systemd/user/stt.service, enable + start
stt --uninstall-service   # retire le service
stt --start               # démarre le service
stt --restart             # redémarre — recharge la config (modèle ASR, wake word…)
stt --stop                # éteint l'instance en cours (service systemd OU process lancé à la main)

Le unit est lié à graphical-session.target (démarre avec la session graphique, s'arrête avec). Suivi : systemctl --user status|restart stt, journalctl --user -u stt -f.

Ces actions sont aussi dispo depuis le HUD (Réglages → section « Service ») : Redémarrer, Mettre à jour (stt --update puis --restart), Arrêter. Le HUD tournant dans le service, ces actions sont lancées en process détaché (start_new_session) → le redémarrage va à son terme même quand le process courant est tué (systemd possède le job).

stt --stop arrête proprement : si le service stt.service (systemd --user) est actif il fait systemctl --user stop, sinon il envoie un SIGTERM aux process stt vocaux trouvés via /proc (en s'excluant lui-même et les sous-commandes utilitaires --update/--version/…).

Caveat DISPLAY : un service systemd --user n'hérite de DISPLAY/WAYLAND_DISPLAY que si la session graphique les a importés dans le gestionnaire user. --install-service exécute un systemctl --user import-environment DISPLAY WAYLAND_DISPLAY XAUTHORITY (best-effort). Si le kiosk ne s'ouvre pas : vérifier que le bureau peuple graphical-session.target (GNOME/KDE le font), ou lancer stt via les « applications au démarrage » du bureau. Le kiosk est piloté par [ui].kiosk (défaut true).

Boot sans login (borne dédiée) : sudo loginctl enable-linger "$USER" + auto-login du bureau — la session graphique démarre au boot et tire le service.


Application de bureau & mise à jour

Lancer STT comme une appli (fenêtre dédiée type Discord, pas un onglet) :

stt --install-desktop     # entrée de menu (.desktop) + icône → épinglable au dock
stt --uninstall-desktop   # la retirer

Le lanceur exécute stt --window app : le HUD s'ouvre en fenêtre chromeless (--app= de Chromium/Brave). Modes via --window ou [ui].window_mode : app (fenêtre), kiosk (plein écran, défaut du service), none (n'ouvre rien). L'icône est stt/hud/icon.svg (aussi favicon). La fenêtre tourne dans un profil + une WM class dédiés (--class=STT-Funk, --user-data-dir sous ~/.local/share/stt/app-profile) → instance autonome avec sa propre icône/entrée de barre des tâches (associée au .desktop via StartupWMClass), séparée de ta session Brave. (Une vraie fenêtre native sans navigateur — via pywebview — reste possible plus tard si besoin.)

⚠️ Le lanceur de bureau et le service systemd (kiosk) utilisent les mêmes ports (9300/9301) : n'en faire tourner qu'un à la fois. Borne dédiée → service kiosk ; usage à la demande → lanceur de bureau (et stt --uninstall-service).

Mettre à jour (sans désinstaller/réinstaller à la main) :

stt --update      # pipx reinstall depuis la source git (via ta clé SSH) — affiche ancienne → nouvelle
stt --version     # version installée

--update re-récupère la dernière version depuis la branche/source d'installation. Pré-requis : avoir installé via pipx depuis git. Astuce : toujours bumper la version du paquet à chaque release, sinon le cache de build pipx/uv peut resservir l'ancienne (cf. pyproject.toml).

Compatibilité distros : tout le client repose sur des standards communs — systemd --user, lanceurs .desktop (XDG ~/.local/share/applications/), pipx — donc identique sur AlmaLinux/Fedora (RHEL) et Debian/Ubuntu. Seul le navigateur diffère : _open_browser teste les binaires natifs (rpm + deb) puis Flatpak (com.brave.Browser, org.chromium.Chromium…), sinon repli sur le navigateur par défaut. update-desktop-database (desktop-file-utils) est optionnel — son absence n'empêche rien.


Roadmap

Phase Objectif État
0 — Cadrage Conception validée
1 — Client voix + HUD stt : voix locale + HUD + websocket
2 — STT-server FastAPI /v1/ask → LiteLLM
3 — Déploiement cluster image ghcr + manifests k8s + ArgoCD (LiteLLM en IP directe) déployé
4 — HUD avancé avatar portrait réactif (anneau/ping/spinner) + transcript + composer texte (chat clavier sans micro) + boutons stop (couper la réponse) / mute micro + drawer réglages (modèle/reset/wake à chaud) + thème accent (à tester sur poste)
5a — Mémoire court-terme historique de session côté serveur
5b — Mémoire long-terme Qdrant stt-memory + embeddings (dégrade si down) validé 17/06 (rappel cross-session OK)
5c — Réparation Qdrant drop funk-docs corrompu + restart (s01) (17/06)
5d — Embeddings dédiés nomic-embed-text (dim 768) sur gpu-01 :1238 → recherche plus précise (rôle llama_server + migration auto collection)
5e — Fix thinking Qwen3 /no_think (content vide / timeout 502) + fallback reasoning_content (17/06)
5f — RAG documentaire (grounding) le cerveau interroge funk-docs (doc admin/) et injecte les passages au prompt → répond depuis la vraie doc au lieu d'halluciner ; réutilise le vecteur nomic du recall (knowledge.py, STT_DOCS_RAG)
6 — Auto-start client stt --install-service (systemd --user, graphical-session.target) + kiosk (à tester sur poste)
7 — Outils Hermes « agir sur Funk » via gateway :8080 (API à spécifier)

État (validé sur cible)

Serveur déployé in-cluster via ArgoCD (image sha-<commit> gérée par CI). Mémoire 5a + 5b validées en prod : court-terme (historique de session) et long-terme (rappel cross-session « Felix », points_count confirmé via /v1/memory/health). HUD avancé (4) implémenté (design Claude Design, câblé au websocket backend) ; auto-start (6) via stt --install-service. Le HUD inclut un composer texte ({"type":"text"}VoiceEngine.respond_text) pour dialoguer au clavier sans micro — utile tant que le bout-en-bout audio n'est pas validé. Reste côté poste : test audio bout-en-bout (micro → STT → serveur → TTS) et test du HUD/kiosk avec portraits réels.


Projets de référence (inspiration, non forkés)

Aucun ne se branche proprement sur LiteLLM + Hermes (ils embarquent leur propre LLM/agent), mais bons pour le design et le code à piocher :