diff --git "a/static/js/app.js" "b/static/js/app.js" --- "a/static/js/app.js" +++ "b/static/js/app.js" @@ -1,4 +1,4 @@ -// Crypto Intelligence Hub - Main JavaScript +// Crypto Intelligence Hub - Main JavaScript with Sidebar Navigation // Enhanced Pro Trading Terminal UI // ============================================================================= @@ -88,11 +88,6 @@ const ToastManager = { } }; -// Initialize toast manager -document.addEventListener('DOMContentLoaded', () => { - ToastManager.init(); -}); - // ============================================================================= // Global State // ============================================================================= @@ -100,14 +95,96 @@ const AppState = { currentTab: 'dashboard', data: {}, charts: {}, - isLoading: false + isLoading: false, + sidebarOpen: false }; -// Initialize app +// ============================================================================= +// Sidebar Navigation +// ============================================================================= + +function toggleSidebar() { + const sidebar = document.getElementById('sidebar'); + const overlay = document.getElementById('sidebar-overlay'); + + if (sidebar && overlay) { + sidebar.classList.toggle('active'); + overlay.classList.toggle('active'); + AppState.sidebarOpen = !AppState.sidebarOpen; + } +} + +function switchTab(tabId) { + // Update nav items + const navItems = document.querySelectorAll('.nav-item'); + navItems.forEach(item => { + if (item.dataset.tab === tabId) { + item.classList.add('active'); + } else { + item.classList.remove('active'); + } + }); + + // Update tab panels + const tabPanels = document.querySelectorAll('.tab-panel'); + tabPanels.forEach(panel => { + if (panel.id === `tab-${tabId}`) { + panel.classList.add('active'); + } else { + panel.classList.remove('active'); + } + }); + + // Update page title + const pageTitles = { + 'dashboard': { title: 'Dashboard', subtitle: 'System Overview' }, + 'market': { title: 'Market Data', subtitle: 'Real-time Cryptocurrency Prices' }, + 'models': { title: 'AI Models', subtitle: 'Hugging Face Models' }, + 'sentiment': { title: 'Sentiment Analysis', subtitle: 'AI-Powered Sentiment Detection' }, + 'trading-assistant': { title: 'Trading Signals', subtitle: 'AI Trading Assistant' }, + 'news': { title: 'Crypto News', subtitle: 'Latest News & Updates' }, + 'settings': { title: 'Settings', subtitle: 'System Configuration' } + }; + + const pageTitle = document.getElementById('page-title'); + const pageSubtitle = document.getElementById('page-subtitle'); + + if (pageTitle && pageTitles[tabId]) { + pageTitle.textContent = pageTitles[tabId].title; + } + if (pageSubtitle && pageTitles[tabId]) { + pageSubtitle.textContent = pageTitles[tabId].subtitle; + } + + // Update state + AppState.currentTab = tabId; + + // Load tab data + loadTabData(tabId); + + // Close sidebar on mobile after selection + if (window.innerWidth <= 768) { + toggleSidebar(); + } +} + +// ============================================================================= +// Initialize App +// ============================================================================= + document.addEventListener('DOMContentLoaded', () => { - initTabs(); + console.log('🚀 Initializing Crypto Intelligence Hub...'); + + // Initialize toast manager + ToastManager.init(); + + // Check API status checkAPIStatus(); - loadDashboard(); + + // Load initial dashboard immediately + setTimeout(() => { + loadDashboard(); + }, 100); // Auto-refresh every 30 seconds setInterval(() => { @@ -121,6 +198,8 @@ document.addEventListener('DOMContentLoaded', () => { console.log('Trading pairs loaded:', e.detail.pairs.length); initTradingPairSelectors(); }); + + console.log('✅ App initialized successfully'); }); // Initialize trading pair selectors after pairs are loaded @@ -137,35 +216,28 @@ function initTradingPairSelectors() { ); } } + + // Initialize trading symbol selector + const tradingSymbolContainer = document.getElementById('trading-symbol-container'); + if (tradingSymbolContainer && window.TradingPairsLoader) { + const pairs = window.TradingPairsLoader.getTradingPairs(); + if (pairs && pairs.length > 0) { + tradingSymbolContainer.innerHTML = window.TradingPairsLoader.createTradingPairCombobox( + 'trading-symbol', + 'Select or type trading pair', + 'BTCUSDT' + ); + } + } } -// Tab Navigation -function initTabs() { - const tabButtons = document.querySelectorAll('.tab-btn'); - const tabContents = document.querySelectorAll('.tab-content'); - - tabButtons.forEach(btn => { - btn.addEventListener('click', () => { - const tabId = btn.dataset.tab; - - // Update buttons - tabButtons.forEach(b => b.classList.remove('active')); - btn.classList.add('active'); - - // Update content - tabContents.forEach(c => c.classList.remove('active')); - document.getElementById(`tab-${tabId}`).classList.add('active'); - - AppState.currentTab = tabId; - - // Load tab data - loadTabData(tabId); - }); - }); -} +// ============================================================================= +// Tab Data Loading +// ============================================================================= -// Load tab-specific data - synchronized with HTML tabs function loadTabData(tabId) { + console.log(`Loading data for tab: ${tabId}`); + switch(tabId) { case 'dashboard': loadDashboard(); @@ -177,11 +249,7 @@ function loadTabData(tabId) { loadModels(); break; case 'sentiment': - loadSentimentModels(); // Populate model dropdown - loadSentimentHistory(); // Load history from localStorage - break; - case 'ai-analyst': - // AI analyst tab is interactive, no auto-load needed + // Sentiment tab is interactive, no auto-load needed break; case 'trading-assistant': // Trading assistant tab is interactive, no auto-load needed @@ -189,83 +257,58 @@ function loadTabData(tabId) { case 'news': loadNews(); break; - case 'providers': - loadProviders(); - break; - case 'diagnostics': - loadDiagnostics(); - break; - case 'api-explorer': - loadAPIEndpoints(); + case 'settings': + loadSettings(); break; default: console.log('No specific loader for tab:', tabId); } } -// Load available API endpoints -function loadAPIEndpoints() { - const endpointSelect = document.getElementById('api-endpoint'); - if (!endpointSelect) return; - - // Add more endpoints - const endpoints = [ - { value: '/api/health', text: 'GET /api/health - Health Check' }, - { value: '/api/status', text: 'GET /api/status - System Status' }, - { value: '/api/stats', text: 'GET /api/stats - Statistics' }, - { value: '/api/market', text: 'GET /api/market - Market Data' }, - { value: '/api/trending', text: 'GET /api/trending - Trending Coins' }, - { value: '/api/sentiment', text: 'GET /api/sentiment - Fear & Greed Index' }, - { value: '/api/news', text: 'GET /api/news - Latest News' }, - { value: '/api/news/latest', text: 'GET /api/news/latest - Latest News (Alt)' }, - { value: '/api/resources', text: 'GET /api/resources - Resources Summary' }, - { value: '/api/providers', text: 'GET /api/providers - List Providers' }, - { value: '/api/models/list', text: 'GET /api/models/list - List Models' }, - { value: '/api/models/status', text: 'GET /api/models/status - Models Status' }, - { value: '/api/models/data/stats', text: 'GET /api/models/data/stats - Models Statistics' }, - { value: '/api/analyze/text', text: 'POST /api/analyze/text - AI Text Analysis' }, - { value: '/api/trading/decision', text: 'POST /api/trading/decision - Trading Signal' }, - { value: '/api/sentiment/analyze', text: 'POST /api/sentiment/analyze - Analyze Sentiment' }, - { value: '/api/logs/recent', text: 'GET /api/logs/recent - Recent Logs' }, - { value: '/api/logs/errors', text: 'GET /api/logs/errors - Error Logs' }, - { value: '/api/diagnostics/last', text: 'GET /api/diagnostics/last - Last Diagnostics' }, - { value: '/api/hf/models', text: 'GET /api/hf/models - HF Models' }, - { value: '/api/hf/health', text: 'GET /api/hf/health - HF Health' } - ]; - - // Clear existing options except first one - endpointSelect.innerHTML = ''; - endpoints.forEach(ep => { - const option = document.createElement('option'); - option.value = ep.value; - option.textContent = ep.text; - endpointSelect.appendChild(option); - }); +function refreshCurrentTab() { + loadTabData(AppState.currentTab); + ToastManager.success('Data refreshed successfully'); } -// Check API Status +// ============================================================================= +// API Status Check +// ============================================================================= + async function checkAPIStatus() { try { const response = await fetch('/health'); const data = await response.json(); - const statusBadge = document.getElementById('api-status'); + const statusIndicator = document.getElementById('sidebar-status'); + if (statusIndicator) { if (data.status === 'healthy') { - statusBadge.className = 'status-badge'; - statusBadge.innerHTML = '✅ System Active'; + statusIndicator.textContent = 'System Active'; + statusIndicator.parentElement.style.background = 'rgba(16, 185, 129, 0.15)'; + statusIndicator.parentElement.style.borderColor = 'rgba(16, 185, 129, 0.3)'; } else { - statusBadge.className = 'status-badge error'; - statusBadge.innerHTML = '❌ Error'; + statusIndicator.textContent = 'System Error'; + statusIndicator.parentElement.style.background = 'rgba(239, 68, 68, 0.15)'; + statusIndicator.parentElement.style.borderColor = 'rgba(239, 68, 68, 0.3)'; + } } } catch (error) { - const statusBadge = document.getElementById('api-status'); - statusBadge.className = 'status-badge error'; - statusBadge.innerHTML = '❌ Connection Failed'; + console.error('Error checking API status:', error); + const statusIndicator = document.getElementById('sidebar-status'); + if (statusIndicator) { + statusIndicator.textContent = 'Connection Failed'; + statusIndicator.parentElement.style.background = 'rgba(239, 68, 68, 0.15)'; + statusIndicator.parentElement.style.borderColor = 'rgba(239, 68, 68, 0.3)'; + } } } -// Load Dashboard +// ============================================================================= +// Dashboard Loading +// ============================================================================= + async function loadDashboard() { + console.log('📊 Loading dashboard...'); + // Show loading state const statsElements = [ 'stat-total-resources', 'stat-free-resources', @@ -284,20 +327,47 @@ async function loadDashboard() { try { // Load resources const resourcesRes = await fetch('/api/resources'); + if (!resourcesRes.ok) { + throw new Error(`Resources API returned ${resourcesRes.status}`); + } const resourcesData = await resourcesRes.json(); + console.log('Resources data:', resourcesData); + if (resourcesData.success && resourcesData.summary) { - document.getElementById('stat-total-resources').textContent = resourcesData.summary.total_resources || 0; - document.getElementById('stat-free-resources').textContent = resourcesData.summary.free_resources || 0; - document.getElementById('stat-models').textContent = resourcesData.summary.models_available || 0; + const totalResources = resourcesData.summary.total_resources || 0; + const freeResources = resourcesData.summary.free_resources || 0; + const modelsAvailable = resourcesData.summary.models_available || 0; + + document.getElementById('stat-total-resources').textContent = totalResources; + document.getElementById('stat-free-resources').textContent = freeResources; + document.getElementById('stat-models').textContent = modelsAvailable; + + // Update sidebar stats + const sidebarResources = document.getElementById('sidebar-resources'); + const sidebarModels = document.getElementById('sidebar-models'); + if (sidebarResources) sidebarResources.textContent = totalResources; + if (sidebarModels) sidebarModels.textContent = modelsAvailable; + + // Load categories chart + if (resourcesData.summary.categories) { + createCategoriesChart(resourcesData.summary.categories); + } + } else { + console.warn('Resources data format unexpected:', resourcesData); + document.getElementById('stat-total-resources').textContent = '0'; + document.getElementById('stat-free-resources').textContent = '0'; + document.getElementById('stat-models').textContent = '0'; } // Load system status try { const statusRes = await fetch('/api/status'); + if (statusRes.ok) { const statusData = await statusRes.json(); - document.getElementById('stat-providers').textContent = statusData.total_apis || statusData.total_providers || 0; + const providers = statusData.total_apis || statusData.total_providers || 0; + document.getElementById('stat-providers').textContent = providers; // Display system status const systemStatusDiv = document.getElementById('system-status'); @@ -315,24 +385,35 @@ async function loadDashboard() { Last Update: ${new Date(statusData.last_update || Date.now()).toLocaleString('en-US')} `; + } else { + throw new Error('Status endpoint not available'); + } } catch (statusError) { - console.warn('Status endpoint not available:', statusError); + console.warn('Status endpoint error:', statusError); document.getElementById('stat-providers').textContent = '-'; + + const systemStatusDiv = document.getElementById('system-status'); + if (systemStatusDiv) { + systemStatusDiv.innerHTML = '
System status unavailable. Core features are operational.
'; + } } - // Load categories chart - if (resourcesData.success && resourcesData.summary.categories) { - createCategoriesChart(resourcesData.summary.categories); - } + console.log('✅ Dashboard loaded successfully'); } catch (error) { - console.error('Error loading dashboard:', error); - showError('Failed to load dashboard. Please check the backend is running.'); + console.error('❌ Error loading dashboard:', error); + ToastManager.error('Failed to load dashboard. Please check the backend.'); // Show error state const systemStatusDiv = document.getElementById('system-status'); if (systemStatusDiv) { - systemStatusDiv.innerHTML = '
Failed to load dashboard data. Please refresh or check backend status.
'; + systemStatusDiv.innerHTML = `
Failed to load dashboard data: ${error.message}
Please refresh or check backend status.
`; } + + // Set default values + statsElements.forEach(id => { + const el = document.getElementById(id); + if (el) el.textContent = '0'; + }); } } @@ -352,13 +433,21 @@ function createCategoriesChart(categories) { AppState.charts.categories.destroy(); } + const labels = Object.keys(categories); + const values = Object.values(categories); + + if (labels.length === 0) { + ctx.parentElement.innerHTML = '

No category data available

'; + return; + } + AppState.charts.categories = new Chart(ctx, { type: 'bar', data: { - labels: Object.keys(categories), + labels: labels, datasets: [{ label: 'Total Resources', - data: Object.values(categories), + data: values, backgroundColor: 'rgba(102, 126, 234, 0.6)', borderColor: 'rgba(102, 126, 234, 1)', borderWidth: 2 @@ -366,6 +455,7 @@ function createCategoriesChart(categories) { }, options: { responsive: true, + maintainAspectRatio: true, plugins: { legend: { display: false } }, @@ -376,9 +466,13 @@ function createCategoriesChart(categories) { }); } -// Load Market Data +// ============================================================================= +// Market Data Loading +// ============================================================================= + async function loadMarketData() { - // Show loading states + console.log('💰 Loading market data...'); + const marketDiv = document.getElementById('market-data'); const trendingDiv = document.getElementById('trending-coins'); const fgDiv = document.getElementById('fear-greed'); @@ -389,10 +483,12 @@ async function loadMarketData() { try { const response = await fetch('/api/market'); + if (!response.ok) { + throw new Error(`Market API returned ${response.status}`); + } const data = await response.json(); if (data.cryptocurrencies && data.cryptocurrencies.length > 0) { - const marketDiv = document.getElementById('market-data'); marketDiv.innerHTML = `
@@ -411,7 +507,7 @@ async function loadMarketData() { @@ -431,16 +527,16 @@ async function loadMarketData() { ` : ''} `; } else { - document.getElementById('market-data').innerHTML = '
No data found
'; + marketDiv.innerHTML = '
No market data available
'; } - // Load trending + // Load trending coins try { const trendingRes = await fetch('/api/trending'); + if (trendingRes.ok) { const trendingData = await trendingRes.json(); if (trendingData.trending && trendingData.trending.length > 0) { - const trendingDiv = document.getElementById('trending-coins'); trendingDiv.innerHTML = `
${trendingData.trending.map((coin, index) => ` @@ -458,24 +554,26 @@ async function loadMarketData() {
`; } else { - document.getElementById('trending-coins').innerHTML = '
No data found
'; + trendingDiv.innerHTML = '
No trending data available
'; + } + } else { + throw new Error('Trending endpoint not available'); } } catch (trendingError) { console.warn('Trending endpoint error:', trendingError); - document.getElementById('trending-coins').innerHTML = '
Error loading trending coins
'; + trendingDiv.innerHTML = '
Trending data unavailable
'; } - // Load Fear & Greed + // Load Fear & Greed Index try { const sentimentRes = await fetch('/api/sentiment'); + if (sentimentRes.ok) { const sentimentData = await sentimentRes.json(); if (sentimentData.fear_greed_index !== undefined) { - const fgDiv = document.getElementById('fear-greed'); const fgValue = sentimentData.fear_greed_index; const fgLabel = sentimentData.fear_greed_label || 'Unknown'; - // Determine color based on value let fgColor = 'var(--warning)'; if (fgValue >= 75) fgColor = 'var(--success)'; else if (fgValue >= 50) fgColor = 'var(--info)'; @@ -499,1989 +597,654 @@ async function loadMarketData() { `; } else { - document.getElementById('fear-greed').innerHTML = '
No data found
'; + fgDiv.innerHTML = '
Fear & Greed Index unavailable
'; + } + } else { + throw new Error('Sentiment endpoint not available'); } - } catch (sentimentError) { - console.warn('Sentiment endpoint error:', sentimentError); - document.getElementById('fear-greed').innerHTML = '
Error loading Fear & Greed Index
'; + } catch (fgError) { + console.warn('Fear & Greed endpoint error:', fgError); + fgDiv.innerHTML = '
Fear & Greed Index unavailable
'; } + + console.log('✅ Market data loaded successfully'); } catch (error) { - console.error('Error loading market data:', error); - showError('Failed to load market data. Please check the backend connection.'); + console.error('❌ Error loading market data:', error); + ToastManager.error('Failed to load market data'); - const marketDiv = document.getElementById('market-data'); - if (marketDiv) { - marketDiv.innerHTML = '
Failed to load market data. The backend may be offline or the CoinGecko API may be unavailable.
'; - } + if (marketDiv) marketDiv.innerHTML = `
Error loading market data: ${error.message}
`; + if (trendingDiv) trendingDiv.innerHTML = '
Error loading trending coins
'; + if (fgDiv) fgDiv.innerHTML = '
Error loading Fear & Greed Index
'; } } -// Format large numbers -function formatNumber(num) { - if (!num) return '0'; - if (num >= 1e12) return (num / 1e12).toFixed(2) + 'T'; - if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B'; - if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'; - if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K'; - return num.toLocaleString('en-US', { maximumFractionDigits: 2 }); -} +// ============================================================================= +// News Loading (FIXED) +// ============================================================================= -// Load Models -async function loadModels() { - // Show loading state - const modelsListDiv = document.getElementById('models-list'); - const statusDiv = document.getElementById('models-status'); +async function fetchNewsFromAPI() { + console.log('📥 Fetching news from CryptoCompare API...'); + ToastManager.info('Fetching latest news from CryptoCompare...'); + + const newsListDiv = document.getElementById('news-list'); + if (!newsListDiv) return; - if (modelsListDiv) modelsListDiv.innerHTML = '
Loading models...
'; - if (statusDiv) statusDiv.innerHTML = '
Loading status...
'; + newsListDiv.innerHTML = '
Fetching news from CryptoCompare API...
'; try { - const response = await fetch('/api/models/list'); + const response = await fetch('/api/news/fetch?limit=50', { method: 'POST' }); + if (!response.ok) { + throw new Error(`Fetch API returned ${response.status}`); + } + const data = await response.json(); + console.log('Fetch news result:', data); - const models = data.models || data || []; + if (data.success) { + ToastManager.success(`Successfully fetched and saved ${data.saved} news articles!`); + // Reload news list + loadNews(); + } else { + ToastManager.error(`Failed to fetch news: ${data.error}`); + newsListDiv.innerHTML = `
Error: ${data.error}
`; + } + } catch (error) { + console.error('❌ Error fetching news:', error); + ToastManager.error('Failed to fetch news from API'); + newsListDiv.innerHTML = `
Error fetching news: ${error.message}
`; + } +} + +// Utility function to format time ago +function formatTimeAgo(dateString) { + if (!dateString) return 'Unknown time'; + + const date = new Date(dateString); + const now = new Date(); + const diffMs = now - date; + const diffSecs = Math.floor(diffMs / 1000); + const diffMins = Math.floor(diffSecs / 60); + const diffHours = Math.floor(diffMins / 60); + const diffDays = Math.floor(diffHours / 24); + + if (diffSecs < 60) return 'Just now'; + if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`; + if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; + if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; + + return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined }); +} + +async function loadNews() { + console.log('📰 Loading news...'); + + const newsListDiv = document.getElementById('news-list'); + if (!newsListDiv) return; + + newsListDiv.innerHTML = '
Loading news...
'; + + try { + const response = await fetch('/api/news?limit=50'); + if (!response.ok) { + throw new Error(`News API returned ${response.status}`); + } - if (models.length > 0) { - const modelsListDiv = document.getElementById('models-list'); - modelsListDiv.innerHTML = ` -
- ${models.map(model => { - const status = model.status || 'unknown'; - const isAvailable = status === 'available' || status === 'loaded'; - const statusColor = isAvailable ? 'var(--success)' : 'var(--danger)'; - const statusBg = isAvailable ? 'rgba(16, 185, 129, 0.2)' : 'rgba(239, 68, 68, 0.2)'; + const data = await response.json(); + console.log('News data:', data); + + if (data.success && data.news && data.news.length > 0) { + // Sort by latest timestamp (published_date or analyzed_at) + const sortedNews = [...data.news].sort((a, b) => { + const dateA = new Date(a.published_date || a.analyzed_at || 0); + const dateB = new Date(b.published_date || b.analyzed_at || 0); + return dateB - dateA; + }); + + newsListDiv.innerHTML = ` +
+ ${sortedNews.map(article => { + const timeAgo = formatTimeAgo(article.published_date || article.analyzed_at); + const symbols = Array.isArray(article.related_symbols) + ? article.related_symbols + : (typeof article.related_symbols === 'string' + ? (article.related_symbols.startsWith('[') + ? JSON.parse(article.related_symbols) + : article.related_symbols.split(',').map(s => s.trim())) + : []); return ` -
-
-
-

${model.model_id || model.name || 'Unknown'}

-
- ${model.task || model.category || 'N/A'} -
- ${model.category ? `
Category: ${model.category}
` : ''} - ${model.requires_auth !== undefined ? `
- ${model.requires_auth ? '🔐 Requires Authentication' : '🔓 No Auth Required'} -
` : ''} +
+
+ + + + +
+
+

+ ${article.url ? `${article.title || 'Untitled'}` : (article.title || 'Untitled')} +

+ ${article.content ? `

${article.content.substring(0, 200)}${article.content.length > 200 ? '...' : ''}

` : ''} +
+
+ + + + + + ${article.source || 'Unknown'} +
+
+ + + + + ${timeAgo}
- - ${isAvailable ? '✅ Available' : '❌ Unavailable'} - + ${article.sentiment_label ? `${article.sentiment_label}` : ''}
- ${model.key ? `
- Key: ${model.key} -
` : ''} + ${symbols.length > 0 ? ` +
+ ${symbols.slice(0, 5).map(symbol => `${symbol}`).join('')} + ${symbols.length > 5 ? `+${symbols.length - 5}` : ''} +
+ ` : ''}
- `; +
+ `; }).join('')}
+
+ Showing ${sortedNews.length} article${sortedNews.length !== 1 ? 's' : ''}${data.source ? ` from ${data.source}` : ''} +
`; - } else { - document.getElementById('models-list').innerHTML = '
No models found
'; + + console.log('✅ News loaded successfully'); + ToastManager.success(`Loaded ${sortedNews.length} news articles`); + } else { + newsListDiv.innerHTML = '
No news articles available at the moment. Click "Fetch Latest News" to load articles.
'; + console.warn('No news data available'); } - + } catch (error) { + console.error('❌ Error loading news:', error); + ToastManager.error('Failed to load news'); + newsListDiv.innerHTML = `
Error loading news: ${error.message}
Please check your internet connection and try again.
`; + } +} + +// ============================================================================= +// Models Loading +// ============================================================================= + +async function loadModels() { + console.log('🤖 Loading models...'); + + const modelsStatusDiv = document.getElementById('models-status'); + const modelsListDiv = document.getElementById('models-list'); + + if (modelsStatusDiv) modelsStatusDiv.innerHTML = '
Loading models status...
'; + if (modelsListDiv) modelsListDiv.innerHTML = '
Loading models list...
'; + + try { // Load models status - try { - const statusRes = await fetch('/api/models/status'); + const statusRes = await fetch('/api/models/status'); + if (statusRes.ok) { const statusData = await statusRes.json(); - const statusDiv = document.getElementById('models-status'); - if (statusDiv) { - // Use honest status from backend - const status = statusData.status || 'unknown'; - const statusMessage = statusData.status_message || 'Unknown status'; - const hfMode = statusData.hf_mode || 'unknown'; - const modelsLoaded = statusData.models_loaded || statusData.pipelines_loaded || 0; - const modelsFailed = statusData.models_failed || 0; - - // Determine status class based on honest status - let statusClass = 'alert-warning'; - if (status === 'ok') statusClass = 'alert-success'; - else if (status === 'disabled' || status === 'transformers_unavailable') statusClass = 'alert-error'; - else if (status === 'partial') statusClass = 'alert-warning'; - - statusDiv.innerHTML = ` -
- Status: ${statusMessage}
- HF Mode: ${hfMode}
- Models Loaded: ${modelsLoaded}
- Models Failed: ${modelsFailed}
- ${statusData.transformers_available !== undefined ? `Transformers Available: ${statusData.transformers_available ? '✅ Yes' : '❌ No'}
` : ''} - ${statusData.initialized !== undefined ? `Initialized: ${statusData.initialized ? '✅ Yes' : '❌ No'}
` : ''} - ${hfMode === 'off' ? `
- Note: HF models are disabled (HF_MODE=off). To enable them, set HF_MODE=public or HF_MODE=auth in the environment. -
` : ''} - ${hfMode !== 'off' && modelsLoaded === 0 && modelsFailed > 0 ? `
- Warning: No models could be loaded. ${modelsFailed} model(s) failed. Check model IDs or HF access. -
` : ''} -
- `; - } - } catch (statusError) { - console.warn('Models status endpoint error:', statusError); + modelsStatusDiv.innerHTML = ` +
+ Status: ${statusData.ok ? '✅ Active' : '⚠️ Partial'}
+ Pipelines Loaded: ${statusData.pipelines_loaded || 0}
+ Available Models: ${statusData.available_models?.length || 0}
+ HF Mode: ${statusData.hf_mode || 'unknown'}
+ Transformers Available: ${statusData.transformers_available ? '✅' : '❌'} +
+ `; + } else { + throw new Error('Models status endpoint not available'); } - // Load models stats - try { - const statsRes = await fetch('/api/models/data/stats'); - const statsData = await statsRes.json(); + // Load models list + const listRes = await fetch('/api/models/list'); + if (listRes.ok) { + const listData = await listRes.json(); - if (statsData.success && statsData.statistics) { - const statsDiv = document.getElementById('models-stats'); - statsDiv.innerHTML = ` -
-
-
${statsData.statistics.total_analyses || 0}
-
Total Analyses
-
-
-
${statsData.statistics.unique_symbols || 0}
-
Unique Symbols
-
- ${statsData.statistics.most_used_model ? ` -
-
${statsData.statistics.most_used_model}
-
Most Used Model
-
- ` : ''} + if (listData.models && listData.models.length > 0) { + modelsListDiv.innerHTML = ` +
+
${coin.rank || '-'} - ${coin.image ? `` : ''} + ${coin.image ? `` : ''} ${coin.symbol} ${coin.name} $${formatNumber(coin.price)}
+ + + + + + + + + + ${listData.models.map(model => ` + + + + + + + `).join('')} + +
Model IDTaskCategoryStatus
${model.model_id || model.key}${model.task || 'N/A'}${model.category || 'N/A'}${model.loaded ? '✅ Loaded' : '❌ Not Loaded'}
`; + } else { + modelsListDiv.innerHTML = '
No models available
'; } - } catch (statsError) { - console.warn('Models stats endpoint error:', statsError); + } else { + throw new Error('Models list endpoint not available'); } + + console.log('✅ Models loaded successfully'); } catch (error) { - console.error('Error loading models:', error); - showError('Failed to load models. Please check the backend connection.'); + console.error('❌ Error loading models:', error); + ToastManager.error('Failed to load models'); - const modelsListDiv = document.getElementById('models-list'); - if (modelsListDiv) { - modelsListDiv.innerHTML = '
Failed to load models. Check backend status.
'; - } + if (modelsStatusDiv) modelsStatusDiv.innerHTML = `
Error loading models status: ${error.message}
`; + if (modelsListDiv) modelsListDiv.innerHTML = '
Error loading models list
'; } } -// Initialize Models async function initializeModels() { + ToastManager.info('Initializing models... This may take a moment.'); + try { const response = await fetch('/api/models/initialize', { method: 'POST' }); + if (!response.ok) { + throw new Error(`Initialize returned ${response.status}`); + } + const data = await response.json(); - if (data.success) { - showSuccess('Models loaded successfully'); - loadModels(); + if (data.status === 'ok') { + ToastManager.success('Models initialized successfully!'); + loadModels(); // Reload models list } else { - showError(data.error || 'Error loading models'); + ToastManager.warning('Models partially initialized'); } } catch (error) { - showError('Error loading models: ' + error.message); + console.error('Error initializing models:', error); + ToastManager.error('Failed to initialize models'); } } -// Load Sentiment Models - updated to populate dropdown for sentiment analysis -async function loadSentimentModels() { - try { - const response = await fetch('/api/models/list'); - const data = await response.json(); - - const models = data.models || data || []; - const select = document.getElementById('sentiment-model'); - if (!select) return; - - select.innerHTML = ''; - - // Filter and add models - only sentiment and generation models - models.filter(m => { - const category = m.category || ''; - const task = m.task || ''; - // Include sentiment models and generation/trading models - return category.includes('sentiment') || - category.includes('generation') || - category.includes('trading') || - task.includes('classification') || - task.includes('generation'); - }).forEach(model => { - const option = document.createElement('option'); - const modelKey = model.key || model.id; - const modelName = model.model_id || model.name || modelKey; - const desc = model.description || model.category || ''; - - option.value = modelKey; - // Show model name with short description - const displayName = modelName.length > 40 ? modelName.substring(0, 37) + '...' : modelName; - option.textContent = displayName; - option.title = desc; // Full description on hover - select.appendChild(option); - }); - - // If no models available, show message - if (select.options.length === 1) { - const option = document.createElement('option'); - option.value = ''; - option.textContent = 'No models available - will use fallback'; - option.disabled = true; - select.appendChild(option); - } - - console.log(`Loaded ${select.options.length - 1} sentiment models into dropdown`); - } catch (error) { - console.error('Error loading sentiment models:', error); - const select = document.getElementById('sentiment-model'); - if (select) { - select.innerHTML = ''; - } - } -} +// ============================================================================= +// Settings +// ============================================================================= -// Analyze Global Market Sentiment -async function analyzeGlobalSentiment() { - const resultDiv = document.getElementById('global-sentiment-result'); - resultDiv.innerHTML = '
Analyzing market sentiment...
'; - - try { - // Use market text analysis with sample market-related text - const marketText = "Cryptocurrency market analysis: Bitcoin, Ethereum, and major altcoins showing mixed signals. Market sentiment analysis required."; - - const response = await fetch('/api/sentiment/analyze', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ text: marketText, mode: 'crypto' }) - }); - - const data = await response.json(); - - if (!data.available) { - resultDiv.innerHTML = ` -
- ⚠️ Models Not Available: ${data.error || 'AI models are currently unavailable'} +function loadSettings() { + const apiInfoDiv = document.getElementById('api-info'); + if (apiInfoDiv) { + apiInfoDiv.innerHTML = ` +
+ API Base URL: ${window.location.origin}
+ Documentation: /docs
+ Health Check: /health
`; - return; - } - - const sentiment = data.sentiment || 'neutral'; - const confidence = data.confidence || 0; - const sentimentEmoji = sentiment === 'bullish' ? '📈' : sentiment === 'bearish' ? '📉' : '➡️'; - const sentimentColor = sentiment === 'bullish' ? 'var(--success)' : sentiment === 'bearish' ? 'var(--danger)' : 'var(--text-secondary)'; - - resultDiv.innerHTML = ` -
-

Global Market Sentiment

-
-
-
${sentimentEmoji}
-
- ${sentiment === 'bullish' ? 'Bullish' : sentiment === 'bearish' ? 'Bearish' : 'Neutral'} -
-
- Confidence: ${(confidence * 100).toFixed(1)}% -
-
-
- Details: -
- This analysis is based on AI models. -
-
-
-
- `; - } catch (error) { - console.error('Global sentiment analysis error:', error); - resultDiv.innerHTML = `
Analysis Error: ${error.message}
`; - showError('Error analyzing market sentiment'); } } -// Analyze Asset Sentiment -async function analyzeAssetSentiment() { - const symbol = document.getElementById('asset-symbol').value.trim().toUpperCase(); - const text = document.getElementById('asset-sentiment-text').value.trim(); - - if (!symbol) { - showError('Please enter a cryptocurrency symbol'); - return; - } - - const resultDiv = document.getElementById('asset-sentiment-result'); - resultDiv.innerHTML = '
Analyzing...
'; - - try { - // Use provided text or default text with symbol - const analysisText = text || `${symbol} market analysis and sentiment`; - - const response = await fetch('/api/sentiment/analyze', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ text: analysisText, mode: 'crypto', symbol: symbol }) - }); - - const data = await response.json(); - - if (!data.available) { - resultDiv.innerHTML = ` -
- ⚠️ Models Not Available: ${data.error || 'AI models are currently unavailable'} -
- `; - return; - } - - const sentiment = data.sentiment || 'neutral'; - const confidence = data.confidence || 0; - const sentimentEmoji = sentiment === 'bullish' ? '📈' : sentiment === 'bearish' ? '📉' : '➡️'; - const sentimentColor = sentiment === 'bullish' ? 'var(--success)' : sentiment === 'bearish' ? 'var(--danger)' : 'var(--text-secondary)'; - - resultDiv.innerHTML = ` -
-

Sentiment Analysis Result for ${symbol}

-
-
- Sentiment: - - ${sentimentEmoji} ${sentiment === 'bullish' ? 'Bullish' : sentiment === 'bearish' ? 'Bearish' : 'Neutral'} - -
-
- Confidence: - - ${(confidence * 100).toFixed(2)}% - -
- ${text ? ` -
- Analyzed Text: -
- "${text.substring(0, 200)}${text.length > 200 ? '...' : ''}" -
-
- ` : ''} -
-
- `; - } catch (error) { - console.error('Asset sentiment analysis error:', error); - resultDiv.innerHTML = `
Analysis Error: ${error.message}
`; - showError('Error analyzing asset sentiment'); - } -} - -// Analyze News Sentiment -async function analyzeNewsSentiment() { - const title = document.getElementById('news-title').value.trim(); - const content = document.getElementById('news-content').value.trim(); - - if (!title && !content) { - showError('Please enter news title or content'); - return; - } - - const resultDiv = document.getElementById('news-sentiment-result'); - resultDiv.innerHTML = '
Analyzing...
'; - - try { - const response = await fetch('/api/news/analyze', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title: title, content: content, description: content }) - }); - - const data = await response.json(); - - if (!data.available) { - resultDiv.innerHTML = ` -
- ⚠️ Models Not Available: ${data.news?.error || data.error || 'AI models are currently unavailable'} -
- `; - return; - } - - const newsData = data.news || {}; - const sentiment = newsData.sentiment || 'neutral'; - const confidence = newsData.confidence || 0; - const sentimentEmoji = sentiment === 'bullish' || sentiment === 'positive' ? '📈' : - sentiment === 'bearish' || sentiment === 'negative' ? '📉' : '➡️'; - const sentimentColor = sentiment === 'bullish' || sentiment === 'positive' ? 'var(--success)' : - sentiment === 'bearish' || sentiment === 'negative' ? 'var(--danger)' : 'var(--text-secondary)'; - - resultDiv.innerHTML = ` -
-

News Sentiment Analysis Result

-
-
- Title: - ${title || 'No title'} -
-
- Sentiment: - - ${sentimentEmoji} ${sentiment === 'bullish' || sentiment === 'positive' ? 'Positive' : - sentiment === 'bearish' || sentiment === 'negative' ? 'Negative' : 'Neutral'} - -
-
- Confidence: - - ${(confidence * 100).toFixed(2)}% - -
-
-
- `; - } catch (error) { - console.error('News sentiment analysis error:', error); - resultDiv.innerHTML = `
Analysis Error: ${error.message}
`; - showError('Error analyzing news sentiment'); - } -} - -// Summarize News -async function summarizeNews() { - const title = document.getElementById('summary-news-title').value.trim(); - const content = document.getElementById('summary-news-content').value.trim(); - - if (!title && !content) { - showError('Please enter news title or content'); - return; - } - - const resultDiv = document.getElementById('news-summary-result'); - resultDiv.innerHTML = '
Generating summary...
'; - - try { - const response = await fetch('/api/news/summarize', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title: title, content: content }) - }); - - const data = await response.json(); - - if (!data.success) { - resultDiv.innerHTML = ` -
- ❌ Summarization Failed: ${data.error || 'Failed to generate summary'} -
- `; - return; - } - - const summary = data.summary || ''; - const model = data.model || 'Unknown'; - const isHFModel = data.available !== false && model !== 'fallback_extractive'; - const modelDisplay = isHFModel ? model : `${model} (Fallback)`; - - // Create collapsible card with summary - resultDiv.innerHTML = ` -
-
-

📝 News Summary

- -
- - ${title ? `
- Title: - ${title} -
` : ''} - -
- Summary: -

- ${summary} -

-
- - - -
- - -
-
- `; - - // Store summary for clipboard - window.lastSummary = summary; - - } catch (error) { - console.error('News summarization error:', error); - resultDiv.innerHTML = `
Summarization Error: ${error.message}
`; - showError('Error summarizing news'); - } -} - -// Toggle summary details -function toggleSummaryDetails() { - const details = document.getElementById('summary-details'); - const icon = document.getElementById('toggle-summary-icon'); - if (details.style.display === 'none') { - details.style.display = 'block'; - icon.textContent = '▲'; - } else { - details.style.display = 'none'; - icon.textContent = '▼'; - } -} - -// Copy summary to clipboard -async function copySummaryToClipboard() { - if (!window.lastSummary) { - showError('No summary to copy'); - return; - } - - try { - await navigator.clipboard.writeText(window.lastSummary); - showSuccess('Summary copied to clipboard!'); - } catch (error) { - console.error('Failed to copy:', error); - showError('Failed to copy summary'); - } -} - -// Clear summary form -function clearSummaryForm() { - document.getElementById('summary-news-title').value = ''; - document.getElementById('summary-news-content').value = ''; - document.getElementById('news-summary-result').innerHTML = ''; - window.lastSummary = null; -} - -// Analyze Sentiment (updated with model_key support) -async function analyzeSentiment() { - const text = document.getElementById('sentiment-text').value; - const mode = document.getElementById('sentiment-mode').value; - const modelKey = document.getElementById('sentiment-model').value; - - if (!text.trim()) { - showError('Please enter text to analyze'); - return; - } - - const resultDiv = document.getElementById('sentiment-result'); - resultDiv.innerHTML = '
Analyzing...
'; - - try { - let response; - - // Build request body - const requestBody = { - text: text, - mode: mode - }; - - // Add model_key if specific model selected - if (modelKey && modelKey !== '') { - requestBody.model_key = modelKey; - } - - // Use the sentiment endpoint with mode and optional model_key - response = await fetch('/api/sentiment', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(requestBody) - }); - - const data = await response.json(); - - if (!data.available) { - resultDiv.innerHTML = ` -
- ⚠️ Models Not Available: ${data.error || 'AI models are currently unavailable'} -
- `; - return; - } - - const label = data.sentiment || 'neutral'; - const confidence = data.confidence || 0; - const result = data.result || {}; - - // Determine sentiment emoji and color - const sentimentEmoji = label === 'bullish' || label === 'positive' ? '📈' : - label === 'bearish' || label === 'negative' ? '📉' : '➡️'; - const sentimentColor = label === 'bullish' || label === 'positive' ? 'var(--success)' : - label === 'bearish' || label === 'negative' ? 'var(--danger)' : 'var(--text-secondary)'; - - resultDiv.innerHTML = ` -
-

Sentiment Analysis Result

-
-
- Sentiment: - - ${sentimentEmoji} ${label === 'bullish' || label === 'positive' ? 'Bullish/Positive' : - label === 'bearish' || label === 'negative' ? 'Bearish/Negative' : 'Neutral'} - -
-
- Confidence: - - ${(confidence * 100).toFixed(2)}% - -
-
- Analysis Type: - ${mode} -
-
- Analyzed Text: -
- "${text.substring(0, 200)}${text.length > 200 ? '...' : ''}" -
-
-
-
- `; - - // Save to history (localStorage) - saveSentimentToHistory({ - text: text.substring(0, 100), - label: label, - confidence: confidence, - model: mode, - timestamp: new Date().toISOString() - }); - - // Reload history - loadSentimentHistory(); - - } catch (error) { - console.error('Sentiment analysis error:', error); - resultDiv.innerHTML = `
Analysis Error: ${error.message}
`; - showError('Error analyzing sentiment'); - } -} - -// Save sentiment to history -function saveSentimentToHistory(analysis) { - try { - const history = JSON.parse(localStorage.getItem('sentiment_history') || '[]'); - history.unshift(analysis); - // Keep only last 50 - if (history.length > 50) history = history.slice(0, 50); - localStorage.setItem('sentiment_history', JSON.stringify(history)); - } catch (e) { - console.warn('Could not save to history:', e); - } -} - -// Load sentiment history -function loadSentimentHistory() { - try { - const history = JSON.parse(localStorage.getItem('sentiment_history') || '[]'); - const historyDiv = document.getElementById('sentiment-history'); - - if (history.length === 0) { - historyDiv.innerHTML = '
No history available
'; - return; - } - - historyDiv.innerHTML = ` -
- ${history.slice(0, 20).map(item => { - const sentimentEmoji = item.label.toUpperCase().includes('POSITIVE') || item.label.toUpperCase().includes('BULLISH') ? '📈' : - item.label.toUpperCase().includes('NEGATIVE') || item.label.toUpperCase().includes('BEARISH') ? '📉' : '➡️'; - return ` -
-
- ${sentimentEmoji} ${item.label} - ${new Date(item.timestamp).toLocaleString('en-US')} -
-
${item.text}
-
- Confidence: ${(item.confidence * 100).toFixed(0)}% | Model: ${item.model} -
-
- `; - }).join('')} -
- `; - } catch (e) { - console.warn('Could not load history:', e); - } -} - -// Load News -async function loadNews() { - // Show loading state - const newsDiv = document.getElementById('news-list'); - if (newsDiv) { - newsDiv.innerHTML = '
Loading news...
'; - } - - try { - // Try /api/news/latest first, fallback to /api/news - let response; - try { - response = await fetch('/api/news/latest?limit=20'); - } catch { - response = await fetch('/api/news?limit=20'); - } - - const data = await response.json(); - - const newsItems = data.news || data.data || []; - - if (newsItems.length > 0) { - const newsDiv = document.getElementById('news-list'); - newsDiv.innerHTML = ` -
- ${newsItems.map((item, index) => { - const sentiment = item.sentiment_label || item.sentiment || 'neutral'; - const sentimentLower = sentiment.toLowerCase(); - const sentimentConfidence = item.sentiment_confidence || 0; - - // Determine sentiment styling - let sentimentColor, sentimentBg, sentimentEmoji, sentimentLabel; - if (sentimentLower.includes('positive') || sentimentLower.includes('bullish')) { - sentimentColor = '#10b981'; - sentimentBg = 'rgba(16, 185, 129, 0.15)'; - sentimentEmoji = '📈'; - sentimentLabel = 'Bullish'; - } else if (sentimentLower.includes('negative') || sentimentLower.includes('bearish')) { - sentimentColor = '#ef4444'; - sentimentBg = 'rgba(239, 68, 68, 0.15)'; - sentimentEmoji = '📉'; - sentimentLabel = 'Bearish'; - } else { - sentimentColor = '#6b7280'; - sentimentBg = 'rgba(107, 114, 128, 0.15)'; - sentimentEmoji = '➡️'; - sentimentLabel = 'Neutral'; - } - - const publishedDate = item.published_date || item.published_at || item.analyzed_at; - const publishedTime = publishedDate ? new Date(publishedDate).toLocaleString('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }) : 'Unknown date'; - - const content = item.content || item.description || ''; - const contentPreview = content.length > 250 ? content.substring(0, 250) + '...' : content; - - return ` -
-
-

- ${item.title || 'No title'} -

-
- ${sentimentEmoji} - - ${sentimentLabel} - -
-
- - ${contentPreview ? ` -

- ${contentPreview} -

- ` : ''} - -
-
-
- 📰 - - ${item.source || 'Unknown Source'} - -
- - ${sentimentConfidence > 0 ? ` -
- 🎯 - - ${(sentimentConfidence * 100).toFixed(0)}% confidence - -
- ` : ''} - -
- 🕒 - - ${publishedTime} - -
- - ${item.related_symbols && Array.isArray(item.related_symbols) && item.related_symbols.length > 0 ? ` -
- 💰 -
- ${item.related_symbols.slice(0, 3).map(symbol => ` - - ${symbol} - - `).join('')} - ${item.related_symbols.length > 3 ? `+${item.related_symbols.length - 3}` : ''} -
-
- ` : ''} -
- - ${item.url ? ` - - Read More → - - ` : ''} -
-
- `; - }).join('')} -
-
- - Showing ${newsItems.length} article${newsItems.length !== 1 ? 's' : ''} • - Last updated: ${new Date().toLocaleTimeString('en-US')} - -
- `; - } else { - document.getElementById('news-list').innerHTML = ` -
-
📰
-
No news articles found
-
- News articles will appear here once they are analyzed and stored in the database. -
-
- `; - } - } catch (error) { - console.error('Error loading news:', error); - showError('Error loading news'); - document.getElementById('news-list').innerHTML = ` -
-
-
Error loading news
-
- ${error.message || 'Failed to fetch news articles. Please try again later.'} -
-
- `; - } -} - -// Load Providers -async function loadProviders() { - // Show loading state - const providersDiv = document.getElementById('providers-list'); - if (providersDiv) { - providersDiv.innerHTML = '
Loading providers...
'; - } - - try { - // Load providers and auto-discovery health summary in parallel - const [providersRes, healthRes] = await Promise.all([ - fetch('/api/providers'), - fetch('/api/providers/health-summary').catch(() => null) // Optional - ]); - - const providersData = await providersRes.json(); - const providers = providersData.providers || providersData || []; - - // Update providers list - const providersDiv = document.getElementById('providers-list'); - if (providersDiv) { - if (providers.length > 0) { - providersDiv.innerHTML = ` -
- - - - - - - - - - - - - ${providers.map(provider => { - const status = provider.status || 'unknown'; - const statusConfig = { - 'VALID': { color: 'var(--success)', bg: 'rgba(16, 185, 129, 0.2)', text: '✅ Valid' }, - 'validated': { color: 'var(--success)', bg: 'rgba(16, 185, 129, 0.2)', text: '✅ Valid' }, - 'available': { color: 'var(--success)', bg: 'rgba(16, 185, 129, 0.2)', text: '✅ Available' }, - 'online': { color: 'var(--success)', bg: 'rgba(16, 185, 129, 0.2)', text: '✅ Online' }, - 'CONDITIONALLY_AVAILABLE': { color: 'var(--warning)', bg: 'rgba(245, 158, 11, 0.2)', text: '⚠️ Conditional' }, - 'INVALID': { color: 'var(--danger)', bg: 'rgba(239, 68, 68, 0.2)', text: '❌ Invalid' }, - 'unvalidated': { color: 'var(--warning)', bg: 'rgba(245, 158, 11, 0.2)', text: '⚠️ Unvalidated' }, - 'not_loaded': { color: 'var(--warning)', bg: 'rgba(245, 158, 11, 0.2)', text: '⚠️ Not Loaded' }, - 'offline': { color: 'var(--danger)', bg: 'rgba(239, 68, 68, 0.2)', text: '❌ Offline' }, - 'degraded': { color: 'var(--warning)', bg: 'rgba(245, 158, 11, 0.2)', text: '⚠️ Degraded' } - }; - const statusInfo = statusConfig[status] || { color: 'var(--text-secondary)', bg: 'rgba(156, 163, 175, 0.2)', text: '❓ Unknown' }; - - return ` - - - - - - - - - `; - }).join('')} - -
IDNameCategoryTypeStatusDetails
${provider.provider_id || provider.id || '-'}${provider.name || 'Unknown'}${provider.category || '-'}${provider.type || '-'} - - ${statusInfo.text} - - - ${provider.response_time_ms ? `${provider.response_time_ms}ms` : ''} - ${provider.endpoint ? `🔗` : ''} - ${provider.error_reason ? `⚠️` : ''} -
-
-
- Total Providers: ${providersData.total || providers.length} -
- `; - } else { - providersDiv.innerHTML = '
No providers found
'; - } - } - - // Update health summary if available - if (healthRes) { - try { - const healthData = await healthRes.json(); - const healthSummaryDiv = document.getElementById('providers-health-summary'); - if (healthSummaryDiv && healthData.ok && healthData.summary) { - const summary = healthData.summary; - healthSummaryDiv.innerHTML = ` -
-

Provider Health Summary

-
-
-
${summary.total_active_providers || 0}
-
Total Active
-
-
-
${summary.http_valid || 0}
-
HTTP Valid
-
-
-
${summary.http_invalid || 0}
-
HTTP Invalid
-
-
-
${summary.http_conditional || 0}
-
Conditional
-
-
-
- `; - } - } catch (e) { - console.warn('Could not load health summary:', e); - } - } - - } catch (error) { - console.error('Error loading providers:', error); - showError('Error loading providers'); - const providersDiv = document.getElementById('providers-list'); - if (providersDiv) { - providersDiv.innerHTML = '
Error loading providers
'; - } - } -} - -// Search Resources -async function searchResources() { - const query = document.getElementById('search-resources').value; - if (!query.trim()) { - showError('Please enter a search query'); - return; - } - - const resultsDiv = document.getElementById('search-results'); - resultsDiv.innerHTML = '
Searching...
'; - - try { - const response = await fetch(`/api/resources/search?q=${encodeURIComponent(query)}`); - const data = await response.json(); - - if (data.success && data.resources && data.resources.length > 0) { - resultsDiv.innerHTML = ` -
-
- ${data.count || data.resources.length} result(s) found -
-
- ${data.resources.map(resource => ` -
-
-
- ${resource.name || 'Unknown'} -
- Category: ${resource.category || 'N/A'} -
- ${resource.base_url ? `
- ${resource.base_url} -
` : ''} -
- ${resource.free !== undefined ? ` - - ${resource.free ? '🆓 Free' : '💰 Paid'} - - ` : ''} -
-
- `).join('')} -
-
- `; - } else { - resultsDiv.innerHTML = '
No results found
'; - } - } catch (error) { - console.error('Search error:', error); - resultsDiv.innerHTML = '
Search error
'; - showError('Search error'); - } -} - -// Load Diagnostics -async function loadDiagnostics() { - try { - // Load system status - try { - const statusRes = await fetch('/api/status'); - const statusData = await statusRes.json(); - - const statusDiv = document.getElementById('diagnostics-status'); - const health = statusData.system_health || 'unknown'; - const healthClass = health === 'healthy' ? 'alert-success' : - health === 'degraded' ? 'alert-warning' : 'alert-error'; - - statusDiv.innerHTML = ` -
-

System Status

-
-
Overall Status: ${health}
-
Total APIs: ${statusData.total_apis || 0}
-
Online: ${statusData.online || 0}
-
Degraded: ${statusData.degraded || 0}
-
Offline: ${statusData.offline || 0}
-
Avg Response Time: ${statusData.avg_response_time_ms || 0}ms
- ${statusData.last_update ? `
Last Update: ${new Date(statusData.last_update).toLocaleString('en-US')}
` : ''} -
-
- `; - } catch (statusError) { - document.getElementById('diagnostics-status').innerHTML = '
Error loading system status
'; - } - - // Load error logs - try { - const errorsRes = await fetch('/api/logs/errors'); - const errorsData = await errorsRes.json(); - - const errors = errorsData.errors || errorsData.error_logs || []; - const errorsDiv = document.getElementById('error-logs'); - - if (errors.length > 0) { - errorsDiv.innerHTML = ` -
- ${errors.slice(0, 10).map(error => ` -
-
- ${error.message || error.error_message || error.type || 'Error'} -
- ${error.error_type ? `
Type: ${error.error_type}
` : ''} - ${error.provider ? `
Provider: ${error.provider}
` : ''} -
- ${error.timestamp ? new Date(error.timestamp).toLocaleString('en-US') : ''} -
-
- `).join('')} -
- ${errors.length > 10 ? `
- Showing ${Math.min(10, errors.length)} of ${errors.length} errors -
` : ''} - `; - } else { - errorsDiv.innerHTML = '
No errors found ✅
'; - } - } catch (errorsError) { - document.getElementById('error-logs').innerHTML = '
Error loading error logs
'; - } - - // Load recent logs - try { - const logsRes = await fetch('/api/logs/recent'); - const logsData = await logsRes.json(); - - const logs = logsData.logs || logsData.recent || []; - const logsDiv = document.getElementById('recent-logs'); - - if (logs.length > 0) { - logsDiv.innerHTML = ` -
- ${logs.slice(0, 20).map(log => { - const level = log.level || log.status || 'info'; - const levelColor = level === 'ERROR' ? 'var(--danger)' : - level === 'WARNING' ? 'var(--warning)' : - 'var(--text-secondary)'; - - return ` -
-
-
- ${level} -
-
- ${log.timestamp ? new Date(log.timestamp).toLocaleString('en-US') : ''} -
-
-
- ${log.message || log.content || JSON.stringify(log)} -
- ${log.provider ? `
Provider: ${log.provider}
` : ''} -
- `; - }).join('')} -
- `; - } else { - logsDiv.innerHTML = '
No logs found
'; - } - } catch (logsError) { - document.getElementById('recent-logs').innerHTML = '
Error loading logs
'; - } - } catch (error) { - console.error('Error loading diagnostics:', error); - showError('Error loading diagnostics'); - } -} - -// Run Diagnostics -async function runDiagnostics() { - try { - const response = await fetch('/api/diagnostics/run', { method: 'POST' }); - const data = await response.json(); - - if (data.success) { - showSuccess('Diagnostics completed successfully'); - setTimeout(loadDiagnostics, 1000); - } else { - showError(data.error || 'Error running diagnostics'); - } - } catch (error) { - showError('Error running diagnostics: ' + error.message); - } -} - -// Load Health Diagnostics -async function loadHealthDiagnostics() { - const resultDiv = document.getElementById('health-diagnostics-result'); - resultDiv.innerHTML = '
Loading health data...
'; - - try { - const response = await fetch('/api/diagnostics/health'); - const data = await response.json(); - - if (data.status !== 'success') { - resultDiv.innerHTML = ` -
- Error: ${data.error || 'Failed to load health diagnostics'} -
- `; - return; - } - - const providerSummary = data.providers.summary; - const modelSummary = data.models.summary; - const providerEntries = data.providers.entries || []; - const modelEntries = data.models.entries || []; - - // Helper function to get status color - const getStatusColor = (status) => { - switch (status) { - case 'healthy': return 'var(--success)'; - case 'degraded': return 'var(--warning)'; - case 'unavailable': return 'var(--danger)'; - default: return 'var(--text-secondary)'; - } - }; - - // Helper function to get status badge - const getStatusBadge = (status, inCooldown) => { - const color = getStatusColor(status); - const icon = status === 'healthy' ? '✅' : - status === 'degraded' ? '⚠️' : - status === 'unavailable' ? '❌' : '❓'; - const cooldownText = inCooldown ? ' (cooldown)' : ''; - return `${icon} ${status}${cooldownText}`; - }; - - resultDiv.innerHTML = ` -
- -
-
-
- ${providerSummary.total} -
-
Total Providers
-
- ✅ ${providerSummary.healthy} - ⚠️ ${providerSummary.degraded} - ❌ ${providerSummary.unavailable} -
-
- -
-
- ${modelSummary.total} -
-
Total Models
-
- ✅ ${modelSummary.healthy} - ⚠️ ${modelSummary.degraded} - ❌ ${modelSummary.unavailable} -
-
- -
-
- ${data.overall_health.providers_ok && data.overall_health.models_ok ? '💚' : '⚠️'} -
-
Overall Health
-
- ${data.overall_health.providers_ok && data.overall_health.models_ok ? 'HEALTHY' : 'DEGRADED'} -
-
-
- - - ${providerEntries.length > 0 ? ` -
-
-

🔌 Provider Health (${providerEntries.length})

-
-
- ${providerEntries.map(provider => ` -
-
-
${provider.name}
- ${getStatusBadge(provider.status, provider.in_cooldown)} -
-
-
Errors: ${provider.error_count} | Successes: ${provider.success_count}
- ${provider.last_success ? `
Last Success: ${new Date(provider.last_success * 1000).toLocaleString()}
` : ''} - ${provider.last_error ? `
Last Error: ${new Date(provider.last_error * 1000).toLocaleString()}
` : ''} - ${provider.last_error_message ? `
Error: ${provider.last_error_message.substring(0, 100)}${provider.last_error_message.length > 100 ? '...' : ''}
` : ''} -
-
- `).join('')} -
-
- ` : '
No provider health data available yet
'} - - - ${modelEntries.length > 0 ? ` -
-
-

🤖 Model Health (${modelEntries.length})

- -
-
- ${modelEntries.filter(m => m.loaded || m.status !== 'unknown').slice(0, 20).map(model => ` -
-
-
-
${model.model_id}
-
${model.key} • ${model.category}
-
-
- ${getStatusBadge(model.status, model.in_cooldown)} - ${model.status === 'unavailable' && !model.in_cooldown ? `` : ''} -
-
-
-
Errors: ${model.error_count} | Successes: ${model.success_count} | Loaded: ${model.loaded ? 'Yes' : 'No'}
- ${model.last_success ? `
Last Success: ${new Date(model.last_success * 1000).toLocaleString()}
` : ''} - ${model.last_error ? `
Last Error: ${new Date(model.last_error * 1000).toLocaleString()}
` : ''} - ${model.last_error_message ? `
Error: ${model.last_error_message.substring(0, 150)}${model.last_error_message.length > 150 ? '...' : ''}
` : ''} -
-
- `).join('')} -
-
- ` : '
No model health data available yet
'} - -
- Last updated: ${new Date(data.timestamp).toLocaleString()} -
-
- `; - - } catch (error) { - console.error('Error loading health diagnostics:', error); - resultDiv.innerHTML = ` -
- Error: ${error.message || 'Failed to load health diagnostics'} -
- `; - } -} - -// Trigger self-heal for all failed models -async function triggerSelfHeal() { - try { - const response = await fetch('/api/diagnostics/self-heal', { method: 'POST' }); - const data = await response.json(); - - if (data.status === 'completed') { - const summary = data.summary; - showSuccess(`Self-heal completed: ${summary.successful}/${summary.total_attempts} successful`); - // Reload health after a short delay - setTimeout(loadHealthDiagnostics, 2000); - } else { - showError(data.error || 'Self-heal failed'); - } - } catch (error) { - showError('Error triggering self-heal: ' + error.message); - } -} - -// Reinitialize specific model -async function reinitModel(modelKey) { - try { - const response = await fetch(`/api/diagnostics/self-heal?model_key=${encodeURIComponent(modelKey)}`, { - method: 'POST' - }); - const data = await response.json(); - - if (data.status === 'completed' && data.results && data.results.length > 0) { - const result = data.results[0]; - if (result.status === 'success') { - showSuccess(`Model ${modelKey} reinitialized successfully`); - } else { - showError(`Failed to reinit ${modelKey}: ${result.message || result.error || 'Unknown error'}`); - } - // Reload health after a short delay - setTimeout(loadHealthDiagnostics, 1500); - } else { - showError(data.error || 'Reinitialization failed'); - } - } catch (error) { - showError('Error reinitializing model: ' + error.message); - } -} - -// Test API -async function testAPI() { - const endpoint = document.getElementById('api-endpoint').value; - const method = document.getElementById('api-method').value; - const bodyText = document.getElementById('api-body').value; - - if (!endpoint) { - showError('Please select an endpoint'); - return; - } - - const resultDiv = document.getElementById('api-result'); - resultDiv.innerHTML = '
Sending request...
'; - - try { - const options = { method }; - - // Parse body if provided - let body = null; - if (method === 'POST' && bodyText) { - try { - body = JSON.parse(bodyText); - options.headers = { 'Content-Type': 'application/json' }; - } catch (e) { - showError('Invalid JSON in body'); - resultDiv.innerHTML = '
JSON parsing error
'; - return; - } - } - - if (body) { - options.body = JSON.stringify(body); - } - - const startTime = Date.now(); - const response = await fetch(endpoint, options); - const responseTime = Date.now() - startTime; - - let data; - const contentType = response.headers.get('content-type'); - - if (contentType && contentType.includes('application/json')) { - data = await response.json(); - } else { - data = { text: await response.text() }; - } - - const statusClass = response.ok ? 'alert-success' : 'alert-error'; - const statusEmoji = response.ok ? '✅' : '❌'; - - resultDiv.innerHTML = ` -
-
-
-
- ${statusEmoji} Status: ${response.status} ${response.statusText} -
-
- Response Time: ${responseTime}ms -
-
-
-
-

Response:

-
${JSON.stringify(data, null, 2)}
-
-
- Endpoint: ${method} ${endpoint} -
-
- `; - } catch (error) { - resultDiv.innerHTML = ` -
-

Error:

-

${error.message}

-
- `; - showError('API test error: ' + error.message); - } -} - -// Utility Functions -function showError(message) { - console.error(message); - ToastManager.error(message); -} - -function showSuccess(message) { - console.log(message); - ToastManager.success(message); -} - -// Additional tab loaders for HTML tabs -async function loadMonitorData() { - // Load API monitor data - try { - const response = await fetch('/api/status'); - const data = await response.json(); - const monitorContainer = document.getElementById('monitor-content'); - if (monitorContainer) { - monitorContainer.innerHTML = ` -
-

API Status

-
${JSON.stringify(data, null, 2)}
-
- `; - } - } catch (error) { - console.error('Error loading monitor data:', error); - } -} - -async function loadAdvancedData() { - // Load advanced/API explorer data - loadAPIEndpoints(); - loadDiagnostics(); -} - -async function loadAdminData() { - // Load admin panel data - try { - const [providersRes, modelsRes] = await Promise.all([ - fetch('/api/providers'), - fetch('/api/models/status') - ]); - const providers = await providersRes.json(); - const models = await modelsRes.json(); - - const adminContainer = document.getElementById('admin-content'); - if (adminContainer) { - adminContainer.innerHTML = ` -
-

System Status

-

Providers: ${providers.total || 0}

-

Models: ${models.models_loaded || 0} loaded

-
- `; - } - } catch (error) { - console.error('Error loading admin data:', error); +function saveSettings() { + ToastManager.success('Settings saved successfully!'); +} + +function toggleTheme() { + document.body.classList.toggle('light-theme'); + const themeSelect = document.getElementById('theme-select'); + if (themeSelect) { + themeSelect.value = document.body.classList.contains('light-theme') ? 'light' : 'dark'; } } -async function loadHFHealth() { - // Load HF models health status - try { - const response = await fetch('/api/models/status'); - const data = await response.json(); - const hfContainer = document.getElementById('hf-status'); - if (hfContainer) { - hfContainer.innerHTML = ` -
-

HF Models Status

-

Mode: ${data.hf_mode || 'unknown'}

-

Loaded: ${data.models_loaded || 0}

-

Failed: ${data.failed_count || 0}

-

Status: ${data.status || 'unknown'}

-
- `; - } - } catch (error) { - console.error('Error loading HF health:', error); +function changeTheme(theme) { + if (theme === 'light') { + document.body.classList.add('light-theme'); + } else { + document.body.classList.remove('light-theme'); } } -async function loadPools() { - // Load provider pools - try { - const response = await fetch('/api/pools'); - const data = await response.json(); - const poolsContainer = document.getElementById('pools-content'); - if (poolsContainer) { - poolsContainer.innerHTML = ` -
-

Provider Pools

-

${data.message || 'No pools available'}

-
${JSON.stringify(data, null, 2)}
-
- `; - } - } catch (error) { - console.error('Error loading pools:', error); - } -} +// ============================================================================= +// Sentiment Analysis Functions with Visualizations +// ============================================================================= -async function loadLogs() { - // Load recent logs - try { - const response = await fetch('/api/logs/recent'); - const data = await response.json(); - const logsContainer = document.getElementById('logs-content'); - if (logsContainer) { - const logsHtml = data.logs && data.logs.length > 0 - ? data.logs.map(log => `
${JSON.stringify(log)}
`).join('') - : '

No logs available

'; - logsContainer.innerHTML = `

Recent Logs

${logsHtml}
`; - } - } catch (error) { - console.error('Error loading logs:', error); +// Create sentiment gauge chart +function createSentimentGauge(containerId, sentimentValue, sentimentClass) { + const container = document.getElementById(containerId); + if (!container) return null; + + // Clear previous chart + container.innerHTML = ''; + + // Create canvas + const canvas = document.createElement('canvas'); + canvas.id = `gauge-${containerId}`; + canvas.width = 300; + canvas.height = 150; + container.appendChild(canvas); + + // Calculate gauge value (0-100, where 50 is neutral) + let gaugeValue = 50; // neutral + if (sentimentClass === 'bullish' || sentimentClass === 'positive') { + gaugeValue = 50 + (sentimentValue * 50); // 50-100 + } else if (sentimentClass === 'bearish' || sentimentClass === 'negative') { + gaugeValue = 50 - (sentimentValue * 50); // 0-50 } + gaugeValue = Math.max(0, Math.min(100, gaugeValue)); + + const ctx = canvas.getContext('2d'); + const centerX = canvas.width / 2; + const centerY = canvas.height / 2; + const radius = 60; + + // Draw gauge background (semi-circle) + ctx.beginPath(); + ctx.arc(centerX, centerY + 20, radius, Math.PI, 0, false); + ctx.lineWidth = 20; + ctx.strokeStyle = 'rgba(31, 41, 55, 0.6)'; + ctx.stroke(); + + // Draw gauge fill + const startAngle = Math.PI; + const endAngle = Math.PI + (Math.PI * (gaugeValue / 100)); + + ctx.beginPath(); + ctx.arc(centerX, centerY + 20, radius, startAngle, endAngle, false); + ctx.lineWidth = 20; + ctx.lineCap = 'round'; + + let gaugeColor; + if (gaugeValue >= 70) gaugeColor = '#10b981'; // green + else if (gaugeValue >= 50) gaugeColor = '#3b82f6'; // blue + else if (gaugeValue >= 30) gaugeColor = '#f59e0b'; // yellow + else gaugeColor = '#ef4444'; // red + + ctx.strokeStyle = gaugeColor; + ctx.stroke(); + + // Draw value text + ctx.fillStyle = '#f9fafb'; + ctx.font = 'bold 32px Inter, sans-serif'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(Math.round(gaugeValue), centerX, centerY + 15); + + // Draw labels + ctx.fillStyle = '#9ca3af'; + ctx.font = '12px Inter, sans-serif'; + ctx.textAlign = 'left'; + ctx.fillText('Bearish', 20, centerY + 50); + ctx.textAlign = 'right'; + ctx.fillText('Bullish', canvas.width - 20, centerY + 50); + + return canvas; } -async function loadReports() { - // Load reports/analytics - try { - const response = await fetch('/api/providers/health-summary'); - const data = await response.json(); - const reportsContainer = document.getElementById('reports-content'); - if (reportsContainer) { - reportsContainer.innerHTML = ` -
-

Provider Health Report

-
${JSON.stringify(data, null, 2)}
-
- `; - } - } catch (error) { - console.error('Error loading reports:', error); - } +// Get trend arrow SVG +function getTrendArrow(sentimentClass) { + const color = sentimentClass === 'bullish' ? 'var(--success)' : + sentimentClass === 'bearish' ? 'var(--danger)' : 'var(--warning)'; + const rotation = sentimentClass === 'bearish' ? 'rotate(180deg)' : + sentimentClass === 'neutral' ? 'rotate(90deg)' : ''; + + return ` + + + + `; } -async function loadResources() { - // Load resources summary - try { - const response = await fetch('/api/resources'); - const data = await response.json(); - const resourcesContainer = document.getElementById('resources-summary'); - if (resourcesContainer) { - const summary = data.summary || {}; - resourcesContainer.innerHTML = ` -
-

Resources Summary

-

Total: ${summary.total_resources || 0}

-

Free: ${summary.free_resources || 0}

-

Models: ${summary.models_available || 0}

-
- `; - } - } catch (error) { - console.error('Error loading resources:', error); - } +// Create confidence bar +function createConfidenceBar(confidence) { + const confidencePercent = Math.round(confidence * 100); + return ` +
+
+ Model Confidence + ${confidencePercent}% +
+
+
+
+
+ `; } -async function loadAPIRegistry() { - // Load API registry from all_apis_merged_2025.json +async function analyzeGlobalSentiment() { + ToastManager.info('Analyzing global market sentiment...'); + const resultDiv = document.getElementById('global-sentiment-result'); + if (resultDiv) { + resultDiv.innerHTML = '
Analyzing...
'; + } + try { - const response = await fetch('/api/resources/apis'); + const response = await fetch('/api/sentiment/analyze', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + text: 'Overall cryptocurrency market sentiment analysis', + mode: 'crypto' + }) + }); + + if (!response.ok) throw new Error(`API returned ${response.status}`); + const data = await response.json(); - if (!data.ok) { - console.warn('API registry not available:', data.error); - const registryContainer = document.getElementById('api-registry-section'); - if (registryContainer) { - registryContainer.innerHTML = ` -
-
📚
-
API Registry Not Available
-
- ${data.error || 'API registry file not found'} -
-
- `; - } - return; - } + if (data.available && data.sentiment) { + const sentiment = data.sentiment.toUpperCase(); + const confidence = data.confidence || 0; + const sentimentClass = sentiment.includes('POSITIVE') || sentiment.includes('BULLISH') ? 'bullish' : + sentiment.includes('NEGATIVE') || sentiment.includes('BEARISH') ? 'bearish' : 'neutral'; - const registryContainer = document.getElementById('api-registry-section'); - if (registryContainer) { - const metadata = data.metadata || {}; - const categories = data.categories || []; - const rawFiles = data.raw_files_preview || []; - - registryContainer.innerHTML = ` -
-
-
-

- 📚 ${metadata.name || 'API Registry'} -

-

- ${metadata.description || 'Comprehensive API registry for cryptocurrency data sources'} -

-
-
-
Version
-
${metadata.version || 'N/A'}
-
+ resultDiv.innerHTML = ` +
+
+

Global Market Sentiment

+ ${sentiment}
- -
-
-
- ${categories.length} -
-
Categories
-
-
-
- ${data.total_raw_files || 0} -
-
Total Files
-
- ${metadata.created_at ? ` -
-
Created
-
- ${new Date(metadata.created_at).toLocaleDateString('en-US')} -
-
- ` : ''} +
+
+ ${getTrendArrow(sentimentClass)} + + ${sentiment} + + ${getTrendArrow(sentimentClass)}
- - ${categories.length > 0 ? ` -
-

- 📂 Categories -

-
- ${categories.map(cat => ` - - ${cat.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} - - `).join('')} -
-
- ` : ''} - - ${rawFiles.length > 0 ? ` -
-

- 📄 Sample Files (${rawFiles.length} of ${data.total_raw_files || 0}) -

-
- ${rawFiles.map(file => ` -
-
- ${file.filename || 'Unknown file'} -
-
- Size: ${file.size ? (file.size / 1024).toFixed(1) + ' KB' : file.full_size ? (file.full_size / 1024).toFixed(1) + ' KB' : 'N/A'} -
- ${file.preview ? ` -
${file.preview}
- ` : ''} -
- `).join('')} -
-
- ` : ''} -
- `; - } + ${createConfidenceBar(confidence)} +

+ Model: ${data.model || 'AI Sentiment Analysis'} | + Engine: ${data.engine || 'N/A'} +

+
+ `; - // Also update metadata container if it exists - const metadataContainer = document.getElementById('api-registry-metadata'); - if (metadataContainer) { - metadataContainer.innerHTML = ` -
-

Metadata

-
${JSON.stringify(metadata, null, 2)}
-
- `; + // Create gauge chart after DOM update + setTimeout(() => { + createSentimentGauge('global-sentiment-gauge', confidence, sentimentClass); + }, 100); + + ToastManager.success('Sentiment analysis complete!'); + } else { + resultDiv.innerHTML = '
Sentiment analysis unavailable
'; } } catch (error) { - console.error('Error loading API registry:', error); - const registryContainer = document.getElementById('api-registry-section'); - if (registryContainer) { - registryContainer.innerHTML = ` -
-
-
Error Loading API Registry
-
- ${error.message || 'Failed to load API registry data'} -
-
- `; - } - } -} - - - -// Theme Toggle -function toggleTheme() { - const body = document.body; - const themeIcon = document.querySelector('.theme-toggle i'); - - if (body.classList.contains('light-theme')) { - body.classList.remove('light-theme'); - themeIcon.className = 'fas fa-moon'; - localStorage.setItem('theme', 'dark'); - } else { - body.classList.add('light-theme'); - themeIcon.className = 'fas fa-sun'; - localStorage.setItem('theme', 'light'); + console.error('Error analyzing sentiment:', error); + resultDiv.innerHTML = `
Error: ${error.message}
`; + ToastManager.error('Failed to analyze sentiment'); } } -// Load theme preference -document.addEventListener('DOMContentLoaded', () => { - const savedTheme = localStorage.getItem('theme'); - if (savedTheme === 'light') { - document.body.classList.add('light-theme'); - const themeIcon = document.querySelector('.theme-toggle i'); - if (themeIcon) themeIcon.className = 'fas fa-sun'; - } -}); - -// Update header stats -function updateHeaderStats() { - const totalResources = document.getElementById('stat-total-resources')?.textContent || '-'; - const totalModels = document.getElementById('stat-models')?.textContent || '-'; - - const headerResources = document.getElementById('header-resources'); - const headerModels = document.getElementById('header-models'); +async function analyzeAssetSentiment() { + const symbol = document.getElementById('asset-symbol')?.value; + const text = document.getElementById('asset-sentiment-text')?.value; - if (headerResources) headerResources.textContent = totalResources; - if (headerModels) headerModels.textContent = totalModels; -} - -// Call updateHeaderStats after loading dashboard -const originalLoadDashboard = loadDashboard; -loadDashboard = async function() { - await originalLoadDashboard(); - updateHeaderStats(); -}; - -// ===== AI Analyst Functions ===== -async function runAIAnalyst() { - const prompt = document.getElementById('ai-analyst-prompt').value.trim(); - const mode = document.getElementById('ai-analyst-mode').value; - const maxLength = parseInt(document.getElementById('ai-analyst-max-length').value); - - if (!prompt) { - showError('Please enter a prompt or question'); + if (!symbol) { + ToastManager.warning('Please select a trading pair'); return; } - const resultDiv = document.getElementById('ai-analyst-result'); - resultDiv.innerHTML = '
Generating analysis...
'; + ToastManager.info('Analyzing asset sentiment...'); + const resultDiv = document.getElementById('asset-sentiment-result'); + if (resultDiv) { + resultDiv.innerHTML = '
Analyzing...
'; + } try { - const response = await fetch('/api/analyze/text', { + const response = await fetch('/api/sentiment/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - prompt: prompt, - mode: mode, - max_length: maxLength + body: JSON.stringify({ + text: text || `Sentiment analysis for ${symbol}`, + mode: 'crypto', + symbol: symbol }) }); + if (!response.ok) throw new Error(`API returned ${response.status}`); + const data = await response.json(); - if (!data.available) { + if (data.available && data.sentiment) { + const sentiment = data.sentiment.toUpperCase(); + const confidence = data.confidence || 0; + const sentimentClass = sentiment.includes('POSITIVE') || sentiment.includes('BULLISH') ? 'bullish' : + sentiment.includes('NEGATIVE') || sentiment.includes('BEARISH') ? 'bearish' : 'neutral'; + resultDiv.innerHTML = ` -
- ⚠️ Model Not Available: ${data.error || 'AI generation model is currently unavailable'} - ${data.note ? `
${data.note}` : ''} +
+
+

${symbol} Sentiment

+ ${sentiment} +
+
+
+ ${getTrendArrow(sentimentClass)} + + ${sentiment} + + ${getTrendArrow(sentimentClass)} +
+ ${createConfidenceBar(confidence)} +

+ Model: ${data.model || 'AI Sentiment Analysis'} | + Engine: ${data.engine || 'N/A'} +

`; - return; + + // Create gauge chart after DOM update + setTimeout(() => { + createSentimentGauge('asset-sentiment-gauge', confidence, sentimentClass); + }, 100); + + ToastManager.success('Asset sentiment analysis complete!'); + } else { + resultDiv.innerHTML = '
Sentiment analysis unavailable
'; } + } catch (error) { + console.error('Error analyzing asset sentiment:', error); + resultDiv.innerHTML = `
Error: ${error.message}
`; + ToastManager.error('Failed to analyze asset sentiment'); + } +} + +async function analyzeSentiment() { + const text = document.getElementById('sentiment-text')?.value; + const mode = document.getElementById('sentiment-mode')?.value || 'auto'; + + if (!text || text.trim() === '') { + ToastManager.warning('Please enter text to analyze'); + return; + } + + ToastManager.info('Analyzing sentiment...'); + const resultDiv = document.getElementById('sentiment-result'); + if (resultDiv) { + resultDiv.innerHTML = '
Analyzing...
'; + } + + try { + const response = await fetch('/api/sentiment/analyze', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text, mode }) + }); - if (!data.success) { - resultDiv.innerHTML = ` -
- ❌ Generation Failed: ${data.error || 'Failed to generate analysis'} -
- `; - return; - } + if (!response.ok) throw new Error(`API returned ${response.status}`); - const generatedText = data.text || ''; - const model = data.model || 'Unknown'; + const data = await response.json(); - resultDiv.innerHTML = ` -
-
-

✨ AI Generated Analysis

-
- -
-
- ${generatedText} + if (data.available && data.sentiment) { + const sentiment = data.sentiment.toUpperCase(); + const confidence = data.confidence || 0; + const sentimentClass = sentiment.includes('POSITIVE') || sentiment.includes('BULLISH') ? 'bullish' : + sentiment.includes('NEGATIVE') || sentiment.includes('BEARISH') ? 'bearish' : 'neutral'; + + resultDiv.innerHTML = ` +
+
+

Sentiment Analysis Result

+ ${sentiment}
-
- -
-
-
- Model: - ${model} -
-
- Mode: - ${mode} -
-
- Prompt: - "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}" -
-
- Timestamp: - ${new Date(data.timestamp).toLocaleString()} -
+
+
+ ${getTrendArrow(sentimentClass)} + + ${sentiment} + + ${getTrendArrow(sentimentClass)}
-
- -
- - -
+ ${createConfidenceBar(confidence)} +

+ Text: ${text.substring(0, 100)}${text.length > 100 ? '...' : ''}
+ Model: ${data.model || 'AI Sentiment Analysis'} | + Engine: ${data.engine || 'N/A'} +

`; - // Store for clipboard - window.lastAIAnalysis = generatedText; + // Create gauge chart after DOM update + setTimeout(() => { + createSentimentGauge('sentiment-gauge', confidence, sentimentClass); + }, 100); + ToastManager.success('Sentiment analysis complete!'); + } else { + resultDiv.innerHTML = '
Sentiment analysis unavailable
'; + } } catch (error) { - console.error('AI analyst error:', error); - resultDiv.innerHTML = `
Generation Error: ${error.message}
`; - showError('Error generating analysis'); - } -} - -function setAIAnalystPrompt(text) { - document.getElementById('ai-analyst-prompt').value = text; -} - -async function copyAIAnalystResult() { - if (!window.lastAIAnalysis) { - showError('No analysis to copy'); - return; - } - - try { - await navigator.clipboard.writeText(window.lastAIAnalysis); - showSuccess('Analysis copied to clipboard!'); - } catch (error) { - console.error('Failed to copy:', error); - showError('Failed to copy analysis'); + console.error('Error analyzing sentiment:', error); + resultDiv.innerHTML = `
Error: ${error.message}
`; + ToastManager.error('Failed to analyze sentiment'); } } -function clearAIAnalystForm() { - document.getElementById('ai-analyst-prompt').value = ''; - document.getElementById('ai-analyst-result').innerHTML = ''; - window.lastAIAnalysis = null; -} +// ============================================================================= +// Trading Assistant +// ============================================================================= -// ===== Trading Assistant Functions ===== async function runTradingAssistant() { - const symbol = document.getElementById('trading-symbol').value.trim().toUpperCase(); - const context = document.getElementById('trading-context').value.trim(); + const symbol = document.getElementById('trading-symbol')?.value; + const context = document.getElementById('trading-context')?.value; if (!symbol) { - showError('Please enter a trading symbol'); + ToastManager.warning('Please select a trading symbol'); return; } + ToastManager.info('Generating trading signal...'); const resultDiv = document.getElementById('trading-assistant-result'); - resultDiv.innerHTML = '
Analyzing and generating trading signal...
'; + if (resultDiv) { + resultDiv.innerHTML = '
Analyzing...
'; + } try { const response = await fetch('/api/trading/decision', { @@ -2489,151 +1252,84 @@ async function runTradingAssistant() { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ symbol: symbol, - context: context + context: context || `Trading decision for ${symbol}` }) }); - const data = await response.json(); + if (!response.ok) throw new Error(`API returned ${response.status}`); - if (!data.available) { - resultDiv.innerHTML = ` -
- ⚠️ Model Not Available: ${data.error || 'Trading signal model is currently unavailable'} - ${data.note ? `
${data.note}` : ''} -
- `; - return; - } + const data = await response.json(); - if (!data.success) { + if (data.decision) { + const decision = data.decision.toUpperCase(); + const confidence = data.confidence ? (data.confidence * 100).toFixed(2) : 'N/A'; + const decisionClass = decision === 'BUY' ? 'bullish' : decision === 'SELL' ? 'bearish' : 'neutral'; + resultDiv.innerHTML = ` -
- ❌ Analysis Failed: ${data.error || 'Failed to generate trading signal'} -
- `; - return; - } - - const decision = data.decision || 'HOLD'; - const confidence = data.confidence || 0; - const rationale = data.rationale || ''; - const model = data.model || 'Unknown'; - - // Determine colors and icons based on decision - let decisionColor, decisionBg, decisionIcon; - if (decision === 'BUY') { - decisionColor = 'var(--success)'; - decisionBg = 'rgba(16, 185, 129, 0.2)'; - decisionIcon = '📈'; - } else if (decision === 'SELL') { - decisionColor = 'var(--danger)'; - decisionBg = 'rgba(239, 68, 68, 0.2)'; - decisionIcon = '📉'; - } else { - decisionColor = 'var(--text-secondary)'; - decisionBg = 'rgba(156, 163, 175, 0.2)'; - decisionIcon = '➡️'; - } - - resultDiv.innerHTML = ` -
-

🎯 Trading Signal for ${symbol}

- -
-
-
${decisionIcon}
-
- ${decision} -
-
- Decision -
-
- -
-
- ${(confidence * 100).toFixed(0)}% -
-
- Confidence -
-
+
+
+

${symbol} Trading Signal

+ ${decision}
- -
- AI Rationale: -

- ${rationale} -

-
- - ${context ? ` -
- Your Context: -
- "${context.substring(0, 200)}${context.length > 200 ? '...' : ''}" +
+
+
${confidence}%
+
Confidence
-
- ` : ''} - -
-
-
- Model: - ${model}
-
- Timestamp: - ${new Date(data.timestamp).toLocaleString()} -
-
-
- -
- ⚠️ Reminder: -

- This is an AI-generated signal for informational purposes only. Always do your own research and consider multiple factors before trading. + ${data.reasoning ? `

Reasoning: ${data.reasoning}

` : ''} +

+ Model: ${data.model || 'AI Trading Assistant'}

-
`; - + ToastManager.success('Trading signal generated!'); + } else { + resultDiv.innerHTML = '
Trading signal unavailable
'; + } } catch (error) { - console.error('Trading assistant error:', error); - resultDiv.innerHTML = `
Analysis Error: ${error.message}
`; - showError('Error generating trading signal'); + console.error('Error generating trading signal:', error); + resultDiv.innerHTML = `
Error: ${error.message}
`; + ToastManager.error('Failed to generate trading signal'); } } -// Initialize trading pair selector for trading assistant tab -function initTradingSymbolSelector() { - const tradingSymbolContainer = document.getElementById('trading-symbol-container'); - if (tradingSymbolContainer && window.TradingPairsLoader) { - const pairs = window.TradingPairsLoader.getTradingPairs(); - if (pairs && pairs.length > 0) { - tradingSymbolContainer.innerHTML = window.TradingPairsLoader.createTradingPairCombobox( - 'trading-symbol', - 'Select or type trading pair', - 'BTCUSDT' - ); - } - } +// ============================================================================= +// Utility Functions +// ============================================================================= + +function formatNumber(num) { + if (num === null || num === undefined) return '0'; + if (num >= 1e12) return (num / 1e12).toFixed(2) + 'T'; + if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B'; + if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'; + if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K'; + return num.toFixed(2); } -// Update loadTabData to handle new tabs -const originalLoadTabData = loadTabData; -loadTabData = function(tabId) { - originalLoadTabData(tabId); - - // Additional handlers for new tabs - if (tabId === 'ai-analyst') { - // No initialization needed for AI Analyst yet - } else if (tabId === 'trading-assistant') { - initTradingSymbolSelector(); - } -}; +// ============================================================================= +// Export for global access +// ============================================================================= -// Listen for trading pairs loaded event to initialize trading symbol selector -document.addEventListener('tradingPairsLoaded', function(e) { - initTradingSymbolSelector(); -}); +window.AppState = AppState; +window.ToastManager = ToastManager; +window.toggleSidebar = toggleSidebar; +window.switchTab = switchTab; +window.refreshCurrentTab = refreshCurrentTab; +window.loadDashboard = loadDashboard; +window.loadMarketData = loadMarketData; +window.loadModels = loadModels; +window.initializeModels = initializeModels; +window.loadNews = loadNews; +window.fetchNewsFromAPI = fetchNewsFromAPI; +window.loadSettings = loadSettings; +window.saveSettings = saveSettings; +window.toggleTheme = toggleTheme; +window.changeTheme = changeTheme; +window.analyzeGlobalSentiment = analyzeGlobalSentiment; +window.analyzeAssetSentiment = analyzeAssetSentiment; +window.analyzeSentiment = analyzeSentiment; +window.runTradingAssistant = runTradingAssistant; +window.formatNumber = formatNumber; + +console.log('✅ App.js loaded successfully');