esfiles / frontend /src /components /SimilarWords.tsx
Besjon Cifliku
feat: initial project setup
db764ae
import { useState } from "react";
import { api } from "../api";
import { useApiCall } from "../hooks/useApiCall";
import ScoreBar from "./ScoreBar";
import StatusMessage from "./StatusMessage";
interface SimilarWord {
word: string;
score: number;
}
export default function SimilarWords() {
const [word, setWord] = useState("");
const [topK, setTopK] = useState(10);
const { data: results, loading, error, run } = useApiCall<SimilarWord[]>();
async function handleSearch() {
if (!word.trim()) return;
await run(() => api.similarWords({ word: word.trim(), top_k: topK }).then((r) => r.similar));
}
return (
<div>
<div className="panel">
<h2>Similar Words</h2>
<p className="panel-desc">
Find words that appear in similar contexts using transformer embeddings.
Unlike Word2Vec (static, one vector per word), this uses the model's contextual understanding.
</p>
<div className="form-row">
<div className="form-group">
<label>Word</label>
<input
value={word}
onChange={(e) => setWord(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
placeholder="e.g. Epstein, flight, island"
/>
</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 || !word.trim()}>
{loading ? "Searching..." : "Find"}
</button>
</div>
</div>
</div>
{error && <StatusMessage type="err" message={error} />}
{results && results.length > 0 && (
<div className="panel">
<h3>Words similar to "{word}" ({results.length})</h3>
<table className="data-table">
<thead>
<tr><th>Word</th><th>Similarity</th></tr>
</thead>
<tbody>
{results.map((r, i) => (
<tr key={i}>
<td style={{ fontWeight: 600 }}>{r.word}</td>
<td><ScoreBar score={r.score} /></td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}