esfiles / frontend /src /components /SemanticSearch.tsx
Besjon Cifliku
feat: simplify the workflow and search patterns
9f87ec0
import { useState } from "react";
import { api } from "../api";
import type { QueryResultItem } from "../types";
import { useApiCall } from "../hooks/useApiCall";
import ScoreBar from "./ScoreBar";
import StatusMessage from "./StatusMessage";
import DocumentViewer from "./DocumentViewer";
export default function SemanticSearch() {
const [query, setQuery] = useState("");
const [topK, setTopK] = useState(10);
const { data: results, loading, error, run } = useApiCall<QueryResultItem[]>();
async function handleSearch() {
if (!query.trim()) return;
await run(() => api.query({ text: query, top_k: topK }).then((r) => r.results));
}
return (
<div>
<div className="panel">
<h2>Semantic Search</h2>
<p className="panel-desc">
Find passages most semantically similar to your query across the entire corpus.
</p>
<div className="form-row">
<div className="form-group">
<label>Query</label>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="e.g. a place where children learn and take tests"
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
/>
</div>
<div className="form-group form-group-sm">
<label>Top K</label>
<input type="number" value={topK} onChange={(e) => setTopK(+e.target.value)} min={1} max={50} />
</div>
<div className="form-group form-group-sm">
<label>&nbsp;</label>
<button className="btn btn-primary" onClick={handleSearch} disabled={loading || !query.trim()}>
{loading ? "Searching..." : "Search"}
</button>
</div>
</div>
</div>
{error && <StatusMessage type="err" message={error} />}
{results && (
<div className="panel">
<h3>Results ({results.length})</h3>
{results.map((r) => (
<DocumentViewer key={`${r.doc_id}-${r.chunk_index}`} docId={r.doc_id}>
<div className="result-card" style={{ cursor: "pointer" }}>
<div className="result-header">
<div>
<span className="badge">#{r.rank}</span>{" "}
<span className="badge">{r.doc_id}</span>{" "}
<span className="tag">chunk {r.chunk_index}</span>
</div>
<ScoreBar score={r.score} />
</div>
<div className="result-text">{r.text}</div>
</div>
</DocumentViewer>
))}
</div>
)}
</div>
);
}