diff --git a/hf-space/requirements.txt b/hf-space/requirements.txt index 64eceb4..d8e10d2 100644 --- a/hf-space/requirements.txt +++ b/hf-space/requirements.txt @@ -1,4 +1,4 @@ -# The Space pulls the package from PyPI with the leaderboard extra so the -# Gradio entrypoint and `commonlid leaderboard serve` CLI stay in sync. Pin a -# concrete release here once we tag one (e.g. `commonlid[leaderboard]==0.1.0`). -commonlid[leaderboard] +# Install commonlid directly from GitHub main so the Space picks up changes +# without a PyPI release cycle. Pin to a tag or commit SHA (e.g. +# `...@v0.2.3` or `...@`) if you need to lock the Space to a known build. +commonlid[leaderboard] @ git+https://github.com/commoncrawl/commonlid-eval.git@main diff --git a/src/commonlid/leaderboard/app.py b/src/commonlid/leaderboard/app.py index 9c3f658..e5d50c4 100644 --- a/src/commonlid/leaderboard/app.py +++ b/src/commonlid/leaderboard/app.py @@ -410,26 +410,25 @@ def _make_select_handler( ) -> Any: """Build the row-select callback as a closure over the captured state. - The callback looks up the clicked row in the *current* table value - (passed in via Gradio's event arg) so that switching the scope radio - and then clicking a row drills down the row at its post-toggle - position, not the row that would have been there before the swap. + Uses ``gr.SelectData.row_value`` (Gradio's per-click payload that + contains the clicked row as a 1-D list) so the drilldown picks up the + *current* table ordering — switching the scope radio and then clicking + a row resolves to the row at its post-toggle position. Passing the + Dataframe component as an event input would not work: Gradio 6 + preprocesses Dataframe inputs into ``pandas.DataFrame`` objects, not + the ``{"data", "headers"}`` dict we feed in via ``_styled_value``. Gradio inspects ``__defaults__`` when registering events, and comparing a DataFrame default against a type annotation hits an unimplemented arrow dtype path. A closure keeps the state out of the function signature. """ - def _on_select(table_value: Any, evt: gr.SelectData) -> tuple[str, Any]: - if evt.index is None: + def _on_select(evt: gr.SelectData) -> tuple[str, Any]: + if evt.index is None or not evt.row_value: return ("_Click a row to load per-language metrics._", None) - row_idx = evt.index[0] if isinstance(evt.index, list | tuple) else evt.index try: - data = table_value.get("data") if isinstance(table_value, dict) else None - if data is None: - return ("_Click a row to load per-language metrics._", None) - model_id = data[row_idx][0] - except (IndexError, KeyError, TypeError): + model_id = evt.row_value[0] + except (IndexError, TypeError): return ("_Could not resolve clicked row._", None) per_lang = _per_language_drilldown(snapshot_root, dataset_id, model_id) return ( @@ -540,7 +539,6 @@ def build_app( ) leaderboard.select( _make_select_handler(dataset_id, snapshot_root), - inputs=[leaderboard], outputs=[drilldown_label, drilldown], ) gr.Markdown(footer) diff --git a/tests/unit/test_leaderboard_data.py b/tests/unit/test_leaderboard_data.py index 4e9be30..8fb75d2 100644 --- a/tests/unit/test_leaderboard_data.py +++ b/tests/unit/test_leaderboard_data.py @@ -466,6 +466,51 @@ def test_format_table_cov_scope_renders_em_dashes(tmp_path: Path) -> None: assert cov_table.iloc[1]["Samples/s"] == "1234.5" +def test_row_select_handler_loads_drilldown_from_row_value(tmp_path: Path) -> None: + """Clicking a row resolves model_id via ``evt.row_value[0]`` and renders the drilldown. + + Regression: an earlier version pulled the model_id from the Dataframe + input, but Gradio 6 preprocesses Dataframe inputs into ``pandas.DataFrame`` + objects rather than the ``{"data", "headers"}`` dict the app feeds in, + so the handler silently returned the "click a row" placeholder. + """ + pytest.importorskip("gradio") + from types import SimpleNamespace + + from commonlid.leaderboard.app import _make_select_handler + + per_lang = _per_language_block({"eng": (10, 10, 8, 0.01), "fra": (10, 10, 4, 0.02)}) + _write_summary(tmp_path, "commonlid", "GlotLID", per_language=per_lang) + handler = _make_select_handler("commonlid", tmp_path) + + evt = SimpleNamespace( + index=(0, 0), + value="GlotLID", + row_value=["GlotLID", "80.0", "60.0", "0.10", "2", "1234.5"], + ) + label, payload = handler(evt) + assert "GlotLID" in label + assert "commonlid" in label + assert payload is not None + headers = payload["headers"] + assert headers[:2] == ["Language", "F1"] + languages = [row[0] for row in payload["data"]] + assert set(languages) == {"eng", "fra"} + + +def test_row_select_handler_returns_placeholder_when_index_missing(tmp_path: Path) -> None: + pytest.importorskip("gradio") + from types import SimpleNamespace + + from commonlid.leaderboard.app import _make_select_handler + + handler = _make_select_handler("commonlid", tmp_path) + evt = SimpleNamespace(index=None, value=None, row_value=None) + label, payload = handler(evt) + assert "Click a row" in label + assert payload is None + + def test_scope_radio_change_swaps_table_and_legend(tmp_path: Path) -> None: """The scope-change handler returns a fresh styled table + legend Markdown.""" pytest.importorskip("gradio")