The ULTRON Agent voice system provides bidirectional voice communication with three components:
- Text-to-Speech (TTS): AI speaks responses using ElevenLabs API or browser fallback
- Speech-to-Text (STT): Microphone captures user voice commands via Web Speech API
- Voice Recognition: Continuous listening with automatic restart
File: gui/ultron_enhanced/web/app.js
Key Methods:
toggleVoice()- Main entry point for enabling/disabling voicetoggleVoiceChat(forceState)- Handles server sync and state managementstartVoiceRecognition()- Initializes Web Speech API microphonestopVoiceRecognition()- Stops microphone and cleanupspeakText(text)- Queues text for TTS playbackdequeueSpeech()- Processes TTS queue with fallback logic
State Variables:
this.voiceEnabled = false; // Master voice enable flag
this.isListening = false; // Microphone is actively listening
this.isSpeaking = false; // TTS is currently playing
this.shouldRestartRecognition = false; // Auto-restart mic after speech
this.recognition = null; // Web Speech API instance
this.ttsQueue = []; // Queued TTS messagesFile: voice.py
Responsibilities:
- ElevenLabs API integration for premium TTS
- pyttsx3 fallback for offline TTS
- Voice status tracking and event emission
- Audio format conversion and streaming
Key Classes:
class VoiceAssistant:
def __init__(self):
self.tts_enabled = False
self.stt_enabled = False
self.elevenlabs_client = None
self.pyttsx3_engine = None
self.voice_model = "e3mik6xHn4Sl51poljxK" # ElevenLabs voice IDAPI Endpoints:
POST /api/voice/toggle- Enable/disable voice (handled byweb_gui_server.py)POST /api/voice/speak- Queue text for TTS playbackGET /api/voice/status- Get current voice service status
File: web_gui_server.py
Voice Route Handlers:
# Line ~345: Voice toggle endpoint
def handle_voice_toggle_request(self):
"""Toggle voice assistant on/off"""
# Syncs with voice.py service
# Returns: {"voice_enabled": bool, "status": str}
# Line ~417: Voice synthesis endpoint
def handle_voice_speak_request(self):
"""Generate TTS audio for text"""
# 1. Try ElevenLabs API (premium)
# 2. Fallback to pyttsx3 (offline)
# 3. Return audio/mpeg or error-
Start ULTRON Agent:
.\run.bat -
Open Web GUI:
- Navigate to: http://localhost:8080
- Click "INITIATE LINK" on start screen
-
Enable Voice Button:
- Click the microphone icon in the top navigation bar
- Browser will prompt: "Allow microphone access?"
- Click "Allow"
-
Voice System Activated:
- System message: "Voice chat enabled. I am listening."
- Microphone starts continuous listening
- AI will speak responses using TTS
-
Disable Voice:
- Click microphone icon again
- System message: "Voice chat disabled"
- Microphone stops, TTS stops
The Web Speech API requires explicit user permission to access the microphone.
Permission States:
- β Granted: Voice recognition works
- β Denied: Error message shown, voice disabled
- β³ Prompt: Browser asks user on first click
Debugging Permission Issues:
// Check current permission state (Developer Console)
navigator.permissions.query({name: 'microphone'})
.then(result => console.log('Mic permission:', result.state));
// States: 'granted', 'denied', or 'prompt'Reset Permissions:
- Click the lock icon in browser address bar
- Find "Microphone" setting
- Change to "Ask" or "Allow"
- Reload page
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β USER INTERACTION β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. User Clicks Voice Button (Microphone Icon) β
β - Calls: toggleVoice() β toggleVoiceChat() β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Frontend Sends POST /api/voice/toggle β
β - Body: {"enable": true} β
β - web_gui_server.py receives request β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Backend Updates Voice Service (voice.py) β
β - Sets: voice_enabled = true β
β - Emits event: voice_status_changed β
β - Returns: {"voice_enabled": true, "status": "enabled"} β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. Frontend Receives Response β
β - Sets: this.voiceEnabled = true β
β - Calls: startVoiceRecognition() β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. Web Speech API Initialized β
β - Creates: new SpeechRecognition() β
β - Sets: continuous = true, lang = 'en-US' β
β - Starts: recognition.start() β
β - Browser prompts for microphone permission β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 6. User Grants Microphone Permission β
β - recognition.onstart fires β
β - Sets: this.isListening = true β
β - Message: "Listening for voice commandsβ¦" β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 7. User Speaks into Microphone β
β - recognition.onresult fires with transcript β
β - Calls: handleVoiceTranscript(transcript) β
β - Sends: sendChatMessage(text, {fromVoice: true}) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 8. AI Processes Command & Responds β
β - POST /api/llm/chat with user text β
β - LLM generates response β
β - Backend calls: voice.speak(response_text) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 9. TTS Playback β
β - Frontend calls: speakText(response) β
β - Queues text in: this.ttsQueue β
β - Calls: dequeueSpeech() β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 10. Audio Generation & Playback β
β - Try ElevenLabs API: POST /api/voice/speak β
β - Success: Play audio via <audio> element β
β - Failure: Fallback to browser speechSynthesis β
β - During playback: Microphone paused β
β - After playback: Microphone resumes β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Primary: ElevenLabs API
- Pros: High-quality, natural-sounding voice
- Cons: Requires API key, uses credits, network latency
- Implementation:
voice.py+web_gui_server.pyintegration - Audio Format: MP3, 44.1kHz, 128kbps
Fallback: Browser SpeechSynthesis
- Pros: Free, offline, no API required
- Cons: Robotic voice quality, browser-dependent
- Implementation:
app.jsWeb Speech API - Activation: Auto-enabled when ElevenLabs fails
CRITICAL: Fixed dual TTS bug (2025-10-24)
Problem (Before Fix):
async dequeueSpeech() {
try {
await audioElement.play(); // API TTS succeeds
} catch (error) {
speechSynthesis.speak(utterance); // Never runs
} finally {
// ALWAYS RUNS - processes queue again!
if (this.ttsQueue.length) this.dequeueSpeech();
}
}
// Result: BOTH API and browser TTS play simultaneouslySolution (After Fix):
async dequeueSpeech() {
try {
// API TTS
audioElement.onended = () => {
// Process queue ONLY in callback
if (this.ttsQueue.length) this.dequeueSpeech();
};
await audioElement.play();
return; // EXIT - don't run fallback or finally
} catch (error) {
// Browser TTS fallback
utterance.onend = () => {
// Process queue ONLY in callback
if (this.ttsQueue.length) this.dequeueSpeech();
};
speechSynthesis.speak(utterance);
return; // EXIT - don't run finally
} finally {
// Only runs if BOTH methods fail
if (this.ttsQueue.length) this.dequeueSpeech();
}
}
// Result: Only ONE TTS method playsKey Fix Points:
- β
Queue processing moved into
onended/onendcallbacks - β
Early
return;statements prevent fallback execution - β
finallyblock only runs if both TTS methods fail - β Microphone automatically pauses during TTS playback
- β Microphone automatically resumes after TTS completes
const recognition = new SpeechRecognition();
recognition.continuous = true; // Keep listening after each result
recognition.interimResults = false; // Only return final results
recognition.lang = 'en-US'; // Language setting
// Event Handlers:
recognition.onstart = () => {
// Microphone activated
this.isListening = true;
this.shouldRestartRecognition = true;
};
recognition.onresult = (event) => {
// Process speech-to-text results
const transcript = event.results[0][0].transcript;
handleVoiceTranscript(transcript);
};
recognition.onerror = (event) => {
// Handle errors (permission denied, no speech, etc.)
if (event.error === 'not-allowed') {
// Microphone permission denied by user
this.voiceEnabled = false;
}
};
recognition.onend = () => {
// Auto-restart if still enabled
this.isListening = false;
if (this.shouldRestartRecognition && this.voiceEnabled) {
recognition.start();
}
};Goal: Keep microphone active even during AI processing
Implementation:
- Recognition starts when voice enabled
onresultfires when user speaks- Transcript sent to LLM for processing
onendfires automatically after each result- If
this.shouldRestartRecognition === true, restart immediately - Cycle continues until user disables voice
Pause During TTS:
// In dequeueSpeech():
if (this.recognition && this.isListening) {
this.recognition.stop(); // Pause mic during AI speech
}
// In audioElement.onended or utterance.onend:
if (this.voiceRecognition && this.voiceEnabled) {
this.recognition.start(); // Resume mic after AI finishes
}1. Get API Key:
- Visit: https://elevenlabs.io/
- Sign up and copy API key from settings
2. Set Environment Variable:
# Windows PowerShell
$env:ELEVENLABS_APIKEY = "your_api_key_here"
# Or add to system environment variables permanently3. Update ultron_config.json:
{
"voice_enabled": true,
"elevenlabs_api_key": "USE_ENV_ELEVENLABS_APIKEY",
"voice_engine": "elevenlabs",
"tts_engine": "elevenlabs",
"stt_engine": "whisper",
"voice_model": "e3mik6xHn4Sl51poljxK"
}4. Verify Connection:
# Check voice.py logs
tail -f logs/voice.log
# Expected output:
# β
ElevenLabs connected successfully
# 2025-10-24 16:21:22 - voice - INFO - ElevenLabs initialized successfully with 32 voices available| Setting | Description | Default | Options |
|---|---|---|---|
voice_enabled |
Master voice toggle | false |
true/false |
voice_engine |
TTS provider | "elevenlabs" |
"elevenlabs", "pyttsx3" |
stt_engine |
STT provider | "whisper" |
"whisper", "browser" |
voice_model |
ElevenLabs voice ID | "e3mik6xHn4Sl51poljxK" |
Any ElevenLabs voice ID |
tts_engine |
Text-to-speech backend | "elevenlabs" |
"elevenlabs", "pyttsx3" |
Symptoms:
- Clicking voice button does nothing
- Browser doesn't prompt for microphone
- Error: "Voice recognition is not supported"
Solutions:
-
Check Browser Compatibility:
- Chrome/Edge: β Full support
- Firefox:
β οΈ Limited support - Safari:
β οΈ Requires webkit prefix
-
Check Microphone Permission:
// In browser console: navigator.permissions.query({name: 'microphone'}) .then(result => console.log(result.state));
-
Reset Browser Permission:
- Chrome:
chrome://settings/content/microphone - Edge:
edge://settings/content/microphone - Allow
localhost:8080
- Chrome:
-
Check Hardware:
# Windows: Test microphone in Settings Start-Process ms-settings:sound
Symptoms:
- AI responds but no audio plays
- Error: "Voice synthesis unavailable"
Solutions:
-
Check ElevenLabs Credits:
- Login to: https://elevenlabs.io/
- Verify credits remaining
- Expected usage: ~250 credits per response
-
Check API Key:
# Verify environment variable set echo $env:ELEVENLABS_APIKEY
-
Check Logs:
# View voice service logs tail -f logs/voice.log # Expected output on success: # 2025-10-24 16:25:16 - voice - INFO - TTS initiated for AI response # Error output on API failure: # 2025-10-24 16:25:17 - voice - WARNING - ElevenLabs TTS failed: quota_exceeded
-
Test Browser Fallback:
// In browser console: const utterance = new SpeechSynthesisUtterance('Test'); window.speechSynthesis.speak(utterance);
Status: β FIXED (2025-10-24)
If Still Occurring:
- Hard refresh browser:
Ctrl+Shift+R - Clear browser cache completely
- Check
app.jsLine 1841 for fix:// Must have early return after successful TTS: await this.audioElement.play(); return; // <-- MUST BE HERE
Status: β FIXED (2025-10-24)
If Still Occurring:
-
Check
app.jsLine 360:async handleStartupAnnouncement() { this.voiceEnabled = false; // <-- MUST BE FALSE // No speakText() call here }
-
Check
app.jsLine 520:// Must NOT auto-enable from server status const voiceStatusText = (voiceSnapshot.status || 'DISABLED').toUpperCase(); // No this.voiceEnabled = true assignment
Step 1: Update voice.py
class VoiceAssistant:
async def speak(self, text: str):
if self.tts_engine == "elevenlabs":
return await self._elevenlabs_speak(text)
elif self.tts_engine == "new_provider":
return await self._new_provider_speak(text)
else:
return await self._fallback_speak(text)Step 2: Add API integration
async def _new_provider_speak(self, text: str):
# Implement new provider API calls
response = await self.new_provider_client.synthesize(text)
return response.audio_contentStep 3: Update config options in ultron_config.json
Unit Test Voice Toggle:
# tests/test_voice.py
async def test_voice_toggle():
voice = VoiceAssistant()
# Test enable
result = await voice.toggle(True)
assert result["voice_enabled"] == True
# Test disable
result = await voice.toggle(False)
assert result["voice_enabled"] == FalseIntegration Test Full Flow:
async def test_voice_conversation():
# 1. Enable voice
response = await client.post("/api/voice/toggle", json={"enable": True})
assert response.json()["voice_enabled"] == True
# 2. Send voice command
response = await client.post("/api/llm/chat", json={"message": "Hello"})
assert "response" in response.json()
# 3. Verify TTS called
# Check logs for TTS initiationvoice.py- Backend voice service (ElevenLabs, pyttsx3)gui/ultron_enhanced/web/app.js- Frontend voice controllerweb_gui_server.py- Voice API endpoints (Lines 345, 417)ultron_config.json- Voice configuration settings
.github/copilot-instructions.md- Main developer guideFIXES_SUMMARY_2025-10-24.md- Recent voice fixesGUI_DOCUMENTATION.md- GUI interaction patterns
logs/voice.log- Voice service logslogs/web_gui_server.log- API endpoint logsultron_master_startup.log- System startup logs
Before reporting voice issues, verify:
- Services Running:
run.batcompleted successfully - Browser Compatible: Using Chrome/Edge (not Firefox/Safari)
- Microphone Permission: Granted in browser settings
- Hardware Working: Test microphone in OS settings
- API Key Set:
$env:ELEVENLABS_APIKEYexists - Credits Available: ElevenLabs account has credits
- Network Connection: Can reach https://api.elevenlabs.io
- Logs Clean: No errors in
logs/voice.log - Port 8080 Open: Web GUI accessible at localhost:8080
- Latest Code: Hard refresh browser (
Ctrl+Shift+R)
Document Version: 1.0 Last Updated: 2025-10-24 17:35 UTC Status: β Voice & Microphone Fully Functional Recent Fixes: Dual TTS prevention, Auto-enable prevention
- Open http://localhost:8080
- Click microphone icon in nav bar
- Allow browser microphone permission
- Speak commands naturally
- Click microphone icon again
- Voice stops listening immediately
- π€ Gray/Off: Voice disabled
- π€ Green/On: Voice enabled, listening
- π€ Red/Error: Permission denied or error
- "What time is it?"
- "Run system diagnostics"
- "Open LLM chat"
- "Show me the tools"
- "Execute [tool name]"
For Support: Check logs/voice.log and browser console for detailed error messages.