disclosure-bureau/web/components/entity-graph-mini.tsx

88 lines
2.8 KiB
TypeScript
Raw Permalink Normal View History

/**
* EntityGraphMini sidebar widget with co-mentioned entities (neighbors)
* and a sample of chunks where they co-occur. Lives next to the entity body.
*
* Gracefully degrades to empty state when entity_mentions is unpopulated
* or the DB is unreachable.
*/
import Link from "next/link";
import { findEntity, getNeighbors } from "@/lib/retrieval/graph";
const CLASS_COLOR: Record<string, string> = {
person: "text-[#ff6ec7] border-[#ff6ec7]",
organization: "text-[#ff8a4d] border-[#ff8a4d]",
location: "text-[#3fde6a] border-[#3fde6a]",
event: "text-[#ffa500] border-[#ffa500]",
uap_object: "text-[#ff3344] border-[#ff3344]",
vehicle: "text-[#5b9bd5] border-[#5b9bd5]",
operation: "text-[#9b5de5] border-[#9b5de5]",
concept: "text-[#06d6a0] border-[#06d6a0]",
};
const CLASS_FOLDER: Record<string, string> = {
person: "people",
organization: "organizations",
location: "locations",
event: "events",
uap_object: "uap-objects",
vehicle: "vehicles",
operation: "operations",
concept: "concepts",
};
export async function EntityGraphMini({
entityClassSingular,
entityId,
}: {
entityClassSingular: string;
entityId: string;
}) {
let neighbors: Awaited<ReturnType<typeof getNeighbors>> = [];
let totalMentions = 0;
try {
const ent = await findEntity(entityClassSingular, entityId);
if (ent) {
totalMentions = ent.total_mentions ?? 0;
neighbors = await getNeighbors(ent.entity_pk, { limit: 25 });
}
} catch {
return null;
}
if (neighbors.length === 0) {
return null;
}
return (
<section className="mt-6 border-t border-[rgba(0,255,156,0.12)] pt-4">
<h3 className="font-mono text-[10px] text-[#8896aa] uppercase tracking-widest mb-3">
🕸 co-mentioned · top {neighbors.length} via {totalMentions} mentions
</h3>
<ul className="space-y-1.5">
{neighbors.map((n) => {
const folder = CLASS_FOLDER[n.entity_class] ?? n.entity_class;
const color = CLASS_COLOR[n.entity_class] ?? "text-[#7fdbff] border-[#7fdbff]";
return (
<li key={n.entity_pk}>
<Link
href={`/e/${folder}/${n.entity_id}`}
className="group flex items-center gap-2 text-xs hover:bg-[rgba(0,255,156,0.04)] rounded px-2 py-1 -mx-2"
>
<span className={`font-mono text-[9px] px-1.5 py-0.5 border rounded ${color} opacity-70 group-hover:opacity-100`}>
{n.entity_class.slice(0, 3)}
</span>
<span className="flex-1 truncate text-[#c8d4e6] group-hover:text-[#00ff9c]">
{n.canonical_name}
</span>
<span className="font-mono text-[10px] text-[#5a6678]">
×{n.weight}
</span>
</Link>
</li>
);
})}
</ul>
</section>
);
}