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. |
||
|---|---|---|
| crates | ||
| frontend | ||
| lib | ||
| resources | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE.txt | ||
| post_build.py | ||
| poster.jpg | ||
| README.md | ||
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. как британского дворецкого Тони Старка — короткие реплики (1–3 предложения), обращение «сэр», без излишней цензуры. Меняется в 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.