File size: 4,467 Bytes
ad19081
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
const errorEl = document.getElementById("error");
const resultCard = document.getElementById("result-card");
const probabilityRingEl = document.getElementById("probability-ring");
const probabilityEl = document.getElementById("probability");
const classificationConfidenceEl = document.getElementById("classification-confidence");
const classificationDecisionEl = document.getElementById("classification-decision");
const buttonEl = document.getElementById("predict-btn");
const decisionThreshold = 0.58;


// number formatting
function formatNumber(value) {
  return value == null || Number.isNaN(Number(value)) ? "-" : Number(value).toFixed(4);
}


function showError(message) {
  errorEl.textContent = message;
  errorEl.classList.remove("hidden"); // remove hidden CSS class
}
function clearError() { // clear the error
  errorEl.textContent = "";
  errorEl.classList.add("hidden");
}


// function metricRating(value) {
//   const score = Number(value);
//   if (Number.isNaN(score)) return "-";
//   if (score >= 0.9) return "Very strong";
//   if (score >= 0.8) return "Good";
//   if (score >= 0.7) return "Fairly good";
//   if (score >= 0.6) return "Moderate";
//   return "Limited";
// }


function probabilityColor(probability) {
  const clamped = Math.max(0, Math.min(1, Number(probability)));
  const hue = 8 + clamped * 126; // low prob is near red/orange; high prob more green
  return `hsl(${hue} 72% 46%)`;
}

function classificationConfidence(probability) {
  if (probability >= 0.9 || probability <= 0.1) return "Surely";
  if (probability >= 0.75 || probability <= 0.25) return "Very Likely";
  if (probability >= 0.6 || probability <= 0.4) return "Likely";
  return "Uncertain";
}

function renderProbability(probability, showClassification = true) {

  const clamped = Math.max(0, Math.min(1, Number(probability)));
  const angle = `${(clamped * 360).toFixed(2)}deg`;
  const color = probabilityColor(clamped);
  const decision = clamped >= decisionThreshold ? "Same author" : "Different author";
  const decisionClass = clamped >= decisionThreshold ? "is-same" : "is-different";

  probabilityRingEl.style.setProperty("--ring-angle", angle);
  probabilityRingEl.style.setProperty("--ring-color", color);
  probabilityEl.textContent = `${(clamped * 100).toFixed(1)}%`;
  classificationDecisionEl.classList.remove("is-same", "is-different");

  if (showClassification) {
    classificationConfidenceEl.textContent = classificationConfidence(clamped);
    classificationDecisionEl.textContent = decision;
    classificationDecisionEl.classList.add(decisionClass);
  } else {
    classificationConfidenceEl.textContent = "";
    classificationDecisionEl.textContent = "";
  }
}

async function loadMetrics() {
  try {
    const response = await fetch("/metrics");
    const metrics = await response.json();
    document.getElementById("metric-f1").textContent = formatNumber(metrics.f1);
    document.getElementById("metric-youden").textContent = formatNumber(metrics.youden_j);
    document.getElementById("metric-auc").textContent = formatNumber(metrics.auc_roc);
    document.getElementById("metric-f1-rating").textContent = metricRating(metrics.f1);
    document.getElementById("metric-youden-rating").textContent = metricRating(metrics.youden_j);
    document.getElementById("metric-auc-rating").textContent = metricRating(metrics.auc_roc);
  } catch (error) {
    console.error("Failed to load metrics", error);
  }}


async function handlePredict() {
  clearError();

  const text1 = document.getElementById("text1").value.trim();
  const text2 = document.getElementById("text2").value.trim();

  if (!text1 || !text2) {
    showError("Please fill in both text fields.");
    return;
  }

  buttonEl.disabled = true; // disable click
  buttonEl.textContent = "Running...";

  try {
    const response = await fetch("/predict", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text1, text2 }),
    });
    const result = await response.json();
    if (!response.ok || result.error) {
      throw new Error(result.error || "Request failed.");
    }
    renderProbability(result.probability);
  } catch (error) {
    showError(error.message || "Request failed.");
  } finally {
    buttonEl.disabled = false;
    buttonEl.textContent = "Predict";
  }}

buttonEl.addEventListener("click", handlePredict);

renderProbability(0, false);
loadMetrics(); // always show performance metrics