mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-14 02:38:20 +02:00
Chaque dictée relançait whisper-cli, qui relisait le modèle depuis le disque avant de transcrire. Ce chargement dominait le temps de réponse — 3,2 s de calcul pour 3,4 s d'audio — et aucun réglage ne pouvait le rattraper. whisper-server garde le modèle en mémoire entre deux phrases. Loki le supervise : démarrage au premier clic sur le micro, extinction après dix minutes sans dictée. Une dictée n'est pas un service permanent, et garder un modèle chargé toute la journée priverait le moteur de chat de sa VRAM. - Réglages serveur (modèle, langue, matériel, réactivité) dans bkState, avec leurs routes. La langue par défaut passe de « auto » à « fr » : sur quelques secondes d'audio la détection se trompe, et une langue mal détectée produit du charabia — des suites de caractères géorgiens ont été observées. - Catalogue de quatre modèles, de small à large-v3 ; large-v3-turbo par défaut. Téléchargement par identifiant, dans un .part renommé à la fin : un transfert interrompu ne laisse plus un .bin tronqué qu'on croirait bon. - Le GPU se choisit par CUDA_VISIBLE_DEVICES, posé en REMPLAÇANT toute valeur héritée. Dupliquée, la variable laisse le gagnant dépendre de la libc. - Les marqueurs de whisper ([BLANK_AUDIO], (silence)) ne sont plus collés dans le champ de saisie comme s'ils étaient du texte dicté. L'image construit DEUX binaires whisper-server, CPU et CUDA. LLAMACPP_IMAGE accepte la variante CPU de l'image amont ; sur cette base les .so CUDA sont absentes et un binaire lié à CUDA n'a même pas de quoi démarrer, donc aucun repli n'est possible depuis le programme. Le garde-fou du Dockerfile vérifie maintenant les deux étapes : les drapeaux de portabilité de la PR #18 doivent survivre à l'arrivée de CUDA. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3ewMAsXhkw9RY11kb9Dc9
88 lines
2.6 KiB
Go
88 lines
2.6 KiB
Go
package loki
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDictateCfgDefauts(t *testing.T) {
|
|
c := DictateCfg{}.withDefauts()
|
|
if c.Model != "large-v3-turbo-q5_0" {
|
|
t.Errorf("modèle par défaut = %q, attendu large-v3-turbo-q5_0", c.Model)
|
|
}
|
|
if c.Lang != "fr" {
|
|
t.Errorf("langue par défaut = %q, attendu fr — la détection auto se trompe sur les tranches courtes", c.Lang)
|
|
}
|
|
if c.Device != "cpu" {
|
|
t.Errorf("matériel par défaut = %q, attendu cpu (aucun GPU supposé)", c.Device)
|
|
}
|
|
if c.Reactivity != "moyen" {
|
|
t.Errorf("réactivité par défaut = %q, attendu moyen", c.Reactivity)
|
|
}
|
|
}
|
|
|
|
func TestCudaVisibleDevices(t *testing.T) {
|
|
cas := []struct{ device, want string }{
|
|
{"cpu", ""},
|
|
{"0", "0"},
|
|
{"1", "1"},
|
|
{"", ""},
|
|
{"bidon", ""},
|
|
{"-1", ""},
|
|
}
|
|
for _, c := range cas {
|
|
got := DictateCfg{Device: c.device}.cudaVisibleDevices()
|
|
if got != c.want {
|
|
t.Errorf("cudaVisibleDevices(%q) = %q, attendu %q", c.device, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChunkBounds(t *testing.T) {
|
|
cas := []struct {
|
|
react string
|
|
min, max time.Duration
|
|
}{
|
|
{"court", 1000 * time.Millisecond, 5 * time.Second},
|
|
{"moyen", 1500 * time.Millisecond, 8 * time.Second},
|
|
{"long", 3 * time.Second, 15 * time.Second},
|
|
{"inconnu", 1500 * time.Millisecond, 8 * time.Second},
|
|
}
|
|
for _, c := range cas {
|
|
min, max := DictateCfg{Reactivity: c.react}.chunkBounds()
|
|
if min != c.min || max != c.max {
|
|
t.Errorf("chunkBounds(%q) = %v/%v, attendu %v/%v", c.react, min, max, c.min, c.max)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDictateCfgAllerRetour(t *testing.T) {
|
|
testHome(t)
|
|
in := DictateCfg{Model: "medium-q5_0", Lang: "en", Device: "1", Reactivity: "long"}
|
|
if err := dictateCfgSave(in); err != nil {
|
|
t.Fatalf("enregistrement : %v", err)
|
|
}
|
|
if out := dictateCfgLoad(); out != in {
|
|
t.Errorf("relu %+v, attendu %+v", out, in)
|
|
}
|
|
}
|
|
|
|
// Un modèle hors catalogue accepté en silence ne se manifesterait qu'au premier
|
|
// clic sur le micro, loin du geste qui l'a causé.
|
|
func TestDictateCfgSaveRefuseModeleInconnu(t *testing.T) {
|
|
testHome(t)
|
|
if err := dictateCfgSave(DictateCfg{Model: "modele-inexistant", Lang: "fr", Device: "cpu", Reactivity: "moyen"}); err == nil {
|
|
t.Error("un modèle hors catalogue doit être refusé")
|
|
}
|
|
}
|
|
|
|
// Sans réglage enregistré, la lecture doit rendre les défauts — et non le zéro
|
|
// de la structure, qui donnerait un modèle vide à whisper-server.
|
|
func TestDictateCfgLoadSansRien(t *testing.T) {
|
|
testHome(t)
|
|
c := dictateCfgLoad()
|
|
if c.Model == "" || c.Lang == "" || c.Device == "" || c.Reactivity == "" {
|
|
t.Errorf("lecture à vide = %+v, aucun champ ne doit rester vide", c)
|
|
}
|
|
}
|