54 lines
1.8 KiB
TypeScript
54 lines
1.8 KiB
TypeScript
/**
|
|
* /api/search/hybrid — Public hybrid search endpoint (no auth required).
|
|
*
|
|
* Used by the global Cmd+K command palette + any external integration.
|
|
* Returns the same shape as the chat tool but exposed via HTTP.
|
|
*
|
|
* GET /api/search/hybrid?q=...&lang=pt&doc_id=...&top_k=10
|
|
*/
|
|
import { NextRequest } from "next/server";
|
|
import { hybridSearch } from "@/lib/retrieval/hybrid";
|
|
|
|
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 q = u.searchParams.get("q")?.trim();
|
|
if (!q) return json({ error: "q required", hits: [] }, 400);
|
|
const lang = (u.searchParams.get("lang") === "en" ? "en" : "pt") as "pt" | "en";
|
|
const doc_id = u.searchParams.get("doc_id") || null;
|
|
const type = u.searchParams.get("type") || null;
|
|
const top_k = Math.min(Number(u.searchParams.get("top_k") ?? 10), 50);
|
|
const ufo_only = u.searchParams.get("ufo_only") === "1";
|
|
const no_rerank = u.searchParams.get("rerank") === "0";
|
|
|
|
try {
|
|
const hits = await hybridSearch({ query: q, lang, doc_id, type, ufo_only, top_k, no_rerank });
|
|
return json({
|
|
query: q,
|
|
lang,
|
|
count: hits.length,
|
|
hits: hits.map((h) => ({
|
|
chunk_id: h.chunk_id,
|
|
doc_id: h.doc_id,
|
|
page: h.page,
|
|
type: h.type,
|
|
bbox: h.bbox,
|
|
classification: h.classification,
|
|
snippet: ((lang === "en" ? h.content_en : h.content_pt) || "").slice(0, 280),
|
|
score: Number((h.rerank_score ?? h.score).toFixed(4)),
|
|
href: `/d/${h.doc_id}#${h.chunk_id}`,
|
|
})),
|
|
});
|
|
} catch (e) {
|
|
return json({ error: "retrieval_unavailable", message: (e as Error).message }, 503);
|
|
}
|
|
}
|