// Crypto Intelligence Hub - Main JavaScript
// Global state
const AppState = {
currentTab: 'dashboard',
data: {},
charts: {}
};
// Initialize app
document.addEventListener('DOMContentLoaded', () => {
initTabs();
checkAPIStatus();
loadDashboard();
// 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();
});
});
// 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'
);
}
}
}
// 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);
});
});
}
// Load tab-specific data - synchronized with HTML tabs
function loadTabData(tabId) {
switch(tabId) {
case 'dashboard':
loadDashboard();
break;
case 'market':
loadMarketData();
break;
case 'models':
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
break;
case 'trading-assistant':
// Trading assistant tab is interactive, no auto-load needed
break;
case 'news':
loadNews();
break;
case 'providers':
loadProviders();
break;
case 'diagnostics':
loadDiagnostics();
break;
case 'api-explorer':
loadAPIEndpoints();
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 = 'Select Endpoint... ';
endpoints.forEach(ep => {
const option = document.createElement('option');
option.value = ep.value;
option.textContent = ep.text;
endpointSelect.appendChild(option);
});
}
// Check API Status
async function checkAPIStatus() {
try {
const response = await fetch('/health');
const data = await response.json();
const statusBadge = document.getElementById('api-status');
if (data.status === 'healthy') {
statusBadge.className = 'status-badge';
statusBadge.innerHTML = '✅ System Active ';
} else {
statusBadge.className = 'status-badge error';
statusBadge.innerHTML = '❌ Error ';
}
} catch (error) {
const statusBadge = document.getElementById('api-status');
statusBadge.className = 'status-badge error';
statusBadge.innerHTML = '❌ Connection Failed ';
}
}
// Load Dashboard
async function loadDashboard() {
// 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 = '
';
}
try {
// Load resources - use enhanced API client with caching
const resourcesData = await window.apiClient.get('/api/resources', {
cacheDuration: 30000
});
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;
}
// Load system status - use enhanced API client
try {
const statusData = await window.apiClient.get('/api/status', {
cacheDuration: 15000
});
document.getElementById('stat-providers').textContent = statusData.total_apis || statusData.total_providers || 0;
// Display system status
const systemStatusDiv = document.getElementById('system-status');
const healthStatus = statusData.system_health || 'unknown';
const healthClass = healthStatus === 'healthy' ? 'alert-success' :
healthStatus === 'degraded' ? 'alert-warning' : 'alert-error';
systemStatusDiv.innerHTML = `
System Status: ${healthStatus}
Online APIs: ${statusData.online || 0}
Degraded APIs: ${statusData.degraded || 0}
Offline APIs: ${statusData.offline || 0}
Avg Response Time: ${statusData.avg_response_time_ms || 0}ms
Last Update: ${new Date(statusData.last_update || Date.now()).toLocaleString('en-US')}
`;
} catch (statusError) {
console.warn('Status endpoint not available:', statusError);
document.getElementById('stat-providers').textContent = '-';
}
// Load categories chart
if (resourcesData.success && resourcesData.summary.categories) {
createCategoriesChart(resourcesData.summary.categories);
}
} catch (error) {
console.error('Error loading dashboard:', error);
showError('Failed to load dashboard. Please check the backend is running.');
// Show error state
const systemStatusDiv = document.getElementById('system-status');
if (systemStatusDiv) {
systemStatusDiv.innerHTML = 'Failed to load dashboard data. Please refresh or check backend status.
';
}
}
}
// Create Categories Chart - Enhanced with better visuals
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();
}
// Enhanced gradient colors
const colors = [
'rgba(102, 126, 234, 0.8)',
'rgba(16, 185, 129, 0.8)',
'rgba(245, 158, 11, 0.8)',
'rgba(59, 130, 246, 0.8)',
'rgba(240, 147, 251, 0.8)',
'rgba(255, 107, 157, 0.8)'
];
const borderColors = [
'rgba(102, 126, 234, 1)',
'rgba(16, 185, 129, 1)',
'rgba(245, 158, 11, 1)',
'rgba(59, 130, 246, 1)',
'rgba(240, 147, 251, 1)',
'rgba(255, 107, 157, 1)'
];
AppState.charts.categories = new Chart(ctx, {
type: 'bar',
data: {
labels: Object.keys(categories),
datasets: [{
label: 'Total Resources',
data: Object.values(categories),
backgroundColor: colors,
borderColor: borderColors,
borderWidth: 2,
borderRadius: 8,
hoverBackgroundColor: borderColors
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
backgroundColor: 'rgba(17, 24, 39, 0.95)',
backdropFilter: 'blur(10px)',
padding: 12,
titleColor: '#f9fafb',
bodyColor: '#f9fafb',
borderColor: 'rgba(102, 126, 234, 0.5)',
borderWidth: 1,
cornerRadius: 8,
displayColors: true,
callbacks: {
title: function(context) {
return context[0].label;
},
label: function(context) {
return 'Resources: ' + context.parsed.y;
}
}
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: 'rgba(255, 255, 255, 0.05)',
drawBorder: false
},
ticks: {
color: '#9ca3af',
font: {
size: 12
}
}
},
x: {
grid: {
display: false
},
ticks: {
color: '#9ca3af',
font: {
size: 12
}
}
}
},
animation: {
duration: 1000,
easing: 'easeInOutQuart'
}
}
});
}
// Load Market Data
async function loadMarketData() {
// Show loading states
const marketDiv = document.getElementById('market-data');
const trendingDiv = document.getElementById('trending-coins');
const fgDiv = document.getElementById('fear-greed');
if (marketDiv) marketDiv.innerHTML = '';
if (trendingDiv) trendingDiv.innerHTML = '
Loading trending coins...
';
if (fgDiv) fgDiv.innerHTML = '
Loading Fear & Greed Index...
';
try {
// Use enhanced API client with caching
const data = await window.apiClient.get('/api/market', {
cacheDuration: 60000 // Cache for 1 minute
});
if (data.cryptocurrencies && data.cryptocurrencies.length > 0) {
const marketDiv = document.getElementById('market-data');
marketDiv.innerHTML = `
#
Name
Price (USD)
24h Change
24h Volume
Market Cap
${data.cryptocurrencies.map(coin => `
${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)}
`).join('')}
${data.total_market_cap ? `
Total Market Cap: $${formatNumber(data.total_market_cap)} |
BTC Dominance: ${(data.btc_dominance || 0).toFixed(2)}%
` : ''}
`;
} else {
document.getElementById('market-data').innerHTML = 'No data found
';
}
// Load trending - use enhanced API client
try {
const trendingData = await window.apiClient.get('/api/trending', {
cacheDuration: 60000
});
if (trendingData.trending && trendingData.trending.length > 0) {
const trendingDiv = document.getElementById('trending-coins');
trendingDiv.innerHTML = `
${trendingData.trending.map((coin, index) => `
#${index + 1}
${coin.symbol || coin.id} - ${coin.name || 'Unknown'}
${coin.market_cap_rank ? `
Market Cap Rank: ${coin.market_cap_rank}
` : ''}
${coin.score ? coin.score.toFixed(2) : 'N/A'}
`).join('')}
`;
} else {
document.getElementById('trending-coins').innerHTML = 'No data found
';
}
} catch (trendingError) {
console.warn('Trending endpoint error:', trendingError);
document.getElementById('trending-coins').innerHTML = 'Error loading trending coins
';
}
// Load Fear & Greed - use enhanced API client
try {
const sentimentData = await window.apiClient.get('/api/sentiment', {
cacheDuration: 60000
});
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)';
else if (fgValue >= 25) fgColor = 'var(--warning)';
else fgColor = 'var(--danger)';
fgDiv.innerHTML = `
${fgValue}
${fgLabel}
Market Fear & Greed Index
${sentimentData.timestamp ? `
Last Update: ${new Date(sentimentData.timestamp).toLocaleString('en-US')}
` : ''}
`;
} else {
document.getElementById('fear-greed').innerHTML = 'No data found
';
}
} catch (sentimentError) {
console.warn('Sentiment endpoint error:', sentimentError);
document.getElementById('fear-greed').innerHTML = 'Error loading Fear & Greed Index
';
}
} catch (error) {
console.error('Error loading market data:', error);
showError('Failed to load market data. Please check the backend connection.');
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.
';
}
}
}
// 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 });
}
// Load Models
async function loadModels() {
// Show loading state
const modelsListDiv = document.getElementById('models-list');
const statusDiv = document.getElementById('models-status');
if (modelsListDiv) modelsListDiv.innerHTML = '';
if (statusDiv) statusDiv.innerHTML = '';
try {
const response = await fetch('/api/models/list');
const data = await response.json();
const models = data.models || data || [];
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)';
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'}
` : ''}
${isAvailable ? '✅ Available' : '❌ Unavailable'}
${model.key ? `
Key: ${model.key}
` : ''}
`;
}).join('')}
`;
} else {
document.getElementById('models-list').innerHTML = 'No models found
';
}
// Load models status
try {
const statusRes = await fetch('/api/models/status');
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);
}
// Load models stats
try {
const statsRes = await fetch('/api/models/data/stats');
const statsData = await statsRes.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
` : ''}
`;
}
} catch (statsError) {
console.warn('Models stats endpoint error:', statsError);
}
} catch (error) {
console.error('Error loading models:', error);
showError('Failed to load models. Please check the backend connection.');
const modelsListDiv = document.getElementById('models-list');
if (modelsListDiv) {
modelsListDiv.innerHTML = 'Failed to load models. Check backend status.
';
}
}
}
// Initialize Models
async function initializeModels() {
try {
const response = await fetch('/api/models/initialize', { method: 'POST' });
const data = await response.json();
if (data.success) {
showSuccess('Models loaded successfully');
loadModels();
} else {
showError(data.error || 'Error loading models');
}
} catch (error) {
showError('Error loading models: ' + error.message);
}
}
// 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 = 'Auto (Mode-based) ';
// 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 = 'Auto (Mode-based) ';
}
}
}
// 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'}
`;
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 = '';
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 = '';
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 = '';
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
▼ Details
${title ? `
Title:
${title}
` : ''}
Model:
${modelDisplay}
${!isHFModel ? '⚠️ HF model unavailable ' : ''}
${data.input_length ? `
Input Length:
${data.input_length} characters
` : ''}
Timestamp:
${new Date(data.timestamp).toLocaleString()}
${data.note ? `
Note: ${data.note}
` : ''}
📋 Copy Summary
🔄 Clear
`;
// 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 = '';
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 = '';
}
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 = '';
}
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 = `
ID
Name
Category
Type
Status
Details
${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 `
${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 ? `⚠️ ` : ''}
`;
}).join('')}
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 = '';
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 = '';
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})
🔧 Auto-Heal Failed Models
${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 ? `Reinit ` : ''}
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 = '';
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 = `
`;
showError('API test error: ' + error.message);
}
}
// Utility Functions
function showError(message) {
const alert = document.createElement('div');
alert.className = 'alert alert-error';
alert.textContent = message;
document.body.appendChild(alert);
setTimeout(() => alert.remove(), 5000);
}
function showSuccess(message) {
const alert = document.createElement('div');
alert.className = 'alert alert-success';
alert.textContent = message;
document.body.appendChild(alert);
setTimeout(() => alert.remove(), 5000);
}
// 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);
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
async function loadAPIRegistry() {
// Load API registry from all_apis_merged_2025.json
try {
const response = await fetch('/api/resources/apis');
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;
}
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'}
${categories.length}
Categories
${data.total_raw_files || 0}
Total Files
${metadata.created_at ? `
Created
${new Date(metadata.created_at).toLocaleDateString('en-US')}
` : ''}
${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('')}
` : ''}
`;
}
// Also update metadata container if it exists
const metadataContainer = document.getElementById('api-registry-metadata');
if (metadataContainer) {
metadataContainer.innerHTML = `
Metadata
${JSON.stringify(metadata, null, 2)}
`;
}
} 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 themeToggle = document.querySelector('.theme-toggle');
if (body.classList.contains('light-theme')) {
body.classList.remove('light-theme');
localStorage.setItem('theme', 'dark');
// Update icon to moon (dark mode)
if (themeToggle) {
themeToggle.innerHTML = ' ';
}
} else {
body.classList.add('light-theme');
localStorage.setItem('theme', 'light');
// Update icon to sun (light mode)
if (themeToggle) {
themeToggle.innerHTML = ' ';
}
}
}
// Load theme preference
document.addEventListener('DOMContentLoaded', () => {
const savedTheme = localStorage.getItem('theme');
const themeToggle = document.querySelector('.theme-toggle');
if (savedTheme === 'light') {
document.body.classList.add('light-theme');
if (themeToggle) {
themeToggle.innerHTML = ' ';
}
}
});
// 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');
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');
return;
}
const resultDiv = document.getElementById('ai-analyst-result');
resultDiv.innerHTML = '';
try {
const response = await fetch('/api/analyze/text', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: prompt,
mode: mode,
max_length: maxLength
})
});
const data = await response.json();
if (!data.available) {
resultDiv.innerHTML = `
⚠️ Model Not Available: ${data.error || 'AI generation model is currently unavailable'}
${data.note ? `${data.note} ` : ''}
`;
return;
}
if (!data.success) {
resultDiv.innerHTML = `
❌ Generation Failed: ${data.error || 'Failed to generate analysis'}
`;
return;
}
const generatedText = data.text || '';
const model = data.model || 'Unknown';
resultDiv.innerHTML = `
✨ AI Generated Analysis
Model:
${model}
Mode:
${mode}
Prompt:
"${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"
Timestamp:
${new Date(data.timestamp).toLocaleString()}
📋 Copy Analysis
🔄 Clear
`;
// Store for clipboard
window.lastAIAnalysis = generatedText;
} 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');
}
}
function clearAIAnalystForm() {
document.getElementById('ai-analyst-prompt').value = '';
document.getElementById('ai-analyst-result').innerHTML = '';
window.lastAIAnalysis = null;
}
// ===== Trading Assistant Functions =====
async function runTradingAssistant() {
const symbol = document.getElementById('trading-symbol').value.trim().toUpperCase();
const context = document.getElementById('trading-context').value.trim();
if (!symbol) {
showError('Please enter a trading symbol');
return;
}
const resultDiv = document.getElementById('trading-assistant-result');
resultDiv.innerHTML = '
Analyzing and generating trading signal...
';
try {
const response = await fetch('/api/trading/decision', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: symbol,
context: context
})
});
const data = await response.json();
if (!data.available) {
resultDiv.innerHTML = `
⚠️ Model Not Available: ${data.error || 'Trading signal model is currently unavailable'}
${data.note ? `${data.note} ` : ''}
`;
return;
}
if (!data.success) {
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
AI Rationale:
${rationale}
${context ? `
Your Context:
"${context.substring(0, 200)}${context.length > 200 ? '...' : ''}"
` : ''}
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.
`;
} catch (error) {
console.error('Trading assistant error:', error);
resultDiv.innerHTML = `Analysis Error: ${error.message}
`;
showError('Error generating 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'
);
}
}
}
// 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();
}
};
// Listen for trading pairs loaded event to initialize trading symbol selector
document.addEventListener('tradingPairsLoaded', function(e) {
initTradingSymbolSelector();
});