const DEFAULT_API_BASE_URL = 'http://127.0.0.1:8100/api'; const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, ''); const getApiBaseUrl = (): string => { const configured = import.meta.env.VITE_API_BASE_URL || DEFAULT_API_BASE_URL; return trimTrailingSlash(configured); }; export { getApiBaseUrl }; type RequestOptions = { method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; body?: unknown; signal?: AbortSignal; headers?: Record; }; export class ApiError extends Error { status: number; constructor(message: string, status: number) { super(message); this.name = 'ApiError'; this.status = status; } } export const apiRequest = async (path: string, options: RequestOptions = {}): Promise => { const url = `${getApiBaseUrl()}${path.startsWith('/') ? path : `/${path}`}`; const response = await fetch(url, { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...options.headers, }, body: options.body === undefined ? undefined : JSON.stringify(options.body), signal: options.signal, }); if (!response.ok) { let message = `Request failed: ${response.status}`; try { const errorData = await response.json(); if (errorData?.detail) { message = typeof errorData.detail === 'string' ? errorData.detail : message; } } catch { // Ignore parsing errors. } throw new ApiError(message, response.status); } if (response.status === 204) { return undefined as T; } return response.json() as Promise; };