66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
|
|
/**
|
||
|
|
* /api/graph — Public entity-graph endpoints (read-only).
|
||
|
|
*
|
||
|
|
* GET /api/graph?op=neighbors&class=people&id=j-edgar-hoover
|
||
|
|
* → top entities co-mentioned with this one
|
||
|
|
*
|
||
|
|
* GET /api/graph?op=paths&from=PK_A&to=PK_B&max_hops=3
|
||
|
|
* → shortest paths between two entities via shared chunks
|
||
|
|
*
|
||
|
|
* GET /api/graph?op=co_mention&a=PK_A&b=PK_B
|
||
|
|
* → chunks where both entities appear together
|
||
|
|
*/
|
||
|
|
import { NextRequest } from "next/server";
|
||
|
|
import { findEntity, getNeighbors, findPaths, getCoMentionChunks } from "@/lib/retrieval/graph";
|
||
|
|
|
||
|
|
export const runtime = "nodejs";
|
||
|
|
export const dynamic = "force-dynamic";
|
||
|
|
|
||
|
|
function json(data: unknown, status = 200) {
|
||
|
|
return new Response(JSON.stringify(data), {
|
||
|
|
status,
|
||
|
|
headers: { "content-type": "application/json" },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function GET(req: NextRequest) {
|
||
|
|
const u = new URL(req.url);
|
||
|
|
const op = u.searchParams.get("op") ?? "neighbors";
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (op === "neighbors") {
|
||
|
|
const cls = u.searchParams.get("class") ?? "";
|
||
|
|
const id = u.searchParams.get("id") ?? "";
|
||
|
|
if (!cls || !id) return json({ error: "class and id required" }, 400);
|
||
|
|
const ent = await findEntity(cls, id);
|
||
|
|
if (!ent) return json({ error: "entity_not_found", class: cls, id }, 404);
|
||
|
|
const filterClasses = u.searchParams.get("filter_classes")?.split(",").filter(Boolean);
|
||
|
|
const limit = Number(u.searchParams.get("limit") ?? 30);
|
||
|
|
const neighbors = await getNeighbors(ent.entity_pk, { limit, classes: filterClasses });
|
||
|
|
return json({ entity: ent, neighbors });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (op === "paths") {
|
||
|
|
const from = Number(u.searchParams.get("from"));
|
||
|
|
const to = Number(u.searchParams.get("to"));
|
||
|
|
const maxHops = Math.min(Number(u.searchParams.get("max_hops") ?? 3), 4);
|
||
|
|
if (!from || !to) return json({ error: "from and to required" }, 400);
|
||
|
|
const paths = await findPaths(from, to, maxHops);
|
||
|
|
return json({ from, to, max_hops: maxHops, paths });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (op === "co_mention") {
|
||
|
|
const a = Number(u.searchParams.get("a"));
|
||
|
|
const b = Number(u.searchParams.get("b"));
|
||
|
|
const limit = Math.min(Number(u.searchParams.get("limit") ?? 20), 100);
|
||
|
|
if (!a || !b) return json({ error: "a and b required" }, 400);
|
||
|
|
const chunks = await getCoMentionChunks(a, b, limit);
|
||
|
|
return json({ a, b, count: chunks.length, chunks });
|
||
|
|
}
|
||
|
|
|
||
|
|
return json({ error: "unknown_op", op }, 400);
|
||
|
|
} catch (e) {
|
||
|
|
return json({ error: "graph_unavailable", message: (e as Error).message }, 503);
|
||
|
|
}
|
||
|
|
}
|