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