2026-02-11 07:21:50 +05:00
|
|
|
use jarvis_core::slots;
|
2026-01-05 01:22:45 +05:00
|
|
|
use std::sync::Arc;
|
2026-01-07 00:15:36 +05:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2026-01-07 23:29:46 +05:00
|
|
|
use std::sync::mpsc;
|
2023-06-11 19:17:50 +05:00
|
|
|
|
2026-01-04 05:19:47 +05:00
|
|
|
// include core
|
|
|
|
|
use jarvis_core::{
|
2026-01-06 23:32:58 +05:00
|
|
|
audio, audio_processing, commands, config, db, listener, recorder, stt, intent,
|
2026-01-07 00:15:36 +05:00
|
|
|
ipc::{self, IpcAction},
|
2026-02-18 21:08:48 +05:00
|
|
|
i18n, voices, models,
|
2026-01-04 05:19:47 +05:00
|
|
|
APP_CONFIG_DIR, APP_LOG_DIR, COMMANDS_LIST, DB,
|
|
|
|
|
};
|
2023-06-11 19:17:50 +05:00
|
|
|
|
|
|
|
|
// include log
|
|
|
|
|
#[macro_use]
|
|
|
|
|
extern crate simple_log;
|
|
|
|
|
mod log;
|
|
|
|
|
|
|
|
|
|
// include app
|
|
|
|
|
mod app;
|
2026-04-23 02:16:02 +03:00
|
|
|
mod llm_fallback;
|
feat: TTS backend abstraction + 4 'imba' features + Lua packs
Big push driven by user feedback ("делай имбу") and web research on what
voice assistants need to be the ideal:
TTS backend abstraction (P0.1)
- new module crates/jarvis-core/src/tts/{mod,sapi,piper,silero}.rs
- TtsBackend trait with SapiBackend (current PowerShell), PiperBackend
(rhasspy/piper, neural quality), SileroBackend (python subprocess)
- JARVIS_TTS env var picks (sapi|piper|silero). Auto-detect Piper if
binary + voice present in tools/piper/. Falls back to SAPI on missing.
- SpeakOpts {lang, detached, raw} replaces ad-hoc args. text_utils
sanitiser applied unless raw=true.
- llm_fallback + lua/api/tts both routed through tts::backend().
- tools/piper/install.ps1 downloads piper.exe + ru_RU-irina-medium.onnx
from rhasspy releases + huggingface. Smoke-test included.
- tools/silero/silero_tts.py helper (PyTorch); rust spawns it as subprocess.
IMBA-1 Agentic LLM router
- crates/jarvis-app/src/llm_router.rs
- When fuzzy/intent matcher fails, LLM picks the closest command from the
full registry. Returns JSON {command_id, confidence, reason}.
- Threshold-gated re-dispatch via substitute phrase. JARVIS_LLM_ROUTER=1
enables; JARVIS_LLM_ROUTER_THRESHOLD overrides 0.55 default.
- Inserted in app.rs::execute_command between "no match" and existing
llm_fallback chat fallback.
IMBA-2 Long-term memory
- crates/jarvis-core/src/long_term_memory.rs — JSON store at
APP_CONFIG_DIR/long_term_memory.json. Atomic write-through.
- remember/recall/search/forget/all/build_context API.
- Lua bindings: jarvis.memory.* (5 functions).
- llm_fallback auto-injects relevant facts (substring search of prompt)
into system message before LLM call.
- Pack resources/commands/memory_pack/ with 4 commands: remember, recall,
forget, list.
IMBA-3 Profile switching (work/game/sleep/driving/default)
- crates/jarvis-core/src/profiles.rs — JSON profiles at APP_CONFIG_DIR/profiles/
Auto-seeds 5 defaults on first run with personality + allow/deny lists +
greetings + emoji icons.
- active_profile.txt persists choice across restart.
- Lua bindings: jarvis.profile.{active,set,list,allows,active_name}.
- llm_fallback prepends profile personality to system prompt.
- Pack resources/commands/profile_switch/ with 6 voice triggers.
IMBA-4 Multimodal screenshot + vision LLM
- crates/jarvis-core/src/lua/api/vision.rs — gated on HTTP sandbox.
- jarvis.vision.screenshot() captures via PowerShell System.Drawing.
- jarvis.vision.describe(prompt?) sends base64 PNG to Groq vision model
(default llama-3.2-11b-vision-preview, override via GROQ_VISION_MODEL).
- Pack resources/commands/vision/ with 2 commands: describe + read_error.
P0.2 Continuous conversation grace window
- config::CONVERSATION_GRACE_MS = 30_000.
- app.rs: after command result, if grace_ms > 0 keep listening WITHOUT
re-wake for the grace duration. Existing CMS_WAIT_DELAY back-dated so
the existing timeout fires at start + grace_ms.
Tests: 24/24 jarvis-core unit tests pass (including 5 text_utils).
Build: cargo build --release -p jarvis-app and -p jarvis-gui both succeed
on Windows MSVC (VS 2026 Enterprise vcvars64).
Notes for setup:
- Piper voice install: pwsh tools/piper/install.ps1 (downloads ~90 MB).
- GROQ_TOKEN needed for IMBA-1 (router) and IMBA-4 (vision).
- All features are opt-in via env vars or auto-detect; existing SAPI +
fuzzy match path remains the default.
2026-05-15 15:32:44 +03:00
|
|
|
mod llm_router;
|
2023-06-11 19:17:50 +05:00
|
|
|
|
|
|
|
|
// include tray
|
|
|
|
|
// @TODO. macOS currently not supported for tray functionality.
|
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
|
|
|
mod tray;
|
|
|
|
|
|
2026-01-07 00:15:36 +05:00
|
|
|
static SHOULD_STOP: AtomicBool = AtomicBool::new(false);
|
|
|
|
|
|
2023-06-11 19:17:50 +05:00
|
|
|
fn main() -> Result<(), String> {
|
feat: 5 new packs + dotenvy + regen icon.ico + shortcuts redo
Iron Man icon regenerated from resources/icons/icon.png at sizes
16/24/32/48/64/128/256 via Pillow (make_ico.py committed alongside);
icon.ico is now a clean 83 KB multi-res for sharp display at any
scale instead of whatever placeholder Tauri originally emitted.
dotenvy added to workspace deps. jarvis-app/main.rs and jarvis-gui/
main.rs both walk up from current_exe() looking for dev.env (up to
5 parent levels), so the Desktop shortcut can launch the exe
directly without a wrapper .bat to pre-set GROQ_TOKEN. Falls back
to dotenvy::dotenv() (cwd lookup) if nothing found near the binary.
5 new portable Lua command packs. Bumps total from 21 to 26.
cargo test -p jarvis-core --lib commands::tests still passes 3/3.
reminders/ — set_reminder. Parses "напомни через N (секунд|минут|
часов) <body>" or English equivalents; understands one Russian word
numerals (один, две, пять, десять, пятнадцать, ...) for the count
when speech recognition returns words instead of digits. Defaults to
5 minutes if number/unit missing. Spawns a detached PowerShell that
Start-Sleeps then fires: BurntToast if installed, System.Speech
SAPI (ru-RU voice preference), and a WScript.Shell.Popup as the
guaranteed-visible last resort.
date_query/ — today / tomorrow / yesterday. Single answer.lua keyed
by command_id, computes the offset via PowerShell Get-Date.AddDays()
in ru-RU culture so the weekday/month come out in Russian, then
speaks via SAPI.
voice_type/ — type_text. Strips the trigger, drops the remainder to
clipboard via jarvis.system.clipboard.set, then synthesises Ctrl+V
through user32!keybd_event so it lands in whatever window currently
has focus. Works in any text field (note app, browser, IDE,
Telegram, etc.).
dice/ — coin_flip / roll_dice / random_number. Single roll.lua,
seeds math.random with os.time + jitter, speaks the result. "1-6"
for dice, "1-100" for random.
stopwatch/ — start / check / stop. Uses jarvis.state.get/set
(persisted via jarvis-core's settings DB) to remember the start
timestamp, computes elapsed via jarvis.context.time.timestamp,
formats as "X сек" / "X мин Y сек" / "X ч Y мин Z сек".
All new packs follow the established patterns (sandbox=full, PS-via-
exec helper where needed, USERPROFILE/SystemDrive everywhere, no
hardcoded paths).
2026-05-15 12:28:44 +03:00
|
|
|
load_dotenv_near_exe();
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: init_dirs");
|
2023-06-11 19:17:50 +05:00
|
|
|
config::init_dirs()?;
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: init_logging");
|
2023-06-11 19:17:50 +05:00
|
|
|
log::init_logging()?;
|
|
|
|
|
|
|
|
|
|
info!("Starting Jarvis v{} ...", config::APP_VERSION.unwrap());
|
2026-01-04 05:19:47 +05:00
|
|
|
info!("Config directory is: {}", APP_CONFIG_DIR.get().unwrap().display());
|
2023-06-11 19:17:50 +05:00
|
|
|
info!("Log directory is: {}", APP_LOG_DIR.get().unwrap().display());
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: db::init");
|
2026-02-18 21:08:48 +05:00
|
|
|
let settings = db::init();
|
|
|
|
|
|
|
|
|
|
DB.set(settings.arc().clone())
|
2026-01-05 01:22:45 +05:00
|
|
|
.expect("DB already initialized");
|
2023-06-11 19:17:50 +05:00
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: voices::init");
|
2026-02-18 21:08:48 +05:00
|
|
|
let voice_id = settings.lock().voice.clone();
|
|
|
|
|
let language = settings.lock().language.clone();
|
|
|
|
|
if let Err(e) = voices::init(&voice_id, &language) {
|
2026-01-07 23:29:46 +05:00
|
|
|
warn!("Failed to init voices: {}", e);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: i18n::init");
|
2026-02-18 21:08:48 +05:00
|
|
|
i18n::init(&settings.lock().language);
|
2023-06-11 19:17:50 +05:00
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: llm_fallback::init");
|
2026-04-23 02:16:02 +03:00
|
|
|
llm_fallback::init();
|
|
|
|
|
|
feat: TTS backend abstraction + 4 'imba' features + Lua packs
Big push driven by user feedback ("делай имбу") and web research on what
voice assistants need to be the ideal:
TTS backend abstraction (P0.1)
- new module crates/jarvis-core/src/tts/{mod,sapi,piper,silero}.rs
- TtsBackend trait with SapiBackend (current PowerShell), PiperBackend
(rhasspy/piper, neural quality), SileroBackend (python subprocess)
- JARVIS_TTS env var picks (sapi|piper|silero). Auto-detect Piper if
binary + voice present in tools/piper/. Falls back to SAPI on missing.
- SpeakOpts {lang, detached, raw} replaces ad-hoc args. text_utils
sanitiser applied unless raw=true.
- llm_fallback + lua/api/tts both routed through tts::backend().
- tools/piper/install.ps1 downloads piper.exe + ru_RU-irina-medium.onnx
from rhasspy releases + huggingface. Smoke-test included.
- tools/silero/silero_tts.py helper (PyTorch); rust spawns it as subprocess.
IMBA-1 Agentic LLM router
- crates/jarvis-app/src/llm_router.rs
- When fuzzy/intent matcher fails, LLM picks the closest command from the
full registry. Returns JSON {command_id, confidence, reason}.
- Threshold-gated re-dispatch via substitute phrase. JARVIS_LLM_ROUTER=1
enables; JARVIS_LLM_ROUTER_THRESHOLD overrides 0.55 default.
- Inserted in app.rs::execute_command between "no match" and existing
llm_fallback chat fallback.
IMBA-2 Long-term memory
- crates/jarvis-core/src/long_term_memory.rs — JSON store at
APP_CONFIG_DIR/long_term_memory.json. Atomic write-through.
- remember/recall/search/forget/all/build_context API.
- Lua bindings: jarvis.memory.* (5 functions).
- llm_fallback auto-injects relevant facts (substring search of prompt)
into system message before LLM call.
- Pack resources/commands/memory_pack/ with 4 commands: remember, recall,
forget, list.
IMBA-3 Profile switching (work/game/sleep/driving/default)
- crates/jarvis-core/src/profiles.rs — JSON profiles at APP_CONFIG_DIR/profiles/
Auto-seeds 5 defaults on first run with personality + allow/deny lists +
greetings + emoji icons.
- active_profile.txt persists choice across restart.
- Lua bindings: jarvis.profile.{active,set,list,allows,active_name}.
- llm_fallback prepends profile personality to system prompt.
- Pack resources/commands/profile_switch/ with 6 voice triggers.
IMBA-4 Multimodal screenshot + vision LLM
- crates/jarvis-core/src/lua/api/vision.rs — gated on HTTP sandbox.
- jarvis.vision.screenshot() captures via PowerShell System.Drawing.
- jarvis.vision.describe(prompt?) sends base64 PNG to Groq vision model
(default llama-3.2-11b-vision-preview, override via GROQ_VISION_MODEL).
- Pack resources/commands/vision/ with 2 commands: describe + read_error.
P0.2 Continuous conversation grace window
- config::CONVERSATION_GRACE_MS = 30_000.
- app.rs: after command result, if grace_ms > 0 keep listening WITHOUT
re-wake for the grace duration. Existing CMS_WAIT_DELAY back-dated so
the existing timeout fires at start + grace_ms.
Tests: 24/24 jarvis-core unit tests pass (including 5 text_utils).
Build: cargo build --release -p jarvis-app and -p jarvis-gui both succeed
on Windows MSVC (VS 2026 Enterprise vcvars64).
Notes for setup:
- Piper voice install: pwsh tools/piper/install.ps1 (downloads ~90 MB).
- GROQ_TOKEN needed for IMBA-1 (router) and IMBA-4 (vision).
- All features are opt-in via env vars or auto-detect; existing SAPI +
fuzzy match path remains the default.
2026-05-15 15:32:44 +03:00
|
|
|
eprintln!("[jarvis-app] step: llm_router::init");
|
|
|
|
|
llm_router::init();
|
|
|
|
|
|
|
|
|
|
eprintln!("[jarvis-app] step: long_term_memory::init");
|
|
|
|
|
if let Err(e) = jarvis_core::long_term_memory::init() {
|
|
|
|
|
warn!("Long-term memory init failed: {}", e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eprintln!("[jarvis-app] step: profiles::init");
|
|
|
|
|
if let Err(e) = jarvis_core::profiles::init() {
|
|
|
|
|
warn!("Profiles init failed: {}", e);
|
|
|
|
|
}
|
|
|
|
|
|
feat(scheduler): IMBA-5 proactive scheduler — reminders, daily briefings, intervals
Background thread that wakes J.A.R.V.I.S. on a schedule to speak reminders.
Nothing else competes here on desktop — Алиса/Сири are reactive-only.
Core (crates/jarvis-core/src/scheduler.rs)
- Schedule::{Daily{h,m}, Interval{secs}, Once{at}} with Schedule::parse for
"daily HH:MM" / "at HH:MM" / "every N minutes|hours" / "in N minutes|hours".
Russian units (час/часа/часов/минут/секунд) also accepted.
- ScheduledTask {id, name, schedule, action, last_fired, enabled, created_at}.
Action::Speak{text} | Action::Lua{script_path}.
- JSON persistence at <APP_CONFIG_DIR>/schedule.json, atomic write-through.
- add/remove/clear/list/find; mark_fired auto-deletes Once tasks.
- start_background() spawns a 30-second tick thread (idempotent). Each tick
calls due_tasks(), fires Action via tts::speak_default (after voices::play_reply
"ahem" cue) or Lua engine.
- 7 unit tests (all passing).
Lua API (crates/jarvis-core/src/lua/api/scheduler.rs)
- jarvis.scheduler.add({name, schedule, action={type, text|script_path}})
- jarvis.scheduler.{list, count, remove(id), clear}.
- Tasks come back as {id, name, schedule_human, action, enabled, last_fired}.
Wire-up (crates/jarvis-app/src/main.rs)
- scheduler::init() + scheduler::start_background() after profiles::init.
- Tasks survive restarts via schedule.json.
Voice commands (resources/commands/scheduler/, 6 ids)
- scheduler.add_reminder "напомни через 5 минут выключить кофеварку"
- scheduler.add_at "напомни в 18:00 забрать ребёнка"
- scheduler.add_recurring "каждые 2 часа напоминай попить воды"
- scheduler.add_daily "каждый день в 9:00 делай briefing"
- scheduler.list "что у меня запланировано"
- scheduler.clear "очисти расписание"
Russian-aware parsers (час/часа/часов, минут/минуту/минуты) live inside the Lua
packs — easy to extend without touching Rust.
Tests: 31/31 jarvis-core unit tests pass (24 prior + 7 scheduler).
Build: cargo build --release -p jarvis-app and -p jarvis-gui both green.
2026-05-15 15:43:04 +03:00
|
|
|
eprintln!("[jarvis-app] step: scheduler::init + background");
|
|
|
|
|
if let Err(e) = jarvis_core::scheduler::init() {
|
|
|
|
|
warn!("Scheduler init failed: {}", e);
|
|
|
|
|
} else {
|
|
|
|
|
jarvis_core::scheduler::start_background();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: recorder::init");
|
2023-06-11 19:17:50 +05:00
|
|
|
if recorder::init().is_err() {
|
2026-05-15 11:58:47 +03:00
|
|
|
notify_mic_problem();
|
2026-04-27 21:07:33 +03:00
|
|
|
app::close(1, "recorder::init failed");
|
2023-06-11 19:17:50 +05:00
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: models::init");
|
2026-02-18 21:08:48 +05:00
|
|
|
if let Err(e) = models::init() {
|
|
|
|
|
warn!("Models registry init failed: {}", e);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: stt::init");
|
2023-06-11 19:17:50 +05:00
|
|
|
if stt::init().is_err() {
|
2026-04-27 21:07:33 +03:00
|
|
|
app::close(1, "stt::init failed");
|
2023-06-11 19:17:50 +05:00
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: commands::parse_commands");
|
2023-06-11 19:17:50 +05:00
|
|
|
info!("Initializing commands.");
|
2026-01-05 01:22:45 +05:00
|
|
|
let cmds = match commands::parse_commands() {
|
|
|
|
|
Ok(c) => c,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
warn!("Failed to parse commands: {}. Starting with empty command list.", e);
|
|
|
|
|
Vec::new()
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-01-13 02:21:59 +05:00
|
|
|
info!("Commands initialized. Count: {}, List: {:?}", cmds.len(), commands::list_paths(&cmds));
|
2026-01-04 05:19:47 +05:00
|
|
|
COMMANDS_LIST.set(cmds).unwrap();
|
2023-06-11 19:17:50 +05:00
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: audio::init");
|
2023-06-11 19:17:50 +05:00
|
|
|
if audio::init().is_err() {
|
2026-04-27 21:07:33 +03:00
|
|
|
app::close(1, "audio::init failed");
|
2023-06-11 19:17:50 +05:00
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: listener::init");
|
2026-02-18 21:08:48 +05:00
|
|
|
if let Err(e) = listener::init() {
|
|
|
|
|
error!("Wake-word engine init failed: {}", e);
|
2026-04-27 21:07:33 +03:00
|
|
|
app::close(1, "listener::init failed");
|
2023-06-11 19:17:50 +05:00
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: tokio runtime");
|
2026-02-18 21:08:48 +05:00
|
|
|
let rt = Arc::new(
|
|
|
|
|
tokio::runtime::Runtime::new().expect("Failed to create tokio runtime")
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: intent::init");
|
2026-01-05 01:22:45 +05:00
|
|
|
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);
|
2026-04-27 21:07:33 +03:00
|
|
|
app::close(1, "intent::init failed");
|
2026-01-05 01:22:45 +05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: slots::init");
|
2026-02-11 07:21:50 +05:00
|
|
|
slots::init().map_err(|e| error!("Slot extraction init failed: {}", e)).ok();
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: audio_processing::init");
|
2026-01-06 23:32:58 +05:00
|
|
|
info!("Initializing audio processing...");
|
|
|
|
|
if let Err(e) = audio_processing::init() {
|
|
|
|
|
warn!("Audio processing init failed: {}", e);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: ipc::init");
|
2026-01-07 00:15:36 +05:00
|
|
|
info!("Initializing IPC...");
|
|
|
|
|
ipc::init();
|
|
|
|
|
|
2026-01-07 23:29:46 +05:00
|
|
|
// 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| {
|
2026-01-07 00:15:36 +05:00
|
|
|
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
|
|
|
|
|
}
|
2026-01-07 23:29:46 +05:00
|
|
|
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
|
|
|
|
|
}
|
2026-01-07 00:15:36 +05:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-18 21:08:48 +05:00
|
|
|
// start WebSocket server on the shared runtime
|
|
|
|
|
let ipc_rt = Arc::clone(&rt);
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
ipc_rt.block_on(ipc::start_server());
|
2026-01-07 00:15:36 +05:00
|
|
|
});
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: spawn app thread");
|
2026-02-18 21:08:48 +05:00
|
|
|
let app_rt = Arc::clone(&rt);
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
let _ = app::start(text_cmd_rx, &app_rt);
|
2026-01-04 05:19:47 +05:00
|
|
|
});
|
|
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: tray::init_blocking");
|
2026-02-18 21:08:48 +05:00
|
|
|
tray::init_blocking(settings);
|
2023-06-11 19:17:50 +05:00
|
|
|
|
2026-04-27 21:07:33 +03:00
|
|
|
eprintln!("[jarvis-app] step: main returning Ok");
|
2023-06-11 19:17:50 +05:00
|
|
|
Ok(())
|
2026-01-07 00:15:36 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn should_stop() -> bool {
|
|
|
|
|
SHOULD_STOP.load(Ordering::SeqCst)
|
2026-02-18 21:08:48 +05:00
|
|
|
}
|
2026-05-15 11:58:47 +03:00
|
|
|
|
feat: 5 new packs + dotenvy + regen icon.ico + shortcuts redo
Iron Man icon regenerated from resources/icons/icon.png at sizes
16/24/32/48/64/128/256 via Pillow (make_ico.py committed alongside);
icon.ico is now a clean 83 KB multi-res for sharp display at any
scale instead of whatever placeholder Tauri originally emitted.
dotenvy added to workspace deps. jarvis-app/main.rs and jarvis-gui/
main.rs both walk up from current_exe() looking for dev.env (up to
5 parent levels), so the Desktop shortcut can launch the exe
directly without a wrapper .bat to pre-set GROQ_TOKEN. Falls back
to dotenvy::dotenv() (cwd lookup) if nothing found near the binary.
5 new portable Lua command packs. Bumps total from 21 to 26.
cargo test -p jarvis-core --lib commands::tests still passes 3/3.
reminders/ — set_reminder. Parses "напомни через N (секунд|минут|
часов) <body>" or English equivalents; understands one Russian word
numerals (один, две, пять, десять, пятнадцать, ...) for the count
when speech recognition returns words instead of digits. Defaults to
5 minutes if number/unit missing. Spawns a detached PowerShell that
Start-Sleeps then fires: BurntToast if installed, System.Speech
SAPI (ru-RU voice preference), and a WScript.Shell.Popup as the
guaranteed-visible last resort.
date_query/ — today / tomorrow / yesterday. Single answer.lua keyed
by command_id, computes the offset via PowerShell Get-Date.AddDays()
in ru-RU culture so the weekday/month come out in Russian, then
speaks via SAPI.
voice_type/ — type_text. Strips the trigger, drops the remainder to
clipboard via jarvis.system.clipboard.set, then synthesises Ctrl+V
through user32!keybd_event so it lands in whatever window currently
has focus. Works in any text field (note app, browser, IDE,
Telegram, etc.).
dice/ — coin_flip / roll_dice / random_number. Single roll.lua,
seeds math.random with os.time + jitter, speaks the result. "1-6"
for dice, "1-100" for random.
stopwatch/ — start / check / stop. Uses jarvis.state.get/set
(persisted via jarvis-core's settings DB) to remember the start
timestamp, computes elapsed via jarvis.context.time.timestamp,
formats as "X сек" / "X мин Y сек" / "X ч Y мин Z сек".
All new packs follow the established patterns (sandbox=full, PS-via-
exec helper where needed, USERPROFILE/SystemDrive everywhere, no
hardcoded paths).
2026-05-15 12:28:44 +03:00
|
|
|
// Look for dev.env next to the exe, then walk parents until we find one.
|
|
|
|
|
// Lets the user double-click jarvis-gui.exe / jarvis-app.exe without needing
|
|
|
|
|
// a wrapper .bat to pre-load GROQ_TOKEN and similar secrets.
|
|
|
|
|
fn load_dotenv_near_exe() {
|
|
|
|
|
if let Ok(exe) = std::env::current_exe() {
|
|
|
|
|
let mut dir = exe.parent().map(|p| p.to_path_buf());
|
|
|
|
|
for _ in 0..5 {
|
|
|
|
|
if let Some(d) = &dir {
|
|
|
|
|
let candidate = d.join("dev.env");
|
|
|
|
|
if candidate.is_file() {
|
|
|
|
|
let _ = dotenvy::from_path(&candidate);
|
|
|
|
|
eprintln!("[jarvis-app] loaded env: {}", candidate.display());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
dir = d.parent().map(|p| p.to_path_buf());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let _ = dotenvy::dotenv();
|
|
|
|
|
}
|
|
|
|
|
|
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");
|
|
|
|
|
}
|