vosk model selection in GUI
This commit is contained in:
parent
36acacf259
commit
cab53abcbe
14 changed files with 323 additions and 40 deletions
|
|
@ -7,7 +7,7 @@ repository.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
jarvis-core = { path = "../jarvis-core" }
|
jarvis-core = { path = "../jarvis-core", features = ["intent"] }
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
simple-log = "2.4"
|
simple-log = "2.4"
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ pub fn init_dirs() -> Result<(), String> {
|
||||||
*/
|
*/
|
||||||
pub const DEFAULT_AUDIO_TYPE: AudioType = AudioType::Kira;
|
pub const DEFAULT_AUDIO_TYPE: AudioType = AudioType::Kira;
|
||||||
pub const DEFAULT_RECORDER_TYPE: RecorderType = RecorderType::PvRecorder;
|
pub const DEFAULT_RECORDER_TYPE: RecorderType = RecorderType::PvRecorder;
|
||||||
pub const DEFAULT_WAKE_WORD_ENGINE: WakeWordEngine = WakeWordEngine::Rustpotter;
|
pub const DEFAULT_WAKE_WORD_ENGINE: WakeWordEngine = WakeWordEngine::Vosk;
|
||||||
pub const DEFAULT_INTENT_RECOGNITION_ENGINE: IntentRecognitionEngine = IntentRecognitionEngine::IntentClassifier;
|
pub const DEFAULT_INTENT_RECOGNITION_ENGINE: IntentRecognitionEngine = IntentRecognitionEngine::IntentClassifier;
|
||||||
pub const DEFAULT_SPEECH_TO_TEXT_ENGINE: SpeechToTextEngine = SpeechToTextEngine::Vosk;
|
pub const DEFAULT_SPEECH_TO_TEXT_ENGINE: SpeechToTextEngine = SpeechToTextEngine::Vosk;
|
||||||
|
|
||||||
|
|
@ -121,19 +121,20 @@ pub const RUSTPOTTER_DEFAULT_CONFIG: Lazy<RustpotterConfig> = Lazy::new(|| {
|
||||||
});
|
});
|
||||||
|
|
||||||
// PICOVOICE
|
// PICOVOICE
|
||||||
pub const COMMANDS_PATH: &str = "commands/";
|
pub const COMMANDS_PATH: &str = "resources/commands/";
|
||||||
pub const KEYWORDS_PATH: &str = "picovoice/keywords/";
|
pub const KEYWORDS_PATH: &str = "resources/picovoice/keywords/";
|
||||||
pub const DEFAULT_KEYWORD: &str = "jarvis_windows.ppn";
|
pub const DEFAULT_KEYWORD: &str = "jarvis_windows.ppn";
|
||||||
pub const DEFAULT_SENSITIVITY: f32 = 1.0;
|
pub const DEFAULT_SENSITIVITY: f32 = 1.0;
|
||||||
|
|
||||||
// VOSK
|
// VOSK
|
||||||
// pub const VOSK_MODEL_PATH: &str = const_concat!(PUBLIC_PATH, "/vosk/model_small");
|
// pub const VOSK_MODEL_PATH: &str = const_concat!(PUBLIC_PATH, "/vosk/model_small");
|
||||||
|
pub const VOSK_MODELS_PATH: &str = "resources/vosk";
|
||||||
|
pub const VOSK_MODEL_PATH: &str = "resources/vosk/model_small";
|
||||||
pub const VOSK_FETCH_PHRASE: &str = "джарвис";
|
pub const VOSK_FETCH_PHRASE: &str = "джарвис";
|
||||||
pub const VOSK_MODEL_PATH: &str = "vosk/model_small";
|
|
||||||
pub const VOSK_MIN_RATIO: f64 = 70.0;
|
pub const VOSK_MIN_RATIO: f64 = 70.0;
|
||||||
|
|
||||||
// IRE (intents recognition)
|
// IRE (intents recognition)
|
||||||
pub const INTENT_CLASSIFIER_MIN_CONFIDENCE: f64 = 0.5;
|
pub const INTENT_CLASSIFIER_MIN_CONFIDENCE: f64 = 0.75;
|
||||||
|
|
||||||
// ETC
|
// ETC
|
||||||
pub const CMD_RATIO_THRESHOLD: f64 = 65f64;
|
pub const CMD_RATIO_THRESHOLD: f64 = 65f64;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ pub struct Settings {
|
||||||
pub intent_recognition_engine: IntentRecognitionEngine,
|
pub intent_recognition_engine: IntentRecognitionEngine,
|
||||||
pub speech_to_text_engine: SpeechToTextEngine,
|
pub speech_to_text_engine: SpeechToTextEngine,
|
||||||
|
|
||||||
|
pub vosk_model: String,
|
||||||
|
|
||||||
pub api_keys: ApiKeys,
|
pub api_keys: ApiKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,6 +29,8 @@ impl Default for Settings {
|
||||||
intent_recognition_engine: config::DEFAULT_INTENT_RECOGNITION_ENGINE,
|
intent_recognition_engine: config::DEFAULT_INTENT_RECOGNITION_ENGINE,
|
||||||
speech_to_text_engine: config::DEFAULT_SPEECH_TO_TEXT_ENGINE,
|
speech_to_text_engine: config::DEFAULT_SPEECH_TO_TEXT_ENGINE,
|
||||||
|
|
||||||
|
vosk_model: String::from(""), // auto detect first available
|
||||||
|
|
||||||
api_keys: ApiKeys {
|
api_keys: ApiKeys {
|
||||||
picovoice: String::from(""),
|
picovoice: String::from(""),
|
||||||
openai: String::from(""),
|
openai: String::from(""),
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,17 @@ pub mod stt;
|
||||||
#[cfg(feature = "intent")]
|
#[cfg(feature = "intent")]
|
||||||
pub mod intent;
|
pub mod intent;
|
||||||
|
|
||||||
|
pub mod vosk_models;
|
||||||
|
|
||||||
// shared statics
|
// shared statics
|
||||||
pub static APP_DIR: Lazy<PathBuf> = Lazy::new(|| std::env::current_dir().unwrap());
|
// pub static APP_DIR: Lazy<PathBuf> = Lazy::new(|| std::env::current_dir().unwrap());
|
||||||
pub static SOUND_DIR: Lazy<PathBuf> = Lazy::new(|| APP_DIR.clone().join("sound"));
|
pub static APP_DIR: Lazy<PathBuf> = Lazy::new(|| {
|
||||||
|
std::env::current_exe()
|
||||||
|
.ok()
|
||||||
|
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
|
||||||
|
.unwrap_or_else(|| std::env::current_dir().unwrap())
|
||||||
|
});
|
||||||
|
pub static SOUND_DIR: Lazy<PathBuf> = Lazy::new(|| APP_DIR.clone().join("resources/sound"));
|
||||||
pub static APP_DIRS: OnceCell<AppDirs> = OnceCell::new();
|
pub static APP_DIRS: OnceCell<AppDirs> = OnceCell::new();
|
||||||
pub static APP_CONFIG_DIR: OnceCell<PathBuf> = OnceCell::new();
|
pub static APP_CONFIG_DIR: OnceCell<PathBuf> = OnceCell::new();
|
||||||
pub static APP_LOG_DIR: OnceCell<PathBuf> = OnceCell::new();
|
pub static APP_LOG_DIR: OnceCell<PathBuf> = OnceCell::new();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
use crate::config::structs::SpeechToTextEngine;
|
use crate::config::structs::SpeechToTextEngine;
|
||||||
|
|
||||||
|
use crate::vosk_models;
|
||||||
|
// use vosk_models::{scan_vosk_models, get_model_path, VoskModelInfo};
|
||||||
|
|
||||||
static STT_TYPE: OnceCell<SpeechToTextEngine> = OnceCell::new();
|
static STT_TYPE: OnceCell<SpeechToTextEngine> = OnceCell::new();
|
||||||
|
|
||||||
pub fn init() -> Result<(), ()> {
|
pub fn init() -> Result<(), ()> {
|
||||||
|
|
@ -30,6 +33,7 @@ pub fn init() -> Result<(), ()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn recognize(data: &[i16], partial: bool) -> Option<String> {
|
pub fn recognize(data: &[i16], partial: bool) -> Option<String> {
|
||||||
match STT_TYPE.get().unwrap() {
|
match STT_TYPE.get().unwrap() {
|
||||||
SpeechToTextEngine::Vosk => vosk::recognize(data, partial),
|
SpeechToTextEngine::Vosk => vosk::recognize(data, partial),
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,26 @@ use vosk::{DecodingState, Model, Recognizer};
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use crate::config::VOSK_MODEL_PATH;
|
// use crate::config::VOSK_MODEL_PATH;
|
||||||
|
use crate::config;
|
||||||
|
use crate::stt::vosk_models;
|
||||||
|
use crate::DB;
|
||||||
|
|
||||||
static MODEL: OnceCell<Model> = OnceCell::new();
|
static MODEL: OnceCell<Model> = OnceCell::new();
|
||||||
static RECOGNIZER: OnceCell<Mutex<Recognizer>> = OnceCell::new();
|
static RECOGNIZER: OnceCell<Mutex<Recognizer>> = OnceCell::new();
|
||||||
|
|
||||||
pub fn init_vosk() {
|
pub fn init_vosk() -> Result<(), String> {
|
||||||
if RECOGNIZER.get().is_some() {
|
if RECOGNIZER.get().is_some() {
|
||||||
return;
|
return Ok(());
|
||||||
} // already initialized
|
} // already initialized
|
||||||
|
|
||||||
let model = Model::new(VOSK_MODEL_PATH).unwrap();
|
let model_path = get_configured_model_path()?;
|
||||||
let mut recognizer = Recognizer::new(&model, 16000.0).unwrap();
|
|
||||||
|
let model = Model::new(model_path.to_str().unwrap())
|
||||||
|
.ok_or_else(|| format!("Failed to load Vosk model from: {}", model_path.display()))?;
|
||||||
|
|
||||||
|
let mut recognizer = Recognizer::new(&model, 16000.0)
|
||||||
|
.ok_or("Failed to create Vosk recognizer")?;
|
||||||
|
|
||||||
recognizer.set_max_alternatives(10);
|
recognizer.set_max_alternatives(10);
|
||||||
recognizer.set_words(true);
|
recognizer.set_words(true);
|
||||||
|
|
@ -22,6 +30,8 @@ pub fn init_vosk() {
|
||||||
|
|
||||||
MODEL.set(model);
|
MODEL.set(model);
|
||||||
RECOGNIZER.set(Mutex::new(recognizer));
|
RECOGNIZER.set(Mutex::new(recognizer));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize(data: &[i16], include_partial: bool) -> Option<String> {
|
pub fn recognize(data: &[i16], include_partial: bool) -> Option<String> {
|
||||||
|
|
@ -73,6 +83,34 @@ pub fn recognize(data: &[i16], include_partial: bool) -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_configured_model_path() -> Result<std::path::PathBuf, String> {
|
||||||
|
// try to get from settings
|
||||||
|
if let Some(db) = DB.get() {
|
||||||
|
let settings = db.read();
|
||||||
|
if !settings.vosk_model.is_empty() {
|
||||||
|
if let Some(path) = vosk_models::get_model_path(&settings.vosk_model) {
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
warn!("Configured Vosk model '{}' not found, falling back to auto-detect", settings.vosk_model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto-detect: use first available model
|
||||||
|
let available = vosk_models::scan_vosk_models();
|
||||||
|
if let Some(first) = available.first() {
|
||||||
|
info!("Auto-detected Vosk model: {}", first.name);
|
||||||
|
return Ok(first.path.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to legacy path
|
||||||
|
let legacy_path = std::path::Path::new(config::VOSK_MODEL_PATH);
|
||||||
|
if legacy_path.exists() {
|
||||||
|
return Ok(legacy_path.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("No Vosk models found".into())
|
||||||
|
}
|
||||||
|
|
||||||
// pub fn stereo_to_mono(input_data: &[i16]) -> Vec<i16> {
|
// pub fn stereo_to_mono(input_data: &[i16]) -> Vec<i16> {
|
||||||
// let mut result = Vec::with_capacity(input_data.len() / 2);
|
// let mut result = Vec::with_capacity(input_data.len() / 2);
|
||||||
// result.extend(
|
// result.extend(
|
||||||
|
|
|
||||||
113
crates/jarvis-core/src/vosk_models.rs
Normal file
113
crates/jarvis-core/src/vosk_models.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::{APP_DIR, config};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VoskModelInfo {
|
||||||
|
pub name: String, // folder name: "vosk-model-small-ru-0.22"
|
||||||
|
pub path: PathBuf, // full path
|
||||||
|
pub language: String, // extracted from name: "ru"
|
||||||
|
pub size: String, // "small", "large", etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan for available Vosk models
|
||||||
|
pub fn scan_vosk_models() -> Vec<VoskModelInfo> {
|
||||||
|
let models_dir = {
|
||||||
|
APP_DIR.join(config::VOSK_MODELS_PATH)
|
||||||
|
};
|
||||||
|
let mut models = Vec::new();
|
||||||
|
|
||||||
|
info!("TESTTTTTTTTTTTTT: {}", models_dir.display());
|
||||||
|
|
||||||
|
if !models_dir.exists() {
|
||||||
|
warn!("Vosk models directory not found: {}", models_dir.display());
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries = match fs::read_dir(models_dir) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to read vosk models directory: {}", e);
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let entry = match entry {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
// must be a directory
|
||||||
|
if !path.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it looks like a vosk model (has am/conf/graph folders or similar)
|
||||||
|
if !is_vosk_model(&path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = path.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let (language, size) = parse_model_name(&name);
|
||||||
|
|
||||||
|
models.push(VoskModelInfo {
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
language,
|
||||||
|
size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
models.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
models
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if directory looks like a Vosk model
|
||||||
|
fn is_vosk_model(path: &Path) -> bool {
|
||||||
|
// vosk models typically have these subdirectories
|
||||||
|
path.join("am").exists() ||
|
||||||
|
path.join("conf").exists() ||
|
||||||
|
path.join("graph").exists() ||
|
||||||
|
path.join("ivector").exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract language and size from model name
|
||||||
|
// e.g., "vosk-model-small-ru-0.22" -> ("ru", "small")
|
||||||
|
fn parse_model_name(name: &str) -> (String, String) {
|
||||||
|
let parts: Vec<&str> = name.split('-').collect();
|
||||||
|
|
||||||
|
let mut language = String::from("unknown");
|
||||||
|
let mut size = String::from("unknown");
|
||||||
|
|
||||||
|
// look for common size indicators
|
||||||
|
for part in &parts {
|
||||||
|
match *part {
|
||||||
|
"small" | "big" | "large" | "lgraph" => size = part.to_string(),
|
||||||
|
// language codes are usually 2 letters
|
||||||
|
s if s.len() == 2 && s.chars().all(|c| c.is_alphabetic()) => {
|
||||||
|
language = s.to_string();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(language, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get model path by name
|
||||||
|
pub fn get_model_path(model_name: &str) -> Option<PathBuf> {
|
||||||
|
let path = Path::new(config::VOSK_MODELS_PATH).join(model_name);
|
||||||
|
if path.exists() && path.is_dir() {
|
||||||
|
Some(path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -61,6 +61,9 @@ fn main() {
|
||||||
tauri_commands::get_peak_ram_usage,
|
tauri_commands::get_peak_ram_usage,
|
||||||
tauri_commands::get_cpu_temp,
|
tauri_commands::get_cpu_temp,
|
||||||
tauri_commands::get_cpu_usage,
|
tauri_commands::get_cpu_usage,
|
||||||
|
|
||||||
|
// vosk
|
||||||
|
tauri_commands::list_vosk_models,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,7 @@ pub use fs::*;
|
||||||
// import SYS commands
|
// import SYS commands
|
||||||
mod sys;
|
mod sys;
|
||||||
pub use sys::*;
|
pub use sys::*;
|
||||||
|
|
||||||
|
// import STT commands
|
||||||
|
mod stt;
|
||||||
|
pub use stt::*;
|
||||||
|
|
@ -10,6 +10,7 @@ pub fn db_read(state: tauri::State<'_, AppState>, key: &str) -> String {
|
||||||
"assistant_voice" => settings.voice.clone(),
|
"assistant_voice" => settings.voice.clone(),
|
||||||
"selected_wake_word_engine" => format!("{:?}", settings.wake_word_engine),
|
"selected_wake_word_engine" => format!("{:?}", settings.wake_word_engine),
|
||||||
"selected_intent_recognition_engine" => format!("{:?}", settings.intent_recognition_engine),
|
"selected_intent_recognition_engine" => format!("{:?}", settings.intent_recognition_engine),
|
||||||
|
"selected_vosk_model" => settings.vosk_model.clone(),
|
||||||
"speech_to_text_engine" => format!("{:?}", settings.speech_to_text_engine),
|
"speech_to_text_engine" => format!("{:?}", settings.speech_to_text_engine),
|
||||||
"api_key__picovoice" => settings.api_keys.picovoice.clone(),
|
"api_key__picovoice" => settings.api_keys.picovoice.clone(),
|
||||||
"api_key__openai" => settings.api_keys.openai.clone(),
|
"api_key__openai" => settings.api_keys.openai.clone(),
|
||||||
|
|
@ -49,6 +50,9 @@ pub fn db_write(state: tauri::State<'_, AppState>, key: &str, val: &str) -> bool
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"selected_vosk_model" => {
|
||||||
|
settings.vosk_model = val.to_string();
|
||||||
|
}
|
||||||
"api_key__picovoice" => {
|
"api_key__picovoice" => {
|
||||||
settings.api_keys.picovoice = val.to_string();
|
settings.api_keys.picovoice = val.to_string();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
crates/jarvis-gui/src/tauri_commands/stt.rs
Normal file
21
crates/jarvis-gui/src/tauri_commands/stt.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use jarvis_core::{vosk_models, DB};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct VoskModel {
|
||||||
|
pub name: String,
|
||||||
|
pub language: String,
|
||||||
|
pub size: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn list_vosk_models() -> Vec<VoskModel> {
|
||||||
|
vosk_models::scan_vosk_models()
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| VoskModel {
|
||||||
|
name: m.name,
|
||||||
|
language: m.language,
|
||||||
|
size: m.size,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let availableMicrophones: MicrophoneOption[] = []
|
let availableMicrophones: MicrophoneOption[] = []
|
||||||
|
let availableVoskModels: { label: string; value: string }[] = []
|
||||||
let settingsSaved = false
|
let settingsSaved = false
|
||||||
let saveButtonDisabled = false
|
let saveButtonDisabled = false
|
||||||
|
|
||||||
|
|
@ -47,6 +48,7 @@
|
||||||
let selectedMicrophone = ""
|
let selectedMicrophone = ""
|
||||||
let selectedWakeWordEngine = ""
|
let selectedWakeWordEngine = ""
|
||||||
let selectedIntentRecognitionEngine = ""
|
let selectedIntentRecognitionEngine = ""
|
||||||
|
let selectedVoskModel = ""
|
||||||
let apiKeyPicovoice = ""
|
let apiKeyPicovoice = ""
|
||||||
let apiKeyOpenai = ""
|
let apiKeyOpenai = ""
|
||||||
|
|
||||||
|
|
@ -73,6 +75,7 @@
|
||||||
invoke("db_write", { key: "selected_microphone", val: selectedMicrophone }),
|
invoke("db_write", { key: "selected_microphone", val: selectedMicrophone }),
|
||||||
invoke("db_write", { key: "selected_wake_word_engine", val: selectedWakeWordEngine }),
|
invoke("db_write", { key: "selected_wake_word_engine", val: selectedWakeWordEngine }),
|
||||||
invoke("db_write", { key: "selected_intent_recognition_engine", val: selectedIntentRecognitionEngine }),
|
invoke("db_write", { key: "selected_intent_recognition_engine", val: selectedIntentRecognitionEngine }),
|
||||||
|
invoke("db_write", { key: "selected_vosk_model", val: selectedVoskModel }),
|
||||||
invoke("db_write", { key: "api_key__picovoice", val: apiKeyPicovoice }),
|
invoke("db_write", { key: "api_key__picovoice", val: apiKeyPicovoice }),
|
||||||
invoke("db_write", { key: "api_key__openai", val: apiKeyOpenai })
|
invoke("db_write", { key: "api_key__openai", val: apiKeyOpenai })
|
||||||
])
|
])
|
||||||
|
|
@ -107,11 +110,19 @@
|
||||||
value: String(idx)
|
value: String(idx)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// load vosk models
|
||||||
|
const voskModels = await invoke<{ name: string; language: string; size: string }[]>("list_vosk_models")
|
||||||
|
availableVoskModels = voskModels.map(m => ({
|
||||||
|
label: `${m.name} (${m.language}, ${m.size})`,
|
||||||
|
value: m.name
|
||||||
|
}))
|
||||||
|
|
||||||
// load settings from db
|
// load settings from db
|
||||||
const [mic, wakeWord, intentReco, pico, openai] = await Promise.all([
|
const [mic, wakeWord, intentReco, voskModel, pico, openai] = await Promise.all([
|
||||||
invoke<string>("db_read", { key: "selected_microphone" }),
|
invoke<string>("db_read", { key: "selected_microphone" }),
|
||||||
invoke<string>("db_read", { key: "selected_wake_word_engine" }),
|
invoke<string>("db_read", { key: "selected_wake_word_engine" }),
|
||||||
invoke<string>("db_read", { key: "selected_intent_recognition_engine" }),
|
invoke<string>("db_read", { key: "selected_intent_recognition_engine" }),
|
||||||
|
invoke<string>("db_read", { key: "selected_vosk_model" }),
|
||||||
invoke<string>("db_read", { key: "api_key__picovoice" }),
|
invoke<string>("db_read", { key: "api_key__picovoice" }),
|
||||||
invoke<string>("db_read", { key: "api_key__openai" })
|
invoke<string>("db_read", { key: "api_key__openai" })
|
||||||
])
|
])
|
||||||
|
|
@ -119,6 +130,7 @@
|
||||||
selectedMicrophone = mic
|
selectedMicrophone = mic
|
||||||
selectedWakeWordEngine = wakeWord
|
selectedWakeWordEngine = wakeWord
|
||||||
selectedIntentRecognitionEngine = intentReco
|
selectedIntentRecognitionEngine = intentReco
|
||||||
|
selectedVoskModel = voskModel
|
||||||
apiKeyPicovoice = pico
|
apiKeyPicovoice = pico
|
||||||
apiKeyOpenai = openai
|
apiKeyOpenai = openai
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -232,6 +244,29 @@
|
||||||
</Alert>
|
</Alert>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<Space h="xl" />
|
||||||
|
{#key availableVoskModels}
|
||||||
|
<NativeSelect
|
||||||
|
data={[
|
||||||
|
{ label: "Авто-определение", value: "" },
|
||||||
|
...availableVoskModels
|
||||||
|
]}
|
||||||
|
label="Модель распознавания речи (Vosk)"
|
||||||
|
description="Выберите модель Vosk для распознавания речи."
|
||||||
|
variant="filled"
|
||||||
|
bind:value={selectedVoskModel}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
{#if availableVoskModels.length === 0}
|
||||||
|
<Space h="sm" />
|
||||||
|
<Alert title="Модели не найдены" color="orange" variant="outline">
|
||||||
|
<Text size="sm" color="gray">
|
||||||
|
Поместите модели Vosk в папку resources/vosk
|
||||||
|
</Text>
|
||||||
|
</Alert>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<Space h="xl" />
|
<Space h="xl" />
|
||||||
<NativeSelect
|
<NativeSelect
|
||||||
data={[
|
data={[
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// vite.config.ts
|
||||||
|
import { defineConfig } from "file:///D:/Rust/jarvis-app/frontend/node_modules/vite/dist/node/index.js";
|
||||||
|
import { svelte } from "file:///D:/Rust/jarvis-app/frontend/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
|
||||||
|
import sveltePreprocess from "file:///D:/Rust/jarvis-app/frontend/node_modules/svelte-preprocess/dist/index.js";
|
||||||
|
import tsconfigPaths from "file:///D:/Rust/jarvis-app/frontend/node_modules/vite-tsconfig-paths/dist/index.mjs";
|
||||||
|
import routify from "file:///D:/Rust/jarvis-app/frontend/node_modules/@roxi/routify/lib/extra/vite-plugin/vite-plugin.js";
|
||||||
|
var vite_config_default = defineConfig({
|
||||||
|
plugins: [
|
||||||
|
svelte({
|
||||||
|
preprocess: [
|
||||||
|
sveltePreprocess({
|
||||||
|
typescript: true
|
||||||
|
})
|
||||||
|
],
|
||||||
|
onwarn: (warning, handler) => {
|
||||||
|
const { code, frame } = warning;
|
||||||
|
if (code === "css-unused-selector")
|
||||||
|
return;
|
||||||
|
handler(warning);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
routify(),
|
||||||
|
tsconfigPaths()
|
||||||
|
],
|
||||||
|
clearScreen: false,
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true
|
||||||
|
},
|
||||||
|
envPrefix: ["VITE_", "TAURI_"],
|
||||||
|
build: {
|
||||||
|
target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13",
|
||||||
|
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
|
||||||
|
sourcemap: !!process.env.TAURI_DEBUG
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export {
|
||||||
|
vite_config_default as default
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxSdXN0XFxcXGphcnZpcy1hcHBcXFxcZnJvbnRlbmRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkQ6XFxcXFJ1c3RcXFxcamFydmlzLWFwcFxcXFxmcm9udGVuZFxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRDovUnVzdC9qYXJ2aXMtYXBwL2Zyb250ZW5kL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInZpdGVcIjtcclxuaW1wb3J0IHsgc3ZlbHRlIH0gZnJvbSBcIkBzdmVsdGVqcy92aXRlLXBsdWdpbi1zdmVsdGVcIjtcclxuaW1wb3J0IHN2ZWx0ZVByZXByb2Nlc3MgZnJvbSBcInN2ZWx0ZS1wcmVwcm9jZXNzXCI7XHJcbmltcG9ydCB0c2NvbmZpZ1BhdGhzIGZyb20gJ3ZpdGUtdHNjb25maWctcGF0aHMnXHJcbmltcG9ydCByb3V0aWZ5IGZyb20gJ0Byb3hpL3JvdXRpZnkvdml0ZS1wbHVnaW4nXHJcblxyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xyXG4gIHBsdWdpbnM6IFtcclxuICAgIHN2ZWx0ZSh7XHJcbiAgICAgIHByZXByb2Nlc3M6IFtcclxuICAgICAgICBzdmVsdGVQcmVwcm9jZXNzKHtcclxuICAgICAgICAgIHR5cGVzY3JpcHQ6IHRydWUsXHJcbiAgICAgICAgfSksXHJcbiAgICAgIF0sXHJcbiAgICAgIG9ud2FybjogKHdhcm5pbmcsIGhhbmRsZXIpID0+IHtcclxuICAgICAgICBjb25zdCB7IGNvZGUsIGZyYW1lIH0gPSB3YXJuaW5nO1xyXG4gICAgICAgIGlmIChjb2RlID09PSBcImNzcy11bnVzZWQtc2VsZWN0b3JcIilcclxuICAgICAgICAgICAgcmV0dXJuO1xyXG5cclxuICAgICAgICBoYW5kbGVyKHdhcm5pbmcpO1xyXG4gICAgICB9LFxyXG4gICAgfSksXHJcbiAgICByb3V0aWZ5KCksXHJcbiAgICB0c2NvbmZpZ1BhdGhzKClcclxuICBdLFxyXG5cclxuICBjbGVhclNjcmVlbjogZmFsc2UsXHJcbiAgc2VydmVyOiB7XHJcbiAgICBwb3J0OiAxNDIwLFxyXG4gICAgc3RyaWN0UG9ydDogdHJ1ZSxcclxuICB9LFxyXG4gIGVudlByZWZpeDogW1wiVklURV9cIiwgXCJUQVVSSV9cIl0sXHJcbiAgYnVpbGQ6IHtcclxuICAgIHRhcmdldDogcHJvY2Vzcy5lbnYuVEFVUklfUExBVEZPUk0gPT0gXCJ3aW5kb3dzXCIgPyBcImNocm9tZTEwNVwiIDogXCJzYWZhcmkxM1wiLFxyXG4gICAgbWluaWZ5OiAhcHJvY2Vzcy5lbnYuVEFVUklfREVCVUcgPyBcImVzYnVpbGRcIiA6IGZhbHNlLFxyXG4gICAgc291cmNlbWFwOiAhIXByb2Nlc3MuZW52LlRBVVJJX0RFQlVHLFxyXG4gIH0sXHJcbn0pOyJdLAogICJtYXBwaW5ncyI6ICI7QUFBMlEsU0FBUyxvQkFBb0I7QUFDeFMsU0FBUyxjQUFjO0FBQ3ZCLE9BQU8sc0JBQXNCO0FBQzdCLE9BQU8sbUJBQW1CO0FBQzFCLE9BQU8sYUFBYTtBQUVwQixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxZQUFZO0FBQUEsUUFDVixpQkFBaUI7QUFBQSxVQUNmLFlBQVk7QUFBQSxRQUNkLENBQUM7QUFBQSxNQUNIO0FBQUEsTUFDQSxRQUFRLENBQUMsU0FBUyxZQUFZO0FBQzVCLGNBQU0sRUFBRSxNQUFNLE1BQU0sSUFBSTtBQUN4QixZQUFJLFNBQVM7QUFDVDtBQUVKLGdCQUFRLE9BQU87QUFBQSxNQUNqQjtBQUFBLElBQ0YsQ0FBQztBQUFBLElBQ0QsUUFBUTtBQUFBLElBQ1IsY0FBYztBQUFBLEVBQ2hCO0FBQUEsRUFFQSxhQUFhO0FBQUEsRUFDYixRQUFRO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixZQUFZO0FBQUEsRUFDZDtBQUFBLEVBQ0EsV0FBVyxDQUFDLFNBQVMsUUFBUTtBQUFBLEVBQzdCLE9BQU87QUFBQSxJQUNMLFFBQVEsUUFBUSxJQUFJLGtCQUFrQixZQUFZLGNBQWM7QUFBQSxJQUNoRSxRQUFRLENBQUMsUUFBUSxJQUFJLGNBQWMsWUFBWTtBQUFBLElBQy9DLFdBQVcsQ0FBQyxDQUFDLFFBQVEsSUFBSTtBQUFBLEVBQzNCO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K
|
||||||
|
|
@ -1,27 +1,26 @@
|
||||||
# Simple python script used to
|
# Simple python script used to
|
||||||
# copy some libraries to the "target" directory
|
# copy some libraries to the "target" directory
|
||||||
# after Rust build
|
# after Rust build
|
||||||
|
|
||||||
# Note that Rust build should be run via "cargo make <cmd>" command
|
# Note that Rust build should be run via "cargo make <cmd>" command
|
||||||
# in order to automate all the compile process
|
# in order to automate all the compile process
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
# some config vars
|
# some config vars
|
||||||
|
# format: (source, destination_name)
|
||||||
SOURCE = (
|
SOURCE = (
|
||||||
"resources/commands/",
|
("resources/commands/", "resources/commands/"),
|
||||||
"resources/vosk/",
|
("resources/vosk/", "resources/vosk/"),
|
||||||
"resources/lib/",
|
("resources/lib/", "lib/"),
|
||||||
"resources/keywords/",
|
("resources/keywords/", "resources/keywords/"),
|
||||||
"resources/rustpotter/",
|
("resources/rustpotter/", "resources/rustpotter/"),
|
||||||
"resources/sound/",
|
("resources/sound/", "resources/sound/"),
|
||||||
"lib/windows/amd64/libgcc_s_seh-1.dll",
|
("lib/windows/amd64/libgcc_s_seh-1.dll", None),
|
||||||
"lib/windows/amd64/libstdc++-6.dll",
|
("lib/windows/amd64/libstdc++-6.dll", None),
|
||||||
"lib/windows/amd64/libvosk.dll",
|
("lib/windows/amd64/libvosk.dll", None),
|
||||||
"lib/windows/amd64/libvosk.lib",
|
("lib/windows/amd64/libvosk.lib", None),
|
||||||
"lib/windows/amd64/libwinpthread-1.dll"
|
("lib/windows/amd64/libwinpthread-1.dll", None)
|
||||||
)
|
)
|
||||||
|
|
||||||
TARGET_DIRS = (
|
TARGET_DIRS = (
|
||||||
|
|
@ -39,27 +38,36 @@ for tdir in TARGET_DIRS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# copy lib files
|
# copy lib files
|
||||||
for src in SOURCE:
|
for entry in SOURCE:
|
||||||
if os.path.isdir(ABS_PATH + src):
|
if isinstance(entry, tuple):
|
||||||
|
src, dest_name = entry
|
||||||
|
else:
|
||||||
|
src, dest_name = entry, None
|
||||||
|
|
||||||
|
src_path = ABS_PATH + src
|
||||||
|
|
||||||
|
if os.path.isdir(src_path):
|
||||||
# copy the whole directory
|
# copy the whole directory
|
||||||
full_target_dir_path = os.path.join(tdir, src)
|
target_name = dest_name if dest_name else os.path.basename(src.rstrip('/'))
|
||||||
|
full_target_dir_path = os.path.join(tdir, target_name)
|
||||||
|
|
||||||
if os.path.isdir(full_target_dir_path):
|
if os.path.isdir(full_target_dir_path):
|
||||||
print("[-] Directory already exists, skipping: ", src)
|
print("[-] Directory already exists, skipping: ", src, "->", target_name)
|
||||||
else:
|
else:
|
||||||
shutil.copytree(ABS_PATH + src, os.path.join(tdir, src))
|
shutil.copytree(src_path, full_target_dir_path)
|
||||||
|
print("[+] Directory copied: ", src, "->", target_name)
|
||||||
|
|
||||||
print("[+] Directory copied: ", src)
|
elif os.path.isfile(src_path):
|
||||||
elif os.path.isfile(ABS_PATH + src):
|
|
||||||
# copy file
|
# copy file
|
||||||
full_target_file_path = os.path.join(tdir, src)
|
target_name = dest_name if dest_name else os.path.basename(src)
|
||||||
|
full_target_file_path = os.path.join(tdir, target_name)
|
||||||
|
|
||||||
if os.path.isfile(full_target_file_path):
|
if os.path.isfile(full_target_file_path):
|
||||||
print("[-] File already exists, skipping: ", src)
|
print("[-] File already exists, skipping: ", src, "->", target_name)
|
||||||
else:
|
else:
|
||||||
shutil.copy(ABS_PATH + src, tdir)
|
shutil.copy(src_path, full_target_file_path)
|
||||||
print("[+] File copied: ", src)
|
print("[+] File copied: ", src, "->", target_name)
|
||||||
else:
|
else:
|
||||||
print("[?] Unknown entity to copy: ", src)
|
print("[?] Unknown entity to copy: ", src)
|
||||||
|
|
||||||
|
|
||||||
print("Post compile build done.")
|
print("Post compile build done.")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue