retro-alpha / static /app.js
sankalphs's picture
Phase 2/3: Gradio Server backend, CRT frontend, engine, agents, mentor, tests, CI/CD
1d0b04b
Raw
History Blame
5.73 kB
const API = {
async call(name, data = {}) {
const client = await window.gradioClient || (window.gradioClient = await import('https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js').then(m => m.Client.connect(window.location.origin)));
return await client.predict(`/${name}`, data);
}
};
const ASSETS = ['cash', 'fd', 'gov_bonds', 'nifty_50', 'nifty_it', 'real_estate', 'crypto', 'gold'];
const ASSET_LABELS = {
cash: 'Cash (INR)',
fd: 'Bank FD',
gov_bonds: 'Gov Bonds',
nifty_50: 'Nifty 50',
nifty_it: 'Nifty IT',
real_estate: 'Real Estate',
crypto: 'Crypto',
gold: 'Gold'
};
let history = [];
function formatMoney(n) {
return '₹' + Math.round(n).toLocaleString('en-IN');
}
function formatPrice(n) {
return n.toFixed(3);
}
function updateClock() {
const now = new Date();
document.getElementById('clock').textContent = now.toLocaleTimeString('en-IN');
}
setInterval(updateClock, 1000);
updateClock();
async function fetchState() {
const result = await API.call('state');
return result.data;
}
function renderState(state) {
document.getElementById('total-value').textContent = formatMoney(state.total_value);
document.getElementById('game-time').textContent = `YEAR ${state.year} / MONTH ${state.month}`;
// Ticker
const tickerItems = ASSETS.map(a => `${ASSET_LABELS[a]}: ${formatPrice(state.prices[a])}`).join(' ');
document.getElementById('ticker').textContent = tickerItems;
// News
const newsEl = document.getElementById('news-display');
if (state.news && state.news.headline) {
newsEl.innerHTML = `<strong>${state.news.headline}</strong><br><br>${formatImpact(state.news.impact)}`;
}
// Holdings
const tbody = document.querySelector('#holdings-table tbody');
tbody.innerHTML = '';
ASSETS.forEach(asset => {
const price = state.prices[asset];
const qty = state.portfolio[asset];
const value = qty * price;
const row = document.createElement('tr');
row.innerHTML = `<td>${ASSET_LABELS[asset]}</td><td>${formatPrice(price)}</td><td>${qty.toFixed(2)}</td><td>${formatMoney(value)}</td>`;
tbody.appendChild(row);
});
// Agent log
const logEl = document.getElementById('agent-log');
logEl.innerHTML = '';
(state.agent_actions || []).forEach(action => {
const div = document.createElement('div');
div.className = `agent-entry agent-${action.agent}`;
const acts = (action.actions || []).map(a => `${a.action.toUpperCase()} ${ASSET_LABELS[a.asset]} ${(a.amount_pct * 100).toFixed(0)}%`).join(', ');
div.innerHTML = `<strong>${action.agent.toUpperCase()}</strong> [${action.sentiment}]<br>${acts}<br><em>${action.actions[0]?.reason || ''}</em>`;
logEl.appendChild(div);
});
// Chart
history.push(state.total_value);
if (history.length > 50) history.shift();
drawChart(history);
}
function formatImpact(impact) {
if (!impact) return '';
return ASSETS.map(a => `${ASSET_LABELS[a]}: ${(impact[a] * 100).toFixed(1)}%`).join(' | ');
}
function drawChart(data) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
ctx.clearRect(0, 0, w, h);
if (data.length < 2) return;
const min = Math.min(...data);
const max = Math.max(...data);
const range = max - min || 1;
ctx.strokeStyle = '#33ff33';
ctx.lineWidth = 2;
ctx.beginPath();
data.forEach((v, i) => {
const x = (i / (data.length - 1)) * (w - 20) + 10;
const y = h - 10 - ((v - min) / range) * (h - 20);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.stroke();
}
async function init() {
// Populate asset select
const select = document.getElementById('trade-asset');
ASSETS.forEach(a => {
const opt = document.createElement('option');
opt.value = a;
opt.textContent = ASSET_LABELS[a];
select.appendChild(opt);
});
// Load initial state
const state = await fetchState();
renderState(state);
// Event listeners
document.getElementById('btn-trade').addEventListener('click', async () => {
const asset = document.getElementById('trade-asset').value;
const action = document.getElementById('trade-action').value;
const amount = parseFloat(document.getElementById('trade-amount').value) / 100;
const result = await API.call('trade', { asset, action, amount_pct: amount });
renderState(result.data);
});
document.getElementById('btn-advance').addEventListener('click', async () => {
const result = await API.call('advance');
renderState(result.data);
if (result.data.month === 0 || result.data.month === 12) {
showMentor();
}
});
document.getElementById('btn-reset').addEventListener('click', async () => {
history = [];
const result = await API.call('reset');
renderState(result.data);
});
document.getElementById('btn-close-mentor').addEventListener('click', () => {
document.getElementById('mentor-modal').classList.add('hidden');
});
}
async function showMentor() {
const result = await API.call('mentor');
const review = result.data.review;
document.getElementById('mentor-roast').textContent = review.roast;
document.getElementById('mentor-lesson').textContent = review.lesson;
document.getElementById('mentor-suggestion').textContent = review.suggestion;
document.getElementById('mentor-modal').classList.remove('hidden');
}
init().catch(console.error);