Really-amin's picture
Upload 196 files
dd7ffbd verified
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;