""" UI Layout components for the Tox21 leaderboard. Pure Gradio structure - no content or business logic. """ import gradio as gr from typing import Callable, Any from .content import LeaderboardContent, AboutContent, SubmissionContent from frontend.leaderboard import refresh_leaderboard, format_parameter_count import os import pandas as pd from gradio_leaderboard import ( Leaderboard, SearchColumns, ColumnFilter, SelectColumns, ) from pathlib import Path import inspect from config.leaderboard import COLUMN_WIDTHS, TASK_NAMES def create_leaderboard_tab(refresh_callback: Callable = None) -> gr.TabItem: """Create the leaderboard tab layout""" with gr.TabItem("Leaderboard", elem_id="leaderboard-tab", id=0) as tab: def get_default_columns(df: pd.DataFrame) -> list: """ Get the list of columns that should be visible by default. Visible by default: - Rank, Type, Model, Organization, Publication - Avg. AUC, Avg. ΔAUC-PR - All ROC-AUC columns - All ΔAUC-PR columns - # Parameters Hidden by default: - Model Description, Publication Link, HF_Space_Tag - Pretrained, Pretraining Data, Zero-shot, Few-shot, N-shot - Date Added """ default_visible = [] for col in df.columns: col_group = col[0] if isinstance(col, tuple) else "" col_name = col[1] if isinstance(col, tuple) else col # Always show these columns if col_name in [ "Rank", "Type", "Model", "Organization", "Publication", "Avg. AUC", "Avg. ΔAUC-PR", "# Parameters", ]: default_visible.append(col) # Show all ROC-AUC and ΔAUC-PR task columns elif col_group in ["ROC-AUC", "ΔAUC-PR"]: default_visible.append(col) return default_visible def apply_filters(search, type_filter, min_auc, selected_columns): new_data = ( refresh_callback().reset_index(drop=True) if refresh_callback else refresh_leaderboard().reset_index(drop=True) ) new_data[("", "Avg. AUC")] = pd.to_numeric( new_data[("", "Avg. AUC")], errors="coerce" ) new_data = new_data.sort_values( by=("", "Avg. AUC"), ascending=False, na_position="last" ) ranks = [] for i in range(1, len(new_data) + 1): if i == 1: ranks.append("🥇") elif i == 2: ranks.append("🥈") elif i == 3: ranks.append("🥉") else: ranks.append(str(i)) new_data.insert(0, ("", "Rank"), ranks) filtered = new_data.copy() # Search filter: search across Model, Organization, and Publication if search and search.strip(): search = search.lower() mask = ( filtered[("", "Model")] .astype(str) .str.lower() .str.contains(search, na=False) | filtered[("", "Organization")] .astype(str) .str.lower() .str.contains(search, na=False) | filtered[("", "Publication")] .astype(str) .str.lower() .str.contains(search, na=False) ) filtered = filtered[mask] # Type filter - extract just the emoji from the checkbox values if type_filter and len(type_filter) > 0: # Extract emoji by splitting on space and taking first part # e.g., "0️⃣ Zero-shot" -> "0️⃣" type_emojis = [ t.split(" ")[0] if " " in t else t for t in type_filter ] filtered = filtered[filtered[("", "Type")].isin(type_emojis)] # Min AUC filter if min_auc and min_auc > 0: filtered = filtered[filtered[("", "Avg. AUC")] >= min_auc] return make_html_table(filtered, selected_columns) def make_html_table(df: pd.DataFrame, selected_columns=None) -> str: # If no column selection provided, show default columns if selected_columns is None: selected_columns = get_default_columns(df) # Always hide these columns (used for links and tooltips) always_hidden = [ ("", "HF_Space_Tag"), ("", "Publication Link"), ("", "Model Description"), ] html = "
" if isinstance(df.columns, pd.MultiIndex): html += "" # --- Top header row --- html += "" prev_top = None colspan = 0 col_idx = 0 for i, (top, sub) in enumerate(df.columns): # Skip always-hidden columns if (top, sub) in always_hidden: continue # Skip columns not in selected_columns if (top, sub) not in selected_columns: continue # Handle blank top headers directly (span both rows) if top == "" or top is None: # close any previous group first if prev_top is not None: html += f"" prev_top, colspan = None, 0 # Rank column should not be sortable if sub == "Rank": html += f"" else: html += f"" col_idx += 1 continue # If same group continues, increment colspan if top == prev_top: colspan += 1 else: # close previous group if exists if prev_top is not None: html += f"" prev_top = top colspan = 1 # close final group if prev_top is not None: html += f"" html += "" # --- Second header row --- html += "" # col_idx continues from first row for top, sub in df.columns: # Skip always-hidden columns if (top, sub) in always_hidden: continue # Skip columns not in selected_columns if (top, sub) not in selected_columns: continue if top not in ("", None): # Add col-score class to AUC columns for consistent width handling extra_class = ( " col-score" if "AUC" in top or "ΔAUC" in top else "" ) html += f"" col_idx += 1 html += "" html += "" else: html += "" col_idx = 0 for col in df.columns: if col not in always_hidden and col in selected_columns: html += f"" col_idx += 1 html += "" # --- Body --- html += "" for _, row in df.iterrows(): # First pass: extract hidden columns for creating links and tooltips hf_space_tag = "" publication_link = "" model_description = "" if ("", "HF_Space_Tag") in df.columns: hf_space_tag = row[("", "HF_Space_Tag")] if ("", "Publication Link") in df.columns: publication_link = row[("", "Publication Link")] if ("", "Model Description") in df.columns: model_description = row[("", "Model Description")] # Second pass: render cells html += "" for col, val in zip(df.columns, row): # Skip always-hidden columns if col in always_hidden: continue # Skip columns not in selected_columns if col not in selected_columns: continue # Determine CSS class based on column type col_name = col[1] if isinstance(col, tuple) else col col_group = col[0] if isinstance(col, tuple) else "" css_class = "" if col_name == "Type": css_class = "col-type" elif col_name == "Model": css_class = "col-model" elif col_name == "Organization": css_class = "col-organization" elif col_name == "Model Description": css_class = "col-description" elif col_name == "Publication": css_class = "col-publication" elif col_name in ["# Parameters"]: css_class = "col-params" elif col_name == "Date Added": css_class = "col-date" elif col_name in ["Pretrained", "Zero-shot", "Few-shot"]: css_class = "col-boolean" elif ( "AUC" in col_name or "ΔAUC" in col_name or "Avg." in col_name or "AUC" in col_group or "ΔAUC" in col_group ): css_class = "col-score" # Get string value for title attribute (escape quotes for HTML) val_str = str(val) if val is not None else "" val_str_escaped = val_str.replace('"', """).replace( "'", "'" ) title_attr = ( f' title="{val_str_escaped}"' if val_str else "" ) # Helper function to check if value should be displayed as empty def is_empty_value(v): if pd.isna(v) or v is None: return True if str(v).lower().strip() in ["nan", "none", ""]: return True return False # Create clickable link for Model column if HF space tag exists if col == ("", "Model"): tag_str = str(hf_space_tag).strip().lower() display_val = "" if is_empty_value(val) else val if ( hf_space_tag and tag_str and tag_str not in ["none", "nan", "", "."] ): if "github" in hf_space_tag: html += f"" else: # Put title on the link itself, not the td html += f"" else: html += f"" # Create clickable link for Publication column with custom tooltip elif col == ("", "Publication"): # Create custom tooltip with Publication and Model Description pub_str = str(val) if val is not None else "" desc_str = ( str(model_description) if model_description else "No description available" ) custom_tooltip = f"Publication:\n{pub_str}\n\nModel Description:\n{desc_str}" custom_tooltip_escaped = custom_tooltip.replace( '"', """ ).replace("'", "'") custom_title_attr = f' title="{custom_tooltip_escaped}"' display_val = "" if is_empty_value(val) else val link_str = str(publication_link).strip().lower() if ( publication_link and link_str and link_str not in ["none", "nan", "", "."] ): # Put custom title on the link itself html += f"" else: html += f"" # Format # Parameters with human-readable display but keep raw value for sorting elif col == ("", "# Parameters"): formatted_val = format_parameter_count(val) # Store raw numeric value in data-value for sorting data_value_attr = ( f' data-value="{val}"' if pd.notna(val) and val != "" else ' data-value=""' ) html += f"" else: # Format score values (AUC, ΔAUC) to 3 decimal places if is_empty_value(val): display_val = "" elif css_class == "col-score": # Format as float with 3 decimal places try: display_val = f"{float(val):.3f}" except (ValueError, TypeError): display_val = val else: display_val = val html += f"" html += "" html += "
{prev_top}{sub}{sub} {prev_top}{prev_top}
{sub}
{col}
{display_val}{display_val}{display_val}{display_val}{display_val}{formatted_val}{display_val}
" # Add JavaScript for sorting - must be inline for Gradio html += """ """ return html # Header section print(Leaderboard.__module__) header_html = gr.HTML(LeaderboardContent.get_header_html()) # Load initial data result_data = refresh_leaderboard() # df # add rank column with medals for top 3 result_data[("", "Avg. AUC")] = pd.to_numeric( result_data[("", "Avg. AUC")], errors="coerce" ) result_data = result_data.sort_values( by=("", "Avg. AUC"), ascending=False, na_position="last" ) ranks = [] for i in range(1, len(result_data) + 1): if i == 1: ranks.append("🥇") elif i == 2: ranks.append("🥈") elif i == 3: ranks.append("🥉") else: ranks.append(str(i)) result_data.insert(0, ("", "Rank"), ranks) # result_data.columns = result_data.columns.map(str) # Search bar search = gr.Textbox( show_label=False, placeholder="Search by model, organization, or publication…", elem_id="leaderboard-search", container=False, max_lines=1, type="text", ) # Filters in an accordion with gr.Accordion( "Filters & Column Selection", open=False, elem_classes=["filter-accordion"], ): with gr.Row(): # Type filter (multi-select checkboxes) type_filter = gr.CheckboxGroup( choices=[ "0️⃣ Zero-shot", "1️⃣ Few-shot", "⤵️ Pre-trained", "🔼 Models trained from scratch", ], label="Model Type", value=[ "0️⃣ Zero-shot", "1️⃣ Few-shot", "⤵️ Pre-trained", "🔼 Models trained from scratch", ], # All selected by default interactive=True, ) # Min AUC filter min_auc = gr.Number( label="Minimum Avg. AUC", value=0, minimum=0, maximum=1, step=0.01, ) # Column selection dropdown # Get all column names for the dropdown all_columns = [ col for col in result_data.columns if col not in [ ("", "HF_Space_Tag"), ("", "Publication Link"), ("", "Model Description"), ] ] default_columns = get_default_columns(result_data) # Format column names for display column_choices = [] column_values = [] for col in all_columns: if isinstance(col, tuple): display_name = f"{col[0]} - {col[1]}" if col[0] else col[1] else: display_name = str(col) column_choices.append(display_name) column_values.append(col) # Create mapping from display name to actual column column_display_to_actual = dict(zip(column_choices, column_values)) default_display_names = [ display_name for display_name, col in column_display_to_actual.items() if col in default_columns ] column_selector = gr.CheckboxGroup( choices=column_choices, value=default_display_names, label="Select Columns to Display", interactive=True, ) # Refresh button refresh_btn = gr.Button( "↻", variant="secondary", size="lg", elem_classes=["refresh-button"] ) # table = gr.Dataframe(result_data, # column_widths=column_widths, # elem_classes=["leaderboard-table"], # interactive=False, # ) table = gr.HTML(make_html_table(result_data)) # Wire up the filter components def apply_filters_wrapper( search, type_filter, min_auc, selected_display_names ): # Convert display names back to actual column tuples selected_columns = [ column_display_to_actual[name] for name in selected_display_names ] return apply_filters(search, type_filter, min_auc, selected_columns) # Connect all inputs to apply_filters for component in [search, type_filter, min_auc, column_selector]: component.change( fn=apply_filters_wrapper, inputs=[search, type_filter, min_auc, column_selector], outputs=table, ) refresh_btn.click( apply_filters_wrapper, [search, type_filter, min_auc, column_selector], table, ) # Connect refresh button def refresh_data(): new_data = ( refresh_callback().reset_index(drop=True) if refresh_callback else refresh_leaderboard().reset_index(drop=True) ) new_data.columns = new_data.columns.map(str) return new_data # Info section info_html = gr.HTML( LeaderboardContent.get_info_html(), elem_classes=["info-leaderboard"], ) return tab, table def create_about_tab() -> gr.TabItem: """Create the about tab layout""" with gr.TabItem("About", elem_id="about-tab", id=1) as tab: content_markdown = gr.Markdown( AboutContent.get_markdown_content(), elem_classes="markdown-text" ) return tab def create_submission_tab(submit_callback: Callable = None) -> gr.TabItem: """Create the submission tab layout""" with gr.TabItem("Submit", elem_id="submission-tab", id=2) as tab: with gr.Blocks(elem_classes=["form-wrapper"]): gr.HTML( f"""

{SubmissionContent.emoji}

{SubmissionContent.title}

""" ) # Instructions instructions_html = gr.HTML( SubmissionContent.get_instructions_html() ) # Submission form with gr.Blocks(): # Required fields gr.HTML( f"
{SubmissionContent.form_labels['model_name']}
", container=False, elem_classes=["field-label"], ) model_name = gr.Textbox( show_label=True, placeholder=SubmissionContent.form_placeholders[ "model_name" ], # info=SubmissionContent.form_info["model_name"], container=False, elem_classes=["form-container"], max_lines=1, ) gr.HTML( f"
{SubmissionContent.form_labels['hf_space_tag']}
", container=False, elem_classes=["field-label"], ) hf_space_tag = gr.Textbox( label=SubmissionContent.form_labels["hf_space_tag"], placeholder=SubmissionContent.form_placeholders[ "hf_space_tag" ], # info=SubmissionContent.form_info["hf_space_tag"], container=False, max_lines=1, elem_classes=["form-container"], ) gr.HTML( f"
{SubmissionContent.form_labels['model_description']}
", container=False, elem_classes=["field-label"], ) model_description = gr.Textbox( label=SubmissionContent.form_labels["model_description"], placeholder=SubmissionContent.form_placeholders[ "model_description" ], # info=SubmissionContent.form_info["model_description"], lines=3, container=False, elem_classes=["form-container"], ) # Optional fields in accordion # with gr.Accordion("Additional Information", open=False): gr.HTML( f"
{SubmissionContent.form_labels['organization']}
", container=False, elem_classes=["field-label"], ) organization = gr.Textbox( label=SubmissionContent.form_labels["organization"], placeholder=SubmissionContent.form_placeholders[ "organization" ], container=False, elem_classes=["form-container"], max_lines=1, ) gr.HTML( f"
{SubmissionContent.form_labels['model_size']}
", container=False, elem_classes=["field-label"], ) model_size = gr.Textbox( label=SubmissionContent.form_labels["model_size"], placeholder=SubmissionContent.form_placeholders[ "model_size" ], container=False, max_lines=1, elem_classes=["form-container"], ) gr.HTML( f"
{SubmissionContent.form_labels['pretrained']}
", container=False, elem_classes=["field-label"], ) pretrained = gr.Radio( choices=["Yes", "No"], value="No", container=False, elem_classes=["form-container-radio"], ) pretraining_label = gr.HTML( f"
{SubmissionContent.form_labels['pretraining_data']}
", container=False, elem_classes=["field-label"], visible=False, ) pretraining_data = gr.Textbox( label=SubmissionContent.form_labels["pretraining_data"], placeholder=SubmissionContent.form_placeholders[ "pretraining_data" ], visible=False, container=False, max_lines=1, elem_classes=["form-container"], ) # When checkbox changes, update visibility def toggle_pretrain_fields(choice): show = choice == "Yes" return [gr.update(visible=show), gr.update(visible=show)] pretrained.change( fn=toggle_pretrain_fields, inputs=pretrained, outputs=[pretraining_label, pretraining_data], ) # pretrained = gr.Textbox( # label=SubmissionContent.form_labels["pretrained"], # placeholder=SubmissionContent.form_placeholders[ # "pretrained" # ], # ) gr.HTML( f"
{SubmissionContent.form_labels['publication_title']}
", container=False, elem_classes=["field-label"], ) publication_title = gr.Textbox( label=SubmissionContent.form_labels["publication_title"], placeholder=SubmissionContent.form_placeholders[ "publication_title" ], container=False, max_lines=1, elem_classes=["form-container"], ) gr.HTML( f"
{SubmissionContent.form_labels['publication_link']}
", container=False, elem_classes=["field-label"], ) publication_link = gr.Textbox( label=SubmissionContent.form_labels["publication_link"], placeholder=SubmissionContent.form_placeholders[ "publication_link" ], max_lines=1, container=False, elem_classes=["form-container"], ) gr.HTML( f"
{SubmissionContent.form_labels['zero_shot']}
", container=False, elem_classes=["field-label"], ) zero_shot = gr.Radio( choices=["Yes", "No"], value="No", container=False, elem_classes=["form-container-radio"], ) gr.HTML( f"
{SubmissionContent.form_labels['few_shot']}
", container=False, elem_classes=["field-label"], ) few_shot = gr.Radio( choices=["Yes", "No"], value="No", container=False, elem_classes=["form-container-radio"], ) n_shot_label = gr.HTML( f"
{SubmissionContent.form_labels['n_shot']}
", container=False, elem_classes=["field-label"], visible=False, ) n_shot = gr.Textbox( label=SubmissionContent.form_labels["n_shot"], placeholder=SubmissionContent.form_placeholders["n_shot"], visible=False, container=False, max_lines=1, elem_classes=["form-container"], ) def toggle_n_shot_fields(choice): show = choice == "Yes" return [gr.update(visible=show), gr.update(visible=show)] few_shot.change( fn=toggle_n_shot_fields, inputs=few_shot, outputs=[n_shot_label, n_shot], ) agree = gr.Checkbox( label="I confirm that the submission fulfills the requirements and that the results are reproducible.", elem_id="agree-checkbox", container=False, elem_classes=["form-container-checkbox"], ) # Submit button and result submit_btn = gr.Button( "Submit", value="primary", interactive=False, elem_classes=["submit-button"], ) agree.change( lambda x: gr.update(interactive=x), inputs=agree, outputs=submit_btn, ) result_msg = gr.HTML() if submit_callback: submit_btn.click( fn=submit_callback, inputs=[ model_name, hf_space_tag, model_description, organization, model_size, pretrained, pretraining_data, publication_title, publication_link, zero_shot, few_shot, n_shot, ], outputs=result_msg, ) return tab def create_main_interface( refresh_callback: Callable = None, submit_callback: Callable = None ) -> gr.Blocks: """Create the main application interface""" css = Path(__file__).with_name("styles.css").read_text(encoding="utf-8") # JavaScript for table sorting js = """ function() { if (typeof window.sortDirections === 'undefined') { window.sortDirections = {}; } window.sortTable = function(columnIndex) { const table = document.querySelector('.sortable-table'); if (!table) return; const tbody = table.querySelector('tbody'); const rows = Array.from(tbody.querySelectorAll('tr')); // Toggle sort direction const currentDirection = window.sortDirections[columnIndex] || 'asc'; const newDirection = currentDirection === 'asc' ? 'desc' : 'asc'; window.sortDirections[columnIndex] = newDirection; // Update sort indicators const headers = table.querySelectorAll('.sortable-header'); headers.forEach((header, idx) => { const indicator = header.querySelector('.sort-indicator'); if (parseInt(header.getAttribute('data-column')) === columnIndex) { indicator.textContent = newDirection === 'asc' ? ' ▲' : ' ▼'; } else { indicator.textContent = ''; } }); // Store original rank values (first column, index 0) const originalRanks = rows.map(row => row.querySelectorAll('td')[0].textContent); // Sort rows rows.sort((a, b) => { const aCell = a.querySelectorAll('td')[columnIndex]; const bCell = b.querySelectorAll('td')[columnIndex]; if (!aCell || !bCell) return 0; // Check for data-value attribute first (used for formatted numeric columns like # Parameters) let aValue = aCell.getAttribute('data-value'); let bValue = bCell.getAttribute('data-value'); // If no data-value, use text content if (aValue === null) aValue = aCell.textContent.trim(); if (bValue === null) bValue = bCell.textContent.trim(); // Check for truly empty values first const aIsEmpty = aValue === ''; const bIsEmpty = bValue === ''; // Sort empty values last if (aIsEmpty && bIsEmpty) return 0; if (aIsEmpty) return 1; // a goes to end if (bIsEmpty) return -1; // b goes to end // Try to parse as number const aNum = parseFloat(aValue); const bNum = parseFloat(bValue); // Both are valid numbers - use numeric comparison if (!isNaN(aNum) && !isNaN(bNum)) { return newDirection === 'asc' ? aNum - bNum : bNum - aNum; } // At least one is not a number - use string comparison if (newDirection === 'asc') { return aValue.localeCompare(bValue); } else { return bValue.localeCompare(aValue); } }); // Re-append sorted rows and restore original rank values rows.forEach((row, index) => { row.querySelectorAll('td')[0].textContent = originalRanks[index]; tbody.appendChild(row); }); }; } """ # Create custom theme with cyan accent color theme = gr.themes.Default( primary_hue="cyan", secondary_hue="cyan", ).set( checkbox_background_color_selected="#20C2D9", checkbox_border_color="#20C2D9", checkbox_border_color_focus="#20C2D9", checkbox_border_color_hover="#20C2D9", radio_circle="#20C2D9", ) with gr.Blocks( css=css, js=js, title="Tox21 Leaderboard", theme=theme ) as app: # Tab container with gr.Tabs(elem_classes="tab-nav") as tabs: # Create all tabs leaderboard_tab, leaderboard_table = create_leaderboard_tab( refresh_callback ) about_tab = create_about_tab() submission_tab = create_submission_tab(submit_callback) return app, leaderboard_table