Spaces:
Running
Running
| (() => { | |
| "use strict"; | |
| const MAX_README_BYTES = 2_000_000; | |
| const REPO_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/; | |
| const checks = [ | |
| ["license", "License", ["license", "license_name", "license_link"], [], [], "Confirm the license identifier or link that governs reuse."], | |
| ["language", "Language coverage", ["language", "languages"], ["\\blanguages?\\b"], [], "Document the languages represented and any known coverage limits."], | |
| ["task", "Task and model type", ["pipeline_tag", "library_name", "tags"], ["\\bmodel description\\b", "\\btask\\b"], [], "Clarify the task, model family, and expected input/output shape."], | |
| ["base_model", "Base model or provenance", ["base_model"], ["\\bmodel description\\b", "\\bmodel details\\b", "\\bprovenance\\b"], ["\\bbase model\\b", "\\bfine[- ]?tuned from\\b", "\\bderived from\\b"], "Name the base model or explain the model's provenance."], | |
| ["intended_uses", "Intended uses", [], ["\\bintended uses?\\b", "\\buse cases?\\b", "\\buses?\\b", "\\bhow to use\\b"], ["\\bintended for\\b", "\\bcan be used\\b", "\\buse cases?\\b"], "Describe supported use cases and the users or settings considered."], | |
| ["out_of_scope", "Out-of-scope uses", [], ["\\bout[- ]of[- ]scope\\b", "\\bmisuse\\b", "\\bnot intended\\b", "\\bprohibited uses?\\b"], ["\\bshould not\\b", "\\bnot intended\\b", "\\bout[- ]of[- ]scope\\b"], "Identify unsupported or inappropriate uses, where relevant."], | |
| ["limitations", "Limitations, risks, or biases", [], ["\\blimitations?\\b", "\\brisks?\\b", "\\bbias(?:es)?\\b", "\\bethical considerations?\\b"], ["\\blimitations?\\b", "\\brisks?\\b", "\\bbias(?:es|ed)?\\b"], "Describe known limitations, biases, or risks and how they were observed."], | |
| ["training_data", "Training data", ["datasets", "dataset"], ["\\btraining data\\b", "\\bdatasets?\\b", "\\btraining details\\b"], ["\\btrained on\\b", "\\btraining dataset\\b"], "Name or characterize the training data and include source links when possible."], | |
| ["evaluation", "Evaluation evidence", ["model-index", "eval_results", "metrics"], ["\\bevaluation\\b", "\\bresults?\\b", "\\bbenchmarks?\\b", "\\bmetrics?\\b"], ["\\bbenchmark\\b", "\\baccuracy\\b", "\\bf1\\b", "\\bbleu\\b", "\\brouge\\b"], "Link evaluation datasets, metrics, values, and their source or procedure."], | |
| ["reproducibility", "Training and reproducibility details", ["training_args"], ["\\btraining procedure\\b", "\\btraining details\\b", "\\bhyperparameters?\\b", "\\breproducibility\\b"], ["\\blearning rate\\b", "\\bbatch size\\b", "\\btraining steps?\\b"], "Add the training procedure, key parameters, code, or experiment links."], | |
| ["citation", "Citation or supporting paper", ["citation"], ["\\bcitation\\b", "\\breferences?\\b", "\\bpaper\\b"], [], "Add a citation, paper, technical report, or supporting reference if one exists."] | |
| ]; | |
| const form = document.querySelector("#review-form"); | |
| const input = document.querySelector("#repo-id"); | |
| const notice = document.querySelector("#notice"); | |
| const results = document.querySelector("#results"); | |
| const summary = document.querySelector("#summary"); | |
| const evidencePanel = document.querySelector("#evidence-panel"); | |
| const worksheetPanel = document.querySelector("#worksheet-panel"); | |
| const discussionPanel = document.querySelector("#discussion-panel"); | |
| const downloadButton = document.querySelector("#download-review"); | |
| const modelOptions = document.querySelector("#model-options"); | |
| const pickerStatus = document.querySelector("#picker-status"); | |
| let latestReview = null; | |
| let searchTimer = null; | |
| let searchNonce = 0; | |
| function cleanRepoId(value) { | |
| let id = value.trim(); | |
| if (id.startsWith("https://huggingface.co/")) id = id.slice("https://huggingface.co/".length).split("/").slice(0, 2).join("/"); | |
| if (!REPO_ID.test(id)) throw new Error("Select a model from the live results, or enter its exact owner/model-name ID."); | |
| return id; | |
| } | |
| function numberLabel(value) { | |
| const number = Number(value || 0); | |
| if (number >= 1_000_000) return `${(number / 1_000_000).toFixed(number >= 10_000_000 ? 0 : 1)}M`; | |
| if (number >= 1_000) return `${(number / 1_000).toFixed(number >= 10_000 ? 0 : 1)}K`; | |
| return String(number); | |
| } | |
| function hideModelOptions() { | |
| modelOptions.hidden = true; | |
| input.setAttribute("aria-expanded", "false"); | |
| } | |
| function showModelOptions(models, label) { | |
| modelOptions.replaceChildren(); | |
| pickerStatus.textContent = label; | |
| if (!models.length) { hideModelOptions(); return; } | |
| models.forEach((model) => { | |
| const id = model.id || model.modelId; | |
| if (!id) return; | |
| const option = make("button", "model-option"); | |
| option.type = "button"; | |
| option.setAttribute("role", "option"); | |
| option.setAttribute("aria-label", `Select ${id}`); | |
| const detail = model.pipeline_tag ? `${model.pipeline_tag} · ${numberLabel(model.downloads)} downloads` : `${numberLabel(model.downloads)} downloads`; | |
| const copy = make("span"); | |
| copy.append(make("strong", "", id), make("span", "", detail)); | |
| option.append(copy); | |
| if (model.pipeline_tag) option.append(make("span", "task", model.pipeline_tag)); | |
| option.addEventListener("click", () => { input.value = id; modelOptions.replaceChildren(); hideModelOptions(); pickerStatus.textContent = `Selected: ${id}`; input.focus(); }); | |
| modelOptions.append(option); | |
| }); | |
| if (!modelOptions.childElementCount) { hideModelOptions(); return; } | |
| modelOptions.hidden = false; | |
| input.setAttribute("aria-expanded", "true"); | |
| } | |
| async function searchModels(query = "") { | |
| const nonce = ++searchNonce; | |
| pickerStatus.textContent = query ? "Searching public models…" : "Loading live public models…"; | |
| const params = new URLSearchParams({ limit: "8", sort: "downloads", direction: "-1" }); | |
| if (query) params.set("search", query); | |
| try { | |
| const response = await fetch(`https://huggingface.co/api/models?${params}`); | |
| if (!response.ok) throw new Error("unavailable"); | |
| const models = (await response.json()).filter((model) => !model.private && !model.gated && !model.disabled); | |
| if (nonce !== searchNonce) return; | |
| showModelOptions(models, query ? "Live public matches" : "Popular public models — start typing to narrow the list"); | |
| } catch (_) { | |
| if (nonce !== searchNonce) return; | |
| pickerStatus.textContent = "Live model picker is unavailable right now. You can still enter an exact public model ID."; | |
| hideModelOptions(); | |
| } | |
| } | |
| function meaningful(value) { | |
| if (value === null || value === undefined || value === false) return false; | |
| if (typeof value === "string") return Boolean(value.trim()); | |
| if (Array.isArray(value)) return value.length > 0; | |
| if (typeof value === "object") return Object.keys(value).length > 0; | |
| return true; | |
| } | |
| function compact(value, limit = 320) { | |
| const text = (typeof value === "object" ? JSON.stringify(value) : String(value)).replace(/\s+/g, " ").trim(); | |
| return text.length > limit ? `${text.slice(0, limit - 1)}…` : text; | |
| } | |
| function frontmatter(readme) { | |
| const lines = readme.split("\n"); | |
| if (lines[0]?.trim() !== "---") return {}; | |
| const end = lines.findIndex((line, index) => index > 0 && line.trim() === "---"); | |
| if (end < 0) return {}; | |
| const metadata = {}; | |
| let current = null; | |
| for (const line of lines.slice(1, end)) { | |
| if (!line.trim() || line.trimStart().startsWith("#")) continue; | |
| const list = line.match(/^\s*-\s+(.+?)\s*$/); | |
| if (list && current) { if (Array.isArray(metadata[current])) metadata[current].push(list[1].replace(/^['"]|['"]$/g, "")); continue; } | |
| const key = line.match(/^([A-Za-z0-9_-]+):(?:\s*(.*))?$/); | |
| if (!key) continue; | |
| current = key[1]; | |
| const value = (key[2] || "").trim(); | |
| metadata[current] = value ? value.replace(/^['"]|['"]$/g, "") : []; | |
| } | |
| return metadata; | |
| } | |
| function sections(readme) { | |
| const lines = readme.split("\n"); | |
| const headings = []; | |
| let fenced = false; | |
| lines.forEach((line, index) => { | |
| if (/^\s*(```|~~~)/.test(line)) { fenced = !fenced; return; } | |
| if (fenced) return; | |
| const match = line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/); | |
| if (match) headings.push({ start: index + 1, level: match[1].length, title: match[2].trim() }); | |
| }); | |
| return headings.map((heading, index) => { | |
| let end = lines.length; | |
| for (const next of headings.slice(index + 1)) { if (next.level <= heading.level) { end = next.start - 1; break; } } | |
| return { ...heading, body: lines.slice(heading.start, end).join("\n").trim() }; | |
| }); | |
| } | |
| function sourceLink(repo, revision, line) { | |
| const link = `https://huggingface.co/${repo}/blob/${revision}/README.md`; | |
| return line ? `${link}#L${line}` : link; | |
| } | |
| function analyze(repo, api, readme) { | |
| const revision = api.sha || "main"; | |
| const metadata = { ...frontmatter(readme), ...(api.cardData || {}) }; | |
| if (!metadata.pipeline_tag && api.pipeline_tag) metadata.pipeline_tag = api.pipeline_tag; | |
| if (!metadata.library_name && api.library_name) metadata.library_name = api.library_name; | |
| if (!metadata.tags && api.tags) metadata.tags = api.tags; | |
| const parsedSections = sections(readme); | |
| const lines = readme.split("\n"); | |
| const findings = checks.map(([key, label, keys, headings, keywords, prompt]) => { | |
| const evidence = []; | |
| const foundKeys = keys.filter((item) => meaningful(metadata[item])); | |
| foundKeys.slice(0, 2).forEach((item) => evidence.push({ type: "Model card metadata", text: `${item}: ${compact(metadata[item])}`, url: sourceLink(repo, revision) })); | |
| const matchedSections = parsedSections.filter((section) => headings.some((pattern) => new RegExp(pattern, "i").test(section.title))); | |
| if (matchedSections[0]) { | |
| const item = matchedSections[0]; | |
| evidence.push({ type: "Model card section", text: compact(`${item.title}: ${item.body}`, 420), url: sourceLink(repo, revision, item.start) }); | |
| } | |
| if (!evidence.length && keywords.length) { | |
| const foundLine = lines.findIndex((line) => line.trim() && !line.trim().startsWith("#") && keywords.some((pattern) => new RegExp(pattern, "i").test(line))); | |
| if (foundLine >= 0) evidence.push({ type: "Model card text", text: compact(lines[foundLine], 420), url: sourceLink(repo, revision, foundLine + 1) }); | |
| } | |
| let status = evidence.length ? "documented" : "not_found"; | |
| let text = evidence.length ? "Relevant information was found in the public card." : "Relevant information was not found by this review."; | |
| if (key === "evaluation" && matchedSections.length && !foundKeys.length) { status = "review"; text = "Evaluation text was found, but structured evaluation metadata was not found."; } | |
| if (key === "task" && foundKeys.length === 1 && foundKeys[0] === "tags") { status = "review"; text = "Relevant tags were found; the narrative description may still need review."; } | |
| return { key, label, status, text, evidence, prompt }; | |
| }); | |
| return { repo, revision, sourceUrl: sourceLink(repo, revision), findings, analyzedAt: new Date().toISOString() }; | |
| } | |
| function counts(review) { return review.findings.reduce((all, finding) => ({ ...all, [finding.status]: (all[finding.status] || 0) + 1 }), { documented: 0, review: 0, not_found: 0 }); } | |
| function statusLabel(status) { return status === "not_found" ? "Not found" : status[0].toUpperCase() + status.slice(1); } | |
| function make(tag, className, text) { const element = document.createElement(tag); if (className) element.className = className; if (text !== undefined) element.textContent = text; return element; } | |
| function renderSummary(review) { | |
| const count = counts(review); | |
| summary.replaceChildren(); | |
| const header = make("section", "result-header"); | |
| const heading = make("div"); | |
| heading.append(make("p", "eyebrow", "PUBLIC MODEL CARD REVIEW"), make("h2", "", review.repo)); | |
| const link = make("a", "", "Open reviewed revision ↗"); link.href = review.sourceUrl; link.target = "_blank"; link.rel = "noopener"; heading.append(link); | |
| const strip = make("div", "count-strip"); | |
| [[count.documented, "documented"], [count.review, "review"], [count.not_found, "not found"]].forEach(([number, label]) => { const entry = make("div"); entry.append(make("strong", "", number), make("span", "", label)); strip.append(entry); }); | |
| header.append(heading, strip); | |
| const note = make("p", "scope-note", "A narrow result: “Not found” means the review did not locate relevant information. It is not a compliance grade or a claim of maintainer error."); | |
| const grid = make("section", "finding-grid"); | |
| review.findings.forEach((finding) => { const card = make("article", `finding-card ${finding.status.replace("_", "-")}`); const top = make("div", "finding-topline"); top.append(make("h3", "", finding.label), make("span", "status-pill", statusLabel(finding.status))); card.append(top, make("p", "", finding.text), make("small", "", finding.evidence.length ? `${finding.evidence.length} source item${finding.evidence.length === 1 ? "" : "s"}` : "Maintainer input needed")); grid.append(card); }); | |
| summary.append(header, note, grid); | |
| } | |
| function renderEvidence(review) { | |
| evidencePanel.replaceChildren(); | |
| review.findings.forEach((finding) => { const item = make("article", "evidence-item"); item.append(make("h3", "", `${finding.label} — ${statusLabel(finding.status)}`), make("p", "", finding.text)); if (finding.evidence.length) { finding.evidence.forEach((evidence) => { const link = make("a", "", evidence.type); link.href = evidence.url; link.target = "_blank"; link.rel = "noopener"; const excerpt = make("div", "excerpt", evidence.text); item.append(link, excerpt); }); } else { item.append(make("p", "", `Question for the maintainer: ${finding.prompt}`)); } evidencePanel.append(item); }); | |
| } | |
| function worksheet(review) { | |
| const unresolved = review.findings.filter((finding) => finding.status !== "documented"); | |
| const lines = [`# Suggested model card additions for ${review.repo}`, "", "> Review worksheet—not a drop-in patch. Replace every TODO with maintainer-confirmed information.", ""]; | |
| if (!unresolved.length) return lines.concat(["The selected checks found relevant documentation for every reviewed area.", "A human should still verify accuracy, currency, and applicability."]).join("\n"); | |
| unresolved.forEach((finding) => lines.push(`## ${finding.label}`, "", `<!-- TODO: ${finding.prompt} -->`, "")); | |
| return lines.concat(["## Review notes", "", "- Confirm that every proposed addition is accurate with the maintainer.", "- Link primary datasets, evaluations, papers, and code where available.", "- Remove sections that are not applicable instead of filling them speculatively."]).join("\n"); | |
| } | |
| function discussion(review) { | |
| const unresolved = review.findings.filter((finding) => finding.status !== "documented"); | |
| const labels = unresolved.slice(0, 4).map((finding) => finding.label.toLowerCase()).join(", "); | |
| const observation = unresolved.length ? `While reading the public card, I could not confidently locate documentation for ${labels}${unresolved.length > 4 ? `, and ${unresolved.length - 4} other area(s)` : ""}. These may be in another source or may not apply to this model.` : "The review found relevant documentation for each checked area. I would still appreciate confirmation that the information is current."; | |
| return ["## Possible documentation contribution", "", `Hi—thank you for sharing \`${review.repo}\`.`, "", observation, "", "Would a small documentation contribution be useful? I would be glad to prepare one, but I do not want to infer training, evaluation, licensing, or intended-use details that only the maintainers can confirm.", "", `Card reviewed: ${review.sourceUrl}`, "", "_Drafted for human review. This message has not been posted._"].join("\n"); | |
| } | |
| function fullReview(review, worksheetText, discussionText) { | |
| const count = counts(review); | |
| const lines = [`# One Good Commit review: ${review.repo}`, "", `- Source: ${review.sourceUrl}`, `- Revision: \`${review.revision}\``, `- Analyzed: ${review.analyzedAt}`, `- Documented: ${count.documented}`, `- Review: ${count.review}`, `- Not found: ${count.not_found}`, "", "> “Not found” means this deterministic review did not locate relevant information. It is not a claim of noncompliance, inaccuracy, or maintainer error.", "", "## Evidence", ""]; | |
| review.findings.forEach((finding) => { lines.push(`### ${finding.label} — ${statusLabel(finding.status)}`, "", finding.text, ""); if (finding.evidence.length) finding.evidence.forEach((evidence) => lines.push(`- **${evidence.type}** ([source](${evidence.url}))`, "", ` > ${evidence.text}`, "")); else lines.push(`- Maintainer question: ${finding.prompt}`, ""); }); | |
| return lines.concat(["---", "", worksheetText, "", "---", "", discussionText]).join("\n"); | |
| } | |
| function showError(message) { notice.textContent = message; notice.hidden = false; results.hidden = true; } | |
| function clearNotice() { notice.hidden = true; notice.textContent = ""; } | |
| async function review(value) { | |
| let repo; | |
| try { repo = cleanRepoId(value); } catch (error) { showError(error.message); return; } | |
| clearNotice(); results.hidden = true; | |
| const button = form.querySelector("button"); button.disabled = true; button.textContent = "Reading public card…"; | |
| try { | |
| const encoded = repo.split("/").map(encodeURIComponent).join("/"); | |
| const apiResponse = await fetch(`https://huggingface.co/api/models/${encoded}`); | |
| if ([401, 403, 404].includes(apiResponse.status)) throw new Error("That model ID is not available as a public Hub repository. Check the ID or choose a public, ungated model."); | |
| if (!apiResponse.ok) throw new Error("The Hub API could not be reached. Try again shortly."); | |
| const api = await apiResponse.json(); | |
| if (api.private || api.gated) throw new Error("That model is private or gated. This version reviews public cards only."); | |
| const revision = api.sha || "main"; | |
| const cardResponse = await fetch(`https://huggingface.co/${encoded}/resolve/${encodeURIComponent(revision)}/README.md`); | |
| if (!cardResponse.ok) throw new Error("The repository does not expose a README.md on its selected revision."); | |
| const readme = await cardResponse.text(); | |
| if (readme.length > MAX_README_BYTES) throw new Error("The model card is larger than the 2 MB review limit."); | |
| latestReview = analyze(repo, api, readme); | |
| const worksheetText = worksheet(latestReview); const discussionText = discussion(latestReview); | |
| renderSummary(latestReview); renderEvidence(latestReview); | |
| worksheetPanel.replaceChildren(make("pre", "worksheet", worksheetText)); | |
| discussionPanel.replaceChildren(make("pre", "discussion", discussionText)); | |
| latestReview.download = fullReview(latestReview, worksheetText, discussionText); | |
| results.hidden = false; | |
| } catch (error) { showError(error.message || "An unexpected error occurred. No contribution was created or posted."); } | |
| finally { button.disabled = false; button.textContent = "Review card"; } | |
| } | |
| form.addEventListener("submit", (event) => { event.preventDefault(); review(input.value); }); | |
| input.addEventListener("input", () => { | |
| window.clearTimeout(searchTimer); | |
| const query = input.value.trim(); | |
| searchTimer = window.setTimeout(() => searchModels(query), 250); | |
| }); | |
| input.addEventListener("focus", () => { if (modelOptions.childElementCount) { modelOptions.hidden = false; input.setAttribute("aria-expanded", "true"); } }); | |
| document.addEventListener("click", (event) => { if (!event.target.closest("#review-form") && !event.target.closest("#model-options")) hideModelOptions(); }); | |
| document.querySelectorAll("[role=tab]").forEach((tab) => tab.addEventListener("click", () => { document.querySelectorAll("[role=tab]").forEach((item) => item.setAttribute("aria-selected", String(item === tab))); document.querySelectorAll("[role=tabpanel]").forEach((panel) => { panel.hidden = panel.id !== tab.dataset.tab; }); })); | |
| downloadButton.addEventListener("click", () => { if (!latestReview?.download) return; const blob = new Blob([latestReview.download], { type: "text/markdown;charset=utf-8" }); const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = `${latestReview.repo.replace("/", "--")}-review.md`; link.click(); URL.revokeObjectURL(link.href); }); | |
| searchModels(); | |
| })(); | |