Spaces:
Running
Running
File size: 14,578 Bytes
f3e5f3c aec6ff1 f3e5f3c 5605c42 f3e5f3c a14bc39 5605c42 f3e5f3c aec6ff1 f3e5f3c 5605c42 f3e5f3c 1dad8a8 f3e5f3c a14bc39 f3e5f3c 5605c42 f3e5f3c 5605c42 f3e5f3c 2a8957e f3e5f3c 5605c42 aec6ff1 f3e5f3c a14bc39 f3e5f3c aec6ff1 f3e5f3c aec6ff1 f3e5f3c a6d466b aec6ff1 a6d466b aec6ff1 a6d466b aec6ff1 a6d466b f3e5f3c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dataset tagger</title>
<link href="https://fonts.googleapis.com/css2?family=Schibsted+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
<style>
:root{--bg:#F7F8FA;--ink:#1D2330;--muted:#6B7280;--rule:#E1E4EA;--bar:#3450C4;--bar-soft:#E3E7F6;--owner:#8A5F0E;--owner-soft:#F5EBD6;--panel:#EEF1F5}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.55 "Schibsted Grotesk",system-ui,sans-serif}
main{max-width:980px;margin:0 auto;padding:52px 24px 72px}
h1{font-weight:700;font-size:36px;line-height:1.1;letter-spacing:-.015em;margin:0 0 8px}
.lede{color:var(--muted);max-width:62ch;margin:0 0 26px}
form{display:flex;gap:8px;max-width:680px;background:#fff;border:1px solid #C9CED8;border-radius:10px;padding:6px}
input{flex:1;border:0;background:transparent;font:500 18px "Schibsted Grotesk",sans-serif;padding:8px 10px;color:var(--ink);min-width:0}
input:focus{outline:none}
form:focus-within{border-color:var(--bar);box-shadow:0 0 0 3px var(--bar-soft)}
button{font:600 15px "Schibsted Grotesk",sans-serif;border:0;border-radius:7px;background:var(--ink);color:#fff;cursor:pointer;padding:0 18px}
button:focus-visible,a:focus-visible{outline:2px solid var(--bar);outline-offset:3px}
#go,button.secondary{border:1px solid var(--ink)}button.secondary{background:#fff;color:var(--ink)}
.examples{margin:10px 0 42px;color:var(--muted);font-size:14px}
.examples button{font:14px "Schibsted Grotesk",sans-serif;background:none;color:var(--ink);text-decoration:underline;text-decoration-color:#C9CED8;text-underline-offset:3px;margin-right:14px;padding:0;border-radius:0}
.grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:44px;align-items:start}
h2{font-size:14px;font-weight:600;margin:0 0 12px;color:var(--muted)}
.dsname{font-size:21px;font-weight:600;margin:0 0 2px;word-break:break-all}
.meta{font-size:14px;margin:0 0 24px}
.meta a,.about a{color:var(--bar);text-underline-offset:3px}
.row{display:grid;grid-template-columns:minmax(0,1fr) 130px 52px;align-items:center;gap:14px;padding:6px 0}
.row .name{color:var(--muted)}
.row.set .name{color:var(--ink);font-weight:600}
.row .track{height:6px;background:var(--bar-soft);border-radius:3px;position:relative;overflow:hidden}
.row .bar{position:absolute;inset:0 auto 0 0;background:var(--bar);border-radius:3px;opacity:.35}
.row.set .bar{opacity:1}
.row .p{text-align:right;font-variant-numeric:tabular-nums;color:var(--muted);font-size:14px}
.row.set .p{color:var(--ink);font-weight:600}
#tags.fb .row{grid-template-columns:minmax(0,1fr) 130px 52px 58px}
.fbt{display:flex;gap:4px;justify-content:flex-end}
.fbt button{font:14px/1 "Schibsted Grotesk",sans-serif;width:26px;height:26px;padding:0;background:none;color:var(--muted);border:1px solid var(--rule);border-radius:6px}
.fbt button[aria-pressed=true].yes{color:var(--bar);border-color:var(--bar);background:var(--bar-soft)}
.fbt button[aria-pressed=true].no{color:#B04A2F;border-color:#B04A2F;background:#F8E6E1}
.fbbar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin:12px 0 0;font-size:13px;color:var(--muted)}.fbbar p{margin:0}.fbbar[hidden]{display:none}
.fbbar button{font-size:14px;padding:6px 14px}
.tag-note{grid-column:1 / -1;font-size:13px;margin:-2px 0 4px;color:var(--muted)}
.owner-mark{display:inline-block;background:var(--owner-soft);color:var(--owner);font-size:12px;font-weight:500;border-radius:999px;padding:1px 8px;margin-left:8px;vertical-align:1px}
.owner{margin-top:20px;font-size:14px;color:var(--muted)}
.owner small{font-size:12px;color:var(--muted)}
.owner span{color:var(--owner);font-weight:500}
.reads{font:13px/1.7 "JetBrains Mono",ui-monospace,monospace;white-space:pre-wrap;word-break:break-word;color:#2A3140;background:var(--panel);border-radius:10px;padding:16px 18px;max-height:420px;overflow:auto;margin:0}
.caption{color:var(--muted);font-size:13px;margin:8px 0 0}
.viewer{margin-top:48px}
.viewer iframe{width:100%;height:520px;border:1px solid var(--rule);border-radius:10px;background:#fff}
.about{margin-top:52px;max-width:64ch;font-size:15px;color:#3A4150;border-top:1px solid var(--rule);padding-top:20px}.about p{margin:0 0 10px}.todo{color:#B04A2F;font-style:italic}
.timing{margin-top:18px;color:var(--muted);font-size:13px}
.status{margin:-28px 0 42px;font-size:15px;max-width:680px}.status:empty{display:none}.status.err{color:#B04A2F}.status.load{color:var(--muted)}button:disabled{opacity:.6;cursor:default}
@media (max-width:760px){.grid{grid-template-columns:1fr;gap:34px}.row{grid-template-columns:minmax(0,1fr) 80px 48px}#tags.fb .row{grid-template-columns:minmax(0,1fr) 56px 44px 58px;gap:10px}}
@media (max-width:560px){#tags.fb .row{grid-template-columns:minmax(0,1fr) 36px 42px 58px}form{flex-wrap:wrap}input{flex:1 1 100%}form button{flex:1 1 100%;padding:11px 18px}}
</style>
</head>
<body>
<main>
<h1>What is this dataset for?</h1>
<p class="lede">Paste a Hugging Face dataset id. A small fine-tuned model reads the column names and the first row, then suggests task tags.</p>
<form id="f"><input id="q" aria-label="Dataset id" placeholder="owner/dataset" autocomplete="off"><button type="button" id="random" class="secondary">Random dataset</button><button id="go">Suggest tags</button></form>
<div class="examples">Examples: <span id="ex"></span></div>
<p class="status" id="status" role="status" aria-live="polite"></p>
<section id="out" hidden>
<div class="grid">
<div>
<p class="dsname" id="name"></p>
<p class="meta"><a id="card" target="_blank" rel="noopener">Open the dataset card</a></p>
<h2>Suggested tags</h2>
<div id="tags"></div>
<div class="fbbar" id="fbbar" hidden><button type="button" class="secondary" id="fbsend" hidden>Send feedback</button><p id="fbnote">Feedback is stored with a pseudonymous id to evaluate and improve the model.</p></div>
<p class="owner">Owner's tags: <span id="owner"></span><br><small>Set by whoever uploaded the dataset, so often incomplete.</small></p>
<p class="timing" id="timing"></p>
</div>
<div>
<h2>What the model reads</h2>
<pre class="reads" id="reads"></pre>
<p class="caption">Column names and types, then the first row, cut to fit the model's input.</p>
</div>
</div>
<div class="viewer">
<h2>First rows</h2>
<iframe id="viewer" title="Dataset viewer" loading="lazy"></iframe>
</div>
</section>
<section class="about">
<h2>About the model</h2>
<p>TypeSafe AI's <a href="https://docs.typesafe.ai/introduction" target="_blank" rel="noopener">Jev</a> has got people excited about "System One" models: models that don't generate text, but read a state and return typed answers with probabilities that software can use directly. This page does the same thing for one narrow job. The state is a dataset's columns and first row. The answer is a set of task tags, each with a probability. The model is small enough to fine-tune yourself on Jobs and to run on a free CPU.</p>
<p>The tags come from <a href="https://huggingface.co/davanstrien/hub-task-tagger-gliner2.5-base" target="_blank" rel="noopener">GLiNER2.5-base</a>, a 194M-parameter model from <a href="https://huggingface.co/fastino/gliner2.5-base-v1" target="_blank" rel="noopener">Fastino</a> that reads a text and a list of labels together. It was fine-tuned on 16,000 Hub datasets and their owners' tags, in 17 minutes on one GPU with <a href="https://huggingface.co/datasets/uv-scripts/classification/blob/main/train-gliner2.py" target="_blank" rel="noopener">this training script</a>.</p>
<p>On 3,000 newer datasets from owners it never saw, its first suggestion matches an owner's tag 69% of the time. That is a noisy target: owners often leave out tags that fit, and in a hand-checked sample about one dataset in ten was missing one. A suggestion that isn't in the owner's tags is not always wrong.</p>
</section>
</main>
<script>
const EXAMPLES = ["hubxrt/LPMusicCapsMTT_a2t", "dougdotcon/douvras-failure-atlas", "mim-chess-vlas/train_800_dense__mask__overlay_a75__sim__agentview_camera__static"];
const $ = id => document.getElementById(id);
function pct(p){ return p < 0.001 ? "<0.1%" : (p*100).toFixed(p < 0.1 ? 1 : 0) + "%"; }
function status(text, kind){ $("status").textContent = text; $("status").className = "status " + (kind || ""); }
function show(ex){
$("out").hidden = false; $("q").value = ex.dataset_id;
$("name").textContent = ex.dataset_id;
$("card").href = "https://huggingface.co/datasets/" + ex.dataset_id;
const declared = new Set(ex.declared), inSet = new Set(ex.set);
$("tags").innerHTML = ex.top.map(t => {
const set = inSet.has(t.tag), own = declared.has(t.tag);
const mark = own ? `<span class="owner-mark">owner's tag</span>` : "";
const note = set && !own ? `<p class="tag-note">Not in the owner's tags.</p>` : "";
const toggles = ex.feedback ? `<span class="fbt"><button type="button" class="yes" data-tag="${t.tag}" aria-pressed="false" aria-label="${t.tag} fits" title="Fits">✓</button><button type="button" class="no" data-tag="${t.tag}" aria-pressed="false" aria-label="${t.tag} does not fit" title="Does not fit">✗</button></span>` : "";
return `<div class="row ${set ? "set" : ""}"><span class="name">${t.tag}${mark}</span><span class="track"><span class="bar" style="width:${Math.max(t.p*100,0.8)}%"></span></span><span class="p">${pct(t.p)}</span>${toggles}${note}</div>`;
}).join("");
$("tags").classList.toggle("fb", !!ex.feedback);
current = ex; votes = {}; sent = false;
$("fbbar").hidden = !ex.feedback; $("fbsend").hidden = true; $("fbsend").disabled = false;
$("fbnote").textContent = "Feedback is stored with a pseudonymous id to evaluate and improve the model.";
$("owner").textContent = ex.declared.length ? ex.declared.join(", ") : "none";
$("reads").textContent = ex.state;
const viewer = `https://huggingface.co/datasets/${ex.dataset_id}/embed/viewer`;
if ($("viewer").src !== viewer) $("viewer").src = viewer;
$("timing").textContent = ex.timing.cached ? "Served from cache."
: `Fetched the preview in ${ex.timing.fetch_ms} ms. The model took ${ex.timing.model_ms} ms on a free CPU.`;
}
let pending = null;
// Feedback on the shown result: votes[tag] = "yes" | "no"; one submission per shown result.
let current = null, votes = {}, sent = false;
$("tags").onclick = e => {
const b = e.target.closest(".fbt button");
if (!b || sent) return;
const tag = b.dataset.tag, v = b.classList.contains("yes") ? "yes" : "no";
votes[tag] = votes[tag] === v ? undefined : v;
for (const x of $("tags").querySelectorAll(`.fbt button[data-tag="${tag}"]`))
x.setAttribute("aria-pressed", String(votes[tag] === (x.classList.contains("yes") ? "yes" : "no")));
$("fbsend").hidden = !Object.values(votes).some(Boolean);
};
$("fbsend").onclick = async () => {
if (sent || !current) return;
const tags = Object.keys(votes);
const body = {
dataset_id: current.dataset_id, sha: current.sha,
model_repo: current.model.repo, model_revision: current.model.revision, T: current.model.T, tau: current.model.tau,
suggested: current.top, set: current.set,
accepted: tags.filter(t => votes[t] === "yes"), rejected: tags.filter(t => votes[t] === "no"),
owner_tags: current.declared, ts: new Date().toISOString(),
};
$("fbsend").disabled = true;
try {
const r = await fetch("api/feedback", {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(body)});
let reply = null;
try { reply = await r.json(); } catch (_) {}
if (!r.ok) {
$("fbnote").textContent = reply && reply.message ? reply.message : `Could not send the feedback (status ${r.status}).`;
$("fbsend").disabled = r.status === 400; // a retry cannot fix invalid feedback
return;
}
sent = true;
$("fbsend").hidden = true;
for (const x of $("tags").querySelectorAll(".fbt button")) x.disabled = true;
$("fbnote").textContent = "Thanks. Your feedback was saved.";
} catch (e) {
$("fbnote").textContent = "Could not reach the server. Try again.";
$("fbsend").disabled = false;
}
};
function busy(on){ $("go").disabled = on; $("random").disabled = on; }
async function run(id){
id = id.trim();
if (!id) { $("out").hidden = true; status("Paste a dataset id first, for example " + EXAMPLES[0] + ".", "err"); return; }
if (pending) pending.abort();
pending = new AbortController();
busy(true); $("out").hidden = true;
status("Reading the dataset…", "load");
try {
const r = await fetch("api/predict", {method: "POST", headers: {"Content-Type": "application/json"},
body: JSON.stringify({dataset_id: id}), signal: pending.signal});
let body = null;
try { body = await r.json(); } catch (_) {}
if (!r.ok) {
status(body && body.message ? body.message
: `Something went wrong on the server (status ${r.status}). Try again in a minute.`, "err");
return;
}
status("");
show(body);
} catch (e) {
if (e.name === "AbortError") return;
status("Could not reach the server. Check your connection and try again.", "err");
} finally {
busy(false);
}
}
$("ex").innerHTML = EXAMPLES.map((id,i) => `<button type="button" data-i="${i}">${id.length > 40 ? id.slice(0,38) + "…" : id}</button>`).join("");
$("ex").onclick = e => { const i = e.target.dataset.i; if (i !== undefined) { $("q").value = EXAMPLES[i]; run(EXAMPLES[i]); } };
$("random").onclick = async () => {
if (pending) pending.abort();
busy(true); $("out").hidden = true;
status("Picking a random dataset…", "load");
try {
const r = await fetch("api/random");
let body = null;
try { body = await r.json(); } catch (_) {}
if (!r.ok) {
status(body && body.message ? body.message
: `Could not pick a random dataset (status ${r.status}). Try one of the examples.`, "err");
return;
}
$("q").value = body.dataset_id;
await run(body.dataset_id); // run() keeps both buttons disabled until the prediction arrives
} catch (e) {
status("Could not reach the server. Check your connection and try again.", "err");
} finally {
busy(false);
}
};
$("f").onsubmit = e => { e.preventDefault(); run($("q").value); };
</script>
</body>
</html> |