Fork of Priler/jarvis (CC BY-NC-SA 4.0) https://github.com/Priler/jarvis
Find a file
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
crates feat: Ollama LLM backend + 5 utility packs (daily briefing, pomodoro, currency convert, stocks, habits, translate clipboard) 2026-05-15 15:51:24 +03:00
frontend arch + features: Lua jarvis.speak() / jarvis.llm() API, /commands GUI page, games / mouse / random_choice packs 2026-05-15 12:58:16 +03:00
lib library files required for the build added 2026-01-17 06:08:09 +05:00
resources feat: Ollama LLM backend + 5 utility packs (daily briefing, pomodoro, currency convert, stocks, habits, translate clipboard) 2026-05-15 15:51:24 +03:00
tools feat: TTS backend abstraction + 4 'imba' features + Lua packs 2026-05-15 15:32:44 +03:00
.gitignore chore: gitignore tauri-generated schema files 2026-04-22 17:55:56 +03:00
Cargo.lock feat: TTS backend abstraction + 4 'imba' features + Lua packs 2026-05-15 15:32:44 +03:00
Cargo.toml feat: 5 new packs + dotenvy + regen icon.ico + shortcuts redo 2026-05-15 12:28:44 +03:00
LICENSE.txt Readme and License 2023-04-28 19:39:48 +05:00
make_ico.py feat: 5 new packs + dotenvy + regen icon.ico + shortcuts redo 2026-05-15 12:28:44 +03:00
post_build.py some fixes + gliner first implementation 2026-02-11 07:21:50 +05:00
poster.jpg Some funky stuff, bruh 2023-05-01 01:52:47 +05:00
README.md docs: README — VAD smart command end 2026-04-23 11:08:09 +03:00
run.bat feat(gui): footer badge "Rust" + one-click run.bat launcher 2026-05-15 12:12:11 +03:00

J.A.R.V.I.S (Rust) — форк Bossiara13

Форк Rust-переписки голосового ассистента Priler/jarvis. Текущий репозиторий: https://github.com/DmitryBykov-ISPO/J.A.R.V.I.S-rust.

Что это

Голосовой ассистент, написанный на Rust, работающий локально (без облака). Текущий стек:

  • Vosk — Speech-to-Text (через vosk-rs).
  • fastembed + ort — локальные эмбеддинги для intent-классификации (MiniLM L6/L12 ONNX).
  • Picovoice Porcupine / Rustpotter / Vosk — три опциональных движка wake-word.
  • mlua (Lua 5.5, vendored) — скрипты пользовательских команд.
  • Tauri + Vite/Svelte — GUI-оболочка (фронтенд в отдельной папке frontend/).
  • nnnoiseless — подавление шума.
  • fluent / unic-langid — i18n (ru, ua, en).

LLM-клиент (Groq / OpenAI-совместимый) добавлен в jarvis-core::llm и подключён к голосовому циклу. Если фраза начинается с триггера («скажи», «ответь», «произнеси»), она уходит в Groq и ответ возвращается через IPC-событие LlmReply. Без триггера всё работает как раньше — wake-word + intent + Lua. Это следующий шаг после CLI-only LLM из v0.2.0.

Это форк

Оригинальный автор — Abraham Tugalov (Priler). Апстрим: https://github.com/Priler/jarvis. Лицензия сохранена: CC BY-NC-SA 4.0 (см. LICENSE.txt). Атрибуция в Cargo.toml и voice.toml пакетов озвучки не изменена.

Что отличается от апстрима

  • Обновлён список авторов в Cargo.toml (добавлен Bossiara13 (fork), оригинал сохранён).
  • README переписан и отражает фактическую архитектуру (апстримный README называет проект "Tauri+Svelte", что давно не соответствует действительности — это workspace из 4-х крейтов).
  • Отсутствующие в апстриме ONNX-модели (all-MiniLM-L6-v2, paraphrase-multilingual-MiniLM-L12-v2-onnx-Q) подтянуты через Git LFS из HuggingFace (Qdrant) и запушены в форк.

Структура репозитория

Cargo workspace из четырёх крейтов:

Крейт Назначение
jarvis-core Библиотека: конфиг, intent, STT, wake-word, аудио, Lua-бэкенд, i18n.
jarvis-app Бинарь-«демон»: собирает всё вместе, tray, IPC.
jarvis-gui Tauri-приложение (использует frontend/dist/client).
jarvis-cli CLI для отладки: классификация intent, список команд, dump конфига.

Прочее:

  • frontend/ — Vite + Svelte UI для jarvis-gui. Собирается отдельно.
  • lib/windows/amd64/ — нативные DLL/LIB для Vosk, Porcupine, PvRecorder.
  • resources/ — голоса, модели, конфиги по умолчанию. ONNX-модели хранятся в Git LFS.
  • post_build.py — постпроцессинг артефактов сборки (Python 3).

Сборка

Требования:

  • Rust 1.93+ (собирается на stable MSVC).
  • Node 24+ и npm — для фронтенда.
  • Python 3 — для post_build.py.
  • MSVC build tools (Windows, x64).
  • Установленные libvosk.lib, libpv_porcupine.dll, libpv_recorder.dll в lib/windows/amd64/ (уже в репозитории).

Перед сборкой jarvis-gui нужно собрать фронтенд:

cd frontend
npm install
npm run build
cd ..

Затем workspace:

cargo build --workspace

Холодная сборка занимает около 10 минут (ONNX runtime, aws-lc-rs, tauri).

Статус сборки в этом форке

На моей машине (cargo build --workspace, stable MSVC) итог:

  • jarvis-core — собрался (1 warning, unused import).
  • jarvis-app — собрался, бинарник target/debug/jarvis-app.exe создан.
  • jarvis-cliпадает на линковке: LNK1181: cannot open input file "libvosk.lib". Причина: у jarvis-cli нет своего build.rs, а .cargo/config.toml с rustc-link-search лежит только внутри crates/jarvis-app/ и не подтягивается для jarvis-cli. Лечится либо добавлением такого же build.rs в crates/jarvis-cli/, либо вынесением config.toml в корень. Сознательно не трогал — фикс выходит за рамки рефакторинга (v0.0.1-import фиксирует поведение апстрима как есть).
  • jarvis-gui — падает в tauri::generate_context!(): frontendDist = "../../frontend/dist/client" не существует. Это ожидаемо, если не запустить npm run build в frontend/ заранее (см. секцию «Сборка»).

Запуск уже собранного:

./target/debug/jarvis-app.exe

Для CLI (jarvis-cli --help, команды classify, execute, list, phrases) нужно сначала починить линковку Vosk (см. выше).

LLM (Groq)

В jarvis-core есть модуль llm — блокирующий клиент для OpenAI-совместимого эндпоинта chat completions. По умолчанию настроен на Groq. Используется через фиче-флаг llm (включён в дефолтный набор jarvis_app, также подтянут в jarvis-cli).

Переменные окружения:

Переменная Обязательна Значение по умолчанию
GROQ_TOKEN да
GROQ_BASE_URL нет https://api.groq.com/openai/v1
GROQ_MODEL нет llama-3.3-70b-versatile

Быстрая проверка через CLI:

set GROQ_TOKEN=gsk_...
jarvis-cli ask "скажи привет одной фразой"

Ответ печатается в stdout. Без GROQ_TOKEN команда завершится с кодом 2 и сообщением об ошибке. При ошибке API — код 1 и тело ответа.

Программное использование из Rust:

use jarvis_core::llm::{LlmClient, ChatMessage};

let client = LlmClient::from_env()?;
let reply = client.complete(&[ChatMessage::user("привет")], 256)?;
println!("{}", reply);

Подключение к голосовому циклу

Помимо CLI, LLM подключён напрямую в jarvis-app. Логика в crates/jarvis-app/src/llm_fallback.rs:

  • При старте jarvis-app пытается прочитать GROQ_TOKEN. Если переменной нет — фоллбэк отключается, в лог пишется warning, голосовые команды продолжают работать как раньше.
  • Распознанная фраза (как из микрофона, так и из текстовой панели GUI) проверяется на префиксы-триггеры из config::get_llm_trigger_phrases (для ru/ua: скажи, ответь, произнеси; для en: say, tell, answer).
  • Если триггер найден — остаток фразы уходит в LlmClient::complete(), ответ публикуется в IPC как IpcEvent::LlmReply { text } (UI/GUI слушает этот ивент и проговаривает текст уже на своей стороне), а звук-«ок» проигрывается из текущего голосового пресета.
  • История разговора хранится в ConversationHistory с потолком LLM_DEFAULT_MAX_HISTORY = 8 ходов; system-prompt всегда сохраняется при вытеснении старых ходов.
  • При сетевой/API-ошибке последний user-turn убирается из истории, в IPC уходит LlmReply с короткой русской фразой («Не могу связаться с сервером, сэр.»), играется звук-«error». Голосовой цикл не падает.

Системный промпт (русский) описывает J.A.R.V.I.S. как британского дворецкого Тони Старка — короткие реплики (13 предложения), обращение «сэр», без излишней цензуры. Меняется в config::LLM_SYSTEM_PROMPT_RU.

VAD: умное завершение команды по тишине

После срабатывания wake-word jarvis-app слушает команду. Раньше окно закрывалось либо по таймауту Vosk, либо по жёсткому пределу из config::CMS_WAIT_DELAY — короткие команды («стоп», «громче») всё равно ждали несколько секунд тишины.

Теперь окно закрывается, как только пользователь замолчал. Реализовано через webrtc-vad (чистый Rust, тот же алгоритм, что в Python-версии). Это паритет с Python v0.2.0, где использовался webrtcvad с теми же параметрами.

Логика (state-машина в crates/jarvis-core/src/audio_processing/vad/listen_window.rs):

  • Кадр VAD: 30 мс при 16 кГц = 480 сэмплов = 960 байт (mono i16). Кадры с микрофона (по 512 сэмплов от pv_recorder) аккумулируются и режутся на VAD-кадры адаптером WebRtcVad::push_samples.
  • Каждый VAD-кадр учитывается в счётчиках speech_ms / silence_ms.
  • Окно закрывается, когда silence_ms >= VAD_COMMAND_END_SILENCE_MS И speech_ms >= VAD_COMMAND_MIN_SPEECH_MS.
  • До истечения VAD_COMMAND_MIN_LISTEN_MS окно не закрывается — пользователю даётся время начать говорить.
  • При достижении VAD_COMMAND_MAX_LISTEN_MS срабатывает hard-cap и управление возвращается в режим ожидания wake-word.
  • На событии «закрыть окно» вызывается stt::finalize_speech() — Vosk форсированно отдаёт финальный результат, не дожидаясь собственного таймаута.

Параметры (crates/jarvis-core/src/config.rs):

Константа Значение Назначение
VAD_AGGRESSIVENESS 2 Уровень WebRTC VAD: 0 — Quality, 3 — VeryAggressive.
VAD_COMMAND_END_SILENCE_MS 1200 Сколько тишины подряд считать «команда закончилась».
VAD_COMMAND_MIN_SPEECH_MS 500 Минимум речи в окне — иначе закрытие игнорируется.
VAD_COMMAND_MIN_LISTEN_MS 1000 Минимальная длительность окна (страховка от ранних закрытий)
VAD_COMMAND_MAX_LISTEN_MS 15000 Жёсткий потолок окна.

На короткие команды отзыв стал заметно быстрее (мы выходим на распознавание сразу после паузы пользователя, а не по 5-секундному порогу Vosk).

Лицензия

Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0). Полный текст — в LICENSE.txt. Атрибуция оригинального автора (Abraham Tugalov) сохранена.

В Cargo.toml декларирован license = "GPL-3.0-only" — это несоответствие унаследовано от апстрима и не правилось, чтобы не расходиться с upstream-конфигом. Приоритет имеет LICENSE.txt.

Python-версия

Старая версия ассистента была на Python. Последний коммит с Python-кодом в апстриме — 943efbf.