mirror of
https://github.com/Priler/jarvis.git
synced 2026-05-26 07:08:11 +00:00
frontend fixes & db rewrite with serde
This commit is contained in:
@@ -19,9 +19,14 @@ platform-dirs.workspace = true
|
||||
rodio.workspace = true
|
||||
kira.workspace = true
|
||||
pv_recorder.workspace = true
|
||||
vosk.workspace = true
|
||||
rustpotter.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
|
||||
# pv_recorder = { workspace = true, optional = true }
|
||||
vosk = { version = "0.3.1", optional = true }
|
||||
# rustpotter = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = ["jarvis_app"]
|
||||
jarvis_app = []
|
||||
jarvis_app = ["vosk"]
|
||||
@@ -68,8 +68,12 @@ pub fn play_sound(filename: &PathBuf) {
|
||||
}
|
||||
|
||||
pub fn get_sound_directory() -> Option<PathBuf> {
|
||||
let voice = DB.get().unwrap().voice.as_str();
|
||||
let voice_path = SOUND_DIR.join(voice);
|
||||
let db = DB.get()?;
|
||||
|
||||
let voice_path = {
|
||||
let s = db.read();
|
||||
SOUND_DIR.join(&s.voice)
|
||||
};
|
||||
|
||||
match voice_path.exists() && voice_path.cmp(&SOUND_DIR) != Ordering::Equal {
|
||||
true => Some(voice_path),
|
||||
|
||||
@@ -14,7 +14,7 @@ impl fmt::Display for WakeWordEngine {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum SpeechToTextEngine {
|
||||
Vosk,
|
||||
}
|
||||
|
||||
@@ -49,10 +49,10 @@ pub fn save_settings(settings: &structs::Settings) -> Result<(), std::io::Error>
|
||||
let db_file_path = get_db_file_path();
|
||||
|
||||
std::fs::write(
|
||||
db_file_path,
|
||||
&db_file_path,
|
||||
serde_json::to_string_pretty(&settings).unwrap(),
|
||||
)?;
|
||||
|
||||
info!("Settings saved.");
|
||||
info!("Settings saved to: {:#}", db_file_path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::config::structs::SpeechToTextEngine;
|
||||
use crate::config::structs::WakeWordEngine;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Settings {
|
||||
pub microphone: i32,
|
||||
pub voice: String,
|
||||
@@ -32,7 +32,7 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ApiKeys {
|
||||
pub picovoice: String,
|
||||
pub openai: String,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
use platform_dirs::AppDirs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -9,8 +11,13 @@ pub mod audio;
|
||||
pub mod commands;
|
||||
pub mod config;
|
||||
pub mod db;
|
||||
|
||||
#[cfg(feature = "jarvis_app")]
|
||||
pub mod listener;
|
||||
|
||||
pub mod recorder;
|
||||
|
||||
#[cfg(feature = "jarvis_app")]
|
||||
pub mod stt;
|
||||
|
||||
// shared statics
|
||||
@@ -19,7 +26,7 @@ pub static SOUND_DIR: Lazy<PathBuf> = Lazy::new(|| APP_DIR.clone().join("sound")
|
||||
pub static APP_DIRS: OnceCell<AppDirs> = OnceCell::new();
|
||||
pub static APP_CONFIG_DIR: OnceCell<PathBuf> = OnceCell::new();
|
||||
pub static APP_LOG_DIR: OnceCell<PathBuf> = OnceCell::new();
|
||||
pub static DB: OnceCell<db::structs::Settings> = OnceCell::new();
|
||||
pub static DB: OnceCell<Arc<RwLock<db::structs::Settings>>> = OnceCell::new();
|
||||
pub static COMMANDS_LIST: OnceCell<Vec<commands::AssistantCommand>> = OnceCell::new();
|
||||
|
||||
// re-exports
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod pvrecorder;
|
||||
|
||||
// mod cpal;
|
||||
// mod portaudio;
|
||||
|
||||
@@ -122,29 +123,37 @@ pub fn stop_recording() -> Result<(), ()> {
|
||||
}
|
||||
|
||||
pub fn get_selected_microphone_index() -> i32 {
|
||||
DB.get().unwrap().microphone
|
||||
DB.get().unwrap().read().microphone
|
||||
}
|
||||
|
||||
pub fn get_audio_devices() -> Vec<String> {
|
||||
match RECORDER_TYPE.get().unwrap() {
|
||||
RecorderType::PvRecorder => pvrecorder::list_audio_devices(),
|
||||
RecorderType::PortAudio => {
|
||||
match RECORDER_TYPE.get() {
|
||||
Some(RecorderType::PvRecorder) => pvrecorder::list_audio_devices(),
|
||||
Some(RecorderType::PortAudio) => {
|
||||
todo!();
|
||||
}
|
||||
RecorderType::Cpal => {
|
||||
Some(RecorderType::Cpal) => {
|
||||
todo!();
|
||||
}
|
||||
None => {
|
||||
// not initialized yet, default to pvrecorder
|
||||
pvrecorder::list_audio_devices()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_audio_device_name(idx: i32) -> String {
|
||||
match RECORDER_TYPE.get().unwrap() {
|
||||
RecorderType::PvRecorder => pvrecorder::get_audio_device_name(idx),
|
||||
RecorderType::PortAudio => {
|
||||
match RECORDER_TYPE.get() {
|
||||
Some(RecorderType::PvRecorder) => pvrecorder::get_audio_device_name(idx),
|
||||
Some(RecorderType::PortAudio) => {
|
||||
todo!();
|
||||
}
|
||||
RecorderType::Cpal => {
|
||||
Some(RecorderType::Cpal) => {
|
||||
todo!();
|
||||
}
|
||||
None => {
|
||||
// not initialized yet, default to pvrecorder
|
||||
pvrecorder::get_audio_device_name(idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[cfg(feature = "vosk")]
|
||||
mod vosk;
|
||||
|
||||
use crate::config;
|
||||
|
||||
@@ -7,22 +7,27 @@ repository.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
jarvis-core = { path = "../jarvis-core", default-features = true }
|
||||
jarvis-core = { path = "../jarvis-core", default-features = false }
|
||||
tauri = { version = "2", features = [] } # v1: "shell-open", "dialog-message", "path-all"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
|
||||
peak_alloc = "0.3.0"
|
||||
systemstat = "0.2"
|
||||
lazy_static = "1.4"
|
||||
|
||||
once_cell.workspace = true
|
||||
log.workspace = true
|
||||
simple-log = "2.4"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
platform-dirs.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
@@ -3,12 +3,20 @@
|
||||
|
||||
use jarvis_core::{config, db, APP_CONFIG_DIR, APP_LOG_DIR, DB};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[macro_use]
|
||||
extern crate simple_log;
|
||||
|
||||
mod events;
|
||||
|
||||
// mod tauri_commands;
|
||||
mod tauri_commands;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub db: Arc<RwLock<db::structs::Settings>>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
config::init_dirs().expect("Failed to init dirs");
|
||||
@@ -16,14 +24,43 @@ fn main() {
|
||||
// basic logging setup (simpler for GUI)
|
||||
simple_log::quick!("info");
|
||||
|
||||
let _ = DB.set(db::init_settings());
|
||||
// init db
|
||||
let settings = db::init_settings();
|
||||
DB.set(Arc::new(RwLock::new(settings)))
|
||||
.expect("DB already initialized");
|
||||
let db_arc = DB.get().unwrap().clone();
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(AppState { db: db_arc })
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// commands will be added here after tauri_commands module is fixed
|
||||
// audio
|
||||
tauri_commands::pv_get_audio_devices,
|
||||
tauri_commands::pv_get_audio_device_name,
|
||||
tauri_commands::play_sound,
|
||||
|
||||
// db
|
||||
tauri_commands::db_read,
|
||||
tauri_commands::db_write,
|
||||
|
||||
// etc
|
||||
tauri_commands::get_app_version,
|
||||
tauri_commands::get_author_name,
|
||||
tauri_commands::get_repository_link,
|
||||
tauri_commands::get_tg_official_link,
|
||||
tauri_commands::get_feedback_link,
|
||||
|
||||
// fs
|
||||
tauri_commands::get_log_file_path,
|
||||
tauri_commands::show_in_folder,
|
||||
|
||||
// sys
|
||||
tauri_commands::get_current_ram_usage,
|
||||
tauri_commands::get_peak_ram_usage,
|
||||
tauri_commands::get_cpu_temp,
|
||||
tauri_commands::get_cpu_usage,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
// import AUDIO commands
|
||||
mod audio;
|
||||
pub use audio::*;
|
||||
|
||||
// import DB related commands
|
||||
mod db;
|
||||
pub use db::*;
|
||||
|
||||
// import RECORDER commands
|
||||
mod audio;
|
||||
pub use audio::*;
|
||||
// import LISTENER commands
|
||||
// @REMOVED: gui not listens anymore
|
||||
// mod listener;
|
||||
// pub use listener::*;
|
||||
|
||||
// import PORCUPINE commands
|
||||
mod listener;
|
||||
pub use listener::*;
|
||||
|
||||
// import SYS commands
|
||||
mod sys;
|
||||
pub use sys::*;
|
||||
|
||||
// import VOICE commands
|
||||
mod voice;
|
||||
pub use voice::*;
|
||||
// import ETC commands
|
||||
mod etc;
|
||||
pub use etc::*;
|
||||
|
||||
// import FS commands
|
||||
mod fs;
|
||||
pub use fs::*;
|
||||
|
||||
// import ETC commands
|
||||
mod etc;
|
||||
pub use etc::*;
|
||||
// import SYS commands
|
||||
mod sys;
|
||||
pub use sys::*;
|
||||
|
||||
@@ -1,33 +1,20 @@
|
||||
use pv_recorder::RecorderBuilder;
|
||||
use jarvis_core::recorder;
|
||||
// use rodio::{Decoder, OutputStream, Sink};
|
||||
use std::path::PathBuf;
|
||||
use jarvis_core::audio;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pv_get_audio_devices() -> Vec<String> {
|
||||
let audio_devices = RecorderBuilder::default().get_audio_devices();
|
||||
match audio_devices {
|
||||
Ok(audio_devices) => audio_devices,
|
||||
Err(err) => panic!("Failed to get audio devices: {}", err),
|
||||
}
|
||||
recorder::get_audio_devices()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pv_get_audio_device_name(idx: i32) -> String {
|
||||
let audio_devices = RecorderBuilder::default().get_audio_devices();
|
||||
let mut first_device: String = String::new();
|
||||
match audio_devices {
|
||||
Ok(audio_devices) => {
|
||||
for (_idx, device) in audio_devices.iter().enumerate() {
|
||||
if idx as usize == _idx {
|
||||
return device.to_string();
|
||||
}
|
||||
|
||||
if _idx == 0 {
|
||||
first_device = device.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => panic!("Failed to get audio devices: {}", err),
|
||||
};
|
||||
|
||||
// return first device as default, if none were matched
|
||||
first_device
|
||||
recorder::get_audio_device_name(idx)
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
pub fn play_sound(filename: &str) {
|
||||
let path = PathBuf::from(filename);
|
||||
audio::play_sound(&path);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,62 @@
|
||||
use crate::DB;
|
||||
use jarvis_core::{db, DB};
|
||||
use crate::AppState;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn db_read(key: &str) -> String {
|
||||
if let Some(value) = DB.lock().unwrap().get::<String>(key) {
|
||||
return value
|
||||
}
|
||||
pub fn db_read(state: tauri::State<'_, AppState>, key: &str) -> String {
|
||||
let settings = state.db.read();
|
||||
|
||||
String::from("")
|
||||
match key {
|
||||
"selected_microphone" => settings.microphone.to_string(),
|
||||
"assistant_voice" => settings.voice.clone(),
|
||||
"selected_wake_word_engine" => format!("{:?}", settings.wake_word_engine),
|
||||
"speech_to_text_engine" => format!("{:?}", settings.speech_to_text_engine),
|
||||
"api_key__picovoice" => settings.api_keys.picovoice.clone(),
|
||||
"api_key__openai" => settings.api_keys.openai.clone(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn db_write(key: &str, val: &str) -> bool {
|
||||
if let Ok(_) = DB.lock().unwrap().set(key, &val) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
pub fn db_write(state: tauri::State<'_, AppState>, key: &str, val: &str) -> bool {
|
||||
let snapshot = {
|
||||
let mut settings = state.db.write();
|
||||
|
||||
match key {
|
||||
"selected_microphone" => {
|
||||
if let Ok(v) = val.parse::<i32>() {
|
||||
// info!("MICROPHONE changed: {}", v);
|
||||
settings.microphone = v;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
"assistant_voice" => {
|
||||
settings.voice = val.to_string();
|
||||
}
|
||||
"selected_wake_word_engine" => {
|
||||
match val.to_lowercase().as_str() {
|
||||
"rustpotter" => settings.wake_word_engine = jarvis_core::config::structs::WakeWordEngine::Rustpotter,
|
||||
"vosk" => settings.wake_word_engine = jarvis_core::config::structs::WakeWordEngine::Vosk,
|
||||
"porcupine" => settings.wake_word_engine = jarvis_core::config::structs::WakeWordEngine::Porcupine,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
"api_key__picovoice" => {
|
||||
settings.api_keys.picovoice = val.to_string();
|
||||
}
|
||||
"api_key__openai" => {
|
||||
settings.api_keys.openai = val.to_string();
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
settings.clone()
|
||||
};
|
||||
|
||||
// save to disk
|
||||
if let Err(e) = db::save_settings(&snapshot) {
|
||||
info!("SETTINGS NOT SAVED");
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::config;
|
||||
use crate::APP_LOG_DIR;
|
||||
use jarvis_core::{config, APP_LOG_DIR};
|
||||
|
||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||
|
||||
@@ -50,5 +49,7 @@ pub fn get_feedback_link() -> String {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_log_file_path() -> String {
|
||||
format!("{}", APP_LOG_DIR.lock().unwrap())
|
||||
APP_LOG_DIR.get()
|
||||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::fs::metadata;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::path::PathBuf;
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
// taken from https://github.com/tauri-apps/tauri/issues/4062#issuecomment-1338048169
|
||||
|
||||
@@ -7,6 +7,7 @@ extern crate systemstat;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use systemstat::{Platform, System};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
static ref SYS: System = System::new();
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use rodio::{Decoder, OutputStream, Sink};
|
||||
|
||||
#[tauri::command(async)]
|
||||
pub fn play_sound(filename: &str, sleep: bool) {
|
||||
// Get a output stream handle to the default physical sound device
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
let sink = Sink::try_new(&stream_handle).unwrap();
|
||||
|
||||
// Load a sound from a file, using a path relative to Cargo.toml
|
||||
// let filepath = format!("{PUBLIC_PATH}/sound/{filename}.wav");
|
||||
let filepath = filename;
|
||||
let file = BufReader::new(File::open(&filepath).unwrap());
|
||||
|
||||
// Decode that sound file into a source
|
||||
let source = Decoder::new(file).unwrap();
|
||||
|
||||
// Play the sound directly on the device
|
||||
println!("Playing {} ...", filepath);
|
||||
// stream_handle.play_raw(source.convert_samples());
|
||||
sink.append(source);
|
||||
|
||||
if sleep {
|
||||
// The sound plays in a separate thread. This call will block the current thread until the sink
|
||||
// has finished playing all its queued sounds.
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user