spb/social-runtime-crawler
Public
TypeScript 91.8%
HTML 3.2%
JavaScript 3%
SQL 1.4%
CSS 0.7%
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