Files
mcpedia/apps/web/app/api/search/route.ts
T

72 lines
2.0 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { keywordSearch, hybridSearch, semanticSearch } from "@mcpedia/core";
// GET /api/search?q=...&mode=hybrid|keyword|semantic
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const q = searchParams.get("q") ?? "";
const mode = searchParams.get("mode") ?? "hybrid";
const limit = Math.min(Math.max(Number(searchParams.get("limit") ?? 20), 1), 50);
if (!q.trim()) {
return NextResponse.json({ results: [] });
}
try {
if (mode === "keyword") {
const hits = await keywordSearch(q, limit);
return NextResponse.json({
results: hits.map((h) => ({
slug: h.doc.slug,
title: h.doc.title,
section: h.doc.section,
score: h.rank,
snippet: h.snippet,
})),
});
}
if (mode === "semantic") {
const hits = await semanticSearch(q, limit);
return NextResponse.json({
results: hits.map((h) => ({
slug: h.slug,
title: h.slug.split("/").pop() ?? h.slug,
section: h.slug.split("/")[0] ?? "docs",
score: h.score,
snippet: h.content.slice(0, 160),
})),
});
}
// Default: hybrid search
const hits = await hybridSearch(q, limit);
return NextResponse.json({
results: hits.map((h) => ({
slug: h.doc.slug,
title: h.doc.title,
section: h.doc.section,
score: h.rank,
snippet: h.snippet,
})),
});
} catch (err) {
console.error("Search API error:", err);
// Fall back to keyword search if embedding provider fails
try {
const hits = await keywordSearch(q, limit);
return NextResponse.json({
results: hits.map((h) => ({
slug: h.doc.slug,
title: h.doc.title,
section: h.doc.section,
score: h.rank,
snippet: h.snippet,
})),
});
} catch {
return NextResponse.json({ results: [], error: "Search failed" }, { status: 500 });
}
}
}