import type { OriginalTrackAsset, OutputFormat, SourceKind, StemAsset, StemResult, } from "./types"; export interface SourceImportResponse { job_id: string; filename: string; source_url: string; resolved_url?: string; title?: string; platform: Exclude; } export async function uploadFile( file: File, onProgress?: (progress: number) => void ): Promise<{ job_id: string; filename: string }> { const formData = new FormData(); formData.append("file", file); const xhr = new XMLHttpRequest(); return new Promise((resolve, reject) => { xhr.upload.addEventListener("progress", (e) => { if (e.lengthComputable && onProgress) { onProgress(e.loaded / e.total); } }); xhr.addEventListener("load", () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText)); } else { try { const err = JSON.parse(xhr.responseText); reject(new Error(err.detail || `Upload failed (${xhr.status})`)); } catch { reject(new Error(`Upload failed (${xhr.status})`)); } } }); xhr.addEventListener("error", () => reject(new Error("Upload failed"))); xhr.open("POST", "/api/upload"); xhr.send(formData); }); } export async function importUrl( url: string ): Promise { const res = await fetch("/api/import-url", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url }), }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: "Request failed" })); throw new Error(err.detail || `Import failed (${res.status})`); } return res.json(); } export async function startSeparation( jobId: string, stems: string[], outputFormat: OutputFormat ): Promise { const res = await fetch("/api/separate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ job_id: jobId, stems, output_format: outputFormat }), }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: "Request failed" })); throw new Error(err.detail || `Separation failed (${res.status})`); } } export interface ProgressEvent { state: string; progress: number; message: string; stems?: Record; error?: string; } export function subscribeProgress( jobId: string, onEvent: (event: ProgressEvent) => void, onDone: (stems: StemResult[]) => void, onError: (error: string) => void ): () => void { const es = new EventSource(`/api/progress/${jobId}`); let closedByApp = false; let terminalEventSeen = false; let errorTimer: number | null = null; const clearErrorTimer = () => { if (errorTimer !== null) { window.clearTimeout(errorTimer); errorTimer = null; } }; es.onopen = () => { clearErrorTimer(); }; es.onmessage = (e) => { try { const data: ProgressEvent = JSON.parse(e.data); clearErrorTimer(); onEvent(data); if (data.state === "done" && data.stems) { terminalEventSeen = true; const stemList: StemResult[] = Object.entries(data.stems).map( ([name, filename]) => ({ name, filename }) ); onDone(stemList); closedByApp = true; es.close(); } else if (data.state === "error") { terminalEventSeen = true; onError(data.error || "Separation failed"); closedByApp = true; es.close(); } } catch { // ignore parse errors } }; es.onerror = () => { if (closedByApp || terminalEventSeen) { return; } if (es.readyState === EventSource.CLOSED) { onError("Connection to server lost"); closedByApp = true; es.close(); return; } if (errorTimer === null) { errorTimer = window.setTimeout(() => { errorTimer = null; if (!closedByApp && !terminalEventSeen && es.readyState !== EventSource.OPEN) { onError("Connection to server lost"); closedByApp = true; es.close(); } }, 5000); } }; return () => { closedByApp = true; clearErrorTimer(); es.close(); }; } export interface ExampleOutputResponse { song?: string; original: OriginalTrackAsset; stems: StemAsset[]; downloadAllUrl: string; } export async function fetchExampleOutput(): Promise { const res = await fetch("/api/examples/default"); if (!res.ok) { const err = await res.json().catch(() => ({ detail: "Request failed" })); throw new Error(err.detail || `Example load failed (${res.status})`); } return res.json(); } export function getAudioUrl(jobId: string, filename: string): string { return `/api/audio/${jobId}/${encodeURIComponent(filename)}`; } export function getDownloadUrl(jobId: string, filename: string): string { return `/api/download/${jobId}/${encodeURIComponent(filename)}`; } export function getDownloadAllUrl(jobId: string): string { return `/api/download/${jobId}/all`; }