/** * AI Tools Page - Comprehensive AI Analysis Suite */ class AIToolsPage { constructor() { this.history = this.loadHistory(); this.currentTab = 'sentiment'; this.init(); } /** * Initialize the page */ init() { this.setupTabs(); this.setupEventListeners(); this.loadModelStatus(); this.updateStats(); this.renderHistory(); } /** * Setup tab navigation */ setupTabs() { const tabs = document.querySelectorAll('#ai-tools-tabs .tab'); const panes = document.querySelectorAll('.tab-pane'); tabs.forEach(tab => { tab.addEventListener('click', () => { const targetTab = tab.dataset.tab; // Update active tab tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Update active pane panes.forEach(p => p.classList.remove('active')); const targetPane = document.getElementById(`tab-${targetTab}`); if (targetPane) { targetPane.classList.add('active'); this.currentTab = targetTab; } }); }); } /** * Setup event listeners */ setupEventListeners() { // Sentiment document.getElementById('analyze-sentiment-btn')?.addEventListener('click', () => this.analyzeSentiment()); // Summarize document.getElementById('summarize-btn')?.addEventListener('click', () => this.summarizeText()); // News document.getElementById('analyze-news-btn')?.addEventListener('click', () => this.analyzeNews()); // Trading document.getElementById('get-trading-decision-btn')?.addEventListener('click', () => this.getTradingDecision()); // Batch document.getElementById('process-batch-btn')?.addEventListener('click', () => this.processBatch()); // History document.getElementById('clear-history-btn')?.addEventListener('click', () => this.clearHistory()); document.getElementById('export-history-btn')?.addEventListener('click', () => this.exportHistory()); // Model Status document.getElementById('refresh-status-btn')?.addEventListener('click', () => this.loadModelStatus()); // Refresh All document.getElementById('refresh-all-btn')?.addEventListener('click', () => { this.loadModelStatus(); this.updateStats(); }); } /** * Update statistics cards - REAL DATA from API */ async updateStats() { try { const [statusRes, resourcesRes] = await Promise.allSettled([ fetch('/api/models/status', { signal: AbortSignal.timeout(10000) }), fetch('/api/resources/summary', { signal: AbortSignal.timeout(10000) }) ]); // Update model stats if (statusRes.status === 'fulfilled' && statusRes.value.ok) { const statusData = await statusRes.value.json(); const modelsLoaded = document.getElementById('models-loaded'); const hfMode = document.getElementById('hf-mode'); const failedModels = document.getElementById('failed-models'); const hfStatus = document.getElementById('hf-status'); const loadedCount = statusData.models_loaded || statusData.models?.total_models || 0; const totalModels = statusData.models?.total_models || statusData.models_loaded || 0; const failedCount = totalModels - loadedCount; if (modelsLoaded) modelsLoaded.textContent = loadedCount; if (hfMode) hfMode.textContent = (statusData.hf_mode || 'off').toUpperCase(); if (failedModels) failedModels.textContent = failedCount; if (hfStatus) { if (statusData.status === 'ready' || statusData.models_loaded > 0) { hfStatus.textContent = 'Ready'; hfStatus.className = 'stat-trend success'; } else { hfStatus.textContent = 'Disabled'; hfStatus.className = 'stat-trend warning'; } } } // Update analyses count const analysesToday = document.getElementById('analyses-today'); if (analysesToday) { const today = new Date().toDateString(); const todayCount = this.history.filter(h => new Date(h.timestamp).toDateString() === today).length; analysesToday.textContent = todayCount; } // Update resources stats if available if (resourcesRes.status === 'fulfilled' && resourcesRes.value.ok) { const resourcesData = await resourcesRes.value.json(); if (resourcesData.resources) { const hfModels = resourcesData.huggingface_models || {}; const totalModels = hfModels.total_models || 0; const loadedModels = hfModels.loaded_models || 0; // Update model stats with real data if (modelsLoaded && !modelsLoaded.textContent) { modelsLoaded.textContent = loadedModels; } } } } catch (error) { console.error('Failed to update stats:', error); } } /** * Analyze sentiment of text */ async analyzeSentiment() { const text = document.getElementById('sentiment-input').value.trim(); const mode = document.getElementById('sentiment-source').value; const symbol = document.getElementById('sentiment-symbol').value.trim().toUpperCase(); const btn = document.getElementById('analyze-sentiment-btn'); const resultDiv = document.getElementById('sentiment-result'); if (!text) { this.showError(resultDiv, 'Please enter text to analyze'); return; } btn.disabled = true; btn.innerHTML = ' Analyzing...'; resultDiv?.classList.add('hidden'); try { const payload = { text, mode, source: 'ai_tools' }; if (symbol) payload.symbol = symbol; const response = await fetch('/api/sentiment/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await response.json(); if (!response.ok || !data.ok) { throw new Error(data.error || 'Sentiment analysis failed'); } this.displaySentimentResult(resultDiv, data); this.addToHistory('sentiment', { text, symbol, result: data }); this.updateStats(); } catch (error) { this.showError(resultDiv, error.message); } finally { btn.disabled = false; btn.innerHTML = ' Analyze Sentiment'; } } /** * Display sentiment analysis result */ displaySentimentResult(container, data) { if (!container) return; const label = data.label || 'unknown'; const score = (data.score * 100).toFixed(1); const labelClass = label.toLowerCase(); const engine = data.engine || 'unknown'; let displayLabel = label; if (label === 'bullish' || label === 'positive') displayLabel = 'Bullish/Positive'; else if (label === 'bearish' || label === 'negative') displayLabel = 'Bearish/Negative'; else if (label === 'neutral') displayLabel = 'Neutral'; let html = '
'; html += '

Sentiment Analysis Result

'; html += `
`; html += `
`; html += `${displayLabel.toUpperCase()}`; html += `${score}%`; html += `
`; html += `
Engine: ${engine}
`; html += `
`; if (data.model) { html += `

Model: ${data.model}

`; } if (data.details && data.details.labels && data.details.scores) { html += '
'; for (let i = 0; i < data.details.labels.length; i++) { const lbl = data.details.labels[i]; const scr = (data.details.scores[i] * 100).toFixed(1); html += '
'; html += `${lbl}`; html += '
'; html += `
`; html += '
'; html += `${scr}%`; html += '
'; } html += '
'; } if (engine === 'fallback_lexical') { html += '
'; html += 'Note: Using fallback lexical analysis. HF models may be unavailable.'; html += '
'; } html += '
'; container.innerHTML = html; container.classList.remove('hidden'); } /** * Summarize text */ async summarizeText() { const text = document.getElementById('summary-input').value.trim(); const maxSentences = parseInt(document.getElementById('max-sentences').value); const style = document.getElementById('summary-style').value; const btn = document.getElementById('summarize-btn'); const resultDiv = document.getElementById('summary-result'); if (!text) { this.showError(resultDiv, 'Please enter text to summarize'); return; } btn.disabled = true; btn.innerHTML = ' Summarizing...'; resultDiv?.classList.add('hidden'); try { const response = await fetch('/api/ai/summarize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, max_sentences: maxSentences, style }) }); const data = await response.json(); if (!response.ok || !data.ok) { throw new Error(data.error || 'Summarization failed'); } this.displaySummaryResult(resultDiv, data, style); this.addToHistory('summarize', { text, maxSentences, result: data }); this.updateStats(); } catch (error) { this.showError(resultDiv, error.message); } finally { btn.disabled = false; btn.innerHTML = ' Summarize'; } } /** * Display summary result */ displaySummaryResult(container, data, style = 'detailed') { if (!container) return; let html = '
'; html += '

Summary

'; if (data.summary) { if (style === 'bullet') { html += ''; } else { html += `
${this.escapeHtml(data.summary)}
`; } } if (data.sentences && data.sentences.length > 0 && style !== 'bullet') { html += '

Key Sentences

'; html += ''; } html += '
'; container.innerHTML = html; container.classList.remove('hidden'); } /** * Analyze news article */ async analyzeNews() { const text = document.getElementById('news-input').value.trim(); const symbol = document.getElementById('news-symbol').value.trim().toUpperCase(); const analysisType = document.getElementById('analysis-type').value; const btn = document.getElementById('analyze-news-btn'); const resultDiv = document.getElementById('news-result'); if (!text) { this.showError(resultDiv, 'Please enter news text to analyze'); return; } btn.disabled = true; btn.innerHTML = ' Analyzing...'; resultDiv?.classList.add('hidden'); try { const results = {}; if (analysisType === 'full' || analysisType === 'sentiment') { const sentimentRes = await fetch('/api/sentiment/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, mode: 'news', symbol }) }); if (sentimentRes.ok) { results.sentiment = await sentimentRes.json(); } } if (analysisType === 'full' || analysisType === 'summary') { const summaryRes = await fetch('/api/ai/summarize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, max_sentences: 3 }) }); if (summaryRes.ok) { results.summary = await summaryRes.json(); } } this.displayNewsResult(resultDiv, results); this.addToHistory('news', { text, symbol, result: results }); this.updateStats(); } catch (error) { this.showError(resultDiv, error.message); } finally { btn.disabled = false; btn.innerHTML = ' Analyze News'; } } /** * Display news analysis result */ displayNewsResult(container, results) { if (!container) return; let html = '
'; html += '

News Analysis Result

'; if (results.sentiment && results.sentiment.ok) { const sent = results.sentiment; const label = sent.label || 'unknown'; const score = (sent.score * 100).toFixed(1); html += '
'; html += '

Sentiment

'; html += `${label.toUpperCase()}`; html += `${score}%`; html += '
'; } if (results.summary && results.summary.ok) { html += '
'; html += '

Summary

'; html += `
${this.escapeHtml(results.summary.summary || '')}
`; html += '
'; } html += '
'; container.innerHTML = html; container.classList.remove('hidden'); } /** * Get trading decision */ async getTradingDecision() { const symbol = document.getElementById('trading-symbol').value.trim().toUpperCase(); const timeframe = document.getElementById('trading-timeframe').value; const context = document.getElementById('trading-context').value.trim(); const btn = document.getElementById('get-trading-decision-btn'); const resultDiv = document.getElementById('trading-result'); if (!symbol) { this.showError(resultDiv, 'Please enter an asset symbol'); return; } btn.disabled = true; btn.innerHTML = ' Analyzing...'; resultDiv?.classList.add('hidden'); try { const response = await fetch('/api/ai/decision', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ symbol, timeframe, context }) }); const data = await response.json(); if (!response.ok || !data.ok) { throw new Error(data.error || 'Trading decision failed'); } this.displayTradingResult(resultDiv, data); this.addToHistory('trading', { symbol, timeframe, result: data }); this.updateStats(); } catch (error) { this.showError(resultDiv, error.message); } finally { btn.disabled = false; btn.innerHTML = ' Get Trading Decision'; } } /** * Display trading decision result */ displayTradingResult(container, data) { if (!container) return; const decision = data.decision || data.action || 'HOLD'; const confidence = data.confidence || data.score || 0; const reasoning = data.reasoning || data.reason || 'No reasoning provided'; // Sanitize all dynamic content const safeDecision = this.escapeHtml(decision); const safeConfidence = this.escapeHtml((confidence * 100).toFixed(1)); const safeReasoning = this.escapeHtml(reasoning); let html = '
'; html += '

Trading Decision

'; html += `
`; html += `${safeDecision}`; html += `${safeConfidence}% Confidence`; html += `
`; html += `
${safeReasoning}
`; html += '
'; container.innerHTML = html; container.classList.remove('hidden'); } /** * Process batch of texts */ async processBatch() { const text = document.getElementById('batch-input').value.trim(); const operation = document.getElementById('batch-operation').value; const format = document.getElementById('batch-format').value; const btn = document.getElementById('process-batch-btn'); const resultDiv = document.getElementById('batch-result'); if (!text) { this.showError(resultDiv, 'Please enter texts to process'); return; } const texts = text.split('\n').filter(t => t.trim()); if (texts.length === 0) { this.showError(resultDiv, 'Please enter at least one text'); return; } btn.disabled = true; const safeCount = this.escapeHtml(String(texts.length)); btn.innerHTML = ` Processing ${safeCount} items...`; resultDiv?.classList.add('hidden'); try { const results = []; for (let i = 0; i < texts.length; i++) { const item = { text: texts[i], index: i + 1 }; if (operation === 'sentiment' || operation === 'both') { const res = await fetch('/api/sentiment/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: texts[i], mode: 'auto' }) }); if (res.ok) { item.sentiment = await res.json(); } } if (operation === 'summarize' || operation === 'both') { const res = await fetch('/api/ai/summarize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: texts[i], max_sentences: 2 }) }); if (res.ok) { item.summary = await res.json(); } } results.push(item); } this.displayBatchResult(resultDiv, results, format); this.addToHistory('batch', { count: texts.length, operation, results }); this.updateStats(); } catch (error) { this.showError(resultDiv, error.message); } finally { btn.disabled = false; btn.innerHTML = ' Process Batch'; } } /** * Display batch processing result */ displayBatchResult(container, results, format) { if (!container) return; let html = '
'; html += '
'; html += `

Batch Results (${results.length} items)

`; html += ``; html += '
'; if (format === 'table') { html += '
'; if (results[0].sentiment) html += ''; if (results[0].summary) html += ''; html += ''; results.forEach(item => { html += ''; html += ``; html += ``; if (item.sentiment && item.sentiment.ok) { const sentimentLabel = this.escapeHtml(item.sentiment.label || 'N/A'); const sentimentClass = this.escapeHtml((item.sentiment.label?.toLowerCase() || 'neutral')); html += ``; } if (item.summary && item.summary.ok) { html += ``; } html += ''; }); html += '
#Text PreviewSentimentSummary
${item.index}${this.escapeHtml(item.text.substring(0, 100))}...${sentimentLabel}${this.escapeHtml(item.summary.summary?.substring(0, 80) || '')}...
'; } else { html += '
';
      html += this.escapeHtml(JSON.stringify(results, null, 2));
      html += '
'; } html += '
'; container.innerHTML = html; container.classList.remove('hidden'); } /** * Download batch results */ downloadBatchResults(results) { const dataStr = JSON.stringify(results, null, 2); const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); const link = document.createElement('a'); link.setAttribute('href', dataUri); link.setAttribute('download', `batch-results-${Date.now()}.json`); link.click(); } /** * Load model status */ async loadModelStatus() { const statusDiv = document.getElementById('registry-status'); const tableDiv = document.getElementById('models-table'); const btn = document.getElementById('refresh-status-btn'); if (btn) { btn.disabled = true; btn.innerHTML = ' Loading...'; } try { const [statusRes, listRes] = await Promise.all([ fetch('/api/models/status'), fetch('/api/models/list') ]); const statusData = await statusRes.json(); const listData = await listRes.json(); this.displayRegistryStatus(statusDiv, statusData); this.displayModelsTable(tableDiv, listData); this.updateStats(); } catch (error) { this.showError(statusDiv, 'Failed to load model status: ' + error.message); } finally { if (btn) { btn.disabled = false; btn.innerHTML = ' Refresh'; } } } /** * Display registry status */ displayRegistryStatus(container, data) { if (!container) return; let html = '
'; html += '
'; html += '
HF Mode
'; html += `
${data.hf_mode || 'unknown'}
`; html += '
'; html += '
'; html += '
Overall Status
'; html += `
${data.status || 'unknown'}
`; html += '
'; html += '
'; html += '
Models Loaded
'; html += `
${data.models_loaded || 0}
`; html += '
'; html += '
'; html += '
Models Failed
'; html += `
${data.models_failed || 0}
`; html += '
'; html += '
'; if (data.status === 'disabled' || data.hf_mode === 'off') { html += '
'; html += 'Note: HF models are disabled. To enable them, set HF_MODE=public or HF_MODE=auth in the environment.'; html += '
'; } else if (data.models_loaded === 0 && data.status !== 'disabled') { html += '
'; html += 'Warning: No models could be loaded. Check model IDs or HF credentials.'; html += '
'; } if (data.error) { html += '
'; html += `Error: ${this.escapeHtml(data.error)}`; html += '
'; } if (data.failed && data.failed.length > 0) { html += '
'; html += '

Failed Models

'; html += '
'; data.failed.forEach(([key, error]) => { html += `
`; html += `${key}: `; html += `${this.escapeHtml(error)}`; html += `
`; }); html += '
'; html += '
'; } container.innerHTML = html; } /** * Display models table */ displayModelsTable(container, data) { if (!container) return; if (!data.models || data.models.length === 0) { container.innerHTML = '
No models configured
'; return; } let html = '
'; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; data.models.forEach(model => { html += ''; html += ``; html += ``; html += ``; html += ''; html += ``; html += ''; }); html += ''; html += '
KeyTaskModel IDLoadedError
${model.key || 'N/A'}${model.task || 'N/A'}${model.model_id || 'N/A'}'; if (model.loaded) { html += 'Yes'; } else { html += 'No'; } html += '${model.error ? this.escapeHtml(model.error) : '-'}
'; html += '
'; container.innerHTML = html; } /** * Add to history */ addToHistory(type, data) { const entry = { type, timestamp: new Date().toISOString(), data }; this.history.unshift(entry); if (this.history.length > 100) { this.history = this.history.slice(0, 100); } this.saveHistory(); this.renderHistory(); } /** * Load history from localStorage */ loadHistory() { try { const stored = localStorage.getItem('ai-tools-history'); return stored ? JSON.parse(stored) : []; } catch { return []; } } /** * Save history to localStorage */ saveHistory() { try { localStorage.setItem('ai-tools-history', JSON.stringify(this.history)); } catch (error) { console.error('Failed to save history:', error); } } /** * Render history list */ renderHistory() { const container = document.getElementById('history-list'); if (!container) return; if (this.history.length === 0) { container.innerHTML = '

No analysis history yet. Start analyzing to see your history here.

'; return; } let html = ''; this.history.slice(0, 50).forEach((entry, index) => { const date = new Date(entry.timestamp); html += `
`; html += `
`; html += `${entry.type.toUpperCase()}`; html += `${date.toLocaleString()}`; html += `
`; html += `
${this.escapeHtml(JSON.stringify(entry.data).substring(0, 150))}...
`; html += ``; html += `
`; }); container.innerHTML = html; } /** * View history item */ viewHistoryItem(index) { const entry = this.history[index]; if (!entry) return; alert(JSON.stringify(entry, null, 2)); } /** * Clear history */ clearHistory() { if (confirm('Are you sure you want to clear all history?')) { this.history = []; this.saveHistory(); this.renderHistory(); this.updateStats(); } } /** * Export history */ exportHistory() { const dataStr = JSON.stringify(this.history, null, 2); const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); const link = document.createElement('a'); link.setAttribute('href', dataUri); link.setAttribute('download', `ai-tools-history-${Date.now()}.json`); link.click(); } /** * Show error message */ showError(container, message) { if (!container) return; container.innerHTML = `
Error: ${this.escapeHtml(message)}
`; container.classList.remove('hidden'); } /** * Escape HTML */ escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } export default AIToolsPage;