Spaces:
Running
Running
| ```typescript | |
| import axios, { AxiosResponse, AxiosError } from 'axios'; | |
| import { GenerateRequest, GenerateResponse, ApiError, ApiStatus, ModelInfo, ApiProvider } from '../types'; | |
| const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000'; | |
| const API_TIMEOUT = 30000; // 30 seconds | |
| class ApiServiceClass { | |
| private axiosInstance = axios.create({ | |
| baseURL: API_BASE_URL, | |
| timeout: API_TIMEOUT, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| }); | |
| constructor() { | |
| // Request interceptor | |
| this.axiosInstance.interceptors.request.use( | |
| (config) => { | |
| console.log(`API Request: ${config.method?.toUpperCase()} ${config.url}`); | |
| return config; | |
| }, | |
| (error) => { | |
| console.error('Request Error:', error); | |
| return Promise.reject(error); | |
| } | |
| ); | |
| // Response interceptor | |
| this.axiosInstance.interceptors.response.use( | |
| (response) => { | |
| console.log(`API Response: ${response.status} ${response.config.url}`); | |
| return response; | |
| }, | |
| (error: AxiosError) => { | |
| console.error('Response Error:', error.response?.status, error.response?.data); | |
| // Transform axios errors to our custom error type | |
| const apiError = new ApiError( | |
| this.getErrorMessage(error), | |
| error.code, | |
| error.response?.status, | |
| error.response?.data | |
| ); | |
| return Promise.reject(apiError); | |
| } | |
| ); | |
| } | |
| private getErrorMessage(error: AxiosError): string { | |
| if (error.response?.data) { | |
| const data = error.response.data as any; | |
| if (data.error) { | |
| return typeof data.error === 'string' ? data.error : data.error.message; | |
| } | |
| if (data.message) { | |
| return data.message; | |
| } | |
| } | |
| if (error.request) { | |
| return 'Network error: Unable to connect to server'; | |
| } | |
| return error.message || 'An unknown error occurred'; | |
| } | |
| // Health check | |
| async healthCheck(): Promise<ApiStatus> { | |
| try { | |
| const response = await this.axiosInstance.get('/api/health'); | |
| const data = response.data; | |
| return { | |
| status: 'online', | |
| version: data.version, | |
| latency: response.headers['x-response-time'] ? | |
| parseInt(response.headers['x-response-time'] as string) : undefined, | |
| uptime: data.uptime | |
| }; | |
| } catch (error) { | |
| throw new ApiError('Health check failed', 'HEALTH_CHECK_FAILED'); | |
| } | |
| } | |
| // Generate website code | |
| async generate(request: GenerateRequest): Promise<GenerateResponse> { | |
| try { | |
| const response = await this.axiosInstance.post<GenerateResponse>( | |
| '/api/generate', | |
| { | |
| prompt: request.prompt, | |
| provider: request.provider, | |
| model: request.model, | |
| temperature: request.temperature || 0.7, | |
| max_tokens: request.maxTokens || 16000 | |
| }, | |
| { | |
| headers: request.apiKey ? { | |
| 'Authorization': `Bearer ${request.apiKey}` | |
| } : undefined | |
| } | |
| ); | |
| return response.data; | |
| } catch (error) { | |
| if (error instanceof ApiError) { | |
| throw error; | |
| } | |
| throw new ApiError('Generation failed', 'GENERATION_FAILED'); | |
| } | |
| } | |
| // Validate API key | |
| async validateKey(provider: ApiProvider, apiKey: string): Promise<{ valid: boolean; error?: string }> { | |
| try { | |
| const response = await this.axiosInstance.post('/api/validate-key', { | |
| provider, | |
| apiKey | |
| }); | |
| return { | |
| valid: response.data.valid, | |
| error: response.data.error | |
| }; | |
| } catch (error) { | |
| return { | |
| valid: false, | |
| error: error instanceof ApiError ? error.message : 'Validation failed' | |
| }; | |
| } | |
| } | |
| // Get available models for a provider | |
| async getModels(provider: ApiProvider): Promise<ModelInfo[]> { | |
| try { | |
| const response = await this.axiosInstance.get(`/api/models/${provider}`); | |
| const models = response.data.models || []; | |
| return models.map((model: string | any) => { | |
| if (typeof model === 'string') { | |
| return { | |
| id: model, | |
| name: model, | |
| contextLength: 4096 | |
| }; | |
| } | |
| return { | |
| id: model.id || model.name || model, | |
| name: model.name || model.id || model, | |
| description: model.description, | |
| contextLength: model.context_length || model.contextLength, | |
| pricing: model.pricing | |
| }; | |
| }); | |
| } catch (error) { | |
| console.error('Failed to fetch models:', error); | |
| return []; | |
| } | |
| } | |
| // Test API connectivity | |
| async testConnection(provider: ApiProvider, config: { apiKey?: string; baseUrl?: string }): Promise<boolean> { | |
| try { | |
| // For now, just try to validate the key | |
| if (config.apiKey) { | |
| const validation = await this.validateKey(provider, config.apiKey); | |
| return validation.valid; | |
| } | |
| return false; | |
| } catch (error) { | |
| console.error('Connection test failed:', error); | |
| return false; | |
| } | |
| } | |
| // Rate limiting helpers | |
| private lastRequestTime = 0; | |
| private readonly MIN_REQUEST_INTERVAL = 1000; // 1 second | |
| private async enforceRateLimit(): Promise<void> { | |
| const now = Date.now(); | |
| const timeSinceLastRequest = now - this.lastRequestTime; | |
| if (timeSinceLastRequest < this.MIN_REQUEST_INTERVAL) { | |
| const waitTime = this.MIN_REQUEST_INTERVAL - timeSinceLastRequest; | |
| await new Promise(resolve => setTimeout(resolve, waitTime)); | |
| } | |
| this.lastRequestTime = Date.now(); | |
| } | |
| // Wrapped generation with rate limiting | |
| async generateWithRateLimit(request: GenerateRequest): Promise<GenerateResponse> { | |
| await this.enforceRateLimit(); | |
| return this.generate(request); | |
| } | |
| // Error recovery | |
| private retryCount = new Map<string, number>(); | |
| private readonly MAX_RETRIES = 3; | |
| async generateWithRetry(request: GenerateRequest): Promise<GenerateResponse> { | |
| const requestKey = `${request.provider}-${request.model}-${Date.now()}`; | |
| const retries = this.retryCount.get(requestKey) || 0; | |
| try { | |
| this.retryCount.set(requestKey, retries + 1); | |
| return await this.generate(request); | |
| } catch (error) { | |
| // Retry on network errors or 5xx status codes | |
| if (retries < this.MAX_RETRIES && | |
| (error instanceof ApiError && | |
| (error.status === undefined || error.status >= 500 || error.status === 0))) { | |
| console.log(`Retrying request (attempt ${retries + 1}/${this.MAX_RETRIES})`); | |
| await new Promise(resolve => setTimeout(resolve, Math.pow(2, retries) * 1000)); // Exponential backoff | |
| return this.generateWithRetry(request); | |
| } | |
| throw error; | |
| } finally { | |
| this.retryCount.delete(requestKey); | |
| } | |
| } | |
| } | |
| export const ApiService = new ApiServiceClass(); | |
| export default ApiService; | |
| ``` |