yaseminozkut commited on
Commit
abc9157
·
1 Parent(s): 38b2b86

update voice ids

Browse files
check_voices.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Check which ElevenLabs voices are available to you.
4
+ Run this with: python check_voices.py YOUR_API_KEY
5
+ """
6
+ import sys
7
+ from elevenlabs import ElevenLabs
8
+
9
+ if len(sys.argv) < 2:
10
+ print("Usage: python check_voices.py YOUR_ELEVENLABS_API_KEY")
11
+ sys.exit(1)
12
+
13
+ api_key = sys.argv[1]
14
+ client = ElevenLabs(api_key=api_key)
15
+
16
+ print("\n=== Available Voices in Your Account ===\n")
17
+
18
+ try:
19
+ voices = client.voices.get_all()
20
+
21
+ print(f"Total voices available: {len(voices.voices)}\n")
22
+
23
+ for voice in voices.voices:
24
+ # Check if it's likely a premade/stock voice
25
+ is_premade = hasattr(voice, 'category') and voice.category == 'premade'
26
+ voice_type = "STOCK" if is_premade else "CUSTOM"
27
+
28
+ print(f" [{voice_type}] {voice.name}")
29
+ print(f" ID: {voice.voice_id}")
30
+ if hasattr(voice, 'labels'):
31
+ labels = ', '.join([f"{k}: {v}" for k, v in voice.labels.items()])
32
+ print(f" Labels: {labels}")
33
+ print()
34
+
35
+ print("\n=== Checking voices in your config.py ===\n")
36
+
37
+ config_voices = {
38
+ "mixtape": "H10ItvDnkRN5ysrvzT9J",
39
+ "angry_boss": "TxWZERZ5Hc6h9dGxVmXa",
40
+ "sarcastic": "FGY2WhTYpPnrIDTdsKH5",
41
+ "disappointed_parent": "Xb7hH8MSUJpSbSDYk0k2",
42
+ "motivational_coach": "IKne3meq5aSn9XLyUdCD",
43
+ "absurdist": "cgSgspJ2msm6clMCkdW9",
44
+ "corporate_ai": "weA4Q36twV5kwSaTEL0Q",
45
+ "british_butler": "JBFqnCBsd6RMkjVDRZzb"
46
+ }
47
+
48
+ all_voice_ids = [v.voice_id for v in voices.voices]
49
+
50
+ problems_found = False
51
+ for personality, vid in config_voices.items():
52
+ if vid in all_voice_ids:
53
+ voice = next(v for v in voices.voices if v.voice_id == vid)
54
+ print(f" ✓ {personality}: {voice.name} ({vid})")
55
+ else:
56
+ print(f" ✗ {personality}: {vid} - NOT AVAILABLE!")
57
+ problems_found = True
58
+
59
+ if problems_found:
60
+ print("\n⚠️ Some voices in your config are NOT available to your account.")
61
+ print(" You need to replace them with voices you have access to.")
62
+ print(" Use the voice IDs from the list above.")
63
+ else:
64
+ print("\n✓ All config voices are available!")
65
+
66
+ except Exception as e:
67
+ print(f"Error: {e}")
68
+ import traceback
69
+ traceback.print_exc()
judgy_reachy_no_phone/audio.py CHANGED
@@ -199,12 +199,13 @@ class TextToSpeech:
199
  self.eleven_client = None
200
  self.chars_used = 0
201
  self.MONTHLY_LIMIT = 9000 # Leave buffer under 10k
 
202
 
203
  if elevenlabs_key:
204
  try:
205
  from elevenlabs import ElevenLabs
206
  self.eleven_client = ElevenLabs(api_key=elevenlabs_key)
207
- logger.info(f"ElevenLabs TTS initialized")
208
  except ImportError:
209
  logger.warning("elevenlabs package not installed, using Edge TTS")
210
  except Exception as e:
@@ -214,26 +215,60 @@ class TextToSpeech:
214
  """Get the appropriate voice based on personality and user override."""
215
  personality_data = PERSONALITIES.get(self.personality, PERSONALITIES["mixtape"])
216
 
217
- # User override always wins
218
  edge_voice = self.user_edge_voice if self.user_edge_voice else personality_data.get("default_voice", "en-US-AnaNeural")
219
- eleven_voice = self.user_eleven_voice if self.user_eleven_voice else personality_data.get("default_eleven_voice", "21m00Tcm4TlvDq8ikWAM")
220
 
221
- return edge_voice, eleven_voice
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
  async def synthesize(self, text: str, output_path: str = "/tmp/judgy_reachy_tts.mp3") -> str:
224
  """Convert text to speech, return path to audio file."""
225
 
226
  # Get appropriate voices for current personality
227
- edge_voice, eleven_voice = self._get_voice_for_personality()
228
 
229
  # Try ElevenLabs first if available and under limit
230
  if self.eleven_client and (self.chars_used + len(text)) < self.MONTHLY_LIMIT:
231
- try:
232
- return await self._synthesize_elevenlabs(text, output_path, eleven_voice)
233
- except Exception as e:
234
- logger.warning(f"ElevenLabs failed: {e}, falling back to Edge TTS")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
 
236
  # Fallback to Edge TTS (always works, unlimited)
 
237
  return await self._synthesize_edge(text, output_path, edge_voice)
238
 
239
  async def _synthesize_elevenlabs(self, text: str, output_path: str, voice_id: str) -> str:
 
199
  self.eleven_client = None
200
  self.chars_used = 0
201
  self.MONTHLY_LIMIT = 9000 # Leave buffer under 10k
202
+ self.working_voice_cache = {} # Cache of personality -> working voice ID
203
 
204
  if elevenlabs_key:
205
  try:
206
  from elevenlabs import ElevenLabs
207
  self.eleven_client = ElevenLabs(api_key=elevenlabs_key)
208
+ logger.info(f"ElevenLabs TTS initialized (voices will be validated on first use)")
209
  except ImportError:
210
  logger.warning("elevenlabs package not installed, using Edge TTS")
211
  except Exception as e:
 
215
  """Get the appropriate voice based on personality and user override."""
216
  personality_data = PERSONALITIES.get(self.personality, PERSONALITIES["mixtape"])
217
 
218
+ # User override always wins for edge voice
219
  edge_voice = self.user_edge_voice if self.user_edge_voice else personality_data.get("default_voice", "en-US-AnaNeural")
 
220
 
221
+ # For ElevenLabs voice, handle list of voices (try in order) or single voice (backward compatibility)
222
+ if self.user_eleven_voice:
223
+ # User specified a custom voice
224
+ eleven_voices = [self.user_eleven_voice]
225
+ else:
226
+ # Get from personality config - handle both list and single voice
227
+ eleven_voice_data = personality_data.get("default_eleven_voices", personality_data.get("default_eleven_voice", "21m00Tcm4TlvDq8ikWAM"))
228
+ if isinstance(eleven_voice_data, list):
229
+ eleven_voices = eleven_voice_data
230
+ else:
231
+ eleven_voices = [eleven_voice_data]
232
+
233
+ return edge_voice, eleven_voices
234
 
235
  async def synthesize(self, text: str, output_path: str = "/tmp/judgy_reachy_tts.mp3") -> str:
236
  """Convert text to speech, return path to audio file."""
237
 
238
  # Get appropriate voices for current personality
239
+ edge_voice, eleven_voices = self._get_voice_for_personality()
240
 
241
  # Try ElevenLabs first if available and under limit
242
  if self.eleven_client and (self.chars_used + len(text)) < self.MONTHLY_LIMIT:
243
+ # Check cache first
244
+ if self.personality in self.working_voice_cache:
245
+ try:
246
+ cached_voice = self.working_voice_cache[self.personality]
247
+ logger.info(f"Using cached ElevenLabs voice: {cached_voice}")
248
+ return await self._synthesize_elevenlabs(text, output_path, cached_voice)
249
+ except Exception as e:
250
+ logger.warning(f"Cached voice failed: {e}, trying other voices")
251
+ # Remove from cache if it failed
252
+ del self.working_voice_cache[self.personality]
253
+
254
+ # Try each voice in the list until one works
255
+ for voice_id in eleven_voices:
256
+ try:
257
+ logger.info(f"Trying ElevenLabs voice: {voice_id}")
258
+ result = await self._synthesize_elevenlabs(text, output_path, voice_id)
259
+ # Success! Cache this voice for future use
260
+ self.working_voice_cache[self.personality] = voice_id
261
+ logger.info(f"✓ Voice {voice_id} works! Cached for {self.personality}")
262
+ return result
263
+ except Exception as e:
264
+ logger.warning(f"Voice {voice_id} failed: {e}, trying next...")
265
+ continue
266
+
267
+ # All voices failed
268
+ logger.warning(f"All ElevenLabs voices failed for {self.personality}, falling back to Edge TTS")
269
 
270
  # Fallback to Edge TTS (always works, unlimited)
271
+ logger.info(f"Using Edge TTS with voice: {edge_voice}")
272
  return await self._synthesize_edge(text, output_path, edge_voice)
273
 
274
  async def _synthesize_elevenlabs(self, text: str, output_path: str, voice_id: str) -> str:
judgy_reachy_no_phone/config.py CHANGED
@@ -97,7 +97,11 @@ PERSONALITIES = {
97
  "name": "🎵 Chaos Mode",
98
  "voice": "Unpredictable. Each response is a completely different personality.",
99
  "default_voice": "en-US-AnaNeural", # Versatile female voice
100
- "default_eleven_voice": "Iz2kaKkJmFf0yaZAMDTV", # Rachel - versatile, neutral
 
 
 
 
101
  "prewritten_shame": None, # Will randomly select from other personalities
102
  "prewritten_praise": None, # Will randomly select from other personalities
103
  "shame": None, # Will randomly select from others
@@ -108,7 +112,10 @@ PERSONALITIES = {
108
  "name": "😠 Angry Boss",
109
  "voice": "A furious manager who's reached their absolute limit. Explosive, aggressive, zero patience left.",
110
  "default_voice": "en-US-EricNeural", # Deep, stern male
111
- "default_eleven_voice": "DGzg6RaUqxGRTHSBjfgF",
 
 
 
112
  "prewritten_shame": [
113
  "Put it down!",
114
  "Unbelievable!",
@@ -150,7 +157,9 @@ PERSONALITIES = {
150
  "name": "🎭 Sarcastic",
151
  "voice": "Dripping with dry wit. Mock enthusiasm, feigned interest. Pretends to take their phone use seriously.",
152
  "default_voice": "en-US-AvaMultilingualNeural", # Female, dry wit
153
- "default_eleven_voice": "50lF5fQMqcxbDQOW6qOs",
 
 
154
  "prewritten_shame": [
155
  "Oh, how vital.",
156
  "Riveting stuff, I'm sure.",
@@ -191,7 +200,9 @@ PERSONALITIES = {
191
  "name": "😔 Disappointed Parent",
192
  "voice": "A heartbroken parent. Not angry—just deeply let down. Maximum guilt. References their potential.",
193
  "default_voice": "en-US-AvaNeural", # Soft female, empathetic
194
- "default_eleven_voice": "roYauZ4bOLAKvVZTPLre",
 
 
195
  "prewritten_shame": [
196
  "I'm so disappointed...",
197
  "We talked about this.",
@@ -233,7 +244,9 @@ PERSONALITIES = {
233
  "name": "💪 Motivational Coach",
234
  "voice": "An intense drill-sergeant coach who believes in you but won't tolerate weakness. High energy, sports metaphors.",
235
  "default_voice": "en-US-GuyNeural", # Energetic male
236
- "default_eleven_voice": "84Fal4DSXWfp7nJ8emqQ",
 
 
237
  "prewritten_shame": [
238
  "Where's your discipline?!",
239
  "Champions don't quit!",
@@ -275,7 +288,9 @@ PERSONALITIES = {
275
  "name": "🤡 Absurdist",
276
  "voice": "Surreal, unexpected, playful. Personifies objects. Makes weird observations. Non sequiturs welcome.",
277
  "default_voice": "en-US-AriaNeural", # Playful, expressive female
278
- "default_eleven_voice": "G0yjIg3xY8gEJZkHpjVm",
 
 
279
  "prewritten_shame": [
280
  "Your thumb called. It's exhausted.",
281
  "Emergency cat video?",
@@ -316,7 +331,10 @@ PERSONALITIES = {
316
  "name": "🤖 Corporate AI",
317
  "voice": "An emotionless productivity monitoring system. Speaks like automated log output. Zero personality.",
318
  "default_voice": "en-US-MichelleNeural", # Neutral, professional male
319
- "default_eleven_voice": "weA4Q36twV5kwSaTEL0Q",
 
 
 
320
  "prewritten_shame": [
321
  "Distraction event detected.",
322
  "Alert: phone in hand.",
@@ -358,7 +376,9 @@ PERSONALITIES = {
358
  "name": "🎩 British Butler",
359
  "voice": "An impeccably polite but quietly judgmental butler. Passive-aggressive courtesy. Disappointment hidden behind manners.",
360
  "default_voice": "en-GB-RyanNeural", # Polite British male
361
- "default_eleven_voice": "lUTamkMw7gOzZbFIwmq4", # James - Professional British Male
 
 
362
  "prewritten_shame": [
363
  "If I may say so, sir...",
364
  "The telephone. Again. Indeed.",
 
97
  "name": "🎵 Chaos Mode",
98
  "voice": "Unpredictable. Each response is a completely different personality.",
99
  "default_voice": "en-US-AnaNeural", # Versatile female voice
100
+ "default_eleven_voices": [ # List of voice IDs to try in order (will use first available)
101
+ "H10ItvDnkRN5ysrvzT9J", # My custom
102
+ "Nggzl2QAXh3OijoXD116", # Candy - Young and Sweet
103
+ "cgSgspJ2msm6clMCkdW9", # Jessica - Playful, Bright
104
+ ],
105
  "prewritten_shame": None, # Will randomly select from other personalities
106
  "prewritten_praise": None, # Will randomly select from other personalities
107
  "shame": None, # Will randomly select from others
 
112
  "name": "😠 Angry Boss",
113
  "voice": "A furious manager who's reached their absolute limit. Explosive, aggressive, zero patience left.",
114
  "default_voice": "en-US-EricNeural", # Deep, stern male
115
+ "default_eleven_voices": [
116
+ "TxWZERZ5Hc6h9dGxVmXa", # Jerry B. - Gruff and Gritty Commander
117
+ "cjVigY5qzO86Huf0OWal", # Eric - Smooth, Trustworthy
118
+ ],
119
  "prewritten_shame": [
120
  "Put it down!",
121
  "Unbelievable!",
 
157
  "name": "🎭 Sarcastic",
158
  "voice": "Dripping with dry wit. Mock enthusiasm, feigned interest. Pretends to take their phone use seriously.",
159
  "default_voice": "en-US-AvaMultilingualNeural", # Female, dry wit
160
+ "default_eleven_voices": [
161
+ "FGY2WhTYpPnrIDTdsKH5", # Laura - Enthusiast, Quirky Attitude
162
+ ],
163
  "prewritten_shame": [
164
  "Oh, how vital.",
165
  "Riveting stuff, I'm sure.",
 
200
  "name": "😔 Disappointed Parent",
201
  "voice": "A heartbroken parent. Not angry—just deeply let down. Maximum guilt. References their potential.",
202
  "default_voice": "en-US-AvaNeural", # Soft female, empathetic
203
+ "default_eleven_voices": [
204
+ "Xb7hH8MSUJpSbSDYk0k2", # Alice - Clear, Engaging
205
+ ],
206
  "prewritten_shame": [
207
  "I'm so disappointed...",
208
  "We talked about this.",
 
244
  "name": "💪 Motivational Coach",
245
  "voice": "An intense drill-sergeant coach who believes in you but won't tolerate weakness. High energy, sports metaphors.",
246
  "default_voice": "en-US-GuyNeural", # Energetic male
247
+ "default_eleven_voices": [
248
+ "IKne3meq5aSn9XLyUdCD", # Charlie - Deep, Confident, Energetic
249
+ ],
250
  "prewritten_shame": [
251
  "Where's your discipline?!",
252
  "Champions don't quit!",
 
288
  "name": "🤡 Absurdist",
289
  "voice": "Surreal, unexpected, playful. Personifies objects. Makes weird observations. Non sequiturs welcome.",
290
  "default_voice": "en-US-AriaNeural", # Playful, expressive female
291
+ "default_eleven_voices": [
292
+ "cgSgspJ2msm6clMCkdW9", # Jessica - Playful, Bright, Warm
293
+ ],
294
  "prewritten_shame": [
295
  "Your thumb called. It's exhausted.",
296
  "Emergency cat video?",
 
331
  "name": "🤖 Corporate AI",
332
  "voice": "An emotionless productivity monitoring system. Speaks like automated log output. Zero personality.",
333
  "default_voice": "en-US-MichelleNeural", # Neutral, professional male
334
+ "default_eleven_voices": [
335
+ "weA4Q36twV5kwSaTEL0Q", # Eva - Futuristic Robot Helper
336
+ "EXAVITQu4vr4xnSDxMaL", # Sarah - Mature, Reassuring, Confident
337
+ ],
338
  "prewritten_shame": [
339
  "Distraction event detected.",
340
  "Alert: phone in hand.",
 
376
  "name": "🎩 British Butler",
377
  "voice": "An impeccably polite but quietly judgmental butler. Passive-aggressive courtesy. Disappointment hidden behind manners.",
378
  "default_voice": "en-GB-RyanNeural", # Polite British male
379
+ "default_eleven_voices": [
380
+ "JBFqnCBsd6RMkjVDRZzb", # George - Warm, Captivating Storyteller (British)
381
+ ],
382
  "prewritten_shame": [
383
  "If I may say so, sir...",
384
  "The telephone. Again. Indeed.",
judgy_reachy_no_phone/main.py CHANGED
@@ -372,11 +372,13 @@ class JudgyReachyNoPhone(ReachyMiniApp):
372
  return {"button_text": button_text}
373
  else:
374
  # Start or Continue monitoring
 
375
  if req.groq_key:
376
  logger.info(f"Initializing LLM with Groq API key: {req.groq_key[:10]}... personality: {req.personality}")
377
  self.llm = LLMResponder(api_key=req.groq_key, personality=req.personality)
378
  else:
379
- logger.info("No Groq API key provided, using pre-written lines")
 
380
 
381
  # Initialize TTS - pass custom voices only if explicitly set (empty string means use personality default)
382
  if req.eleven_key:
@@ -535,8 +537,11 @@ class JudgyReachyNoPhone(ReachyMiniApp):
535
  @self.settings_app.post("/api/test")
536
  def test_shame(req: ToggleRequest):
537
  # Apply settings from UI before testing (but don't start monitoring)
 
538
  if req.groq_key:
539
  self.llm = LLMResponder(api_key=req.groq_key, personality=req.personality)
 
 
540
 
541
  # Pass voice overrides only if explicitly set (empty string means use personality default)
542
  if req.eleven_key:
 
372
  return {"button_text": button_text}
373
  else:
374
  # Start or Continue monitoring
375
+ # Always update LLM responder with personality (for prewritten lines even without API key)
376
  if req.groq_key:
377
  logger.info(f"Initializing LLM with Groq API key: {req.groq_key[:10]}... personality: {req.personality}")
378
  self.llm = LLMResponder(api_key=req.groq_key, personality=req.personality)
379
  else:
380
+ logger.info(f"No Groq API key provided, using pre-written lines with personality: {req.personality}")
381
+ self.llm = LLMResponder(api_key="", personality=req.personality)
382
 
383
  # Initialize TTS - pass custom voices only if explicitly set (empty string means use personality default)
384
  if req.eleven_key:
 
537
  @self.settings_app.post("/api/test")
538
  def test_shame(req: ToggleRequest):
539
  # Apply settings from UI before testing (but don't start monitoring)
540
+ # Always update LLM responder with personality (for prewritten lines even without API key)
541
  if req.groq_key:
542
  self.llm = LLMResponder(api_key=req.groq_key, personality=req.personality)
543
+ else:
544
+ self.llm = LLMResponder(api_key="", personality=req.personality)
545
 
546
  # Pass voice overrides only if explicitly set (empty string means use personality default)
547
  if req.eleven_key:
judgy_reachy_no_phone/static/main.js CHANGED
@@ -65,8 +65,8 @@ async function updateUIForAPIKeys() {
65
  const cooldown = document.getElementById('cooldown').value;
66
  const praise = document.getElementById('praise-toggle').checked;
67
 
68
- // If no Groq key, show notice but keep personalities enabled (they use pre-written lines)
69
- if (!groqKey) {
70
  document.getElementById('mode-text').textContent = 'YOLO | Pre-written personality lines → Edge TTS';
71
  document.getElementById('api-notice').classList.remove('hidden');
72
  // Keep personalities enabled - they still have different voices and pre-written lines
@@ -77,7 +77,7 @@ async function updateUIForAPIKeys() {
77
  // Get voice override for selected personality
78
  const voiceOverride = voiceOverrides[selectedPersonality] || {};
79
 
80
- // Validate keys with backend
81
  const response = await fetch('/api/validate-keys', {
82
  method: 'POST',
83
  headers: { 'Content-Type': 'application/json' },
 
65
  const cooldown = document.getElementById('cooldown').value;
66
  const praise = document.getElementById('praise-toggle').checked;
67
 
68
+ // If no API keys at all, show default message
69
+ if (!groqKey && !elevenKey) {
70
  document.getElementById('mode-text').textContent = 'YOLO | Pre-written personality lines → Edge TTS';
71
  document.getElementById('api-notice').classList.remove('hidden');
72
  // Keep personalities enabled - they still have different voices and pre-written lines
 
77
  // Get voice override for selected personality
78
  const voiceOverride = voiceOverrides[selectedPersonality] || {};
79
 
80
+ // Validate keys with backend (even if only one is provided)
81
  const response = await fetch('/api/validate-keys', {
82
  method: 'POST',
83
  headers: { 'Content-Type': 'application/json' },