openhands / src /components /shared /modals /settings /settings-form.tsx
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
9b906ea verified
Raw History Blame Contribute Delete
4.86 kB
import { useTranslation } from "react-i18next";
import React from "react";
import { useTracking } from "#/hooks/use-tracking";
import { useNavigation } from "#/context/navigation-context";
import { I18nKey } from "#/i18n/declaration";
import { DangerModal } from "../confirmation-modals/danger-modal";
import { extractSettings } from "#/utils/settings-utils";
import { ModalBackdrop } from "../modal-backdrop";
import { ModelSelector } from "./model-selector";
import { Settings } from "#/types/settings";
import { BrandButton } from "#/components/features/settings/brand-button";
import { SettingsInput } from "#/components/features/settings/settings-input";
import { HelpLink } from "#/ui/help-link";
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";
import { getAgentSettingValue } from "#/utils/sdk-settings-schema";
import { SETTINGS_FORM } from "#/utils/constants";
interface SettingsFormProps {
settings: Settings;
onClose: () => void;
}
export function SettingsForm({ settings, onClose }: SettingsFormProps) {
const { trackSettingsSaved } = useTracking();
const { mutate: saveUserSettings } = useSaveSettings();
const { currentPath } = useNavigation();
const { t } = useTranslation("openhands");
const formRef = React.useRef<HTMLFormElement>(null);
const [confirmEndSessionModalOpen, setConfirmEndSessionModalOpen] =
React.useState(false);
const handleFormSubmission = async (formData: FormData) => {
const newSettings = extractSettings(formData);
await saveUserSettings(newSettings, {
onSuccess: () => {
onClose();
const agentLlm =
((newSettings.agent_settings_diff as Record<string, unknown>)
?.llm as Record<string, unknown>) ?? {};
trackSettingsSaved({
llmModel: agentLlm.model,
llmApiKeySet: agentLlm.api_key ? "SET" : "UNSET",
searchApiKeySet: newSettings.search_api_key ? "SET" : "UNSET",
remoteRuntimeResourceFactor:
newSettings.remote_runtime_resource_factor,
});
},
});
};
const handleConfirmEndSession = () => {
const formData = new FormData(formRef.current ?? undefined);
handleFormSubmission(formData);
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
if (currentPath.startsWith("/conversations/")) {
setConfirmEndSessionModalOpen(true);
} else {
handleFormSubmission(formData);
}
};
const isLLMKeySet = settings.llm_api_key_set;
const currentModel = getAgentSettingValue(settings, "llm.model");
return (
<div>
<form
ref={formRef}
data-testid="settings-form"
className="flex flex-col gap-6"
onSubmit={handleSubmit}
>
<div className="flex flex-col gap-[17px]">
<ModelSelector
currentModel={
typeof currentModel === "string" ? currentModel : undefined
}
wrapperClassName="!flex-col !gap-[17px]"
labelClassName={SETTINGS_FORM.LABEL_CLASSNAME}
/>
<SettingsInput
testId="llm-api-key-input"
name="llm-api-key-input"
label={t(I18nKey.SETTINGS_FORM$API_KEY)}
type="password"
className="w-full"
// eslint-disable-next-line i18next/no-literal-string -- masked-key sentinel, not translatable
placeholder={isLLMKeySet ? "<hidden>" : ""}
labelClassName={SETTINGS_FORM.LABEL_CLASSNAME}
/>
<HelpLink
testId="llm-api-key-help-anchor"
text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}
linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}
href="https://docs.openhands.dev/usage/local-setup#getting-an-api-key"
size="settings"
linkColor="white"
/>
</div>
<div className="flex flex-col gap-2">
<BrandButton
testId="save-settings-button"
type="submit"
variant="primary"
className="w-full"
>
{t(I18nKey.BUTTON$SAVE)}
</BrandButton>
</div>
</form>
{confirmEndSessionModalOpen && (
<ModalBackdrop>
<DangerModal
title={t(I18nKey.MODAL$END_SESSION_TITLE)}
description={t(I18nKey.MODAL$END_SESSION_MESSAGE)}
buttons={{
danger: {
text: t(I18nKey.BUTTON$END_SESSION),
onClick: handleConfirmEndSession,
},
cancel: {
text: t(I18nKey.BUTTON$CANCEL),
onClick: () => setConfirmEndSessionModalOpen(false),
},
}}
/>
</ModalBackdrop>
)}
</div>
);
}