jarvis-cli added for dev tests
This commit is contained in:
parent
03da13ed73
commit
36acacf259
39 changed files with 369652 additions and 5 deletions
14
crates/jarvis-cli/Cargo.toml
Normal file
14
crates/jarvis-cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "jarvis-cli"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
jarvis-core = { path = "../jarvis-core", default-features = false, features = ["intent"]}
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
log.workspace = true
|
||||
env_logger = "0.11"
|
||||
parking_lot.workspace = true
|
||||
194
crates/jarvis-cli/src/main.rs
Normal file
194
crates/jarvis-cli/src/main.rs
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
use std::{io::{self, Write}, sync::Arc};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use jarvis_core::{COMMANDS_LIST, DB, JCommandsList, commands, config, db, intent};
|
||||
|
||||
fn print_help() {
|
||||
println!("
|
||||
--## Jarvis CLI - Testing Tool ##--
|
||||
|
||||
Commands:
|
||||
classify <text> - Test intent classification
|
||||
execute <text> - Simulate voice input and execute command
|
||||
list - List all loaded commands
|
||||
phrases - List all training phrases
|
||||
hash - Show commands hash
|
||||
reload - Reload commands from disk
|
||||
help - Show this help
|
||||
exit - Exit the CLI
|
||||
");
|
||||
}
|
||||
|
||||
fn list_commands(commands: &Vec<JCommandsList>) {
|
||||
println!("\n[ Loaded Commands ]");
|
||||
for cmd_list in commands {
|
||||
println!(" 📁 {}", cmd_list.path.display());
|
||||
for cmd in &cmd_list.commands {
|
||||
println!(" ├─ id: {}", cmd.id);
|
||||
println!(" ├─ action: {}", cmd.action);
|
||||
println!(" └─ phrases: {} total", cmd.phrases.len());
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn list_phrases(commands: &Vec<JCommandsList>) {
|
||||
println!("\n[ Training Phrases ]");
|
||||
for cmd_list in commands {
|
||||
for cmd in &cmd_list.commands {
|
||||
println!(" [{}]", cmd.id);
|
||||
for phrase in &cmd.phrases {
|
||||
println!(" - {}", phrase);
|
||||
}
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
async fn classify_text(text: &str) {
|
||||
match intent::classify(text).await {
|
||||
Some((intent_id, confidence)) => {
|
||||
println!(" ✓ Intent: {} (confidence: {:.2}%)", intent_id, confidence * 100.0);
|
||||
}
|
||||
None => {
|
||||
println!(" ✗ No intent matched (below threshold)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_text(commands: &'static Vec<JCommandsList>, text: &str) {
|
||||
// try intent classification first
|
||||
if let Some((intent_id, confidence)) = intent::classify(text).await {
|
||||
println!(" Intent: {} (confidence: {:.2}%)", intent_id, confidence * 100.0);
|
||||
|
||||
if let Some((cmd_path, cmd)) = intent::get_command_by_intent(commands, &intent_id) {
|
||||
println!(" Command: {:?}", cmd_path);
|
||||
println!(" Action: {}", cmd.action);
|
||||
println!(" Executing...");
|
||||
|
||||
match commands::execute_command(cmd_path, cmd) {
|
||||
Ok(chain) => println!(" ✓ Success (chain: {})", chain),
|
||||
Err(e) => println!(" ✗ Error: {}", e),
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to levenshtein
|
||||
println!(" Intent not matched, trying levenshtein fallback...");
|
||||
if let Some((cmd_path, cmd)) = commands::fetch_command(text, commands) {
|
||||
println!(" Command: {:?}", cmd_path);
|
||||
println!(" Action: {}", cmd.action);
|
||||
println!(" Executing...");
|
||||
|
||||
match commands::execute_command(cmd_path, cmd) {
|
||||
Ok(chain) => println!(" ✓ Success (chain: {})", chain),
|
||||
Err(e) => println!(" ✗ Error: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!(" ✗ No command matched");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// init logging
|
||||
env_logger::Builder::from_env(
|
||||
env_logger::Env::default().default_filter_or("info")
|
||||
).init();
|
||||
|
||||
println!("Jarvis CLI v{}", config::APP_VERSION.unwrap_or("unknown"));
|
||||
|
||||
// init dirs
|
||||
config::init_dirs()?;
|
||||
|
||||
// parse commands
|
||||
println!("\n[*] Loading commands...");
|
||||
let cmds = match commands::parse_commands() {
|
||||
Ok(c) => {
|
||||
println!(" Loaded {} command groups", c.len());
|
||||
c
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" Warning: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
COMMANDS_LIST.set(cmds).expect("Failed to set commands list");
|
||||
|
||||
// init intent classifier
|
||||
println!("[*] Initializing intent classifier...");
|
||||
match intent::init(COMMANDS_LIST.get().unwrap()).await {
|
||||
Ok(_) => println!(" Intent classifier ready"),
|
||||
Err(e) => println!(" Warning: {}", e),
|
||||
}
|
||||
|
||||
print_help();
|
||||
|
||||
// init db
|
||||
DB.set(Arc::new(RwLock::new(db::init_settings())))
|
||||
.expect("DB already initialized");
|
||||
|
||||
|
||||
// init sound
|
||||
println!("[*] Initializing audio...");
|
||||
if let Err(e) = jarvis_core::audio::init() {
|
||||
println!(" Warning: Audio init failed: {:?}", e);
|
||||
}
|
||||
|
||||
// REPL loop
|
||||
let mut input = String::new();
|
||||
loop {
|
||||
print!("jarvis> ");
|
||||
io::stdout().flush()?;
|
||||
|
||||
input.clear();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
let input = input.trim();
|
||||
|
||||
if input.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parts: Vec<&str> = input.splitn(2, ' ').collect();
|
||||
let cmd = parts[0];
|
||||
let arg = parts.get(1).map(|s| *s).unwrap_or("");
|
||||
|
||||
match cmd {
|
||||
"exit" | "quit" | "q" => {
|
||||
println!("Bye!");
|
||||
break;
|
||||
}
|
||||
"help" | "h" | "?" => print_help(),
|
||||
"list" | "ls" => list_commands(COMMANDS_LIST.get().unwrap()),
|
||||
"phrases" => list_phrases(COMMANDS_LIST.get().unwrap()),
|
||||
"hash" => {
|
||||
let hash = commands::commands_hash(COMMANDS_LIST.get().unwrap());
|
||||
println!(" Commands hash: {}", hash);
|
||||
}
|
||||
"classify" | "c" => {
|
||||
if arg.is_empty() {
|
||||
println!(" Usage: classify <text>");
|
||||
} else {
|
||||
classify_text(arg).await;
|
||||
}
|
||||
}
|
||||
"execute" | "exec" | "e" => {
|
||||
if arg.is_empty() {
|
||||
println!(" Usage: execute <text>");
|
||||
} else {
|
||||
execute_text(COMMANDS_LIST.get().unwrap(), arg).await;
|
||||
}
|
||||
}
|
||||
"reload" => {
|
||||
println!(" Note: Reload requires app restart (statics can't be reset)");
|
||||
}
|
||||
_ => {
|
||||
// treat unknown commands as text to classify
|
||||
classify_text(input).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -33,4 +33,5 @@ tokio = { version = "1", features = ["sync"], optional = true }
|
|||
|
||||
[features]
|
||||
default = ["jarvis_app"]
|
||||
jarvis_app = ["vosk", "intent-classifier", "tokio"]
|
||||
jarvis_app = ["vosk", "intent-classifier", "tokio"]
|
||||
intent = ["intent-classifier", "tokio"]
|
||||
|
|
@ -20,7 +20,7 @@ pub mod recorder;
|
|||
#[cfg(feature = "jarvis_app")]
|
||||
pub mod stt;
|
||||
|
||||
#[cfg(feature = "jarvis_app")]
|
||||
#[cfg(feature = "intent")]
|
||||
pub mod intent;
|
||||
|
||||
// shared statics
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub fn init() -> Result<(), ()> {
|
|||
// wake word files list
|
||||
// @TODO. Make it configurable via GUI for custom user voice.
|
||||
let rustpotter_wake_word_files: [&str; 1] = [
|
||||
"rustpotter/jarvis-default.rpw",
|
||||
"resources/rustpotter/jarvis-default.rpw",
|
||||
// "rustpotter/jarvis-community-1.rpw",
|
||||
// "rustpotter/jarvis-community-2.rpw",
|
||||
// "rustpotter/jarvis-community-3.rpw",
|
||||
|
|
@ -58,6 +58,8 @@ pub fn data_callback(frame_buffer: &[i16]) -> Option<i32> {
|
|||
// let detection = rustpotter.process_samples(frame_buffer.to_vec()); // @TODO. Temp crutch. Fix optimization issue, frame_buffer should not be copied to a new vector!
|
||||
let detection = rustpotter.process_samples(frame_buffer);
|
||||
|
||||
// info!("Ruspotter data callback");
|
||||
|
||||
if let Some(detection) = detection {
|
||||
if detection.score > config::RUSPOTTER_MIN_SCORE {
|
||||
info!("Rustpotter detection info:\n{:?}", detection);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue