import { useEffect, useState } from "react"; const METHODS = [ { id: "hybrid", label: "Hybrid", hint: "BM25 + BERT fusion (default)", semantic: true }, { id: "bert", label: "BERT", hint: "Dense semantic retrieval", semantic: true }, { id: "bm25", label: "BM25", hint: "Modern lexical ranking" }, { id: "tfidf", label: "TF-IDF", hint: "Classic ranking, for comparison" }, { id: "prf", label: "Relevance Feedback", hint: "Mark results relevant, then refine" }, { id: "wordnet", label: "WordNet", hint: "BM25 + synonym expansion" }, ]; // Only allow http(s) links through to href; guards against javascript:/data: // URLs sneaking in from the dataset (defensive — the dataset is trusted). function safeUrl(url) { return typeof url === "string" && /^https?:\/\//i.test(url) ? url : "#"; } const EXAMPLES = [ "covid vaccine health", "election president", "movie film review", "stock market crash", "climate change", ]; export default function App() { const [query, setQuery] = useState(""); const [method, setMethod] = useState("hybrid"); const [topK, setTopK] = useState(10); const [category, setCategory] = useState(""); const [categories, setCategories] = useState([]); const [health, setHealth] = useState(null); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); // Doc ids the user marked relevant (for true relevance feedback on "prf"). const [relevantIds, setRelevantIds] = useState(() => new Set()); useEffect(() => { fetch("/api/health") .then((r) => r.json()) .then((d) => { setHealth(d); // Fall back to BM25 if BERT embeddings weren't built (e.g. `run.bat lite`). if (!d.bert_available) setMethod("bm25"); }) .catch(() => {}); fetch("/api/categories") .then((r) => r.json()) .then((d) => setCategories(d.categories || [])) .catch(() => {}); }, []); // withFeedback=true reuses the marked-relevant docs (the "Refine" action); // a fresh search clears them. async function runSearch(q = query, withFeedback = false) { if (!q.trim()) return; if (!withFeedback) setRelevantIds(new Set()); setLoading(true); setError(""); try { const params = new URLSearchParams({ q, method, top_k: topK }); if (category) params.set("category", category); if (method === "prf" && withFeedback) { for (const id of relevantIds) params.append("relevant_ids", id); } const res = await fetch(`/api/search?${params}`); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.detail || `Request failed (${res.status})`); } setData(await res.json()); } catch (e) { setError(e.message); setData(null); } finally { setLoading(false); } } function toggleRelevant(id) { setRelevantIds((prev) => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; }); } const bertDisabled = health && !health.bert_available; const feedbackMode = method === "prf"; return (
Lexical (BM25/TF-IDF) & semantic (BERT) retrieval with query expansion {health && ( {" "}· {health.documents.toLocaleString()} docs ·{" "} {health.vocabulary.toLocaleString()} terms )}
Relevance feedback: tick the results that match what you want, then refine the search.
)}{r.short_description}