J.A.R.V.I.S-rust/resources/commands/scheduler/add_reminder.lua
Bossiara13 12b1ed4ccb feat(scheduler): IMBA-5 proactive scheduler — reminders, daily briefings, intervals
Background thread that wakes J.A.R.V.I.S. on a schedule to speak reminders.
Nothing else competes here on desktop — Алиса/Сири are reactive-only.

Core (crates/jarvis-core/src/scheduler.rs)
  - Schedule::{Daily{h,m}, Interval{secs}, Once{at}} with Schedule::parse for
    "daily HH:MM" / "at HH:MM" / "every N minutes|hours" / "in N minutes|hours".
    Russian units (час/часа/часов/минут/секунд) also accepted.
  - ScheduledTask {id, name, schedule, action, last_fired, enabled, created_at}.
    Action::Speak{text} | Action::Lua{script_path}.
  - JSON persistence at <APP_CONFIG_DIR>/schedule.json, atomic write-through.
  - add/remove/clear/list/find; mark_fired auto-deletes Once tasks.
  - start_background() spawns a 30-second tick thread (idempotent). Each tick
    calls due_tasks(), fires Action via tts::speak_default (after voices::play_reply
    "ahem" cue) or Lua engine.
  - 7 unit tests (all passing).

Lua API (crates/jarvis-core/src/lua/api/scheduler.rs)
  - jarvis.scheduler.add({name, schedule, action={type, text|script_path}})
  - jarvis.scheduler.{list, count, remove(id), clear}.
  - Tasks come back as {id, name, schedule_human, action, enabled, last_fired}.

Wire-up (crates/jarvis-app/src/main.rs)
  - scheduler::init() + scheduler::start_background() after profiles::init.
  - Tasks survive restarts via schedule.json.

Voice commands (resources/commands/scheduler/, 6 ids)
  - scheduler.add_reminder    "напомни через 5 минут выключить кофеварку"
  - scheduler.add_at          "напомни в 18:00 забрать ребёнка"
  - scheduler.add_recurring   "каждые 2 часа напоминай попить воды"
  - scheduler.add_daily       "каждый день в 9:00 делай briefing"
  - scheduler.list            "что у меня запланировано"
  - scheduler.clear           "очисти расписание"

Russian-aware parsers (час/часа/часов, минут/минуту/минуты) live inside the Lua
packs — easy to extend without touching Rust.

Tests: 31/31 jarvis-core unit tests pass (24 prior + 7 scheduler).
Build: cargo build --release -p jarvis-app and -p jarvis-gui both green.
2026-05-15 15:43:04 +03:00

102 lines
3.7 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.

-- "напомни мне через 5 минут выключить кофеварку"
local phrase = (jarvis.context.phrase or ""):lower()
-- Strip the trigger
local body = jarvis.text.strip_trigger(phrase, {
"напомни мне через",
"напомни через",
"поставь напоминалку через",
"поставь напоминание через",
"разбуди через",
"remind me in",
"set a reminder in",
"нагадай через",
"постав нагадування через",
})
body = body:gsub("^[%s,:%.]+", ""):gsub("%s+$", "")
if body == "" then
jarvis.speak("Через сколько и про что напомнить?")
jarvis.audio.play_error()
return { chain = false }
end
-- Parse "<N> <unit> <text>"
-- N can be digit or word (один, два, ...) — for v1 only digits
local n_str, unit, rest = body:match("^(%d+)%s+(%S+)%s*(.*)$")
if not n_str then
-- maybe "час" / "минуту" (singular)
local single_unit, rest2 = body:match("^([%a]+)%s*(.*)$")
if single_unit then
local map = {
["час"] = "1 hours",
["минуту"] = "1 minutes",
["секунду"] = "1 seconds",
}
if map[single_unit] then
local schedule = "in " .. map[single_unit]
local text_body = rest2:gsub("^[%s,:%.]+", ""):gsub("%s+$", "")
if text_body == "" then text_body = "Напоминание." end
local ok, err = pcall(function()
jarvis.scheduler.add({
name = "Reminder",
schedule = schedule,
action = { type = "speak", text = text_body }
})
end)
if not ok then
jarvis.log("warn", "scheduler.add: " .. tostring(err))
jarvis.speak("Не получилось.")
jarvis.audio.play_error()
return { chain = false }
end
jarvis.speak("Напомню через " .. single_unit .. ".")
jarvis.audio.play_ok()
return { chain = false }
end
end
jarvis.speak("Не понял через сколько. Скажите например: через 5 минут.")
jarvis.audio.play_error()
return { chain = false }
end
local n = tonumber(n_str)
local unit_norm
local sample
if unit:match("^минут") or unit == "минуты" or unit == "минуту" or unit:match("^minute") then
unit_norm = "minutes"
sample = n .. " минут"
elseif unit:match("^час") or unit:match("^hour") then
unit_norm = "hours"
sample = n .. " час" .. (n == 1 and "" or (n < 5 and "а" or "ов"))
elseif unit:match("^секунд") or unit:match("^second") then
unit_norm = "seconds"
sample = n .. " секунд"
else
jarvis.speak("Не понял единицу. Минуты или часы.")
jarvis.audio.play_error()
return { chain = false }
end
local schedule = string.format("in %d %s", n, unit_norm)
local text_body = rest:gsub("^[%s,:%.]+", ""):gsub("%s+$", "")
if text_body == "" then text_body = "Напоминание." end
local ok, err = pcall(function()
jarvis.scheduler.add({
name = "Reminder",
schedule = schedule,
action = { type = "speak", text = text_body }
})
end)
if not ok then
jarvis.log("warn", "scheduler.add: " .. tostring(err))
jarvis.speak("Не получилось добавить напоминание.")
jarvis.audio.play_error()
return { chain = false }
end
jarvis.speak("Напомню через " .. sample .. ".")
jarvis.audio.play_ok()
return { chain = false }