VibeSync
', unsafe_allow_html=True) st.markdown('Your personality, translated into sound.
', unsafe_allow_html=True) # Spotify Connection Status user = check_spotify_auth() if user: st.markdown(f'Connect Spotify to export your magic.
import streamlit as st import os import json from typing import List, Dict, Optional from dotenv import load_dotenv from groq import Groq import spotipy from spotipy.oauth2 import SpotifyOAuth import time # Load environment variables load_dotenv() # Page configuration st.set_page_config( page_title="VibeSync - AI Playlist Generator", page_icon="🎵", layout="wide", initial_sidebar_state="collapsed" ) # Custom CSS for premium styling st.markdown(""" """, unsafe_allow_html=True) # Quiz Questions QUIZ_QUESTIONS = [ { "question": "How would you describe your current mood?", "options": ["Happy & Upbeat 😄", "Calm & Relaxed 😌", "Reflective & Thoughtful 🤔", "Energized & Motivated 💪", "Romantic & Dreamy 💕"], "key": "mood" }, { "question": "What's your ideal Friday night?", "options": ["Dancing at a party 🎉", "Netflix and chill 📺", "Deep conversations with friends 💬", "Working out or sports 🏋️", "Candlelit dinner 🕯️"], "key": "friday_night" }, { "question": "Pick a weather that matches your vibe:", "options": ["Sunny & warm ☀️", "Cloudy & cool ☁️", "Rainy & cozy 🌧️", "Storm & intense ⛈️", "Sunset & peaceful 🌅"], "key": "weather" }, { "question": "What's your energy level right now?", "options": ["Super high! ⚡", "Relaxed & steady 🌊", "Low & contemplative 🌙", "Ready to conquer! 🏆", "Soft & gentle 🦋"], "key": "energy" }, { "question": "Choose a color that speaks to you:", "options": ["Bright Yellow 💛", "Ocean Blue 💙", "Deep Purple 💜", "Fiery Red ❤️", "Soft Pink 💗"], "key": "color" }, { "question": "What kind of lyrics do you prefer?", "options": ["Uplifting & positive ✨", "Mellow & smooth 🎵", "Deep & meaningful 📖", "Powerful & inspiring 💥", "Sweet & emotional 💌"], "key": "lyrics" }, { "question": "If your life was a movie, what genre would it be?", "options": ["Comedy 😂", "Indie Drama 🎬", "Psychological Thriller 🧠", "Action Adventure 🎯", "Romance 💑"], "key": "movie" }, { "question": "What time of day do you feel most alive?", "options": ["Morning & fresh ☀️", "Afternoon & steady 🌤️", "Late night & introspective 🌙", "Peak hours & busy 📈", "Golden hour & magical ✨"], "key": "time_of_day" }, { "question": "Which languages are you familiar with? (Select all that apply)", "options": ["English", "Spanish", "French", "German", "Italian", "Portuguese", "Japanese", "Korean", "Chinese","Punjabi", "Hindi","Bengali", "Arabic", "Russian", "Turkish", "Indonesian"], "key": "languages", "type": "multiselect" } ] # Initialize session state if 'stage' not in st.session_state: st.session_state.stage = 'landing' if 'quiz_index' not in st.session_state: st.session_state.quiz_index = 0 if 'quiz_answers' not in st.session_state: st.session_state.quiz_answers = {} if 'playlist' not in st.session_state: st.session_state.playlist = [] if 'spotify_connected' not in st.session_state: st.session_state.spotify_connected = False if 'spotify_playlist_id' not in st.session_state: st.session_state.spotify_playlist_id = None # API Configuration GROQ_API_KEY = os.getenv("GROQ_API_KEY") SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID") SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET") SPOTIFY_REDIRECT_URI = os.getenv("SPOTIFY_REDIRECT_URI") # --- MULTI-USER TOKEN MANAGEMENT --- class StreamlitSessionCacheHandler(spotipy.cache_handler.CacheHandler): """ Custom cache handler for Spotify tokens that stores them in Streamlit session state. This is essential for web deployment to isolate tokens between different users. """ def __init__(self): if 'spotify_token' not in st.session_state: st.session_state.spotify_token = None def get_cached_token(self): return st.session_state.spotify_token def save_token_to_cache(self, token_info): st.session_state.spotify_token = token_info def get_groq_client(): """Initialize Groq client""" if GROQ_API_KEY: return Groq(api_key=GROQ_API_KEY) return None def get_spotify_oauth(): """Initialize Spotify OAuth object with session-based cache""" scope = "playlist-modify-public playlist-modify-private" return SpotifyOAuth( client_id=SPOTIFY_CLIENT_ID, client_secret=SPOTIFY_CLIENT_SECRET, redirect_uri=SPOTIFY_REDIRECT_URI, scope=scope, cache_handler=StreamlitSessionCacheHandler(), open_browser=False ) def get_spotify_client(): """Initialize Spotify client using session-based cache""" if SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET: try: sp_oauth = get_spotify_oauth() # Check for cached token in session state token_info = sp_oauth.get_cached_token() if token_info: # Refresh token if expired if sp_oauth.is_token_expired(token_info): token_info = sp_oauth.refresh_access_token(token_info['refresh_token']) return spotipy.Spotify(auth=token_info['access_token']) # Check if we got a code from the redirect query_params = st.query_params if 'code' in query_params: code = query_params['code'] try: token_info = sp_oauth.get_access_token(code, as_dict=True, check_cache=False) if token_info: # Clear the code from URL st.query_params.clear() return spotipy.Spotify(auth=token_info['access_token']) except Exception as e: st.error(f"Error getting token: {str(e)}") return None return None except Exception as e: st.error(f"Spotify authentication error: {str(e)}") return None return None def generate_playlist_with_groq(quiz_answers: Dict, count: int = 20) -> List[Dict]: """Generate playlist using Groq AI based on quiz answers""" groq_client = get_groq_client() if not groq_client: st.warning("⚠️ Groq API not configured. Using demo mode.") return get_demo_playlist() # Create prompt from quiz answers languages = quiz_answers.get('languages', ['English']) if isinstance(languages, list): languages_str = ", ".join(languages) else: languages_str = str(languages) prompt = f"""Based on the following personality quiz responses, recommend {count} songs that match this person's vibe perfectly. IMPORTANT: The user is familiar with these languages: {languages_str}. You MUST ONLY recommend songs in one of these languages. Do not include songs in other languages. Quiz Responses: {json.dumps(quiz_answers, indent=2)} Please analyze the responses and recommend songs that match their mood, energy, and preferences. For each song, provide: 1. Song title 2. Artist name 3. Genre 4. A brief reason why this song matches their vibe (MAX 10 words) Format your response as a JSON array with this structure: [ {{ "title": "Song Title", "artist": "Artist Name", "genre": "Genre", "reasoning": "Why this song matches their vibe" }} ] Only return the JSON array, no additional text.""" try: with st.spinner("✨ AI is curating your perfect playlist..."): response = groq_client.chat.completions.create( model="llama-3.3-70b-versatile", messages=[ {"role": "system", "content": "You are a music expert who understands personality and creates perfect playlists. Always respond with valid JSON."}, {"role": "user", "content": prompt} ], temperature=0.5, max_tokens=6000 ) content = response.choices[0].message.content.strip() # Extract JSON if wrapped in code blocks if "```json" in content: content = content.split("```json")[1].split("```")[0].strip() elif "```" in content: content = content.split("```")[1].split("```")[0].strip() try: songs = json.loads(content) except json.JSONDecodeError as je: # If JSON is truncated, try to fix the last entry if "Expecting value" in str(je) or "Unterminated string" in str(je): # Find the last complete song object last_bracket = content.rfind('}') if last_bracket != -1: fixed_content = content[:last_bracket+1] + ']' try: songs = json.loads(fixed_content) st.warning("⚠️ Some songs were omitted due to length limits.") except: raise je else: raise je else: raise je return songs[:count] except Exception as e: print(f"Error generating playlist with AI: {str(e)}") st.error(f"Error generating playlist with AI: {str(e)}") return get_demo_playlist() def get_demo_playlist() -> List[Dict]: """Fallback demo playlist""" return [ {"title": "Blinding Lights", "artist": "The Weeknd", "genre": "Pop", "reasoning": "High energy and upbeat vibe"}, {"title": "Levitating", "artist": "Dua Lipa", "genre": "Pop", "reasoning": "Perfect for happy moods"}, {"title": "Good 4 U", "artist": "Olivia Rodrigo", "genre": "Pop", "reasoning": "Energetic and powerful"}, {"title": "Shivers", "artist": "Ed Sheeran", "genre": "Pop", "reasoning": "Romantic and catchy"}, {"title": "Heat Waves", "artist": "Glass Animals", "genre": "Indie", "reasoning": "Chill yet engaging"}, {"title": "Stay", "artist": "The Kid LAROI & Justin Bieber", "genre": "Pop", "reasoning": "Emotional and melodic"}, {"title": "Peaches", "artist": "Justin Bieber", "genre": "R&B", "reasoning": "Smooth and relaxed"}, {"title": "Montero", "artist": "Lil Nas X", "genre": "Hip-Hop", "reasoning": "Bold and confident"}, {"title": "drivers license", "artist": "Olivia Rodrigo", "genre": "Pop", "reasoning": "Deep emotional resonance"}, {"title": "Save Your Tears", "artist": "The Weeknd", "genre": "Pop", "reasoning": "Uplifting with depth"}, {"title": "Positions", "artist": "Ariana Grande", "genre": "R&B", "reasoning": "Sweet and romantic"}, {"title": "Willow", "artist": "Taylor Swift", "genre": "Pop", "reasoning": "Dreamy and enchanting"} ] def search_and_add_to_spotify_playlist(songs: List[Dict], playlist_id: str): """Search for songs on Spotify and add them to playlist""" sp = get_spotify_client() if not sp: return False track_uris = [] with st.spinner("🔍 Finding songs on Spotify..."): for song in songs: try: query = f"{song['title']} {song['artist']}" results = sp.search(q=query, type='track', limit=1) if results['tracks']['items']: track_uris.append(results['tracks']['items'][0]['uri']) time.sleep(0.1) # Rate limiting except Exception as e: st.warning(f"Couldn't find '{song['title']}' on Spotify") continue # Add tracks to playlist in batches of 100 try: for i in range(0, len(track_uris), 100): sp.playlist_add_items(playlist_id, track_uris[i:i+100]) return True except Exception as e: st.error(f"Error adding songs to playlist: {str(e)}") return False def create_spotify_playlist(playlist_name: str) -> Optional[str]: """Create a new Spotify playlist""" sp = get_spotify_client() if not sp: return None try: user_id = sp.current_user()['id'] playlist = sp.user_playlist_create( user=user_id, name=playlist_name, public=True, description="Created by VibeSync - AI-powered playlist generator" ) return playlist['id'] except Exception as e: st.error(f"Error creating Spotify playlist: {str(e)}") return None def display_song_card(song: Dict, index: int): """Display a song in a beautiful card format""" reasoning_html = f'
Your personality, translated into sound.
', unsafe_allow_html=True) # Spotify Connection Status user = check_spotify_auth() if user: st.markdown(f'Connect Spotify to export your magic.
Question {st.session_state.quiz_index + 1} of {len(QUIZ_QUESTIONS)}
', unsafe_allow_html=True) # Progress bar progress = (st.session_state.quiz_index) / len(QUIZ_QUESTIONS) st.progress(progress) st.markdown('{current_q["question"]}
', unsafe_allow_html=True) if current_q.get("type") == "multiselect": answer = st.multiselect("Choose languages:", current_q["options"], key=f"q_{st.session_state.quiz_index}") else: answer = st.radio("Choose one:", current_q["options"], key=f"q_{st.session_state.quiz_index}", label_visibility="collapsed") col1, col2, col3 = st.columns([1, 2, 1]) with col2: if st.button("Next Question ➡️", key=f"next_{st.session_state.quiz_index}"): # Save answer st.session_state.quiz_answers[current_q["key"]] = answer if st.session_state.quiz_index < len(QUIZ_QUESTIONS) - 1: st.session_state.quiz_index += 1 st.rerun() else: # Quiz completed, generate playlist with AI st.session_state.playlist = generate_playlist_with_groq(st.session_state.quiz_answers) st.session_state.stage = 'results' st.rerun() st.markdown('A {len(st.session_state.playlist)}-track journey curated by AI.
", unsafe_allow_html=True) # Display playlist for idx, song in enumerate(st.session_state.playlist, 1): display_song_card(song, idx) # Add more songs with AI st.markdown("Check your Spotify account