Add mood scanner and surprise me features to discover page
Add mood_energy and mood_valence sliders that inject mood context into AI recommendation prompts. Add "Surprise Me" button that generates recommendations from a creative, unexpected angle without requiring any user input. Includes backend endpoints, schema updates, and full frontend UI integration.
This commit is contained in:
@@ -31,6 +31,25 @@ async def generate(
|
||||
recs, remaining = await generate_recommendations(
|
||||
db, user, playlist_id=data.playlist_id, query=data.query, bandcamp_mode=data.bandcamp_mode,
|
||||
mode=data.mode, adventurousness=data.adventurousness, exclude=data.exclude, count=data.count,
|
||||
mood_energy=data.mood_energy, mood_valence=data.mood_valence,
|
||||
)
|
||||
|
||||
if not recs and remaining == 0:
|
||||
raise HTTPException(status_code=429, detail="Weekly recommendation limit reached. Upgrade to Premium for unlimited.")
|
||||
|
||||
return RecommendationResponse(
|
||||
recommendations=[RecommendationItem.model_validate(r) for r in recs],
|
||||
remaining_this_week=remaining,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/surprise", response_model=RecommendationResponse)
|
||||
async def surprise(
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
recs, remaining = await generate_recommendations(
|
||||
db, user, query=None, mode="surprise", count=5
|
||||
)
|
||||
|
||||
if not recs and remaining == 0:
|
||||
|
||||
@@ -11,6 +11,8 @@ class RecommendationRequest(BaseModel):
|
||||
adventurousness: int = 3 # 1-5
|
||||
exclude: str | None = None # comma-separated genres to exclude
|
||||
count: int = 5 # Number of recommendations (5, 10, 15, 20)
|
||||
mood_energy: int | None = None # 1-5, 1=chill, 5=energetic
|
||||
mood_valence: int | None = None # 1-5, 1=sad/dark, 5=happy/bright
|
||||
|
||||
|
||||
class RecommendationItem(BaseModel):
|
||||
|
||||
@@ -70,6 +70,7 @@ MODE_PROMPTS = {
|
||||
"era_bridge": "Suggest classic artists from earlier eras who directly inspired their current favorites. Trace musical lineage — if they love Tame Impala, suggest the 70s psych rock that influenced him. Bridge eras.",
|
||||
"deep_cuts": "Find B-sides, album tracks, rarities, and lesser-known songs from artists already in their library. Focus on tracks they probably haven't heard even from artists they already know.",
|
||||
"rising": "Find artists with under 50K monthly listeners who match their taste. Focus on brand new, up-and-coming artists who haven't broken through yet. Think artists who just released their debut album or EP.",
|
||||
"surprise": "Be wildly creative. Pick ONE obscure, unexpected angle from their taste profile — maybe a specific production technique, a niche sub-genre, a particular era, or an unusual sonic quality — and build all recommendations around that single thread. Start your 'reason' for the first recommendation by explaining the angle you chose. Make it feel like a curated rabbit hole they never knew they wanted.",
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +93,8 @@ async def generate_recommendations(
|
||||
adventurousness: int = 3,
|
||||
exclude: str | None = None,
|
||||
count: int = 5,
|
||||
mood_energy: int | None = None,
|
||||
mood_valence: int | None = None,
|
||||
) -> tuple[list[Recommendation], int | None]:
|
||||
"""Generate AI music recommendations using Claude."""
|
||||
|
||||
@@ -145,7 +148,10 @@ async def generate_recommendations(
|
||||
disliked_artists = list({a for a in disliked_result.scalars().all()})
|
||||
|
||||
# Build prompt
|
||||
user_request = query or "Find me music I'll love based on my taste profile. Prioritize lesser-known artists and hidden gems."
|
||||
if mode == "surprise" and not query:
|
||||
user_request = "Surprise me with something unexpected based on my taste profile. Pick a creative, unusual angle I wouldn't think of myself."
|
||||
else:
|
||||
user_request = query or "Find me music I'll love based on my taste profile. Prioritize lesser-known artists and hidden gems."
|
||||
|
||||
if bandcamp_mode:
|
||||
focus_instruction = "IMPORTANT: Strongly prioritize independent and underground artists who release music on Bandcamp. Think DIY, indie labels, self-released artists, and the kind of music you'd find crate-digging on Bandcamp. Focus on artists who self-publish or release on small indie labels."
|
||||
@@ -158,6 +164,18 @@ async def generate_recommendations(
|
||||
# Adventurousness instruction
|
||||
adventurousness_instruction = build_adventurousness_prompt(adventurousness)
|
||||
|
||||
# Mood instruction
|
||||
mood_instruction = ""
|
||||
if mood_energy is not None or mood_valence is not None:
|
||||
energy_desc = {1: "very chill and calm", 2: "relaxed", 3: "moderate energy", 4: "upbeat and energetic", 5: "high energy and intense"}
|
||||
valence_desc = {1: "dark, melancholy, or moody", 2: "introspective or bittersweet", 3: "neutral mood", 4: "positive and uplifting", 5: "happy, euphoric, or celebratory"}
|
||||
parts = []
|
||||
if mood_energy is not None:
|
||||
parts.append(f"Energy: {energy_desc.get(mood_energy, 'moderate energy')}")
|
||||
if mood_valence is not None:
|
||||
parts.append(f"Mood: {valence_desc.get(mood_valence, 'neutral mood')}")
|
||||
mood_instruction = f"\nMatch this mood: {'. '.join(parts)}"
|
||||
|
||||
# Exclude genres instruction
|
||||
exclude_instruction = ""
|
||||
combined_exclude = exclude or ""
|
||||
@@ -180,6 +198,7 @@ User request: {user_request}
|
||||
Discovery mode: {mode_instruction}
|
||||
|
||||
{adventurousness_instruction}
|
||||
{mood_instruction}
|
||||
|
||||
IMPORTANT: If the user mentions specific artists or songs in their request, do NOT recommend anything BY those artists. The user already knows them — recommend music by OTHER artists that match the vibe. For example, if they say "I like Sublime", recommend artists similar to Sublime, but NEVER Sublime themselves.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user