J.A.R.V.I.S-rust/frontend/src/routes/settings/index.svelte

819 lines
26 KiB
Svelte
Raw Normal View History

<script lang="ts">
2026-01-04 22:50:34 +05:00
import { onMount } from "svelte"
import { invoke } from "@tauri-apps/api/core"
import { goto } from "@roxi/routify"
import { setTimeout } from "worker-timers"
import { showInExplorer } from "@/functions"
feat(gui): wire Footer + Settings to daemon over IPC (closes the gap from a6a098d) a6a098d added daemon-side IPC handlers (SwitchLlm, ReloadLlm, QueryHealth) but the GUI was still talking only to its own process. This commit wires the frontend to actually use those handlers. frontend/src/lib/ipc.ts - New DaemonHealth interface + daemonHealth: writable<DaemonHealth | null>. - handleEvent: "health_snapshot" case fills daemonHealth from daemon's IpcEvent. - New senders: switchDaemonLlm(backend) → action "switch_llm" reloadDaemonLlm() → action "reload_llm" queryDaemonHealth() → action "query_health" frontend/src/stores.ts - Re-exports daemonHealth + the three new senders + DaemonHealth type. frontend/src/components/Footer.svelte - Polls every 5s but PREFERS IPC: if daemon connected, calls queryDaemonHealth (snapshot arrives via daemonHealth store). - Falls back to invoke("get_active_backends") if daemon offline — shows GUI-process view in that case, with the chip tooltip distinguishing "daemon" vs "gui-process" source. frontend/src/routes/settings/index.svelte (AI Backends tab) - applyLlmBackend now ALSO fires switchDaemonLlm so the running listener picks the new backend without restart. - "Auto" (empty) path calls reloadDaemonLlm so daemon re-reads DB. End-to-end flow now: GUI dropdown → set_llm_backend (Tauri, persists DB + swaps GUI proc) ↘ switchDaemonLlm (IPC, swaps daemon proc too) ↘ daemon emits health_snapshot on next QueryHealth ↘ daemonHealth store updates ↘ Footer chips re-render with new state Build: cargo build --release -p jarvis-gui green.
2026-05-15 17:42:40 +03:00
import {
appInfo, assistantVoice, translations, translate,
fix: TTS hot-swap actually works + GUI max-width + Python builder discoverable ROOT CAUSE ANALYSIS User reported: (a) TTS dropdown doesn't apply, (b) GUI feels stretched, (c) no link to command builder, (d) Python doesn't even start. Issues (a), (b), (c) addressed here. (d) addressed in python repo. === Fix A: TTS hot-swap (was completely broken) === Two distinct bugs: 1. `tts::init_backend()` only read JARVIS_TTS env var, NEVER consulted `Settings.tts_backend` in the DB. So saving "Silero" in the GUI dropdown changed nothing — not even on restart. 2. `BACKEND: OnceCell<...>` couldn't be replaced at runtime, so even if init had picked the right backend, the dropdown was useless mid-session. Fixed: - `tts::BACKEND` is now `Lazy<RwLock<Option<Arc<dyn TtsBackend>>>>`. - `choose_backend_name()` resolves from DB → env → auto-detect. - New `swap_to(name)` replaces the live backend atomically. - IPC: new `IpcAction::SwitchTts { backend }`. jarvis-app reacts to it the same way it reacts to SwitchLlm. - GUI's `set_tts_backend` now persists + hot-swaps GUI's TTS + Svelte page also fires `switchDaemonTts()` over WS so the running daemon installs the new backend. - Removed "Применится при следующем запуске" hint — now applies immediately. RU + EN locale strings updated. === Fix B: GUI max-width === main + #header had no max-width, so on a 1920px monitor the labels sprawled across 1860px of space, which felt stretched. Added max-width: 1280px + margin: 0 auto + box-sizing: border-box. Smaller windows are unaffected. Also added the same cap on .app-container.assist-page so the home screen arc reactor centres properly. === Fix C: Command builder discoverable === The Python fork ships a separate yaml-editing tool at `python/tools/command_builder/` (pywebview GUI). No way to find it from the main Rust GUI. Fixed: - New tauri command `open_command_builder` (crates/jarvis-gui/src/ tauri_commands/builder.rs). Locates the Python fork via $JARVIS_PYTHON_DIR → sibling python/ → C:\Jarvis\python, prefers the project's .venv, spawns `python -m tools.command_builder`. - New blue "Конструктор команд (Python)" button on /settings page near the Save / Back buttons. Tests: 140 rust + 104 python all pass. Release builds of jarvis-app and jarvis-gui both green.
2026-05-24 22:12:04 +03:00
switchDaemonLlm, reloadDaemonLlm, switchDaemonTts,
feat(gui): wire Footer + Settings to daemon over IPC (closes the gap from a6a098d) a6a098d added daemon-side IPC handlers (SwitchLlm, ReloadLlm, QueryHealth) but the GUI was still talking only to its own process. This commit wires the frontend to actually use those handlers. frontend/src/lib/ipc.ts - New DaemonHealth interface + daemonHealth: writable<DaemonHealth | null>. - handleEvent: "health_snapshot" case fills daemonHealth from daemon's IpcEvent. - New senders: switchDaemonLlm(backend) → action "switch_llm" reloadDaemonLlm() → action "reload_llm" queryDaemonHealth() → action "query_health" frontend/src/stores.ts - Re-exports daemonHealth + the three new senders + DaemonHealth type. frontend/src/components/Footer.svelte - Polls every 5s but PREFERS IPC: if daemon connected, calls queryDaemonHealth (snapshot arrives via daemonHealth store). - Falls back to invoke("get_active_backends") if daemon offline — shows GUI-process view in that case, with the chip tooltip distinguishing "daemon" vs "gui-process" source. frontend/src/routes/settings/index.svelte (AI Backends tab) - applyLlmBackend now ALSO fires switchDaemonLlm so the running listener picks the new backend without restart. - "Auto" (empty) path calls reloadDaemonLlm so daemon re-reads DB. End-to-end flow now: GUI dropdown → set_llm_backend (Tauri, persists DB + swaps GUI proc) ↘ switchDaemonLlm (IPC, swaps daemon proc too) ↘ daemon emits health_snapshot on next QueryHealth ↘ daemonHealth store updates ↘ Footer chips re-render with new state Build: cargo build --release -p jarvis-gui green.
2026-05-15 17:42:40 +03:00
} from "@/stores"
2026-01-04 22:50:34 +05:00
import HDivider from "@/components/elements/HDivider.svelte"
import Footer from "@/components/Footer.svelte"
import {
Notification,
Button,
Text,
Tabs,
Space,
Alert,
Input,
InputWrapper,
NativeSelect,
Switch
2026-01-04 22:50:34 +05:00
} from "@svelteuidev/core"
import {
Check,
Mix,
Cube,
Code,
Gear,
QuestionMarkCircled,
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
CrossCircled,
ChatBubble,
2026-01-04 22:50:34 +05:00
} from "radix-icons-svelte"
$: t = (key: string) => translate($translations, key)
interface VoiceMeta {
id: string
name: string
author: string
languages: string[]
}
interface VoiceConfig {
voice: VoiceMeta
}
let availableVoices: VoiceMeta[] = []
async function selectVoice(voiceId: string) {
voiceVal = voiceId
// play preview sound
try {
await invoke("preview_voice", { voiceId })
} catch (err) {
console.error("Failed to preview voice:", err)
}
}
2026-01-04 22:50:34 +05:00
// ### STATE
interface MicrophoneOption {
label: string
value: string
}
let availableMicrophones: MicrophoneOption[] = []
2026-01-05 03:38:04 +05:00
let availableVoskModels: { label: string; value: string }[] = []
let availableGlinerModels: { label: string; value: string }[] = []
2026-01-04 22:50:34 +05:00
let settingsSaved = false
let saveButtonDisabled = false
// form values (state vars)
2026-01-04 22:50:34 +05:00
let voiceVal = ""
let selectedMicrophone = ""
let selectedWakeWordEngine = ""
let selectedIntentRecognitionEngine = ""
let selectedSlotExtractionEngine = ""
let selectedGlinerModel = ""
2026-01-05 03:38:04 +05:00
let selectedVoskModel = ""
let selectedNoiseSuppression = ""
let selectedVad = ""
let gainNormalizerEnabled = false
2026-01-04 22:50:34 +05:00
let apiKeyPicovoice = ""
let apiKeyOpenai = ""
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
// ── AI backends (new tab) ─────────────────────────────────────────────
interface ActiveBackends {
tts: string
llm: string
llm_model: string | null
profile: string
}
let activeBackends: ActiveBackends | null = null
let selectedLlmBackend = "" // "" = auto, "groq", "ollama"
let selectedTtsBackend = "" // "" = auto, "sapi", "piper", "silero"
let backendSwapBusy = false
let backendSwapError = ""
async function refreshActiveBackends() {
try {
activeBackends = await invoke<ActiveBackends>("get_active_backends")
} catch (err) {
console.error("Failed to read active backends:", err)
}
}
async function applyLlmBackend(target: string) {
backendSwapError = ""
if (target === "") {
try {
await invoke("db_write", { key: "llm_backend", val: "" })
feat(gui): wire Footer + Settings to daemon over IPC (closes the gap from a6a098d) a6a098d added daemon-side IPC handlers (SwitchLlm, ReloadLlm, QueryHealth) but the GUI was still talking only to its own process. This commit wires the frontend to actually use those handlers. frontend/src/lib/ipc.ts - New DaemonHealth interface + daemonHealth: writable<DaemonHealth | null>. - handleEvent: "health_snapshot" case fills daemonHealth from daemon's IpcEvent. - New senders: switchDaemonLlm(backend) → action "switch_llm" reloadDaemonLlm() → action "reload_llm" queryDaemonHealth() → action "query_health" frontend/src/stores.ts - Re-exports daemonHealth + the three new senders + DaemonHealth type. frontend/src/components/Footer.svelte - Polls every 5s but PREFERS IPC: if daemon connected, calls queryDaemonHealth (snapshot arrives via daemonHealth store). - Falls back to invoke("get_active_backends") if daemon offline — shows GUI-process view in that case, with the chip tooltip distinguishing "daemon" vs "gui-process" source. frontend/src/routes/settings/index.svelte (AI Backends tab) - applyLlmBackend now ALSO fires switchDaemonLlm so the running listener picks the new backend without restart. - "Auto" (empty) path calls reloadDaemonLlm so daemon re-reads DB. End-to-end flow now: GUI dropdown → set_llm_backend (Tauri, persists DB + swaps GUI proc) ↘ switchDaemonLlm (IPC, swaps daemon proc too) ↘ daemon emits health_snapshot on next QueryHealth ↘ daemonHealth store updates ↘ Footer chips re-render with new state Build: cargo build --release -p jarvis-gui green.
2026-05-15 17:42:40 +03:00
reloadDaemonLlm() // tell daemon to re-read DB (now auto)
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
} catch (err) {
backendSwapError = String(err)
}
await refreshActiveBackends()
return
}
backendSwapBusy = true
try {
feat(gui): wire Footer + Settings to daemon over IPC (closes the gap from a6a098d) a6a098d added daemon-side IPC handlers (SwitchLlm, ReloadLlm, QueryHealth) but the GUI was still talking only to its own process. This commit wires the frontend to actually use those handlers. frontend/src/lib/ipc.ts - New DaemonHealth interface + daemonHealth: writable<DaemonHealth | null>. - handleEvent: "health_snapshot" case fills daemonHealth from daemon's IpcEvent. - New senders: switchDaemonLlm(backend) → action "switch_llm" reloadDaemonLlm() → action "reload_llm" queryDaemonHealth() → action "query_health" frontend/src/stores.ts - Re-exports daemonHealth + the three new senders + DaemonHealth type. frontend/src/components/Footer.svelte - Polls every 5s but PREFERS IPC: if daemon connected, calls queryDaemonHealth (snapshot arrives via daemonHealth store). - Falls back to invoke("get_active_backends") if daemon offline — shows GUI-process view in that case, with the chip tooltip distinguishing "daemon" vs "gui-process" source. frontend/src/routes/settings/index.svelte (AI Backends tab) - applyLlmBackend now ALSO fires switchDaemonLlm so the running listener picks the new backend without restart. - "Auto" (empty) path calls reloadDaemonLlm so daemon re-reads DB. End-to-end flow now: GUI dropdown → set_llm_backend (Tauri, persists DB + swaps GUI proc) ↘ switchDaemonLlm (IPC, swaps daemon proc too) ↘ daemon emits health_snapshot on next QueryHealth ↘ daemonHealth store updates ↘ Footer chips re-render with new state Build: cargo build --release -p jarvis-gui green.
2026-05-15 17:42:40 +03:00
// 1. Swap on GUI process (persists to DB).
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
await invoke<string>("set_llm_backend", { name: target })
feat(gui): wire Footer + Settings to daemon over IPC (closes the gap from a6a098d) a6a098d added daemon-side IPC handlers (SwitchLlm, ReloadLlm, QueryHealth) but the GUI was still talking only to its own process. This commit wires the frontend to actually use those handlers. frontend/src/lib/ipc.ts - New DaemonHealth interface + daemonHealth: writable<DaemonHealth | null>. - handleEvent: "health_snapshot" case fills daemonHealth from daemon's IpcEvent. - New senders: switchDaemonLlm(backend) → action "switch_llm" reloadDaemonLlm() → action "reload_llm" queryDaemonHealth() → action "query_health" frontend/src/stores.ts - Re-exports daemonHealth + the three new senders + DaemonHealth type. frontend/src/components/Footer.svelte - Polls every 5s but PREFERS IPC: if daemon connected, calls queryDaemonHealth (snapshot arrives via daemonHealth store). - Falls back to invoke("get_active_backends") if daemon offline — shows GUI-process view in that case, with the chip tooltip distinguishing "daemon" vs "gui-process" source. frontend/src/routes/settings/index.svelte (AI Backends tab) - applyLlmBackend now ALSO fires switchDaemonLlm so the running listener picks the new backend without restart. - "Auto" (empty) path calls reloadDaemonLlm so daemon re-reads DB. End-to-end flow now: GUI dropdown → set_llm_backend (Tauri, persists DB + swaps GUI proc) ↘ switchDaemonLlm (IPC, swaps daemon proc too) ↘ daemon emits health_snapshot on next QueryHealth ↘ daemonHealth store updates ↘ Footer chips re-render with new state Build: cargo build --release -p jarvis-gui green.
2026-05-15 17:42:40 +03:00
// 2. Tell daemon to swap too, so the running listener uses the
// new backend immediately. No-op if daemon not connected.
switchDaemonLlm(target as "groq" | "ollama")
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
await refreshActiveBackends()
} catch (err) {
backendSwapError = String(err)
} finally {
backendSwapBusy = false
}
}
async function applyTtsBackend(target: string) {
try {
fix: TTS hot-swap actually works + GUI max-width + Python builder discoverable ROOT CAUSE ANALYSIS User reported: (a) TTS dropdown doesn't apply, (b) GUI feels stretched, (c) no link to command builder, (d) Python doesn't even start. Issues (a), (b), (c) addressed here. (d) addressed in python repo. === Fix A: TTS hot-swap (was completely broken) === Two distinct bugs: 1. `tts::init_backend()` only read JARVIS_TTS env var, NEVER consulted `Settings.tts_backend` in the DB. So saving "Silero" in the GUI dropdown changed nothing — not even on restart. 2. `BACKEND: OnceCell<...>` couldn't be replaced at runtime, so even if init had picked the right backend, the dropdown was useless mid-session. Fixed: - `tts::BACKEND` is now `Lazy<RwLock<Option<Arc<dyn TtsBackend>>>>`. - `choose_backend_name()` resolves from DB → env → auto-detect. - New `swap_to(name)` replaces the live backend atomically. - IPC: new `IpcAction::SwitchTts { backend }`. jarvis-app reacts to it the same way it reacts to SwitchLlm. - GUI's `set_tts_backend` now persists + hot-swaps GUI's TTS + Svelte page also fires `switchDaemonTts()` over WS so the running daemon installs the new backend. - Removed "Применится при следующем запуске" hint — now applies immediately. RU + EN locale strings updated. === Fix B: GUI max-width === main + #header had no max-width, so on a 1920px monitor the labels sprawled across 1860px of space, which felt stretched. Added max-width: 1280px + margin: 0 auto + box-sizing: border-box. Smaller windows are unaffected. Also added the same cap on .app-container.assist-page so the home screen arc reactor centres properly. === Fix C: Command builder discoverable === The Python fork ships a separate yaml-editing tool at `python/tools/command_builder/` (pywebview GUI). No way to find it from the main Rust GUI. Fixed: - New tauri command `open_command_builder` (crates/jarvis-gui/src/ tauri_commands/builder.rs). Locates the Python fork via $JARVIS_PYTHON_DIR → sibling python/ → C:\Jarvis\python, prefers the project's .venv, spawns `python -m tools.command_builder`. - New blue "Конструктор команд (Python)" button on /settings page near the Save / Back buttons. Tests: 140 rust + 104 python all pass. Release builds of jarvis-app and jarvis-gui both green.
2026-05-24 22:12:04 +03:00
// 1. GUI Tauri command — persists to DB + hot-swaps GUI's TTS.
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
await invoke<string>("set_tts_backend", { name: target })
fix: TTS hot-swap actually works + GUI max-width + Python builder discoverable ROOT CAUSE ANALYSIS User reported: (a) TTS dropdown doesn't apply, (b) GUI feels stretched, (c) no link to command builder, (d) Python doesn't even start. Issues (a), (b), (c) addressed here. (d) addressed in python repo. === Fix A: TTS hot-swap (was completely broken) === Two distinct bugs: 1. `tts::init_backend()` only read JARVIS_TTS env var, NEVER consulted `Settings.tts_backend` in the DB. So saving "Silero" in the GUI dropdown changed nothing — not even on restart. 2. `BACKEND: OnceCell<...>` couldn't be replaced at runtime, so even if init had picked the right backend, the dropdown was useless mid-session. Fixed: - `tts::BACKEND` is now `Lazy<RwLock<Option<Arc<dyn TtsBackend>>>>`. - `choose_backend_name()` resolves from DB → env → auto-detect. - New `swap_to(name)` replaces the live backend atomically. - IPC: new `IpcAction::SwitchTts { backend }`. jarvis-app reacts to it the same way it reacts to SwitchLlm. - GUI's `set_tts_backend` now persists + hot-swaps GUI's TTS + Svelte page also fires `switchDaemonTts()` over WS so the running daemon installs the new backend. - Removed "Применится при следующем запуске" hint — now applies immediately. RU + EN locale strings updated. === Fix B: GUI max-width === main + #header had no max-width, so on a 1920px monitor the labels sprawled across 1860px of space, which felt stretched. Added max-width: 1280px + margin: 0 auto + box-sizing: border-box. Smaller windows are unaffected. Also added the same cap on .app-container.assist-page so the home screen arc reactor centres properly. === Fix C: Command builder discoverable === The Python fork ships a separate yaml-editing tool at `python/tools/command_builder/` (pywebview GUI). No way to find it from the main Rust GUI. Fixed: - New tauri command `open_command_builder` (crates/jarvis-gui/src/ tauri_commands/builder.rs). Locates the Python fork via $JARVIS_PYTHON_DIR → sibling python/ → C:\Jarvis\python, prefers the project's .venv, spawns `python -m tools.command_builder`. - New blue "Конструктор команд (Python)" button on /settings page near the Save / Back buttons. Tests: 140 rust + 104 python all pass. Release builds of jarvis-app and jarvis-gui both green.
2026-05-24 22:12:04 +03:00
// 2. Tell the running daemon (separate process) to switch too.
switchDaemonTts(target || "auto")
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
} catch (err) {
backendSwapError = String(err)
}
}
async function resetLlmContext() {
try {
await invoke("llm_reset_context")
} catch (err) {
console.error(err)
}
}
2026-01-04 22:50:34 +05:00
// subscribe to stores
assistantVoice.subscribe(value => {
voiceVal = value
})
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
let issuesLink = ""
2026-01-04 22:50:34 +05:00
let logFilePath = ""
appInfo.subscribe(info => {
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
issuesLink = info.issuesLink
2026-01-04 22:50:34 +05:00
logFilePath = info.logFilePath
})
// ### FUNCTIONS
async function saveSettings() {
saveButtonDisabled = true
settingsSaved = false
try {
await Promise.all([
invoke("db_write", { key: "assistant_voice", val: voiceVal }),
invoke("db_write", { key: "selected_microphone", val: selectedMicrophone }),
invoke("db_write", { key: "selected_wake_word_engine", val: selectedWakeWordEngine }),
invoke("db_write", { key: "selected_intent_recognition_engine", val: selectedIntentRecognitionEngine }),
invoke("db_write", { key: "selected_slot_extraction_engine", val: selectedSlotExtractionEngine }),
invoke("db_write", { key: "selected_gliner_model", val: selectedGlinerModel }),
2026-01-05 03:38:04 +05:00
invoke("db_write", { key: "selected_vosk_model", val: selectedVoskModel }),
invoke("db_write", { key: "noise_suppression", val: selectedNoiseSuppression }),
invoke("db_write", { key: "vad", val: selectedVad }),
invoke("db_write", { key: "gain_normalizer", val: gainNormalizerEnabled.toString() }),
2026-01-04 22:50:34 +05:00
invoke("db_write", { key: "api_key__picovoice", val: apiKeyPicovoice }),
invoke("db_write", { key: "api_key__openai", val: apiKeyOpenai })
])
// update shared store
assistantVoice.set(voiceVal)
settingsSaved = true
// hide alert after 5 seconds
setTimeout(() => {
settingsSaved = false
}, 5000)
// restart listening with new settings
// stopListening(() => startListening())
2026-01-04 22:50:34 +05:00
} catch (err) {
console.error("failed to save settings:", err)
}
setTimeout(() => {
saveButtonDisabled = false
}, 1000)
}
// ### INIT
onMount(async () => {
// load voices
try {
const voices = await invoke<VoiceConfig[]>("list_voices")
availableVoices = voices.map(v => v.voice)
} catch (err) {
console.error("Failed to load voices:", err)
availableVoices = []
}
2026-01-04 22:50:34 +05:00
try {
// load microphones
const mics = await invoke<string[]>("pv_get_audio_devices")
availableMicrophones = [
{ label: t('settings-mic-default'), value: "-1" }, // system default
...mics.map((name, idx) => ({
label: name,
value: String(idx)
}))
]
2026-01-04 22:50:34 +05:00
2026-01-05 03:38:04 +05:00
// load vosk models
const languageNames: Record<string, string> = {
us: 'English',
ru: 'Русский',
de: 'German',
fr: 'French',
es: 'Spanish',
// ..
};
2026-01-05 03:38:04 +05:00
const voskModels = await invoke<{ name: string; language: string; size: string }[]>("list_vosk_models")
availableVoskModels = voskModels.map(m => ({
label: `${m.name} (${languageNames[m.language] ?? m.language}, ${m.size})`,
2026-01-05 03:38:04 +05:00
value: m.name
}))
// load gliner models
const glinerModels = await invoke<{ display_name: string; value: string }[]>("list_gliner_models")
availableGlinerModels = glinerModels.map(m => ({
label: m.display_name,
value: m.value,
}))
2026-01-04 22:50:34 +05:00
// load settings from db
const [mic, wakeWord, intentReco, slotEngine, glinerModel, voskModel,
noiseSuppression, vad, gainNormalizer,
pico, openai] = await Promise.all([
2026-01-04 22:50:34 +05:00
invoke<string>("db_read", { key: "selected_microphone" }),
invoke<string>("db_read", { key: "selected_wake_word_engine" }),
invoke<string>("db_read", { key: "selected_intent_recognition_engine" }),
invoke<string>("db_read", { key: "selected_slot_extraction_engine" }),
invoke<string>("db_read", { key: "selected_gliner_model" }),
2026-01-05 03:38:04 +05:00
invoke<string>("db_read", { key: "selected_vosk_model" }),
invoke<string>("db_read", { key: "noise_suppression" }),
invoke<string>("db_read", { key: "vad" }),
invoke<string>("db_read", { key: "gain_normalizer" }),
2026-01-04 22:50:34 +05:00
invoke<string>("db_read", { key: "api_key__picovoice" }),
invoke<string>("db_read", { key: "api_key__openai" })
])
selectedMicrophone = mic
selectedWakeWordEngine = wakeWord
selectedIntentRecognitionEngine = intentReco
selectedSlotExtractionEngine = slotEngine
2026-01-05 03:38:04 +05:00
selectedVoskModel = voskModel
selectedGlinerModel = glinerModel
selectedNoiseSuppression = noiseSuppression
selectedVad = vad
gainNormalizerEnabled = gainNormalizer === "true"
2026-01-04 22:50:34 +05:00
apiKeyPicovoice = pico
apiKeyOpenai = openai
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
// load AI backend prefs
try {
const [llm_pref, tts_pref] = await Promise.all([
invoke<string>("db_read", { key: "llm_backend" }),
invoke<string>("db_read", { key: "tts_backend" }),
])
selectedLlmBackend = llm_pref ?? ""
selectedTtsBackend = tts_pref ?? ""
} catch (err) {
console.error("Failed to load backend prefs:", err)
}
await refreshActiveBackends()
2026-01-04 22:50:34 +05:00
} catch (err) {
console.error("failed to load settings:", err)
}
})
</script>
<Space h="xl" />
2026-01-04 22:50:34 +05:00
<Notification
title={t('settings-beta-title')}
2026-01-04 22:50:34 +05:00
icon={QuestionMarkCircled}
color="blue"
withCloseButton={false}
>
{t('settings-beta-desc')}<br />
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
{t('settings-beta-feedback')} <a href={issuesLink} target="_blank" rel="noopener noreferrer">{t('settings-beta-bot')}</a>.
2026-01-04 22:50:34 +05:00
<Space h="sm" />
<Button
color="gray"
radius="md"
size="xs"
uppercase
on:click={() => showInExplorer(logFilePath)}
>
{t('settings-open-logs')}
2026-01-04 22:50:34 +05:00
</Button>
</Notification>
<Space h="xl" />
2026-01-04 22:50:34 +05:00
{#if settingsSaved}
<Notification
title={t('notification-saved')}
2026-01-04 22:50:34 +05:00
icon={Check}
color="teal"
on:close={() => { settingsSaved = false }}
/>
<Space h="xl" />
2026-01-04 22:50:34 +05:00
{/if}
2026-01-04 22:50:34 +05:00
<Tabs class="form" color="#8AC832" position="left">
<Tabs.Tab label={t('settings-general')} icon={Gear}>
2026-01-04 22:50:34 +05:00
<Space h="sm" />
<div class="voice-select">
<label>{t('settings-voice')}</label>
<p class="description">{t('settings-voice-desc')}</p>
<div class="voice-options">
{#each availableVoices as voice}
<button
type="button"
class="voice-option"
class:selected={voiceVal === voice.id}
on:click={() => selectVoice(voice.id)}
>
<div class="voice-info">
<span class="voice-name">{voice.name}</span>
{#if voice.author}
<span class="voice-author">by {voice.author}</span>
{/if}
</div>
<div class="voice-languages">
{#each voice.languages as lang}
<img
src="/media/flags/{lang.toUpperCase()}.png"
alt={lang}
width="20"
title={lang}
/>
{/each}
</div>
</button>
{/each}
{#if availableVoices.length === 0}
<p class="no-voices">{t('settings-no-voices')}</p>
{/if}
</div>
</div>
2026-01-04 22:50:34 +05:00
</Tabs.Tab>
<Tabs.Tab label={t('settings-devices')} icon={Mix}>
2026-01-04 22:50:34 +05:00
<Space h="sm" />
<NativeSelect
data={availableMicrophones}
label={t('settings-microphone')}
description={t('settings-microphone-desc')}
2026-01-04 22:50:34 +05:00
variant="filled"
bind:value={selectedMicrophone}
/>
</Tabs.Tab>
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
<Tabs.Tab label={t('settings-ai-backends') || 'AI Backends'} icon={ChatBubble}>
<Space h="sm" />
{#if activeBackends}
<Alert title={t('settings-ai-active') || 'Активный движок'} color="cyan" variant="outline">
<Text size="sm" color="gray">
LLM: <strong>{activeBackends.llm}</strong>
{#if activeBackends.llm_model}({activeBackends.llm_model}){/if}
&nbsp;·&nbsp; TTS: <strong>{activeBackends.tts}</strong>
&nbsp;·&nbsp; {t('settings-profile') || 'Profile'}: <strong>{activeBackends.profile}</strong>
</Text>
</Alert>
<Space h="md" />
{/if}
<NativeSelect
data={[
{ label: t('settings-ai-auto') || 'Авто (по env / GROQ_TOKEN)', value: "" },
{ label: 'Groq (cloud, fast, free tier)', value: "groq" },
{ label: 'Ollama (local, offline, private)', value: "ollama" },
]}
label={t('settings-llm-backend') || 'LLM движок'}
description={t('settings-llm-backend-desc') || 'Где исполнять LLM-запросы. Hot-swap, без рестарта.'}
variant="filled"
bind:value={selectedLlmBackend}
on:change={() => applyLlmBackend(selectedLlmBackend)}
/>
<Space h="md" />
<NativeSelect
data={[
{ label: t('settings-ai-auto') || 'Авто (по env / Piper detect)', value: "" },
{ label: 'SAPI (Windows, robotic)', value: "sapi" },
{ label: 'Piper (neural, recommended)', value: "piper" },
{ label: 'Silero (PyTorch helper)', value: "silero" },
]}
label={t('settings-tts-backend') || 'TTS движок'}
fix: TTS hot-swap actually works + GUI max-width + Python builder discoverable ROOT CAUSE ANALYSIS User reported: (a) TTS dropdown doesn't apply, (b) GUI feels stretched, (c) no link to command builder, (d) Python doesn't even start. Issues (a), (b), (c) addressed here. (d) addressed in python repo. === Fix A: TTS hot-swap (was completely broken) === Two distinct bugs: 1. `tts::init_backend()` only read JARVIS_TTS env var, NEVER consulted `Settings.tts_backend` in the DB. So saving "Silero" in the GUI dropdown changed nothing — not even on restart. 2. `BACKEND: OnceCell<...>` couldn't be replaced at runtime, so even if init had picked the right backend, the dropdown was useless mid-session. Fixed: - `tts::BACKEND` is now `Lazy<RwLock<Option<Arc<dyn TtsBackend>>>>`. - `choose_backend_name()` resolves from DB → env → auto-detect. - New `swap_to(name)` replaces the live backend atomically. - IPC: new `IpcAction::SwitchTts { backend }`. jarvis-app reacts to it the same way it reacts to SwitchLlm. - GUI's `set_tts_backend` now persists + hot-swaps GUI's TTS + Svelte page also fires `switchDaemonTts()` over WS so the running daemon installs the new backend. - Removed "Применится при следующем запуске" hint — now applies immediately. RU + EN locale strings updated. === Fix B: GUI max-width === main + #header had no max-width, so on a 1920px monitor the labels sprawled across 1860px of space, which felt stretched. Added max-width: 1280px + margin: 0 auto + box-sizing: border-box. Smaller windows are unaffected. Also added the same cap on .app-container.assist-page so the home screen arc reactor centres properly. === Fix C: Command builder discoverable === The Python fork ships a separate yaml-editing tool at `python/tools/command_builder/` (pywebview GUI). No way to find it from the main Rust GUI. Fixed: - New tauri command `open_command_builder` (crates/jarvis-gui/src/ tauri_commands/builder.rs). Locates the Python fork via $JARVIS_PYTHON_DIR → sibling python/ → C:\Jarvis\python, prefers the project's .venv, spawns `python -m tools.command_builder`. - New blue "Конструктор команд (Python)" button on /settings page near the Save / Back buttons. Tests: 140 rust + 104 python all pass. Release builds of jarvis-app and jarvis-gui both green.
2026-05-24 22:12:04 +03:00
description={t('settings-tts-backend-desc') || 'Применяется сразу — без перезапуска.'}
feat(gui): footer backend chips + Settings 'AI Backends' tab + i18n Surfaces the LLM/TTS hot-swap UX in the GUI so the user doesn't need env vars or voice commands. Tauri commands for this landed in b243e67; this commit wires them to Svelte. Footer (frontend/src/components/Footer.svelte) - Poll get_active_backends() every 5 s, show TTS / LLM / Profile chips. - Chips have border accents by kind (cyan = TTS, lime = LLM, orange = Profile). - Friendly title tooltips with full backend + model names. - Silent if jarvis-gui can't reach the command (e.g. cold start) — no UI flicker. Settings — new "AI Backends" tab (frontend/src/routes/settings/index.svelte) - Status banner showing active LLM (with model name) + TTS + Profile. - LLM selector: Auto / Groq / Ollama → calls set_llm_backend on change (hot-swap, persisted to DB). - TTS selector: Auto / SAPI / Piper / Silero → calls set_tts_backend (persisted, effective on next jarvis-app restart). - "Reset context" button → llm_reset_context Tauri command. - Error toast for swap failures (e.g. Groq selected but no GROQ_TOKEN). - Tips alert: how to install Ollama / Piper / voice commands. - Loads prefs via db_read('llm_backend' / 'tts_backend') on mount. i18n strings (ru/en/ua, .ftl files) - 14 new keys: settings-ai-backends, settings-ai-active, settings-ai-auto, settings-ai-applying, settings-ai-error, settings-ai-tips, settings-llm-backend{,-desc}, settings-tts-backend{,-desc}, settings-llm-context{,-desc}, settings-llm-reset, settings-profile. Build glue (crates/jarvis-gui/Cargo.toml) - jarvis-core dep now includes the `llm` feature so backends.rs can resolve `jarvis_core::llm::*` (was failing to compile after the global LLM module). .gitignore - tools/piper/*.dll, piper.exe, voices/, espeak-ng-data/ — locally installed binaries from tools/piper/install.ps1, not committed. Build: cargo build --release -p jarvis-gui green (4m, includes npm/Vite). Test path: rebuild + launch jarvis-gui, /settings → "AI Backends" tab.
2026-05-15 17:06:46 +03:00
variant="filled"
bind:value={selectedTtsBackend}
on:change={() => applyTtsBackend(selectedTtsBackend)}
/>
<Space h="md" />
{#if backendSwapBusy}
<Text size="xs" color="gray">
{t('settings-ai-applying') || 'Применяю...'}
</Text>
{/if}
{#if backendSwapError}
<Notification
title={t('settings-ai-error') || 'Ошибка переключения'}
icon={CrossCircled}
color="red"
withCloseButton={true}
on:close={() => { backendSwapError = "" }}
>
{backendSwapError}
</Notification>
{/if}
<Space h="md" />
<InputWrapper label={t('settings-llm-context') || 'Контекст разговора'}>
<Text size="sm" color="gray">
{t('settings-llm-context-desc') || 'LLM помнит предыдущие реплики. Сбросить, если ответы становятся странными.'}
</Text>
<Space h="xs" />
<Button
color="gray"
radius="md"
size="xs"
uppercase
on:click={resetLlmContext}
>
{t('settings-llm-reset') || 'Сбросить контекст'}
</Button>
</InputWrapper>
<Space h="xl" />
<Alert title={t('settings-ai-tips') || 'Подсказки'} color="gray" variant="outline">
<Text size="sm" color="gray">
<strong>Ollama</strong>: установи с <a href="https://ollama.com" target="_blank">ollama.com</a>,
выполни <code>ollama pull qwen2.5:3b</code>.<br />
<strong>Piper</strong>: запусти <code>pwsh tools/piper/install.ps1</code> чтобы скачать голос.<br />
<strong>Голосом</strong>: «переключись на локальный» / «переключись на облако»,
«какой у тебя мозг» — спрашивает статус.<br />
<strong>Сброс/повтор</strong>: «сбрось контекст» / «повтори последнее».
</Text>
</Alert>
</Tabs.Tab>
<Tabs.Tab label={t('settings-neural-networks')} icon={Cube}>
2026-01-04 22:50:34 +05:00
<Space h="sm" />
<NativeSelect
data={[
{ label: "Rustpotter", value: "Rustpotter" },
{ label: "Vosk", value: "Vosk" },
{ label: "Picovoice Porcupine", value: "Picovoice" }
2026-01-04 22:50:34 +05:00
]}
label={t('settings-wake-word-engine')}
description={t('settings-wake-word-desc')}
2026-01-04 22:50:34 +05:00
variant="filled"
bind:value={selectedWakeWordEngine}
/>
{#if selectedWakeWordEngine === "picovoice"}
<Space h="sm" />
<Alert title={t('settings-attention')} color="#868E96" variant="outline">
2026-01-04 22:50:34 +05:00
<Notification
title={t('settings-picovoice-warning')}
2026-01-04 22:50:34 +05:00
icon={CrossCircled}
color="orange"
withCloseButton={false}
>
{t('settings-picovoice-waiting')}
2026-01-04 22:50:34 +05:00
</Notification>
<Space h="sm" />
<Text size="sm" color="gray">
{t('settings-picovoice-key-desc')}
2026-01-04 22:50:34 +05:00
<a href="https://console.picovoice.ai/" target="_blank">Picovoice Console</a>.
</Text>
<Space h="sm" />
<Input
icon={Code}
placeholder={t('settings-picovoice-key')}
2026-01-04 22:50:34 +05:00
variant="filled"
autocomplete="off"
bind:value={apiKeyPicovoice}
/>
</Alert>
{/if}
2026-01-05 03:38:04 +05:00
<Space h="xl" />
{#key availableVoskModels}
<NativeSelect
data={[
{ label: t('settings-auto-detect'), value: "" },
2026-01-05 03:38:04 +05:00
...availableVoskModels
]}
label={t('settings-vosk-model')}
description={t('settings-vosk-model-desc')}
2026-01-05 03:38:04 +05:00
variant="filled"
bind:value={selectedVoskModel}
/>
{/key}
{#if availableVoskModels.length === 0}
<Space h="sm" />
<Alert title={t('settings-models-not-found')} color="orange" variant="outline">
2026-01-05 03:38:04 +05:00
<Text size="sm" color="gray">
{t('settings-models-hint')}
2026-01-05 03:38:04 +05:00
</Text>
</Alert>
{/if}
2026-01-04 22:50:34 +05:00
<Space h="xl" />
<NativeSelect
data={[
{ label: "Intent Classifier", value: "IntentClassifier" },
{ label: "Embedding Classifier", value: "EmbeddingClassifier" }
]}
label={t('settings-intent-engine')}
description={t('settings-intent-engine-desc')}
variant="filled"
bind:value={selectedIntentRecognitionEngine}
/>
<Space h="xl" />
<NativeSelect
data={[
{ label: t('settings-disabled'), value: "None" },
{ label: "GLiNER (NER)", value: "GLiNER" }
]}
label={t('settings-slot-engine')}
description={t('settings-slot-engine-desc')}
variant="filled"
bind:value={selectedSlotExtractionEngine}
/>
{#if selectedSlotExtractionEngine === "GLiNER"}
<Space h="sm" />
{#key availableGlinerModels}
<NativeSelect
data={[
{ label: t('settings-auto-detect'), value: "" },
...availableGlinerModels
]}
label={t('settings-gliner-model')}
description={t('settings-gliner-model-desc')}
variant="filled"
bind:value={selectedGlinerModel}
/>
{/key}
{#if availableGlinerModels.length === 0}
<Space h="sm" />
<Alert title={t('settings-models-not-found')} color="orange" variant="outline">
<Text size="sm" color="gray">
{t('settings-gliner-models-hint')}
</Text>
</Alert>
{/if}
{/if}
<Space h="xl" />
<NativeSelect
data={[
{ label: t('settings-disabled'), value: "None" },
{ label: "Nnnoiseless", value: "Nnnoiseless" }
]}
label={t('settings-noise-suppression')}
description={t('settings-noise-suppression-desc')}
variant="filled"
bind:value={selectedNoiseSuppression}
/>
<Space h="md" />
<NativeSelect
data={[
{ label: t('settings-disabled'), value: "None" },
{ label: "Energy", value: "Energy" },
{ label: "Nnnoiseless", value: "Nnnoiseless" }
]}
label={t('settings-vad')}
description={t('settings-vad-desc')}
variant="filled"
bind:value={selectedVad}
/>
<Space h="md" />
<InputWrapper label={t('settings-gain-normalizer')}>
<Text size="sm" color="gray">
{t('settings-gain-normalizer-desc')}
</Text>
<Space h="xs" />
<Switch
label={gainNormalizerEnabled ? t('settings-enabled') : t('settings-disabled')}
bind:checked={gainNormalizerEnabled}
/>
</InputWrapper>
<Space h="xl" />
2026-01-04 22:50:34 +05:00
<InputWrapper label={t('settings-openai-key')}>
2026-01-04 22:50:34 +05:00
<Text size="sm" color="gray">
{t('settings-openai-not-supported')}
2026-01-04 22:50:34 +05:00
</Text>
<Space h="sm" />
<Input
icon={Code}
placeholder={t('settings-openai-key')}
2026-01-04 22:50:34 +05:00
variant="filled"
autocomplete="off"
bind:value={apiKeyOpenai}
disabled
/>
</InputWrapper>
</Tabs.Tab>
</Tabs>
<Space h="xl" />
2026-01-04 22:50:34 +05:00
<Button
color="lime"
radius="md"
size="sm"
uppercase
ripple
fullSize
on:click={saveSettings}
disabled={saveButtonDisabled}
>
{t('settings-save')}
</Button>
2026-01-04 22:50:34 +05:00
<Space h="sm" />
2026-01-04 22:50:34 +05:00
fix: TTS hot-swap actually works + GUI max-width + Python builder discoverable ROOT CAUSE ANALYSIS User reported: (a) TTS dropdown doesn't apply, (b) GUI feels stretched, (c) no link to command builder, (d) Python doesn't even start. Issues (a), (b), (c) addressed here. (d) addressed in python repo. === Fix A: TTS hot-swap (was completely broken) === Two distinct bugs: 1. `tts::init_backend()` only read JARVIS_TTS env var, NEVER consulted `Settings.tts_backend` in the DB. So saving "Silero" in the GUI dropdown changed nothing — not even on restart. 2. `BACKEND: OnceCell<...>` couldn't be replaced at runtime, so even if init had picked the right backend, the dropdown was useless mid-session. Fixed: - `tts::BACKEND` is now `Lazy<RwLock<Option<Arc<dyn TtsBackend>>>>`. - `choose_backend_name()` resolves from DB → env → auto-detect. - New `swap_to(name)` replaces the live backend atomically. - IPC: new `IpcAction::SwitchTts { backend }`. jarvis-app reacts to it the same way it reacts to SwitchLlm. - GUI's `set_tts_backend` now persists + hot-swaps GUI's TTS + Svelte page also fires `switchDaemonTts()` over WS so the running daemon installs the new backend. - Removed "Применится при следующем запуске" hint — now applies immediately. RU + EN locale strings updated. === Fix B: GUI max-width === main + #header had no max-width, so on a 1920px monitor the labels sprawled across 1860px of space, which felt stretched. Added max-width: 1280px + margin: 0 auto + box-sizing: border-box. Smaller windows are unaffected. Also added the same cap on .app-container.assist-page so the home screen arc reactor centres properly. === Fix C: Command builder discoverable === The Python fork ships a separate yaml-editing tool at `python/tools/command_builder/` (pywebview GUI). No way to find it from the main Rust GUI. Fixed: - New tauri command `open_command_builder` (crates/jarvis-gui/src/ tauri_commands/builder.rs). Locates the Python fork via $JARVIS_PYTHON_DIR → sibling python/ → C:\Jarvis\python, prefers the project's .venv, spawns `python -m tools.command_builder`. - New blue "Конструктор команд (Python)" button on /settings page near the Save / Back buttons. Tests: 140 rust + 104 python all pass. Release builds of jarvis-app and jarvis-gui both green.
2026-05-24 22:12:04 +03:00
<Button
color="blue"
radius="md"
size="sm"
uppercase
fullSize
on:click={async () => {
try {
const msg = await invoke<string>("open_command_builder")
backendSwapError = "Конструктор команд: " + msg
} catch (e) {
backendSwapError = "Не удалось запустить конструктор: " + String(e)
}
}}
>
Конструктор команд (Python)
</Button>
<Space h="sm" />
2026-01-04 22:50:34 +05:00
<Button
color="gray"
radius="md"
size="sm"
uppercase
fullSize
on:click={() => $goto("/")}
>
{t('settings-back')}
</Button>
<HDivider />
2026-01-04 22:50:34 +05:00
<Footer />
<style lang="scss">
.voice-select {
margin-bottom: 1rem;
label {
font-weight: 600;
font-size: 0.9rem;
color: #fff;
display: block;
margin-bottom: 0.25rem;
}
.description {
font-size: 0.75rem;
color: rgba(255,255,255,0.5);
margin: 0 0 0.75rem;
white-space: pre-line;
}
}
$voice-item-height: 70px;
$voice-item-gap: 0.5rem;
$voice-max-visible: 3;
.voice-options {
display: flex;
flex-direction: column;
gap: $voice-item-gap;
max-height: $voice-item-height * $voice-max-visible;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
&:hover {
background: rgba(255, 255, 255, 0.3);
}
}
}
.voice-option {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
background: rgba(30, 40, 45, 0.8);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
text-align: left;
width: 100%;
&:hover {
background: rgba(40, 55, 60, 0.9);
border-color: rgba(255,255,255,0.2);
}
&.selected {
background: rgba(82, 254, 254, 0.1);
border-color: rgba(82, 254, 254, 0.4);
}
}
.voice-info {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.15rem;
}
.voice-name {
font-size: 0.85rem;
color: #fff;
font-weight: 500;
}
.voice-author {
font-size: 0.7rem;
color: rgba(255,255,255,0.4);
}
.voice-languages {
display: flex;
gap: 0.35rem;
img {
opacity: 0.8;
border-radius: 2px;
}
}
.no-voices {
font-size: 0.8rem;
color: rgba(255,255,255,0.4);
font-style: italic;
}
</style>