| """CADGenBench Leaderboard Space. |
| |
| Step 3 prototype: a hand-crafted ``results.jsonl`` drives the leaderboard |
| table, and the Submit tab is a UI-only stub. The read path (Step 5) will |
| swap the JSONL for ``datasets.load_dataset(HF_SUBMISSIONS_REPO, 'results')`` |
| and the write path (Step 6) will run ``cadgenbench evaluate`` and push a |
| result row back to the submissions dataset via ``HfApi``. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import json |
| import os |
| from pathlib import Path |
|
|
| import gradio as gr |
| import pandas as pd |
| from huggingface_hub import hf_hub_download |
|
|
| HF_ORG = os.getenv("HF_ORG", "michaelr27") |
| HF_SUBMISSIONS_REPO = os.getenv( |
| "HF_SUBMISSIONS_REPO", f"{HF_ORG}/cadgenbench-submissions" |
| ) |
| HF_DATA_REPO = os.getenv("HF_DATA_REPO", f"{HF_ORG}/cadgenbench-data") |
|
|
| LOCAL_RESULTS_PATH = Path(__file__).parent / "results.jsonl" |
|
|
| LEADERBOARD_COLS = [ |
| "submission_name", |
| "submitter_name", |
| "aggregate_score", |
| "validity_rate", |
| "submitted_at", |
| "cadgenbench_version", |
| ] |
|
|
|
|
| def _load_rows_from_hub() -> list[dict] | None: |
| """Pull results.jsonl from the submissions dataset. |
| |
| Returns None on any failure so callers can fall back to the local file. |
| """ |
| try: |
| path = hf_hub_download( |
| repo_id=HF_SUBMISSIONS_REPO, |
| filename="results.jsonl", |
| repo_type="dataset", |
| force_download=True, |
| ) |
| return [ |
| json.loads(line) |
| for line in Path(path).read_text().splitlines() |
| if line.strip() |
| ] |
| except Exception as e: |
| print(f"[load_leaderboard] Hub fetch failed ({type(e).__name__}: {e})") |
| return None |
|
|
|
|
| def _load_rows_from_local() -> list[dict]: |
| if not LOCAL_RESULTS_PATH.exists(): |
| return [] |
| return [ |
| json.loads(line) |
| for line in LOCAL_RESULTS_PATH.read_text().splitlines() |
| if line.strip() |
| ] |
|
|
|
|
| def _fmt_pct(x: float | None) -> str: |
| """Render a 0-1 fraction as 'NN%' (or 'NN.N%' for non-whole values).""" |
| if x is None: |
| return "" |
| pct = float(x) * 100 |
| return f"{pct:.0f}%" if pct == int(pct) else f"{pct:.1f}%" |
|
|
|
|
| def load_leaderboard() -> pd.DataFrame: |
| rows = _load_rows_from_hub() |
| if rows is None: |
| print("[load_leaderboard] falling back to local results.jsonl") |
| rows = _load_rows_from_local() |
| if not rows: |
| return pd.DataFrame(columns=LEADERBOARD_COLS) |
| df = pd.DataFrame(rows) |
| cols = [c for c in LEADERBOARD_COLS if c in df.columns] |
| df = ( |
| df[cols] |
| .sort_values("aggregate_score", ascending=False, na_position="last") |
| .reset_index(drop=True) |
| ) |
| if "validity_rate" in df.columns: |
| df["validity_rate"] = df["validity_rate"].map(_fmt_pct) |
| return df |
|
|
|
|
| def handle_submit( |
| zip_file, |
| submission_name: str, |
| submitter: str, |
| agent_url: str, |
| notes: str, |
| agree: bool, |
| ) -> str: |
| if zip_file is None: |
| return "**Error:** please attach a submission zip." |
| if not submission_name.strip(): |
| return "**Error:** please fill in the Submission name." |
| if not submitter.strip(): |
| return "**Error:** please fill in your Submitter name." |
| if not agree: |
| return "**Error:** you must agree to publish before submitting." |
|
|
| name = Path(zip_file.name).name |
| return ( |
| f"Received `{name}` - submission `{submission_name}` by `{submitter}`.\n\n" |
| f"_Evaluation is not wired yet (Step 6 of the build plan). Once it " |
| f"is, this submission will run the CPU eval inline and append a row " |
| f"to `{HF_SUBMISSIONS_REPO}`._" |
| ) |
|
|
|
|
| ABOUT_MD = f"""## About |
| |
| **CADGenBench** evaluates AI-driven CAD generation: how well a model can |
| turn a description of a mechanical part into a valid, geometrically |
| correct 3D model. |
| |
| - **Reference baseline**: an iterative AI agent that writes build123d Python. |
| - **Submission flow**: upload a zip of per-fixture STEP files; the Space |
| runs the eval and appends a row to the submissions dataset. |
| - **Datasets**: fixture inputs in |
| [`{HF_DATA_REPO}`](https://huggingface.co/datasets/{HF_DATA_REPO}); |
| submissions and computed results in |
| [`{HF_SUBMISSIONS_REPO}`](https://huggingface.co/datasets/{HF_SUBMISSIONS_REPO}). |
| - **Code**: [`huggingface/cadgenbench`](https://github.com/huggingface/cadgenbench). |
| """ |
|
|
| with gr.Blocks(title="CADGenBench Leaderboard") as app: |
| gr.Markdown( |
| "# CADGenBench Leaderboard\n" |
| "_Benchmarking AI-driven CAD generation._" |
| ) |
|
|
| with gr.Tab("Leaderboard"): |
| df_view = gr.Dataframe( |
| value=load_leaderboard(), |
| interactive=False, |
| wrap=True, |
| label="Results (sorted by aggregate CAD score)", |
| ) |
| refresh_btn = gr.Button("Refresh", size="sm") |
| refresh_btn.click(fn=load_leaderboard, outputs=df_view) |
|
|
| with gr.Tab("Submit"): |
| gr.Markdown( |
| f""" |
| **Submission format.** A single zip with: |
| |
| - one folder per fixture in `{HF_DATA_REPO}`, each containing `output.step`; |
| - a top-level `meta.json`: |
| |
| ```json |
| {{ |
| "submitter_name": "your name or team", |
| "submission_name": "MyAgent v2.3 (or whatever describes your system)", |
| "agent_url": "https://github.com/... (optional)", |
| "notes": "free text, optional, max 500 chars, single line, plain text", |
| "agree_to_publish": true |
| }} |
| ``` |
| |
| **Submission name.** Free text describing the system being benchmarked, |
| however you choose to describe it. The benchmark is system-agnostic - your |
| submission may use no LLM, one, or many. If you want to disclose your |
| stack, put it here or in `notes`. |
| |
| **Notes field.** Plain text only (no markdown / HTML). Capped at 500 chars |
| and stripped to a single line. Shown in the per-submission detail view, |
| not in the main leaderboard table. |
| |
| The Space runs the CPU eval inline and appends a row to |
| `{HF_SUBMISSIONS_REPO}`. You can fill the fields below to override |
| `meta.json` for a quick test. |
| """ |
| ) |
| zip_in = gr.File(label="Submission ZIP", file_types=[".zip"]) |
| with gr.Row(): |
| submission_name_in = gr.Textbox( |
| label="Submission name", |
| placeholder='e.g. "MyAgent v2.3" or "build123d baseline (Claude Opus 4.7)"', |
| ) |
| submitter_in = gr.Textbox(label="Submitter name") |
| with gr.Row(): |
| agent_url_in = gr.Textbox( |
| label="Agent / paper URL (optional)", |
| placeholder="https://github.com/...", |
| ) |
| notes_in = gr.Textbox(label="Notes (optional)") |
| agree_in = gr.Checkbox( |
| label="I agree to publish this result on the public leaderboard." |
| ) |
| submit_btn = gr.Button("Submit", variant="primary") |
| submit_out = gr.Markdown() |
| submit_btn.click( |
| fn=handle_submit, |
| inputs=[ |
| zip_in, |
| submission_name_in, |
| submitter_in, |
| agent_url_in, |
| notes_in, |
| agree_in, |
| ], |
| outputs=submit_out, |
| ) |
|
|
| with gr.Tab("About"): |
| gr.Markdown(ABOUT_MD) |
|
|
|
|
| if __name__ == "__main__": |
| app.launch(theme=gr.themes.Soft()) |
|
|