feat: persist LLM/TTS backend choice + reset/repeat context + quick-search + diagnostics

Closes the UX hole I created in 5c72450: voice-swap to Ollama used to vanish
after restart. Plus P0.3 (long-standing roadmap item) and two new packs.

Persistent backend choice (crates/jarvis-core/src/db/structs.rs)
  - Settings struct gains llm_backend + tts_backend fields (both String,
    "" / "auto" = follow env/auto-detect).
  - set("llm_backend", "groq"|"ollama"|"auto") validates input.
  - llm::init_global() reads DB first, then JARVIS_LLM env, then auto-detect.
  - llm::swap_to() now persists the choice via db::save_settings.
  - Voice swap "переключись на локальный" now survives restart.

Shared conversation history (P0.3, crates/jarvis-core/src/llm/mod.rs)
  - HISTORY: Lazy<RwLock<Option<ConversationHistory>>> singleton.
  - Helpers: init_history, history_push_user, history_push_assistant,
    history_snapshot, history_clear, history_pop_last_user, history_last_assistant.
  - llm_fallback migrated off its own Mutex<History> — now reads/writes shared.
  - ConversationHistory gains last_assistant() method.

New Lua APIs
  - jarvis.llm_reset()        → clear conversation turns (keeps system prompt).
  - jarvis.llm_last_reply()   → string or nil (last assistant message text).
  - jarvis.health()           → debug table {tts_backend, llm_backend, llm_model,
                                  active_profile, memory_facts, scheduled_tasks,
                                  language, voice, microphone, vosk_model,
                                  noise_suppression}. No secrets included.

New voice packs
  - resources/commands/llm_context/   (P0.3)
    * "сбрось контекст" / "забудь разговор"     → llm.reset
    * "повтори последнее" / "повтори ответ"     → llm.repeat (uses last_assistant)
  - resources/commands/quick_search/   (imba P1 item)
    * "найди в гугле <X>" / "загугли <X>"
    * Uses DuckDuckGo Instant Answer API (api.duckduckgo.com, no key required).
      Pulls AbstractText or RelatedTopics into LLM prompt; falls back to pure
      LLM knowledge if DDG returns nothing useful. Speaks 2-4 sentence answer.
  - resources/commands/diagnostics/
    * "диагностика" / "доложи о себе" / "статус"
    * Reads jarvis.health() and speaks a one-line summary. Useful when
      debugging — user can read out their current state for a bug report.

Build: cargo build --release -p jarvis-app -p jarvis-gui green.
Tests: 52/52 jarvis-core unit tests pass.
This commit is contained in:
Bossiara13 2026-05-15 16:25:28 +03:00
parent 5c7245012e
commit 385bd5c8ce
15 changed files with 413 additions and 37 deletions

View file

@ -12,4 +12,5 @@ pub mod memory;
pub mod profile;
pub mod vision;
pub mod scheduler;
pub mod cmd;
pub mod cmd;
pub mod health;

View file

@ -0,0 +1,50 @@
//! `jarvis.health()` — debug snapshot of active runtime state.
//!
//! Returns a table that can be JSON-encoded for bug reports. Doesn't include
//! secrets (no API keys, no LLM history content).
//!
//! Example use from Lua:
//! local h = jarvis.health()
//! for k, v in pairs(h) do print(k, v) end
use mlua::{Lua, Table};
pub fn register(lua: &Lua, jarvis: &Table) -> mlua::Result<()> {
let health_fn = lua.create_function(|lua, ()| {
let t = lua.create_table()?;
// TTS
t.set("tts_backend", crate::tts::backend().name())?;
// LLM
t.set("llm_backend", crate::llm::current_backend_name())?;
if let Some(c) = crate::llm::current() {
t.set("llm_model", c.model().to_string())?;
}
// Profile
t.set("active_profile", crate::profiles::active_name())?;
// Memory size
t.set("memory_facts", crate::long_term_memory::all().len())?;
// Scheduler size
t.set("scheduled_tasks", crate::scheduler::list().len())?;
// Language
t.set("language", crate::i18n::get_language())?;
// Voice from settings
if let Some(db) = crate::DB.get() {
let s = db.read();
t.set("voice", s.voice.clone())?;
t.set("microphone", s.microphone)?;
t.set("vosk_model", s.vosk_model.clone())?;
t.set("noise_suppression", format!("{:?}", s.noise_suppression))?;
}
Ok(t)
})?;
jarvis.set("health", health_fn)?;
Ok(())
}

View file

@ -98,5 +98,25 @@ pub fn register(lua: &Lua, jarvis: &Table) -> mlua::Result<()> {
})?;
jarvis.set("llm_switch", switch_fn)?;
// Clear conversation turns (keeps system prompt). Returns true if history was
// initialised, false if LLM is not configured.
let reset_fn = lua.create_function(|_, ()| {
llm::history_clear();
Ok(true)
})?;
jarvis.set("llm_reset", reset_fn)?;
// Return the most recent assistant message text, or nil.
let last_fn = lua.create_function(|lua, ()| {
match llm::history_last_assistant() {
Some(text) => {
let s = lua.create_string(text)?;
Ok(Value::String(s))
}
None => Ok(Value::Nil),
}
})?;
jarvis.set("llm_last_reply", last_fn)?;
Ok(())
}

View file

@ -82,6 +82,7 @@ impl LuaEngine {
api::profile::register(&self.lua, &jarvis)?;
api::scheduler::register(&self.lua, &jarvis)?;
api::cmd::register(&self.lua, &jarvis)?;
api::health::register(&self.lua, &jarvis)?;
// sandbox-controlled APIs
if self.sandbox.allows_http() {