| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | import { CONFIG, API_ENDPOINTS, buildApiUrl, getCacheKey } from './config.js';
|
| |
|
| | |
| | |
| |
|
| | class APIClient {
|
| | constructor(baseURL = CONFIG.API_BASE_URL) {
|
| | this.baseURL = baseURL;
|
| | this.cache = new Map();
|
| | this.cacheTTL = CONFIG.CACHE_TTL;
|
| | this.maxRetries = CONFIG.MAX_RETRIES;
|
| | this.retryDelay = CONFIG.RETRY_DELAY;
|
| | this.requestLog = [];
|
| | this.errorLog = [];
|
| | this.maxLogSize = 100;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async request(endpoint, options = {}) {
|
| | const url = `${this.baseURL}${endpoint}`;
|
| | const method = options.method || 'GET';
|
| | const startTime = performance.now();
|
| |
|
| |
|
| | if (method === 'GET' && !options.skipCache) {
|
| |
|
| | const shouldSkipCache = endpoint.includes('/models/status') ||
|
| | endpoint.includes('/models/summary') ||
|
| | options.forceRefresh;
|
| |
|
| | if (!shouldSkipCache) {
|
| | const cached = this._getFromCache(endpoint);
|
| | if (cached) {
|
| | console.log(`[APIClient] Cache hit: ${endpoint}`);
|
| | return cached;
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | let lastError;
|
| | for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
| | try {
|
| | const response = await fetch(url, {
|
| | method,
|
| | headers: {
|
| | 'Content-Type': 'application/json',
|
| | ...options.headers,
|
| | },
|
| | body: options.body ? JSON.stringify(options.body) : undefined,
|
| | signal: options.signal,
|
| | });
|
| |
|
| | if (!response.ok) {
|
| | throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
| | }
|
| |
|
| | const data = await response.json();
|
| | const duration = performance.now() - startTime;
|
| |
|
| |
|
| | if (method === 'GET' && !endpoint.includes('/models/status') && !endpoint.includes('/models/summary')) {
|
| | this._saveToCache(endpoint, data);
|
| | }
|
| |
|
| |
|
| | this._logRequest({
|
| | method,
|
| | endpoint,
|
| | status: response.status,
|
| | duration: Math.round(duration),
|
| | timestamp: Date.now(),
|
| | });
|
| |
|
| | return data;
|
| |
|
| | } catch (error) {
|
| | lastError = error;
|
| | const errorDetails = {
|
| | attempt,
|
| | maxRetries: this.maxRetries,
|
| | endpoint,
|
| | message: error.message,
|
| | name: error.name,
|
| | stack: error.stack
|
| | };
|
| |
|
| | console.warn(`[APIClient] Attempt ${attempt}/${this.maxRetries} failed for ${endpoint}:`, error.message);
|
| |
|
| |
|
| | if (attempt === this.maxRetries) {
|
| | console.error('[APIClient] All retries exhausted. Error details:', errorDetails);
|
| | }
|
| |
|
| | if (attempt < this.maxRetries) {
|
| | await this._sleep(this.retryDelay);
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | const duration = performance.now() - startTime;
|
| | this._logError({
|
| | method,
|
| | endpoint,
|
| | message: lastError?.message || lastError?.toString() || 'Unknown error',
|
| | duration: Math.round(duration),
|
| | timestamp: Date.now(),
|
| | });
|
| |
|
| |
|
| | return this._getFallbackData(endpoint, lastError);
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async get(endpoint, options = {}) {
|
| | return this.request(endpoint, { ...options, method: 'GET' });
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async post(endpoint, data, options = {}) {
|
| | return this.request(endpoint, {
|
| | ...options,
|
| | method: 'POST',
|
| | body: data,
|
| | });
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async put(endpoint, data, options = {}) {
|
| | return this.request(endpoint, {
|
| | ...options,
|
| | method: 'PUT',
|
| | body: data,
|
| | });
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async delete(endpoint, options = {}) {
|
| | return this.request(endpoint, { ...options, method: 'DELETE' });
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | |
| | |
| |
|
| | _getFromCache(key) {
|
| | const cacheKey = getCacheKey(key);
|
| | const cached = this.cache.get(cacheKey);
|
| |
|
| | if (!cached) return null;
|
| |
|
| | const now = Date.now();
|
| | if (now - cached.timestamp > this.cacheTTL) {
|
| | this.cache.delete(cacheKey);
|
| | return null;
|
| | }
|
| |
|
| | return cached.data;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | _saveToCache(key, data) {
|
| | const cacheKey = getCacheKey(key);
|
| | this.cache.set(cacheKey, {
|
| | data,
|
| | timestamp: Date.now(),
|
| | });
|
| | }
|
| |
|
| | |
| | |
| |
|
| | clearCache() {
|
| | this.cache.clear();
|
| | console.log('[APIClient] Cache cleared');
|
| | }
|
| |
|
| | |
| | |
| |
|
| | clearCacheEntry(key) {
|
| | const cacheKey = getCacheKey(key);
|
| | this.cache.delete(cacheKey);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | |
| | |
| |
|
| | _logRequest(entry) {
|
| | this.requestLog.unshift(entry);
|
| | if (this.requestLog.length > this.maxLogSize) {
|
| | this.requestLog.pop();
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | _logError(entry) {
|
| |
|
| | if (!entry.timestamp) {
|
| | entry.timestamp = Date.now();
|
| | }
|
| |
|
| |
|
| | entry.time = new Date(entry.timestamp).toISOString();
|
| |
|
| | this.errorLog.unshift(entry);
|
| | if (this.errorLog.length > this.maxLogSize) {
|
| | this.errorLog.pop();
|
| | }
|
| |
|
| |
|
| | console.error('[APIClient] Error logged:', {
|
| | endpoint: entry.endpoint,
|
| | method: entry.method,
|
| | message: entry.message,
|
| | duration: entry.duration
|
| | });
|
| | }
|
| |
|
| | |
| | |
| |
|
| | getRequestLogs(limit = 20) {
|
| | return this.requestLog.slice(0, limit);
|
| | }
|
| |
|
| | |
| | |
| |
|
| | getErrorLogs(limit = 20) {
|
| | return this.errorLog.slice(0, limit);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | |
| | |
| |
|
| | _sleep(ms) {
|
| | return new Promise(resolve => setTimeout(resolve, ms));
|
| | }
|
| |
|
| | |
| | |
| | |
| |
|
| | _getFallbackData(endpoint, error) {
|
| |
|
| | if (endpoint.includes('/resources/summary')) {
|
| | return {
|
| | success: false,
|
| | error: error.message,
|
| | summary: {
|
| | total_resources: 0,
|
| | free_resources: 0,
|
| | models_available: 0,
|
| | local_routes_count: 0,
|
| | total_api_keys: 0,
|
| | categories: {}
|
| | },
|
| | fallback: true,
|
| | timestamp: new Date().toISOString()
|
| | };
|
| | }
|
| |
|
| | if (endpoint.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 (endpoint.includes('/models/summary')) {
|
| | 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 (endpoint.includes('/health') || endpoint.includes('/status')) {
|
| | return {
|
| | status: 'offline',
|
| | healthy: false,
|
| | error: error.message,
|
| | fallback: true,
|
| | timestamp: new Date().toISOString()
|
| | };
|
| | }
|
| |
|
| |
|
| | return {
|
| | error: error.message,
|
| | fallback: true,
|
| | data: null,
|
| | timestamp: new Date().toISOString()
|
| | };
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | export class CryptoMonitorAPI extends APIClient {
|
| |
|
| |
|
| |
|
| |
|
| | async getHealth() {
|
| | return this.get(API_ENDPOINTS.HEALTH);
|
| | }
|
| |
|
| | async getStatus() {
|
| | return this.get(API_ENDPOINTS.STATUS);
|
| | }
|
| |
|
| | async getStats() {
|
| | return this.get(API_ENDPOINTS.STATS);
|
| | }
|
| |
|
| | async getResources() {
|
| | return this.get(API_ENDPOINTS.RESOURCES);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getMarket() {
|
| | return this.get(API_ENDPOINTS.MARKET);
|
| | }
|
| |
|
| | async getTrending() {
|
| | return this.get(API_ENDPOINTS.TRENDING);
|
| | }
|
| |
|
| | async getSentiment() {
|
| | return this.get(API_ENDPOINTS.SENTIMENT);
|
| | }
|
| |
|
| | async getDefi() {
|
| | return this.get(API_ENDPOINTS.DEFI);
|
| | }
|
| |
|
| | async getTopCoins(limit = 50) {
|
| | return this.get(`${API_ENDPOINTS.COINS_TOP}?limit=${limit}`);
|
| | }
|
| |
|
| | async getCoinDetails(symbol) {
|
| | return this.get(API_ENDPOINTS.COIN_DETAILS(symbol));
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getPriceChart(symbol, timeframe = '7D') {
|
| | return this.get(`${API_ENDPOINTS.PRICE_CHART(symbol)}?timeframe=${timeframe}`);
|
| | }
|
| |
|
| | async analyzeChart(symbol, timeframe, indicators) {
|
| | return this.post(API_ENDPOINTS.ANALYZE_CHART, {
|
| | symbol,
|
| | timeframe,
|
| | indicators,
|
| | });
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getLatestNews(limit = 40) {
|
| | return this.get(`${API_ENDPOINTS.NEWS_LATEST}?limit=${limit}`);
|
| | }
|
| |
|
| | async analyzeNews(title, content) {
|
| | return this.post(API_ENDPOINTS.NEWS_ANALYZE, { title, content });
|
| | }
|
| |
|
| | async summarizeNews(title, content) {
|
| | return this.post(API_ENDPOINTS.NEWS_SUMMARIZE, { title, content });
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getModelsList() {
|
| | return this.get(API_ENDPOINTS.MODELS_LIST);
|
| | }
|
| |
|
| | async getModelsStatus() {
|
| | return this.get(API_ENDPOINTS.MODELS_STATUS);
|
| | }
|
| |
|
| | async getModelsStats() {
|
| | return this.get(API_ENDPOINTS.MODELS_STATS);
|
| | }
|
| |
|
| | async testModel(modelName, input) {
|
| | return this.post(API_ENDPOINTS.MODELS_TEST, {
|
| | model: modelName,
|
| | input,
|
| | });
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async analyzeSentiment(text, mode = 'crypto', model = null) {
|
| | return this.post(API_ENDPOINTS.SENTIMENT_ANALYZE, {
|
| | text,
|
| | mode,
|
| | model,
|
| | });
|
| | }
|
| |
|
| | async getGlobalSentiment() {
|
| | return this.get(API_ENDPOINTS.SENTIMENT_GLOBAL);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getAIDecision(symbol, horizon, riskTolerance, context, model) {
|
| | return this.post(API_ENDPOINTS.AI_DECISION, {
|
| | symbol,
|
| | horizon,
|
| | risk_tolerance: riskTolerance,
|
| | context,
|
| | model,
|
| | });
|
| | }
|
| |
|
| | async getAISignals(symbol) {
|
| | return this.get(`${API_ENDPOINTS.AI_SIGNALS}?symbol=${symbol}`);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getDatasetsList() {
|
| | return this.get(API_ENDPOINTS.DATASETS_LIST);
|
| | }
|
| |
|
| | async previewDataset(name, limit = 10) {
|
| | return this.get(`${API_ENDPOINTS.DATASET_PREVIEW(name)}?limit=${limit}`);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getProviders() {
|
| | return this.get(API_ENDPOINTS.PROVIDERS);
|
| | }
|
| |
|
| | async getProviderDetails(id) {
|
| | return this.get(API_ENDPOINTS.PROVIDER_DETAILS(id));
|
| | }
|
| |
|
| | async checkProviderHealth(id) {
|
| | return this.get(API_ENDPOINTS.PROVIDER_HEALTH(id));
|
| | }
|
| |
|
| | async getProvidersConfig() {
|
| | return this.get(API_ENDPOINTS.PROVIDERS_CONFIG);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getLogs() {
|
| | return this.get(API_ENDPOINTS.LOGS);
|
| | }
|
| |
|
| | async getRecentLogs(limit = 50) {
|
| | return this.get(`${API_ENDPOINTS.LOGS_RECENT}?limit=${limit}`);
|
| | }
|
| |
|
| | async getErrorLogs(limit = 50) {
|
| | return this.get(`${API_ENDPOINTS.LOGS_ERRORS}?limit=${limit}`);
|
| | }
|
| |
|
| | async clearLogs() {
|
| | return this.delete(API_ENDPOINTS.LOGS_CLEAR);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async runResourceDiscovery() {
|
| | return this.post(API_ENDPOINTS.RESOURCES_DISCOVERY);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getHFHealth() {
|
| | return this.get(API_ENDPOINTS.HF_HEALTH);
|
| | }
|
| |
|
| | async runHFSentiment(text) {
|
| | return this.post(API_ENDPOINTS.HF_RUN_SENTIMENT, { text });
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getFeatureFlags() {
|
| | return this.get(API_ENDPOINTS.FEATURE_FLAGS);
|
| | }
|
| |
|
| | async updateFeatureFlag(name, value) {
|
| | return this.put(API_ENDPOINTS.FEATURE_FLAG_UPDATE(name), { value });
|
| | }
|
| |
|
| | async resetFeatureFlags() {
|
| | return this.post(API_ENDPOINTS.FEATURE_FLAGS_RESET);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | async getSettings() {
|
| | return this.get(API_ENDPOINTS.SETTINGS);
|
| | }
|
| |
|
| | async saveTokens(tokens) {
|
| | return this.post(API_ENDPOINTS.SETTINGS_TOKENS, tokens);
|
| | }
|
| |
|
| | async saveTelegramSettings(settings) {
|
| | return this.post(API_ENDPOINTS.SETTINGS_TELEGRAM, settings);
|
| | }
|
| |
|
| | async saveSignalSettings(settings) {
|
| | return this.post(API_ENDPOINTS.SETTINGS_SIGNALS, settings);
|
| | }
|
| |
|
| | async saveSchedulingSettings(settings) {
|
| | return this.post(API_ENDPOINTS.SETTINGS_SCHEDULING, settings);
|
| | }
|
| |
|
| | async saveNotificationSettings(settings) {
|
| | return this.post(API_ENDPOINTS.SETTINGS_NOTIFICATIONS, settings);
|
| | }
|
| |
|
| | async saveAppearanceSettings(settings) {
|
| | return this.post(API_ENDPOINTS.SETTINGS_APPEARANCE, settings);
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | export const api = new CryptoMonitorAPI();
|
| | export default api;
|
| |
|
| | |
| | |
| | |
| |
|
| | export const apiClient = {
|
| | async fetch(url, options = {}) {
|
| |
|
| | const method = (options.method || 'GET').toUpperCase();
|
| | const endpoint = url.replace(/^.*\/api/, '/api');
|
| |
|
| | try {
|
| | let data;
|
| | if (method === 'GET') {
|
| | data = await api.get(endpoint, { skipCache: options.skipCache, forceRefresh: options.forceRefresh });
|
| | } else if (method === 'POST') {
|
| | const body = options.body ? (typeof options.body === 'string' ? JSON.parse(options.body) : options.body) : {};
|
| | data = await api.post(endpoint, body);
|
| | } else if (method === 'PUT') {
|
| | const body = options.body ? (typeof options.body === 'string' ? JSON.parse(options.body) : options.body) : {};
|
| | data = await api.put(endpoint, body);
|
| | } else if (method === 'DELETE') {
|
| | data = await api.delete(endpoint);
|
| | } else {
|
| | data = await api.get(endpoint);
|
| | }
|
| |
|
| |
|
| | return new Response(JSON.stringify(data), {
|
| | status: 200,
|
| | statusText: 'OK',
|
| | headers: { 'Content-Type': 'application/json' }
|
| | });
|
| | } catch (error) {
|
| |
|
| | return new Response(JSON.stringify({
|
| | error: error.message || 'Request failed',
|
| | success: false
|
| | }), {
|
| | status: error.status || 500,
|
| | statusText: error.statusText || 'Internal Server Error',
|
| | headers: { 'Content-Type': 'application/json' }
|
| | });
|
| | }
|
| | }
|
| | };
|
| |
|
| | console.log('[APIClient] Initialized (HTTP-only, no WebSocket)');
|
| |
|