from __future__ import annotations import base64 import json from functools import cmp_to_key from html import escape from pathlib import Path from typing import Any import gradio as gr HOME_VIEW = "HOME" TASK_ORDER = [ "K-disentQA", "SQA", "Instruct", "ASR", "Translation", "LSQA", ] ROOT = Path(__file__).parent DATA_PATH = ROOT / "data" / "leaderboard-data.json" IMAGES_DIR = ROOT / "images" def to_num(value: Any) -> float | None: try: num = float(value) except (TypeError, ValueError): return None return num def average(values: list[float | None]) -> float | None: valid = [value for value in values if value is not None] if not valid: return None return sum(valid) / len(valid) def compare_scores(left: float | None, right: float | None, lower_better: bool) -> int: if left is None and right is None: return 0 if left is None: return 1 if right is None: return -1 if left == right: return 0 if lower_better: return -1 if left < right else 1 return -1 if left > right else 1 def ordered_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]: def sort_key(task: dict[str, Any]) -> tuple[int, str]: try: task_index = TASK_ORDER.index(task["id"]) except ValueError: task_index = 10**6 return task_index, task["label"] return sorted(tasks, key=sort_key) def dataset_ids(task: dict[str, Any]) -> list[str]: return [dataset["id"] for dataset in task.get("datasets", [])] def metric_value(entry: dict[str, Any], task_id: str, dataset_id: str) -> float | None: dataset = entry.get("tasks", {}).get(task_id, {}).get(dataset_id) if not dataset: return None return to_num(dataset.get("value")) def metric_display(entry: dict[str, Any], task_id: str, dataset_id: str) -> str: dataset = entry.get("tasks", {}).get(task_id, {}).get(dataset_id) if not dataset: return "-" if dataset.get("display") is not None: return str(dataset["display"]) if dataset.get("value") is None: return "-" return str(dataset["value"]) def compute_task_overall(entry: dict[str, Any], task: dict[str, Any]) -> float | None: return average([metric_value(entry, task["id"], dataset_id) for dataset_id in dataset_ids(task)]) def normalize_task_scores(entries: list[dict[str, Any]], tasks: list[dict[str, Any]]) -> dict[str, dict[str, float] | None]: ranges: dict[str, dict[str, float] | None] = {} for task in tasks: values = [entry["task_overall"][task["id"]] for entry in entries if entry["task_overall"][task["id"]] is not None] if not values: ranges[task["id"]] = None continue ranges[task["id"]] = {"min": min(values), "max": max(values)} return ranges def normalized_score(value: float | None, score_range: dict[str, float] | None, lower_better: bool) -> float | None: if value is None or score_range is None: return None if score_range["min"] == score_range["max"]: return 100.0 if lower_better: return ((score_range["max"] - value) / (score_range["max"] - score_range["min"])) * 100 return ((value - score_range["min"]) / (score_range["max"] - score_range["min"])) * 100 def enrich_entries(entries: list[dict[str, Any]], tasks: list[dict[str, Any]]) -> list[dict[str, Any]]: entries_with_task_overall = [] for entry in entries: task_overall = {} for task in tasks: task_overall[task["id"]] = compute_task_overall(entry, task) entries_with_task_overall.append({**entry, "task_overall": task_overall}) task_ranges = normalize_task_scores(entries_with_task_overall, tasks) enriched_entries = [] for entry in entries_with_task_overall: normalized_task_scores = {} for task in tasks: normalized_task_scores[task["id"]] = normalized_score( entry["task_overall"][task["id"]], task_ranges[task["id"]], task["lowerBetter"], ) enriched_entries.append( { **entry, "normalized_task_scores": normalized_task_scores, "overall": average([normalized_task_scores[task["id"]] for task in tasks]), } ) return enriched_entries def sort_overall(entries: list[dict[str, Any]]) -> list[dict[str, Any]]: sorted_entries = sorted( entries, key=cmp_to_key(lambda left, right: compare_scores(left["overall"], right["overall"], False)), ) return [{**entry, "rank": index} for index, entry in enumerate(sorted_entries, start=1)] def sort_task(entries: list[dict[str, Any]], task: dict[str, Any], dataset_id: str) -> list[dict[str, Any]]: def compare(left: dict[str, Any], right: dict[str, Any]) -> int: left_value = left["task_overall"][task["id"]] if dataset_id == "Overall" else metric_value(left, task["id"], dataset_id) right_value = right["task_overall"][task["id"]] if dataset_id == "Overall" else metric_value(right, task["id"], dataset_id) return compare_scores(left_value, right_value, task["lowerBetter"]) sorted_entries = sorted(entries, key=cmp_to_key(compare)) return [{**entry, "rank": index} for index, entry in enumerate(sorted_entries, start=1)] def metric_class(lower_better: bool, value: float | None) -> str: if value is None: return "muted" return "metric-bad" if lower_better else "metric-good" def fmt_score(value: float | None) -> str: return "-" if value is None else f"{value:.2f}" def image_data_uri(path: Path) -> str: encoded = base64.b64encode(path.read_bytes()).decode("ascii") suffix = path.suffix.lower().lstrip(".") or "png" mime = "image/png" if suffix == "png" else f"image/{suffix}" return f"data:{mime};base64,{encoded}" def load_payload() -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: payload = json.loads(DATA_PATH.read_text(encoding="utf-8")) tasks = ordered_tasks(payload.get("tasks", [])) entries = enrich_entries(payload.get("entries", []), tasks) return tasks, entries TASKS, ENTRIES = load_payload() TASK_MAP = {task["id"]: task for task in TASKS} RANKED_OVERALL = sort_overall(ENTRIES) BADGE_IMAGES = { 1: image_data_uri(IMAGES_DIR / "1st.png"), 2: image_data_uri(IMAGES_DIR / "2nd.png"), 3: image_data_uri(IMAGES_DIR / "3rd.png"), } EXTERNAL_LINK_IMAGE = image_data_uri(IMAGES_DIR / "external-link.png") def menu_choices() -> list[tuple[str, str]]: choices = [("Home\nOverall ranking", HOME_VIEW)] for task in TASKS: choices.append((f"{task['label']}\n{len(task['datasets'])} datasets", task["id"])) return choices def dataset_choices(task_id: str) -> list[str]: task = TASK_MAP[task_id] return ["Overall", *[dataset["id"] for dataset in task.get("datasets", [])]] def render_rank_strip(entries: list[dict[str, Any]]) -> str: cards = [] for entry in entries[:12]: if entry["rank"] in BADGE_IMAGES: badge = f'{entry[' else: badge = f'#{entry["rank"]}' cards.append( f"""
{badge}
{escape(entry["rank_name"])}
""" ) return f"""

Top Ranking

{"".join(cards)}
""" def render_home_table(entries: list[dict[str, Any]]) -> str: header_top = [ 'Rank', 'RankName', 'Model', 'URL', 'Overall', *[f'{escape(task["label"])}' for task in TASKS], ] header_bottom = [f'{escape(task["shortMetric"])}' for task in TASKS] rows = [] for entry in entries: url = entry.get("url") or "" if url: url_cell = ( f'' ) else: url_cell = "-" task_cells = [] for task in TASKS: value = entry["task_overall"][task["id"]] task_cells.append(f'{fmt_score(value)}') rows.append( "" f'{entry["rank"]}' f'{escape(entry["rank_name"])}' f'{escape(entry.get("model") or entry["rank_name"])}' f"{url_cell}" f"{fmt_score(entry['overall'])}" f"{''.join(task_cells)}" "" ) return f"""

Overall Leaderboard

{"".join("" for _ in TASKS)} {"".join(header_top)}{"".join(header_bottom)}{"".join(rows)}
""" def render_home() -> str: return f"{render_rank_strip(RANKED_OVERALL)}{render_home_table(RANKED_OVERALL)}" def render_task_title(task: dict[str, Any]) -> str: return f"""

Task : {escape(task["label"])}

""" def render_task_table(task: dict[str, Any], dataset_id: str) -> str: ranked_entries = sort_task(ENTRIES, task, dataset_id) active_label = "Overall" if dataset_id != "Overall": active_label = next( (dataset["label"] for dataset in task["datasets"] if dataset["id"] == dataset_id), dataset_id, ) rows = [] for entry in ranked_entries: numeric_value = entry["task_overall"][task["id"]] if dataset_id == "Overall" else metric_value(entry, task["id"], dataset_id) display_value = fmt_score(numeric_value) if dataset_id == "Overall" else metric_display(entry, task["id"], dataset_id) rows.append( "" f'{entry["rank"]}' f'{escape(entry["rank_name"])}' f'{escape(entry.get("model") or entry["rank_name"])}' f'{escape(display_value)}' "" ) return f"""
{"".join(rows)}
Rank RankName Model {escape(active_label)}
""" def update_view(active_view: str, current_dataset: str | None) -> tuple[Any, Any, str, str, str]: if active_view == HOME_VIEW: first_task = TASKS[0] return ( gr.update(visible=True), gr.update(visible="hidden"), gr.update(choices=dataset_choices(first_task["id"]), value="Overall"), render_task_title(first_task), first_task["metricLabel"], render_task_table(first_task, "Overall"), ) task = TASK_MAP[active_view] choices = dataset_choices(active_view) dataset_id = current_dataset if current_dataset in choices else "Overall" return ( gr.update(visible="hidden"), gr.update(visible=True), gr.update(choices=choices, value=dataset_id), render_task_title(task), task["metricLabel"], render_task_table(task, dataset_id), ) CUSTOM_CSS = """ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&display=swap'); :root { --bg: #f2f4f8; --bg-strong: #dde5f2; --panel: rgba(255, 255, 255, 0.84); --panel-strong: #ffffff; --text: #0c1730; --muted: #66748b; --line: rgba(12, 23, 48, 0.14); --primary: #0e3a8a; --accent: #c56b12; --pending: #74674a; --pending-bg: #f4ecd8; --success: #0c8f61; --danger: #b8612f; --shadow: 0 14px 38px rgba(12, 23, 48, 0.08); } html, body, .gradio-container { margin: 0 !important; min-height: 100vh; font-family: "Noto Sans KR", "Space Grotesk", sans-serif !important; color: var(--text); background: radial-gradient(circle at 15% 0%, #dce8ff 0%, transparent 32%), radial-gradient(circle at 95% 5%, #ffe4cf 0%, transparent 24%), linear-gradient(180deg, #f8fbff 0%, #eef2f7 100%) !important; } .gradio-container { max-width: 100% !important; } .app-root { position: relative; max-width: 1440px; margin: 0 auto; padding: 24px 20px 56px; overflow-x: auto; -webkit-overflow-scrolling: touch; } .bg-orb { position: fixed; border-radius: 999px; filter: blur(70px); opacity: 0.45; pointer-events: none; z-index: 0; } .orb-1 { width: 280px; height: 280px; background: #a7c5ff; top: -110px; left: -80px; } .orb-2 { width: 240px; height: 240px; background: #ffd2a4; top: -70px; right: -60px; } .layout-row { position: relative; z-index: 1; flex-wrap: nowrap !important; gap: 24px; align-items: flex-start; min-width: 1180px; } .sidebar-panel { flex: 0 0 260px !important; width: 260px !important; min-width: 260px !important; max-width: 260px !important; position: sticky; top: 20px; align-self: start; border: 1px solid var(--line); border-radius: 26px; background: rgba(7, 17, 40, 0.95); color: #ffffff; padding: 22px 18px; box-shadow: var(--shadow); } .sidebar-head, .sidebar-head * { color: #ffffff !important; } .sidebar-head { margin-bottom: 26px; } .sidebar-kicker, .kicker { margin: 0; letter-spacing: 0.12em; text-transform: uppercase; font-size: 12px; } .sidebar-kicker { color: rgba(255, 255, 255, 0.82) !important; } .sidebar-head h1 { color: #ffffff !important; } .sidebar-head h1 { margin: 8px 0 0; font-size: 34px; line-height: 0.95; color: #ffffff; } .content-panel { flex: 1 1 auto !important; min-width: 0; } .hero { margin-bottom: 20px; } .hero .kicker { font-size: 14px !important; letter-spacing: 0.18em; color: var(--muted) !important; } .hero-topline { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; text-align: center; } .hero-topline > div { flex: 1; } .hero h2 { margin: 8px 0 6px; font-family: "Space Grotesk", sans-serif; font-size: clamp(56px, 7vw, 80px) !important; line-height: 1.02; color: var(--text); font-weight: 700; } .desc { margin: 0 auto; max-width: 760px; color: var(--muted); font-size: 17px; } .card { background: var(--panel); border: 1px solid var(--line); border-radius: 24px; box-shadow: var(--shadow); backdrop-filter: blur(8px); } .section-card { padding: 18px; } .section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 12px; } .section-head h3, .task-title { margin: 0; font-size: 28px; font-family: "Space Grotesk", sans-serif; } .rank-strip-list { display: grid; grid-template-columns: repeat(6, minmax(120px, 1fr)); gap: 10px; } .rank-pill { border: 1px solid var(--line); border-radius: 16px; padding: 12px; background: linear-gradient(135deg, #ffffff 0%, var(--bg-strong) 100%); } .rank-badge-wrap { min-height: 28px; } .rank-badge { display: inline-flex !important; align-items: center; justify-content: center; min-width: 34px; height: 34px; padding: 0 10px; border-radius: 999px; background: var(--primary); color: #ffffff !important; font-size: 13px; font-weight: 700; line-height: 1; } .rank-badge-image { display: block; width: auto; height: 28px; } .rank-name { display: block; margin-top: 8px; font-weight: 700; color: var(--text) !important; } .table-scroll { overflow-x: auto; overflow-y: visible; border-radius: 0; border: 1px solid #ffffff !important; background: var(--panel-strong); } table { width: 100%; min-width: 1080px; border-collapse: collapse; border: 1px solid #ffffff !important; } .task-performance-table { table-layout: fixed; } thead th { background: #e8edf6; border-bottom: 1px solid #ffffff !important; white-space: nowrap; } thead tr:first-child th.grouped { text-align: center; } th, td { padding: 12px 14px; text-align: left; border-bottom: 1px solid #ffffff !important; border-right: 1px solid #ffffff !important; font-size: 14px; } th:first-child, td:first-child { border-left: 1px solid #ffffff !important; } th:last-child, td:last-child { border-right: 0; } .table-scroll table, .table-scroll thead, .table-scroll tbody, .table-scroll tr, .table-scroll th, .table-scroll td { border-color: #ffffff !important; } tbody tr:hover { background: #f8fbff; } .rank-col { font-family: "Space Grotesk", sans-serif; font-weight: 700; width: 72px; } .col-rank { width: 96px; } .col-rankname { width: 240px; } .col-model { width: 320px; } .url-link { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; } .url-link img { display: block; width: 18px; height: 18px; } .metric-good { color: var(--success); font-weight: 700; } .metric-bad { color: var(--danger); font-weight: 700; } .muted { color: var(--muted); } .task-filters { display: flex; flex-wrap: nowrap; align-items: flex-start; gap: 12px; margin-top: 12px; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 6px; background: transparent !important; border: 0 !important; box-shadow: none !important; } .task-filters .dataset-wrap { flex: 0 0 620px; min-width: 620px; } .task-filters .metric-wrap { flex: 0 0 340px; min-width: 340px; } .task-filters .dataset-wrap, .task-filters .metric-wrap, .task-filters .dataset-wrap > div, .task-filters .metric-wrap > div { background: transparent !important; border: 0 !important; box-shadow: none !important; } /* Keep Dataset / Metric columns pinned to the same top baseline. */ .task-filters .dataset-wrap, .task-filters .metric-wrap { align-self: flex-start !important; justify-self: flex-start !important; margin-top: 0 !important; padding-top: 0 !important; } .task-view-shell .filter-title { margin: 0 0 6px 0 !important; font-size: 13px !important; color: var(--muted) !important; line-height: 1.2 !important; } #taskTableMount { margin-top: 18px; } .task-menu-radio { gap: 8px; background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important; } .task-menu-radio > div, .task-menu-radio .block, .task-menu-radio .gradio-radio, .task-menu-radio .form { background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important; } .task-menu-radio label > span, .task-menu-radio label > div, .task-menu-radio label .wrap { white-space: pre-line !important; } .task-menu-radio label:has(input[type="radio"]) { width: 100%; margin: 0 !important; border: 1px solid rgba(255, 255, 255, 0.1) !important; background: rgba(255, 255, 255, 0.05) !important; color: #f5f7fb !important; border-radius: 14px !important; padding: 12px 14px !important; cursor: pointer !important; min-height: 72px; align-content: center; box-shadow: none !important; transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease; } .task-menu-radio label:has(input[type="radio"]):hover { background: rgba(255, 255, 255, 0.1) !important; } .task-menu-radio label:has(input[type="radio"]:checked) { background: linear-gradient(135deg, #fcf7eb 0%, #dfeaff 100%) !important; color: var(--text) !important; border-color: transparent !important; } .task-menu-radio input[type="radio"] { display: none !important; } .task-menu-radio label span, .task-menu-radio label div, .task-menu-radio label p { color: inherit !important; } .task-menu-radio label:has(input[type="radio"]) span, .task-menu-radio label:has(input[type="radio"]) div { color: #f5f7fb !important; } .task-menu-radio label:has(input[type="radio"]:checked) span, .task-menu-radio label:has(input[type="radio"]:checked) div { color: var(--text) !important; } .task-menu-radio .wrap, .task-menu-radio label span:last-child { opacity: 0.72; font-size: 12px; } .task-view-shell .gradio-radio, .task-view-shell .gradio-textbox { margin: 0 !important; min-width: 0 !important; background: transparent !important; border: 0 !important; box-shadow: none !important; } .task-view-shell .gradio-radio label, .task-view-shell .gradio-textbox label { font-size: 13px !important; color: var(--muted) !important; } .task-view-shell .dataset-radio { background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important; margin-left: 0 !important; margin-bottom: 12px !important; max-width: 100% !important; width: 100% !important; } .task-view-shell .dataset-radio > div, .task-view-shell .dataset-radio .block, .task-view-shell .dataset-radio .form { background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important; display: grid !important; grid-template-columns: repeat(2, minmax(180px, 1fr)) !important; gap: 8px 10px !important; align-items: start !important; justify-content: start !important; min-height: 86px !important; align-content: start !important; max-width: 100% !important; width: 100% !important; } .task-view-shell .dataset-radio label:has(input[type="radio"]) { margin: 0 !important; border: 1px solid var(--line) !important; background: rgba(255, 255, 255, 0.88) !important; color: var(--text) !important; border-radius: 10px !important; padding: 0 12px !important; height: 40px !important; min-height: 40px !important; width: auto !important; display: flex !important; align-items: center !important; box-shadow: none !important; justify-content: flex-start !important; font-size: 14px !important; line-height: 1.2 !important; } .task-view-shell .dataset-radio label:has(input[type="radio"]:checked) { background: linear-gradient(135deg, #fcf7eb 0%, #dfeaff 100%) !important; border-color: rgba(14, 58, 138, 0.22) !important; font-weight: 700 !important; } .task-view-shell .dataset-radio input[type="radio"] { display: none !important; } .task-view-shell .metric-field, .task-view-shell .dataset-field { align-self: flex-start !important; background: transparent !important; } /* Remove any residual top spacing on metric box so it aligns with Dataset. */ .task-view-shell .metric-wrap .metric-field, .task-view-shell .metric-wrap .gradio-textbox, .task-view-shell .metric-wrap .gradio-textbox > div, .task-view-shell .metric-wrap .gradio-textbox .block, .task-view-shell .metric-wrap .gradio-textbox .form, .task-view-shell .metric-wrap .gradio-textbox .wrap { margin-top: 0 !important; padding-top: 0 !important; } .task-view-shell .metric-field > div, .task-view-shell .dataset-field > div { background: transparent !important; } .task-view-shell .metric-field .gradio-textbox, .task-view-shell .metric-field .gradio-textbox > div, .task-view-shell .metric-field .gradio-textbox .block, .task-view-shell .metric-field .gradio-textbox .form { background: transparent !important; border: 0 !important; box-shadow: none !important; } .task-view-shell .metric-field, .task-view-shell .metric-field .gradio-textbox, .task-view-shell .metric-field .gradio-textbox .wrap { width: 100% !important; max-width: none !important; } .task-view-shell .metric-field .gradio-textbox .wrap, .task-view-shell .metric-field .gradio-textbox textarea, .task-view-shell .metric-field .gradio-textbox input { height: 40px !important; min-height: 40px !important; width: 100% !important; } .task-view-shell input, .task-view-shell textarea, .task-view-shell .wrap-inner, .task-view-shell button.secondary-down-arrow, .task-view-shell .gradio-textbox .wrap { border-radius: 12px !important; } .task-view-shell .gradio-textbox .wrap, .task-view-shell .gradio-textbox textarea, .task-view-shell .gradio-textbox input { border: 1px solid var(--line) !important; background: rgba(255, 255, 255, 0.88) !important; color: var(--text) !important; } @media (max-width: 1280px) { .layout-row { min-width: 1120px; } .task-filters .dataset-wrap { flex-basis: 560px; min-width: 560px; } .task-filters .metric-wrap { flex-basis: 320px; min-width: 320px; } .task-view-shell .dataset-radio > div, .task-view-shell .dataset-radio .block, .task-view-shell .dataset-radio .form { grid-template-columns: repeat(2, minmax(150px, 1fr)) !important; } } @media (max-width: 980px) { .layout-row { min-width: 1040px; } .rank-strip-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } .task-view-shell .dataset-radio > div, .task-view-shell .dataset-radio .block, .task-view-shell .dataset-radio .form { grid-template-columns: repeat(2, minmax(140px, 1fr)) !important; } } @media (max-width: 720px) { .app-root { padding: 16px 14px 40px; } .hero-topline { flex-direction: column; align-items: center; } .hero h2 { font-size: 40px; } .rank-strip-list { grid-template-columns: 1fr; } .task-view-shell .dataset-radio > div, .task-view-shell .dataset-radio .block, .task-view-shell .dataset-radio .form { grid-template-columns: 1fr !important; } } """ def build_app() -> gr.Blocks: with gr.Blocks(title="Ko-Speech-Eval Leaderboard", fill_width=True) as demo: with gr.Column(elem_classes=["app-root"]): gr.HTML('
') with gr.Row(elem_classes=["layout-row"]): with gr.Column(scale=0, min_width=260, elem_classes=["sidebar-panel"]): gr.HTML( """ """ ) menu = gr.Radio( choices=menu_choices(), value=HOME_VIEW, show_label=False, container=False, elem_classes=["task-menu", "task-menu-radio"], ) with gr.Column(scale=1, elem_classes=["content-panel"]): gr.HTML( """

Korean Audio Language benchmark

Leaderboard for KoALa

""" ) home_view = gr.HTML(render_home(), visible=True) with gr.Column(visible="hidden", elem_classes=["task-view-shell", "section-card", "card"]) as task_view: task_title = gr.HTML() with gr.Row(elem_classes=["task-filters"]): with gr.Column(scale=3, min_width=420, elem_classes=["dataset-wrap"]): gr.HTML('

Dataset

') dataset_dropdown = gr.Radio( choices=dataset_choices(TASKS[0]["id"]), value="Overall", show_label=False, elem_classes=["dataset-field", "dataset-radio"], ) with gr.Column(scale=2, min_width=280, elem_classes=["metric-wrap"]): gr.HTML('

Metric

') metric_text = gr.Textbox( show_label=False, interactive=False, elem_classes=["metric-field"], ) task_table = gr.HTML() menu.change( fn=update_view, inputs=[menu, dataset_dropdown], outputs=[home_view, task_view, dataset_dropdown, task_title, metric_text, task_table], ) dataset_dropdown.change( fn=update_view, inputs=[menu, dataset_dropdown], outputs=[home_view, task_view, dataset_dropdown, task_title, metric_text, task_table], ) demo.load( fn=update_view, inputs=[menu, dataset_dropdown], outputs=[home_view, task_view, dataset_dropdown, task_title, metric_text, task_table], ) return demo if __name__ == "__main__": app = build_app() app.launch(css=CUSTOM_CSS)