sjw commited on
Commit
127301c
โ€ข
1 Parent(s): adb5f30

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +903 -0
  2. messages.py +106 -0
  3. requirements.txt +14 -0
app.py ADDED
@@ -0,0 +1,903 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Standard Library Imports
2
+ import os
3
+ import random
4
+ import re
5
+ import time
6
+ from urllib.parse import urlparse, parse_qs
7
+
8
+ # Third-Party Imports
9
+ import gradio as gr
10
+ import lyricsgenius
11
+ import requests
12
+ import spotipy
13
+ from bs4 import BeautifulSoup
14
+ from dotenv import load_dotenv
15
+ from fuzzywuzzy import fuzz
16
+ from pydantic import BaseModel, Field
17
+ from requests.exceptions import Timeout
18
+ from sentence_transformers import SentenceTransformer
19
+ from sklearn.metrics.pairwise import cosine_similarity
20
+ from spotipy.exceptions import SpotifyException
21
+
22
+ # Local Application/Library Specific Imports
23
+ import openai
24
+ from langchain.agents import OpenAIFunctionsAgent, AgentExecutor, tool
25
+ from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
26
+ from langchain.chat_models import ChatOpenAI
27
+ from langchain.memory import ConversationBufferMemory
28
+ from langchain.prompts import MessagesPlaceholder
29
+ from langchain.schema import SystemMessage, HumanMessage
30
+
31
+
32
+ from messages import SYSTEM_MESSAGE, GENRE_LIST, HTML, APOLLO_MESSAGE
33
+ from dotenv import load_dotenv
34
+ load_dotenv()
35
+
36
+
37
+ # ------------------------------
38
+ # Section: Global Vars
39
+ # ------------------------------
40
+
41
+
42
+ GENIUS_TOKEN = os.getenv("GENIUS_ACCESS_TOKEN")
43
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
44
+
45
+ DEBUG_MODE = True
46
+ def debug_print(*args, **kwargs):
47
+ if DEBUG_MODE:
48
+ print(*args, **kwargs)
49
+
50
+ THEME = gr.themes.Default(
51
+ primary_hue=gr.themes.colors.blue,
52
+ font=[gr.themes.GoogleFont("Space Mono"), "monospace", "sans-serif"],
53
+ spacing_size=gr.themes.sizes.spacing_sm,
54
+ radius_size=gr.themes.sizes.radius_sm,
55
+ text_size=gr.themes.sizes.text_lg
56
+ ).set(
57
+ body_background_fill="#000000",
58
+ #button_primary_background_fill="white",
59
+ #button_primary_text_color="black",
60
+ button_primary_background_fill_hover="black",
61
+ #button_primary_text_color_hover="white"
62
+
63
+ )
64
+
65
+ # TODO: switch to personal website
66
+ REDIRECT_URI = "https://jonaswaller.com"
67
+
68
+ # Spotify functions
69
+ SCOPE = [
70
+ 'user-library-read',
71
+ 'user-read-playback-state',
72
+ 'user-modify-playback-state',
73
+ 'playlist-modify-public',
74
+ 'user-top-read'
75
+ ]
76
+
77
+ MOOD_SETTINGS = {
78
+ "happy": {"max_instrumentalness": 0.001, "min_valence": 0.6},
79
+ "sad": {"max_danceability": 0.65, "max_valence": 0.4},
80
+ "energetic": {"min_tempo": 120, "min_danceability": 0.75},
81
+ "calm": {"max_energy": 0.65, "max_tempo": 130}
82
+ }
83
+
84
+ # genre + mood function
85
+ NUM_ARTISTS = 20 # artists to retrieve from user's top artists
86
+ TIME_RANGE = "medium_term" # short, medium, long
87
+ NUM_TRACKS = 10 # tracks to add to playback
88
+ MAX_ARTISTS = 4 # sp.recommendations() seeds: 4/5 artists, 1/5 genre
89
+
90
+ # artist + mood function
91
+ NUM_ALBUMS = 20 # maximum number of albums to retrieve from an artist
92
+ MAX_TRACKS = 10 # tracks to randomly select from an artist
93
+
94
+ # matching playlists + moods
95
+ MODEL = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2') # smaller BERT
96
+ os.environ["TOKENIZERS_PARALLELISM"] = "false" # warning
97
+ MOOD_LIST = ["happy", "sad", "energetic", "calm"]
98
+ MOOD_EMBEDDINGS = MODEL.encode(MOOD_LIST)
99
+ GENRE_EMBEDDINGS = MODEL.encode(GENRE_LIST)
100
+
101
+ # agent tools
102
+ RETURN_DIRECT = True
103
+
104
+ #LLM_MODEL = "gpt-3.5-turbo-0613"
105
+ LLM_MODEL = "gpt-4"
106
+
107
+ # adjectives for playlist names
108
+ THEMES = ["Epic", "Hypnotic", "Dreamy", "Legendary", "Majestic",
109
+ "Enchanting", "Ethereal", "Super Lit", "Harmonious", "Heroic"]
110
+
111
+
112
+ with gr.Blocks(theme=THEME) as app:
113
+
114
+ # ------------------------------
115
+ # Section: Spotify Authentication
116
+ # ------------------------------
117
+ gr.HTML(HTML)
118
+
119
+ # incredibly important for preserving using isolation
120
+ ACCESS_TOKEN_VAR = gr.State()
121
+ AGENT_EXECUTOR_VAR = gr.State()
122
+
123
+ with gr.Row():
124
+ client_id = gr.Textbox(placeholder="7. Paste Spotify Client ID", container=False, text_align="left")
125
+ generate_link = gr.Button("Submit ID", variant="primary")
126
+ display_link = gr.HTML()
127
+
128
+ with gr.Row():
129
+ url = gr.Textbox(placeholder="9. Paste entire URL", container=False, text_align="left")
130
+ authorize_url = gr.Button("Submit URL", variant="primary")
131
+ auth_result = gr.HTML()
132
+
133
+
134
+ def spotify_auth(client_id, url=None, access_tokens=None):
135
+ """
136
+ Authenticate Spotify with the provided client_id and url.
137
+ """
138
+ if url:
139
+ parsed_url = urlparse(url)
140
+ fragment = parsed_url.fragment
141
+ access_token = parse_qs(fragment)['access_token'][0]
142
+ debug_print(access_token)
143
+
144
+ return access_token, '<p class="hover-item" style="color: #CECECE; font-size: 26px; padding: 12px 0; text-align: left;">Your Spotify is connected!</p>'
145
+
146
+ else:
147
+ auth_url = (
148
+ f"https://accounts.spotify.com/authorize?response_type=token&client_id={client_id}"
149
+ f"&scope={'%20'.join(SCOPE)}&redirect_uri={REDIRECT_URI}"
150
+ )
151
+ return f'<p class="hover-item" style="color: #CECECE; font-size: 26px; padding: 12px 0;">' \
152
+ f'<span style="color: #5491FA;">8.</span> Click <a href="{auth_url}" target="_blank" ' \
153
+ f'style="color: inherit !important; text-decoration: none !important;" ' \
154
+ f'onmouseover="this.style.fontSize=\'105%\';" onmouseout="this.style.fontSize=\'100%\';">' \
155
+ f'here</a> and copy the entire URL</p>'
156
+
157
+
158
+ generate_link.click(spotify_auth, inputs=[client_id], outputs=display_link)
159
+ authorize_url.click(spotify_auth, inputs=[client_id, url, ACCESS_TOKEN_VAR], outputs=[ACCESS_TOKEN_VAR, auth_result])
160
+
161
+ create_agent_button = gr.Button("Start Session", variant="primary")
162
+ create_agent_result = gr.HTML()
163
+
164
+ def create_agent(access_token):
165
+
166
+ # included 'client' parameter to fix error
167
+ llm = ChatOpenAI(
168
+ model=LLM_MODEL,
169
+ openai_api_key=OPENAI_API_KEY,
170
+ streaming=True,
171
+ callbacks=[StreamingStdOutCallbackHandler()],
172
+ client=openai.ChatCompletion
173
+ )
174
+
175
+
176
+ # ------------------------------
177
+ # Section: Spotify Functions
178
+ # ------------------------------
179
+
180
+
181
+ sp = spotipy.Spotify(auth=access_token)
182
+ devices = sp.devices()
183
+ device_id = devices['devices'][0]['id']
184
+
185
+
186
+ def find_track_by_name(track_name):
187
+ """
188
+ Finds the Spotify track URI given the track name.
189
+ """
190
+ results = sp.search(q=track_name, type='track')
191
+ track_uri = results['tracks']['items'][0]['uri']
192
+ return track_uri
193
+
194
+
195
+ def play_track_by_name(track_name):
196
+ """
197
+ Plays a track given its name. Uses the above function.
198
+ """
199
+ track_uri = find_track_by_name(track_name)
200
+ track_name = sp.track(track_uri)["name"]
201
+ artist_name = sp.track(track_uri)['artists'][0]['name']
202
+
203
+ try:
204
+ sp.start_playback(device_id=device_id, uris=[track_uri])
205
+ return f"โ™ซ Now playing {track_name} by {artist_name} โ™ซ"
206
+ except SpotifyException as e:
207
+ return f"An error occurred with Spotify: {e}. \n\n**Remember to wake up Spotify.**"
208
+ except Exception as e:
209
+ return f"An unexpected error occurred: {e}."
210
+
211
+
212
+ def queue_track_by_name(track_name):
213
+ """
214
+ Queues track given its name.
215
+ """
216
+ track_uri = find_track_by_name(track_name)
217
+ track_name = sp.track(track_uri)["name"]
218
+ sp.add_to_queue(uri=track_uri, device_id=device_id)
219
+ return f"โ™ซ Added {track_name} to your queue โ™ซ"
220
+
221
+
222
+ def pause_track():
223
+ """
224
+ Pauses the current playback.
225
+ """
226
+ sp.pause_playback(device_id=device_id)
227
+ return "โ™ซ Playback paused โ™ซ"
228
+
229
+
230
+ def resume_track():
231
+ """
232
+ Resumes the current playback.
233
+ """
234
+ sp.start_playback(device_id=device_id)
235
+ return "โ™ซ Playback started โ™ซ"
236
+
237
+
238
+ def skip_track():
239
+ """
240
+ Skips the current playback.
241
+ """
242
+ sp.next_track(device_id=device_id)
243
+ return "โ™ซ Skipped to your next track โ™ซ"
244
+
245
+
246
+ ### ### ### More Elaborate Functions ### ### ###
247
+
248
+
249
+ def play_album_by_name_and_artist(album_name, artist_name):
250
+ """
251
+ Plays an album given its name and the artist.
252
+ context_uri (provide a context_uri to start playback of an album, artist, or playlist) expects a string.
253
+ """
254
+ results = sp.search(q=f'{album_name} {artist_name}', type='album')
255
+ album_id = results['albums']['items'][0]['id']
256
+ album_info = sp.album(album_id)
257
+ album_name = album_info['name']
258
+ artist_name = album_info['artists'][0]['name']
259
+
260
+ try:
261
+ sp.start_playback(device_id=device_id, context_uri=f'spotify:album:{album_id}')
262
+ return f"โ™ซ Now playing {album_name} by {artist_name} โ™ซ"
263
+ except spotipy.SpotifyException as e:
264
+ return f"An error occurred with Spotify: {e}. \n\n**Remember to wake up Spotify.**"
265
+ except Timeout:
266
+ return f"An unexpected error occurred: {e}."
267
+
268
+
269
+ def play_playlist_by_name(playlist_name):
270
+ """
271
+ Plays an existing playlist in the user's library given its name.
272
+ """
273
+ playlists = sp.current_user_playlists()
274
+ playlist_dict = {playlist['name']: (playlist['id'], playlist['owner']['display_name']) for playlist in playlists['items']}
275
+ playlist_names = [key for key in playlist_dict.keys()]
276
+
277
+ # defined inside to capture user-specific playlists
278
+ playlist_name_embeddings = MODEL.encode(playlist_names)
279
+ user_playlist_embedding = MODEL.encode([playlist_name])
280
+
281
+ # compares (embedded) given name to (embedded) playlist library and outputs the closest match
282
+ similarity_scores = cosine_similarity(user_playlist_embedding, playlist_name_embeddings)
283
+ most_similar_index = similarity_scores.argmax()
284
+ playlist_name = playlist_names[most_similar_index]
285
+
286
+ try:
287
+ playlist_id, creator_name = playlist_dict[playlist_name]
288
+ sp.start_playback(device_id=device_id, context_uri=f'spotify:playlist:{playlist_id}')
289
+ return f'โ™ซ Now playing {playlist_name} by {creator_name} โ™ซ'
290
+ except:
291
+ return "Unable to find playlist. Please try again."
292
+
293
+
294
+ def get_track_info():
295
+ """
296
+ Harvests information for explain_track() using Genius' API and basic webscraping.
297
+ """
298
+ current_track_item = sp.current_user_playing_track()['item']
299
+ track_name = current_track_item['name']
300
+ artist_name = current_track_item['artists'][0]['name']
301
+ album_name = current_track_item['album']['name']
302
+ release_date = current_track_item['album']['release_date']
303
+ basic_info = {
304
+ 'track_name': track_name,
305
+ 'artist_name': artist_name,
306
+ 'album_name': album_name,
307
+ 'release_date': release_date,
308
+ }
309
+
310
+ # define inside to avoid user conflicts (simultaneously query Genius)
311
+ genius = lyricsgenius.Genius(GENIUS_TOKEN)
312
+ # removing feature information from song titles to avoid scewing search
313
+ track_name = re.split(' \(with | \(feat\. ', track_name)[0]
314
+ result = genius.search_song(track_name, artist_name)
315
+
316
+ # if no Genius page exists
317
+ if result is not None and hasattr(result, 'artist'):
318
+ genius_artist = result.artist.lower().replace(" ", "")
319
+ spotify_artist = artist_name.lower().replace(" ", "")
320
+ debug_print(spotify_artist)
321
+ debug_print(genius_artist)
322
+ if spotify_artist not in genius_artist:
323
+ return basic_info, None, None, None
324
+ else:
325
+ genius_artist = None
326
+ return basic_info, None, None, None
327
+
328
+ # if Genius page exists
329
+ lyrics = result.lyrics
330
+ url = result.url
331
+ response = requests.get(url)
332
+
333
+ # parsing the webpage and locating 'About' section
334
+ soup = BeautifulSoup(response.text, 'html.parser')
335
+ # universal 'About' section element across all Genius song lyrics pages
336
+ about_section = soup.select_one('div[class^="RichText__Container-oz284w-0"]')
337
+
338
+ # if no 'About' section exists
339
+ if not about_section:
340
+ return basic_info, None, lyrics, url
341
+
342
+ # if 'About' section exists
343
+ else:
344
+ about_section = about_section.get_text(separator='\n')
345
+ return basic_info, about_section, lyrics, url
346
+
347
+
348
+ def explain_track():
349
+ """
350
+ Displays track information in an organized, informational, and compelling manner.
351
+ Uses the above function.
352
+ """
353
+ basic_info, about_section, lyrics, url = get_track_info()
354
+ debug_print(basic_info, about_section, lyrics, url)
355
+
356
+ if lyrics: # if Genius page exists
357
+ system_message_content = """
358
+ Your task is to create an engaging summary for a track using the available details
359
+ about the track and its lyrics. If there's insufficient or no additional information
360
+ besides the lyrics, craft the entire summary based solely on the lyrical content."
361
+ """
362
+ human_message_content = f"{about_section}\n\n{lyrics}"
363
+ messages = [
364
+ SystemMessage(content=system_message_content),
365
+ HumanMessage(content=human_message_content)
366
+ ]
367
+ ai_response = llm(messages).content
368
+ summary = f"""
369
+ **Name:** <span style="color: #E457E2; font-weight: bold; font-style: italic;">{basic_info["track_name"]}</span>
370
+ **Artist:** {basic_info["artist_name"]}
371
+ **Album:** {basic_info["album_name"]}
372
+ **Release:** {basic_info["release_date"]}
373
+
374
+ **About:**
375
+ {ai_response}
376
+
377
+ <a href='{url}'>Click here for more information on Genius!</a>
378
+ """
379
+ return summary
380
+
381
+ else: # if no Genius page exists
382
+ url = "https://genius.com/Genius-how-to-add-songs-to-genius-annotated"
383
+ summary = f"""
384
+ **Name:** <span style="color: #E457E2; font-weight: bold; font-style: italic;">{basic_info["track_name"]}</span>
385
+ **Artist:** {basic_info["artist_name"]}
386
+ **Album:** {basic_info["album_name"]}
387
+ **Release:** {basic_info["release_date"]}
388
+
389
+ **About:**
390
+ Unfortunately, this track has not been uploaded to Genius.com
391
+
392
+ <a href='{url}'>Be the first to change that!</a>
393
+ """
394
+ return summary
395
+
396
+
397
+ ### ### ### Genre + Mood ### ### ###
398
+
399
+
400
+ def get_user_mood(user_mood):
401
+ """
402
+ Categorizes the user's mood as either 'happy', 'sad', 'energetic', or 'calm'.
403
+ Uses same cosine similarity/embedding concepts as with determining playlist names.
404
+ """
405
+ if user_mood.lower() in MOOD_LIST:
406
+ user_mood = user_mood.lower()
407
+ return user_mood
408
+ else:
409
+ user_mood_embedding = MODEL.encode([user_mood.lower()])
410
+ similarity_scores = cosine_similarity(user_mood_embedding, MOOD_EMBEDDINGS)
411
+ most_similar_index = similarity_scores.argmax()
412
+ user_mood = MOOD_LIST[most_similar_index]
413
+ return user_mood
414
+
415
+
416
+ def get_genre_by_name(genre_name):
417
+ """
418
+ Matches user's desired genre to closest (most similar) existing genre in the list of genres.
419
+ recommendations() only accepts genres from this list.
420
+ """
421
+ if genre_name.lower() in GENRE_LIST:
422
+ genre_name = genre_name.lower()
423
+ return genre_name
424
+ else:
425
+ genre_name_embedding = MODEL.encode([genre_name.lower()])
426
+ similarity_scores = cosine_similarity(genre_name_embedding, GENRE_EMBEDDINGS)
427
+ most_similar_index = similarity_scores.argmax()
428
+ genre_name = GENRE_LIST[most_similar_index]
429
+ return genre_name
430
+
431
+
432
+ def is_genre_match(genre1, genre2, threshold=75):
433
+ """
434
+ Determines if two genres are semantically similar.
435
+ token_set_ratio() - for quantifying semantic similarity - and
436
+ threshold of 75 (out of 100) were were arbitrarily determined through basic testing.
437
+ """
438
+ score = fuzz.token_set_ratio(genre1, genre2)
439
+ debug_print(score)
440
+ return score >= threshold
441
+
442
+
443
+ def create_track_list_str(track_uris):
444
+ """
445
+ Creates an organized list of track names.
446
+ Used in final return statements by functions below.
447
+ """
448
+ track_details = sp.tracks(track_uris)
449
+ track_names_with_artists = [f"{track['name']} by {track['artists'][0]['name']}" for track in track_details['tracks']]
450
+ track_list_str = "<br>".join(track_names_with_artists)
451
+ return track_list_str
452
+
453
+
454
+ def play_genre_by_name_and_mood(genre_name, user_mood):
455
+ """
456
+ 1. Retrieves user's desired genre and current mood.
457
+ 2. Matches genre and mood to existing options.
458
+ 3. Gets 4 of user's top artists that align with genre.
459
+ 4. Conducts personalized recommendations() search.
460
+ 5. Plays selected track, clears the queue, and adds the rest to the now-empty queue.
461
+ """
462
+ genre_name = get_genre_by_name(genre_name)
463
+ user_mood = get_user_mood(user_mood).lower()
464
+ debug_print(genre_name)
465
+ debug_print(user_mood)
466
+
467
+ # increased personalization
468
+ user_top_artists = sp.current_user_top_artists(limit=NUM_ARTISTS, time_range=TIME_RANGE)
469
+ matching_artists_ids = []
470
+
471
+ for artist in user_top_artists['items']:
472
+ debug_print(artist['genres'])
473
+ for artist_genre in artist['genres']:
474
+ if is_genre_match(genre_name, artist_genre):
475
+ matching_artists_ids.append(artist['id'])
476
+ break # don't waste time cycling artist genres after match
477
+ if len(matching_artists_ids) == MAX_ARTISTS:
478
+ break
479
+
480
+ if not matching_artists_ids:
481
+ matching_artists_ids = None
482
+ else:
483
+ artist_names = [artist['name'] for artist in sp.artists(matching_artists_ids)['artists']]
484
+ debug_print(artist_names)
485
+ debug_print(matching_artists_ids)
486
+
487
+ recommendations = sp.recommendations( # accepts maximum {genre + artists} = 5 seeds
488
+ seed_artists=matching_artists_ids,
489
+ seed_genres=[genre_name],
490
+ seed_tracks=None,
491
+ limit=NUM_TRACKS, # number of tracks to return
492
+ country=None,
493
+ **MOOD_SETTINGS[user_mood]) # maps to mood settings dictionary
494
+
495
+ track_uris = [track['uri'] for track in recommendations['tracks']]
496
+ track_list_str = create_track_list_str(track_uris)
497
+ sp.start_playback(device_id=device_id, uris=track_uris)
498
+
499
+ return f"""
500
+ **โ™ซ Now Playing:** <span style="color: #E457E2; font-weight: bold; font-style: italic;">{genre_name}</span> โ™ซ
501
+
502
+ **Selected Tracks:**\n
503
+ {track_list_str}
504
+ """
505
+
506
+
507
+ ### ### ### Artist + Mood ### ### ###
508
+
509
+
510
+ def play_artist_by_name_and_mood(artist_name, user_mood):
511
+ """
512
+ Plays tracks (randomly selected) by a given artist that matches the user's mood.
513
+ """
514
+ user_mood = get_user_mood(user_mood).lower()
515
+ debug_print(user_mood)
516
+
517
+ # retrieving and shuffling all artist's tracks
518
+ first_name = artist_name.split(',')[0].strip()
519
+ results = sp.search(q=first_name, type='artist')
520
+ artist_id = results['artists']['items'][0]['id']
521
+ # most recent albums retrieved first
522
+ artist_albums = sp.artist_albums(artist_id, album_type='album', limit=NUM_ALBUMS)
523
+ artist_tracks = []
524
+ for album in artist_albums['items']:
525
+ album_tracks = sp.album_tracks(album['id'])['items']
526
+ artist_tracks.extend(album_tracks)
527
+ random.shuffle(artist_tracks)
528
+
529
+ # filtering until we find enough (MAX_TRACKS) tracks that match user's mood
530
+ selected_tracks = []
531
+ for track in artist_tracks:
532
+ if len(selected_tracks) == MAX_TRACKS:
533
+ break
534
+ features = sp.audio_features([track['id']])[0]
535
+ mood_criteria = MOOD_SETTINGS[user_mood]
536
+
537
+ match = True
538
+ for criteria, threshold in mood_criteria.items():
539
+ if "min_" in criteria and features[criteria[4:]] < threshold:
540
+ match = False
541
+ break
542
+ elif "max_" in criteria and features[criteria[4:]] > threshold:
543
+ match = False
544
+ break
545
+ if match:
546
+ debug_print(f"{features}\n{mood_criteria}\n\n")
547
+ selected_tracks.append(track)
548
+
549
+ track_names = [track['name'] for track in selected_tracks]
550
+ track_list_str = "<br>".join(track_names) # using HTML line breaks for each track name
551
+ debug_print(track_list_str)
552
+ track_uris = [track['uri'] for track in selected_tracks]
553
+ sp.start_playback(device_id=device_id, uris=track_uris)
554
+
555
+ return f"""
556
+ **โ™ซ Now Playing:** <span style="color: #E457E2; font-weight: bold; font-style: italic;">{artist_name}</span> โ™ซ
557
+
558
+ **Selected Tracks:**\n
559
+ {track_list_str}
560
+ """
561
+
562
+
563
+ ### ### ### Recommendations ### ### ###
564
+
565
+
566
+ def recommend_tracks(genre_name=None, artist_name=None, track_name=None, user_mood=None):
567
+ """
568
+ 1. Retrieves user's preferences based on artist_name, track_name, genre_name, and/or user_mood.
569
+ 2. Uses these parameters to conduct personalized recommendations() search.
570
+ 3. Returns the track URIs of (NUM_TRACKS) recommendation tracks.
571
+ """
572
+ user_mood = get_user_mood(user_mood).lower() if user_mood else None
573
+ debug_print(user_mood)
574
+
575
+ seed_genre, seed_artist, seed_track = None, None, None
576
+
577
+ if genre_name:
578
+ first_name = genre_name.split(',')[0].strip()
579
+ genre_name = get_genre_by_name(first_name)
580
+ seed_genre = [genre_name]
581
+ debug_print(seed_genre)
582
+
583
+ if artist_name:
584
+ first_name = artist_name.split(',')[0].strip() # if user provides multiple artists, use the first
585
+ results = sp.search(q='artist:' + first_name, type='artist')
586
+ seed_artist = [results['artists']['items'][0]['id']]
587
+
588
+ if track_name:
589
+ first_name = track_name.split(',')[0].strip()
590
+ results = sp.search(q='track:' + first_name, type='track')
591
+ seed_track = [results['tracks']['items'][0]['id']]
592
+
593
+ # if user requests recommendations without specifying anything but their mood
594
+ # this is because recommendations() requires at least one seed
595
+ if seed_genre is None and seed_artist is None and seed_track is None:
596
+ raise ValueError("At least one genre, artist, or track must be provided.")
597
+
598
+ recommendations = sp.recommendations( # passing in 3 seeds
599
+ seed_artists=seed_artist,
600
+ seed_genres=seed_genre,
601
+ seed_tracks=seed_track,
602
+ limit=NUM_TRACKS,
603
+ country=None,
604
+ **MOOD_SETTINGS[user_mood] if user_mood else {})
605
+
606
+ track_uris = [track['uri'] for track in recommendations['tracks']]
607
+ return track_uris
608
+
609
+
610
+ def play_recommended_tracks(genre_name=None, artist_name=None, track_name=None, user_mood=None):
611
+ """
612
+ Plays the track_uris returned by recommend_tracks().
613
+ """
614
+ try:
615
+ track_uris = recommend_tracks(genre_name, artist_name, track_name, user_mood)
616
+ track_list_str = create_track_list_str(track_uris)
617
+ sp.start_playback(device_id=device_id, uris=track_uris)
618
+
619
+ return f"""
620
+ **โ™ซ Now Playing Recommendations Based On:** <span style="color: #E457E2; font-weight: bold; font-style: italic;">
621
+ {', '.join(filter(None, [genre_name, artist_name, track_name, "Your Mood"]))}</span> โ™ซ
622
+
623
+ **Selected Tracks:**\n
624
+ {track_list_str}
625
+ """
626
+ except ValueError as e:
627
+ return str(e)
628
+
629
+
630
+ def create_playlist_from_recommendations(genre_name=None, artist_name=None, track_name=None, user_mood=None):
631
+ """
632
+ Creates a playlist from recommend_tracks().
633
+ """
634
+ user = sp.current_user()
635
+ user_id = user['id']
636
+ user_name = user["display_name"]
637
+
638
+ playlists = sp.current_user_playlists()
639
+ playlist_names = [playlist['name'] for playlist in playlists["items"]]
640
+ chosen_theme = random.choice(THEMES)
641
+ playlist_name = f"{user_name}'s {chosen_theme} Playlist"
642
+ # ensuring the use of new adjective each time
643
+ while playlist_name in playlist_names:
644
+ chosen_theme = random.choice(THEMES)
645
+ playlist_name = f"{user_name}'s {chosen_theme} Playlist"
646
+
647
+ playlist_description=f"Apollo AI's personalized playlist for {user_name}. Get yours here: (add link)." # TODO: add link to project
648
+ new_playlist = sp.user_playlist_create(user=user_id, name=playlist_name,
649
+ public=True, collaborative=False, description=playlist_description)
650
+
651
+ track_uris = recommend_tracks(genre_name, artist_name, track_name, user_mood)
652
+ track_list_str = create_track_list_str(track_uris)
653
+ sp.user_playlist_add_tracks(user=user_id, playlist_id=new_playlist['id'], tracks=track_uris, position=None)
654
+ playlist_url = f"https://open.spotify.com/playlist/{new_playlist['id']}"
655
+
656
+ return f"""
657
+ โ™ซ Created *{playlist_name}* Based On: <span style='color: #E457E2; font-weight: bold; font-style: italic;'>
658
+ {', '.join(filter(None, [genre_name, artist_name, track_name, 'Your Mood']))}</span> โ™ซ
659
+
660
+ **Selected Tracks:**\n
661
+ {track_list_str}
662
+
663
+ <a href='{playlist_url}'>Click here to listen to the playlist on Spotify!</a>
664
+ """
665
+
666
+
667
+ # ------------------------------
668
+ # Section: Agent Tools
669
+ # ------------------------------
670
+
671
+
672
+ class TrackNameInput(BaseModel):
673
+ track_name: str = Field(description="Track name in the user's request.")
674
+
675
+
676
+ class AlbumNameAndArtistNameInput(BaseModel):
677
+ album_name: str = Field(description="Album name in the user's request.")
678
+ artist_name: str = Field(description="Artist name in the user's request.")
679
+
680
+
681
+ class PlaylistNameInput(BaseModel):
682
+ playlist_name: str = Field(description="Playlist name in the user's request.")
683
+
684
+
685
+ class GenreNameAndUserMoodInput(BaseModel):
686
+ genre_name: str = Field(description="Genre name in the user's request.")
687
+ user_mood: str = Field(description="User's current mood/state-of-being.")
688
+
689
+
690
+ class ArtistNameAndUserMoodInput(BaseModel):
691
+ artist_name: str = Field(description="Artist name in the user's request.")
692
+ user_mood: str = Field(description="User's current mood/state-of-being.")
693
+
694
+
695
+ class RecommendationsInput(BaseModel):
696
+ genre_name: str = Field(description="Genre name in the user's request.")
697
+ artist_name: str = Field(description="Artist name in the user's request.")
698
+ track_name: str = Field(description="Track name in the user's request.")
699
+ user_mood: str = Field(description="User's current mood/state-of-being.")
700
+
701
+
702
+ @tool("play_track_by_name", return_direct=RETURN_DIRECT, args_schema=TrackNameInput)
703
+ def tool_play_track_by_name(track_name: str) -> str:
704
+ """
705
+ Use this tool when a user wants to play a particular track by its name.
706
+ You will need to identify the track name from the user's request.
707
+ Usually, the requests will look like 'play {track name}'.
708
+ This tool is specifically designed for clear and accurate track requests.
709
+ """
710
+ return play_track_by_name(track_name)
711
+
712
+
713
+ @tool("queue_track_by_name", return_direct=RETURN_DIRECT, args_schema=TrackNameInput)
714
+ def tool_queue_track_by_name(track_name: str) -> str:
715
+ """
716
+ Always use this tool when a user says "queue" in their request.
717
+ """
718
+ return queue_track_by_name(track_name)
719
+
720
+
721
+ @tool("pause_track", return_direct=RETURN_DIRECT)
722
+ def tool_pause_track(query: str) -> str:
723
+ """
724
+ Always use this tool when a user says "pause" or "stop" in their request.
725
+ """
726
+ return pause_track()
727
+
728
+
729
+ @tool("resume_track", return_direct=RETURN_DIRECT)
730
+ def tool_resume_track(query: str) -> str:
731
+ """
732
+ Always use this tool when a user says "resume" or "unpause" in their request.
733
+ """
734
+ return resume_track()
735
+
736
+
737
+ @tool("skip_track", return_direct=RETURN_DIRECT)
738
+ def tool_skip_track(query: str) -> str:
739
+ """
740
+ Always use this tool when a user says "skip" or "next" in their request.
741
+ """
742
+ return skip_track()
743
+
744
+
745
+ @tool("play_album_by_name_and_artist", return_direct=RETURN_DIRECT, args_schema=AlbumNameAndArtistNameInput)
746
+ def tool_play_album_by_name_and_artist(album_name: str, artist_name: str) -> str:
747
+ """
748
+ Use this tool when a user wants to play an album.
749
+ You will need to identify both the album name and artist name from the user's request.
750
+ Usually, the requests will look like 'play the album {album_name} by {artist_name}'.
751
+ """
752
+ return play_album_by_name_and_artist(album_name, artist_name)
753
+
754
+
755
+ @tool("play_playlist_by_name", return_direct=RETURN_DIRECT, args_schema=PlaylistNameInput)
756
+ def tool_play_playlist_by_name(playlist_name: str) -> str:
757
+ """
758
+ Use this tool when a user wants to play one of their playlists.
759
+ You will need to identify the playlist name from the user's request.
760
+ """
761
+ return play_playlist_by_name(playlist_name)
762
+
763
+
764
+ @tool("explain_track", return_direct=RETURN_DIRECT)
765
+ def tool_explain_track(query: str) -> str:
766
+ """
767
+ Use this tool when a user wants to know about the currently playing track.
768
+ """
769
+ return explain_track()
770
+
771
+
772
+ @tool("play_genre_by_name_and_mood", return_direct=RETURN_DIRECT, args_schema=GenreNameAndUserMoodInput)
773
+ def tool_play_genre_by_name_and_mood(genre_name: str, user_mood: str) -> str:
774
+ """
775
+ Use this tool when a user wants to play a genre.
776
+ You will need to identify both the genre name from the user's request,
777
+ and also their current mood, which you should always be monitoring.
778
+ """
779
+ return play_genre_by_name_and_mood(genre_name, user_mood)
780
+
781
+
782
+ @tool("play_artist_by_name_and_mood", return_direct=RETURN_DIRECT, args_schema=ArtistNameAndUserMoodInput)
783
+ def tool_play_artist_by_name_and_mood(artist_name: str, user_mood: str) -> str:
784
+ """
785
+ Use this tool when a user wants to play an artist.
786
+ You will need to identify both the artist name from the user's request,
787
+ and also their current mood, which you should always be monitoring.
788
+ If you don't know the user's mood, ask them before using this tool.
789
+ """
790
+ return play_artist_by_name_and_mood(artist_name, user_mood)
791
+
792
+
793
+ @tool("play_recommended_tracks", return_direct=RETURN_DIRECT, args_schema=RecommendationsInput)
794
+ def tool_play_recommended_tracks(genre_name: str, artist_name: str, track_name: str, user_mood: str) -> str:
795
+ """
796
+ Use this tool when a user wants track recommendations.
797
+ You will need to identify the genre name, artist name, and/or track name
798
+ from the user's request... and also their current mood, which you should always be monitoring.
799
+ The user must provide at least genre, artist, or track.
800
+ """
801
+ return play_recommended_tracks(genre_name, artist_name, track_name, user_mood)
802
+
803
+
804
+ @tool("create_playlist_from_recommendations", return_direct=RETURN_DIRECT, args_schema=RecommendationsInput)
805
+ def tool_create_playlist_from_recommendations(genre_name: str, artist_name: str, track_name: str, user_mood: str) -> str:
806
+ """
807
+ Use this tool when a user wants a playlist created (from recommended tracks).
808
+ You will need to identify the genre name, artist name, and/or track name
809
+ from the user's request... and also their current mood, which you should always be monitoring.
810
+ The user must provide at least genre, artist, or track.
811
+ """
812
+ return create_playlist_from_recommendations(genre_name, artist_name, track_name, user_mood)
813
+
814
+
815
+ CUSTOM_TOOLS =[
816
+ tool_play_track_by_name,
817
+ tool_queue_track_by_name,
818
+ tool_pause_track,
819
+ tool_resume_track,
820
+ tool_skip_track,
821
+ tool_play_album_by_name_and_artist,
822
+ tool_play_playlist_by_name,
823
+ tool_explain_track,
824
+ tool_play_genre_by_name_and_mood,
825
+ tool_play_artist_by_name_and_mood,
826
+ tool_play_recommended_tracks,
827
+ tool_create_playlist_from_recommendations
828
+ ]
829
+
830
+
831
+ # ------------------------------
832
+ # Section: Chatbot
833
+ # ------------------------------
834
+
835
+
836
+ system_message = SystemMessage(content=SYSTEM_MESSAGE)
837
+ MEMORY_KEY = "chat_history"
838
+ prompt = OpenAIFunctionsAgent.create_prompt(
839
+ system_message=system_message,
840
+ extra_prompt_messages=[MessagesPlaceholder(variable_name=MEMORY_KEY)]
841
+ )
842
+ memory = ConversationBufferMemory(memory_key=MEMORY_KEY, return_messages=True)
843
+ # NOTE: llm defined above to power explain_track() function
844
+ agent = OpenAIFunctionsAgent(llm=llm, tools=CUSTOM_TOOLS, prompt=prompt)
845
+ agent_executor = AgentExecutor(agent=agent, tools=CUSTOM_TOOLS, memory=memory, verbose=True)
846
+
847
+ return agent_executor, '<p class="hover-item" style="color: #CECECE; font-size: 26px; padding: 12px 0; text-align: left;">Success! Type -music to view commands</p>'
848
+
849
+ create_agent_button.click(create_agent, inputs=[ACCESS_TOKEN_VAR], outputs=[AGENT_EXECUTOR_VAR, create_agent_result])
850
+
851
+
852
+ # ------------------------------
853
+ # Section: Chat Interface
854
+ # ------------------------------
855
+
856
+
857
+ chatbot = gr.Chatbot(
858
+ bubble_full_width=False,
859
+ label="Apollo",
860
+ height=460,
861
+ avatar_images=(None, (os.path.join(os.path.dirname(__file__), "avatar.png")))
862
+ )
863
+ msg = gr.Textbox(
864
+ placeholder="What would you like to hear?",
865
+ container=False,
866
+ text_align="left"
867
+ )
868
+
869
+ def respond(user_message, chat_history, agent_executor):
870
+ try:
871
+ if user_message.strip() == "-music":
872
+ bot_message = APOLLO_MESSAGE
873
+ else:
874
+ bot_message = agent_executor.run(user_message)
875
+ chat_history.append((user_message, bot_message))
876
+ except Exception as e:
877
+ bot_message = "Error: Unable to **connect to your Spotify** | Please ensure you followed the above steps correctly"
878
+ chat_history.append((user_message, bot_message))
879
+ time.sleep(1)
880
+ return "", chat_history
881
+
882
+ msg.submit(respond, inputs=[msg, chatbot, AGENT_EXECUTOR_VAR], outputs=[msg, chatbot])
883
+
884
+ gr.Examples(["Play chill rap",
885
+ "I'm feeling great, match my vibe",
886
+ "Make me a relaxing playlist of SZA-like songs"],
887
+ inputs=[msg], label="Quick Start ๐Ÿš€")
888
+
889
+ gr.HTML('''
890
+ <p class="hover-item" style="color: #CECECE; font-size: 13px; padding: 12px 0; text-align: left;">
891
+ <a href="YOUR_SOURCE_CODE_URL" target="_blank">GitHub Repo</a> |
892
+ I'd love to hear your feedback: stuart.j.waller@vanderbilt.edu
893
+ </p>
894
+ ''')
895
+ app.launch()
896
+ #app.launch(share=True)
897
+
898
+
899
+
900
+
901
+
902
+
903
+
messages.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SYSTEM_MESSAGE = """
2
+ You are Apollo, an AI music-player assistant, designed to provide a personalized and engaging listening experience through thoughtful interaction and intelligent tool usage.
3
+
4
+ Your Main Responsibilities:
5
+
6
+ 1. **Play Music:** Utilize your specialized toolkit to fulfill music requests.
7
+
8
+ 2. **Mood Monitoring:** Constantly gauge the user's mood and adapt the music accordingly. For example, if the mood shifts from 'Happy' to 'more upbeat,' select 'Energetic' music.
9
+
10
+ 3. **Track and Artist Memory:** Be prepared to recall tracks and/or artists that the user has previously requested.
11
+
12
+ 4. **Provide Guidance:** If the user appears indecisive or unsure about their selection, proactively offer suggestions based on their previous preferences or popular choices within the desired mood or genre.
13
+
14
+ 5. **Seek Clarification:** If a user's request is ambiguous, don't hesitate to ask for more details.
15
+ """
16
+
17
+ GENRE_LIST = [
18
+ 'acoustic', 'afrobeat', 'alt-rock', 'alternative', 'ambient', 'anime',
19
+ 'black-metal', 'bluegrass', 'blues', 'bossanova', 'brazil', 'breakbeat',
20
+ 'british', 'cantopop', 'chicago-house', 'children', 'chill', 'classical',
21
+ 'club', 'comedy', 'country', 'dance', 'dancehall', 'death-metal',
22
+ 'deep-house', 'detroit-techno', 'disco', 'disney', 'drum-and-bass', 'dub',
23
+ 'dubstep', 'edm', 'electro', 'electronic', 'emo', 'folk', 'forro', 'french',
24
+ 'funk', 'garage', 'german', 'gospel', 'goth', 'grindcore', 'groove',
25
+ 'grunge', 'guitar', 'happy', 'hard-rock', 'hardcore', 'hardstyle',
26
+ 'heavy-metal', 'hip-hop', 'holidays', 'honky-tonk', 'house', 'idm',
27
+ 'indian', 'indie', 'indie-pop', 'industrial', 'iranian', 'j-dance',
28
+ 'j-idol', 'j-pop', 'j-rock', 'jazz', 'k-pop', 'kids', 'latin', 'latino',
29
+ 'malay', 'mandopop', 'metal', 'metal-misc', 'metalcore', 'minimal-techno',
30
+ 'movies', 'mpb', 'new-age', 'new-release', 'opera', 'pagode', 'party',
31
+ 'philippines-opm', 'piano', 'pop', 'pop-film', 'post-dubstep', 'power-pop',
32
+ 'progressive-house', 'psych-rock', 'punk', 'punk-rock', 'r-n-b',
33
+ 'rainy-day', 'reggae', 'reggaeton', 'road-trip', 'rock', 'rock-n-roll',
34
+ 'rockabilly', 'romance', 'sad', 'salsa', 'samba', 'sertanejo', 'show-tunes',
35
+ 'singer-songwriter', 'ska', 'sleep', 'songwriter', 'soul', 'soundtracks',
36
+ 'spanish', 'study', 'summer', 'swedish', 'synth-pop', 'tango', 'techno',
37
+ 'trance', 'trip-hop', 'turkish', 'work-out', 'world-music'
38
+ ]
39
+
40
+
41
+ HTML = """
42
+ <!DOCTYPE html>
43
+ <html lang="en">
44
+ <head>
45
+ <link href="https://fonts.googleapis.com/css2?family=Gruppo&display=swap" rel="stylesheet">
46
+ <style>
47
+ h1 {
48
+ font-family: 'Gruppo', sans-serif;
49
+ }
50
+ .hover-item:hover {
51
+ color: white !important;
52
+ }
53
+ .hover-item a:hover {
54
+ font-size: 105%;
55
+ }
56
+ a {
57
+ color: inherit !important;
58
+ text-decoration: none !important;
59
+ }
60
+ .container {
61
+ position: relative;
62
+ }
63
+ .apollo-gif {
64
+ position: absolute;
65
+ top: 60%;
66
+ right: 9%;
67
+ transform: translateY(-50%);
68
+ }
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <div class="container" style="text-align: left; margin-top: 50px;">
73
+ <h1 style="color: white; font-size: 56px;">Apollo<sup style="color: #E457E2;">AI</sup> Music Assistant</h1>
74
+ <h2 style="color: #E457E2; font-size: 26px;">Experience personalized, intelligent music interaction like never before</h2>
75
+ <ul style="list-style-type: none; padding: 12px 0; margin: 0;">
76
+ <li class="hover-item" style="color: #CECECE; font-size: 26px; padding: 12px 0;"><span style="color: #5491FA;">1.</span> Ensure your Spotify client is open</li>
77
+ <li class="hover-item" style="color: #CECECE; font-size: 26px; padding: 12px 0;"><span style="color: #5491FA;">2.</span> Login to <a href="https://developer.spotify.com">Spotify Developer</a> website</li>
78
+ <li class="hover-item" style="color: #CECECE; font-size: 26px; padding: 12px 0;"><span style="color: #5491FA;">3.</span> Go to <a href="https://developer.spotify.com/dashboard">Dashboard</a> then 'Create app'</li>
79
+ <li class="hover-item" style="color: #CECECE; font-size: 26px; padding: 12px 0; margin-bottom: -12px;"><span style="color: #5491FA;">4.</span> Set Redirect URI to <a href="https://jonaswaller.com">https://jonaswaller.com</a></li>
80
+ <li style="color: #969696; font-size: 20px; padding: 0; margin-top: -12px; text-indent: 55px;">The other fields can be anything</li>
81
+ <li class="hover-item" style="color: #CECECE; font-size: 26px; padding: 12px 0;"><span style="color: #5491FA;">5.</span> Hit 'Save' then 'Settings'</li>
82
+ <li class="hover-item" style="color: #CECECE; font-size: 26px; padding: 12px 0;"><span style="color: #5491FA;">6.</span> Copy your Spotify Client ID</li>
83
+ </ul>
84
+ <img src="file/apollo.gif" class="apollo-gif" width='400'>
85
+ </div>
86
+ </body>
87
+ </html>
88
+ """
89
+
90
+
91
+ APOLLO_MESSAGE = """
92
+ Welcome! Tell me your **mood** to help me select the best music for you
93
+
94
+ ### <span style="color: #E457E2;"> ๐Ÿ’ฟ Standard ๐Ÿ’ฟ</span>
95
+ - **Specific Song:** Play Passionfruit
96
+ - **Controls:** Queue, Pause, Resume, Skip
97
+ - **More Info:** Explain this song
98
+ - **Album:** Play the album from Barbie
99
+ - **Playlist:** Play my Shower playlist
100
+
101
+ ### <span style="color: #E457E2;"> ๐Ÿ’ฟ Mood-Based ๐Ÿ’ฟ</span>
102
+ - **Genre:** I'm happy, play country
103
+ - **Artist:** Play Migos hype songs
104
+ - **Recommendations:** I love Drake and house, recommend songs
105
+ - **Create Playlist:** Make a relaxing playlist of SZA-like songs
106
+ """
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==3.40.1
2
+ spotipy==2.23.0
3
+ requests==2.31.0
4
+ beautifulsoup4==4.12.2
5
+ sentence_transformers==2.2.2
6
+ fuzzywuzzy==0.18.0
7
+ numpy==1.25.1
8
+ scikit-learn==1.3.0
9
+ lyricsgenius==3.0.1
10
+ langchain==0.0.271
11
+ pydantic==1.10.11
12
+ openai==0.27.9
13
+ python-dotenv==1.0.0
14
+ huggingface_hub==0.16.4