"use client"; import { useRef, useEffect } from "react"; import type { EventItem, Observation } from "../lib/types"; type Props = { events: EventItem[]; observation: Observation | null; info: { total_reward: number; score: number; adversarial_detections?: number; adversarial_poisonings?: number } | undefined; }; function logClass(e: EventItem): string { if (e.outcome === "poisoned") return "alert"; if (e.outcome === "blocked") return "warn"; if (e.outcome === "success") return "ok"; return ""; } function formatTime(step: number): string { const m = 14, s = 3 + step; const ss = s % 60; const mm = m + Math.floor(s / 60); return `${mm}:${String(ss).padStart(2, "0")}:${String(Math.floor(Math.random() * 60)).padStart(2, "0")}`; } export default function ExecutionLog({ events, observation, info }: Props) { const scrollRef = useRef(null); useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [events.length]); const detectRate = info?.adversarial_detections !== undefined && info?.adversarial_poisonings !== undefined ? (info.adversarial_detections + info.adversarial_poisonings) > 0 ? Math.round((info.adversarial_detections / (info.adversarial_detections + info.adversarial_poisonings)) * 100) : 0 : null; return (
EVENT LOG
{events.slice(-12).map((e, i) => (
{formatTime(e.step)} {e.action === "reset" ? "EPISODE RESET ⟳" : `${e.specialist ?? "SYS"} ${e.summary.substring(0, 40)}` }
))} {events.length === 0 && (
Waiting for simulation data...
)}
EPISODE METRICS
CUMULATIVE REWARD = 0 ? "g" : "r"}`}> {(info?.total_reward ?? 0) >= 0 ? "+" : ""}{(info?.total_reward ?? 0).toFixed(2)}
SCORE {(info?.score ?? 0).toFixed(3)}
{detectRate !== null && (
DETECT RATE {detectRate}%
)}
STEP {observation?.step_count ?? 0}/{observation?.max_steps ?? 0}
); }