openhands openhands commited on
Commit
4f4eb00
·
1 Parent(s): 76b9525

Refactor Winners by Category to single unified table

Browse files

- Changed from multiple category cards to a single horizontal table
- Format: | [emoji] Category | Score | ... for each category
- Display only model name (removed SDK version)
- Deduplicate by model name, keeping highest score
- Updated CSS for new table layout with responsive design

Co-authored-by: openhands <openhands@all-hands.dev>

Files changed (2) hide show
  1. content.py +78 -121
  2. ui_components.py +67 -67
content.py CHANGED
@@ -966,187 +966,144 @@ h3 .header-link-icon {
966
 
967
  /* ====== Winners by Category Section ====== */
968
  .winners-by-category-container {
969
- display: grid;
970
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
971
- gap: 20px;
972
  margin: 20px 0;
 
973
  }
974
 
975
- .winner-category-card {
 
 
 
976
  background: #fff;
977
  border: 1px solid #032629;
978
  border-radius: 12px;
979
- padding: 16px;
980
- transition: box-shadow 0.2s ease;
981
  }
982
 
983
- .dark .winner-category-card {
984
  background: rgba(250, 242, 233, 0.05);
985
  border-color: #9fead1;
986
  }
987
 
988
- .winner-category-card:hover {
989
- box-shadow: 0 4px 12px rgba(3, 38, 41, 0.15);
990
- }
991
-
992
- .dark .winner-category-card:hover {
993
- box-shadow: 0 4px 12px rgba(159, 234, 209, 0.2);
994
- }
995
-
996
- .winner-category-header {
997
- display: flex;
998
- align-items: center;
999
- gap: 12px;
1000
- margin-bottom: 16px;
1001
- padding-bottom: 12px;
1002
- border-bottom: 2px solid #F0529C;
1003
- }
1004
-
1005
- .dark .winner-category-header {
1006
- border-bottom-color: #0FCB8C;
1007
  }
1008
 
1009
- .winner-category-icon {
1010
- width: 32px;
1011
- height: 32px;
1012
  }
1013
 
1014
- .winner-category-header h3 {
1015
- margin: 0;
1016
- font-size: 16px;
1017
  font-weight: 700;
 
1018
  color: #032629;
 
 
 
1019
  }
1020
 
1021
- .dark .winner-category-header h3 {
1022
  color: #fff;
 
 
1023
  }
1024
 
1025
- .winner-table {
1026
- width: 100%;
1027
- border-collapse: collapse;
1028
- font-size: 13px;
1029
- }
1030
-
1031
- .winner-table thead tr {
1032
- border-bottom: 1px solid #ddd;
1033
- }
1034
-
1035
- .dark .winner-table thead tr {
1036
- border-bottom-color: #4a5a5a;
1037
- }
1038
-
1039
- .winner-table th {
1040
- padding: 8px 4px;
1041
- text-align: left;
1042
- font-weight: 600;
1043
- color: #667876;
1044
- font-size: 11px;
1045
- text-transform: uppercase;
1046
  }
1047
 
1048
- .dark .winner-table th {
1049
- color: #9fead1;
 
 
 
1050
  }
1051
 
1052
- .winner-table td {
1053
- padding: 10px 4px;
1054
  vertical-align: middle;
1055
  border-bottom: 1px solid #eee;
1056
  }
1057
 
1058
- .dark .winner-table td {
1059
  border-bottom-color: #2a3a3a;
1060
  }
1061
 
1062
- .winner-table tbody tr:last-child td {
1063
  border-bottom: none;
1064
  }
1065
 
1066
- .winner-table .rank-col {
1067
- width: 32px;
1068
- text-align: center;
1069
- font-size: 14px;
1070
  }
1071
 
1072
- .winner-table .score-col {
1073
- width: 60px;
1074
- text-align: right;
1075
- color: #F0529C;
1076
- font-size: 14px;
1077
- }
1078
-
1079
- .dark .winner-table .score-col {
1080
- color: #0FCB8C;
1081
- }
1082
-
1083
- .system-info {
1084
- display: flex;
1085
- align-items: center;
1086
- gap: 10px;
1087
  }
1088
 
1089
- .company-logo-small {
1090
- width: 20px;
1091
- height: 20px;
1092
- flex-shrink: 0;
1093
- }
1094
-
1095
- .system-details {
1096
- display: flex;
1097
- flex-direction: column;
1098
- overflow: hidden;
1099
- }
1100
-
1101
- .sdk-name {
1102
- font-weight: 600;
1103
- color: #032629;
1104
- font-size: 13px;
1105
  white-space: nowrap;
1106
- overflow: hidden;
1107
- text-overflow: ellipsis;
 
1108
  }
1109
 
1110
- .dark .sdk-name {
1111
  color: #fff;
1112
  }
1113
 
1114
- .model-name {
1115
- font-size: 11px;
1116
- color: #667876;
1117
- white-space: nowrap;
1118
- overflow: hidden;
1119
- text-overflow: ellipsis;
 
1120
  }
1121
 
1122
- .dark .model-name {
1123
- color: #9fead1;
 
1124
  }
1125
 
1126
- .no-data {
1127
- text-align: center;
1128
- color: #999;
1129
- font-style: italic;
1130
- padding: 20px !important;
 
 
 
 
1131
  }
1132
 
1133
  /* Responsive adjustments for winners section */
1134
- @media (max-width: 600px) {
1135
- .winners-by-category-container {
1136
- grid-template-columns: 1fr;
 
 
 
 
 
1137
  }
1138
 
1139
- .winner-category-card {
1140
- padding: 12px;
 
 
1141
  }
1142
 
1143
- .winner-table .score-col,
1144
- .winner-table .rank-col {
1145
- font-size: 12px;
1146
  }
1147
 
1148
- .sdk-name {
1149
- font-size: 12px;
 
 
1150
  }
1151
 
1152
  .model-name {
 
966
 
967
  /* ====== Winners by Category Section ====== */
968
  .winners-by-category-container {
 
 
 
969
  margin: 20px 0;
970
+ overflow-x: auto;
971
  }
972
 
973
+ .winners-unified-table {
974
+ width: 100%;
975
+ border-collapse: collapse;
976
+ font-size: 13px;
977
  background: #fff;
978
  border: 1px solid #032629;
979
  border-radius: 12px;
980
+ overflow: hidden;
 
981
  }
982
 
983
+ .dark .winners-unified-table {
984
  background: rgba(250, 242, 233, 0.05);
985
  border-color: #9fead1;
986
  }
987
 
988
+ .winners-unified-table thead tr {
989
+ background: linear-gradient(to right, rgba(240, 82, 156, 0.1), rgba(15, 203, 140, 0.1));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
990
  }
991
 
992
+ .dark .winners-unified-table thead tr {
993
+ background: linear-gradient(to right, rgba(240, 82, 156, 0.2), rgba(15, 203, 140, 0.2));
 
994
  }
995
 
996
+ .winners-unified-table .category-header {
997
+ padding: 12px 8px;
998
+ text-align: center;
999
  font-weight: 700;
1000
+ font-size: 13px;
1001
  color: #032629;
1002
+ border-bottom: 2px solid #F0529C;
1003
+ border-right: 1px solid #eee;
1004
+ white-space: nowrap;
1005
  }
1006
 
1007
+ .dark .winners-unified-table .category-header {
1008
  color: #fff;
1009
+ border-bottom-color: #0FCB8C;
1010
+ border-right-color: #2a3a3a;
1011
  }
1012
 
1013
+ .winners-unified-table .category-header:last-child {
1014
+ border-right: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1015
  }
1016
 
1017
+ .winner-category-icon-small {
1018
+ width: 18px;
1019
+ height: 18px;
1020
+ vertical-align: middle;
1021
+ margin-right: 6px;
1022
  }
1023
 
1024
+ .winners-unified-table td {
1025
+ padding: 8px 6px;
1026
  vertical-align: middle;
1027
  border-bottom: 1px solid #eee;
1028
  }
1029
 
1030
+ .dark .winners-unified-table td {
1031
  border-bottom-color: #2a3a3a;
1032
  }
1033
 
1034
+ .winners-unified-table tbody tr:last-child td {
1035
  border-bottom: none;
1036
  }
1037
 
1038
+ .winners-unified-table tbody tr:hover {
1039
+ background: rgba(240, 82, 156, 0.05);
 
 
1040
  }
1041
 
1042
+ .dark .winners-unified-table tbody tr:hover {
1043
+ background: rgba(15, 203, 140, 0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
1044
  }
1045
 
1046
+ .winners-unified-table .model-cell {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1047
  white-space: nowrap;
1048
+ color: #032629;
1049
+ font-weight: 500;
1050
+ border-right: none;
1051
  }
1052
 
1053
+ .dark .winners-unified-table .model-cell {
1054
  color: #fff;
1055
  }
1056
 
1057
+ .winners-unified-table .score-cell {
1058
+ text-align: right;
1059
+ font-weight: 700;
1060
+ color: #F0529C;
1061
+ padding-right: 12px;
1062
+ border-right: 1px solid #eee;
1063
+ min-width: 50px;
1064
  }
1065
 
1066
+ .dark .winners-unified-table .score-cell {
1067
+ color: #0FCB8C;
1068
+ border-right-color: #2a3a3a;
1069
  }
1070
 
1071
+ .winners-unified-table td:nth-last-child(1) {
1072
+ border-right: none;
1073
+ }
1074
+
1075
+ .company-logo-tiny {
1076
+ width: 16px;
1077
+ height: 16px;
1078
+ vertical-align: middle;
1079
+ margin-right: 6px;
1080
  }
1081
 
1082
  /* Responsive adjustments for winners section */
1083
+ @media (max-width: 900px) {
1084
+ .winners-unified-table {
1085
+ font-size: 11px;
1086
+ }
1087
+
1088
+ .winners-unified-table .category-header {
1089
+ font-size: 11px;
1090
+ padding: 8px 4px;
1091
  }
1092
 
1093
+ .winner-category-icon-small {
1094
+ width: 14px;
1095
+ height: 14px;
1096
+ margin-right: 4px;
1097
  }
1098
 
1099
+ .winners-unified-table td {
1100
+ padding: 6px 4px;
 
1101
  }
1102
 
1103
+ .company-logo-tiny {
1104
+ width: 14px;
1105
+ height: 14px;
1106
+ margin-right: 4px;
1107
  }
1108
 
1109
  .model-name {
ui_components.py CHANGED
@@ -426,14 +426,15 @@ CATEGORY_DEFINITIONS = [
426
 
427
  def get_winners_by_category(df: pd.DataFrame, top_n: int = 5) -> dict:
428
  """
429
- Extract the top N systems for each category based on their score.
 
430
 
431
  Args:
432
  df: The full leaderboard DataFrame with all scores
433
- top_n: Number of top systems to return per category (default: 5)
434
 
435
  Returns:
436
- Dictionary mapping category name to list of top systems with their scores
437
  """
438
  winners = {}
439
 
@@ -452,72 +453,87 @@ def get_winners_by_category(df: pd.DataFrame, top_n: int = 5) -> dict:
452
  winners[cat_name] = []
453
  continue
454
 
455
- # Sort by score descending and take top N
456
- cat_df = cat_df.sort_values(score_col, ascending=False).head(top_n)
 
 
 
 
 
 
 
 
 
 
 
 
457
 
458
  # Extract relevant info
459
- top_systems = []
460
  for rank, (_, row) in enumerate(cat_df.iterrows(), 1):
461
- system_info = {
462
  "rank": rank,
463
- "sdk_version": row.get("SDK Version", "Unknown"),
464
  "language_model": row.get("Language Model", "Unknown"),
465
  "score": row[score_col],
466
  }
467
- top_systems.append(system_info)
468
 
469
- winners[cat_name] = top_systems
470
 
471
  return winners
472
 
473
 
474
  def create_winners_by_category_html(df: pd.DataFrame, top_n: int = 5) -> str:
475
  """
476
- Create an HTML table displaying the top N winners for each category.
 
477
 
478
  Args:
479
  df: The full leaderboard DataFrame
480
- top_n: Number of top systems to show per category
481
 
482
  Returns:
483
  HTML string with the winners table
484
  """
485
  winners = get_winners_by_category(df, top_n)
486
 
487
- # Build the HTML table
488
  html_parts = ['<div class="winners-by-category-container">']
 
489
 
490
- # Create a card for each category
 
491
  for cat_def in CATEGORY_DEFINITIONS:
492
  cat_name = cat_def["name"]
493
  icon_file = cat_def["icon"]
494
  icon_uri = get_svg_as_data_uri(f"assets/{icon_file}")
495
-
496
- top_systems = winners.get(cat_name, [])
497
-
498
  html_parts.append(f'''
499
- <div class="winner-category-card">
500
- <div class="winner-category-header">
501
- <img src="{icon_uri}" alt="{cat_name}" class="winner-category-icon">
502
- <h3>{cat_name}</h3>
503
- </div>
504
- <table class="winner-table">
505
- <thead>
506
- <tr>
507
- <th class="rank-col">#</th>
508
- <th class="system-col">System</th>
509
- <th class="score-col">Score</th>
510
- </tr>
511
- </thead>
512
- <tbody>
513
  ''')
 
 
 
 
 
 
514
 
515
- if top_systems:
516
- for system in top_systems:
517
- rank = system["rank"]
518
- sdk_version = system["sdk_version"]
519
- language_model = system["language_model"]
520
- score = system["score"]
 
 
 
 
 
 
 
 
521
 
522
  # Get company logo for the model
523
  company_info = get_company_from_model(language_model)
@@ -526,46 +542,30 @@ def create_winners_by_category_html(df: pd.DataFrame, top_n: int = 5) -> str:
526
  # Format model name - clean it if it's a list
527
  if isinstance(language_model, list):
528
  language_model = language_model[0] if language_model else "Unknown"
529
- model_display = str(language_model).split('/')[-1] # Remove provider prefix
530
 
531
  # Add medal emoji for top 3
532
- rank_display = rank
533
  if rank == 1:
534
- rank_display = "🥇"
535
  elif rank == 2:
536
- rank_display = "🥈"
537
  elif rank == 3:
538
- rank_display = "🥉"
539
 
540
  html_parts.append(f'''
541
- <tr>
542
- <td class="rank-col">{rank_display}</td>
543
- <td class="system-col">
544
- <div class="system-info">
545
- <img src="{logo_uri}" alt="{company_info['name']}" class="company-logo-small">
546
- <div class="system-details">
547
- <span class="sdk-name">{sdk_version}</span>
548
- <span class="model-name">{model_display}</span>
549
- </div>
550
- </div>
551
- </td>
552
- <td class="score-col"><strong>{score:.1f}</strong></td>
553
- </tr>
554
  ''')
555
- else:
556
- html_parts.append('''
557
- <tr>
558
- <td colspan="3" class="no-data">No submissions yet</td>
559
- </tr>
560
- ''')
561
 
562
- html_parts.append('''
563
- </tbody>
564
- </table>
565
- </div>
566
- ''')
567
 
568
- html_parts.append('</div>')
569
 
570
  return ''.join(html_parts)
571
 
 
426
 
427
  def get_winners_by_category(df: pd.DataFrame, top_n: int = 5) -> dict:
428
  """
429
+ Extract the top N models for each category based on their score.
430
+ Deduplicates by model name, keeping only the highest score per model.
431
 
432
  Args:
433
  df: The full leaderboard DataFrame with all scores
434
+ top_n: Number of top models to return per category (default: 5)
435
 
436
  Returns:
437
+ Dictionary mapping category name to list of top models with their scores
438
  """
439
  winners = {}
440
 
 
453
  winners[cat_name] = []
454
  continue
455
 
456
+ # Clean model names for deduplication
457
+ def clean_model_name(model):
458
+ if isinstance(model, list):
459
+ model = model[0] if model else "Unknown"
460
+ return str(model).split('/')[-1]
461
+
462
+ cat_df['_clean_model'] = cat_df['Language Model'].apply(clean_model_name)
463
+
464
+ # Deduplicate by model name, keeping highest score
465
+ cat_df = cat_df.sort_values(score_col, ascending=False)
466
+ cat_df = cat_df.drop_duplicates(subset=['_clean_model'], keep='first')
467
+
468
+ # Take top N after dedup
469
+ cat_df = cat_df.head(top_n)
470
 
471
  # Extract relevant info
472
+ top_models = []
473
  for rank, (_, row) in enumerate(cat_df.iterrows(), 1):
474
+ model_info = {
475
  "rank": rank,
 
476
  "language_model": row.get("Language Model", "Unknown"),
477
  "score": row[score_col],
478
  }
479
+ top_models.append(model_info)
480
 
481
+ winners[cat_name] = top_models
482
 
483
  return winners
484
 
485
 
486
  def create_winners_by_category_html(df: pd.DataFrame, top_n: int = 5) -> str:
487
  """
488
+ Create a single HTML table displaying the top N winners for each category side by side.
489
+ Format: | [emoji] Category | Score | [emoji] Category | Score | ...
490
 
491
  Args:
492
  df: The full leaderboard DataFrame
493
+ top_n: Number of top models to show per category
494
 
495
  Returns:
496
  HTML string with the winners table
497
  """
498
  winners = get_winners_by_category(df, top_n)
499
 
500
+ # Build a single unified table
501
  html_parts = ['<div class="winners-by-category-container">']
502
+ html_parts.append('<table class="winners-unified-table">')
503
 
504
+ # Header row with category icons and names
505
+ html_parts.append('<thead><tr>')
506
  for cat_def in CATEGORY_DEFINITIONS:
507
  cat_name = cat_def["name"]
508
  icon_file = cat_def["icon"]
509
  icon_uri = get_svg_as_data_uri(f"assets/{icon_file}")
 
 
 
510
  html_parts.append(f'''
511
+ <th class="category-header" colspan="2">
512
+ <img src="{icon_uri}" alt="{cat_name}" class="winner-category-icon-small">
513
+ {cat_name}
514
+ </th>
 
 
 
 
 
 
 
 
 
 
515
  ''')
516
+ html_parts.append('</tr></thead>')
517
+
518
+ # Body rows - one row per rank position
519
+ html_parts.append('<tbody>')
520
+ for rank in range(1, top_n + 1):
521
+ html_parts.append('<tr>')
522
 
523
+ for cat_def in CATEGORY_DEFINITIONS:
524
+ cat_name = cat_def["name"]
525
+ top_models = winners.get(cat_name, [])
526
+
527
+ # Find the model at this rank
528
+ model_at_rank = None
529
+ for m in top_models:
530
+ if m["rank"] == rank:
531
+ model_at_rank = m
532
+ break
533
+
534
+ if model_at_rank:
535
+ language_model = model_at_rank["language_model"]
536
+ score = model_at_rank["score"]
537
 
538
  # Get company logo for the model
539
  company_info = get_company_from_model(language_model)
 
542
  # Format model name - clean it if it's a list
543
  if isinstance(language_model, list):
544
  language_model = language_model[0] if language_model else "Unknown"
545
+ model_display = str(language_model).split('/')[-1]
546
 
547
  # Add medal emoji for top 3
548
+ rank_prefix = ""
549
  if rank == 1:
550
+ rank_prefix = "🥇 "
551
  elif rank == 2:
552
+ rank_prefix = "🥈 "
553
  elif rank == 3:
554
+ rank_prefix = "🥉 "
555
 
556
  html_parts.append(f'''
557
+ <td class="model-cell">
558
+ <img src="{logo_uri}" alt="{company_info['name']}" class="company-logo-tiny">
559
+ {rank_prefix}{model_display}
560
+ </td>
561
+ <td class="score-cell">{score:.1f}</td>
 
 
 
 
 
 
 
 
562
  ''')
563
+ else:
564
+ html_parts.append('<td class="model-cell">-</td><td class="score-cell">-</td>')
 
 
 
 
565
 
566
+ html_parts.append('</tr>')
 
 
 
 
567
 
568
+ html_parts.append('</tbody></table></div>')
569
 
570
  return ''.join(html_parts)
571