set_city used a plain `phrases = [...]` array which fails the lang-map schema in JCommand — the whole weather pack was being skipped at load. Normalized to per-language phrases. browser/command.toml pointed at Priler's bundled AutoHotKey exes that do not ship in this fork, so the entries were dead. Renamed to .disabled (the loader skips packs without command.toml) so we do not collide with the new Lua apps pack while keeping the directory for reference. |
||
|---|---|---|
| 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.