app: fix leaderboard auto-refresh via @gr .render(key=...)
Browse filesIn Gradio 6.14 a `gr.Dataframe` identity-checks its component id and
short-circuits the diff when the new value's shape + column structure
look unchanged, even when the row data differs. That swallowed
updates from `every=` on the Dataframe AND from
`gr.Timer().tick(outputs=df_view)` AND from a manual Refresh button
click: the server-side fetch ran every tick (`Loaded 10 rows from
Hub` in logs) but the browser never saw the new rows. Only a full
page reload picked them up.
Documented fix (post Gradio's March 2026 Dataframe overhaul): wrap
the component in `@gr.render` and give the inner Dataframe a `key=`
that changes on every tick. Gradio then tears down and rebuilds the
component subtree in place, picking up the fresh value instead of
trying to diff in place.
Shape:
- tick_state: gr.State holding a ms-timestamp that bumps on every
refresh; included in the Dataframe `key=`.
- table_state: gr.State holding the pandas DataFrame.
- `_refresh_table()` runs once per tick, returns
(new_df, new_tick); writes both via outputs=[table_state,
tick_state]. One server-side fetch shared across all subscribers
per tick instead of N viewers x N fetches.
- `@gr.render(inputs=[table_state, tick_state])` rebuilds the
Dataframe with `key=f"leaderboard-df-{t}"` each tick.
- gr.Timer(10).tick wires _refresh_table; Refresh button click
wires the same function.
Verified locally against the live submissions dataset: 10 rows render
correctly (including the latest `Auto-refresh test BETA` row);
server-side Timer ticks every 10s (`Loaded 10 rows from Hub` lines
in the local log).
Diagnosis credit: Leandro reviewed the symptoms and pointed at the
Gradio 6 identity-diff failure mode + the @gr .render(key=) workaround;
this is his pattern.
|
@@ -6,6 +6,7 @@ Read path lives in :mod:`leaderboard`. Submit-tab validation lives in
|
|
| 6 |
from __future__ import annotations
|
| 7 |
|
| 8 |
import logging
|
|
|
|
| 9 |
|
| 10 |
import gradio as gr
|
| 11 |
|
|
@@ -49,20 +50,43 @@ with gr.Blocks(title="CADGenBench Leaderboard") as app:
|
|
| 49 |
)
|
| 50 |
|
| 51 |
with gr.Tab("Leaderboard"):
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
#
|
| 59 |
-
#
|
| 60 |
-
#
|
| 61 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
auto_refresh_timer = gr.Timer(10)
|
| 63 |
-
auto_refresh_timer.tick(
|
|
|
|
|
|
|
| 64 |
refresh_btn = gr.Button("Refresh", size="sm")
|
| 65 |
-
refresh_btn.click(
|
|
|
|
|
|
|
| 66 |
|
| 67 |
with gr.Tab("Submit"):
|
| 68 |
gr.Markdown(
|
|
|
|
| 6 |
from __future__ import annotations
|
| 7 |
|
| 8 |
import logging
|
| 9 |
+
import time
|
| 10 |
|
| 11 |
import gradio as gr
|
| 12 |
|
|
|
|
| 50 |
)
|
| 51 |
|
| 52 |
with gr.Tab("Leaderboard"):
|
| 53 |
+
# Gradio 6's gr.Dataframe identity-checks its component id and
|
| 54 |
+
# short-circuits the diff when the new value's shape + column
|
| 55 |
+
# structure look unchanged, even though the row data differs.
|
| 56 |
+
# That swallows updates from every= on the Dataframe AND from
|
| 57 |
+
# gr.Timer().tick(outputs=df_view) AND from a manual refresh
|
| 58 |
+
# button click. The fix is @gr.render with a `key=` that
|
| 59 |
+
# changes on every tick: Gradio tears down and rebuilds the
|
| 60 |
+
# component subtree in place, picking up the fresh value.
|
| 61 |
+
#
|
| 62 |
+
# The fetch happens once per tick in `_refresh_table` (server
|
| 63 |
+
# side) and the result rides on gr.State to all subscribers,
|
| 64 |
+
# so N concurrent viewers don't cause N HTTPS GETs per tick.
|
| 65 |
+
table_state = gr.State(value=load_leaderboard())
|
| 66 |
+
tick_state = gr.State(value=0)
|
| 67 |
+
|
| 68 |
+
@gr.render(inputs=[table_state, tick_state])
|
| 69 |
+
def render_leaderboard(df, t: int) -> None:
|
| 70 |
+
gr.Dataframe(
|
| 71 |
+
value=df,
|
| 72 |
+
interactive=False,
|
| 73 |
+
wrap=True,
|
| 74 |
+
label="Results (sorted by aggregate CAD score)",
|
| 75 |
+
key=f"leaderboard-df-{t}",
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
def _refresh_table():
|
| 79 |
+
# ms-resolution so rapid clicks always increment the key.
|
| 80 |
+
return load_leaderboard(), int(time.time() * 1000)
|
| 81 |
+
|
| 82 |
auto_refresh_timer = gr.Timer(10)
|
| 83 |
+
auto_refresh_timer.tick(
|
| 84 |
+
fn=_refresh_table, outputs=[table_state, tick_state],
|
| 85 |
+
)
|
| 86 |
refresh_btn = gr.Button("Refresh", size="sm")
|
| 87 |
+
refresh_btn.click(
|
| 88 |
+
fn=_refresh_table, outputs=[table_state, tick_state],
|
| 89 |
+
)
|
| 90 |
|
| 91 |
with gr.Tab("Submit"):
|
| 92 |
gr.Markdown(
|