|
import {
|
|
Autocomplete,
|
|
AutocompleteItem,
|
|
AutocompleteSection,
|
|
} from "@nextui-org/react";
|
|
import React from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { I18nKey } from "#/i18n/declaration";
|
|
import { mapProvider } from "#/utils/map-provider";
|
|
import { VERIFIED_MODELS, VERIFIED_PROVIDERS } from "#/utils/verified-models";
|
|
import { extractModelAndProvider } from "#/utils/extract-model-and-provider";
|
|
|
|
interface ModelSelectorProps {
|
|
isDisabled?: boolean;
|
|
models: Record<string, { separator: string; models: string[] }>;
|
|
currentModel?: string;
|
|
}
|
|
|
|
export function ModelSelector({
|
|
isDisabled,
|
|
models,
|
|
currentModel,
|
|
}: ModelSelectorProps) {
|
|
const [, setLitellmId] = React.useState<string | null>(null);
|
|
const [selectedProvider, setSelectedProvider] = React.useState<string | null>(
|
|
null,
|
|
);
|
|
const [selectedModel, setSelectedModel] = React.useState<string | null>(null);
|
|
|
|
React.useEffect(() => {
|
|
if (currentModel) {
|
|
|
|
const { provider, model } = extractModelAndProvider(currentModel);
|
|
|
|
setLitellmId(currentModel);
|
|
setSelectedProvider(provider);
|
|
setSelectedModel(model);
|
|
}
|
|
}, [currentModel]);
|
|
|
|
const handleChangeProvider = (provider: string) => {
|
|
setSelectedProvider(provider);
|
|
setSelectedModel(null);
|
|
|
|
const separator = models[provider]?.separator || "";
|
|
setLitellmId(provider + separator);
|
|
};
|
|
|
|
const handleChangeModel = (model: string) => {
|
|
const separator = models[selectedProvider || ""]?.separator || "";
|
|
let fullModel = selectedProvider + separator + model;
|
|
if (selectedProvider === "openai") {
|
|
|
|
fullModel = model;
|
|
}
|
|
setLitellmId(fullModel);
|
|
setSelectedModel(model);
|
|
};
|
|
|
|
const clear = () => {
|
|
setSelectedProvider(null);
|
|
setLitellmId(null);
|
|
};
|
|
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<div data-testid="model-selector" className="flex flex-col gap-2">
|
|
<div className="flex flex-row gap-3">
|
|
<fieldset className="flex flex-col gap-2">
|
|
<label htmlFor="agent" className="font-[500] text-[#A3A3A3] text-xs">
|
|
{t(I18nKey.LLM$PROVIDER)}
|
|
</label>
|
|
<Autocomplete
|
|
data-testid="llm-provider"
|
|
isRequired
|
|
isVirtualized={false}
|
|
name="llm-provider"
|
|
isDisabled={isDisabled}
|
|
aria-label={t(I18nKey.LLM$PROVIDER)}
|
|
placeholder={t(I18nKey.LLM$SELECT_PROVIDER_PLACEHOLDER)}
|
|
isClearable={false}
|
|
onSelectionChange={(e) => {
|
|
if (e?.toString()) handleChangeProvider(e.toString());
|
|
}}
|
|
onInputChange={(value) => !value && clear()}
|
|
defaultSelectedKey={selectedProvider ?? undefined}
|
|
selectedKey={selectedProvider}
|
|
inputProps={{
|
|
classNames: {
|
|
inputWrapper: "bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
|
|
},
|
|
}}
|
|
>
|
|
<AutocompleteSection title="Verified">
|
|
{Object.keys(models)
|
|
.filter((provider) => VERIFIED_PROVIDERS.includes(provider))
|
|
.map((provider) => (
|
|
<AutocompleteItem
|
|
data-testid={`provider-item-${provider}`}
|
|
key={provider}
|
|
value={provider}
|
|
>
|
|
{mapProvider(provider)}
|
|
</AutocompleteItem>
|
|
))}
|
|
</AutocompleteSection>
|
|
<AutocompleteSection title="Others">
|
|
{Object.keys(models)
|
|
.filter((provider) => !VERIFIED_PROVIDERS.includes(provider))
|
|
.map((provider) => (
|
|
<AutocompleteItem key={provider} value={provider}>
|
|
{mapProvider(provider)}
|
|
</AutocompleteItem>
|
|
))}
|
|
</AutocompleteSection>
|
|
</Autocomplete>
|
|
</fieldset>
|
|
|
|
<fieldset className="flex flex-col gap-2">
|
|
<label htmlFor="agent" className="font-[500] text-[#A3A3A3] text-xs">
|
|
{t(I18nKey.LLM$MODEL)}
|
|
</label>
|
|
<Autocomplete
|
|
data-testid="llm-model"
|
|
isRequired
|
|
isVirtualized={false}
|
|
name="llm-model"
|
|
aria-label={t(I18nKey.LLM$MODEL)}
|
|
placeholder={t(I18nKey.LLM$SELECT_MODEL_PLACEHOLDER)}
|
|
isClearable={false}
|
|
onSelectionChange={(e) => {
|
|
if (e?.toString()) handleChangeModel(e.toString());
|
|
}}
|
|
isDisabled={isDisabled || !selectedProvider}
|
|
selectedKey={selectedModel}
|
|
defaultSelectedKey={selectedModel ?? undefined}
|
|
inputProps={{
|
|
classNames: {
|
|
inputWrapper: "bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
|
|
},
|
|
}}
|
|
>
|
|
<AutocompleteSection title="Verified">
|
|
{models[selectedProvider || ""]?.models
|
|
.filter((model) => VERIFIED_MODELS.includes(model))
|
|
.map((model) => (
|
|
<AutocompleteItem key={model} value={model}>
|
|
{model}
|
|
</AutocompleteItem>
|
|
))}
|
|
</AutocompleteSection>
|
|
<AutocompleteSection title="Others">
|
|
{models[selectedProvider || ""]?.models
|
|
.filter((model) => !VERIFIED_MODELS.includes(model))
|
|
.map((model) => (
|
|
<AutocompleteItem
|
|
data-testid={`model-item-${model}`}
|
|
key={model}
|
|
value={model}
|
|
>
|
|
{model}
|
|
</AutocompleteItem>
|
|
))}
|
|
</AutocompleteSection>
|
|
</Autocomplete>
|
|
</fieldset>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|