esfiles / frontend /src /components /KeywordAnalysis.tsx
Besjon Cifliku
feat: initial project setup
db764ae
import { useState } from "react";
import { api } from "../api";
import type { KeywordAnalysisResponse } from "../types";
import { useApiCall } from "../hooks/useApiCall";
import ScoreBar from "./ScoreBar";
import StatusMessage from "./StatusMessage";
export default function KeywordAnalysis() {
const [keyword, setKeyword] = useState("");
const [topK, setTopK] = useState(5);
const [threshold, setThreshold] = useState(0.4);
const { data: analysis, loading, error, run } = useApiCall<KeywordAnalysisResponse>();
async function handleAnalyze() {
if (!keyword.trim()) return;
await run(() => api.analyzeKeyword({ keyword, top_k: topK, cluster_threshold: threshold }));
}
return (
<div>
<div className="panel">
<h2>Keyword Analysis</h2>
<p className="panel-desc">
Find all occurrences of a keyword, cluster them by contextual meaning,
and discover semantically similar passages for each meaning.
</p>
<div className="form-row">
<div className="form-group">
<label>Keyword</label>
<input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="e.g. pizza"
onKeyDown={(e) => e.key === "Enter" && handleAnalyze()}
/>
</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-md">
<label>Cluster Threshold</label>
<input type="number" value={threshold} onChange={(e) => setThreshold(+e.target.value)} min={0.1} max={1} step={0.05} />
</div>
<div className="form-group form-group-sm">
<label>&nbsp;</label>
<button className="btn btn-primary" onClick={handleAnalyze} disabled={loading || !keyword.trim()}>
{loading ? "Analyzing..." : "Analyze"}
</button>
</div>
</div>
</div>
{error && <StatusMessage type="err" message={error} />}
{analysis && (
<div className="panel">
<h3>
"{analysis.keyword}" &mdash; {analysis.total_occurrences} occurrence(s),{" "}
{analysis.meaning_clusters.length} meaning cluster(s)
</h3>
{analysis.meaning_clusters.map((cluster) => (
<div key={cluster.cluster_id} className="result-card mt-2">
<div className="result-header">
<div>
<strong>Cluster {cluster.cluster_id}</strong>{" "}
<span className="tag">{cluster.size} occurrence(s)</span>
</div>
</div>
<div className="mt-1 mb-2">
<div className="section-label">Contexts:</div>
{cluster.contexts.map((ctx, i) => (
<div key={i} className="result-text" style={{ marginBottom: 4, paddingLeft: 12 }}>
<span className="badge" style={{ marginRight: 6 }}>{ctx.doc_id}</span>
{ctx.text.slice(0, 200)}...
</div>
))}
</div>
<div>
<div className="section-label">Similar passages:</div>
{cluster.similar_passages.map((sp) => (
<div key={sp.rank} className="flex-row" style={{ alignItems: "start", marginBottom: 6 }}>
<ScoreBar score={sp.score} />
<span className="result-text" style={{ flex: 1 }}>
<span className="badge" style={{ marginRight: 4 }}>{sp.doc_id}</span>
{sp.text.slice(0, 150)}...
</span>
</div>
))}
</div>
</div>
))}
</div>
)}
</div>
);
}