feat: Wave 3 — plugin system + custom wake-word trainer
#29 Plugin system: - New jarvis-core::plugins module: discovers user packs in %APPDATA%\com.priler.jarvis\plugins\<name>\command.toml so authors can ship voice commands without rebuilding. Sandbox capped at "standard" — plugins cannot escalate to "full" (which would expose `os`). Disabled via a `disabled` flag file. Malformed packs warn and skip; never poison the rest of the list. 8 unit tests. - commands::parse_commands() merges plugins into the loaded list. - New /plugins GUI page with enable/disable switches, error reporting, "Open folder" button. Tauri commands plugins_list / plugins_set_enabled / plugins_open_folder. Header gets a "Плагины" button. i18n keys added for ru/en/ua. #8 Custom wake-word trainer wizard: - New jarvis-core::wake_trainer module: opens its own pv_recorder instance, records N short PCM clips, WAV-encodes them in memory, then calls rustpotter's WakewordRef::new_from_sample_buffers to train and persist a .rpw model under %APPDATA%/com.priler.jarvis/wake_words/. Keepsake WAVs are also dumped for retraining later. Sanitises names to block path traversal. 5 unit tests. - Settings gain `custom_wake_word: String`; listener/rustpotter.rs now loads the user's selected model first and always falls back to the bundled default so the assistant keeps working even if the custom file is missing. - New /wake-trainer GUI page: stepper UI for record-sample → train, shows recorded count, threshold slider, refuses to start while jarvis-app is running (mic exclusivity). Lists existing trained models with size + delete button. - 8 Tauri commands wired through (status/defaults/start/record_sample/ finish/cancel/list_models/delete_model). Tests: 115 → 128 (+8 plugins +5 wake_trainer). Release builds green for all three binaries (jarvis-app / jarvis-cli / jarvis-gui).
This commit is contained in:
parent
c4b22618f8
commit
4e21024509
17 changed files with 1625 additions and 24 deletions
47
crates/jarvis-gui/src/tauri_commands/plugins.rs
Normal file
47
crates/jarvis-gui/src/tauri_commands/plugins.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
//! Tauri commands for the plugin system. GUI uses these to render
|
||||
//! `/plugins` page: list installed plugins, toggle enabled state, reveal the
|
||||
//! plugins folder in Explorer, and open the docs page.
|
||||
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct PluginEntry {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub enabled: bool,
|
||||
pub command_count: usize,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn plugins_list() -> Vec<PluginEntry> {
|
||||
jarvis_core::plugins::list()
|
||||
.into_iter()
|
||||
.map(|p| PluginEntry {
|
||||
name: p.name,
|
||||
path: p.path.display().to_string(),
|
||||
enabled: p.enabled,
|
||||
command_count: p.command_count,
|
||||
error: p.error,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn plugins_set_enabled(name: String, enabled: bool) -> Result<(), String> {
|
||||
jarvis_core::plugins::set_enabled(&name, enabled)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn plugins_open_folder() -> Result<String, String> {
|
||||
let dir: PathBuf = jarvis_core::plugins::plugins_dir()?;
|
||||
// Open the folder in Explorer (Windows-only — fall back to printing path).
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let _ = std::process::Command::new("explorer.exe")
|
||||
.arg(&dir)
|
||||
.spawn();
|
||||
}
|
||||
Ok(dir.display().to_string())
|
||||
}
|
||||
50
crates/jarvis-gui/src/tauri_commands/wake_trainer.rs
Normal file
50
crates/jarvis-gui/src/tauri_commands/wake_trainer.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
//! Tauri commands for the wake-word trainer wizard. The GUI drives the
|
||||
//! whole flow over these — see `frontend/src/routes/wake-trainer/index.svelte`.
|
||||
//!
|
||||
//! Key safety note: pv_recorder claims the mic exclusively, so the wizard
|
||||
//! refuses to enter recording mode while jarvis-app.exe is running. The frontend
|
||||
//! also checks this beforehand and prompts the user to stop the daemon.
|
||||
|
||||
use jarvis_core::wake_trainer::{
|
||||
self, TrainedModelInfo, TrainerStatus, DEFAULT_SAMPLE_SECONDS, DEFAULT_TARGET_SAMPLES,
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn wake_trainer_status() -> TrainerStatus {
|
||||
wake_trainer::status()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn wake_trainer_defaults() -> (f32, u8) {
|
||||
(DEFAULT_SAMPLE_SECONDS, DEFAULT_TARGET_SAMPLES)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn wake_trainer_start(name: String, target_samples: u8) -> Result<(), String> {
|
||||
wake_trainer::start(&name, target_samples)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn wake_trainer_record_sample(seconds: f32) -> Result<usize, String> {
|
||||
wake_trainer::record_sample(seconds)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn wake_trainer_finish(threshold: f32) -> Result<String, String> {
|
||||
wake_trainer::finish(threshold).map(|p| p.display().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn wake_trainer_cancel() {
|
||||
wake_trainer::cancel()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn wake_trainer_list_models() -> Vec<TrainedModelInfo> {
|
||||
wake_trainer::list_models()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn wake_trainer_delete_model(name: String) -> Result<(), String> {
|
||||
wake_trainer::delete_model(&name)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue