| ... | @@ -96,6 +96,21 @@ export function groupModelsForDisplay< | ... | @@ -96,6 +96,21 @@ export function groupModelsForDisplay< |
| 96 | ]; | 96 | ]; |
| 97 | } | 97 | } |
| 98 | | 98 | |
| | 99 | export function nextHiddenModelsForBulkToggle( |
| | 100 | models: ReadonlyArray<Pick<ServerProviderModel, "slug" | "isCustom">>, |
| | 101 | hiddenModels: ReadonlyArray<string>, |
| | 102 | ): string[] { |
| | 103 | const builtInSlugs = models.filter((model) => !model.isCustom).map((model) => model.slug); |
| | 104 | const builtInSlugSet = new Set(builtInSlugs); |
| | 105 | const allBuiltInModelsHidden = builtInSlugs.every((slug) => hiddenModels.includes(slug)); |
| | 106 | |
| | 107 | if (allBuiltInModelsHidden) { |
| | 108 | return hiddenModels.filter((slug) => !builtInSlugSet.has(slug)); |
| | 109 | } |
| | 110 | |
| | 111 | return [...new Set([...hiddenModels, ...builtInSlugs])]; |
| | 112 | } |
| | 113 | |
| 99 | interface ProviderModelsSectionProps { | 114 | interface ProviderModelsSectionProps { |
| 100 | /** Identifier used to namespace input ids within the DOM. */ | 115 | /** Identifier used to namespace input ids within the DOM. */ |
| 101 | readonly instanceId: ProviderInstanceId; | 116 | readonly instanceId: ProviderInstanceId; |
| ... | @@ -181,6 +196,8 @@ export function ProviderModelsSection({ | ... | @@ -181,6 +196,8 @@ export function ProviderModelsSection({ |
| 181 | (model) => !model.isCustom && hiddenModelSet.has(model.slug), | 196 | (model) => !model.isCustom && hiddenModelSet.has(model.slug), |
| 182 | ).length; | 197 | ).length; |
| 183 | const builtInModels = useMemo(() => models.filter((model) => !model.isCustom), [models]); | 198 | const builtInModels = useMemo(() => models.filter((model) => !model.isCustom), [models]); |
| | 199 | const allBuiltInModelsHidden = |
| | 200 | builtInModels.length > 0 && builtInModels.every((model) => hiddenModelSet.has(model.slug)); |
| 184 | const showFilter = models.length > FILTER_THRESHOLD; | 201 | const showFilter = models.length > FILTER_THRESHOLD; |
| 185 | const normalizedFilter = filter.trim().toLowerCase(); | 202 | const normalizedFilter = filter.trim().toLowerCase(); |
| 186 | const isFiltering = showFilter && normalizedFilter.length > 0; | 203 | const isFiltering = showFilter && normalizedFilter.length > 0; |
| ... | @@ -502,11 +519,27 @@ export function ProviderModelsSection({ | ... | @@ -502,11 +519,27 @@ export function ProviderModelsSection({ |
| 502 | aria-label="Filter models" | 519 | aria-label="Filter models" |
| 503 | /> | 520 | /> |
| 504 | ) : null} | 521 | ) : null} |
| 505 | <span className="text-xs text-muted-foreground"> | 522 | <div className="flex items-center gap-2"> |
| 506 | {models.length} model{models.length === 1 ? "" : "s"} | 523 | {builtInModels.length > 0 ? ( |
| 507 | {favoriteCount > 0 ? ` · ${favoriteCount} favorite${favoriteCount === 1 ? "" : "s"}` : ""} | 524 | <Button |
| 508 | {hiddenCount > 0 ? ` · ${hiddenCount} hidden` : ""} | 525 | type="button" |
| 509 | </span> | 526 | size="xs" |
| | 527 | variant="ghost-muted" |
| | 528 | onClick={() => |
| | 529 | onHiddenModelsChange(nextHiddenModelsForBulkToggle(models, hiddenModels)) |
| | 530 | } |
| | 531 | > |
| | 532 | {allBuiltInModelsHidden ? "Enable all" : "Disable all"} |
| | 533 | </Button> |
| | 534 | ) : null} |
| | 535 | <span className="text-xs text-muted-foreground"> |
| | 536 | {models.length} model{models.length === 1 ? "" : "s"} |
| | 537 | {favoriteCount > 0 |
| | 538 | ? ` · ${favoriteCount} favorite${favoriteCount === 1 ? "" : "s"}` |
| | 539 | : ""} |
| | 540 | {hiddenCount > 0 ? ` · ${hiddenCount} hidden` : ""} |
| | 541 | </span> |
| | 542 | </div> |
| 510 | </div> | 543 | </div> |
| 511 | <div | 544 | <div |
| 512 | ref={listRef} | 545 | ref={listRef} |