Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion python/voice-live-avatar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ The avatar feature is currently available in the following service regions: Sout

- `AZURE_VOICELIVE_ENDPOINT` - Your Azure AI Services endpoint
- `AZURE_VOICELIVE_API_KEY` - Your API key
- `VOICELIVE_MODEL` - Model to use (default: `gpt-4o-realtime`)
- `VOICELIVE_MODEL` - Model to use (default: `gpt-realtime`)
- `VOICELIVE_VOICE` - Voice name (default: `en-US-AvaMultilingualNeural`)

3. **Run the server:**
Expand Down
2 changes: 1 addition & 1 deletion python/voice-live-avatar/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ async def health_check():
async def get_config():
"""Return default configuration to the frontend."""
return {
"model": os.getenv("VOICELIVE_MODEL", "gpt-4o-realtime"),
"model": os.getenv("VOICELIVE_MODEL", "gpt-realtime"),
"voice": os.getenv("VOICELIVE_VOICE", "en-US-AvaMultilingualNeural"),
"endpoint": os.getenv("AZURE_VOICELIVE_ENDPOINT", ""),
"hasApiKey": bool(os.getenv("AZURE_VOICELIVE_API_KEY")),
Expand Down
38 changes: 33 additions & 5 deletions python/voice-live-avatar/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ async function fetchServerConfig() {
const resp = await fetch('/api/config');
const config = await resp.json();
if (config.endpoint) document.getElementById('endpoint').value = config.endpoint;
if (config.model) document.getElementById('model').value = config.model;
if (config.model) {
const modelEl = document.getElementById('model');
modelEl.value = config.model;
modelEl.dispatchEvent(new Event('change'));
}
if (config.voice) document.getElementById('voiceName').value = config.voice;
} catch (e) {
console.log('No server config available, using defaults');
Expand All @@ -67,7 +71,17 @@ function setupUIBindings() {
// Mode change
document.getElementById('mode').addEventListener('change', updateConditionalFields);
// Model change
document.getElementById('model').addEventListener('change', updateConditionalFields);
document.getElementById('model').addEventListener('change', (e) => {
const voiceTypeEl = document.getElementById('voiceType');
if (e.target.value === 'azure-realtime') {
voiceTypeEl.value = 'azure-realtime-native';
const nativeEl = document.getElementById('nativeVoiceName');
if (nativeEl) nativeEl.value = 'ava';
} else if (voiceTypeEl.value === 'azure-realtime-native') {
voiceTypeEl.value = 'standard';
}
updateConditionalFields();
});
Comment thread
zhifzhan marked this conversation as resolved.
// Voice type change
document.getElementById('voiceType').addEventListener('change', updateConditionalFields);
// Voice name change
Expand Down Expand Up @@ -198,7 +212,11 @@ function updateConditionalFields() {
const srModel = document.getElementById('srModel').value;

// Cascaded models
const cascadedModels = ['gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-4o', 'gpt-4o-mini', 'phi4-mm', 'phi4-mini'];
const cascadedModels = [
'gpt-5.4', 'gpt-5.3-chat', 'gpt-5.2', 'gpt-5.2-chat', 'gpt-5.1', 'gpt-5.1-chat',
'gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-5-chat',
'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-4o', 'gpt-4o-mini',
];
const isCascaded = cascadedModels.includes(model);
const isRealtime = model && model.includes('realtime');

Expand Down Expand Up @@ -229,11 +247,17 @@ function updateConditionalFields() {
show('standardVoiceField', voiceType === 'standard');
show('customVoiceFields', voiceType === 'custom');
show('personalVoiceFields', voiceType === 'personal');
show('nativeVoiceField', voiceType === 'azure-realtime-native');

// Voice temperature (DragonHD or personal voice)
const isDragonHD = voiceName && voiceName.includes('DragonHD');
const isPersonal = voiceType === 'personal';
show('voiceTempField', isDragonHD || isPersonal);
show('voiceTempField', voiceType !== 'azure-realtime-native' && (isDragonHD || isPersonal));

show('voiceSpeedField', voiceType !== 'azure-realtime-native');

const nativeOpt = document.querySelector('#voiceType option[value="azure-realtime-native"]');
if (nativeOpt) nativeOpt.hidden = (model !== 'azure-realtime');

// Avatar settings
show('avatarSettings', avatarEnabled);
Expand Down Expand Up @@ -317,11 +341,15 @@ function gatherConfig() {

const voiceSpeed = parseFloat(document.getElementById('voiceSpeed').value) / 100;

const voiceName = voiceType === 'azure-realtime-native'
? document.getElementById('nativeVoiceName').value
: document.getElementById('voiceName').value;

const config = {
mode: mode,
model: model,
voiceType: voiceType,
voiceName: document.getElementById('voiceName').value,
voiceName: voiceName,
voiceSpeed: voiceSpeed,
voiceTemperature: parseFloat(document.getElementById('voiceTemperature').value),
voiceDeploymentId: document.getElementById('voiceDeploymentId').value,
Expand Down
38 changes: 34 additions & 4 deletions python/voice-live-avatar/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,25 @@
<div id="modelField">
<label>Model
<select id="model">
<option value="gpt-4o-realtime">GPT Realtime</option>
<option value="gpt-4o-mini-realtime">GPT Realtime Mini</option>
<option value="gpt-realtime-1.5">GPT Realtime 1.5</option>
<option value="azure-realtime">Azure Realtime</option>
<option value="gpt-realtime">GPT Realtime</option>
<option value="gpt-realtime-mini">GPT Realtime Mini</option>
<option value="gpt-5.4">GPT-5.4 (Cascaded)</option>
<option value="gpt-5.3-chat">GPT-5.3 Chat (Cascaded)</option>
<option value="gpt-5.2">GPT-5.2 (Cascaded)</option>
<option value="gpt-5.2-chat">GPT-5.2 Chat (Cascaded)</option>
<option value="gpt-5.1">GPT-5.1 (Cascaded)</option>
<option value="gpt-5.1-chat">GPT-5.1 Chat (Cascaded)</option>
<option value="gpt-5">GPT-5 (Cascaded)</option>
<option value="gpt-5-mini">GPT-5 Mini (Cascaded)</option>
<option value="gpt-5-nano">GPT-5 Nano (Cascaded)</option>
<option value="gpt-5-chat">GPT-5 Chat (Cascaded)</option>
<option value="gpt-4.1">GPT-4.1 (Cascaded)</option>
<option value="gpt-4.1-mini">GPT-4.1 Mini (Cascaded)</option>
<option value="gpt-4.1-nano">GPT-4.1 Nano (Cascaded)</option>
<option value="gpt-4o">GPT-4o (Cascaded)</option>
<option value="gpt-4o-mini">GPT-4o Mini (Cascaded)</option>
<option value="phi4-mm">Phi4-MM Realtime</option>
<option value="phi4-mini">Phi4 Mini (Cascaded)</option>
</select>
</label>
</div>
Expand Down Expand Up @@ -183,6 +193,7 @@
<option value="standard" selected>Standard Voices</option>
<option value="custom">Custom Voice</option>
<option value="personal">Personal Voice</option>
<option value="azure-realtime-native">Azure Realtime Native</option>
</select>
</label>

Expand Down Expand Up @@ -251,6 +262,25 @@
</label>
</div>

<!-- Native Voice (azure-realtime model) -->
<div id="nativeVoiceField" class="voice-variant standard-border" style="display:none">
<label>Voice
<select id="nativeVoiceName">
<option value="aarti">Aarti</option>
<option value="andrew">Andrew</option>
<option value="ava" selected>Ava</option>
<option value="denise">Denise</option>
<option value="elsa">Elsa</option>
<option value="florian">Florian</option>
<option value="francisca">Francisca</option>
<option value="meera">Meera</option>
<option value="xiaoxiao">Xiaoxiao</option>
<option value="ximena">Ximena</option>
<option value="yunxi">Yunxi</option>
</select>
</label>
</div>

<!-- Voice Temperature (DragonHD / personal) -->
<div id="voiceTempField" style="display:none">
<label>Voice Temperature
Expand Down
12 changes: 10 additions & 2 deletions python/voice-live-avatar/voice_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
AzureSemanticDetection,
AzureSemanticVad,
AzureStandardVoice,
AzureVoice,
AudioInputTranscriptionOptions,
Background,
ClientEventSessionAvatarConnect,
Expand Down Expand Up @@ -69,7 +70,7 @@ async def start(self):
"""Start the Voice Live session."""
try:
self.is_running = True
model = self.config.get("model", os.getenv("VOICELIVE_MODEL", "gpt-4o-realtime"))
model = self.config.get("model", os.getenv("VOICELIVE_MODEL", "gpt-realtime"))
mode = self.config.get("mode", "model")

# Build connection model string based on mode
Expand Down Expand Up @@ -115,7 +116,7 @@ async def _setup_session(self, connection):
"""Configure the Voice Live session with avatar, voice, and other settings."""
config = self.config
mode = config.get("mode", "model")
model = config.get("model", "gpt-4o-realtime")
model = config.get("model", "gpt-realtime")

# Build voice configuration
voice_config = self._build_voice_config(config)
Expand Down Expand Up @@ -255,6 +256,13 @@ def _build_voice_config(self, config: dict):
voice_temperature = config.get("voiceTemperature", 0.9)
voice_speed = config.get("voiceSpeed", 1.0)

if voice_type == "azure-realtime-native":
# Native voice names are simple lowercase ids (e.g. "ava"). Reject anything that
# looks like a standard voice id (contains "-") so the env default doesn't leak in.
client_voice = config.get("voiceName", "")
native_name = client_voice if client_voice and "-" not in client_voice else "ava"
return AzureVoice({"type": "azure-realtime-native", "name": native_name})

if voice_type == "custom":
custom_voice_name = config.get("customVoiceName", "")
deployment_id = config.get("voiceDeploymentId", "")
Expand Down
Loading