File size: 4,252 Bytes
31dd200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import { base } from '$app/paths';
import { getJsonHeaders, getAuthHeaders } from './api-headers';
import { UrlPrefix } from '$lib/enums';

/**

 * 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(UrlPrefix.HTTP) || path.startsWith(UrlPrefix.HTTPS) ? 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}`;
}