Minette Kaunismäki commited on
Commit
3f9b0a5
·
1 Parent(s): 4527ab9

ui updates

Browse files
Files changed (4) hide show
  1. app.py +388 -677
  2. data/text_to_image.jsonl +0 -31
  3. requirements.txt +1 -2
  4. ui.py +998 -538
app.py CHANGED
@@ -124,10 +124,11 @@ button, a, label, input, select, textarea,
124
  .gradio-container * {
125
  -webkit-tap-highlight-color: transparent !important;
126
  }
127
- html, body, .gradio-container, .main {
128
- background: var(--pruna-bg-primary) !important;
129
- color: var(--pruna-text-primary) !important;
130
- font-family: var(--pruna-font) !important;
 
131
  }
132
 
133
  /* Subtle depth — not a marketing-site hero glow */
@@ -160,10 +161,9 @@ body, .gradio-container {
160
  .main-tabs,
161
  .main-tabs .tab-wrapper,
162
  .main-tabs .tabitem,
163
- .benchmark-catalogue,
164
- .benchmark-detail,
165
- .benchmark-panel,
166
  .leaderboard-controls,
 
 
167
  .view-filters {
168
  max-width: 100% !important;
169
  }
@@ -189,17 +189,26 @@ body, .gradio-container {
189
  position: relative;
190
  display: flex;
191
  flex-direction: column;
192
- align-items: center;
193
  width: 100%;
194
- margin: 8px 0 16px;
195
- padding: 8px 48px 0;
196
  text-align: center;
197
  }
 
 
 
 
 
 
 
198
  .app-header .theme-toggle,
199
  button.theme-toggle {
200
- position: absolute;
201
- top: 8px;
202
- right: 0;
 
 
203
  display: inline-flex !important;
204
  align-items: center !important;
205
  justify-content: center !important;
@@ -262,9 +271,11 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
262
  align-items: center;
263
  justify-content: center;
264
  gap: 12px;
 
 
265
  width: auto !important;
266
  max-width: 100%;
267
- margin: 0 auto;
268
  }
269
  .app-header-logo {
270
  display: block !important;
@@ -275,16 +286,43 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
275
  flex: 0 0 auto;
276
  }
277
 
278
- /* —— Page tabs: centered underline nav (Gradio 5 uses tab-wrapper/container) —— */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  .tabs {
280
  gap: 0 !important;
281
  }
282
  .main-tabs > .tab-wrapper {
 
283
  height: auto !important;
284
  min-height: 0 !important;
285
  padding: 0 !important;
286
- margin: 0 0 22px !important;
287
  justify-content: center !important;
 
288
  }
289
  .main-tabs .tab-container {
290
  height: auto !important;
@@ -329,6 +367,7 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
329
  .main-tabs .tabitem {
330
  padding: 8px 0 0 !important;
331
  border-radius: 0 !important;
 
332
  }
333
 
334
  /* —— About: two columns on desktop, stacked on phone —— */
@@ -384,17 +423,14 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
384
  .gradio-container .row,
385
  .gradio-container .column,
386
  .gradio-container .wrap,
387
- .benchmark-catalogue,
388
- .benchmark-detail,
389
- .benchmark-panel,
390
  .leaderboard-controls,
391
  .view-filters {
392
  min-width: 0 !important;
393
  max-width: 100% !important;
394
  }
395
  .app-header {
396
- padding: 4px 40px 0 8px;
397
- margin: 4px 0 12px;
398
  }
399
  .app-header-logo {
400
  width: 36px !important;
@@ -409,7 +445,7 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
409
  padding: 0 8px !important;
410
  }
411
  .main-tabs > .tab-wrapper {
412
- margin: 0 0 14px !important;
413
  }
414
  .main-tabs .tab-container {
415
  flex-wrap: wrap !important;
@@ -445,30 +481,6 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
445
  .community-footer-links {
446
  gap: 10px 14px;
447
  }
448
- .home-snapshots-grid {
449
- grid-template-columns: 1fr;
450
- gap: 22px;
451
- }
452
- .home-model {
453
- overflow-wrap: anywhere;
454
- }
455
- .benchmark-catalogue-row,
456
- .benchmark-catalogue-row.row,
457
- .benchmark-catalogue-row .form {
458
- flex-direction: column !important;
459
- flex-wrap: nowrap !important;
460
- align-items: stretch !important;
461
- height: auto !important;
462
- flex-grow: 0 !important;
463
- }
464
- .benchmark-catalogue-row .benchmark-card-col {
465
- width: 100% !important;
466
- flex: 0 0 auto !important;
467
- align-self: stretch !important;
468
- min-width: 0 !important;
469
- min-height: 0 !important;
470
- height: auto !important;
471
- }
472
  .leaderboard-controls,
473
  .leaderboard-controls.row,
474
  .leaderboard-controls .form,
@@ -573,15 +585,28 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
573
  }
574
  }
575
 
576
- /* —— Benchmark view menu (Leaderboards / Pareto plots / Compare) —— */
 
 
 
 
 
 
 
 
 
577
  .view-filters {
578
  display: flex !important;
579
  flex-wrap: wrap !important;
580
  align-items: end !important;
581
- gap: 10px !important;
582
- margin: 0 0 8px;
 
583
  }
584
- .view-filters > div {
 
 
 
585
  min-width: 0 !important;
586
  }
587
  .view-filters > .block,
@@ -591,286 +616,156 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
591
  background: transparent !important;
592
  box-shadow: none !important;
593
  padding: 0 !important;
 
594
  --block-border-width: 0 !important;
595
  }
596
  .view-filters label {
 
 
 
597
  color: var(--pruna-text-muted) !important;
598
  font-size: 0.8rem !important;
599
  font-weight: 500 !important;
 
600
  }
601
- .view-filters .wrap,
602
- .view-filters .wrap-inner,
603
- .view-filters .secondary-wrap {
604
- min-height: 40px !important;
605
- border: 1px solid var(--pruna-input-border) !important;
606
- border-radius: 10px !important;
607
- background: var(--pruna-input-bg) !important;
608
- box-shadow: none !important;
609
- }
610
- .view-title,
611
- .view-title.block,
612
- .view-title .padded {
613
- border: none !important;
614
- background: transparent !important;
615
- box-shadow: none !important;
616
- padding: 0 !important;
617
- margin: 0 0 8px !important;
618
- }
619
- .view-title h1,
620
- .view-title .prose h1,
621
- .gradio-container .view-title h1 {
622
- margin: 0.35rem 0 0.4rem !important;
623
- font-size: 1.45rem !important;
624
- }
625
- .workspace-back-btn,
626
- .workspace-back-btn.block,
627
- .workspace-back-btn .padded,
628
- .benchmark-back-btn,
629
- .benchmark-back-btn.block,
630
- .benchmark-back-btn .padded {
631
- width: fit-content !important;
632
  min-width: 0 !important;
633
- margin: 0 0 4px !important;
634
- padding: 0 !important;
635
  border: none !important;
636
  background: transparent !important;
637
  box-shadow: none !important;
 
638
  }
639
- .workspace-back-btn button,
640
- .workspace-back-btn button.secondary,
641
- .workspace-back-btn button.lg,
642
- .workspace-back-btn button.sm,
643
- .benchmark-back-btn button,
644
- .benchmark-back-btn button.secondary,
645
- .benchmark-back-btn button.lg,
646
- .benchmark-back-btn button.sm {
647
- display: inline-flex !important;
648
  align-items: center !important;
649
- width: auto !important;
650
- min-width: 0 !important;
651
- min-height: 0 !important;
652
- height: auto !important;
653
- margin: 0 !important;
654
- padding: 0 !important;
655
- border: none !important;
656
- border-radius: 0 !important;
657
- background: transparent !important;
658
- box-shadow: none !important;
659
- color: var(--pruna-text-muted) !important;
660
- font-family: var(--pruna-font) !important;
661
- font-size: 0.9rem !important;
662
- font-weight: 500 !important;
663
- letter-spacing: -0.01em !important;
664
- line-height: 1.4 !important;
665
- }
666
- .workspace-back-btn button:hover,
667
- .benchmark-back-btn button:hover {
668
- color: var(--pruna-link) !important;
669
- background: transparent !important;
670
- border: none !important;
671
- box-shadow: none !important;
672
- }
673
- .benchmark-view-menu,
674
- .benchmark-view-menu.block,
675
- .benchmark-view-menu .form {
676
- border: none !important;
677
- background: transparent !important;
678
  box-shadow: none !important;
679
- padding: 0 !important;
680
- margin: 4px 0 20px !important;
681
- max-width: 100% !important;
682
- min-width: 0 !important;
683
- }
684
- .benchmark-view-menu .wrap {
685
- display: grid !important;
686
- grid-template-columns: repeat(3, minmax(0, 1fr));
687
- gap: 4px !important;
688
- width: 100%;
689
- max-width: 100%;
690
- min-width: 0;
691
- padding: 4px !important;
692
- border: 1px solid var(--pruna-menu-border) !important;
693
- border-radius: 12px !important;
694
- background: var(--pruna-menu-bg) !important;
695
  }
696
- .benchmark-view-menu label {
697
  display: flex !important;
 
698
  align-items: center !important;
699
- justify-content: center !important;
 
 
700
  margin: 0 !important;
701
- padding: 10px 12px !important;
702
  border: none !important;
703
- border-radius: 8px !important;
704
  background: transparent !important;
705
  box-shadow: none !important;
706
- color: var(--pruna-text-muted) !important;
707
- font-family: var(--pruna-font) !important;
708
- font-size: 0.9rem !important;
709
- font-weight: 600 !important;
710
- letter-spacing: -0.015em !important;
711
- cursor: pointer;
712
  }
713
- .benchmark-view-menu label:hover {
714
- color: var(--pruna-menu-hover-color) !important;
715
- background: var(--pruna-menu-hover-bg) !important;
 
 
 
716
  }
717
- .benchmark-view-menu label.selected {
718
- color: var(--pruna-menu-selected-color) !important;
719
- background: var(--pruna-menu-selected-bg) !important;
720
  }
721
- .benchmark-view-menu input[type="radio"] {
722
- position: absolute !important;
723
- opacity: 0 !important;
724
- width: 0 !important;
725
- height: 0 !important;
726
- margin: 0 !important;
727
- pointer-events: none !important;
728
  }
729
- .benchmark-view-menu label span {
730
- margin: 0 !important;
731
- color: inherit !important;
732
  }
733
- @media (max-width: 700px) {
734
- .benchmark-view-menu .wrap {
735
- grid-template-columns: repeat(3, minmax(0, 1fr));
736
- }
737
- .benchmark-view-menu label {
738
- padding: 8px 4px !important;
739
- font-size: 0.75rem !important;
740
- text-align: center;
741
- }
742
  }
743
-
744
- /* Surfaces */
745
- .benchmark-catalogue,
746
- .benchmark-catalogue .row,
747
- .benchmark-catalogue .column,
748
- .benchmark-catalogue .form,
749
- .benchmark-detail,
750
- .benchmark-detail .row,
751
- .benchmark-detail .column,
752
- .benchmark-detail .form,
753
- .benchmark-panel,
754
- .benchmark-panel .row,
755
- .benchmark-panel .column,
756
- .benchmark-panel .form {
757
- min-width: 0 !important;
758
- max-width: 100% !important;
759
  }
760
- .benchmark-catalogue.column,
761
- .benchmark-catalogue.block,
762
- .benchmark-catalogue .stretch,
763
- .benchmark-panel.column,
764
- .benchmark-panel.block,
765
- .benchmark-panel .stretch {
766
- flex-grow: 0 !important;
767
- height: auto !important;
768
- min-height: 0 !important;
769
- justify-content: flex-start !important;
770
  }
771
- .benchmark-catalogue-row,
772
- .benchmark-catalogue-row.row,
773
- .benchmark-catalogue-row .form {
774
- display: flex !important;
775
- flex-direction: row !important;
776
- flex-wrap: wrap !important;
777
  width: 100% !important;
778
- gap: 16px !important;
779
- align-items: stretch !important;
780
- align-content: flex-start !important;
781
- flex-grow: 0 !important;
782
- height: auto !important;
783
- }
784
- .benchmark-catalogue-row .benchmark-card-col {
785
- flex: 1 1 280px !important;
786
- align-self: stretch !important;
787
- min-width: 280px !important;
788
- max-width: 100% !important;
789
- height: auto !important;
790
- }
791
- .benchmark-card-col,
792
- .benchmark-card-col.block,
793
- .benchmark-card-col.column,
794
- .benchmark-card-col.stretch {
795
- display: flex !important;
796
- flex-direction: column !important;
797
- justify-content: flex-start !important;
798
- align-items: stretch !important;
799
- gap: 12px !important;
800
- height: auto !important;
801
- min-height: 0 !important;
802
- padding: 16px !important;
803
- border: 1px solid var(--pruna-border) !important;
804
- border-radius: 16px !important;
805
- background: var(--pruna-card-bg) !important;
806
- box-shadow: var(--pruna-card-shadow) !important;
807
  }
808
- .benchmark-card-col .form {
809
- display: flex !important;
810
- flex-direction: column !important;
811
- justify-content: flex-start !important;
812
- align-items: stretch !important;
813
- gap: 12px !important;
814
- width: 100% !important;
815
- height: auto !important;
816
- min-height: 0 !important;
817
  flex: 0 0 auto !important;
 
 
818
  }
819
- .benchmark-card-col:hover {
820
- border-color: color-mix(in srgb, var(--pruna-accent) 50%, transparent) !important;
821
- }
822
- .benchmark-card-body,
823
- .benchmark-card-col > .block,
824
- .benchmark-card-col .html-container,
825
- .benchmark-card-col .prose,
826
- .benchmark-card-col .padded,
827
- .benchmark-card-col .block {
828
- flex: 0 0 auto !important;
829
- height: auto !important;
830
- min-height: 0 !important;
831
- margin: 0 !important;
832
- padding: 0 !important;
833
  border: none !important;
834
  background: transparent !important;
835
  box-shadow: none !important;
 
 
836
  }
837
- .benchmark-card-title {
838
- display: flex;
839
- align-items: center;
840
- gap: 8px;
841
- color: var(--pruna-text-primary);
842
- font-size: 1.15rem;
843
- font-weight: 700;
844
- line-height: 1.3;
845
- margin: 0 0 6px;
846
  }
847
- .benchmark-card-emoji {
848
- display: inline-flex;
849
- align-items: center;
850
- justify-content: center;
851
- flex-shrink: 0;
852
- line-height: 1;
 
 
 
 
853
  }
854
- .benchmark-card-blurb {
855
- margin: 0;
856
- color: var(--pruna-text-body);
857
- font-size: 0.9rem;
858
- line-height: 1.45;
 
 
 
 
 
859
  }
860
- .benchmark-card-col .benchmark-open-btn,
861
- .benchmark-card-col button,
862
- .benchmark-card-col button.sm,
863
- .benchmark-card-col button.primary {
864
- display: flex !important;
865
- justify-content: center !important;
866
- align-items: center !important;
867
- width: 100% !important;
868
- min-width: 0 !important;
869
  margin: 0 !important;
870
- align-self: stretch !important;
871
- flex: 0 0 auto !important;
 
 
 
 
 
 
 
 
 
 
872
  }
873
- .home-callouts > div,
874
  .compare-prompt-block {
875
  border: 1px solid var(--pruna-border) !important;
876
  border-radius: 16px !important;
@@ -912,260 +807,89 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
912
  gap: 8px 16px;
913
  align-items: center;
914
  }
915
- .community-footer-links a {
916
- color: var(--pruna-text-muted) !important;
917
- text-decoration: none !important;
918
- font-size: 0.9rem !important;
919
- font-weight: 500 !important;
920
- }
921
- .community-footer-links a:hover {
922
- color: var(--pruna-link) !important;
923
- }
924
-
925
- /* Citation accordion — full-width control, not a section heading */
926
- .citation-accordion {
927
- margin: 0 0 28px !important;
928
- }
929
- .citation-accordion,
930
- .citation-accordion.block {
931
- border: 1px solid var(--pruna-accordion-border) !important;
932
- border-radius: 10px !important;
933
- background: var(--pruna-accordion-bg) !important;
934
- box-shadow: none !important;
935
- overflow: hidden !important;
936
- }
937
- .citation-accordion > .label-wrap,
938
- .citation-accordion .label-wrap {
939
- border: none !important;
940
- background: transparent !important;
941
- box-shadow: none !important;
942
- padding: 11px 14px !important;
943
- font-size: 0.92rem !important;
944
- font-weight: 500 !important;
945
- color: var(--pruna-text-primary) !important;
946
- letter-spacing: -0.01em !important;
947
- }
948
- .citation-accordion > .label-wrap:hover,
949
- .citation-accordion .label-wrap:hover {
950
- background: var(--pruna-menu-hover-bg) !important;
951
- }
952
- .citation-accordion > .label-wrap span,
953
- .citation-accordion .label-wrap span {
954
- color: var(--pruna-text-primary) !important;
955
- font-size: 0.92rem !important;
956
- font-weight: 500 !important;
957
- }
958
- .citation-accordion .icon,
959
- .citation-accordion .label-wrap .icon {
960
- color: var(--pruna-text-muted) !important;
961
- opacity: 0.9;
962
- }
963
- .citation-accordion .prose,
964
- .citation-accordion .markdown,
965
- .citation-accordion pre,
966
- .citation-accordion code {
967
- color: var(--pruna-text-muted) !important;
968
- background: transparent !important;
969
- border: none !important;
970
- box-shadow: none !important;
971
- }
972
- .citation-accordion .wrap,
973
- .citation-accordion > .wrap {
974
- border-top: 1px solid var(--pruna-hairline) !important;
975
- padding: 4px 14px 12px !important;
976
- background: transparent !important;
977
- }
978
- .citation-accordion pre {
979
- margin: 0 !important;
980
- padding: 0 !important;
981
- font-size: 0.8rem !important;
982
- line-height: 1.55 !important;
983
- overflow-x: auto;
984
- }
985
-
986
- .home-callouts-host,
987
- .home-callouts-host .html-container,
988
- .home-callouts-host .prose,
989
- .home-callouts-host.block,
990
- .home-callouts-host .padded,
991
- .home-section-title,
992
- .home-section-title.block,
993
- .home-section-title .prose,
994
- .home-section-title.padded {
995
- border: none !important;
996
- background: transparent !important;
997
- box-shadow: none !important;
998
- padding: 0 !important;
999
- margin: 0 !important;
1000
- width: 100% !important;
1001
- max-width: none !important;
1002
- overflow: visible !important;
1003
- }
1004
- .home-section-title h2,
1005
- .home-section-title .prose h2,
1006
- .gradio-container .home-section-title h2 {
1007
- margin: 1.25rem 0 0.6rem !important;
1008
- }
1009
- .home-callouts {
1010
- display: grid;
1011
- grid-template-columns: repeat(3, minmax(0, 1fr));
1012
- gap: 12px;
1013
- margin: 8px 0 8px;
1014
- width: 100%;
1015
- }
1016
- .home-callouts > div {
1017
- padding: 14px 16px;
1018
- background: var(--pruna-callout-bg) !important;
1019
- box-shadow: none !important;
1020
- }
1021
- .home-callouts span {
1022
- display: block;
1023
- }
1024
- .home-callouts strong {
1025
- display: block;
1026
- margin-top: 6px;
1027
- word-break: break-word;
1028
- }
1029
- .home-callouts em {
1030
- display: block;
1031
- margin-top: 4px;
1032
- font-style: normal;
1033
- }
1034
- .home-snapshots-host,
1035
- .home-snapshots-host .html-container,
1036
- .home-snapshots-host .prose,
1037
- .home-snapshots-host.block,
1038
- .home-snapshots-host .padded {
1039
- border: none !important;
1040
- background: transparent !important;
1041
- box-shadow: none !important;
1042
- padding: 0 !important;
1043
- margin: 0 !important;
1044
- width: 100% !important;
1045
- max-width: none !important;
1046
- overflow: visible !important;
1047
- }
1048
- .home-snapshots-grid {
1049
- display: grid;
1050
- grid-template-columns: repeat(2, minmax(0, 1fr));
1051
- gap: 20px 32px;
1052
- width: 100%;
1053
- margin: 4px 0 8px;
1054
- align-items: stretch;
1055
- }
1056
- .home-snap-card {
1057
- display: flex;
1058
- flex-direction: column;
1059
- min-width: 0;
1060
- height: 100%;
1061
- }
1062
- .home-snap-card .home-snap-blurb {
1063
- margin-top: 6px !important;
1064
- }
1065
- .home-snap-card .home-snap-top {
1066
- margin-top: auto;
1067
- }
1068
- .home-snap-title,
1069
- .prose .home-snap-title {
1070
- display: flex !important;
1071
- align-items: center !important;
1072
- gap: 8px !important;
1073
- margin: 0 !important;
1074
- min-height: 1.5rem;
1075
- font-size: 0.95rem !important;
1076
- font-weight: 600 !important;
1077
- letter-spacing: -0.01em;
1078
- line-height: 1.3 !important;
1079
- color: var(--pruna-text-primary) !important;
1080
- }
1081
- .home-snap-emoji {
1082
- display: inline-flex;
1083
- align-items: center;
1084
- justify-content: center;
1085
- width: 1.25rem;
1086
- height: 1.25rem;
1087
- flex-shrink: 0;
1088
- font-size: 1rem;
1089
- line-height: 1;
1090
- }
1091
- .home-snap-blurb,
1092
- .prose .home-snap-blurb,
1093
- .prose p.home-snap-blurb {
1094
- margin: 0 !important;
1095
  color: var(--pruna-text-muted) !important;
1096
- font-size: 0.875rem !important;
1097
- font-weight: 400 !important;
1098
- line-height: 1.45 !important;
1099
  }
1100
- .home-snap-top {
1101
- padding-top: 10px;
1102
- border-top: 1px solid var(--pruna-hairline);
1103
- width: 100%;
1104
  }
1105
- .home-top-label {
1106
- margin: 0;
 
 
1107
  }
1108
- .home-top-list {
1109
- list-style: none;
1110
- margin: 8px 0 0;
1111
- padding: 0;
 
 
 
1112
  }
1113
- .home-top-list li,
1114
- .prose .home-top-list li {
1115
- display: grid;
1116
- grid-template-columns: 28px minmax(0, 1fr) 4.75rem;
1117
- gap: 10px;
1118
- align-items: center;
1119
- padding: 8px 0;
1120
- border-bottom: 1px solid var(--pruna-hairline);
1121
  color: var(--pruna-text-primary) !important;
 
1122
  }
1123
- @media (max-width: 700px) {
1124
- .home-snapshots-grid {
1125
- grid-template-columns: 1fr;
1126
- gap: 22px;
1127
- }
1128
- }
1129
- .home-rank,
1130
- .prose .home-rank {
1131
- width: 28px;
1132
- height: 28px;
1133
- border-radius: 999px;
1134
- background: var(--pruna-rank-bg) !important;
1135
- color: var(--pruna-lavender) !important;
1136
- font-weight: 700;
1137
- font-size: 13px;
1138
- display: inline-flex;
1139
- align-items: center;
1140
- justify-content: center;
1141
- box-shadow: var(--pruna-rank-ring);
1142
  }
1143
- .home-model,
1144
- .prose .home-model {
1145
- font-weight: 600;
1146
- word-break: break-word;
1147
  color: var(--pruna-text-primary) !important;
 
 
1148
  }
1149
- .home-score,
1150
- .prose .home-score {
1151
- font-variant-numeric: tabular-nums;
1152
- color: var(--pruna-score) !important;
1153
- font-weight: 600;
1154
- text-align: right;
1155
  }
1156
- .home-empty,
1157
- .prose .home-empty {
 
 
1158
  color: var(--pruna-text-muted) !important;
 
 
 
1159
  }
1160
-
1161
- @media (max-width: 700px) {
1162
- .home-callouts { grid-template-columns: 1fr; }
 
 
 
 
 
 
 
 
 
1163
  }
1164
 
1165
- .pareto-plot { width: 100%; }
 
1166
  .pareto-plot .plotly,
1167
  .pareto-plot .js-plotly-plot,
1168
- .pareto-plot .plot-container { width: 100% !important; }
 
 
 
 
 
 
 
1169
  .pareto-plot .modebar { display: none !important; }
1170
 
1171
  .leaderboard-controls {
@@ -1174,6 +898,7 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1174
  align-items: end !important;
1175
  gap: 10px !important;
1176
  margin-bottom: 12px;
 
1177
  }
1178
  .leaderboard-controls > div {
1179
  min-width: 0 !important;
@@ -1192,16 +917,6 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1192
  font-size: 0.8rem !important;
1193
  font-weight: 500 !important;
1194
  }
1195
- .leaderboard-search label,
1196
- .leaderboard-search .block-label,
1197
- .leaderboard-search .block-label span {
1198
- color: var(--pruna-text-primary) !important;
1199
- font-size: 0.85rem !important;
1200
- font-weight: 700 !important;
1201
- letter-spacing: -0.01em !important;
1202
- }
1203
- .leaderboard-search input,
1204
- .leaderboard-controls input,
1205
  .leaderboard-controls textarea {
1206
  min-height: 40px !important;
1207
  height: 40px !important;
@@ -1216,29 +931,61 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1216
  line-height: 40px !important;
1217
  resize: none !important;
1218
  }
1219
- .leaderboard-search input {
1220
- font-weight: 600 !important;
1221
- }
1222
- .leaderboard-search input::placeholder,
1223
- .leaderboard-controls input::placeholder {
1224
- color: var(--pruna-text-muted) !important;
1225
- opacity: 0.85;
1226
- }
1227
- .leaderboard-search input:focus,
1228
- .leaderboard-controls input:focus,
1229
  .leaderboard-controls textarea:focus {
1230
  border-color: var(--pruna-focus-border) !important;
1231
  outline: none !important;
1232
  box-shadow: var(--pruna-focus-ring) !important;
1233
  }
1234
- .leaderboard-controls .wrap,
1235
- .leaderboard-controls .wrap-inner,
1236
- .leaderboard-controls .secondary-wrap {
 
 
 
 
 
 
 
 
 
1237
  min-height: 40px !important;
 
 
1238
  border: 1px solid var(--pruna-input-border) !important;
1239
  border-radius: 10px !important;
1240
  background: var(--pruna-input-bg) !important;
1241
  box-shadow: none !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1242
  }
1243
  .ranking-table-host,
1244
  .ranking-table-host .html-container,
@@ -1739,25 +1486,15 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1739
  .pareto-layout .form {
1740
  display: flex !important;
1741
  flex-wrap: wrap !important;
1742
- align-items: flex-start !important;
1743
  gap: 16px !important;
1744
  width: 100% !important;
1745
  max-width: 100% !important;
1746
  }
1747
  .pareto-layout > .pareto-col,
1748
  .pareto-layout .form > .pareto-col {
1749
- flex: 1 1 320px !important;
1750
- min-width: 280px !important;
1751
- max-width: 100% !important;
1752
- }
1753
- .pareto-plot,
1754
- .pareto-plot.block,
1755
- .pareto-plot .plotly,
1756
- .pareto-plot .js-plotly-plot,
1757
- .pareto-plot .plot-container,
1758
- .pareto-plot .svg-container,
1759
- .pareto-plot .main-svg {
1760
- width: 100% !important;
1761
  max-width: 100% !important;
1762
  }
1763
  .compare-cell { min-width: 0; }
@@ -1885,31 +1622,6 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1885
  font-weight: 400 !important;
1886
  line-height: 1.55 !important;
1887
  }
1888
- .home-intro,
1889
- .home-intro.block,
1890
- .home-intro .prose,
1891
- .home-intro.prose,
1892
- .home-intro.padded {
1893
- border: none !important;
1894
- background: transparent !important;
1895
- box-shadow: none !important;
1896
- padding: 0 !important;
1897
- margin: 0 auto 12px !important;
1898
- max-width: 42rem;
1899
- width: 100%;
1900
- text-align: center;
1901
- overflow: visible !important;
1902
- }
1903
- .home-intro p,
1904
- .home-intro .prose p,
1905
- .gradio-container .home-intro p {
1906
- color: var(--pruna-text-body) !important;
1907
- font-size: 1.08rem !important;
1908
- font-weight: 400 !important;
1909
- line-height: 1.65 !important;
1910
- margin: 0 auto 0.7rem !important;
1911
- text-align: center !important;
1912
- }
1913
  .markdown li, .md li, .prose li {
1914
  color: var(--pruna-text-body) !important;
1915
  font-size: 0.95rem;
@@ -1920,45 +1632,6 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1920
  font-weight: 600 !important;
1921
  }
1922
 
1923
- /* Card / list titles sit under section headings */
1924
- .home-snap-title,
1925
- .benchmark-card-title {
1926
- display: flex !important;
1927
- align-items: center !important;
1928
- gap: 8px !important;
1929
- margin: 0 0 6px !important;
1930
- font-size: 0.95rem !important;
1931
- font-weight: 600 !important;
1932
- letter-spacing: -0.01em;
1933
- line-height: 1.3;
1934
- color: var(--pruna-text-primary) !important;
1935
- }
1936
- .home-snap-blurb,
1937
- .benchmark-card-blurb {
1938
- color: var(--pruna-text-muted) !important;
1939
- font-size: 0.875rem !important;
1940
- font-weight: 400 !important;
1941
- }
1942
-
1943
- /* Meta labels only — not used for real headings */
1944
- .home-callouts span,
1945
- .home-top-label {
1946
- color: var(--pruna-text-muted) !important;
1947
- font-size: 11px !important;
1948
- font-weight: 600 !important;
1949
- letter-spacing: 0.06em !important;
1950
- text-transform: uppercase !important;
1951
- }
1952
- .home-callouts strong {
1953
- color: var(--pruna-text-primary) !important;
1954
- font-size: 1.15rem !important;
1955
- font-weight: 600 !important;
1956
- }
1957
- .home-callouts em {
1958
- color: var(--pruna-text-muted) !important;
1959
- font-size: 0.85rem !important;
1960
- }
1961
-
1962
  label, .block-label span {
1963
  color: var(--pruna-text-muted) !important;
1964
  font-size: 0.8rem !important;
@@ -1988,45 +1661,48 @@ button.secondary:active, button.secondary:focus,
1988
  border-color: var(--pruna-border) !important;
1989
  background: transparent !important;
1990
  }
1991
- input, textarea, select, .wrap .secondary-wrap {
1992
  background: var(--pruna-bg-card) !important;
1993
  border-color: var(--pruna-border) !important;
1994
  color: var(--pruna-text-primary) !important;
1995
- border-radius: 8px !important;
1996
- }
1997
- footer, .footer { display: none !important; }
1998
-
1999
- /* Search label lives in a Gradio BlockTitle span with its own font-weight */
2000
- .leaderboard-search span[data-testid="block-info"],
2001
- .leaderboard-search label > span {
2002
- color: var(--pruna-text-primary) !important;
2003
- font-size: 0.95rem !important;
2004
- font-weight: 700 !important;
2005
- letter-spacing: -0.015em !important;
2006
  }
2007
- .leaderboard-search input {
2008
- font-size: 0.95rem !important;
2009
- font-weight: 600 !important;
 
 
 
 
 
 
2010
  }
2011
- .leaderboard-search input::placeholder {
2012
- color: var(--pruna-text-muted) !important;
2013
- font-size: 0.95rem !important;
2014
- font-weight: 600 !important;
2015
- opacity: 1 !important;
2016
  }
 
2017
 
2018
- .pareto-help {
2019
- color: var(--pruna-text-muted) !important;
2020
- font-size: 0.95rem;
2021
- }
2022
- .pareto-help-emphasis {
2023
- color: var(--pruna-text-primary) !important;
2024
- font-size: 1rem;
 
 
2025
  }
2026
- .pareto-panel-host {
2027
- display: flex;
2028
- flex-direction: column;
2029
- gap: 1.5rem;
 
 
 
 
 
2030
  }
2031
  .pareto-metric-block {
2032
  padding-bottom: 0.5rem;
@@ -2041,14 +1717,51 @@ footer, .footer { display: none !important; }
2041
  font-size: 1.05rem;
2042
  font-weight: 600;
2043
  }
2044
- .pareto-subhead {
2045
- margin: 0 0 0.35rem;
 
 
 
 
 
 
 
2046
  color: var(--pruna-text-muted);
2047
- font-size: 0.9rem;
 
 
 
 
 
 
2048
  }
2049
 
2050
  .options,
2051
- ul.options,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2052
  .dropdown-arrow-inner {
2053
  background: var(--pruna-bg-card) !important;
2054
  color: var(--pruna-text-primary) !important;
@@ -2067,14 +1780,6 @@ ul.options,
2067
  input[type="range"] {
2068
  accent-color: var(--pruna-accent);
2069
  }
2070
- .pareto-plot,
2071
- .pareto-plot.block,
2072
- .pareto-plot .plotly,
2073
- .pareto-plot .js-plotly-plot,
2074
- .pareto-plot .plot-container {
2075
- background: transparent !important;
2076
- border-color: var(--pruna-border) !important;
2077
- }
2078
  """
2079
 
2080
  theme = gr.themes.Base(
@@ -2267,12 +1972,8 @@ def load_oneig_dataframe(path):
2267
  "OneIG Anime Elo",
2268
  "OneIG Human Elo",
2269
  "OneIG Object Elo",
2270
- "Median Inference Time",
2271
- "Median Inference Time (s)",
2272
  "Median Generation Time (s)",
2273
  "Min Generation Time (s)",
2274
- "Median Total Duration (s)",
2275
- "Price per Image",
2276
  "Price / Image (USD)",
2277
  "Evaluation Date (UTC)",
2278
  "URL",
@@ -2634,10 +2335,7 @@ datasets = [
2634
  "data": qwen_df,
2635
  "columns": qwen_display_columns,
2636
  "metric_ids": qwen_metric_ids,
2637
- "note": (
2638
- "> Ranked by the selected metric on the Qwen Image Dataset. "
2639
- "Rapidata Elo is a metric on this dataset, not a dataset of its own."
2640
- ),
2641
  "samples": qwen_samples,
2642
  },
2643
  {
@@ -2647,7 +2345,7 @@ datasets = [
2647
  "columns": oneig_display_columns,
2648
  "metric_ids": oneig_metric_ids,
2649
  "note": (
2650
- "> Alignment Overall is the mean of the available category scores. "
2651
  "Missing categories are skipped for that model."
2652
  ),
2653
  "samples": oneig_samples,
@@ -2892,6 +2590,19 @@ custom_head = """
2892
  });
2893
  })();
2894
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
2895
  """
2896
 
2897
  with gr.Blocks(
 
124
  .gradio-container * {
125
  -webkit-tap-highlight-color: transparent !important;
126
  }
127
+
128
+ .gradio-container {
129
+ --input-radius: 10px;
130
+ --container-radius: 10px;
131
+ --block-radius: 10px;
132
  }
133
 
134
  /* Subtle depth — not a marketing-site hero glow */
 
161
  .main-tabs,
162
  .main-tabs .tab-wrapper,
163
  .main-tabs .tabitem,
 
 
 
164
  .leaderboard-controls,
165
+ .workspace-shell,
166
+ .workspace-filters,
167
  .view-filters {
168
  max-width: 100% !important;
169
  }
 
189
  position: relative;
190
  display: flex;
191
  flex-direction: column;
192
+ align-items: stretch;
193
  width: 100%;
194
+ margin: 8px 0 0;
195
+ padding: 4px 0 10px;
196
  text-align: center;
197
  }
198
+ .app-header-bar {
199
+ display: grid;
200
+ grid-template-columns: minmax(36px, 1fr) auto minmax(36px, 1fr);
201
+ align-items: center;
202
+ width: 100%;
203
+ column-gap: 8px;
204
+ }
205
  .app-header .theme-toggle,
206
  button.theme-toggle {
207
+ position: static !important;
208
+ top: auto;
209
+ right: auto;
210
+ grid-column: 3;
211
+ justify-self: end;
212
  display: inline-flex !important;
213
  align-items: center !important;
214
  justify-content: center !important;
 
271
  align-items: center;
272
  justify-content: center;
273
  gap: 12px;
274
+ grid-column: 2;
275
+ justify-self: center;
276
  width: auto !important;
277
  max-width: 100%;
278
+ margin: 0;
279
  }
280
  .app-header-logo {
281
  display: block !important;
 
286
  flex: 0 0 auto;
287
  }
288
 
289
+ /* —— Page tabs: centered underline nav —— */
290
+ .workspace-shell,
291
+ .workspace-shell.block,
292
+ .workspace-shell.column,
293
+ .workspace-shell.gap {
294
+ display: flex !important;
295
+ flex-direction: column !important;
296
+ gap: 0 !important;
297
+ padding: 0 !important;
298
+ margin: 0 !important;
299
+ border: none !important;
300
+ background: transparent !important;
301
+ box-shadow: none !important;
302
+ }
303
+ .workspace-shell > .tabs,
304
+ .workspace-shell > .main-tabs,
305
+ .workspace-shell > .block:not(.workspace-filters),
306
+ .workspace-shell .tabs.main-tabs {
307
+ display: contents !important;
308
+ }
309
+ .workspace-filters {
310
+ order: 2 !important;
311
+ }
312
+ .main-tabs .tabitem {
313
+ order: 3 !important;
314
+ }
315
  .tabs {
316
  gap: 0 !important;
317
  }
318
  .main-tabs > .tab-wrapper {
319
+ order: 1 !important;
320
  height: auto !important;
321
  min-height: 0 !important;
322
  padding: 0 !important;
323
+ margin: 0 0 16px !important;
324
  justify-content: center !important;
325
+ width: 100% !important;
326
  }
327
  .main-tabs .tab-container {
328
  height: auto !important;
 
367
  .main-tabs .tabitem {
368
  padding: 8px 0 0 !important;
369
  border-radius: 0 !important;
370
+ overflow: visible !important;
371
  }
372
 
373
  /* —— About: two columns on desktop, stacked on phone —— */
 
423
  .gradio-container .row,
424
  .gradio-container .column,
425
  .gradio-container .wrap,
 
 
 
426
  .leaderboard-controls,
427
  .view-filters {
428
  min-width: 0 !important;
429
  max-width: 100% !important;
430
  }
431
  .app-header {
432
+ padding: 4px 0 8px;
433
+ margin: 4px 0 0;
434
  }
435
  .app-header-logo {
436
  width: 36px !important;
 
445
  padding: 0 8px !important;
446
  }
447
  .main-tabs > .tab-wrapper {
448
+ margin: 4px 0 14px !important;
449
  }
450
  .main-tabs .tab-container {
451
  flex-wrap: wrap !important;
 
481
  .community-footer-links {
482
  gap: 10px 14px;
483
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  .leaderboard-controls,
485
  .leaderboard-controls.row,
486
  .leaderboard-controls .form,
 
585
  }
586
  }
587
 
588
+ /* —— Workspace filters (dataset / metric / models) —— */
589
+ .workspace-filters,
590
+ .workspace-filters.block,
591
+ .workspace-filters.column,
592
+ .workspace-filters.gap {
593
+ margin: 0 0 12px;
594
+ padding: 0 !important;
595
+ gap: 12px !important;
596
+ overflow: visible !important;
597
+ }
598
  .view-filters {
599
  display: flex !important;
600
  flex-wrap: wrap !important;
601
  align-items: end !important;
602
+ gap: 12px !important;
603
+ margin: 0;
604
+ overflow: visible !important;
605
  }
606
+ .view-filters > div,
607
+ .view-filters > .block,
608
+ .view-filters > .form {
609
+ flex: 1 1 0 !important;
610
  min-width: 0 !important;
611
  }
612
  .view-filters > .block,
 
616
  background: transparent !important;
617
  box-shadow: none !important;
618
  padding: 0 !important;
619
+ overflow: visible !important;
620
  --block-border-width: 0 !important;
621
  }
622
  .view-filters label {
623
+ display: block !important;
624
+ margin: 0 !important;
625
+ padding: 0 0 8px !important;
626
  color: var(--pruna-text-muted) !important;
627
  font-size: 0.8rem !important;
628
  font-weight: 500 !important;
629
+ line-height: 1.2 !important;
630
  }
631
+ .view-filters .wrap {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
632
  min-width: 0 !important;
 
 
633
  border: none !important;
634
  background: transparent !important;
635
  box-shadow: none !important;
636
+ overflow: visible !important;
637
  }
638
+ .view-filters .wrap-inner {
639
+ display: flex !important;
640
+ flex-wrap: nowrap !important;
 
 
 
 
 
 
641
  align-items: center !important;
642
+ gap: 6px !important;
643
+ min-height: 40px !important;
644
+ height: 40px !important;
645
+ padding: 0 10px !important;
646
+ border: 1px solid var(--pruna-input-border) !important;
647
+ border-radius: 10px !important;
648
+ background: var(--pruna-input-bg) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  box-shadow: none !important;
650
+ overflow: hidden !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  }
652
+ .view-filters .secondary-wrap {
653
  display: flex !important;
654
+ flex-wrap: nowrap !important;
655
  align-items: center !important;
656
+ flex: 1 1 auto !important;
657
+ min-width: 0 !important;
658
+ height: 40px !important;
659
  margin: 0 !important;
660
+ padding: 0 !important;
661
  border: none !important;
 
662
  background: transparent !important;
663
  box-shadow: none !important;
664
+ overflow: visible !important;
 
 
 
 
 
665
  }
666
+ .view-filters .filter-chips .wrap-inner {
667
+ overflow-x: auto !important;
668
+ overflow-y: hidden !important;
669
+ overscroll-behavior-x: contain;
670
+ scrollbar-width: thin;
671
+ scrollbar-color: var(--pruna-border) transparent;
672
  }
673
+ .view-filters .filter-chips .secondary-wrap {
674
+ min-width: 2.5rem !important;
 
675
  }
676
+ .view-filters .filter-chips .wrap-inner::-webkit-scrollbar {
677
+ height: 6px;
 
 
 
 
 
678
  }
679
+ .view-filters .filter-chips .wrap-inner::-webkit-scrollbar-thumb {
680
+ background: var(--pruna-border);
681
+ border-radius: 99px;
682
  }
683
+ .view-filters .filter-chips .token {
684
+ flex: 0 0 auto !important;
685
+ white-space: nowrap !important;
686
+ word-break: keep-all !important;
 
 
 
 
 
687
  }
688
+ .view-filters .filter-chips .token span {
689
+ white-space: nowrap !important;
690
+ overflow: hidden !important;
691
+ text-overflow: ellipsis !important;
692
+ max-width: 14rem;
 
 
 
 
 
 
 
 
 
 
 
693
  }
694
+ .view-filters .token:empty {
695
+ display: none !important;
 
 
 
 
 
 
 
 
696
  }
697
+ .view-filters input {
698
+ border: none !important;
699
+ background: transparent !important;
700
+ box-shadow: none !important;
701
+ min-height: 28px !important;
702
+ height: 28px !important;
703
  width: 100% !important;
704
+ min-width: 0 !important;
705
+ padding: 0 4px !important;
706
+ line-height: 28px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
707
  }
708
+ .view-filters .dropdown-arrow,
709
+ .view-filters .icon-wrap {
 
 
 
 
 
 
 
710
  flex: 0 0 auto !important;
711
+ align-self: center !important;
712
+ margin-left: auto !important;
713
  }
714
+ .view-title,
715
+ .view-title.block,
716
+ .view-title .padded {
 
 
 
 
 
 
 
 
 
 
 
717
  border: none !important;
718
  background: transparent !important;
719
  box-shadow: none !important;
720
+ padding: 0 !important;
721
+ margin: 0 0 8px !important;
722
  }
723
+ .view-title h1,
724
+ .view-title .prose h1,
725
+ .gradio-container .view-title h1 {
726
+ margin: 0.35rem 0 0.4rem !important;
727
+ font-size: 1.45rem !important;
 
 
 
 
728
  }
729
+ .filter-help-host,
730
+ .filter-help-host.block,
731
+ .filter-help-host .padded,
732
+ .filter-help-host .html-container,
733
+ .filter-help-host .prose {
734
+ border: none !important;
735
+ background: transparent !important;
736
+ box-shadow: none !important;
737
+ padding: 0 !important;
738
+ margin: 0 !important;
739
  }
740
+ .view-help-host,
741
+ .view-help-host.block,
742
+ .view-help-host .padded,
743
+ .view-help-host .html-container,
744
+ .view-help-host .prose {
745
+ border: none !important;
746
+ background: transparent !important;
747
+ box-shadow: none !important;
748
+ padding: 0 !important;
749
+ margin: 0 0 10px !important;
750
  }
751
+ .filter-help,
752
+ .prose .filter-help,
753
+ .view-help,
754
+ .prose .view-help {
 
 
 
 
 
755
  margin: 0 !important;
756
+ color: var(--pruna-text-muted) !important;
757
+ font-size: 0.95rem !important;
758
+ line-height: 1.45 !important;
759
+ font-weight: 400 !important;
760
+ }
761
+ .view-filters span[data-testid="block-info"],
762
+ .view-filters .info,
763
+ .view-filters .block-info {
764
+ color: var(--pruna-text-muted) !important;
765
+ font-size: 0.75rem !important;
766
+ line-height: 1.35 !important;
767
+ margin-top: 4px !important;
768
  }
 
769
  .compare-prompt-block {
770
  border: 1px solid var(--pruna-border) !important;
771
  border-radius: 16px !important;
 
807
  gap: 8px 16px;
808
  align-items: center;
809
  }
810
+ .community-footer-links a {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  color: var(--pruna-text-muted) !important;
812
+ text-decoration: none !important;
813
+ font-size: 0.9rem !important;
814
+ font-weight: 500 !important;
815
  }
816
+ .community-footer-links a:hover {
817
+ color: var(--pruna-link) !important;
 
 
818
  }
819
+
820
+ /* Citation accordion — full-width control, not a section heading */
821
+ .citation-accordion {
822
+ margin: 0 0 28px !important;
823
  }
824
+ .citation-accordion,
825
+ .citation-accordion.block {
826
+ border: 1px solid var(--pruna-accordion-border) !important;
827
+ border-radius: 10px !important;
828
+ background: var(--pruna-accordion-bg) !important;
829
+ box-shadow: none !important;
830
+ overflow: hidden !important;
831
  }
832
+ .citation-accordion > .label-wrap,
833
+ .citation-accordion .label-wrap {
834
+ border: none !important;
835
+ background: transparent !important;
836
+ box-shadow: none !important;
837
+ padding: 11px 14px !important;
838
+ font-size: 0.92rem !important;
839
+ font-weight: 500 !important;
840
  color: var(--pruna-text-primary) !important;
841
+ letter-spacing: -0.01em !important;
842
  }
843
+ .citation-accordion > .label-wrap:hover,
844
+ .citation-accordion .label-wrap:hover {
845
+ background: var(--pruna-menu-hover-bg) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846
  }
847
+ .citation-accordion > .label-wrap span,
848
+ .citation-accordion .label-wrap span {
 
 
849
  color: var(--pruna-text-primary) !important;
850
+ font-size: 0.92rem !important;
851
+ font-weight: 500 !important;
852
  }
853
+ .citation-accordion .icon,
854
+ .citation-accordion .label-wrap .icon {
855
+ color: var(--pruna-text-muted) !important;
856
+ opacity: 0.9;
 
 
857
  }
858
+ .citation-accordion .prose,
859
+ .citation-accordion .markdown,
860
+ .citation-accordion pre,
861
+ .citation-accordion code {
862
  color: var(--pruna-text-muted) !important;
863
+ background: transparent !important;
864
+ border: none !important;
865
+ box-shadow: none !important;
866
  }
867
+ .citation-accordion .wrap,
868
+ .citation-accordion > .wrap {
869
+ border-top: 1px solid var(--pruna-hairline) !important;
870
+ padding: 4px 14px 12px !important;
871
+ background: transparent !important;
872
+ }
873
+ .citation-accordion pre {
874
+ margin: 0 !important;
875
+ padding: 0 !important;
876
+ font-size: 0.8rem !important;
877
+ line-height: 1.55 !important;
878
+ overflow-x: auto;
879
  }
880
 
881
+ .pareto-plot,
882
+ .pareto-plot.block,
883
  .pareto-plot .plotly,
884
  .pareto-plot .js-plotly-plot,
885
+ .pareto-plot .plot-container,
886
+ .pareto-plot .svg-container,
887
+ .pareto-plot .main-svg {
888
+ width: 100% !important;
889
+ max-width: 100% !important;
890
+ background: transparent !important;
891
+ border-color: var(--pruna-border) !important;
892
+ }
893
  .pareto-plot .modebar { display: none !important; }
894
 
895
  .leaderboard-controls {
 
898
  align-items: end !important;
899
  gap: 10px !important;
900
  margin-bottom: 12px;
901
+ overflow: visible !important;
902
  }
903
  .leaderboard-controls > div {
904
  min-width: 0 !important;
 
917
  font-size: 0.8rem !important;
918
  font-weight: 500 !important;
919
  }
 
 
 
 
 
 
 
 
 
 
920
  .leaderboard-controls textarea {
921
  min-height: 40px !important;
922
  height: 40px !important;
 
931
  line-height: 40px !important;
932
  resize: none !important;
933
  }
 
 
 
 
 
 
 
 
 
 
934
  .leaderboard-controls textarea:focus {
935
  border-color: var(--pruna-focus-border) !important;
936
  outline: none !important;
937
  box-shadow: var(--pruna-focus-ring) !important;
938
  }
939
+ .leaderboard-controls .wrap {
940
+ min-height: 0 !important;
941
+ border: none !important;
942
+ background: transparent !important;
943
+ box-shadow: none !important;
944
+ overflow: visible !important;
945
+ }
946
+ .leaderboard-controls .wrap-inner {
947
+ display: flex !important;
948
+ flex-wrap: wrap !important;
949
+ align-items: center !important;
950
+ gap: 6px !important;
951
  min-height: 40px !important;
952
+ height: auto !important;
953
+ padding: 4px 10px !important;
954
  border: 1px solid var(--pruna-input-border) !important;
955
  border-radius: 10px !important;
956
  background: var(--pruna-input-bg) !important;
957
  box-shadow: none !important;
958
+ overflow: visible !important;
959
+ }
960
+ .leaderboard-controls .secondary-wrap {
961
+ display: flex !important;
962
+ flex-wrap: wrap !important;
963
+ align-items: center !important;
964
+ gap: 6px !important;
965
+ flex: 1 1 auto !important;
966
+ min-width: 0 !important;
967
+ min-height: 0 !important;
968
+ height: auto !important;
969
+ margin: 0 !important;
970
+ padding: 0 !important;
971
+ border: none !important;
972
+ background: transparent !important;
973
+ box-shadow: none !important;
974
+ }
975
+ .leaderboard-controls .token {
976
+ display: inline-flex !important;
977
+ align-items: center !important;
978
+ max-width: 100% !important;
979
+ margin: 0 !important;
980
+ }
981
+ .leaderboard-controls .token:empty {
982
+ display: none !important;
983
+ }
984
+ .leaderboard-controls .dropdown-arrow,
985
+ .leaderboard-controls .icon-wrap {
986
+ flex: 0 0 auto !important;
987
+ align-self: center !important;
988
+ margin-left: auto !important;
989
  }
990
  .ranking-table-host,
991
  .ranking-table-host .html-container,
 
1486
  .pareto-layout .form {
1487
  display: flex !important;
1488
  flex-wrap: wrap !important;
1489
+ align-items: stretch !important;
1490
  gap: 16px !important;
1491
  width: 100% !important;
1492
  max-width: 100% !important;
1493
  }
1494
  .pareto-layout > .pareto-col,
1495
  .pareto-layout .form > .pareto-col {
1496
+ flex: 1 1 360px !important;
1497
+ min-width: 0 !important;
 
 
 
 
 
 
 
 
 
 
1498
  max-width: 100% !important;
1499
  }
1500
  .compare-cell { min-width: 0; }
 
1622
  font-weight: 400 !important;
1623
  line-height: 1.55 !important;
1624
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1625
  .markdown li, .md li, .prose li {
1626
  color: var(--pruna-text-body) !important;
1627
  font-size: 0.95rem;
 
1632
  font-weight: 600 !important;
1633
  }
1634
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1635
  label, .block-label span {
1636
  color: var(--pruna-text-muted) !important;
1637
  font-size: 0.8rem !important;
 
1661
  border-color: var(--pruna-border) !important;
1662
  background: transparent !important;
1663
  }
1664
+ input, textarea, select {
1665
  background: var(--pruna-bg-card) !important;
1666
  border-color: var(--pruna-border) !important;
1667
  color: var(--pruna-text-primary) !important;
1668
+ border-radius: 10px !important;
 
 
 
 
 
 
 
 
 
 
1669
  }
1670
+ .view-filters input,
1671
+ .leaderboard-controls .wrap-inner input {
1672
+ background: transparent !important;
1673
+ border: none !important;
1674
+ box-shadow: none !important;
1675
+ min-height: 28px !important;
1676
+ height: 28px !important;
1677
+ padding: 0 4px !important;
1678
+ line-height: 28px !important;
1679
  }
1680
+ .view-filters .wrap-inner:focus-within,
1681
+ .leaderboard-controls .wrap-inner:focus-within {
1682
+ border-color: var(--pruna-focus-border) !important;
1683
+ box-shadow: var(--pruna-focus-ring) !important;
 
1684
  }
1685
+ footer, .footer { display: none !important; }
1686
 
1687
+ .pareto-note,
1688
+ .pareto-note.block,
1689
+ .pareto-note .html-container,
1690
+ .pareto-note .prose {
1691
+ border: none !important;
1692
+ background: transparent !important;
1693
+ box-shadow: none !important;
1694
+ padding: 0 !important;
1695
+ margin: 0 0 12px !important;
1696
  }
1697
+ .pareto-note-copy {
1698
+ margin: 0;
1699
+ padding: 14px 16px;
1700
+ background: var(--pruna-callout-bg, var(--pruna-bg-card));
1701
+ border: 1px dashed color-mix(in oklab, var(--pruna-border) 70%, transparent);
1702
+ border-radius: 10px;
1703
+ color: var(--pruna-text-muted);
1704
+ font-size: 0.95rem;
1705
+ line-height: 1.5;
1706
  }
1707
  .pareto-metric-block {
1708
  padding-bottom: 0.5rem;
 
1717
  font-size: 1.05rem;
1718
  font-weight: 600;
1719
  }
1720
+ .pareto-subhead,
1721
+ .pareto-subhead.block,
1722
+ .pareto-subhead .prose,
1723
+ .pareto-subhead .html-container {
1724
+ border: none !important;
1725
+ background: transparent !important;
1726
+ box-shadow: none !important;
1727
+ padding: 0 !important;
1728
+ margin: 0 0 0.4rem !important;
1729
  color: var(--pruna-text-muted);
1730
+ }
1731
+ .pareto-subhead h4,
1732
+ .pareto-subhead .prose h4 {
1733
+ margin: 0 !important;
1734
+ color: var(--pruna-text-muted) !important;
1735
+ font-size: 0.95rem !important;
1736
+ font-weight: 600 !important;
1737
  }
1738
 
1739
  .options,
1740
+ ul.options {
1741
+ position: absolute !important;
1742
+ top: calc(100% + 4px) !important;
1743
+ bottom: auto !important;
1744
+ left: 0 !important;
1745
+ right: auto !important;
1746
+ width: 100% !important;
1747
+ max-height: min(280px, 60vh) !important;
1748
+ background: var(--pruna-bg-card) !important;
1749
+ color: var(--pruna-text-primary) !important;
1750
+ border: 1px solid var(--pruna-border) !important;
1751
+ border-radius: 10px !important;
1752
+ overflow: auto !important;
1753
+ z-index: 50 !important;
1754
+ }
1755
+ .options .item:first-child,
1756
+ .options li:first-child {
1757
+ border-top-left-radius: 10px !important;
1758
+ border-top-right-radius: 10px !important;
1759
+ }
1760
+ .options .item:last-child,
1761
+ .options li:last-child {
1762
+ border-bottom-left-radius: 10px !important;
1763
+ border-bottom-right-radius: 10px !important;
1764
+ }
1765
  .dropdown-arrow-inner {
1766
  background: var(--pruna-bg-card) !important;
1767
  color: var(--pruna-text-primary) !important;
 
1780
  input[type="range"] {
1781
  accent-color: var(--pruna-accent);
1782
  }
 
 
 
 
 
 
 
 
1783
  """
1784
 
1785
  theme = gr.themes.Base(
 
1972
  "OneIG Anime Elo",
1973
  "OneIG Human Elo",
1974
  "OneIG Object Elo",
 
 
1975
  "Median Generation Time (s)",
1976
  "Min Generation Time (s)",
 
 
1977
  "Price / Image (USD)",
1978
  "Evaluation Date (UTC)",
1979
  "URL",
 
2335
  "data": qwen_df,
2336
  "columns": qwen_display_columns,
2337
  "metric_ids": qwen_metric_ids,
2338
+ "note": "Rapidata Elo is a metric on this dataset, not a dataset of its own.",
 
 
 
2339
  "samples": qwen_samples,
2340
  },
2341
  {
 
2345
  "columns": oneig_display_columns,
2346
  "metric_ids": oneig_metric_ids,
2347
  "note": (
2348
+ "Alignment Overall is the mean of the available category scores. "
2349
  "Missing categories are skipped for that model."
2350
  ),
2351
  "samples": oneig_samples,
 
2590
  });
2591
  })();
2592
  </script>
2593
+ <script>
2594
+ (() => {
2595
+ if (window.__inferbenchChipScrollBound) return;
2596
+ window.__inferbenchChipScrollBound = true;
2597
+ document.addEventListener("wheel", (event) => {
2598
+ const row = event.target.closest?.(".view-filters .filter-chips .wrap-inner");
2599
+ if (!row || row.scrollWidth <= row.clientWidth + 1) return;
2600
+ if (Math.abs(event.deltaY) < Math.abs(event.deltaX)) return;
2601
+ row.scrollLeft += event.deltaY;
2602
+ event.preventDefault();
2603
+ }, { capture: true, passive: false });
2604
+ })();
2605
+ </script>
2606
  """
2607
 
2608
  with gr.Blocks(
data/text_to_image.jsonl DELETED
@@ -1,31 +0,0 @@
1
- {"Platform": "Replicate", "Owner": "Pruna AI", "Device": "1xH100", "Model": "FLUX Schnell", "Optimization": "speed_mode_juiced", "URL": "https://replicate.com/prunaai/flux-schnell", "PartiPromts (ARNIQA)": 0.5665, "PartiPromts (ClipScore)": 27.4594, "PartiPromts (ClipIQA)": 0.8594, "PartiPromts (Sharpness - Laplacian Variance)": 4385.7579, "Long Text Bench (edit_distance)": 178.5125, "Long Text Bench (text_word_accuracy)": null, "HPS (v2.1)": 0.2106, "DrawBench (ClipScore)": 27.9985, "DrawBench (Image Reward)": 1.0057, "GenAI-Bench (VQA)": 0.79, "OneIG (Anime and Stylization) (Alignment Score)": null, "Median Inference Time": 0.9082, "Price per Image": 0.055}
2
- {"Platform": "fal.ai", "Owner": "fal.ai", "Device": "Undisclosed", "Model": "FLUX 1.1 Pro", "Optimization": "Undisclosed", "URL": "https://fal.ai/models/fal-ai/flux-pro/v1.1", "GenAI-Bench (VQA)": 0.7745, "HPS (v2.1)": 0.2093, "PartiPromts (ARNIQA)": 0.5998, "PartiPromts (ClipScore)": 26.7942, "PartiPromts (ClipIQA)": 0.9282, "PartiPromts (Sharpness - Laplacian Variance)": 14290.615, "Long Text Bench (edit_distance)": 163.5862, "Long Text Bench (text_word_accuracy)": null, "OneIG (Anime and Stylization) (Alignment Score)": null, "DrawBench (ClipScore)": 27.2099, "DrawBench (Image Reward)": 0.8609, "Median Inference Time": 4.031, "Price per Image": 0.04}
3
- {"Platform": "fal.ai", "Owner": "fal.ai", "Device": "Undisclosed", "Model": "FLUX Dev", "Optimization": "dev", "URL": "https://fal.ai/models/fal-ai/flux/dev", "Long Text Bench (edit_distance)": 177.7438, "Long Text Bench (text_word_accuracy)": null, "OneIG (Anime and Stylization) (Alignment Score)": null, "GenAI-Bench (VQA)": 0.7443, "DrawBench (ClipScore)": 27.0164, "DrawBench (Image Reward)": 0.8933, "PartiPromts (ARNIQA)": 0.6305, "PartiPromts (ClipScore)": 27.2592, "PartiPromts (ClipIQA)": 0.8593, "PartiPromts (Sharpness - Laplacian Variance)": 5212.9337, "HPS (v2.1)": 0.1932, "Median Inference Time": 1.9838, "Price per Image": 0.025}
4
- {"Platform": "Prodia", "Owner": "Prodia", "Device": "Undisclosed", "Model": "FLUX Dev", "Optimization": "fast", "URL": "https://app.prodia.com/models", "PartiPromts (ARNIQA)": 0.6224, "PartiPromts (ClipScore)": 26.8405, "PartiPromts (ClipIQA)": 0.9159, "PartiPromts (Sharpness - Laplacian Variance)": 5774.8605, "DrawBench (ClipScore)": 26.6909, "DrawBench (Image Reward)": 0.9675, "GenAI-Bench (VQA)": 0.7363, "OneIG (Anime and Stylization) (Alignment Score)": null, "Long Text Bench (edit_distance)": 178.3375, "Long Text Bench (text_word_accuracy)": null, "HPS (v2.1)": 0.1995, "Median Inference Time": 1.9977, "Price per Image": 0.02}
5
- {"Platform": "Prodia", "Owner": "Prodia", "Device": "Undisclosed", "Model": "FLUX 1.1 Pro", "Optimization": "Undisclosed", "URL": "https://app.prodia.com/models", "HPS (v2.1)": 0.2075, "Long Text Bench (edit_distance)": 178.5125, "Long Text Bench (text_word_accuracy)": null, "DrawBench (ClipScore)": 27.6498, "DrawBench (Image Reward)": 0.9484, "GenAI-Bench (VQA)": 0.7739, "OneIG (Anime and Stylization) (Alignment Score)": null, "PartiPromts (ARNIQA)": 0.6181, "PartiPromts (ClipScore)": 27.3402, "PartiPromts (ClipIQA)": 0.895, "PartiPromts (Sharpness - Laplacian Variance)": 6932.3266, "Median Inference Time": 3.285, "Price per Image": 0.04}
6
- {"Platform": "fal.ai", "Owner": "fal.ai", "Device": "Undisclosed", "Model": "FLUX Schnell", "Optimization": "schnell", "URL": "https://fal.ai/models/fal-ai/flux/schnell", "HPS (v2.1)": 0.206, "Long Text Bench (edit_distance)": 178.3125, "Long Text Bench (text_word_accuracy)": 0.0003, "PartiPromts (ARNIQA)": 0.6637, "PartiPromts (ClipScore)": 27.755, "PartiPromts (ClipIQA)": 0.8991, "PartiPromts (Sharpness - Laplacian Variance)": 6420.4761, "OneIG (Anime and Stylization) (Alignment Score)": null, "GenAI-Bench (VQA)": 0.7919, "DrawBench (ClipScore)": 28.0585, "DrawBench (Image Reward)": 0.9376, "Median Inference Time": 0.7981, "Price per Image": 0.003}
7
- {"Platform": "Together AI", "Owner": "Black Forest Labs", "Device": "Undisclosed", "Model": "FLUX Dev", "Optimization": "dev", "URL": "https://www.together.ai/models/flux-1-dev", "Long Text Bench (edit_distance)": 178.0437, "Long Text Bench (text_word_accuracy)": null, "GenAI-Bench (VQA)": 0.7592, "OneIG (Anime and Stylization) (Alignment Score)": null, "PartiPromts (ARNIQA)": 0.5982, "PartiPromts (ClipScore)": 27.5003, "PartiPromts (ClipIQA)": 0.8799, "PartiPromts (Sharpness - Laplacian Variance)": 5101.113, "HPS (v2.1)": 0.1973, "DrawBench (ClipScore)": 27.3007, "DrawBench (Image Reward)": 0.9612, "Median Inference Time": 3.862, "Price per Image": 0.025}
8
- {"Platform": "Replicate", "Owner": "Black Forest Labs", "Device": "1xH100", "Model": "FLUX 1.1 Pro", "Optimization": "Undisclosed", "URL": "https://replicate.com/black-forest-labs/flux-1.1-pro", "PartiPromts (ARNIQA)": 0.5879, "PartiPromts (ClipScore)": 27.8015, "PartiPromts (ClipIQA)": 0.8273, "PartiPromts (Sharpness - Laplacian Variance)": 6773.8274, "HPS (v2.1)": 0.2048, "OneIG (Anime and Stylization) (Alignment Score)": 0.87, "OneIG (General Object) (Alignment Score)": 0.83, "OneIG (Portrait) (Alignment Score)": 0.78, "DrawBench (ClipScore)": 27.7958, "DrawBench (Image Reward)": 0.9258, "Long Text Bench (edit_distance)": 163.28, "Long Text Bench (text_word_accuracy)": null, "GenAI-Bench (VQA)": 0.7813, "Median Inference Time": 2.8571, "Price per Image": 0.04}
9
- {"Platform": "Black Forest Labs", "Owner": "Black Forest Labs", "Device": "1xH100", "Model": "FLUX 2 pro", "Optimization": "Undisclosed", "URL": "https://bfl.ai/models", "PartiPromts (ARNIQA)": 0.6488, "PartiPromts (ClipScore)": 28.0808, "PartiPromts (ClipIQA)": 0.9172, "PartiPromts (Sharpness - Laplacian Variance)": 9335.3041, "GenAI-Bench (VQA)": 0.9633, "OneIG (Anime and Stylization) (Alignment Score)": null, "DrawBench (ClipScore)": 28.22, "DrawBench (Image Reward)": 1.0339, "Long Text Bench (edit_distance)": 159.2424, "Long Text Bench (text_word_accuracy)": null, "HPS (v2.1)": 0.1894, "Median Inference Time": 9.1787, "Price per Image": 0.1}
10
- {"Platform": "fal.ai", "Owner": "fal.ai", "Device": "Undisclosed", "Model": "FLUX Krea", "Optimization": "Undisclosed", "URL": "https://fal.ai/models/fal-ai/flux/krea", "DrawBench (ClipScore)": 28.1482, "DrawBench (Image Reward)": 0.9853, "OneIG (Anime and Stylization) (Alignment Score)": null, "Long Text Bench (edit_distance)": 178.5125, "Long Text Bench (text_word_accuracy)": null, "GenAI-Bench (VQA)": 0.7949, "PartiPromts (ARNIQA)": 0.615, "PartiPromts (ClipScore)": 27.7512, "PartiPromts (ClipIQA)": 0.848, "PartiPromts (Sharpness - Laplacian Variance)": 4043.6115, "HPS (v2.1)": 0.1902, "Median Inference Time": 2.0193, "Price per Image": 0.025}
11
- {"Platform": "Runware", "Owner": "Runware", "Device": "Undisclosed", "Model": "FLUX Dev", "Optimization": "Undisclosed", "URL": "https://runware.ai/models#", "PartiPromts (ARNIQA)": 0.638, "PartiPromts (ClipScore)": 27.3993, "PartiPromts (ClipIQA)": 0.8887, "PartiPromts (Sharpness - Laplacian Variance)": 6238.0671, "DrawBench (ClipScore)": 27.2074, "DrawBench (Image Reward)": 0.9923, "GenAI-Bench (VQA)": 0.7761, "Long Text Bench (edit_distance)": 178.3625, "Long Text Bench (text_word_accuracy)": null, "HPS (v2.1)": 0.194, "OneIG (Anime and Stylization) (Alignment Score)": null, "Median Inference Time": 4.2133, "Price per Image": 0.0038}
12
- {"Platform": "Black Forest Labs", "Owner": "Black Forest Labs", "Device": "1xH100", "Model": "FLUX 2 beta", "Optimization": "Undisclosed", "URL": "https://bfl.ai/models", "DrawBench (ClipScore)": 28.2489, "DrawBench (Image Reward)": 1.0498, "PartiPromts (ARNIQA)": 0.5988, "PartiPromts (ClipScore)": 28.2471, "PartiPromts (ClipIQA)": 0.8992, "PartiPromts (Sharpness - Laplacian Variance)": 7910.49, "Long Text Bench (edit_distance)": 155.9429, "Long Text Bench (text_word_accuracy)": null, "OneIG (Anime and Stylization) (Alignment Score)": null, "HPS (v2.1)": 0.2121, "GenAI-Bench (VQA)": 0.9673, "Median Inference Time": 15.5713, "Price per Image": 0.025}
13
- {"Platform": "Replicate", "Owner": "Black Forest Labs", "Device": "1xH100", "Model": "FLUX Schnell", "Optimization": "go_fast", "URL": "https://replicate.com/black-forest-labs/flux-schnell", "DrawBench (ClipScore)": 28.0363, "DrawBench (Image Reward)": 0.9009, "GenAI-Bench (VQA)": 0.7765, "OneIG (Anime and Stylization) (Alignment Score)": 0.88, "OneIG (Portrait) (Alignment Score)": 0.79, "OneIG (General Object) (Alignment Score)": 0.83, "PartiPromts (ARNIQA)": 0.5714, "PartiPromts (ClipScore)": 27.9771, "PartiPromts (ClipIQA)": 0.7947, "PartiPromts (Sharpness - Laplacian Variance)": 5685.5457, "HPS (v2.1)": 0.2086, "Long Text Bench (edit_distance)": 178.5125, "Long Text Bench (text_word_accuracy)": null, "Median Inference Time": 0.979, "Price per Image": 0.003}
14
- {"Platform": "Replicate", "Owner": "Black Forest Labs", "Device": "1xH100", "Model": "FLUX Krea", "Optimization": "go_fast", "URL": "https://replicate.com/black-forest-labs/flux-krea-dev", "DrawBench (ClipScore)": 28.3217, "DrawBench (Image Reward)": 0.9533, "PartiPromts (ARNIQA)": 0.6336, "PartiPromts (ClipScore)": 27.88, "PartiPromts (ClipIQA)": 0.8167, "PartiPromts (Sharpness - Laplacian Variance)": 4121.4541, "HPS (v2.1)": 0.1967, "OneIG (Anime and Stylization) (Alignment Score)": 0.88, "OneIG (Portrait) (Alignment Score)": 0.81, "OneIG (General Object) (Alignment Score)": 0.84, "GenAI-Bench (VQA)": 0.8218, "Long Text Bench (edit_distance)": 178.5125, "Long Text Bench (text_word_accuracy)": null, "Median Inference Time": 1.8774, "Price per Image": 0.025}
15
- {"Platform": "Prodia", "Owner": "Prodia", "Device": "Undisclosed", "Model": "FLUX Schnell", "Optimization": "fast", "URL": "https://app.prodia.com/models", "PartiPromts (ARNIQA)": 0.5726, "PartiPromts (ClipScore)": 27.388, "PartiPromts (ClipIQA)": 0.8827, "PartiPromts (Sharpness - Laplacian Variance)": 5464.0814, "OneIG (Anime and Stylization) (Alignment Score)": null, "Long Text Bench (edit_distance)": 178.5125, "Long Text Bench (text_word_accuracy)": null, "DrawBench (ClipScore)": 27.7805, "DrawBench (Image Reward)": 0.9845, "GenAI-Bench (VQA)": 0.7878, "HPS (v2.1)": 0.2128, "Median Inference Time": 0.7472, "Price per Image": 0.0015}
16
- {"Platform": "Replicate", "Owner": "Black Forest Labs", "Device": "1xH100", "Model": "FLUX Dev", "Optimization": "go_fast", "URL": "https://replicate.com/black-forest-labs/flux-dev", "PartiPromts (ARNIQA)": 0.5764, "PartiPromts (ClipScore)": 27.8144, "PartiPromts (ClipIQA)": 0.7859, "PartiPromts (Sharpness - Laplacian Variance)": 5164.9466, "Long Text Bench (edit_distance)": 177.8931, "Long Text Bench (text_word_accuracy)": null, "DrawBench (ClipScore)": 27.7887, "DrawBench (Image Reward)": 1.0318, "OneIG (Anime and Stylization) (Alignment Score)": 0.85, "OneIG (Portrait) (Alignment Score)": 0.78, "OneIG (General Object) (Alignment Score)": 0.8, "HPS (v2.1)": 0.1982, "GenAI-Bench (VQA)": 0.7686, "Median Inference Time": 1.8018, "Price per Image": 0.025}
17
- {"Platform": "Together AI", "Owner": "Black Forest Labs", "Device": "Undisclosed", "Model": "FLUX Schnell", "Optimization": "schnell", "URL": "https://www.together.ai/models/flux-1-schnell", "PartiPromts (ARNIQA)": 0.6082, "PartiPromts (ClipScore)": 27.6475, "PartiPromts (ClipIQA)": 0.898, "PartiPromts (Sharpness - Laplacian Variance)": 6446.8138, "DrawBench (ClipScore)": 27.6897, "DrawBench (Image Reward)": 0.9301, "GenAI-Bench (VQA)": 0.7706, "OneIG (Anime and Stylization) (Alignment Score)": null, "HPS (v2.1)": 0.2058, "Long Text Bench (edit_distance)": 178.5125, "Long Text Bench (text_word_accuracy)": null, "Median Inference Time": 1.3602, "Price per Image": 0.003}
18
- {"Platform": "Runware", "Owner": "Black Forest Labs", "Device": "Undisclosed", "Model": "FLUX 1.1 Pro", "Optimization": "Undisclosed", "URL": "https://runware.ai/models#", "PartiPromts (ARNIQA)": 0.659, "PartiPromts (ClipScore)": 27.4929, "PartiPromts (ClipIQA)": 0.9074, "PartiPromts (Sharpness - Laplacian Variance)": 6874.5826, "Long Text Bench (edit_distance)": 167.1325, "Long Text Bench (text_word_accuracy)": null, "DrawBench (ClipScore)": 27.7755, "DrawBench (Image Reward)": 0.9277, "HPS (v2.1)": 0.1971, "OneIG (Anime and Stylization) (Alignment Score)": null, "GenAI-Bench (VQA)": 0.7723, "Median Inference Time": 4.2135, "Price per Image": 0.04}
19
- {"Platform": "Runware", "Owner": "Runware", "Device": "Undisclosed", "Model": "FLUX Schnell", "Optimization": "Undisclosed", "URL": "https://runware.ai/models#", "HPS (v2.1)": 0.2026, "PartiPromts (ARNIQA)": 0.6344, "PartiPromts (ClipScore)": 27.7251, "PartiPromts (ClipIQA)": 0.8873, "PartiPromts (Sharpness - Laplacian Variance)": 7144.7132, "GenAI-Bench (VQA)": 0.769, "OneIG (Anime and Stylization) (Alignment Score)": null, "DrawBench (ClipScore)": 27.9708, "DrawBench (Image Reward)": 0.9571, "Long Text Bench (edit_distance)": 178.3812, "Long Text Bench (text_word_accuracy)": 0.0005, "Median Inference Time": 1.7584, "Price per Image": 0.0013}
20
- {"Platform": "Together AI", "Owner": "Black Forest Labs", "Device": "Undisclosed", "Model": "FLUX Krea", "Optimization": "dev", "URL": "https://www.together.ai/models/flux-1-krea-dev", "Long Text Bench (edit_distance)": 178.5125, "Long Text Bench (text_word_accuracy)": null, "DrawBench (ClipScore)": 28.2254, "DrawBench (Image Reward)": 1.101, "GenAI-Bench (VQA)": 0.797, "PartiPromts (ARNIQA)": 0.618, "PartiPromts (ClipScore)": 27.6353, "PartiPromts (ClipIQA)": 0.8776, "PartiPromts (Sharpness - Laplacian Variance)": 5297.8361, "OneIG (Anime and Stylization) (Alignment Score)": null, "HPS (v2.1)": 0.1981, "Median Inference Time": 4.3831, "Price per Image": 0.025}
21
- {"Platform": "Bria", "Owner": "Bria", "Device": "Undisclosed", "Model":"FIBO", Optimization: "Undisclosed", "URL": "https://bria.ai/models", "OneIG (Anime and Stylization) (Alignment Score)": 0.87, "Median Inference Time": 15.8328 , "Price per Image": 0.04}
22
- {"Platform": "Replicate", "Owner": "Black Forest Labs", "Device": "Undisclosed", "Model": "FLUX 1.1 Pro Ultra", "Optimization": "Undisclosed", "URL": "", "OneIG (Anime and Stylization) (Alignment Score)": 0.89, "OneIG (Portrait) (Alignment Score)": 0.8, "OneIG (General Object) (Alignment Score)": 0.85}
23
- {"Platform": "Replicate", "Owner": "Black Forest Labs", "Device": "Undisclosed", "Model": "FLUX 2 Pro", "Optimization": "", URL: "", "OneIG (Anime and Stylization) (Alignment Score)": 0.92, "OneIG (Portrait) (Alignment Score)": 0.84, "OneIG (General Object) (Alignment Score)": 0.88}
24
- {"Platform": "Replicate", "Owner": "Black Forest Labs", "Device": "Undisclosed", "Model": "FLUX 2 Flex", "Optimization": "", URL: "", "OneIG (Anime and Stylization) (Alignment Score)": 0.92, "OneIG (Portrait) (Alignment Score)": 0.84, "OneIG (General Object) (Alignment Score)": 0.90}
25
- {"Platform": "Replicate", "Owner": "Black Forest Labs", "Device": "Undisclosed", "Model": "Flux 2 Max", "Optimization": "", URL: "", "OneIG (Anime and Stylization) (Alignment Score)": 0.93, "OneIG (Portrait) (Alignment Score)": 0.85, "OneIG (General Object) (Alignment Score)": 0.89}
26
- {"Platform": "Replicate", "Owner": "Black Forest Labs", "Device": "Undisclosed", "Model": "Flux 2 Pro", "Optimization": "", URL: "", "OneIG (Anime and Stylization) (Alignment Score)": null, "OneIG (Portrait) (Alignment Score)": 0.84, "OneIG (General Object) (Alignment Score)": 0.90}
27
- {"Platform": "Replicate", "Owner": "Open AI", "Device": "Undisclosed", "Model": "GPT Image 1.5", "Optimization": "", URL: "", "OneIG (Anime and Stylization) (Alignment Score)": 0.92, "OneIG (Portrait) (Alignment Score)": 0.85, "OneIG (General Object) (Alignment Score)": null}
28
- {"Platform": "Replicate", "Owner": "Pruna AI", "Device": "Undisclosed", "Model": "Hidream I1 Dev", "Optimzation": "Extra Juiced", "URL":"", "OneIG (Anime and Stylization) (Alignment Score)": 0.87, "OneIG (Portrait) (Alignment Score)": 0.79, "OneIG (General Object) (Alignment Score)": 0.82}
29
- {"Platform": "Replicate", "Owner": "Pruna AI", "Device": "Undisclosed", "Model": "Hidream I1 Faast", "Optimzation": "Extra Juiced", "URL":"", "OneIG (Anime and Stylization) (Alignment Score)": 0.87, "OneIG (Portrait) (Alignment Score)": 0.79, "OneIG (General Object) (Alignment Score)": 0.82}
30
-
31
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,2 +1 @@
1
- gradio-leaderboard
2
- plotly
 
1
+ plotly
 
ui.py CHANGED
@@ -22,6 +22,22 @@ MAX_COMPARE_MODELS = 4
22
  DEFAULT_COMPARE_PROMPTS = 3
23
  MAX_COMPARE_PROMPTS = 8
24
  MAX_PARETO_METRICS = 8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  ABOUT_OVERVIEW_CONTENT = """
27
  # About P-Bench
@@ -33,7 +49,7 @@ across P-Bench.
33
 
34
  ## How to read it
35
 
36
- 1. Pick a **dataset** and a **metric**. The title is always Dataset | Metric.
37
  2. **Leaderboards**: ranked by that metric. Price and generation time sit in
38
  the same table.
39
  3. **Pareto plots**: mark models that are not beaten on both higher score
@@ -154,18 +170,20 @@ def render_header():
154
  gr.HTML(
155
  f"""
156
  <header class="app-header">
157
- <button type="button" class="theme-toggle" data-mode="dark" aria-label="Switch to light mode" title="Switch to light mode">
158
- <svg class="theme-icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
159
- <circle cx="12" cy="12" r="4"></circle>
160
- <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
161
- </svg>
162
- <svg class="theme-icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
163
- <path d="M21 14.5A8.5 8.5 0 1 1 9.5 3 7 7 0 0 0 21 14.5z"></path>
164
- </svg>
165
- </button>
166
- <div class="app-header-brand">
167
- <img class="app-header-logo" src="{_MASCOT_DATA_URI}" alt="" />
168
- <h1>P-Bench</h1>
 
 
169
  </div>
170
  <p class="app-header-tagline">Compare text-to-image models on quality, speed, and price</p>
171
  </header>
@@ -201,6 +219,9 @@ def _coerce_sample_dataset(datasets, dataset_id):
201
  return dataset_id
202
 
203
 
 
 
 
204
  def _metric_choices(datasets, metrics, dataset_id):
205
  dataset = _item(datasets, dataset_id)
206
  if not dataset:
@@ -209,41 +230,62 @@ def _metric_choices(datasets, metrics, dataset_id):
209
  data = dataset.get("data")
210
  columns = getattr(data, "columns", [])
211
  return [
212
- (metric["name"], metric["id"])
213
  for metric in metrics
214
  if metric["id"] in allowed and metric["column"] in columns
215
  ]
216
 
217
 
218
- def _coerce_metric(datasets, metrics, dataset_id, metric_id):
219
- """Keep a selected metric only if it is valid for this dataset."""
220
- if isinstance(metric_id, (list, tuple)):
221
- metric_id = metric_id[0] if metric_id else None
222
- if metric_id is None or metric_id == "":
223
- return None
224
  choices = _metric_choices(datasets, metrics, dataset_id)
225
- ids = [choice[1] for choice in choices]
226
- if metric_id in ids:
227
- return metric_id
228
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
 
231
  def _metric_dropdown_value(metric_id):
232
- """Gradio multiselect value for an optional single metric."""
233
- return [metric_id] if metric_id else []
 
 
 
234
 
235
 
236
  def _model_choices(datasets, dataset_id):
 
 
 
237
  dataset = _item(datasets, dataset_id)
238
  data = dataset.get("data") if dataset else None
239
  if data is None or "Model" not in getattr(data, "columns", []):
 
240
  return []
241
  models = data["Model"].dropna().astype(str).unique().tolist()
242
  # (label, value) so the UI shows the shared name but filters on the raw id.
243
- return sorted(
244
  ((display_model_name(model), model) for model in models),
245
  key=lambda item: item[0].casefold(),
246
  )
 
 
247
 
248
 
249
  def _model_choice_values(choices):
@@ -267,10 +309,24 @@ def _metric_columns(datasets, metrics, dataset_id):
267
  def _view_title(datasets, metrics, dataset_id, metric_id):
268
  dataset = _item(datasets, dataset_id)
269
  dataset_name = dataset["name"] if dataset else "Dataset"
270
- metric = _item(metrics, metric_id) if metric_id else None
271
- if not metric:
 
 
 
 
 
272
  return dataset_name
273
- return f"{dataset_name} | {metric['name']}"
 
 
 
 
 
 
 
 
 
274
 
275
 
276
  _LEADERBOARD_META_COLUMNS = [
@@ -283,7 +339,12 @@ _LEADERBOARD_META_COLUMNS = [
283
 
284
 
285
  def _columns_for_metric(dataset, metric_column):
286
- """When a metric is selected, show identity + that score + time/price."""
 
 
 
 
 
287
  available = list(getattr(dataset.get("data"), "columns", [])) or list(
288
  dataset.get("columns") or []
289
  )
@@ -293,8 +354,9 @@ def _columns_for_metric(dataset, metric_column):
293
  if column in available
294
  ]
295
  meta = [column for column in _LEADERBOARD_META_COLUMNS if column in available]
296
- if metric_column and metric_column in available:
297
- return [*identity, metric_column, *meta]
 
298
  return [column for column in (dataset.get("columns") or available) if column != "URL"]
299
 
300
 
@@ -302,12 +364,21 @@ def resolve_view(datasets, metrics, dataset_id, metric_id):
302
  dataset = _item(datasets, dataset_id)
303
  if not dataset:
304
  return None
305
- metric_id = _coerce_metric(datasets, metrics, dataset_id, metric_id)
306
- metric = _item(metrics, metric_id) if metric_id else None
307
- score_column = metric["column"] if metric else None
308
- if score_column:
309
- columns = _columns_for_metric(dataset, score_column)
310
- score_columns = [score_column]
 
 
 
 
 
 
 
 
 
311
  else:
312
  columns = [
313
  column
@@ -315,11 +386,13 @@ def resolve_view(datasets, metrics, dataset_id, metric_id):
315
  if column != "URL"
316
  ]
317
  score_columns = _metric_columns(datasets, metrics, dataset_id)
 
 
318
  return {
319
  "dataset": dataset,
320
  "metric": metric,
321
- "metric_id": metric_id,
322
- "title": _view_title(datasets, metrics, dataset["id"], metric_id),
323
  "data": dataset["data"],
324
  "columns": columns,
325
  "score_column": score_column,
@@ -385,6 +458,22 @@ def _leaderboard_cell_class(column):
385
  return "metric-score"
386
 
387
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  def _leaderboard_html(data, columns, score_columns, overall_column):
389
  leaderboard = _leaderboard_dataframe(
390
  data, columns, score_columns, overall_column
@@ -441,28 +530,11 @@ def _filter_choices(data, column):
441
  return sorted(data[column].dropna().astype(str).unique().tolist())
442
 
443
 
444
- def _filter_leaderboard(data, search_term, platform, owner, optimized, models=None):
445
  filtered = data.copy()
446
  if models:
447
  if "Model" in filtered.columns:
448
  filtered = filtered[filtered["Model"].astype(str).isin(models)]
449
- if search_term:
450
- search_columns = [
451
- column
452
- for column in ["Model", "Platform", "Endpoint Owner"]
453
- if column in filtered.columns
454
- ]
455
- matches = pd.Series(False, index=filtered.index)
456
- for column in search_columns:
457
- matches |= filtered[column].astype(str).str.contains(
458
- search_term, case=False, na=False
459
- )
460
- if "Model" in filtered.columns:
461
- display_names = filtered["Model"].map(display_model_name).astype(str)
462
- matches |= display_names.str.contains(
463
- search_term, case=False, na=False
464
- )
465
- filtered = filtered[matches]
466
 
467
  for column, values in [
468
  ("Platform", platform),
@@ -475,7 +547,7 @@ def _filter_leaderboard(data, search_term, platform, owner, optimized, models=No
475
 
476
 
477
  def _leaderboard_dataframe(data, columns, score_columns, overall_column): # noqa: ARG001
478
- skip_columns = {"URL", "Rank"}
479
  preferred_prefix = [
480
  column
481
  for column in ["Model", "Platform", "Endpoint Owner", "Optimized"]
@@ -515,20 +587,18 @@ def _leaderboard_dataframe(data, columns, score_columns, overall_column): # noq
515
  if column not in seen:
516
  seen.add(column)
517
  ordered_columns.append(column)
 
 
518
 
519
  leaderboard = data[ordered_columns].copy()
520
 
521
- if overall_column and overall_column in data.columns:
522
- leaderboard = (
523
- leaderboard.assign(_sort_key=data[overall_column])
524
- .sort_values("_sort_key", ascending=False, na_position="last")
525
- .drop(columns=["_sort_key"])
526
- .reset_index(drop=True)
527
  )
528
- else:
529
- leaderboard = leaderboard.reset_index(drop=True)
530
-
531
- leaderboard.insert(0, "Rank", leaderboard.index + 1)
532
  return leaderboard.rename(columns=_display_label)
533
 
534
 
@@ -545,6 +615,7 @@ def _display_label(column):
545
  "P-Judge Overall": "P-Judger (Pruna)",
546
  "Datapoint Elo": "Datapoint Elo",
547
  "Rapidata Elo": "Rapidata Elo",
 
548
  "Arena Elo": "Overall Elo",
549
  "Arena Branding / Commercial Elo": "Branding / Commercial",
550
  "Arena 3D Imaging Elo": "3D Imaging",
@@ -583,29 +654,52 @@ def _pareto_frontier_mask(x_values, scores):
583
  return mask
584
 
585
 
586
- def _empty_figure(message):
587
- fig = go.Figure()
588
- fig.add_annotation(
589
- text=message,
590
- xref="paper",
591
- yref="paper",
592
- x=0.5,
593
- y=0.5,
594
- showarrow=False,
595
- font={"color": "#a3a3a3", "size": 14},
596
  )
597
- fig.update_layout(
598
- title=None,
599
- autosize=True,
600
- height=420,
601
- margin={"l": 56, "r": 28, "t": 28, "b": 80},
602
- plot_bgcolor="#1d1429",
603
- paper_bgcolor="#171021",
604
- font={"color": "#d4d4d4", "size": 13},
605
- xaxis={"visible": False},
606
- yaxis={"visible": False},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  )
608
- return fig
609
 
610
 
611
  def _build_pareto_figure(
@@ -719,62 +813,129 @@ def _build_pareto_figure(
719
  return fig
720
 
721
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722
  def _pareto_pair(data, score_column):
 
723
  if data is None or not score_column or score_column not in data.columns:
724
- empty = _empty_figure("No score data is available yet.")
725
- return empty, empty
726
-
727
- price_column = "Price / Image (USD)"
728
- time_column = "Min Generation Time (s)"
729
- price_fig = None
730
- time_fig = None
731
- if price_column in data.columns:
732
- price_fig = _build_pareto_figure(
733
- data,
734
- score_column,
735
- x_column=price_column,
736
- x_title="Price per image (USD)",
737
- x_hover_prefix="$",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
  )
739
- if time_column in data.columns:
740
- time_fig = _build_pareto_figure(
741
- data,
742
- score_column,
743
- x_column=time_column,
744
- x_title="Min generation time (s)",
745
- x_hover_suffix="s",
746
  )
747
  return (
748
- price_fig or _empty_figure("No price data available."),
749
- time_fig or _empty_figure("No min generation time data available."),
750
  )
751
 
752
 
 
 
 
 
 
 
 
 
 
 
 
 
 
753
  def _pareto_slot_updates(data, score_columns):
754
  """Updates for a fixed bank of Gradio Plot slots (visible/hidden)."""
755
  score_columns = [column for column in (score_columns or []) if column]
756
- updates = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
757
  for index in range(MAX_PARETO_METRICS):
758
- if index < len(score_columns):
759
- score_column = score_columns[index]
760
- price_fig, time_fig = _pareto_pair(data, score_column)
761
- updates.extend(
762
- [
763
- gr.update(visible=True),
764
- f"#### {_display_label(score_column)}",
765
- price_fig,
766
- time_fig,
767
- ]
768
- )
769
- else:
770
- updates.extend(
771
- [
772
- gr.update(visible=False),
773
- "",
774
- None,
775
- None,
776
- ]
777
- )
 
 
 
 
 
 
 
 
 
778
  return updates
779
 
780
 
@@ -860,12 +1021,22 @@ def _build_compare_samples_html(samples, selected_models, num_prompts, seed=0):
860
  return "\n".join(blocks)
861
 
862
 
863
- def _title_markdown(title):
864
- return f"# {title}"
 
 
 
865
 
866
 
867
- def _note_markdown(note):
868
- return note or ""
 
 
 
 
 
 
 
869
 
870
 
871
  def _filter_row(
@@ -877,8 +1048,6 @@ def _filter_row(
877
  require_samples=False,
878
  include_metric=True,
879
  ):
880
- metric_choices = _metric_choices(datasets, metrics, default_dataset_id)
881
- model_choices = _model_choices(datasets, default_dataset_id)
882
  metric_id = _coerce_metric(
883
  datasets, metrics, default_dataset_id, default_metric_id
884
  )
@@ -888,39 +1057,39 @@ def _filter_row(
888
  value=default_dataset_id,
889
  label="Dataset",
890
  type="value",
 
891
  scale=2,
892
  min_width=160,
893
  )
894
  metric_dd = None
895
  if include_metric:
896
  metric_dd = gr.Dropdown(
897
- choices=metric_choices,
 
 
898
  value=_metric_dropdown_value(metric_id),
899
  label="Metric",
900
  type="value",
901
  multiselect=True,
902
- max_choices=1,
903
- allow_custom_value=True,
904
  scale=2,
905
  min_width=180,
 
906
  )
907
  models_dd = gr.Dropdown(
908
- choices=model_choices,
909
  value=[],
910
  multiselect=True,
911
  label="Models",
912
  type="value",
913
- allow_custom_value=True,
914
- scale=3,
915
- min_width=200,
 
 
916
  )
917
- title = gr.Markdown(
918
- _title_markdown(
919
- _view_title(datasets, metrics, default_dataset_id, metric_id)
920
- ),
921
- elem_classes="view-title",
922
- )
923
- return dataset_dd, metric_dd, models_dd, title
924
 
925
 
926
  def render_image_workspace(datasets, metrics, default_dataset_id, default_metric_id):
@@ -931,216 +1100,219 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
931
  initial_data = initial["data"]
932
  initial_columns = initial["columns"]
933
  initial_score_columns = initial["score_columns"]
934
- initial_samples = initial.get("samples")
935
- initial_pareto_updates = _pareto_slot_updates(
936
- _filter_leaderboard(initial_data, "", [], [], [], models=[]),
937
- initial_score_columns,
938
- )
939
-
940
- with gr.Tabs(elem_classes="main-tabs"):
941
- with gr.TabItem("Leaderboards"):
942
- lb_dataset, lb_metric, lb_models, lb_title = _filter_row(
943
- datasets, metrics, default_dataset_id, None
944
- )
945
- lb_note = gr.Markdown(_note_markdown(initial.get("note")))
946
- platform_choices = _filter_choices(initial_data, "Platform")
947
- owner_choices = _filter_choices(initial_data, "Endpoint Owner")
948
- optimized_choices = _filter_choices(initial_data, "Optimized")
949
- with gr.Row(elem_classes="leaderboard-controls"):
950
- search = gr.Textbox(
951
- label="Search models",
952
- placeholder="Type a model or provider name…",
953
- scale=3,
954
- max_lines=1,
955
- elem_classes="leaderboard-search",
956
- )
957
- platform = gr.Dropdown(
958
- choices=platform_choices,
959
- value=[],
960
- label="Providers",
961
- multiselect=True,
962
- scale=1,
963
- visible=bool(platform_choices),
964
- )
965
- owner = gr.Dropdown(
966
- choices=owner_choices,
967
- value=[],
968
- label="Endpoint owners",
969
- multiselect=True,
970
- scale=1,
971
- visible=bool(owner_choices),
972
- )
973
- optimized = gr.Dropdown(
974
- choices=optimized_choices,
975
- value=[],
976
- label="Optimized",
977
- multiselect=True,
978
- scale=1,
979
- visible=bool(optimized_choices),
980
- )
981
- ranking = gr.HTML(
982
- _leaderboard_html(
983
- initial_data,
984
- initial_columns,
985
- initial_score_columns,
986
- initial_score_columns[0] if initial_score_columns else None,
987
- ),
988
- padding=False,
989
- elem_classes="ranking-table-host",
990
  )
991
-
992
- with gr.TabItem("Pareto Plots"):
993
- pp_dataset, pp_metric, pp_models, pp_title = _filter_row(
994
  datasets, metrics, default_dataset_id, None
995
  )
996
- gr.Markdown(
997
- "<span class='pareto-help'>"
998
- "Green = on the frontier (lower cost or time at the same or better score). "
999
- "Lavender = below the frontier. Pick a metric to focus on one pair of plots; "
1000
- "leave it empty to see every metric."
1001
- "</span><br/>"
1002
- "<strong class='pareto-help-emphasis'>Hover a point to see which model it is.</strong>"
1003
- )
1004
- pareto_slots = []
1005
- for slot_index in range(MAX_PARETO_METRICS):
1006
- offset = slot_index * 4
1007
- visible = slot_index < len(initial_score_columns)
1008
- with gr.Column(
1009
- visible=visible,
1010
- elem_classes="pareto-metric-block",
1011
- ) as slot_group:
1012
- slot_title = gr.Markdown(
1013
- (
1014
- f"#### {_display_label(initial_score_columns[slot_index])}"
1015
- if visible
1016
- else ""
1017
- ),
1018
- elem_classes="pareto-metric-title",
 
1019
  )
1020
- with gr.Row(equal_height=False, elem_classes="pareto-layout"):
1021
- with gr.Column(scale=1, min_width=320, elem_classes="pareto-col"):
1022
- gr.Markdown("#### Price vs score")
1023
- slot_price = gr.Plot(
1024
- value=(
1025
- initial_pareto_updates[offset + 2]
1026
- if visible
1027
- else None
1028
- ),
1029
- show_label=False,
1030
- elem_classes="pareto-plot",
1031
- )
1032
- with gr.Column(scale=1, min_width=320, elem_classes="pareto-col"):
1033
- gr.Markdown("#### Min generation time vs score")
1034
- slot_time = gr.Plot(
1035
- value=(
1036
- initial_pareto_updates[offset + 3]
1037
- if visible
1038
- else None
1039
- ),
1040
- show_label=False,
1041
- elem_classes="pareto-plot",
1042
- )
1043
- pareto_slots.append(
1044
- (slot_group, slot_title, slot_price, slot_time)
 
 
 
 
 
 
 
1045
  )
1046
 
1047
- with gr.TabItem("Samples"):
1048
- sample_default_dataset_id = _coerce_sample_dataset(
1049
- datasets, default_dataset_id
1050
- )
1051
- sm_dataset, _, sm_models, sm_title = _filter_row(
1052
- datasets,
1053
- metrics,
1054
- sample_default_dataset_id,
1055
- None,
1056
- require_samples=True,
1057
- include_metric=False,
1058
- )
1059
- with gr.Column(visible=bool(initial_samples)) as samples_panel:
1060
  gr.Markdown(
1061
- f"""
1062
- <p class="compare-samples-help">
1063
- Filter models above (up to <strong>{MAX_COMPARE_MODELS}</strong> are
1064
- shown). If none are selected, two defaults appear. Images come from
1065
- the public generation URLs for this dataset. Prompts to show chooses
1066
- how many shared prompts appear (1–{MAX_COMPARE_PROMPTS}).
1067
- </p>
1068
- """
1069
  )
1070
- with gr.Row(equal_height=False, elem_classes="compare-controls"):
1071
- prompt_count = gr.Slider(
1072
- minimum=1,
1073
- maximum=MAX_COMPARE_PROMPTS,
1074
- value=DEFAULT_COMPARE_PROMPTS,
1075
- step=1,
1076
- label="Prompts to show",
1077
- container=False,
1078
- show_reset_button=False,
1079
- scale=1,
1080
- min_width=180,
1081
- elem_classes="compare-prompt-count",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1082
  )
1083
- shuffle_button = gr.Button(
1084
- "Shuffle prompts",
1085
- variant="primary",
1086
- scale=0,
1087
- min_width=140,
1088
- elem_classes="compare-shuffle",
 
 
 
 
1089
  )
1090
- gallery = gr.HTML(
1091
- value=_samples_html(
1092
- initial_samples, [], DEFAULT_COMPARE_PROMPTS, seed=0
1093
- ),
1094
- elem_classes="compare-gallery",
1095
- )
1096
- seed_state = gr.State(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1097
 
1098
- with gr.TabItem("About"):
1099
- render_about()
1100
 
1101
  def _synced_filters(dataset_id, metric_id, models, *, clear_metric=False):
1102
  if clear_metric:
1103
- metric_id = None
1104
  else:
1105
  metric_id = _coerce_metric(datasets, metrics, dataset_id, metric_id)
1106
  model_choices = _model_choices(datasets, dataset_id)
1107
  model_values = set(_model_choice_values(model_choices))
1108
  models = [model for model in (models or []) if model in model_values]
1109
- metric_choices = _metric_choices(datasets, metrics, dataset_id)
1110
- title = _title_markdown(
1111
- _view_title(datasets, metrics, dataset_id, metric_id)
1112
- )
1113
- dataset_update = gr.update(value=dataset_id)
1114
- metric_update = gr.update(
1115
- choices=metric_choices, value=_metric_dropdown_value(metric_id)
1116
- )
1117
- models_update = gr.update(choices=model_choices, value=models)
1118
-
1119
- sample_dataset_id = _coerce_sample_dataset(datasets, dataset_id)
1120
- sample_model_choices = _model_choices(datasets, sample_dataset_id)
1121
- sample_model_values = set(_model_choice_values(sample_model_choices))
1122
- sample_models = [
1123
- model for model in (models or []) if model in sample_model_values
1124
- ]
1125
- sample_title = _title_markdown(
1126
- _view_title(datasets, metrics, sample_dataset_id, None)
1127
- )
1128
  return (
1129
  dataset_id,
1130
  metric_id,
1131
  models,
1132
- sample_dataset_id,
1133
- dataset_update,
1134
- dataset_update,
1135
- gr.update(value=sample_dataset_id),
1136
- metric_update,
1137
- metric_update,
1138
- models_update,
1139
- models_update,
1140
- gr.update(choices=sample_model_choices, value=sample_models),
1141
- title,
1142
- title,
1143
- sample_title,
1144
  )
1145
 
1146
  def _leaderboard_extras(data, platform_value, owner_value, optimized_value):
@@ -1175,78 +1347,186 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1175
  platform_value,
1176
  owner_value,
1177
  optimized_value,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1178
  )
1179
 
1180
  def _views(
1181
  dataset_id,
1182
  metric_id,
1183
  models,
1184
- search_term,
1185
  platform_value,
1186
  owner_value,
1187
  optimized_value,
1188
  num_prompts,
1189
  seed,
 
 
 
 
1190
  ):
1191
  view = resolve_view(datasets, metrics, dataset_id, metric_id)
1192
  data = view["data"]
1193
- filtered = _filter_leaderboard(
1194
- data,
1195
- search_term,
1196
- platform_value or [],
1197
- owner_value or [],
1198
- optimized_value or [],
1199
- models=models,
1200
- )
1201
- sort_column = view["score_column"] or (
1202
- view["score_columns"][0] if view["score_columns"] else None
1203
- )
1204
- ranking_html = _leaderboard_html(
1205
- filtered,
1206
- view["columns"],
1207
- view["score_columns"],
1208
- sort_column,
1209
- )
1210
- pareto_data = _filter_leaderboard(data, "", [], [], [], models=models)
1211
- pareto_updates = _pareto_slot_updates(pareto_data, view["score_columns"])
1212
- sample_dataset_id = _coerce_sample_dataset(datasets, dataset_id)
1213
- sample_view = resolve_view(datasets, metrics, sample_dataset_id, None)
1214
- samples = sample_view.get("samples") if sample_view else None
1215
- sample_models = [
1216
- model
1217
- for model in (models or [])
1218
- if model
1219
- in _model_choice_values(_model_choices(datasets, sample_dataset_id))
1220
- ]
1221
- samples_html = _samples_html(
1222
- samples,
1223
- sample_models,
1224
- int(num_prompts or DEFAULT_COMPARE_PROMPTS),
1225
- int(seed or 0),
1226
- )
 
 
 
 
 
 
 
 
 
 
 
1227
  return (
1228
- _note_markdown(view.get("note")),
1229
  ranking_html,
1230
  *pareto_updates,
1231
  samples_html,
1232
- gr.update(visible=bool(samples)),
1233
  )
1234
 
1235
  def on_dataset(
1236
  dataset_id,
1237
  metric_id,
1238
  models,
1239
- search_term,
1240
  platform_value,
1241
  owner_value,
1242
  optimized_value,
1243
  num_prompts,
1244
  seed,
 
1245
  ):
 
 
 
1246
  synced = _synced_filters(
1247
- dataset_id, metric_id, models, clear_metric=True
 
 
 
1248
  )
1249
  dataset_id, metric_id, models = synced[:3]
 
 
 
 
 
 
 
1250
  view = resolve_view(datasets, metrics, dataset_id, metric_id)
1251
  extras = _leaderboard_extras(
1252
  view["data"], platform_value, owner_value, optimized_value
@@ -1255,134 +1535,280 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1255
  dataset_id,
1256
  metric_id,
1257
  models,
1258
- search_term,
1259
  extras[3],
1260
  extras[4],
1261
  extras[5],
1262
  num_prompts,
1263
  seed,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1264
  )
1265
- return (*synced[4:], extras[0], extras[1], extras[2], *views)
1266
 
1267
  def on_metric(
1268
  dataset_id,
1269
  metric_id,
1270
  models,
1271
- search_term,
1272
  platform_value,
1273
  owner_value,
1274
  optimized_value,
1275
  num_prompts,
1276
  seed,
 
1277
  ):
 
 
 
1278
  metric_id = _coerce_metric(datasets, metrics, dataset_id, metric_id)
1279
- metric_choices = _metric_choices(datasets, metrics, dataset_id)
1280
- title = _title_markdown(
1281
- _view_title(datasets, metrics, dataset_id, metric_id)
1282
- )
1283
- metric_update = gr.update(
1284
- choices=metric_choices, value=_metric_dropdown_value(metric_id)
1285
- )
1286
- sample_title = _title_markdown(
1287
- _view_title(
1288
- datasets,
1289
- metrics,
1290
- _coerce_sample_dataset(datasets, dataset_id),
1291
- None,
1292
- )
1293
- )
1294
  views = _views(
1295
  dataset_id,
1296
  metric_id,
1297
  models,
1298
- search_term,
1299
  platform_value,
1300
  owner_value,
1301
  optimized_value,
1302
  num_prompts,
1303
  seed,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1304
  )
1305
  return (
1306
  metric_update,
1307
- metric_update,
1308
- title,
1309
- title,
1310
- sample_title,
1311
  *views,
 
1312
  )
1313
 
1314
  def on_models(
1315
  dataset_id,
1316
  metric_id,
1317
  models,
1318
- search_term,
1319
  platform_value,
1320
  owner_value,
1321
  optimized_value,
1322
  num_prompts,
1323
  seed,
 
1324
  ):
1325
- model_choices = _model_choices(datasets, dataset_id)
1326
- model_values = set(_model_choice_values(model_choices))
1327
- models = [model for model in (models or []) if model in model_values]
1328
- models_update = gr.update(choices=model_choices, value=models)
1329
-
1330
- sample_dataset_id = _coerce_sample_dataset(datasets, dataset_id)
1331
- sample_model_choices = _model_choices(datasets, sample_dataset_id)
1332
- sample_model_values = set(_model_choice_values(sample_model_choices))
1333
- sample_models = [
1334
- model for model in (models or []) if model in sample_model_values
1335
- ]
1336
- sample_models_update = gr.update(
1337
- choices=sample_model_choices, value=sample_models
1338
  )
 
 
 
 
1339
  views = _views(
1340
  dataset_id,
1341
  metric_id,
1342
  models,
1343
- search_term,
1344
  platform_value,
1345
  owner_value,
1346
  optimized_value,
1347
  num_prompts,
1348
  seed,
 
1349
  )
1350
- return (
1351
- models_update,
1352
- models_update,
1353
- sample_models_update,
1354
- *views,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1355
  )
 
 
 
1356
 
1357
  def on_leaderboard_filters(
1358
  dataset_id,
1359
  metric_id,
1360
  models,
1361
- search_term,
1362
  platform_value,
1363
  owner_value,
1364
  optimized_value,
 
1365
  ):
1366
- view = resolve_view(datasets, metrics, dataset_id, metric_id)
1367
- filtered = _filter_leaderboard(
1368
- view["data"],
1369
- search_term,
1370
- platform_value or [],
1371
- owner_value or [],
1372
- optimized_value or [],
1373
- models=models,
1374
  )
 
1375
  sort_column = view["score_column"] or (
1376
  view["score_columns"][0] if view["score_columns"] else None
1377
  )
1378
- return _leaderboard_html(
1379
- filtered,
1380
- view["columns"],
1381
- view["score_columns"],
1382
- sort_column,
 
 
 
 
 
 
 
 
 
1383
  )
1384
 
1385
  def on_samples_controls(dataset_id, models, num_prompts, seed):
 
1386
  view = resolve_view(datasets, metrics, dataset_id, None)
1387
  return _samples_html(
1388
  view.get("samples") if view else None,
@@ -1392,6 +1818,7 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1392
  )
1393
 
1394
  def on_shuffle(dataset_id, models, num_prompts, seed):
 
1395
  next_seed = int(seed or 0) + 1
1396
  view = resolve_view(datasets, metrics, dataset_id, None)
1397
  return next_seed, _samples_html(
@@ -1401,142 +1828,175 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1401
  next_seed,
1402
  )
1403
 
1404
- no_metric = gr.State(None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1405
  pareto_outputs = [
1406
- component
1407
- for slot_group, slot_title, slot_price, slot_time in pareto_slots
1408
- for component in (slot_group, slot_title, slot_price, slot_time)
 
 
 
 
 
 
 
 
 
 
 
 
1409
  ]
1410
-
1411
- dataset_outputs = [
1412
- lb_dataset,
1413
- pp_dataset,
1414
- sm_dataset,
1415
- lb_metric,
1416
- pp_metric,
1417
- lb_models,
1418
- pp_models,
1419
- sm_models,
1420
- lb_title,
1421
- pp_title,
1422
- sm_title,
1423
  platform,
1424
  owner,
1425
  optimized,
 
 
 
 
 
1426
  lb_note,
1427
  ranking,
1428
  *pareto_outputs,
1429
  gallery,
1430
  samples_panel,
1431
  ]
1432
- for dataset_dd, metric_dd, models_dd in (
1433
- (lb_dataset, lb_metric, lb_models),
1434
- (pp_dataset, pp_metric, pp_models),
1435
- (sm_dataset, no_metric, sm_models),
1436
- ):
1437
- dataset_dd.change(
1438
- on_dataset,
1439
- inputs=[
1440
- dataset_dd,
1441
- metric_dd,
1442
- models_dd,
1443
- search,
1444
- platform,
1445
- owner,
1446
- optimized,
1447
- prompt_count,
1448
- seed_state,
1449
- ],
1450
- outputs=dataset_outputs,
1451
- )
1452
 
1453
  metric_outputs = [
1454
- lb_metric,
1455
- pp_metric,
1456
- lb_title,
1457
- pp_title,
1458
- sm_title,
1459
- lb_note,
1460
- ranking,
1461
- *pareto_outputs,
1462
- gallery,
1463
- samples_panel,
1464
  ]
1465
- for dataset_dd, metric_dd, models_dd in (
1466
- (lb_dataset, lb_metric, lb_models),
1467
- (pp_dataset, pp_metric, pp_models),
1468
- ):
1469
- metric_dd.change(
1470
- on_metric,
1471
- inputs=[
1472
- dataset_dd,
1473
- metric_dd,
1474
- models_dd,
1475
- search,
1476
- platform,
1477
- owner,
1478
- optimized,
1479
- prompt_count,
1480
- seed_state,
1481
- ],
1482
- outputs=metric_outputs,
1483
- )
1484
 
1485
  models_outputs = [
1486
- lb_models,
1487
- pp_models,
1488
- sm_models,
1489
- lb_note,
1490
- ranking,
1491
- *pareto_outputs,
1492
- gallery,
1493
- samples_panel,
 
 
 
 
 
 
 
 
 
 
 
 
1494
  ]
1495
- for dataset_dd, metric_dd, models_dd in (
1496
- (lb_dataset, lb_metric, lb_models),
1497
- (pp_dataset, pp_metric, pp_models),
1498
- (sm_dataset, no_metric, sm_models),
 
1499
  ):
1500
- models_dd.change(
1501
- on_models,
1502
- inputs=[
1503
- dataset_dd,
1504
- metric_dd,
1505
- models_dd,
1506
- search,
1507
- platform,
1508
- owner,
1509
- optimized,
1510
- prompt_count,
1511
- seed_state,
1512
- ],
1513
- outputs=models_outputs,
1514
  )
1515
 
1516
- for component in (search, platform, owner, optimized):
1517
  component.change(
1518
  on_leaderboard_filters,
1519
  inputs=[
1520
- lb_dataset,
1521
- lb_metric,
1522
- lb_models,
1523
- search,
1524
  platform,
1525
  owner,
1526
  optimized,
 
1527
  ],
1528
- outputs=ranking,
 
1529
  )
1530
 
1531
  prompt_count.change(
1532
  on_samples_controls,
1533
- inputs=[sm_dataset, sm_models, prompt_count, seed_state],
1534
  outputs=gallery,
 
1535
  )
1536
  shuffle_button.click(
1537
  on_shuffle,
1538
- inputs=[sm_dataset, sm_models, prompt_count, seed_state],
1539
  outputs=[seed_state, gallery],
 
1540
  )
1541
 
1542
  def render_about():
 
22
  DEFAULT_COMPARE_PROMPTS = 3
23
  MAX_COMPARE_PROMPTS = 8
24
  MAX_PARETO_METRICS = 8
25
+ _PARETO_SLOT_COUNT = 1 + MAX_PARETO_METRICS * 8
26
+ _PARETO_PRICE_COLUMN = "Price / Image (USD)"
27
+ _PARETO_TIME_COLUMN = "Min Generation Time (s)"
28
+
29
+ TAB_LEADERBOARDS = "leaderboards"
30
+ TAB_PARETO = "pareto"
31
+ TAB_SAMPLES = "samples"
32
+ TAB_ABOUT = "about"
33
+
34
+ _MODEL_CHOICES_CACHE = {}
35
+ _VIEW_EVENTS = {
36
+ "show_progress": "hidden",
37
+ "trigger_mode": "always_last",
38
+ "concurrency_id": "workspace-views",
39
+ "concurrency_limit": 1,
40
+ }
41
 
42
  ABOUT_OVERVIEW_CONTENT = """
43
  # About P-Bench
 
49
 
50
  ## How to read it
51
 
52
+ 1. Pick a **dataset** and a **metric**.
53
  2. **Leaderboards**: ranked by that metric. Price and generation time sit in
54
  the same table.
55
  3. **Pareto plots**: mark models that are not beaten on both higher score
 
170
  gr.HTML(
171
  f"""
172
  <header class="app-header">
173
+ <div class="app-header-bar">
174
+ <div class="app-header-brand">
175
+ <img class="app-header-logo" src="{_MASCOT_DATA_URI}" alt="" />
176
+ <h1>P-Bench</h1>
177
+ </div>
178
+ <button type="button" class="theme-toggle" data-mode="dark" aria-label="Switch to light mode" title="Switch to light mode">
179
+ <svg class="theme-icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
180
+ <circle cx="12" cy="12" r="4"></circle>
181
+ <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
182
+ </svg>
183
+ <svg class="theme-icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
184
+ <path d="M21 14.5A8.5 8.5 0 1 1 9.5 3 7 7 0 0 0 21 14.5z"></path>
185
+ </svg>
186
+ </button>
187
  </div>
188
  <p class="app-header-tagline">Compare text-to-image models on quality, speed, and price</p>
189
  </header>
 
219
  return dataset_id
220
 
221
 
222
+ ALL_METRICS_ID = "__all__"
223
+
224
+
225
  def _metric_choices(datasets, metrics, dataset_id):
226
  dataset = _item(datasets, dataset_id)
227
  if not dataset:
 
230
  data = dataset.get("data")
231
  columns = getattr(data, "columns", [])
232
  return [
233
+ (_display_label(metric["column"]), metric["id"])
234
  for metric in metrics
235
  if metric["id"] in allowed and metric["column"] in columns
236
  ]
237
 
238
 
239
+ def _metric_dropdown_choices(datasets, metrics, dataset_id):
 
 
 
 
 
240
  choices = _metric_choices(datasets, metrics, dataset_id)
241
+ if not choices:
242
+ return []
243
+ return [("Select all", ALL_METRICS_ID)] + choices
244
+
245
+
246
+ def _normalize_metric_ids(metric_id):
247
+ if metric_id is None or metric_id == "":
248
+ return []
249
+ if isinstance(metric_id, (list, tuple)):
250
+ return [item for item in metric_id if item]
251
+ return [metric_id]
252
+
253
+
254
+ def _coerce_metric(datasets, metrics, dataset_id, metric_id):
255
+ """Valid metric ids for this dataset. Empty means all metrics."""
256
+ wanted = _normalize_metric_ids(metric_id)
257
+ valid_ids = [choice[1] for choice in _metric_choices(datasets, metrics, dataset_id)]
258
+ valid = set(valid_ids)
259
+ if ALL_METRICS_ID in wanted:
260
+ return list(valid_ids)
261
+ return [item for item in wanted if item in valid]
262
 
263
 
264
  def _metric_dropdown_value(metric_id):
265
+ return [
266
+ item
267
+ for item in _normalize_metric_ids(metric_id)
268
+ if item != ALL_METRICS_ID
269
+ ]
270
 
271
 
272
  def _model_choices(datasets, dataset_id):
273
+ cached = _MODEL_CHOICES_CACHE.get(dataset_id)
274
+ if cached is not None:
275
+ return cached
276
  dataset = _item(datasets, dataset_id)
277
  data = dataset.get("data") if dataset else None
278
  if data is None or "Model" not in getattr(data, "columns", []):
279
+ _MODEL_CHOICES_CACHE[dataset_id] = []
280
  return []
281
  models = data["Model"].dropna().astype(str).unique().tolist()
282
  # (label, value) so the UI shows the shared name but filters on the raw id.
283
+ choices = sorted(
284
  ((display_model_name(model), model) for model in models),
285
  key=lambda item: item[0].casefold(),
286
  )
287
+ _MODEL_CHOICES_CACHE[dataset_id] = choices
288
+ return choices
289
 
290
 
291
  def _model_choice_values(choices):
 
309
  def _view_title(datasets, metrics, dataset_id, metric_id):
310
  dataset = _item(datasets, dataset_id)
311
  dataset_name = dataset["name"] if dataset else "Dataset"
312
+ selected = [
313
+ item
314
+ for item in _normalize_metric_ids(metric_id)
315
+ if item != ALL_METRICS_ID
316
+ ]
317
+ all_ids = [choice[1] for choice in _metric_choices(datasets, metrics, dataset_id)]
318
+ if not selected or set(selected) == set(all_ids):
319
  return dataset_name
320
+ names = []
321
+ for metric_key in selected:
322
+ metric = _item(metrics, metric_key)
323
+ if metric:
324
+ names.append(_display_label(metric["column"]))
325
+ if not names:
326
+ return dataset_name
327
+ if len(names) == 1:
328
+ return f"{dataset_name} | {names[0]}"
329
+ return f"{dataset_name} | {', '.join(names)}"
330
 
331
 
332
  _LEADERBOARD_META_COLUMNS = [
 
339
 
340
 
341
  def _columns_for_metric(dataset, metric_column):
342
+ """When metrics are selected, show identity + those scores + time/price."""
343
+ metric_columns = (
344
+ [metric_column]
345
+ if isinstance(metric_column, str)
346
+ else [column for column in (metric_column or []) if column]
347
+ )
348
  available = list(getattr(dataset.get("data"), "columns", [])) or list(
349
  dataset.get("columns") or []
350
  )
 
354
  if column in available
355
  ]
356
  meta = [column for column in _LEADERBOARD_META_COLUMNS if column in available]
357
+ scores = [column for column in metric_columns if column in available]
358
+ if scores:
359
+ return [*identity, *scores, *meta]
360
  return [column for column in (dataset.get("columns") or available) if column != "URL"]
361
 
362
 
 
364
  dataset = _item(datasets, dataset_id)
365
  if not dataset:
366
  return None
367
+ metric_ids = _coerce_metric(datasets, metrics, dataset_id, metric_id)
368
+ selected_metrics = []
369
+ for metric_key in metric_ids:
370
+ metric = _item(metrics, metric_key)
371
+ if metric:
372
+ selected_metrics.append(metric)
373
+ score_columns = [
374
+ metric["column"]
375
+ for metric in selected_metrics
376
+ if metric["column"] in getattr(dataset.get("data"), "columns", [])
377
+ ]
378
+ if score_columns:
379
+ columns = _columns_for_metric(dataset, score_columns)
380
+ score_column = score_columns[0]
381
+ metric = selected_metrics[0]
382
  else:
383
  columns = [
384
  column
 
386
  if column != "URL"
387
  ]
388
  score_columns = _metric_columns(datasets, metrics, dataset_id)
389
+ score_column = score_columns[0] if score_columns else None
390
+ metric = None
391
  return {
392
  "dataset": dataset,
393
  "metric": metric,
394
+ "metric_id": metric_ids,
395
+ "title": _view_title(datasets, metrics, dataset["id"], metric_ids),
396
  "data": dataset["data"],
397
  "columns": columns,
398
  "score_column": score_column,
 
458
  return "metric-score"
459
 
460
 
461
+ def _assign_leaderboard_ranks(data, overall_column):
462
+ """Rank the full table by the selected metric. Filters keep these numbers."""
463
+ if data is None:
464
+ return data
465
+ ranked = data.copy()
466
+ if "Rank" in ranked.columns:
467
+ ranked = ranked.drop(columns=["Rank"])
468
+ if overall_column and overall_column in ranked.columns:
469
+ ranked = ranked.sort_values(
470
+ overall_column, ascending=False, na_position="last"
471
+ )
472
+ ranked = ranked.reset_index(drop=True)
473
+ ranked.insert(0, "Rank", ranked.index + 1)
474
+ return ranked
475
+
476
+
477
  def _leaderboard_html(data, columns, score_columns, overall_column):
478
  leaderboard = _leaderboard_dataframe(
479
  data, columns, score_columns, overall_column
 
530
  return sorted(data[column].dropna().astype(str).unique().tolist())
531
 
532
 
533
+ def _filter_leaderboard(data, platform, owner, optimized, models=None):
534
  filtered = data.copy()
535
  if models:
536
  if "Model" in filtered.columns:
537
  filtered = filtered[filtered["Model"].astype(str).isin(models)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
 
539
  for column, values in [
540
  ("Platform", platform),
 
547
 
548
 
549
  def _leaderboard_dataframe(data, columns, score_columns, overall_column): # noqa: ARG001
550
+ skip_columns = {"URL"}
551
  preferred_prefix = [
552
  column
553
  for column in ["Model", "Platform", "Endpoint Owner", "Optimized"]
 
587
  if column not in seen:
588
  seen.add(column)
589
  ordered_columns.append(column)
590
+ if "Rank" in data.columns:
591
+ ordered_columns = ["Rank", *[c for c in ordered_columns if c != "Rank"]]
592
 
593
  leaderboard = data[ordered_columns].copy()
594
 
595
+ if overall_column and overall_column in leaderboard.columns:
596
+ leaderboard = leaderboard.sort_values(
597
+ overall_column, ascending=False, na_position="last"
 
 
 
598
  )
599
+ leaderboard = leaderboard.reset_index(drop=True)
600
+ if "Rank" not in leaderboard.columns:
601
+ leaderboard.insert(0, "Rank", leaderboard.index + 1)
 
602
  return leaderboard.rename(columns=_display_label)
603
 
604
 
 
615
  "P-Judge Overall": "P-Judger (Pruna)",
616
  "Datapoint Elo": "Datapoint Elo",
617
  "Rapidata Elo": "Rapidata Elo",
618
+ "Artificial Analysis Elo": "Artificial Analysis Elo",
619
  "Arena Elo": "Overall Elo",
620
  "Arena Branding / Commercial Elo": "Branding / Commercial",
621
  "Arena 3D Imaging Elo": "3D Imaging",
 
654
  return mask
655
 
656
 
657
+ def _pareto_unavailable_html(message):
658
+ return (
659
+ "<p class='pareto-note-copy'>"
660
+ f"{escape(message)}"
661
+ "</p>"
 
 
 
 
 
662
  )
663
+
664
+
665
+ def _pareto_note_update(message):
666
+ if message:
667
+ return gr.update(
668
+ value=_pareto_unavailable_html(message),
669
+ visible=True,
670
+ )
671
+ return gr.update(value="", visible=False)
672
+
673
+
674
+ def _pareto_plot_update(fig):
675
+ if fig is not None:
676
+ return gr.update(value=fig, visible=True)
677
+ return gr.update(value=None, visible=False)
678
+
679
+
680
+ def _skip_all(count):
681
+ return tuple(gr.skip() for _ in range(count))
682
+
683
+
684
+ def _pareto_skip_updates():
685
+ return _skip_all(_PARETO_SLOT_COUNT)
686
+
687
+
688
+ def _selection_key(dataset_id, metric_id, models):
689
+ return (
690
+ dataset_id,
691
+ tuple(_normalize_metric_ids(metric_id)),
692
+ tuple(models or ()),
693
+ )
694
+
695
+
696
+ def _applied_key(view_state):
697
+ view_state = view_state or {}
698
+ return _selection_key(
699
+ view_state.get("dataset_id"),
700
+ view_state.get("metric_id"),
701
+ view_state.get("models"),
702
  )
 
703
 
704
 
705
  def _build_pareto_figure(
 
813
  return fig
814
 
815
 
816
+ def _pareto_axis(data, score_column, x_column, x_title, missing_message, empty_message, **hover):
817
+ if x_column not in data.columns:
818
+ return None, missing_message
819
+ fig = _build_pareto_figure(
820
+ data,
821
+ score_column,
822
+ x_column=x_column,
823
+ x_title=x_title,
824
+ **hover,
825
+ )
826
+ if fig is None:
827
+ return None, empty_message
828
+ return fig, None
829
+
830
+
831
  def _pareto_pair(data, score_column):
832
+ score_missing = "No score data is available for this metric."
833
  if data is None or not score_column or score_column not in data.columns:
834
+ return None, score_missing, None, score_missing
835
+
836
+ price_fig, price_message = _pareto_axis(
837
+ data,
838
+ score_column,
839
+ _PARETO_PRICE_COLUMN,
840
+ "Price per image (USD)",
841
+ "Price per image isn't available for this dataset.",
842
+ "No models have both a score and a price for this metric.",
843
+ x_hover_prefix="$",
844
+ )
845
+ time_fig, time_message = _pareto_axis(
846
+ data,
847
+ score_column,
848
+ _PARETO_TIME_COLUMN,
849
+ "Min generation time (s)",
850
+ "Min generation time isn't available for this dataset.",
851
+ "No models have both a score and a min generation time for this metric.",
852
+ x_hover_suffix="s",
853
+ )
854
+ return price_fig, price_message, time_fig, time_message
855
+
856
+
857
+ def _pareto_dataset_message(data):
858
+ has_price = data is not None and _PARETO_PRICE_COLUMN in data.columns
859
+ has_time = data is not None and _PARETO_TIME_COLUMN in data.columns
860
+ if has_price and has_time:
861
+ return None
862
+ if not has_price and not has_time:
863
+ return (
864
+ "Price per image and min generation time aren't available for "
865
+ "this dataset, so these plots can't be drawn."
866
  )
867
+ if not has_price:
868
+ return (
869
+ "Price per image isn't available for this dataset, so only min "
870
+ "generation time vs score is shown."
 
 
 
871
  )
872
  return (
873
+ "Min generation time isn't available for this dataset, so only "
874
+ "price vs score is shown."
875
  )
876
 
877
 
878
+ def _pareto_slot_note(price_fig, price_message, time_fig, time_message, data):
879
+ has_price = data is not None and _PARETO_PRICE_COLUMN in data.columns
880
+ has_time = data is not None and _PARETO_TIME_COLUMN in data.columns
881
+ notes = []
882
+ if price_fig is None and has_price:
883
+ notes.append(price_message)
884
+ if time_fig is None and has_time:
885
+ notes.append(time_message)
886
+ if len(notes) == 2 and notes[0] == notes[1]:
887
+ notes = notes[:1]
888
+ return " ".join(notes)
889
+
890
+
891
  def _pareto_slot_updates(data, score_columns):
892
  """Updates for a fixed bank of Gradio Plot slots (visible/hidden)."""
893
  score_columns = [column for column in (score_columns or []) if column]
894
+ has_price = data is not None and _PARETO_PRICE_COLUMN in data.columns
895
+ has_time = data is not None and _PARETO_TIME_COLUMN in data.columns
896
+ dataset_note = _pareto_dataset_message(data)
897
+ updates = [_pareto_note_update(dataset_note)]
898
+ hide_all_slots = not has_price and not has_time
899
+ hidden_slot = (
900
+ gr.update(visible=False),
901
+ "",
902
+ _pareto_note_update(""),
903
+ gr.update(visible=False),
904
+ gr.update(visible=False),
905
+ _pareto_plot_update(None),
906
+ gr.update(visible=False),
907
+ _pareto_plot_update(None),
908
+ )
909
  for index in range(MAX_PARETO_METRICS):
910
+ if hide_all_slots or index >= len(score_columns):
911
+ updates.extend(hidden_slot)
912
+ continue
913
+ score_column = score_columns[index]
914
+ price_fig, price_message, time_fig, time_message = _pareto_pair(
915
+ data, score_column
916
+ )
917
+ show_price = price_fig is not None
918
+ show_time = time_fig is not None
919
+ updates.extend(
920
+ [
921
+ gr.update(visible=True),
922
+ f"#### {_display_label(score_column)}",
923
+ _pareto_note_update(
924
+ _pareto_slot_note(
925
+ price_fig,
926
+ price_message,
927
+ time_fig,
928
+ time_message,
929
+ data,
930
+ )
931
+ ),
932
+ gr.update(visible=show_price or show_time),
933
+ gr.update(visible=show_price),
934
+ _pareto_plot_update(price_fig),
935
+ gr.update(visible=show_time),
936
+ _pareto_plot_update(time_fig),
937
+ ]
938
+ )
939
  return updates
940
 
941
 
 
1021
  return "\n".join(blocks)
1022
 
1023
 
1024
+ def _plain_note(note):
1025
+ text = (note or "").strip()
1026
+ if text.startswith(">"):
1027
+ text = text.lstrip(">").strip()
1028
+ return text
1029
 
1030
 
1031
+ def _leaderboard_intro_markdown(note):
1032
+ extra = _plain_note(note)
1033
+ parts = [
1034
+ "Models are ranked by the selected metric, with price and generation "
1035
+ "time in the same table."
1036
+ ]
1037
+ if extra:
1038
+ parts.append(extra)
1039
+ return "<p class='view-help'>" + " ".join(parts) + "</p>"
1040
 
1041
 
1042
  def _filter_row(
 
1048
  require_samples=False,
1049
  include_metric=True,
1050
  ):
 
 
1051
  metric_id = _coerce_metric(
1052
  datasets, metrics, default_dataset_id, default_metric_id
1053
  )
 
1057
  value=default_dataset_id,
1058
  label="Dataset",
1059
  type="value",
1060
+ filterable=False,
1061
  scale=2,
1062
  min_width=160,
1063
  )
1064
  metric_dd = None
1065
  if include_metric:
1066
  metric_dd = gr.Dropdown(
1067
+ choices=_metric_dropdown_choices(
1068
+ datasets, metrics, default_dataset_id
1069
+ ),
1070
  value=_metric_dropdown_value(metric_id),
1071
  label="Metric",
1072
  type="value",
1073
  multiselect=True,
1074
+ allow_custom_value=False,
1075
+ filterable=True,
1076
  scale=2,
1077
  min_width=180,
1078
+ elem_classes="filter-chips",
1079
  )
1080
  models_dd = gr.Dropdown(
1081
+ choices=_model_choices(datasets, default_dataset_id),
1082
  value=[],
1083
  multiselect=True,
1084
  label="Models",
1085
  type="value",
1086
+ allow_custom_value=False,
1087
+ filterable=True,
1088
+ scale=2,
1089
+ min_width=180,
1090
+ elem_classes="filter-chips",
1091
  )
1092
+ return dataset_dd, metric_dd, models_dd
 
 
 
 
 
 
1093
 
1094
 
1095
  def render_image_workspace(datasets, metrics, default_dataset_id, default_metric_id):
 
1100
  initial_data = initial["data"]
1101
  initial_columns = initial["columns"]
1102
  initial_score_columns = initial["score_columns"]
1103
+ sample_dataset_id = _coerce_sample_dataset(datasets, default_dataset_id)
1104
+ if sample_dataset_id == default_dataset_id:
1105
+ initial_samples = initial.get("samples")
1106
+ else:
1107
+ initial_samples = resolve_view(
1108
+ datasets, metrics, sample_dataset_id, None
1109
+ ).get("samples")
1110
+ with gr.Column(elem_classes="workspace-shell"):
1111
+ with gr.Column(elem_classes="workspace-filters") as filters_host:
1112
+ gr.Markdown(
1113
+ "<p class='filter-help'>"
1114
+ "These filters apply to Leaderboards, Pareto plots, and Samples. "
1115
+ "Search in Models, or leave it empty to include every model."
1116
+ "</p>",
1117
+ elem_classes="filter-help-host",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1118
  )
1119
+ dataset_dd, metric_dd, models_dd = _filter_row(
 
 
1120
  datasets, metrics, default_dataset_id, None
1121
  )
1122
+ with gr.Tabs(elem_classes="main-tabs"):
1123
+ with gr.TabItem("Leaderboards", id=TAB_LEADERBOARDS) as lb_tab:
1124
+ lb_note = gr.Markdown(
1125
+ _leaderboard_intro_markdown(initial.get("note")),
1126
+ elem_classes="view-help-host",
1127
+ )
1128
+ platform_choices = _filter_choices(initial_data, "Platform")
1129
+ owner_choices = _filter_choices(initial_data, "Endpoint Owner")
1130
+ optimized_choices = _filter_choices(initial_data, "Optimized")
1131
+ with gr.Row(
1132
+ elem_classes="leaderboard-controls",
1133
+ visible=bool(
1134
+ platform_choices or owner_choices or optimized_choices
1135
+ ),
1136
+ ) as lb_controls:
1137
+ platform = gr.Dropdown(
1138
+ choices=platform_choices,
1139
+ value=[],
1140
+ label="Providers",
1141
+ multiselect=True,
1142
+ allow_custom_value=False,
1143
+ filterable=False,
1144
+ scale=1,
1145
+ visible=bool(platform_choices),
1146
  )
1147
+ owner = gr.Dropdown(
1148
+ choices=owner_choices,
1149
+ value=[],
1150
+ label="Endpoint owners",
1151
+ multiselect=True,
1152
+ allow_custom_value=False,
1153
+ filterable=False,
1154
+ scale=1,
1155
+ visible=bool(owner_choices),
1156
+ )
1157
+ optimized = gr.Dropdown(
1158
+ choices=optimized_choices,
1159
+ value=[],
1160
+ label="Optimized",
1161
+ multiselect=True,
1162
+ allow_custom_value=False,
1163
+ filterable=False,
1164
+ scale=1,
1165
+ visible=bool(optimized_choices),
1166
+ )
1167
+ ranking = gr.HTML(
1168
+ _leaderboard_html(
1169
+ _assign_leaderboard_ranks(
1170
+ initial_data,
1171
+ initial_score_columns[0] if initial_score_columns else None,
1172
+ ),
1173
+ initial_columns,
1174
+ initial_score_columns,
1175
+ initial_score_columns[0] if initial_score_columns else None,
1176
+ ),
1177
+ padding=False,
1178
+ elem_classes="ranking-table-host",
1179
  )
1180
 
1181
+ with gr.TabItem("Pareto Plots", id=TAB_PARETO) as pp_tab:
 
 
 
 
 
 
 
 
 
 
 
 
1182
  gr.Markdown(
1183
+ "<p class='view-help'>"
1184
+ "Score against price and generation time. Green points are on the "
1185
+ "frontier; lavender points sit below it. Hover a point to see "
1186
+ "which model it is."
1187
+ "</p>",
1188
+ elem_classes="view-help-host",
 
 
1189
  )
1190
+ pareto_dataset_note = gr.HTML(
1191
+ "",
1192
+ padding=False,
1193
+ visible=False,
1194
+ elem_classes="pareto-note",
1195
+ )
1196
+ pareto_slots = []
1197
+ for slot_index in range(MAX_PARETO_METRICS):
1198
+ with gr.Column(
1199
+ visible=False,
1200
+ elem_classes="pareto-metric-block",
1201
+ ) as slot_group:
1202
+ slot_title = gr.Markdown(
1203
+ "",
1204
+ elem_classes="pareto-metric-title",
1205
+ )
1206
+ slot_note = gr.HTML(
1207
+ "",
1208
+ padding=False,
1209
+ visible=False,
1210
+ elem_classes="pareto-note",
1211
+ )
1212
+ with gr.Row(
1213
+ equal_height=True,
1214
+ elem_classes="pareto-layout",
1215
+ ) as slot_layout:
1216
+ with gr.Column(
1217
+ scale=1,
1218
+ min_width=320,
1219
+ elem_classes="pareto-col",
1220
+ ) as slot_price_col:
1221
+ gr.Markdown(
1222
+ "#### Price vs score",
1223
+ elem_classes="pareto-subhead",
1224
+ )
1225
+ slot_price = gr.Plot(
1226
+ value=None,
1227
+ show_label=False,
1228
+ elem_classes="pareto-plot",
1229
+ )
1230
+ with gr.Column(
1231
+ scale=1,
1232
+ min_width=320,
1233
+ elem_classes="pareto-col",
1234
+ ) as slot_time_col:
1235
+ gr.Markdown(
1236
+ "#### Min generation time vs score",
1237
+ elem_classes="pareto-subhead",
1238
+ )
1239
+ slot_time = gr.Plot(
1240
+ value=None,
1241
+ show_label=False,
1242
+ elem_classes="pareto-plot",
1243
+ )
1244
+ pareto_slots.append(
1245
+ (
1246
+ slot_group,
1247
+ slot_title,
1248
+ slot_note,
1249
+ slot_layout,
1250
+ slot_price_col,
1251
+ slot_price,
1252
+ slot_time_col,
1253
+ slot_time,
1254
+ )
1255
  )
1256
+
1257
+ with gr.TabItem("Samples", id=TAB_SAMPLES) as sm_tab:
1258
+ with gr.Column(visible=bool(initial_samples)) as samples_panel:
1259
+ gr.Markdown(
1260
+ f"<p class='view-help'>"
1261
+ f"The same prompts, side by side. Select up to "
1262
+ f"<strong>{MAX_COMPARE_MODELS}</strong> models above, or leave "
1263
+ f"Models empty for two defaults."
1264
+ f"</p>",
1265
+ elem_classes="view-help-host",
1266
  )
1267
+ with gr.Row(equal_height=False, elem_classes="compare-controls"):
1268
+ prompt_count = gr.Slider(
1269
+ minimum=1,
1270
+ maximum=MAX_COMPARE_PROMPTS,
1271
+ value=DEFAULT_COMPARE_PROMPTS,
1272
+ step=1,
1273
+ label="Prompts to show",
1274
+ container=False,
1275
+ show_reset_button=False,
1276
+ scale=1,
1277
+ min_width=180,
1278
+ elem_classes="compare-prompt-count",
1279
+ )
1280
+ shuffle_button = gr.Button(
1281
+ "Shuffle prompts",
1282
+ variant="primary",
1283
+ scale=0,
1284
+ min_width=140,
1285
+ elem_classes="compare-shuffle",
1286
+ )
1287
+ gallery = gr.HTML(
1288
+ value=_samples_html(
1289
+ initial_samples, [], DEFAULT_COMPARE_PROMPTS, seed=0
1290
+ ),
1291
+ elem_classes="compare-gallery",
1292
+ )
1293
+ seed_state = gr.State(0)
1294
 
1295
+ with gr.TabItem("About", id=TAB_ABOUT) as about_tab:
1296
+ render_about()
1297
 
1298
  def _synced_filters(dataset_id, metric_id, models, *, clear_metric=False):
1299
  if clear_metric:
1300
+ metric_id = []
1301
  else:
1302
  metric_id = _coerce_metric(datasets, metrics, dataset_id, metric_id)
1303
  model_choices = _model_choices(datasets, dataset_id)
1304
  model_values = set(_model_choice_values(model_choices))
1305
  models = [model for model in (models or []) if model in model_values]
1306
+ metric_choices = _metric_dropdown_choices(datasets, metrics, dataset_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1307
  return (
1308
  dataset_id,
1309
  metric_id,
1310
  models,
1311
+ gr.update(
1312
+ choices=metric_choices,
1313
+ value=_metric_dropdown_value(metric_id),
1314
+ ),
1315
+ gr.update(choices=model_choices, value=models),
 
 
 
 
 
 
 
1316
  )
1317
 
1318
  def _leaderboard_extras(data, platform_value, owner_value, optimized_value):
 
1347
  platform_value,
1348
  owner_value,
1349
  optimized_value,
1350
+ gr.update(
1351
+ visible=bool(
1352
+ platform_choices or owner_choices or optimized_choices
1353
+ )
1354
+ ),
1355
+ )
1356
+
1357
+ def _content_flags(tab):
1358
+ return {
1359
+ "include_leaderboard": tab == TAB_LEADERBOARDS,
1360
+ "include_pareto": tab == TAB_PARETO,
1361
+ "include_samples": tab == TAB_SAMPLES,
1362
+ }
1363
+
1364
+ def _commit_state(
1365
+ view_state,
1366
+ dataset_id,
1367
+ metric_id,
1368
+ models,
1369
+ tab,
1370
+ flags,
1371
+ extras=None,
1372
+ ):
1373
+ prev = dict(view_state or {})
1374
+ extras = extras or {}
1375
+ return {
1376
+ "dataset_id": dataset_id,
1377
+ "metric_id": metric_id,
1378
+ "models": list(models or []),
1379
+ "current_tab": tab,
1380
+ "platform": list(
1381
+ extras.get("platform", prev.get("platform") or [])
1382
+ ),
1383
+ "owner": list(extras.get("owner", prev.get("owner") or [])),
1384
+ "optimized": list(
1385
+ extras.get("optimized", prev.get("optimized") or [])
1386
+ ),
1387
+ "stale": {
1388
+ TAB_LEADERBOARDS: not flags["include_leaderboard"],
1389
+ TAB_PARETO: not flags["include_pareto"],
1390
+ TAB_SAMPLES: not flags["include_samples"],
1391
+ },
1392
+ }
1393
+
1394
+ def _save_leaderboard_filters(
1395
+ view_state, platform_value, owner_value, optimized_value
1396
+ ):
1397
+ view_state["platform"] = list(platform_value or [])
1398
+ view_state["owner"] = list(owner_value or [])
1399
+ view_state["optimized"] = list(optimized_value or [])
1400
+ return view_state
1401
+
1402
+ def _restore_leaderboard_filters(
1403
+ view_state, platform_value, owner_value, optimized_value
1404
+ ):
1405
+ stored_platform = (view_state or {}).get("platform") or []
1406
+ stored_owner = (view_state or {}).get("owner") or []
1407
+ stored_optimized = (view_state or {}).get("optimized") or []
1408
+ view = resolve_view(
1409
+ datasets,
1410
+ metrics,
1411
+ (view_state or {}).get("dataset_id"),
1412
+ (view_state or {}).get("metric_id"),
1413
+ )
1414
+ extras = _leaderboard_extras(
1415
+ view["data"] if view else None,
1416
+ stored_platform,
1417
+ stored_owner,
1418
+ stored_optimized,
1419
+ )
1420
+ platform_update = extras[0] if list(platform_value or []) != extras[3] else gr.skip()
1421
+ owner_update = extras[1] if list(owner_value or []) != extras[4] else gr.skip()
1422
+ optimized_update = extras[2] if list(optimized_value or []) != extras[5] else gr.skip()
1423
+ return (
1424
+ extras[6],
1425
+ platform_update,
1426
+ owner_update,
1427
+ optimized_update,
1428
+ extras[3],
1429
+ extras[4],
1430
+ extras[5],
1431
  )
1432
 
1433
  def _views(
1434
  dataset_id,
1435
  metric_id,
1436
  models,
 
1437
  platform_value,
1438
  owner_value,
1439
  optimized_value,
1440
  num_prompts,
1441
  seed,
1442
+ *,
1443
+ include_leaderboard=True,
1444
+ include_pareto=False,
1445
+ include_samples=False,
1446
  ):
1447
  view = resolve_view(datasets, metrics, dataset_id, metric_id)
1448
  data = view["data"]
1449
+ if include_leaderboard:
1450
+ sort_column = view["score_column"] or (
1451
+ view["score_columns"][0] if view["score_columns"] else None
1452
+ )
1453
+ note = _leaderboard_intro_markdown(view.get("note"))
1454
+ ranking_html = _leaderboard_html(
1455
+ _filter_leaderboard(
1456
+ _assign_leaderboard_ranks(data, sort_column),
1457
+ platform_value or [],
1458
+ owner_value or [],
1459
+ optimized_value or [],
1460
+ models=models,
1461
+ ),
1462
+ view["columns"],
1463
+ view["score_columns"],
1464
+ sort_column,
1465
+ )
1466
+ else:
1467
+ note = gr.skip()
1468
+ ranking_html = gr.skip()
1469
+ if include_pareto:
1470
+ pareto_data = _filter_leaderboard(data, [], [], [], models=models)
1471
+ pareto_updates = _pareto_slot_updates(pareto_data, view["score_columns"])
1472
+ else:
1473
+ pareto_updates = _pareto_skip_updates()
1474
+ if include_samples:
1475
+ sample_dataset_id = _coerce_sample_dataset(datasets, dataset_id)
1476
+ sample_view = resolve_view(datasets, metrics, sample_dataset_id, None)
1477
+ samples = sample_view.get("samples") if sample_view else None
1478
+ sample_models = [
1479
+ model
1480
+ for model in (models or [])
1481
+ if model
1482
+ in _model_choice_values(_model_choices(datasets, sample_dataset_id))
1483
+ ]
1484
+ samples_html = _samples_html(
1485
+ samples,
1486
+ sample_models,
1487
+ int(num_prompts or DEFAULT_COMPARE_PROMPTS),
1488
+ int(seed or 0),
1489
+ )
1490
+ samples_visible = gr.update(visible=bool(samples))
1491
+ else:
1492
+ samples_html = gr.skip()
1493
+ samples_visible = gr.skip()
1494
  return (
1495
+ note,
1496
  ranking_html,
1497
  *pareto_updates,
1498
  samples_html,
1499
+ samples_visible,
1500
  )
1501
 
1502
  def on_dataset(
1503
  dataset_id,
1504
  metric_id,
1505
  models,
 
1506
  platform_value,
1507
  owner_value,
1508
  optimized_value,
1509
  num_prompts,
1510
  seed,
1511
+ view_state,
1512
  ):
1513
+ view_state = dict(view_state or {})
1514
+ tab = view_state.get("current_tab") or TAB_LEADERBOARDS
1515
+ dataset_changed = dataset_id != view_state.get("dataset_id")
1516
  synced = _synced_filters(
1517
+ dataset_id,
1518
+ metric_id,
1519
+ models,
1520
+ clear_metric=dataset_changed,
1521
  )
1522
  dataset_id, metric_id, models = synced[:3]
1523
+ if (
1524
+ not dataset_changed
1525
+ and _applied_key(view_state)
1526
+ == _selection_key(dataset_id, metric_id, models)
1527
+ ):
1528
+ return _skip_all(len(dataset_outputs))
1529
+ flags = _content_flags(tab)
1530
  view = resolve_view(datasets, metrics, dataset_id, metric_id)
1531
  extras = _leaderboard_extras(
1532
  view["data"], platform_value, owner_value, optimized_value
 
1535
  dataset_id,
1536
  metric_id,
1537
  models,
 
1538
  extras[3],
1539
  extras[4],
1540
  extras[5],
1541
  num_prompts,
1542
  seed,
1543
+ **flags,
1544
+ )
1545
+ extras_payload = (
1546
+ {
1547
+ "platform": extras[3],
1548
+ "owner": extras[4],
1549
+ "optimized": extras[5],
1550
+ }
1551
+ if tab == TAB_LEADERBOARDS
1552
+ else {}
1553
+ )
1554
+ new_state = _commit_state(
1555
+ view_state,
1556
+ dataset_id,
1557
+ metric_id,
1558
+ models,
1559
+ tab,
1560
+ flags,
1561
+ extras=extras_payload,
1562
+ )
1563
+ return (
1564
+ synced[3],
1565
+ synced[4],
1566
+ extras[6],
1567
+ extras[0],
1568
+ extras[1],
1569
+ extras[2],
1570
+ *views,
1571
+ new_state,
1572
  )
 
1573
 
1574
  def on_metric(
1575
  dataset_id,
1576
  metric_id,
1577
  models,
 
1578
  platform_value,
1579
  owner_value,
1580
  optimized_value,
1581
  num_prompts,
1582
  seed,
1583
+ view_state,
1584
  ):
1585
+ view_state = dict(view_state or {})
1586
+ tab = view_state.get("current_tab") or TAB_LEADERBOARDS
1587
+ selected_raw = _normalize_metric_ids(metric_id)
1588
  metric_id = _coerce_metric(datasets, metrics, dataset_id, metric_id)
1589
+ models = list(models or [])
1590
+ if (
1591
+ ALL_METRICS_ID not in selected_raw
1592
+ and _applied_key(view_state)
1593
+ == _selection_key(dataset_id, metric_id, models)
1594
+ ):
1595
+ return _skip_all(len(metric_outputs))
1596
+ flags = _content_flags(tab)
 
 
 
 
 
 
 
1597
  views = _views(
1598
  dataset_id,
1599
  metric_id,
1600
  models,
 
1601
  platform_value,
1602
  owner_value,
1603
  optimized_value,
1604
  num_prompts,
1605
  seed,
1606
+ **flags,
1607
+ )
1608
+ extras_payload = (
1609
+ {
1610
+ "platform": platform_value or [],
1611
+ "owner": owner_value or [],
1612
+ "optimized": optimized_value or [],
1613
+ }
1614
+ if tab == TAB_LEADERBOARDS
1615
+ else {}
1616
+ )
1617
+ new_state = _commit_state(
1618
+ view_state,
1619
+ dataset_id,
1620
+ metric_id,
1621
+ models,
1622
+ tab,
1623
+ flags,
1624
+ extras=extras_payload,
1625
+ )
1626
+ metric_update = (
1627
+ gr.update(
1628
+ choices=_metric_dropdown_choices(datasets, metrics, dataset_id),
1629
+ value=_metric_dropdown_value(metric_id),
1630
+ )
1631
+ if ALL_METRICS_ID in selected_raw
1632
+ else gr.skip()
1633
  )
1634
  return (
1635
  metric_update,
 
 
 
 
1636
  *views,
1637
+ new_state,
1638
  )
1639
 
1640
  def on_models(
1641
  dataset_id,
1642
  metric_id,
1643
  models,
 
1644
  platform_value,
1645
  owner_value,
1646
  optimized_value,
1647
  num_prompts,
1648
  seed,
1649
+ view_state,
1650
  ):
1651
+ view_state = dict(view_state or {})
1652
+ tab = view_state.get("current_tab") or TAB_LEADERBOARDS
1653
+ metric_id = _coerce_metric(datasets, metrics, dataset_id, metric_id)
1654
+ incoming = list(models or [])
1655
+ model_values = set(
1656
+ _model_choice_values(_model_choices(datasets, dataset_id))
 
 
 
 
 
 
 
1657
  )
1658
+ models = [model for model in incoming if model in model_values]
1659
+ if _applied_key(view_state) == _selection_key(dataset_id, metric_id, models):
1660
+ return _skip_all(len(models_outputs))
1661
+ flags = _content_flags(tab)
1662
  views = _views(
1663
  dataset_id,
1664
  metric_id,
1665
  models,
 
1666
  platform_value,
1667
  owner_value,
1668
  optimized_value,
1669
  num_prompts,
1670
  seed,
1671
+ **flags,
1672
  )
1673
+ extras_payload = (
1674
+ {
1675
+ "platform": platform_value or [],
1676
+ "owner": owner_value or [],
1677
+ "optimized": optimized_value or [],
1678
+ }
1679
+ if tab == TAB_LEADERBOARDS
1680
+ else {}
1681
+ )
1682
+ new_state = _commit_state(
1683
+ view_state,
1684
+ dataset_id,
1685
+ metric_id,
1686
+ models,
1687
+ tab,
1688
+ flags,
1689
+ extras=extras_payload,
1690
+ )
1691
+ models_update = (
1692
+ gr.update(value=models) if models != incoming else gr.skip()
1693
+ )
1694
+ return (models_update, *views, new_state)
1695
+
1696
+ def on_tab_select(
1697
+ tab,
1698
+ dataset_id,
1699
+ metric_id,
1700
+ models,
1701
+ platform_value,
1702
+ owner_value,
1703
+ optimized_value,
1704
+ num_prompts,
1705
+ seed,
1706
+ view_state,
1707
+ ):
1708
+ view_state = dict(view_state or {})
1709
+ prev_tab = view_state.get("current_tab") or TAB_LEADERBOARDS
1710
+ if prev_tab == TAB_LEADERBOARDS:
1711
+ _save_leaderboard_filters(
1712
+ view_state,
1713
+ platform_value,
1714
+ owner_value,
1715
+ optimized_value,
1716
+ )
1717
+ view_state["current_tab"] = tab
1718
+ metric_id = _coerce_metric(datasets, metrics, dataset_id, metric_id)
1719
+ models = list(models or [])
1720
+ view_state["dataset_id"] = dataset_id
1721
+ view_state["metric_id"] = metric_id
1722
+ view_state["models"] = models
1723
+ show_filters = tab != TAB_ABOUT
1724
+ show_metric = tab in (TAB_LEADERBOARDS, TAB_PARETO)
1725
+ was_filters = prev_tab != TAB_ABOUT
1726
+ was_metric = prev_tab in (TAB_LEADERBOARDS, TAB_PARETO)
1727
+ filters_vis = (
1728
+ gr.update(visible=show_filters)
1729
+ if show_filters != was_filters
1730
+ else gr.skip()
1731
+ )
1732
+ metric_vis = (
1733
+ gr.update(visible=show_metric)
1734
+ if show_metric != was_metric
1735
+ else gr.skip()
1736
+ )
1737
+ if tab == TAB_LEADERBOARDS:
1738
+ restored = _restore_leaderboard_filters(
1739
+ view_state,
1740
+ platform_value,
1741
+ owner_value,
1742
+ optimized_value,
1743
+ )
1744
+ platform_value = restored[4]
1745
+ owner_value = restored[5]
1746
+ optimized_value = restored[6]
1747
+ lb_filters = restored[:4]
1748
+ else:
1749
+ lb_filters = _skip_all(4)
1750
+ stale = dict(view_state.get("stale") or {})
1751
+ chrome = (filters_vis, metric_vis, *lb_filters)
1752
+ if tab == TAB_ABOUT or not stale.get(tab, True):
1753
+ return (
1754
+ *chrome,
1755
+ *_skip_all(len(view_outputs)),
1756
+ view_state,
1757
+ )
1758
+ flags = _content_flags(tab)
1759
+ views = _views(
1760
+ dataset_id,
1761
+ metric_id,
1762
+ models,
1763
+ platform_value,
1764
+ owner_value,
1765
+ optimized_value,
1766
+ num_prompts,
1767
+ seed,
1768
+ **flags,
1769
  )
1770
+ stale[tab] = False
1771
+ view_state["stale"] = stale
1772
+ return (*chrome, *views, view_state)
1773
 
1774
  def on_leaderboard_filters(
1775
  dataset_id,
1776
  metric_id,
1777
  models,
 
1778
  platform_value,
1779
  owner_value,
1780
  optimized_value,
1781
+ view_state,
1782
  ):
1783
+ view_state = dict(view_state or {})
1784
+ _save_leaderboard_filters(
1785
+ view_state,
1786
+ platform_value,
1787
+ owner_value,
1788
+ optimized_value,
 
 
1789
  )
1790
+ view = resolve_view(datasets, metrics, dataset_id, metric_id)
1791
  sort_column = view["score_column"] or (
1792
  view["score_columns"][0] if view["score_columns"] else None
1793
  )
1794
+ return (
1795
+ _leaderboard_html(
1796
+ _filter_leaderboard(
1797
+ _assign_leaderboard_ranks(view["data"], sort_column),
1798
+ platform_value or [],
1799
+ owner_value or [],
1800
+ optimized_value or [],
1801
+ models=models,
1802
+ ),
1803
+ view["columns"],
1804
+ view["score_columns"],
1805
+ sort_column,
1806
+ ),
1807
+ view_state,
1808
  )
1809
 
1810
  def on_samples_controls(dataset_id, models, num_prompts, seed):
1811
+ dataset_id = _coerce_sample_dataset(datasets, dataset_id)
1812
  view = resolve_view(datasets, metrics, dataset_id, None)
1813
  return _samples_html(
1814
  view.get("samples") if view else None,
 
1818
  )
1819
 
1820
  def on_shuffle(dataset_id, models, num_prompts, seed):
1821
+ dataset_id = _coerce_sample_dataset(datasets, dataset_id)
1822
  next_seed = int(seed or 0) + 1
1823
  view = resolve_view(datasets, metrics, dataset_id, None)
1824
  return next_seed, _samples_html(
 
1828
  next_seed,
1829
  )
1830
 
1831
+ def _on_tab(tab):
1832
+ def handler(
1833
+ dataset_id,
1834
+ metric_id,
1835
+ models,
1836
+ platform_value,
1837
+ owner_value,
1838
+ optimized_value,
1839
+ num_prompts,
1840
+ seed,
1841
+ view_state,
1842
+ ):
1843
+ return on_tab_select(
1844
+ tab,
1845
+ dataset_id,
1846
+ metric_id,
1847
+ models,
1848
+ platform_value,
1849
+ owner_value,
1850
+ optimized_value,
1851
+ num_prompts,
1852
+ seed,
1853
+ view_state,
1854
+ )
1855
+
1856
+ handler.__name__ = f"on_tab_{tab}"
1857
+ return handler
1858
+
1859
+ view_state = gr.State(
1860
+ {
1861
+ "dataset_id": default_dataset_id,
1862
+ "metric_id": None,
1863
+ "models": [],
1864
+ "current_tab": TAB_LEADERBOARDS,
1865
+ "platform": [],
1866
+ "owner": [],
1867
+ "optimized": [],
1868
+ "stale": {
1869
+ TAB_LEADERBOARDS: False,
1870
+ TAB_PARETO: True,
1871
+ TAB_SAMPLES: False,
1872
+ },
1873
+ }
1874
+ )
1875
  pareto_outputs = [
1876
+ pareto_dataset_note,
1877
+ *[
1878
+ component
1879
+ for slot_group, slot_title, slot_note, slot_layout, slot_price_col, slot_price, slot_time_col, slot_time in pareto_slots
1880
+ for component in (
1881
+ slot_group,
1882
+ slot_title,
1883
+ slot_note,
1884
+ slot_layout,
1885
+ slot_price_col,
1886
+ slot_price,
1887
+ slot_time_col,
1888
+ slot_time,
1889
+ )
1890
+ ],
1891
  ]
1892
+ view_inputs = [
 
 
 
 
 
 
 
 
 
 
 
 
1893
  platform,
1894
  owner,
1895
  optimized,
1896
+ prompt_count,
1897
+ seed_state,
1898
+ view_state,
1899
+ ]
1900
+ view_outputs = [
1901
  lb_note,
1902
  ranking,
1903
  *pareto_outputs,
1904
  gallery,
1905
  samples_panel,
1906
  ]
1907
+ filter_inputs = [dataset_dd, metric_dd, models_dd, *view_inputs]
1908
+
1909
+ dataset_outputs = [
1910
+ metric_dd,
1911
+ models_dd,
1912
+ lb_controls,
1913
+ platform,
1914
+ owner,
1915
+ optimized,
1916
+ *view_outputs,
1917
+ view_state,
1918
+ ]
1919
+ dataset_dd.change(
1920
+ on_dataset,
1921
+ inputs=filter_inputs,
1922
+ outputs=dataset_outputs,
1923
+ **_VIEW_EVENTS,
1924
+ )
 
 
1925
 
1926
  metric_outputs = [
1927
+ metric_dd,
1928
+ *view_outputs,
1929
+ view_state,
 
 
 
 
 
 
 
1930
  ]
1931
+ metric_dd.change(
1932
+ on_metric,
1933
+ inputs=filter_inputs,
1934
+ outputs=metric_outputs,
1935
+ **_VIEW_EVENTS,
1936
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
1937
 
1938
  models_outputs = [
1939
+ models_dd,
1940
+ *view_outputs,
1941
+ view_state,
1942
+ ]
1943
+ models_dd.change(
1944
+ on_models,
1945
+ inputs=filter_inputs,
1946
+ outputs=models_outputs,
1947
+ **_VIEW_EVENTS,
1948
+ )
1949
+
1950
+ tab_outputs = [
1951
+ filters_host,
1952
+ metric_dd,
1953
+ lb_controls,
1954
+ platform,
1955
+ owner,
1956
+ optimized,
1957
+ *view_outputs,
1958
+ view_state,
1959
  ]
1960
+ for tab, tab_item in (
1961
+ (TAB_LEADERBOARDS, lb_tab),
1962
+ (TAB_PARETO, pp_tab),
1963
+ (TAB_SAMPLES, sm_tab),
1964
+ (TAB_ABOUT, about_tab),
1965
  ):
1966
+ tab_item.select(
1967
+ _on_tab(tab),
1968
+ inputs=filter_inputs,
1969
+ outputs=tab_outputs,
1970
+ show_progress="hidden",
 
 
 
 
 
 
 
 
 
1971
  )
1972
 
1973
+ for component in (platform, owner, optimized):
1974
  component.change(
1975
  on_leaderboard_filters,
1976
  inputs=[
1977
+ dataset_dd,
1978
+ metric_dd,
1979
+ models_dd,
 
1980
  platform,
1981
  owner,
1982
  optimized,
1983
+ view_state,
1984
  ],
1985
+ outputs=[ranking, view_state],
1986
+ show_progress="hidden",
1987
  )
1988
 
1989
  prompt_count.change(
1990
  on_samples_controls,
1991
+ inputs=[dataset_dd, models_dd, prompt_count, seed_state],
1992
  outputs=gallery,
1993
+ show_progress="hidden",
1994
  )
1995
  shuffle_button.click(
1996
  on_shuffle,
1997
+ inputs=[dataset_dd, models_dd, prompt_count, seed_state],
1998
  outputs=[seed_state, gallery],
1999
+ show_progress="hidden",
2000
  )
2001
 
2002
  def render_about():