Commit graph

40 commits

Author SHA1 Message Date
Bossiara13
5c7245012e feat: hot-swap LLM backend + media keys + codebase Q&A + scheduler cancel-by-text + TTS pre-warm
Single biggest user-facing change: you can now switch local↔cloud LLM by voice
without restarting. Plus four new packs and a perf nudge.

Shared LLM global (crates/jarvis-core/src/llm/mod.rs)
  - GLOBAL: Lazy<RwLock<Option<Arc<LlmClient>>>>. Single source of truth.
  - init_global() / current() / swap_to(LlmBackend) / current_backend_name() / parse_backend(str).
  - llm_fallback + llm_router migrated: they no longer own a separate LlmClient,
    they read from the global on each request. Hot-swap is now LIVE — change
    backend, next chat call uses the new one.
  - parse_backend accepts both english and russian aliases:
      "groq"/"cloud"/"облако"/"клауд"        → Groq
      "ollama"/"local"/"локал"/"локальный"  → Ollama

Voice + Lua LLM switcher (resources/commands/llm_switch/, 3 commands)
  - "переключись на локальный" / "перейди на оллама"  → swap to Ollama
  - "переключись на облако" / "используй грок"        → swap to Groq
  - "какой у тебя мозг" / "облако или локально"       → status query

  Underlying Lua API (registered globally):
    jarvis.llm({messages}, opts)   -- as before, now uses global client
    jarvis.llm_status()            -> "groq" | "ollama" | "none"
    jarvis.llm_switch(name)        -> name on success, nil on failure

Media keys pack (resources/commands/media_keys/, 4 commands)
  - "пауза" / "плей"           → VK_MEDIA_PLAY_PAUSE
  - "следующий трек"           → VK_MEDIA_NEXT_TRACK
  - "предыдущий трек"          → VK_MEDIA_PREV_TRACK
  - "стоп музыка"              → VK_MEDIA_STOP
  - Helper _media.ps1 uses user32.keybd_event (P/Invoke through Add-Type).
  - Works with anything that listens to global media keys: Spotify, Yandex Music,
    YouTube (focused tab), Foobar2000, Winamp, etc. No OAuth, no API keys.

Codebase Q&A pack (resources/commands/codebase_qa/, 3 commands)
  - "укажи проект <path>"             → stores path in jarvis.memory under codebase.root
  - "какой проект сейчас"             → speaks current path
  - "что делает функция X" / "найди в коде Y" / "объясни код"
    → walks the folder (depth 3, 30 files cap, 4KB per file, 50KB total),
      filters by source extensions (rs/py/ts/lua/go/...), feeds digest to LLM
      with a "senior reviewer" system prompt, speaks 3-5 sentence answer.

Scheduler cancel-by-text (crates/jarvis-core/src/scheduler.rs)
  - new pub fn find_by_text(query, limit) -> Vec<ScheduledTask>
  - new pub fn remove_by_text(query) -> usize (count removed)
  - new pure helper find_by_text_in(tasks, query, limit) for tests
  - Lua: jarvis.scheduler.{find_by_text, remove_by_text}
  - Voice: "отмени напоминание про воду" / "удали задачу про разминку"
  - 3 new unit tests (52 total in jarvis-core).

TTS pre-warm (crates/jarvis-app/src/main.rs)
  - Call jarvis_core::tts::backend() once on startup so the first speak()
    doesn't pay Piper binary discovery + voice loading cost. Cuts first-speak
    latency by ~150-300ms depending on backend.

Build: cargo build --release -p jarvis-app -p jarvis-gui both green.
Tests: 52/52 jarvis-core unit tests pass.
2026-05-15 16:15:59 +03:00
Bossiara13
6225198821 refactor: maintainability pass — dedup, central env config, cmd helpers, tests, ARCHITECTURE.md
User asked the code stays easy to fix/extend as the feature surface grows.
This commit refactors what was duplicated, centralises what was scattered,
adds tests where there were none, and writes the architecture doc that
contributors will need.

Deduplication
  - tts::play_wav extracted to tts/mod.rs as pub(crate). Was identical in
    piper.rs and silero.rs (~25 lines × 2). Both backends now call super::play_wav.

Centralised env config
  - new crates/jarvis-core/src/runtime_config.rs — single doc-file for every
    JARVIS_* / GROQ_* / OLLAMA_* env var. Includes:
      - ENV_* constants with doc comments
      - get(name) / get_bool(name, default) / get_parse(name, default) helpers
      - feature-flag wrappers: llm_tts_enabled(), llm_router_enabled(),
        llm_router_threshold()
      - log_effective_config() prints active values on startup
  - migrated llm_fallback (JARVIS_LLM_TTS), llm_router (JARVIS_LLM_ROUTER,
    JARVIS_LLM_ROUTER_THRESHOLD) to use the new helpers. Pattern set for
    future migrations.

Lua boilerplate killer
  - new crates/jarvis-core/src/lua/api/cmd.rs exposing:
      jarvis.cmd.ok(msg?)         — play_ok + speak + {chain=false}
      jarvis.cmd.chain_ok(msg?)   — same but chain=true
      jarvis.cmd.error(msg?)      — play_error + speak + {chain=false}
      jarvis.cmd.not_found(msg?)  — play_not_found + speak + {chain=false}
      jarvis.cmd.silent_ok / silent_error
  - refactored 5 packs (daily_briefing/off, memory_pack/list, pomodoro/stop,
    habit_nudge/stop_all, scheduler/clear) — each lost 3-4 lines of repetitive
    play_*/speak/return boilerplate. Pattern for future packs documented in
    ARCHITECTURE.md.

Tests (was 32, now 49)
  - long_term_memory: 8 new tests for normalize_key, search_in (rank, limit,
    empty), build_context_from (empty + populated), serde round-trips for
    MemoryRecord and Store. Extracted pure logic (search_in, build_context_from)
    into pub(crate) functions to enable testing without global state.
  - profiles: 6 new tests for Profile::allows_command (empty/whitelist/blacklist/
    deny-wins-over-allow), serde round-trip with all fields + minimal-fields
    tolerance via #[serde(default)].
  - runtime_config: 2 tests for get_bool / get_parse defaults.
  - All 49 tests pass.

New mood/energy log pack
  - resources/commands/mood_log/ with 2 commands:
      mood.record  "запиши настроение 7" / "сегодня мне грустно" → stores
                   timestamped entry via jarvis.memory.remember
      mood.recap   "как прошла неделя" → LLM summarises last 30 entries
  - Showcase: composes memory + llm + cmd helpers in <30 lines per script.

ARCHITECTURE.md (new, 250 lines)
  - Crate layout, data flow diagram (mic→action 10 steps), per-module
    responsibility table, configuration layers, TTS pipeline diagram,
    Lua sandbox details with API quick-ref, background-services overview,
    "how to add a pack/feature/TTS backend" recipes, test coverage map,
    build instructions with MSVC env, git workflow with Forgejo NO_PROXY trick.
  - Aimed at someone who just cloned the repo and needs to fix a bug fast.

Build: cargo build --release -p jarvis-app -p jarvis-gui both green.
Tests: 49/49 jarvis-core unit tests pass.
2026-05-15 16:06:18 +03:00
Bossiara13
45243c3e3c feat: Ollama LLM backend + 5 utility packs (daily briefing, pomodoro, currency convert, stocks, habits, translate clipboard)
Local-LLM support so J.A.R.V.I.S. works offline without GROQ_TOKEN.

LLM multi-backend (crates/jarvis-core/src/llm/client.rs)
  - LlmBackend enum {Groq, Ollama}. Both use OpenAI-compatible /v1/chat/completions
    (Ollama exposes one natively at :11434/v1).
  - LlmClient::from_env() now dispatches on JARVIS_LLM=groq|ollama. Auto-detect:
    Groq if GROQ_TOKEN set, else Ollama.
  - New helpers: LlmClient::groq() (was: from_env), LlmClient::ollama().
  - Reqwest client now has a 60s timeout (was: unbounded — Groq freezes could hang).
  - api_key skipped when empty (Ollama doesn't need auth).
  - Defaults: OLLAMA_BASE_URL=http://localhost:11434/v1, OLLAMA_MODEL=qwen2.5:3b.
    Override via OLLAMA_BASE_URL / OLLAMA_MODEL env.
  - llm_fallback + llm_router log the active backend on init.
  - Tests: 32/32 pass. Renamed `from_env_fails` → `groq_fails_when_token_missing`,
    added `ollama_works_without_token`.

Daily briefing pack (resources/commands/daily_briefing/, 3 commands)
  - setup.lua: "настрой утренний брифинг на 9:00" → adds a daily scheduled task
    that runs now.lua via the scheduler's Lua-action support.
  - now.lua: greeting + time + active profile + scheduled task count + memory recap
    + LLM motivational nudge. Voice-triggered ("утренний брифинг") works the same.
  - off.lua: removes the scheduled task.

Pomodoro pack (resources/commands/pomodoro/, 2 commands)
  - start.lua / stop.lua + tick.lua (re-scheduled by itself). State stored in
    jarvis.state — phase alternates work (25 min) / break (5 min) until stop.

Currency conversion (resources/commands/currency/convert.lua)
  - "сколько будет 1000 долларов в рублях" — fetches CBR daily rates from
    cbr-xml-daily.ru, normalises by Nominal, prints both directions.
  - Recognises USD/EUR/CNY/GBP/JPY/RUB by substring. Pluralises russian units.

Stocks pack (resources/commands/stocks/, 1 command)
  - "сколько Сбер" → MOEX ISS /iss/engines/stock/markets/shares/securities/<TKR>.
  - Mapping of 22 popular Russian tickers to common names (Сбер→SBER, Газпром→GAZP,
    Лукойл→LKOH, Яндекс→YDEX, Т-банк→T, etc).
  - Picks TQBR (main board) row, reports LAST + LASTTOPREVPRICE%.

Habit nudges (resources/commands/habit_nudge/, 4 commands)
  - "напоминай мне пить воду"     → every 2 hours
  - "напоминай мне размяться"     → every 50 minutes
  - "напоминай отдыхать глазам"  → every 20 minutes (20-20-20 rule)
  - "отключи все привычки"        → stops all three by id.

Translate clipboard (resources/commands/translate/clipboard.lua)
  - "переведи буфер на английский" → grabs clipboard, sends to LLM, speaks first
    sentence, replaces clipboard with full translation, fires notification.

Build: cargo build --release -p jarvis-app -p jarvis-gui both green. 32/32 tests.

To use Ollama:
  1. Install + run Ollama (https://ollama.com).
  2. `ollama pull qwen2.5:3b` (or any chat model).
  3. Optional: `set JARVIS_LLM=ollama` (auto-picked if GROQ_TOKEN unset).
2026-05-15 15:51:24 +03:00
Bossiara13
12b1ed4ccb 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
Bossiara13
0b1f1d4480 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
Bossiara13
80b54af1ee fix(tts): sanitise text before SAPI so it stops reading "J.A.R.V.I.S." as "J точка A точка R точка..."
User complaint: SAPI reads every dotted acronym character-by-character with a
"точка" between letters. Unbearable on every speak. No real TTS analog
shipping yet (Silero/Coqui/Inworld in roadmap), so the immediate fix is text
preprocessing.

New module `jarvis-core::text_utils` with `sanitize_for_speech()`:
- Specific brand mapping: J.A.R.V.I.S. → Джарвис, U.S.A. → США, U.K. →
  Британия, U.S. → США, S.O.S. → сос.
- Generic dotted-acronym collapse: any `[letter].[letter].[letter].(...)`
  run of 3+ letters gets its dots stripped. "T.O.N." → "TON".
- URL stripping: anything starting with http/https gets replaced with the
  word "ссылка". SAPI reading URLs char-by-char is unlistenable.
- Em/en-dash normalisation: — → -, « » " stripped.
- Whitespace collapse.

Wired in two places:
- `jarvis.speak(text, opts?)` Lua API runs sanitiser unless opts.raw=true.
- `jarvis-app/llm_fallback::speak_via_sapi` runs sanitiser before the SAPI
  PowerShell shell-out for LLM auto-fallback replies.

5 unit tests in `text_utils::tests` covering jarvis-name collapse, generic
acronym pattern, URL stripping, dash normalisation, and "leave clean
russian sentences alone" — all green.

Real TTS upgrade (Silero v4 first cut) tracked as P0.1 in roadmap.
2026-05-15 14:48:42 +03:00
Bossiara13
a7c002c9d4 arch + features: jarvis.text API + brightness/window_switch/spelling/network/summarize (41 packs)
Architecture — close the trigger-strip duplication:

  lua/api/text.rs (new):
  + jarvis.text.strip_trigger(phrase, triggers) — case-insensitive
    "longest trigger wins" stripping, returns trimmed remainder. The
    11 packs that previously inlined a `for _, t in ipairs(triggers) do
    string.find ... s == 1 then sub` loop can drop to one call.
    Demonstrated in spelling/, window_switch/, summarize/.
  + jarvis.text.contains_any(phrase, needles) — boolean check for any
    of N substrings, case-insensitive. Useful for keyword routing.

5 new portable Lua packs (sandbox=full). 36 → 41 total. cargo test
commands::tests still 3/3.

brightness/ — brightness_up / down / max / min. WMI WmiMonitorBrightness
(read CurrentBrightness) + WmiMonitorBrightnessMethods.WmiSetBrightness
(write). ±20% step for up/down; max=100, min=10. Desktop monitors that
don'\''t expose the WMI namespace get a friendly "не поддерживается"
toast instead of a silent failure.

window_switch/ — switch_to_window. Strip trigger ("переключись на" /
"switch to" / etc.), apply the same alias map as process_kill
(хром→chrome, телега→telegram, ...), then PowerShell finds processes
by name OR window-title substring sorted by StartTime desc and uses
(New-Object -ComObject WScript.Shell).AppActivate($pid) on the top
match. Speaks the window title that got focus.

spelling/ — spell_out. Strips "произнеси по буквам" / "spell out" /
etc., iterates `word:gmatch(".")`, joins letters with ". " so SAPI
gives each one a natural micro-pause. Uses jarvis.text.strip_trigger
+ jarvis.speak — 24 lines total.

network/ — open_wifi_settings (ms-settings:network-wifi) +
open_bluetooth_settings (ms-settings:bluetooth) + my_ip. my_ip pulls
the first non-loopback, non-APIPA IPv4 with Get-NetIPAddress and
fetches WAN IP from api.ipify.org (5s timeout); speaks "Локальный X.
Внешний Y".

summarize/ — summarize_selection. Synthesises Ctrl+C through user32!
keybd_event (so the focused window copies its selection), waits
220 ms for the clipboard to populate, reads via
jarvis.system.clipboard.get(), bails if < 20 chars, truncates at
8000, sends to Groq @ temp 0.3 with a "3-5 sentences, keep names"
prompt, drops the summary back into the clipboard and speaks it.
Lets you select ANY text in ANY app (browser, PDF, IDE) and say
"суммируй" to get a spoken TL;DR.

The new packs explicitly use the new jarvis.text + jarvis.llm +
jarvis.speak API surface — no inline PS-SAPI boilerplate, no inline
Groq plumbing. Code is now intent-first.
2026-05-15 13:11:18 +03:00
Bossiara13
a16d2401e7 arch + features: Lua jarvis.speak() / jarvis.llm() API, /commands GUI page, games / mouse / random_choice packs
Architecture — DRY at the Lua API surface:

  lua/api/tts.rs (new): jarvis.speak(text, opts?). opts.lang = ISO
  two-letter code (default "ru"); auto-picks first installed voice
  matching the culture. opts.async = true|false (default true). The
  9 packs that previously inlined a 60-character PS-SAPI string for
  every spoken reply can now reduce to one line. Refactored fun/,
  dice/, clipboard_read/ as proof — fun/ask.lua went from 75 lines
  of HTTP+SAPI boilerplate to ~28 lines of intent code.

  lua/api/llm.rs (new): jarvis.llm(messages, opts?). messages is a
  list of {role, content} tables, opts.max_tokens / .temperature /
  .top_p. Returns the assistant string or nil. Gated by sandbox
  allows_http() (so Standard+ packs only). Internally uses the
  existing jarvis_core::llm::LlmClient::complete_with, no duplicated
  HTTP plumbing.

  llm/client.rs: split complete() into complete_with(messages,
  max_tokens, temperature, top_p) for the new caller; old complete()
  is now a thin wrapper at temp=0.7 top_p=1.0 for backward compat.

GUI — /commands page rewrite:

  Replaces the "Раздел в разработке" placeholder. Calls Tauri command
  get_commands_list (already existed in tauri_commands/commands.rs,
  was wired but unused). Renders a search-filterable card grid:
    - cmd id (monospace)
    - type badge with colour by kind (lua=blue, ahk=red, cli=grey,
      voice=green, terminate=red, stop_chaining=violet)
    - sandbox badge
    - description line (if non-empty)
    - all phrases for currentLanguage, fallback to en, fallback to
      first available; each phrase in a small inline pill
  Toolbar: text input with magnifier icon + reload button (↻).
  Counter line shows visible/total + "Скажи Джарвис + любую фразу"
  hint. Counts down as you type the filter.

New packs (3, total 33 → 36):

  games/ — launch_game / list_games. Reads or creates
  %USERPROFILE%\Documents\jarvis-games.json (the sample preloads dota,
  cs2, elden ring, witcher 3, factorio + minecraft launcher path +
  fortnite epic URI). Trigger-strip + fuzzy name/alias match, then
  launches via steam://rungameid/N (Steam), epic:// (Epic Games), or
  start "" "path" (anything else). list_games speaks the configured
  library.

  mouse/ — left/right/middle/double click + scroll up/down. Single
  dispatch.lua + _mouse_helper.ps1. PS P/Invokes user32!mouse_event
  with the right flag pair (LEFTDOWN+LEFTUP / etc.), 30ms gap, doubles
  do two LEFT cycles with a 50ms gap, wheel uses ±360 ticks.

  random_choice/ — "выбери из X или Y или Z". Strips the trigger,
  splits on " или " / " or " / ", " / " либо " / " чи ", math.random
  picks one, speaks "Я выбираю N".

cargo test commands::tests still 3/3. 36 packs verified via
jarvis-gui startup log.
2026-05-15 12:58:16 +03:00
Bossiara13
69a38b0d89 feat: LLM auto-fallback + codegen pack + OCR pack
LLM auto-fallback (jarvis-app/src/app.rs + jarvis-core/src/config.rs):
- When neither intent classifier nor levenshtein finds a command match,
  route the utterance straight to the LLM instead of just playing the
  "not found" sound. Triggers ("скажи X", "answer Y") still work and
  take precedence — they short-circuit before command lookup.
- Two new config knobs:
    LLM_AUTO_FALLBACK = true        — master toggle
    LLM_AUTO_FALLBACK_MIN_CHARS = 4 — suppress for very short utterances
                                       so background noise doesn't burn
                                       Groq quota
- Requires GROQ_TOKEN; if absent, behaviour is unchanged (play_not_found).

codegen/ command pack:
- Phrases: "напиши код X", "сгенерируй скрипт Y", "write code Z", etc.
- Strips the trigger from the recognized phrase, sends what remains to
  Groq with a strict system prompt ("return ONLY code, no fences, no
  commentary") at temperature 0.2.
- Parses the JSON content, unescapes \n / \" / \\ / \t, strips any
  remaining ```lang ... ``` fences, drops the result into the clipboard
  via jarvis.system.clipboard.set.
- Notifies a 120-char preview + plays ok-sound. Works for any language
  the model handles (Python by default if unspecified).
- GROQ_TOKEN / GROQ_MODEL / GROQ_BASE_URL read from env at call time —
  same envvars the voice-loop fallback already uses.

ocr/ command pack:
- Phrases: "прочитай экран", "что на экране", "read screen", etc.
- Captures the primary screen via System.Windows.Forms + System.Drawing
  to a temp PNG, then shells to tesseract.exe (-l rus+eng for ru/ua,
  -l eng for en).
- Resolves Tesseract by PATH first, then C:\Program Files\Tesseract-OCR
  and the x86 install dir; if none works the user gets a friendly
  "winget install UB-Mannheim.TesseractOCR" hint in a notification.
- Recognized text goes to clipboard + a 200-char preview notification.

All three features are portable (env-var resolution, no hardcoded user
paths). Command-pack total is now 11. cargo test -p jarvis-core --lib
commands::tests passes 3/3.
2026-05-15 01:39:21 +03:00
Bossiara13
429113175a build(core): add build.rs so cargo test links against libvosk
cargo test on jarvis-core was failing with `LNK1181 cannot open input
file "libvosk.lib"`. The link-search path is declared in jarvis-app's
build.rs (and jarvis-cli's), but the test binary for jarvis-core has
no such config and the workspace .cargo/config.toml didn't cover it.

Adds a minimal build.rs that emits `cargo:rustc-link-search=native=...`
pointing at lib/windows/amd64 (resolved relative to CARGO_MANIFEST_DIR,
no hardcoded absolute paths so the project still builds for anyone
cloning under a different drive/path).

Verified: cargo test -p jarvis-core --lib commands::tests now passes
3/3 (every_command_toml_parses, lua_command_scripts_exist,
every_command_has_phrases).
2026-05-15 01:32:17 +03:00
Bossiara13
24457ad6c9 chore(gui): rebrand — strip TG/Boosty/Patreon/feedback-bot, keep CC BY-NC-SA attribution
The previous feature/gui-rebrand-polish work only updated the appInfo store
keys; Footer.svelte, settings, and the commands page were still rendering
upstream's Telegram channel, Boosty/Patreon support links, feedback bot,
and the original author byline. This finishes the job.

Rust side (applies after `cargo build` — currently a no-op until MSVC Build
Tools are reinstalled):
- Cargo.toml: authors = ["Bossiara13"], repository = J.A.R.V.I.S-rust fork.
  Original attribution stays in LICENSE.txt per CC BY-NC-SA 4.0; the UI
  no longer renders an author byline.
- config.rs: dropped TG_OFFICIAL_LINK / FEEDBACK_LINK / SUPPORT_BOOSTY_LINK
  / SUPPORT_PATREON_LINK. Added UPSTREAM_REPOSITORY_LINK (Priler/jarvis for
  the licence attribution), ISSUES_LINK (this fork's GH issues), and
  PYTHON_FORK_LINK.
- tauri_commands/etc.rs: dropped get_tg_official_link/get_boosty_link/
  get_patreon_link/get_feedback_link. Added get_upstream_link, get_issues_link,
  get_python_fork_link. Tidied up the Option<&str> -> String boilerplate
  with unwrap_or.
- main.rs: invoke_handler updated to match.

Frontend side (visible only after Rust rebuild — Tauri bundles the dist into
the exe):
- stores.ts: appInfo shape reduced to repositoryLink / upstreamLink /
  issuesLink / pythonForkLink / logFilePath. loadAppInfo() uses fetchOr()
  with hardcoded fallbacks so the UI still populates correctly when run
  against an un-rebuilt binary; fetchRepoOr() additionally overrides
  CARGO_PKG_REPOSITORY if it still resolves to upstream's Priler/jarvis.
- Footer.svelte: rewritten — © year + "J.A.R.V.I.S." + repo link + issues
  link + small "fork of Priler/jarvis · CC BY-NC-SA 4.0" attribution line.
  No author byline (it's in LICENSE.txt).
- routes/settings/index.svelte: feedback link now points to GitHub Issues.
- routes/commands/index.svelte: TG-channel reference replaced with a link
  to the Python fork (where the command builder lives) plus a pointer to
  resources/commands/ in this repo. Bruh GIF removed.
- locales/{ru,en,ua}.ftl: removed footer-author / footer-telegram /
  footer-support, added footer-issues / footer-fork-of, rewrote
  commands-wip-* strings, retargeted settings-beta-bot to "GitHub Issues".

Bundle identifier (com.priler.jarvis) kept intact to avoid moving the app
data directory.
2026-05-15 01:09:14 +03:00
Bossiara13
85a004e263 test(commands): schema and Lua script validation for resources/commands/
Three unit tests catch the kind of bug that broke weather/set_city (a
phrases array instead of lang→array map silently dropped the whole pack):

- every_command_toml_parses: every resources/commands/*/command.toml round-
  trips through toml::from_str::<JCommandsList>. Reports all failures at
  once instead of failing on the first.
- lua_command_scripts_exist: for every type="lua" command, the named (or
  default script.lua) script file exists in the pack dir.
- every_command_has_phrases: structural commands (terminate/stop_chaining)
  excluded, every other command has ≥1 phrase across all languages.

Tests use env!("CARGO_MANIFEST_DIR") -> ../.. -> resources/commands so they
work from any cwd.

NB: cargo test does not run on this machine right now — aws-lc-sys needs
cl.exe (MSVC Build Tools missing). The release binary was built earlier
with MSVC available; toolchain install is a follow-up. The tests are still
valid for CI / a re-armed dev box.
2026-05-15 00:55:01 +03:00
Bossiara13
acebb02a8e test: vad listening window timing 2026-04-23 11:08:02 +03:00
Bossiara13
37c8371f3f feat(core): vad-driven listening window state machine 2026-04-23 11:07:35 +03:00
Bossiara13
3f19366ad0 chore: add webrtc-vad workspace dep 2026-04-23 11:07:04 +03:00
Bossiara13
e5aaa7e064 feat(app): wire LlmClient into voice loop with trigger-prefix fallback
new module crates/jarvis-app/src/llm_fallback.rs holds an optional
LlmClient + ConversationHistory built once at startup. if GROQ_TOKEN is
unset the module logs a warning and stays disabled — voice commands keep
working as before.

both the wake-word voice path (recognize_command in app.rs) and the
gui-side text command path (process_text_command) now check whether the
recognized phrase starts with one of the configured trigger phrases
(ru: 'скажи' / 'ответь' / 'произнеси'). when it does, the remainder of
the phrase is sent to Groq and the reply is published as a new
IpcEvent::LlmReply { text } so the gui can speak it.

on api error the trailing user turn is popped from history, the russian
fallback line is sent over ipc and a 'error' voice cue plays. the loop
itself never panics.
2026-04-23 02:16:02 +03:00
Bossiara13
176f405310 feat(core): pop_last_user helper on conversation history
used by llm fallback to roll back the user turn when the api call fails,
so the next attempt does not leave a dangling user message in the history.
2026-04-23 02:15:41 +03:00
Bossiara13
1e2c883e68 chore(core): llm config knobs, system prompt, trigger phrases
add LLM_DEFAULT_ENABLED / LLM_DEFAULT_MAX_HISTORY / LLM_DEFAULT_MAX_TOKENS
defaults, the russian J.A.R.V.I.S. system prompt, a fallback error line,
and per-language helpers get_llm_trigger_phrases() and get_llm_system_prompt()
so the voice loop can opt into Groq with a 'скажи …' style prefix.
2026-04-22 23:16:30 +03:00
Bossiara13
40f2f8b5f9 feat(core): conversation history with bounded length
split llm.rs into a module with separate client and history submodules.
ConversationHistory holds an optional system prompt plus a FIFO of user/
assistant turns capped at max_turns; oldest turns evict on overflow,
system prompt is preserved across truncation and clear().
2026-04-22 23:16:25 +03:00
Bossiara13
02f5829aaa feat(core): add Groq/OpenAI-compatible LlmClient
New jarvis-core::llm module providing a blocking client for
OpenAI-compatible chat completions endpoints (Groq by default).

- LlmClient::new / from_env (GROQ_TOKEN, GROQ_BASE_URL, GROQ_MODEL)
- complete(messages, max_tokens) -> String
- thiserror-based LlmError / ConfigError
- gated behind a new llm feature, included in default jarvis_app

Not yet wired into the wake-word/intent loop; that lands in v0.3.
2026-04-22 19:36:14 +03:00
Priler
520b98143f AI models shared registry + Code cleanup + Better async handling + Some fixes, etc 2026-02-18 21:08:48 +05:00
Priler
a8ff3442ff some fixes + gliner first implementation 2026-02-11 07:21:50 +05:00
Priler
b9d5f41bbd Fix intent classifier init 2026-02-08 07:30:46 +05:00
Priler
8e830334e8 New intent classification engine - MiniLM L6v2 and MiniLM L12v2 ONNX 2026-02-08 07:16:03 +05:00
Priler
4815c7f9bb Bump to Lua 5.5 2026-02-08 06:42:58 +05:00
Priler
acc806d403 Improved wake-word & command detection, with dual mode 2026-02-08 06:37:39 +05:00
Priler
7440baa1b2 basic Lua 5.4 implementation with few example commands 2026-01-17 05:46:38 +05:00
Priler
11c2500d9c Commands/voices multilanguage support + Ukranian vosk model added 2026-01-13 02:21:59 +05:00
Priler
e2370dc046 VAD fixes + some calibrations 2026-01-08 00:35:21 +05:00
Priler
47b7e7a65d Tray options implementation + Voice system rewrite + build fixes 2026-01-07 23:29:46 +05:00
Priler
412acb7e2d Multilingual support via Fluent + Frontend improvements + Rewrite of ArcReactor 2026-01-07 05:04:04 +05:00
Priler
adec595cfa websockets based IPC added in order to bind GUI/app together 2026-01-07 00:15:36 +05:00
Priler
eb0d40bae6 noise suppression added via nnnoiseless + vad + gain-normalizer + few frontend changes 2026-01-06 23:32:58 +05:00
Priler
a640e6caea vosk usage optimization 2026-01-05 04:20:43 +05:00
Priler
cab53abcbe vosk model selection in GUI 2026-01-05 03:38:04 +05:00
Priler
36acacf259 jarvis-cli added for dev tests 2026-01-05 02:23:32 +05:00
Priler
5f1f9ce321 intent recognition added (first implementation) 2026-01-05 01:22:45 +05:00
Priler
091a41ac33 frontend fixes & db rewrite with serde 2026-01-04 09:00:51 +05:00
Priler
61b7a79455 rustpotter detections optimization + few code improvements 2026-01-04 06:14:05 +05:00
Priler
09f72622bc project restructure with Rust workspaces 2026-01-04 05:19:47 +05:00