package router import "strings" // thinSingleToken — is a one-word utterance thin evidence, or is it a whole // sentence? // // The rule this replaces was `len(strings.Fields(u)) <= 1`, an English // intuition. It does not transfer: Russian packs a subject, a tense and a // gender into one word, so "поужинал" is a complete report and "привет" a // complete greeting, yet both got thinned and came back as "не совсем поняла". // Meanwhile the case the rule exists for is real — a bare noun like "вода" or // "бэкап" genuinely does not say fact-vs-query or act-vs-report. // // So: still one token, but only thin it when the token is a bare nominal. // Two escapes, both cheap and both offline: // // - a closed lexicon of social and command singles, which are complete by // definition ("привет", "спасибо", "стоп", "yes"); // - a suffix test for an inflected predicate — past tense, 2nd person, // reflexive. Verbs carry their own subject, so a verb IS a sentence. // // The suffix test is deliberately loose about nouns that happen to end the // same way ("канал" reads as past tense here). That direction of error only // costs a clarify we would not have asked for; the other direction — treating // a real report as thin — is the bug being fixed. func thinSingleToken(utterance string) bool { f := strings.Fields(utterance) if len(f) != 1 { return false } w := strings.ToLower(strings.Trim(f[0], ".,!?;:—-\"'«»()")) if w == "" { return false } if completeSingles[w] { return false } return !looksInflected(w) } // completeSingles — one-word utterances that need no second half. Greetings, // acknowledgements and the control words a voice loop has to honour instantly. var completeSingles = map[string]bool{ // ru: social "привет": true, "здравствуй": true, "здравствуйте": true, "здорово": true, "пока": true, "прощай": true, "спокойной": true, "спасибо": true, "благодарю": true, "извини": true, "прости": true, "пожалуйста": true, "да": true, "нет": true, "ага": true, "угу": true, "ок": true, "окей": true, "хорошо": true, "ладно": true, "конечно": true, "верно": true, "точно": true, // ru: control "стоп": true, "отмена": true, "отбой": true, "хватит": true, "тихо": true, "повтори": true, "продолжай": true, "помоги": true, "помощь": true, // en "hi": true, "hello": true, "hey": true, "bye": true, "goodbye": true, "thanks": true, "thank": true, "sorry": true, "please": true, "yes": true, "no": true, "yep": true, "nope": true, "ok": true, "okay": true, "sure": true, "right": true, "stop": true, "cancel": true, "help": true, "repeat": true, "continue": true, } // inflectedSuffixes — endings that mark a finite or past-tense Russian verb. // Ordered longest-first is unnecessary (any match wins), but each entry is // chosen to be long enough that common nouns rarely collide. var inflectedSuffixes = []string{ // reflexive — strongly verbal whatever precedes it "ся", "сь", // past tense "ал", "ял", "ил", "ел", "ыл", "ул", "ёл", "ала", "яла", "ила", "ела", "ыла", "ула", "али", "яли", "или", "ели", // 2nd person singular "ешь", "ишь", "ёшь", // 1st/2nd person plural, 3rd person plural "аем", "яем", "уем", "аете", "ите", "ают", "яют", "уют", "ат", "ят", } // looksInflected — does the word carry a verb ending? Short words are exempt: // a three-letter token is not enough stem to trust a two-letter suffix on // ("газ" would otherwise never match, but "нос" and "лес" would). func looksInflected(w string) bool { if len([]rune(w)) < 5 { return false } for _, s := range inflectedSuffixes { if strings.HasSuffix(w, s) { return true } } return false }