diff --git a/src/lib/components/views/DynamicSuggestView.svelte b/src/lib/components/views/DynamicSuggestView.svelte
new file mode 100644
index 000000000..7e5f85b92
--- /dev/null
+++ b/src/lib/components/views/DynamicSuggestView.svelte
@@ -0,0 +1,543 @@
+
+
+
+
+
+ {#if error}
+
{error}
+ {/if}
+
+
+
+ {#if !result && loading}
+
{$t('actions.loading')}
+ {:else if result && filteredTracks.length === 0}
+
{$t('yourMixes.result.empty')}
+ {:else if result}
+
+
+
+ {#each filteredTracks as track, index}
+ playTrack(track)}
+ />
+ {/each}
+
+ {/if}
+
+
+ {
+ showPlaylistModal = false;
+ }}
+ onSuccess={() => {
+ showToast($t('toast.addedToPlaylist'), 'success');
+ showPlaylistModal = false;
+ }}
+/>
+
+
diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json
index cd0ac08b3..80520f358 100644
--- a/src/lib/i18n/locales/en.json
+++ b/src/lib/i18n/locales/en.json
@@ -1678,6 +1678,40 @@
},
"regionNotice": "Purchases availability depends on your Qobuz account and region. If unavailable, this may be expected."
},
+ "yourMixes": {
+ "title": "DailyQ",
+ "subtitle": "Based on your recent listening.",
+ "actions": {
+ "saveAsPlaylist": "Save as playlist"
+ },
+ "form": {
+ "limit": "Limit",
+ "listenedTrackIds": "Listened track IDs (comma-separated)",
+ "listenedTrackIdsPlaceholder": "123,456,789",
+ "tracksToAnalyse": "Tracks to analyse (one row: trackId,artistId,genreId,labelId)",
+ "tracksToAnalysePlaceholder": "123,45,78,90",
+ "prefillFromRecents": "Use QBZ recent log",
+ "run": "Fetch Suggestions",
+ "probeRaw": "Probe Raw Response",
+ "rawResponse": "Raw response"
+ },
+ "result": {
+ "algorithm": "Algorithm",
+ "count": "{count} tracks",
+ "sourceCached": "Cached for today",
+ "sourceLive": "Freshly generated",
+ "empty": "No suggested tracks returned.",
+ "emptyHint": "Tried fallback payload strategies. This account/seed set may currently return no mix candidates.",
+ "unknownArtist": "Unknown artist"
+ },
+ "errors": {
+ "fetchFailed": "Failed to fetch dynamic suggestions.",
+ "prefillFailed": "Failed to load recent data from QBZ logs.",
+ "rawProbeFailed": "Failed to fetch raw endpoint response.",
+ "playlistsLoadFailed": "Failed to load your playlists.",
+ "emptyPlaylist": "No tracks available to save as playlist."
+ }
+ },
"linkResolver": {
"title": "Open Qobuz Link",
"placeholder": "Paste a Qobuz link (album, track, artist, playlist)...",
diff --git a/src/lib/i18n/locales/es.json b/src/lib/i18n/locales/es.json
index 2b2ba1cdb..4192baffa 100644
--- a/src/lib/i18n/locales/es.json
+++ b/src/lib/i18n/locales/es.json
@@ -1678,6 +1678,40 @@
},
"regionNotice": "La disponibilidad de Compras depende de tu cuenta y region de Qobuz. Si no aparece disponible, puede ser esperado."
},
+ "yourMixes": {
+ "title": "DailyQ",
+ "subtitle": "Basado en tus escuchas recientes.",
+ "actions": {
+ "saveAsPlaylist": "Guardar como playlist"
+ },
+ "form": {
+ "limit": "Limite",
+ "listenedTrackIds": "IDs de pistas escuchadas (separadas por coma)",
+ "listenedTrackIdsPlaceholder": "123,456,789",
+ "tracksToAnalyse": "Pistas a analizar (una linea: trackId,artistId,genreId,labelId)",
+ "tracksToAnalysePlaceholder": "123,45,78,90",
+ "prefillFromRecents": "Usar log reciente de QBZ",
+ "run": "Obtener sugerencias",
+ "probeRaw": "Probar respuesta raw",
+ "rawResponse": "Respuesta raw"
+ },
+ "result": {
+ "algorithm": "Algoritmo",
+ "count": "{count} pistas",
+ "sourceCached": "Cacheado por hoy",
+ "sourceLive": "Generado al momento",
+ "empty": "No se devolvieron pistas sugeridas.",
+ "emptyHint": "Se probaron estrategias de fallback del payload. Esta cuenta/semillas puede no estar devolviendo candidatos ahora.",
+ "unknownArtist": "Artista desconocido"
+ },
+ "errors": {
+ "fetchFailed": "No se pudieron obtener sugerencias dinamicas.",
+ "prefillFailed": "No se pudieron cargar datos recientes del log de QBZ.",
+ "rawProbeFailed": "No se pudo obtener la respuesta raw del endpoint.",
+ "playlistsLoadFailed": "No se pudieron cargar tus playlists.",
+ "emptyPlaylist": "No hay pistas para guardar como playlist."
+ }
+ },
"linkResolver": {
"title": "Abrir enlace de Qobuz",
"placeholder": "Pega un enlace de Qobuz (album, track, artista, playlist)...",
diff --git a/src/lib/services/dynamicSuggest.ts b/src/lib/services/dynamicSuggest.ts
new file mode 100644
index 000000000..22282f8db
--- /dev/null
+++ b/src/lib/services/dynamicSuggest.ts
@@ -0,0 +1,25 @@
+import { invoke } from '@tauri-apps/api/core';
+import type {
+ DynamicSuggestRequest,
+ DynamicSuggestResponse
+} from '$lib/types/dynamicSuggest';
+
+export async function getDynamicSuggest(
+ payload: DynamicSuggestRequest
+): Promise {
+ return invoke('v2_dynamic_suggest', {
+ limit: payload.limit ?? null,
+ listenedTrackIds: payload.listenedTrackIds ?? [],
+ tracksToAnalyse: payload.tracksToAnalyse ?? []
+ });
+}
+
+export async function getDynamicSuggestRaw(
+ payload: DynamicSuggestRequest
+): Promise {
+ return invoke('v2_dynamic_suggest_raw', {
+ limit: payload.limit ?? null,
+ listenedTrackIds: payload.listenedTrackIds ?? [],
+ tracksToAnalyse: payload.tracksToAnalyse ?? []
+ });
+}
diff --git a/src/lib/types/dynamicSuggest.ts b/src/lib/types/dynamicSuggest.ts
new file mode 100644
index 000000000..e1fdafe95
--- /dev/null
+++ b/src/lib/types/dynamicSuggest.ts
@@ -0,0 +1,38 @@
+export interface DynamicTrackToAnalyseInput {
+ trackId: number;
+ artistId: number;
+ genreId: number;
+ labelId: number;
+}
+
+export interface DynamicSuggestTrack {
+ id: number;
+ title: string;
+ duration?: number;
+ performer?: { id?: number; name: string };
+ album?: {
+ id?: string;
+ title: string;
+ image?: { small?: string; thumbnail?: string; large?: string };
+ };
+ hires?: boolean;
+ maximum_bit_depth?: number;
+ maximum_sampling_rate?: number;
+ streamable?: boolean;
+}
+
+export interface DynamicSuggestResponse {
+ algorithm: string;
+ tracks: {
+ offset: number;
+ limit: number;
+ total: number;
+ items: DynamicSuggestTrack[];
+ };
+}
+
+export interface DynamicSuggestRequest {
+ limit?: number;
+ listenedTrackIds?: number[];
+ tracksToAnalyse?: DynamicTrackToAnalyseInput[];
+}