import { t } from '../i18n/index.js'; // The assistant's name on its card. i18n-ignore: product name const ASSISTANT_NAME = 'GEV'; /** * Short state label for the narrow voice heading. The `voice` context lets * languages use shorter words; unknown states show upper-cased. * @param {string} status machine state (dataset.status) * @returns {string} */ export function voiceStatusLabel(status) { switch (status) { case 'idle': return t('OFF', { context: 'voice' }); case 'connecting': return t('CONNECTING', { context: 'voice' }); case 'listening': return t('LISTENING', { context: 'voice' }); case 'executing': return t('EXECUTING', { context: 'voice' }); case 'error': return t('ERROR', { context: 'voice' }); default: return String(status || '').toUpperCase(); } } /** Build the voice control independently of its connection backend. */ export function createVoiceControl({ reset = false, panel = false } = {}) { let root = document.getElementById('gev-voice-control'); if (root && reset) { root.remove(); root = null; } if (!root) { root = document.createElement('div'); root.id = 'gev-voice-control'; root.dataset.status = 'idle'; root.dataset.speaker = 'idle'; root.innerHTML = ` ${panel ? `` : ''}
${t('AI AGENT')}
${voiceStatusLabel('idle')}
${ panel ? `` : `
~$0.00
` }
${t('VOICE STANDBY')}
${ panel ? `` : '' } `; const commandDock = document.getElementById('command-dock'); if (commandDock) { const locationBar = document.getElementById('location-bar'); const controlPanel = document.getElementById('control-panel'); commandDock.appendChild(root); if (locationBar) commandDock.insertBefore(locationBar, root); if (controlPanel) commandDock.appendChild(controlPanel); } else { document.body.appendChild(root); } root .querySelector('.gev-voice-error-dismiss') ?.addEventListener('click', () => { root.classList.add('error-dismissed'); }); } return { root, voicePanel: root.querySelector('#gev-voice-panel'), previewPanel: root.querySelector('#gev-voice-preview-panel'), voiceSettingsPanel: root.querySelector('#gev-voice-settings-panel'), previewTab: root.querySelector('#gev-voice-preview-tab'), settingsTab: root.querySelector('#gev-voice-settings-tab'), voiceSettingsButton: root.querySelector('#gev-voice-settings-button'), voiceSettingsClose: root.querySelector('#gev-voice-settings-close'), previewEmpty: root.querySelector('#gev-voice-preview-empty'), voicePreviewInput: root.querySelector('#gev-voice-preview-enabled'), voicePreviewNote: root.querySelector('#gev-voice-preview-note'), voiceTimeoutSelect: root.querySelector('#gev-voice-timeout-select'), voiceTimeoutInput: root.querySelector('#gev-voice-timeout-input'), voiceTimeoutNote: root.querySelector('#gev-voice-timeout-note'), voiceTimeoutStatus: root.querySelector('#gev-voice-timeout-status'), providerField: root.querySelector('#gev-voice-provider-field'), providerSelect: root.querySelector('#gev-voice-provider'), button: root.querySelector('#gev-voice-button'), buttonLabel: root.querySelector('.gev-mic-label'), status: root.querySelector('#gev-voice-status'), detail: root.querySelector('#gev-voice-detail'), helpDetail: root.querySelector('.gev-voice-help-detail'), errorDetail: root.querySelector('#gev-voice-error-detail'), tierButton: root.querySelector('#gev-voice-tier'), costValue: root.querySelector('#gev-voice-cost-value'), costWarning: root.querySelector('#gev-voice-cost-warning'), card: root.querySelector('#gev-voice-card'), }; }