mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 17:34:43 +02:00
Avant : jeton Ghostfolio en double (config client pour l'intent vocal + secret k8s pour
le contexte serveur). Désormais une SEULE source — la config client — transmise au
serveur, qui fait tous les appels Ghostfolio (un seul code, lecture seule).
Serveur :
- sources.py : cœur _ghostfolio_fetch(token) partagé ; ghostfolio_block (contexte) et
ghostfolio_phrase (intent) acceptent un jeton par requête (priorité client > env).
- app.py : /v1/ask accepte `secrets` (jetons client) passés aux sources ; nouvel
endpoint POST /v1/portfolio {token} → valeur du portefeuille.
- bump 0.4.0 → 0.5.0.
Client :
- api.py : ServerClient.secrets transmis dans /v1/ask ; méthode portfolio() → /v1/portfolio.
- cli.py : _make_client renseigne secrets["ghostfolio_token"] depuis [ghostfolio].
- ui/app.py : l'intent « combien ghostfolio » appelle client.portfolio (serveur).
- supprime portal/ghostfolio.py (client) — l'appel API vit côté serveur.
- config : [ghostfolio] = juste access_token (transmis au serveur).
- bump 0.12.0 → 0.13.0.
Le secret k8s STT_GHOSTFOLIO_TOKEN devient un simple fallback. Validé bout-en-bout
contre une instance serveur locale + Ghostfolio réel : « …vaut environ 16 520 EUR. »
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
213 lines
9.5 KiB
Python
213 lines
9.5 KiB
Python
"""Configuration du client STT.
|
|
|
|
Défauts embarqués (robuste pour pipx). `stt --setup` les écrit dans
|
|
~/.config/stt/stt.toml ; au lancement on fusionne le fichier utilisateur par-dessus.
|
|
|
|
Le client ne contient PLUS le cerveau : il envoie le texte au STT-server (URL paramétrable).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tomllib
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
CONFIG_DIR = Path.home() / ".config" / "stt"
|
|
CONFIG_PATH = CONFIG_DIR / "stt.toml"
|
|
DATA_DIR = Path.home() / ".local" / "share" / "stt"
|
|
|
|
# Noms courts → alias LiteLLM (pour --model et la commande /model)
|
|
MODEL_PRESETS = {
|
|
"hermes": "hermes-default", # Qwen3-8B local (défaut, gratuit)
|
|
"qwen": "qwen3-8b",
|
|
"claude": "claude-sonnet-4-6", # Claude (facturé)
|
|
"opus": "claude-opus-4-7",
|
|
}
|
|
|
|
|
|
def resolve_model(name: str) -> str:
|
|
"""Nom court → alias LiteLLM (ou renvoie tel quel si déjà un alias)."""
|
|
return MODEL_PRESETS.get(name, name)
|
|
|
|
DEFAULT_CONFIG: dict[str, Any] = {
|
|
"server": {
|
|
# STT-server (in-cluster) — joignable via Traefik / wildcard *.lab.local
|
|
"url": "http://stt.lab.local",
|
|
"timeout_sec": 90,
|
|
# Modèle demandé au serveur (alias LiteLLM, ou nom court ci-dessous)
|
|
"model": "hermes-default",
|
|
},
|
|
"voice": {
|
|
"wake_word": "hermes",
|
|
# ASR : "whisper" (faster-whisper CPU, défaut) | "onnx" (Parakeet/Canary/Nemotron)
|
|
"asr_engine": "whisper",
|
|
"whisper_model": "large-v3",
|
|
# onnx-asr (si asr_engine="onnx") — modèle multilingue + execution providers.
|
|
# CPU par défaut ; pour le GPU AMD : ["ROCMExecutionProvider", "CPUExecutionProvider"].
|
|
"onnx_model": "nemo-parakeet-tdt-0.6b-v3",
|
|
"onnx_providers": ["CPUExecutionProvider"],
|
|
"piper_voice": "fr_FR-upmc-medium",
|
|
"language": "fr",
|
|
"silence_sec": 1.2,
|
|
"min_speech_sec": 0.6,
|
|
"chat_timeout_sec": 60,
|
|
},
|
|
"ui": {
|
|
# theme / avatar : désormais pilotés dans le HUD (Réglages → localStorage),
|
|
# conservés pour compat mais ignorés par le HUD actuel.
|
|
"theme": "arc-reactor",
|
|
"avatar": "default",
|
|
"kiosk": True,
|
|
# window_mode : "app" (fenêtre type Discord) | "kiosk" (plein écran) | "none".
|
|
# Si absent, repli sur kiosk ci-dessus (compat).
|
|
"window_mode": "kiosk",
|
|
"ws_host": "127.0.0.1",
|
|
"ws_port": 9300,
|
|
"http_port": 9301,
|
|
},
|
|
"memory": {
|
|
# cache local de conversation (brut, gitignoré) — la vraie mémoire est côté serveur
|
|
"enabled": True,
|
|
"local_db": str(DATA_DIR / "memory.sqlite"),
|
|
},
|
|
# Portail de services — panneau « Services » du HUD (tuiles + pages détail), santé
|
|
# live, ET commande vocale « ouvre <service> ». Ajouter un service = une entrée ici
|
|
# (ou un bloc [[services]] dans stt.toml, qui remplace alors toute cette liste).
|
|
# Champs : id, name, url, icon, aliases (voix) ; + pour la page détail :
|
|
# description, health_url (sondé), components[{name, prom}] (PromQL → 1/0),
|
|
# alerts_match (labels Alertmanager : alerte pertinente si TOUS matchent).
|
|
"services": [
|
|
{
|
|
"id": "ghostfolio", "name": "Ghostfolio",
|
|
"url": "http://ghostfolio.lab.local", "icon": "💰",
|
|
"aliases": ["portefeuille", "portfolio", "ghosto", "bourse", "investissements"],
|
|
"description": "Suivi de portefeuille et d'investissements (actions, crypto). "
|
|
"Base PostgreSQL sur storage-01 + cache Redis in-cluster.",
|
|
"health_url": "http://ghostfolio.lab.local/api/v1/health",
|
|
"components": [
|
|
{"name": "Application", "prom": 'kube_pod_status_ready{namespace="ai",pod=~"ghostfolio-.+",pod!~"ghostfolio-redis-.+",condition="true"}'},
|
|
{"name": "Redis (cache)", "prom": 'kube_pod_status_ready{namespace="ai",pod=~"ghostfolio-redis-.+",condition="true"}'},
|
|
{"name": "PostgreSQL — storage-01", "prom": 'up{job="storage-01"}'},
|
|
],
|
|
"alerts_match": {"namespace": "ai"},
|
|
},
|
|
{
|
|
"id": "grafana", "name": "Grafana",
|
|
"url": "http://grafana.lab.local", "icon": "📊",
|
|
"aliases": ["graphana", "metriques", "monitoring", "dashboard", "graphiques"],
|
|
"description": "Tableaux de bord et visualisation des métriques du homelab "
|
|
"(Prometheus comme source de données).",
|
|
"health_url": "http://grafana.lab.local/api/health",
|
|
"components": [
|
|
{"name": "Pod", "prom": 'kube_pod_status_ready{namespace="monitoring",pod=~".*grafana.*",condition="true"}'},
|
|
],
|
|
"alerts_match": {"namespace": "monitoring"},
|
|
},
|
|
{
|
|
"id": "argocd", "name": "ArgoCD",
|
|
"url": "http://argocd.lab.local", "icon": "🚢",
|
|
"aliases": ["argo", "argo cd", "deploiements", "gitops"],
|
|
"description": "Déploiement continu GitOps : synchronise les workloads k8s "
|
|
"depuis le dépôt Git (pattern App-of-Apps).",
|
|
"health_url": "http://argocd.lab.local/healthz",
|
|
"components": [
|
|
{"name": "Pods (server, repo, redis…)", "prom": 'kube_pod_status_ready{namespace="argocd",condition="true"}'},
|
|
],
|
|
"alerts_match": {"namespace": "argocd"},
|
|
},
|
|
{
|
|
"id": "n8n", "name": "n8n",
|
|
"url": "http://n8n.lab.local", "icon": "🔀",
|
|
"aliases": ["workflows", "automatisation", "nodes", "huit n huit"],
|
|
"description": "Automatisation et workflows (alertes, rapport horaire, worker "
|
|
"doc Hermès). Base PostgreSQL sur storage-01.",
|
|
"health_url": "http://n8n.lab.local/healthz",
|
|
"components": [
|
|
{"name": "Application", "prom": 'kube_pod_status_ready{namespace="ai",pod=~"n8n-.+",condition="true"}'},
|
|
{"name": "PostgreSQL — storage-01", "prom": 'up{job="storage-01"}'},
|
|
],
|
|
"alerts_match": {"namespace": "ai"},
|
|
},
|
|
{
|
|
"id": "openwebui", "name": "Open WebUI",
|
|
"url": "http://openwebui.lab.local", "icon": "💬",
|
|
"aliases": ["open web ui", "webui", "chat", "llm", "interface ia"],
|
|
"description": "Interface de chat web vers les LLM du homelab "
|
|
"(backend LiteLLM sur storage-01).",
|
|
"health_url": "http://openwebui.lab.local/health",
|
|
"components": [
|
|
{"name": "Application", "prom": 'kube_pod_status_ready{namespace="ai",pod=~"open-webui-.+",condition="true"}'},
|
|
{"name": "LiteLLM — storage-01", "prom": 'up{job="storage-01"}'},
|
|
],
|
|
"alerts_match": {"namespace": "ai"},
|
|
},
|
|
{
|
|
"id": "prometheus", "name": "Prometheus",
|
|
"url": "http://prometheus.lab.local", "icon": "🔥",
|
|
"aliases": ["prometheus", "metriques brutes", "tsdb"],
|
|
"description": "Collecte et stockage des métriques (TSDB) — scrute hôtes, "
|
|
"nœuds k8s et services.",
|
|
"health_url": "http://prometheus.lab.local/-/healthy",
|
|
"components": [
|
|
{"name": "Pod", "prom": 'kube_pod_status_ready{namespace="monitoring",pod=~"prometheus-.+",condition="true"}'},
|
|
],
|
|
"alerts_match": {"namespace": "monitoring"},
|
|
},
|
|
{
|
|
"id": "alertmanager", "name": "Alertmanager",
|
|
"url": "http://alertmanager.lab.local", "icon": "🚨",
|
|
"aliases": ["alertes", "alert manager", "alarmes"],
|
|
"description": "Routage et notification des alertes Prometheus "
|
|
"(→ webhook Hermès, n8n, e-mail).",
|
|
"health_url": "http://alertmanager.lab.local/-/healthy",
|
|
"components": [
|
|
{"name": "Pod", "prom": 'kube_pod_status_ready{namespace="monitoring",pod=~"alertmanager-.+",condition="true"}'},
|
|
],
|
|
"alerts_match": {"namespace": "monitoring"},
|
|
},
|
|
],
|
|
# Santé live du portail (pages détail). Sondage non bloquant en thread.
|
|
"portal": {
|
|
"status_enabled": True,
|
|
"status_interval_sec": 15, # cadence de rafraîchissement
|
|
"probe_timeout_sec": 4,
|
|
"prometheus_url": "http://prometheus.lab.local",
|
|
"alertmanager_url": "http://alertmanager.lab.local",
|
|
},
|
|
# Ghostfolio — jeton (« Security token » du compte). SOURCE UNIQUE du jeton :
|
|
# le client le transmet au serveur, qui fait l'appel API (intent « combien sur mon
|
|
# portefeuille » via /v1/portfolio + contexte Ghostfolio). Vide → indisponible.
|
|
"ghostfolio": {
|
|
"access_token": "",
|
|
},
|
|
}
|
|
|
|
|
|
def _deep_merge(base: dict, over: dict) -> dict:
|
|
out = dict(base)
|
|
for k, v in over.items():
|
|
if isinstance(v, dict) and isinstance(out.get(k), dict):
|
|
out[k] = _deep_merge(out[k], v)
|
|
else:
|
|
out[k] = v
|
|
return out
|
|
|
|
|
|
def load_config() -> dict[str, Any]:
|
|
if CONFIG_PATH.exists():
|
|
with CONFIG_PATH.open("rb") as f:
|
|
user = tomllib.load(f)
|
|
return _deep_merge(DEFAULT_CONFIG, user)
|
|
return DEFAULT_CONFIG
|
|
|
|
|
|
def write_default_config(force: bool = False) -> Path:
|
|
import tomli_w
|
|
|
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
if CONFIG_PATH.exists() and not force:
|
|
return CONFIG_PATH
|
|
with CONFIG_PATH.open("wb") as f:
|
|
tomli_w.dump(DEFAULT_CONFIG, f)
|
|
return CONFIG_PATH
|