export const API_BASE = import.meta.env.VITE_API_BASE ?? "http://localhost:8000"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface StepSummary { step_number: number; status: "pending" | "processing" | "completed" | "failed"; original_image_url: string | null; result_image_url: string | null; error_message: string | null; } // --------------------------------------------------------------------------- // Upload // --------------------------------------------------------------------------- /** * Upload an image file to the backend. * POSTs as multipart/form-data; backend applies EXIF rotation, saves step 0, * and returns the job_id. */ export async function uploadImage(file: File): Promise<{ job_id: string }> { const formData = new FormData(); formData.append("file", file); const response = await fetch(`${API_BASE}/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 }>; } // --------------------------------------------------------------------------- // Generate // --------------------------------------------------------------------------- /** * POST /generate — send the mask JSON to the backend and start inpainting. * * The backend creates a new step (truncating any forward history if needed), * starts inference in a BackgroundTask, and returns immediately. */ export async function generateMask( jobId: string, maskJson: object, maskSource: "manual" | "ocr" | "mixed" = "manual", ): Promise<{ job_id: string; step_number: number; status: string }> { const response = await fetch(`${API_BASE}/generate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ job_id: jobId, mask_source: maskSource, mask_json: maskJson, }), }); 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; step_number: number; status: string }>; } // --------------------------------------------------------------------------- // Result polling // --------------------------------------------------------------------------- /** * GET /result/{job_id} — poll current job state. * * Returns top-level `status` / `error_message` for the current step * (backward-compatible with the polling hook), plus the full `steps[]` array * and `current_step` for the multi-step history navigator. */ export async function getJobResult(jobId: string): Promise<{ job_id: string; current_step: number; status: "pending" | "processing" | "completed" | "failed"; result_image_url: string | null; original_image_url: string | null; error_message: string | null; steps: StepSummary[]; }> { const response = await fetch(`${API_BASE}/result/${jobId}`); if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); } // --------------------------------------------------------------------------- // Step navigation // --------------------------------------------------------------------------- /** * POST /jobs/{job_id}/goto-step — set job.current_step to stepNumber. * * Only completed steps may be navigated to. Truncation of forward history * happens lazily on the next POST /generate call. */ export async function goToStep( jobId: string, stepNumber: number, ): Promise<{ job_id: string; current_step: number }> { const response = await fetch(`${API_BASE}/jobs/${jobId}/goto-step`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ step_number: stepNumber }), }); 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; current_step: number }>; }