Really-amin's picture
Upload 325 files
b66240d verified
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;