From c22a24ccd85a220297d705f8a328b8a4c4ad4242 Mon Sep 17 00:00:00 2001 From: Bossiara13 <236771060+DmitryBykov-ISPO@users.noreply.github.com> Date: Fri, 15 May 2026 17:28:22 +0300 Subject: [PATCH] feat(gui): /macros + /scheduler pages + README rewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaces voice macros and scheduled tasks in the GUI, so the user doesn't have to remember voice commands or grep schedule.json by hand. Tauri commands (crates/jarvis-gui/src/tauri_commands/) - macros.rs: macros_list, macros_replay, macros_delete, macros_is_recording, macros_recording_name, macros_start_recording, macros_save_recording, macros_cancel_recording (8 commands). - scheduler.rs: scheduler_list, scheduler_remove, scheduler_clear (3 commands). - Both exposed in main.rs invoke_handler. GUI pages (frontend/src/routes/) - macros/index.svelte: * Lists all macros with name, steps_count, first 5 step previews, created/last_run timestamps. * Top: TextInput + "Начать запись" button. While recording shows orange banner with "Сохранить"/"Отменить" buttons + polls is_recording every 2s. * Per-card: "Запустить" (with busy state for replay duration), "Удалить". * confirm() before delete. - scheduler/index.svelte: * Lists tasks with name, schedule_human (e.g. "каждые 2 ч"), action body, ID, timestamps, action_kind badge. * "Отменить" per task + "Очистить всё (N)" bottom button. * Auto-polls every 5s (so the list updates as scheduler ticks fire tasks). Header (frontend/src/components/Header.svelte) - Two new buttons: "Макросы" → /macros, "Расписание" → /scheduler. - Beside existing /commands and /settings buttons. i18n - ru/en/ua FTL: settings-ai-backends, settings-llm-*, settings-tts-*, settings-profile, header-macros, header-scheduler. - Re-applied AI Backends keys for ua.ftl (earlier edit hadn't taken). README.md (full rewrite) - Old README was 178 lines mostly explaining LLM-trigger flow and VAD config. - New README is ~190 lines covering: features (LLM hot-swap, memory, profiles, vision, scheduler, macros, utilities table), quick start, env vars table, build steps with vcvars setup, test command, structure tree, voice workflow diagram, license, roadmap. - Up to date for 59 packs and all new infra. Build: cargo build --release -p jarvis-gui green (2m). 55/55 tests pass. --- README.md | 435 +++++++++++------- crates/jarvis-core/src/i18n/locales/en.ftl | 6 +- crates/jarvis-core/src/i18n/locales/ru.ftl | 6 +- crates/jarvis-core/src/i18n/locales/ua.ftl | 22 +- crates/jarvis-gui/src/main.rs | 15 + crates/jarvis-gui/src/tauri_commands.rs | 10 +- .../jarvis-gui/src/tauri_commands/macros.rs | 58 +++ .../src/tauri_commands/scheduler.rs | 59 +++ frontend/src/components/Header.svelte | 10 +- frontend/src/routes/macros/index.svelte | 306 ++++++++++++ frontend/src/routes/scheduler/index.svelte | 238 ++++++++++ 11 files changed, 998 insertions(+), 167 deletions(-) create mode 100644 crates/jarvis-gui/src/tauri_commands/macros.rs create mode 100644 crates/jarvis-gui/src/tauri_commands/scheduler.rs create mode 100644 frontend/src/routes/macros/index.svelte create mode 100644 frontend/src/routes/scheduler/index.svelte diff --git a/README.md b/README.md index 1fcb1c9..594f0bf 100644 --- a/README.md +++ b/README.md @@ -1,177 +1,288 @@ -# J.A.R.V.I.S (Rust) — форк Bossiara13 +# J.A.R.V.I.S. (Rust) — форк Bossiara13 -Форк Rust-переписки голосового ассистента [Priler/jarvis](https://github.com/Priler/jarvis). -Текущий репозиторий: . +Локальный голосовой ассистент для Windows. Форк Rust-переписки +[Priler/jarvis](https://github.com/Priler/jarvis). Сильно расширен в этом форке +(см. ниже). Может работать **полностью офлайн** через Ollama, или с облачным +LLM через Groq — выбор переключается **голосом** на лету. -## Что это +Зеркала: +- GitHub: +- Forgejo (self-hosted): `http://192.168.0.10:3000/bossiara13/J.A.R.V.I.S-rust` + (только в LAN; используй `NO_PROXY=192.168.0.10` если у тебя локальный прокси) -Голосовой ассистент, написанный на Rust, работающий локально (без облака). -Текущий стек: +Для контрибьюторов: см. [ARCHITECTURE.md](ARCHITECTURE.md) — карта кода, data flow, +рецепты "как добавить пак / фичу / TTS backend". -- 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. +- **STT**: Vosk + webrtc-vad для умного завершения по тишине. +- **Wake-word**: Vosk / Rustpotter / Picovoice Porcupine (выбор в Settings). +- **TTS**: трёхбэкендовая абстракция — SAPI (стоковый), **Piper** (нейронный, + рекомендуется, ~90 МБ), Silero (PyTorch). Выбор через `JARVIS_TTS` или GUI. +- **LLM**: двухбэкендовая — **Groq** (облако, бесплатно) или **Ollama** (локально, + офлайн). Hot-swap через голос / GUI / `JARVIS_LLM`. Выбор сохраняется в БД. +- **Lua sandbox**: каждая команда — Lua-скрипт с тремя уровнями песочницы. + Полный API: `jarvis.speak/llm/http/fs/state/system/memory/profile/scheduler/ + vision/macros/cmd/text/...` — см. ARCHITECTURE.md. +- **Tauri 2 GUI**: 4 страницы (главная, /commands список 59 паков, /settings, + футер с активными движками). + +## Возможности (то, чего нет в апстриме) + +### Управление LLM + +- **Agentic LLM router (IMBA-1)**: если fuzzy/intent не нашёл команду, LLM + выбирает ближайшую из реестра по JSON-схеме. Алисе/Сири такое не снилось. +- **Hot-swap локального и облачного LLM**: "переключись на локальный" / + "переключись на облако". Выбор персистентен (Settings → AI Backends). +- **Reset/repeat контекст**: "сбрось контекст" / "повтори последнее". +- **Memory injection**: LLM автоматически получает релевантные факты из + долгосрочной памяти в system prompt. + +### Долгосрочная память + +- "Запомни что я люблю чай улун" → JSON-стор в `/long_term_memory.json`. +- "Что ты помнишь про чай" / "забудь про X" / "что ты знаешь обо мне". +- LLM-чат получает релевантные факты через substring search. + +### Профили + +- 5 предзаполненных: default ★, work 💼, game 🎮, sleep 🌙, driving 🚗. +- "Режим работа" / "режим игра" / "режим сон" / "режим за рулём" / "обычный режим". +- Каждый профиль может иметь allow/deny списки команд + personality для LLM. + +### Vision (multimodal) + +- "Что на экране" / "опиши экран" / "прочитай ошибку" → скриншот через + PowerShell → base64 → Groq vision (`llama-3.2-11b-vision-preview`) → голос. +- Требует `GROQ_TOKEN` (Ollama пока без vision в этой интеграции). + +### Проактивный планировщик + +- "Напомни через 5 минут выключить кофеварку" → one-shot. +- "Каждые 2 часа напоминай попить воды" → interval. +- "Каждый день в 9:00 делай брифинг" → daily. +- "Отмени напоминание про воду" / "очисти расписание" / "что у меня запланировано". +- Фоновый поток (тик каждые 30 сек). Сохраняется в `/schedule.json`. + +### Voice macros (VoiceAttack-style) + +- "Запиши макрос работа" → начинает запись. +- Любые следующие команды (открой браузер, режим работа, ...) копятся. +- "Сохрани макрос" → персистится. +- "Запусти макрос работа" → проигрывает каждый шаг с интервалом 800мс. +- Список / удаление / отмена. + +### Утилиты + +| Команда | Что делает | +|---|---| +| "Найди в гугле X" | DuckDuckGo Instant Answer + LLM-перессказ | +| "Википедия X" / "что такое X" | Wikipedia REST API (ru→en fallback) + LLM-перевод | +| "Сколько 1000 долларов в рублях" | CBR-XML курс + конвертация | +| "Сколько Сбер" | MOEX биржа, 22 тикера в карте | +| "Переведи буфер" | Clipboard → LLM → обратно в clipboard | +| "Напоминай мне пить воду" | Habit nudge: каждые 2 часа | +| "Напоминай размяться" | Каждые 50 минут | +| "Напоминай отдыхать глазам" | 20-20-20 rule, каждые 20 минут | +| "Запусти помодоро" | 25/5 циклы с голосовыми напоминаниями | +| "Запиши настроение 7" / "как прошла неделя" | Mood log + LLM-сводка | +| "Утренний брифинг" / "настрой утренний брифинг на 9:00" | Время+профиль+задачи+память+LLM-мотивашка | +| "Укажи проект C:\..." / "что делает функция X" | Codebase Q&A — LLM по локальной кодобазе | +| "Открытые пиары" / "разбери последний PR" | gh CLI + LLM-ревью | +| "Пауза" / "следующий трек" | Windows media keys (Spotify / YouTube / Foobar) | +| "Диагностика" / "доложи о себе" | Состояние всех бэкендов одной строкой | + +Всего паков: **59** (`resources/commands/*/command.toml`). +Полный список: GUI → /commands (с поиском и фильтрами). + +## Быстрый старт + +```powershell +# 1. Клонировать +git clone https://github.com/DmitryBykov-ISPO/J.A.R.V.I.S-rust.git C:\Jarvis\rust +cd C:\Jarvis\rust + +# 2. Поставить нейронный TTS (один раз, ~90 МБ) +pwsh tools/piper/install.ps1 + +# 3a. Облачный LLM — получи токен на https://console.groq.com/keys +echo "GROQ_TOKEN=gsk_..." > dev.env + +# 3b. ИЛИ локальный LLM +# Скачай https://ollama.com/download, выполни: +# ollama pull qwen2.5:3b +# (Jarvis сам подхватит, если GROQ_TOKEN отсутствует) + +# 4. Сборка +cd frontend && npm install && npm run build && cd .. +cargo build --release -p jarvis-app -p jarvis-gui + +# 5. Запуск +target\release\jarvis-app.exe # фоновый демон с микрофоном +target\release\jarvis-gui.exe # GUI для управления +``` + +Подробнее: см. секцию "Сборка" ниже. + +## Конфигурация (env vars) + +Все JARVIS_* / GROQ_* / OLLAMA_* переменные документированы в +`crates/jarvis-core/src/runtime_config.rs`. Ключевые: + +| Переменная | По умолчанию | Назначение | +|---|---|---| +| `GROQ_TOKEN` | — | Токен для облачного LLM (без него — Ollama / выкл) | +| `GROQ_MODEL` | `llama-3.3-70b-versatile` | Groq-модель | +| `GROQ_VISION_MODEL` | `llama-3.2-11b-vision-preview` | Vision-модель для "что на экране" | +| `OLLAMA_BASE_URL` | `http://localhost:11434/v1` | Адрес Ollama | +| `OLLAMA_MODEL` | `qwen2.5:3b` | Локальная модель (любая через `ollama pull`) | +| `JARVIS_LLM` | auto | `groq` / `ollama` / пусто (auto-detect) | +| `JARVIS_TTS` | auto | `sapi` / `piper` / `silero` / пусто (auto) | +| `JARVIS_TTS_PIPER_BIN` | автопоиск | Путь к piper.exe | +| `JARVIS_TTS_PIPER_VOICE` | автопоиск | Путь к голосу .onnx | +| `JARVIS_LLM_ROUTER` | `1` | Включить agentic router | +| `JARVIS_LLM_ROUTER_THRESHOLD` | `0.55` | Confidence threshold | +| `JARVIS_LLM_TTS` | `true` | Озвучивать LLM-ответ | + +Файл `dev.env` подхватывается автоматически (jarvis-app ищет от exe вверх по 5 +уровням), так что переменные можно держать в одном месте. + +Персистентные настройки (`voice`, `microphone`, `vosk_model`, `llm_backend`, +`tts_backend`, ...) хранятся в `/settings.json` и правятся +через GUI Settings или Lua `jarvis.settings.set(key, val)`. + +## Сборка + +Требования: + +- Rust 1.93+ (stable MSVC). +- Node 20+ и npm для фронтенда. +- MSVC build tools (`vcvars64.bat`). VS 2022 / 2026 / 2026 — любая. +- Python 3 (только для `post_build.py`, опционально). +- Vosk/Porcupine/PvRecorder DLL'и в `lib/windows/amd64/` (уже в репо). + +Команды: + +```powershell +# Один раз: загрузить MSVC окружение +$vc = "C:\Program Files\Microsoft Visual Studio\18\Enterprise\VC\Auxiliary\Build\vcvars64.bat" +cmd /c "`"$vc`" >NUL && set" | % { + if ($_ -match '^([^=]+)=(.*)$') { + [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process') + } +} + +# Собрать фронтенд +cd frontend +npm install +npm run build +cd .. + +# Workspace +cargo build --release -p jarvis-app -p jarvis-gui +``` + +Холодная сборка ~10 мин (ONNX runtime, aws-lc-rs, tauri). Инкремент ~30 сек. + +`jarvis-gui/build.rs` сам зовёт `npm run build` если фронтенд устарел, так что +для итераций по UI достаточно `cargo build -p jarvis-gui`. + +## Тесты + +```powershell +cargo test -p jarvis-core --lib # все unit-тесты +cargo test -p jarvis-core --lib scheduler:: # фильтр по модулю +``` + +Текущее покрытие: **55 unit-тестов**, см. таблицу в ARCHITECTURE.md. + +## Структура + +``` +crates/ + jarvis-core library — STT, wake-word, TTS, LLM, intent, commands, IPC, + Lua sandbox, scheduler, memory, profiles, macros, vision + jarvis-app daemon бинарь — микрофон, listening loop, dispatcher, tray, IPC + jarvis-gui Tauri + Svelte GUI — settings, commands list, footer chips + jarvis-cli debug CLI (classify/execute/list) + +frontend/ Vite + Svelte UI для jarvis-gui +resources/ + commands/ 59 voice-command packs (Lua скрипты + TOML) + models/ ONNX (intent classifier, GLiNER) — Git LFS + vosk/ Vosk модели (small-ru, small-en, ...) + voices/ пресеты звуков реакций (greet/ok/error/...) + +tools/ + piper/ Piper TTS бинарник + голоса (.onnx) — устанавливаются install.ps1 + silero/ Python helper для Silero TTS (опционально) + +ARCHITECTURE.md — для контрибьюторов: data flow, рецепты, тесты. +``` + +## Голосовой workflow + +Поднимаешь `jarvis-app.exe`, говоришь: + +``` +"Джарвис" ─ wake-word + ↓ играет "reply" звук +"Какая сегодня погода?" + ↓ STT (Vosk) распознаёт + ↓ Intent classifier (MiniLM ONNX) пробует найти команду + ↓ Levenshtein fallback + ↓ Agentic LLM router (если включён) + ↓ LLM fallback (chat) + ↓ Озвучивает ответ +"Какая температура завтра?" + ↓ 30-сек grace window — без повторного wake-word + ↓ LLM помнит контекст разговора +``` + +После каждой команды есть 30-секундное окно для follow-up без повторного +"Джарвис" (`CONVERSATION_GRACE_MS`). ## Это форк Оригинальный автор — Abraham Tugalov (Priler). Апстрим: . Лицензия сохранена: **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` нужно собрать фронтенд: - -```bash -cd frontend -npm install -npm run build -cd .. -``` - -Затем workspace: - -```bash -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/` заранее (см. секцию «Сборка»). - -Запуск уже собранного: - -```bash -./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: - -```bash -set GROQ_TOKEN=gsk_... -jarvis-cli ask "скажи привет одной фразой" -``` - -Ответ печатается в stdout. Без `GROQ_TOKEN` команда завершится с кодом 2 и сообщением об ошибке. При ошибке API — код 1 и тело ответа. - -Программное использование из Rust: - -```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`. +В `Cargo.toml` декларирован `license = "GPL-3.0-only"` — это несоответствие +унаследовано от апстрима и не правилось, чтобы не расходиться с upstream-конфигом. +Приоритет имеет `LICENSE.txt`. ## Python-версия -Старая версия ассистента была на Python. -Последний коммит с Python-кодом в апстриме — [943efbf](https://github.com/Priler/jarvis/tree/943efbfbdb8aeb5889fa5e2dc7348ca4ea0b81df). +Старая версия ассистента была на Python. Последний коммит с Python-кодом в +апстриме — [943efbf](https://github.com/Priler/jarvis/tree/943efbfbdb8aeb5889fa5e2dc7348ca4ea0b81df). +Параллельный Python-форк живёт в отдельном репо +([J.A.R.V.I.S-py](https://github.com/DmitryBykov-ISPO/J.A.R.V.I.S-py)) — там же, +но **17 новых паков из Rust-форка пока туда не зеркалированы** (TODO). + +## Поддерживаемость + +- `runtime_config.rs` — все env vars в одном файле с doc-комментами. +- `tts/` — trait-based, добавить новый бэкенд = реализовать `TtsBackend`. +- `llm/mod.rs` — shared global client, hot-swap через `swap_to(LlmBackend)`. +- `jarvis.cmd.{ok/error/not_found}` — boilerplate-killer для Lua-паков. +- 55 unit-тестов прикрывают всё, что не требует мика/динамика/сети. +- ARCHITECTURE.md содержит рецепты "как добавить X". + +## Что в roadmap (что ещё хочется) + +- **faster-whisper STT** рядом с Vosk для длинных LLM-фраз. +- **Реальные Win32 макросы** (keyboard + mouse hooks), не только voice replay. +- **Spotify Web API** через OAuth — "что играет", "сохрани в плейлист". +- **Outlook COM** — голосовая почта / календарь. +- **GUI /macros + /scheduler страницы** — управление без голоса. +- **Python parity** для 17 новых паков. + +См. `~/.claude/projects/C--Jarvis/memory/project_jarvis_roadmap.md` (если ты +— Claude помогаешь автору) или открой issue. + +## Лицензия + +CC BY-NC-SA 4.0 — Creative Commons **Attribution-NonCommercial-ShareAlike**. +Полный текст: `LICENSE.txt`. Атрибуция Abraham Tugalov сохранена. diff --git a/crates/jarvis-core/src/i18n/locales/en.ftl b/crates/jarvis-core/src/i18n/locales/en.ftl index 9282cb4..2c6e690 100644 --- a/crates/jarvis-core/src/i18n/locales/en.ftl +++ b/crates/jarvis-core/src/i18n/locales/en.ftl @@ -157,4 +157,8 @@ settings-tts-backend-desc = Takes effect on next jarvis-app launch. settings-llm-context = Conversation context settings-llm-context-desc = LLM remembers previous turns. Reset if answers get weird. settings-llm-reset = Reset context -settings-profile = Profile \ No newline at end of file +settings-profile = Profile + +# Header buttons +header-macros = Macros +header-scheduler = Schedule \ No newline at end of file diff --git a/crates/jarvis-core/src/i18n/locales/ru.ftl b/crates/jarvis-core/src/i18n/locales/ru.ftl index 12ab39d..8a8d4a5 100644 --- a/crates/jarvis-core/src/i18n/locales/ru.ftl +++ b/crates/jarvis-core/src/i18n/locales/ru.ftl @@ -157,4 +157,8 @@ settings-tts-backend-desc = Применится при следующем за settings-llm-context = Контекст разговора settings-llm-context-desc = LLM помнит предыдущие реплики. Сбросить, если ответы становятся странными. settings-llm-reset = Сбросить контекст -settings-profile = Профиль \ No newline at end of file +settings-profile = Профиль + +# Header buttons +header-macros = Макросы +header-scheduler = Расписание \ No newline at end of file diff --git a/crates/jarvis-core/src/i18n/locales/ua.ftl b/crates/jarvis-core/src/i18n/locales/ua.ftl index e513352..c40bf96 100644 --- a/crates/jarvis-core/src/i18n/locales/ua.ftl +++ b/crates/jarvis-core/src/i18n/locales/ua.ftl @@ -141,4 +141,24 @@ settings-gliner-models-hint = Моделі GLiNER не знайдено. # ETC search-error-not-running = Асистент не запущено search-error-failed = Не вдалося виконати команду -settings-no-voices = Голоси не знайдено \ No newline at end of file +settings-no-voices = Голоси не знайдено + +# AI Backends tab +settings-ai-backends = ШІ-рушії +settings-ai-active = Активний рушій +settings-ai-auto = Авто +settings-ai-applying = Застосовую... +settings-ai-error = Помилка перемикання +settings-ai-tips = Підказки +settings-llm-backend = LLM рушій +settings-llm-backend-desc = Де виконувати LLM-запити. Hot-swap, без перезапуску. +settings-tts-backend = TTS рушій +settings-tts-backend-desc = Застосується при наступному запуску jarvis-app. +settings-llm-context = Контекст розмови +settings-llm-context-desc = LLM пам'ятає попередні репліки. Скиньте, якщо відповіді дивні. +settings-llm-reset = Скинути контекст +settings-profile = Профіль + +# Header buttons +header-macros = Макроси +header-scheduler = Розклад \ No newline at end of file diff --git a/crates/jarvis-gui/src/main.rs b/crates/jarvis-gui/src/main.rs index 32daea3..f495bdb 100644 --- a/crates/jarvis-gui/src/main.rs +++ b/crates/jarvis-gui/src/main.rs @@ -107,6 +107,21 @@ fn main() { tauri_commands::set_tts_backend, tauri_commands::llm_reset_context, tauri_commands::llm_get_last_reply, + + // Voice macros + tauri_commands::macros_list, + tauri_commands::macros_replay, + tauri_commands::macros_delete, + tauri_commands::macros_is_recording, + tauri_commands::macros_recording_name, + tauri_commands::macros_start_recording, + tauri_commands::macros_save_recording, + tauri_commands::macros_cancel_recording, + + // Scheduler tasks + tauri_commands::scheduler_list, + tauri_commands::scheduler_remove, + tauri_commands::scheduler_clear, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/crates/jarvis-gui/src/tauri_commands.rs b/crates/jarvis-gui/src/tauri_commands.rs index 3a07df0..44c7a06 100644 --- a/crates/jarvis-gui/src/tauri_commands.rs +++ b/crates/jarvis-gui/src/tauri_commands.rs @@ -41,4 +41,12 @@ pub use voices::*; // LLM/TTS backend introspection + hot-swap mod backends; -pub use backends::*; \ No newline at end of file +pub use backends::*; + +// Voice macros +mod macros; +pub use macros::*; + +// Scheduler tasks +mod scheduler; +pub use scheduler::*; \ No newline at end of file diff --git a/crates/jarvis-gui/src/tauri_commands/macros.rs b/crates/jarvis-gui/src/tauri_commands/macros.rs new file mode 100644 index 0000000..357901e --- /dev/null +++ b/crates/jarvis-gui/src/tauri_commands/macros.rs @@ -0,0 +1,58 @@ +//! Tauri commands for the voice-macros system. + +use serde::Serialize; + +#[derive(Serialize)] +pub struct MacroInfo { + pub name: String, + pub steps_count: usize, + pub steps: Vec, + pub created_at: i64, + pub last_run: Option, +} + +#[tauri::command] +pub fn macros_list() -> Vec { + jarvis_core::macros::list().into_iter().map(|m| MacroInfo { + name: m.name, + steps_count: m.steps.len(), + steps: m.steps, + created_at: m.created_at, + last_run: m.last_run, + }).collect() +} + +#[tauri::command] +pub fn macros_replay(name: String) -> Result { + jarvis_core::macros::replay(&name) +} + +#[tauri::command] +pub fn macros_delete(name: String) -> bool { + jarvis_core::macros::delete(&name) +} + +#[tauri::command] +pub fn macros_is_recording() -> bool { + jarvis_core::macros::is_recording() +} + +#[tauri::command] +pub fn macros_recording_name() -> Option { + jarvis_core::macros::recording_name() +} + +#[tauri::command] +pub fn macros_start_recording(name: String) -> Result<(), String> { + jarvis_core::macros::start_recording(&name) +} + +#[tauri::command] +pub fn macros_save_recording() -> Result { + jarvis_core::macros::save_recording() +} + +#[tauri::command] +pub fn macros_cancel_recording() -> bool { + jarvis_core::macros::cancel_recording() +} diff --git a/crates/jarvis-gui/src/tauri_commands/scheduler.rs b/crates/jarvis-gui/src/tauri_commands/scheduler.rs new file mode 100644 index 0000000..e156246 --- /dev/null +++ b/crates/jarvis-gui/src/tauri_commands/scheduler.rs @@ -0,0 +1,59 @@ +//! Tauri commands for the proactive scheduler. + +use serde::Serialize; + +#[derive(Serialize)] +pub struct ScheduledTaskInfo { + pub id: String, + pub name: String, + pub schedule_human: String, + pub action_kind: String, + pub action_text: Option, + pub last_fired: Option, + pub enabled: bool, + pub created_at: i64, +} + +fn schedule_human(s: &jarvis_core::scheduler::Schedule) -> String { + use jarvis_core::scheduler::Schedule; + match s { + Schedule::Daily { hour, minute } => format!("каждый день в {:02}:{:02}", hour, minute), + Schedule::Interval { seconds } => { + if *seconds % 3600 == 0 { format!("каждые {} ч", seconds / 3600) } + else if *seconds % 60 == 0 { format!("каждые {} мин", seconds / 60) } + else { format!("каждые {} сек", seconds) } + } + Schedule::Once { at } => format!("один раз @ {}", at), + } +} + +#[tauri::command] +pub fn scheduler_list() -> Vec { + use jarvis_core::scheduler::Action; + jarvis_core::scheduler::list().into_iter().map(|t| { + let (action_kind, action_text) = match &t.action { + Action::Speak { text } => ("speak".to_string(), Some(text.clone())), + Action::Lua { script_path } => ("lua".to_string(), Some(script_path.clone())), + }; + ScheduledTaskInfo { + id: t.id, + name: t.name, + schedule_human: schedule_human(&t.schedule), + action_kind, + action_text, + last_fired: t.last_fired, + enabled: t.enabled, + created_at: t.created_at, + } + }).collect() +} + +#[tauri::command] +pub fn scheduler_remove(id: String) -> bool { + jarvis_core::scheduler::remove(&id) +} + +#[tauri::command] +pub fn scheduler_clear() -> usize { + jarvis_core::scheduler::clear() +} diff --git a/frontend/src/components/Header.svelte b/frontend/src/components/Header.svelte index ef778aa..e00d9a4 100644 --- a/frontend/src/components/Header.svelte +++ b/frontend/src/components/Header.svelte @@ -71,7 +71,15 @@ {t('header-commands')} {commandsCount}+ - + + + + + diff --git a/frontend/src/routes/macros/index.svelte b/frontend/src/routes/macros/index.svelte new file mode 100644 index 0000000..54aa17d --- /dev/null +++ b/frontend/src/routes/macros/index.svelte @@ -0,0 +1,306 @@ + + + + +

Макросы

+ + Запиши последовательность голосовых команд, потом запускай её одной фразой. + + + + +{#if isRecording} + + + Записывается макрос {recordingName ?? "—"}. + Произнесите команды, потом нажмите «Сохранить» или скажите «Сохрани макрос». + + + +   + + + +{:else} +
+ + +
+ +{/if} + +{#if error} + { error = "" }} + > + {error} + + +{/if} + +{#if loading} + +{:else if macros.length === 0} + + Макросов пока нет. Создайте первый: введите имя, нажмите «Начать запись», + произнесите команды, нажмите «Сохранить». + +{:else} +
+ {#each macros as m} +
+
+ {m.name} + + {m.steps_count} шагов + +
+
+ {#each m.steps.slice(0, 5) as step} +
• {step}
+ {/each} + {#if m.steps.length > 5} +
+ ещё {m.steps.length - 5}…
+ {/if} +
+
+ Создан: {fmtDate(m.created_at)} + Последний запуск: {fmtDate(m.last_run)} +
+
+ +   + +
+
+ {/each} +
+{/if} + + + + + + +