app.py CHANGED
@@ -581,14 +581,19 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
581
  .prose .ranking-table td {
582
  padding: 8px 10px !important;
583
  }
 
 
 
 
584
  .ranking-table .rank,
585
  .prose .ranking-table .rank,
586
  .ranking-table th.rank {
587
  position: sticky !important;
588
  left: 0 !important;
589
- width: 2.4rem;
590
- min-width: 2.4rem;
591
- box-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.45);
 
592
  }
593
  .ranking-table .model-cell,
594
  .prose .ranking-table .model-cell {
@@ -598,6 +603,7 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
598
  min-width: 140px;
599
  max-width: none;
600
  background: transparent !important;
 
601
  }
602
  .ranking-table th.model-cell,
603
  .prose .ranking-table th.model-cell {
@@ -608,6 +614,7 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
608
  min-width: 140px;
609
  max-width: none;
610
  background: var(--pruna-bg-header) !important;
 
611
  }
612
  .ranking-table tbody tr:hover .model-cell {
613
  background: var(--pruna-table-hover) !important;
@@ -1107,11 +1114,11 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1107
  max-height: min(70vh, 720px);
1108
  overflow-x: auto;
1109
  overflow-y: auto;
1110
- -webkit-overflow-scrolling: touch;
1111
- overscroll-behavior-x: contain;
1112
  }
1113
  .ranking-table,
1114
  .prose .ranking-table {
 
1115
  width: 100%;
1116
  margin: 0 !important;
1117
  overflow: visible;
@@ -1187,10 +1194,14 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1187
  .prose .ranking-table .rank {
1188
  position: sticky;
1189
  left: 0;
1190
- z-index: 1;
1191
  box-sizing: border-box;
1192
- width: 3.25rem;
1193
- min-width: 3.25rem;
 
 
 
 
1194
  color: var(--pruna-lavender) !important;
1195
  font-weight: 700 !important;
1196
  text-align: center;
@@ -1207,18 +1218,21 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1207
  .ranking-table .model-cell,
1208
  .prose .ranking-table .model-cell {
1209
  position: sticky;
1210
- left: 3.25rem;
1211
- z-index: 1;
 
1212
  min-width: 180px;
1213
  max-width: 260px;
1214
  background: var(--pruna-table-sticky) !important;
 
1215
  }
1216
  .ranking-table th.model-cell,
1217
  .prose .ranking-table th.model-cell {
1218
  top: 0;
1219
- left: 3.25rem;
1220
  z-index: 5;
1221
  background: var(--pruna-bg-header) !important;
 
1222
  }
1223
  .ranking-table tbody tr:hover .rank,
1224
  .ranking-table tbody tr:hover .model-cell {
@@ -1769,6 +1783,9 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1769
  max-width: 100% !important;
1770
  }
1771
  .compare-cell { min-width: 0; }
 
 
 
1772
  .compare-model-label {
1773
  margin-bottom: 6px;
1774
  color: var(--pruna-lavender);
@@ -1776,15 +1793,23 @@ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !import
1776
  font-weight: 700;
1777
  word-break: break-word;
1778
  }
1779
- .compare-cell img {
 
1780
  display: block;
1781
  width: 100%;
1782
- aspect-ratio: 1 / 1;
1783
- object-fit: cover;
1784
  border-radius: 12px;
1785
  border: 1px solid var(--pruna-border);
1786
  background: var(--pruna-bg-elevated);
1787
  }
 
 
 
 
 
 
 
 
 
1788
  .compare-empty,
1789
  .pareto-note-copy {
1790
  margin: 0;
@@ -2143,14 +2168,20 @@ def load_sample_comparison_data(folder):
2143
  return None
2144
 
2145
  prompts = {}
 
2146
  with prompts_path.open() as handle:
2147
  for line in handle:
2148
  if not line.strip():
2149
  continue
2150
  row = json.loads(line)
2151
- prompts[row["prompt_id"]] = row.get("text", "")
 
 
 
 
2152
 
2153
  images = defaultdict(dict)
 
2154
  with generations_path.open() as handle:
2155
  for line in handle:
2156
  if not line.strip():
@@ -2158,20 +2189,33 @@ def load_sample_comparison_data(folder):
2158
  row = json.loads(line)
2159
  model_id = row["model_id"]
2160
  prompt_id = row["prompt_id"]
2161
- image_url = row.get("image")
2162
- if model_id and prompt_id and image_url:
2163
- images[model_id][prompt_id] = image_url
 
 
 
 
 
 
 
 
 
2164
 
2165
  models = sorted(images)
2166
  if not models or not prompts:
2167
  return None
2168
 
2169
- return {
2170
  "prompts": prompts,
2171
  "images": {model: dict(prompt_map) for model, prompt_map in images.items()},
2172
  "models": models,
2173
  "prompt_ids": sorted(prompts),
 
2174
  }
 
 
 
2175
 
2176
 
2177
  def _as_numeric(df, columns):
@@ -2301,6 +2345,60 @@ def load_qwen_combined_dataframe(path):
2301
  return df.reset_index(drop=True)
2302
 
2303
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2304
  df = load_oneig_dataframe(oneig_path)
2305
 
2306
  oneig_metric_columns = [
@@ -2348,6 +2446,10 @@ qwen_combined_dir = _resolve_data_path(
2348
  data_dir / "qwen_image_bench_combined",
2349
  space_root.parent / "qwen_image_bench_combined",
2350
  )
 
 
 
 
2351
  qwen_path = _resolve_data_path(
2352
  data_dir / "qwen_image_bench_model_price_and_median_generation_time_10_august.csv",
2353
  space_root.parent / "qwen_image_bench_model_price_and_median_generation_time_10_august.csv",
@@ -2360,10 +2462,15 @@ arena_path = _resolve_data_path(
2360
  data_dir / "arena_ai_text_to_image_leaderboard.csv",
2361
  space_root.parent / "arena_ai_text_to_image_leaderboard.csv",
2362
  )
 
 
 
 
2363
 
2364
  qwen_df = load_qwen_combined_dataframe(qwen_path)
2365
  aa_df = load_artificial_analysis_dataframe(aa_path)
2366
  arena_df = load_arena_ai_dataframe(arena_path)
 
2367
  qwen_display_columns = [
2368
  col
2369
  for col in [
@@ -2395,9 +2502,22 @@ arena_display_columns = [
2395
  ]
2396
  if col in arena_df.columns
2397
  ]
 
 
 
 
 
 
 
 
 
 
 
 
2398
 
2399
  oneig_samples = load_sample_comparison_data(oneig_combined_dir)
2400
  qwen_samples = load_sample_comparison_data(qwen_combined_dir)
 
2401
 
2402
  metrics = [
2403
  {"id": "datapoint_elo", "column": "Datapoint Elo"},
@@ -2456,11 +2576,27 @@ arena_metric_ids = _metric_ids_for(
2456
  "arena_text",
2457
  ],
2458
  )
 
2459
 
2460
  datasets = [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2461
  {
2462
  "id": "qwen",
2463
  "name": "Qwen Image Dataset",
 
2464
  "data": qwen_df,
2465
  "columns": qwen_display_columns,
2466
  "metric_ids": qwen_metric_ids,
@@ -2470,6 +2606,7 @@ datasets = [
2470
  {
2471
  "id": "oneig",
2472
  "name": "OneIG Alignment Dataset",
 
2473
  "data": oneig_df,
2474
  "columns": oneig_display_columns,
2475
  "metric_ids": oneig_metric_ids,
@@ -2482,6 +2619,7 @@ datasets = [
2482
  {
2483
  "id": "artificial_analysis",
2484
  "name": "Artificial Analysis Dataset",
 
2485
  "data": aa_df,
2486
  "columns": aa_display_columns,
2487
  "metric_ids": aa_metric_ids,
@@ -2491,6 +2629,7 @@ datasets = [
2491
  {
2492
  "id": "arena_ai",
2493
  "name": "Arena AI Dataset",
 
2494
  "data": arena_df,
2495
  "columns": arena_display_columns,
2496
  "metric_ids": arena_metric_ids,
@@ -2501,8 +2640,11 @@ datasets = [
2501
  datasets = [dataset for dataset in datasets if dataset["metric_ids"]]
2502
 
2503
  DEFAULT_DATASET_ID = next(
2504
- (dataset["id"] for dataset in datasets if dataset["id"] == "qwen"),
2505
- datasets[0]["id"] if datasets else None,
 
 
 
2506
  )
2507
  DEFAULT_METRIC_ID = None
2508
 
 
581
  .prose .ranking-table td {
582
  padding: 8px 10px !important;
583
  }
584
+ .ranking-table,
585
+ .prose .ranking-table {
586
+ --rank-col-width: 3.25rem;
587
+ }
588
  .ranking-table .rank,
589
  .prose .ranking-table .rank,
590
  .ranking-table th.rank {
591
  position: sticky !important;
592
  left: 0 !important;
593
+ width: var(--rank-col-width);
594
+ min-width: var(--rank-col-width);
595
+ max-width: var(--rank-col-width);
596
+ box-shadow: none;
597
  }
598
  .ranking-table .model-cell,
599
  .prose .ranking-table .model-cell {
 
603
  min-width: 140px;
604
  max-width: none;
605
  background: transparent !important;
606
+ box-shadow: none !important;
607
  }
608
  .ranking-table th.model-cell,
609
  .prose .ranking-table th.model-cell {
 
614
  min-width: 140px;
615
  max-width: none;
616
  background: var(--pruna-bg-header) !important;
617
+ box-shadow: 0 1px 0 var(--pruna-hairline) !important;
618
  }
619
  .ranking-table tbody tr:hover .model-cell {
620
  background: var(--pruna-table-hover) !important;
 
1114
  max-height: min(70vh, 720px);
1115
  overflow-x: auto;
1116
  overflow-y: auto;
1117
+ overscroll-behavior: none;
 
1118
  }
1119
  .ranking-table,
1120
  .prose .ranking-table {
1121
+ --rank-col-width: 4.25rem;
1122
  width: 100%;
1123
  margin: 0 !important;
1124
  overflow: visible;
 
1194
  .prose .ranking-table .rank {
1195
  position: sticky;
1196
  left: 0;
1197
+ z-index: 2;
1198
  box-sizing: border-box;
1199
+ width: var(--rank-col-width);
1200
+ min-width: var(--rank-col-width);
1201
+ max-width: var(--rank-col-width);
1202
+ padding-left: 0.5rem !important;
1203
+ padding-right: 0.5rem !important;
1204
+ overflow: hidden;
1205
  color: var(--pruna-lavender) !important;
1206
  font-weight: 700 !important;
1207
  text-align: center;
 
1218
  .ranking-table .model-cell,
1219
  .prose .ranking-table .model-cell {
1220
  position: sticky;
1221
+ left: var(--rank-col-width);
1222
+ z-index: 2;
1223
+ box-sizing: border-box;
1224
  min-width: 180px;
1225
  max-width: 260px;
1226
  background: var(--pruna-table-sticky) !important;
1227
+ box-shadow: 8px 0 10px -8px rgba(0, 0, 0, 0.35) !important;
1228
  }
1229
  .ranking-table th.model-cell,
1230
  .prose .ranking-table th.model-cell {
1231
  top: 0;
1232
+ left: var(--rank-col-width);
1233
  z-index: 5;
1234
  background: var(--pruna-bg-header) !important;
1235
+ box-shadow: 0 1px 0 var(--pruna-hairline), 8px 0 10px -8px rgba(0, 0, 0, 0.35) !important;
1236
  }
1237
  .ranking-table tbody tr:hover .rank,
1238
  .ranking-table tbody tr:hover .model-cell {
 
1783
  max-width: 100% !important;
1784
  }
1785
  .compare-cell { min-width: 0; }
1786
+ .compare-cell.compare-source .compare-model-label {
1787
+ color: var(--pruna-text-muted);
1788
+ }
1789
  .compare-model-label {
1790
  margin-bottom: 6px;
1791
  color: var(--pruna-lavender);
 
1793
  font-weight: 700;
1794
  word-break: break-word;
1795
  }
1796
+ .compare-cell img,
1797
+ .compare-cell video {
1798
  display: block;
1799
  width: 100%;
 
 
1800
  border-radius: 12px;
1801
  border: 1px solid var(--pruna-border);
1802
  background: var(--pruna-bg-elevated);
1803
  }
1804
+ .compare-cell img {
1805
+ aspect-ratio: 1 / 1;
1806
+ object-fit: cover;
1807
+ }
1808
+ .compare-cell video {
1809
+ aspect-ratio: 16 / 9;
1810
+ max-height: 360px;
1811
+ object-fit: contain;
1812
+ }
1813
  .compare-empty,
1814
  .pareto-note-copy {
1815
  margin: 0;
 
2168
  return None
2169
 
2170
  prompts = {}
2171
+ source_videos = {}
2172
  with prompts_path.open() as handle:
2173
  for line in handle:
2174
  if not line.strip():
2175
  continue
2176
  row = json.loads(line)
2177
+ prompt_id = row["prompt_id"]
2178
+ prompts[prompt_id] = row.get("text", "")
2179
+ source_video = row.get("source_video")
2180
+ if source_video:
2181
+ source_videos[prompt_id] = source_video
2182
 
2183
  images = defaultdict(dict)
2184
+ kinds = set()
2185
  with generations_path.open() as handle:
2186
  for line in handle:
2187
  if not line.strip():
 
2189
  row = json.loads(line)
2190
  model_id = row["model_id"]
2191
  prompt_id = row["prompt_id"]
2192
+ media_url = row.get("image") or row.get("video")
2193
+ if row.get("video"):
2194
+ kinds.add("video")
2195
+ elif row.get("image"):
2196
+ kinds.add("image")
2197
+ if model_id and prompt_id and media_url:
2198
+ images[model_id][prompt_id] = media_url
2199
+ if prompt_id and prompt_id not in source_videos:
2200
+ params = row.get("params") or {}
2201
+ input_video = params.get("input_video") or row.get("input_video")
2202
+ if input_video:
2203
+ source_videos[prompt_id] = input_video
2204
 
2205
  models = sorted(images)
2206
  if not models or not prompts:
2207
  return None
2208
 
2209
+ loaded = {
2210
  "prompts": prompts,
2211
  "images": {model: dict(prompt_map) for model, prompt_map in images.items()},
2212
  "models": models,
2213
  "prompt_ids": sorted(prompts),
2214
+ "kind": "video" if "video" in kinds else "image",
2215
  }
2216
+ if source_videos:
2217
+ loaded["source_videos"] = source_videos
2218
+ return loaded
2219
 
2220
 
2221
  def _as_numeric(df, columns):
 
2345
  return df.reset_index(drop=True)
2346
 
2347
 
2348
+ def load_video_editing_dataframe(path):
2349
+ """Load the video-to-video editing leaderboard."""
2350
+ df = pd.read_csv(path, na_values=["N/A", "n/a", ""])
2351
+ df = df.rename(
2352
+ columns={
2353
+ "display_name": "Model",
2354
+ "elo": "Datapoint Elo",
2355
+ "min_generation_s": "Min Generation Time (s)",
2356
+ "median_generation_s": "Median Generation Time (s)",
2357
+ "p20_generation_s": "P20 Generation Time (s)",
2358
+ "generation_s_per_output_video_s": "Time / Output Video Second (s)",
2359
+ "predict_time_s_per_output_video_s": "Predict Time / Output Video Second (s)",
2360
+ "model_execution_time_s_per_output_video_s": (
2361
+ "Execution Time / Output Video Second (s)"
2362
+ ),
2363
+ "price": "Price / Second of Video (USD)",
2364
+ }
2365
+ )
2366
+ df = df.drop(columns=["wandb_run_ids", "n_generations"], errors="ignore")
2367
+ df["Model"] = df["Model"].astype(str).str.strip()
2368
+ df = _as_numeric(
2369
+ df,
2370
+ [
2371
+ "Datapoint Elo",
2372
+ "Min Generation Time (s)",
2373
+ "Median Generation Time (s)",
2374
+ "P20 Generation Time (s)",
2375
+ "Time / Output Video Second (s)",
2376
+ "Predict Time / Output Video Second (s)",
2377
+ "Execution Time / Output Video Second (s)",
2378
+ "Price / Second of Video (USD)",
2379
+ ],
2380
+ )
2381
+ end_to_end = df.get("Time / Output Video Second (s)")
2382
+ execution = df.get("Execution Time / Output Video Second (s)")
2383
+ if end_to_end is not None:
2384
+ if "model_id" in df.columns:
2385
+ is_ours = df["model_id"].astype(str).str.lower().str.startswith(
2386
+ "p_video_edit"
2387
+ )
2388
+ else:
2389
+ is_ours = df["Model"].astype(str).str.casefold().str.startswith(
2390
+ "p-video-edit"
2391
+ )
2392
+ if execution is None:
2393
+ pareto_time = end_to_end
2394
+ else:
2395
+ ours_time = execution.where(execution.notna(), end_to_end)
2396
+ pareto_time = end_to_end.where(~is_ours, ours_time)
2397
+ df["Pareto Time / Output Video Second (s)"] = pareto_time
2398
+ df = df.drop(columns=["model_id"], errors="ignore")
2399
+ return df.reset_index(drop=True)
2400
+
2401
+
2402
  df = load_oneig_dataframe(oneig_path)
2403
 
2404
  oneig_metric_columns = [
 
2446
  data_dir / "qwen_image_bench_combined",
2447
  space_root.parent / "qwen_image_bench_combined",
2448
  )
2449
+ video_combined_dir = _resolve_data_path(
2450
+ data_dir / "video_editing_combined",
2451
+ space_root.parent / "video_editing_combined",
2452
+ )
2453
  qwen_path = _resolve_data_path(
2454
  data_dir / "qwen_image_bench_model_price_and_median_generation_time_10_august.csv",
2455
  space_root.parent / "qwen_image_bench_model_price_and_median_generation_time_10_august.csv",
 
2462
  data_dir / "arena_ai_text_to_image_leaderboard.csv",
2463
  space_root.parent / "arena_ai_text_to_image_leaderboard.csv",
2464
  )
2465
+ video_path = _resolve_data_path(
2466
+ data_dir / "video-editing-leaderboard.csv",
2467
+ space_root.parent / "video-editing-leaderboard.csv",
2468
+ )
2469
 
2470
  qwen_df = load_qwen_combined_dataframe(qwen_path)
2471
  aa_df = load_artificial_analysis_dataframe(aa_path)
2472
  arena_df = load_arena_ai_dataframe(arena_path)
2473
+ video_df = load_video_editing_dataframe(video_path)
2474
  qwen_display_columns = [
2475
  col
2476
  for col in [
 
2502
  ]
2503
  if col in arena_df.columns
2504
  ]
2505
+ video_display_columns = [
2506
+ col
2507
+ for col in [
2508
+ "Model",
2509
+ "Datapoint Elo",
2510
+ "Time / Output Video Second (s)",
2511
+ "Median Generation Time (s)",
2512
+ "Min Generation Time (s)",
2513
+ "Price / Second of Video (USD)",
2514
+ ]
2515
+ if col in video_df.columns
2516
+ ]
2517
 
2518
  oneig_samples = load_sample_comparison_data(oneig_combined_dir)
2519
  qwen_samples = load_sample_comparison_data(qwen_combined_dir)
2520
+ video_samples = load_sample_comparison_data(video_combined_dir)
2521
 
2522
  metrics = [
2523
  {"id": "datapoint_elo", "column": "Datapoint Elo"},
 
2576
  "arena_text",
2577
  ],
2578
  )
2579
+ video_metric_ids = _metric_ids_for(video_df, ["datapoint_elo"])
2580
 
2581
  datasets = [
2582
+ {
2583
+ "id": "video_editing",
2584
+ "name": "Pruna Internal Video-Edit Benchmark",
2585
+ "modality": "video_to_video",
2586
+ "data": video_df,
2587
+ "columns": video_display_columns,
2588
+ "metric_ids": video_metric_ids,
2589
+ "note": (
2590
+ "Datapoint Elo from pairwise video-edit preference. Price is USD "
2591
+ "per second of output video. Generation time per second of video "
2592
+ "is end-to-end wall time to produce one second of output."
2593
+ ),
2594
+ "samples": video_samples,
2595
+ },
2596
  {
2597
  "id": "qwen",
2598
  "name": "Qwen Image Dataset",
2599
+ "modality": "text_to_image",
2600
  "data": qwen_df,
2601
  "columns": qwen_display_columns,
2602
  "metric_ids": qwen_metric_ids,
 
2606
  {
2607
  "id": "oneig",
2608
  "name": "OneIG Alignment Dataset",
2609
+ "modality": "text_to_image",
2610
  "data": oneig_df,
2611
  "columns": oneig_display_columns,
2612
  "metric_ids": oneig_metric_ids,
 
2619
  {
2620
  "id": "artificial_analysis",
2621
  "name": "Artificial Analysis Dataset",
2622
+ "modality": "text_to_image",
2623
  "data": aa_df,
2624
  "columns": aa_display_columns,
2625
  "metric_ids": aa_metric_ids,
 
2629
  {
2630
  "id": "arena_ai",
2631
  "name": "Arena AI Dataset",
2632
+ "modality": "text_to_image",
2633
  "data": arena_df,
2634
  "columns": arena_display_columns,
2635
  "metric_ids": arena_metric_ids,
 
2640
  datasets = [dataset for dataset in datasets if dataset["metric_ids"]]
2641
 
2642
  DEFAULT_DATASET_ID = next(
2643
+ (dataset["id"] for dataset in datasets if dataset["id"] == "video_editing"),
2644
+ next(
2645
+ (dataset["id"] for dataset in datasets if dataset["id"] == "qwen"),
2646
+ datasets[0]["id"] if datasets else None,
2647
+ ),
2648
  )
2649
  DEFAULT_METRIC_ID = None
2650
 
data/video-editing-leaderboard.csv ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model_id,display_name,elo,wandb_run_ids,n_generations,min_generation_s,median_generation_s,p20_generation_s,generation_s_per_output_video_s,predict_time_s_per_output_video_s,model_execution_time_s_per_output_video_s,price
2
+ gemini_omni_flash_edit__fal,Gemini Omni Flash Edit,1054,3iuctwj9,56,32.574746752001374,58.2327566820004,50.44402900500063,13.78,,,0.13
3
+ grok_imagine_video__replicate,Grok Imagine Video,974,q0ogvekx,59,31.640928319000523,43.75649321700257,32.88782280539963,11.04,,,0.05
4
+ happyhorse_1_0__wavespeed,HappyHorse 1.0,1048,k4zj5wqk,71,106.5824136010051,201.2431439649954,141.46595527700265,46.73,,,0.14
5
+ ltx_2_3_quality_reference_video_to_video__fal,LTX 2.3 Video Edit,911,p19ks8ad,73,60.768441981999786,73.62109239100027,70.20137865180223,15.83,,,0.054
6
+ lucy_edit_pro__fal,Lucy Edit Pro,879,na6bz9xx,72,118.3459930579993,136.44442766549764,124.13885582720104,27.18,,,0.15
7
+ minimax_h3_reference_to_video__fal,MiniMax H3 Reference-to-Video,1060,639xmakz,63,180.8385945170012,308.29198316100155,248.2394383729996,58.31,,,0.06
8
+ p_video_edit_preview__replicate_final,P-Video-Edit,1000,m8irpsa6,73,31.41135125700021,86.86665409700072,57.21095684959946,23.18,17.99,12.06,0.045
9
+ p_video_edit_preview__replicate_final__draft,P-Video-Edit Draft,994,6f88e6mx,73,23.04809326099712,42.9797806409988,33.19193872759861,11.48,11.28,4.46,0.025
10
+ seedance_2_5_video_edit_turbo__wavespeed,Seedance 2.5 Video Edit Turbo,1063,2nr274vp,68,142.49452170499717,293.7401115540015,223.11569286320045,59.93,,,0.24
11
+ wan_2_7_video_edit__wavespeed,Wan 2.7 Video Edit,1055,9lq803be,66,134.3744560209998,313.957433804002,219.2491260079987,68.04,,,0.2
12
+
data/video_editing_combined/generations.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3ec4ea0a431484e65f30990c8cc4292b00c665be96db587775426da1c84a4ab8
3
+ size 762227
data/video_editing_combined/prompts.jsonl ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"prompt_id": "video_edit_internal__advertising__p0000", "text": "Replace the white bottle with an orange sunscreen bottle.", "dataset": "video_edit_internal", "category": "advertising", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/advertising/4620329ad65820ce.mp4"}
2
+ {"prompt_id": "video_edit_internal__advertising__p0001", "text": "Replace the woman with an asian woman riding on a donkey through a chinese small town.", "dataset": "video_edit_internal", "category": "advertising", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/advertising/44db5689846be901.mp4"}
3
+ {"prompt_id": "video_edit_internal__advertising__p0002", "text": "Place the couple on a snowy mountain next to a mountain hut.", "dataset": "video_edit_internal", "category": "advertising", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/advertising/481db4a047688bbe.mp4"}
4
+ {"prompt_id": "video_edit_internal__advertising__p0003", "text": "Turn this into a scene in the summer with birds flying in the sky and butterflies in the foreground.", "dataset": "video_edit_internal", "category": "advertising", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/advertising/32b9f56763c56482.mp4"}
5
+ {"prompt_id": "video_edit_internal__advertising__p0004", "text": "Replace the robot arm with a dancing hamster on a small table.", "dataset": "video_edit_internal", "category": "advertising", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/advertising/22d02ae2df7f9045.mp4"}
6
+ {"prompt_id": "video_edit_internal__anonymization__p0000", "text": "Anonymize the womans face", "dataset": "video_edit_internal", "category": "anonymization", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/anonymization/7c410085f2fd939d.mp4"}
7
+ {"prompt_id": "video_edit_internal__anonymization__p0001", "text": "Anonymize the mans face", "dataset": "video_edit_internal", "category": "anonymization", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/anonymization/7b495805c0c270ea.mp4"}
8
+ {"prompt_id": "video_edit_internal__anonymization__p0002", "text": "Anonymize the mans face", "dataset": "video_edit_internal", "category": "anonymization", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/anonymization/736b6fb504bd32be.mp4"}
9
+ {"prompt_id": "video_edit_internal__anonymization__p0003", "text": "Anonymize the girls face", "dataset": "video_edit_internal", "category": "anonymization", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/anonymization/2c82413d39579cd5.mp4"}
10
+ {"prompt_id": "video_edit_internal__artificial_analysis__p0000", "text": "Change the rainforest setting to a neon-lit urban alley at night with steam from vents and wet reflective asphalt, and move into an ariel shot of the character after the initial camera movement to focus on the character", "dataset": "video_edit_internal", "category": "artificial_analysis", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/artificial_analysis/e2f6121269a206d3.mp4"}
11
+ {"prompt_id": "video_edit_internal__artificial_analysis__p0001", "text": "Replace the magenta hover-car with a chrome-blue car, keeping the drift and neon light trails.", "dataset": "video_edit_internal", "category": "artificial_analysis", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/artificial_analysis/55d1baa7f3f71b26.mp4"}
12
+ {"prompt_id": "video_edit_internal__artificial_analysis__p0002", "text": "Make the footage look like it was shot on a 1970s film camera, with grainy film texture, faded warm colors, slight softness, and slightly darker corners around the edges.", "dataset": "video_edit_internal", "category": "artificial_analysis", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/artificial_analysis/fd1ab436de020152.mp4"}
13
+ {"prompt_id": "video_edit_internal__artificial_analysis__p0003", "text": "A person appears at the top of the waterfall, leaps into the lake below, and disappears into the misty water beneath the falls.", "dataset": "video_edit_internal", "category": "artificial_analysis", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/artificial_analysis/7ee1623303a8da68.mp4"}
14
+ {"prompt_id": "video_edit_internal__artificial_analysis__p0004", "text": "A fluffy orange cat leaps gracefully from the floor onto the couch, paws sinking into the soft cushions. It circles once in place, tail swaying gently, before curling up tightly on one of the cushions and settling in comfortably.", "dataset": "video_edit_internal", "category": "artificial_analysis", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/artificial_analysis/9a73c608b343bbbe.mp4"}
15
+ {"prompt_id": "video_edit_internal__camera_editing__p0000", "text": "Zoom in on the man's face to show his focused expression", "dataset": "video_edit_internal", "category": "camera_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/camera_editing/0336e64b0594bd7a.mp4"}
16
+ {"prompt_id": "video_edit_internal__camera_editing__p0001", "text": "Perform an arc shot around the tram as it arrives at the station", "dataset": "video_edit_internal", "category": "camera_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/camera_editing/204aa93703da117b.mp4"}
17
+ {"prompt_id": "video_edit_internal__camera_editing__p0002", "text": "Change the view to a high angle.", "dataset": "video_edit_internal", "category": "camera_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/camera_editing/c2ccb5351be5c718.mp4"}
18
+ {"prompt_id": "video_edit_internal__camera_editing__p0003", "text": "Change the view to a high angle.", "dataset": "video_edit_internal", "category": "camera_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/camera_editing/b725aaded02d65e9.mp4"}
19
+ {"prompt_id": "video_edit_internal__camera_editing__p0004", "text": "Gradually move the camera away from the doctor", "dataset": "video_edit_internal", "category": "camera_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/camera_editing/33f24f4d083df743.mp4"}
20
+ {"prompt_id": "video_edit_internal__design_arena__p0000", "text": "Create a transition of the video of the product whihc is the jeans on the lady model with an atitiude that goes into a zoom out camera that she is in a party .Focus on the vibes", "dataset": "video_edit_internal", "category": "design_arena", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/design_arena/b0b4642272a481b1.mp4"}
21
+ {"prompt_id": "video_edit_internal__design_arena__p0001", "text": "add ethnic urban women dancing at the bottem and at the bar wearing DMI Tshirts", "dataset": "video_edit_internal", "category": "design_arena", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/design_arena/0f730f98f3c51356.mp4"}
22
+ {"prompt_id": "video_edit_internal__design_arena__p0002", "text": "Generate a commercial of this dog drinking beer at an electronic music party in a world where dogs and humans are mixed together.", "dataset": "video_edit_internal", "category": "design_arena", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/design_arena/575fc4b9b6d7b3ab.mp4"}
23
+ {"prompt_id": "video_edit_internal__design_arena__p0003", "text": "everything is the same excpet the winter season, snowing", "dataset": "video_edit_internal", "category": "design_arena", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/design_arena/8674fd022867a736.mp4"}
24
+ {"prompt_id": "video_edit_internal__design_arena__p0004", "text": "A cinematic character introduction of Jason, framed in a medium close-up with subtle camera movement, confident body language, and expressive facial detail. Moody, high-contrast lighting with a cool-toned color palette, shallow depth of field, and a slow dramatic reveal that builds intrigue over a few seconds. I want a video in a 9:16 aspect ratio for tiktok. remove all text and have him in a mid day campus setting", "dataset": "video_edit_internal", "category": "design_arena", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/design_arena/91873bbafe4173da.mp4"}
25
+ {"prompt_id": "video_edit_internal__e_commerce__p0000", "text": "Remove the black blazer.", "dataset": "video_edit_internal", "category": "e_commerce", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/e_commerce/b89faaa5080c31db.mp4"}
26
+ {"prompt_id": "video_edit_internal__e_commerce__p0001", "text": "Replace her skirt with a jeans skirt.", "dataset": "video_edit_internal", "category": "e_commerce", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/e_commerce/c34a33d07e843804.mp4"}
27
+ {"prompt_id": "video_edit_internal__e_commerce__p0002", "text": "Replace the black leggings and the black shirt with a white dress.", "dataset": "video_edit_internal", "category": "e_commerce", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/e_commerce/489d2439cbaeed79.mp4"}
28
+ {"prompt_id": "video_edit_internal__e_commerce__p0003", "text": "Replace the white woman with a lebanese-looking woman.", "dataset": "video_edit_internal", "category": "e_commerce", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/e_commerce/0ad8fb773f303a11.mp4"}
29
+ {"prompt_id": "video_edit_internal__e_commerce__p0004", "text": "Remove all items on the table and place an eyeshadow pallete on the table next to the woman.", "dataset": "video_edit_internal", "category": "e_commerce", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/e_commerce/f28f3aa12b1ec220.mp4"}
30
+ {"prompt_id": "video_edit_internal__lighting__p0000", "text": "After the sun sets behind the mountains, the scene transitions into nighttime.", "dataset": "video_edit_internal", "category": "lighting", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/lighting/229c165e0ac97daf.mp4"}
31
+ {"prompt_id": "video_edit_internal__lighting__p0001", "text": "Change the weather to a dazzling starry night.", "dataset": "video_edit_internal", "category": "lighting", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/lighting/f42c41ab2f3adc26.mp4"}
32
+ {"prompt_id": "video_edit_internal__lighting__p0002", "text": "Change the weather to a thunderstorm with heavy rain.", "dataset": "video_edit_internal", "category": "lighting", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/lighting/daa36567634ca6be.mp4"}
33
+ {"prompt_id": "video_edit_internal__lighting__p0003", "text": "Change the weather to a torrential downpour.", "dataset": "video_edit_internal", "category": "lighting", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/lighting/6817a4142b69d602.mp4"}
34
+ {"prompt_id": "video_edit_internal__lighting__p0004", "text": "Change the weather to a torrential downpour.", "dataset": "video_edit_internal", "category": "lighting", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/lighting/ac01488a685a1096.mp4"}
35
+ {"prompt_id": "video_edit_internal__long__p0000", "text": "Change the weather to a dazzling starry night.", "dataset": "video_edit_internal", "category": "long", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/long/e9af2a7039e43f16.mp4"}
36
+ {"prompt_id": "video_edit_internal__long__p0001", "text": "Adjust the color of book to blue", "dataset": "video_edit_internal", "category": "long", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/long/eab696d1555be316.mp4"}
37
+ {"prompt_id": "video_edit_internal__long__p0002", "text": "Make the young woman turn into sand and blow away.", "dataset": "video_edit_internal", "category": "long", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/long/d18378f1d7e4ca0e.mp4"}
38
+ {"prompt_id": "video_edit_internal__long__p0003", "text": "Make the bird flap its wings", "dataset": "video_edit_internal", "category": "long", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/long/3835b37b43f5e5bf.mp4"}
39
+ {"prompt_id": "video_edit_internal__long__p0004", "text": "Transform the video into a ukiyo-e style", "dataset": "video_edit_internal", "category": "long", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/long/09fe5bb4b5304b66.mp4"}
40
+ {"prompt_id": "video_edit_internal__movie_concept_art__p0000", "text": "Add more blood and wounds to the mans face.", "dataset": "video_edit_internal", "category": "movie_concept_art", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/movie_concept_art/1d1a99167475720b.mp4"}
41
+ {"prompt_id": "video_edit_internal__movie_concept_art__p0001", "text": "Make the scene less dark.", "dataset": "video_edit_internal", "category": "movie_concept_art", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/movie_concept_art/10cb44d8c0bf4116.mp4"}
42
+ {"prompt_id": "video_edit_internal__movie_concept_art__p0002", "text": "Replace the female warrior with a male warrior with ginger hair.", "dataset": "video_edit_internal", "category": "movie_concept_art", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/movie_concept_art/9e45454390c20b8a.mp4"}
43
+ {"prompt_id": "video_edit_internal__movie_concept_art__p0003", "text": "Turn the man's hand into a robotic hand.", "dataset": "video_edit_internal", "category": "movie_concept_art", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/movie_concept_art/99977e7a91a950b4.mp4"}
44
+ {"prompt_id": "video_edit_internal__real_estate__p0000", "text": "Replace the interior with a gold-black interior heavy luxury style.", "dataset": "video_edit_internal", "category": "real_estate", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/real_estate/869064ab06bb5daa.mp4"}
45
+ {"prompt_id": "video_edit_internal__real_estate__p0001", "text": "Replace the grey wallpaper with a beige painted wall and turn the grey curtains a dark brown.", "dataset": "video_edit_internal", "category": "real_estate", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/real_estate/1c2f358bfe9b8299.mp4"}
46
+ {"prompt_id": "video_edit_internal__real_estate__p0002", "text": "Remove all decoration from the walls and keep only the furniture.", "dataset": "video_edit_internal", "category": "real_estate", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/real_estate/2058bcf38389f8fe.mp4"}
47
+ {"prompt_id": "video_edit_internal__real_estate__p0003", "text": "Exchange the wooden floor for a marble floor.", "dataset": "video_edit_internal", "category": "real_estate", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/real_estate/6a4c09a23dda57d9.mp4"}
48
+ {"prompt_id": "video_edit_internal__real_estate__p0004", "text": "Replace the bedframe with a modern wooden bed.", "dataset": "video_edit_internal", "category": "real_estate", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/real_estate/3e0f6dc5df891001.mp4"}
49
+ {"prompt_id": "video_edit_internal__style_transfer__p0000", "text": "Transform the video into a cyberpunk style", "dataset": "video_edit_internal", "category": "style_transfer", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/style_transfer/2253f6ed3674f006.mp4"}
50
+ {"prompt_id": "video_edit_internal__style_transfer__p0001", "text": "Convert to different shades of orange", "dataset": "video_edit_internal", "category": "style_transfer", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/style_transfer/edb6a105be03bae0.mp4"}
51
+ {"prompt_id": "video_edit_internal__style_transfer__p0002", "text": "Convert to black and white", "dataset": "video_edit_internal", "category": "style_transfer", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/style_transfer/c3dc058b3be26cbd.mp4"}
52
+ {"prompt_id": "video_edit_internal__style_transfer__p0003", "text": "Apply Ghibli-style editing to the video", "dataset": "video_edit_internal", "category": "style_transfer", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/style_transfer/83a638223a20911c.mp4"}
53
+ {"prompt_id": "video_edit_internal__style_transfer__p0004", "text": "Relight the scene as if it were shot during golden hour, with warm low-angle sunlight, soft shadows, and natural highlights on faces and surfaces.", "dataset": "video_edit_internal", "category": "style_transfer", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/style_transfer/daa36567634ca6be.mp4"}
54
+ {"prompt_id": "video_edit_internal__subject_editing__p0000", "text": "Replace the rainbow colors of the logo with different shades of purple", "dataset": "video_edit_internal", "category": "subject_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_editing/6358f26b46baadf5.mp4"}
55
+ {"prompt_id": "video_edit_internal__subject_editing__p0001", "text": "Add a small dog running beside the scooter", "dataset": "video_edit_internal", "category": "subject_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_editing/052c0bfd7b68fea7.mp4"}
56
+ {"prompt_id": "video_edit_internal__subject_editing__p0002", "text": "Replace the splashing waves with a calm water surface", "dataset": "video_edit_internal", "category": "subject_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_editing/c3f79578a39f415d.mp4"}
57
+ {"prompt_id": "video_edit_internal__subject_editing__p0003", "text": "Add a violinist in the background", "dataset": "video_edit_internal", "category": "subject_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_editing/a0eebf32a076e2fc.mp4"}
58
+ {"prompt_id": "video_edit_internal__subject_editing__p0004", "text": "Add a group of people walking on the pathway", "dataset": "video_edit_internal", "category": "subject_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_editing/3c2b7e3a5b284e1d.mp4"}
59
+ {"prompt_id": "video_edit_internal__subject_motion_editing__p0000", "text": "Make the bird hop around instead of walking and foraging", "dataset": "video_edit_internal", "category": "subject_motion_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_motion_editing/ac38b2a97d1a5c3a.mp4"}
60
+ {"prompt_id": "video_edit_internal__subject_motion_editing__p0001", "text": "The male colleague is walking around to observe.", "dataset": "video_edit_internal", "category": "subject_motion_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_motion_editing/8aaab7d304c5ad99.mp4"}
61
+ {"prompt_id": "video_edit_internal__subject_motion_editing__p0002", "text": "Make the static spider-man in the mural dynamic and make him swing faster", "dataset": "video_edit_internal", "category": "subject_motion_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_motion_editing/3412b39686fd5879.mp4"}
62
+ {"prompt_id": "video_edit_internal__subject_motion_editing__p0003", "text": "Change the woman's jogging to taking off.", "dataset": "video_edit_internal", "category": "subject_motion_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_motion_editing/17c4dc0514772321.mp4"}
63
+ {"prompt_id": "video_edit_internal__subject_motion_editing__p0004", "text": "Make the knight lunging forward and the creature swiping at the knight", "dataset": "video_edit_internal", "category": "subject_motion_editing", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/subject_motion_editing/980354550011911b.mp4"}
64
+ {"prompt_id": "video_edit_internal__synthetic_data__p0000", "text": "Turn the scene into nighttime.", "dataset": "video_edit_internal", "category": "synthetic_data", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/synthetic_data/e3b1e603bfaa1aec.mp4"}
65
+ {"prompt_id": "video_edit_internal__synthetic_data__p0001", "text": "Remove the crosswalk.", "dataset": "video_edit_internal", "category": "synthetic_data", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/synthetic_data/30df5fd5fe28af65.mp4"}
66
+ {"prompt_id": "video_edit_internal__synthetic_data__p0002", "text": "Add a bicycle riding in front of the car.", "dataset": "video_edit_internal", "category": "synthetic_data", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/synthetic_data/d093491e25682b3a.mp4"}
67
+ {"prompt_id": "video_edit_internal__synthetic_data__p0003", "text": "Turn the scene into a snowstorm scene.", "dataset": "video_edit_internal", "category": "synthetic_data", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/synthetic_data/1da5913f13362f8d.mp4"}
68
+ {"prompt_id": "video_edit_internal__synthetic_data__p0004", "text": "Make it rain in the scene.", "dataset": "video_edit_internal", "category": "synthetic_data", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/synthetic_data/936bdb89558f5a7f.mp4"}
69
+ {"prompt_id": "video_edit_internal__text__p0000", "text": "Add the text \"True Love\" in the foreground in pink romantic font.", "dataset": "video_edit_internal", "category": "text", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/text/c763407793ca9b0e.mp4"}
70
+ {"prompt_id": "video_edit_internal__text__p0001", "text": "Replace any mention of \"Fanta\" with the branding \"Cola\".", "dataset": "video_edit_internal", "category": "text", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/text/7b878ea6d99834b2.mp4"}
71
+ {"prompt_id": "video_edit_internal__text__p0002", "text": "Remove all text from the video.", "dataset": "video_edit_internal", "category": "text", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/text/76763c31b2609109.mp4"}
72
+ {"prompt_id": "video_edit_internal__text__p0003", "text": "Replace the branding \"Royalty\" with the phrasing \"Princess\".", "dataset": "video_edit_internal", "category": "text", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/text/d221d299c9e49064.mp4"}
73
+ {"prompt_id": "video_edit_internal__text__p0004", "text": "Remove all text from the video.", "dataset": "video_edit_internal", "category": "text", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/text/5c169901af8e4ed6.mp4"}
74
+ {"prompt_id": "video_edit_internal__transitions__p0000", "text": "After a black screen transition, the road transforms into lush grass.", "dataset": "video_edit_internal", "category": "transitions", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/transitions/964c35b19ee1fc1f.mp4"}
75
+ {"prompt_id": "video_edit_internal__transitions__p0001", "text": "After a wave-foam transition, the small fishing boat is eaten by a giant whale.", "dataset": "video_edit_internal", "category": "transitions", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/transitions/b8b88f43316363f3.mp4"}
76
+ {"prompt_id": "video_edit_internal__transitions__p0002", "text": "Add a cut transition, then show a bowl of ramen topped with parsley.", "dataset": "video_edit_internal", "category": "transitions", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/transitions/c823d6c77bdc87f4.mp4"}
77
+ {"prompt_id": "video_edit_internal__transitions__p0003", "text": "Add a smoke transition, then show the circuit board burning.", "dataset": "video_edit_internal", "category": "transitions", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/transitions/edb9927ff06c1175.mp4"}
78
+ {"prompt_id": "video_edit_internal__transitions__p0004", "text": "After a smoke transition, the Basilica of the Sacred Heart of Paris catches fire.", "dataset": "video_edit_internal", "category": "transitions", "source_video": "https://d2j1a65dna040x.cloudfront.net/benchmark_inputs/video_edit_internal/transitions/77bb900383d4f370.mp4"}
model_display.py CHANGED
@@ -88,6 +88,22 @@ MODEL_DISPLAY_NAMES = {
88
  "p_image_2_ideogram_high_2k": "P-Image-Ideogram High 2K",
89
  "P-Image-Ideogram (High)": "P-Image-Ideogram High",
90
  "p_image_2_ideogram_very_high_1k": "P-Image-Ideogram Very High 1K",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  # Others overlapping P-Bench
92
  "z_image": "Z-Image",
93
  "glm_image": "GLM-Image",
@@ -210,6 +226,6 @@ def display_model_name(model_id) -> str:
210
  if raw in MODEL_DISPLAY_NAMES:
211
  return MODEL_DISPLAY_NAMES[raw]
212
  # Already a human label (spaces / punctuation) — keep as-is.
213
- if re.search(r"[\s.\[\]()]", raw):
214
  return raw
215
  return _prettify_snake_case(raw)
 
88
  "p_image_2_ideogram_high_2k": "P-Image-Ideogram High 2K",
89
  "P-Image-Ideogram (High)": "P-Image-Ideogram High",
90
  "p_image_2_ideogram_very_high_1k": "P-Image-Ideogram Very High 1K",
91
+ # P-Video-Edit
92
+ "P-Video-Edit": "P-Video-Edit",
93
+ "P-Video-Edit Draft": "P-Video-Edit Draft",
94
+ "P-Video Edit Final": "P-Video-Edit",
95
+ "P-Video Edit Final (draft)": "P-Video-Edit Draft",
96
+ "p_video_edit_preview__replicate_final": "P-Video-Edit",
97
+ "p_video_edit_preview__replicate_final__draft": "P-Video-Edit Draft",
98
+ # Video-to-video leaderboard
99
+ "gemini_omni_flash_edit__fal": "Gemini Omni Flash Edit",
100
+ "grok_imagine_video__replicate": "Grok Imagine Video",
101
+ "happyhorse_1_0__wavespeed": "HappyHorse 1.0",
102
+ "ltx_2_3_quality_reference_video_to_video__fal": "LTX 2.3 Video Edit",
103
+ "lucy_edit_pro__fal": "Lucy Edit Pro",
104
+ "minimax_h3_reference_to_video__fal": "MiniMax H3 Reference-to-Video",
105
+ "seedance_2_5_video_edit_turbo__wavespeed": "Seedance 2.5 Video Edit Turbo",
106
+ "wan_2_7_video_edit__wavespeed": "Wan 2.7 Video Edit",
107
  # Others overlapping P-Bench
108
  "z_image": "Z-Image",
109
  "glm_image": "GLM-Image",
 
226
  if raw in MODEL_DISPLAY_NAMES:
227
  return MODEL_DISPLAY_NAMES[raw]
228
  # Already a human label (spaces / punctuation) — keep as-is.
229
+ if re.search(r"[\s.\[\]()-]", raw):
230
  return raw
231
  return _prettify_snake_case(raw)
ui.py CHANGED
@@ -25,19 +25,41 @@ MAX_COMPARE_PROMPTS = 8
25
  MAX_PARETO_METRICS = 8
26
  _PARETO_SLOT_COUNT = 1 + MAX_PARETO_METRICS * 8
27
  _PARETO_PRICE_COLUMN = "Price / Image (USD)"
 
 
28
  _PARETO_TIME_COLUMN = "Min Generation Time (s)"
 
 
 
 
 
 
 
 
 
 
29
  _PARETO_SCALE_CHOICES = [
30
  ("Log", "Logarithmic"),
31
  ("Linear", "Linear"),
32
  ]
33
  _PARETO_SCALE_VALUES = {value for _, value in _PARETO_SCALE_CHOICES}
34
  _PARETO_SCALE_DEFAULT = "Logarithmic"
 
 
 
35
 
36
  TAB_LEADERBOARDS = "leaderboards"
37
  TAB_PARETO = "pareto"
38
  TAB_SAMPLES = "samples"
39
  TAB_ABOUT = "about"
40
 
 
 
 
 
 
 
 
41
  _MODEL_CHOICES_CACHE = {}
42
  _VIEW_EVENTS = {
43
  "show_progress": "hidden",
@@ -49,21 +71,24 @@ _VIEW_EVENTS = {
49
  ABOUT_OVERVIEW_CONTENT = """
50
  # About P-Bench
51
 
52
- P-Bench compares **text-to-image models**, including optimized or accelerated
53
- endpoints, on **quality, speed, and price**. Each view is a **dataset** scored
54
- with a **metric**, written as `Dataset | Metric`. There is no single score
55
- across P-Bench.
56
 
57
  ## How to read it
58
 
59
- 1. Pick a **dataset** and a **metric**.
 
60
  2. **Leaderboards**: ranked by that metric. Price and generation time sit in
61
  the same table when the source publishes them.
62
  3. **Pareto plots**: mark models that are not beaten on both higher score
63
  and lower price (or time). Only datasets with price or generation time
64
  can open this tab (not Arena AI).
65
  4. **Samples**: the same prompts, side by side. Only for datasets we
66
- generated (Qwen Image Dataset and OneIG Alignment Dataset).
 
 
67
 
68
  ## How a score is made
69
 
@@ -82,6 +107,17 @@ prompt suites, so samples are not shown.
82
 
83
  ## Current datasets
84
 
 
 
 
 
 
 
 
 
 
 
 
85
  ### Qwen Image Dataset
86
  100 prompts from the 1,000-prompt Qwen Image Bench set, sampled for coverage
87
  across its fine-grained (L3) categories. Metrics include Datapoint Elo,
@@ -124,12 +160,14 @@ ABOUT_DETAILS_CONTENT = """
124
  - **Arena Elo**: Elo published by Arena AI on their own dataset, plus
125
  category Elos (branding, 3D, cartoon/anime, photorealistic, art, portraits,
126
  text rendering).
127
- - **Generation time**: median and minimum generation time in seconds, as
128
- reported in the evaluation table. This is not a p95, and we do not state
129
- warm vs cold or concurrent load. Not available for Arena AI.
130
- - **Price**: USD per image in the evaluation table. We do not state list
131
- price vs amount paid, or whether failed generations are included. Not
132
- available for Arena AI.
 
 
133
 
134
  Scores from different datasets or metrics are **not interchangeable**. A high
135
  OneIG alignment score is not the same quantity as a Datapoint Elo. Compare
@@ -143,14 +181,18 @@ models *within* a Dataset | Metric view.
143
  - **Prompt counts:** OneIG Alignment uses 100 anime, 100 human, and 99 object
144
  prompts (299 total). Qwen Image Dataset uses 100 prompts sampled from the
145
  1,000-prompt pool for roughly even coverage of its fine-grained (L3)
146
- categories. Artificial Analysis and Arena AI use their own private prompt
147
- sets.
 
 
148
  - **Generation (Qwen and OneIG):** one image per prompt per endpoint when
149
  the run exists. Default resolution is 1024×1024. Exceptions: FLUX 1.1 Pro
150
  Ultra at 2K, FLUX 2 Flex at 1008×1008, and any endpoint labeled 2K. The
151
  seed is derived from the prompt, so every model gets the same seed for the
152
  same prompt. Steps, CFG, prompt rewrite, and safety filters follow each
153
  endpoint's default. This does not describe Artificial Analysis or Arena AI.
 
 
154
  - **Datapoint (Qwen and OneIG):** every model pair is compared on every
155
  prompt, with 10 votes per battle.
156
  - **Rapidata (Qwen and OneIG):** prompts longer than 400 characters are
@@ -222,7 +264,7 @@ def render_header():
222
  </svg>
223
  </button>
224
  </div>
225
- <p class="app-header-tagline">Compare text-to-image models on quality, speed, and price</p>
226
  </header>
227
  """,
228
  padding=False,
@@ -237,10 +279,42 @@ def _item(items, item_id):
237
  return items[0] if items else None
238
 
239
 
240
- def _dataset_choices(datasets, *, require_samples=False, require_pareto=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  return [
242
  (dataset["name"], dataset["id"])
243
- for dataset in datasets
244
  if (not require_samples or dataset.get("samples"))
245
  and (not require_pareto or _dataset_has_pareto(datasets, dataset["id"]))
246
  ]
@@ -256,20 +330,72 @@ def _sample_model_ids(datasets, dataset_id):
256
  samples = dataset.get("samples") if dataset else None
257
  if not samples:
258
  return set()
259
- return set(samples.get("models") or [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
 
261
 
262
  def _dataset_has_pareto(datasets, dataset_id):
263
  dataset = _item(datasets, dataset_id)
264
- columns = getattr(dataset.get("data") if dataset else None, "columns", [])
265
- return _PARETO_PRICE_COLUMN in columns or _PARETO_TIME_COLUMN in columns
 
 
 
266
 
267
 
268
- def _dataset_dropdown_update(datasets, tab, dataset_id):
269
  """Limit the dataset list to what the current tab can show."""
 
 
270
  return gr.update(
271
  choices=_dataset_choices(
272
  datasets,
 
273
  require_samples=tab == TAB_SAMPLES
274
  and _dataset_has_samples(datasets, dataset_id),
275
  require_pareto=tab == TAB_PARETO
@@ -375,9 +501,11 @@ _LEADERBOARD_IDENTITY_COLUMNS = [
375
  "Optimized",
376
  ]
377
  _LEADERBOARD_META_COLUMNS = [
 
378
  "Median Generation Time (s)",
379
  "Min Generation Time (s)",
380
  "Price / Image (USD)",
 
381
  "Evaluation Date (UTC)",
382
  "Date",
383
  ]
@@ -604,7 +732,9 @@ def _display_label(column):
604
  "Arena Text Rendering Elo": "Text Rendering",
605
  "Median Generation Time (s)": "Median generation time",
606
  "Min Generation Time (s)": "Min generation time",
 
607
  "Price / Image (USD)": "Price per image",
 
608
  "Evaluation Date (UTC)": "Date",
609
  "Date": "Date",
610
  }
@@ -679,6 +809,23 @@ def _applied_key(view_state):
679
  )
680
 
681
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682
  def _build_pareto_figure(
683
  data,
684
  score_column,
@@ -703,6 +850,8 @@ def _build_pareto_figure(
703
 
704
  dominated = scatter.loc[[not flag for flag in on_frontier]].copy()
705
  frontier = scatter.loc[on_frontier].sort_values(x_column).copy()
 
 
706
  if not dominated.empty:
707
  dominated["Model"] = dominated["Model"].map(display_model_name)
708
  if not frontier.empty:
@@ -723,10 +872,11 @@ def _build_pareto_figure(
723
  name="Below frontier",
724
  text=dominated["Model"],
725
  hovertemplate=hover,
 
726
  marker={
727
  "size": 9,
728
- "color": "#d8b4fe",
729
- "opacity": 0.8,
730
  "line": {"width": 0},
731
  },
732
  )
@@ -740,14 +890,51 @@ def _build_pareto_figure(
740
  name="On frontier",
741
  text=frontier["Model"],
742
  hovertemplate=hover,
743
- line={"color": "#69a45c", "width": 2.5},
 
744
  marker={
745
  "size": 12,
746
- "color": "#69a45c",
747
- "line": {"width": 1.5, "color": "#86c077"},
748
  },
749
  )
750
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
 
752
  score_label = _display_label(score_column)
753
  fig.update_layout(
@@ -887,23 +1074,43 @@ def _pareto_pair(
887
  if data is None or not score_column or score_column not in data.columns:
888
  return None, score_missing, None, score_missing
889
 
 
 
 
 
 
 
 
890
  price_fig, price_message = _pareto_axis(
891
  data,
892
  score_column,
893
- _PARETO_PRICE_COLUMN,
894
- "Price per image (USD)",
895
- "Price per image isn't available for this dataset.",
896
  "No models have both a score and a price for this metric.",
897
  x_hover_prefix="$",
898
  x_axis_type=_pareto_axis_type(price_scale),
899
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
900
  time_fig, time_message = _pareto_axis(
901
  data,
902
  score_column,
903
- _PARETO_TIME_COLUMN,
904
- "Min generation time (s)",
905
- "Min generation time isn't available for this dataset.",
906
- "No models have both a score and a min generation time for this metric.",
907
  x_hover_suffix="s",
908
  x_axis_type=_pareto_axis_type(latency_scale),
909
  )
@@ -911,29 +1118,29 @@ def _pareto_pair(
911
 
912
 
913
  def _pareto_dataset_message(data):
914
- has_price = data is not None and _PARETO_PRICE_COLUMN in data.columns
915
- has_time = data is not None and _PARETO_TIME_COLUMN in data.columns
916
  if has_price or has_time:
917
  return None
918
  return (
919
- "Price per image and min generation time aren't available for "
920
  "this dataset, so these plots can't be drawn."
921
  )
922
 
923
 
924
  def _pareto_slot_note(price_fig, price_message, time_fig, time_message, data):
925
- has_price = data is not None and _PARETO_PRICE_COLUMN in data.columns
926
- has_time = data is not None and _PARETO_TIME_COLUMN in data.columns
927
  notes = []
928
  if has_price and not has_time:
929
  notes.append(
930
- "Min generation time isn't available for this dataset, so only "
931
  "price vs score is shown."
932
  )
933
  elif has_time and not has_price:
934
  notes.append(
935
- "Price per image isn't available for this dataset, so only min "
936
- "generation time vs score is shown."
937
  )
938
  if price_fig is None and has_price:
939
  notes.append(price_message)
@@ -954,8 +1161,8 @@ def _pareto_slot_updates(
954
  score_columns = [column for column in (score_columns or []) if column]
955
  price_scales = _normalize_pareto_scales(price_scales)
956
  time_scales = _normalize_pareto_scales(time_scales)
957
- has_price = data is not None and _PARETO_PRICE_COLUMN in data.columns
958
- has_time = data is not None and _PARETO_TIME_COLUMN in data.columns
959
  dataset_note = _pareto_dataset_message(data)
960
  updates = [_pareto_note_update(dataset_note)]
961
  hide_all_slots = not has_price and not has_time
@@ -1034,15 +1241,48 @@ def _samples_html(samples, selected_models, num_prompts, seed=0):
1034
  return _pareto_unavailable_html(
1035
  "Samples aren't available for this dataset."
1036
  )
1037
- images = samples.get("images", {})
1038
- models = [model for model in (selected_models or []) if model in images]
 
 
 
1039
  if not models:
1040
- models = (samples.get("models") or [])[:2]
1041
  return _build_compare_samples_html(samples, models, num_prompts, seed)
1042
 
1043
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1044
  def _build_compare_samples_html(samples, selected_models, num_prompts, seed=0):
1045
  selected_models = list(selected_models or [])[:MAX_COMPARE_MODELS]
 
 
 
1046
 
1047
  if not selected_models:
1048
  return (
@@ -1053,7 +1293,7 @@ def _build_compare_samples_html(samples, selected_models, num_prompts, seed=0):
1053
 
1054
  shared_prompt_ids = None
1055
  for model in selected_models:
1056
- model_prompt_ids = set(samples["images"][model])
1057
  shared_prompt_ids = (
1058
  model_prompt_ids
1059
  if shared_prompt_ids is None
@@ -1073,29 +1313,31 @@ def _build_compare_samples_html(samples, selected_models, num_prompts, seed=0):
1073
  rng.shuffle(prompt_pool)
1074
  chosen = prompt_pool[: max(1, min(int(num_prompts), len(prompt_pool)))]
1075
 
1076
- columns = len(selected_models)
1077
  blocks = []
1078
  for index, prompt_id in enumerate(chosen, start=1):
1079
  prompt_text = escape(samples["prompts"].get(prompt_id, ""))
1080
  cells = []
 
 
 
 
 
 
 
1081
  for model in selected_models:
1082
- image_url = escape(samples["images"][model][prompt_id], quote=True)
1083
  cells.append(
1084
- f"""
1085
- <div class="compare-cell">
1086
- <div class="compare-model-label">{escape(display_model_name(model))}</div>
1087
- <a href="{image_url}" target="_blank" rel="noopener noreferrer">
1088
- <img src="{image_url}" alt="{escape(display_model_name(model))} sample" loading="lazy" />
1089
- </a>
1090
- </div>
1091
- """
1092
  )
 
1093
  blocks.append(
1094
  f"""
1095
  <div class="compare-prompt-block">
1096
  <div class="compare-prompt-meta">
1097
  <span>Prompt {index}</span>
1098
- <span>{escape(prompt_id)}</span>
1099
  </div>
1100
  <p class="compare-prompt-text">{prompt_text}</p>
1101
  <div class="compare-row" style="grid-template-columns: repeat({columns}, minmax(0, 1fr));">
@@ -1123,9 +1365,19 @@ def _filter_row(datasets, metrics, default_dataset_id, default_metric_id=None):
1123
  metric_id = _coerce_metric(
1124
  datasets, metrics, default_dataset_id, default_metric_id
1125
  )
 
1126
  with gr.Row(elem_classes="view-filters"):
 
 
 
 
 
 
 
 
 
1127
  dataset_dd = gr.Dropdown(
1128
- choices=_dataset_choices(datasets),
1129
  value=default_dataset_id,
1130
  label="Dataset",
1131
  type="value",
@@ -1157,7 +1409,7 @@ def _filter_row(datasets, metrics, default_dataset_id, default_metric_id=None):
1157
  min_width=180,
1158
  elem_classes="filter-chips",
1159
  )
1160
- return dataset_dd, metric_dd, models_dd
1161
 
1162
 
1163
  def render_image_workspace(datasets, metrics, default_dataset_id, default_metric_id):
@@ -1172,15 +1424,16 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1172
  with gr.Column(elem_classes="workspace-filters") as filters_host:
1173
  gr.Markdown(
1174
  "<p class='filter-help'>"
1175
- "These filters apply to Leaderboards, Pareto plots, and Samples. "
1176
- "On Samples, only datasets and models we have generations for "
1177
- "are listed. On Pareto plots, only datasets with price or "
1178
- "generation time are listed. Search in Models, or leave it "
1179
- "empty to include every model."
 
1180
  "</p>",
1181
  elem_classes="filter-help-host",
1182
  )
1183
- dataset_dd, metric_dd, models_dd = _filter_row(
1184
  datasets, metrics, default_dataset_id, None
1185
  )
1186
  with gr.Tabs(elem_classes="main-tabs") as main_tabs:
@@ -1306,7 +1559,7 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1306
  elem_classes="pareto-col",
1307
  ) as slot_time_col:
1308
  slot_time_scale = _pareto_plot_heading(
1309
- "Min generation time vs score"
1310
  )
1311
  slot_time = gr.Plot(
1312
  value=None,
@@ -1342,7 +1595,8 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1342
  with gr.Column(visible=bool(initial_samples)) as samples_panel:
1343
  gr.Markdown(
1344
  f"<p class='view-help'>"
1345
- f"The same prompts, side by side. Select up to "
 
1346
  f"<strong>{MAX_COMPARE_MODELS}</strong> models above, or leave "
1347
  f"Models empty for two defaults."
1348
  f"</p>",
@@ -1460,8 +1714,14 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1460
  ):
1461
  prev = dict(view_state or {})
1462
  extras = extras or {}
 
 
 
 
1463
  return {
1464
  "dataset_id": dataset_id,
 
 
1465
  "metric_id": metric_id,
1466
  "models": list(models or []),
1467
  "current_tab": tab,
@@ -1599,13 +1859,14 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1599
  tab = view_state.get("current_tab") or TAB_LEADERBOARDS
1600
  selected_raw = _normalize_metric_ids(metric_id)
1601
  incoming_models = list(models or [])
1602
- dataset_changed = source == "dataset" and dataset_id != view_state.get(
 
1603
  "dataset_id"
1604
  )
1605
  can_pareto = _dataset_has_pareto(datasets, dataset_id)
1606
  can_samples = _dataset_has_samples(datasets, dataset_id)
1607
  selected_tab = tab
1608
- if source == "dataset":
1609
  if tab == TAB_SAMPLES and not can_samples:
1610
  selected_tab = TAB_LEADERBOARDS
1611
  elif tab == TAB_PARETO and not can_pareto:
@@ -1614,7 +1875,7 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1614
  dataset_id,
1615
  metric_id,
1616
  models,
1617
- clear_metric=dataset_changed,
1618
  require_samples=selected_tab == TAB_SAMPLES,
1619
  )
1620
  dataset_id, metric_id, models = synced[:3]
@@ -1659,7 +1920,7 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1659
  list(optimized_value or []),
1660
  )
1661
  extra_updates = None
1662
- if source == "dataset":
1663
  view = resolve_view(datasets, metrics, dataset_id, metric_id)
1664
  extra_updates = _leaderboard_extras(
1665
  view["data"] if view else None,
@@ -1712,6 +1973,67 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
1712
  ),
1713
  }
1714
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1715
  def on_dataset(
1716
  dataset_id,
1717
  metric_id,
@@ -2061,9 +2383,12 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
2061
  handler.__name__ = f"on_tab_{tab}"
2062
  return handler
2063
 
 
2064
  view_state = gr.State(
2065
  {
2066
  "dataset_id": default_dataset_id,
 
 
2067
  "metric_id": None,
2068
  "models": [],
2069
  "current_tab": TAB_LEADERBOARDS,
@@ -2145,6 +2470,12 @@ def render_image_workspace(datasets, metrics, default_dataset_id, default_metric
2145
  main_tabs,
2146
  view_state,
2147
  ]
 
 
 
 
 
 
2148
  dataset_dd.change(
2149
  on_dataset,
2150
  inputs=filter_inputs,
 
25
  MAX_PARETO_METRICS = 8
26
  _PARETO_SLOT_COUNT = 1 + MAX_PARETO_METRICS * 8
27
  _PARETO_PRICE_COLUMN = "Price / Image (USD)"
28
+ _PARETO_VIDEO_PRICE_COLUMN = "Price / Second of Video (USD)"
29
+ _PARETO_PRICE_COLUMNS = (_PARETO_PRICE_COLUMN, _PARETO_VIDEO_PRICE_COLUMN)
30
  _PARETO_TIME_COLUMN = "Min Generation Time (s)"
31
+ _PARETO_VIDEO_TIME_COLUMN = "Pareto Time / Output Video Second (s)"
32
+ _PARETO_TIME_COLUMNS = (_PARETO_VIDEO_TIME_COLUMN, _PARETO_TIME_COLUMN)
33
+ _PARETO_PRICE_TITLES = {
34
+ _PARETO_PRICE_COLUMN: "Price per image (USD)",
35
+ _PARETO_VIDEO_PRICE_COLUMN: "Price per second of video (USD)",
36
+ }
37
+ _PARETO_TIME_TITLES = {
38
+ _PARETO_TIME_COLUMN: "Min generation time (s)",
39
+ _PARETO_VIDEO_TIME_COLUMN: "Generation time per second of video",
40
+ }
41
  _PARETO_SCALE_CHOICES = [
42
  ("Log", "Logarithmic"),
43
  ("Linear", "Linear"),
44
  ]
45
  _PARETO_SCALE_VALUES = {value for _, value in _PARETO_SCALE_CHOICES}
46
  _PARETO_SCALE_DEFAULT = "Logarithmic"
47
+ _PARETO_PRUNA_COLOR = "#c084fc"
48
+ _PARETO_OTHER_COLOR = "#9aa3b5"
49
+ _PARETO_FRONTIER_OUTLINE = "#3fa87e"
50
 
51
  TAB_LEADERBOARDS = "leaderboards"
52
  TAB_PARETO = "pareto"
53
  TAB_SAMPLES = "samples"
54
  TAB_ABOUT = "about"
55
 
56
+ MODALITY_VIDEO_TO_VIDEO = "video_to_video"
57
+ MODALITY_TEXT_TO_IMAGE = "text_to_image"
58
+ MODALITY_CHOICES = [
59
+ ("Video to Video", MODALITY_VIDEO_TO_VIDEO),
60
+ ("Text to Image", MODALITY_TEXT_TO_IMAGE),
61
+ ]
62
+
63
  _MODEL_CHOICES_CACHE = {}
64
  _VIEW_EVENTS = {
65
  "show_progress": "hidden",
 
71
  ABOUT_OVERVIEW_CONTENT = """
72
  # About P-Bench
73
 
74
+ P-Bench compares **text-to-image** and **video-to-video** models, including
75
+ optimized or accelerated endpoints, on **quality, speed, and price**. Each
76
+ view is a **dataset** scored with a **metric**, written as `Dataset | Metric`.
77
+ There is no single score across P-Bench.
78
 
79
  ## How to read it
80
 
81
+ 1. Pick a **type** (Video to Video or Text to Image), then a **dataset**
82
+ and a **metric**.
83
  2. **Leaderboards**: ranked by that metric. Price and generation time sit in
84
  the same table when the source publishes them.
85
  3. **Pareto plots**: mark models that are not beaten on both higher score
86
  and lower price (or time). Only datasets with price or generation time
87
  can open this tab (not Arena AI).
88
  4. **Samples**: the same prompts, side by side. Only for datasets we
89
+ generated (Qwen Image Dataset, OneIG Alignment Dataset, and the
90
+ Pruna Internal Video-Edit Benchmark). Video samples show the source
91
+ clip first, then each model's edit.
92
 
93
  ## How a score is made
94
 
 
107
 
108
  ## Current datasets
109
 
110
+ ### Pruna Internal Video-Edit Benchmark
111
+ Pruna's internal video-to-video editing benchmark, collected by our
112
+ research engineers. It combines prompts from public video-editing
113
+ benchmarks with use-case examples we gathered for advertisement,
114
+ e-commerce, real estate, concept art, and similar work. The suite also
115
+ covers camera-angle and movement changes, lighting, and text in video
116
+ (altering, adding, or removing it). Quality is Datapoint Elo from
117
+ pairwise preference. Price is USD per second of output video;
118
+ generation time is wall time per second of output video. Samples show
119
+ the source clip beside each model's edit.
120
+
121
  ### Qwen Image Dataset
122
  100 prompts from the 1,000-prompt Qwen Image Bench set, sampled for coverage
123
  across its fine-grained (L3) categories. Metrics include Datapoint Elo,
 
160
  - **Arena Elo**: Elo published by Arena AI on their own dataset, plus
161
  category Elos (branding, 3D, cartoon/anime, photorealistic, art, portraits,
162
  text rendering).
163
+ - **Generation time**: median and minimum generation time in seconds for
164
+ images, as reported in the evaluation table. For video, generation time
165
+ per second of output video is the more informative figure (end-to-end
166
+ wall time). This is not a p95, and we do not state warm vs cold or
167
+ concurrent load. Not available for Arena AI.
168
+ - **Price**: USD per image for text-to-image, or USD per second of output
169
+ video for video-to-video. We do not state list price vs amount paid, or
170
+ whether failed generations are included. Not available for Arena AI.
171
 
172
  Scores from different datasets or metrics are **not interchangeable**. A high
173
  OneIG alignment score is not the same quantity as a Datapoint Elo. Compare
 
181
  - **Prompt counts:** OneIG Alignment uses 100 anime, 100 human, and 99 object
182
  prompts (299 total). Qwen Image Dataset uses 100 prompts sampled from the
183
  1,000-prompt pool for roughly even coverage of its fine-grained (L3)
184
+ categories. The Pruna Internal Video-Edit Benchmark uses 78 prompts
185
+ across advertising, e-commerce, real estate, camera, lighting, text, and
186
+ related categories. Artificial Analysis and Arena AI use their own
187
+ private prompt sets.
188
  - **Generation (Qwen and OneIG):** one image per prompt per endpoint when
189
  the run exists. Default resolution is 1024×1024. Exceptions: FLUX 1.1 Pro
190
  Ultra at 2K, FLUX 2 Flex at 1008×1008, and any endpoint labeled 2K. The
191
  seed is derived from the prompt, so every model gets the same seed for the
192
  same prompt. Steps, CFG, prompt rewrite, and safety filters follow each
193
  endpoint's default. This does not describe Artificial Analysis or Arena AI.
194
+ - **Generation (Video-Edit):** one edited clip per prompt per endpoint when
195
+ the run exists. Every model sees the same source video for a prompt.
196
  - **Datapoint (Qwen and OneIG):** every model pair is compared on every
197
  prompt, with 10 votes per battle.
198
  - **Rapidata (Qwen and OneIG):** prompts longer than 400 characters are
 
264
  </svg>
265
  </button>
266
  </div>
267
+ <p class="app-header-tagline">Compare models on quality, speed, and price</p>
268
  </header>
269
  """,
270
  padding=False,
 
279
  return items[0] if items else None
280
 
281
 
282
+ def _dataset_modality(dataset):
283
+ return (dataset or {}).get("modality") or MODALITY_TEXT_TO_IMAGE
284
+
285
+
286
+ def _datasets_for_modality(datasets, modality):
287
+ if not modality:
288
+ return list(datasets)
289
+ scoped = [
290
+ dataset
291
+ for dataset in datasets
292
+ if _dataset_modality(dataset) == modality
293
+ ]
294
+ return scoped or list(datasets)
295
+
296
+
297
+ def _modality_choices(datasets):
298
+ present = {_dataset_modality(dataset) for dataset in datasets}
299
+ return [
300
+ (label, value) for label, value in MODALITY_CHOICES if value in present
301
+ ]
302
+
303
+
304
+ def _default_dataset_id(datasets, modality, preferred=None):
305
+ scoped = _datasets_for_modality(datasets, modality)
306
+ if preferred and any(dataset["id"] == preferred for dataset in scoped):
307
+ return preferred
308
+ return scoped[0]["id"] if scoped else None
309
+
310
+
311
+ def _dataset_choices(
312
+ datasets, *, modality=None, require_samples=False, require_pareto=False
313
+ ):
314
+ scoped = _datasets_for_modality(datasets, modality)
315
  return [
316
  (dataset["name"], dataset["id"])
317
+ for dataset in scoped
318
  if (not require_samples or dataset.get("samples"))
319
  and (not require_pareto or _dataset_has_pareto(datasets, dataset["id"]))
320
  ]
 
330
  samples = dataset.get("samples") if dataset else None
331
  if not samples:
332
  return set()
333
+ models = set(samples.get("models") or [])
334
+ return models | {display_model_name(model) for model in models}
335
+
336
+
337
+ def _sample_media_map(samples):
338
+ return (samples or {}).get("images") or {}
339
+
340
+
341
+ def _resolve_sample_model(samples, model):
342
+ media = _sample_media_map(samples)
343
+ if model in media:
344
+ return model
345
+ wanted = {str(model or "").strip(), display_model_name(model)}
346
+ wanted.discard("")
347
+ for key in media:
348
+ if key in wanted or display_model_name(key) in wanted:
349
+ return key
350
+ return None
351
+
352
+
353
+ def _default_sample_models(samples):
354
+ models = list((samples or {}).get("models") or [])
355
+ preferred = [model for model in models if _is_pruna_model(model)]
356
+ preferred.sort(
357
+ key=lambda model: (
358
+ "draft" in str(model).casefold()
359
+ or "draft" in display_model_name(model).casefold(),
360
+ display_model_name(model).casefold(),
361
+ )
362
+ )
363
+ return (preferred or models)[:2]
364
+
365
+
366
+ def _pareto_price_column(data):
367
+ columns = getattr(data, "columns", []) if data is not None else []
368
+ for column in _PARETO_PRICE_COLUMNS:
369
+ if column in columns:
370
+ return column
371
+ return None
372
+
373
+
374
+ def _pareto_time_column(data):
375
+ columns = getattr(data, "columns", []) if data is not None else []
376
+ for column in _PARETO_TIME_COLUMNS:
377
+ if column in columns:
378
+ return column
379
+ return None
380
 
381
 
382
  def _dataset_has_pareto(datasets, dataset_id):
383
  dataset = _item(datasets, dataset_id)
384
+ data = dataset.get("data") if dataset else None
385
+ return (
386
+ _pareto_price_column(data) is not None
387
+ or _pareto_time_column(data) is not None
388
+ )
389
 
390
 
391
+ def _dataset_dropdown_update(datasets, tab, dataset_id, modality=None):
392
  """Limit the dataset list to what the current tab can show."""
393
+ if modality is None:
394
+ modality = _dataset_modality(_item(datasets, dataset_id))
395
  return gr.update(
396
  choices=_dataset_choices(
397
  datasets,
398
+ modality=modality,
399
  require_samples=tab == TAB_SAMPLES
400
  and _dataset_has_samples(datasets, dataset_id),
401
  require_pareto=tab == TAB_PARETO
 
501
  "Optimized",
502
  ]
503
  _LEADERBOARD_META_COLUMNS = [
504
+ "Time / Output Video Second (s)",
505
  "Median Generation Time (s)",
506
  "Min Generation Time (s)",
507
  "Price / Image (USD)",
508
+ "Price / Second of Video (USD)",
509
  "Evaluation Date (UTC)",
510
  "Date",
511
  ]
 
732
  "Arena Text Rendering Elo": "Text Rendering",
733
  "Median Generation Time (s)": "Median generation time",
734
  "Min Generation Time (s)": "Min generation time",
735
+ "Time / Output Video Second (s)": "Generation time per second of video",
736
  "Price / Image (USD)": "Price per image",
737
+ "Price / Second of Video (USD)": "Price per second of video",
738
  "Evaluation Date (UTC)": "Date",
739
  "Date": "Date",
740
  }
 
809
  )
810
 
811
 
812
+ def _is_pruna_model(model_id) -> bool:
813
+ raw = str(model_id or "").casefold()
814
+ label = display_model_name(model_id).casefold()
815
+ return any(
816
+ value.startswith(prefix)
817
+ for value in (raw, label)
818
+ for prefix in ("p-image", "p_image", "p-video", "p_video")
819
+ )
820
+
821
+
822
+ def _pareto_fill_colors(models):
823
+ return [
824
+ _PARETO_PRUNA_COLOR if _is_pruna_model(model) else _PARETO_OTHER_COLOR
825
+ for model in models
826
+ ]
827
+
828
+
829
  def _build_pareto_figure(
830
  data,
831
  score_column,
 
850
 
851
  dominated = scatter.loc[[not flag for flag in on_frontier]].copy()
852
  frontier = scatter.loc[on_frontier].sort_values(x_column).copy()
853
+ dominated_colors = _pareto_fill_colors(dominated["Model"]) if not dominated.empty else []
854
+ frontier_colors = _pareto_fill_colors(frontier["Model"]) if not frontier.empty else []
855
  if not dominated.empty:
856
  dominated["Model"] = dominated["Model"].map(display_model_name)
857
  if not frontier.empty:
 
872
  name="Below frontier",
873
  text=dominated["Model"],
874
  hovertemplate=hover,
875
+ showlegend=False,
876
  marker={
877
  "size": 9,
878
+ "color": dominated_colors,
879
+ "opacity": 0.85,
880
  "line": {"width": 0},
881
  },
882
  )
 
890
  name="On frontier",
891
  text=frontier["Model"],
892
  hovertemplate=hover,
893
+ showlegend=False,
894
+ line={"color": _PARETO_FRONTIER_OUTLINE, "width": 2.5},
895
  marker={
896
  "size": 12,
897
+ "color": frontier_colors,
898
+ "line": {"width": 2.5, "color": _PARETO_FRONTIER_OUTLINE},
899
  },
900
  )
901
  )
902
+ for name, marker in (
903
+ (
904
+ "Pruna",
905
+ {
906
+ "size": 10,
907
+ "color": _PARETO_PRUNA_COLOR,
908
+ "line": {"width": 0},
909
+ },
910
+ ),
911
+ (
912
+ "Other models",
913
+ {
914
+ "size": 10,
915
+ "color": _PARETO_OTHER_COLOR,
916
+ "line": {"width": 0},
917
+ },
918
+ ),
919
+ (
920
+ "On frontier",
921
+ {
922
+ "size": 12,
923
+ "color": "rgba(0,0,0,0)",
924
+ "line": {"width": 2.5, "color": _PARETO_FRONTIER_OUTLINE},
925
+ },
926
+ ),
927
+ ):
928
+ fig.add_trace(
929
+ go.Scatter(
930
+ x=[None],
931
+ y=[None],
932
+ mode="markers",
933
+ name=name,
934
+ marker=marker,
935
+ hoverinfo="skip",
936
+ )
937
+ )
938
 
939
  score_label = _display_label(score_column)
940
  fig.update_layout(
 
1074
  if data is None or not score_column or score_column not in data.columns:
1075
  return None, score_missing, None, score_missing
1076
 
1077
+ price_column = _pareto_price_column(data) or _PARETO_PRICE_COLUMN
1078
+ price_title = _PARETO_PRICE_TITLES.get(price_column, "Price (USD)")
1079
+ price_missing = (
1080
+ "Price per second of video isn't available for this dataset."
1081
+ if price_column == _PARETO_VIDEO_PRICE_COLUMN
1082
+ else "Price per image isn't available for this dataset."
1083
+ )
1084
  price_fig, price_message = _pareto_axis(
1085
  data,
1086
  score_column,
1087
+ price_column,
1088
+ price_title,
1089
+ price_missing,
1090
  "No models have both a score and a price for this metric.",
1091
  x_hover_prefix="$",
1092
  x_axis_type=_pareto_axis_type(price_scale),
1093
  )
1094
+ time_column = _pareto_time_column(data) or _PARETO_TIME_COLUMN
1095
+ time_title = _PARETO_TIME_TITLES.get(time_column, "Generation time (s)")
1096
+ time_missing = (
1097
+ "Generation time per second of video isn't available for this dataset."
1098
+ if time_column == _PARETO_VIDEO_TIME_COLUMN
1099
+ else "Min generation time isn't available for this dataset."
1100
+ )
1101
+ time_empty = (
1102
+ "No models have both a score and generation time per second of "
1103
+ "video for this metric."
1104
+ if time_column == _PARETO_VIDEO_TIME_COLUMN
1105
+ else "No models have both a score and a min generation time for this metric."
1106
+ )
1107
  time_fig, time_message = _pareto_axis(
1108
  data,
1109
  score_column,
1110
+ time_column,
1111
+ time_title,
1112
+ time_missing,
1113
+ time_empty,
1114
  x_hover_suffix="s",
1115
  x_axis_type=_pareto_axis_type(latency_scale),
1116
  )
 
1118
 
1119
 
1120
  def _pareto_dataset_message(data):
1121
+ has_price = _pareto_price_column(data) is not None
1122
+ has_time = _pareto_time_column(data) is not None
1123
  if has_price or has_time:
1124
  return None
1125
  return (
1126
+ "Price and generation time aren't available for "
1127
  "this dataset, so these plots can't be drawn."
1128
  )
1129
 
1130
 
1131
  def _pareto_slot_note(price_fig, price_message, time_fig, time_message, data):
1132
+ has_price = _pareto_price_column(data) is not None
1133
+ has_time = _pareto_time_column(data) is not None
1134
  notes = []
1135
  if has_price and not has_time:
1136
  notes.append(
1137
+ "Generation time isn't available for this dataset, so only "
1138
  "price vs score is shown."
1139
  )
1140
  elif has_time and not has_price:
1141
  notes.append(
1142
+ "Price isn't available for this dataset, so only "
1143
+ "time vs score is shown."
1144
  )
1145
  if price_fig is None and has_price:
1146
  notes.append(price_message)
 
1161
  score_columns = [column for column in (score_columns or []) if column]
1162
  price_scales = _normalize_pareto_scales(price_scales)
1163
  time_scales = _normalize_pareto_scales(time_scales)
1164
+ has_price = _pareto_price_column(data) is not None
1165
+ has_time = _pareto_time_column(data) is not None
1166
  dataset_note = _pareto_dataset_message(data)
1167
  updates = [_pareto_note_update(dataset_note)]
1168
  hide_all_slots = not has_price and not has_time
 
1241
  return _pareto_unavailable_html(
1242
  "Samples aren't available for this dataset."
1243
  )
1244
+ models = [
1245
+ resolved
1246
+ for model in (selected_models or [])
1247
+ if (resolved := _resolve_sample_model(samples, model))
1248
+ ]
1249
  if not models:
1250
+ models = _default_sample_models(samples)
1251
  return _build_compare_samples_html(samples, models, num_prompts, seed)
1252
 
1253
 
1254
+ def _compare_media_html(url, label, *, kind):
1255
+ safe_url = escape(url, quote=True)
1256
+ safe_label = escape(label)
1257
+ if kind == "video":
1258
+ return (
1259
+ f'<video src="{safe_url}" controls preload="metadata" '
1260
+ f'playsinline></video>'
1261
+ )
1262
+ return (
1263
+ f'<a href="{safe_url}" target="_blank" rel="noopener noreferrer">'
1264
+ f'<img src="{safe_url}" alt="{safe_label} sample" loading="lazy" />'
1265
+ f"</a>"
1266
+ )
1267
+
1268
+
1269
+ def _compare_cell_html(label, url, *, kind, extra_class=""):
1270
+ classes = "compare-cell"
1271
+ if extra_class:
1272
+ classes = f"{classes} {extra_class}"
1273
+ return f"""
1274
+ <div class="{classes}">
1275
+ <div class="compare-model-label">{escape(label)}</div>
1276
+ {_compare_media_html(url, label, kind=kind)}
1277
+ </div>
1278
+ """
1279
+
1280
+
1281
  def _build_compare_samples_html(samples, selected_models, num_prompts, seed=0):
1282
  selected_models = list(selected_models or [])[:MAX_COMPARE_MODELS]
1283
+ media = _sample_media_map(samples)
1284
+ kind = (samples or {}).get("kind") or "image"
1285
+ source_videos = (samples or {}).get("source_videos") or {}
1286
 
1287
  if not selected_models:
1288
  return (
 
1293
 
1294
  shared_prompt_ids = None
1295
  for model in selected_models:
1296
+ model_prompt_ids = set(media.get(model) or [])
1297
  shared_prompt_ids = (
1298
  model_prompt_ids
1299
  if shared_prompt_ids is None
 
1313
  rng.shuffle(prompt_pool)
1314
  chosen = prompt_pool[: max(1, min(int(num_prompts), len(prompt_pool)))]
1315
 
 
1316
  blocks = []
1317
  for index, prompt_id in enumerate(chosen, start=1):
1318
  prompt_text = escape(samples["prompts"].get(prompt_id, ""))
1319
  cells = []
1320
+ source_url = source_videos.get(prompt_id)
1321
+ if source_url:
1322
+ cells.append(
1323
+ _compare_cell_html(
1324
+ "Source", source_url, kind="video", extra_class="compare-source"
1325
+ )
1326
+ )
1327
  for model in selected_models:
 
1328
  cells.append(
1329
+ _compare_cell_html(
1330
+ display_model_name(model),
1331
+ media[model][prompt_id],
1332
+ kind=kind,
1333
+ )
 
 
 
1334
  )
1335
+ columns = len(cells)
1336
  blocks.append(
1337
  f"""
1338
  <div class="compare-prompt-block">
1339
  <div class="compare-prompt-meta">
1340
  <span>Prompt {index}</span>
 
1341
  </div>
1342
  <p class="compare-prompt-text">{prompt_text}</p>
1343
  <div class="compare-row" style="grid-template-columns: repeat({columns}, minmax(0, 1fr));">
 
1365
  metric_id = _coerce_metric(
1366
  datasets, metrics, default_dataset_id, default_metric_id
1367
  )
1368
+ default_modality = _dataset_modality(_item(datasets, default_dataset_id))
1369
  with gr.Row(elem_classes="view-filters"):
1370
+ modality_dd = gr.Dropdown(
1371
+ choices=_modality_choices(datasets),
1372
+ value=default_modality,
1373
+ label="Type",
1374
+ type="value",
1375
+ filterable=False,
1376
+ scale=1,
1377
+ min_width=150,
1378
+ )
1379
  dataset_dd = gr.Dropdown(
1380
+ choices=_dataset_choices(datasets, modality=default_modality),
1381
  value=default_dataset_id,
1382
  label="Dataset",
1383
  type="value",
 
1409
  min_width=180,
1410
  elem_classes="filter-chips",
1411
  )
1412
+ return modality_dd, dataset_dd, metric_dd, models_dd
1413
 
1414
 
1415
  def render_image_workspace(datasets, metrics, default_dataset_id, default_metric_id):
 
1424
  with gr.Column(elem_classes="workspace-filters") as filters_host:
1425
  gr.Markdown(
1426
  "<p class='filter-help'>"
1427
+ "Start with Type to switch between Video to Video and Text "
1428
+ "to Image. The rest of the filters follow you across "
1429
+ "Leaderboards, Pareto plots, and Samples. Samples only "
1430
+ "lists datasets and models we have generations for; Pareto "
1431
+ "plots only lists datasets with price or generation time. "
1432
+ "Search in Models, or leave it empty to include every model."
1433
  "</p>",
1434
  elem_classes="filter-help-host",
1435
  )
1436
+ modality_dd, dataset_dd, metric_dd, models_dd = _filter_row(
1437
  datasets, metrics, default_dataset_id, None
1438
  )
1439
  with gr.Tabs(elem_classes="main-tabs") as main_tabs:
 
1559
  elem_classes="pareto-col",
1560
  ) as slot_time_col:
1561
  slot_time_scale = _pareto_plot_heading(
1562
+ "Time vs score"
1563
  )
1564
  slot_time = gr.Plot(
1565
  value=None,
 
1595
  with gr.Column(visible=bool(initial_samples)) as samples_panel:
1596
  gr.Markdown(
1597
  f"<p class='view-help'>"
1598
+ f"The same prompts, side by side. Video edits show the "
1599
+ f"source clip first. Select up to "
1600
  f"<strong>{MAX_COMPARE_MODELS}</strong> models above, or leave "
1601
  f"Models empty for two defaults."
1602
  f"</p>",
 
1714
  ):
1715
  prev = dict(view_state or {})
1716
  extras = extras or {}
1717
+ modality = _dataset_modality(_item(datasets, dataset_id))
1718
+ last_by_modality = dict(prev.get("dataset_by_modality") or {})
1719
+ if dataset_id:
1720
+ last_by_modality[modality] = dataset_id
1721
  return {
1722
  "dataset_id": dataset_id,
1723
+ "modality": modality,
1724
+ "dataset_by_modality": last_by_modality,
1725
  "metric_id": metric_id,
1726
  "models": list(models or []),
1727
  "current_tab": tab,
 
1859
  tab = view_state.get("current_tab") or TAB_LEADERBOARDS
1860
  selected_raw = _normalize_metric_ids(metric_id)
1861
  incoming_models = list(models or [])
1862
+ filter_changed = source in {"dataset", "modality"}
1863
+ dataset_changed = filter_changed and dataset_id != view_state.get(
1864
  "dataset_id"
1865
  )
1866
  can_pareto = _dataset_has_pareto(datasets, dataset_id)
1867
  can_samples = _dataset_has_samples(datasets, dataset_id)
1868
  selected_tab = tab
1869
+ if filter_changed:
1870
  if tab == TAB_SAMPLES and not can_samples:
1871
  selected_tab = TAB_LEADERBOARDS
1872
  elif tab == TAB_PARETO and not can_pareto:
 
1875
  dataset_id,
1876
  metric_id,
1877
  models,
1878
+ clear_metric=source == "modality" or dataset_changed,
1879
  require_samples=selected_tab == TAB_SAMPLES,
1880
  )
1881
  dataset_id, metric_id, models = synced[:3]
 
1920
  list(optimized_value or []),
1921
  )
1922
  extra_updates = None
1923
+ if filter_changed:
1924
  view = resolve_view(datasets, metrics, dataset_id, metric_id)
1925
  extra_updates = _leaderboard_extras(
1926
  view["data"] if view else None,
 
1973
  ),
1974
  }
1975
 
1976
+ def on_modality(
1977
+ modality,
1978
+ dataset_id,
1979
+ metric_id,
1980
+ models,
1981
+ platform_value,
1982
+ owner_value,
1983
+ optimized_value,
1984
+ num_prompts,
1985
+ seed,
1986
+ view_state,
1987
+ ):
1988
+ view_state = dict(view_state or {})
1989
+ last_by_modality = dict(view_state.get("dataset_by_modality") or {})
1990
+ current_modality = view_state.get("modality") or _dataset_modality(
1991
+ _item(datasets, dataset_id)
1992
+ )
1993
+ if dataset_id:
1994
+ last_by_modality[current_modality] = dataset_id
1995
+ dataset_id = _default_dataset_id(
1996
+ datasets, modality, last_by_modality.get(modality)
1997
+ )
1998
+ view_state["modality"] = modality
1999
+ view_state["dataset_by_modality"] = last_by_modality
2000
+ result = _apply_filter_change(
2001
+ "modality",
2002
+ dataset_id,
2003
+ metric_id,
2004
+ models,
2005
+ platform_value,
2006
+ owner_value,
2007
+ optimized_value,
2008
+ num_prompts,
2009
+ seed,
2010
+ view_state,
2011
+ )
2012
+ if result is None:
2013
+ return _skip_all(len(dataset_outputs))
2014
+ extras = result["extra_updates"]
2015
+ return (
2016
+ _dataset_dropdown_update(
2017
+ datasets,
2018
+ result["selected_tab"],
2019
+ result["dataset_id"],
2020
+ modality=modality,
2021
+ ),
2022
+ result["metric_update"],
2023
+ result["models_update"],
2024
+ extras[6],
2025
+ extras[0],
2026
+ extras[1],
2027
+ extras[2],
2028
+ *result["views"],
2029
+ gr.update(interactive=result["can_pareto"]),
2030
+ gr.update(interactive=result["can_samples"]),
2031
+ gr.update(selected=result["selected_tab"])
2032
+ if result["selected_tab"] != result["tab"]
2033
+ else gr.skip(),
2034
+ result["state"],
2035
+ )
2036
+
2037
  def on_dataset(
2038
  dataset_id,
2039
  metric_id,
 
2383
  handler.__name__ = f"on_tab_{tab}"
2384
  return handler
2385
 
2386
+ default_modality = _dataset_modality(_item(datasets, default_dataset_id))
2387
  view_state = gr.State(
2388
  {
2389
  "dataset_id": default_dataset_id,
2390
+ "modality": default_modality,
2391
+ "dataset_by_modality": {default_modality: default_dataset_id},
2392
  "metric_id": None,
2393
  "models": [],
2394
  "current_tab": TAB_LEADERBOARDS,
 
2470
  main_tabs,
2471
  view_state,
2472
  ]
2473
+ modality_dd.change(
2474
+ on_modality,
2475
+ inputs=[modality_dd, *filter_inputs],
2476
+ outputs=dataset_outputs,
2477
+ **_VIEW_EVENTS,
2478
+ )
2479
  dataset_dd.change(
2480
  on_dataset,
2481
  inputs=filter_inputs,