/** * upscaleClient.ts — API client for the Quality Enhance (upscaler) feature. * * Completely independent of client.ts and the watermark-removal pipeline. * Do not import from client.ts or share state with watermark-removal hooks. * * URL note * -------- * The backend stores result paths relative to STORAGE_DIR (e.g. "/upscale/{id}/result.jpg"). * The FastAPI StaticFiles mount is at /storage, so the full URL is: * http://localhost:8000/storage/upscale/{id}/result.jpg * Use buildUpscaleUrl() to convert a stored relative path to a usable URL. */ export const UPSCALE_API_BASE = import.meta.env.VITE_API_BASE ?? "http://localhost:8000"; /** * Convert a relative result path (e.g. "/upscale/{id}/result.jpg") to the * full URL served by the FastAPI /storage StaticFiles mount. */ export function buildUpscaleUrl(relPath: string): string { return `${UPSCALE_API_BASE}/storage${relPath}`; } // --------------------------------------------------------------------------- // Upload // --------------------------------------------------------------------------- /** * POST /upscale/upload — submit an image for upscaling. * * The backend applies EXIF correction, persists the image, and immediately * kicks off background upscaling. Returns job_id; poll getUpscaleResult() * to track progress. * * outputScale * ----------- * "4x" (default) — save the raw Real-ESRGAN 4x output. * "2x" — generate at 4x internally, then downscale 50% via LANCZOS * before saving. Produces a more natural-looking result on * organic textures (grass, foliage, skin) by smoothing the * model's over-sharpened high-frequency detail. */ export async function uploadForUpscale( file: File, outputScale: "4x" | "2x" | "1.5x" = "4x", denoise: boolean = false, sharpen: boolean = false, ): Promise<{ job_id: string }> { const formData = new FormData(); formData.append("file", file); formData.append("output_scale", outputScale); formData.append("denoise", String(denoise)); formData.append("sharpen", String(sharpen)); const response = await fetch(`${UPSCALE_API_BASE}/upscale/upload`, { method: "POST", body: formData, }); if (!response.ok) { let detail = `HTTP ${response.status}`; try { const json = await response.json(); if (json?.detail) detail = json.detail; } catch { /* ignore */ } throw new Error(detail); } return response.json() as Promise<{ job_id: string }>; } // --------------------------------------------------------------------------- // Result polling // --------------------------------------------------------------------------- export interface UpscaleResult { job_id: string; status: "pending" | "processing" | "completed" | "failed"; result_image_url: string | null; // relative path, use buildUpscaleUrl() for full URL error_message: string | null; } /** * GET /upscale/result/{job_id} — poll the current status of an upscale job. */ export async function getUpscaleResult(jobId: string): Promise { const response = await fetch(`${UPSCALE_API_BASE}/upscale/result/${jobId}`); if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json() as Promise; }