mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
fix(hud): ask again in the new language after an in-place switch
- _relocalize() now drops the received reply and aborts one in flight, so the next tick asks in the new language instead of keeping old-language text under the new-language state tag. - The English-echo check also allows state words inside a place label (Estación Loading Dock). - Reword the non-English instruction so it reads for every language name. - Add the two new test files to the format scope.
This commit is contained in:
1 parent
1a6687632d
commit
0d3c1ef1d9
5 files changed
+64
-8
No files matched your search
@@ -5365,8 +5365,8 @@ and unreachable upstream (502/504) separately from road geometry.
|
||||
|
||||
- HUD `SUMMARY` readout requests a five-word intelligence-style summary from `/api/openai/hud-summary` (model `OPENAI_HUD_SUMMARY_MODEL`, default `gpt-5-nano`, minimal reasoning).
|
||||
- Input is the live basemap label context (place/street/nearby-place labels + enabled layers) and `feedProvenance`, with every layer name as the user sees it; outside English the narration note and the per-layer state ids are left out (`overall` still carries the state), since a small model copies English words. The model is instructed not to infer from coordinates. A view with no place, street or nearby-place label is not sent, since with layers alone the model invents a place: the deterministic line is shown.
|
||||
- Language (October 2026): the HUD sends `locale`, which is part of the dedupe signature. The proxy keeps a shipped locale (English otherwise) and names the language in the instructions and at the start of the input; Japanese and Chinese get a 20-character phrase instead of five words.
|
||||
- The reply is structured (`text.format` JSON schema): `{summary, feedState}`, `feedState` being the snapshot state or `none`. For a view that is not nominal, a missing or different `feedState` falls back to the deterministic line. An accepted summary and the deterministic line carry the HUD's own translated state: the state word leads and the layer names trail (`STALE | … | LIVE FLIGHTS`), so the one-line readout never clips the warning; the text and then the layer names may be cut off on narrow layouts. Outside English a reply that echoes a feed-state id or an English layer name the UI translates falls back too, even written flush against kana or Han; a state word the locale itself keeps in English (de `FALLBACK`) and a name inside a place label do not count. `get_hud_caption` builds its caption the same way and returns `feedState`.
|
||||
- Language (October 2026): the HUD sends `locale`, which is part of the dedupe signature. The proxy keeps a shipped locale (English otherwise) and names the language in the instructions and at the start of the input; Japanese and Chinese get a 20-character phrase instead of five words. Switching language in place drops the reply received and aborts one in flight, so the next tick asks again in the new language.
|
||||
- The reply is structured (`text.format` JSON schema): `{summary, feedState}`, `feedState` being the snapshot state or `none`. For a view that is not nominal, a missing or different `feedState` falls back to the deterministic line. An accepted summary and the deterministic line carry the HUD's own translated state: the state word leads and the layer names trail (`STALE | … | LIVE FLIGHTS`), so the one-line readout never clips the warning; the text and then the layer names may be cut off on narrow layouts. Outside English a reply that echoes a feed-state id or an English layer name the UI translates falls back too, even written flush against kana or Han; a state word the locale itself keeps in English (de `FALLBACK`) and words inside a place label do not count. `get_hud_caption` builds its caption the same way and returns `feedState`.
|
||||
- Output is sanitized to five words, or for a Japanese or Chinese request locale to 20 characters (stepping back to the start of a Latin word rather than splitting it); falls back to the deterministic telemetry summary on error/timeout (5s abort); typewriter animation on update; a camera move during typing shows the deterministic line instead of the fragment.
|
||||
- `partial` is a feed state of its own, ranked between `fallback` and `nominal`, so one partial layer no longer reads as off and partial plus nominal reads partial (HUD, voice view state, `analyst_query`, `brief`, the agent world packet). Its narration note says coverage is incomplete rather than not live.
|
||||
- A summary asked for before the camera starts moving never paints over the next view: `moveStart` marks the summary dirty and bumps its revision, as `moveEnd` does, aborts the request in flight and stops a reply still typing; neither a reply nor a fallback line (on an error, the 5 s timeout, the abort, or a failed context build) paints once the revision has moved. A dropped request forgets its signature and a timeout counts as a failure, so the next 15 s tick asks again even over the same place. An interrupted typewriter reply also forgets its signature; fully displayed answers remain reusable.
|
||||
|
||||
@@ -1109,6 +1109,8 @@
|
||||
"src/voice/globeResetConfirmation.test.mjs",
|
||||
"src/hudDms.test.mjs",
|
||||
"src/hudSummaryGate.test.mjs",
|
||||
"src/hudSummaryLine.test.mjs",
|
||||
"src/tools/queries/hud-caption-feed-state.test.mjs",
|
||||
"src/voice/hudPlaceLabels.test.mjs",
|
||||
"src/voice/panel/preferences.test.mjs",
|
||||
"src/voice/panel/preferencesView.test.mjs",
|
||||
|
||||
+12
-5
@@ -132,7 +132,8 @@ function withProvenance(text, provenance) {
|
||||
/**
|
||||
* The English a summary outside English must not carry: feed-state ids and
|
||||
* translated layers' English names. A state word the locale keeps in English
|
||||
* (de FALLBACK) and a layer name inside a place label (Radio City) are fine.
|
||||
* (de FALLBACK) and words inside a place label (Radio City, Loading Dock) are
|
||||
* fine.
|
||||
*/
|
||||
function summaryEnglishWords(context) {
|
||||
const places = [
|
||||
@@ -144,7 +145,9 @@ function summaryEnglishWords(context) {
|
||||
.toLowerCase();
|
||||
return [
|
||||
...Object.keys(FEED_STATE_LABELS).filter(
|
||||
(state) => t(FEED_STATE_LABELS[state]).toLowerCase() !== state,
|
||||
(state) =>
|
||||
t(FEED_STATE_LABELS[state]).toLowerCase() !== state &&
|
||||
!places.includes(state),
|
||||
),
|
||||
...(context.translatedLayerNames || []).filter(
|
||||
(name) => !places.includes(name.toLowerCase()),
|
||||
@@ -323,11 +326,15 @@ export class IntelHUD {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the HUD's own words in the new locale. The deterministic summary
|
||||
* is composed again; the AI reply does not depend on the locale, so the
|
||||
* next tick shows the one already received rather than asking again.
|
||||
* Show the HUD's own words in the new locale. The AI reply is written in
|
||||
* the locale too, so the one received is dropped like a camera move drops
|
||||
* it: a request in flight is aborted and the next tick asks again.
|
||||
*/
|
||||
_relocalize() {
|
||||
this._markSummaryDirty();
|
||||
this._lastSummarySignature = null;
|
||||
this._lastSummaryText = null;
|
||||
this._summaryRequest?.abort();
|
||||
this._present({
|
||||
mode: MODE_LABELS[this._currentStyle]
|
||||
? t(MODE_LABELS[this._currentStyle])
|
||||
|
||||
@@ -185,6 +185,7 @@ for (const [locale, summary, places] of [
|
||||
['es', 'Tráfico aéreo nominal sobre Madrid', ['Madrid']],
|
||||
['de', 'Fallback Daten über Berlin', ['Berlin']],
|
||||
['ja', 'Live Flights Plaza周辺', ['Live Flights Plaza']],
|
||||
['es', 'Estación Loading Dock Madrid', ['Estación Loading Dock']],
|
||||
]) {
|
||||
test(`a ${locale} word that is not an echo is shown: ${summary}`, async (t) => {
|
||||
const { hud, shown } = createHud(
|
||||
@@ -196,3 +197,49 @@ for (const [locale, summary, places] of [
|
||||
assert.ok(shown().includes(summary), shown());
|
||||
});
|
||||
}
|
||||
|
||||
test('a language switch drops the reply in flight and asks again', async (t) => {
|
||||
const { hud, sent, shown } = createHud(t, {});
|
||||
let answer;
|
||||
hud.summaryService.summarize = (context, { signal }) => {
|
||||
sent.push(context);
|
||||
return new Promise((resolve, reject) => {
|
||||
answer = () =>
|
||||
resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
data: {
|
||||
error: null,
|
||||
summary: 'Остин рейсы частично',
|
||||
feedState: 'partial',
|
||||
},
|
||||
});
|
||||
signal.addEventListener('abort', () =>
|
||||
reject(Object.assign(new Error('aborted'), { name: 'AbortError' })),
|
||||
);
|
||||
});
|
||||
};
|
||||
const pending = hud._updateSummary(false, true);
|
||||
while (!answer) await new Promise((resolve) => setImmediate(resolve));
|
||||
setLocale('es');
|
||||
answer();
|
||||
await pending;
|
||||
assert.ok(!shown().includes('Остин'), shown());
|
||||
assert.equal(shown(), hud._composeSummary());
|
||||
|
||||
hud.summaryService.summarize = async (context) => {
|
||||
sent.push(context);
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
data: {
|
||||
error: null,
|
||||
summary: 'Austin vuelos parciales',
|
||||
feedState: 'partial',
|
||||
},
|
||||
};
|
||||
};
|
||||
await hud._updateSummary(false);
|
||||
assert.equal(sent.at(-1).locale, 'es');
|
||||
assert.ok(shown().startsWith('PARCIAL | Austin vuelos parciales'), shown());
|
||||
});
|
||||
@@ -165,7 +165,7 @@ export function hudSummaryInstructions(locale) {
|
||||
const length = hudSummaryIsUnspaced(tag)
|
||||
? ` Instead of five words, write one phrase of at most ${HUD_SUMMARY_MAX_UNSPACED_CHARS} characters.`
|
||||
: '';
|
||||
return `${HUD_SUMMARY_INSTRUCTIONS} Write the summary in ${language} (${tag}), not English, giving places their usual ${language} names and layers the names in enabledLayerLabels; feedState stays the English value.${length}`;
|
||||
return `${HUD_SUMMARY_INSTRUCTIONS} Write the summary in ${language} (${tag}), not English. Give places their usual names in that language and layers the names in enabledLayerLabels; feedState stays the English value.${length}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user