Nikita Makarov commited on
Commit
9b6a0be
·
1 Parent(s): c04db7b
Files changed (4) hide show
  1. src/app.py +66 -18
  2. src/config.py +1 -1
  3. src/radio_agent.py +40 -10
  4. src/tts_service.py +14 -0
src/app.py CHANGED
@@ -108,7 +108,8 @@ user_memory = UserMemoryService()
108
 
109
  def save_preferences(name: str, favorite_genres: List[str], interests: List[str],
110
  podcast_interests: List[str], mood: str,
111
- music_filter: bool, news_filter: bool, podcast_filter: bool, story_filter: bool) -> str:
 
112
  """Save user preferences to RAG system"""
113
 
114
  # Initialize user if not already done
@@ -117,12 +118,20 @@ def save_preferences(name: str, favorite_genres: List[str], interests: List[str]
117
  radio_state["user_id"] = user_id
118
  print(f"👤 User initialized: {radio_state['user_id']}")
119
 
 
 
 
 
 
 
120
  preferences = {
121
  "name": name or "Friend",
122
  "favorite_genres": favorite_genres or ["pop"],
123
  "interests": interests or ["world"],
124
  "podcast_interests": podcast_interests or ["technology"],
125
  "mood": mood or "happy",
 
 
126
  "content_filter": {
127
  "music": music_filter,
128
  "news": news_filter,
@@ -137,6 +146,11 @@ def save_preferences(name: str, favorite_genres: List[str], interests: List[str]
137
 
138
  radio_state["user_preferences"] = preferences
139
 
 
 
 
 
 
140
  # Save to RAG system with user_id
141
  user_id = radio_state.get("user_id")
142
  agent.rag_system.store_user_preferences(preferences, user_id=user_id)
@@ -153,20 +167,33 @@ def get_saved_preferences() -> tuple:
153
  """Get saved preferences for the current user to autofill the form
154
 
155
  Returns:
156
- Tuple of (name, genres, interests, podcast_interests, mood, music, news, podcast, story)
157
  """
158
  user_id = radio_state.get("user_id")
159
  if not user_id:
160
  # Return defaults
161
- return "Friend", ["pop", "rock"], ["technology", "world"], ["technology"], "happy", True, True, True, True
162
 
163
  prefs = user_memory.get_user_preferences(user_id)
164
  if not prefs:
165
- return "Friend", ["pop", "rock"], ["technology", "world"], ["technology"], "happy", True, True, True, True
166
 
167
  # Extract content filter
168
  content_filter = prefs.get("content_filter", {})
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  return (
171
  prefs.get("name", "Friend"),
172
  prefs.get("favorite_genres", ["pop", "rock"]),
@@ -176,7 +203,8 @@ def get_saved_preferences() -> tuple:
176
  content_filter.get("music", True),
177
  content_filter.get("news", True),
178
  content_filter.get("podcasts", True),
179
- content_filter.get("stories", True)
 
180
  )
181
 
182
  def start_radio_stream():
@@ -200,7 +228,9 @@ def start_radio_stream():
200
  "favorite_genres": ["pop", "rock"],
201
  "interests": ["technology", "world"],
202
  "podcast_interests": ["technology"],
203
- "mood": "happy"
 
 
204
  }
205
  radio_state["content_filter"] = {
206
  "music": True, "news": True, "podcasts": True, "stories": True
@@ -263,7 +293,11 @@ def start_and_play_first_segment():
263
  radio_state["content_filter"] = saved.get("content_filter", {
264
  "music": True, "news": True, "podcasts": True, "stories": True
265
  })
 
 
 
266
  print(f"📂 Loaded saved preferences for user {user_id}")
 
267
 
268
  # Still no preferences? Use defaults
269
  if not radio_state["user_preferences"]:
@@ -272,11 +306,15 @@ def start_and_play_first_segment():
272
  "favorite_genres": ["pop", "rock"],
273
  "interests": ["technology", "world"],
274
  "podcast_interests": ["technology"],
275
- "mood": "happy"
 
 
276
  }
277
  radio_state["content_filter"] = {
278
  "music": True, "news": True, "podcasts": True, "stories": True
279
  }
 
 
280
  print("📋 Using default preferences")
281
 
282
  # Check if resuming from stopped state (only if show was manually stopped, not completed)
@@ -994,7 +1032,8 @@ def handle_voice_request():
994
  user_memory.add_to_history(user_id, track)
995
 
996
  # Generate host response using LLM
997
- host_response = agent.generate_song_request_response(recognized_text, track)
 
998
 
999
  # Generate TTS for host response
1000
  audio_file = None
@@ -1538,6 +1577,15 @@ with gr.Blocks(css=custom_css, title="AI Radio 🎵", theme=gr.themes.Soft(), he
1538
  choices=["happy", "energetic", "calm", "focused", "relaxed"],
1539
  value="happy"
1540
  )
 
 
 
 
 
 
 
 
 
1541
 
1542
  with gr.Column():
1543
  genres_input = gr.Dropdown(
@@ -1666,7 +1714,7 @@ with gr.Blocks(css=custom_css, title="AI Radio 🎵", theme=gr.themes.Soft(), he
1666
  save_pref_btn.click(
1667
  fn=save_preferences,
1668
  inputs=[name_input, genres_input, interests_input, podcast_input, mood_input,
1669
- music_filter, news_filter, podcast_filter, story_filter],
1670
  outputs=[pref_status]
1671
  ).then(
1672
  fn=lambda: gr.Tabs(selected=2), # Go to Radio Player tab (id=2)
@@ -1681,14 +1729,14 @@ with gr.Blocks(css=custom_css, title="AI Radio 🎵", theme=gr.themes.Soft(), he
1681
  # Get preferences to autofill
1682
  prefs = get_saved_preferences()
1683
  # Returns: message, user_id, passphrase, passphrase_display, auth_status_md,
1684
- # name, genres, interests, podcasts, mood, music, news, podcast, story
1685
  return (message, user_id, pp, pp, f"✅ Logged in! Passphrase: **{pp}**",
1686
  prefs[0], prefs[1], prefs[2], prefs[3], prefs[4],
1687
- prefs[5], prefs[6], prefs[7], prefs[8])
1688
  # Failed login - return empty/default values
1689
  return (message, "", "", "", "*Login failed*",
1690
  "Friend", ["pop", "rock"], ["technology", "world"], ["technology"], "happy",
1691
- True, True, True, True)
1692
 
1693
  def handle_create_account():
1694
  """Handle new account creation"""
@@ -1721,16 +1769,16 @@ Your passphrase: **{passphrase}**
1721
  • Play history: {stats.get('total_plays', 0)} tracks"""
1722
 
1723
  # Return: message, user_id, passphrase, passphrase_display,
1724
- # name, genres, interests, podcasts, mood, music, news, podcast, story
1725
  return (message, user_id, passphrase, passphrase,
1726
  prefs[0], prefs[1], prefs[2], prefs[3], prefs[4],
1727
- prefs[5], prefs[6], prefs[7], prefs[8])
1728
  else:
1729
  # No cookie - return defaults
1730
  return ("*No saved account found. Create a new account or log in with your passphrase.*",
1731
  "", "", "",
1732
  "Friend", ["pop", "rock"], ["technology", "world"], ["technology"], "happy",
1733
- True, True, True, True)
1734
 
1735
  # Login button - single handler that returns everything
1736
  login_btn.click(
@@ -1738,7 +1786,7 @@ Your passphrase: **{passphrase}**
1738
  inputs=[passphrase_input],
1739
  outputs=[login_status, auth_user_id, auth_passphrase, passphrase_display, auth_status,
1740
  name_input, genres_input, interests_input, podcast_input, mood_input,
1741
- music_filter, news_filter, podcast_filter, story_filter]
1742
  ).then(
1743
  # Save to localStorage via JS
1744
  fn=None,
@@ -1780,7 +1828,7 @@ Your passphrase: **{passphrase}**
1780
  inputs=[auth_user_id],
1781
  outputs=[auth_status, auth_user_id, auth_passphrase, passphrase_display,
1782
  name_input, genres_input, interests_input, podcast_input, mood_input,
1783
- music_filter, news_filter, podcast_filter, story_filter]
1784
  ).then(
1785
  # Save to localStorage (in case new user was created)
1786
  fn=None,
@@ -1844,7 +1892,7 @@ Your passphrase: **{passphrase}**
1844
  - **📰 Custom News**: Real-time news updates on topics you care about
1845
  - **🎙️ Podcast Recommendations**: Discover interesting podcasts matching your interests
1846
  - **📖 AI-Generated Stories**: Entertaining stories and fun facts
1847
- - **🤖 AI Host**: Dynamic AI radio host (Lera) that introduces segments
1848
  - **💾 Smart Recommendations**: RAG system learns from your listening history
1849
  - **🔐 User Accounts**: Passphrase-based authentication with saved preferences
1850
 
 
108
 
109
  def save_preferences(name: str, favorite_genres: List[str], interests: List[str],
110
  podcast_interests: List[str], mood: str,
111
+ music_filter: bool, news_filter: bool, podcast_filter: bool, story_filter: bool,
112
+ voice_name: str = None) -> str:
113
  """Save user preferences to RAG system"""
114
 
115
  # Initialize user if not already done
 
118
  radio_state["user_id"] = user_id
119
  print(f"👤 User initialized: {radio_state['user_id']}")
120
 
121
+ # Map voice name to voice_id if needed
122
+ from tts_service import VOICE_OPTIONS
123
+ selected_voice_id = "21m00Tcm4TlvDq8ikWAM" # Default Rachel
124
+ if voice_name and voice_name in VOICE_OPTIONS:
125
+ selected_voice_id = VOICE_OPTIONS[voice_name]
126
+
127
  preferences = {
128
  "name": name or "Friend",
129
  "favorite_genres": favorite_genres or ["pop"],
130
  "interests": interests or ["world"],
131
  "podcast_interests": podcast_interests or ["technology"],
132
  "mood": mood or "happy",
133
+ "voice_id": selected_voice_id,
134
+ "voice_name": voice_name or "Rachel (Female)",
135
  "content_filter": {
136
  "music": music_filter,
137
  "news": news_filter,
 
146
 
147
  radio_state["user_preferences"] = preferences
148
 
149
+ # Update TTS service with new voice
150
+ global tts_service
151
+ tts_service.voice_id = selected_voice_id
152
+ print(f"🎤 Voice updated to: {voice_name} (ID: {selected_voice_id})")
153
+
154
  # Save to RAG system with user_id
155
  user_id = radio_state.get("user_id")
156
  agent.rag_system.store_user_preferences(preferences, user_id=user_id)
 
167
  """Get saved preferences for the current user to autofill the form
168
 
169
  Returns:
170
+ Tuple of (name, genres, interests, podcast_interests, mood, music, news, podcast, story, voice_name)
171
  """
172
  user_id = radio_state.get("user_id")
173
  if not user_id:
174
  # Return defaults
175
+ return "Friend", ["pop", "rock"], ["technology", "world"], ["technology"], "happy", True, True, True, True, "Rachel (Female)"
176
 
177
  prefs = user_memory.get_user_preferences(user_id)
178
  if not prefs:
179
+ return "Friend", ["pop", "rock"], ["technology", "world"], ["technology"], "happy", True, True, True, True, "Rachel (Female)"
180
 
181
  # Extract content filter
182
  content_filter = prefs.get("content_filter", {})
183
 
184
+ # Get voice name (prefer stored voice_name, otherwise map from voice_id)
185
+ voice_name = prefs.get("voice_name", "Rachel (Female)")
186
+ if not voice_name or voice_name not in ["Rachel (Female)", "Lera (Female)", "Bella (Female)", "Antoni (Male)", "Arnold (Male)", "Adam (Male)", "Domi (Female)", "Elli (Female)", "Josh (Male)", "Sam (Male)"]:
187
+ # Try to map voice_id back to name
188
+ from tts_service import VOICE_OPTIONS
189
+ voice_id = prefs.get("voice_id", "21m00Tcm4TlvDq8ikWAM")
190
+ for name, vid in VOICE_OPTIONS.items():
191
+ if vid == voice_id:
192
+ voice_name = name
193
+ break
194
+ else:
195
+ voice_name = "Rachel (Female)" # Default fallback
196
+
197
  return (
198
  prefs.get("name", "Friend"),
199
  prefs.get("favorite_genres", ["pop", "rock"]),
 
203
  content_filter.get("music", True),
204
  content_filter.get("news", True),
205
  content_filter.get("podcasts", True),
206
+ content_filter.get("stories", True),
207
+ voice_name
208
  )
209
 
210
  def start_radio_stream():
 
228
  "favorite_genres": ["pop", "rock"],
229
  "interests": ["technology", "world"],
230
  "podcast_interests": ["technology"],
231
+ "mood": "happy",
232
+ "voice_id": "21m00Tcm4TlvDq8ikWAM",
233
+ "voice_name": "Rachel (Female)"
234
  }
235
  radio_state["content_filter"] = {
236
  "music": True, "news": True, "podcasts": True, "stories": True
 
293
  radio_state["content_filter"] = saved.get("content_filter", {
294
  "music": True, "news": True, "podcasts": True, "stories": True
295
  })
296
+ # Update TTS service with saved voice
297
+ voice_id = saved.get("voice_id", "21m00Tcm4TlvDq8ikWAM")
298
+ tts_service.voice_id = voice_id
299
  print(f"📂 Loaded saved preferences for user {user_id}")
300
+ print(f"🎤 Voice set to: {saved.get('voice_name', 'Rachel (Female)')} (ID: {voice_id})")
301
 
302
  # Still no preferences? Use defaults
303
  if not radio_state["user_preferences"]:
 
306
  "favorite_genres": ["pop", "rock"],
307
  "interests": ["technology", "world"],
308
  "podcast_interests": ["technology"],
309
+ "mood": "happy",
310
+ "voice_id": "21m00Tcm4TlvDq8ikWAM",
311
+ "voice_name": "Rachel (Female)"
312
  }
313
  radio_state["content_filter"] = {
314
  "music": True, "news": True, "podcasts": True, "stories": True
315
  }
316
+ # Update TTS service with default voice
317
+ tts_service.voice_id = "21m00Tcm4TlvDq8ikWAM"
318
  print("📋 Using default preferences")
319
 
320
  # Check if resuming from stopped state (only if show was manually stopped, not completed)
 
1032
  user_memory.add_to_history(user_id, track)
1033
 
1034
  # Generate host response using LLM
1035
+ user_prefs = radio_state.get("user_preferences", {})
1036
+ host_response = agent.generate_song_request_response(recognized_text, track, user_prefs)
1037
 
1038
  # Generate TTS for host response
1039
  audio_file = None
 
1577
  choices=["happy", "energetic", "calm", "focused", "relaxed"],
1578
  value="happy"
1579
  )
1580
+
1581
+ # Add voice selection dropdown
1582
+ from tts_service import VOICE_OPTIONS
1583
+ voice_input = gr.Dropdown(
1584
+ label="🎤 Host Voice",
1585
+ choices=list(VOICE_OPTIONS.keys()),
1586
+ value="Rachel (Female)",
1587
+ info="Choose the voice for your radio host"
1588
+ )
1589
 
1590
  with gr.Column():
1591
  genres_input = gr.Dropdown(
 
1714
  save_pref_btn.click(
1715
  fn=save_preferences,
1716
  inputs=[name_input, genres_input, interests_input, podcast_input, mood_input,
1717
+ music_filter, news_filter, podcast_filter, story_filter, voice_input],
1718
  outputs=[pref_status]
1719
  ).then(
1720
  fn=lambda: gr.Tabs(selected=2), # Go to Radio Player tab (id=2)
 
1729
  # Get preferences to autofill
1730
  prefs = get_saved_preferences()
1731
  # Returns: message, user_id, passphrase, passphrase_display, auth_status_md,
1732
+ # name, genres, interests, podcasts, mood, music, news, podcast, story, voice
1733
  return (message, user_id, pp, pp, f"✅ Logged in! Passphrase: **{pp}**",
1734
  prefs[0], prefs[1], prefs[2], prefs[3], prefs[4],
1735
+ prefs[5], prefs[6], prefs[7], prefs[8], prefs[9])
1736
  # Failed login - return empty/default values
1737
  return (message, "", "", "", "*Login failed*",
1738
  "Friend", ["pop", "rock"], ["technology", "world"], ["technology"], "happy",
1739
+ True, True, True, True, "Rachel (Female)")
1740
 
1741
  def handle_create_account():
1742
  """Handle new account creation"""
 
1769
  • Play history: {stats.get('total_plays', 0)} tracks"""
1770
 
1771
  # Return: message, user_id, passphrase, passphrase_display,
1772
+ # name, genres, interests, podcasts, mood, music, news, podcast, story, voice
1773
  return (message, user_id, passphrase, passphrase,
1774
  prefs[0], prefs[1], prefs[2], prefs[3], prefs[4],
1775
+ prefs[5], prefs[6], prefs[7], prefs[8], prefs[9])
1776
  else:
1777
  # No cookie - return defaults
1778
  return ("*No saved account found. Create a new account or log in with your passphrase.*",
1779
  "", "", "",
1780
  "Friend", ["pop", "rock"], ["technology", "world"], ["technology"], "happy",
1781
+ True, True, True, True, "Rachel (Female)")
1782
 
1783
  # Login button - single handler that returns everything
1784
  login_btn.click(
 
1786
  inputs=[passphrase_input],
1787
  outputs=[login_status, auth_user_id, auth_passphrase, passphrase_display, auth_status,
1788
  name_input, genres_input, interests_input, podcast_input, mood_input,
1789
+ music_filter, news_filter, podcast_filter, story_filter, voice_input]
1790
  ).then(
1791
  # Save to localStorage via JS
1792
  fn=None,
 
1828
  inputs=[auth_user_id],
1829
  outputs=[auth_status, auth_user_id, auth_passphrase, passphrase_display,
1830
  name_input, genres_input, interests_input, podcast_input, mood_input,
1831
+ music_filter, news_filter, podcast_filter, story_filter, voice_input]
1832
  ).then(
1833
  # Save to localStorage (in case new user was created)
1834
  fn=None,
 
1892
  - **📰 Custom News**: Real-time news updates on topics you care about
1893
  - **🎙️ Podcast Recommendations**: Discover interesting podcasts matching your interests
1894
  - **📖 AI-Generated Stories**: Entertaining stories and fun facts
1895
+ - **🤖 AI Host**: Dynamic AI radio host that introduces segments (choose your preferred voice!)
1896
  - **💾 Smart Recommendations**: RAG system learns from your listening history
1897
  - **🔐 User Accounts**: Passphrase-based authentication with saved preferences
1898
 
src/config.py CHANGED
@@ -6,7 +6,7 @@ class RadioConfig(BaseModel):
6
  """Configuration for the radio app"""
7
 
8
  # API Keys
9
- elevenlabs_api_key: str = "sk_2dde999f3cedf21dff7ba4671ce27f292e48ea37d30c5e4a"
10
  llamaindex_api_key: str = "llx-WRsj0iehk2ZlSlNIenOLyyhO9X1yFT4CmJXpl0qk6hapFi01"
11
  nebius_api_key: str = "v1.CmQKHHN0YXRpY2tleS1lMDB0eTkxeTdwY3lxNDk5OWcSIXNlcnZpY2VhY2NvdW50LWUwMGowemtmZWpqc2E3ZHF3aDIMCKb4oskGENS9j8MBOgwIpfu6lAcQgOqAhwNAAloDZTAw.AAAAAAAAAAGEI_L5sJCQ7XR93nSzvXCPO-J3-gHjqPiRqrvkrMLeDtd-70zGWB1-c8yovnX-q7yEc1dHOnA2L8FUa3Le6X8D"
12
  # Nebius OpenAI-compatible endpoint and model
 
6
  """Configuration for the radio app"""
7
 
8
  # API Keys
9
+ elevenlabs_api_key: str = "sk_d0a3622fe5acdf6c747236728ef193a3726c29bc943519d5"
10
  llamaindex_api_key: str = "llx-WRsj0iehk2ZlSlNIenOLyyhO9X1yFT4CmJXpl0qk6hapFi01"
11
  nebius_api_key: str = "v1.CmQKHHN0YXRpY2tleS1lMDB0eTkxeTdwY3lxNDk5OWcSIXNlcnZpY2VhY2NvdW50LWUwMGowemtmZWpqc2E3ZHF3aDIMCKb4oskGENS9j8MBOgwIpfu6lAcQgOqAhwNAAloDZTAw.AAAAAAAAAAGEI_L5sJCQ7XR93nSzvXCPO-J3-gHjqPiRqrvkrMLeDtd-70zGWB1-c8yovnX-q7yEc1dHOnA2L8FUa3Le6X8D"
12
  # Nebius OpenAI-compatible endpoint and model
src/radio_agent.py CHANGED
@@ -203,9 +203,13 @@ class RadioAgent:
203
  time_of_day = self._get_time_of_day()
204
  interests = preferences.get('interests', ['technology'])
205
 
 
 
 
 
206
  if self.client:
207
  try:
208
- prompt = f"""You are a charismatic, entertaining radio host named Lera, you work on AI RADIO. Create a warm, engaging {time_of_day} greeting
209
  for {name}. The listener is feeling {mood} and interested in {', '.join(interests[:2])}.
210
 
211
  Make it:
@@ -415,8 +419,12 @@ class RadioAgent:
415
  else:
416
  print(f" ℹ️ [RAG] No context available - using standard prompt")
417
 
 
 
 
 
418
  try:
419
- prompt = f"""You are an energetic, entertaining radio DJ named Lera. This is a middle of a show. Introduce this song in a fun, engaging way:
420
  Title: {track['title']}
421
  Artist: {track['artist']}
422
  Genre: {track['genre']}
@@ -496,8 +504,12 @@ class RadioAgent:
496
  news_text = "\n".join([f"- {item['title']}: {item['summary']}" for item in valid_items])
497
  print(f"📰 Generating news script from {len(valid_items)} items")
498
 
499
- prompt = f"""You are a professional radio news presenter named Lera. Say that this is news time on the show.
500
- Present these news items in a conversational, engaging way. Text includes only what Lera needs to say. This should be about 1 minute of speech (about 150-200 words):
 
 
 
 
501
 
502
  News items:
503
  {news_text}
@@ -583,8 +595,12 @@ Requirements:
583
  if not podcast or not self.client:
584
  return "Time for an interesting podcast!"
585
 
 
 
 
 
586
  try:
587
- prompt = f"""You are a radio host Lera introducing a podcast. This is a middle of the show. Create a brief, engaging intro (2-3 sentences, max_tokens = 200):
588
 
589
  Podcast: {podcast['title']}
590
  Host: {podcast['host']}
@@ -612,14 +628,21 @@ Requirements:
612
  except Exception as e:
613
  return f"Check out {podcast['title']} hosted by {podcast['host']}!"
614
 
615
- def _generate_story(self, mood: str, interests: List[str]) -> str:
616
  """Generate an interesting story or fun fact"""
617
  if not self.client:
618
  return "Here's a fun fact: Music can boost your mood and productivity!"
619
 
 
 
 
 
 
 
 
620
  try:
621
  interest = random.choice(interests) if interests else "general knowledge"
622
- prompt = f"""Share a fascinating, {mood} story or fun fact about {interest}.
623
  Keep it engaging and under 100-200 words. max_tokens = 600"""
624
 
625
  response = self.client.chat.completions.create(
@@ -642,17 +665,24 @@ Requirements:
642
  except Exception as e:
643
  return "Here's something interesting: The world's oldest radio station has been broadcasting since 1920!"
644
 
645
- def generate_song_request_response(self, user_request: str, track: Dict[str, Any]) -> str:
646
  """Generate a short, engaging host response for a voice song request"""
647
  title = track.get('title', 'this track')
648
  artist = track.get('artist', 'Unknown Artist')
649
  genre = track.get('genre', '')
650
 
 
 
 
 
 
 
 
651
  if not self.client:
652
  return f"Great choice! Here's '{title}' by {artist} coming right up!"
653
 
654
  try:
655
- prompt = f"""You are an upbeat radio DJ. A listener just asked: "{user_request}"
656
  You found the song "{title}" by {artist}{f' ({genre})' if genre else ''}.
657
 
658
  Write a SHORT, enthusiastic 1-2 sentence response (max 200 tokens) introducing the song.
@@ -827,7 +857,7 @@ Don't use emojis. Just speak naturally like a real DJ."""
827
  if not segment.get("content"):
828
  mood = prefs.get("mood", "happy")
829
  interests = prefs.get("interests", ["technology"])
830
- segment["content"] = self._generate_story(mood, interests)
831
 
832
  return segment
833
 
 
203
  time_of_day = self._get_time_of_day()
204
  interests = preferences.get('interests', ['technology'])
205
 
206
+ # Get voice name from preferences (remove gender suffix)
207
+ voice_name = preferences.get('voice_name', 'Rachel (Female)')
208
+ host_name = voice_name.split(' (')[0] if ' (' in voice_name else voice_name
209
+
210
  if self.client:
211
  try:
212
+ prompt = f"""You are a charismatic, entertaining radio host named {host_name}, you work on AI RADIO. Create a warm, engaging {time_of_day} greeting
213
  for {name}. The listener is feeling {mood} and interested in {', '.join(interests[:2])}.
214
 
215
  Make it:
 
419
  else:
420
  print(f" ℹ️ [RAG] No context available - using standard prompt")
421
 
422
+ # Get voice name from preferences (remove gender suffix)
423
+ voice_name = preferences.get('voice_name', 'Rachel (Female)')
424
+ host_name = voice_name.split(' (')[0] if ' (' in voice_name else voice_name
425
+
426
  try:
427
+ prompt = f"""You are an energetic, entertaining radio DJ named {host_name}. This is a middle of a show. Introduce this song in a fun, engaging way:
428
  Title: {track['title']}
429
  Artist: {track['artist']}
430
  Genre: {track['genre']}
 
504
  news_text = "\n".join([f"- {item['title']}: {item['summary']}" for item in valid_items])
505
  print(f"📰 Generating news script from {len(valid_items)} items")
506
 
507
+ # Get voice name from preferences (remove gender suffix)
508
+ voice_name = preferences.get('voice_name', 'Rachel (Female)')
509
+ host_name = voice_name.split(' (')[0] if ' (' in voice_name else voice_name
510
+
511
+ prompt = f"""You are a professional radio news presenter named {host_name}. Say that this is news time on the show.
512
+ Present these news items in a conversational, engaging way. Text includes only what {host_name} needs to say. This should be about 1 minute of speech (about 150-200 words):
513
 
514
  News items:
515
  {news_text}
 
595
  if not podcast or not self.client:
596
  return "Time for an interesting podcast!"
597
 
598
+ # Get voice name from preferences (remove gender suffix)
599
+ voice_name = preferences.get('voice_name', 'Rachel (Female)')
600
+ host_name = voice_name.split(' (')[0] if ' (' in voice_name else voice_name
601
+
602
  try:
603
+ prompt = f"""You are a radio host {host_name} introducing a podcast. This is a middle of the show. Create a brief, engaging intro (2-3 sentences, max_tokens = 200):
604
 
605
  Podcast: {podcast['title']}
606
  Host: {podcast['host']}
 
628
  except Exception as e:
629
  return f"Check out {podcast['title']} hosted by {podcast['host']}!"
630
 
631
+ def _generate_story(self, mood: str, interests: List[str], preferences: Dict[str, Any] = None) -> str:
632
  """Generate an interesting story or fun fact"""
633
  if not self.client:
634
  return "Here's a fun fact: Music can boost your mood and productivity!"
635
 
636
+ # Get voice name from preferences (remove gender suffix)
637
+ if preferences:
638
+ voice_name = preferences.get('voice_name', 'Rachel (Female)')
639
+ host_name = voice_name.split(' (')[0] if ' (' in voice_name else voice_name
640
+ else:
641
+ host_name = "Rachel" # Default fallback
642
+
643
  try:
644
  interest = random.choice(interests) if interests else "general knowledge"
645
+ prompt = f"""You are a radio host named {host_name}. Share a fascinating, {mood} story or fun fact about {interest}.
646
  Keep it engaging and under 100-200 words. max_tokens = 600"""
647
 
648
  response = self.client.chat.completions.create(
 
665
  except Exception as e:
666
  return "Here's something interesting: The world's oldest radio station has been broadcasting since 1920!"
667
 
668
+ def generate_song_request_response(self, user_request: str, track: Dict[str, Any], preferences: Dict[str, Any] = None) -> str:
669
  """Generate a short, engaging host response for a voice song request"""
670
  title = track.get('title', 'this track')
671
  artist = track.get('artist', 'Unknown Artist')
672
  genre = track.get('genre', '')
673
 
674
+ # Get voice name from preferences (remove gender suffix)
675
+ if preferences:
676
+ voice_name = preferences.get('voice_name', 'Rachel (Female)')
677
+ host_name = voice_name.split(' (')[0] if ' (' in voice_name else voice_name
678
+ else:
679
+ host_name = "Rachel" # Default fallback
680
+
681
  if not self.client:
682
  return f"Great choice! Here's '{title}' by {artist} coming right up!"
683
 
684
  try:
685
+ prompt = f"""You are an upbeat radio DJ named {host_name}. A listener just asked: "{user_request}"
686
  You found the song "{title}" by {artist}{f' ({genre})' if genre else ''}.
687
 
688
  Write a SHORT, enthusiastic 1-2 sentence response (max 200 tokens) introducing the song.
 
857
  if not segment.get("content"):
858
  mood = prefs.get("mood", "happy")
859
  interests = prefs.get("interests", ["technology"])
860
+ segment["content"] = self._generate_story(mood, interests, prefs)
861
 
862
  return segment
863
 
src/tts_service.py CHANGED
@@ -3,6 +3,20 @@ import os
3
  from typing import Optional, List, Dict
4
  from elevenlabs import ElevenLabs, VoiceSettings
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  class TTSService:
8
  """Text-to-Speech service using ElevenLabs API"""
 
3
  from typing import Optional, List, Dict
4
  from elevenlabs import ElevenLabs, VoiceSettings
5
 
6
+ # Voice options mapping (name -> voice_id)
7
+ VOICE_OPTIONS = {
8
+ "Rachel (Female)": "21m00Tcm4TlvDq8ikWAM", # Default - Rachel
9
+ "Lera (Female)": "EXAVITQu4vr4xnSDxMaL", # Lera - female voice
10
+ "Bella (Female)": "EXAVITQu4vr4xnSDxMaL", # Alternative female
11
+ "Antoni (Male)": "ErXwobaYiN019PkySvjV", # Antoni - male voice
12
+ "Arnold (Male)": "VR6AewLTigWG4xSOukaG", # Arnold - male voice
13
+ "Adam (Male)": "pNInz6obpgDQGcFmaJgB", # Adam - male voice
14
+ "Domi (Female)": "AZnzlk1XvdvUeBnXmlld", # Domi - female voice
15
+ "Elli (Female)": "MF3mGyEYCl7XYWbV9V6O", # Elli - female voice
16
+ "Josh (Male)": "TxGEqnHWrfWFTfGW9XjX", # Josh - male voice
17
+ "Sam (Male)": "yoZ06aMxZJJ28mfd3POQ", # Sam - male voice
18
+ }
19
+
20
 
21
  class TTSService:
22
  """Text-to-Speech service using ElevenLabs API"""