J.A.R.V.I.S-rust/resources/commands/quick_search/search.lua
Bossiara13 385bd5c8ce 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.
2026-05-15 16:25:28 +03:00

83 lines
3.2 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- "Найди в гугле что такое квантовая запутанность"
-- Uses DuckDuckGo's free Instant Answer API (api.duckduckgo.com). No key.
-- The IA response often has a "Abstract" / "AbstractText" / "AbstractURL" set
-- for well-known topics. We feed Abstract + RelatedTopics to the LLM for a
-- spoken summary. Falls back to pure LLM knowledge if DDG returns nothing useful.
local phrase = (jarvis.context.phrase or "")
local query = jarvis.text.strip_trigger(phrase:lower(), {
"найди и расскажи",
"найди в гугле",
"найди в интернете",
"поищи в гугле",
"посмотри в гугле",
"загугли",
"поищи информацию о",
"поищи информацию",
"найди информацию о",
"google for",
"search for",
"find online",
"look up",
"знайди в гуглі",
"погугли",
})
query = query:gsub("^[%s,:%.]+", ""):gsub("%s+$", "")
if query == "" then
return jarvis.cmd.error("Что искать?")
end
local function urlencode(s)
return (s:gsub("[^A-Za-z0-9%-_%.~]", function(c)
return string.format("%%%02X", string.byte(c))
end))
end
local url = "https://api.duckduckgo.com/?q=" .. urlencode(query)
.. "&format=json&no_html=1&skip_disambig=1"
local data = jarvis.http.json(url)
local context = ""
if data then
if data.AbstractText and data.AbstractText ~= "" then
context = data.AbstractText
if data.AbstractURL and data.AbstractURL ~= "" then
context = context .. "\nИсточник: " .. data.AbstractURL
end
elseif data.RelatedTopics and #data.RelatedTopics > 0 then
local lines = {}
for i = 1, math.min(4, #data.RelatedTopics) do
local t = data.RelatedTopics[i]
if t.Text then table.insert(lines, t.Text) end
end
context = table.concat(lines, "\n")
end
end
local user_prompt
if context ~= "" then
user_prompt = string.format(
"Вопрос пользователя: %s\n\nДанные из веб-поиска:\n%s\n\nОтветь на вопрос по-русски, кратко (2-4 предложения), используя данные. Если данных мало — добавь из своих знаний.",
query, context
)
else
user_prompt = string.format(
"Вопрос пользователя: %s\n\nВеб-поиск ничего полезного не дал. Ответь по-русски кратко (2-4 предложения), используя свои знания. Если не уверен — скажи об этом.",
query
)
end
local answer = jarvis.llm({
{ role = "system", content = "Ты — справочный помощник. Отвечай кратко и по делу, без вводных фраз." },
{ role = "user", content = user_prompt }
}, { max_tokens = 350, temperature = 0.3 })
if not answer or answer == "" then
return jarvis.cmd.error("Не получилось получить ответ.")
end
jarvis.system.notify("Поиск: " .. query, answer:sub(1, 200))
return jarvis.cmd.ok(answer)