feat(discover): wire A4 search + A5 downloads to backend

Adds DownloadJob/ExternalSearchResult/SourceInfo contract types + mappers, the downloads + search RTKQ endpoints, and the SearchDownloadPage (search external sources, per-result download states) and DownloadsManagerPage (active/history, progress, retry/cancel, poll-while-active). en/ru i18n. Snapshot also bundles in-progress queue/metadata-editor/storage UI work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Senko-san
2026-06-14 14:04:43 +03:00
parent cdcacc56d1
commit 231887c3b7
23 changed files with 1168 additions and 91 deletions
+47 -5
View File
@@ -78,20 +78,62 @@ export interface PlaylistTrack extends Track {
addedAt: string;
}
/** Lifecycle of a download job, mirroring the backend's `DownloadStatus`.
* `enriching` = file fetched, metadata pipeline running; `done` = imported. */
export type DownloadStatus =
| 'queued'
| 'downloading'
| 'enriching'
| 'done'
| 'failed';
export interface DownloadJob {
id: string;
url: string;
title?: string;
artist?: string;
album?: string;
status: 'queued' | 'downloading' | 'processing' | 'done' | 'error';
/** Source backend the job pulls from (e.g. `youtube`). */
source: string;
/** Stable per-source id of the item (e.g. a YouTube videoId). */
sourceId?: string;
/** The free-text query the job was created from, for display. */
query?: string;
status: DownloadStatus;
/** Fraction complete, 0..1. */
progress: number;
errorMessage?: string;
/** Set once the download finishes and the library track exists. */
trackId?: string;
retryCount: number;
createdAt: string;
updatedAt: string;
}
/** Result of POST /downloads. Either the item is already in the library
* (`alreadyInLibrary`, `trackId` set), or a job covers it (`job`). */
export interface DownloadRequestResult {
alreadyInLibrary: boolean;
trackId?: string;
job?: DownloadJob;
}
/** One hit from an external (fetch) source — the §A4 discover screen. */
export interface ExternalSearchResult {
source: string;
sourceId: string;
title: string;
artist?: string;
album?: string;
durationMs?: number;
thumbnailUrl?: string;
}
/** A registered source backend, from GET /sources. `kind`: `indexable` (a
* mounted folder) or `fetch` (searchable + downloadable, e.g. YouTube). */
export interface SourceInfo {
name: string;
label: string;
kind: 'indexable' | 'fetch';
available: boolean;
}
export interface UploadResponse {
track_id: string;
title: string;