File size: 3,953 Bytes
db764ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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>
  );
}