Istihad's picture
Update static/script.js
1fc4c0f verified
Raw
History Blame Contribute Delete
6.98 kB
/* =========================================================================
Bangla Text Analyzer — frontend logic
========================================================================= */
const $ = (sel) => document.querySelector(sel);
const textInput = $("#text-input");
const dropZone = $("#drop-zone");
const fileInput = $("#file-input");
const fileBtn = $("#file-btn");
const fileName = $("#file-name");
const charCount = $("#char-count");
const taskSelect = $("#task-select");
const modelWrap = $("#model-wrap");
const modelSelect = $("#model-select");
const runBtn = $("#run-btn");
const outputBody = $("#output-body");
const outputDot = $(".output-head__dot");
// Holds a dropped/selected file so we can send it instead of textarea text.
let pendingFile = null;
/* ---------------- character counter ---------------- */
function updateCount() {
const n = textInput.value.length;
charCount.textContent = `${n.toLocaleString("bn-BD")} অক্ষর`;
}
textInput.addEventListener("input", () => {
// If the user types, we abandon any previously attached file.
if (pendingFile && textInput.value.trim()) clearFile();
updateCount();
});
/* ---------------- file handling ---------------- */
function clearFile() {
pendingFile = null;
fileName.textContent = "";
fileInput.value = "";
}
function attachFile(file) {
pendingFile = file;
fileName.textContent = "📎 " + file.name;
// Show a preview of the file's text in the textarea.
const reader = new FileReader();
reader.onload = (e) => {
textInput.value = e.target.result;
updateCount();
};
reader.readAsText(file, "utf-8");
}
fileBtn.addEventListener("click", () => fileInput.click());
fileInput.addEventListener("change", (e) => {
if (e.target.files.length) attachFile(e.target.files[0]);
});
// Drag & drop onto the text area.
["dragenter", "dragover"].forEach((evt) =>
dropZone.addEventListener(evt, (e) => {
e.preventDefault();
dropZone.classList.add("dragover");
})
);
["dragleave", "drop"].forEach((evt) =>
dropZone.addEventListener(evt, (e) => {
e.preventDefault();
if (evt === "dragleave" && dropZone.contains(e.relatedTarget)) return;
dropZone.classList.remove("dragover");
})
);
dropZone.addEventListener("drop", (e) => {
const file = e.dataTransfer.files[0];
if (file) attachFile(file);
});
/* ---------------- running analysis ---------------- */
async function analyze() {
const task = taskSelect.value;
const text = textInput.value.trim();
// Only the hate-speech task offers a model choice; default otherwise.
const model = task === "hate_speech" ? modelSelect.value : "banglabert";
if (!text) {
renderError("দয়া করে কিছু টেক্সট লিখুন বা একটি ফাইল দিন।");
return;
}
setLoading(true);
try {
let response;
if (pendingFile) {
// Send the raw file via multipart form-data.
const fd = new FormData();
fd.append("file", pendingFile);
fd.append("task", task);
fd.append("model", model);
response = await fetch("/analyze", { method: "POST", body: fd });
} else {
// Send typed text as JSON.
response = await fetch("/analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, task, model }),
});
}
const data = await response.json();
if (!response.ok) {
renderError(data.error || "একটি ত্রুটি ঘটেছে।");
} else {
renderResult(data);
}
} catch (err) {
renderError("সার্ভারের সাথে সংযোগ ব্যর্থ হয়েছে: " + err.message);
} finally {
setLoading(false);
}
}
function setLoading(on) {
runBtn.disabled = on;
outputDot.classList.toggle("live", on);
if (on) {
outputBody.className = "output-body";
outputBody.innerHTML =
'<span class="spinner"></span> বিশ্লেষণ চলছে…';
}
}
/* ---------------- rendering ---------------- */
function renderError(msg) {
outputBody.className = "output-body";
outputBody.innerHTML = `<div class="error-box">${escapeHtml(msg)}</div>`;
}
function classifyLabel(label) {
const l = (label || "").toLowerCase();
if (/(not|human|positive|neutral|নয়|মানব|ইতিবাচক)/.test(l)) return "is-good";
if (/(hate|offensive|ai|negative|generated|ঘৃণা|নেতিবাচক|কৃত্রিম)/.test(l))
return "is-bad";
return "";
}
function renderResult(data) {
const r = data.result || {};
const label = r.label ?? "—";
const score = typeof r.score === "number" ? r.score : null;
const labelClass = classifyLabel(label);
let html = `<div class="result-card">`;
html += `<div class="result-card__top">
<span class="result-label ${labelClass}">${escapeHtml(label)}</span>
<span class="result-task">${escapeHtml(data.task_name || "")}</span>
</div>`;
if (score !== null) {
const pct = Math.round(score * 100);
html += `<div class="confidence">
<div class="confidence__bar"><div class="confidence__fill" style="width:0%"></div></div>
<div class="confidence__num">আত্মবিশ্বাস · confidence: ${pct}%</div>
</div>`;
}
// Render any extra details the model returned.
const details = r.details || {};
const keys = Object.keys(details);
if (keys.length) {
html += `<div class="details-grid">`;
for (const k of keys) {
let v = details[k];
if (typeof v === "number") v = (v * 100).toFixed(1) + "%";
html += `<div class="detail-row"><span class="k">${escapeHtml(
k
)}</span><span class="v">${escapeHtml(String(v))}</span></div>`;
}
html += `</div>`;
}
html += `</div>`;
outputBody.className = "output-body";
outputBody.innerHTML = html;
// Animate the confidence bar after it's in the DOM.
if (score !== null) {
requestAnimationFrame(() => {
const fill = outputBody.querySelector(".confidence__fill");
if (fill) fill.style.width = Math.round(score * 100) + "%";
});
}
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
/* ---------------- events ---------------- */
runBtn.addEventListener("click", analyze);
// Show the model picker only when the hate-speech task is selected.
function updateModelVisibility() {
modelWrap.hidden = taskSelect.value !== "hate_speech";
}
taskSelect.addEventListener("change", updateModelVisibility);
updateModelVisibility(); // set correct state on page load
// Ctrl/Cmd + Enter (or plain Enter when not adding a newline) runs analysis.
textInput.addEventListener("keydown", (e) => {
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
analyze();
}
});
updateCount();