perf(suggestions): optimize loading with backend MBID resolution
- Move MBID resolution from frontend to backend - Eliminates multiple sequential IPC calls - Uses MusicBrainz cache for faster resolution - Logs resolution progress for debugging - Implement progressive loading in UI - Initial load: 18 tracks (3 pages) - "Load more" expands to 60 tracks on demand - Better feedback during loading states - Fix refresh button styling - Remove background while spinning - Update title based on available actions - Add "Load more" link in pagination area
This commit is contained in:
1 parent
f0aa539849
commit
e9a5dfcb80
3 files changed
+180
-78
No files matched your search
@@ -10,14 +10,24 @@ use crate::artist_vectors::{
|
||||
ArtistVectorBuilder, ArtistVectorStoreState, RelationshipWeights, StoreStats,
|
||||
SuggestionConfig, SuggestionResult, SuggestionsEngine,
|
||||
};
|
||||
use crate::musicbrainz::MusicBrainzSharedState;
|
||||
use crate::musicbrainz::{MatchConfidence, MusicBrainzSharedState, ResolvedArtist};
|
||||
use crate::AppState;
|
||||
|
||||
/// Artist info from the frontend
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct PlaylistArtist {
|
||||
/// Artist name (required)
|
||||
pub name: String,
|
||||
/// Qobuz artist ID (optional, for better matching)
|
||||
#[serde(default)]
|
||||
pub qobuz_id: Option<u64>,
|
||||
}
|
||||
|
||||
/// Input for generating suggestions
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct SuggestionsInput {
|
||||
/// Artist MBIDs from the playlist
|
||||
pub artist_mbids: Vec<String>,
|
||||
/// Artists from the playlist (names + optional Qobuz IDs)
|
||||
pub artists: Vec<PlaylistArtist>,
|
||||
/// Track IDs already in the playlist (to exclude)
|
||||
pub exclude_track_ids: Vec<u64>,
|
||||
/// Whether to include reason strings (dev mode)
|
||||
@@ -35,6 +45,83 @@ pub async fn get_playlist_suggestions_v2(
|
||||
mb_state: State<'_, MusicBrainzSharedState>,
|
||||
app_state: State<'_, AppState>,
|
||||
) -> Result<SuggestionResult, String> {
|
||||
// Resolve artist names to MBIDs (with caching)
|
||||
let mut artist_mbids = Vec::new();
|
||||
|
||||
for artist in &input.artists {
|
||||
// Check cache first
|
||||
let cached = {
|
||||
let cache = mb_state.cache.lock().await;
|
||||
cache.get_artist(&artist.name).ok().flatten()
|
||||
};
|
||||
|
||||
if let Some(resolved) = cached {
|
||||
if let Some(mbid) = resolved.mbid {
|
||||
if resolved.confidence != MatchConfidence::None
|
||||
&& resolved.confidence != MatchConfidence::Low
|
||||
{
|
||||
artist_mbids.push(mbid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to resolve via MusicBrainz search
|
||||
if mb_state.client.is_enabled().await {
|
||||
match mb_state.client.search_artist(&artist.name).await {
|
||||
Ok(response) => {
|
||||
if let Some(mb_artist) = response
|
||||
.artists
|
||||
.iter()
|
||||
.find(|a| a.score.unwrap_or(0) >= 90)
|
||||
{
|
||||
let mbid = mb_artist.id.clone();
|
||||
// Cache the resolved artist
|
||||
let resolved = ResolvedArtist {
|
||||
mbid: Some(mbid.clone()),
|
||||
name: Some(mb_artist.name.clone()),
|
||||
sort_name: mb_artist.sort_name.clone(),
|
||||
artist_type: None,
|
||||
country: mb_artist.country.clone(),
|
||||
disambiguation: mb_artist.disambiguation.clone(),
|
||||
confidence: MatchConfidence::High,
|
||||
};
|
||||
{
|
||||
let cache = mb_state.cache.lock().await;
|
||||
let _ = cache.set_artist(&artist.name, &resolved);
|
||||
}
|
||||
artist_mbids.push(mbid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("Failed to resolve artist '{}': {}", artist.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache negative result
|
||||
{
|
||||
let cache = mb_state.cache.lock().await;
|
||||
let _ = cache.set_artist(&artist.name, &ResolvedArtist::empty());
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Resolved {}/{} artists to MBIDs for suggestions",
|
||||
artist_mbids.len(),
|
||||
input.artists.len()
|
||||
);
|
||||
|
||||
if artist_mbids.is_empty() {
|
||||
return Ok(SuggestionResult {
|
||||
tracks: Vec::new(),
|
||||
source_artists: Vec::new(),
|
||||
playlist_artists_count: input.artists.len(),
|
||||
similar_artists_count: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Create the builder (just cloning Arcs, cheap)
|
||||
let builder = Arc::new(ArtistVectorBuilder::new(
|
||||
store_state.store.clone(),
|
||||
@@ -58,7 +145,7 @@ pub async fn get_playlist_suggestions_v2(
|
||||
|
||||
// Generate suggestions
|
||||
engine
|
||||
.generate_suggestions(&input.artist_mbids, &exclude_set, input.include_reasons)
|
||||
.generate_suggestions(&artist_mbids, &exclude_set, input.include_reasons)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user