feat: Introduce centralized API fetch utilities
refactor(models): Use new API fetch utilities refactor(props): Use new API fetch utilities
This commit is contained in:
parent
948278d663
commit
ace0de145a
|
|
@ -1,6 +1,5 @@
|
|||
import { base } from '$app/paths';
|
||||
import { ServerModelStatus } from '$lib/enums';
|
||||
import { getJsonHeaders } from '$lib/utils';
|
||||
import { apiFetch, apiPost } from '$lib/utils';
|
||||
|
||||
/**
|
||||
* ModelsService - Stateless service for model management API communication
|
||||
|
|
@ -31,15 +30,7 @@ export class ModelsService {
|
|||
* Works in both MODEL and ROUTER modes
|
||||
*/
|
||||
static async list(): Promise<ApiModelListResponse> {
|
||||
const response = await fetch(`${base}/v1/models`, {
|
||||
headers: getJsonHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch model list (status ${response.status})`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<ApiModelListResponse>;
|
||||
return apiFetch<ApiModelListResponse>('/v1/models');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -47,15 +38,7 @@ export class ModelsService {
|
|||
* Returns models with load status, paths, and other metadata
|
||||
*/
|
||||
static async listRouter(): Promise<ApiRouterModelsListResponse> {
|
||||
const response = await fetch(`${base}/v1/models`, {
|
||||
headers: getJsonHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch router models list (status ${response.status})`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<ApiRouterModelsListResponse>;
|
||||
return apiFetch<ApiRouterModelsListResponse>('/v1/models');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -78,18 +61,7 @@ export class ModelsService {
|
|||
payload.extra_args = extraArgs;
|
||||
}
|
||||
|
||||
const response = await fetch(`${base}/models/load`, {
|
||||
method: 'POST',
|
||||
headers: getJsonHeaders(),
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.error || `Failed to load model (status ${response.status})`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<ApiRouterModelsLoadResponse>;
|
||||
return apiPost<ApiRouterModelsLoadResponse>('/models/load', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -98,18 +70,7 @@ export class ModelsService {
|
|||
* @param modelId - Model identifier to unload
|
||||
*/
|
||||
static async unload(modelId: string): Promise<ApiRouterModelsUnloadResponse> {
|
||||
const response = await fetch(`${base}/models/unload`, {
|
||||
method: 'POST',
|
||||
headers: getJsonHeaders(),
|
||||
body: JSON.stringify({ model: modelId })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.error || `Failed to unload model (status ${response.status})`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<ApiRouterModelsUnloadResponse>;
|
||||
return apiPost<ApiRouterModelsUnloadResponse>('/models/unload', { model: modelId });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getAuthHeaders } from '$lib/utils';
|
||||
import { apiFetchWithParams } from '$lib/utils';
|
||||
|
||||
/**
|
||||
* PropsService - Server properties management
|
||||
|
|
@ -31,23 +31,12 @@ export class PropsService {
|
|||
* @throws {Error} If the request fails or returns invalid data
|
||||
*/
|
||||
static async fetch(autoload = false): Promise<ApiLlamaCppServerProps> {
|
||||
const url = new URL('./props', window.location.href);
|
||||
const params: Record<string, string> = {};
|
||||
if (!autoload) {
|
||||
url.searchParams.set('autoload', 'false');
|
||||
params.autoload = 'false';
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch server properties: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data as ApiLlamaCppServerProps;
|
||||
return apiFetchWithParams<ApiLlamaCppServerProps>('./props', params, { authOnly: true });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -59,23 +48,11 @@ export class PropsService {
|
|||
* @throws {Error} If the request fails or returns invalid data
|
||||
*/
|
||||
static async fetchForModel(modelId: string, autoload = false): Promise<ApiLlamaCppServerProps> {
|
||||
const url = new URL('./props', window.location.href);
|
||||
url.searchParams.set('model', modelId);
|
||||
const params: Record<string, string> = { model: modelId };
|
||||
if (!autoload) {
|
||||
url.searchParams.set('autoload', 'false');
|
||||
params.autoload = 'false';
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch model properties: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data as ApiLlamaCppServerProps;
|
||||
return apiFetchWithParams<ApiLlamaCppServerProps>('./props', params, { authOnly: true });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
import { base } from '$app/paths';
|
||||
import { getJsonHeaders, getAuthHeaders } from './api-headers';
|
||||
|
||||
/**
|
||||
* API Fetch Utilities
|
||||
*
|
||||
* Provides common fetch patterns used across services:
|
||||
* - Automatic JSON headers
|
||||
* - Error handling with proper error messages
|
||||
* - Base path resolution
|
||||
*/
|
||||
|
||||
export interface ApiFetchOptions extends Omit<RequestInit, 'headers'> {
|
||||
/**
|
||||
* Use auth-only headers (no Content-Type).
|
||||
* Default: false (uses JSON headers with Content-Type: application/json)
|
||||
*/
|
||||
authOnly?: boolean;
|
||||
/**
|
||||
* Additional headers to merge with default headers.
|
||||
*/
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON data from an API endpoint with standard headers and error handling.
|
||||
*
|
||||
* @param path - API path (will be prefixed with base path)
|
||||
* @param options - Fetch options with additional authOnly flag
|
||||
* @returns Parsed JSON response
|
||||
* @throws Error with formatted message on failure
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // GET request
|
||||
* const models = await apiFetch<ApiModelListResponse>('/v1/models');
|
||||
*
|
||||
* // POST request
|
||||
* const result = await apiFetch<ApiResponse>('/models/load', {
|
||||
* method: 'POST',
|
||||
* body: JSON.stringify({ model: 'gpt-4' })
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function apiFetch<T>(path: string, options: ApiFetchOptions = {}): Promise<T> {
|
||||
const { authOnly = false, headers: customHeaders, ...fetchOptions } = options;
|
||||
|
||||
const baseHeaders = authOnly ? getAuthHeaders() : getJsonHeaders();
|
||||
const headers = { ...baseHeaders, ...customHeaders };
|
||||
|
||||
const url = path.startsWith('http') ? path : `${base}${path}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
...fetchOptions,
|
||||
headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorMessage = await parseErrorMessage(response);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch with URL constructed from base URL and query parameters.
|
||||
*
|
||||
* @param basePath - Base API path
|
||||
* @param params - Query parameters to append
|
||||
* @param options - Fetch options
|
||||
* @returns Parsed JSON response
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const props = await apiFetchWithParams<ApiProps>('./props', {
|
||||
* model: 'gpt-4',
|
||||
* autoload: 'false'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function apiFetchWithParams<T>(
|
||||
basePath: string,
|
||||
params: Record<string, string>,
|
||||
options: ApiFetchOptions = {}
|
||||
): Promise<T> {
|
||||
const url = new URL(basePath, window.location.href);
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
url.searchParams.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
const { authOnly = false, headers: customHeaders, ...fetchOptions } = options;
|
||||
|
||||
const baseHeaders = authOnly ? getAuthHeaders() : getJsonHeaders();
|
||||
const headers = { ...baseHeaders, ...customHeaders };
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
...fetchOptions,
|
||||
headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorMessage = await parseErrorMessage(response);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST JSON data to an API endpoint.
|
||||
*
|
||||
* @param path - API path
|
||||
* @param body - Request body (will be JSON stringified)
|
||||
* @param options - Additional fetch options
|
||||
* @returns Parsed JSON response
|
||||
*/
|
||||
export async function apiPost<T, B = unknown>(
|
||||
path: string,
|
||||
body: B,
|
||||
options: ApiFetchOptions = {}
|
||||
): Promise<T> {
|
||||
return apiFetch<T>(path, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse error message from a failed response.
|
||||
* Tries to extract error message from JSON body, falls back to status text.
|
||||
*/
|
||||
async function parseErrorMessage(response: Response): Promise<string> {
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
if (errorData?.error?.message) {
|
||||
return errorData.error.message;
|
||||
}
|
||||
if (errorData?.error && typeof errorData.error === 'string') {
|
||||
return errorData.error;
|
||||
}
|
||||
if (errorData?.message) {
|
||||
return errorData.message;
|
||||
}
|
||||
} catch {
|
||||
// JSON parsing failed, use status text
|
||||
}
|
||||
|
||||
return `Request failed: ${response.status} ${response.statusText}`;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
// API utilities
|
||||
export { getAuthHeaders, getJsonHeaders } from './api-headers';
|
||||
export { apiFetch, apiFetchWithParams, apiPost, type ApiFetchOptions } from './api-fetch';
|
||||
export { validateApiKey } from './api-key-validation';
|
||||
|
||||
// Attachment utilities
|
||||
|
|
|
|||
Loading…
Reference in New Issue