// Crypto Intelligence Hub - Main JavaScript with Sidebar Navigation // Enhanced Pro Trading Terminal UI // ============================================================================= // Console Warning Filter (suppress external server warnings) // ============================================================================= (function() { const originalWarn = console.warn; const originalError = console.error; console.warn = function(...args) { const message = args.join(' '); // Suppress Permissions-Policy warnings from external servers (e.g., Hugging Face) if (message.includes('Unrecognized feature:') && (message.includes('ambient-light-sensor') || message.includes('battery') || message.includes('document-domain') || message.includes('layout-animations') || message.includes('legacy-image-formats') || message.includes('oversized-images') || message.includes('vr') || message.includes('wake-lock'))) { return; // Suppress these warnings } originalWarn.apply(console, args); }; console.error = function(...args) { const message = args.join(' '); // Suppress Hugging Face Spaces SSE errors (only relevant for HF deployment) if (message.includes('/api/spaces/') && message.includes('/events') || message.includes('Failed to fetch Space status') || message.includes('SSE Stream ended') || message.includes('ERR_HTTP2_PROTOCOL_ERROR')) { return; // Suppress these errors (not relevant for local deployment) } originalError.apply(console, args); }; })(); // ============================================================================= // Toast Notification System (use the one from toast.js) // ============================================================================= // Helper function to get toast manager (loaded from toast.js) function getToast() { return window.toastManager || window.toast || { init() {}, show(msg, type) { console.log(`Toast: ${type} - ${msg}`); }, success(msg) { this.show(msg, 'success'); }, error(msg) { this.show(msg, 'error'); }, warning(msg) { this.show(msg, 'warning'); }, info(msg) { this.show(msg, 'info'); } }; } // ============================================================================= // Global State // ============================================================================= const AppState = { currentTab: 'dashboard', data: {}, charts: {}, isLoading: false, sidebarOpen: false }; // ============================================================================= // 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' }, 'diagnostics': { title: 'Test & Diagnostics', subtitle: 'System Diagnostics & Model Testing' }, 'ai-tools': { title: 'AI Design Tools', subtitle: 'AI-Powered Tools & Utilities' }, 'providers': { title: 'Providers', subtitle: 'Provider Management' }, 'resources': { title: 'Resources', subtitle: 'Resource Management' }, 'defi': { title: 'DeFi Analytics', subtitle: 'DeFi Protocol Analytics' }, 'system-status': { title: 'System Status', subtitle: 'System Health Monitoring' } }; 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', () => { console.log('πŸš€ Initializing Crypto Intelligence Hub...'); // Initialize toast manager getToast().init(); // Check API status checkAPIStatus(); // Load initial dashboard immediately setTimeout(() => { loadDashboard(); }, 100); // Auto-refresh every 30 seconds setInterval(() => { if (AppState.currentTab === 'dashboard') { loadDashboard(); } }, 30000); // Listen for trading pairs loaded event document.addEventListener('tradingPairsLoaded', function(e) { console.log('Trading pairs loaded:', e.detail.pairs.length); initTradingPairSelectors(); }); console.log('βœ… App initialized successfully'); }); // Initialize trading pair selectors after pairs are loaded function initTradingPairSelectors() { // Initialize asset symbol selector const assetSymbolContainer = document.getElementById('asset-symbol-container'); if (assetSymbolContainer && window.TradingPairsLoader) { const pairs = window.TradingPairsLoader.getTradingPairs(); if (pairs && pairs.length > 0) { assetSymbolContainer.innerHTML = window.TradingPairsLoader.createTradingPairCombobox( 'asset-symbol', 'Select or type trading pair', 'BTCUSDT' ); } } // 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 Data Loading // ============================================================================= function loadTabData(tabId) { console.log(`Loading data for tab: ${tabId}`); switch(tabId) { case 'dashboard': loadDashboard(); break; case 'market': loadMarketData(); break; case 'models': loadModels(); break; case 'sentiment': // Sentiment tab is interactive, no auto-load needed break; case 'trading-assistant': // Trading assistant tab is interactive, no auto-load needed break; case 'news': loadNews(); break; case 'settings': loadSettings(); break; case 'diagnostics': refreshDiagnosticStatus(); break; case 'ai-tools': loadAITools(); break; default: console.log('No specific loader for tab:', tabId); } } function refreshCurrentTab() { loadTabData(AppState.currentTab); getToast().success('Data refreshed successfully'); } // ============================================================================= // API Status Check // ============================================================================= async function checkAPIStatus() { try { const response = await fetch('/health'); const data = await response.json(); const statusIndicator = document.getElementById('sidebar-status'); if (statusIndicator) { if (data.status === 'healthy') { 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 { 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) { 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)'; } } } // ============================================================================= // Dashboard Loading // ============================================================================= async function loadDashboard() { console.log('πŸ“Š Loading dashboard...'); // Show loading state const statsElements = [ 'stat-total-resources', 'stat-free-resources', 'stat-models', 'stat-providers' ]; statsElements.forEach(id => { const el = document.getElementById(id); if (el) el.textContent = '...'; }); const systemStatusDiv = document.getElementById('system-status'); if (systemStatusDiv) { systemStatusDiv.innerHTML = '
Loading system status...
'; } try { // Load resources summary (use the correct endpoint) const resourcesRes = await fetch('/api/resources/summary'); if (!resourcesRes.ok) { throw new Error(`Resources API returned ${resourcesRes.status}`); } let resourcesData = await resourcesRes.json(); console.log('Resources data:', resourcesData); // Check if data is an array (unexpected format - might be from wrong endpoint) if (Array.isArray(resourcesData)) { // Try to extract summary from array if it contains objects with summary property const summaryObj = resourcesData.find(item => item && typeof item === 'object' && !Array.isArray(item) && item.summary); if (summaryObj && summaryObj.summary) { resourcesData = summaryObj; console.log('Extracted summary from array response'); } else { // Fallback: use array length as total resources estimate const totalResources = resourcesData.length; console.log(`Using array length (${totalResources}) as resource count estimate`); document.getElementById('stat-total-resources').textContent = totalResources; document.getElementById('stat-free-resources').textContent = Math.floor(totalResources * 0.8); // Estimate 80% free document.getElementById('stat-models').textContent = '0'; // Update sidebar stats const sidebarResources = document.getElementById('sidebar-resources'); const sidebarModels = document.getElementById('sidebar-models'); if (sidebarResources) sidebarResources.textContent = totalResources; if (sidebarModels) sidebarModels.textContent = '0'; return; // Exit early since we can't process array format properly } } // Check if we have the summary object let summary = null; if (resourcesData && typeof resourcesData === 'object' && !Array.isArray(resourcesData)) { summary = resourcesData.summary || resourcesData; } // Validate that summary is an object with expected properties if (summary && typeof summary === 'object' && !Array.isArray(summary)) { // Check if it has at least one of the expected properties const hasExpectedProperties = summary.total_resources !== undefined || summary.free_resources !== undefined || summary.models_available !== undefined || (resourcesData.success !== false && resourcesData.success !== undefined); if (hasExpectedProperties || resourcesData.success === true) { const totalResources = summary.total_resources || 0; const freeResources = summary.free_resources || 0; const modelsAvailable = summary.models_available || 0; // Update metric cards - ensure elements exist before updating const totalResourcesEl = document.getElementById('stat-total-resources'); const freeResourcesEl = document.getElementById('stat-free-resources'); const modelsEl = document.getElementById('stat-models'); if (totalResourcesEl) totalResourcesEl.textContent = totalResources; if (freeResourcesEl) freeResourcesEl.textContent = freeResources; if (modelsEl) modelsEl.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 - handle both object and simple count format if (summary.categories && typeof summary.categories === 'object' && !Array.isArray(summary.categories)) { const categories = summary.categories; // Convert {category: {count: N}} to {category: N} for chart const chartData = {}; for (const [key, value] of Object.entries(categories)) { chartData[key] = typeof value === 'object' && value !== null ? (value.count || value) : value; } createCategoriesChart(chartData); } } else { // Data structure exists but doesn't have expected properties console.warn('Resources data missing expected properties:', resourcesData); document.getElementById('stat-total-resources').textContent = '0'; document.getElementById('stat-free-resources').textContent = '0'; document.getElementById('stat-models').textContent = '0'; } } else { // Invalid data format - log minimal info to avoid console spam if (Array.isArray(resourcesData)) { console.log(`Resources API returned array (${resourcesData.length} items) instead of summary object`); } else { console.log('Resources data format unexpected - not a valid object:', typeof 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(); // Handle different response formats let providers = 0; if (statusData.providers && typeof statusData.providers === 'object') { providers = statusData.providers.total || 0; } else { providers = statusData.total_apis || statusData.total_providers || statusData.providers || 0; } const providersEl = document.getElementById('stat-providers'); if (providersEl) { providersEl.textContent = providers; } // Display system status - handle different response formats const systemStatusDiv = document.getElementById('system-status'); if (systemStatusDiv) { // Try to get health status from different possible fields const healthStatus = statusData.system_health || statusData.status || 'ok'; const healthClass = healthStatus === 'healthy' || healthStatus === 'ok' ? 'alert-success' : healthStatus === 'degraded' ? 'alert-warning' : 'alert-error'; // Get provider counts const providers = statusData.providers || {}; const totalProviders = providers.total || statusData.total_apis || 0; const onlineProviders = statusData.online || 0; const degradedProviders = statusData.degraded || 0; const offlineProviders = statusData.offline || 0; const avgResponseTime = statusData.avg_response_time_ms || 0; const lastUpdate = statusData.last_update || statusData.timestamp || new Date().toISOString(); // Format last update time let formattedTime = 'N/A'; try { const updateDate = new Date(lastUpdate); formattedTime = updateDate.toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } catch (e) { formattedTime = lastUpdate; } // Create a properly formatted system status display const statusIcon = healthStatus === 'healthy' || healthStatus === 'ok' ? '' : healthStatus === 'degraded' ? '' : ''; const statusText = healthStatus === 'ok' ? 'Healthy' : healthStatus === 'healthy' ? 'Healthy' : healthStatus === 'degraded' ? 'Degraded' : healthStatus === 'error' ? 'Error' : 'Unknown'; systemStatusDiv.innerHTML = `
${statusIcon}
System Status
${statusText}
Total Providers
${totalProviders}
Online APIs
${onlineProviders}
Degraded APIs
${degradedProviders}
Offline APIs
${offlineProviders}
Avg Response Time
${avgResponseTime}ms
Last Update
${formattedTime}
`; } } else { throw new Error('Status endpoint not available'); } } catch (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.
'; } } console.log('βœ… Dashboard loaded successfully'); } catch (error) { console.error('❌ Error loading dashboard:', error); getToast().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: ${error.message}
Please refresh or check backend status.
`; } // Set default values statsElements.forEach(id => { const el = document.getElementById(id); if (el) el.textContent = '0'; }); } } // Create Categories Chart function createCategoriesChart(categories) { const ctx = document.getElementById('categories-chart'); if (!ctx) return; // Check if Chart.js is loaded if (typeof Chart === 'undefined') { console.error('Chart.js is not loaded'); ctx.parentElement.innerHTML = '

Chart library not loaded

'; return; } if (AppState.charts.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: labels, datasets: [{ label: 'Total Resources', data: values, backgroundColor: 'rgba(102, 126, 234, 0.6)', borderColor: 'rgba(102, 126, 234, 1)', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true } } } }); } // ============================================================================= // Market Data Loading // ============================================================================= async function loadMarketData() { console.log('πŸ’° Loading market data...'); const marketDiv = document.getElementById('market-data'); const trendingDiv = document.getElementById('trending-coins'); const fgDiv = document.getElementById('fear-greed'); if (marketDiv) marketDiv.innerHTML = '
Loading market data...
'; if (trendingDiv) trendingDiv.innerHTML = '
Loading trending coins...
'; if (fgDiv) fgDiv.innerHTML = '
Loading Fear & Greed Index...
'; 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) { marketDiv.innerHTML = `
${data.cryptocurrencies.map(coin => ` `).join('')}
# Name Price (USD) 24h Change 24h Volume Market Cap
${coin.rank || '-'} ${coin.image ? `` : ''} ${coin.symbol} ${coin.name} $${formatNumber(coin.price)} ${coin.change_24h >= 0 ? '↑' : '↓'} ${Math.abs(coin.change_24h || 0).toFixed(2)}% $${formatNumber(coin.volume_24h)} $${formatNumber(coin.market_cap)}
${data.total_market_cap ? `
Total Market Cap: $${formatNumber(data.total_market_cap)} | BTC Dominance: ${(data.btc_dominance || 0).toFixed(2)}%
` : ''} `; } else { marketDiv.innerHTML = '
No market data available
'; } // 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) { trendingDiv.innerHTML = ` `; } else { trendingDiv.innerHTML = '
No trending data available
'; } } else { throw new Error('Trending endpoint not available'); } } catch (trendingError) { console.warn('Trending endpoint error:', trendingError); trendingDiv.innerHTML = '
Trending data unavailable
'; } // Load Fear & Greed Index with enhanced visualization try { const sentimentRes = await fetch('/api/sentiment'); if (sentimentRes.ok) { const sentimentData = await sentimentRes.json(); if (sentimentData.fear_greed_index !== undefined) { const fgValue = sentimentData.fear_greed_index; const fgLabel = sentimentData.fear_greed_label || 'Unknown'; // Determine sentiment classification and colors let sentimentClass = ''; let sentimentIcon = ''; let fgColor = ''; let bgGradient = ''; let description = ''; if (fgValue >= 75) { sentimentClass = 'Extreme Greed'; sentimentIcon = 'πŸš€'; fgColor = '#10b981'; bgGradient = 'linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(5, 150, 105, 0.1) 100%)'; description = 'Market shows extreme greed. Consider taking profits.'; } else if (fgValue >= 50) { sentimentClass = 'Greed'; sentimentIcon = 'πŸ“ˆ'; fgColor = '#3b82f6'; bgGradient = 'linear-gradient(135deg, rgba(59, 130, 246, 0.2) 0%, rgba(37, 99, 235, 0.1) 100%)'; description = 'Market sentiment is bullish. Optimistic outlook.'; } else if (fgValue >= 25) { sentimentClass = 'Fear'; sentimentIcon = '⚠️'; fgColor = '#f59e0b'; bgGradient = 'linear-gradient(135deg, rgba(245, 158, 11, 0.2) 0%, rgba(217, 119, 6, 0.1) 100%)'; description = 'Market shows fear. Caution advised.'; } else { sentimentClass = 'Extreme Fear'; sentimentIcon = '😱'; fgColor = '#ef4444'; bgGradient = 'linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(220, 38, 38, 0.1) 100%)'; description = 'Extreme fear in market. Potential buying opportunity.'; } // Calculate progress bar percentage (0-100) const progressPercent = fgValue; // Create circular gauge SVG const circumference = 2 * Math.PI * 90; // radius = 90 const offset = circumference - (progressPercent / 100) * circumference; fgDiv.innerHTML = `
${sentimentIcon}
${fgValue}
/ 100
${sentimentClass}
${description}
Fear & Greed Index ${fgValue}/100
0 25 50 75 100
😱 Extreme Fear ⚠️ Fear 😐 Neutral πŸ“ˆ Greed πŸš€ Extreme Greed
Current Value
${fgValue}
Classification
${fgLabel}
${sentimentData.timestamp ? `
Last Update
${new Date(sentimentData.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}
` : ''}
Source
Alternative.me
πŸ’‘
Market Interpretation
${fgValue >= 75 ? 'The market is showing extreme greed. Historically, this may indicate a potential market top. Consider taking profits and being cautious with new positions.' : fgValue >= 50 ? 'Market sentiment is positive with greed prevailing. This suggests bullish momentum, but monitor for overbought conditions.' : fgValue >= 25 ? 'Fear is present in the market. This could indicate a buying opportunity for long-term investors, but exercise caution.' : 'Extreme fear dominates the market. Historically, this has often been a good time to buy, but ensure you have a solid risk management strategy.'}
`; } else { fgDiv.innerHTML = '
Fear & Greed Index unavailable
'; } } else { throw new Error('Sentiment endpoint not available'); } } catch (fgError) { console.warn('Fear & Greed endpoint error:', fgError); fgDiv.innerHTML = `
⚠️
Fear & Greed Index Unavailable
Unable to fetch sentiment data at this time. Please try again later.
`; } console.log('βœ… Market data loaded successfully'); } catch (error) { console.error('❌ Error loading market data:', error); getToast().error('Failed to load market data'); 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
'; } } // ============================================================================= // News Loading (FIXED) // ============================================================================= async function fetchNewsFromAPI() { console.log('πŸ“₯ Fetching news from CryptoCompare API...'); getToast().info('Fetching latest news from CryptoCompare...'); const newsListDiv = document.getElementById('news-list'); if (!newsListDiv) return; newsListDiv.innerHTML = '
Fetching news from CryptoCompare API...
'; try { 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); if (data.success) { getToast().success(`Successfully fetched and saved ${data.saved} news articles!`); // Reload news list loadNews(); } else { getToast().error(`Failed to fetch news: ${data.error}`); newsListDiv.innerHTML = `
Error: ${data.error}
`; } } catch (error) { console.error('❌ Error fetching news:', error); getToast().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}`); } 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 `

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

${article.content ? `

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

` : ''}
${article.source || 'Unknown'}
${timeAgo}
${article.sentiment_label ? `${article.sentiment_label}` : ''}
${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}` : ''}
`; console.log('βœ… News loaded successfully'); getToast().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); getToast().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 const statusRes = await fetch('/api/models/status'); if (statusRes.ok) { const statusData = await statusRes.json(); // Handle different response formats from API const isOk = statusData.ok || statusData.success || statusData.status === 'ok'; const pipelinesLoaded = statusData.pipelines_loaded || statusData.models_loaded || 0; const availableModels = statusData.available_models || statusData.loaded_models || []; const modelsCount = Array.isArray(availableModels) ? availableModels.length : (availableModels || 0); const hfMode = statusData.hf_mode || 'unknown'; const transformersAvailable = statusData.transformers_available !== undefined ? statusData.transformers_available : false; const statusMessage = statusData.status_message || (isOk ? 'Active' : 'Partial'); // Determine if lazy loading is active const isLazyLoading = hfMode === 'public' && pipelinesLoaded === 0 && modelsCount > 0; const alertClass = isLazyLoading ? 'alert-info' : (isOk ? 'alert-success' : 'alert-warning'); const statusIcon = isLazyLoading ? 'ℹ️' : (isOk ? 'βœ…' : '⚠️'); modelsStatusDiv.innerHTML = `
Status: ${statusIcon} ${statusMessage}
Models Configured: ${modelsCount} (Lazy Loading)
Models Loaded: ${pipelinesLoaded}
HF Mode: ${hfMode}
Transformers: ${transformersAvailable ? 'βœ… Available' : '❌ Not Available'} ${isLazyLoading ? '

πŸ’‘ Models will load automatically on first use (sentiment analysis, etc.)' : ''}
`; // Update stat-models in dashboard and sidebar with actual loaded count const modelsStatEl = document.getElementById('stat-models'); const sidebarModelsEl = document.getElementById('sidebar-models'); // Use pipelines_loaded if available, otherwise use modelsCount const displayCount = pipelinesLoaded > 0 ? pipelinesLoaded : modelsCount; if (modelsStatEl) { modelsStatEl.textContent = displayCount; } if (sidebarModelsEl) { sidebarModelsEl.textContent = displayCount; } } else { throw new Error('Models status endpoint not available'); } // Load models list const listRes = await fetch('/api/models/list'); if (listRes.ok) { const listData = await listRes.json(); // Update sidebar models count const sidebarModels = document.getElementById('sidebar-models'); const modelsStatEl = document.getElementById('stat-models'); const totalModels = listData.total_models || (listData.models ? listData.models.length : 0); const loadedModels = listData.models ? listData.models.filter(m => m.loaded).length : 0; if (sidebarModels) { sidebarModels.textContent = loadedModels > 0 ? loadedModels : totalModels; } if (modelsStatEl) { modelsStatEl.textContent = loadedModels > 0 ? loadedModels : totalModels; } if (listData.models && listData.models.length > 0) { modelsListDiv.innerHTML = `
${listData.models.map(model => { const modelId = model.model_id || model.id || model.key || 'N/A'; const task = model.task || 'N/A'; const category = model.category || 'N/A'; const isLoaded = model.loaded === true; const hasError = model.error && model.error.length > 0; const statusClass = isLoaded ? 'available' : (hasError ? 'error' : 'standby'); const statusText = isLoaded ? 'βœ… Loaded' : (hasError ? '❌ Error' : '⏸️ Standby'); return ` `; }).join('')}
Model ID Task Category Status
${modelId} ${task} ${category} ${statusText}
`; } else { modelsListDiv.innerHTML = '
No models available
'; } } else { throw new Error('Models list endpoint not available'); } console.log('βœ… Models loaded successfully'); } catch (error) { console.error('❌ Error loading models:', error); getToast().error('Failed to load models'); if (modelsStatusDiv) modelsStatusDiv.innerHTML = `
Error loading models status: ${error.message}
`; if (modelsListDiv) modelsListDiv.innerHTML = '
Error loading models list
'; } } async function initializeModels() { getToast().info('Initializing models... This may take a moment.'); const modelsStatusDiv = document.getElementById('models-status'); if (modelsStatusDiv) { modelsStatusDiv.innerHTML = '
Initializing models...
'; } 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(); // Handle different response formats const isOk = data.status === 'ok' || data.ok === true || (data.models_loaded && data.models_loaded > 0); const modelsLoaded = data.models_loaded || data.pipelines_loaded || 0; const modelsFailed = data.models_failed || data.pipelines_failed || 0; if (isOk) { getToast().success(`Models initialized successfully! ${modelsLoaded} model(s) loaded.`); } else if (modelsLoaded > 0) { getToast().warning(`Models partially initialized: ${modelsLoaded} loaded, ${modelsFailed} failed`); } else { getToast().warning('No models loaded. Using fallback mode.'); } // Reload models list and status await loadModels(); } catch (error) { console.error('Error initializing models:', error); getToast().error('Failed to initialize models: ' + error.message); if (modelsStatusDiv) { modelsStatusDiv.innerHTML = `
Error initializing models: ${error.message}
`; } } } // ============================================================================= // Settings // ============================================================================= function loadSettings() { const apiInfoDiv = document.getElementById('api-info'); if (apiInfoDiv) { apiInfoDiv.innerHTML = `
API Base URL: ${window.location.origin}
Documentation: /docs
Health Check: /health
`; } } function saveSettings() { getToast().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'; } } function changeTheme(theme) { if (theme === 'light') { document.body.classList.add('light-theme'); } else { document.body.classList.remove('light-theme'); } } // ============================================================================= // Sentiment Analysis Functions with Visualizations // ============================================================================= // 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; } // 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 ` `; } // Create confidence bar function createConfidenceBar(confidence) { const confidencePercent = Math.round(confidence * 100); return `
Model Confidence ${confidencePercent}%
`; } async function analyzeGlobalSentiment() { getToast().info('Analyzing global market sentiment...'); const resultDiv = document.getElementById('global-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: 'Overall cryptocurrency market sentiment analysis', mode: 'crypto' }) }); if (!response.ok) throw new Error(`API returned ${response.status}`); const data = await response.json(); 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 = `

Global Market Sentiment

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

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

`; // Create gauge chart after DOM update setTimeout(() => { createSentimentGauge('global-sentiment-gauge', confidence, sentimentClass); }, 100); getToast().success('Sentiment analysis complete!'); } else { resultDiv.innerHTML = '
Sentiment analysis unavailable
'; } } catch (error) { console.error('Error analyzing sentiment:', error); resultDiv.innerHTML = `
Error: ${error.message}
`; getToast().error('Failed to analyze sentiment'); } } async function analyzeAssetSentiment() { const symbol = document.getElementById('asset-symbol')?.value; const text = document.getElementById('asset-sentiment-text')?.value; if (!symbol) { getToast().warning('Please select a trading pair'); return; } getToast().info('Analyzing asset sentiment...'); const resultDiv = document.getElementById('asset-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: 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 && 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 = `

${symbol} Sentiment

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

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

`; // Create gauge chart after DOM update setTimeout(() => { createSentimentGauge('asset-sentiment-gauge', confidence, sentimentClass); }, 100); getToast().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}
`; getToast().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() === '') { getToast().warning('Please enter text to analyze'); return; } getToast().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 (!response.ok) throw new Error(`API returned ${response.status}`); const data = await response.json(); 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}
${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'}

`; // Create gauge chart after DOM update setTimeout(() => { createSentimentGauge('sentiment-gauge', confidence, sentimentClass); }, 100); getToast().success('Sentiment analysis complete!'); } else { resultDiv.innerHTML = '
Sentiment analysis unavailable
'; } } catch (error) { console.error('Error analyzing sentiment:', error); resultDiv.innerHTML = `
Error: ${error.message}
`; getToast().error('Failed to analyze sentiment'); } } // ============================================================================= // Trading Assistant // ============================================================================= async function runTradingAssistant() { const symbol = document.getElementById('trading-symbol')?.value; const context = document.getElementById('trading-context')?.value; if (!symbol) { getToast().warning('Please select a trading symbol'); return; } getToast().info('Generating trading signal...'); const resultDiv = document.getElementById('trading-assistant-result'); if (resultDiv) { resultDiv.innerHTML = '
Analyzing...
'; } try { const response = await fetch('/api/trading/decision', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ symbol: symbol, context: context || `Trading decision for ${symbol}` }) }); if (!response.ok) throw new Error(`API returned ${response.status}`); const data = await response.json(); 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 = `

${symbol} Trading Signal

${decision}
${confidence}%
Confidence
${data.reasoning ? `

Reasoning: ${data.reasoning}

` : ''}

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

`; getToast().success('Trading signal generated!'); } else { resultDiv.innerHTML = '
Trading signal unavailable
'; } } catch (error) { console.error('Error generating trading signal:', error); resultDiv.innerHTML = `
Error: ${error.message}
`; getToast().error('Failed to generate trading signal'); } } // ============================================================================= // 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); } // ============================================================================= // Export for global access // ============================================================================= window.AppState = AppState; // ToastManager is loaded from toast.js as window.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; // ===== DIAGNOSTICS FUNCTIONS ===== // Export diagnostic functions to window for onclick handlers async function runDiagnostic() { const runBtn = document.getElementById('run-diagnostics-btn'); const progressDiv = document.getElementById('test-progress'); const outputPre = document.getElementById('diagnostic-output'); const summaryDiv = document.getElementById('diagnostic-summary'); // Disable button and show progress runBtn.disabled = true; runBtn.textContent = 'Running...'; progressDiv.style.display = 'block'; summaryDiv.style.display = 'none'; outputPre.textContent = ''; try { const response = await fetch('/api/diagnostics/run-test', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); // Display output with color coding outputPre.innerHTML = colorCodeOutput(data.output); // Update summary updateDiagnosticSummary(data); // Store last run time localStorage.setItem('lastDiagnosticRun', data.timestamp); // Update status cards updateStatusCards(data.summary); // Show summary summaryDiv.style.display = 'block'; // Auto-scroll to bottom outputPre.scrollTop = outputPre.scrollHeight; } catch (error) { console.error('Diagnostic error:', error); outputPre.innerHTML = `❌ Error running diagnostic: ${error.message}`; showToast('❌ Diagnostic failed: ' + error.message, 'error'); } finally { // Re-enable button runBtn.disabled = false; runBtn.innerHTML = '▢️ Run Full Diagnostic'; progressDiv.style.display = 'none'; } } function colorCodeOutput(output) { if (!output) return ''; return output .replace(/βœ…/g, 'βœ…') .replace(/❌/g, '❌') .replace(/⚠️/g, '⚠️') .replace(/πŸ”/g, 'πŸ”') .replace(/πŸ“¦/g, 'πŸ“¦') .replace(/🌐/g, '🌐') .replace(/πŸ§ͺ/g, 'πŸ§ͺ') .replace(/πŸ“„/g, 'πŸ“„') .replace(/πŸ’‘/g, 'πŸ’‘') .replace(/⏭️/g, '⏭️') .split('\n').join('
'); } function updateDiagnosticSummary(data) { document.getElementById('summary-duration').textContent = `${data.duration_seconds}s`; document.getElementById('summary-passed').textContent = data.summary.transformers_available && data.summary.hf_hub_connected ? '2/2' : '1/2'; document.getElementById('summary-failed').textContent = (!data.summary.transformers_available || !data.summary.hf_hub_connected) ? '1/2' : '0/2'; document.getElementById('summary-critical').textContent = data.summary.critical_issues.length; const fixesDiv = document.getElementById('suggested-fixes'); if (data.summary.critical_issues.length > 0) { fixesDiv.innerHTML = '

πŸ”§ Suggested Fixes:

'; } else { fixesDiv.innerHTML = '

βœ… No critical issues found

'; } } function updateStatusCards(summary) { const transformersEl = document.getElementById('transformers-status-value'); if (transformersEl) { transformersEl.textContent = summary.transformers_available ? 'Available' : 'Not Available'; transformersEl.style.color = summary.transformers_available ? 'var(--success)' : 'var(--danger)'; } const hfEl = document.getElementById('hf-status-value'); if (hfEl) { hfEl.textContent = summary.hf_hub_connected ? 'Connected' : 'Disconnected'; hfEl.style.color = summary.hf_hub_connected ? 'var(--success)' : 'var(--danger)'; } const modelsEl = document.getElementById('models-status-value'); if (modelsEl) { modelsEl.textContent = summary.models_loaded || 0; } const lastRun = localStorage.getItem('lastDiagnosticRun'); const lastTestEl = document.getElementById('last-test-value'); if (lastTestEl) { lastTestEl.textContent = lastRun ? new Date(lastRun).toLocaleString() : 'Never'; } } async function refreshDiagnosticStatus() { try { // Get models status to determine transformers and HF hub status const modelsResponse = await fetch('/api/models/status'); if (modelsResponse.ok) { const modelsData = await modelsResponse.json(); // Update status cards const transformersStatusEl = document.getElementById('transformers-status-value'); const hfStatusEl = document.getElementById('hf-status-value'); const modelsLoadedEl = document.getElementById('models-status-value'); if (transformersStatusEl) { const transformersAvailable = modelsData.transformers_available || false; transformersStatusEl.textContent = transformersAvailable ? 'βœ… Installed' : '❌ Not Installed'; transformersStatusEl.style.color = transformersAvailable ? 'var(--success)' : 'var(--danger)'; } if (hfStatusEl) { const hfMode = modelsData.hf_mode || 'off'; const isConnected = hfMode !== 'off'; const modeText = hfMode === 'public' ? 'Public' : hfMode === 'auth' ? 'Authenticated' : 'Offline'; hfStatusEl.textContent = isConnected ? `βœ… ${modeText}` : '⚠️ Offline'; hfStatusEl.style.color = isConnected ? 'var(--success)' : 'var(--warning)'; } if (modelsLoadedEl) { const modelsLoaded = modelsData.models_loaded || 0; const modelsFailed = modelsData.models_failed || 0; if (modelsLoaded > 0) { modelsLoadedEl.textContent = `${modelsLoaded} Ready`; modelsLoadedEl.style.color = 'var(--success)'; } else if (modelsFailed > 0) { modelsLoadedEl.textContent = `${modelsFailed} Failed`; modelsLoadedEl.style.color = 'var(--danger)'; } else { modelsLoadedEl.textContent = '0'; modelsLoadedEl.style.color = 'var(--text-secondary)'; } } } // Update the last test time const lastRun = localStorage.getItem('lastDiagnosticRun'); const lastTestEl = document.getElementById('last-test-value'); if (lastTestEl) { lastTestEl.textContent = lastRun ? new Date(lastRun).toLocaleString() : 'Never'; } getToast().success('Status refreshed'); } catch (error) { console.error('Error refreshing status:', error); getToast().error('Failed to refresh status'); } } function downloadDiagnosticLog() { const output = document.getElementById('diagnostic-output').textContent; if (!output.trim()) { showToast('❌ No diagnostic output to download', 'warning'); return; } const blob = new Blob([output], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `diagnostic-log-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast('βœ… Log downloaded', 'success'); } // Note: Diagnostics initialization is handled in the enhanced section below // ===== ENHANCED DIAGNOSTIC FUNCTIONS ===== let autoRefreshInterval = null; let autoRefreshEnabled = false; function toggleAutoRefresh() { autoRefreshEnabled = !autoRefreshEnabled; const btn = document.getElementById('auto-refresh-btn'); if (autoRefreshEnabled) { btn.innerHTML = 'Auto: ON (30s)'; btn.style.background = 'rgba(16, 185, 129, 0.2)'; btn.style.borderColor = 'var(--success)'; autoRefreshInterval = setInterval(() => { refreshDiagnosticStatus(); loadSystemHealth(); loadProviderHealth(); }, 30000); getToast().success('Auto-refresh enabled (30s interval)'); } else { btn.innerHTML = 'Auto: OFF'; btn.style.background = ''; btn.style.borderColor = ''; if (autoRefreshInterval) { clearInterval(autoRefreshInterval); autoRefreshInterval = null; } getToast().info('Auto-refresh disabled'); } } function switchDiagnosticTab(tabName) { // Hide all tabs document.querySelectorAll('.diagnostic-tab-content').forEach(tab => { tab.classList.remove('active'); }); document.querySelectorAll('.diagnostic-tab-btn').forEach(btn => { btn.classList.remove('active'); }); // Show selected tab document.getElementById(`diagnostic-tab-${tabName}`).classList.add('active'); document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); // Load content if needed if (tabName === 'health' && document.getElementById('health-details-content').innerHTML.includes('Click')) { loadSystemHealth(); } else if (tabName === 'logs') { loadRecentLogs(); } } async function loadSystemHealth() { try { const response = await fetch('/api/diagnostics/health'); if (!response.ok) throw new Error('Failed to fetch health data'); const data = await response.json(); const container = document.getElementById('system-health-overview'); if (!container) return; const providers = data.providers?.summary || {}; const models = data.models?.summary || {}; const overall = data.overall_health || {}; container.innerHTML = `
Providers
${providers.healthy || 0}/${providers.total || 0}
${overall.providers_ok ? 'βœ… Healthy' : '⚠️ Degraded'}
AI Models
${models.healthy || 0}/${models.total || 0}
${overall.models_ok ? 'βœ… Healthy' : '⚠️ Degraded'}
In Cooldown
${(providers.in_cooldown || 0) + (models.in_cooldown || 0)}
${(providers.in_cooldown || 0) + (models.in_cooldown || 0) > 0 ? '⚠️ Some services cooling' : 'βœ… All active'}
Degraded
${(providers.degraded || 0) + (models.degraded || 0)}
${(providers.degraded || 0) + (models.degraded || 0) > 0 ? '⚠️ Needs attention' : 'βœ… All optimal'}
`; // Update health details tab const healthDetails = document.getElementById('health-details-content'); if (healthDetails) { healthDetails.innerHTML = `

Provider Health Summary

Total
${providers.total || 0}
Healthy
${providers.healthy || 0}
Degraded
${providers.degraded || 0}
Unavailable
${providers.unavailable || 0}

Model Health Summary

Total
${models.total || 0}
Healthy
${models.healthy || 0}
Degraded
${models.degraded || 0}
Unavailable
${models.unavailable || 0}
`; } } catch (error) { console.error('Error loading system health:', error); getToast().error('Failed to load system health'); } } // Export to window immediately window.loadSystemHealth = loadSystemHealth; async function loadProviderHealth() { try { const response = await fetch('/api/diagnostics/health'); if (!response.ok) throw new Error('Failed to fetch provider health'); const data = await response.json(); const tbody = document.getElementById('provider-health-table'); if (!tbody) return; const providers = data.providers?.entries || []; const models = data.models?.entries || []; let html = ''; // Add providers providers.slice(0, 10).forEach(entry => { const statusClass = entry.status === 'healthy' ? 'healthy' : entry.status === 'degraded' ? 'degraded' : entry.status === 'unavailable' ? 'unavailable' : 'unknown'; const lastCheck = entry.last_success ? new Date(entry.last_success * 1000).toLocaleString() : 'Never'; html += ` ${entry.name || entry.id} Provider ${entry.status || 'unknown'} ${lastCheck} ${entry.in_cooldown ? '⏳ Cooldown' : '-'} `; }); // Add models models.slice(0, 10).forEach(entry => { const statusClass = entry.status === 'healthy' ? 'healthy' : entry.status === 'degraded' ? 'degraded' : entry.status === 'unavailable' ? 'unavailable' : 'unknown'; html += ` ${entry.name || entry.key || 'Unknown'} AI Model ${entry.status || 'unknown'} - ${entry.in_cooldown ? '⏳ Cooldown' : '-'} `; }); if (html === '') { html = 'No health data available'; } tbody.innerHTML = html; } catch (error) { console.error('Error loading provider health:', error); getToast().error('Failed to load provider health'); } } // Export to window immediately window.loadProviderHealth = loadProviderHealth; async function triggerSelfHeal() { try { getToast().info('Triggering self-healing...'); const response = await fetch('/api/diagnostics/self-heal', { method: 'POST' }); const data = await response.json(); if (data.status === 'completed') { getToast().success(`Self-healing completed: ${data.summary.successful} successful, ${data.summary.failed} failed`); loadProviderHealth(); loadSystemHealth(); } else { getToast().error('Self-healing failed: ' + (data.error || 'Unknown error')); } } catch (error) { console.error('Error triggering self-heal:', error); getToast().error('Failed to trigger self-healing'); } } // Export immediately after definition window.triggerSelfHeal = triggerSelfHeal; // Update the actual implementation (replacing placeholder) async function testAPIEndpoints() { const resultsDiv = document.getElementById('api-test-results'); if (!resultsDiv) return; resultsDiv.innerHTML = '
Testing API endpoints...'; const endpoints = [ { name: 'Health Check', url: '/api/health' }, { name: 'System Status', url: '/api/status' }, { name: 'Market Data', url: '/api/market' }, { name: 'Models Status', url: '/api/models/status' }, { name: 'Providers', url: '/api/providers' }, ]; let html = '
'; let passed = 0; let failed = 0; for (const endpoint of endpoints) { try { const startTime = performance.now(); const response = await fetch(endpoint.url); const duration = (performance.now() - startTime).toFixed(0); if (response.ok) { passed++; html += `
βœ… ${endpoint.name}
${endpoint.url}
${duration}ms
`; } else { failed++; html += `
❌ ${endpoint.name}
${endpoint.url} - HTTP ${response.status}
`; } } catch (error) { failed++; html += `
❌ ${endpoint.name}
${endpoint.url} - ${error.message}
`; } } html += `
Summary: ${passed} passed, ${failed} failed
`; resultsDiv.innerHTML = html; getToast().success(`API tests completed: ${passed} passed, ${failed} failed`); } // Export to window immediately window.testAPIEndpoints = testAPIEndpoints; async function checkDatabaseHealth() { try { getToast().info('Checking database health...'); const response = await fetch('/api/diagnostics/run?auto_fix=false'); const data = await response.json(); const output = document.getElementById('diagnostic-output'); if (output) { output.textContent = JSON.stringify(data, null, 2); } if (data.issues_found === 0) { getToast().success('Database health check passed'); } else { getToast().warning(`Database health check found ${data.issues_found} issues`); } } catch (error) { console.error('Error checking database:', error); getToast().error('Failed to check database health'); } } // Export to window immediately window.checkDatabaseHealth = checkDatabaseHealth; async function testNetworkConnectivity() { const output = document.getElementById('diagnostic-output'); if (output) { output.textContent = 'Testing network connectivity...\n'; } const endpoints = [ { name: 'HuggingFace Hub', url: 'https://huggingface.co' }, { name: 'CoinGecko API', url: 'https://api.coingecko.com/api/v3/ping' }, { name: 'Alternative.me', url: 'https://api.alternative.me/fng/' }, ]; let results = 'Network Connectivity Test Results:\n' + '='.repeat(50) + '\n\n'; for (const endpoint of endpoints) { try { const startTime = performance.now(); const response = await fetch(endpoint.url, { method: 'HEAD', mode: 'no-cors' }); const duration = (performance.now() - startTime).toFixed(0); results += `βœ… ${endpoint.name}: Reachable (${duration}ms)\n`; } catch (error) { results += `❌ ${endpoint.name}: ${error.message}\n`; } } if (output) { output.textContent = results; } getToast().success('Network connectivity test completed'); } // Export to window immediately window.testNetworkConnectivity = testNetworkConnectivity; async function loadRecentLogs() { try { const response = await fetch('/api/logs/recent'); const data = await response.json(); const container = document.getElementById('recent-logs-content'); if (!container) return; if (data.logs && data.logs.length > 0) { let html = '
'; data.logs.slice(0, 20).forEach(log => { const level = log.level || 'INFO'; const levelColor = level === 'ERROR' ? 'var(--danger)' : level === 'WARNING' ? 'var(--warning)' : level === 'INFO' ? 'var(--info)' : 'var(--text-secondary)'; html += `
[${level}] ${log.timestamp || ''}
${log.message || JSON.stringify(log)}
`; }); html += '
'; container.innerHTML = html; } else { container.innerHTML = '

No recent logs available

'; } } catch (error) { console.error('Error loading logs:', error); document.getElementById('recent-logs-content').innerHTML = '

Failed to load logs

'; } } // Export to window immediately window.loadRecentLogs = loadRecentLogs; // Export diagnostic functions to window // Note: loadSystemHealth, loadProviderHealth, triggerSelfHeal, testAPIEndpoints, // checkDatabaseHealth, testNetworkConnectivity, loadRecentLogs, and handleAIToolsIframeLoad // are exported immediately after their definitions above (not here to avoid overwriting) // Export diagnostic functions to window (actual implementations) window.runDiagnostic = runDiagnostic; window.refreshDiagnosticStatus = refreshDiagnosticStatus; window.downloadDiagnosticLog = downloadDiagnosticLog; window.toggleAutoRefresh = toggleAutoRefresh; window.switchDiagnosticTab = switchDiagnosticTab; // ===== AI TOOLS LOADER ===== function loadAITools() { const iframe = document.getElementById('ai-tools-iframe'); const loading = document.getElementById('ai-tools-loading'); if (!iframe) return; // Show loading, hide iframe if (loading) loading.style.display = 'block'; iframe.style.display = 'none'; // Reload iframe if it already has content, or just show it if (iframe.src && iframe.src.includes('/ai-tools')) { // Iframe already loaded, just show it setTimeout(() => { if (loading) loading.style.display = 'none'; iframe.style.display = 'block'; }, 100); } else { // Set src to load the page iframe.src = '/ai-tools'; } } function handleAIToolsIframeLoad() { const iframe = document.getElementById('ai-tools-iframe'); const loading = document.getElementById('ai-tools-loading'); if (loading) loading.style.display = 'none'; if (iframe) iframe.style.display = 'block'; console.log('βœ… AI Tools iframe loaded successfully'); } window.loadAITools = loadAITools; window.handleAIToolsIframeLoad = handleAIToolsIframeLoad; // Initialize diagnostics on page load document.addEventListener('DOMContentLoaded', function() { refreshDiagnosticStatus(); loadSystemHealth(); loadProviderHealth(); }); console.log('βœ… App.js loaded successfully');