interface RequestOptions extends RequestInit { params?: Record; } class ApiClient { private baseUrl: string = '/api'; async get(url: string, params?: Record): Promise { return this.request(url, { method: 'GET', params }); } async post(url: string, body: any): Promise { return this.request(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); } async put(url: string, body: any): Promise { return this.request(url, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); } async delete(url: string): Promise { return this.request(url, { method: 'DELETE' }); } async getBlob(url: string): Promise { // Ensure endpoint starts with / if not empty and is relative if (url && !url.startsWith('/') && !url.startsWith('http')) { url = '/' + url; } // Handle absolute URLs (like preloading external) const fetchUrl = url.startsWith('http') ? url : `${this.baseUrl}${url}`; const response = await fetch(fetchUrl); if (!response.ok) throw new Error("Fetch failed"); return response.blob(); } private async request(endpoint: string, options: RequestOptions = {}): Promise { // Ensure endpoint starts with / if not empty if (endpoint && !endpoint.startsWith('/')) { endpoint = '/' + endpoint; } let url = `${this.baseUrl}${endpoint}`; if (options.params) { const query = new URLSearchParams(options.params).toString(); url += `?${query}`; } const response = await fetch(url, options); if (!response.ok) { const errorBody = await response.json().catch(() => ({})); throw new Error(errorBody.detail || `HTTP Error ${response.status}`); } if (response.status === 204) { return {} as T; } return response.json(); } } export const api = new ApiClient();