J.A.R.V.I.S-rust/resources/commands/translate/translate.lua
Bossiara13 100db82bd6 feat(commands): tier-1 — translate, math, theme toggle, project opener, sound panel
Five more portable Lua command packs. Bumps the total from 16 to 21
packs. cargo test -p jarvis-core --lib commands::tests still 3/3.

translate/ — translate.lua. Picks the target language from triggers
like "переведи на английский" / "translate to russian"; falls back to
English when only "переведи X" is heard. Sends to Groq with a strict
"translation only" system prompt at temp 0.3. Drops the result into
the clipboard, fires a notify, and speaks via PowerShell SAPI in the
matching ISO voice (ru-RU / uk-UA / en-US / de-DE etc.) so a German
translation actually sounds German.

math/ — math.lua. Strips the trigger ("посчитай" / "calculate" /
"сколько будет" / "what is" / ...), sends the remainder to Groq with
temp=0.0 and max_tokens=64, asking ONLY for the numeric result. If
the model replies "нет" (the system-prompt sentinel for non-math),
plays not_found instead of speaking nonsense. Otherwise speaks
"Получилось X" via SAPI.

theme/ — set.lua dispatched by command_id (theme_dark / theme_light).
Flips AppsUseLightTheme + SystemUsesLightTheme under
HKCU\...\Themes\Personalize via Set-ItemProperty. No reboot needed,
Windows shell re-themes immediately.

projects/ — open.lua + list.lua. Reads/creates
%USERPROFILE%\Documents\jarvis-projects.json (a flat
[{"name": ..., "path": ...}] list). open.lua strips the trigger,
exact-matches then substring-matches the project name, opens the path
in VS Code if `code.cmd` is on PATH, otherwise in Explorer. First
call creates a sample config with jarvis-rust / jarvis-python /
dietpi entries pointing under USERPROFILE so it works on any machine.
list.lua just notifies the configured project names.

sound_panel/ — open.lua. Two-liner: rundll32 mmsys.cpl,,1 (Recording
tab). Useful both as a standalone "открой настройки звука" and as
the place to fix the mic-disabled state that prevents jarvis-app
from starting.

Portability: USERPROFILE / SystemDrive / PATH lookup throughout.
2026-05-15 12:06:30 +03:00

124 lines
4.6 KiB
Lua
Raw 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.

local lang = jarvis.context.language
local phrase = (jarvis.context.phrase or ""):lower()
local target_languages = {
["английский"] = "English", ["english"] = "English",
["русский"] = "Russian", ["russian"] = "Russian",
["украинский"] = "Ukrainian", ["ukrainian"] = "Ukrainian", ["українську"] = "Ukrainian",
["немецкий"] = "German", ["german"] = "German", ["німецьку"] = "German",
["французский"]= "French", ["french"] = "French",
["испанский"] = "Spanish", ["spanish"] = "Spanish",
["итальянский"]= "Italian",
["китайский"] = "Chinese", ["chinese"] = "Chinese",
["японский"] = "Japanese", ["japanese"] = "Japanese",
["польский"] = "Polish",
["турецкий"] = "Turkish",
}
local triggers_with_lang = {
"переведи на ", "translate to ", "переклади на ", "how to say in ",
}
local generic_triggers = {
"переведи", "translate", "переклади", "как по ", "how to say",
}
local target = nil
local body = phrase
for _, t in ipairs(triggers_with_lang) do
local s, e = string.find(body, t, 1, true)
if s == 1 then
local rest = body:sub(e + 1)
local first_word = rest:match("^(%S+)")
if first_word and target_languages[first_word] then
target = target_languages[first_word]
body = rest:sub(#first_word + 1)
break
end
end
end
if not target then
for _, t in ipairs(generic_triggers) do
local s, e = string.find(body, t, 1, true)
if s == 1 then
body = body:sub(e + 1)
break
end
end
end
body = body:gsub("^%s+", ""):gsub("%s+$", "")
if body == "" then
jarvis.system.notify(
"Translate",
lang == "ru" and "Что переводить?" or "What to translate?"
)
jarvis.audio.play_error()
return { chain = false }
end
local token = jarvis.system.env("GROQ_TOKEN")
if not token or token == "" then
jarvis.system.notify("Translate", "GROQ_TOKEN не задан")
jarvis.audio.play_error()
return { chain = false }
end
local base = jarvis.system.env("GROQ_BASE_URL"); if not base or base == "" then base = "https://api.groq.com/openai/v1" end
local model = jarvis.system.env("GROQ_MODEL"); if not model or model == "" then model = "llama-3.3-70b-versatile" end
local target_label = target or (lang == "ru" and "English" or "Russian")
local sys = string.format(
"You are a professional translator. Translate the user message into %s. "
.. "Output ONLY the translation, no explanations, no quotes, no prefix. "
.. "Preserve idioms and tone.",
target_label
)
jarvis.log("info", "translate -> " .. target_label .. " : " .. body)
local payload = {
model = model,
messages = {
{ role = "system", content = sys },
{ role = "user", content = body },
},
max_tokens = 512,
temperature = 0.3,
}
local res = jarvis.http.post_json(base .. "/chat/completions", payload, { Authorization = "Bearer " .. token })
if not res.ok then
jarvis.log("error", "translate api failed: " .. tostring(res.status) .. " " .. tostring(res.body):sub(1, 200))
jarvis.system.notify("Translate", "Ошибка API")
jarvis.audio.play_error()
return { chain = false }
end
local body_resp = res.body or ""
local content = body_resp:match('"content"%s*:%s*"(.-[^\\])"')
if not content then
jarvis.system.notify("Translate", "Не смог распарсить ответ")
jarvis.audio.play_error()
return { chain = false }
end
content = content:gsub('\\n', '\n'):gsub('\\"', '"'):gsub('\\\\', '\\'):gsub('\\t', '\t')
content = content:gsub("^%s+", ""):gsub("%s+$", "")
jarvis.system.clipboard.set(content)
jarvis.system.notify(target_label, content:sub(1, 200))
jarvis.log("info", "translation: " .. content)
local sapi_text = content:gsub("'", "''"):gsub("\r?\n", " ")
local ps = string.format(
[[Add-Type -AssemblyName System.Speech; $s = New-Object System.Speech.Synthesis.SpeechSynthesizer; $iso='%s'; foreach ($v in $s.GetInstalledVoices()) { if ($v.VoiceInfo.Culture.TwoLetterISOLanguageName -eq $iso) { try { $s.SelectVoice($v.VoiceInfo.Name); break } catch {} } } $s.Speak('%s')]],
(target_label == "Russian" and "ru") or (target_label == "Ukrainian" and "uk") or "en",
sapi_text
)
jarvis.system.exec(string.format('powershell -NoProfile -ExecutionPolicy Bypass -Command "%s"', ps:gsub('"', '\\"')))
jarvis.audio.play_ok()
return { chain = false }