Spaces:
Running
Running
File size: 1,939 Bytes
a36db1b | 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 | "use client";
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import type { EventItem } from "../lib/types";
const ICONS: Record<EventItem["outcome"], string> = {
success: "✅",
blocked: "🛡️",
poisoned: "☠️",
skipped: "⏭️",
reset: "🔄",
};
export default function FlightRecorder({
events,
lastReq,
lastRes,
}: {
events: EventItem[];
lastReq: Record<string, unknown> | null;
lastRes: Record<string, unknown> | null;
}) {
const [showJson, setShowJson] = useState(false);
const recent = events.slice(-10).reverse();
return (
<>
<div className="fr-list">
<AnimatePresence initial={false}>
{recent.map((ev) => (
<motion.div
key={`${ev.step}-${ev.action}`}
className="fr-row"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.2 }}
>
<span className="fr-step">#{ev.step}</span>
<div>
<span className="fr-action">
<span className="fr-icon">{ICONS[ev.outcome]} </span>
{ev.action}{ev.specialist ? `:${ev.specialist}` : ""}
</span>
<div className="fr-summary">{ev.summary}</div>
</div>
<span className={`fr-reward ${ev.reward >= 0.5 ? "pos" : "neg"}`}>
{ev.outcome === "reset" ? "—" : ev.reward.toFixed(2)}
</span>
</motion.div>
))}
</AnimatePresence>
</div>
<button className="fr-toggle" onClick={() => setShowJson(!showJson)}>
{showJson ? "Hide" : "Show"} raw JSON
</button>
{showJson && (
<div className="json-view">
<pre>{JSON.stringify({ request: lastReq, response: lastRes }, null, 2)}</pre>
</div>
)}
</>
);
}
|