esfiles / frontend /src /components /KeywordMatcher.tsx
Besjon Cifliku
feat: initial project setup
db764ae
import { useState } from "react";
import { api, getErrorMessage } from "../api";
import type { MatchResponse } from "../types";
import { useApiCall } from "../hooks/useApiCall";
import ScoreBar from "./ScoreBar";
import StatusMessage from "./StatusMessage";
export default function KeywordMatcher() {
const [keyword, setKeyword] = useState("");
const [meaningsText, setMeaningsText] = useState("");
const { data: results, loading, error, setError, run } = useApiCall<MatchResponse>();
async function handleMatch() {
if (!keyword.trim() || !meaningsText.trim()) return;
const candidates = meaningsText.split("\n").map((s) => s.trim()).filter(Boolean);
if (candidates.length < 2) {
setError("Provide at least 2 candidate meanings (one per line).");
return;
}
await run(() => api.matchKeyword({ keyword, candidate_meanings: candidates }));
}
return (
<div>
<div className="panel">
<h2>Keyword Meaning Matcher</h2>
<p className="panel-desc">
Match each occurrence of a keyword to the most likely intended meaning.
For example: keyword "pizza" with candidates "food" and "school".
</p>
<div className="form-row">
<div className="form-group form-group-lg">
<label>Keyword</label>
<input value={keyword} onChange={(e) => setKeyword(e.target.value)} placeholder="e.g. pizza" />
</div>
</div>
<div className="form-group mb-2">
<label>Candidate Meanings (one per line)</label>
<textarea
value={meaningsText}
onChange={(e) => setMeaningsText(e.target.value)}
placeholder={`Italian food made with dough, tomato sauce, and cheese\nSchool, education, and academic activities`}
rows={4}
/>
</div>
<button className="btn btn-primary" onClick={handleMatch} disabled={loading || !keyword.trim() || !meaningsText.trim()}>
{loading ? "Matching..." : "Match"}
</button>
</div>
{error && <StatusMessage type="err" message={error} />}
{results && (
<div className="panel">
<h3>Matches for "{results.keyword}" ({results.matches.length} occurrences)</h3>
{results.matches.map((m, idx) => (
<div key={idx} className="result-card mt-1">
<div className="result-header">
<div>
<span className="badge">{m.doc_id}</span>{" "}
<span className="tag">chunk {m.chunk_index}</span>
</div>
<span className="tag tag-best">{m.best_match}</span>
</div>
<div className="result-text mb-1">{m.text.slice(0, 250)}...</div>
<div className="flex-row flex-wrap gap-2">
{Object.entries(m.all_scores).map(([meaning, score]) => (
<div key={meaning} style={{ flex: "1 1 200px" }}>
<div
style={{
fontSize: "0.78rem",
color: meaning === m.best_match ? "var(--ok)" : "var(--text-dim)",
fontWeight: meaning === m.best_match ? 700 : 400,
marginBottom: 2,
}}
>
{meaning.slice(0, 60)}
</div>
<ScoreBar score={score} />
</div>
))}
</div>
</div>
))}
</div>
)}
</div>
);
}