feat: 5 new packs + dotenvy + regen icon.ico + shortcuts redo
Iron Man icon regenerated from resources/icons/icon.png at sizes 16/24/32/48/64/128/256 via Pillow (make_ico.py committed alongside); icon.ico is now a clean 83 KB multi-res for sharp display at any scale instead of whatever placeholder Tauri originally emitted. dotenvy added to workspace deps. jarvis-app/main.rs and jarvis-gui/ main.rs both walk up from current_exe() looking for dev.env (up to 5 parent levels), so the Desktop shortcut can launch the exe directly without a wrapper .bat to pre-set GROQ_TOKEN. Falls back to dotenvy::dotenv() (cwd lookup) if nothing found near the binary. 5 new portable Lua command packs. Bumps total from 21 to 26. cargo test -p jarvis-core --lib commands::tests still passes 3/3. reminders/ — set_reminder. Parses "напомни через N (секунд|минут| часов) <body>" or English equivalents; understands one Russian word numerals (один, две, пять, десять, пятнадцать, ...) for the count when speech recognition returns words instead of digits. Defaults to 5 minutes if number/unit missing. Spawns a detached PowerShell that Start-Sleeps then fires: BurntToast if installed, System.Speech SAPI (ru-RU voice preference), and a WScript.Shell.Popup as the guaranteed-visible last resort. date_query/ — today / tomorrow / yesterday. Single answer.lua keyed by command_id, computes the offset via PowerShell Get-Date.AddDays() in ru-RU culture so the weekday/month come out in Russian, then speaks via SAPI. voice_type/ — type_text. Strips the trigger, drops the remainder to clipboard via jarvis.system.clipboard.set, then synthesises Ctrl+V through user32!keybd_event so it lands in whatever window currently has focus. Works in any text field (note app, browser, IDE, Telegram, etc.). dice/ — coin_flip / roll_dice / random_number. Single roll.lua, seeds math.random with os.time + jitter, speaks the result. "1-6" for dice, "1-100" for random. stopwatch/ — start / check / stop. Uses jarvis.state.get/set (persisted via jarvis-core's settings DB) to remember the start timestamp, computes elapsed via jarvis.context.time.timestamp, formats as "X сек" / "X мин Y сек" / "X ч Y мин Z сек". All new packs follow the established patterns (sandbox=full, PS-via- exec helper where needed, USERPROFILE/SystemDrive everywhere, no hardcoded paths).
This commit is contained in:
parent
ae279aeef7
commit
dcee98bddf
18 changed files with 516 additions and 1 deletions
119
resources/commands/reminders/set.lua
Normal file
119
resources/commands/reminders/set.lua
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
local lang = jarvis.context.language
|
||||
local phrase = (jarvis.context.phrase or ""):lower()
|
||||
|
||||
local triggers = {
|
||||
"поставь напоминание через", "установи таймер на",
|
||||
"напомни мне через", "напомни через", "напомни",
|
||||
"remind me in", "set reminder in", "set timer for", "timer for",
|
||||
"постав нагадування через", "нагадай через", "таймер на",
|
||||
}
|
||||
|
||||
local rest = phrase
|
||||
for _, t in ipairs(triggers) do
|
||||
local s, e = string.find(rest, t, 1, true)
|
||||
if s == 1 then
|
||||
rest = rest:sub(e + 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
rest = rest:gsub("^%s+", "")
|
||||
|
||||
local unit_words = {
|
||||
["секунд"] = 1, ["секунду"] = 1, ["секунды"] = 1,
|
||||
["second"] = 1, ["seconds"] = 1, ["sec"] = 1,
|
||||
["минут"] = 60, ["минуту"] = 60, ["минуты"] = 60, ["мин"] = 60,
|
||||
["minute"] = 60, ["minutes"] = 60, ["min"] = 60,
|
||||
["час"] = 3600, ["часа"] = 3600, ["часов"] = 3600, ["часик"] = 3600,
|
||||
["hour"] = 3600, ["hours"] = 3600, ["hr"] = 3600,
|
||||
}
|
||||
local rus_numerals = {
|
||||
["один"] = 1, ["одну"] = 1, ["одна"] = 1, ["полторы"] = 1, ["полтора"] = 1,
|
||||
["два"] = 2, ["две"] = 2, ["три"] = 3, ["четыре"] = 4, ["пять"] = 5,
|
||||
["шесть"] = 6, ["семь"] = 7, ["восемь"] = 8, ["девять"] = 9, ["десять"] = 10,
|
||||
["пятнадцать"] = 15, ["двадцать"] = 20, ["тридцать"] = 30, ["сорок"] = 40,
|
||||
["пятьдесят"] = 50, ["час"] = 60, -- "через час" without number
|
||||
}
|
||||
|
||||
local number, unit, body = nil, nil, nil
|
||||
|
||||
local num_str, rest_after_num = rest:match("^(%d+)%s+(.+)")
|
||||
if num_str then
|
||||
number = tonumber(num_str)
|
||||
rest = rest_after_num
|
||||
else
|
||||
local first_word, after = rest:match("^(%S+)%s*(.*)")
|
||||
if first_word and rus_numerals[first_word] then
|
||||
number = rus_numerals[first_word]
|
||||
rest = after or ""
|
||||
end
|
||||
end
|
||||
|
||||
if rest and #rest > 0 then
|
||||
local first_word, after = rest:match("^(%S+)%s*(.*)")
|
||||
if first_word then
|
||||
for w, secs in pairs(unit_words) do
|
||||
if first_word:sub(1, #w) == w then
|
||||
unit = secs
|
||||
body = after or ""
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not number then
|
||||
number = 5
|
||||
if not unit then unit = 60 end
|
||||
body = rest
|
||||
elseif not unit then
|
||||
unit = 60
|
||||
body = rest
|
||||
end
|
||||
|
||||
local total_seconds = number * unit
|
||||
if total_seconds < 1 then total_seconds = 1 end
|
||||
if total_seconds > 86400 then total_seconds = 86400 end
|
||||
|
||||
body = (body or ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||||
if body == "" then
|
||||
body = lang == "ru" and "Напоминание!" or "Reminder!"
|
||||
end
|
||||
|
||||
local function human_time(sec)
|
||||
if sec < 60 then return sec .. " сек" end
|
||||
if sec < 3600 then return math.floor(sec/60) .. " мин" end
|
||||
return string.format("%d ч %d мин", math.floor(sec/3600), math.floor((sec%3600)/60))
|
||||
end
|
||||
|
||||
local title_esc = (lang == "ru" and "Напоминание J.A.R.V.I.S." or "J.A.R.V.I.S. reminder"):gsub("'", "''")
|
||||
local body_esc = body:gsub("'", "''")
|
||||
local sapi_text = ((lang == "ru" and "Сэр, напоминаю: " or "Reminder: ") .. body):gsub("'", "''"):gsub("\r?\n", " ")
|
||||
|
||||
local ps = string.format(
|
||||
[[Start-Sleep -Seconds %d; ]]
|
||||
.. [[$null = New-Object -ComObject Shell.Application; ]]
|
||||
.. [[try { ]]
|
||||
.. [[ Import-Module BurntToast -ErrorAction SilentlyContinue; ]]
|
||||
.. [[ New-BurntToastNotification -Text '%s', '%s' -ErrorAction SilentlyContinue ]]
|
||||
.. [[} catch {}; ]]
|
||||
.. [[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'); ]]
|
||||
.. [[$wsh = New-Object -ComObject WScript.Shell; $wsh.Popup('%s', 30, '%s', 64) | Out-Null]],
|
||||
total_seconds, title_esc, body_esc, sapi_text, body_esc, title_esc
|
||||
)
|
||||
local cmd = string.format(
|
||||
'powershell -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command "%s"',
|
||||
ps:gsub('"', '\\"')
|
||||
)
|
||||
|
||||
jarvis.system.exec("start /b " .. cmd)
|
||||
|
||||
jarvis.system.notify(
|
||||
lang == "ru" and "Таймер установлен" or "Timer set",
|
||||
string.format(lang == "ru" and "Через %s: %s" or "In %s: %s", human_time(total_seconds), body)
|
||||
)
|
||||
jarvis.log("info", string.format("reminder in %ds: %s", total_seconds, body))
|
||||
jarvis.audio.play_ok()
|
||||
return { chain = false }
|
||||
Loading…
Add table
Add a link
Reference in a new issue