DungeonMaster-AI / ui /components /voice_controls.py
bhupesh-sf's picture
first commit
f8ba6bf verified
"""
DungeonMaster AI - Voice Controls Component
Voice narration settings and controls.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import gradio as gr
from ui import styles
if TYPE_CHECKING:
pass
# =============================================================================
# Component Creation
# =============================================================================
def create_voice_controls() -> tuple[
gr.Checkbox, # voice_toggle
gr.Checkbox, # autoplay_toggle
gr.HTML, # voice_status (changed from Markdown)
]:
"""
Create the voice controls component.
Returns:
Tuple of all Gradio components for event wiring.
"""
with gr.Group():
gr.Markdown("### 🔊 Voice Settings")
# Voice enable/disable toggle
voice_toggle = gr.Checkbox(
label="Enable Voice Narration",
value=True,
info="AI voice will narrate DM responses",
)
# Auto-play toggle
autoplay_toggle = gr.Checkbox(
label="Auto-play Narration",
value=True,
info="Automatically play audio when available",
)
# Voice status
voice_status = gr.HTML(
value=format_voice_status(available=True, enabled=True),
)
return (
voice_toggle,
autoplay_toggle,
voice_status,
)
# =============================================================================
# Status Formatting
# =============================================================================
def format_voice_status(
available: bool = True,
enabled: bool = True,
provider: str = "ElevenLabs",
) -> str:
"""Format voice status display as styled HTML."""
container_style = (
"display: flex; "
"align-items: center; "
"gap: 8px; "
"padding: 0.5rem; "
"background: rgba(0, 0, 0, 0.15); "
"border-radius: 6px; "
"margin-top: 0.5rem;"
)
text_style = f"font-size: 0.85rem; color: {styles.PARCHMENT};"
muted_style = f"font-size: 0.8rem; color: {styles.PARCHMENT_MUTED}; margin-top: 0.25rem;"
if not available:
return f'''
<div style="{container_style}">
<span style="{styles.status_dot_style(styles.STATUS_RED)}"></span>
<div>
<div style="{text_style}; color: {styles.STATUS_RED};">Voice Unavailable</div>
<div style="{muted_style}">Text responses will continue normally.</div>
</div>
</div>
'''
if not enabled:
return f'''
<div style="{container_style}">
<span style="{styles.status_dot_style(styles.STATUS_YELLOW)}"></span>
<div>
<div style="{text_style}; color: {styles.STATUS_YELLOW};">Voice Disabled</div>
<div style="{muted_style}">Enable above to hear the DM speak!</div>
</div>
</div>
'''
return f'''
<div style="{container_style}">
<span style="{styles.status_dot_style(styles.STATUS_GREEN)}"></span>
<div>
<div style="{text_style}; color: {styles.STATUS_GREEN};">Voice Active</div>
<div style="{muted_style}">Provider: {provider}</div>
</div>
</div>
'''
# =============================================================================
# Update Functions
# =============================================================================
def update_voice_status(
available: bool,
enabled: bool,
provider: str = "ElevenLabs",
) -> str:
"""Update voice status display."""
return format_voice_status(
available=available,
enabled=enabled,
provider=provider,
)
def handle_voice_toggle(enabled: bool) -> tuple[bool, str]:
"""
Handle voice toggle change.
Args:
enabled: New toggle state
Returns:
Tuple of (enabled_state, status_text)
"""
return enabled, format_voice_status(available=True, enabled=enabled)
def handle_autoplay_toggle(enabled: bool) -> gr.update:
"""
Handle autoplay toggle change.
Args:
enabled: New toggle state
Returns:
Audio component update with autoplay setting
"""
return gr.update(autoplay=enabled)