import React from "react";
import { useTranslation } from "react-i18next";
import { AxiosError } from "axios";
import { ModelSelector } from "#/components/shared/modals/settings/model-selector";
import { organizeModelsAndProviders } from "#/utils/organize-models-and-providers";
import { useAIConfigOptions } from "#/hooks/query/use-ai-config-options";
import { useSettings } from "#/hooks/query/use-settings";
import { hasAdvancedSettingsSet } from "#/utils/has-advanced-settings-set";
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";
import { SettingsSwitch } from "#/components/features/settings/settings-switch";
import { I18nKey } from "#/i18n/declaration";
import { SettingsInput } from "#/components/features/settings/settings-input";
import { HelpLink } from "#/components/features/settings/help-link";
import { BrandButton } from "#/components/features/settings/brand-button";
import {
displayErrorToast,
displaySuccessToast,
} from "#/utils/custom-toast-handlers";
import { retrieveAxiosErrorMessage } from "#/utils/retrieve-axios-error-message";
import { SettingsDropdownInput } from "#/components/features/settings/settings-dropdown-input";
import { useConfig } from "#/hooks/query/use-config";
import { isCustomModel } from "#/utils/is-custom-model";
import { LlmSettingsInputsSkeleton } from "#/components/features/settings/llm-settings/llm-settings-inputs-skeleton";
import { KeyStatusIcon } from "#/components/features/settings/key-status-icon";
import { DEFAULT_SETTINGS } from "#/services/settings";
function LlmSettingsScreen() {
const { t } = useTranslation();
const { mutate: saveSettings, isPending } = useSaveSettings();
const { data: resources } = useAIConfigOptions();
const { data: settings, isLoading, isFetching } = useSettings();
const { data: config } = useConfig();
const [view, setView] = React.useState<"basic" | "advanced">("basic");
const [securityAnalyzerInputIsVisible, setSecurityAnalyzerInputIsVisible] =
React.useState(false);
const [dirtyInputs, setDirtyInputs] = React.useState({
model: false,
apiKey: false,
searchApiKey: false,
baseUrl: false,
agent: false,
confirmationMode: false,
enableDefaultCondenser: false,
securityAnalyzer: false,
});
const modelsAndProviders = organizeModelsAndProviders(
resources?.models || [],
);
React.useEffect(() => {
const determineWhetherToToggleAdvancedSettings = () => {
if (resources && settings) {
return (
isCustomModel(resources.models, settings.LLM_MODEL) ||
hasAdvancedSettingsSet({
...settings,
})
);
}
return false;
};
const userSettingsIsAdvanced = determineWhetherToToggleAdvancedSettings();
if (settings) setSecurityAnalyzerInputIsVisible(settings.CONFIRMATION_MODE);
if (userSettingsIsAdvanced) setView("advanced");
else setView("basic");
}, [settings, resources]);
const handleSuccessfulMutation = () => {
displaySuccessToast(t(I18nKey.SETTINGS$SAVED));
setDirtyInputs({
model: false,
apiKey: false,
searchApiKey: false,
baseUrl: false,
agent: false,
confirmationMode: false,
enableDefaultCondenser: false,
securityAnalyzer: false,
});
};
const handleErrorMutation = (error: AxiosError) => {
const errorMessage = retrieveAxiosErrorMessage(error);
displayErrorToast(errorMessage || t(I18nKey.ERROR$GENERIC));
};
const basicFormAction = (formData: FormData) => {
const provider = formData.get("llm-provider-input")?.toString();
const model = formData.get("llm-model-input")?.toString();
const apiKey = formData.get("llm-api-key-input")?.toString();
const searchApiKey = formData.get("search-api-key-input")?.toString();
const fullLlmModel =
provider && model && `${provider}/${model}`.toLowerCase();
saveSettings(
{
LLM_MODEL: fullLlmModel,
llm_api_key: apiKey || null,
SEARCH_API_KEY: searchApiKey || "",
// reset advanced settings
LLM_BASE_URL: DEFAULT_SETTINGS.LLM_BASE_URL,
AGENT: DEFAULT_SETTINGS.AGENT,
CONFIRMATION_MODE: DEFAULT_SETTINGS.CONFIRMATION_MODE,
SECURITY_ANALYZER: DEFAULT_SETTINGS.SECURITY_ANALYZER,
ENABLE_DEFAULT_CONDENSER: DEFAULT_SETTINGS.ENABLE_DEFAULT_CONDENSER,
},
{
onSuccess: handleSuccessfulMutation,
onError: handleErrorMutation,
},
);
};
const advancedFormAction = (formData: FormData) => {
const model = formData.get("llm-custom-model-input")?.toString();
const baseUrl = formData.get("base-url-input")?.toString();
const apiKey = formData.get("llm-api-key-input")?.toString();
const searchApiKey = formData.get("search-api-key-input")?.toString();
const agent = formData.get("agent-input")?.toString();
const confirmationMode =
formData.get("enable-confirmation-mode-switch")?.toString() === "on";
const enableDefaultCondenser =
formData.get("enable-memory-condenser-switch")?.toString() === "on";
const securityAnalyzer = formData
.get("security-analyzer-input")
?.toString();
saveSettings(
{
LLM_MODEL: model,
LLM_BASE_URL: baseUrl,
llm_api_key: apiKey || null,
SEARCH_API_KEY: searchApiKey || "",
AGENT: agent,
CONFIRMATION_MODE: confirmationMode,
ENABLE_DEFAULT_CONDENSER: enableDefaultCondenser,
SECURITY_ANALYZER: confirmationMode ? securityAnalyzer : undefined,
},
{
onSuccess: handleSuccessfulMutation,
onError: handleErrorMutation,
},
);
};
const formAction = (formData: FormData) => {
if (view === "basic") basicFormAction(formData);
else advancedFormAction(formData);
};
const handleToggleAdvancedSettings = (isToggled: boolean) => {
setSecurityAnalyzerInputIsVisible(!!settings?.CONFIRMATION_MODE);
setView(isToggled ? "advanced" : "basic");
setDirtyInputs({
model: false,
apiKey: false,
searchApiKey: false,
baseUrl: false,
agent: false,
confirmationMode: false,
enableDefaultCondenser: false,
securityAnalyzer: false,
});
};
const handleModelIsDirty = (model: string | null) => {
// openai providers are special case; see ModelSelector
// component for details
const modelIsDirty = model !== settings?.LLM_MODEL.replace("openai/", "");
setDirtyInputs((prev) => ({
...prev,
model: modelIsDirty,
}));
};
const handleApiKeyIsDirty = (apiKey: string) => {
const apiKeyIsDirty = apiKey !== "";
setDirtyInputs((prev) => ({
...prev,
apiKey: apiKeyIsDirty,
}));
};
const handleSearchApiKeyIsDirty = (searchApiKey: string) => {
const searchApiKeyIsDirty = searchApiKey !== settings?.SEARCH_API_KEY;
setDirtyInputs((prev) => ({
...prev,
searchApiKey: searchApiKeyIsDirty,
}));
};
const handleCustomModelIsDirty = (model: string) => {
const modelIsDirty = model !== settings?.LLM_MODEL && model !== "";
setDirtyInputs((prev) => ({
...prev,
model: modelIsDirty,
}));
};
const handleBaseUrlIsDirty = (baseUrl: string) => {
const baseUrlIsDirty = baseUrl !== settings?.LLM_BASE_URL;
setDirtyInputs((prev) => ({
...prev,
baseUrl: baseUrlIsDirty,
}));
};
const handleAgentIsDirty = (agent: string) => {
const agentIsDirty = agent !== settings?.AGENT && agent !== "";
setDirtyInputs((prev) => ({
...prev,
agent: agentIsDirty,
}));
};
const handleConfirmationModeIsDirty = (isToggled: boolean) => {
setSecurityAnalyzerInputIsVisible(isToggled);
const confirmationModeIsDirty = isToggled !== settings?.CONFIRMATION_MODE;
setDirtyInputs((prev) => ({
...prev,
confirmationMode: confirmationModeIsDirty,
}));
};
const handleEnableDefaultCondenserIsDirty = (isToggled: boolean) => {
const enableDefaultCondenserIsDirty =
isToggled !== settings?.ENABLE_DEFAULT_CONDENSER;
setDirtyInputs((prev) => ({
...prev,
enableDefaultCondenser: enableDefaultCondenserIsDirty,
}));
};
const handleSecurityAnalyzerIsDirty = (securityAnalyzer: string) => {
const securityAnalyzerIsDirty =
securityAnalyzer !== settings?.SECURITY_ANALYZER;
setDirtyInputs((prev) => ({
...prev,
securityAnalyzer: securityAnalyzerIsDirty,
}));
};
const formIsDirty = Object.values(dirtyInputs).some((isDirty) => isDirty);
if (!settings || isFetching) return