quickshell: answer "where"/"when"/"who" questions, pick the right article
"where is K2" had no scoring signal at all — "where" is a stopword and "k2" fell under the 3-char term floor, so every sentence tied at zero and the card fell back to the opening line. Fixes: - answer-type hints: a "where" question scores sentences carrying the vocabulary of place, "when" of dates, "who" of attribution. Stems, not whole words — the Telephone intro says "granted a United States patent", never "invented". "is a"/"was a" deliberately excluded, since they match the definitional opening of every article. - term floor down to 2 chars so "K2" and "UK" count. - fetch 3 candidates and choose: an exact title match wins, and a disambiguated title loses unless the query asked for that sense. Search rank alone put "Chernobyl (miniseries)" above the city. Verified 11/11 on live responses by running the generated QML functions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
95637d91c8
commit
5c3d84d0c9
1 changed files with 58 additions and 9 deletions
|
|
@ -232,6 +232,9 @@ in
|
|||
# title-only endpoints return nothing. --data-urlencode keeps the query
|
||||
# out of the URL literal.
|
||||
#
|
||||
# Three candidates, not one: search rank puts "Chernobyl (miniseries)"
|
||||
# above the city, so the QML picks between them (see pickPage).
|
||||
#
|
||||
# Prints nothing on any failure, which the QML treats as "no answer" —
|
||||
# including the empty 4xx/5xx that Wikipedia returns when a burst of
|
||||
# requests gets throttled.
|
||||
|
|
@ -241,7 +244,7 @@ in
|
|||
${pkgs.curl}/bin/curl -sf --max-time 6 -G \
|
||||
--data-urlencode "gsrsearch=$q" \
|
||||
-H 'User-Agent: quickshell-launcher/1.0 (personal desktop use)' \
|
||||
'https://en.wikipedia.org/w/api.php?action=query&format=json&generator=search&gsrlimit=1&prop=extracts&exintro&explaintext&redirects=1'
|
||||
'https://en.wikipedia.org/w/api.php?action=query&format=json&generator=search&gsrlimit=3&prop=extracts&exintro&explaintext&redirects=1'
|
||||
'';
|
||||
|
||||
# cava in raw mode: one newline-terminated frame per tick, bars as
|
||||
|
|
@ -1698,6 +1701,22 @@ in
|
|||
"speed", "fast", "temperature", "cost", "price", "year",
|
||||
"when", "many", "much", "capital", "big"
|
||||
]
|
||||
// What a sentence answering THIS kind of question looks like.
|
||||
// A digit is the tell for "how tall"; for "where" it's the
|
||||
// vocabulary of place. Stems, not whole words — the Telephone
|
||||
// intro says "granted a United States patent", never
|
||||
// "invented", so "patent" has to match "patent" in either.
|
||||
readonly property var answerHints: ({
|
||||
where: ["located", "lies", "situated", "region", "country",
|
||||
"border", "city", "province", "state", "range",
|
||||
"north", "south", "east", "west", "coast", "island", "near"],
|
||||
when: ["century", "founded", "established", "opened",
|
||||
"began", "completed", "built", "inaugurated"],
|
||||
// Deliberately NOT "is a"/"was a": those match the
|
||||
// definitional opening of every article.
|
||||
who: ["invent", "discover", "found", "creat", "develop",
|
||||
"credit", "patent", "known for", "named after"]
|
||||
})
|
||||
|
||||
// "firefox" and "vs code" are app searches; "mount everest
|
||||
// height" is a question. Two words plus some length sorts
|
||||
|
|
@ -1796,10 +1815,18 @@ in
|
|||
|
||||
const ql = query.toLowerCase();
|
||||
const tw = title.toLowerCase().split(/[^a-z0-9]+/);
|
||||
const terms = ql.split(/[^a-z0-9]+/).filter(w =>
|
||||
w.length > 2 && answerStop.indexOf(w) === -1
|
||||
const words = ql.split(/[^a-z0-9]+/);
|
||||
// 2 chars, not 3: "K2" and "UK" are real search terms.
|
||||
const terms = words.filter(w =>
|
||||
w.length >= 2 && answerStop.indexOf(w) === -1
|
||||
&& (answerAttrs.indexOf(w) !== -1 || tw.indexOf(w) === -1));
|
||||
const wantsNumber = answerAttrs.some(a => ql.indexOf(a) !== -1);
|
||||
// "where is K2" carries no scoring terms at all once the
|
||||
// stopword and the title word are gone — the answer-type
|
||||
// hints are the only signal it has.
|
||||
let hints = [];
|
||||
for (const k in answerHints)
|
||||
if (words.indexOf(k) !== -1) hints = hints.concat(answerHints[k]);
|
||||
|
||||
let best = 0;
|
||||
let bestScore = -1;
|
||||
|
|
@ -1809,6 +1836,7 @@ in
|
|||
for (let t = 0; t < terms.length; t++)
|
||||
if (s.indexOf(terms[t]) !== -1) sc += 3;
|
||||
if (wantsNumber && /\d/.test(sents[i])) sc += 4;
|
||||
if (hints.some(h => s.indexOf(h) !== -1)) sc += 4;
|
||||
if (sc > bestScore) { bestScore = sc; best = i; }
|
||||
}
|
||||
return {
|
||||
|
|
@ -1819,17 +1847,38 @@ in
|
|||
};
|
||||
}
|
||||
|
||||
// Which of the three candidates the query actually meant.
|
||||
// Search rank alone is not enough: "chernobyl" ranks the HBO
|
||||
// miniseries above the city.
|
||||
function pickPage(pages, query) {
|
||||
const ql = query.toLowerCase();
|
||||
const content = ql.split(/[^a-z0-9]+/)
|
||||
.filter(w => w.length >= 2 && answerStop.indexOf(w) === -1)
|
||||
.join(" ");
|
||||
// A title that IS the query beats whatever ranked first.
|
||||
const exact = pages.find(p => (p.title || "").toLowerCase() === content);
|
||||
if (exact !== undefined) return exact;
|
||||
// Otherwise skip a disambiguated title unless the query
|
||||
// asked for that sense ("chernobyl miniseries").
|
||||
const plain = pages.find(p => {
|
||||
const m = (p.title || "").match(/\(([^)]+)\)/);
|
||||
return m === null || ql.indexOf(m[1].toLowerCase()) !== -1;
|
||||
});
|
||||
return plain !== undefined ? plain : pages[0];
|
||||
}
|
||||
|
||||
function takeAnswer(raw) {
|
||||
if (answerDropNext) { answerDropNext = false; return; }
|
||||
let title = "";
|
||||
let extract = "";
|
||||
try {
|
||||
const pages = JSON.parse(raw).query.pages;
|
||||
for (const k in pages) { // single page under an unpredictable key
|
||||
title = pages[k].title || "";
|
||||
extract = pages[k].extract || "";
|
||||
break;
|
||||
}
|
||||
const obj = JSON.parse(raw).query.pages;
|
||||
let pages = [];
|
||||
for (const k in obj) pages.push(obj[k]); // keyed by pageid
|
||||
pages.sort((a, b) => a.index - b.index); // restore search order
|
||||
const p = pickPage(pages, answerQuery);
|
||||
title = p.title || "";
|
||||
extract = p.extract || "";
|
||||
} catch (e) {
|
||||
title = ""; // no match, or a throttled empty reply
|
||||
extract = "";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue