BenjaminB HF Staff commited on
Commit
6042f92
·
verified ·
1 Parent(s): 91b51f2

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. README.md +1 -1
  2. app.py +132 -63
  3. requirements.txt +1 -1
README.md CHANGED
@@ -9,7 +9,7 @@ emoji: 🛍️
9
 
10
  # PEFT Shop
11
 
12
- A Gradio app to browse PEFT methods like an online store: filter by capabilities (merging, multi-adapter support, quantization backends, targetable layer types, …) and check benchmark results — star ratings for the benchmark-specific metrics (test accuracy and forgetting for MetaMathQA, DINO similarity and drift for image generation) as well as peak memory, checkpoint size, and train time, switchable between the benchmarks of the method comparison suite. Methods can be added to a cart 🛒, which shows usage code snippets and a feature comparison table for the collected methods — and checkout is, of course, free.
13
 
14
  ## Running
15
 
 
9
 
10
  # PEFT Shop
11
 
12
+ A Gradio app to browse PEFT methods like an online store: filter by capabilities (merging, multi-adapter support, quantization backends, targetable layer types, …) or by minimum customer rating ("★★★☆☆ & up"), and check benchmark results — star ratings for the benchmark-specific metrics (test accuracy and forgetting for MetaMathQA, DINO similarity and drift for image generation) as well as peak memory, checkpoint size, and train time, switchable between the benchmarks of the method comparison suite. Methods can be added to a cart 🛒, which shows usage code snippets and a feature comparison table for the collected methods — and checkout is, of course, free.
13
 
14
  ## Running
15
 
app.py CHANGED
@@ -14,8 +14,8 @@
14
  """The PEFT shop: a Gradio app to browse PEFT methods like an online store.
15
 
16
  Users can filter methods by their capabilities (merging, multi-adapter support, quantization backends, targetable
17
- layer types, ...), check benchmark results (switchable between the benchmarks of the method comparison suite, e.g.
18
- MetaMathQA or image generation), and jump to the PEFT docs. The app has two tabs: one to browse the shop, one for the
19
  cart, which shows usage code snippets and a feature comparison table for the collected methods. In keeping with the
20
  shop theme, every method has a (crossed-out) price tag, benchmark results double as customer star ratings, and
21
  checkout is free.
@@ -509,8 +509,8 @@ def _stars_span(n_stars: int, title: str) -> str:
509
  return f'<span class="stars" title="{esc(title)}">{"★" * n_stars}{"☆" * (5 - n_stars)}</span>'
510
 
511
 
512
- def benchmark_stars(spec: BenchmarkSpec, field: str, value: float, title: str, lower_is_better: bool = False) -> str:
513
- """A benchmark metric as a customer star rating, based on quantiles among the benchmarked PEFT methods.
514
 
515
  The best 20% of the methods get five stars, the next 20% four, and so on; even the worst method keeps one star.
516
  Quantiles are used instead of e.g. min-max scaling so that a single outlier cannot compress everyone else's
@@ -519,8 +519,35 @@ def benchmark_stars(spec: BenchmarkSpec, field: str, value: float, title: str, l
519
  """
520
  values = [info["benchmarks"][spec.key][field] for info in METHODS.values() if spec.key in info["benchmarks"]]
521
  rank = sum(other < value if lower_is_better else other > value for other in values)
522
- n_stars = 5 - int(5 * rank / len(values))
523
- return _stars_span(n_stars, title)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
 
525
 
526
  def badge(value: bool | None, label: str, title: str | None = None) -> str:
@@ -559,55 +586,30 @@ def render_card(method: str, spec: BenchmarkSpec) -> str:
559
  else ""
560
  )
561
 
562
- # per row: field in the benchmark entry, display label, formatted value, description for the hover text,
563
- # whether lower values are better, and an optional reference note
564
- bench_rows = [
565
- (
566
- metric.field,
567
- metric.label,
568
- fmt_metric(metric, bench[metric.field]),
569
- f"{metric.label} on the PEFT {spec.label} benchmark",
570
- not metric.higher_is_better,
571
- reference if metric is score else "",
572
- )
573
- for metric in spec.metrics
574
- ]
575
- bench_rows += [
576
- (
577
- "peak_memory_bytes",
578
- "max memory allocated",
579
- fmt_bytes(bench["peak_memory_bytes"]),
580
- f"Peak accelerator memory while training on the PEFT {spec.label} benchmark",
581
- True,
582
- "",
583
- ),
584
- (
585
- "adapter_file_size_bytes",
586
- "checkpoint size",
587
- fmt_megabytes(bench["adapter_file_size_bytes"]),
588
- f"Size of the saved checkpoint on the PEFT {spec.label} benchmark",
589
- True,
590
- "",
591
- ),
592
- (
593
- "train_time_sec",
594
- "train time",
595
- fmt_minutes(bench["train_time_sec"]),
596
- f"Training time on the PEFT {spec.label} benchmark",
597
- True,
598
- "",
599
- ),
600
- ]
601
  # the hover text sits on the whole row (and, redundantly, on the stars span inside it), so hovering the
602
  # metric name or the value explains the metric as well
603
  row_html = []
604
- for field, label, value, description, lower_is_better, ref in bench_rows:
605
  direction = "lower is better" if lower_is_better else "higher is better"
606
- title = f"{description}; {direction}{ref}. Stars rank the method among the other PEFT methods."
 
607
  stars = benchmark_stars(spec, field, bench[field], title, lower_is_better=lower_is_better)
608
  row_html.append(
609
  f'<div class="bench-row" title="{esc(title)}"><span>{stars} {esc(label)}:</span>'
610
- f"<strong>{esc(value)}</strong></div>"
611
  )
612
  bench_html = f"""
613
  <div class="bench" title="Best of {bench["num_runs"]} run(s): {esc(bench["experiment_name"])}">
@@ -660,6 +662,7 @@ def matches_filters(
660
  quant: list[str],
661
  benchmarked_only: bool,
662
  bench_key: str,
 
663
  ) -> bool:
664
  """Filter semantics ("e-commerce" style):
665
 
@@ -669,25 +672,39 @@ def matches_filters(
669
  - across filter groups: AND
670
  - values reported as "unknown" by the capability script never match a positive filter: users filtering for a
671
  feature should only see methods where support is established.
 
 
672
  """
673
  info = METHODS[method]
674
  if search:
675
  haystack = f"{method} {info['config_class']} {info['description']}".lower()
676
  if search.lower() not in haystack:
677
  return False
 
678
  if categories and feature(method, "category")["value"] not in categories:
679
  return False
 
680
  if any(capability_value(method, key) is not True for key in capabilities):
681
  return False
 
682
  if layers:
683
  supported = layer_types(method)
684
  if supported is None or any(not supported.get(layer) for layer in layers):
685
  return False
 
686
  if quant and not any(supports_quant(method, backend) for backend in quant):
687
  return False
688
- if benchmarked_only and bench_key not in info["benchmarks"]:
689
- return False
690
- return True
 
 
 
 
 
 
 
 
691
 
692
 
693
  def sort_key(sort_by: str, bench_key: str):
@@ -714,19 +731,24 @@ ADD_TO_CART_LABEL = "🛒 Add to cart"
714
  IN_CART_LABEL = "✅ In cart"
715
 
716
 
717
- def update_cards(search, categories, capabilities, layers, quant, benchmarked_only, bench_key, sort_by, cart):
 
 
718
  """Assign the filtered, sorted methods to the fixed pool of card slots.
719
 
720
- Returns the count markdown followed by (visibility, card HTML, method name, add button) for every slot; the
721
- button label shows whether the slot's method is already in the cart. Slots beyond the number of matching methods
722
- are hidden and get an empty method name, which add_to_cart treats as a no-op.
 
723
  """
724
  spec = BENCHMARKS_BY_KEY[bench_key]
725
  cart = cart or []
726
  selected = [
727
  method
728
  for method in METHODS
729
- if matches_filters(method, search, categories, capabilities, layers, quant, benchmarked_only, bench_key)
 
 
730
  ]
731
  selected.sort(key=sort_key(sort_by, bench_key))
732
  count = f"**{len(selected)} of {len(METHODS)} items** — all free, all in stock"
@@ -751,7 +773,17 @@ def reset_filters():
751
 
752
  Programmatically resetting the components triggers their change listeners, which re-render the cards.
753
  """
754
- return "", [], [], [], [], False, BENCHMARKS[0].key, "name"
 
 
 
 
 
 
 
 
 
 
755
 
756
 
757
  # ---------------------------------------------------------------------------------------------------------------
@@ -1019,6 +1051,18 @@ CSS = """
1019
  hidden, independently of whether Gradio applies the visible=False update to the column. */
1020
  .method-card:not(:has(.card)) { display: none !important; }
1021
 
 
 
 
 
 
 
 
 
 
 
 
 
1022
  /* Enlarge the tab labels (Browse methods / Cart) so they are hard to miss. Tab buttons carry the ARIA role "tab";
1023
  the .tab-nav fallback covers Gradio versions that don't set it. */
1024
  button[role="tab"], .tab-nav button { font-size: 1.3rem !important; font-weight: 600 !important;
@@ -1065,6 +1109,25 @@ def build_demo() -> gr.Blocks:
1065
  quant = gr.CheckboxGroup(
1066
  choices=sorted(quant_names), label="Quantization (any selected backend)"
1067
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1068
  benchmarked_only = gr.Checkbox(label="Only methods with results on the selected benchmark")
1069
  with gr.Column(scale=3):
1070
  with gr.Row():
@@ -1116,10 +1179,12 @@ def build_demo() -> gr.Blocks:
1116
  "each method shows its best run on the selected benchmark.</small>"
1117
  )
1118
 
1119
- filter_inputs = [search, categories, capabilities, layers, quant, benchmarked_only, benchmark, sort_by]
1120
- # the cart is an extra input to the card rendering (for the "in cart" button labels) but deliberately not
1121
- # part of filter_inputs: resetting the filters must not clear the cart
1122
- card_inputs = filter_inputs + [cart_select]
 
 
1123
  slot_outputs = [count_md]
1124
  for slot_column, slot_html, slot_method, slot_button in slots:
1125
  slot_outputs.extend([slot_column, slot_html, slot_method, slot_button])
@@ -1142,9 +1207,13 @@ def build_demo() -> gr.Blocks:
1142
  trigger_mode="always_last",
1143
  )
1144
  demo.load(update_cards, inputs=card_inputs, outputs=slot_outputs, show_progress="hidden")
 
 
1145
 
1146
  for _, _, slot_method, slot_button in slots:
1147
- slot_button.click(add_to_cart, inputs=[cart_select, slot_method], outputs=cart_select, show_progress="hidden")
 
 
1148
 
1149
  # Updating cart_select programmatically (from the cards' add buttons or "clear cart") also triggers these
1150
  # listeners, so the code snippet, the comparison table, the pay receipt, the cart tab label, and the
@@ -1152,7 +1221,7 @@ def build_demo() -> gr.Blocks:
1152
  cart_outputs = [cart_code, compare_html, receipt_html, cart_tab]
1153
  cart_select.change(update_cart, inputs=cart_select, outputs=cart_outputs, show_progress="hidden")
1154
  cart_select.change(update_cards, inputs=card_inputs, outputs=slot_outputs, show_progress="hidden")
1155
- clear_button.click(lambda: [], outputs=cart_select, show_progress="hidden")
1156
  # the receipt is kept up to date by update_cart, so paying only needs to open the popover, which is a purely
1157
  # client-side affair (fn=None + js); same for writing to the clipboard, which only the browser can do
1158
  pay_button.click(None, js="() => document.getElementById('pay-receipt')?.togglePopover(true)")
 
14
  """The PEFT shop: a Gradio app to browse PEFT methods like an online store.
15
 
16
  Users can filter methods by their capabilities (merging, multi-adapter support, quantization backends, targetable
17
+ layer types, ...) and by minimum star ratings, check benchmark results (switchable between the benchmarks of the
18
+ method comparison suite, e.g. MetaMathQA or image generation), and jump to the PEFT docs. The app has two tabs: one to browse the shop, one for the
19
  cart, which shows usage code snippets and a feature comparison table for the collected methods. In keeping with the
20
  shop theme, every method has a (crossed-out) price tag, benchmark results double as customer star ratings, and
21
  checkout is free.
 
509
  return f'<span class="stars" title="{esc(title)}">{"★" * n_stars}{"☆" * (5 - n_stars)}</span>'
510
 
511
 
512
+ def star_rating(spec: BenchmarkSpec, field: str, value: float, lower_is_better: bool = False) -> int:
513
+ """The customer star rating of one benchmark metric value, based on quantiles among the benchmarked PEFT methods.
514
 
515
  The best 20% of the methods get five stars, the next 20% four, and so on; even the worst method keeps one star.
516
  Quantiles are used instead of e.g. min-max scaling so that a single outlier cannot compress everyone else's
 
519
  """
520
  values = [info["benchmarks"][spec.key][field] for info in METHODS.values() if spec.key in info["benchmarks"]]
521
  rank = sum(other < value if lower_is_better else other > value for other in values)
522
+ return 5 - int(5 * rank / len(values))
523
+
524
+
525
+ def benchmark_stars(spec: BenchmarkSpec, field: str, value: float, title: str, lower_is_better: bool = False) -> str:
526
+ """A benchmark metric as a customer star rating (see star_rating), rendered as a hoverable span."""
527
+ return _stars_span(star_rating(spec, field, value, lower_is_better), title)
528
+
529
+
530
+ def rated_metrics(spec: BenchmarkSpec) -> list[tuple[str, str, bool]]:
531
+ """(field, label, lower_is_better) of every star-rated metric of a benchmark, in card-row order.
532
+
533
+ This is the single source of truth for the rated rows: the cards, the rating filters, and their labels all
534
+ derive from it.
535
+ """
536
+ rows = [(metric.field, metric.label, not metric.higher_is_better) for metric in spec.metrics]
537
+ rows += [
538
+ ("peak_memory_bytes", "max memory allocated", True),
539
+ ("adapter_file_size_bytes", "checkpoint size", True),
540
+ ("train_time_sec", "train time", True),
541
+ ]
542
+ return rows
543
+
544
+
545
+ # The five clickable stars of a minimum-rating filter (a gr.Radio restyled into a star bar via the .star-filter
546
+ # CSS). Every choice renders as one star, the value is the minimum rating ("n stars & up"). The default of 1 filters
547
+ # nothing, as even the worst-rated method keeps one star.
548
+ RATING_CHOICES = [("★", n) for n in range(1, 6)]
549
+ # Number of rating filter slots; for benchmarks with fewer rated metrics, the surplus slots are hidden.
550
+ N_RATING_SLOTS = max(len(rated_metrics(spec)) for spec in BENCHMARKS)
551
 
552
 
553
  def badge(value: bool | None, label: str, title: str | None = None) -> str:
 
586
  else ""
587
  )
588
 
589
+ # per-field value formatting and hover-text description; the rows themselves come from rated_metrics
590
+ formatters = {metric.field: (lambda v, metric=metric: fmt_metric(metric, v)) for metric in spec.metrics}
591
+ formatters |= {
592
+ "peak_memory_bytes": fmt_bytes,
593
+ "adapter_file_size_bytes": fmt_megabytes,
594
+ "train_time_sec": fmt_minutes,
595
+ }
596
+ descriptions = {metric.field: f"{metric.label} on the PEFT {spec.label} benchmark" for metric in spec.metrics}
597
+ descriptions |= {
598
+ "peak_memory_bytes": f"Peak accelerator memory while training on the PEFT {spec.label} benchmark",
599
+ "adapter_file_size_bytes": f"Size of the saved checkpoint on the PEFT {spec.label} benchmark",
600
+ "train_time_sec": f"Training time on the PEFT {spec.label} benchmark",
601
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  # the hover text sits on the whole row (and, redundantly, on the stars span inside it), so hovering the
603
  # metric name or the value explains the metric as well
604
  row_html = []
605
+ for field, label, lower_is_better in rated_metrics(spec):
606
  direction = "lower is better" if lower_is_better else "higher is better"
607
+ ref = reference if field == score.field else ""
608
+ title = f"{descriptions[field]}; {direction}{ref}. Stars rank the method among the other PEFT methods."
609
  stars = benchmark_stars(spec, field, bench[field], title, lower_is_better=lower_is_better)
610
  row_html.append(
611
  f'<div class="bench-row" title="{esc(title)}"><span>{stars} {esc(label)}:</span>'
612
+ f"<strong>{esc(formatters[field](bench[field]))}</strong></div>"
613
  )
614
  bench_html = f"""
615
  <div class="bench" title="Best of {bench["num_runs"]} run(s): {esc(bench["experiment_name"])}">
 
662
  quant: list[str],
663
  benchmarked_only: bool,
664
  bench_key: str,
665
+ min_stars: tuple[int, ...],
666
  ) -> bool:
667
  """Filter semantics ("e-commerce" style):
668
 
 
672
  - across filter groups: AND
673
  - values reported as "unknown" by the capability script never match a positive filter: users filtering for a
674
  feature should only see methods where support is established.
675
+ - min_stars holds the minimum-rating filters, one per rated metric (in rated_metrics order, 1 = no minimum);
676
+ they apply to the selected benchmark, so methods without results on it cannot match.
677
  """
678
  info = METHODS[method]
679
  if search:
680
  haystack = f"{method} {info['config_class']} {info['description']}".lower()
681
  if search.lower() not in haystack:
682
  return False
683
+
684
  if categories and feature(method, "category")["value"] not in categories:
685
  return False
686
+
687
  if any(capability_value(method, key) is not True for key in capabilities):
688
  return False
689
+
690
  if layers:
691
  supported = layer_types(method)
692
  if supported is None or any(not supported.get(layer) for layer in layers):
693
  return False
694
+
695
  if quant and not any(supports_quant(method, backend) for backend in quant):
696
  return False
697
+
698
+ if any(minimum > 1 for minimum in min_stars):
699
+ bench = info["benchmarks"].get(bench_key)
700
+ if bench is None:
701
+ return False
702
+ spec = BENCHMARKS_BY_KEY[bench_key]
703
+ for (field, _, lower_is_better), minimum in zip(rated_metrics(spec), min_stars):
704
+ if star_rating(spec, field, bench[field], lower_is_better) < minimum:
705
+ return False
706
+
707
+ return not (benchmarked_only and bench_key not in info["benchmarks"])
708
 
709
 
710
  def sort_key(sort_by: str, bench_key: str):
 
731
  IN_CART_LABEL = "✅ In cart"
732
 
733
 
734
+ def update_cards(
735
+ search, categories, capabilities, layers, quant, benchmarked_only, bench_key, sort_by, cart, *min_stars
736
+ ):
737
  """Assign the filtered, sorted methods to the fixed pool of card slots.
738
 
739
+ The trailing arguments are the values of the rating filter slots (in rated_metrics order). Returns the count
740
+ markdown followed by (visibility, card HTML, method name, add button) for every slot; the button label shows
741
+ whether the slot's method is already in the cart. Slots beyond the number of matching methods are hidden and get
742
+ an empty method name, which add_to_cart treats as a no-op.
743
  """
744
  spec = BENCHMARKS_BY_KEY[bench_key]
745
  cart = cart or []
746
  selected = [
747
  method
748
  for method in METHODS
749
+ if matches_filters(
750
+ method, search, categories, capabilities, layers, quant, benchmarked_only, bench_key, min_stars
751
+ )
752
  ]
753
  selected.sort(key=sort_key(sort_by, bench_key))
754
  count = f"**{len(selected)} of {len(METHODS)} items** — all free, all in stock"
 
773
 
774
  Programmatically resetting the components triggers their change listeners, which re-render the cards.
775
  """
776
+ return ("", [], [], [], [], False, BENCHMARKS[0].key, "name") + (1,) * N_RATING_SLOTS
777
+
778
+
779
+ def update_rating_filters(bench_key):
780
+ """Relabel the rating filter slots to the selected benchmark's rated metrics; surplus slots are hidden and
781
+ reset, so that a stale minimum cannot keep filtering invisibly."""
782
+ rows = rated_metrics(BENCHMARKS_BY_KEY[bench_key])
783
+ return [
784
+ gr.update(label=rows[i][1], visible=True) if i < len(rows) else gr.update(value=1, visible=False)
785
+ for i in range(N_RATING_SLOTS)
786
+ ]
787
 
788
 
789
  # ---------------------------------------------------------------------------------------------------------------
 
1051
  hidden, independently of whether Gradio applies the visible=False update to the column. */
1052
  .method-card:not(:has(.card)) { display: none !important; }
1053
 
1054
+ /* The minimum-rating filters: a gr.Radio restyled into a clickable star bar. The radio circles are hidden and every
1055
+ choice renders as one star; the stars up to the selected one stay gold, the ones after it are dimmed -- clicking
1056
+ the n-th star therefore reads as "n stars & up". */
1057
+ .star-filter .wrap { display: flex; flex-direction: row; flex-wrap: nowrap; gap: 0.15rem; }
1058
+ .star-filter label { background: none !important; border: none !important; box-shadow: none !important;
1059
+ padding: 0 !important; margin: 0 !important; cursor: pointer; }
1060
+ .star-filter label span { font-size: 1.5rem; line-height: 1; color: #f5b50a; padding: 0;
1061
+ display: inline-block; transition: transform 0.1s ease; }
1062
+ .star-filter label:hover span { transform: scale(1.2); }
1063
+ .star-filter input[type="radio"] { display: none; }
1064
+ .star-filter label:has(input:checked) ~ label span { color: #9ca3af; opacity: 0.45; }
1065
+
1066
  /* Enlarge the tab labels (Browse methods / Cart) so they are hard to miss. Tab buttons carry the ARIA role "tab";
1067
  the .tab-nav fallback covers Gradio versions that don't set it. */
1068
  button[role="tab"], .tab-nav button { font-size: 1.3rem !important; font-weight: 600 !important;
 
1109
  quant = gr.CheckboxGroup(
1110
  choices=sorted(quant_names), label="Quantization (any selected backend)"
1111
  )
1112
+ # one minimum-rating filter per star-rated card row, like a shop's "customer rating" filter;
1113
+ # the labels follow the selected benchmark (see update_rating_filters)
1114
+ with gr.Accordion("⭐ Minimum customer rating", open=False):
1115
+ gr.Markdown(
1116
+ "<small>Click the lowest acceptable rating ('n stars & up'). Ratings refer to the "
1117
+ "selected benchmark; with a minimum above one star, methods without results on it "
1118
+ "are filtered out.</small>"
1119
+ )
1120
+ default_rows = rated_metrics(BENCHMARKS[0])
1121
+ rating_filters = [
1122
+ gr.Radio(
1123
+ choices=RATING_CHOICES,
1124
+ value=1,
1125
+ label=default_rows[i][1] if i < len(default_rows) else "",
1126
+ visible=i < len(default_rows),
1127
+ elem_classes="star-filter",
1128
+ )
1129
+ for i in range(N_RATING_SLOTS)
1130
+ ]
1131
  benchmarked_only = gr.Checkbox(label="Only methods with results on the selected benchmark")
1132
  with gr.Column(scale=3):
1133
  with gr.Row():
 
1179
  "each method shows its best run on the selected benchmark.</small>"
1180
  )
1181
 
1182
+ basic_filters = [search, categories, capabilities, layers, quant, benchmarked_only, benchmark, sort_by]
1183
+ filter_inputs = basic_filters + rating_filters
1184
+ # The cart is an extra input to the card rendering (for the "in cart" button labels) but deliberately not
1185
+ # part of filter_inputs: resetting the filters must not clear the cart. It sits between the basic filters
1186
+ # and the rating filters so that update_cards can take the latter as its variadic tail.
1187
+ card_inputs = basic_filters + [cart_select] + rating_filters
1188
  slot_outputs = [count_md]
1189
  for slot_column, slot_html, slot_method, slot_button in slots:
1190
  slot_outputs.extend([slot_column, slot_html, slot_method, slot_button])
 
1207
  trigger_mode="always_last",
1208
  )
1209
  demo.load(update_cards, inputs=card_inputs, outputs=slot_outputs, show_progress="hidden")
1210
+ # the rating filters are labeled after the selected benchmark's metrics
1211
+ benchmark.change(update_rating_filters, inputs=benchmark, outputs=rating_filters, show_progress="hidden")
1212
 
1213
  for _, _, slot_method, slot_button in slots:
1214
+ slot_button.click(
1215
+ add_to_cart, inputs=[cart_select, slot_method], outputs=cart_select, show_progress="hidden"
1216
+ )
1217
 
1218
  # Updating cart_select programmatically (from the cards' add buttons or "clear cart") also triggers these
1219
  # listeners, so the code snippet, the comparison table, the pay receipt, the cart tab label, and the
 
1221
  cart_outputs = [cart_code, compare_html, receipt_html, cart_tab]
1222
  cart_select.change(update_cart, inputs=cart_select, outputs=cart_outputs, show_progress="hidden")
1223
  cart_select.change(update_cards, inputs=card_inputs, outputs=slot_outputs, show_progress="hidden")
1224
+ clear_button.click(list, outputs=cart_select, show_progress="hidden")
1225
  # the receipt is kept up to date by update_cart, so paying only needs to open the popover, which is a purely
1226
  # client-side affair (fn=None + js); same for writing to the clipboard, which only the browser can do
1227
  pay_button.click(None, js="() => document.getElementById('pay-receipt')?.togglePopover(true)")
requirements.txt CHANGED
@@ -1 +1 @@
1
- gradio>=5.0
 
1
+ gradio>=6.2