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
|
# title-only endpoints return nothing. --data-urlencode keeps the query
|
||||||
# out of the URL literal.
|
# 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" —
|
# Prints nothing on any failure, which the QML treats as "no answer" —
|
||||||
# including the empty 4xx/5xx that Wikipedia returns when a burst of
|
# including the empty 4xx/5xx that Wikipedia returns when a burst of
|
||||||
# requests gets throttled.
|
# requests gets throttled.
|
||||||
|
|
@ -241,7 +244,7 @@ in
|
||||||
${pkgs.curl}/bin/curl -sf --max-time 6 -G \
|
${pkgs.curl}/bin/curl -sf --max-time 6 -G \
|
||||||
--data-urlencode "gsrsearch=$q" \
|
--data-urlencode "gsrsearch=$q" \
|
||||||
-H 'User-Agent: quickshell-launcher/1.0 (personal desktop use)' \
|
-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
|
# cava in raw mode: one newline-terminated frame per tick, bars as
|
||||||
|
|
@ -1698,6 +1701,22 @@ in
|
||||||
"speed", "fast", "temperature", "cost", "price", "year",
|
"speed", "fast", "temperature", "cost", "price", "year",
|
||||||
"when", "many", "much", "capital", "big"
|
"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
|
// "firefox" and "vs code" are app searches; "mount everest
|
||||||
// height" is a question. Two words plus some length sorts
|
// height" is a question. Two words plus some length sorts
|
||||||
|
|
@ -1796,10 +1815,18 @@ in
|
||||||
|
|
||||||
const ql = query.toLowerCase();
|
const ql = query.toLowerCase();
|
||||||
const tw = title.toLowerCase().split(/[^a-z0-9]+/);
|
const tw = title.toLowerCase().split(/[^a-z0-9]+/);
|
||||||
const terms = ql.split(/[^a-z0-9]+/).filter(w =>
|
const words = ql.split(/[^a-z0-9]+/);
|
||||||
w.length > 2 && answerStop.indexOf(w) === -1
|
// 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));
|
&& (answerAttrs.indexOf(w) !== -1 || tw.indexOf(w) === -1));
|
||||||
const wantsNumber = answerAttrs.some(a => ql.indexOf(a) !== -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 best = 0;
|
||||||
let bestScore = -1;
|
let bestScore = -1;
|
||||||
|
|
@ -1809,6 +1836,7 @@ in
|
||||||
for (let t = 0; t < terms.length; t++)
|
for (let t = 0; t < terms.length; t++)
|
||||||
if (s.indexOf(terms[t]) !== -1) sc += 3;
|
if (s.indexOf(terms[t]) !== -1) sc += 3;
|
||||||
if (wantsNumber && /\d/.test(sents[i])) sc += 4;
|
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; }
|
if (sc > bestScore) { bestScore = sc; best = i; }
|
||||||
}
|
}
|
||||||
return {
|
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) {
|
function takeAnswer(raw) {
|
||||||
if (answerDropNext) { answerDropNext = false; return; }
|
if (answerDropNext) { answerDropNext = false; return; }
|
||||||
let title = "";
|
let title = "";
|
||||||
let extract = "";
|
let extract = "";
|
||||||
try {
|
try {
|
||||||
const pages = JSON.parse(raw).query.pages;
|
const obj = JSON.parse(raw).query.pages;
|
||||||
for (const k in pages) { // single page under an unpredictable key
|
let pages = [];
|
||||||
title = pages[k].title || "";
|
for (const k in obj) pages.push(obj[k]); // keyed by pageid
|
||||||
extract = pages[k].extract || "";
|
pages.sort((a, b) => a.index - b.index); // restore search order
|
||||||
break;
|
const p = pickPage(pages, answerQuery);
|
||||||
}
|
title = p.title || "";
|
||||||
|
extract = p.extract || "";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
title = ""; // no match, or a throttled empty reply
|
title = ""; // no match, or a throttled empty reply
|
||||||
extract = "";
|
extract = "";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue