From 31997a0beb9a2fd2e8757a122eb50e0b076a4c2b Mon Sep 17 00:00:00 2001 From: Logan Abell Date: Mon, 9 Feb 2026 15:56:06 -0500 Subject: [PATCH 1/3] fix: switch silero VAD to sherpa-rs-sys --- app/src-tauri/Cargo.lock | 274 +++----------------------------- app/src-tauri/Cargo.toml | 4 +- app/src-tauri/src/vad/silero.rs | 193 +++++++++++----------- 3 files changed, 113 insertions(+), 358 deletions(-) diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index 73300c4..c3c360d 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -166,12 +166,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - [[package]] name = "bindgen" version = "0.69.5" @@ -473,7 +467,7 @@ version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ - "smallvec 1.15.1", + "smallvec", "target-lexicon", ] @@ -659,7 +653,7 @@ dependencies = [ "bitflags 2.9.4", "core-foundation 0.10.1", "core-graphics-types", - "foreign-types 0.5.0", + "foreign-types", "libc", ] @@ -798,7 +792,7 @@ dependencies = [ "phf 0.10.1", "proc-macro2", "quote", - "smallvec 1.15.1", + "smallvec", "syn 1.0.109", ] @@ -988,16 +982,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "pem-rfc7468", - "zeroize", -] - [[package]] name = "deranged" version = "0.5.4" @@ -1311,7 +1295,7 @@ dependencies = [ "lebe", "miniz_oxide", "rayon-core", - "smallvec 1.15.1", + "smallvec", "zune-inflate", ] @@ -1325,12 +1309,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "fdeflate" version = "0.3.7" @@ -1390,15 +1368,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - [[package]] name = "foreign-types" version = "0.5.0" @@ -1406,7 +1375,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared 0.3.1", + "foreign-types-shared", ] [[package]] @@ -1420,12 +1389,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1712,7 +1675,7 @@ dependencies = [ "libc", "once_cell", "pin-project-lite", - "smallvec 1.15.1", + "smallvec", "thiserror 1.0.69", ] @@ -1748,7 +1711,7 @@ dependencies = [ "libc", "memchr", "once_cell", - "smallvec 1.15.1", + "smallvec", "thiserror 1.0.69", ] @@ -2053,7 +2016,7 @@ dependencies = [ "itoa", "pin-project-lite", "pin-utils", - "smallvec 1.15.1", + "smallvec", "tokio", "want", ] @@ -2167,7 +2130,7 @@ dependencies = [ "icu_normalizer_data", "icu_properties", "icu_provider", - "smallvec 1.15.1", + "smallvec", "zerovec", ] @@ -2229,7 +2192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", - "smallvec 1.15.1", + "smallvec", "utf8_iter", ] @@ -2828,23 +2791,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ndarray" version = "0.16.1" @@ -3434,7 +3380,6 @@ dependencies = [ "inotify", "libc", "once_cell", - "ort", "parking_lot", "regex", "reqwest 0.11.27", @@ -3444,6 +3389,7 @@ dependencies = [ "serde_json", "sha2", "sherpa-rs", + "sherpa-rs-sys", "sysinfo", "tar", "tauri", @@ -3458,81 +3404,12 @@ dependencies = [ "zip 0.6.6", ] -[[package]] -name = "openssl" -version = "0.10.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.110" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ort" -version = "2.0.0-rc.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa7e49bd669d32d7bc2a15ec540a527e7764aec722a45467814005725bcd721" -dependencies = [ - "ndarray", - "ort-sys", - "smallvec 2.0.0-alpha.10", - "tracing", -] - -[[package]] -name = "ort-sys" -version = "2.0.0-rc.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2aba9f5c7c479925205799216e7e5d07cc1d4fa76ea8058c60a9a30f6a4e890" -dependencies = [ - "flate2", - "pkg-config", - "sha2", - "tar", - "ureq 3.1.2", -] - [[package]] name = "pango" version = "0.18.3" @@ -3577,7 +3454,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.15.1", + "smallvec", "windows-link 0.2.1", ] @@ -3587,15 +3464,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -4287,7 +4155,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", - "rustls-pemfile 1.0.4", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", @@ -4463,15 +4331,6 @@ dependencies = [ "base64 0.21.7", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -4523,15 +4382,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "schemars" version = "0.8.22" @@ -4605,29 +4455,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "selectors" version = "0.24.0" @@ -4643,7 +4470,7 @@ dependencies = [ "phf_codegen 0.8.0", "precomputed-hash", "servo_arc", - "smallvec 1.15.1", + "smallvec", ] [[package]] @@ -4901,7 +4728,7 @@ dependencies = [ "serde_json", "sha2", "tar", - "ureq 2.12.1", + "ureq", ] [[package]] @@ -4940,12 +4767,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "smallvec" -version = "2.0.0-alpha.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d44cfb396c3caf6fbfd0ab422af02631b69ddd96d2eff0b0f0724f9024051b" - [[package]] name = "socket2" version = "0.5.10" @@ -4986,7 +4807,7 @@ dependencies = [ "bytemuck", "cfg_aliases", "core-graphics", - "foreign-types 0.5.0", + "foreign-types", "js-sys", "log", "objc2 0.5.2", @@ -5538,19 +5359,6 @@ dependencies = [ "toml 0.9.8", ] -[[package]] -name = "tempfile" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix 1.1.2", - "windows-sys 0.61.2", -] - [[package]] name = "tendril" version = "0.4.3" @@ -5965,7 +5773,7 @@ dependencies = [ "once_cell", "regex-automata", "sharded-slab", - "smallvec 1.15.1", + "smallvec", "thread_local", "tracing", "tracing-core", @@ -6081,7 +5889,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de" dependencies = [ - "smallvec 1.15.1", + "smallvec", ] [[package]] @@ -6130,37 +5938,6 @@ dependencies = [ "webpki-roots 0.26.11", ] -[[package]] -name = "ureq" -version = "3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537" -dependencies = [ - "base64 0.22.1", - "der", - "log", - "native-tls", - "percent-encoding", - "rustls-pemfile 2.2.0", - "rustls-pki-types", - "socks", - "ureq-proto", - "utf-8", - "webpki-root-certs", -] - -[[package]] -name = "ureq-proto" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2" -dependencies = [ - "base64 0.22.1", - "http 1.3.1", - "httparse", - "log", -] - [[package]] name = "url" version = "2.5.7" @@ -6215,12 +5992,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version-compare" version = "0.2.0" @@ -6442,15 +6213,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "webpki-root-certs" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webpki-roots" version = "0.25.4" diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index b7c54a8..efc29fb 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -35,8 +35,8 @@ hound = "3.5" regex = "1.10" globset = "0.4" sha2 = "0.10" -ort = { version = "2.0.0-rc.10", optional = true } sherpa-rs = { version = "0.6.8", optional = true, features = ["download-binaries"] } +sherpa-rs-sys = { version = "0.6.8", optional = true } ct2rs = { version = "0.9.16", optional = true, features = ["whisper"] } sentencepiece-sys = { version = "0.12.0", optional = true, features = ["static"] } webrtc-audio-processing = { version = "0.5", optional = true, features = ["bundled"] } @@ -58,7 +58,7 @@ audio = [] hud = [] models = [] real-audio = [] -vad-silero = ["ort"] +vad-silero = ["sherpa-rs-sys"] asr-sherpa = ["sherpa-rs"] asr-ct2 = ["ct2rs", "sentencepiece-sys"] webrtc-apm = ["webrtc-audio-processing"] diff --git a/app/src-tauri/src/vad/silero.rs b/app/src-tauri/src/vad/silero.rs index 8264905..d725f4d 100644 --- a/app/src-tauri/src/vad/silero.rs +++ b/app/src-tauri/src/vad/silero.rs @@ -1,137 +1,130 @@ #[cfg(feature = "vad-silero")] mod silero { use anyhow::{anyhow, Context, Result}; - use ort::{ - session::{builder::GraphOptimizationLevel, Session}, - value::Tensor, - }; + use std::ffi::CString; - const SAMPLE_RATE: usize = 16_000; - const FRAME_SIZE: usize = 512; + use sherpa_rs_sys as sys; + + const SAMPLE_RATE: i32 = 16_000; + const WINDOW_SIZE: i32 = 512; + const BUFFER_SIZE_SECONDS: f32 = 30.0; pub struct SileroVad { - session: Session, - hidden_state: Option<(Vec, Vec)>, - pending: Vec, - pending_offset: usize, - last_probability: f32, + vad: *const sys::SherpaOnnxVoiceActivityDetector, + last_score: f32, speech_threshold: f32, } impl SileroVad { - pub fn new(model_bytes: &[u8], speech_threshold: f32) -> Result { - let session = Session::builder() - .map_err(|err| anyhow!(err))? - .with_optimization_level(GraphOptimizationLevel::Level3) - .map_err(|err| anyhow!(err))? - .commit_from_memory(model_bytes) - .map_err(|err| anyhow!(err))?; - - Ok(Self { - session, - hidden_state: None, - pending: Vec::with_capacity(FRAME_SIZE * 4), - pending_offset: 0, - last_probability: 0.0, - speech_threshold: speech_threshold.clamp(0.0, 1.0), - }) + pub fn new(model_path: &str, speech_threshold: f32) -> Result { + let provider = std::env::var("SHERPA_PROVIDER").unwrap_or_else(|_| "cpu".into()); + let num_threads = std::env::var("SHERPA_THREADS") + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(1); + + Self::new_with_runtime(model_path, speech_threshold, &provider, num_threads, false) } pub fn from_env(speech_threshold: f32) -> Result { - let path = std::env::var("SILERO_VAD_MODEL").context("SILERO_VAD_MODEL not set")?; - let bytes = std::fs::read(path).context("read silero model")?; - Self::new(&bytes, speech_threshold) + let model_path = + std::env::var("SILERO_VAD_MODEL").context("SILERO_VAD_MODEL not set")?; + Self::new(&model_path, speech_threshold) + } + + fn new_with_runtime( + model_path: &str, + speech_threshold: f32, + provider: &str, + num_threads: i32, + debug: bool, + ) -> Result { + // sherpa-onnx validates threshold to be >= 0.01 and < 1.0. + let speech_threshold = speech_threshold.clamp(0.01, 0.99); + + let model_c = CString::new(model_path).context("silero model path contains NUL")?; + let provider_c = CString::new(provider).context("provider contains NUL")?; + + let silero_config = sys::SherpaOnnxSileroVadModelConfig { + model: model_c.as_ptr(), + threshold: speech_threshold, + // Keep these low; OpenFlow applies its own hangover in VoiceActivityDetector. + min_silence_duration: 0.1, + min_speech_duration: 0.15, + window_size: WINDOW_SIZE, + max_speech_duration: 20.0, + }; + + let vad_config = sys::SherpaOnnxVadModelConfig { + silero_vad: silero_config, + sample_rate: SAMPLE_RATE, + num_threads, + provider: provider_c.as_ptr(), + debug: if debug { 1 } else { 0 }, + // ten_vad is unused when silero_vad.model is set. + ten_vad: unsafe { std::mem::zeroed::() }, + }; + + let vad = unsafe { + sys::SherpaOnnxCreateVoiceActivityDetector(&vad_config, BUFFER_SIZE_SECONDS) + }; + if vad.is_null() { + return Err(anyhow!( + "failed to create SherpaOnnxVoiceActivityDetector (silero model: {})", + model_path + )); + } + + Ok(Self { + vad, + last_score: 0.0, + speech_threshold, + }) } pub fn reset(&mut self) { - self.hidden_state = None; - self.pending.clear(); - self.pending_offset = 0; - self.last_probability = 0.0; + self.last_score = 0.0; + unsafe { + sys::SherpaOnnxVoiceActivityDetectorReset(self.vad); + } } pub fn speech_threshold(&self) -> f32 { self.speech_threshold } - /// Ingest audio and return the latest speech probability. + /// Ingest audio and return a 0..1 speech score. /// - /// Silero expects contiguous 512-sample windows at 16kHz. - /// Our capture uses 20ms frames (320 samples), so we buffer across calls - /// and only run inference on real 512-sample chunks. + /// sherpa-onnx VAD is stateful and returns a detected/silent decision. + /// We expose it as `1.0` / `0.0` to fit OpenFlow's diagnostics interface. pub fn ingest(&mut self, audio: &[f32]) -> Result { if audio.is_empty() { - return Ok(self.last_probability); - } - - self.pending.extend_from_slice(audio); - - while self.pending.len().saturating_sub(self.pending_offset) >= FRAME_SIZE { - let start = self.pending_offset; - let end = start + FRAME_SIZE; - let frame = self.pending[start..end].to_vec(); - self.pending_offset = end; - - let prob = self.run_model(&frame)?; - self.last_probability = prob; + return Ok(self.last_score); } - // Periodically compact the pending buffer to avoid unbounded growth. - if self.pending_offset > 0 && self.pending_offset >= FRAME_SIZE * 8 { - self.pending.drain(..self.pending_offset); - self.pending_offset = 0; + let n = i32::try_from(audio.len()).map_err(|_| anyhow!("audio frame too large"))?; + unsafe { + sys::SherpaOnnxVoiceActivityDetectorAcceptWaveform(self.vad, audio.as_ptr(), n); } - Ok(self.last_probability) + let detected = unsafe { sys::SherpaOnnxVoiceActivityDetectorDetected(self.vad) } != 0; + let score = if detected { 1.0 } else { 0.0 }; + self.last_score = score; + Ok(score) } + } - fn run_model(&mut self, frame: &[f32]) -> Result { - if frame.len() != FRAME_SIZE { - return Err(anyhow!( - "silero frame size mismatch (got {}, expected {})", - frame.len(), - FRAME_SIZE - )); + impl Drop for SileroVad { + fn drop(&mut self) { + unsafe { + sys::SherpaOnnxDestroyVoiceActivityDetector(self.vad); } - - let audio_tensor = - Tensor::from_array(([1usize, FRAME_SIZE], frame.to_vec().into_boxed_slice())) - .map_err(|err| anyhow!(err))?; - let sr_tensor = - Tensor::from_array(([1usize], vec![SAMPLE_RATE as f32].into_boxed_slice())) - .map_err(|err| anyhow!(err))?; - - let outputs = if let Some((state_shape, state_data)) = self.hidden_state.as_ref() { - let hidden_tensor = Tensor::from_array(( - state_shape.clone(), - state_data.clone().into_boxed_slice(), - )) - .map_err(|err| anyhow!(err))?; - self.session - .run(ort::inputs![audio_tensor, sr_tensor, hidden_tensor]) - .map_err(|err| anyhow!(err))? - } else { - self.session - .run(ort::inputs![audio_tensor, sr_tensor]) - .map_err(|err| anyhow!(err))? - }; - - let (_, speech_tensor) = outputs[0] - .try_extract_tensor::() - .map_err(|err| anyhow!(err))?; - let speech_prob = speech_tensor.first().copied().unwrap_or(0.0); - - let (state_shape, state_tensor) = outputs[1] - .try_extract_tensor::() - .map_err(|err| anyhow!(err))?; - self.hidden_state = Some(( - state_shape.iter().map(|dim| *dim as usize).collect(), - state_tensor.to_vec(), - )); - - Ok(speech_prob) } } + + unsafe impl Send for SileroVad {} + unsafe impl Sync for SileroVad {} } #[cfg(feature = "vad-silero")] From 06485a6c990e0071facada27b1cd2df84b67cee7 Mon Sep 17 00:00:00 2001 From: Logan Abell Date: Mon, 9 Feb 2026 16:49:37 -0500 Subject: [PATCH 2/3] ci: fix linux matrix smoke/e2e --- .github/workflows/ci-linux.yml | 3 +++ .github/workflows/release-linux.yml | 3 +++ app/src-tauri/tauri.conf.json | 1 + install.sh | 15 +-------------- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index 3557a82..214b028 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -289,8 +289,10 @@ jobs: set -eux dnf -y install \ ca-certificates curl which tar gzip xz \ + gcc make pkgconf-pkg-config \ openssl-devel \ webkit2gtk4.1 webkit2gtk4.1-devel \ + webkitgtk6.0 \ gtk3 \ alsa-lib \ libevdev \ @@ -378,6 +380,7 @@ jobs: pacman-key --populate archlinux || true pacman -Sy --noconfirm --needed \ ca-certificates curl which tar gzip xz \ + base-devel \ openssl \ gtk3 \ alsa-lib \ diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 72b3d2c..c1cf6d3 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -292,8 +292,10 @@ jobs: set -eux dnf -y install \ ca-certificates curl which tar gzip xz \ + gcc make pkgconf-pkg-config \ openssl-devel \ webkit2gtk4.1 webkit2gtk4.1-devel \ + webkitgtk6.0 \ gtk3 \ alsa-lib \ libevdev \ @@ -381,6 +383,7 @@ jobs: pacman-key --populate archlinux || true pacman -Sy --noconfirm --needed \ ca-certificates curl which tar gzip xz \ + base-devel \ openssl \ gtk3 \ alsa-lib \ diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json index 3d98734..76549bd 100644 --- a/app/src-tauri/tauri.conf.json +++ b/app/src-tauri/tauri.conf.json @@ -46,6 +46,7 @@ { "title": "", "label": "status-overlay", + "create": false, "url": "overlay.html", "decorations": false, "transparent": true, diff --git a/install.sh b/install.sh index fe6985b..9bb500a 100644 --- a/install.sh +++ b/install.sh @@ -208,10 +208,7 @@ install_deps() { fi pm_install apt libwebkit2gtk-4.1-0 - if ! apt-cache show libsentencepiece0 >/dev/null 2>&1; then - die "OpenFlow requires the SentencePiece runtime (libsentencepiece0). On Ubuntu you may need to enable Universe: sudo add-apt-repository universe && sudo apt-get update" - fi - pm_install apt libsentencepiece0 + # SentencePiece is statically linked (sentencepiece-sys). # Tray: dynamically loaded at runtime (ayatana preferred). if ! have_appindicator_libs; then @@ -224,10 +221,6 @@ install_deps() { dnf) pm_install dnf wl-clipboard xclip polkit acl bzip2 curl ca-certificates alsa-lib gtk3 webkit2gtk4.1 libevdev - if ! pm_install_any dnf sentencepiece sentencepiece-libs; then - die "failed to install sentencepiece runtime (tried sentencepiece, sentencepiece-libs)" - fi - if ! have_appindicator_libs; then pm_install_any dnf libayatana-appindicator-gtk3 libappindicator-gtk3 || die "failed to install appindicator runtime (tried libayatana-appindicator-gtk3, libappindicator-gtk3)" fi @@ -238,8 +231,6 @@ install_deps() { pacman) pm_install pacman wl-clipboard xclip polkit acl bzip2 curl ca-certificates alsa-lib gtk3 webkit2gtk-4.1 libevdev - pm_install pacman sentencepiece - if ! have_appindicator_libs; then pm_install_any pacman libayatana-appindicator libappindicator || die "failed to install appindicator runtime (tried libayatana-appindicator, libappindicator)" fi @@ -260,10 +251,6 @@ install_deps() { pm_install zypper libwebkit2gtk-4_1-0 - if ! pm_install_any zypper libsentencepiece0 sentencepiece; then - die "failed to install sentencepiece runtime (tried libsentencepiece0, sentencepiece)" - fi - if ! have_appindicator_libs; then pm_install_any zypper libayatana-appindicator3-1 libappindicator3-1 || die "failed to install appindicator runtime (tried libayatana-appindicator3-1, libappindicator3-1)" fi From 4d7ae2884ca3dd5aa1c74a75126f2ea9ee801c3f Mon Sep 17 00:00:00 2001 From: Logan Abell Date: Mon, 9 Feb 2026 17:04:10 -0500 Subject: [PATCH 3/3] test: make settings click webdriver-safe --- e2e-tests/tests/smoke.spec.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/e2e-tests/tests/smoke.spec.js b/e2e-tests/tests/smoke.spec.js index 5a50355..8dfdc7d 100644 --- a/e2e-tests/tests/smoke.spec.js +++ b/e2e-tests/tests/smoke.spec.js @@ -154,7 +154,16 @@ describe("OpenFlow UI", function () { until.elementLocated(By.xpath("//button[contains(., 'Settings')]")), 60000, ); - await settingsButton.click(); + try { + await settingsButton.click(); + } catch (error) { + // Some WebKit WebDriver builds (notably on Fedora) don't support native element clicks. + // Fall back to a DOM click so we can still validate basic UI wiring. + if (error?.name !== "UnsupportedOperationError") { + throw error; + } + await driver.executeScript("arguments[0].click()", settingsButton); + } const panelHeader = await driver.wait( until.elementLocated(By.xpath("//h2[contains(., 'Settings')]")),