spb/social-runtime-crawler
Public
TypeScript 91.8%
HTML 3.2%
JavaScript 3%
SQL 1.4%
CSS 0.7%
1"use client";23import Link from "next/link";4import { ExternalLink } from "lucide-react";5import { fmtDuration, fmtNum, truncate } from "@/lib/format";6import { SurfaceChips, Table, TypeTag } from "./ui";78export interface EntityRow {9 fingerprint: string;10 entity_type?: string;11 type?: string;12 platform: string;13 platform_id?: string | null;14 url?: string | null;15 name?: string | null;16 text_excerpt?: string | null;17 text?: string | null;18 author?: string | null;19 metrics?: Record<string, number> | null;20 media?: { has_video?: boolean; duration_s?: number; thumbnail_url?: string } | null;21 fields?: Record<string, { value: unknown; provenance: { surface: string; confidence: number; detail?: string }[] }> | null;22 provenance?: { surface: string; confidence: number; detail?: string }[] | null;23 confidence?: number | null;24 seen_count?: number;25 last_seen?: string;26 last_step?: number;27}2829export function provenanceOf(e: EntityRow) {30 if (e.provenance?.length) return e.provenance;31 if (e.fields) return Object.values(e.fields).flatMap((f) => f.provenance ?? []);32 return [];33}3435export function EntityTable({ rows, showPlatform = false, dense = true }: { rows: EntityRow[]; showPlatform?: boolean; dense?: boolean }) {36 return (37 <Table head={["type", "entity", "author", "metrics", "evidence", "conf", ""]} dense={dense}>38 {rows.map((e) => {39 const prov = provenanceOf(e);40 const conf = e.confidence ?? Math.max(0, ...prov.map((p) => p.confidence));41 const type = e.entity_type ?? e.type ?? "?";42 return (43 <tr key={e.fingerprint}>44 <td>45 <TypeTag type={type} />46 {showPlatform && <div className="text-[10px] text-dim mono mt-0.5">{e.platform}</div>}47 </td>48 <td className="max-w-[420px]">49 <Link href={`/entities/${encodeURIComponent(e.fingerprint)}`} className="text-fg hover:text-cyan">50 {truncate(e.name ?? e.text ?? e.text_excerpt ?? e.platform_id ?? e.fingerprint, 90)}51 </Link>52 {e.media?.has_video && e.media.duration_s ? <span className="ml-2 text-[10.5px] mono text-dim">{fmtDuration(e.media.duration_s)}</span> : null}53 <div className="text-[10.5px] mono text-dim truncate">{e.platform_id}</div>54 </td>55 <td className="text-fg-2 whitespace-nowrap">{truncate(e.author ?? "", 28)}</td>56 <td className="mono text-[11px] text-fg-2 whitespace-nowrap">57 {e.metrics && Object.keys(e.metrics).length58 ? Object.entries(e.metrics)59 .map(([k, v]) => `${fmtNum(v)} ${k}`)60 .join(" · ")61 : "—"}62 </td>63 <td>64 <SurfaceChips provenance={prov} />65 </td>66 <td className="mono text-[11px]">{conf ? conf.toFixed(2) : "—"}</td>67 <td>68 {e.url && (69 <a href={e.url} target="_blank" rel="noreferrer" className="text-dim hover:text-cyan" title="open on platform">70 <ExternalLink className="h-3.5 w-3.5" />71 </a>72 )}73 </td>74 </tr>75 );76 })}77 </Table>78 );79}80