Spaces:
Sleeping
Sleeping
Nikita Makarov
commited on
Commit
·
9b6a0be
1
Parent(s):
c04db7b
v2.4
Browse files- src/app.py +66 -18
- src/config.py +1 -1
- src/radio_agent.py +40 -10
- 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
|
|
|
|
| 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 |
-
|
|
|
|
| 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
|
| 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 = "
|
| 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
|
| 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
|
| 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 |
-
|
| 500 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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"""
|