configurable span threshold + confidence chip
Browse files- index.html +29 -0
- main.js +24 -6
index.html
CHANGED
|
@@ -679,6 +679,14 @@
|
|
| 679 |
.tree-root { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
|
| 680 |
.tree-name { font-weight: 600; font-size: 0.92rem; color: var(--text); }
|
| 681 |
.tree-ms { color: var(--text-subtle); font-size: 0.7rem; font-family: var(--mono); margin-left: auto; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
.tree-body {
|
| 683 |
position: relative; margin: 0.4rem 0 0 0.4rem;
|
| 684 |
padding-left: 1.15rem; border-left: 1px solid var(--border-strong);
|
|
@@ -753,6 +761,21 @@
|
|
| 753 |
/* Actions / CTA --------------------------------------------------- */
|
| 754 |
.actions { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
|
| 755 |
.actions .meta-text { font-family: var(--mono); font-size: 0.72rem; color: var(--text-subtle); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 756 |
.btn {
|
| 757 |
display: inline-flex; align-items: center; gap: 0.55rem;
|
| 758 |
font: inherit; font-weight: 600; font-size: 0.88rem; color: var(--btn-fg);
|
|
@@ -1273,6 +1296,12 @@
|
|
| 1273 |
<textarea id="text" placeholder="Paste research text here…">the Demographic and Health Survey (DHS) by EuStatistics in 2021 is the main dataset</textarea>
|
| 1274 |
</div>
|
| 1275 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1276 |
<div class="actions">
|
| 1277 |
<span class="meta-text num">WebGPU · client-side · no server</span>
|
| 1278 |
<button class="btn" id="run" disabled>
|
|
|
|
| 679 |
.tree-root { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
|
| 680 |
.tree-name { font-weight: 600; font-size: 0.92rem; color: var(--text); }
|
| 681 |
.tree-ms { color: var(--text-subtle); font-size: 0.7rem; font-family: var(--mono); margin-left: auto; }
|
| 682 |
+
.tree-conf {
|
| 683 |
+
font-family: var(--mono); font-size: 0.68rem; font-weight: 600;
|
| 684 |
+
padding: 0.12rem 0.5rem; border-radius: 999px; letter-spacing: 0.02em;
|
| 685 |
+
white-space: nowrap;
|
| 686 |
+
}
|
| 687 |
+
.tree-conf.c-high { color: var(--ok); background: color-mix(in srgb, var(--ok) 14%, transparent); }
|
| 688 |
+
.tree-conf.c-mid { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
|
| 689 |
+
.tree-conf.c-low { color: var(--err); background: color-mix(in srgb, var(--err) 14%, transparent); }
|
| 690 |
.tree-body {
|
| 691 |
position: relative; margin: 0.4rem 0 0 0.4rem;
|
| 692 |
padding-left: 1.15rem; border-left: 1px solid var(--border-strong);
|
|
|
|
| 761 |
/* Actions / CTA --------------------------------------------------- */
|
| 762 |
.actions { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
|
| 763 |
.actions .meta-text { font-family: var(--mono); font-size: 0.72rem; color: var(--text-subtle); }
|
| 764 |
+
.threshold-control {
|
| 765 |
+
display: flex; align-items: center; gap: 0.7rem;
|
| 766 |
+
font-family: var(--mono); font-size: 0.72rem; color: var(--text-muted);
|
| 767 |
+
}
|
| 768 |
+
.threshold-control .thr-label {
|
| 769 |
+
letter-spacing: 0.08em; text-transform: uppercase;
|
| 770 |
+
font-weight: 600; color: var(--text-subtle); white-space: nowrap;
|
| 771 |
+
}
|
| 772 |
+
.threshold-control input[type="range"] {
|
| 773 |
+
flex: 1; accent-color: var(--accent-2); cursor: pointer; height: 18px;
|
| 774 |
+
}
|
| 775 |
+
.threshold-control .thr-val {
|
| 776 |
+
color: var(--accent-2); font-weight: 600; min-width: 3ch; text-align: right;
|
| 777 |
+
font-variant-numeric: tabular-nums;
|
| 778 |
+
}
|
| 779 |
.btn {
|
| 780 |
display: inline-flex; align-items: center; gap: 0.55rem;
|
| 781 |
font: inherit; font-weight: 600; font-size: 0.88rem; color: var(--btn-fg);
|
|
|
|
| 1296 |
<textarea id="text" placeholder="Paste research text here…">the Demographic and Health Survey (DHS) by EuStatistics in 2021 is the main dataset</textarea>
|
| 1297 |
</div>
|
| 1298 |
|
| 1299 |
+
<div class="threshold-control">
|
| 1300 |
+
<span class="thr-label">Span threshold</span>
|
| 1301 |
+
<input type="range" id="span-threshold" min="0" max="1" step="0.05" value="0.6" aria-label="Span detection threshold">
|
| 1302 |
+
<span class="thr-val" id="span-threshold-val">0.60</span>
|
| 1303 |
+
</div>
|
| 1304 |
+
|
| 1305 |
<div class="actions">
|
| 1306 |
<span class="meta-text num">WebGPU · client-side · no server</span>
|
| 1307 |
<button class="btn" id="run" disabled>
|
main.js
CHANGED
|
@@ -10,7 +10,7 @@ const SPAN_LABELS = ["NAMED_DATA", "DESCRIPTIVE_DATA", "VAGUE_DATA"];
|
|
| 10 |
const MAX_WIDTH = 12;
|
| 11 |
const MAX_WIDTH2 = 8;
|
| 12 |
const MAX_COUNT2 = 20;
|
| 13 |
-
|
| 14 |
|
| 15 |
// Local ONNX files (serve the repo root, not just web/, so ../onnx is reachable).
|
| 16 |
const LOCAL = {
|
|
@@ -278,15 +278,24 @@ function leavesHtml(obj, keys) {
|
|
| 278 |
return out.length ? out.join("") : '<div class="tree-none">none</div>';
|
| 279 |
}
|
| 280 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
function mentionTreeHtml(m) {
|
| 282 |
const badge = `<span class="badge badge-${m.label}">${escapeHtml(m.label)}</span>`;
|
| 283 |
const name = `<span class="tree-name">${escapeHtml(m.text)}</span>`;
|
|
|
|
| 284 |
if (m.label === "VAGUE_DATA") {
|
| 285 |
-
return `<div class="tree"><div class="tree-root">${badge}${name}</div><div class="tree-none">no attributes</div></div>`;
|
| 286 |
}
|
| 287 |
return (
|
| 288 |
`<div class="tree">` +
|
| 289 |
-
`<div class="tree-root">${badge}${name}<span class="tree-ms">…</span></div>` +
|
| 290 |
`<div class="tree-body">` +
|
| 291 |
`<div class="tree-slot" data-slot="prov"><div class="tree-stream streaming">attributing…</div></div>` +
|
| 292 |
`<div class="tree-group"><div class="tree-label">usage/impact</div><div class="tree-slot" data-slot="ui"></div></div>` +
|
|
@@ -399,14 +408,14 @@ async function runSpan(session, tokenizer, text) {
|
|
| 399 |
const kept = [];
|
| 400 |
const used = new Set();
|
| 401 |
for (const sp of spans) {
|
| 402 |
-
if (sp.score <
|
| 403 |
let overlaps = false;
|
| 404 |
for (let i = sp.start; i <= sp.end; i++) if (used.has(i)) { overlaps = true; break; }
|
| 405 |
if (overlaps) continue;
|
| 406 |
for (let i = sp.start; i <= sp.end; i++) used.add(i);
|
| 407 |
const label = SPAN_LABELS[sp.cls];
|
| 408 |
const txt = words.slice(sp.start, sp.end + 1).join(" ");
|
| 409 |
-
kept.push({ text: txt, label });
|
| 410 |
}
|
| 411 |
return kept;
|
| 412 |
}
|
|
@@ -512,7 +521,7 @@ async function runSpan2(session, tokenizer, text) {
|
|
| 512 |
if (!mask[w * K + k]) continue;
|
| 513 |
const idx = (((w * K + k) * MC + 0) * M) + mIdx;
|
| 514 |
const sc = scores[idx];
|
| 515 |
-
if (sc >=
|
| 516 |
const start = w;
|
| 517 |
const end = w + k; // inclusive word span [w, w+k]
|
| 518 |
cand.push({ start, end, score: sc });
|
|
@@ -844,6 +853,15 @@ shuffleBtn?.addEventListener("click", () => {
|
|
| 844 |
selectSample(target);
|
| 845 |
});
|
| 846 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 847 |
// Fetch model READMEs (metrics + training details) for the "Models & tasks" panel.
|
| 848 |
async function loadReadme(repo, elId) {
|
| 849 |
const el = document.getElementById(elId);
|
|
|
|
| 10 |
const MAX_WIDTH = 12;
|
| 11 |
const MAX_WIDTH2 = 8;
|
| 12 |
const MAX_COUNT2 = 20;
|
| 13 |
+
let spanThreshold = 0.6;
|
| 14 |
|
| 15 |
// Local ONNX files (serve the repo root, not just web/, so ../onnx is reachable).
|
| 16 |
const LOCAL = {
|
|
|
|
| 278 |
return out.length ? out.join("") : '<div class="tree-none">none</div>';
|
| 279 |
}
|
| 280 |
|
| 281 |
+
function confidenceChip(score) {
|
| 282 |
+
if (typeof score !== "number" || Number.isNaN(score)) return "";
|
| 283 |
+
const p = 1 / (1 + Math.exp(-score));
|
| 284 |
+
const pct = Math.round(p * 100);
|
| 285 |
+
const level = p >= 0.85 ? "c-high" : p >= 0.7 ? "c-mid" : "c-low";
|
| 286 |
+
return `<span class="tree-conf ${level}" title="confidence ${pct}%">${pct}%</span>`;
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
function mentionTreeHtml(m) {
|
| 290 |
const badge = `<span class="badge badge-${m.label}">${escapeHtml(m.label)}</span>`;
|
| 291 |
const name = `<span class="tree-name">${escapeHtml(m.text)}</span>`;
|
| 292 |
+
const conf = confidenceChip(m.score);
|
| 293 |
if (m.label === "VAGUE_DATA") {
|
| 294 |
+
return `<div class="tree"><div class="tree-root">${badge}${name}${conf}</div><div class="tree-none">no attributes</div></div>`;
|
| 295 |
}
|
| 296 |
return (
|
| 297 |
`<div class="tree">` +
|
| 298 |
+
`<div class="tree-root">${badge}${name}${conf}<span class="tree-ms">…</span></div>` +
|
| 299 |
`<div class="tree-body">` +
|
| 300 |
`<div class="tree-slot" data-slot="prov"><div class="tree-stream streaming">attributing…</div></div>` +
|
| 301 |
`<div class="tree-group"><div class="tree-label">usage/impact</div><div class="tree-slot" data-slot="ui"></div></div>` +
|
|
|
|
| 408 |
const kept = [];
|
| 409 |
const used = new Set();
|
| 410 |
for (const sp of spans) {
|
| 411 |
+
if (sp.score < spanThreshold) continue;
|
| 412 |
let overlaps = false;
|
| 413 |
for (let i = sp.start; i <= sp.end; i++) if (used.has(i)) { overlaps = true; break; }
|
| 414 |
if (overlaps) continue;
|
| 415 |
for (let i = sp.start; i <= sp.end; i++) used.add(i);
|
| 416 |
const label = SPAN_LABELS[sp.cls];
|
| 417 |
const txt = words.slice(sp.start, sp.end + 1).join(" ");
|
| 418 |
+
kept.push({ text: txt, label, score: sp.score });
|
| 419 |
}
|
| 420 |
return kept;
|
| 421 |
}
|
|
|
|
| 521 |
if (!mask[w * K + k]) continue;
|
| 522 |
const idx = (((w * K + k) * MC + 0) * M) + mIdx;
|
| 523 |
const sc = scores[idx];
|
| 524 |
+
if (sc >= spanThreshold) {
|
| 525 |
const start = w;
|
| 526 |
const end = w + k; // inclusive word span [w, w+k]
|
| 527 |
cand.push({ start, end, score: sc });
|
|
|
|
| 853 |
selectSample(target);
|
| 854 |
});
|
| 855 |
|
| 856 |
+
const thresholdInput = document.getElementById("span-threshold");
|
| 857 |
+
const thresholdVal = document.getElementById("span-threshold-val");
|
| 858 |
+
if (thresholdInput) {
|
| 859 |
+
thresholdInput.addEventListener("input", () => {
|
| 860 |
+
spanThreshold = Number(thresholdInput.value);
|
| 861 |
+
if (thresholdVal) thresholdVal.textContent = spanThreshold.toFixed(2);
|
| 862 |
+
});
|
| 863 |
+
}
|
| 864 |
+
|
| 865 |
// Fetch model READMEs (metrics + training details) for the "Models & tasks" panel.
|
| 866 |
async function loadReadme(repo, elId) {
|
| 867 |
const el = document.getElementById(elId);
|