| |
| |
| |
|
|
|
|
| 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;
|
|
|
|
|