CADGenBench / app.py
Michael Rabinovich
app: fix leaderboard auto-refresh via @gr .render(key=...)
f2f35be
Raw
History Blame
4.87 kB
"""CADGenBench Leaderboard Space - Gradio UI assembly.
Read path lives in :mod:`leaderboard`. Submit-tab validation lives in
:mod:`submit`. Both are wired into the Gradio app below.
"""
from __future__ import annotations
import logging
import time
import gradio as gr
from leaderboard import (
HF_DATA_REPO,
HF_SUBMISSIONS_REPO,
load_leaderboard,
)
from submit import handle_submit
# Surface module-level logger.info / logger.warning / logger.exception
# calls from leaderboard.py + submit.py in the Space's runtime logs.
# Otherwise they go nowhere and any refresh / worker pathology is
# silent. Format keeps timestamps + module + level + message.
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
)
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"):
# Gradio 6's gr.Dataframe identity-checks its component id and
# short-circuits the diff when the new value's shape + column
# structure look unchanged, even though the row data differs.
# That swallows updates from every= on the Dataframe AND from
# gr.Timer().tick(outputs=df_view) AND from a manual refresh
# button click. The fix is @gr.render with a `key=` that
# changes on every tick: Gradio tears down and rebuilds the
# component subtree in place, picking up the fresh value.
#
# The fetch happens once per tick in `_refresh_table` (server
# side) and the result rides on gr.State to all subscribers,
# so N concurrent viewers don't cause N HTTPS GETs per tick.
table_state = gr.State(value=load_leaderboard())
tick_state = gr.State(value=0)
@gr.render(inputs=[table_state, tick_state])
def render_leaderboard(df, t: int) -> None:
gr.Dataframe(
value=df,
interactive=False,
wrap=True,
label="Results (sorted by aggregate CAD score)",
key=f"leaderboard-df-{t}",
)
def _refresh_table():
# ms-resolution so rapid clicks always increment the key.
return load_leaderboard(), int(time.time() * 1000)
auto_refresh_timer = gr.Timer(10)
auto_refresh_timer.tick(
fn=_refresh_table, outputs=[table_state, tick_state],
)
refresh_btn = gr.Button("Refresh", size="sm")
refresh_btn.click(
fn=_refresh_table, outputs=[table_state, tick_state],
)
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.
**Consent.** `"agree_to_publish": true` in `meta.json` is your consent
to publish the resulting row on the public leaderboard.
"""
)
zip_in = gr.File(label="Submission ZIP", file_types=[".zip"])
submit_btn = gr.Button("Submit", variant="primary")
submit_out = gr.Markdown()
submit_btn.click(
fn=handle_submit,
inputs=[zip_in],
outputs=submit_out,
)
with gr.Tab("About"):
gr.Markdown(ABOUT_MD)
if __name__ == "__main__":
app.launch(theme=gr.themes.Soft())