| 1 | import type { ModelOption, ProviderGroup } from "../../lib/modelOptions"; |
| 2 | import type { ProviderInstanceId } from "@t3tools/contracts"; |
| 3 | |
| 4 | export function modelFavoriteKey(provider: ProviderInstanceId, model: string): string { |
| 5 | return `${provider}:${model}`; |
| 6 | } |
| 7 | |
| 8 | /** Keep catalog order within each group when favorites move to the front. */ |
| 9 | export function favoritesFirst( |
| 10 | models: ReadonlyArray<ModelOption>, |
| 11 | favoriteKeys: ReadonlySet<string>, |
| 12 | ): ReadonlyArray<ModelOption> { |
| 13 | const favorites: ModelOption[] = []; |
| 14 | const others: ModelOption[] = []; |
| 15 | for (const model of models) { |
| 16 | (favoriteKeys.has(model.key) ? favorites : others).push(model); |
| 17 | } |
| 18 | return [...favorites, ...others]; |
| 19 | } |
| 20 | |
| 21 | /** Match the terms a user can actually see or recognize in the model picker. */ |
| 22 | export function modelMatchesCatalogQuery(input: { |
| 23 | readonly model: ModelOption; |
| 24 | readonly providerLabel: string; |
| 25 | readonly query: string; |
| 26 | }): boolean { |
| 27 | const query = input.query.trim().toLocaleLowerCase(); |
| 28 | if (query.length === 0) { |
| 29 | return true; |
| 30 | } |
| 31 | |
| 32 | return [ |
| 33 | input.model.label, |
| 34 | input.model.subtitle, |
| 35 | input.model.selection.model, |
| 36 | input.providerLabel, |
| 37 | ].some((value) => value.toLocaleLowerCase().includes(query)); |
| 38 | } |
| 39 | |
| 40 | /** Preserve staged provider options when the highlighted model is tapped again. */ |
| 41 | export function pendingModelAfterPress(input: { |
| 42 | readonly current: ModelOption | null; |
| 43 | readonly pressed: ModelOption; |
| 44 | readonly pressedIsApplied: boolean; |
| 45 | }): ModelOption | null { |
| 46 | if (input.pressedIsApplied) { |
| 47 | return null; |
| 48 | } |
| 49 | return input.current?.key === input.pressed.key ? input.current : input.pressed; |
| 50 | } |
| 51 | |
| 52 | /** A model can disappear while the picker is open. */ |
| 53 | export function canCommitPendingModel( |
| 54 | pending: ModelOption, |
| 55 | groups: ReadonlyArray<ProviderGroup>, |
| 56 | ): boolean { |
| 57 | return groups.some((group) => |
| 58 | group.models.some( |
| 59 | (model) => model.key === pending.key && !model.isUnavailable && !model.isHidden, |
| 60 | ), |
| 61 | ); |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * Primary and selected providers start open; all other catalogs start closed. |
| 66 | * A user's disclosure tap inverts that default until the picker is dismissed. |
| 67 | */ |
| 68 | export function providerSectionIsCollapsed(input: { |
| 69 | readonly defaultExpanded: boolean; |
| 70 | readonly hasExpansionOverride: boolean; |
| 71 | readonly isNarrowed: boolean; |
| 72 | }): boolean { |
| 73 | if (input.isNarrowed) { |
| 74 | return false; |
| 75 | } |
| 76 | return input.defaultExpanded ? input.hasExpansionOverride : !input.hasExpansionOverride; |
| 77 | } |