fix: include manual external mappings in fallback playlist stats and add live UI refresh

This commit is contained in:
2026-02-07 03:36:26 -05:00
parent 8ab2923493
commit 6c1a578b35
2 changed files with 78 additions and 24 deletions

View File

@@ -469,30 +469,53 @@ public class AdminController : ControllerBase
foreach (var track in spotifyTracks) foreach (var track in spotifyTracks)
{ {
var isLocal = false; var isLocal = false;
var hasExternalMapping = false;
if (localTracks.Count > 0) // FIRST: Check for manual Jellyfin mapping
var manualMappingKey = $"spotify:manual-map:{config.Name}:{track.SpotifyId}";
var manualJellyfinId = await _cache.GetAsync<string>(manualMappingKey);
if (!string.IsNullOrEmpty(manualJellyfinId))
{ {
var bestMatch = localTracks // Manual Jellyfin mapping exists - this track is definitely local
.Select(local => new isLocal = true;
{ }
Local = local, else
TitleScore = FuzzyMatcher.CalculateSimilarity(track.Title, local.Title), {
ArtistScore = FuzzyMatcher.CalculateSimilarity(track.PrimaryArtist, local.Artist) // Check for external manual mapping
}) var externalMappingKey = $"spotify:external-map:{config.Name}:{track.SpotifyId}";
.Select(x => new var externalMappingJson = await _cache.GetStringAsync(externalMappingKey);
{
x.Local,
x.TitleScore,
x.ArtistScore,
TotalScore = (x.TitleScore * 0.7) + (x.ArtistScore * 0.3)
})
.OrderByDescending(x => x.TotalScore)
.FirstOrDefault();
// Use 70% threshold (same as playback matching) if (!string.IsNullOrEmpty(externalMappingJson))
if (bestMatch != null && bestMatch.TotalScore >= 70)
{ {
isLocal = true; // External manual mapping exists
hasExternalMapping = true;
}
else if (localTracks.Count > 0)
{
// SECOND: No manual mapping, try fuzzy matching with local tracks
var bestMatch = localTracks
.Select(local => new
{
Local = local,
TitleScore = FuzzyMatcher.CalculateSimilarity(track.Title, local.Title),
ArtistScore = FuzzyMatcher.CalculateSimilarity(track.PrimaryArtist, local.Artist)
})
.Select(x => new
{
x.Local,
x.TitleScore,
x.ArtistScore,
TotalScore = (x.TitleScore * 0.7) + (x.ArtistScore * 0.3)
})
.OrderByDescending(x => x.TotalScore)
.FirstOrDefault();
// Use 70% threshold (same as playback matching)
if (bestMatch != null && bestMatch.TotalScore >= 70)
{
isLocal = true;
}
} }
} }
@@ -502,8 +525,8 @@ public class AdminController : ControllerBase
} }
else else
{ {
// Check if external track is matched // Check if external track is matched (either manual mapping or auto-matched)
if (matchedSpotifyIds.Contains(track.SpotifyId)) if (hasExternalMapping || matchedSpotifyIds.Contains(track.SpotifyId))
{ {
externalMatchedCount++; externalMatchedCount++;
} }

View File

@@ -1202,8 +1202,37 @@
if (hash) { if (hash) {
switchTab(hash); switchTab(hash);
} }
// Start auto-refresh for playlists tab (every 5 seconds)
startPlaylistAutoRefresh();
}); });
// Auto-refresh functionality for playlists
let playlistAutoRefreshInterval = null;
function startPlaylistAutoRefresh() {
// Clear any existing interval
if (playlistAutoRefreshInterval) {
clearInterval(playlistAutoRefreshInterval);
}
// Refresh every 5 seconds when on playlists tab
playlistAutoRefreshInterval = setInterval(() => {
const playlistsTab = document.getElementById('tab-playlists');
if (playlistsTab && playlistsTab.classList.contains('active')) {
// Silently refresh without showing loading state
fetchPlaylists(true);
}
}, 5000);
}
function stopPlaylistAutoRefresh() {
if (playlistAutoRefreshInterval) {
clearInterval(playlistAutoRefreshInterval);
playlistAutoRefreshInterval = null;
}
}
// Toast notification // Toast notification
function showToast(message, type = 'success', duration = 3000) { function showToast(message, type = 'success', duration = 3000) {
const toast = document.createElement('div'); const toast = document.createElement('div');
@@ -1343,7 +1372,7 @@
} }
} }
async function fetchPlaylists() { async function fetchPlaylists(silent = false) {
try { try {
const res = await fetch('/api/admin/playlists'); const res = await fetch('/api/admin/playlists');
const data = await res.json(); const data = await res.json();
@@ -1351,7 +1380,9 @@
const tbody = document.getElementById('playlist-table-body'); const tbody = document.getElementById('playlist-table-body');
if (data.playlists.length === 0) { if (data.playlists.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:var(--text-secondary);padding:40px;">No playlists configured. Link playlists from the Jellyfin Playlists tab.</td></tr>'; if (!silent) {
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:var(--text-secondary);padding:40px;">No playlists configured. Link playlists from the Jellyfin Playlists tab.</td></tr>';
}
return; return;
} }