diff --git a/resources/commands/math/command.toml b/resources/commands/math/command.toml new file mode 100644 index 0000000..131fcc1 --- /dev/null +++ b/resources/commands/math/command.toml @@ -0,0 +1,27 @@ +[[commands]] +id = "math_eval" +type = "lua" +script = "math.lua" +sandbox = "full" +timeout = 15000 + +[commands.phrases] +ru = [ + "посчитай", + "сколько будет", + "вычисли", + "реши", + "сколько это", +] +en = [ + "calculate", + "how much is", + "compute", + "solve", + "what is", +] +ua = [ + "порахуй", + "скільки буде", + "обчисли", +] diff --git a/resources/commands/math/math.lua b/resources/commands/math/math.lua new file mode 100644 index 0000000..cd45d54 --- /dev/null +++ b/resources/commands/math/math.lua @@ -0,0 +1,87 @@ +local lang = jarvis.context.language +local phrase = (jarvis.context.phrase or ""):lower() + +local triggers = { + "сколько будет", "сколько это", "посчитай", "вычисли", "реши", + "how much is", "calculate", "compute", "solve", "what is", + "скільки буде", "порахуй", "обчисли", +} + +local query = phrase +for _, t in ipairs(triggers) do + local s, e = string.find(query, t, 1, true) + if s == 1 then + query = query:sub(e + 1) + break + end +end +query = query:gsub("^%s+", ""):gsub("%s+$", "") + +if query == "" then + jarvis.system.notify("Math", lang == "ru" and "Что посчитать?" or "What to compute?") + jarvis.audio.play_error() + return { chain = false } +end + +local token = jarvis.system.env("GROQ_TOKEN") +if not token or token == "" then + jarvis.system.notify("Math", "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 sys = "Ты калькулятор. Решай арифметику, простые уравнения, конверсии единиц, проценты. " + .. "Отвечай ТОЛЬКО результатом числом или короткой строкой (для уравнений — значения корней). " + .. "Если запрос — не математика, ответь одним словом «нет»." + +local payload = { + model = model, + messages = { + { role = "system", content = sys }, + { role = "user", content = query }, + }, + max_tokens = 64, + temperature = 0.0, +} + +jarvis.log("info", "math query: " .. query) +local res = jarvis.http.post_json(base .. "/chat/completions", payload, { Authorization = "Bearer " .. token }) + +if not res.ok then + jarvis.log("error", "math api failed: " .. tostring(res.status)) + jarvis.system.notify("Math", "Ошибка API") + jarvis.audio.play_error() + return { chain = false } +end + +local body = res.body or "" +local content = body:match('"content"%s*:%s*"(.-[^\\])"') +if not content then + jarvis.system.notify("Math", "Не смог распарсить ответ") + jarvis.audio.play_error() + return { chain = false } +end +content = content:gsub('\\n', '\n'):gsub('\\"', '"'):gsub('\\\\', '\\'):gsub('\\t', '\t') +content = content:gsub("^%s+", ""):gsub("%s+$", "") + +if content:lower() == "нет" or content == "" then + jarvis.system.notify("Math", lang == "ru" and "Это не математика" or "Not math") + jarvis.audio.play_not_found() + return { chain = false } +end + +jarvis.system.notify(query, content) +jarvis.log("info", "math result: " .. content) + +local sapi_text = ((lang == "ru" and "Получилось " or "Result: ") .. content):gsub("'", "''"):gsub("\r?\n", " ") +local ps = string.format( + [[Add-Type -AssemblyName System.Speech; $s = New-Object System.Speech.Synthesis.SpeechSynthesizer; foreach ($v in $s.GetInstalledVoices()) { if ($v.VoiceInfo.Culture.TwoLetterISOLanguageName -eq 'ru') { try { $s.SelectVoice($v.VoiceInfo.Name); break } catch {} } } $s.Speak('%s')]], + sapi_text +) +jarvis.system.exec(string.format('powershell -NoProfile -ExecutionPolicy Bypass -Command "%s"', ps:gsub('"', '\\"'))) + +jarvis.audio.play_ok() +return { chain = false } diff --git a/resources/commands/projects/command.toml b/resources/commands/projects/command.toml new file mode 100644 index 0000000..95d8151 --- /dev/null +++ b/resources/commands/projects/command.toml @@ -0,0 +1,36 @@ +[[commands]] +id = "open_project" +type = "lua" +script = "open.lua" +sandbox = "full" +timeout = 5000 + +[commands.phrases] +ru = [ + "открой проект", + "запусти проект", + "проект", + "открой папку проекта", +] +en = [ + "open project", + "launch project", + "project", +] +ua = [ + "відкрий проект", + "запусти проект", +] + + +[[commands]] +id = "list_projects" +type = "lua" +script = "list.lua" +sandbox = "full" +timeout = 5000 + +[commands.phrases] +ru = ["какие у меня проекты", "список проектов", "покажи проекты"] +en = ["list projects", "show projects"] +ua = ["які в мене проекти", "перелік проектів"] diff --git a/resources/commands/projects/list.lua b/resources/commands/projects/list.lua new file mode 100644 index 0000000..fd06507 --- /dev/null +++ b/resources/commands/projects/list.lua @@ -0,0 +1,34 @@ +local lang = jarvis.context.language +local userprofile = jarvis.system.env("USERPROFILE") or "C:\\Users\\Public" +local config_path = userprofile .. "\\Documents\\jarvis-projects.json" + +if not jarvis.fs.exists(config_path) then + jarvis.system.notify( + lang == "ru" and "Проекты" or "Projects", + lang == "ru" and "Конфиг ещё не создан — скажи «открой проект» один раз" + or "Config not created yet — say 'open project' once" + ) + jarvis.audio.play_not_found() + return { chain = false } +end + +local content = jarvis.fs.read(config_path) +local names = {} +for name in content:gmatch('"name"%s*:%s*"([^"]+)"') do + table.insert(names, name) +end + +if #names == 0 then + jarvis.system.notify("Projects", lang == "ru" and "Список пуст" or "Empty") + jarvis.audio.play_not_found() + return { chain = false } +end + +local body = table.concat(names, ", ") +jarvis.system.notify( + (lang == "ru" and "Проектов: " or "Projects: ") .. #names, + body +) +jarvis.log("info", "projects: " .. body) +jarvis.audio.play_ok() +return { chain = false } diff --git a/resources/commands/projects/open.lua b/resources/commands/projects/open.lua new file mode 100644 index 0000000..e269ec3 --- /dev/null +++ b/resources/commands/projects/open.lua @@ -0,0 +1,97 @@ +local lang = jarvis.context.language +local userprofile = jarvis.system.env("USERPROFILE") or "C:\\Users\\Public" +local config_path = userprofile .. "\\Documents\\jarvis-projects.json" + +if not jarvis.fs.exists(config_path) then + local sample = string.format([[[ + {"name": "jarvis-rust", "path": "%s\\Jarvis\\rust"}, + {"name": "jarvis-python", "path": "%s\\Jarvis\\python"}, + {"name": "dietpi", "path": "%s\\Desktop\\Dietpi"} +] +]], userprofile, userprofile, userprofile) + jarvis.fs.write(config_path, sample) + jarvis.system.notify( + lang == "ru" and "Проекты" or "Projects", + lang == "ru" and "Создан шаблон в Documents/jarvis-projects.json — отредактируй и повтори" + or "Template created at Documents/jarvis-projects.json — edit and retry" + ) + jarvis.system.open(config_path) + jarvis.audio.play_not_found() + return { chain = false } +end + +local content = jarvis.fs.read(config_path) + +local entries = {} +for name, path in content:gmatch('"name"%s*:%s*"([^"]+)"%s*,%s*"path"%s*:%s*"([^"]+)"') do + table.insert(entries, { name = name:lower(), original_name = name, path = path:gsub('\\\\', '\\') }) +end + +if #entries == 0 then + jarvis.system.notify("Projects", lang == "ru" and "Проектов в конфиге не нашёл" or "No projects in config") + jarvis.audio.play_not_found() + return { chain = false } +end + +local phrase = (jarvis.context.phrase or ""):lower() +local triggers = { + "открой папку проекта", "запусти проект", "открой проект", "проект", + "open project", "launch project", "project", + "відкрий проект", "запусти проект", +} +local query = phrase +for _, t in ipairs(triggers) do + local s, e = string.find(query, t, 1, true) + if s == 1 then + query = query:sub(e + 1) + break + end +end +query = query:gsub("^%s+", ""):gsub("%s+$", "") + +if query == "" then + jarvis.system.notify("Projects", lang == "ru" and "Какой проект?" or "Which project?") + jarvis.audio.play_error() + return { chain = false } +end + +local match = nil +for _, e in ipairs(entries) do + if e.name == query then match = e; break end +end +if not match then + for _, e in ipairs(entries) do + if string.find(e.name, query, 1, true) or string.find(query, e.name, 1, true) then + match = e; break + end + end +end + +if not match then + jarvis.system.notify("Projects", + (lang == "ru" and "Не нашёл: " or "Not found: ") .. query) + jarvis.audio.play_not_found() + return { chain = false } +end + +if not jarvis.fs.exists(match.path) then + jarvis.system.notify("Projects", + (lang == "ru" and "Путь не существует: " or "Path missing: ") .. match.path) + jarvis.audio.play_error() + return { chain = false } +end + +local code_check = jarvis.system.exec("where code.cmd") +if code_check.success and (code_check.stdout or ""):gsub("[\r\n]+$", "") ~= "" then + jarvis.system.exec(string.format('code "%s"', match.path)) +else + jarvis.system.open(match.path) +end + +jarvis.system.notify( + lang == "ru" and "Проект" or "Project", + match.original_name .. "\n" .. match.path +) +jarvis.log("info", "opened project: " .. match.original_name .. " at " .. match.path) +jarvis.audio.play_ok() +return { chain = false } diff --git a/resources/commands/sound_panel/command.toml b/resources/commands/sound_panel/command.toml new file mode 100644 index 0000000..de6e883 --- /dev/null +++ b/resources/commands/sound_panel/command.toml @@ -0,0 +1,23 @@ +[[commands]] +id = "open_sound_panel" +type = "lua" +script = "open.lua" +sandbox = "full" +timeout = 3000 + +[commands.phrases] +ru = [ + "открой настройки звука", + "панель звука", + "настройки микрофона", + "управление звуком", +] +en = [ + "open sound settings", + "sound panel", + "microphone settings", +] +ua = [ + "відкрий налаштування звуку", + "панель звуку", +] diff --git a/resources/commands/sound_panel/open.lua b/resources/commands/sound_panel/open.lua new file mode 100644 index 0000000..d13187b --- /dev/null +++ b/resources/commands/sound_panel/open.lua @@ -0,0 +1,3 @@ +jarvis.system.exec('rundll32.exe shell32.dll,Control_RunDLL mmsys.cpl,,1') +jarvis.audio.play_ok() +return { chain = false } diff --git a/resources/commands/theme/command.toml b/resources/commands/theme/command.toml new file mode 100644 index 0000000..c2ede93 --- /dev/null +++ b/resources/commands/theme/command.toml @@ -0,0 +1,24 @@ +[[commands]] +id = "theme_dark" +type = "lua" +script = "set.lua" +sandbox = "full" +timeout = 3000 + +[commands.phrases] +ru = ["тёмная тема", "темная тема", "включи тёмную тему", "тёмный режим", "темный режим"] +en = ["dark theme", "dark mode", "enable dark mode"] +ua = ["темна тема", "темний режим"] + + +[[commands]] +id = "theme_light" +type = "lua" +script = "set.lua" +sandbox = "full" +timeout = 3000 + +[commands.phrases] +ru = ["светлая тема", "включи светлую тему", "светлый режим"] +en = ["light theme", "light mode", "enable light mode"] +ua = ["світла тема", "світлий режим"] diff --git a/resources/commands/theme/set.lua b/resources/commands/theme/set.lua new file mode 100644 index 0000000..8c8e850 --- /dev/null +++ b/resources/commands/theme/set.lua @@ -0,0 +1,26 @@ +local cmd_id = jarvis.context.command_id +local lang = jarvis.context.language +local is_light = (cmd_id == "theme_light") +local value = is_light and 1 or 0 + +local key = [[HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize]] +local ps = string.format( + "Set-ItemProperty -Path 'Registry::%s' -Name AppsUseLightTheme -Value %d -Type DWord; " + .. "Set-ItemProperty -Path 'Registry::%s' -Name SystemUsesLightTheme -Value %d -Type DWord", + key, value, key, value +) +local res = jarvis.system.exec(string.format('powershell -NoProfile -Command "%s"', ps:gsub('"', '\\"'))) + +if res.success then + jarvis.system.notify( + lang == "ru" and "Тема" or "Theme", + is_light and (lang == "ru" and "Светлая включена" or "Light enabled") + or (lang == "ru" and "Тёмная включена" or "Dark enabled") + ) + jarvis.audio.play_ok() +else + jarvis.log("error", "theme set failed: " .. tostring(res.stderr)) + jarvis.audio.play_error() +end + +return { chain = false } diff --git a/resources/commands/translate/command.toml b/resources/commands/translate/command.toml new file mode 100644 index 0000000..e4fdda0 --- /dev/null +++ b/resources/commands/translate/command.toml @@ -0,0 +1,34 @@ +[[commands]] +id = "translate" +type = "lua" +script = "translate.lua" +sandbox = "full" +timeout = 20000 + +[commands.phrases] +ru = [ + "переведи на английский", + "переведи на русский", + "переведи на украинский", + "переведи на немецкий", + "переведи на французский", + "переведи на испанский", + "переведи на китайский", + "переведи на японский", + "переведи", + "как по английски", + "как по русски", + "как по немецки", +] +en = [ + "translate to english", + "translate to russian", + "translate to ukrainian", + "translate", + "how to say in", +] +ua = [ + "переклади на англійську", + "переклади на російську", + "переклади", +] diff --git a/resources/commands/translate/translate.lua b/resources/commands/translate/translate.lua new file mode 100644 index 0000000..2e04925 --- /dev/null +++ b/resources/commands/translate/translate.lua @@ -0,0 +1,124 @@ +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 }