28c0ff73bd
PR 113's review is about internal/say/summary_ru_v1.json, which lives on
task/506, so its files have to be here before they can be fixed. Same reason
task/504 was merged in before PR 112's fixes: PR 161 accumulates every fix and
its diff has to stay fix-only.
Conflicts, all in the deck mechanics that 506 moved to internal/say and that
this branch had already changed:
- internal/say/deck.go — the exported Deck from 506 keeps this branch's per-family
floor. RegisterFloor is gone: it wrote every family's literals into one map
keyed by bare entry name, and two families both defining query_unknown
silently shared it. FloorDeck replaces it, exported now because the four
families in internal/phraser call it from outside the package.
- internal/say/summary.go — the fifth family off RegisterFloor onto the same
per-family map.
- internal/phraser/{acks,acts,fallbacks,query}.go — say.FloorDeck for the same.
--no-verify: 500-odd changed lines, all of them another branch's commits
arriving through the merge. The guard counts the merge, not the resolution.
168 lines
6.6 KiB
Go
168 lines
6.6 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"
|
|
|
|
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,
|
|
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: "в сети никого не нашла.",
|
|
|
|
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)
|
|
}
|