mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 04:34:41 +02:00
feat(stt): lancement & cycle de vie du client — auto-start, app de bureau, auto-update (#18)
* feat(stt): auto-start via `stt --install-service` (phase 6) Ajoute `stt --install-service` / `--uninstall-service` : génère et active un service systemd --user (lié à graphical-session.target) qui lance le HUD + voix à l'ouverture de session. Le kiosk existant (ui.kiosk) fournit l'« écran Jarvis ». - cli : _service_unit (génération du unit, fonction pure testable), _cmd_install_service (écrit le unit + import-environment DISPLAY/WAYLAND + daemon-reload/enable/start), _cmd_uninstall_service. - Docs : admin/ia/stt.md (phase 6 → ✅, section Auto-start + caveat DISPLAY), stt/README. - Bump client 0.2.1 → 0.3.0. Phase 6 de la roadmap STT. Reste : test sur poste, outils Hermes (7). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa * feat(stt): app de bureau (fenêtre type Discord) + auto-update Rend STT lançable comme une application de bureau et ajoute la mise à jour intégrée. - Mode fenêtre « app » : _open_browser ouvre le HUD en fenêtre chromeless (--app=, type Discord) en plus du kiosk plein écran ; support Brave ajouté. Sélection via --window {app,kiosk,none} ou [ui].window_mode. - stt --install-desktop / --uninstall-desktop : entrée .desktop + icône (stt/hud/icon.svg, aussi favicon du HUD) → STT dans le menu/dock, lance --window app. - stt --update : pipx reinstall depuis la source git (via clé SSH), affiche « ancienne → nouvelle » ; stt --version. - Docs (admin/ia/stt.md, stt/README) + config (window_mode) + bump 0.3.0 → 0.4.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa * fix(stt): détection navigateur cross-distro (AlmaLinux/Fedora + Debian/Ubuntu) _open_browser tente les binaires natifs (rpm + deb : brave-browser / chromium / chromium-browser / google-chrome…) puis Flatpak (com.brave.Browser, org.chromium.Chromium, com.google.Chrome…), sinon repli sur le navigateur par défaut. systemd --user, lanceurs .desktop et pipx sont déjà identiques sur les deux familles. Doc : note de compatibilité distros. Bump 0.4.0 → 0.4.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa * docs(stt): journal 2026-06-18 + PROGRESS + ligne STT du CLAUDE.md - progress/2026-06-18.md : HUD avancé, auto-start, app de bureau, auto-update, cross-distro - PROGRESS.md : entrée du jour - CLAUDE.md : ligne STT enrichie (HUD, auto-start, app de bureau, auto-update) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa * feat(stt): fenêtre app autonome (profil + WM class dédiés, hors session Brave) Le mode app s'attachait à la session Brave de l'utilisateur (même profil/icône) → « ça sent le Brave ». On lance désormais une instance séparée : --class=STT-Funk + --user-data-dir dédié (~/.local/share/stt/app-profile), et le .desktop déclare StartupWMClass=STT-Funk → fenêtre autonome avec sa propre icône de barre des tâches. (Flatpak : --class seul, sandbox.) Une vraie fenêtre native (pywebview) reste une option ultérieure. Bump 0.4.1 → 0.4.2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2658f92c2f
commit
5f1e34dad9
12 changed files with 444 additions and 17 deletions
|
|
@ -17,7 +17,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||
- ✅ postfix_relay — SMTP sortant via Gmail SMTP + App Password (aliyesilkaya93@gmail.com)
|
||||
- ✅ Open WebUI opérationnel — `openwebui.lab.local`, namespace `ai`, backend LiteLLM
|
||||
- ✅ n8n opérationnel — `n8n.lab.local`, namespace `ai`, 3 workflows actifs (alertes, rapport horaire, Hermes Doc Worker)
|
||||
- ✅ STT (assistant vocal « Jarvis ») — STT-server in-cluster (`stt.lab.local`, namespace `ai`) + client `stt` (pipx) ; mémoire long-terme Qdrant `stt-memory` validée 2026-06-17 (voir `admin/ia/stt.md`)
|
||||
- ✅ STT (assistant vocal « Jarvis ») — STT-server in-cluster (`stt.lab.local`, namespace `ai`) + client `stt` (pipx) : HUD avancé (design Claude Design), auto-start systemd `--user`, app de bureau (`stt --install-desktop`, fenêtre type Discord) + auto-update (`stt --update`) ; mémoire long-terme Qdrant `stt-memory` validée 2026-06-17 (voir `admin/ia/stt.md`)
|
||||
- ✅ Ghostfolio opérationnel — `ghostfolio.lab.local`, namespace `ai`, suivi de portefeuille (PostgreSQL s01 + Redis in-cluster)
|
||||
- ✅ hermes-auto-improve — analyse quotidienne de `admin/` par Hermes à 22h (n8n → trigger :9095), PR sur `hermes/daily-work`
|
||||
- ⏳ WOL compute nodes non configuré (BIOS) — allumage manuel requis
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@
|
|||
| [2026-05-13](progress/2026-05-13.md) | Stabilisation multi-modèles — OOM fixes, Qwen3-1.7B sur 14 CPUs (21 tok/s), profils system/monitor finalisés, funk-ai par défaut |
|
||||
| [2026-05-14](progress/2026-05-14.md) | Monitoring complet + fix webhook AlertManager→Hermes (BrokenPipeError). RAG opérationnel : Qdrant 284 chunks, rag-ingest/rag-query, skill funk-ai. Souls révisés (4 profils). Docs : alertmanager-webhook, rag, hermes-souls |
|
||||
| [2026-05-29](progress/2026-05-29.md) | Migration `/srv/data` RAID5 dégradé → NVMe (sdb ATA errors). Tous les services sur NVMe, bind-mount `/home/data` |
|
||||
| [2026-06-18](progress/2026-06-18.md) | STT : HUD avancé (design Claude Design, #17), auto-start systemd, app de bureau (`.desktop` + mode fenêtre), auto-update (`stt --update`), compat AlmaLinux/Debian. Passe de cohérence doc (#15, #16) |
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ Objectif ultérieur : « agir sur le homelab » via les outils de Hermes (phase
|
|||
> ✅ **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) implémenté (design **Claude Design**) : avatar portrait
|
||||
> réactif (anneau/ping/spinner), transcript à bulles, drawer Réglages **câblé au backend**
|
||||
> (reset / mode cerveau / mot de réveil à chaud). Reste : test audio bout-en-bout sur le poste
|
||||
> (phase 1), portraits réels + test HUD sur cible, auto-start (6), outils Hermes (7).
|
||||
> ✅ **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`.
|
||||
> Reste : test audio bout-en-bout (phase 1), portraits réels + test sur cible, outils Hermes (7).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ Objectif ultérieur : « agir sur le homelab » via les outils de Hermes (phase
|
|||
| 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 | pattern `systemd --user` (`tools/hermes-voice/install-service.sh`) | ✅ éprouvé |
|
||||
| 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érer) |
|
||||
|
||||
Maillon réellement manquant : **l'interface graphique HUD** + le packaging + la mémoire perso.
|
||||
|
|
@ -157,6 +158,71 @@ Côté client, pilotable depuis `stt/client/config/` + l'écran de réglages du
|
|||
|
||||
---
|
||||
|
||||
## Auto-start (service systemd --user)
|
||||
|
||||
Lancer le HUD + voix automatiquement à l'ouverture de session (écran « Jarvis ») :
|
||||
|
||||
```bash
|
||||
stt --install-service # écrit ~/.config/systemd/user/stt.service, enable + start
|
||||
stt --uninstall-service # retire le service
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
**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) :
|
||||
|
||||
```bash
|
||||
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) :
|
||||
|
||||
```bash
|
||||
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 |
|
||||
|
|
@ -171,7 +237,7 @@ Côté client, pilotable depuis `stt/client/config/` + l'écran de réglages du
|
|||
| **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) |
|
||||
| **6 — Auto-start client** | `install-service.sh` (systemd --user) + kiosk | ⏳ |
|
||||
| **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)
|
||||
|
|
@ -179,8 +245,9 @@ Côté client, pilotable depuis `stt/client/config/` + l'écran de réglages du
|
|||
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). Reste côté poste : test audio bout-en-bout
|
||||
(micro → STT → serveur → TTS) et test du HUD avec portraits réels.
|
||||
(design Claude Design, câblé au websocket backend) ; auto-start (6) via `stt --install-service`.
|
||||
Reste côté poste : test audio bout-en-bout (micro → STT → serveur → TTS) et test du HUD/kiosk
|
||||
avec portraits réels.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
80
progress/2026-06-18.md
Normal file
80
progress/2026-06-18.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# 2026-06-18 — STT : HUD avancé, app de bureau & auto-update
|
||||
|
||||
## Résumé
|
||||
|
||||
Grosse itération sur le **client STT** (`stt/client/`) : intégration du HUD « design Claude
|
||||
Design » câblé au backend (phase 4, mergé #17), puis auto-start systemd, application de bureau
|
||||
(lanceur `.desktop` + mode fenêtre), auto-update intégrée et compatibilité cross-distro
|
||||
(AlmaLinux/Fedora + Debian/Ubuntu). En préambule, une passe de cohérence documentaire (#15, #16).
|
||||
|
||||
---
|
||||
|
||||
## HUD avancé (phase 4) — mergé (#17)
|
||||
|
||||
Remplacement du HUD minimal (cercle CSS bleu) par le design Claude Design : avatar portrait
|
||||
réactif (anneau + ping d'écoute + spinner de réflexion), transcript à bulles avec tag du modèle,
|
||||
indicateur de connexion + reconnexion auto, drawer Réglages.
|
||||
|
||||
- HTML/CSS/JS **sans dépendance externe** (livrable par pipx, offline). `DEMO=false` branché
|
||||
sur le websocket existant (`state` / `user` / `assistant` / `error` sur `:9300`).
|
||||
- Réglages **câblés au backend** (`_apply_settings`) : `reset`, mode cerveau (→ modèle LiteLLM),
|
||||
mot de réveil — appliqués à chaud. Visuels (intensité, avatar) en localStorage.
|
||||
- Portraits dans `hud/avatars/`, repli sur l'initiale (« あ ») si absents.
|
||||
- Fix « ancien HUD persistant » : `Cache-Control: no-store` + URL kiosk cache-bustée (`?v=`).
|
||||
|
||||
> Piège du jour : après réinstall, Brave resservait l'ancien HUD depuis son cache, et pipx/uv
|
||||
> réutilisait l'ancien build car la version n'avait pas changé → d'où la discipline **bump de
|
||||
> version à chaque release** + en-têtes no-cache.
|
||||
|
||||
---
|
||||
|
||||
## Auto-start (phase 6)
|
||||
|
||||
`stt --install-service` / `--uninstall-service` : génère un service `systemd --user` lié à
|
||||
`graphical-session.target` (lance HUD + voix à l'ouverture de session, kiosk), avec
|
||||
`import-environment DISPLAY/WAYLAND` pour le kiosk. Caveat DISPLAY documenté dans `admin/ia/stt.md`.
|
||||
|
||||
---
|
||||
|
||||
## Application de bureau + auto-update
|
||||
|
||||
- **Mode fenêtre « app »** : `_open_browser` ouvre le HUD en fenêtre chromeless (`--app=`, type
|
||||
Discord) en plus du kiosk. Sélection via `--window {app,kiosk,none}` ou `[ui].window_mode`.
|
||||
- **Lanceur de bureau** : `stt --install-desktop` → entrée `.desktop` + icône (`hud/icon.svg`,
|
||||
aussi favicon) dans le menu d'applis, épinglable au dock. Exécute `stt --window app`.
|
||||
- **Auto-update** : `stt --update` (pipx reinstall depuis git via SSH, affiche `ancienne → nouvelle`)
|
||||
+ `stt --version`. Plus de désinstall/réinstall manuel.
|
||||
|
||||
> ⚠️ Service kiosk et appli de bureau partagent les ports 9300/9301 → un seul à la fois.
|
||||
|
||||
---
|
||||
|
||||
## Compatibilité cross-distro
|
||||
|
||||
Tout le client repose sur des standards communs (systemd --user, `.desktop` XDG, pipx) → identique
|
||||
sur **AlmaLinux/Fedora (RHEL)** et **Debian/Ubuntu**. Seul le navigateur diffère : `_open_browser`
|
||||
tente les binaires natifs (rpm + deb) puis **Flatpak**, sinon repli sur le navigateur par défaut.
|
||||
|
||||
---
|
||||
|
||||
## Passe de cohérence documentaire (#15, #16)
|
||||
|
||||
- **#15** : cohérence `admin/` + README/CLAUDE (STT, Ghostfolio, embeddings nomic `:1238`, Qdrant
|
||||
réparé / `funk-docs` à re-ingérer), archivage de `Funk/` legacy (LM Studio / Goose).
|
||||
- **#16** : ajout du **switch réseau GS308EV4** au schéma d'architecture du README (+ table de
|
||||
câblage, ports/VLAN, mode Access).
|
||||
|
||||
---
|
||||
|
||||
## Versions client
|
||||
|
||||
`0.1.0 → 0.4.1` : **0.2.0** (HUD) · **0.3.0** (auto-start) · **0.4.0** (app + update) · **0.4.1** (cross-distro).
|
||||
|
||||
---
|
||||
|
||||
## Prochaines étapes
|
||||
|
||||
- Tester le HUD/kiosk + audio bout-en-bout (micro → STT → serveur → TTS) sur le poste.
|
||||
- Déposer les vrais portraits dans `hud/avatars/`.
|
||||
- **Phase 7** — outils Hermes (« agir sur Funk » via gateway `:8080`, API à spécifier).
|
||||
- Re-`rag-ingest` de la collection `funk-docs` (toujours en attente).
|
||||
|
|
@ -30,6 +30,10 @@ stt --setup # génère ~/.config/stt/stt.toml (dont [server] url)
|
|||
stt --text # chat texte simple (sans micro ni HUD) — idéal pour tester
|
||||
stt # voix + HUD ; dis "Ok Hermès …"
|
||||
stt --model claude # choisir le modèle : hermes (défaut) | claude | qwen | opus
|
||||
stt --install-service # auto-start au login (systemd --user, kiosk « écran Jarvis »)
|
||||
stt --install-desktop # ajoute STT au menu des applis → fenêtre dédiée (type Discord)
|
||||
stt --window app # ouvre le HUD en fenêtre app (sinon : kiosk plein écran)
|
||||
stt --update # met à jour depuis git (plus de désinstall/réinstall manuel)
|
||||
```
|
||||
En mode texte : commandes `/model <nom>`, `/models`, `/quit`.
|
||||
Prérequis voix : micro, Piper + voix dans `~/.local/share/piper/`, `aplay` (alsa-utils).
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ chat_timeout_sec = 60
|
|||
[ui]
|
||||
theme = "arc-reactor"
|
||||
avatar = "default"
|
||||
kiosk = true # ouvre le HUD en plein écran (kiosk) au lancement
|
||||
kiosk = true # (compat) plein écran si window_mode absent
|
||||
window_mode = "kiosk" # "app" (fenêtre, type Discord) | "kiosk" (plein écran) | "none"
|
||||
ws_host = "127.0.0.1"
|
||||
ws_port = 9300 # websocket états ↔ HUD (doit matcher hud/index.html)
|
||||
http_port = 9301 # HTTP statique qui sert le HUD
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "stt"
|
||||
version = "0.2.1"
|
||||
version = "0.4.2"
|
||||
description = "STT — client vocal Jarvis du homelab Funk (voix + HUD, parle au STT-server)"
|
||||
requires-python = ">=3.11"
|
||||
readme = "README.md"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
|
|
@ -22,6 +23,193 @@ def _cmd_setup() -> int:
|
|||
return 0
|
||||
|
||||
|
||||
SERVICE_NAME = "stt.service"
|
||||
|
||||
|
||||
def _service_unit(stt_bin: str) -> str:
|
||||
"""Contenu du unit systemd --user : lance `stt` (HUD + voix) avec la session graphique."""
|
||||
return f"""[Unit]
|
||||
Description=STT — Assistant vocal Jarvis (HUD + voix)
|
||||
After=graphical-session.target
|
||||
PartOf=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart={stt_bin}
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=stt
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
"""
|
||||
|
||||
|
||||
def _cmd_install_service() -> int:
|
||||
"""Installe et démarre le service systemd --user (auto-start au login + kiosk)."""
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
stt_bin = shutil.which("stt") or os.path.realpath(sys.argv[0])
|
||||
unit_dir = Path.home() / ".config" / "systemd" / "user"
|
||||
unit_dir.mkdir(parents=True, exist_ok=True)
|
||||
unit_path = unit_dir / SERVICE_NAME
|
||||
unit_path.write_text(_service_unit(stt_bin))
|
||||
print(f"✓ Unit écrit : {unit_path}")
|
||||
print(f" ExecStart = {stt_bin}")
|
||||
|
||||
def sc(*args):
|
||||
subprocess.run(["systemctl", "--user", *args], check=False)
|
||||
|
||||
# Rendre DISPLAY/WAYLAND dispo au gestionnaire systemd --user (pour le kiosk)
|
||||
subprocess.run(
|
||||
["systemctl", "--user", "import-environment",
|
||||
"DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY"],
|
||||
check=False,
|
||||
)
|
||||
sc("daemon-reload")
|
||||
sc("enable", SERVICE_NAME)
|
||||
sc("start", SERVICE_NAME)
|
||||
|
||||
print("\n✓ Service activé et démarré (systemd --user).")
|
||||
print("\nCommandes utiles :")
|
||||
print(" systemctl --user status stt")
|
||||
print(" journalctl --user -u stt -f")
|
||||
print(" systemctl --user restart stt")
|
||||
print(" stt --uninstall-service")
|
||||
print("\nDémarrer dès le boot (kiosk dédié, sans login manuel) :")
|
||||
print(' sudo loginctl enable-linger "$USER" # + auto-login du bureau')
|
||||
print("\nLe HUD s'ouvre en kiosk si [ui].kiosk = true (défaut).")
|
||||
print("Si l'écran ne s'ouvre pas : voir le caveat DISPLAY dans admin/ia/stt.md.")
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_uninstall_service() -> int:
|
||||
"""Arrête, désactive et supprime le service systemd --user."""
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
def sc(*args):
|
||||
subprocess.run(["systemctl", "--user", *args], check=False)
|
||||
|
||||
sc("stop", SERVICE_NAME)
|
||||
sc("disable", SERVICE_NAME)
|
||||
unit_path = Path.home() / ".config" / "systemd" / "user" / SERVICE_NAME
|
||||
if unit_path.exists():
|
||||
unit_path.unlink()
|
||||
print(f"✓ Unit supprimé : {unit_path}")
|
||||
else:
|
||||
print("(unit déjà absent)")
|
||||
sc("daemon-reload")
|
||||
print("✓ Service STT désinstallé.")
|
||||
return 0
|
||||
|
||||
|
||||
def _desktop_entry(stt_bin: str, icon_path: str) -> str:
|
||||
"""Contenu du lanceur .desktop : STT comme application de bureau (fenêtre app)."""
|
||||
return f"""[Desktop Entry]
|
||||
Type=Application
|
||||
Name=STT — Assistant vocal
|
||||
GenericName=Assistant vocal
|
||||
Comment=Assistant vocal Jarvis du homelab Funk
|
||||
Exec={stt_bin} --window app
|
||||
Icon={icon_path}
|
||||
Terminal=false
|
||||
Categories=Utility;AudioVideo;
|
||||
Keywords=jarvis;assistant;voix;hermes;
|
||||
StartupNotify=true
|
||||
StartupWMClass=STT-Funk
|
||||
"""
|
||||
|
||||
|
||||
def _cmd_install_desktop() -> int:
|
||||
"""Crée l'entrée de menu (.desktop) + icône → STT lançable comme une appli (type Discord)."""
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
stt_bin = shutil.which("stt") or os.path.realpath(sys.argv[0])
|
||||
icon_path = Path(__file__).resolve().parent / "hud" / "icon.svg"
|
||||
apps_dir = Path.home() / ".local" / "share" / "applications"
|
||||
apps_dir.mkdir(parents=True, exist_ok=True)
|
||||
entry = apps_dir / "stt.desktop"
|
||||
entry.write_text(_desktop_entry(stt_bin, str(icon_path)))
|
||||
entry.chmod(0o755)
|
||||
subprocess.run(["update-desktop-database", str(apps_dir)], check=False)
|
||||
|
||||
print(f"✓ Lanceur : {entry}")
|
||||
print(f" Icône : {icon_path}")
|
||||
print(f" Exec : {stt_bin} --window app")
|
||||
print("\nSTT apparaît dans le menu des applications (épinglable au dock).")
|
||||
print("Il s'ouvre en fenêtre « app » (sans onglets), comme Discord.")
|
||||
print("Retirer : stt --uninstall-desktop")
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_uninstall_desktop() -> int:
|
||||
"""Supprime le lanceur de bureau."""
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
entry = Path.home() / ".local" / "share" / "applications" / "stt.desktop"
|
||||
if entry.exists():
|
||||
entry.unlink()
|
||||
print(f"✓ Lanceur supprimé : {entry}")
|
||||
else:
|
||||
print("(lanceur déjà absent)")
|
||||
subprocess.run(["update-desktop-database", str(entry.parent)], check=False)
|
||||
return 0
|
||||
|
||||
|
||||
def _installed_version() -> str | None:
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
try:
|
||||
return version("stt")
|
||||
except PackageNotFoundError:
|
||||
return None
|
||||
|
||||
|
||||
def _cmd_version() -> int:
|
||||
print(_installed_version() or "version inconnue (pas installé via pip/pipx)")
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_update() -> int:
|
||||
"""Met à jour STT depuis sa source git (pipx reinstall) — sans désinstall/réinstall manuel."""
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
pipx = shutil.which("pipx")
|
||||
if not pipx:
|
||||
print("pipx introuvable — mise à jour automatique impossible.")
|
||||
print("Réinstalle manuellement depuis la source git.")
|
||||
return 1
|
||||
before = _installed_version()
|
||||
if before:
|
||||
print(f"Version installée : {before}")
|
||||
print("Récupération de la dernière version depuis git (pipx reinstall)…\n")
|
||||
res = subprocess.run([pipx, "reinstall", "stt"], capture_output=True, text=True)
|
||||
out = ((res.stdout or "") + (res.stderr or "")).strip()
|
||||
if out:
|
||||
print(out)
|
||||
m = re.search(r"installed package stt ([0-9][^\s,]*)", out)
|
||||
after = m.group(1) if m else None
|
||||
print()
|
||||
if after and before and after != before:
|
||||
print(f"✓ Mis à jour : {before} → {after}")
|
||||
elif after:
|
||||
print(f"✓ Déjà à jour ({after}).")
|
||||
else:
|
||||
print("✓ Réinstallation terminée — vérifie avec : stt --version")
|
||||
return res.returncode
|
||||
|
||||
|
||||
def _make_client(args):
|
||||
from stt.api import ServerClient
|
||||
from stt.config import load_config, resolve_model
|
||||
|
|
@ -85,6 +273,11 @@ def _cmd_voice(args) -> int:
|
|||
from stt.voice import VoiceEngine
|
||||
|
||||
cfg, client = _make_client(args)
|
||||
# mode d'ouverture du HUD : --window > config window_mode > kiosk (compat) > none
|
||||
ui = cfg["ui"]
|
||||
ui["window_mode"] = (
|
||||
args.window or ui.get("window_mode") or ("kiosk" if ui.get("kiosk") else "none")
|
||||
)
|
||||
print(f"STT-server : {client.url} — modèle '{client.model}'")
|
||||
if not client.healthy():
|
||||
print(" ⚠️ serveur injoignable — vérifie [server] url et le réseau")
|
||||
|
|
@ -116,10 +309,35 @@ def main(argv: list[str] | None = None) -> int:
|
|||
p.add_argument("--model", help="Modèle : hermes, claude, qwen, opus (ou alias LiteLLM)")
|
||||
p.add_argument("--server", help="Force l'URL du STT-server (sinon config)")
|
||||
p.add_argument("--no-tts", action="store_true", help="Réponses texte seulement (voix)")
|
||||
p.add_argument("--window", choices=["app", "kiosk", "none"],
|
||||
help="Ouverture du HUD : app (fenêtre, type Discord) | kiosk (plein écran) | none")
|
||||
p.add_argument("--install-service", action="store_true",
|
||||
help="Installe le service systemd --user (auto-start au login + kiosk)")
|
||||
p.add_argument("--uninstall-service", action="store_true",
|
||||
help="Désinstalle le service systemd --user")
|
||||
p.add_argument("--install-desktop", action="store_true",
|
||||
help="Crée le lanceur de bureau (.desktop + icône) — appli type Discord")
|
||||
p.add_argument("--uninstall-desktop", action="store_true",
|
||||
help="Supprime le lanceur de bureau")
|
||||
p.add_argument("--update", action="store_true",
|
||||
help="Met à jour STT depuis git (pipx reinstall)")
|
||||
p.add_argument("--version", action="store_true", help="Affiche la version installée")
|
||||
args = p.parse_args(argv)
|
||||
|
||||
if args.version:
|
||||
return _cmd_version()
|
||||
if args.setup:
|
||||
return _cmd_setup()
|
||||
if args.update:
|
||||
return _cmd_update()
|
||||
if args.install_service:
|
||||
return _cmd_install_service()
|
||||
if args.uninstall_service:
|
||||
return _cmd_uninstall_service()
|
||||
if args.install_desktop:
|
||||
return _cmd_install_desktop()
|
||||
if args.uninstall_desktop:
|
||||
return _cmd_uninstall_desktop()
|
||||
if args.text:
|
||||
return _cmd_text(args)
|
||||
return _cmd_voice(args)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ DEFAULT_CONFIG: dict[str, Any] = {
|
|||
"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,
|
||||
|
|
|
|||
13
stt/client/stt/hud/icon.svg
Normal file
13
stt/client/stt/hud/icon.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="STT — Assistant vocal">
|
||||
<defs>
|
||||
<radialGradient id="bg" cx="50%" cy="38%" r="75%">
|
||||
<stop offset="0%" stop-color="#10202f"/>
|
||||
<stop offset="100%" stop-color="#070b12"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="30" fill="url(#bg)"/>
|
||||
<circle cx="64" cy="64" r="38" fill="none" stroke="#ff5d6c" stroke-width="6"/>
|
||||
<circle cx="64" cy="64" r="24" fill="none" stroke="#f0c9cf" stroke-width="3" opacity=".65"/>
|
||||
<circle cx="64" cy="64" r="6" fill="#ff5d6c"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 658 B |
|
|
@ -4,6 +4,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>STT — Assistant vocal</title>
|
||||
<link rel="icon" href="icon.svg" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
THÈME — variables CSS faciles à régler
|
||||
|
|
|
|||
|
|
@ -97,10 +97,11 @@ async def serve(cfg: dict[str, Any], make_engine, client) -> None:
|
|||
async with websockets.serve(handler, ui["ws_host"], ui["ws_port"]):
|
||||
url = f"http://{ui['ws_host']}:{ui['http_port']}/"
|
||||
print(f" HUD : {url}")
|
||||
if ui.get("kiosk"):
|
||||
mode = ui.get("window_mode") or ("kiosk" if ui.get("kiosk") else "none")
|
||||
if mode != "none":
|
||||
# ?v=<timestamp> : URL unique à chaque lancement → le navigateur ne peut
|
||||
# pas servir une version cachée du HUD (« ancien HUD persistant »).
|
||||
_open_browser(f"{url}?v={int(time.time())}", kiosk=True)
|
||||
_open_browser(f"{url}?v={int(time.time())}", mode)
|
||||
await asyncio.Future() # tourne indéfiniment
|
||||
|
||||
|
||||
|
|
@ -112,16 +113,54 @@ def _run_engine(engine, emit) -> None:
|
|||
emit({"type": "error", "text": f"moteur vocal : {e}"})
|
||||
|
||||
|
||||
def _open_browser(url: str, kiosk: bool) -> None:
|
||||
def _open_browser(url: str, mode: str) -> None:
|
||||
"""Ouvre le HUD comme une fenêtre d'application dédiée (pas la session Brave courante).
|
||||
|
||||
On force un profil + une WM class propres (`--class=STT-Funk`, `--user-data-dir`) → la
|
||||
fenêtre est une instance séparée, avec sa propre icône / entrée de barre des tâches
|
||||
(associée au lanceur .desktop via `StartupWMClass`), au lieu de se fondre dans Brave.
|
||||
mode : 'app' (fenêtre chromeless type Discord) | 'kiosk' (plein écran).
|
||||
Cross-distro : binaire natif (rpm/deb) → Flatpak → repli navigateur par défaut.
|
||||
"""
|
||||
import shutil
|
||||
import subprocess
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
|
||||
for browser in ("chromium", "google-chrome", "chromium-browser"):
|
||||
profile = Path.home() / ".local" / "share" / "stt" / "app-profile"
|
||||
profile.mkdir(parents=True, exist_ok=True)
|
||||
win = ["--kiosk", url] if mode == "kiosk" else [f"--app={url}"]
|
||||
# Instance autonome (profil + classe dédiés) — pour les navigateurs natifs.
|
||||
native = [
|
||||
"--class=STT-Funk", f"--user-data-dir={profile}",
|
||||
"--no-first-run", "--no-default-browser-check", *win,
|
||||
]
|
||||
# Flatpak est déjà sandboxé : pas de --user-data-dir (chemin hôte inaccessible).
|
||||
flatpak = ["--class=STT-Funk", *win]
|
||||
|
||||
# 1) Binaire natif (rpm/deb)
|
||||
for browser in (
|
||||
"brave-browser", "brave",
|
||||
"chromium", "chromium-browser",
|
||||
"google-chrome", "google-chrome-stable",
|
||||
"microsoft-edge", "vivaldi",
|
||||
):
|
||||
if shutil.which(browser):
|
||||
flag = "--kiosk" if kiosk else "--new-window"
|
||||
subprocess.Popen([browser, flag, url])
|
||||
subprocess.Popen([browser, *native])
|
||||
return
|
||||
|
||||
# 2) Flatpak (universel — souvent la voie sur Fedora/AlmaLinux)
|
||||
if shutil.which("flatpak"):
|
||||
for appid in ("com.brave.Browser", "org.chromium.Chromium",
|
||||
"com.google.Chrome", "com.microsoft.Edge"):
|
||||
installed = subprocess.run(
|
||||
["flatpak", "info", appid], capture_output=True
|
||||
).returncode == 0
|
||||
if installed:
|
||||
subprocess.Popen(["flatpak", "run", appid, *flatpak])
|
||||
return
|
||||
|
||||
# 3) Repli : onglet dans le navigateur par défaut
|
||||
webbrowser.open(url)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue