SPB Git forge
7commits 1branches 0releases
229.0 KBsize
maindefault branch
12 days agolast push
TypeScript 91.8% HTML 3.2% JavaScript 3% SQL 1.4% CSS 0.7%
6.1 KB · 111 lines tsx
Raw Blame History
1"use client";23import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Legend, Line, LineChart, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";4import { TYPE_COLORS, fmtNum } from "@/lib/format";56const AXIS = { stroke: "#6b7a8c", fontSize: 10.5, fontFamily: "var(--font-mono)" } as const;7const TT = { contentStyle: { background: "#0d1219", border: "1px solid #243042", borderRadius: 8, fontSize: 12, fontFamily: "var(--font-mono)" }, labelStyle: { color: "#a9b6c6" }, itemStyle: { color: "#e6edf5" } } as const;89export function ActivityChart({ data, height = 220 }: { data: { bucket: string; entities: number; responses: number; actions: number; schemas: number }[]; height?: number }) {10  const rows = data.map((d) => ({ ...d, t: new Date(d.bucket).toLocaleTimeString("en-CA", { hour12: false, hour: "2-digit", minute: "2-digit" }) }));11  return (12    <ResponsiveContainer width="100%" height={height}>13      <AreaChart data={rows} margin={{ top: 8, right: 8, left: -14, bottom: 0 }}>14        <defs>15          <linearGradient id="gEnt" x1="0" x2="0" y1="0" y2="1">16            <stop offset="0" stopColor="#38e1ff" stopOpacity={0.5} />17            <stop offset="1" stopColor="#38e1ff" stopOpacity={0} />18          </linearGradient>19          <linearGradient id="gResp" x1="0" x2="0" y1="0" y2="1">20            <stop offset="0" stopColor="#9d7bff" stopOpacity={0.4} />21            <stop offset="1" stopColor="#9d7bff" stopOpacity={0} />22          </linearGradient>23        </defs>24        <CartesianGrid stroke="#1a2331" vertical={false} />25        <XAxis dataKey="t" tick={AXIS} tickLine={false} axisLine={false} minTickGap={40} />26        <YAxis tick={AXIS} tickLine={false} axisLine={false} tickFormatter={(v) => fmtNum(v)} />27        <Tooltip {...TT} />28        <Area type="monotone" dataKey="responses" name="network responses" stroke="#9d7bff" fill="url(#gResp)" strokeWidth={1.5} />29        <Area type="monotone" dataKey="entities" name="entities discovered" stroke="#38e1ff" fill="url(#gEnt)" strokeWidth={1.8} />30        <Line type="monotone" dataKey="actions" name="actions" stroke="#ffb347" strokeWidth={1.2} dot={false} />31      </AreaChart>32    </ResponsiveContainer>33  );34}3536export function TypeDonut({ data, height = 200 }: { data: { entity_type: string; n: number }[]; height?: number }) {37  const agg = new Map<string, number>();38  for (const d of data) agg.set(d.entity_type, (agg.get(d.entity_type) ?? 0) + Number(d.n));39  const rows = [...agg.entries()].map(([name, value]) => ({ name, value })).sort((a, b) => b.value - a.value);40  return (41    <ResponsiveContainer width="100%" height={height}>42      <PieChart>43        <Pie data={rows} dataKey="value" nameKey="name" innerRadius="58%" outerRadius="85%" paddingAngle={2} stroke="none">44          {rows.map((r) => (45            <Cell key={r.name} fill={TYPE_COLORS[r.name] ?? "#6b7a8c"} />46          ))}47        </Pie>48        <Tooltip {...TT} />49        <Legend iconType="circle" iconSize={7} wrapperStyle={{ fontSize: 11, fontFamily: "var(--font-mono)", color: "#a9b6c6" }} />50      </PieChart>51    </ResponsiveContainer>52  );53}5455export function GainChart({ data, height = 200 }: { data: { step: number; expected_gain: number; novelty: number; relevance: number; action_type: string; new_entities?: number }[]; height?: number }) {56  return (57    <ResponsiveContainer width="100%" height={height}>58      <LineChart data={data} margin={{ top: 8, right: 8, left: -18, bottom: 0 }}>59        <CartesianGrid stroke="#1a2331" vertical={false} />60        <XAxis dataKey="step" tick={AXIS} tickLine={false} axisLine={false} />61        <YAxis tick={AXIS} tickLine={false} axisLine={false} domain={[0, 1]} />62        <Tooltip {...TT} labelFormatter={(l) => `step ${l}`} />63        <Line type="monotone" dataKey="expected_gain" name="information gain" stroke="#ffb347" strokeWidth={2} dot={{ r: 2 }} />64        <Line type="monotone" dataKey="novelty" name="novelty" stroke="#38e1ff" strokeWidth={1.2} dot={false} />65        <Line type="monotone" dataKey="relevance" name="relevance" stroke="#9d7bff" strokeWidth={1.2} dot={false} />66      </LineChart>67    </ResponsiveContainer>68  );69}7071export function YieldBars({ data, height = 200 }: { data: { action_type: string; n: number; avg_new_entities: number | null; success_rate: number | null }[]; height?: number }) {72  const rows = data.filter((d) => d.n > 0).map((d) => ({ ...d, avg_new_entities: Number(d.avg_new_entities ?? 0), label: d.action_type.replace(/_/g, " ").toLowerCase() }));73  return (74    <ResponsiveContainer width="100%" height={height}>75      <BarChart data={rows} layout="vertical" margin={{ top: 4, right: 12, left: 10, bottom: 0 }}>76        <CartesianGrid stroke="#1a2331" horizontal={false} />77        <XAxis type="number" tick={AXIS} tickLine={false} axisLine={false} />78        <YAxis type="category" dataKey="label" tick={AXIS} tickLine={false} axisLine={false} width={110} />79        <Tooltip {...TT} />80        <Bar dataKey="avg_new_entities" name="avg new entities / action" radius={[0, 4, 4, 0]}>81          {rows.map((r) => (82            <Cell key={r.action_type} fill={r.action_type.startsWith("OPEN") ? "#38e1ff" : r.action_type === "SEARCH" ? "#ffb347" : "#9d7bff"} />83          ))}84        </Bar>85      </BarChart>86    </ResponsiveContainer>87  );88}8990export function SurfaceBars({ dom, network, both, height = 120 }: { dom: number; network: number; both: number; height?: number }) {91  const rows = [92    { name: "DOM only", value: Math.max(0, dom - both), fill: "#43e69a" },93    { name: "Both surfaces", value: both, fill: "#38e1ff" },94    { name: "Network only", value: Math.max(0, network - both), fill: "#9d7bff" },95  ];96  return (97    <ResponsiveContainer width="100%" height={height}>98      <BarChart data={rows} margin={{ top: 4, right: 8, left: -18, bottom: 0 }}>99        <XAxis dataKey="name" tick={AXIS} tickLine={false} axisLine={false} />100        <YAxis tick={AXIS} tickLine={false} axisLine={false} tickFormatter={(v) => fmtNum(v)} />101        <Tooltip {...TT} />102        <Bar dataKey="value" radius={[4, 4, 0, 0]}>103          {rows.map((r) => (104            <Cell key={r.name} fill={r.fill} />105          ))}106        </Bar>107      </BarChart>108    </ResponsiveContainer>109  );110}111