File size: 3,914 Bytes
722753e 900a32d e4daa3b 1ce2fdc 900a32d 1ce2fdc 900a32d 1ce2fdc 900a32d 1ce2fdc 900a32d 1ce2fdc 900a32d 1ce2fdc 900a32d 1ce2fdc e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 722753e e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 722753e e4daa3b 900a32d 722753e e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 722753e 900a32d 722753e 900a32d 722753e 900a32d 722753e 900a32d 722753e 900a32d 722753e 900a32d 722753e 900a32d 722753e e4daa3b 900a32d e4daa3b 900a32d e4daa3b 900a32d |
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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
import type {
CasesResponse,
CreateJobResponse,
JobStatusResponse,
} from "../types";
/**
* Safely parse JSON error response, logging failures in development.
* Returns empty object if parsing fails (e.g., HTML error pages from proxies).
* (BUG-013 fix: was silently returning {} without any logging)
*/
async function parseErrorJson(
response: Response,
): Promise<{ detail?: string }> {
try {
return await response.json();
} catch (parseError) {
// Log in development to help debug malformed responses
if (import.meta.env.DEV) {
console.warn(
"Failed to parse error response as JSON:",
parseError,
"Status:",
response.status,
response.statusText,
);
}
return {};
}
}
function getApiBase(): string {
const url = import.meta.env.VITE_API_URL;
// In production, VITE_API_URL must be set - fail fast with clear error
if (import.meta.env.PROD && !url) {
throw new Error(
"VITE_API_URL environment variable is required in production. " +
"Set it to the backend API URL (e.g., https://your-app.hf.space).",
);
}
// In development, fall back to localhost
return url || "http://localhost:7860";
}
const API_BASE = getApiBase();
export class ApiError extends Error {
status: number;
detail?: string;
constructor(message: string, status: number, detail?: string) {
super(message);
this.name = "ApiError";
this.status = status;
this.detail = detail;
}
}
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
/**
* Get list of available cases
*/
async getCases(signal?: AbortSignal): Promise<CasesResponse> {
const response = await fetch(`${this.baseUrl}/api/cases`, { signal });
if (!response.ok) {
const error = await parseErrorJson(response);
throw new ApiError(
`Failed to fetch cases: ${response.statusText}`,
response.status,
error.detail,
);
}
return response.json();
}
/**
* Create a segmentation job (async - returns immediately with job ID)
*
* The actual ML inference runs in the background. Poll getJobStatus()
* to track progress and retrieve results when complete.
*/
async createSegmentJob(
caseId: string,
fastMode: boolean = true,
signal?: AbortSignal,
): Promise<CreateJobResponse> {
const response = await fetch(`${this.baseUrl}/api/segment`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
case_id: caseId,
fast_mode: fastMode,
}),
signal,
});
if (!response.ok) {
const error = await parseErrorJson(response);
throw new ApiError(
`Failed to create job: ${error.detail || response.statusText}`,
response.status,
error.detail,
);
}
return response.json();
}
/**
* Get the status of a segmentation job
*
* Poll this endpoint to track progress and retrieve results.
* When status is 'completed', the result field contains segmentation data.
* When status is 'failed', the error field contains the error message.
*/
async getJobStatus(
jobId: string,
signal?: AbortSignal,
): Promise<JobStatusResponse> {
const response = await fetch(`${this.baseUrl}/api/jobs/${jobId}`, {
signal,
});
if (response.status === 404) {
throw new ApiError(
"Job not found or expired",
404,
"Jobs expire after 1 hour",
);
}
if (!response.ok) {
const error = await parseErrorJson(response);
throw new ApiError(
`Failed to get job status: ${error.detail || response.statusText}`,
response.status,
error.detail,
);
}
return response.json();
}
}
export const apiClient = new ApiClient(API_BASE);
|