import type { ModelRef } from '@scn-chat/lexicons' import { ChevronDownIcon } from '../../../shared/icons.tsx' import { Select, type SelectOption } from '../../../shared/ui/Select.tsx' import { SkeletonText } from '../../../shared/ui/Skeleton.tsx' import { type ModelOption, modelRef, sameModel } from '../models.ts' const keyOf = (m: ModelRef) => `${m.provider}/${m.id}` /** The choice of no model: an option, or only what the button says while none is chosen. */ interface None { label: string /** Whether "none" can be picked, rather than only shown until something is. */ selectable: boolean } interface Props { models: ModelOption[] value: ModelRef | null onChange: (model: ModelRef | null) => void none?: None id?: string 'aria-label'?: string title?: string /** How the button looks: a field, or quiet text in a toolbar, as in the composer. */ quiet?: boolean /** Whether the models are on their way, which holds the button with a line in place of a name. */ loading?: boolean className?: string } /** A choice among the models on offer, or none, by reference. A chosen model that is not on offer shows as unavailable. */ export function ModelSelect(props: Props) { const value = props.value const offered = !value || props.models.some((m) => sameModel(m, value)) const options: SelectOption[] = [ ...(props.none?.selectable ? [{ value: '', label: props.none.label }] : []), ...(value && !offered ? [{ value: keyOf(value), label: `Unavailable: ${keyOf(value)}`, disabled: true }] : []), // A search finds a model by its ID or provider too, as when its name says less. ...props.models.map((m) => ({ value: keyOf(m), label: m.name, keywords: keyOf(m) })), ] const key = value ? keyOf(value) : '' if (props.loading) return ( ) return (