feat(discovery): Navidrome AudioMuse-AI client integration and library scoping

Add per-server toggle for AudioMuse-style discovery: Instant Mix via
getSimilarSongs, server similar artists instead of Last.fm on artist pages,
and higher similar-artist count in Now Playing when enabled.

When browsing a single music folder, filter similar/top song results by
album ids from that scope (Navidrome does not apply musicFolderId to those
endpoints). Request larger similar batches under a narrow scope.

Keep radio-from-track as two blocks (shuffled top songs, then shuffled
similar-by-artist) so it differs from Instant Mix. Show Alpha badge beside
the setting. Add research/ to .gitignore.
This commit is contained in:
Maxim Isaev
2026-04-10 12:07:32 +03:00
parent 74985fe331
commit 36f3d42dbe
14 changed files with 259 additions and 29 deletions
+20
View File
@@ -104,6 +104,13 @@ interface AuthState {
entityRatingSupportByServer: Record<string, EntityRatingSupportLevel>;
setEntityRatingSupport: (serverId: string, level: EntityRatingSupportLevel) => void;
/**
* Per server: Navidrome has the AudioMuse-AI plugin — use `getSimilarSongs` (Instant Mix) and
* `getArtistInfo2` similar artists instead of Last.fm for discovery on this server.
*/
audiomuseNavidromeByServer: Record<string, boolean>;
setAudiomuseNavidromeEnabled: (serverId: string, enabled: boolean) => void;
// Status
isLoggedIn: boolean;
isConnecting: boolean;
@@ -250,6 +257,7 @@ export const useAuthStore = create<AuthState>()(
musicLibraryFilterByServer: {},
musicLibraryFilterVersion: 0,
entityRatingSupportByServer: {},
audiomuseNavidromeByServer: {},
isLoggedIn: false,
isConnecting: false,
connectionError: null,
@@ -272,11 +280,13 @@ export const useAuthStore = create<AuthState>()(
const newServers = s.servers.filter(srv => srv.id !== id);
const switchedAway = s.activeServerId === id;
const { [id]: _r, ...entityRatingRest } = s.entityRatingSupportByServer;
const { [id]: _a, ...audiomuseRest } = s.audiomuseNavidromeByServer;
return {
servers: newServers,
activeServerId: switchedAway ? (newServers[0]?.id ?? null) : s.activeServerId,
isLoggedIn: switchedAway ? false : s.isLoggedIn,
entityRatingSupportByServer: entityRatingRest,
audiomuseNavidromeByServer: audiomuseRest,
};
});
},
@@ -403,6 +413,16 @@ export const useAuthStore = create<AuthState>()(
entityRatingSupportByServer: { ...s.entityRatingSupportByServer, [serverId]: level },
})),
setAudiomuseNavidromeEnabled: (serverId, enabled) =>
set(s => ({
audiomuseNavidromeByServer: enabled
? { ...s.audiomuseNavidromeByServer, [serverId]: true }
: (() => {
const { [serverId]: _removed, ...rest } = s.audiomuseNavidromeByServer;
return rest;
})(),
})),
logout: () => set({ isLoggedIn: false, musicFolders: [] }),
getBaseUrl: () => {