sammy786 commited on
Commit
ceed1e7
·
verified ·
1 Parent(s): a3009f5

Create voice_assistant.py

Browse files
Files changed (1) hide show
  1. utils/voice_assistant.py +168 -0
utils/voice_assistant.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ElevenLabs Voice Assistant for RewardPilot
3
+ Converts AI text responses to natural speech
4
+ """
5
+
6
+ import os
7
+ import logging
8
+ from typing import Optional, List, Dict
9
+ import io
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Check if ElevenLabs is available
14
+ try:
15
+ from elevenlabs import generate, Voice, VoiceSettings
16
+ from elevenlabs.client import ElevenLabs
17
+ ELEVENLABS_AVAILABLE = True
18
+ except ImportError:
19
+ ELEVENLABS_AVAILABLE = False
20
+ logger.warning("ElevenLabs not installed. Voice features will be disabled.")
21
+
22
+
23
+ class VoiceAssistant:
24
+ """Handle text-to-speech conversion using ElevenLabs"""
25
+
26
+ def __init__(self):
27
+ self.api_key = os.getenv("ELEVENLABS_API_KEY")
28
+ self.enabled = ELEVENLABS_AVAILABLE and bool(self.api_key)
29
+
30
+ if self.enabled:
31
+ self.client = ElevenLabs(api_key=self.api_key)
32
+ logger.info("✅ ElevenLabs Voice Assistant initialized")
33
+ else:
34
+ logger.warning("⚠️ ElevenLabs disabled (missing API key or library)")
35
+
36
+ # Voice configurations
37
+ self.voices = {
38
+ "Rachel": {
39
+ "voice_id": "21m00Tcm4TlvDq8ikWAM", # Professional female
40
+ "description": "Clear, professional female voice"
41
+ },
42
+ "Adam": {
43
+ "voice_id": "pNInz6obpgDQGcFmaJgB", # Deep male
44
+ "description": "Deep, authoritative male voice"
45
+ },
46
+ "Bella": {
47
+ "voice_id": "EXAVITQu4vr4xnSDxMaL", # Friendly female
48
+ "description": "Warm, friendly female voice"
49
+ },
50
+ "Antoni": {
51
+ "voice_id": "ErXwobaYiN019PkySvjV", # Well-rounded male
52
+ "description": "Well-rounded, versatile male voice"
53
+ },
54
+ "Elli": {
55
+ "voice_id": "MF3mGyEYCl7XYWbV9V6O", # Young female
56
+ "description": "Young, energetic female voice"
57
+ }
58
+ }
59
+
60
+ def text_to_speech(
61
+ self,
62
+ text: str,
63
+ voice_name: str = "Rachel",
64
+ model: str = "eleven_turbo_v2"
65
+ ) -> Optional[bytes]:
66
+ """
67
+ Convert text to speech audio
68
+
69
+ Args:
70
+ text: Text to convert
71
+ voice_name: Name of voice to use
72
+ model: ElevenLabs model (eleven_turbo_v2 is fastest)
73
+
74
+ Returns:
75
+ Audio bytes or None if failed
76
+ """
77
+ if not self.enabled:
78
+ logger.warning("Voice generation skipped (ElevenLabs not enabled)")
79
+ return None
80
+
81
+ if not text or len(text.strip()) == 0:
82
+ logger.warning("Empty text provided for voice generation")
83
+ return None
84
+
85
+ # Limit text length to avoid API errors
86
+ if len(text) > 2500:
87
+ text = text[:2500] + "..."
88
+ logger.info(f"Text truncated to 2500 characters for voice generation")
89
+
90
+ try:
91
+ voice_config = self.voices.get(voice_name, self.voices["Rachel"])
92
+ voice_id = voice_config["voice_id"]
93
+
94
+ logger.info(f"🎤 Generating speech with {voice_name} ({len(text)} chars)")
95
+
96
+ # Generate audio using ElevenLabs
97
+ audio = self.client.generate(
98
+ text=text,
99
+ voice=Voice(
100
+ voice_id=voice_id,
101
+ settings=VoiceSettings(
102
+ stability=0.5, # Balance between consistency and expressiveness
103
+ similarity_boost=0.75, # How closely to match the original voice
104
+ style=0.0, # Exaggeration level
105
+ use_speaker_boost=True # Enhance clarity
106
+ )
107
+ ),
108
+ model=model
109
+ )
110
+
111
+ # Convert generator to bytes
112
+ audio_bytes = b"".join(audio)
113
+
114
+ logger.info(f"✅ Generated {len(audio_bytes)} bytes of audio")
115
+ return audio_bytes
116
+
117
+ except Exception as e:
118
+ logger.error(f"❌ Voice generation failed: {e}")
119
+ return None
120
+
121
+ def get_voice_list(self) -> List[Dict[str, str]]:
122
+ """Get list of available voices"""
123
+ return [
124
+ {"name": name, "description": config["description"]}
125
+ for name, config in self.voices.items()
126
+ ]
127
+
128
+ def create_audio_summary(self, recommendation_data: dict) -> str:
129
+ """
130
+ Create a concise audio-friendly summary of recommendation
131
+
132
+ Args:
133
+ recommendation_data: Normalized recommendation data
134
+
135
+ Returns:
136
+ Audio-optimized text
137
+ """
138
+ card = recommendation_data.get('recommended_card', 'Unknown Card')
139
+ rewards = recommendation_data.get('rewards_earned', 0)
140
+ rate = recommendation_data.get('rewards_rate', 'N/A')
141
+ merchant = recommendation_data.get('merchant', 'this merchant')
142
+ reasoning = recommendation_data.get('reasoning', '')
143
+
144
+ # Create concise, natural-sounding summary
145
+ summary = f"For your purchase at {merchant}, I recommend using your {card}. "
146
+ summary += f"You'll earn {rewards:.2f} dollars in rewards at a rate of {rate}. "
147
+
148
+ # Add simplified reasoning (first sentence only)
149
+ if reasoning:
150
+ first_sentence = reasoning.split('.')[0].strip()
151
+ if first_sentence and len(first_sentence) > 20:
152
+ summary += f"{first_sentence}. "
153
+
154
+ # Add warnings if present
155
+ warnings = recommendation_data.get('warnings', [])
156
+ if warnings:
157
+ summary += "Important note: " + warnings[0]
158
+
159
+ return summary
160
+
161
+
162
+ # Global instance
163
+ voice_assistant = VoiceAssistant()
164
+
165
+
166
+ def get_voice_assistant() -> VoiceAssistant:
167
+ """Get the global voice assistant instance"""
168
+ return voice_assistant