fix(voice): a missing-key error keeps its wording and hides the microphone hint after a language change (#1599)

The voice error was matched against the translated key message to decide
whether to show the microphone hint, so switching language brought the hint
back and left the message in the old language. The error now carries a
missing-key flag into the status, which words the message at render time.
A controller test covers the language switch, and CURRENT-STATE.md documents
the keyless voice response.
This commit is contained in:
Sameh Khamis committed 2026-11-10 16:11:30 -08:00
1 parent 0687e0e3da
commit f86b8954f6
4 files changed
+48 -6

No files matched your search

+30
View File
@@ -35,6 +35,7 @@ import {
} from './gevRealtime.js';
import { createVoiceCostTracker } from './voiceCost.js';
import { createTestVoiceUi } from '../testSupport/voiceUi.mjs';
import { setLocale } from '../i18n/index.js';
test('push-to-talk recognizes Space by code or key', () => {
assert.equal(PUSH_TO_TALK_HOLD_DELAY_MS, 500);
@@ -3734,3 +3735,32 @@ test('the controller forwards viewport escalation diagnostics to its debug log',
}],
);
});
test('a missing server key says where to add it, without the microphone hint, in every language', () => {
const ui = createTestVoiceUi();
const controller = new GevRealtimeController({
ui,
runner: async () => ({ ok: true }),
radioLayer: { subscribePlaybackControls: () => () => {}, setVoiceDucked() {} },
});
try {
controller.reportError(
'Realtime connection',
Object.assign(new Error('OPENAI_API_KEY is not set'), { code: 'API_KEY_MISSING' }),
);
assert.equal(ui.state.error.text, 'Needs OPENAI_API_KEY — add it in Provider Settings');
assert.equal(ui.state.error.hint, false);
setLocale('es');
assert.notEqual(ui.state.error.text, 'Needs OPENAI_API_KEY — add it in Provider Settings');
assert.match(ui.state.error.text, /OPENAI_API_KEY/);
assert.equal(ui.state.error.hint, false);
setLocale('en');
controller.reportError('Realtime connection', new Error('ICE failed'));
assert.match(ui.state.error.text, /ICE failed/);
assert.equal(ui.state.error.hint, true);
} finally {
setLocale('en');
}
});
+12 -4
View File
@@ -407,7 +407,14 @@ export class GevRealtimeController extends RealtimeFacade {
}
}
setStatus(status, detail) {
/**
* @param {string} status
* @param {string} [detail]
* @param {{missingKey?: boolean}} [options] True when the error is a
* missing server key: it is worded in the current language and shown
* without the microphone hint.
*/
setStatus(status, detail, { missingKey = false } = {}) {
this.status = status;
this.emitSessionEvent({ type: 'state', state: status, detail });
this.ui.update('root', {
@@ -435,11 +442,12 @@ export class GevRealtimeController extends RealtimeFacade {
this.ui.update('error', {
text: () =>
status === 'error'
? resolvedDetail() || t('Voice session could not be started.')
? (missingKey && missingVoiceKeyText()) ||
resolvedDetail() ||
t('Voice session could not be started.')
: '',
// "Check microphone permission" is wrong advice for a missing key.
hint: () =>
!(status === 'error' && resolvedDetail() === missingVoiceKeyText()),
hint: () => !(status === 'error' && missingKey),
});
if (status === 'idle' || status === 'connecting' || status === 'error') {
this.setVoiceSpeaker('idle');
+5 -1
View File
@@ -183,7 +183,11 @@ export class RealtimeDiagnostics {
storeErrors(this.errors);
console.error('[GEV Realtime]', record);
this.debugLog('error', record);
this.setStatus('error', formatErrorForDisplay(record));
this.setStatus(
'error',
formatErrorForDisplay(record),
record.code === 'API_KEY_MISSING' ? { missingKey: true } : undefined,
);
return record;
}