smart-mcq-solver / index.html
Code2aum's picture
Single-model build with model dropdown
623c70a verified
Raw
History Blame Contribute Delete
8.33 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Smart MCQ Solver</title>
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.19.2/dist/ort.min.js"></script>
<style>
:root {
color-scheme: light;
--surface: #fcfcfb;
--card: #ffffff;
--border: #e4e3df;
--text-primary: #0b0b0b;
--text-secondary: #52514e;
--accent: #2a78d6;
--track: #edecea;
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--surface: #1a1a19;
--card: #232322;
--border: #3a3a38;
--text-primary: #ffffff;
--text-secondary: #c3c2b7;
--accent: #3987e5;
--track: #313130;
}
}
* { box-sizing: border-box; margin: 0; }
body {
background: var(--surface);
color: var(--text-primary);
font: 15px/1.55 -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
padding: 2rem 1rem 4rem;
}
.wrap { max-width: 780px; margin: 0 auto; }
h1 { font-size: 1.5rem; margin-bottom: .3rem; }
.sub { color: var(--text-secondary); margin-bottom: 1.5rem; font-size: .92rem; }
.card {
background: var(--card); border: 1px solid var(--border);
border-radius: 12px; padding: 1.25rem; margin-bottom: 1.25rem;
}
label { display: block; font-size: .8rem; font-weight: 600; color: var(--text-secondary); margin: .8rem 0 .25rem; }
textarea, input[type=text], select {
width: 100%; border: 1px solid var(--border); border-radius: 8px;
background: var(--surface); color: var(--text-primary);
padding: .55rem .7rem; font: inherit; resize: vertical;
}
textarea:focus, input:focus, select:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.examples { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem; }
.examples button {
border: 1px solid var(--border); background: var(--card); color: var(--text-secondary);
border-radius: 999px; padding: .3rem .8rem; font-size: .8rem; cursor: pointer;
}
.examples button:hover { border-color: var(--accent); color: var(--accent); }
#solve {
margin-top: 1.1rem; background: var(--accent); color: #fff; border: none;
border-radius: 8px; padding: .6rem 1.6rem; font: inherit; font-weight: 600; cursor: pointer;
}
#solve:disabled { opacity: .55; cursor: wait; }
#status { font-size: .85rem; color: var(--text-secondary); margin-left: .8rem; }
.top3 { font-size: 1.05rem; margin-bottom: 1rem; }
.top3 b { letter-spacing: .15em; color: var(--accent); }
.bar-row { display: grid; grid-template-columns: 1.4rem 1fr 3.6rem; align-items: center; gap: .6rem; margin: .45rem 0; }
.bar-row .letter { font-weight: 600; font-size: .85rem; }
.bar-row .val { font-size: .82rem; color: var(--text-secondary); text-align: right; font-variant-numeric: tabular-nums; }
.track { background: var(--track); border-radius: 4px; height: 14px; overflow: hidden; }
.fill { background: var(--accent); height: 100%; border-radius: 0 4px 4px 0; width: 0; transition: width .35s ease; }
.winner .letter, .winner .val { color: var(--accent); }
footer { color: var(--text-secondary); font-size: .8rem; margin-top: 2rem; }
#result { display: none; }
</style>
</head>
<body>
<div class="wrap">
<h1>🧠 Smart MCQ Solver</h1>
<p class="sub">
Final deployment — DLGenAI course project. Solves 5-option (A–E) multiple-choice
science questions and returns the top-3 ranked answers (Kaggle MAP@3 format).
</p>
<div class="card">
<div class="examples" id="examples"></div>
<label for="model">Model</label>
<select id="model">
<option value="from_scratch_v2">from_scratch_v2</option>
<option value="qwen3.5">qwen3.5</option>
<option value="deberta_v3">deberta_v3</option>
</select>
<label for="prompt">Question prompt</label>
<textarea id="prompt" rows="3" placeholder="Enter the multiple-choice question…"></textarea>
<div id="options"></div>
<button id="solve">Solve</button><span id="status">loading model…</span>
</div>
<div class="card" id="result">
<div class="top3">Top-3 prediction: <b id="top3"></b></div>
<div id="bars"></div>
</div>
<footer>Transformer trained with W&amp;B tracking on the competition data, running client-side via ONNX.</footer>
</div>
<script>
const OPTIONS = ["A", "B", "C", "D", "E"];
const MAX_LEN = 128, PAD = 0n, UNK = 1, CLS = 2n, SEP = 3n;
let vocab = null, session = null, examples = [];
const optsDiv = document.getElementById("options");
for (const o of OPTIONS) {
optsDiv.insertAdjacentHTML("beforeend",
`<label for="opt${o}">Option ${o}</label><input type="text" id="opt${o}">`);
}
const barsDiv = document.getElementById("bars");
for (const o of OPTIONS) {
barsDiv.insertAdjacentHTML("beforeend",
`<div class="bar-row" id="row${o}">
<span class="letter">${o}</span>
<div class="track"><div class="fill" id="fill${o}"></div></div>
<span class="val" id="val${o}"></span>
</div>`);
}
function encodeChoice(prompt, option) {
const words = t => String(t).toLowerCase().split(/\s+/).filter(Boolean);
const ids = [CLS];
for (const w of words(prompt)) ids.push(BigInt(vocab[w] ?? UNK));
ids.push(SEP);
for (const w of words(option)) ids.push(BigInt(vocab[w] ?? UNK));
ids.push(SEP);
ids.length = Math.min(ids.length, MAX_LEN);
const mask = new Float32Array(MAX_LEN);
mask.fill(1, 0, ids.length);
while (ids.length < MAX_LEN) ids.push(PAD);
return { ids, mask };
}
async function solve() {
const prompt = document.getElementById("prompt").value.trim();
const opts = OPTIONS.map(o => document.getElementById("opt" + o).value.trim());
if (!prompt || opts.some(o => !o)) { alert("Please fill in the prompt and all five options."); return; }
const allIds = new BigInt64Array(5 * MAX_LEN);
const allMask = new Float32Array(5 * MAX_LEN);
opts.forEach((opt, i) => {
const { ids, mask } = encodeChoice(prompt, opt);
allIds.set(ids, i * MAX_LEN);
allMask.set(mask, i * MAX_LEN);
});
const out = await session.run({
input_ids: new ort.Tensor("int64", allIds, [1, 5, MAX_LEN]),
attention_mask: new ort.Tensor("float32", allMask, [1, 5, MAX_LEN]),
});
const logits = Array.from(out.logits.data);
const m = Math.max(...logits);
const exps = logits.map(v => Math.exp(v - m));
const s = exps.reduce((a, b) => a + b, 0);
const probs = exps.map(v => v / s);
const ranked = probs.map((p, i) => [p, i]).sort((a, b) => b[0] - a[0]);
document.getElementById("top3").textContent = ranked.slice(0, 3).map(r => OPTIONS[r[1]]).join(" ");
const winner = ranked[0][1];
probs.forEach((p, i) => {
const o = OPTIONS[i];
document.getElementById("fill" + o).style.width = (p * 100).toFixed(1) + "%";
document.getElementById("val" + o).textContent = (p * 100).toFixed(1) + "%";
document.getElementById("row" + o).classList.toggle("winner", i === winner);
});
document.getElementById("result").style.display = "block";
}
function loadExample(ex) {
document.getElementById("prompt").value = ex[0];
OPTIONS.forEach((o, i) => document.getElementById("opt" + o).value = ex[i + 1]);
document.getElementById("result").style.display = "none";
}
async function init() {
const status = document.getElementById("status");
const btn = document.getElementById("solve");
btn.disabled = true;
try {
const [vocabRes, exRes] = await Promise.all([fetch("vocab2.json"), fetch("examples.json")]);
vocab = await vocabRes.json();
examples = await exRes.json();
const exDiv = document.getElementById("examples");
examples.forEach((ex, i) => {
const b = document.createElement("button");
b.textContent = "Example " + (i + 1);
b.title = ex[0];
b.onclick = () => loadExample(ex);
exDiv.appendChild(b);
});
session = await ort.InferenceSession.create("mcq_scratch2.onnx");
status.textContent = "model ready ✓";
btn.disabled = false;
loadExample(examples[0]);
} catch (e) {
status.textContent = "failed to load model: " + e.message;
}
}
document.getElementById("solve").addEventListener("click", () =>
solve().catch(e => { document.getElementById("status").textContent = "error: " + e.message; }));
init();
</script>
</body>
</html>