| import apiClient from './apiClient.js'; | |
| class ChartLabView { | |
| constructor(section) { | |
| this.section = section; | |
| this.symbolSelect = section.querySelector('[data-chart-symbol]'); | |
| this.timeframeButtons = section.querySelectorAll('[data-chart-timeframe]'); | |
| this.indicatorInputs = section.querySelectorAll('[data-indicator]'); | |
| this.analyzeButton = section.querySelector('[data-run-analysis]'); | |
| this.canvas = section.querySelector('#chart-lab-canvas'); | |
| this.insightsContainer = section.querySelector('[data-ai-insights]'); | |
| this.chart = null; | |
| this.symbol = 'BTC'; | |
| this.timeframe = '7d'; | |
| } | |
| async init() { | |
| await this.loadChart(); | |
| this.bindEvents(); | |
| } | |
| bindEvents() { | |
| if (this.symbolSelect) { | |
| this.symbolSelect.addEventListener('change', async () => { | |
| this.symbol = this.symbolSelect.value; | |
| await this.loadChart(); | |
| }); | |
| } | |
| this.timeframeButtons.forEach((btn) => { | |
| btn.addEventListener('click', async () => { | |
| this.timeframeButtons.forEach((b) => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| this.timeframe = btn.dataset.chartTimeframe; | |
| await this.loadChart(); | |
| }); | |
| }); | |
| if (this.analyzeButton) { | |
| this.analyzeButton.addEventListener('click', () => this.runAnalysis()); | |
| } | |
| } | |
| async loadChart() { | |
| if (!this.canvas) return; | |
| const result = await apiClient.getPriceChart(this.symbol, this.timeframe); | |
| const container = this.canvas.parentElement; | |
| if (!result.ok) { | |
| if (container) { | |
| let errorNode = container.querySelector('.chart-error'); | |
| if (!errorNode) { | |
| errorNode = document.createElement('div'); | |
| errorNode.className = 'inline-message inline-error chart-error'; | |
| container.appendChild(errorNode); | |
| } | |
| errorNode.textContent = result.error; | |
| } | |
| return; | |
| } | |
| if (container) { | |
| const errorNode = container.querySelector('.chart-error'); | |
| if (errorNode) errorNode.remove(); | |
| } | |
| const points = result.data || []; | |
| const labels = points.map((point) => point.time || point.timestamp || ''); | |
| const prices = points.map((point) => point.price || point.close || point.value); | |
| if (this.chart) { | |
| this.chart.destroy(); | |
| } | |
| this.chart = new Chart(this.canvas, { | |
| type: 'line', | |
| data: { | |
| labels, | |
| datasets: [ | |
| { | |
| label: `${this.symbol} (${this.timeframe})`, | |
| data: prices, | |
| borderColor: '#f472b6', | |
| backgroundColor: 'rgba(244, 114, 182, 0.2)', | |
| fill: true, | |
| tension: 0.4, | |
| }, | |
| ], | |
| }, | |
| options: { | |
| scales: { | |
| x: { ticks: { color: 'var(--text-muted)' } }, | |
| y: { ticks: { color: 'var(--text-muted)' } }, | |
| }, | |
| plugins: { | |
| legend: { display: false }, | |
| }, | |
| }, | |
| }); | |
| } | |
| async runAnalysis() { | |
| if (!this.insightsContainer) return; | |
| const enabledIndicators = Array.from(this.indicatorInputs) | |
| .filter((input) => input.checked) | |
| .map((input) => input.value); | |
| this.insightsContainer.innerHTML = '<p>Running AI analysis...</p>'; | |
| const result = await apiClient.analyzeChart(this.symbol, this.timeframe, enabledIndicators); | |
| if (!result.ok) { | |
| this.insightsContainer.innerHTML = `<div class="inline-message inline-error">${result.error}</div>`; | |
| return; | |
| } | |
| const payload = result.data || {}; | |
| const insights = payload.insights || result.insights || payload; | |
| if (!insights) { | |
| this.insightsContainer.innerHTML = '<p>No AI insights returned.</p>'; | |
| return; | |
| } | |
| const summary = | |
| insights.narrative?.summary?.summary || insights.narrative?.summary || insights.narrative?.summary_text; | |
| const signals = insights.narrative?.signals || {}; | |
| const bullets = Object.entries(signals) | |
| .map(([key, value]) => `<li><strong>${key}:</strong> ${(value?.label || 'n/a')} (${value?.score ?? 'β'})</li>`) | |
| .join(''); | |
| this.insightsContainer.innerHTML = ` | |
| <h4>AI Insights</h4> | |
| <p><strong>Direction:</strong> ${insights.change_direction || 'N/A'} (${insights.change_percent ?? 'β'}%)</p> | |
| <p><strong>Range:</strong> High ${insights.high ?? 'β'} / Low ${insights.low ?? 'β'}</p> | |
| <p>${summary || insights.narrative?.summary?.summary || insights.narrative?.summary || ''}</p> | |
| <ul>${bullets || '<li>No sentiment signals provided.</li>'}</ul> | |
| `; | |
| } | |
| } | |
| export default ChartLabView; | |