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%
3.2 KB · 80 lines tsx
Raw Blame History
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