File size: 3,560 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
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>
  );
}