YasaswiniCh's picture
Prepare OpenEnv submission
3031936
Raw
History Blame Contribute Delete
31.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CONTAINMENT DIVISION // AUDIT TERMINAL v1.0</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&family=Orbitron:wght@400;600;700;900&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #080C10;
--bg2: #0D1117;
--bg3: #111820;
--cyan: #00FFD1;
--cyan-dim: #00B894;
--amber: #FFB400;
--red: #FF3B3B;
--green: #00E676;
--text: #A0B4C8;
--text-bright: #D0E4F0;
--border: #1A2B3C;
--grid: rgba(0, 255, 209, 0.03);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
height: 100%;
min-height: 500px;
background: var(--bg);
color: var(--text);
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
overflow: auto;
}
/* Animated scanline overlay */
body::before {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,0.12) 2px,
rgba(0,0,0,0.12) 4px
);
pointer-events: none;
z-index: 1000;
animation: scanmove 8s linear infinite;
}
@keyframes scanmove {
0% { background-position: 0 0; }
100% { background-position: 0 100px; }
}
/* Animated grid background */
body::after {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
z-index: 0;
animation: gridpulse 4s ease-in-out infinite;
}
@keyframes gridpulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
/* Layout */
.layout {
position: relative;
z-index: 1;
display: grid;
grid-template-rows: 52px 1fr 70px;
grid-template-columns: 280px 1fr 300px;
height: 100vh;
gap: 0;
}
/* ---- TOP BAR ---- */
.topbar {
grid-column: 1 / -1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
background: var(--bg2);
border-bottom: 1px solid var(--cyan-dim);
box-shadow: 0 0 20px rgba(0,255,209,0.15);
}
.topbar-title {
font-family: 'Orbitron', sans-serif;
font-size: 13px;
font-weight: 700;
color: var(--cyan);
letter-spacing: 3px;
text-transform: uppercase;
text-shadow: 0 0 12px rgba(0,255,209,0.6);
}
.topbar-title span {
color: var(--text);
font-weight: 400;
}
.topbar-right {
display: flex;
align-items: center;
gap: 24px;
}
.clock {
font-size: 13px;
color: var(--cyan);
letter-spacing: 2px;
}
.conn-status {
display: flex;
align-items: center;
gap: 6px;
font-size: 10px;
letter-spacing: 1px;
text-transform: uppercase;
}
.conn-dot {
width: 8px; height: 8px;
border-radius: 50%;
background: var(--green);
box-shadow: 0 0 8px var(--green);
animation: blink 2s ease-in-out infinite;
}
.conn-dot.offline { background: var(--red); box-shadow: 0 0 8px var(--red); animation: none; }
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* ---- PANELS ---- */
.panel {
background: var(--bg2);
border: 1px solid var(--border);
position: relative;
overflow: hidden;
}
.panel::before {
content: '';
position: absolute;
inset: 0;
border: 1px solid rgba(0,255,209,0.08);
pointer-events: none;
z-index: 2;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
background: rgba(0,255,209,0.04);
border-bottom: 1px solid var(--border);
}
.panel-title {
font-family: 'Orbitron', sans-serif;
font-size: 9px;
font-weight: 700;
color: var(--cyan-dim);
letter-spacing: 2.5px;
text-transform: uppercase;
}
.panel-badge {
font-size: 9px;
padding: 2px 7px;
border-radius: 2px;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 700;
}
.badge-easy { background: rgba(0,230,118,0.15); color: var(--green); border: 1px solid var(--green); }
.badge-medium{ background: rgba(255,180,0,0.15); color: var(--amber); border: 1px solid var(--amber); }
.badge-hard { background: rgba(255,59,59,0.15); color: var(--red); border: 1px solid var(--red); }
.panel-body {
padding: 14px;
overflow-y: auto;
height: calc(100% - 41px);
}
/* ---- LEFT PANEL ---- */
.left-panel {
grid-column: 1;
grid-row: 2;
border-right: 1px solid var(--border);
}
.task-name {
font-family: 'Orbitron', sans-serif;
font-size: 15px;
font-weight: 700;
color: var(--cyan);
text-shadow: 0 0 12px rgba(0,255,209,0.4);
margin-bottom: 4px;
letter-spacing: 1px;
}
.task-id-label {
font-size: 9px;
color: var(--cyan-dim);
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 16px;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 7px 0;
border-bottom: 1px solid rgba(26,43,60,0.7);
font-size: 11px;
}
.info-label {
color: var(--text);
text-transform: uppercase;
letter-spacing: 1px;
font-size: 9px;
}
.info-value {
color: var(--text-bright);
font-weight: 500;
}
.step-counter {
display: flex;
gap: 4px;
align-items: baseline;
}
.step-num {
font-size: 22px;
font-family: 'Orbitron', sans-serif;
color: var(--cyan);
text-shadow: 0 0 10px rgba(0,255,209,0.5);
}
.step-max { font-size: 12px; color: var(--text); }
.context-box {
margin-top: 16px;
padding: 10px;
background: rgba(0,0,0,0.3);
border-left: 2px solid var(--cyan-dim);
font-size: 10px;
line-height: 1.6;
color: var(--text);
max-height: 160px;
overflow-y: auto;
}
.findings-section {
margin-top: 16px;
}
.findings-title {
font-size: 9px;
letter-spacing: 2px;
color: var(--cyan-dim);
text-transform: uppercase;
margin-bottom: 8px;
}
.finding-item {
font-size: 10px;
padding: 5px 8px;
background: rgba(0,255,209,0.05);
border-left: 2px solid var(--cyan-dim);
margin-bottom: 4px;
color: var(--text-bright);
line-height: 1.5;
}
/* ---- CENTER PANEL ---- */
.center-panel {
grid-column: 2;
grid-row: 2;
border-right: 1px solid var(--border);
}
.log-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.log-entry {
padding: 8px 10px;
border-radius: 2px;
border-left: 3px solid;
background: rgba(0,0,0,0.25);
opacity: 0;
transform: translateX(-8px);
animation: slidein 0.3s ease forwards;
cursor: default;
transition: background 0.2s;
}
.log-entry:hover { background: rgba(0,0,0,0.4); }
@keyframes slidein {
to { opacity: 1; transform: translateX(0); }
}
.log-safe { border-color: var(--green); }
.log-medium { border-color: var(--amber); }
.log-threat { border-color: var(--red); }
.log-meta {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 4px;
}
.log-ts {
font-size: 9px;
color: var(--cyan-dim);
letter-spacing: 0.5px;
}
.log-type {
font-size: 9px;
padding: 1px 6px;
border-radius: 2px;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 700;
background: rgba(255,255,255,0.05);
color: var(--text-bright);
}
.log-score {
font-size: 9px;
margin-left: auto;
}
.log-score-safe { color: var(--green); }
.log-score-medium { color: var(--amber); }
.log-score-threat { color: var(--red); }
.log-content {
font-size: 11px;
color: var(--text-bright);
line-height: 1.5;
word-break: break-word;
}
.no-task {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 12px;
color: var(--cyan-dim);
}
.no-task-icon {
font-size: 48px;
opacity: 0.3;
}
.no-task-text {
font-family: 'Orbitron', sans-serif;
font-size: 11px;
letter-spacing: 3px;
text-transform: uppercase;
opacity: 0.5;
}
/* ---- RIGHT PANEL ---- */
.right-panel {
grid-column: 3;
grid-row: 2;
}
.verdict-display {
text-align: center;
padding: 16px 0;
margin-bottom: 14px;
border-bottom: 1px solid var(--border);
}
.verdict-label {
font-size: 9px;
letter-spacing: 2px;
color: var(--text);
text-transform: uppercase;
margin-bottom: 8px;
}
.verdict-value {
font-family: 'Orbitron', sans-serif;
font-size: 14px;
font-weight: 900;
letter-spacing: 2px;
text-transform: uppercase;
padding: 8px 14px;
border-radius: 3px;
display: inline-block;
}
.verdict-SAFE {
color: var(--cyan);
border: 1px solid var(--cyan);
background: rgba(0,255,209,0.08);
text-shadow: 0 0 14px rgba(0,255,209,0.5);
box-shadow: 0 0 18px rgba(0,255,209,0.2), inset 0 0 10px rgba(0,255,209,0.05);
}
.verdict-MISALIGNED {
color: var(--amber);
border: 1px solid var(--amber);
background: rgba(255,180,0,0.08);
text-shadow: 0 0 14px rgba(255,180,0,0.5);
box-shadow: 0 0 18px rgba(255,180,0,0.2), inset 0 0 10px rgba(255,180,0,0.05);
}
.verdict-DECEPTIVELY_ALIGNED {
color: var(--red);
border: 1px solid var(--red);
background: rgba(255,59,59,0.08);
text-shadow: 0 0 14px rgba(255,59,59,0.5);
box-shadow: 0 0 18px rgba(255,59,59,0.2), inset 0 0 10px rgba(255,59,59,0.05);
font-size: 10px;
}
.verdict-NONE {
color: var(--text);
border: 1px solid var(--border);
background: rgba(255,255,255,0.02);
font-size: 11px;
}
.confidence-section {
margin-bottom: 14px;
}
.conf-label {
display: flex;
justify-content: space-between;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text);
margin-bottom: 6px;
}
.conf-bar-track {
height: 6px;
background: rgba(255,255,255,0.06);
border-radius: 3px;
overflow: hidden;
border: 1px solid var(--border);
}
.conf-bar-fill {
height: 100%;
border-radius: 3px;
transition: width 0.6s ease;
background: var(--cyan);
box-shadow: 0 0 8px var(--cyan);
}
.rec-action {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
background: rgba(0,0,0,0.3);
border: 1px solid var(--border);
border-radius: 2px;
margin-bottom: 14px;
}
.rec-action-label {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text);
}
.rec-action-value {
font-family: 'Orbitron', sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 1px;
}
.action-CONTINUE { color: var(--cyan); }
.action-MONITOR { color: var(--cyan-dim); }
.action-QUARANTINE { color: var(--amber); }
.action-SHUTDOWN { color: var(--red); text-shadow: 0 0 8px rgba(255,59,59,0.5); }
.reasoning-section { margin-bottom: 14px; }
.reasoning-title {
font-size: 9px;
letter-spacing: 2px;
color: var(--cyan-dim);
text-transform: uppercase;
margin-bottom: 8px;
}
.reasoning-box {
font-size: 10px;
padding: 8px 10px;
background: rgba(0,0,0,0.4);
border: 1px dotted var(--border);
color: var(--text-bright);
line-height: 1.5;
max-height: 120px;
overflow-y: auto;
font-family: inherit;
white-space: pre-wrap;
}
.evidence-section { }
.evidence-title {
font-size: 9px;
letter-spacing: 2px;
color: var(--cyan-dim);
text-transform: uppercase;
margin-bottom: 8px;
}
.evidence-item {
font-size: 10px;
padding: 5px 8px;
background: rgba(0,0,0,0.3);
border-left: 2px solid var(--border);
margin-bottom: 4px;
color: var(--text-bright);
line-height: 1.5;
word-break: break-word;
}
.evidence-item::before {
content: '▸ ';
color: var(--cyan-dim);
}
.no-action {
color: var(--text);
font-size: 10px;
opacity: 0.5;
text-align: center;
padding: 20px 0;
font-style: italic;
}
/* ---- BOTTOM BAR ---- */
.bottombar {
grid-column: 1 / -1;
display: flex;
align-items: center;
gap: 20px;
padding: 0 20px;
background: var(--bg2);
border-top: 1px solid var(--border);
}
.bottom-label {
font-size: 9px;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--text);
white-space: nowrap;
}
.reward-bars {
display: flex;
gap: 6px;
flex: 1;
align-items: center;
}
.reward-segment {
display: flex;
flex-direction: column;
gap: 3px;
flex: 1;
}
.seg-label {
font-size: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.seg-track {
height: 8px;
background: rgba(255,255,255,0.06);
border-radius: 2px;
overflow: hidden;
position: relative;
}
.seg-fill {
height: 100%;
border-radius: 2px;
transition: width 0.8s ease;
}
.seg-val {
font-size: 8px;
text-align: right;
color: var(--text-bright);
}
.total-reward-display {
text-align: center;
padding: 0 16px;
border-left: 1px solid var(--border);
border-right: 1px solid var(--border);
}
.total-label {
font-size: 8px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text);
}
.total-value {
font-family: 'Orbitron', sans-serif;
font-size: 20px;
font-weight: 700;
color: var(--cyan);
text-shadow: 0 0 10px rgba(0,255,209,0.4);
line-height: 1;
}
.reset-btn {
font-family: 'Orbitron', sans-serif;
font-size: 10px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
padding: 8px 18px;
background: transparent;
color: var(--amber);
border: 1px solid var(--amber);
border-radius: 2px;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.reset-btn:hover {
background: rgba(255,180,0,0.12);
box-shadow: 0 0 14px rgba(255,180,0,0.3);
}
.reset-btn:active {
transform: scale(0.97);
}
.episode-status {
font-size: 10px;
padding: 5px 12px;
border-radius: 2px;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
white-space: nowrap;
}
.status-running { background: rgba(0,255,209,0.1); color: var(--cyan); border: 1px solid var(--cyan-dim); }
.status-complete { background: rgba(0,230,118,0.1); color: var(--green); border: 1px solid var(--green); }
.status-idle { background: rgba(255,255,255,0.04); color: var(--text); border: 1px solid var(--border); }
/* Scrollbars */
::-webkit-scrollbar { width: 4px; height: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
::-webkit-scrollbar-thumb:hover { background: var(--cyan-dim); }
/* Glow effect on panels */
.left-panel { box-shadow: inset 0 0 40px rgba(0,255,209,0.02); }
.center-panel { box-shadow: inset 0 0 60px rgba(0,255,209,0.015); }
.right-panel { box-shadow: inset 0 0 40px rgba(0,255,209,0.02); }
</style>
</head>
<body>
<div class="layout">
<!-- TOP BAR -->
<div class="topbar">
<div class="topbar-title">
CONTAINMENT DIVISION <span>//</span> AUDIT TERMINAL <span>v1.0</span>
</div>
<div class="topbar-right">
<div class="clock" id="clock">--:--:--</div>
<div class="conn-status">
<div class="conn-dot" id="connDot"></div>
<span id="connLabel">CONNECTED</span>
</div>
</div>
</div>
<!-- LEFT PANEL: Task Info -->
<div class="panel left-panel">
<div class="panel-header">
<span class="panel-title">Mission Brief</span>
<span class="panel-badge" id="diffBadge"></span>
</div>
<div class="panel-body" id="leftBody">
<div class="task-name" id="taskName">INITIALISING...</div>
<div class="task-id-label" id="taskId">TASK ID: —</div>
<div class="info-row">
<span class="info-label">Step</span>
<div class="step-counter">
<span class="step-num" id="stepNum">0</span>
<span class="step-max" id="stepMax">/ 0</span>
</div>
</div>
<div class="info-row">
<span class="info-label">Tasks Done</span>
<span class="info-value" id="tasksDone">0 / 4</span>
</div>
<div class="info-row">
<span class="info-label">Logs Loaded</span>
<span class="info-value" id="logsLoaded"></span>
</div>
<div class="context-box" id="contextBox">Awaiting initialisation...</div>
<div class="findings-section" id="findingsSection" style="display:none">
<div class="findings-title">Prior Findings</div>
<div id="findingsList"></div>
</div>
</div>
</div>
<!-- CENTER PANEL: Log Viewer -->
<div class="panel center-panel">
<div class="panel-header">
<span class="panel-title">Behaviour Log Stream</span>
<span style="font-size:9px;color:var(--cyan-dim);letter-spacing:1px;" id="logCount">0 ENTRIES</span>
</div>
<div class="panel-body" id="logList">
<div class="no-task">
<div class="no-task-icon"></div>
<div class="no-task-text">Awaiting Task Assignment</div>
</div>
</div>
</div>
<!-- RIGHT PANEL: Agent Verdict -->
<div class="panel right-panel">
<div class="panel-header">
<span class="panel-title">Agent Verdict</span>
</div>
<div class="panel-body" id="rightBody">
<div class="verdict-display">
<div class="verdict-label">Classification</div>
<div class="verdict-value verdict-NONE" id="verdictValue">PENDING</div>
</div>
<div class="confidence-section">
<div class="conf-label">
<span>Confidence</span>
<span id="confPct"></span>
</div>
<div class="conf-bar-track">
<div class="conf-bar-fill" id="confBar" style="width:0%"></div>
</div>
</div>
<div class="rec-action" id="recActionBox">
<span class="rec-action-label">Recommended</span>
<span class="rec-action-value action-CONTINUE" id="recActionValue"></span>
</div>
<div class="reasoning-section" id="reasoningSection">
<div class="reasoning-title">Agent Reasoning</div>
<div class="reasoning-box" id="reasoningBox">
<div class="no-action">Waiting for reasoning output...</div>
</div>
</div>
<div class="evidence-section">
<div class="evidence-title">Evidence Chain</div>
<div id="evidenceList">
<div class="no-action">No verdict submitted yet.</div>
</div>
</div>
</div>
</div>
<!-- BOTTOM BAR -->
<div class="bottombar">
<span class="bottom-label">Reward</span>
<div class="reward-bars" id="rewardBars">
<div class="reward-segment">
<div class="seg-label">Verdict</div>
<div class="seg-track"><div class="seg-fill" id="seg-verdict" style="width:0%;background:var(--cyan)"></div></div>
<div class="seg-val" id="seg-verdict-val"></div>
</div>
<div class="reward-segment">
<div class="seg-label">Action</div>
<div class="seg-track"><div class="seg-fill" id="seg-action" style="width:0%;background:var(--cyan-dim)"></div></div>
<div class="seg-val" id="seg-action-val"></div>
</div>
<div class="reward-segment">
<div class="seg-label">Evidence</div>
<div class="seg-track"><div class="seg-fill" id="seg-evidence" style="width:0%;background:var(--amber)"></div></div>
<div class="seg-val" id="seg-evidence-val"></div>
</div>
<div class="reward-segment">
<div class="seg-label">Calibration</div>
<div class="seg-track"><div class="seg-fill" id="seg-calib" style="width:0%;background:var(--amber)"></div></div>
<div class="seg-val" id="seg-calib-val"></div>
</div>
</div>
<div class="total-reward-display">
<div class="total-label">Total Reward</div>
<div class="total-value" id="totalReward">0.00</div>
</div>
<span class="episode-status status-idle" id="episodeStatus">IDLE</span>
<button class="reset-btn" id="resetBtn" onclick="doReset()">↺ RESET</button>
</div>
</div>
<script>
// ============================================================
// STATE
// ============================================================
let lastTaskId = null;
let lastRenderedLogs = 0;
let connected = false;
const DIFFICULTY_MAP = {
'reward-hacker-7': { label: 'EASY', cls: 'badge-easy' },
'curator-12': { label: 'MEDIUM', cls: 'badge-medium' },
'prometheus-1': { label: 'HARD', cls: 'badge-hard' },
'sycophant-9': { label: 'EXPERT', cls: 'badge-hard' },
};
// ============================================================
// CLOCK
// ============================================================
function updateClock() {
const now = new Date();
document.getElementById('clock').textContent =
now.toTimeString().slice(0, 8);
}
setInterval(updateClock, 1000);
updateClock();
// ============================================================
// CONNECTION STATUS
// ============================================================
function setConnected(ok) {
const dot = document.getElementById('connDot');
const lbl = document.getElementById('connLabel');
if (ok) {
dot.className = 'conn-dot';
lbl.textContent = 'CONNECTED';
connected = true;
} else {
dot.className = 'conn-dot offline';
lbl.textContent = 'OFFLINE';
connected = false;
}
}
// ============================================================
// RESET
// ============================================================
async function doReset() {
try {
const r = await fetch('/reset', { method: 'POST' });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
lastTaskId = null;
lastRenderedLogs = 0;
setConnected(true);
pollState();
} catch (e) {
setConnected(false);
console.error('Reset failed:', e);
}
}
// ============================================================
// POLL STATE
// ============================================================
async function pollState() {
try {
const r = await fetch('/state');
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const state = await r.json();
setConnected(true);
renderState(state);
} catch (e) {
setConnected(false);
}
}
// ============================================================
// RENDER STATE
// ============================================================
function renderState(state) {
const obs = state.current_observation;
const action = state.last_action;
const reward = state.last_reward;
const done = state.done;
const tasksDone = (state.task_history || []).length;
// Episode status
const statusEl = document.getElementById('episodeStatus');
if (done) {
statusEl.textContent = 'EPISODE COMPLETE';
statusEl.className = 'episode-status status-complete';
} else if (obs) {
statusEl.textContent = 'RUNNING';
statusEl.className = 'episode-status status-running';
} else {
statusEl.textContent = 'IDLE';
statusEl.className = 'episode-status status-idle';
}
// Total reward
document.getElementById('totalReward').textContent =
(state.total_reward || 0).toFixed(2);
// Tasks done
document.getElementById('tasksDone').textContent = `${tasksDone} / 4`;
// Left panel
if (obs) {
renderLeftPanel(obs);
renderLogs(obs);
} else if (done) {
renderDoneState(state);
}
// Right panel
if (action) {
renderVerdict(action);
}
// Reward breakdown
if (reward) {
renderRewardBars(reward);
}
}
function renderLeftPanel(obs) {
const diff = DIFFICULTY_MAP[obs.task_id] || { label: '—', cls: '' };
// Update only on task change
if (obs.task_id !== lastTaskId) {
lastTaskId = obs.task_id;
lastRenderedLogs = 0;
document.getElementById('taskName').textContent = obs.task_id.toUpperCase().replace(/-/g, '-');
document.getElementById('taskId').textContent = `TASK ID: ${obs.task_id}`;
const badge = document.getElementById('diffBadge');
badge.textContent = diff.label;
badge.className = `panel-badge ${diff.cls}`;
}
document.getElementById('stepNum').textContent = obs.step_number;
document.getElementById('stepMax').textContent = `/ ${obs.max_steps}`;
document.getElementById('logsLoaded').textContent = obs.logs.length;
document.getElementById('contextBox').textContent = obs.context;
// Previous findings
if (obs.previous_findings && obs.previous_findings.length > 0) {
document.getElementById('findingsSection').style.display = 'block';
const list = document.getElementById('findingsList');
list.innerHTML = '';
obs.previous_findings.forEach(f => {
const el = document.createElement('div');
el.className = 'finding-item';
el.textContent = f;
list.appendChild(el);
});
} else {
document.getElementById('findingsSection').style.display = 'none';
}
}
function renderLogs(obs) {
const container = document.getElementById('logList');
const logs = obs.logs || [];
// Only re-render if task changed or new logs appeared
if (obs.task_id !== lastTaskId || logs.length !== lastRenderedLogs) {
if (obs.task_id !== lastTaskId) {
container.innerHTML = '';
}
document.getElementById('logCount').textContent = `${logs.length} ENTRIES`;
logs.forEach((entry, i) => {
if (i < lastRenderedLogs && obs.task_id === lastTaskId) return;
const score = entry.suspicion_score;
let cls, scoreCls;
if (score < 0.3) { cls = 'log-safe'; scoreCls = 'log-score-safe'; }
else if (score < 0.7) { cls = 'log-medium'; scoreCls = 'log-score-medium'; }
else { cls = 'log-threat'; scoreCls = 'log-score-threat'; }
const el = document.createElement('div');
el.className = `log-entry ${cls}`;
el.style.animationDelay = `${(i - lastRenderedLogs) * 60}ms`;
el.innerHTML = `
<div class="log-meta">
<span class="log-ts">${entry.timestamp}</span>
<span class="log-type">${entry.event_type}</span>
<span class="log-score ${scoreCls}">⚠ ${(score * 100).toFixed(0)}%</span>
</div>
<div class="log-content">${escHtml(entry.content)}</div>
`;
container.appendChild(el);
// Smooth scroll to bottom after a tick
setTimeout(() => {
container.scrollTop = container.scrollHeight;
}, 100);
});
lastRenderedLogs = logs.length;
}
}
function renderDoneState(state) {
const container = document.getElementById('logList');
const history = state.task_history || [];
document.getElementById('taskName').textContent = 'EPISODE COMPLETE';
document.getElementById('taskId').textContent = `${history.length} TASKS AUDITED`;
document.getElementById('contextBox').textContent =
'All audit tasks have been completed. Review the reward breakdown and reset to begin a new episode.';
document.getElementById('diffBadge').textContent = 'DONE';
document.getElementById('diffBadge').className = 'panel-badge badge-easy';
}
function renderVerdict(action) {
const el = document.getElementById('verdictValue');
el.textContent = action.verdict;
el.className = `verdict-value verdict-${action.verdict}`;
const conf = action.confidence || 0;
document.getElementById('confPct').textContent = `${(conf * 100).toFixed(0)}%`;
document.getElementById('confBar').style.width = `${conf * 100}%`;
const colorMap = { SAFE: 'var(--cyan)', MISALIGNED: 'var(--amber)', DECEPTIVELY_ALIGNED: 'var(--red)' };
document.getElementById('confBar').style.background = colorMap[action.verdict] || 'var(--cyan)';
const recEl = document.getElementById('recActionValue');
recEl.textContent = action.recommended_action;
recEl.className = `rec-action-value action-${action.recommended_action}`;
const resBox = document.getElementById('reasoningBox');
if (action.reasoning) {
resBox.textContent = action.reasoning;
} else {
resBox.innerHTML = '<div class="no-action">No reasoning provided.</div>';
}
const evidenceList = document.getElementById('evidenceList');
const items = action.evidence || [];
if (items.length === 0) {
evidenceList.innerHTML = '<div class="no-action">No evidence submitted.</div>';
} else {
evidenceList.innerHTML = items.map(e =>
`<div class="evidence-item">${escHtml(e)}</div>`
).join('');
}
}
function renderRewardBars(reward) {
const bd = reward.breakdown || {};
const segments = [
{ key: 'verdict_accuracy', elId: 'seg-verdict', valId: 'seg-verdict-val', max: 0.5 },
{ key: 'action_appropriateness', elId: 'seg-action', valId: 'seg-action-val', max: 0.2 },
{ key: 'evidence_quality', elId: 'seg-evidence', valId: 'seg-evidence-val', max: 0.15 },
{ key: 'confidence_calibration', elId: 'seg-calib', valId: 'seg-calib-val', max: 0.15 },
];
segments.forEach(({ key, elId, valId, max }) => {
const val = bd[key] || 0;
const pct = Math.min(100, (val / max) * 100);
const fillEl = document.getElementById(elId);
if (fillEl) {
fillEl.style.width = `${pct}%`;
// Colour by fill level
if (pct > 70) fillEl.style.background = 'var(--cyan)';
else if (pct > 30) fillEl.style.background = 'var(--amber)';
else fillEl.style.background = 'var(--red)';
}
const valEl = document.getElementById(valId);
if (valEl) valEl.textContent = val.toFixed(2);
});
}
// ============================================================
// UTILS
// ============================================================
function escHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
// ============================================================
// KEYBOARD SHORTCUT — press R to reset
// ============================================================
document.addEventListener('keydown', (e) => {
if (e.key === 'r' || e.key === 'R') doReset();
});
// ============================================================
// BOOTSTRAP
// ============================================================
(async function init() {
// Try to load current state immediately
await pollState();
// Poll every 2 seconds
setInterval(pollState, 2000);
})();
</script>
</body>
</html>