File size: 5,669 Bytes
f1c15c1
13ad9ff
 
f1c15c1
 
 
13ad9ff
f1c15c1
13ad9ff
f1c15c1
 
 
 
13ad9ff
f1c15c1
13ad9ff
 
 
 
 
 
f1c15c1
 
 
13ad9ff
 
 
 
 
 
 
 
f1c15c1
 
13ad9ff
 
 
 
f1c15c1
13ad9ff
f1c15c1
13ad9ff
 
 
 
 
 
 
 
 
f1c15c1
13ad9ff
f1c15c1
 
13ad9ff
f1c15c1
13ad9ff
 
 
 
 
 
 
 
 
f1c15c1
13ad9ff
 
 
f1c15c1
 
13ad9ff
 
f1c15c1
13ad9ff
 
 
 
 
 
 
f1c15c1
13ad9ff
 
f1c15c1
 
13ad9ff
 
 
 
 
 
 
 
 
 
f1c15c1
13ad9ff
 
f1c15c1
13ad9ff
 
 
 
 
f1c15c1
13ad9ff
 
f1c15c1
13ad9ff
 
 
 
 
f1c15c1
13ad9ff
 
 
 
 
 
 
 
f1c15c1
13ad9ff
f1c15c1
13ad9ff
f1c15c1
13ad9ff
f1c15c1
 
 
13ad9ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f1c15c1
13ad9ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f1c15c1
13ad9ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
"""
Voice Narration for Incident Alerts
Supports multiple TTS providers with graceful fallback
"""

import os
import requests
import logging
from typing import Optional, Dict, Any

logger = logging.getLogger(__name__)


class VoiceNarrator:
    """
    Narrate critical incidents using TTS API
    
    Supports:
    - Hathora Voice API (REST)
    - Generic TTS APIs
    - Graceful fallback (silent fail)
    """
    
    def __init__(self):
        # Check for API key (set in HF Secrets)
        self.api_key = os.environ.get("HATHORA_VOICE_API_KEY", "") or \
                       os.environ.get("VOICE_API_KEY", "")
        
        # API endpoint (update with actual Hathora endpoint)
        self.api_endpoint = os.environ.get(
            "VOICE_API_ENDPOINT",
            "https://api.hathora.dev/v1/tts"  # PLACEHOLDER
        )
        
        self.enabled = bool(self.api_key)
        
        if self.enabled:
            logger.info("✅ Voice narrator initialized with API key")
        else:
            logger.warning("⚠️ Voice narrator disabled (no API key found)")
    
    def narrate_incident(
        self,
        component: str,
        severity: str,
        latency: float,
        error_rate: float,
        root_cause: str = "Unknown",
        recovery_action: str = "Investigating"
    ) -> Optional[str]:
        """
        Generate voice narration for a critical incident
        
        Returns:
            Audio URL (str) if successful, None if failed
        """
        
        if not self.enabled:
            logger.debug("Voice narration skipped (disabled)")
            return None
        
        # Only narrate HIGH and CRITICAL incidents
        if severity not in ["HIGH", "CRITICAL"]:
            return None
        
        try:
            # Build dramatic narration text (30-60 seconds when spoken)
            narration_text = self._build_narration(
                component, severity, latency, error_rate, root_cause, recovery_action
            )
            
            # Call TTS API
            audio_url = self._call_tts_api(narration_text)
            
            if audio_url:
                logger.info(f"✅ Generated voice narration for {component}")
                return audio_url
            else:
                logger.warning("Voice API returned no audio URL")
                return None
                
        except Exception as e:
            # Silent fail - don't break the app
            logger.error(f"Voice narration failed: {e}", exc_info=True)
            return None
    
    def _build_narration(
        self,
        component: str,
        severity: str,
        latency: float,
        error_rate: float,
        root_cause: str,
        recovery_action: str
    ) -> str:
        """Build dramatic narration text"""
        
        # Format component name (remove dashes, capitalize)
        component_spoken = component.replace("-", " ").title()
        
        # Severity-specific intro
        if severity == "CRITICAL":
            intro = f"Critical alert. {component_spoken} is experiencing severe failure."
        else:
            intro = f"High priority alert. {component_spoken} degradation detected."
        
        # Metrics
        metrics = f"Latency: {int(latency)} milliseconds. Error rate: {error_rate*100:.0f} percent."
        
        # Root cause (if available)
        if root_cause and root_cause != "Unknown":
            cause = f"Root cause: {root_cause}."
        else:
            cause = "Root cause under investigation."
        
        # Recovery action
        action = f"Recovery action: {recovery_action}."
        
        # Combine into ~30-60 second narration
        full_text = f"{intro} {metrics} {cause} {action} Immediate attention required."
        
        logger.debug(f"Narration text: {full_text[:100]}...")
        return full_text
    
    def _call_tts_api(self, text: str) -> Optional[str]:
        """
        Call TTS API to generate audio
        
        GENERIC implementation - adapt to actual Hathora Voice API format
        """
        
        try:
            # Make REST API call
            response = requests.post(
                self.api_endpoint,
                headers={
                    "Authorization": f"Bearer {self.api_key}",
                    "Content-Type": "application/json"
                },
                json={
                    "text": text,
                    "voice": "en-US-neural",
                    "speed": 1.0,
                    "format": "mp3"
                },
                timeout=10.0
            )
            
            if response.status_code == 200:
                data = response.json()
                
                # Extract audio URL
                audio_url = data.get("audio_url") or \
                           data.get("url") or \
                           data.get("audioUrl")
                
                return audio_url
            else:
                logger.error(f"TTS API error: {response.status_code} - {response.text[:200]}")
                return None
                
        except requests.exceptions.Timeout:
            logger.warning("TTS API timeout")
            return None
        except Exception as e:
            logger.error(f"TTS API call failed: {e}")
            return None


# Singleton instance
_narrator = None

def get_narrator() -> VoiceNarrator:
    """Get global narrator instance"""
    global _narrator
    if _narrator is None:
        _narrator = VoiceNarrator()
    return _narrator


# Backward compatibility aliases
VoiceHandler = VoiceNarrator
get_voice_handler = get_narrator