package main import ( "context" "log" "regexp" "github.com/kami/maven/internal/phraser" ) // A question about her — "что ты умеешь", "кто ты" — used to have no answer at // all (Vikunja #555). It reached the personal boundary, which claimed it as his // and said "не знаю — не нашла у тебя такой записи", because the boundary knows // two sides and this is neither: her own description is not his data and it is // not the world's either. Letting it past the boundary is no better, because // then SearXNG answers about somebody else's assistant. // // The description does NOT live in the note store. Notes are his. A note about // her sitting in his index would come back for "что я записал", would be fed to // the digestion worker as something he said, and would be recalled by vector // proximity for questions that are not about her at all. It is her own text, so // it lives here, in one place, and it is the only copy. // // This source sits ABOVE the boundary, because a question about her never had // an answer below it. // selfDescription — what she is and what this box actually does. Frozen text, // and the one rule for editing it: name only what is really wired. Anything // that depends on config — the house, the LAN, the feeds, telegram, search — is // named as depending on what he allowed, never claimed outright. Inventing a // capability here is the same defect as inventing a fact, and it is worse than // silence because he would plan around it. // // Written in her own voice, feminine, addressing him informally, because it is // handed to the phraser as the evidence for the answer and the phraser will // keep the words it is given. const selfDescription = `Я Мэйвен, твоя помощница. Я живу на твоём сервере, ` + `и наружу уходит только поисковый запрос — больше ничего. Что я делаю сама: запоминаю, что ты мне говоришь, и потом отвечаю на вопросы ` + `об этом; веду заметки; ставлю напоминания; читаю твой календарь и задачи; ` + `отвечаю на вопросы о мире — сначала поиском, а если сети нет, то по ` + `офлайновой энциклопедии. Что зависит от того, что ты мне разрешил: дом, локальная сеть, ленты, ` + `список покупок, погода, телеграм. Если что-то из этого не настроено, я ` + `скажу об этом прямо, а не буду выдумывать ответ. Говорю по-русски и по-английски.` // selfSeeds — the questions this source claims. Scoring data like every other // topic set: editing one moves the recogniser and has to be re-measured against // TestONNXTopics. // // All of them are about HER — what she is, what she can do, who made her. The // neighbouring set is topicAttend, "что требует внимания", which asks about the // state of his things; the two share almost nothing but the second person. var selfSeeds = []string{ "что ты умеешь", "что ты можешь делать", "кто ты такая", "расскажи о себе", "какие у тебя возможности", // Added after measuring: it won self by 0.0002, under the margin, and the // floor does not carry it — "способна" names no verb the floor matches. "на что ты способна", "чем ты можешь помочь", "what can you do", "who are you", } // selfFloor — the offline floor, for a handler with no embedder or a turn whose // vector never got computed. Narrow on purpose, like every other floor here: it // answers only when the seeds cannot, and a broad guess made blind is worse // than a narrow one. // // Go's \b is ASCII-only and never fires next to a Cyrillic letter, so the // Russian patterns spell the boundary out. var selfPatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)(^|[^\p{L}\p{N}])ты\s+(умеешь|можешь)([^\p{L}\p{N}]|$)`), regexp.MustCompile(`(?i)(^|[^\p{L}\p{N}])кто\s+ты([^\p{L}\p{N}]|$)`), regexp.MustCompile(`(?i)(^|[^\p{L}\p{N}])(расскажи|поведай)\s+о\s+себе([^\p{L}\p{N}]|$)`), regexp.MustCompile(`(?i)\bwhat\s+can\s+you\s+do\b`), regexp.MustCompile(`(?i)\bwho\s+are\s+you\b`), } func selfFloor(utterance string) bool { for _, re := range selfPatterns { if re.MatchString(utterance) { return true } } return false } // querySelf answers a question about her from selfDescription. The description // goes through the phraser as evidence so the answer is shaped to what he // asked — "что ты умеешь" and "кто ты" want different halves of it — and falls // back to the text itself, which is already readable, if the model is down. func (h *reactiveHandler) querySelf(ctx context.Context, t *queryTurn) (string, bool) { if !h.turnIsAbout(ctx, t, topicSelf, selfFloor) { return "", false } var reply string if h.phraser != nil { var err error reply, err = h.phraser.PhraseSelf(ctx, t.dec.Utterance, selfDescription) if err != nil { log.Printf("voice: phrase self: %v", err) } } if reply == "" { reply = phraser.Q(phraser.QueryFound, map[string]string{"text": selfDescription}) } return reply, true }