| <!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; |
| } |
| |
| |
| 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; } |
| } |
| |
| |
| 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 { |
| position: relative; |
| z-index: 1; |
| display: grid; |
| grid-template-rows: 52px 1fr 70px; |
| grid-template-columns: 280px 1fr 300px; |
| height: 100vh; |
| gap: 0; |
| } |
| |
| |
| .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; } |
| } |
| |
| |
| .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 { |
| 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 { |
| 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 { |
| 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; |
| } |
| |
| |
| .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); } |
| |
| |
| ::-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); } |
| |
| |
| .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"> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
| |
| |
| |
| 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' }, |
| }; |
| |
| |
| |
| |
| function updateClock() { |
| const now = new Date(); |
| document.getElementById('clock').textContent = |
| now.toTimeString().slice(0, 8); |
| } |
| setInterval(updateClock, 1000); |
| updateClock(); |
| |
| |
| |
| |
| 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; |
| } |
| } |
| |
| |
| |
| |
| 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); |
| } |
| } |
| |
| |
| |
| |
| 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); |
| } |
| } |
| |
| |
| |
| |
| 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; |
| |
| |
| 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'; |
| } |
| |
| |
| document.getElementById('totalReward').textContent = |
| (state.total_reward || 0).toFixed(2); |
| |
| |
| document.getElementById('tasksDone').textContent = `${tasksDone} / 4`; |
| |
| |
| if (obs) { |
| renderLeftPanel(obs); |
| renderLogs(obs); |
| } else if (done) { |
| renderDoneState(state); |
| } |
| |
| |
| if (action) { |
| renderVerdict(action); |
| } |
| |
| |
| if (reward) { |
| renderRewardBars(reward); |
| } |
| } |
| |
| function renderLeftPanel(obs) { |
| const diff = DIFFICULTY_MAP[obs.task_id] || { label: '—', cls: '' }; |
| |
| |
| 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; |
| |
| |
| 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 || []; |
| |
| |
| 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); |
| |
| |
| 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}%`; |
| |
| 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); |
| }); |
| } |
| |
| |
| |
| |
| function escHtml(str) { |
| return String(str) |
| .replace(/&/g, '&') |
| .replace(/</g, '<') |
| .replace(/>/g, '>') |
| .replace(/"/g, '"'); |
| } |
| |
| |
| |
| |
| document.addEventListener('keydown', (e) => { |
| if (e.key === 'r' || e.key === 'R') doReset(); |
| }); |
| |
| |
| |
| |
| (async function init() { |
| |
| await pollState(); |
| |
| setInterval(pollState, 2000); |
| })(); |
| </script> |
| </body> |
| </html> |
|
|