- ${statsData.statistics.most_used_model ? `
-
- ` : ''}
+ if (listData.models && listData.models.length > 0) {
+ modelsListDiv.innerHTML = `
+
`;
+ } else {
+ modelsListDiv.innerHTML = '
';
}
- } catch (statsError) {
- console.warn('Models stats endpoint error:', statsError);
+ } else {
+ throw new Error('Models list endpoint not available');
}
+
+ console.log('✅ Models loaded successfully');
} catch (error) {
- console.error('Error loading models:', error);
- showError('Failed to load models. Please check the backend connection.');
+ console.error('❌ Error loading models:', error);
+ ToastManager.error('Failed to load models');
- const modelsListDiv = document.getElementById('models-list');
- if (modelsListDiv) {
- modelsListDiv.innerHTML = '
Failed to load models. Check backend status.
';
}
}
-// Initialize Models
async function initializeModels() {
+ ToastManager.info('Initializing models... This may take a moment.');
+
try {
const response = await fetch('/api/models/initialize', { method: 'POST' });
+ if (!response.ok) {
+ throw new Error(`Initialize returned ${response.status}`);
+ }
+
const data = await response.json();
- if (data.success) {
- showSuccess('Models loaded successfully');
- loadModels();
+ if (data.status === 'ok') {
+ ToastManager.success('Models initialized successfully!');
+ loadModels(); // Reload models list
} else {
- showError(data.error || 'Error loading models');
+ ToastManager.warning('Models partially initialized');
}
} catch (error) {
- showError('Error loading models: ' + error.message);
+ console.error('Error initializing models:', error);
+ ToastManager.error('Failed to initialize models');
}
}
-// Load Sentiment Models - updated to populate dropdown for sentiment analysis
-async function loadSentimentModels() {
- try {
- const response = await fetch('/api/models/list');
- const data = await response.json();
-
- const models = data.models || data || [];
- const select = document.getElementById('sentiment-model');
- if (!select) return;
-
- select.innerHTML = '
';
-
- // Filter and add models - only sentiment and generation models
- models.filter(m => {
- const category = m.category || '';
- const task = m.task || '';
- // Include sentiment models and generation/trading models
- return category.includes('sentiment') ||
- category.includes('generation') ||
- category.includes('trading') ||
- task.includes('classification') ||
- task.includes('generation');
- }).forEach(model => {
- const option = document.createElement('option');
- const modelKey = model.key || model.id;
- const modelName = model.model_id || model.name || modelKey;
- const desc = model.description || model.category || '';
-
- option.value = modelKey;
- // Show model name with short description
- const displayName = modelName.length > 40 ? modelName.substring(0, 37) + '...' : modelName;
- option.textContent = displayName;
- option.title = desc; // Full description on hover
- select.appendChild(option);
- });
-
- // If no models available, show message
- if (select.options.length === 1) {
- const option = document.createElement('option');
- option.value = '';
- option.textContent = 'No models available - will use fallback';
- option.disabled = true;
- select.appendChild(option);
- }
-
- console.log(`Loaded ${select.options.length - 1} sentiment models into dropdown`);
- } catch (error) {
- console.error('Error loading sentiment models:', error);
- const select = document.getElementById('sentiment-model');
- if (select) {
- select.innerHTML = '
';
- }
- }
-}
+// =============================================================================
+// Settings
+// =============================================================================
-// Analyze Global Market Sentiment
-async function analyzeGlobalSentiment() {
- const resultDiv = document.getElementById('global-sentiment-result');
- resultDiv.innerHTML = '
';
-
- try {
- // Use market text analysis with sample market-related text
- const marketText = "Cryptocurrency market analysis: Bitcoin, Ethereum, and major altcoins showing mixed signals. Market sentiment analysis required.";
-
- const response = await fetch('/api/sentiment/analyze', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ text: marketText, mode: 'crypto' })
- });
-
- const data = await response.json();
-
- if (!data.available) {
- resultDiv.innerHTML = `
-
-
⚠️ Models Not Available: ${data.error || 'AI models are currently unavailable'}
+function loadSettings() {
+ const apiInfoDiv = document.getElementById('api-info');
+ if (apiInfoDiv) {
+ apiInfoDiv.innerHTML = `
+
+
API Base URL: ${window.location.origin}
+
Documentation: /docs
+
Health Check: /health
`;
- return;
- }
-
- const sentiment = data.sentiment || 'neutral';
- const confidence = data.confidence || 0;
- const sentimentEmoji = sentiment === 'bullish' ? '📈' : sentiment === 'bearish' ? '📉' : '➡️';
- const sentimentColor = sentiment === 'bullish' ? 'var(--success)' : sentiment === 'bearish' ? 'var(--danger)' : 'var(--text-secondary)';
-
- resultDiv.innerHTML = `
-
-
Global Market Sentiment
-
-
-
${sentimentEmoji}
-
- ${sentiment === 'bullish' ? 'Bullish' : sentiment === 'bearish' ? 'Bearish' : 'Neutral'}
-
-
- Confidence: ${(confidence * 100).toFixed(1)}%
-
-
-
-
Details:
-
- This analysis is based on AI models.
-
-
-
-
- `;
- } catch (error) {
- console.error('Global sentiment analysis error:', error);
- resultDiv.innerHTML = `
Analysis Error: ${error.message}
`;
- showError('Error analyzing market sentiment');
}
}
-// Analyze Asset Sentiment
-async function analyzeAssetSentiment() {
- const symbol = document.getElementById('asset-symbol').value.trim().toUpperCase();
- const text = document.getElementById('asset-sentiment-text').value.trim();
-
- if (!symbol) {
- showError('Please enter a cryptocurrency symbol');
- return;
- }
-
- const resultDiv = document.getElementById('asset-sentiment-result');
- resultDiv.innerHTML = '
';
-
- 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}
-
` : ''}
-
-
-
Summary:
-
- ${summary}
-
-
-
-
-
-
- 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 = `
-
-
Error:
-
${error.message}
-
- `;
- showError('API test error: ' + error.message);
- }
-}
-
-// Utility Functions
-function showError(message) {
- console.error(message);
- ToastManager.error(message);
-}
-
-function showSuccess(message) {
- console.log(message);
- ToastManager.success(message);
-}
-
-// Additional tab loaders for HTML tabs
-async function loadMonitorData() {
- // Load API monitor data
- try {
- const response = await fetch('/api/status');
- const data = await response.json();
- const monitorContainer = document.getElementById('monitor-content');
- if (monitorContainer) {
- monitorContainer.innerHTML = `
-
-
API Status
-
${JSON.stringify(data, null, 2)}
-
- `;
- }
- } catch (error) {
- console.error('Error loading monitor data:', error);
- }
-}
-
-async function loadAdvancedData() {
- // Load advanced/API explorer data
- loadAPIEndpoints();
- loadDiagnostics();
-}
-
-async function loadAdminData() {
- // Load admin panel data
- try {
- const [providersRes, modelsRes] = await Promise.all([
- fetch('/api/providers'),
- fetch('/api/models/status')
- ]);
- const providers = await providersRes.json();
- const models = await modelsRes.json();
-
- const adminContainer = document.getElementById('admin-content');
- if (adminContainer) {
- adminContainer.innerHTML = `
-
-
System Status
-
Providers: ${providers.total || 0}
-
Models: ${models.models_loaded || 0} loaded
-
- `;
- }
- } catch (error) {
- console.error('Error loading admin data:', error);
+function saveSettings() {
+ ToastManager.success('Settings saved successfully!');
+}
+
+function toggleTheme() {
+ document.body.classList.toggle('light-theme');
+ const themeSelect = document.getElementById('theme-select');
+ if (themeSelect) {
+ themeSelect.value = document.body.classList.contains('light-theme') ? 'light' : 'dark';
}
}
-async function loadHFHealth() {
- // Load HF models health status
- try {
- const response = await fetch('/api/models/status');
- const data = await response.json();
- const hfContainer = document.getElementById('hf-status');
- if (hfContainer) {
- hfContainer.innerHTML = `
-
-
HF Models Status
-
Mode: ${data.hf_mode || 'unknown'}
-
Loaded: ${data.models_loaded || 0}
-
Failed: ${data.failed_count || 0}
-
Status: ${data.status || 'unknown'}
-
- `;
- }
- } catch (error) {
- console.error('Error loading HF health:', error);
+function changeTheme(theme) {
+ if (theme === 'light') {
+ document.body.classList.add('light-theme');
+ } else {
+ document.body.classList.remove('light-theme');
}
}
-async function loadPools() {
- // Load provider pools
- try {
- const response = await fetch('/api/pools');
- const data = await response.json();
- const poolsContainer = document.getElementById('pools-content');
- if (poolsContainer) {
- poolsContainer.innerHTML = `
-
-
Provider Pools
-
${data.message || 'No pools available'}
-
${JSON.stringify(data, null, 2)}
-
- `;
- }
- } catch (error) {
- console.error('Error loading pools:', error);
- }
-}
+// =============================================================================
+// Sentiment Analysis Functions with Visualizations
+// =============================================================================
-async function loadLogs() {
- // Load recent logs
- try {
- const response = await fetch('/api/logs/recent');
- const data = await response.json();
- const logsContainer = document.getElementById('logs-content');
- if (logsContainer) {
- const logsHtml = data.logs && data.logs.length > 0
- ? data.logs.map(log => `
${JSON.stringify(log)}
`).join('')
- : '
No logs available
';
- logsContainer.innerHTML = `
Recent Logs ${logsHtml}`;
- }
- } catch (error) {
- console.error('Error loading logs:', error);
+// Create sentiment gauge chart
+function createSentimentGauge(containerId, sentimentValue, sentimentClass) {
+ const container = document.getElementById(containerId);
+ if (!container) return null;
+
+ // Clear previous chart
+ container.innerHTML = '';
+
+ // Create canvas
+ const canvas = document.createElement('canvas');
+ canvas.id = `gauge-${containerId}`;
+ canvas.width = 300;
+ canvas.height = 150;
+ container.appendChild(canvas);
+
+ // Calculate gauge value (0-100, where 50 is neutral)
+ let gaugeValue = 50; // neutral
+ if (sentimentClass === 'bullish' || sentimentClass === 'positive') {
+ gaugeValue = 50 + (sentimentValue * 50); // 50-100
+ } else if (sentimentClass === 'bearish' || sentimentClass === 'negative') {
+ gaugeValue = 50 - (sentimentValue * 50); // 0-50
}
+ gaugeValue = Math.max(0, Math.min(100, gaugeValue));
+
+ const ctx = canvas.getContext('2d');
+ const centerX = canvas.width / 2;
+ const centerY = canvas.height / 2;
+ const radius = 60;
+
+ // Draw gauge background (semi-circle)
+ ctx.beginPath();
+ ctx.arc(centerX, centerY + 20, radius, Math.PI, 0, false);
+ ctx.lineWidth = 20;
+ ctx.strokeStyle = 'rgba(31, 41, 55, 0.6)';
+ ctx.stroke();
+
+ // Draw gauge fill
+ const startAngle = Math.PI;
+ const endAngle = Math.PI + (Math.PI * (gaugeValue / 100));
+
+ ctx.beginPath();
+ ctx.arc(centerX, centerY + 20, radius, startAngle, endAngle, false);
+ ctx.lineWidth = 20;
+ ctx.lineCap = 'round';
+
+ let gaugeColor;
+ if (gaugeValue >= 70) gaugeColor = '#10b981'; // green
+ else if (gaugeValue >= 50) gaugeColor = '#3b82f6'; // blue
+ else if (gaugeValue >= 30) gaugeColor = '#f59e0b'; // yellow
+ else gaugeColor = '#ef4444'; // red
+
+ ctx.strokeStyle = gaugeColor;
+ ctx.stroke();
+
+ // Draw value text
+ ctx.fillStyle = '#f9fafb';
+ ctx.font = 'bold 32px Inter, sans-serif';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(Math.round(gaugeValue), centerX, centerY + 15);
+
+ // Draw labels
+ ctx.fillStyle = '#9ca3af';
+ ctx.font = '12px Inter, sans-serif';
+ ctx.textAlign = 'left';
+ ctx.fillText('Bearish', 20, centerY + 50);
+ ctx.textAlign = 'right';
+ ctx.fillText('Bullish', canvas.width - 20, centerY + 50);
+
+ return canvas;
}
-async function loadReports() {
- // Load reports/analytics
- try {
- const response = await fetch('/api/providers/health-summary');
- const data = await response.json();
- const reportsContainer = document.getElementById('reports-content');
- if (reportsContainer) {
- reportsContainer.innerHTML = `
-
-
Provider Health Report
-
${JSON.stringify(data, null, 2)}
-
- `;
- }
- } catch (error) {
- console.error('Error loading reports:', error);
- }
+// Get trend arrow SVG
+function getTrendArrow(sentimentClass) {
+ const color = sentimentClass === 'bullish' ? 'var(--success)' :
+ sentimentClass === 'bearish' ? 'var(--danger)' : 'var(--warning)';
+ const rotation = sentimentClass === 'bearish' ? 'rotate(180deg)' :
+ sentimentClass === 'neutral' ? 'rotate(90deg)' : '';
+
+ return `
+
+
+
+ `;
}
-async function loadResources() {
- // Load resources summary
- try {
- const response = await fetch('/api/resources');
- const data = await response.json();
- const resourcesContainer = document.getElementById('resources-summary');
- if (resourcesContainer) {
- const summary = data.summary || {};
- resourcesContainer.innerHTML = `
-
-
Resources Summary
-
Total: ${summary.total_resources || 0}
-
Free: ${summary.free_resources || 0}
-
Models: ${summary.models_available || 0}
-
- `;
- }
- } catch (error) {
- console.error('Error loading resources:', error);
- }
+// Create confidence bar
+function createConfidenceBar(confidence) {
+ const confidencePercent = Math.round(confidence * 100);
+ return `
+
+
+ Model Confidence
+ ${confidencePercent}%
+
+
+
+ `;
}
-async function loadAPIRegistry() {
- // Load API registry from all_apis_merged_2025.json
+async function analyzeGlobalSentiment() {
+ ToastManager.info('Analyzing global market sentiment...');
+ const resultDiv = document.getElementById('global-sentiment-result');
+ if (resultDiv) {
+ resultDiv.innerHTML = '
';
+ }
+
try {
- const response = await fetch('/api/resources/apis');
+ const response = await fetch('/api/sentiment/analyze', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ text: 'Overall cryptocurrency market sentiment analysis',
+ mode: 'crypto'
+ })
+ });
+
+ if (!response.ok) throw new Error(`API returned ${response.status}`);
+
const data = await response.json();
- if (!data.ok) {
- console.warn('API registry not available:', data.error);
- const registryContainer = document.getElementById('api-registry-section');
- if (registryContainer) {
- registryContainer.innerHTML = `
-
-
📚
-
API Registry Not Available
-
- ${data.error || 'API registry file not found'}
-
-
- `;
- }
- return;
- }
+ if (data.available && data.sentiment) {
+ const sentiment = data.sentiment.toUpperCase();
+ const confidence = data.confidence || 0;
+ const sentimentClass = sentiment.includes('POSITIVE') || sentiment.includes('BULLISH') ? 'bullish' :
+ sentiment.includes('NEGATIVE') || sentiment.includes('BEARISH') ? 'bearish' : 'neutral';
- const registryContainer = document.getElementById('api-registry-section');
- if (registryContainer) {
- const metadata = data.metadata || {};
- const categories = data.categories || [];
- const rawFiles = data.raw_files_preview || [];
-
- registryContainer.innerHTML = `
-
-
-
-
- 📚 ${metadata.name || 'API Registry'}
-
-
- ${metadata.description || 'Comprehensive API registry for cryptocurrency data sources'}
-
-
-
-
Version
-
${metadata.version || 'N/A'}
-
+ resultDiv.innerHTML = `
+
+
-
-
-
-
- ${categories.length}
-
-
Categories
-
-
-
- ${data.total_raw_files || 0}
-
-
Total Files
-
- ${metadata.created_at ? `
-
-
Created
-
- ${new Date(metadata.created_at).toLocaleDateString('en-US')}
-
-
- ` : ''}
+
+
+ ${getTrendArrow(sentimentClass)}
+
+ ${sentiment}
+
+ ${getTrendArrow(sentimentClass)}
-
- ${categories.length > 0 ? `
-
-
- 📂 Categories
-
-
- ${categories.map(cat => `
-
- ${cat.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
-
- `).join('')}
-
-
- ` : ''}
-
- ${rawFiles.length > 0 ? `
-
-
- 📄 Sample Files (${rawFiles.length} of ${data.total_raw_files || 0})
-
-
- ${rawFiles.map(file => `
-
-
- ${file.filename || 'Unknown file'}
-
-
- Size: ${file.size ? (file.size / 1024).toFixed(1) + ' KB' : file.full_size ? (file.full_size / 1024).toFixed(1) + ' KB' : 'N/A'}
-
- ${file.preview ? `
-
${file.preview}
- ` : ''}
-
- `).join('')}
-
-
- ` : ''}
-
- `;
- }
+ ${createConfidenceBar(confidence)}
+
+ Model: ${data.model || 'AI Sentiment Analysis'} |
+ Engine: ${data.engine || 'N/A'}
+
+
+ `;
- // Also update metadata container if it exists
- const metadataContainer = document.getElementById('api-registry-metadata');
- if (metadataContainer) {
- metadataContainer.innerHTML = `
-
-
Metadata
-
${JSON.stringify(metadata, null, 2)}
-
- `;
+ // Create gauge chart after DOM update
+ setTimeout(() => {
+ createSentimentGauge('global-sentiment-gauge', confidence, sentimentClass);
+ }, 100);
+
+ ToastManager.success('Sentiment analysis complete!');
+ } else {
+ resultDiv.innerHTML = '
Sentiment analysis unavailable
';
}
} catch (error) {
- console.error('Error loading API registry:', error);
- const registryContainer = document.getElementById('api-registry-section');
- if (registryContainer) {
- registryContainer.innerHTML = `
-
-
❌
-
Error Loading API Registry
-
- ${error.message || 'Failed to load API registry data'}
-
-
- `;
- }
- }
-}
-
-
-
-// Theme Toggle
-function toggleTheme() {
- const body = document.body;
- const themeIcon = document.querySelector('.theme-toggle i');
-
- if (body.classList.contains('light-theme')) {
- body.classList.remove('light-theme');
- themeIcon.className = 'fas fa-moon';
- localStorage.setItem('theme', 'dark');
- } else {
- body.classList.add('light-theme');
- themeIcon.className = 'fas fa-sun';
- localStorage.setItem('theme', 'light');
+ console.error('Error analyzing sentiment:', error);
+ resultDiv.innerHTML = `
Error: ${error.message}
`;
+ ToastManager.error('Failed to analyze sentiment');
}
}
-// Load theme preference
-document.addEventListener('DOMContentLoaded', () => {
- const savedTheme = localStorage.getItem('theme');
- if (savedTheme === 'light') {
- document.body.classList.add('light-theme');
- const themeIcon = document.querySelector('.theme-toggle i');
- if (themeIcon) themeIcon.className = 'fas fa-sun';
- }
-});
-
-// Update header stats
-function updateHeaderStats() {
- const totalResources = document.getElementById('stat-total-resources')?.textContent || '-';
- const totalModels = document.getElementById('stat-models')?.textContent || '-';
-
- const headerResources = document.getElementById('header-resources');
- const headerModels = document.getElementById('header-models');
+async function analyzeAssetSentiment() {
+ const symbol = document.getElementById('asset-symbol')?.value;
+ const text = document.getElementById('asset-sentiment-text')?.value;
- if (headerResources) headerResources.textContent = totalResources;
- if (headerModels) headerModels.textContent = totalModels;
-}
-
-// Call updateHeaderStats after loading dashboard
-const originalLoadDashboard = loadDashboard;
-loadDashboard = async function() {
- await originalLoadDashboard();
- updateHeaderStats();
-};
-
-// ===== AI Analyst Functions =====
-async function runAIAnalyst() {
- const prompt = document.getElementById('ai-analyst-prompt').value.trim();
- const mode = document.getElementById('ai-analyst-mode').value;
- const maxLength = parseInt(document.getElementById('ai-analyst-max-length').value);
-
- if (!prompt) {
- showError('Please enter a prompt or question');
+ if (!symbol) {
+ ToastManager.warning('Please select a trading pair');
return;
}
- const resultDiv = document.getElementById('ai-analyst-result');
- resultDiv.innerHTML = '
';
+ ToastManager.info('Analyzing asset sentiment...');
+ const resultDiv = document.getElementById('asset-sentiment-result');
+ if (resultDiv) {
+ resultDiv.innerHTML = '
';
+ }
try {
- const response = await fetch('/api/analyze/text', {
+ const response = await fetch('/api/sentiment/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- prompt: prompt,
- mode: mode,
- max_length: maxLength
+ body: JSON.stringify({
+ text: text || `Sentiment analysis for ${symbol}`,
+ mode: 'crypto',
+ symbol: symbol
})
});
+ if (!response.ok) throw new Error(`API returned ${response.status}`);
+
const data = await response.json();
- if (!data.available) {
+ if (data.available && data.sentiment) {
+ const sentiment = data.sentiment.toUpperCase();
+ const confidence = data.confidence || 0;
+ const sentimentClass = sentiment.includes('POSITIVE') || sentiment.includes('BULLISH') ? 'bullish' :
+ sentiment.includes('NEGATIVE') || sentiment.includes('BEARISH') ? 'bearish' : 'neutral';
+
resultDiv.innerHTML = `
-
-
⚠️ Model Not Available: ${data.error || 'AI generation model is currently unavailable'}
- ${data.note ? `
${data.note} ` : ''}
+
+
+
+
+ ${getTrendArrow(sentimentClass)}
+
+ ${sentiment}
+
+ ${getTrendArrow(sentimentClass)}
+
+ ${createConfidenceBar(confidence)}
+
+ Model: ${data.model || 'AI Sentiment Analysis'} |
+ Engine: ${data.engine || 'N/A'}
+
`;
- return;
+
+ // Create gauge chart after DOM update
+ setTimeout(() => {
+ createSentimentGauge('asset-sentiment-gauge', confidence, sentimentClass);
+ }, 100);
+
+ ToastManager.success('Asset sentiment analysis complete!');
+ } else {
+ resultDiv.innerHTML = '
Sentiment analysis unavailable
';
}
+ } catch (error) {
+ console.error('Error analyzing asset sentiment:', error);
+ resultDiv.innerHTML = `
Error: ${error.message}
`;
+ ToastManager.error('Failed to analyze asset sentiment');
+ }
+}
+
+async function analyzeSentiment() {
+ const text = document.getElementById('sentiment-text')?.value;
+ const mode = document.getElementById('sentiment-mode')?.value || 'auto';
+
+ if (!text || text.trim() === '') {
+ ToastManager.warning('Please enter text to analyze');
+ return;
+ }
+
+ ToastManager.info('Analyzing sentiment...');
+ const resultDiv = document.getElementById('sentiment-result');
+ if (resultDiv) {
+ resultDiv.innerHTML = '
';
+ }
+
+ try {
+ const response = await fetch('/api/sentiment/analyze', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ text, mode })
+ });
- if (!data.success) {
- resultDiv.innerHTML = `
-
- ❌ Generation Failed: ${data.error || 'Failed to generate analysis'}
-
- `;
- return;
- }
+ if (!response.ok) throw new Error(`API returned ${response.status}`);
- const generatedText = data.text || '';
- const model = data.model || 'Unknown';
+ const data = await response.json();
- resultDiv.innerHTML = `
-
-
-
✨ AI Generated Analysis
-
-
-
-
- ${generatedText}
+ if (data.available && data.sentiment) {
+ const sentiment = data.sentiment.toUpperCase();
+ const confidence = data.confidence || 0;
+ const sentimentClass = sentiment.includes('POSITIVE') || sentiment.includes('BULLISH') ? 'bullish' :
+ sentiment.includes('NEGATIVE') || sentiment.includes('BEARISH') ? 'bearish' : 'neutral';
+
+ resultDiv.innerHTML = `
+
+
-
-
-
-
-
- Model:
- ${model}
-
-
- Mode:
- ${mode}
-
-
- Prompt:
- "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"
-
-
- Timestamp:
- ${new Date(data.timestamp).toLocaleString()}
-
+
+
+ ${getTrendArrow(sentimentClass)}
+
+ ${sentiment}
+
+ ${getTrendArrow(sentimentClass)}
-
-
-
-
- 📋 Copy Analysis
-
-
- 🔄 Clear
-
-
+ ${createConfidenceBar(confidence)}
+
+ Text: ${text.substring(0, 100)}${text.length > 100 ? '...' : ''}
+ Model: ${data.model || 'AI Sentiment Analysis'} |
+ Engine: ${data.engine || 'N/A'}
+
`;
- // Store for clipboard
- window.lastAIAnalysis = generatedText;
+ // Create gauge chart after DOM update
+ setTimeout(() => {
+ createSentimentGauge('sentiment-gauge', confidence, sentimentClass);
+ }, 100);
+ ToastManager.success('Sentiment analysis complete!');
+ } else {
+ resultDiv.innerHTML = '
Sentiment analysis unavailable
';
+ }
} catch (error) {
- console.error('AI analyst error:', error);
- resultDiv.innerHTML = `
Generation Error: ${error.message}
`;
- showError('Error generating analysis');
- }
-}
-
-function setAIAnalystPrompt(text) {
- document.getElementById('ai-analyst-prompt').value = text;
-}
-
-async function copyAIAnalystResult() {
- if (!window.lastAIAnalysis) {
- showError('No analysis to copy');
- return;
- }
-
- try {
- await navigator.clipboard.writeText(window.lastAIAnalysis);
- showSuccess('Analysis copied to clipboard!');
- } catch (error) {
- console.error('Failed to copy:', error);
- showError('Failed to copy analysis');
+ console.error('Error analyzing sentiment:', error);
+ resultDiv.innerHTML = `
Error: ${error.message}
`;
+ ToastManager.error('Failed to analyze sentiment');
}
}
-function clearAIAnalystForm() {
- document.getElementById('ai-analyst-prompt').value = '';
- document.getElementById('ai-analyst-result').innerHTML = '';
- window.lastAIAnalysis = null;
-}
+// =============================================================================
+// Trading Assistant
+// =============================================================================
-// ===== Trading Assistant Functions =====
async function runTradingAssistant() {
- const symbol = document.getElementById('trading-symbol').value.trim().toUpperCase();
- const context = document.getElementById('trading-context').value.trim();
+ const symbol = document.getElementById('trading-symbol')?.value;
+ const context = document.getElementById('trading-context')?.value;
if (!symbol) {
- showError('Please enter a trading symbol');
+ ToastManager.warning('Please select a trading symbol');
return;
}
+ ToastManager.info('Generating trading signal...');
const resultDiv = document.getElementById('trading-assistant-result');
- resultDiv.innerHTML = '
Analyzing and generating trading signal...
';
+ if (resultDiv) {
+ resultDiv.innerHTML = '
';
+ }
try {
const response = await fetch('/api/trading/decision', {
@@ -2489,151 +1252,84 @@ async function runTradingAssistant() {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: symbol,
- context: context
+ context: context || `Trading decision for ${symbol}`
})
});
- const data = await response.json();
+ if (!response.ok) throw new Error(`API returned ${response.status}`);
- if (!data.available) {
- resultDiv.innerHTML = `
-
- ⚠️ Model Not Available: ${data.error || 'Trading signal model is currently unavailable'}
- ${data.note ? `${data.note} ` : ''}
-
- `;
- return;
- }
+ const data = await response.json();
- if (!data.success) {
+ if (data.decision) {
+ const decision = data.decision.toUpperCase();
+ const confidence = data.confidence ? (data.confidence * 100).toFixed(2) : 'N/A';
+ const decisionClass = decision === 'BUY' ? 'bullish' : decision === 'SELL' ? 'bearish' : 'neutral';
+
resultDiv.innerHTML = `
-
- ❌ Analysis Failed: ${data.error || 'Failed to generate trading signal'}
-
- `;
- return;
- }
-
- const decision = data.decision || 'HOLD';
- const confidence = data.confidence || 0;
- const rationale = data.rationale || '';
- const model = data.model || 'Unknown';
-
- // Determine colors and icons based on decision
- let decisionColor, decisionBg, decisionIcon;
- if (decision === 'BUY') {
- decisionColor = 'var(--success)';
- decisionBg = 'rgba(16, 185, 129, 0.2)';
- decisionIcon = '📈';
- } else if (decision === 'SELL') {
- decisionColor = 'var(--danger)';
- decisionBg = 'rgba(239, 68, 68, 0.2)';
- decisionIcon = '📉';
- } else {
- decisionColor = 'var(--text-secondary)';
- decisionBg = 'rgba(156, 163, 175, 0.2)';
- decisionIcon = '➡️';
- }
-
- resultDiv.innerHTML = `
-
-
🎯 Trading Signal for ${symbol}
-
-
-
-
${decisionIcon}
-
- ${decision}
-
-
- Decision
-
-
-
-
-
- ${(confidence * 100).toFixed(0)}%
-
-
- Confidence
-
-
+
+
-
-
-
AI Rationale:
-
- ${rationale}
-
-
-
- ${context ? `
-
-
Your Context:
-
- "${context.substring(0, 200)}${context.length > 200 ? '...' : ''}"
+
+
+
${confidence}%
+
Confidence
-
- ` : ''}
-
-
-
-
- Model:
- ${model}
-
- Timestamp:
- ${new Date(data.timestamp).toLocaleString()}
-
-
-
-
-
-
⚠️ Reminder:
-
- This is an AI-generated signal for informational purposes only. Always do your own research and consider multiple factors before trading.
+ ${data.reasoning ? `
Reasoning: ${data.reasoning}
` : ''}
+
+ Model: ${data.model || 'AI Trading Assistant'}
-
`;
-
+ ToastManager.success('Trading signal generated!');
+ } else {
+ resultDiv.innerHTML = '
Trading signal unavailable
';
+ }
} catch (error) {
- console.error('Trading assistant error:', error);
- resultDiv.innerHTML = `
Analysis Error: ${error.message}
`;
- showError('Error generating trading signal');
+ console.error('Error generating trading signal:', error);
+ resultDiv.innerHTML = `
Error: ${error.message}
`;
+ ToastManager.error('Failed to generate trading signal');
}
}
-// Initialize trading pair selector for trading assistant tab
-function initTradingSymbolSelector() {
- const tradingSymbolContainer = document.getElementById('trading-symbol-container');
- if (tradingSymbolContainer && window.TradingPairsLoader) {
- const pairs = window.TradingPairsLoader.getTradingPairs();
- if (pairs && pairs.length > 0) {
- tradingSymbolContainer.innerHTML = window.TradingPairsLoader.createTradingPairCombobox(
- 'trading-symbol',
- 'Select or type trading pair',
- 'BTCUSDT'
- );
- }
- }
+// =============================================================================
+// Utility Functions
+// =============================================================================
+
+function formatNumber(num) {
+ if (num === null || num === undefined) return '0';
+ if (num >= 1e12) return (num / 1e12).toFixed(2) + 'T';
+ if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
+ if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
+ if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
+ return num.toFixed(2);
}
-// Update loadTabData to handle new tabs
-const originalLoadTabData = loadTabData;
-loadTabData = function(tabId) {
- originalLoadTabData(tabId);
-
- // Additional handlers for new tabs
- if (tabId === 'ai-analyst') {
- // No initialization needed for AI Analyst yet
- } else if (tabId === 'trading-assistant') {
- initTradingSymbolSelector();
- }
-};
+// =============================================================================
+// Export for global access
+// =============================================================================
-// Listen for trading pairs loaded event to initialize trading symbol selector
-document.addEventListener('tradingPairsLoaded', function(e) {
- initTradingSymbolSelector();
-});
+window.AppState = AppState;
+window.ToastManager = ToastManager;
+window.toggleSidebar = toggleSidebar;
+window.switchTab = switchTab;
+window.refreshCurrentTab = refreshCurrentTab;
+window.loadDashboard = loadDashboard;
+window.loadMarketData = loadMarketData;
+window.loadModels = loadModels;
+window.initializeModels = initializeModels;
+window.loadNews = loadNews;
+window.fetchNewsFromAPI = fetchNewsFromAPI;
+window.loadSettings = loadSettings;
+window.saveSettings = saveSettings;
+window.toggleTheme = toggleTheme;
+window.changeTheme = changeTheme;
+window.analyzeGlobalSentiment = analyzeGlobalSentiment;
+window.analyzeAssetSentiment = analyzeAssetSentiment;
+window.analyzeSentiment = analyzeSentiment;
+window.runTradingAssistant = runTradingAssistant;
+window.formatNumber = formatNumber;
+
+console.log('✅ App.js loaded successfully');