mirror of
https://github.com/R0m1k3/EveFlow.git
synced 2026-10-23 07:08:14 +02:00
feat: fin de phrase Silero, vision d'écran et actions locales (v2.3.0) (#13)
Écoute permanente - Silero VAD dans le worker sherpa-onnx (vad.start/audio/stop) ; le renderer envoie des trames de 128 ms pendant la commande et reçoit le segment WAV complet (speech-start / segment). Repli sur le VAD énergétique si le modèle n'est pas installé ou si l'option est désactivée. - Modèle « silero-vad » (0,6 Mo) dans le catalogue, section « Fin de phrase » dans Modèles locaux, réglage dans Paramètres → Micro. Vision d'écran - IPC system:screen-capture (desktopCapturer, JPEG 1600 px) ; bouton dans la barre de commande ; « regarde mon écran… » joint la capture à la question envoyée à Hermes (transport chat completions pour les images). Actions locales - IPC system:action à liste blanche : verrouiller la session, touches média (volume, mute, lecture, piste), ouvrir une application connue ou une URL http(s), presse-papiers, recherche de fichiers dans les dossiers utilisateur. - Routeur d'intentions FR/EN exécuté avant Hermes (« coupe le son », « ouvre Spotify », « verrouille la session »…), résultat affiché et lu ; désactivable. Correctif - Chat completions : les premiers fragments SSE arrivés avant l'événement de démarrage étaient perdus (premier mot manquant) ; ils sont maintenant rejoués. Tests : 26 tests unitaires (intentions locales ajoutées), e2e Electron sous Xvfb avec les vrais modèles (Silero : un segment par phrase, 6,7 s d'audio traités en 190 ms ; capture 109 ko reçue par Hermes ; intention locale traitée sans Hermes). Claude-Session: https://claude.ai/code/session_017Wn5VX9HNbJ7N54hR24u9Y Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
4f6074183f
commit
af8bdc5e9d
24 files changed
+691
-47
No files matched your search
@@ -113,8 +113,21 @@ class VoiceController {
|
||||
this.unsubscribeKws?.();
|
||||
this.unsubscribeKws = api.voice.onKwsDetected((d) => this.onWakeDetected(d.keyword));
|
||||
const sensitivity = Math.min(5, Math.max(1, Math.round(settings.sensitivity))) as 1 | 2 | 3 | 4 | 5;
|
||||
let neuralVad = false;
|
||||
if (settings.neuralVad) {
|
||||
try {
|
||||
const installed = (await api.voice.listModels()).some((m) => m.id === 'silero-vad' && m.installed);
|
||||
if (installed) {
|
||||
await api.voice.vadStart({ modelId: 'silero-vad', silenceMs: Math.max(300, Math.min(1500, settings.silenceMs - 200)), threshold: 0.5, maxUtteranceSec: 25 });
|
||||
neuralVad = true;
|
||||
}
|
||||
} catch (err) {
|
||||
Log.warn('voice', `silero unavailable, energy VAD fallback: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
await this.wake.start({
|
||||
deviceId: settings.micDeviceId || undefined,
|
||||
neuralVad,
|
||||
vad: { silenceMs: settings.silenceMs, speechRatio: SENSITIVITY_RATIO[sensitivity], minRms: SENSITIVITY_MIN_RMS[sensitivity] },
|
||||
callbacks: {
|
||||
onPhase: (phase) => {
|
||||
@@ -141,7 +154,8 @@ class VoiceController {
|
||||
}
|
||||
});
|
||||
voice.setWake('spotting', result.accepted.map((k) => k.replace(/_/g, ' ')));
|
||||
Log.info('voice', `wake mode on: ${result.accepted.join(', ')}${result.rejected.length ? ` (rejetés : ${result.rejected.join(', ')})` : ''}`);
|
||||
voice.setNeuralVad(neuralVad);
|
||||
Log.info('voice', `wake mode on: ${result.accepted.join(', ')}${result.rejected.length ? ` (rejetés : ${result.rejected.join(', ')})` : ''}${neuralVad ? ' · Silero' : ''}`);
|
||||
} catch (err) {
|
||||
const message = (err as Error).message;
|
||||
voice.setWake('error');
|
||||
@@ -149,6 +163,7 @@ class VoiceController {
|
||||
useChat.getState().setError(`Mot d’activation : ${message}`);
|
||||
Log.error('voice', `wake mode failed: ${message}`);
|
||||
await api.voice.kwsStop().catch(() => undefined);
|
||||
await api.voice.vadStop().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +172,10 @@ class VoiceController {
|
||||
this.unsubscribeKws = null;
|
||||
this.wake.stop();
|
||||
useVoice.getState().setWake('off');
|
||||
await bridge()?.voice.kwsStop().catch(() => undefined);
|
||||
useVoice.getState().setNeuralVad(false);
|
||||
const api = bridge();
|
||||
await api?.voice.kwsStop().catch(() => undefined);
|
||||
await api?.voice.vadStop().catch(() => undefined);
|
||||
}
|
||||
|
||||
/** Restart spotting with the current settings (wake word, sensitivity, microphone). */
|
||||
|
||||
Reference in new issue
Block a user