J.A.R.V.I.S-rust/crates/jarvis-app/src/main.rs

199 lines
6.2 KiB
Rust
Raw Normal View History

use jarvis_core::slots;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
// include core
use jarvis_core::{
audio, audio_processing, commands, config, db, listener, recorder, stt, intent,
ipc::{self, IpcAction},
i18n, voices, models,
APP_CONFIG_DIR, APP_LOG_DIR, COMMANDS_LIST, DB,
};
// include log
#[macro_use]
extern crate simple_log;
mod log;
// include app
mod app;
mod llm_fallback;
// include tray
// @TODO. macOS currently not supported for tray functionality.
#[cfg(not(target_os = "macos"))]
mod tray;
static SHOULD_STOP: AtomicBool = AtomicBool::new(false);
fn main() -> Result<(), String> {
eprintln!("[jarvis-app] step: init_dirs");
config::init_dirs()?;
eprintln!("[jarvis-app] step: init_logging");
log::init_logging()?;
info!("Starting Jarvis v{} ...", config::APP_VERSION.unwrap());
info!("Config directory is: {}", APP_CONFIG_DIR.get().unwrap().display());
info!("Log directory is: {}", APP_LOG_DIR.get().unwrap().display());
eprintln!("[jarvis-app] step: db::init");
let settings = db::init();
DB.set(settings.arc().clone())
.expect("DB already initialized");
eprintln!("[jarvis-app] step: voices::init");
let voice_id = settings.lock().voice.clone();
let language = settings.lock().language.clone();
if let Err(e) = voices::init(&voice_id, &language) {
warn!("Failed to init voices: {}", e);
}
eprintln!("[jarvis-app] step: i18n::init");
i18n::init(&settings.lock().language);
eprintln!("[jarvis-app] step: llm_fallback::init");
llm_fallback::init();
eprintln!("[jarvis-app] step: recorder::init");
if recorder::init().is_err() {
fix(app): show toast + open Sound settings when mic enumeration returns nothing Before: clicking "Запустить" in jarvis-gui spawned jarvis-app, which silently exited with code 1 (or in release builds, looked like the console window flashed and closed). The pv_recorder library returns INVALID_ARGUMENT from pv_recorder_init when Windows Core Audio reports zero capture endpoints (some other app holding the mic exclusively, or all input devices disabled in mmsys.cpl). User saw no actionable feedback. Now: on recorder::init failure jarvis-app calls notify_mic_problem() which (Windows-only): - Fires a long-duration Windows toast titled "J.A.R.V.I.S.: микрофон не найден" with a hint pointing at mmsys.cpl / Recording. - Spawns "start ms-settings:sound" so the Sound settings page opens automatically — user can re-enable the mic in two clicks. Then the original app::close(1, ...) path runs to keep the same exit behaviour the GUI's get_jarvis_app_stats poller expects. Cargo.toml: jarvis-app now pulls winrt-notification (already in workspace.dependencies via jarvis-core) for the toast. Also incidentally fixed: the release-build C0000139 (entrypoint not found) loader crash that was showing up before this change. It went away after the workspace dep was added and the release relink ran. Most likely the previous release exe had a stale import table from an earlier partial rebuild; the clean relink resolves it. Non-Windows builds get a no-op eprintln so the binary still compiles for Linux/macOS.
2026-05-15 11:58:47 +03:00
notify_mic_problem();
app::close(1, "recorder::init failed");
}
eprintln!("[jarvis-app] step: models::init");
if let Err(e) = models::init() {
warn!("Models registry init failed: {}", e);
}
eprintln!("[jarvis-app] step: stt::init");
if stt::init().is_err() {
app::close(1, "stt::init failed");
}
eprintln!("[jarvis-app] step: commands::parse_commands");
info!("Initializing commands.");
let cmds = match commands::parse_commands() {
Ok(c) => c,
Err(e) => {
warn!("Failed to parse commands: {}. Starting with empty command list.", e);
Vec::new()
}
};
info!("Commands initialized. Count: {}, List: {:?}", cmds.len(), commands::list_paths(&cmds));
COMMANDS_LIST.set(cmds).unwrap();
eprintln!("[jarvis-app] step: audio::init");
if audio::init().is_err() {
app::close(1, "audio::init failed");
}
eprintln!("[jarvis-app] step: listener::init");
if let Err(e) = listener::init() {
error!("Wake-word engine init failed: {}", e);
app::close(1, "listener::init failed");
}
eprintln!("[jarvis-app] step: tokio runtime");
let rt = Arc::new(
tokio::runtime::Runtime::new().expect("Failed to create tokio runtime")
);
eprintln!("[jarvis-app] step: intent::init");
rt.block_on(async {
2026-02-08 07:30:46 +05:00
if let Err(e) = intent::init(COMMANDS_LIST.get().unwrap()).await {
error!("Failed to initialize intent classifier: {}", e);
app::close(1, "intent::init failed");
}
});
eprintln!("[jarvis-app] step: slots::init");
slots::init().map_err(|e| error!("Slot extraction init failed: {}", e)).ok();
eprintln!("[jarvis-app] step: audio_processing::init");
info!("Initializing audio processing...");
if let Err(e) = audio_processing::init() {
warn!("Audio processing init failed: {}", e);
}
eprintln!("[jarvis-app] step: ipc::init");
info!("Initializing IPC...");
ipc::init();
// channel for text commands (manually written in the GUI)
let (text_cmd_tx, text_cmd_rx) = mpsc::channel::<String>();
ipc::set_action_handler(move |action| {
match action {
IpcAction::Stop => {
info!("Received stop command from GUI");
SHOULD_STOP.store(true, Ordering::SeqCst);
}
IpcAction::ReloadCommands => {
info!("Received reload commands request");
// TODO: implement reload
}
IpcAction::SetMuted { muted } => {
info!("Received mute request: {}", muted);
// TODO: implement mute
}
IpcAction::TextCommand { text } => {
info!("Received text command: {}", text);
if let Err(e) = text_cmd_tx.send(text) {
error!("Failed to send text command to app: {}", e);
}
}
IpcAction::Ping => {
// handled internally by server
}
_ => {}
}
});
// start WebSocket server on the shared runtime
let ipc_rt = Arc::clone(&rt);
std::thread::spawn(move || {
ipc_rt.block_on(ipc::start_server());
});
eprintln!("[jarvis-app] step: spawn app thread");
let app_rt = Arc::clone(&rt);
std::thread::spawn(move || {
let _ = app::start(text_cmd_rx, &app_rt);
});
eprintln!("[jarvis-app] step: tray::init_blocking");
tray::init_blocking(settings);
eprintln!("[jarvis-app] step: main returning Ok");
Ok(())
}
pub fn should_stop() -> bool {
SHOULD_STOP.load(Ordering::SeqCst)
}
fix(app): show toast + open Sound settings when mic enumeration returns nothing Before: clicking "Запустить" in jarvis-gui spawned jarvis-app, which silently exited with code 1 (or in release builds, looked like the console window flashed and closed). The pv_recorder library returns INVALID_ARGUMENT from pv_recorder_init when Windows Core Audio reports zero capture endpoints (some other app holding the mic exclusively, or all input devices disabled in mmsys.cpl). User saw no actionable feedback. Now: on recorder::init failure jarvis-app calls notify_mic_problem() which (Windows-only): - Fires a long-duration Windows toast titled "J.A.R.V.I.S.: микрофон не найден" with a hint pointing at mmsys.cpl / Recording. - Spawns "start ms-settings:sound" so the Sound settings page opens automatically — user can re-enable the mic in two clicks. Then the original app::close(1, ...) path runs to keep the same exit behaviour the GUI's get_jarvis_app_stats poller expects. Cargo.toml: jarvis-app now pulls winrt-notification (already in workspace.dependencies via jarvis-core) for the toast. Also incidentally fixed: the release-build C0000139 (entrypoint not found) loader crash that was showing up before this change. It went away after the workspace dep was added and the release relink ran. Most likely the previous release exe had a stale import table from an earlier partial rebuild; the clean relink resolves it. Non-Windows builds get a no-op eprintln so the binary still compiles for Linux/macOS.
2026-05-15 11:58:47 +03:00
#[cfg(target_os = "windows")]
fn notify_mic_problem() {
use winrt_notification::{Toast, Duration as ToastDuration};
let _ = Toast::new(Toast::POWERSHELL_APP_ID)
.title("J.A.R.V.I.S.: микрофон не найден")
.text1("Windows не видит ни одного устройства записи.")
.text2("Откройте mmsys.cpl → Recording → включите микрофон и перезапустите Jarvis.")
.duration(ToastDuration::Long)
.show();
// Open Sound recording settings so the user can fix it in two clicks.
let _ = std::process::Command::new("cmd")
.args(["/C", "start", "", "ms-settings:sound"])
.spawn();
}
#[cfg(not(target_os = "windows"))]
fn notify_mic_problem() {
eprintln!("[jarvis-app] no recording device detected; check OS audio settings");
}