refactor: separate voice controls and configurable Realtime connections

This commit is contained in:
Sameh Khamis committed 2026-09-13 21:54:51 -07:00
1 parent 0ba00f4662
commit 1a1cbadf5b
21 files changed
+3126 -2793

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
/** Build the voice control independently of its connection backend. */
export function createVoiceControl({ reset = 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 = `
<div class="gev-voice-heading">
<div class="gev-voice-kicker">AI AGENT</div>
<div id="gev-voice-status">OFF</div>
<div class="gev-voice-cost">
<button id="gev-voice-tier" class="gev-voice-tier-btn" type="button" aria-pressed="false" title="Voice model tier — applies next session">STD</button>
<span id="gev-voice-cost-value" class="gev-voice-cost-value" data-level="ok" title="Estimated session cost">~$0.00</span>
</div>
</div>
<button id="gev-voice-button" type="button" aria-label="Voice control — activate to toggle voice; hold Space to speak" aria-describedby="gev-voice-help">
<span class="gev-mic-orbit"><img src="/mic.svg" alt="" /></span>
<span class="gev-mic-label">ON/OFF</span>
</button>
<div class="gev-voice-visualizer" aria-hidden="true">
${Array.from({ length: 15 }, (_, index) => `<span style="--bar:${index}"></span>`).join('')}
</div>
<div class="gev-voice-readout">
<div id="gev-voice-detail">VOICE STANDBY</div>
</div>
<div id="gev-voice-help" class="gev-voice-help-tray" role="tooltip">
<span class="gev-voice-help-kicker">VOICE CONTROL</span>
<span class="gev-voice-help-detail">Hold Space to speak · tap Space to activate focused controls</span>
</div>
<div class="gev-voice-error-tray" role="alert" aria-live="assertive">
<div class="gev-voice-error-header">
<span>VOICE SYSTEM ERROR</span>
<button class="gev-voice-error-dismiss" type="button">DISMISS</button>
</div>
<div id="gev-voice-error-detail"></div>
<div class="gev-voice-error-hint">Check microphone permission and network access, then try again.</div>
</div>
`;
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,
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'),
};
}