AI models shared registry + Code cleanup + Better async handling + Some fixes, etc
This commit is contained in:
parent
a8ff3442ff
commit
520b98143f
62 changed files with 1683 additions and 1239 deletions
87
crates/jarvis-core/src/db/manager.rs
Normal file
87
crates/jarvis-core/src/db/manager.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::structs::Settings;
|
||||
use super::save_settings;
|
||||
|
||||
// centralized settings manager.
|
||||
// wraps Arc<RwLock<Settings>> and handles locking + auto-save
|
||||
// can be used anywhere, ex. from GUI, tray, IPC, CLI, etc.
|
||||
#[derive(Clone)]
|
||||
pub struct SettingsManager {
|
||||
inner: Arc<RwLock<Settings>>,
|
||||
}
|
||||
|
||||
impl SettingsManager {
|
||||
pub fn new(settings: Settings) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(settings)),
|
||||
}
|
||||
}
|
||||
|
||||
// wrap an existing Arc<RwLock<Settings>>
|
||||
pub fn from_arc(arc: Arc<RwLock<Settings>>) -> Self {
|
||||
Self { inner: arc }
|
||||
}
|
||||
|
||||
// read a setting by key
|
||||
pub fn read(&self, key: &str) -> Option<String> {
|
||||
self.inner.read().get(key)
|
||||
}
|
||||
|
||||
// write a setting by key, auto-saves to disk
|
||||
pub fn write(&self, key: &str, val: &str) -> Result<(), String> {
|
||||
let snapshot = {
|
||||
let mut settings = self.inner.write();
|
||||
settings.set(key, val)?;
|
||||
settings.clone()
|
||||
};
|
||||
|
||||
save_settings(&snapshot)
|
||||
.map_err(|e| format!("failed to save settings: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// write multiple settings at once, single save
|
||||
pub fn write_many(&self, pairs: &[(&str, &str)]) -> Result<(), String> {
|
||||
let snapshot = {
|
||||
let mut settings = self.inner.write();
|
||||
for (key, val) in pairs {
|
||||
settings.set(key, val)?;
|
||||
}
|
||||
settings.clone()
|
||||
};
|
||||
|
||||
save_settings(&snapshot)
|
||||
.map_err(|e| format!("failed to save settings: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// direct read access to the full Settings struct (for init code that
|
||||
// needs to read multiple fields at once without key-based access)
|
||||
pub fn lock(&self) -> parking_lot::RwLockReadGuard<'_, Settings> {
|
||||
self.inner.read()
|
||||
}
|
||||
|
||||
// direct write access (for bulk operations not covered by set())
|
||||
pub fn lock_mut(&self) -> parking_lot::RwLockWriteGuard<'_, Settings> {
|
||||
self.inner.write()
|
||||
}
|
||||
|
||||
// get the underlying Arc
|
||||
pub fn arc(&self) -> &Arc<RwLock<Settings>> {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
// dump all settings as key-value pairs (for debugging)
|
||||
pub fn dump(&self) -> Vec<(String, String)> {
|
||||
let settings = self.inner.read();
|
||||
Settings::keys().iter()
|
||||
.filter_map(|&key| {
|
||||
settings.get(key).map(|val| (key.to_string(), val))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::config::structs::SpeechToTextEngine;
|
||||
use crate::config::structs::WakeWordEngine;
|
||||
use crate::config::structs::IntentRecognitionEngine;
|
||||
use crate::config::structs::NoiseSuppressionBackend;
|
||||
use crate::config::structs::VadBackend;
|
||||
use crate::config::structs::SlotExtractionEngine;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Settings {
|
||||
|
|
@ -14,9 +11,15 @@ pub struct Settings {
|
|||
pub voice: String,
|
||||
|
||||
pub wake_word_engine: WakeWordEngine,
|
||||
pub intent_recognition_engine: IntentRecognitionEngine,
|
||||
|
||||
pub slot_extraction_engine: SlotExtractionEngine,
|
||||
// backend selections (string IDs matching model or code backend IDs)
|
||||
#[serde(default = "default_intent_backend")]
|
||||
pub intent_backend: String,
|
||||
#[serde(default = "default_slots_backend")]
|
||||
pub slots_backend: String,
|
||||
#[serde(default = "default_vad_backend")]
|
||||
pub vad_backend: String,
|
||||
|
||||
pub gliner_model: String,
|
||||
|
||||
pub speech_to_text_engine: SpeechToTextEngine,
|
||||
|
|
@ -24,14 +27,127 @@ pub struct Settings {
|
|||
|
||||
// audio processing
|
||||
pub noise_suppression: NoiseSuppressionBackend,
|
||||
pub vad: VadBackend,
|
||||
pub gain_normalizer: bool,
|
||||
|
||||
#[serde(default = "default_language")]
|
||||
pub language: String,
|
||||
|
||||
pub api_keys: ApiKeys,
|
||||
}
|
||||
|
||||
fn default_intent_backend() -> String { config::DEFAULT_INTENT_BACKEND.to_string() }
|
||||
fn default_slots_backend() -> String { config::DEFAULT_SLOTS_BACKEND.to_string() }
|
||||
fn default_vad_backend() -> String { config::DEFAULT_VAD_BACKEND.to_string() }
|
||||
fn default_language() -> String { crate::i18n::detect_system_language().to_string() }
|
||||
|
||||
// ### KEY-VALUE ACCESS
|
||||
|
||||
impl Settings {
|
||||
/// read a setting by key. returns None for unknown keys.
|
||||
pub fn get(&self, key: &str) -> Option<String> {
|
||||
match key {
|
||||
"selected_microphone" => Some(self.microphone.to_string()),
|
||||
"assistant_voice" => Some(self.voice.clone()),
|
||||
"selected_wake_word_engine" => Some(format!("{:?}", self.wake_word_engine)),
|
||||
"intent_backend" => Some(self.intent_backend.clone()),
|
||||
"slots_backend" => Some(self.slots_backend.clone()),
|
||||
"vad_backend" => Some(self.vad_backend.clone()),
|
||||
"selected_gliner_model" => Some(self.gliner_model.clone()),
|
||||
"selected_vosk_model" => Some(self.vosk_model.clone()),
|
||||
"speech_to_text_engine" => Some(format!("{:?}", self.speech_to_text_engine)),
|
||||
"noise_suppression" => Some(format!("{:?}", self.noise_suppression)),
|
||||
"gain_normalizer" => Some(self.gain_normalizer.to_string()),
|
||||
"language" => Some(self.language.clone()),
|
||||
"api_key__picovoice" => Some(self.api_keys.picovoice.clone()),
|
||||
"api_key__openai" => Some(self.api_keys.openai.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// write a setting by key. returns Err for unknown keys or invalid values.
|
||||
pub fn set(&mut self, key: &str, val: &str) -> Result<(), String> {
|
||||
match key {
|
||||
"selected_microphone" => {
|
||||
self.microphone = val.parse::<i32>()
|
||||
.map_err(|_| format!("invalid integer: '{}'", val))?;
|
||||
}
|
||||
"assistant_voice" => {
|
||||
self.voice = val.to_string();
|
||||
}
|
||||
"selected_wake_word_engine" => {
|
||||
self.wake_word_engine = match val.to_lowercase().as_str() {
|
||||
"rustpotter" => WakeWordEngine::Rustpotter,
|
||||
"vosk" => WakeWordEngine::Vosk,
|
||||
"porcupine" => WakeWordEngine::Porcupine,
|
||||
_ => return Err(format!("unknown wake word engine: '{}'", val)),
|
||||
};
|
||||
}
|
||||
"intent_backend" => {
|
||||
self.intent_backend = val.to_string();
|
||||
}
|
||||
"slots_backend" => {
|
||||
self.slots_backend = val.to_string();
|
||||
}
|
||||
"vad_backend" => {
|
||||
self.vad_backend = val.to_string();
|
||||
}
|
||||
"selected_gliner_model" => {
|
||||
self.gliner_model = val.to_string();
|
||||
}
|
||||
"selected_vosk_model" => {
|
||||
self.vosk_model = val.to_string();
|
||||
}
|
||||
"noise_suppression" => {
|
||||
self.noise_suppression = match val.to_lowercase().as_str() {
|
||||
"none" => NoiseSuppressionBackend::None,
|
||||
"nnnoiseless" => NoiseSuppressionBackend::Nnnoiseless,
|
||||
_ => return Err(format!("unknown noise suppression backend: '{}'", val)),
|
||||
};
|
||||
}
|
||||
"gain_normalizer" => {
|
||||
self.gain_normalizer = match val.to_lowercase().as_str() {
|
||||
"true" => true,
|
||||
"false" => false,
|
||||
_ => return Err(format!("expected 'true' or 'false', got: '{}'", val)),
|
||||
};
|
||||
}
|
||||
"language" => {
|
||||
self.language = val.to_string();
|
||||
}
|
||||
"api_key__picovoice" => {
|
||||
self.api_keys.picovoice = val.to_string();
|
||||
}
|
||||
"api_key__openai" => {
|
||||
self.api_keys.openai = val.to_string();
|
||||
}
|
||||
_ => return Err(format!("unknown setting: '{}'", key)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// all valid setting keys (for enumeration, debugging, etc.)
|
||||
pub fn keys() -> &'static [&'static str] {
|
||||
&[
|
||||
"selected_microphone",
|
||||
"assistant_voice",
|
||||
"selected_wake_word_engine",
|
||||
"intent_backend",
|
||||
"slots_backend",
|
||||
"vad_backend",
|
||||
"selected_gliner_model",
|
||||
"selected_vosk_model",
|
||||
"speech_to_text_engine",
|
||||
"noise_suppression",
|
||||
"gain_normalizer",
|
||||
"language",
|
||||
"api_key__picovoice",
|
||||
"api_key__openai",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// ### DEFAULT
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
|
|
@ -39,18 +155,19 @@ impl Default for Settings {
|
|||
voice: String::from(""),
|
||||
|
||||
wake_word_engine: config::DEFAULT_WAKE_WORD_ENGINE,
|
||||
intent_recognition_engine: config::DEFAULT_INTENT_RECOGNITION_ENGINE,
|
||||
slot_extraction_engine: SlotExtractionEngine::None,
|
||||
|
||||
intent_backend: config::DEFAULT_INTENT_BACKEND.to_string(),
|
||||
slots_backend: config::DEFAULT_SLOTS_BACKEND.to_string(),
|
||||
vad_backend: config::DEFAULT_VAD_BACKEND.to_string(),
|
||||
|
||||
gliner_model: String::new(),
|
||||
speech_to_text_engine: config::DEFAULT_SPEECH_TO_TEXT_ENGINE,
|
||||
vosk_model: String::from(""), // auto detect first available
|
||||
vosk_model: String::from(""),
|
||||
|
||||
// audio processing defaults
|
||||
noise_suppression: config::DEFAULT_NOISE_SUPPRESSION,
|
||||
vad: config::DEFAULT_VAD,
|
||||
gain_normalizer: config::DEFAULT_GAIN_NORMALIZER,
|
||||
|
||||
language: String::from("ru"),
|
||||
language: crate::i18n::detect_system_language().to_string(),
|
||||
|
||||
api_keys: ApiKeys {
|
||||
picovoice: String::from(""),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue