| import apiClient from './apiClient.js'; | |
| import { formatCurrency, formatPercent } from './uiUtils.js'; | |
| class AIAdvisorView { | |
| constructor(section) { | |
| this.section = section; | |
| this.form = section?.querySelector('[data-ai-form]'); | |
| this.decisionContainer = section?.querySelector('[data-ai-result]'); | |
| this.sentimentContainer = section?.querySelector('[data-sentiment-result]'); | |
| this.disclaimer = section?.querySelector('[data-ai-disclaimer]'); | |
| this.contextInput = section?.querySelector('textarea[name="context"]'); | |
| this.modelSelect = section?.querySelector('select[name="model"]'); | |
| } | |
| init() { | |
| if (!this.form) return; | |
| this.form.addEventListener('submit', async (event) => { | |
| event.preventDefault(); | |
| const formData = new FormData(this.form); | |
| await this.handleSubmit(formData); | |
| }); | |
| } | |
| async handleSubmit(formData) { | |
| const symbol = formData.get('symbol') || 'BTC'; | |
| const horizon = formData.get('horizon') || 'swing'; | |
| const risk = formData.get('risk') || 'moderate'; | |
| const context = (formData.get('context') || '').trim(); | |
| const mode = formData.get('model') || 'auto'; | |
| if (this.decisionContainer) { | |
| this.decisionContainer.innerHTML = '<p>Generating AI strategy...</p>'; | |
| } | |
| if (this.sentimentContainer && context) { | |
| this.sentimentContainer.innerHTML = '<p>Running sentiment model...</p>'; | |
| } | |
| const decisionPayload = { | |
| query: `Provide ${horizon} outlook for ${symbol} with ${risk} risk. ${context}`, | |
| symbol, | |
| task: 'decision', | |
| options: { horizon, risk }, | |
| }; | |
| const jobs = [apiClient.runQuery(decisionPayload)]; | |
| if (context) { | |
| jobs.push(apiClient.analyzeSentiment({ text: context, mode })); | |
| } | |
| const [decisionResult, sentimentResult] = await Promise.all(jobs); | |
| if (!decisionResult.ok) { | |
| this.decisionContainer.innerHTML = `<div class="inline-message inline-error">${decisionResult.error}</div>`; | |
| } else { | |
| this.renderDecisionResult(decisionResult.data || {}); | |
| } | |
| if (context && this.sentimentContainer) { | |
| if (!sentimentResult?.ok) { | |
| this.sentimentContainer.innerHTML = `<div class="inline-message inline-error">${sentimentResult?.error || 'AI sentiment endpoint unavailable'}</div>`; | |
| } else { | |
| this.renderSentimentResult(sentimentResult.data || sentimentResult); | |
| } | |
| } | |
| } | |
| renderDecisionResult(response) { | |
| if (!this.decisionContainer) return; | |
| const payload = response.data || {}; | |
| const analysis = payload.analysis || payload; | |
| const summary = analysis.summary?.summary || analysis.summary || 'No summary provided.'; | |
| const signals = analysis.signals || {}; | |
| const topCoins = (payload.top_coins || []).slice(0, 3); | |
| this.decisionContainer.innerHTML = ` | |
| <div class="ai-result"> | |
| <p class="text-muted">${response.message || 'Decision support summary'}</p> | |
| <p>${summary}</p> | |
| <div class="grid-two"> | |
| <div> | |
| <h4>Market Signals</h4> | |
| <ul> | |
| ${Object.entries(signals) | |
| .map(([, value]) => `<li>${value?.label || 'neutral'} (${value?.score ?? '—'})</li>`) | |
| .join('') || '<li>No model signals.</li>'} | |
| </ul> | |
| </div> | |
| <div> | |
| <h4>Watchlist</h4> | |
| <ul> | |
| ${topCoins | |
| .map( | |
| (coin) => | |
| `<li>${coin.symbol || coin.ticker}: ${formatCurrency(coin.price)} (${formatPercent(coin.change_24h)})</li>`, | |
| ) | |
| .join('') || '<li>No coin highlights.</li>'} | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| if (this.disclaimer) { | |
| this.disclaimer.textContent = | |
| response.data?.disclaimer || 'This AI output is experimental research and not financial advice.'; | |
| } | |
| } | |
| renderSentimentResult(result) { | |
| const container = this.sentimentContainer; | |
| if (!container) return; | |
| const payload = result.result || result; | |
| const signals = result.signals || payload.signals || {}; | |
| container.innerHTML = ` | |
| <div class="glass-card"> | |
| <h4>Sentiment (${result.mode || 'auto'})</h4> | |
| <p><strong>Label:</strong> ${payload.label || payload.classification || 'neutral'}</p> | |
| <p><strong>Score:</strong> ${payload.score ?? payload.sentiment?.score ?? '—'}</p> | |
| <div class="chip-row"> | |
| ${Object.entries(signals) | |
| .map(([key, value]) => `<span class="chip">${key}: ${value?.label || 'n/a'}</span>`) | |
| .join('') || ''} | |
| </div> | |
| <p>${payload.summary?.summary || payload.summary?.summary_text || payload.summary || ''}</p> | |
| </div> | |
| `; | |
| } | |
| } | |
| export default AIAdvisorView; | |