mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
fix(hud): catch the UNAVAILABLE word flush against Han and keep multi-word names whole
Han and kana have no word edges, so a CJK forbidden word now counts anywhere
in the reply. The proxy no longer counts words: the model reads Rue de Rivoli
as one, so only the 80-character word-boundary cap remains.
Also restore src/voice/gevActions.test.mjs, reformatted by mistake in 818d2c82.
This commit is contained in:
1 parent
818d2c8295
commit
645e13aeaa
5 files changed
+645
-2164
No files matched your search
@@ -37,12 +37,12 @@ function parseHudSummaryReply(text, locale) {
|
||||
? reply.feedState
|
||||
: null;
|
||||
return {
|
||||
summary: toFiveWordHudSummary(reply?.summary, locale),
|
||||
summary: toHudSummary(reply?.summary, locale),
|
||||
feedState,
|
||||
};
|
||||
}
|
||||
|
||||
function toFiveWordHudSummary(value, requested) {
|
||||
function toHudSummary(value, requested) {
|
||||
const locale = hudSummaryLocale(requested);
|
||||
const words = String(value || '')
|
||||
.normalize('NFC')
|
||||
@@ -55,7 +55,8 @@ function toFiveWordHudSummary(value, requested) {
|
||||
// Japanese and Chinese have no spaces to count words by: cap characters.
|
||||
if (hudSummaryIsUnspaced(locale))
|
||||
return capAtWord(words.join(' '), HUD_SUMMARY_MAX_UNSPACED_CHARS, locale);
|
||||
return capAtWord(words.slice(0, 5).join(' '), HUD_SUMMARY_MAX_CHARS, locale);
|
||||
// No word count: the model counts Rue de Rivoli as one word.
|
||||
return capAtWord(words.join(' '), HUD_SUMMARY_MAX_CHARS, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,4 +171,4 @@ async function handleHudSummary(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
export { handleHudSummary };
|
||||
export { handleHudSummary, parseHudSummaryReply };
|
||||
@@ -258,6 +258,22 @@ test('with every feed healthy a reply calling one unavailable falls back', async
|
||||
assert.equal(shown(), hud._composeSummary());
|
||||
});
|
||||
|
||||
for (const [locale, place, summary] of [
|
||||
['zh-CN', '北京', '北京航班不可用'],
|
||||
['zh-Hant', '北京', '北京航班不可用'],
|
||||
['ja', '東京', '東京航空便利用不可'],
|
||||
]) {
|
||||
test(`a ${locale} reply calling a feed unavailable flush against Han falls back`, async (t) => {
|
||||
const { hud, shown } = createHud(
|
||||
t,
|
||||
{ summary, feedState: null },
|
||||
{ locale, places: [place], layers: () => [] },
|
||||
);
|
||||
await hud._updateSummary(false, true);
|
||||
assert.equal(shown(), hud._composeSummary());
|
||||
});
|
||||
}
|
||||
|
||||
test('an ar reply opening with a Latin name keeps reading right to left', async (t) => {
|
||||
const { hud, shown } = createHud(
|
||||
t,
|
||||
|
||||
@@ -72,7 +72,7 @@ export const HUD_SUMMARY_INSTRUCTIONS = [
|
||||
/** Longest summary, in characters, for a script written without spaces. */
|
||||
export const HUD_SUMMARY_MAX_UNSPACED_CHARS = 20;
|
||||
|
||||
/** Longest five-word summary, in characters. */
|
||||
/** Longest summary, in characters; the cut falls between words. */
|
||||
export const HUD_SUMMARY_MAX_CHARS = 80;
|
||||
|
||||
/** feedState values the model may return besides null. */
|
||||
@@ -142,6 +142,9 @@ function flatWords(value) {
|
||||
.trim();
|
||||
}
|
||||
|
||||
const UNSPACED_WORD =
|
||||
/^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー]+$/u;
|
||||
|
||||
function sameScript(a, b) {
|
||||
return WORD_SCRIPTS.some((script) => script.test(a ?? '') && script.test(b));
|
||||
}
|
||||
@@ -149,6 +152,7 @@ function sameScript(a, b) {
|
||||
/**
|
||||
* Whether `word` stands on its own in `text`. Only a letter of the same
|
||||
* script extends it, so `stale` flush against kana or Han still counts.
|
||||
* Han and kana have no word edges: a word in them counts anywhere.
|
||||
* @param {string} text
|
||||
* @param {string} word
|
||||
* @returns {boolean}
|
||||
@@ -157,6 +161,7 @@ export function hasWord(text, word) {
|
||||
const haystack = flatWords(text);
|
||||
const needle = flatWords(word);
|
||||
if (!needle) return false;
|
||||
if (UNSPACED_WORD.test(needle)) return haystack.includes(needle);
|
||||
for (
|
||||
let at = haystack.indexOf(needle);
|
||||
at >= 0;
|
||||
|
||||
@@ -373,7 +373,21 @@ for (const [locale, summary] of [
|
||||
});
|
||||
}
|
||||
|
||||
test('a five-word summary is capped in characters too', async () => {
|
||||
for (const [locale, summary] of [
|
||||
['fr', 'Rue de Rivoli Vols en direct'],
|
||||
['es', 'Times Square Nueva York Vuelos en vivo'],
|
||||
['ru', 'Times Square Рейсы онлайн и суда'],
|
||||
]) {
|
||||
test(`a ${locale} summary keeps a multi-word name whole: ${summary}`, async () => {
|
||||
// The parser itself: the file's HUD requests share one rate limiter.
|
||||
const { parseHudSummaryReply } =
|
||||
await import('../server/providers/openai/hud-summary.js');
|
||||
const reply = JSON.stringify({ summary, feedState: 'partial' });
|
||||
assert.equal(parseHudSummaryReply(reply, locale).summary, summary);
|
||||
});
|
||||
}
|
||||
|
||||
test('a summary is capped in characters', async () => {
|
||||
const { response } = await postHudSummary(
|
||||
{ locale: 'de', feedProvenance: { overall: null } },
|
||||
JSON.stringify({ summary: `Berlin ${'x'.repeat(200)}`, feedState: null }),
|
||||
@@ -386,7 +400,7 @@ for (const [locale, summary] of [
|
||||
['es', 'Vuelos sobre 東京駅 datos antiguos'],
|
||||
['ru', 'Башня Тайбэй 101 台北101 рейсы'],
|
||||
]) {
|
||||
test(`a ${locale} summary with a Han place name keeps its five words`, async () => {
|
||||
test(`a ${locale} summary with a Han place name keeps its words`, async () => {
|
||||
const { response } = await postHudSummary(
|
||||
{ locale, feedProvenance: { overall: 'stale' } },
|
||||
JSON.stringify({ summary, feedState: 'stale' }),
|
||||
|
||||
+602
-2157
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user