| 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; |
|
|
|
|
| |
| function formatNumber(value) { |
| return value == null || Number.isNaN(Number(value)) ? "-" : Number(value).toFixed(4); |
| } |
|
|
|
|
| function showError(message) { |
| errorEl.textContent = message; |
| errorEl.classList.remove("hidden"); |
| } |
| function clearError() { |
| errorEl.textContent = ""; |
| errorEl.classList.add("hidden"); |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| function probabilityColor(probability) { |
| const clamped = Math.max(0, Math.min(1, Number(probability))); |
| const hue = 8 + clamped * 126; |
| 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; |
| 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(); |
|
|