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: { "Что записать?", "Что именно отметить?", "Назови, что записать — например, «выпил воды».", }, dialogue.SlotFn: { "Что сделать?", "Какое действие выполнить?", "Назови действие — я умею только то, что ты мне разрешил.", }, } // 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) }