import { noop, useMutation } from '@tanstack/react-query' import { Link } from '@tanstack/react-router' import { api, json, read } from '../../../shared/api.ts' import { cn } from '../../../shared/cn.ts' import { lastError } from '../../../shared/errors.ts' import { useAppForm } from '../../../shared/form.tsx' import { ErrorAlert } from '../../../shared/ui/Alert.tsx' import { Badge } from '../../../shared/ui/Badge.tsx' import { Button } from '../../../shared/ui/Button.tsx' import { Code } from '../../../shared/ui/Code.tsx' import { undivided } from '../../../shared/ui/divided-rows.ts' import * as ItemList from '../../../shared/ui/ItemList.tsx' import { inlineForm, SettingRow, SettingsEmpty, SettingsGroup, } from '../../../shared/ui/SettingsPage.tsx' import { ToggleChip } from '../../../shared/ui/ToggleChip.tsx' import { type Capabilities, capabilities } from '../../models/capabilities.ts' import { ModelTable } from '../../models/components/ModelTable.tsx' import { useModelsChanged, usePluginsChanged } from '../hooks/changes.ts' import type { AdminModel } from '../lib/models.ts' import type { AdminPlugin } from '../queries.ts' type Provider = AdminPlugin['providers'][number] interface Props { instance: AdminPlugin /** The plugin's options as they are in its form, saved or not, for listing models. */ options: Record provider: Provider models: AdminModel[] } /** A provider's admin models, each opening its page, and the ways to add more. */ export function ProviderModels(props: Props) { const modelsChanged = useModelsChanged() const add = useMutation({ mutationFn: (model: AdminModel) => read(api.admin.models.$post({}, json(model))), onSuccess: modelsChanged, }) const list = useMutation({ mutationFn: () => read( api.admin.plugins['list-models'].$post( {}, json({ package: props.instance.package, instanceId: props.instance.id, options: props.options, }), ), ), }) const listed = (list.data?.models ?? []).filter((model) => model.provider === props.provider.id) // A new model starts with the user role and the provider's default capabilities. const offer = (model: { id: string; name: string; capabilities: Capabilities }) => add.mutateAsync({ provider: props.provider.id, ...model, roles: ['user'], default: false }) /** Whether that model is on its way in, so a second tick doesn't add it twice. */ const adding = (id: string) => add.isPending && add.variables?.id === id const offered = new Set(props.models.map((model) => model.id)) const form = useAppForm({ defaultValues: { id: '' }, // Returning the add keeps Add model busy until it lands. A failure shows under the form. onSubmit: ({ value, formApi }) => { const id = value.id.trim() return offer({ id, name: id, capabilities: props.provider.defaultCapabilities }).then( () => formApi.reset(), noop, ) }, }) const name = props.provider.name return ( <> {listed.length > 0 && ( // Models the provider lists, to tick the ones to offer, over the field to type one. { if (adding(model.id)) return void offer({ id: model.id, name: model.name, capabilities: model.capabilities, }).catch(noop) }} /> )} 0 && undivided)}> {(field) => ( )} Add model {props.provider.listsModels && ( )} ) } interface ModelDefaultsProps { provider: Provider } /** The provider's default capabilities field. */ function ModelDefaults(props: ModelDefaultsProps) { const pluginsChanged = usePluginsChanged() const save = useMutation({ mutationFn: (defaults: Capabilities) => read( api.admin.providers[':id'].defaults.$put( { param: { id: props.provider.id } }, json(defaults), ), ), onSuccess: pluginsChanged, }) const defaults = (save.isPending && save.variables) || props.provider.defaultCapabilities return ( } >
{capabilities.map((capability) => ( save.mutate({ ...defaults, [capability.key]: pressed })} > {capability.label} ))}
) } /** What adding a provider's models takes, which depends on its key and whether it lists them. */ function addHint(provider: Provider) { if (!provider.hasAdminKey) return "Add an admin key to offer this provider's models." if (provider.listsModels) return 'Pick from the models the provider lists, using the settings above as they are now, or type a model ID.' return 'Type the ID of a model to offer.' } interface ModelListProps { pluginId: string models: AdminModel[] } /** The provider's admin models, each opening its page, with what it can do and who gets it. */ function ModelList(props: ModelListProps) { if (props.models.length === 0) return No models yet. return ( {props.models.map((model) => { const can = capabilities .filter((capability) => model.capabilities[capability.key]) .map((capability) => capability.label) return ( {model.name} {' '} {model.default && Default} } details={[ {model.id}, can.length > 0 && can.join(', '), model.roles.length ? `For ${model.roles.join(', ')}` : 'No roles', ]} /> ) })} ) }