69ecea19d5
The confirmation was phrased by the replier off Slots.Text, so it named
whatever hour the utterance contained — including one the parser rejected
or read differently. He heard 'напомню в семь' with no row at seven, and
stopped thinking about it.
actionReminder now phrases it itself from the stored fire time, so the
sentence and the row cannot disagree. Deterministic: the one sentence that
must match a database row is not one to hand to a 1.7B.
Also fixes formatTime, which had t.Format("2 января") — Go reads that as a
literal, so every fact older than a day read as January. The month comes
from internal/lexicon now, which is where months live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60 lines
2.3 KiB
Go
60 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/kami/maven/internal/lexicon"
|
|
"github.com/kami/maven/internal/phraser"
|
|
"github.com/kami/maven/internal/router"
|
|
)
|
|
|
|
// actionReminder handles router.IntentReminder: parse the time when stage-0
|
|
// skipped the extractor, then create the reminder.
|
|
func (h *reactiveHandler) actionReminder(ctx context.Context, dec router.Decision) string {
|
|
if !dec.Slots.HasTime {
|
|
// Stage-0 (reminder-wakeword grammar) skips the extractor, so the
|
|
// time wasn't parsed. Run the parser as a fallback.
|
|
if dec.Stage == 0 && h.timeParser != nil {
|
|
t, ok, err := h.timeParser.Parse(ctx, dec.Utterance, h.now())
|
|
if err == nil && ok {
|
|
dec.Slots.Time = t
|
|
dec.Slots.HasTime = true
|
|
}
|
|
}
|
|
if !dec.Slots.HasTime {
|
|
return phraser.Ack(phraser.FailReminderTime, nil)
|
|
}
|
|
}
|
|
// The body is what she says at the hour, so the marker and the time come
|
|
// out of it: the fire time is already a column, and "напомни" is an
|
|
// instruction that has been carried out (Vikunja #469).
|
|
payload := `{"text":` + jsonString(reminderBody(dec.Utterance, dec.Slots.Text)) + `}`
|
|
if _, err := h.api.CreateReminder(ctx, dec.Slots.Time, payload, ""); err != nil {
|
|
log.Printf("voice: create reminder: %v", err)
|
|
return phraser.Ack(phraser.FailReminder, nil)
|
|
}
|
|
// Phrased from the row, never from the utterance (Vikunja #507). The
|
|
// replier only ever saw Slots.Text, so it named whatever hour the sentence
|
|
// contained — including one the parser had rejected or read differently.
|
|
// A confirmation naming an hour no row holds is worse than a clarify,
|
|
// because he stops thinking about it.
|
|
return reminderConfirm(dec.Slots.Time, h.now())
|
|
}
|
|
|
|
// reminderConfirm — the confirmation for a reminder that exists, naming the
|
|
// stored fire time. Deterministic on purpose: the one sentence that must match
|
|
// a database row is not one to hand to a 1.7B.
|
|
func reminderConfirm(fire, now time.Time) string {
|
|
when := dayPrefix(now, fire)
|
|
if when == "это" {
|
|
// Further out than the day words reach — say the date instead of a
|
|
// word that would be wrong.
|
|
date := fmt.Sprintf("%d %s", fire.Day(), lexicon.MonthGenitive(int(fire.Month())))
|
|
return "хорошо, напомню " + date + " в " + fire.Format("15:04") + "."
|
|
}
|
|
return "хорошо, напомню " + when + " в " + fire.Format("15:04") + "."
|
|
}
|