File size: 3,341 Bytes
a3953cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
 * 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<UpscaleResult> {
  const response = await fetch(`${UPSCALE_API_BASE}/upscale/result/${jobId}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json() as Promise<UpscaleResult>;
}