Lot 9 : dictée vocale tenue par la façade (LOKI_DICTATE=rust)

La façade sert /api/transcribe et /api/dictate/* quand LOKI_DICTATE=rust
(expérimental ; le Go reste le défaut) et supervise elle-même le serveur
de transcription sherpa-onnx : lancé à la demande sur la boucle locale,
GPU masqués et sans le jeton interne, prêt dès que sa socket répond, mort
au démarrage expliquée, éteint après dix minutes d'inactivité ou quand
les réglages changent. Modèles Parakeet téléchargés et décompressés au fil
de l'eau (tar.bz2, entrées hors du dossier refusées, liens ignorés),
protocole WebSocket identique (en-tête de 8 octets, trames de 10 Kio).

Côté Go, facade_dictate.go remet ces routes à la façade quand elles
arrivent par le tunnel du relais, et asrShutdownVite (libération de la
VRAM, optimiseur) lui demande l'arrêt par l'API interne.

Parité : parity_dictate_test.go tire du Go la lecture de treize WAV, les
chemins d'archive, réglages, catalogue, arguments et treize appels de
route, rejoués par tests/dictate.rs ; supervision testée contre un faux
sherpa-onnx qui parle le protocole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
Claude committed 2026-10-15 16:17:29 +00:00
1 parent 17ec2291d4
commit 46bfab3343
21 files changed
+2699 -19

No files matched your search

+19 -2
View File
@@ -121,6 +121,9 @@ fn serve() -> ExitCode {
// LOKI_MODELS=rust : les routes des modèles aussi (le Go remet alors
// celles du tunnel et ne vide plus les .part).
let models_rust = models_held_by_facade();
// LOKI_DICTATE=rust : la dictée vocale aussi (le Go lui demande alors
// d'arrêter son serveur quand il libère la VRAM).
let dictate_rust = held_by_facade("LOKI_DICTATE");
let mut env = vec![(auth::INTERNAL_ENV.to_string(), token.clone())];
if chat_rust {
env.push(("LOKI_CHAT".into(), "rust".into()));
@@ -128,7 +131,10 @@ fn serve() -> ExitCode {
if models_rust {
env.push(("LOKI_MODELS".into(), "rust".into()));
}
if chat_rust || models_rust {
if dictate_rust {
env.push(("LOKI_DICTATE".into(), "rust".into()));
}
if chat_rust || models_rust || dictate_rust {
env.push(("LOKI_FACADE_ADDR".into(), format!("127.0.0.1:{web_port}")));
}
let sup = child::Supervisor::spawn(go_bin, go_port, env);
@@ -167,6 +173,11 @@ fn serve() -> ExitCode {
None
};
let dictate = dictate_rust.then(|| {
eprintln!("[loki-rs] dictée tenue par la façade (LOKI_DICTATE=rust)");
std::sync::Arc::new(loki_rs::dictate::Dictation::new(&engine_paths.home))
});
let addr = SocketAddr::from(([0, 0, 0, 0], web_port));
let listener = match tokio::net::TcpListener::bind(addr).await {
Ok(l) => l,
@@ -184,6 +195,7 @@ fn serve() -> ExitCode {
engine: engine_paths,
chat,
models,
dictate,
};
let res = proxy::serve(listener, cfg, shutdown_signal()).await;
eprintln!("[loki-rs] arrêt demandé — arrêt du serveur Go");
@@ -205,7 +217,12 @@ fn chat_held_by_facade() -> bool {
/// `LOKI_MODELS=rust` : routes des modèles tenues par la façade.
fn models_held_by_facade() -> bool {
std::env::var("LOKI_MODELS").is_ok_and(|v| v.trim().eq_ignore_ascii_case("rust"))
held_by_facade("LOKI_MODELS")
}
/// `<VAR>=rust` : ce lot est tenu par la façade (le Go par défaut).
fn held_by_facade(var: &str) -> bool {
std::env::var(var).is_ok_and(|v| v.trim().eq_ignore_ascii_case("rust"))
}
/// Les `.part` d'un téléchargement tué ne reprendront jamais : vidés au