Spaces:
Running
Running
| <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&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> | |