Spaces:
Sleeping
Sleeping
| const DEFAULT_TTL = 60 * 1000; // 1 minute cache | |
| class ApiClient { | |
| constructor() { | |
| // Use current origin by default to avoid hardcoded URLs | |
| this.baseURL = window.location.origin; | |
| // Allow override via window.BACKEND_URL if needed | |
| if (typeof window.BACKEND_URL === 'string' && window.BACKEND_URL.trim()) { | |
| this.baseURL = window.BACKEND_URL.trim().replace(/\/$/, ''); | |
| } | |
| console.log('[ApiClient] Using Backend:', this.baseURL); | |
| this.cache = new Map(); | |
| this.requestLogs = []; | |
| this.errorLogs = []; | |
| this.logSubscribers = new Set(); | |
| this.errorSubscribers = new Set(); | |
| } | |
| buildUrl(endpoint) { | |
| if (!endpoint.startsWith('/')) { | |
| return `${this.baseURL}/${endpoint}`; | |
| } | |
| return `${this.baseURL}${endpoint}`; | |
| } | |
| notifyLog(entry) { | |
| this.requestLogs.push(entry); | |
| this.requestLogs = this.requestLogs.slice(-100); | |
| this.logSubscribers.forEach((cb) => cb(entry)); | |
| } | |
| notifyError(entry) { | |
| this.errorLogs.push(entry); | |
| this.errorLogs = this.errorLogs.slice(-100); | |
| this.errorSubscribers.forEach((cb) => cb(entry)); | |
| } | |
| onLog(callback) { | |
| this.logSubscribers.add(callback); | |
| return () => this.logSubscribers.delete(callback); | |
| } | |
| onError(callback) { | |
| this.errorSubscribers.add(callback); | |
| return () => this.errorSubscribers.delete(callback); | |
| } | |
| getLogs() { | |
| return [...this.requestLogs]; | |
| } | |
| getErrors() { | |
| return [...this.errorLogs]; | |
| } | |
| async request(method, endpoint, { body, cache = true, ttl = DEFAULT_TTL } = {}) { | |
| const url = this.buildUrl(endpoint); | |
| const cacheKey = `${method}:${url}`; | |
| if (method === 'GET' && cache && this.cache.has(cacheKey)) { | |
| const cached = this.cache.get(cacheKey); | |
| if (Date.now() - cached.timestamp < ttl) { | |
| return { ok: true, data: cached.data, cached: true }; | |
| } | |
| } | |
| const started = performance.now(); | |
| const randomId = (window.crypto && window.crypto.randomUUID && window.crypto.randomUUID()) | |
| || `${Date.now()}-${Math.random()}`; | |
| const entry = { | |
| id: randomId, | |
| method, | |
| endpoint, | |
| status: 'pending', | |
| duration: 0, | |
| time: new Date().toISOString(), | |
| }; | |
| try { | |
| const response = await fetch(url, { | |
| method, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: body ? JSON.stringify(body) : undefined, | |
| }); | |
| const duration = performance.now() - started; | |
| entry.duration = Math.round(duration); | |
| entry.status = response.status; | |
| const contentType = response.headers.get('content-type') || ''; | |
| let data = null; | |
| if (contentType.includes('application/json')) { | |
| data = await response.json(); | |
| } else if (contentType.includes('text')) { | |
| data = await response.text(); | |
| } | |
| if (!response.ok) { | |
| const error = new Error((data && data.message) || response.statusText || 'Unknown error'); | |
| error.status = response.status; | |
| throw error; | |
| } | |
| if (method === 'GET' && cache) { | |
| this.cache.set(cacheKey, { timestamp: Date.now(), data }); | |
| } | |
| this.notifyLog({ ...entry, success: true }); | |
| return { ok: true, data }; | |
| } catch (error) { | |
| const duration = performance.now() - started; | |
| entry.duration = Math.round(duration); | |
| entry.status = error.status || 'error'; | |
| this.notifyLog({ ...entry, success: false, error: error.message }); | |
| this.notifyError({ | |
| message: error.message, | |
| endpoint, | |
| method, | |
| time: new Date().toISOString(), | |
| }); | |
| return { ok: false, error: error.message }; | |
| } | |
| } | |
| get(endpoint, options) { | |
| return this.request('GET', endpoint, options); | |
| } | |
| post(endpoint, body, options = {}) { | |
| return this.request('POST', endpoint, { ...options, body }); | |
| } | |
| // ===== Specific API helpers ===== | |
| // Note: Backend uses api_server_extended.py which has different endpoints | |
| getHealth() { | |
| // Backend doesn't have /api/health, use /api/status instead | |
| return this.get('/api/status'); | |
| } | |
| getTopCoins(limit = 10) { | |
| // Backend uses /api/market which returns cryptocurrencies array | |
| return this.get('/api/market').then(result => { | |
| if (result.ok && result.data && result.data.cryptocurrencies) { | |
| return { | |
| ok: true, | |
| data: result.data.cryptocurrencies.slice(0, limit) | |
| }; | |
| } | |
| return result; | |
| }); | |
| } | |
| getCoinDetails(symbol) { | |
| // Get from market data and filter by symbol | |
| return this.get('/api/market').then(result => { | |
| if (result.ok && result.data && result.data.cryptocurrencies) { | |
| const coin = result.data.cryptocurrencies.find( | |
| c => c.symbol.toUpperCase() === symbol.toUpperCase() | |
| ); | |
| return coin ? { ok: true, data: coin } : { ok: false, error: 'Coin not found' }; | |
| } | |
| return result; | |
| }); | |
| } | |
| getMarketStats() { | |
| // Backend returns stats in /api/market response | |
| return this.get('/api/market').then(result => { | |
| if (result.ok && result.data) { | |
| return { | |
| ok: true, | |
| data: { | |
| total_market_cap: result.data.total_market_cap, | |
| btc_dominance: result.data.btc_dominance, | |
| total_volume_24h: result.data.total_volume_24h, | |
| market_cap_change_24h: result.data.market_cap_change_24h | |
| } | |
| }; | |
| } | |
| return result; | |
| }); | |
| } | |
| getLatestNews(limit = 20) { | |
| // Backend doesn't have news endpoint yet, return empty for now | |
| return Promise.resolve({ | |
| ok: true, | |
| data: { | |
| articles: [], | |
| message: 'News endpoint not yet implemented in backend' | |
| } | |
| }); | |
| } | |
| getProviders() { | |
| return this.get('/api/providers'); | |
| } | |
| getPriceChart(symbol, timeframe = '7d') { | |
| // Backend uses /api/ohlcv | |
| const cleanSymbol = encodeURIComponent(String(symbol || 'BTC').trim().toUpperCase()); | |
| // Map timeframe to interval and limit | |
| const intervalMap = { '1d': '1h', '7d': '1h', '30d': '4h', '90d': '1d', '365d': '1d' }; | |
| const limitMap = { '1d': 24, '7d': 168, '30d': 180, '90d': 90, '365d': 365 }; | |
| const interval = intervalMap[timeframe] || '1h'; | |
| const limit = limitMap[timeframe] || 168; | |
| return this.get(`/api/ohlcv?symbol=${cleanSymbol}USDT&interval=${interval}&limit=${limit}`); | |
| } | |
| analyzeChart(symbol, timeframe = '7d', indicators = []) { | |
| // Not implemented in backend yet | |
| return Promise.resolve({ | |
| ok: false, | |
| error: 'Chart analysis not yet implemented in backend' | |
| }); | |
| } | |
| runQuery(payload) { | |
| // Not implemented in backend yet | |
| return Promise.resolve({ | |
| ok: false, | |
| error: 'Query endpoint not yet implemented in backend' | |
| }); | |
| } | |
| analyzeSentiment(payload) { | |
| // Backend has /api/sentiment but it returns market sentiment, not text analysis | |
| // For now, return the market sentiment | |
| return this.get('/api/sentiment'); | |
| } | |
| summarizeNews(item) { | |
| // Not implemented in backend yet | |
| return Promise.resolve({ | |
| ok: false, | |
| error: 'News summarization not yet implemented in backend' | |
| }); | |
| } | |
| getDatasetsList() { | |
| // Not implemented in backend yet | |
| return Promise.resolve({ | |
| ok: true, | |
| data: { | |
| datasets: [], | |
| message: 'Datasets endpoint not yet implemented in backend' | |
| } | |
| }); | |
| } | |
| getDatasetSample(name) { | |
| // Not implemented in backend yet | |
| return Promise.resolve({ | |
| ok: false, | |
| error: 'Dataset sample not yet implemented in backend' | |
| }); | |
| } | |
| getModelsList() { | |
| // Backend has /api/hf/models | |
| return this.get('/api/hf/models'); | |
| } | |
| testModel(payload) { | |
| // Not implemented in backend yet | |
| return Promise.resolve({ | |
| ok: false, | |
| error: 'Model testing not yet implemented in backend' | |
| }); | |
| } | |
| // ===== Additional methods for backend compatibility ===== | |
| getTrending() { | |
| return this.get('/api/trending'); | |
| } | |
| getStats() { | |
| return this.get('/api/stats'); | |
| } | |
| getHFHealth() { | |
| return this.get('/api/hf/health'); | |
| } | |
| runDiagnostics(autoFix = false) { | |
| return this.post('/api/diagnostics/run', { auto_fix: autoFix }); | |
| } | |
| getLastDiagnostics() { | |
| return this.get('/api/diagnostics/last'); | |
| } | |
| runAPLScan() { | |
| return this.post('/api/apl/run'); | |
| } | |
| getAPLReport() { | |
| return this.get('/api/apl/report'); | |
| } | |
| getAPLSummary() { | |
| return this.get('/api/apl/summary'); | |
| } | |
| } | |
| const apiClient = new ApiClient(); | |
| export default apiClient; |