MBA601Module3 / index.html
srishtisrivastavaphd's picture
Upload index.html
d998c04 verified
Raw History Blame Contribute Delete
7.62 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Zero-Shot Topic Classifier</title>
<style>
:root {
--bg: #f6f7f9; --card: #ffffff; --ink: #1c2230; --muted: #5d6677;
--line: #dde1e8; --accent: #2f5bd3; --accent-ink: #ffffff; --bar: #2f5bd3;
--bar-bg: #e8ecf5; --warn: #9a5b00; --warn-bg: #fff4e0;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #12151c; --card: #1b2029; --ink: #e9ecf2; --muted: #a2aabb;
--line: #2d3442; --accent: #6f93ff; --accent-ink: #0d1220; --bar: #6f93ff;
--bar-bg: #262d3b; --warn: #ffc56b; --warn-bg: #2e2516;
}
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--ink);
font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
main { max-width: 760px; margin: 0 auto; padding: 24px 16px 48px; }
h1 { font-size: 1.5rem; margin: 0 0 4px; }
.sub { color: var(--muted); margin: 0 0 20px; }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 18px; margin-bottom: 16px; }
label { display: block; font-weight: 600; margin: 0 0 6px; }
.hint { font-weight: 400; color: var(--muted); font-size: .9rem; }
textarea, input[type=text] { width: 100%; padding: 10px 12px; border: 1px solid var(--line);
border-radius: 8px; background: var(--bg); color: var(--ink); font: inherit; }
textarea { min-height: 140px; resize: vertical; }
.row { margin-bottom: 14px; }
.check { display: flex; gap: 8px; align-items: center; font-weight: 400; }
button { background: var(--accent); color: var(--accent-ink); border: 0; border-radius: 8px;
padding: 11px 22px; font: 600 1rem system-ui, sans-serif; cursor: pointer; }
button:disabled { opacity: .55; cursor: wait; }
#status { margin-top: 12px; color: var(--muted); font-size: .95rem; min-height: 1.5em; }
progress { width: 100%; height: 10px; margin-top: 8px; display: none; }
.warn { background: var(--warn-bg); color: var(--warn); border-radius: 8px; padding: 10px 12px; font-size: .9rem; margin-top: 12px; display: none; }
.res { display: grid; grid-template-columns: minmax(110px, 34%) 1fr 64px; gap: 8px 12px; align-items: center; }
.res .name { overflow-wrap: anywhere; }
.res .top { font-weight: 700; }
.track { background: var(--bar-bg); border-radius: 6px; height: 14px; overflow: hidden; }
.fill { background: var(--bar); height: 100%; }
.pct { text-align: right; font-variant-numeric: tabular-nums; }
.record { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); font-size: .95rem; }
.record code { background: var(--bar-bg); padding: 2px 6px; border-radius: 4px; }
footer { color: var(--muted); font-size: .85rem; }
</style>
</head>
<body>
<main>
<h1>Zero-Shot Topic Classifier</h1>
<p class="sub">Semantic analysis: what is this text about? Model: facebook/bart-large-mnli, running free in your browser. No account or credits needed.</p>
<div class="card">
<div class="row">
<label for="text">Review text</label>
<textarea id="text" placeholder="Paste one customer review here..."></textarea>
</div>
<div class="row">
<label for="labels">Candidate labels <span class="hint">(4 to 6 topics, separated by commas)</span></label>
<input id="labels" type="text" placeholder="product quality, price, speed of service, staff friendliness, mobile app">
</div>
<div class="row">
<label class="check"><input id="multi" type="checkbox"> Allow multiple true labels <span class="hint">(leave unchecked for the assignment)</span></label>
</div>
<button id="run">Classify</button>
<progress id="prog" max="100" value="0"></progress>
<div id="status">The first run downloads the model (about 400 MB, one time only). Later runs take a few seconds.</div>
<div id="warn" class="warn"></div>
</div>
<div class="card" id="out" hidden>
<div class="res" id="bars"></div>
<div class="record" id="record"></div>
</div>
<footer>Runs on Transformers.js. Your text never leaves your computer. Use Chrome, Edge or Firefox on a laptop or desktop.</footer>
</main>
<script type="module">
import { pipeline } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1";
const $ = (id) => document.getElementById(id);
const runBtn = $("run"), status = $("status"), prog = $("prog"), warn = $("warn");
let classifierPromise = null;
function loadModel() {
if (classifierPromise) return classifierPromise;
const files = {};
prog.style.display = "block";
classifierPromise = pipeline("zero-shot-classification", "Xenova/bart-large-mnli", {
dtype: "q8",
progress_callback: (p) => {
if (p.status === "progress" && p.total) {
files[p.file] = { loaded: p.loaded, total: p.total };
const all = Object.values(files);
const loaded = all.reduce((s, f) => s + f.loaded, 0);
const total = all.reduce((s, f) => s + f.total, 0);
prog.value = Math.round((loaded / total) * 100);
status.textContent = `Downloading model: ${Math.round(loaded / 1e6)} of ${Math.round(total / 1e6)} MB (one time only)...`;
} else if (p.status === "ready") {
status.textContent = "Model loaded. Classifying...";
}
},
}).then((c) => { prog.style.display = "none"; return c; })
.catch((e) => { classifierPromise = null; prog.style.display = "none"; throw e; });
return classifierPromise;
}
function showWarn(msg) { warn.textContent = msg; warn.style.display = msg ? "block" : "none"; }
function esc(s) {
return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
}
runBtn.addEventListener("click", async () => {
const text = $("text").value.trim();
const labels = [...new Set($("labels").value.split(",").map((s) => s.trim()).filter(Boolean))];
showWarn("");
if (!text) { showWarn("Paste a review first."); return; }
if (labels.length < 2) { showWarn("Enter at least 2 labels, separated by commas."); return; }
if (labels.length > 6) showWarn("The assignment asks for 4 to 6 labels. You entered " + labels.length + ".");
runBtn.disabled = true;
try {
const classifier = await loadModel();
status.textContent = "Classifying...";
const t0 = performance.now();
const out = await classifier(text, labels, { multi_label: $("multi").checked });
const secs = ((performance.now() - t0) / 1000).toFixed(1);
$("bars").innerHTML = out.labels.map((lab, i) => {
const pct = (out.scores[i] * 100).toFixed(1);
return `<div class="name ${i === 0 ? "top" : ""}">${esc(lab)}</div>
<div class="track"><div class="fill" style="width:${pct}%"></div></div>
<div class="pct">${out.scores[i].toFixed(3)}</div>`;
}).join("");
$("record").innerHTML = `Record in your sheet: top label <code>${esc(out.labels[0])}</code>, score <code>${out.scores[0].toFixed(3)}</code>` +
($("multi").checked ? " (multi-label mode: scores are independent and do not add to 1)" : " (scores add up to 1)");
$("out").hidden = false;
status.textContent = `Done in ${secs} s.`;
} catch (e) {
console.error(e);
status.textContent = "";
showWarn("Something went wrong: " + (e && e.message ? e.message : e) + ". Refresh the page and try again, or try another browser.");
} finally {
runBtn.disabled = false;
}
});
</script>
</body>
</html>