char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
521a64cd0
char/aliceai-t5-moe
1import { getAuthHeaders , getJsonHeaders } from './api-headers' ; 2import { base } from '$app/paths' ; 3import { API_ABSOLUTE_URL_PROTOCOLS , ERROR_MESSAGES , HTTP_CODE_TO_STRING } from '$lib/constants' ; 4 5/** 6* API Fetch Utilities 7* 8* Provides common fetch patterns used across services: 9* - Automatic JSON headers 10* - Error handling with proper error messages 11* - Base path resolution 12*/ 13 14/** 15* Error thrown when an API request fails, carrying the HTTP status code 16* so callers can distinguish e.g. a 503 "still loading" response from a 17* genuine failure. 18*/ 19export class ApiError extends Error { 20status :number ; 21 22constructor ( message :string , status :number ) { 23super ( message ); 24this . name = 'ApiError' ; 25this . status = status ; 26} 27} 28 29export interface ApiFetchOptions extends Omit < RequestInit , 'headers' > { 30/** 31* Use auth-only headers (no Content-Type). 32* Default: false (uses JSON headers with Content-Type: application/json) 33*/ 34authOnly ?:boolean ; 35/** 36* Additional headers to merge with default headers. 37*/ 38headers ?:Record < string , string >; 39} 40 41/** 42* Fetch JSON data from an API endpoint with standard headers and error handling. 43* 44* @param path - API path (will be prefixed with base path) 45* @param options - Fetch options with additional authOnly flag 46* @returns Parsed JSON response 47* @throws Error with formatted message on failure 48* 49* @example 50* ```typescript 51* // GET request 52* const models = await apiFetch<ApiModelListResponse>('/v1/models'); 53* 54* // POST request 55* const result = await apiFetch<ApiResponse>('/models/load', { 56* method: 'POST', 57* body: JSON.stringify({ model: 'gpt-4' }) 58* }); 59* ``` 60*/ 61export async function apiFetch < T > ( path :string , options :ApiFetchOptions = {}) :Promise < T > { 62const { authOnly= false , headers :customHeaders , ...fetchOptions } = options ; 63const baseHeaders = authOnly ?getAuthHeaders () :getJsonHeaders (); 64const headers = { ...baseHeaders , ...customHeaders }; 65// absolute URLs with an allowed protocol pass through untouched; relative paths get the base prefix 66const url = API_ABSOLUTE_URL_PROTOCOLS . some (( p ) => path . startsWith ( p )) ?path :` ${ base } ${ path } ` ; 67 68let response ; 69 70try { 71response = await fetch ( url , { 72 ...fetchOptions , 73 headers 74}); 75} catch ( e ) { 76throw new Error ( beautifyNetworkError ( e )); 77} 78 79if ( ! response . ok ) { 80const errorMessage = await parseErrorMessage ( response ); 81 82throw new ApiError ( errorMessage , response . status ); 83} 84 85return response . json () as Promise < T >; 86} 87 88/** 89* Fetch with URL constructed from base URL and query parameters. 90* 91* @param basePath - Base API path 92* @param params - Query parameters to append 93* @param options - Fetch options 94* @returns Parsed JSON response 95* 96* @example 97* ```typescript 98* const props = await apiFetchWithParams<ApiProps>('./props', { 99* model: 'gpt-4', 100* autoload: 'false' 101* }); 102* ``` 103*/ 104export async function apiFetchWithParams < T > ( 105basePath :string , 106params :Record < string , string >, 107options :ApiFetchOptions = {} 108) :Promise < T > { 109const url = new URL ( basePath , window . location . href ); 110 111for ( const [ key , value ] of Object . entries ( params )) { 112if ( value !== undefined && value !== null ) { 113url . searchParams . set ( key , value ); 114} 115} 116 117return apiFetch < T >( url . toString (), options ); 118} 119 120/** 121* POST JSON data to an API endpoint. 122* 123* @param path - API path 124* @param body - Request body (will be JSON stringified) 125* @param options - Additional fetch options 126* @returns Parsed JSON response 127*/ 128export async function apiPost < T , B = unknown > ( 129path :string , 130body :B , 131options :ApiFetchOptions = {} 132) :Promise < T > { 133return apiFetch < T >( path , { 134body :JSON . stringify ( body ), 135method :'POST' , 136 ...options 137}); 138} 139 140/** 141* Parse error message from a failed response. 142* Tries to extract error message from JSON body, falls back to status text. 143*/ 144async function parseErrorMessage ( response :Response ) :Promise < string > { 145try { 146const errorData = await response . json (); 147 148if ( errorData ?. error ?. message ) { 149return errorData . error . message ; 150} 151 152if ( errorData ?. error && typeof errorData . error === 'string' ) { 153return errorData . error ; 154} 155 156if ( errorData ?. message ) { 157return errorData . message ; 158} 159} catch { 160// JSON parsing failed, use status text 161} 162 163const httpErrorStr = HTTP_CODE_TO_STRING [ response . status ]; 164 165if ( httpErrorStr ) { 166return httpErrorStr ; 167} 168 169return ` ${ ERROR_MESSAGES . HTTP . GENERIC } : ${ response . status } ${ response . statusText } ` ; 170} 171 172/** 173* Converts a network issue into a human-readable message. 174* @param throwable - The throwable raised during fetch operation 175* @returns Error in an human-readable format 176*/ 177function beautifyNetworkError ( throwable :unknown ) :string { 178let message ; 179 180if ( throwable instanceof Error ) { 181message = throwable . message ; 182 183if ( throwable . name === 'TypeError' && message . includes ( 'fetch' )) { 184return ERROR_MESSAGES . NETWORK . UNREACHABLE ; 185} 186} else { 187message = String ( throwable ); 188} 189 190if ( message . includes ( 'ECONNREFUSED' )) { 191return ERROR_MESSAGES . NETWORK . REFUSED ; 192} else if ( message . includes ( 'ENOTFOUND' )) { 193return ERROR_MESSAGES . NETWORK . NXDOMAIN ; 194} else if ( message . includes ( 'ETIMEDOUT' )) { 195return ERROR_MESSAGES . NETWORK . TIMEOUT ; 196} 197 198return ` ${ ERROR_MESSAGES . NETWORK . GENERIC } ( ${ message } )` ; 199}