File size: 3,114 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
/**
 * facerestoreClient.ts — API client for the Face Restoration feature.
 *
 * Completely independent of client.ts, upscaleClient.ts, bgremoveClient.ts,
 * and objectRemovalClient.ts.
 * Do not import from or share state with any other feature's client.
 *
 * URL note
 * --------
 * The backend stores result paths relative to STORAGE_DIR
 * (e.g. "/facerestore/{folder}/result.jpg").
 * The FastAPI StaticFiles mount is at /storage, so the full URL is:
 *   http://localhost:8000/storage/facerestore/{folder}/result.jpg
 * Use buildFaceRestoreUrl() to convert a stored relative path to a usable URL.
 */

export const FACERESTORE_API_BASE = import.meta.env.VITE_API_BASE ?? "http://localhost:8000";

/**
 * Convert a relative result path (e.g. "/facerestore/{folder}/result.jpg")
 * to the full URL served by the FastAPI /storage StaticFiles mount.
 */
export function buildFaceRestoreUrl(relPath: string): string {
  return `${FACERESTORE_API_BASE}/storage${relPath}`;
}

// ---------------------------------------------------------------------------
// Upload
// ---------------------------------------------------------------------------

/**
 * POST /facerestore/upload — submit an image for face restoration.
 *
 * The backend applies EXIF correction, persists the image, and immediately
 * kicks off CodeFormer + ArcFace identity-safeguarded restoration.
 * Returns job_id; poll getFaceRestoreResult() to track progress.
 */
export type FidelityPreset = "preserve" | "balanced" | "sharpen";

export async function uploadForFaceRestore(
  file: File,
  fidelityPreset: FidelityPreset = "balanced",
): Promise<{ job_id: string }> {
  const formData = new FormData();
  formData.append("file", file);
  formData.append("fidelity_preset", fidelityPreset);

  const response = await fetch(`${FACERESTORE_API_BASE}/facerestore/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 FaceRestoreResult {
  job_id:           string;
  status:           "pending" | "processing" | "completed" | "failed";
  result_image_url: string | null;   // relative path — use buildFaceRestoreUrl()
  error_message:    string | null;
  faces_detected:   number;
  faces_restored:   number;
  faces_rejected:   number;
}

/**
 * GET /facerestore/result/{job_id} — poll the current status of a face
 * restoration job.
 */
export async function getFaceRestoreResult(
  jobId: string,
): Promise<FaceRestoreResult> {
  const response = await fetch(
    `${FACERESTORE_API_BASE}/facerestore/result/${jobId}`,
  );
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json() as Promise<FaceRestoreResult>;
}