File size: 3,346 Bytes
1d68c54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"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<HTMLDivElement>(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 (
    <div className="sim-panel" style={{ borderRight: "none", borderLeft: "1px solid rgba(0,200,255,0.08)" }}>
      <div className="sim-panel-label">EVENT LOG</div>

      <div ref={scrollRef} style={{ maxHeight: 220, overflowY: "auto" }}>
        {events.slice(-12).map((e, i) => (
          <div key={i} className={`log-entry ${logClass(e)}`}>
            <span className="log-time">{formatTime(e.step)}</span>
            {e.action === "reset"
              ? "EPISODE RESET ⟳"
              : `${e.specialist ?? "SYS"} ${e.summary.substring(0, 40)}`
            }
          </div>
        ))}
        {events.length === 0 && (
          <div className="log-entry" style={{ opacity: 0.3 }}>
            Waiting for simulation data...
          </div>
        )}
      </div>

      <div style={{ marginTop: 16, paddingTop: 16, borderTop: "1px solid rgba(0,200,255,0.08)" }}>
        <div className="sim-panel-label">EPISODE METRICS</div>
        <div className="sim-metric-row">
          <span className="sim-metric-label">CUMULATIVE REWARD</span>
          <span className={`sim-metric-val ${(info?.total_reward ?? 0) >= 0 ? "g" : "r"}`}>
            {(info?.total_reward ?? 0) >= 0 ? "+" : ""}{(info?.total_reward ?? 0).toFixed(2)}
          </span>
        </div>
        <div className="sim-metric-row">
          <span className="sim-metric-label">SCORE</span>
          <span className="sim-metric-val c">{(info?.score ?? 0).toFixed(3)}</span>
        </div>
        {detectRate !== null && (
          <div className="sim-metric-row">
            <span className="sim-metric-label">DETECT RATE</span>
            <span className="sim-metric-val c">{detectRate}%</span>
          </div>
        )}
        <div className="sim-metric-row">
          <span className="sim-metric-label">STEP</span>
          <span className="sim-metric-val a">
            {observation?.step_count ?? 0}/{observation?.max_steps ?? 0}
          </span>
        </div>
      </div>
    </div>
  );
}