| |
| |
| |
| |
|
|
| export class APIHelper { |
| |
| |
| |
| |
| static getHeaders() { |
| const token = localStorage.getItem('HF_TOKEN'); |
| const headers = { |
| 'Content-Type': 'application/json' |
| }; |
| |
| if (token && token.trim()) { |
| |
| if (this.isTokenExpired(token)) { |
| console.warn('[APIHelper] Token expired, removing from storage'); |
| localStorage.removeItem('HF_TOKEN'); |
| } else { |
| headers['Authorization'] = `Bearer ${token}`; |
| } |
| } |
| |
| return headers; |
| } |
|
|
| |
| |
| |
| |
| |
| static isTokenExpired(token) { |
| try { |
| |
| const parts = token.split('.'); |
| if (parts.length !== 3) return false; |
| |
| const payload = JSON.parse(atob(parts[1])); |
| if (!payload.exp) return false; |
| |
| const now = Math.floor(Date.now() / 1000); |
| return payload.exp < now; |
| } catch (e) { |
| console.warn('[APIHelper] Token validation error:', e); |
| return false; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static async fetchAPI(url, options = {}) { |
| const headers = this.getHeaders(); |
| |
| try { |
| const response = await fetch(url, { |
| ...options, |
| headers: { |
| ...headers, |
| ...options.headers |
| } |
| }); |
|
|
| if (!response.ok) { |
| throw new Error(`HTTP ${response.status}: ${response.statusText}`); |
| } |
|
|
| const contentType = response.headers.get('content-type'); |
| if (contentType && contentType.includes('application/json')) { |
| return await response.json(); |
| } |
| |
| return await response.text(); |
| } catch (error) { |
| console.error(`[APIHelper] Fetch error for ${url}:`, error); |
| |
| |
| return this._getFallbackData(url, error); |
| } |
| } |
|
|
| |
| |
| |
| |
| static _getFallbackData(url, error) { |
| |
| if (url.includes('/resources/summary') || url.includes('/resources')) { |
| return { |
| success: false, |
| error: error.message, |
| summary: { |
| total_resources: 0, |
| free_resources: 0, |
| models_available: 0, |
| total_api_keys: 0, |
| categories: {} |
| }, |
| fallback: true |
| }; |
| } |
| |
| if (url.includes('/models/status')) { |
| return { |
| success: false, |
| error: error.message, |
| status: 'error', |
| status_message: `Error: ${error.message}`, |
| models_loaded: 0, |
| models_failed: 0, |
| hf_mode: 'unknown', |
| transformers_available: false, |
| fallback: true, |
| timestamp: new Date().toISOString() |
| }; |
| } |
| |
| if (url.includes('/models/summary') || url.includes('/models')) { |
| return { |
| ok: false, |
| error: error.message, |
| summary: { |
| total_models: 0, |
| loaded_models: 0, |
| failed_models: 0, |
| hf_mode: 'error', |
| transformers_available: false |
| }, |
| categories: {}, |
| health_registry: [], |
| fallback: true, |
| timestamp: new Date().toISOString() |
| }; |
| } |
| |
| if (url.includes('/health') || url.includes('/status')) { |
| return { |
| status: 'offline', |
| healthy: false, |
| error: error.message, |
| fallback: true |
| }; |
| } |
| |
| |
| return { |
| error: error.message, |
| fallback: true, |
| data: null |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static extractArray(data, keys = ['data', 'items', 'results', 'list']) { |
| |
| if (Array.isArray(data)) { |
| return data; |
| } |
|
|
| |
| for (const key of keys) { |
| if (data && Array.isArray(data[key])) { |
| return data[key]; |
| } |
| } |
|
|
| |
| if (data && typeof data === 'object' && !Array.isArray(data)) { |
| const values = Object.values(data); |
| if (values.length > 0 && values.every(v => typeof v === 'object')) { |
| return values; |
| } |
| } |
|
|
| console.warn('[APIHelper] Could not extract array from:', data); |
| return []; |
| } |
|
|
| |
| |
| |
| |
| static async checkHealth() { |
| try { |
| const controller = new AbortController(); |
| const timeoutId = setTimeout(() => controller.abort(), 5000); |
| |
| const response = await fetch('/api/health', { |
| signal: controller.signal, |
| cache: 'no-cache' |
| }); |
| |
| clearTimeout(timeoutId); |
| |
| if (response.ok) { |
| const data = await response.json(); |
| return { |
| status: 'online', |
| healthy: true, |
| data: data |
| }; |
| } else { |
| return { |
| status: 'degraded', |
| healthy: false, |
| httpStatus: response.status |
| }; |
| } |
| } catch (error) { |
| return { |
| status: 'offline', |
| healthy: false, |
| error: error.message |
| }; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static monitorHealth(callback, interval = 30000) { |
| |
| this.checkHealth().then(callback); |
| |
| |
| return setInterval(async () => { |
| if (!document.hidden) { |
| const health = await this.checkHealth(); |
| callback(health); |
| } |
| }, interval); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static showToast(message, type = 'info', duration = 3000) { |
| const colors = { |
| success: '#22c55e', |
| error: '#ef4444', |
| warning: '#f59e0b', |
| info: '#3b82f6' |
| }; |
| |
| const toast = document.createElement('div'); |
| toast.style.cssText = ` |
| position: fixed; |
| top: 20px; |
| right: 20px; |
| padding: 12px 20px; |
| border-radius: 8px; |
| background: ${colors[type] || colors.info}; |
| color: white; |
| font-weight: 500; |
| z-index: 9999; |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); |
| animation: slideIn 0.3s ease; |
| `; |
| toast.textContent = message; |
| |
| document.body.appendChild(toast); |
| setTimeout(() => { |
| toast.style.animation = 'slideOut 0.3s ease'; |
| setTimeout(() => toast.remove(), 300); |
| }, duration); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static formatNumber(num, options = {}) { |
| return new Intl.NumberFormat('en-US', options).format(num); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static formatCurrency(amount, currency = 'USD') { |
| return this.formatNumber(amount, { |
| style: 'currency', |
| currency: currency, |
| minimumFractionDigits: 2, |
| maximumFractionDigits: 2 |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static formatPercentage(value, decimals = 2) { |
| return `${value >= 0 ? '+' : ''}${value.toFixed(decimals)}%`; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static debounce(func, wait = 300) { |
| let timeout; |
| return function executedFunction(...args) { |
| const later = () => { |
| clearTimeout(timeout); |
| func(...args); |
| }; |
| clearTimeout(timeout); |
| timeout = setTimeout(later, wait); |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static throttle(func, limit = 300) { |
| let inThrottle; |
| return function executedFunction(...args) { |
| if (!inThrottle) { |
| func(...args); |
| inThrottle = true; |
| setTimeout(() => (inThrottle = false), limit); |
| } |
| }; |
| } |
| } |
|
|
| export default APIHelper; |
|
|
|
|