diff --git a/ui/admin/app/components/form/BasicInputItem.tsx b/ui/admin/app/components/form/BasicInputItem.tsx index c368b51c..77cfd717 100644 --- a/ui/admin/app/components/form/BasicInputItem.tsx +++ b/ui/admin/app/components/form/BasicInputItem.tsx @@ -8,12 +8,7 @@ import { FormMessage, } from "~/components/ui/form"; -export function BasicInputItem({ - children, - classNames = {}, - label, - description, -}: { +export type BasicInputItemProps = { children: ReactNode; classNames?: { wrapper?: string; @@ -23,7 +18,14 @@ export function BasicInputItem({ }; label?: ReactNode; description?: ReactNode; -}) { +}; + +export function BasicInputItem({ + children, + classNames = {}, + label, + description, +}: BasicInputItemProps) { return ( {label && ( diff --git a/ui/admin/app/components/form/controlledInputs.tsx b/ui/admin/app/components/form/controlledInputs.tsx index 1c79d5f3..2fa4d50d 100644 --- a/ui/admin/app/components/form/controlledInputs.tsx +++ b/ui/admin/app/components/form/controlledInputs.tsx @@ -10,7 +10,10 @@ import { import { cn } from "~/lib/utils"; -import { BasicInputItem } from "~/components/form/BasicInputItem"; +import { + BasicInputItem, + BasicInputItemProps, +} from "~/components/form/BasicInputItem"; import { Checkbox } from "~/components/ui/checkbox"; import { FormControl, @@ -232,6 +235,7 @@ export type ControlledCustomInputProps< TValues extends FieldValues, TName extends FieldPath, > = BaseProps & { + classNames?: BasicInputItemProps["classNames"]; children: (props: { field: ControllerRenderProps; fieldState: ControllerFieldState; @@ -248,6 +252,7 @@ export function ControlledCustomInput< name, label, description, + classNames, children, }: ControlledCustomInputProps) { return ( @@ -255,7 +260,11 @@ export function ControlledCustomInput< control={control} name={name} render={(args) => ( - + {children({ ...args, className: getFieldStateClasses(args.fieldState), diff --git a/ui/admin/app/components/model/DefaultModelAliasForm.tsx b/ui/admin/app/components/model/DefaultModelAliasForm.tsx new file mode 100644 index 00000000..75f87c4c --- /dev/null +++ b/ui/admin/app/components/model/DefaultModelAliasForm.tsx @@ -0,0 +1,222 @@ +import { useEffect, useMemo, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import useSWR from "swr"; + +import { UpdateDefaultModelAlias } from "~/lib/model/defaultModelAliases"; +import { Model, getModelUsageFromAlias } from "~/lib/model/models"; +import { DefaultModelAliasApiService } from "~/lib/service/api/defaultModelAliasApiService"; +import { ModelApiService } from "~/lib/service/api/modelApiService"; + +import { Button } from "~/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "~/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "~/components/ui/form"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "~/components/ui/select"; +import { useAsync } from "~/hooks/useAsync"; + +export function DefaultModelAliasForm({ + onSuccess, +}: { + onSuccess?: () => void; +}) { + const { data: defaultAliases } = useSWR( + DefaultModelAliasApiService.getAliases.key(), + DefaultModelAliasApiService.getAliases + ); + + const { data: models } = useSWR( + ModelApiService.getModels.key(), + ModelApiService.getModels + ); + + const modelUsageMap = useMemo(() => { + return (models ?? []).reduce((acc, model) => { + if (!acc.has(model.usage)) acc.set(model.usage, []); + + acc.get(model.usage)?.push(model); + + return acc; + }, new Map()); + }, [models]); + + const update = useAsync( + async (updates: UpdateDefaultModelAlias[]) => { + await Promise.all( + updates.map((update) => + DefaultModelAliasApiService.updateAlias( + update.alias, + update + ) + ) + ); + }, + { + onSuccess: () => { + toast.success("Default model aliases updated"); + onSuccess?.(); + }, + } + ); + + const defaultValues = useMemo(() => { + return defaultAliases?.reduce( + (acc, alias) => { + acc[alias.alias] = alias.model; + return acc; + }, + {} as Record + ); + }, [defaultAliases]); + + const form = useForm>({ defaultValues }); + + useEffect(() => { + return form.watch((values) => { + const changedItems = defaultAliases?.filter(({ alias, model }) => { + return values[alias] !== model; + }); + + if (!changedItems?.length) return; + }).unsubscribe; + }, [defaultAliases, form]); + + useEffect(() => { + form.reset(defaultValues); + }, [defaultValues, form]); + + const handleSubmit = form.handleSubmit((values) => { + const updates = defaultAliases + ?.filter(({ alias, model }) => values[alias] !== model) + .map(({ alias }) => ({ + alias, + model: values[alias], + })); + + update.execute(updates ?? []); + }); + + return ( +
+ + {defaultAliases?.map(({ alias, model: defaultModel }) => ( + { + const usage = getModelUsageFromAlias(alias); + const modelOptions = usage + ? modelUsageMap.get(usage) + : []; + + return ( + + {alias} + +
+ + + + + +
+
+ ); + }} + /> + ))} + + + + + ); +} + +export function DefaultModelAliasFormDialog() { + const [open, setOpen] = useState(false); + + return ( + + + + + + + + Default Model Aliases + + + + Set the default model for each usage. + + + setOpen(false)} /> + + + ); +} diff --git a/ui/admin/app/components/model/ModelForm.tsx b/ui/admin/app/components/model/ModelForm.tsx index 87bd2423..76cdbc9d 100644 --- a/ui/admin/app/components/model/ModelForm.tsx +++ b/ui/admin/app/components/model/ModelForm.tsx @@ -14,13 +14,9 @@ import { getModelUsageLabel, getModelsForProvider, } from "~/lib/model/models"; -import { BadRequestError } from "~/lib/service/api/apiErrors"; import { ModelApiService } from "~/lib/service/api/modelApiService"; -import { - ControlledCheckbox, - ControlledCustomInput, -} from "~/components/form/controlledInputs"; +import { ControlledCustomInput } from "~/components/form/controlledInputs"; import { Button } from "~/components/ui/button"; import { Form } from "~/components/ui/form"; import { @@ -71,7 +67,6 @@ export function ModelForm(props: ModelFormProps) { targetModel: model?.targetModel ?? "", modelProvider: model?.modelProvider ?? "", active: model?.active ?? true, - default: model?.default ?? false, usage: model?.usage ?? ModelUsage.LLM, }; }, [model]); @@ -184,12 +179,6 @@ export function ModelForm(props: ModelFormProps) { )} - -