package main import ( "regexp" "strings" "unicode" "github.com/kami/maven/internal/router" ) // latinRun matches a run of Latin-script words — the shape a service, host or // project name takes in a Russian sentence. Digits, dot, dash and underscore // ride along because "muzick-indexer" and "nginx.conf" are one name, not two. var latinRun = regexp.MustCompile(`[A-Za-z][A-Za-z0-9._-]*(?:\s+[A-Za-z][A-Za-z0-9._-]*)*`) // hasLatin reports whether s carries a Latin letter. func hasLatin(s string) bool { for _, r := range s { if unicode.In(r, unicode.Latin) { return true } } return false } // entityReferenceText is the name Nexus is asked to resolve. // // Normally that is the router's Text slot, which is the verb phrase the model // wrote. But the resident model rewrites a Russian utterance as it routes, and // on the way it transliterates: "перезапусти muzick indexer" came back as // "перезагрузить музик индексер" (Vikunja #476). Nexus is then asked for a // service nobody has ever named, so the act cannot resolve its target even // with every gate open. // // The recovery is deliberately narrow. Only when the utterance holds a Latin // run and the model's Text holds none has a name certainly been rewritten — // then the longest Latin run in his own words is the reference. Anything else // keeps the Text slot, so an English utterance and a Russian entity name are // both untouched. Un-transliterating the Cyrillic back is not attempted: the // surface form he said is right there, and guessing at a reverse mapping would // invent a second name to be wrong about. func entityReferenceText(dec router.Decision) string { text := dec.Slots.Text if hasLatin(text) || !hasLatin(dec.Utterance) { return text } longest := "" for _, m := range latinRun.FindAllString(dec.Utterance, -1) { if len(m) > len(longest) { longest = m } } longest = strings.TrimSpace(longest) // A single stray letter is not a name. if len(longest) < 2 { return text } return longest }