5bd1406c7a
Reciting the allowlist answered a question he did not ask. She says the command is not one she knows, once, and parks nothing.
124 lines
5.2 KiB
Go
124 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"github.com/kami/maven/internal/dialogue"
|
|
"github.com/kami/maven/internal/router"
|
|
)
|
|
|
|
// The clarify copy deck (Vikunja #457).
|
|
//
|
|
// Every clarify turn used to say one sentence per gap, and a re-ask repeated
|
|
// that sentence word for word. A question he already failed to answer is the
|
|
// worst one to ask again unchanged: the second wording is the one that tells
|
|
// him which part she missed.
|
|
//
|
|
// Fixed templates, not model output, for the reason clarifyQuestions has always
|
|
// given: the resident model would wander, and a question whose wording changes
|
|
// at random is harder to answer than a blunt one. What changes here is that the
|
|
// wording varies with the attempt rather than with a die roll — the first ask is
|
|
// short, the second names the gap, the third spells it out.
|
|
//
|
|
// No schema_version, unlike internal/phraser/nudge_templates.go. These are Go
|
|
// constants compiled into the daemon, so there is no file that can drift out of
|
|
// step with the code that reads it.
|
|
//
|
|
// Persona holds: infinitive and imperative questions, so there is no gender
|
|
// agreement to get wrong, "ты" throughout, and no pet names.
|
|
var clarifyQuestionVariants = map[dialogue.Slot][]string{
|
|
dialogue.SlotTime: {
|
|
"Когда?",
|
|
"Во сколько напомнить?",
|
|
"Скажи время — например, «в семь вечера» или «через час».",
|
|
},
|
|
dialogue.SlotText: {
|
|
"О чём напомнить?",
|
|
"Что сказать тебе в это время?",
|
|
"Скажи одной фразой, о чём напомнить.",
|
|
},
|
|
dialogue.SlotKey: {
|
|
"Что записать?",
|
|
"Что именно отметить?",
|
|
"Назови, что записать — например, «выпил воды».",
|
|
},
|
|
// Not spoken since Vikunja #556: askClarify answers actNotRecognized for a
|
|
// missing capability rather than asking. Kept because clarifyQuestion still
|
|
// reports the gap, and a re-ask deck with a hole in it is harder to read.
|
|
dialogue.SlotFn: {
|
|
"Что сделать?",
|
|
"Какое действие выполнить?",
|
|
"Назови действие — я умею только то, что ты мне разрешил.",
|
|
},
|
|
}
|
|
|
|
// actNotRecognized is what an act she cannot run gets (Vikunja #556).
|
|
//
|
|
// The deck used to ask "Что сделать?" instead. That question has no answer he
|
|
// can give: he already said what he wanted, and nothing he repeats will match a
|
|
// capability that is not there. So she asked, failed, asked again and gave up —
|
|
// three turns spent on one refusal. She says it once now, and parks nothing.
|
|
//
|
|
// It does not recite the allowlist. A list of names he did not ask about is not
|
|
// an answer to the thing he did ask about.
|
|
const actNotRecognized = "Такую команду я не знаю."
|
|
|
|
// clarifyQuestionFor picks the wording for this attempt. attempt is 1-based, as
|
|
// PendingQuestion.Attempts counts it; anything past the list uses the last and
|
|
// most explicit phrasing rather than wrapping round to the short one, because
|
|
// wrapping would ask the same short question he has already not answered.
|
|
//
|
|
// Deterministic on purpose, unlike clarifyExpiredLine: an expiry notice is the
|
|
// same statement however it is worded, and a re-ask is not.
|
|
func clarifyQuestionFor(slot dialogue.Slot, attempt int) (string, bool) {
|
|
variants, ok := clarifyQuestionVariants[slot]
|
|
if !ok || len(variants) == 0 {
|
|
return "", false
|
|
}
|
|
i := attempt - 1
|
|
if i < 0 {
|
|
i = 0
|
|
}
|
|
if i >= len(variants) {
|
|
i = len(variants) - 1
|
|
}
|
|
return variants[i], true
|
|
}
|
|
|
|
// clarifyMissedVariants — she is asking for the whole utterance again, because
|
|
// the gate fired on an intent with nothing identifiable to ask about (note,
|
|
// query, chat, system are not in wantedSlots).
|
|
//
|
|
// Rotated like the expiry lines and for the same reason: this is the line he
|
|
// hears whenever she misses him completely, so it is a line that repeats, and
|
|
// the same sentence every time is what makes a house assistant sound like a
|
|
// kiosk. All of them say the same two things — she did not catch it, and he
|
|
// should say it again — because the wording may vary and the meaning may not.
|
|
var clarifyMissedVariants = []string{
|
|
"Не совсем поняла — скажи, пожалуйста, ещё раз.",
|
|
"Я тебя не разобрала. Повтори, пожалуйста.",
|
|
"Не уловила. Скажи это по-другому?",
|
|
"Прости, не поняла — попробуй сказать иначе.",
|
|
}
|
|
|
|
// clarifyMissedFor picks a wording by the utterance itself, so the same words
|
|
// asked twice get the same answer and two different misses sound different.
|
|
//
|
|
// A hash, not rand: a test that drives an utterance twice must not depend on a
|
|
// die roll, and the point of rotating is only that consecutive misses differ.
|
|
func clarifyMissedFor(utterance string) string {
|
|
var sum int
|
|
for _, r := range utterance {
|
|
sum += int(r)
|
|
}
|
|
return clarifyMissedVariants[sum%len(clarifyMissedVariants)]
|
|
}
|
|
|
|
// clarifyMissedLine is the canned reply for a clarify decision she cannot turn
|
|
// into a question. Returns "" for a decision that is not a clarify, so the
|
|
// caller keeps its own reply.
|
|
func clarifyMissedLine(dec router.Decision) string {
|
|
if !dec.Clarify {
|
|
return ""
|
|
}
|
|
return clarifyMissedFor(dec.Utterance)
|
|
}
|