File size: 3,239 Bytes
497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff |
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 |
import type {
CasesResponse,
CreateJobResponse,
JobStatusResponse,
} from '../types'
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 response.json().catch(() => ({}))
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 response.json().catch(() => ({}))
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 response.json().catch(() => ({}))
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)
|