Angshuman28's picture
Upload folder using huggingface_hub
16e3f36 verified
Raw
History Blame Contribute Delete
23.3 kB
import { resetEnvironment, stepEnvironment } from './api.js';
import {
ACTION_KINDS,
AUTHORITIES,
DATA_TYPES,
RESOURCE_TYPES,
SEVERITIES,
buildAction,
formatAction,
labelize,
normalizeRegionSelection,
resourceValue,
} from './actions.js';
import { narrateCouncil } from './council-narration.js';
import { goToFrame, setReplayTrace, stopReplay, toggleReplayPlayback } from './replay.js';
import { SAMPLE_TRACE } from './samples.js';
import { AppState, addTraceFrame, setState, showToast, subscribe } from './state.js';
const app = document.getElementById('app');
let liveAutoplayTimer = null;
function init() {
const first = SAMPLE_TRACE[0];
setState({
replayTrace: SAMPLE_TRACE,
replayIndex: 0,
observation: first.observation,
reward: first.reward,
done: first.done,
council: narrateCouncil(first.observation, first.reward),
});
subscribe(render);
render();
}
function render() {
const regionPatch = normalizeRegionSelection(AppState);
if (Object.keys(regionPatch).length) {
setState(regionPatch);
return;
}
const council = narrateCouncil(AppState.observation, AppState.reward || 0);
AppState.council = council;
app.innerHTML = `
<main class="shell">
${renderTopbar()}
${renderCommandbar()}
<section class="main-grid">
<div class="left-col">
${renderPhasePanel(council)}
${renderWorldPanel()}
${renderResourcesPanel()}
</div>
<div class="center-col">
${renderActionPanel(council)}
${renderTimelinePanel()}
</div>
<div class="right-col">
${renderCouncilPanel(council)}
${renderReplayPanel()}
</div>
</section>
</main>
`;
bindEvents();
}
function renderTopbar() {
return `
<header class="topbar">
<div class="brand">
<div class="mark">CW</div>
<div>
<h1>CrisisWorld Cortex</h1>
<p>Outbreak response dashboard for live OpenEnv episodes and replay traces.</p>
</div>
</div>
<div class="top-actions">
<span class="badge ${AppState.mode === 'live' ? 'live' : 'sample'}">${AppState.mode === 'live' ? 'Live env' : 'Sample replay'}</span>
${AppState.done ? '<span class="badge done">Terminal</span>' : ''}
<span class="badge">${AppState.connection}</span>
<button class="btn ghost" data-action="load-sample">Load Sample</button>
<button class="btn ghost" data-action="open-web">Open /web</button>
</div>
</header>
`;
}
function renderCommandbar() {
return `
<section class="commandbar">
<div class="fieldrow">
<label class="field wide">
<span>Task</span>
<select data-bind="taskName">
${option('outbreak_easy', 'Outbreak Easy', AppState.taskName)}
${option('outbreak_medium', 'Outbreak Medium', AppState.taskName)}
${option('outbreak_hard', 'Outbreak Hard', AppState.taskName)}
</select>
</label>
<label class="field compact">
<span>Seed</span>
<input type="number" min="0" step="1" data-bind="seed" value="${AppState.seed}">
</label>
<label class="field compact">
<span>Max ticks</span>
<input type="number" min="1" max="30" step="1" data-bind="maxTicks" value="${AppState.maxTicks}">
</label>
<button class="btn primary" data-action="reset-live">Reset Live Episode</button>
</div>
<div class="fieldrow">
<label class="field wide">
<span>Replay speed</span>
<input type="range" min="350" max="2600" step="50" data-bind="replaySpeedMs" value="${AppState.replaySpeedMs}">
</label>
<span class="badge">${AppState.replaySpeedMs} ms</span>
</div>
<div class="fieldrow">
<button class="btn ${AppState.autoplayLive ? 'warn' : 'blue'}" data-action="toggle-live-autoplay" ${AppState.mode !== 'live' || AppState.done ? 'disabled' : ''}>
${AppState.autoplayLive ? 'Stop Autoplay' : 'Autoplay Decisions'}
</button>
</div>
</section>
`;
}
function renderPhasePanel(council) {
const obs = AppState.observation;
return `
<section class="panel">
<div class="panel-header">
<h2>Episode State</h2>
<span class="badge">${AppState.statusMessage}</span>
</div>
<div class="panel-body">
<div class="metrics">
${metric('Tick', obs?.tick ?? '-', `${obs?.ticks_remaining ?? '-'} left`)}
${metric('Phase', council.phase, `round ${council.round}`)}
${metric('Budget', council.budget || '-', 'cognition')}
${metric('Reward', formatReward(AppState.reward), `total ${formatReward(AppState.totalReward)}`)}
</div>
</div>
</section>
`;
}
function renderWorldPanel() {
const obs = AppState.observation;
if (!obs) return panel('World State', '<div class="empty">No observation loaded.</div>');
const regions = obs.regions || [];
const maxScore = Math.max(...regions.map((region) => pressure(region)), 1);
const html = `
<div class="world-map">
${regions.map((region) => renderRegion(region, pressure(region) / maxScore)).join('')}
</div>
`;
return panel('World State', html);
}
function renderRegion(region, heat) {
const hot = heat > 0.78 ? 'hot' : '';
return `
<article class="region-node ${hot}">
<div class="region-top">
<span class="region-name">${region.region}</span>
<span class="badge">${Math.round(heat * 100)} pressure</span>
</div>
<div class="region-stats">
${statline('Cases', clamp(region.reported_cases_d_ago / 180, 0, 1), region.reported_cases_d_ago)}
${statline('Load', region.hospital_load, pct(region.hospital_load), 'load')}
${statline('Comply', region.compliance_proxy, pct(region.compliance_proxy), 'comp')}
</div>
</article>
`;
}
function renderResourcesPanel() {
const obs = AppState.observation;
const resources = obs?.resources || {};
const constraints = obs?.legal_constraints || [];
const restrictions = obs?.active_restrictions || [];
return panel('Resources and Constraints', `
<div class="resource-grid">
${RESOURCE_TYPES.map((type) => `
<div class="resource">
<span class="micro-label">${labelize(type)}</span>
<strong>${resourceValue(resources, type)}</strong>
</div>
`).join('')}
</div>
<div style="height: 12px"></div>
<div class="section-title">Active restrictions</div>
<div class="timeline" style="max-height: 130px; margin-top: 8px;">
${restrictions.length ? restrictions.map((item) => `
<div class="timeline-entry">
<strong>${item.region}</strong>
<span>${labelize(item.severity)} movement limits</span>
<span class="badge">${item.ticks_remaining} ticks</span>
</div>
`).join('') : '<div class="tiny">None active.</div>'}
</div>
<div style="height: 12px"></div>
<div class="section-title">Legal constraints</div>
<div class="timeline" style="max-height: 130px; margin-top: 8px;">
${constraints.length ? constraints.map((item) => `
<div class="timeline-entry rejected">
<strong>${item.rule_id}</strong>
<span>Blocks ${item.blocked_action}</span>
<span class="badge">${item.unlock_via}</span>
</div>
`).join('') : '<div class="tiny">No active legal blockers.</div>'}
</div>
`);
}
function renderActionPanel(council) {
const selected = AppState.selectedActionKind;
return panel('Task Controls', `
<div class="action-panel">
<div class="segmented" role="tablist" aria-label="Action type">
${ACTION_KINDS.map((kind) => `
<button data-kind="${kind.id}" class="${selected === kind.id ? 'active' : ''}">${kind.label}</button>
`).join('')}
</div>
<div class="action-form">
${renderActionInputs(selected)}
<div class="fieldrow">
<button class="btn primary" data-action="submit-action" ${AppState.mode !== 'live' || AppState.done ? 'disabled' : ''}>Submit Action</button>
<button class="btn blue" data-action="apply-recommendation" ${AppState.mode !== 'live' || AppState.done ? 'disabled' : ''}>Apply Recommendation</button>
<span class="tiny">Recommendation: ${formatAction(council.decision)}</span>
</div>
</div>
</div>
`);
}
function renderActionInputs(kind) {
const regions = AppState.observation?.regions || [{ region: AppState.selectedRegion || 'R1' }];
const regionSelect = `
<label class="field">
<span>Region</span>
<select data-bind="selectedRegion">
${regions.map((region) => option(region.region, region.region, AppState.selectedRegion)).join('')}
</select>
</label>
`;
const resourceSelect = (bindName, label) => `
<label class="field">
<span>${label}</span>
<select data-bind="${bindName}">
${RESOURCE_TYPES.map((type) => option(type, labelize(type), AppState[bindName])).join('')}
</select>
</label>
`;
const quantity = `
<label class="field">
<span>${kind === 'reallocate_budget' ? 'Amount' : 'Quantity'}</span>
<input type="number" min="0" step="1" data-bind="quantity" value="${AppState.quantity}">
</label>
`;
if (kind === 'deploy_resource') {
return `<div class="param-grid">${regionSelect}${resourceSelect('selectedResource', 'Resource')}${quantity}</div>`;
}
if (kind === 'request_data') {
return `
<div class="param-grid">
${regionSelect}
<label class="field">
<span>Data type</span>
<select data-bind="dataType">${DATA_TYPES.map((type) => option(type, labelize(type), AppState.dataType)).join('')}</select>
</label>
</div>
`;
}
if (kind === 'restrict_movement') {
return `
<div class="param-grid">
${regionSelect}
<label class="field">
<span>Severity</span>
<select data-bind="severity">${SEVERITIES.map((type) => option(type, labelize(type), AppState.severity)).join('')}</select>
</label>
</div>
`;
}
if (kind === 'escalate') {
return `
<div class="param-grid">
<label class="field">
<span>Authority</span>
<select data-bind="authority">${AUTHORITIES.map((type) => option(type, labelize(type), AppState.authority)).join('')}</select>
</label>
</div>
`;
}
if (kind === 'reallocate_budget') {
return `<div class="param-grid">${resourceSelect('selectedResource', 'From')}${resourceSelect('selectedToResource', 'To')}${quantity}</div>`;
}
return '<div class="tiny">No parameters required.</div>';
}
function renderCouncilPanel(council) {
return panel('Narrated Council', `
<div class="tiny" style="margin-bottom: 10px;">
Frontend visualization derived from observations. It is not a live Cortex runtime or LLM council.
</div>
<div class="decision">
<h3>Converged action</h3>
<strong>${formatAction(council.decision)}</strong>
<p class="tiny">${council.rationale}</p>
</div>
<div style="height: 10px"></div>
<div class="council-stack">
${council.recommendations.map((report) => renderBrain(report)).join('')}
</div>
<div style="height: 10px"></div>
<div class="section-title">Preserved dissent</div>
<div class="timeline" style="max-height: 150px; margin-top: 8px;">
${council.preservedDissent.length ? council.preservedDissent.map((item, index) => `
<div class="timeline-entry">
<strong>D${index + 1}</strong>
<span>${item}</span>
<span></span>
</div>
`).join('') : '<div class="tiny">No dissent preserved on this frame.</div>'}
</div>
`);
}
function renderBrain(report) {
return `
<article class="brain-card">
<div class="brain-head">
<h3>${report.name}</h3>
<span class="badge">${Math.round(report.confidence * 100)} conf</span>
</div>
<div class="bar"><span style="width: ${Math.round(report.confidence * 100)}%"></span></div>
<p><strong>${formatAction(report.action)}</strong></p>
<p>${report.summary}</p>
<p><span class="micro-label">Challenge</span><br>${report.challenge}</p>
<p><span class="micro-label">Minority</span><br>${formatAction(report.minority)}</p>
</article>
`;
}
function renderTimelinePanel() {
const log = AppState.observation?.recent_action_log || [];
const liveTrace = AppState.liveTrace || [];
return panel('Final Action Timeline', `
<div class="timeline">
${log.length ? log.map((entry) => `
<div class="timeline-entry ${entry.accepted ? '' : 'rejected'}">
<strong>T${entry.tick}</strong>
<span>${formatAction(entry.action)}</span>
<span class="badge">${entry.accepted ? 'accepted' : 'rejected'}</span>
</div>
`).join('') : '<div class="empty">No actions have been submitted yet.</div>'}
</div>
<div style="height: 12px"></div>
<div class="tiny">Live trace frames captured this session: ${liveTrace.length}</div>
`);
}
function renderReplayPanel() {
const max = Math.max(0, AppState.replayTrace.length - 1);
return panel('Replay', `
<div class="replay-row">
<button class="btn" data-action="toggle-replay">${AppState.replayPlaying ? 'Pause' : 'Play'}</button>
<input type="range" min="0" max="${max}" step="1" value="${AppState.replayIndex}" data-action="scrub-replay" ${max === 0 ? 'disabled' : ''}>
<span class="badge">${AppState.replayIndex + 1}/${AppState.replayTrace.length || 1}</span>
</div>
<div style="height: 10px"></div>
<div class="fieldrow">
<button class="btn ghost" data-action="use-live-trace" ${AppState.liveTrace.length ? '' : 'disabled'}>Use Live Trace</button>
<button class="btn ghost" data-action="load-sample">Use Sample Trace</button>
</div>
<p class="tiny">Replay changes the displayed frame only. It does not step the environment.</p>
`);
}
function panel(title, body) {
return `
<section class="panel">
<div class="panel-header">
<h2>${title}</h2>
</div>
<div class="panel-body">${body}</div>
</section>
`;
}
function metric(label, value, hint) {
return `
<div class="metric">
<span class="micro-label">${label}</span>
<strong>${value}</strong>
<span class="tiny">${hint}</span>
</div>
`;
}
function statline(label, value, display, extraClass = '') {
return `
<div class="statline">
<span>${label}</span>
<div class="bar ${extraClass}"><span style="width: ${Math.round(clamp(value, 0, 1) * 100)}%"></span></div>
<strong>${display}</strong>
</div>
`;
}
function bindEvents() {
document.querySelectorAll('[data-bind]').forEach((element) => {
element.addEventListener('change', () => {
const key = element.dataset.bind;
const value = element.type === 'number' || element.type === 'range'
? Number.parseInt(element.value, 10)
: element.value;
setState({ [key]: value });
});
if (element.type === 'range') {
element.addEventListener('input', () => {
const key = element.dataset.bind;
setState({ [key]: Number.parseInt(element.value, 10) });
});
}
});
document.querySelectorAll('[data-kind]').forEach((button) => {
button.addEventListener('click', () => setState({ selectedActionKind: button.dataset.kind }));
});
document.querySelectorAll('[data-action]').forEach((element) => {
element.addEventListener('click', () => handleAction(element.dataset.action, element));
if (element.dataset.action === 'scrub-replay') {
element.addEventListener('input', () => {
stopReplay();
goToFrame(Number.parseInt(element.value, 10));
});
}
});
}
async function handleAction(actionName, element) {
if (actionName === 'open-web') {
window.location.href = '/web/';
return;
}
if (actionName === 'load-sample') {
stopLiveAutoplay();
setReplayTrace(SAMPLE_TRACE);
setState({
mode: 'sample',
statusMessage: 'Sample trace loaded',
council: narrateCouncil(SAMPLE_TRACE[0].observation, SAMPLE_TRACE[0].reward),
});
showToast('Sample trace loaded', 'success');
return;
}
if (actionName === 'use-live-trace') {
if (!AppState.liveTrace.length) return;
setReplayTrace(AppState.liveTrace);
setState({ mode: 'replay', statusMessage: 'Live trace replay' });
return;
}
if (actionName === 'toggle-replay') {
toggleReplayPlayback();
return;
}
if (actionName === 'scrub-replay') {
stopReplay();
goToFrame(Number.parseInt(element.value, 10));
return;
}
if (actionName === 'reset-live') {
await resetLive();
return;
}
if (actionName === 'submit-action') {
await submitAction(buildAction(AppState));
return;
}
if (actionName === 'apply-recommendation') {
await submitAction(AppState.council?.decision || { kind: 'no_op' });
return;
}
if (actionName === 'toggle-live-autoplay') {
toggleLiveAutoplay();
}
}
async function resetLive() {
stopReplay();
stopLiveAutoplay();
setState({ connection: 'resetting', statusMessage: 'Resetting live episode' });
try {
const data = await resetEnvironment({
taskName: AppState.taskName,
seed: AppState.seed,
maxTicks: AppState.maxTicks,
});
const frame = {
label: `Live reset: ${AppState.taskName}`,
action: null,
observation: data.observation,
reward: data.reward ?? 0,
done: Boolean(data.done),
};
setState({
mode: 'live',
connection: 'connected',
observation: data.observation,
reward: data.reward ?? 0,
done: Boolean(data.done),
totalReward: 0,
liveTrace: [frame],
replayTrace: [frame],
replayIndex: 0,
statusMessage: 'Live episode ready',
});
showToast('Live episode reset', 'success');
} catch (error) {
setState({ connection: 'error', statusMessage: 'Reset failed' });
showToast(`Reset failed: ${error.message}`, 'error');
}
}
async function submitAction(payload) {
if (AppState.mode !== 'live') {
showToast('Reset a live episode before submitting actions.', 'error');
return;
}
if (AppState.done) {
showToast('Episode is terminal. Reset to continue.', 'error');
return;
}
setState({ connection: 'stepping', statusMessage: `Submitting ${payload.kind}` });
try {
const data = await stepEnvironment(payload);
const reward = Number(data.reward ?? 0);
const frame = {
label: `Submitted ${formatAction(payload)}`,
action: payload,
observation: data.observation,
reward,
done: Boolean(data.done),
};
addTraceFrame(frame);
setState({
mode: 'live',
connection: 'connected',
observation: data.observation,
reward,
done: Boolean(data.done),
totalReward: AppState.totalReward + reward,
statusMessage: data.done ? 'Episode complete' : 'Action accepted',
});
if (data.done) {
stopLiveAutoplay();
showToast('Episode complete', 'success');
}
} catch (error) {
stopLiveAutoplay();
setState({ connection: 'error', statusMessage: 'Step failed' });
showToast(`Step failed: ${error.message}`, 'error');
}
}
function toggleLiveAutoplay() {
if (AppState.autoplayLive) {
stopLiveAutoplay();
return;
}
if (AppState.mode !== 'live' || AppState.done) return;
setState({ autoplayLive: true });
liveAutoplayTimer = window.setInterval(async () => {
if (AppState.done || AppState.mode !== 'live') {
stopLiveAutoplay();
return;
}
await submitAction(AppState.council?.decision || { kind: 'no_op' });
}, AppState.replaySpeedMs);
}
function stopLiveAutoplay() {
if (liveAutoplayTimer) {
window.clearInterval(liveAutoplayTimer);
liveAutoplayTimer = null;
}
if (AppState.autoplayLive) setState({ autoplayLive: false });
}
function option(value, label, selected) {
return `<option value="${value}" ${value === selected ? 'selected' : ''}>${label}</option>`;
}
function pressure(region) {
return region.reported_cases_d_ago / 1000 + region.hospital_load * 1.55 + (1 - region.compliance_proxy) * 0.75;
}
function pct(value) {
return `${Math.round((value || 0) * 100)}%`;
}
function formatReward(value) {
return typeof value === 'number' ? value.toFixed(2) : '-';
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
init();