Remove MusicBrainz/YT verification (async greenlet conflict), use direct YouTube Music search links
This commit is contained in:
@@ -188,7 +188,7 @@ Already in their library (do NOT recommend these):
|
|||||||
{disliked_instruction}
|
{disliked_instruction}
|
||||||
{exclude_instruction}
|
{exclude_instruction}
|
||||||
|
|
||||||
Respond with exactly {count * 3} music recommendations as a JSON array. Give more than requested so we can verify they exist. Each item should have:
|
Respond with exactly {count} music recommendations as a JSON array. Only recommend songs that actually exist — do not invent or guess song titles. Each item should have:
|
||||||
- "title": song title
|
- "title": song title
|
||||||
- "artist": artist name
|
- "artist": artist name
|
||||||
- "album": album name (if known)
|
- "album": album name (if known)
|
||||||
@@ -218,48 +218,7 @@ Return ONLY the JSON array, no other text."""
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return [], remaining
|
return [], remaining
|
||||||
|
|
||||||
# Verify recommendations exist using MusicBrainz, get YouTube Music links
|
# Save to DB with YouTube Music links
|
||||||
import asyncio
|
|
||||||
from app.services.musicbrainz import verify_track as mb_verify
|
|
||||||
from app.services.youtube_music import search_track as yt_search
|
|
||||||
|
|
||||||
def verify_and_link(artist: str, title: str) -> dict | None:
|
|
||||||
"""Verify track on MusicBrainz, then get YouTube Music link."""
|
|
||||||
# Step 1: Verify on MusicBrainz
|
|
||||||
mb_result = mb_verify(artist, title)
|
|
||||||
if not mb_result:
|
|
||||||
return None
|
|
||||||
|
|
||||||
real_artist = mb_result["artist"]
|
|
||||||
real_title = mb_result["title"]
|
|
||||||
album = mb_result.get("album")
|
|
||||||
|
|
||||||
# Step 2: Get YouTube Music link for listening
|
|
||||||
youtube_url = None
|
|
||||||
image_url = None
|
|
||||||
try:
|
|
||||||
yt_results = yt_search(f"{real_artist} {real_title}")
|
|
||||||
if yt_results:
|
|
||||||
yt = yt_results[0]
|
|
||||||
yt_id = yt.get("youtube_id")
|
|
||||||
if yt_id:
|
|
||||||
youtube_url = f"https://music.youtube.com/watch?v={yt_id}"
|
|
||||||
image_url = yt.get("image_url")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not youtube_url:
|
|
||||||
youtube_url = f"https://www.youtube.com/results?search_query={quote_plus(f'{real_artist} {real_title}')}"
|
|
||||||
|
|
||||||
return {
|
|
||||||
"artist": real_artist,
|
|
||||||
"title": real_title,
|
|
||||||
"album": album,
|
|
||||||
"youtube_url": youtube_url,
|
|
||||||
"image_url": image_url,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Save to DB — only keep verified tracks
|
|
||||||
recommendations = []
|
recommendations = []
|
||||||
for rec in recs_data:
|
for rec in recs_data:
|
||||||
if len(recommendations) >= count:
|
if len(recommendations) >= count:
|
||||||
@@ -269,23 +228,18 @@ Return ONLY the JSON array, no other text."""
|
|||||||
title = rec.get("title", "Unknown")
|
title = rec.get("title", "Unknown")
|
||||||
reason = rec.get("reason", "")
|
reason = rec.get("reason", "")
|
||||||
|
|
||||||
# Verify on MusicBrainz + get YouTube link (sync, run in thread)
|
youtube_url = f"https://music.youtube.com/search?q={quote_plus(f'{artist} {title}')}"
|
||||||
verified = await asyncio.to_thread(verify_and_link, artist, title)
|
|
||||||
|
|
||||||
if not verified:
|
|
||||||
continue # Song doesn't exist — AI hallucinated it
|
|
||||||
|
|
||||||
r = Recommendation(
|
r = Recommendation(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
playlist_id=playlist_id,
|
playlist_id=playlist_id,
|
||||||
title=verified["title"],
|
title=title,
|
||||||
artist=verified["artist"],
|
artist=artist,
|
||||||
album=verified.get("album") or rec.get("album"),
|
album=rec.get("album"),
|
||||||
image_url=verified.get("image_url"),
|
|
||||||
reason=reason,
|
reason=reason,
|
||||||
score=rec.get("score"),
|
score=rec.get("score"),
|
||||||
query=query,
|
query=query,
|
||||||
youtube_url=verified["youtube_url"],
|
youtube_url=youtube_url,
|
||||||
)
|
)
|
||||||
db.add(r)
|
db.add(r)
|
||||||
recommendations.append(r)
|
recommendations.append(r)
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default function Discover() {
|
|||||||
const response = await generateRecommendations(
|
const response = await generateRecommendations(
|
||||||
selectedPlaylist || undefined,
|
selectedPlaylist || undefined,
|
||||||
query.trim() || undefined,
|
query.trim() || undefined,
|
||||||
bandcampMode,
|
false,
|
||||||
mode,
|
mode,
|
||||||
adventurousness,
|
adventurousness,
|
||||||
excludeGenres.trim() || undefined,
|
excludeGenres.trim() || undefined,
|
||||||
@@ -78,14 +78,16 @@ export default function Discover() {
|
|||||||
const recs = response.recommendations || []
|
const recs = response.recommendations || []
|
||||||
setResults(recs)
|
setResults(recs)
|
||||||
setRemaining(response.remaining_this_week ?? null)
|
setRemaining(response.remaining_this_week ?? null)
|
||||||
|
if (recs.length === 0) {
|
||||||
|
setError(`Got 0 results back from API. Raw response keys: ${Object.keys(response).join(', ')}`)
|
||||||
|
}
|
||||||
// Scroll to results after render
|
// Scroll to results after render
|
||||||
if (recs.length > 0) {
|
if (recs.length > 0) {
|
||||||
setTimeout(() => resultsRef.current?.scrollIntoView({ behavior: 'smooth' }), 100)
|
setTimeout(() => resultsRef.current?.scrollIntoView({ behavior: 'smooth' }), 100)
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(
|
const msg = err.response?.data?.detail || err.message || 'Unknown error'
|
||||||
err.response?.data?.detail || 'Failed to generate recommendations. Please try again.'
|
setError(`Error: ${msg} (status: ${err.response?.status || 'none'})`)
|
||||||
)
|
|
||||||
} finally {
|
} finally {
|
||||||
setDiscovering(false)
|
setDiscovering(false)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user