mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +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
|
? reply.feedState
|
||||||
: null;
|
: null;
|
||||||
return {
|
return {
|
||||||
summary: toFiveWordHudSummary(reply?.summary, locale),
|
summary: toHudSummary(reply?.summary, locale),
|
||||||
feedState,
|
feedState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function toFiveWordHudSummary(value, requested) {
|
function toHudSummary(value, requested) {
|
||||||
const locale = hudSummaryLocale(requested);
|
const locale = hudSummaryLocale(requested);
|
||||||
const words = String(value || '')
|
const words = String(value || '')
|
||||||
.normalize('NFC')
|
.normalize('NFC')
|
||||||
@@ -55,7 +55,8 @@ function toFiveWordHudSummary(value, requested) {
|
|||||||
// Japanese and Chinese have no spaces to count words by: cap characters.
|
// Japanese and Chinese have no spaces to count words by: cap characters.
|
||||||
if (hudSummaryIsUnspaced(locale))
|
if (hudSummaryIsUnspaced(locale))
|
||||||
return capAtWord(words.join(' '), HUD_SUMMARY_MAX_UNSPACED_CHARS, 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());
|
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) => {
|
test('an ar reply opening with a Latin name keeps reading right to left', async (t) => {
|
||||||
const { hud, shown } = createHud(
|
const { hud, shown } = createHud(
|
||||||
t,
|
t,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const HUD_SUMMARY_INSTRUCTIONS = [
|
|||||||
/** Longest summary, in characters, for a script written without spaces. */
|
/** Longest summary, in characters, for a script written without spaces. */
|
||||||
export const HUD_SUMMARY_MAX_UNSPACED_CHARS = 20;
|
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;
|
export const HUD_SUMMARY_MAX_CHARS = 80;
|
||||||
|
|
||||||
/** feedState values the model may return besides null. */
|
/** feedState values the model may return besides null. */
|
||||||
@@ -142,6 +142,9 @@ function flatWords(value) {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UNSPACED_WORD =
|
||||||
|
/^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー]+$/u;
|
||||||
|
|
||||||
function sameScript(a, b) {
|
function sameScript(a, b) {
|
||||||
return WORD_SCRIPTS.some((script) => script.test(a ?? '') && script.test(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
|
* 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.
|
* 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} text
|
||||||
* @param {string} word
|
* @param {string} word
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
@@ -157,6 +161,7 @@ export function hasWord(text, word) {
|
|||||||
const haystack = flatWords(text);
|
const haystack = flatWords(text);
|
||||||
const needle = flatWords(word);
|
const needle = flatWords(word);
|
||||||
if (!needle) return false;
|
if (!needle) return false;
|
||||||
|
if (UNSPACED_WORD.test(needle)) return haystack.includes(needle);
|
||||||
for (
|
for (
|
||||||
let at = haystack.indexOf(needle);
|
let at = haystack.indexOf(needle);
|
||||||
at >= 0;
|
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(
|
const { response } = await postHudSummary(
|
||||||
{ locale: 'de', feedProvenance: { overall: null } },
|
{ locale: 'de', feedProvenance: { overall: null } },
|
||||||
JSON.stringify({ summary: `Berlin ${'x'.repeat(200)}`, feedState: null }),
|
JSON.stringify({ summary: `Berlin ${'x'.repeat(200)}`, feedState: null }),
|
||||||
@@ -386,7 +400,7 @@ for (const [locale, summary] of [
|
|||||||
['es', 'Vuelos sobre 東京駅 datos antiguos'],
|
['es', 'Vuelos sobre 東京駅 datos antiguos'],
|
||||||
['ru', 'Башня Тайбэй 101 台北101 рейсы'],
|
['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(
|
const { response } = await postHudSummary(
|
||||||
{ locale, feedProvenance: { overall: 'stale' } },
|
{ locale, feedProvenance: { overall: 'stale' } },
|
||||||
JSON.stringify({ summary, feedState: '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