voice/seeds: track seed files, add russian time parser, guard replySystem
five fixes spotted during routing investigation:
- gitignore: replace blanket models/ ignore with per-dir exceptions
(/models/embedder/, /models/stt/, /models/tts/) so the seed text
files under models/seeds/ are tracked in version control
- query.txt: fix merged line — 'сколько стоит свет в этом месяце' and
'найди заметку про сервер' were fused with no separator
- reminder.txt: add 11 pure-verb reminder seeds without time expressions
to shift centroid toward the reminding intent rather than time-lexicon
- StubDateTimeParser: add Russian 'через <N> <unit>'/'через час'/'через
полчаса', 'сегодня'/'завтра'/'послезавтра' with optional clock, and
'в <clock>' scan. Add Russian word numbers (один-десять) and unit
inflections (час/часа/часов, минута/минуты/минут, день/дня/дней,
неделя/недели/недель). Also adds missing English day/week units.
- replySystem: guard time branch against duration queries ('сколько
времени прошло') reaching it via the classifier path after the
stage-0 grammar's build filter rejects them. Mirrors stage0.go
duration keywords.
This commit is contained in:
+5
-2
@@ -13,8 +13,11 @@ certs/
|
|||||||
# Dependencies (fetch/build, not vendored)
|
# Dependencies (fetch/build, not vendored)
|
||||||
deps/
|
deps/
|
||||||
|
|
||||||
# ML models (large, downloaded separately)
|
# ML models (large, downloaded separately) — specific dirs, not blanket,
|
||||||
models/
|
# because models/seeds/*.txt are small, tracked files the classifier needs.
|
||||||
|
/models/embedder/
|
||||||
|
/models/stt/
|
||||||
|
/models/tts/
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
*.db
|
*.db
|
||||||
|
|||||||
@@ -712,6 +712,13 @@ func (h *reactiveHandler) replySystem(ctx context.Context, dec router.Decision)
|
|||||||
u := strings.ToLower(dec.Utterance)
|
u := strings.ToLower(dec.Utterance)
|
||||||
now := h.now()
|
now := h.now()
|
||||||
|
|
||||||
|
// stage-0 grammars catch the exact time/date patterns, but duration
|
||||||
|
// queries ("сколько времени прошло") bypass the grammar's build filter
|
||||||
|
// and can reach replySystem via the classifier path. Guard against them.
|
||||||
|
if hasDurationWords(u) {
|
||||||
|
return "пока не умею отвечать на этот вопрос."
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(u, "час") || strings.Contains(u, "врем"):
|
case strings.Contains(u, "час") || strings.Contains(u, "врем"):
|
||||||
h := now.Hour()
|
h := now.Hour()
|
||||||
@@ -735,6 +742,29 @@ func (h *reactiveHandler) replySystem(ctx context.Context, dec router.Decision)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasDurationWords checks whether u is asking about elapsed/remaining time
|
||||||
|
// rather than the current clock — guards replySystem from replying "сейчас
|
||||||
|
// X часов" to "сколько времени прошло". Mirrors the stage0.go build filter.
|
||||||
|
func hasDurationWords(u string) bool {
|
||||||
|
s := strings.ToLower(strings.TrimSpace(u))
|
||||||
|
// First-word duration markers (same keywords as timeQueryBuild in stage0).
|
||||||
|
first := strings.Fields(s)
|
||||||
|
if len(first) > 0 {
|
||||||
|
switch first[0] {
|
||||||
|
case "прошло", "осталось", "пройдет", "минуло", "проходит":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Broader duration keywords appearing anywhere in the utterance.
|
||||||
|
if strings.Contains(s, "прошло") || strings.Contains(s, "осталось") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.Contains(s, " до ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// reply wraps a text reply through TTS to produce a PushToTalkResp. If TTS
|
// reply wraps a text reply through TTS to produce a PushToTalkResp. If TTS
|
||||||
// fails, the response carries an empty audio + the text — the client can
|
// fails, the response carries an empty audio + the text — the client can
|
||||||
// still display text if it can't play. The routedChannels field is
|
// still display text if it can't play. The routedChannels field is
|
||||||
|
|||||||
@@ -198,6 +198,72 @@ func (StubDateTimeParser) Parse(_ context.Context, text string, now time.Time) (
|
|||||||
return t, true, nil
|
return t, true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Russian time expressions (stub floor; dateparser replaces) ---
|
||||||
|
|
||||||
|
// "в <clock>" anywhere — mirror of the English "at" scan.
|
||||||
|
for i := 0; i+1 < len(toks); i++ {
|
||||||
|
if toks[i] != "в" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t, ok := parseClock(toks[i+1], now); ok {
|
||||||
|
return t, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "через <N> <unit>" / "через <unit>" (bare = 1) / "через полчаса".
|
||||||
|
for i := 0; i+1 < len(toks); i++ {
|
||||||
|
if toks[i] != "через" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// "через N unit" — three-token scan.
|
||||||
|
if i+2 < len(toks) {
|
||||||
|
n, unit, ok := splitNumUnit(toks[i+1] + " " + toks[i+2])
|
||||||
|
if ok {
|
||||||
|
if d, ok := unitToDuration(n, unit); ok {
|
||||||
|
return now.Add(d), true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// "через полчаса"
|
||||||
|
if toks[i+1] == "полчаса" {
|
||||||
|
return now.Add(30 * time.Minute), true, nil
|
||||||
|
}
|
||||||
|
// "через unit" (bare unit without number = 1, e.g. "через час")
|
||||||
|
if d, ok := unitToDuration(1, toks[i+1]); ok {
|
||||||
|
return now.Add(d), true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calendar day: "сегодня", "завтра", "послезавтра" [в] <clock>
|
||||||
|
for i := 0; i < len(toks); i++ {
|
||||||
|
var dayShift time.Duration
|
||||||
|
switch toks[i] {
|
||||||
|
case "сегодня":
|
||||||
|
dayShift = 0
|
||||||
|
case "завтра":
|
||||||
|
dayShift = 24 * time.Hour
|
||||||
|
case "послезавтра":
|
||||||
|
dayShift = 48 * time.Hour
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
base := now.Truncate(24 * time.Hour).Add(dayShift)
|
||||||
|
// Look for clock after the day word (optional "в").
|
||||||
|
nextIdx := i + 1
|
||||||
|
if nextIdx < len(toks) && toks[nextIdx] == "в" {
|
||||||
|
nextIdx++
|
||||||
|
}
|
||||||
|
if nextIdx < len(toks) {
|
||||||
|
if t, ok := parseClock(toks[nextIdx], now); ok {
|
||||||
|
t = time.Date(base.Year(), base.Month(), base.Day(), t.Hour(), t.Minute(), 0, 0, now.Location())
|
||||||
|
return t, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No clock — return midnight of that day.
|
||||||
|
return base, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// bare clock at start ("7:30").
|
// bare clock at start ("7:30").
|
||||||
if len(toks) > 0 {
|
if len(toks) > 0 {
|
||||||
if t, ok := parseClock(toks[0], now); ok {
|
if t, ok := parseClock(toks[0], now); ok {
|
||||||
@@ -286,6 +352,11 @@ var wordNumbers = map[string]int{
|
|||||||
"six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10,
|
"six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10,
|
||||||
"eleven": 11, "twelve": 12, "fifteen": 15, "twenty": 20,
|
"eleven": 11, "twelve": 12, "fifteen": 15, "twenty": 20,
|
||||||
"thirty": 30, "forty": 40, "fifty": 50, "sixty": 60,
|
"thirty": 30, "forty": 40, "fifty": 50, "sixty": 60,
|
||||||
|
// Russian word numbers (gender variants cover natural речи)
|
||||||
|
"один": 1, "одна": 1, "одно": 1,
|
||||||
|
"два": 2, "две": 2, "три": 3, "четыре": 4,
|
||||||
|
"пять": 5, "шесть": 6, "семь": 7, "восемь": 8,
|
||||||
|
"девять": 9, "десять": 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
func leadingWordNumber(s string) (int, string, bool) {
|
func leadingWordNumber(s string) (int, string, bool) {
|
||||||
@@ -308,6 +379,18 @@ func unitToDuration(n int, unit string) (time.Duration, bool) {
|
|||||||
return time.Duration(n) * time.Minute, true
|
return time.Duration(n) * time.Minute, true
|
||||||
case "s", "sec", "secs", "second", "seconds":
|
case "s", "sec", "secs", "second", "seconds":
|
||||||
return time.Duration(n) * time.Second, true
|
return time.Duration(n) * time.Second, true
|
||||||
|
// English day/week (pre-existing gap)
|
||||||
|
case "day", "days":
|
||||||
|
return time.Duration(n) * 24 * time.Hour, true
|
||||||
|
// Russian units (inflected forms)
|
||||||
|
case "час", "часа", "часов":
|
||||||
|
return time.Duration(n) * time.Hour, true
|
||||||
|
case "минута", "минуты", "минут":
|
||||||
|
return time.Duration(n) * time.Minute, true
|
||||||
|
case "день", "дня", "дней":
|
||||||
|
return time.Duration(n) * 24 * time.Hour, true
|
||||||
|
case "неделя", "недели", "недель":
|
||||||
|
return time.Duration(n) * 7 * 24 * time.Hour, true
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
maven, restart nginx
|
||||||
|
maven, stop the backup
|
||||||
|
maven, start the deployment
|
||||||
|
maven, run the backup script
|
||||||
|
maven, turn on the heater
|
||||||
|
maven, выключи свет
|
||||||
|
maven, запусти обновление
|
||||||
|
maven, открой шлагбаум
|
||||||
|
maven, сделай бэкап
|
||||||
|
maven, выключи сервер
|
||||||
|
включи микроволновку
|
||||||
|
выключи кондиционер
|
||||||
|
включи свет на кухне
|
||||||
|
открой ворота
|
||||||
|
запусти вентиляцию
|
||||||
|
выключи телевизор
|
||||||
|
включи музыку
|
||||||
|
выключи музыку
|
||||||
|
сделай громче
|
||||||
|
сделай тише
|
||||||
|
перезагрузи роутер
|
||||||
|
включи кофеварку
|
||||||
|
открой окно
|
||||||
|
закрой окно
|
||||||
|
запусти пылесос
|
||||||
|
останови пылесос
|
||||||
|
включи обогреватель
|
||||||
|
выключи обогреватель
|
||||||
|
включи чайник
|
||||||
|
открой шторы
|
||||||
|
закрой шторы
|
||||||
|
включи полив
|
||||||
|
выключи полив
|
||||||
|
заблокируй дверь
|
||||||
|
открой дверь
|
||||||
|
запусти обновление системы
|
||||||
|
останови сервис
|
||||||
|
перезапусти медиацентр
|
||||||
|
включи кондиционер
|
||||||
|
поставь таймер на 10 минут
|
||||||
|
выключи всё
|
||||||
|
перезагрузи компьютер
|
||||||
|
разблокируй дверь
|
||||||
|
запусти диагностику
|
||||||
|
останови все
|
||||||
|
# --- homelab read-only queries ---
|
||||||
|
покажи статус nginx
|
||||||
|
статус докера
|
||||||
|
покажи запущенные контейнеры
|
||||||
|
сколько места на диске
|
||||||
|
свободная память
|
||||||
|
покажи uptime
|
||||||
|
покажи логи nginx
|
||||||
|
лог докера
|
||||||
|
статус sshd
|
||||||
|
как работает сервер
|
||||||
|
что запущено
|
||||||
|
|
||||||
|
# --- homelab destructive (with confirm gate) ---
|
||||||
|
перезапусти nginx
|
||||||
|
перезагрузи докер
|
||||||
|
останови nginx
|
||||||
|
запусти nginx
|
||||||
|
перезагрузи сервер
|
||||||
|
останови контейнер
|
||||||
|
перезапусти сервис
|
||||||
|
останови сервис
|
||||||
|
запусти сервис
|
||||||
|
перезагрузи систему
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
отметь что я выпил воды
|
||||||
|
slept 6h
|
||||||
|
запиши что я пообедал
|
||||||
|
отметь тренировку 45 минут
|
||||||
|
записываю вес 72 килограмма
|
||||||
|
отметь что покормил кота
|
||||||
|
принял лекарство
|
||||||
|
выпил кофе
|
||||||
|
отметь что я сделал зарядку
|
||||||
|
logged 8 hours of sleep
|
||||||
|
запиши что съел на завтрак
|
||||||
|
отметь температуру 36.6
|
||||||
|
записываю давление 120 на 80
|
||||||
|
отметь пробежку 5 километров
|
||||||
|
выпил 2 литра воды
|
||||||
|
записываю что почистил зубы
|
||||||
|
отметь приём витаминов
|
||||||
|
запиши обед: гречка с курицей
|
||||||
|
отметь что полил цветы
|
||||||
|
вес 73.5 килограмма
|
||||||
|
тренировка 30 минут
|
||||||
|
сон 7 часов
|
||||||
|
выпил таблетку от головы
|
||||||
|
пообедал в 2 часа
|
||||||
|
выпил стакан сока
|
||||||
|
записываю что сходил в душ
|
||||||
|
отметь что переоделся
|
||||||
|
вечерний приём лекарств
|
||||||
|
заряжался 15 минут
|
||||||
|
записываю утреннюю пробежку
|
||||||
|
accounted 2000 ml of water
|
||||||
|
weight 71.8 kg
|
||||||
|
ran 3 kilometers
|
||||||
|
meditation 10 minutes
|
||||||
|
breakfast: oatmeal with berries
|
||||||
|
took vitamin d
|
||||||
|
walked 8000 steps
|
||||||
|
stretching 20 minutes
|
||||||
|
logged blood sugar 5.2
|
||||||
|
dinner at 7pm
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
note: staggered cooldown by time of day
|
||||||
|
заметка: купить новый фильтр для воды
|
||||||
|
запиши идею про автоматизацию полива
|
||||||
|
note: check the DNS config after update
|
||||||
|
заметка: починить ручку на двери
|
||||||
|
запиши рецепт: 3 яйца, мука, молоко
|
||||||
|
заметка: сменить масло в машине
|
||||||
|
note: upgrade wireguard on the phone
|
||||||
|
запиши: купить подарок на день рождения
|
||||||
|
заметка: заменить лампочку в коридоре
|
||||||
|
напомни себе купить новый жесткий диск
|
||||||
|
запиши: проверить проводку на даче
|
||||||
|
заметка: переустановить home assistant
|
||||||
|
note: add a backup job for the database
|
||||||
|
запиши пароль от wifi в заметки
|
||||||
|
заметка: починить протечку крана
|
||||||
|
купить новый фильтр для аквариума
|
||||||
|
заметка: записаться к стоматологу
|
||||||
|
запиши рецепт блинов
|
||||||
|
заметка: посадить рассаду в марте
|
||||||
|
запиши адрес: москва, тверская 7
|
||||||
|
заметка: просмотреть документацию по API
|
||||||
|
запиши: купить семена для огорода
|
||||||
|
заметка: обновить прошивку роутера
|
||||||
|
запиши книгу, которую посоветовали
|
||||||
|
заметка: сделать прививку от гриппа
|
||||||
|
запиши цену на стройматериалы
|
||||||
|
заметка: проверить уровень масла
|
||||||
|
запиши размеры полки для шкафа
|
||||||
|
заметка: заказать запчасти для стиралки
|
||||||
|
note: ideas for the garden automation
|
||||||
|
запиши что нужно купить в магазине
|
||||||
|
заметка: переклеить обои в спальне
|
||||||
|
запиши время работы химчистки
|
||||||
@@ -35,7 +35,8 @@ how many hours did I sleep this week
|
|||||||
покажи логи за сегодня
|
покажи логи за сегодня
|
||||||
кто последний раз делал бэкап
|
кто последний раз делал бэкап
|
||||||
какой ip адрес у сервера
|
какой ip адрес у сервера
|
||||||
сколько стоит свет в этом месяценайди заметку про сервер
|
сколько стоит свет в этом месяце
|
||||||
|
найди заметку про сервер
|
||||||
найди мою заметку о бэкапах
|
найди мою заметку о бэкапах
|
||||||
поищи заметку про роутер
|
поищи заметку про роутер
|
||||||
найди заметку где я записал пароль
|
найди заметку где я записал пароль
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
напомни через 4 часа размяться
|
||||||
|
remind me in 30 minutes to drink water
|
||||||
|
напомни завтра в 9 утра позвонить
|
||||||
|
напомни через час проверить духовку
|
||||||
|
напомни в пятницу вынести мусор
|
||||||
|
remind me at 6pm to take out the trash
|
||||||
|
напомни через 15 минут снять бельё
|
||||||
|
напомни в 8 вечера встречу
|
||||||
|
напомни через 2 дня оплатить счёт
|
||||||
|
напомни завтра утром покормить кота
|
||||||
|
remind me tomorrow at 8am to call the doctor
|
||||||
|
напомни через 10 минут снять с огня
|
||||||
|
напомни в среду оплатить интернет
|
||||||
|
напомни сегодня в 3 часа собрание
|
||||||
|
remind me next monday to submit the report
|
||||||
|
напомни через 3 часа принять лекарство
|
||||||
|
напомни завтра вечером купить хлеб
|
||||||
|
remind me in 5 minutes to check the oven
|
||||||
|
напомни в понедельник утром вынести мусор
|
||||||
|
напомни через 30 минут выключить чайник
|
||||||
|
напомни сегодня вечером позвонить маме
|
||||||
|
remind me on friday at noon to water plants
|
||||||
|
напомни через неделю заплатить за квартиру
|
||||||
|
напомни завтра в 10 утра тренировка
|
||||||
|
напомни в воскресенье убраться в гараже
|
||||||
|
напомни через 2 часа покормить собаку
|
||||||
|
напомни сегодня в полдень обед
|
||||||
|
напомни в 7 утра зарядка
|
||||||
|
напомни через минуту снять пробу
|
||||||
|
напомни завтра в 5 вечера забрать посылку
|
||||||
|
remind me tonight at 10pm to take medicine
|
||||||
|
напомни в четверг заплатить налоги
|
||||||
|
напомни через 20 минут выключить духовку
|
||||||
|
напомни в субботу убрать в комнате
|
||||||
|
напомни завтра после обеда позвонить
|
||||||
|
# --- pure-verb reminder seeds (no time expression) — shifts centroid away
|
||||||
|
# from time-lexicon dominance toward the reminding intent itself.
|
||||||
|
напомни мне
|
||||||
|
напомни пожалуйста
|
||||||
|
не забудь напомнить
|
||||||
|
поставь напоминание
|
||||||
|
создай напоминание
|
||||||
|
добавь напоминание
|
||||||
|
запиши напоминание
|
||||||
|
set a reminder
|
||||||
|
create a reminder
|
||||||
|
make a reminder
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
который час
|
||||||
|
сколько времени
|
||||||
|
какой сегодня день
|
||||||
|
какое сегодня число
|
||||||
|
который час в Москве
|
||||||
|
сколько сейчас времени
|
||||||
|
который час у нас
|
||||||
|
сколько времени осталось до вечера
|
||||||
|
какой сегодня день недели
|
||||||
|
какая температура воздуха
|
||||||
|
сколько человек дома
|
||||||
|
кто сейчас дома
|
||||||
|
есть ли кто дома
|
||||||
|
кто дома сейчас
|
||||||
|
все ли дома
|
||||||
|
сколько памяти занято
|
||||||
|
какая загрузка процессора
|
||||||
|
сколько свободного места на диске
|
||||||
|
какой ip адрес у сервера
|
||||||
|
как дела у сервера
|
||||||
|
всё ли работает
|
||||||
|
сколько сервер работает без перезагрузки
|
||||||
|
когда сервер запускался
|
||||||
|
какая версия софта
|
||||||
|
сколько аптайм
|
||||||
|
какой статус сервисов
|
||||||
|
все ли сервисы работают
|
||||||
|
что с интернетом
|
||||||
|
интернет работает
|
||||||
|
когда последний раз перезагружался
|
||||||
|
сколько трафика сегодня
|
||||||
|
какая скорость интернета
|
||||||
|
загрузка сети
|
||||||
|
сколько процессов запущено
|
||||||
|
как загрузка системы
|
||||||
|
сколько оперативной памяти свободно
|
||||||
|
какая температура процессора
|
||||||
|
тихий режим
|
||||||
|
тихо
|
||||||
|
не шуми
|
||||||
|
тихий режим включи
|
||||||
|
включи тихий режим
|
||||||
|
не беспокоить
|
||||||
|
выключи тихий режим
|
||||||
|
шумный режим
|
||||||
|
громкий режим
|
||||||
|
отключи тихий режим
|
||||||
|
quiet mode on
|
||||||
|
quiet mode off
|
||||||
|
quiet on
|
||||||
|
quiet off
|
||||||
Reference in New Issue
Block a user