spb/social-runtime-crawler
Public
TypeScript 91.8%
HTML 3.2%
JavaScript 3%
SQL 1.4%
CSS 0.7%
1"use client";23import { useState } from "react";4import { useApi } from "@/lib/api";5import { fmtDate } from "@/lib/format";6import { Empty, Panel, Skeleton, Table, Tag } from "@/components/ui";78interface Schema {9 platform: string;10 shape_hash: string;11 fingerprint: { hostname: string; path_pattern: string; method: string; graphql_operation?: string; observed_entity_types?: string[] };12 schema: { candidate_entity_types: { type: string; confidence: number; path: string }[]; fields: { path: string; semantic?: { kind: string; confidence: number }; example?: string; types?: string[] }[]; object_count: number; repeated_object_paths: string[] };13 observed_count: number;14 sample_url: string;15 first_seen: string;16 last_seen: string;17}1819export default function Schemas() {20 const [platform, setPlatform] = useState("");21 const { data, loading } = useApi<Schema[]>(`/schemas${platform ? `?platform=${platform}` : ""}`, { refreshMs: 15_000 });22 const platforms = [...new Set((data ?? []).map((s) => s.platform))];23 return (24 <div className="space-y-4">25 <div className="flex flex-wrap items-end gap-3">26 <div>27 <h1 className="text-2xl font-semibold tracking-tight">Runtime APIs</h1>28 <p className="text-sm text-fg-2 max-w-2xl">Response shapes the browser received while a human-like session unfolded. Endpoints are fingerprinted by (host, path pattern, method, structure hash) and their fields are semantically inferred — nothing here was written by hand.</p>29 </div>30 <select value={platform} onChange={(e) => setPlatform(e.target.value)} className="ml-auto rounded-lg bg-bg-2 border border-line-2 px-2 py-1.5 text-sm">31 <option value="">all platforms</option>32 {["youtube", "reddit", ...platforms.filter((p) => !["youtube", "reddit"].includes(p))].map((p) => (33 <option key={p} value={p}>34 {p}35 </option>36 ))}37 </select>38 </div>39 {loading ? (40 <Skeleton rows={8} />41 ) : data?.length ? (42 <div className="space-y-3">43 {data.map((sc) => (44 <details key={sc.platform + sc.shape_hash} className="panel open:glow-cyan/30">45 <summary className="cursor-pointer px-4 py-3 flex flex-wrap items-center gap-2 text-sm">46 <Tag>{sc.platform}</Tag>47 <Tag color="#9d7bff">{sc.fingerprint.method}</Tag>48 <span className="mono">49 {sc.fingerprint.hostname}50 <span className="text-fg-2">{sc.fingerprint.path_pattern}</span>51 </span>52 {sc.fingerprint.graphql_operation && <Tag color="#ff7ad9">{sc.fingerprint.graphql_operation}</Tag>}53 <span className="ml-auto flex gap-1 flex-wrap">54 {sc.schema.candidate_entity_types.slice(0, 5).map((c) => (55 <Tag key={c.type + c.path} color="#38e1ff" title={c.path}>56 {c.type} {Number(c.confidence).toFixed(2)}57 </Tag>58 ))}59 </span>60 <span className="mono text-xs text-dim whitespace-nowrap">61 seen {sc.observed_count}× · {sc.schema.object_count} objects · #{sc.shape_hash.slice(0, 8)}62 </span>63 </summary>64 <div className="px-4 pb-4 space-y-3">65 <div className="text-[11px] text-dim mono break-all">66 sample {sc.sample_url} · first {fmtDate(sc.first_seen)} · last {fmtDate(sc.last_seen)}67 </div>68 {sc.schema.repeated_object_paths?.length ? (69 <div className="text-xs text-fg-2">70 repeated object arrays (entity lists): <span className="mono">{sc.schema.repeated_object_paths.slice(0, 5).join(" · ")}</span>71 </div>72 ) : null}73 <Table head={["field path", "inferred semantic", "conf", "types", "example"]} dense>74 {sc.schema.fields.slice(0, 80).map((f) => (75 <tr key={f.path}>76 <td className="mono text-[11px] text-fg-2 max-w-[560px] truncate" title={f.path}>77 {f.path}78 </td>79 <td>{f.semantic ? <Tag color={f.semantic.kind === "identifier" ? "#ffb347" : f.semantic.kind.includes("url") ? "#ff7ad9" : "#43e69a"}>{f.semantic.kind}</Tag> : "—"}</td>80 <td className="mono text-[11px]">{f.semantic ? Number(f.semantic.confidence).toFixed(2) : ""}</td>81 <td className="mono text-[11px] text-dim">{f.types?.join("|")}</td>82 <td className="mono text-[11px] text-dim max-w-[320px] truncate">{f.example}</td>83 </tr>84 ))}85 </Table>86 </div>87 </details>88 ))}89 </div>90 ) : (91 <Panel>92 <Empty>No schemas discovered yet.</Empty>93 </Panel>94 )}95 </div>96 );97}98