Files
AI-VideoAssistant/web/services/apiClient.ts
2026-02-26 01:58:39 +08:00

60 lines
1.6 KiB
TypeScript

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<string, string>;
};
export class ApiError extends Error {
status: number;
constructor(message: string, status: number) {
super(message);
this.name = 'ApiError';
this.status = status;
}
}
export const apiRequest = async <T>(path: string, options: RequestOptions = {}): Promise<T> => {
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<T>;
};