| 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> </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> |
| ); |
| } |
|
|