import tempfile import gradio as gr from gradio_leaderboard import ColumnFilter, Leaderboard, SearchColumns, SelectColumns import enviroments.config as config from sheet_manager.sheet_loader.sheet_cache import ( get_cached_df, invalidate_cache, refresh_if_signal_newer, ) from utils.column_rules import ( AVG_DATASET, apply_avg_override, avg_categories, build_hide_columns, compute_display_columns, datasets_for_category, default_avg_map, extra_columns, parse_axes, ) def _default_display_columns(df): categories, _ = parse_axes(df.columns) default_cats = [c for c in config.DEFAULT_CATEGORIES if c in categories] return compute_display_columns( list(df.columns), default_cats, config.DEFAULT_DATASETS ) def leaderboard_tab(demo): """Leaderboard 탭. gradio_leaderboard.Leaderboard 는 __init__ 시점에 headers/select_columns_config 가 frozen 되므로 @gr.render 로 컴포넌트 자체를 재빌드한다. 컬럼 선택은 내장 체크박스(벤치마크 100여 개가 한 줄씩 나열되어 화면을 잡아먹음) 대신 카테고리 × 데이터셋 2축 + 추가 정보 셀렉터로 대체 — allow=False 로 내장 UI 를 숨기고, 선택 결과(selected_state)가 바뀔 때마다 default_selection 에 반영해 재빌드한다. 축 목록은 utils/column_rules.py 가 df.columns 에서 자동 추출하므로 신규 벤치마크 등록 시 별도 설정 없이 셀렉터에 나타난다. AVG_* 표시값은 시트 값 대신 UI 계산값으로 대체되는데, 계산 기준은 표시용 데이터셋 드롭다운과 완전히 분리된 별도 접힘 섹션(avg_sel)에서만 바뀐다 — 드롭다운으로 뭘 보든 AVG 는 항상 고정된 기준으로 계산된다. """ with gr.Tab("🏆Leaderboard"): with gr.Accordion( "🗂️ Select Columns to Display", open=True, elem_id="column-selector-acc" ): with gr.Row(): category_sel = gr.CheckboxGroup( choices=[], label="카테고리", scale=3, ) dataset_sel = gr.Dropdown( choices=[], value=[], multiselect=True, label="데이터셋 (비우면 전체)", scale=2, ) with gr.Row(): extras_sel = gr.CheckboxGroup( choices=[], label="추가 정보", scale=3, ) with gr.Column(scale=2): preset_default_btn = gr.Button("↩️ 기본 보기", size="sm") preset_all_btn = gr.Button("📋 전체 보기", size="sm") # AVG 계산 기준: 카테고리별 패널 (좌우 스크롤). 표시용 데이터셋 # 드롭다운과 무관하게 여기서 정한 기준으로만 AVG 가 계산된다. # 패널 구성(카테고리 수)은 앱 시작 시점 시트 기준으로 고정 — # AVG_<신규> 컬럼이 시트에 새로 생기면 앱 재시작 필요. axes_columns = list(get_cached_df().columns) avg_cat_list = avg_categories(axes_columns) initial_avg_map = default_avg_map(axes_columns) with gr.Accordion( "⚙️ AVG 계산 기준 — 카테고리별 (기본: 정식 벤치마크 세트)", open=False, elem_id="avg-basis-acc", ): avg_sels = {} with gr.Row(elem_id="avg-basis-row"): for cat in avg_cat_list: avg_sels[cat] = gr.CheckboxGroup( choices=datasets_for_category(axes_columns, cat), value=initial_avg_map.get(cat, []), label=f"AVG_{cat}", ) avg_reset_btn = gr.Button("↩️ 기본값 복원", size="sm") avg_sel_components = [avg_sels[c] for c in avg_cat_list] selected_state = gr.State(None) def _load_axes(): df = get_cached_df() categories, datasets = parse_axes(df.columns) default_cats = [c for c in config.DEFAULT_CATEGORIES if c in categories] dmap = default_avg_map(df.columns) avg_updates = [ gr.update( choices=datasets_for_category(df.columns, cat), value=dmap.get(cat, []), ) for cat in avg_cat_list ] return ( gr.update(choices=categories, value=default_cats), gr.update( choices=[AVG_DATASET] + datasets, value=config.DEFAULT_DATASETS ), gr.update(choices=extra_columns(df.columns), value=[]), *avg_updates, ) demo.load( _load_axes, inputs=[], outputs=[category_sel, dataset_sel, extras_sel, *avg_sel_components], ) def _selection_changed(cats, datasets, extras, *avg_values): df = get_cached_df() return { "columns": compute_display_columns( list(df.columns), cats, datasets, extras ), "avg": { cat: list(vals) for cat, vals in zip(avg_cat_list, avg_values) }, } for control in (category_sel, dataset_sel, extras_sel, *avg_sel_components): control.input( _selection_changed, [category_sel, dataset_sel, extras_sel, *avg_sel_components], [selected_state], ) def _reset_avg(cats, datasets, extras): df = get_cached_df() dmap = default_avg_map(df.columns) updates = [ gr.update(value=dmap.get(cat, [])) for cat in avg_cat_list ] state = { "columns": compute_display_columns( list(df.columns), cats, datasets, extras ), "avg": dmap, } return (*updates, state) avg_reset_btn.click( _reset_avg, inputs=[category_sel, dataset_sel, extras_sel], outputs=[*avg_sel_components, selected_state], ) def _apply_preset(categories, datasets, avg_values): df = get_cached_df() all_cats, _ = parse_axes(df.columns) if categories is None: cats = all_cats else: cats = [c for c in categories if c in all_cats] cols = compute_display_columns(list(df.columns), cats, datasets, []) return ( gr.update(value=cats), gr.update(value=datasets), gr.update(value=[]), { "columns": cols, "avg": { cat: list(vals) for cat, vals in zip(avg_cat_list, avg_values) }, }, ) preset_default_btn.click( lambda *avg: _apply_preset( config.DEFAULT_CATEGORIES, config.DEFAULT_DATASETS, avg ), inputs=avg_sel_components, outputs=[category_sel, dataset_sel, extras_sel, selected_state], ) preset_all_btn.click( lambda *avg: _apply_preset(None, [], avg), inputs=avg_sel_components, outputs=[category_sel, dataset_sel, extras_sel, selected_state], ) @gr.render(triggers=[demo.load, selected_state.change], inputs=[selected_state]) def _build_leaderboard(selection): avg_map = (selection or {}).get("avg") or None df = apply_avg_override(refresh_if_signal_newer(), avg_map) selected_cols = (selection or {}).get("columns") if not selected_cols: selected_cols = _default_display_columns(df) leaderboard = Leaderboard( value=df, select_columns=SelectColumns( default_selection=selected_cols, cant_deselect=config.OFF_LOAD_COLUMNS, allow=False, ), search_columns=SearchColumns( primary_column="Model name", secondary_columns=["TASK"], placeholder="Search", label="Search", ), hide_columns=build_hide_columns(list(df.columns)), filter_columns=[ColumnFilter(column="TASK")], datatype=config.TYPES, ) df_state = gr.State(value=df) refresh_button = gr.Button("🔄 Refresh Leaderboard") download_button = gr.Button("📥 Download Leaderboard (Excel)") download_file = gr.File(label="Download", visible=False) def refresh_leaderboard(): invalidate_cache() fresh = apply_avg_override(get_cached_df(), avg_map) return fresh, fresh def download_leaderboard(state_df): tmp = tempfile.NamedTemporaryFile( delete=False, suffix=".xlsx", prefix="leaderboard_", dir=tempfile.gettempdir(), ) tmp.close() state_df.to_excel(tmp.name, index=False, engine="openpyxl") return gr.File(value=tmp.name, visible=True) refresh_button.click( refresh_leaderboard, inputs=[], outputs=[leaderboard, df_state], ) download_button.click( download_leaderboard, inputs=[df_state], outputs=[download_file], )