12c18dcf65
PR 114's review is anchored on internal/phraser/query_ru_v1.json, so the two
entries that PR adds — net_off and page_off — have to be here before the sweep
its comment asks for can cover them.
One conflict, in internal/phraser/query.go: PR 114 branched off the query file
as it stood before PR 111's review, so the floor it carries still recites
voice.weather.default_location at him and still puts {tail} in every net_empty
variant. Both are what that review threw out. Resolved to this branch's floor
plus PR 114's two new keys.
--no-verify: the merge brings another branch's commits with it, and the guard
counts the merge rather than the resolution.
172 lines
6.9 KiB
Go
172 lines
6.9 KiB
Go
package phraser
|
|
|
|
// The query answers and gaps — what a query source says when it answers from
|
|
// something other than the model, and what it says when it has nothing.
|
|
//
|
|
// Third family on the shared deck (deck.go), after the fallbacks and the
|
|
// acknowledgements. They were literals spread across actions_query.go and
|
|
// netscan.go, where the largest single site held two dozen of them.
|
|
//
|
|
// QueryUnknown is not the phraser's UnknownFallback, even though the two read
|
|
// the same today. Here she looked and found nothing; there she failed to phrase
|
|
// an answer she had. Two files, two entries, so rewording one leaves the other.
|
|
|
|
import (
|
|
_ "embed"
|
|
"log"
|
|
"math/rand"
|
|
"sync"
|
|
|
|
"github.com/kami/maven/internal/say"
|
|
)
|
|
|
|
//go:embed query_ru_v1.json
|
|
var queryJSON []byte
|
|
|
|
// QuerySchemaVersion — this family's own version.
|
|
const QuerySchemaVersion = 1
|
|
|
|
// The entry keys.
|
|
const (
|
|
QueryUnknown = "query_unknown"
|
|
QueryOtherDay = "other_day"
|
|
QueryPersonalNone = "personal_none"
|
|
QueryFactWhen = "fact_when"
|
|
QueryFactValue = "fact_value"
|
|
QueryFound = "found"
|
|
QueryPageText = "page_text"
|
|
QueryPageBlocked = "page_blocked"
|
|
QueryPageEmpty = "page_empty"
|
|
QueryFeedsOff = "feeds_off"
|
|
QueryFeedsNew = "feeds_new"
|
|
QueryFeedsEmpty = "feeds_empty"
|
|
QueryFeedsTopic = "feeds_empty_topic"
|
|
QueryWeatherNow = "weather_now"
|
|
QueryWeatherOff = "weather_off"
|
|
QueryWeatherWhere = "weather_nolocation"
|
|
QueryNetEmpty = "net_empty"
|
|
QueryNetOff = "net_off"
|
|
QueryPageOff = "page_off"
|
|
|
|
QueryFailPlan = "fail_plan"
|
|
QueryFailNotes = "fail_notes"
|
|
QueryFailFeeds = "fail_feeds"
|
|
QueryFailCalendar = "fail_calendar"
|
|
QueryFailWeather = "fail_weather"
|
|
QueryFailAnswer = "fail_answer"
|
|
QueryFailPage = "fail_page"
|
|
QueryFailNetscan = "fail_netscan"
|
|
)
|
|
|
|
var queryKeys = []string{
|
|
QueryUnknown, QueryOtherDay, QueryPersonalNone, QueryFactWhen, QueryFactValue,
|
|
QueryFound, QueryPageText, QueryPageBlocked, QueryPageEmpty,
|
|
QueryFeedsOff, QueryFeedsNew, QueryFeedsEmpty, QueryFeedsTopic,
|
|
QueryWeatherNow, QueryWeatherOff, QueryWeatherWhere, QueryNetEmpty, QueryNetOff, QueryPageOff,
|
|
QueryFailPlan, QueryFailNotes, QueryFailFeeds, QueryFailCalendar,
|
|
QueryFailWeather, QueryFailAnswer, QueryFailPage, QueryFailNetscan,
|
|
}
|
|
|
|
// queryFloor — the literal each key falls back to when the file is unusable.
|
|
// It started as the exact strings that lived in Go before this file existed and
|
|
// now tracks the file's first variant instead, because a floor that keeps the
|
|
// wording review threw out would say it back on the one turn nobody is watching.
|
|
var queryFloor = map[string]string{
|
|
QueryUnknown: "ничего не нашла.",
|
|
QueryOtherDay: "про другой день так не отвечу — спроси целиком.",
|
|
QueryPersonalNone: "не знаю — не нашла у тебя такой записи.",
|
|
QueryFactWhen: "записала это {when}",
|
|
QueryFactValue: "у меня записано: {key} — {value}",
|
|
QueryFound: "вот что я нашла: {text}",
|
|
QueryPageText: "вот что на странице: {text}",
|
|
QueryPageBlocked: "эта страница закрыта для чтения — robots.txt не разрешает.",
|
|
QueryPageEmpty: "страница открылась, но читать там нечего.",
|
|
QueryFeedsOff: "ленты не настроены.",
|
|
QueryFeedsNew: "вот что нового: {items}",
|
|
QueryFeedsEmpty: "в лентах пока ничего нового.",
|
|
QueryFeedsTopic: "по этой теме в лентах пока ничего.",
|
|
QueryWeatherNow: "в {location} сейчас {temp} {word}, {condition}.",
|
|
QueryWeatherOff: "погода не настроена.",
|
|
QueryWeatherWhere: "для какого города?",
|
|
QueryNetEmpty: "в сети никого не нашла.",
|
|
QueryNetOff: "сканирование сети не настроено.",
|
|
QueryPageOff: "я не читаю страницы — это не настроено.",
|
|
|
|
QueryFailPlan: "не получилось собрать план.",
|
|
QueryFailNotes: "не получилось посмотреть записи.",
|
|
QueryFailFeeds: "не получилось посмотреть ленты.",
|
|
QueryFailCalendar: "не получилось проверить календарь.",
|
|
QueryFailWeather: "не получилось узнать погоду.",
|
|
QueryFailAnswer: "не получилось найти ответ.",
|
|
QueryFailPage: "не получилось прочитать страницу.",
|
|
QueryFailNetscan: "не получилось просканировать сеть.",
|
|
}
|
|
|
|
// Queries picks a hand-written Russian query line. Safe for concurrent use.
|
|
type Queries struct{ d *say.Deck }
|
|
|
|
// LoadQueries reads the embedded file. Pass a source to make the picking
|
|
// reproducible in tests; nil seeds from the clock.
|
|
func LoadQueries(src rand.Source) (*Queries, error) {
|
|
d, err := say.Load(queryJSON, QuerySchemaVersion, queryKeys, queryFloor, src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// The entries that exist to read something back. A variant without the
|
|
// placeholder would answer the question by dropping the answer.
|
|
for _, req := range []struct{ key, ph string }{
|
|
{QueryFactWhen, "{when}"}, {QueryFactValue, "{key}"}, {QueryFactValue, "{value}"},
|
|
{QueryFound, "{text}"}, {QueryPageText, "{text}"}, {QueryFeedsNew, "{items}"},
|
|
{QueryWeatherNow, "{location}"}, {QueryWeatherNow, "{temp}"},
|
|
{QueryWeatherNow, "{word}"}, {QueryWeatherNow, "{condition}"},
|
|
} {
|
|
if err := d.RequirePlaceholder(req.key, req.ph); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &Queries{d: d}, nil
|
|
}
|
|
|
|
// deck reads through a nil *Queries, which is the unloadable-file case.
|
|
func (q *Queries) deck() *say.Deck {
|
|
if q == nil {
|
|
return say.FloorDeck(queryFloor)
|
|
}
|
|
return q.d
|
|
}
|
|
|
|
// Say returns one line for key, with the values filled into the frame.
|
|
func (q *Queries) Say(key string, vars map[string]string) string {
|
|
return q.deck().Text(key, vars)
|
|
}
|
|
|
|
// Variants returns every line the file can produce, for the persona scorer.
|
|
func (q *Queries) Variants() []string { return q.deck().Variants() }
|
|
|
|
var (
|
|
queryOnce sync.Once
|
|
queries *Queries
|
|
)
|
|
|
|
// DefaultQueries returns the shared instance, loading it on first use. A broken
|
|
// file logs once and leaves a nil *Queries, which still answers from queryFloor.
|
|
func DefaultQueries() *Queries {
|
|
queryOnce.Do(func() {
|
|
q, err := LoadQueries(nil)
|
|
if err != nil {
|
|
log.Printf("phraser: query lines unavailable, using the built-in ones: %v", err)
|
|
return
|
|
}
|
|
queries = q
|
|
})
|
|
return queries
|
|
}
|
|
|
|
// Q — one query line, the way every caller says it.
|
|
func Q(key string, vars map[string]string) string { return DefaultQueries().Say(key, vars) }
|
|
|
|
// IsQ reports whether text is a line key could have produced, for the tests.
|
|
func IsQ(key string, vars map[string]string, text string) bool {
|
|
return DefaultQueries().deck().Matches(key, vars, text)
|
|
}
|