Skip to content

Commit

Permalink
Merge branch 'main' into feat-search-conversations
Browse files Browse the repository at this point in the history
  • Loading branch information
tofarr committed Dec 31, 2024
2 parents 858d4cb + ab0eabd commit 5b197b4
Show file tree
Hide file tree
Showing 26 changed files with 434 additions and 249 deletions.
19 changes: 18 additions & 1 deletion frontend/src/api/open-hands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Conversation,
} from "./open-hands.types";
import { openHands } from "./open-hands-axios";
import { Settings } from "#/services/settings";
import { ApiSettings, Settings } from "#/services/settings";

class OpenHands {
/**
Expand Down Expand Up @@ -297,6 +297,23 @@ class OpenHands {
});
return data;
}

/**
* Get the settings from the server or use the default settings if not found
*/
static async getSettings(): Promise<ApiSettings> {
const { data } = await openHands.get<ApiSettings>("/api/settings");
return data;
}

/**
* Save the settings to the server. Only valid settings are saved.
* @param settings - the settings to save
*/
static async saveSettings(settings: Partial<ApiSettings>): Promise<boolean> {
const data = await openHands.post("/api/settings", settings);
return data.status === 200;
}
}

export default OpenHands;
18 changes: 11 additions & 7 deletions frontend/src/components/features/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from "react";
import { useLocation } from "react-router";
import FolderIcon from "#/icons/docs.svg?react";
import { useAuth } from "#/context/auth-context";
import { useSettings } from "#/context/settings-context";
import { useGitHubUser } from "#/hooks/query/use-github-user";
import { useIsAuthed } from "#/hooks/query/use-is-authed";
import { UserActions } from "./user-actions";
Expand All @@ -14,18 +13,19 @@ import { LoadingSpinner } from "#/components/shared/loading-spinner";
import { AccountSettingsModal } from "#/components/shared/modals/account-settings/account-settings-modal";
import { ExitProjectConfirmationModal } from "#/components/shared/modals/exit-project-confirmation-modal";
import { SettingsModal } from "#/components/shared/modals/settings/settings-modal";
import { useSettingsUpToDate } from "#/context/settings-up-to-date-context";
import { useSettings } from "#/hooks/query/use-settings";
import { ConversationPanel } from "../conversation-panel/conversation-panel";
import { cn } from "#/utils/utils";
import { MULTI_CONVO_UI_IS_ENABLED } from "#/utils/constants";

export function Sidebar() {
const location = useLocation();

const user = useGitHubUser();
const { data: isAuthed } = useIsAuthed();

const { logout } = useAuth();
const { settingsAreUpToDate } = useSettings();
const { data: settings, isError: settingsIsError } = useSettings();
const { isUpToDate: settingsAreUpToDate } = useSettingsUpToDate();

const [accountSettingsModalOpen, setAccountSettingsModalOpen] =
React.useState(false);
Expand Down Expand Up @@ -109,9 +109,13 @@ export function Sidebar() {
{accountSettingsModalOpen && (
<AccountSettingsModal onClose={handleAccountSettingsModalClose} />
)}
{showSettingsModal && (
<SettingsModal onClose={() => setSettingsModalIsOpen(false)} />
)}
{settingsIsError ||
(showSettingsModal && (
<SettingsModal
settings={settings}
onClose={() => setSettingsModalIsOpen(false)}
/>
))}
{startNewProjectModalIsOpen && (
<ExitProjectConfirmationModal
onClose={() => setStartNewProjectModalIsOpen(false)}
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/components/shared/buttons/settings-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ interface SettingsButtonProps {

export function SettingsButton({ onClick }: SettingsButtonProps) {
return (
<TooltipButton tooltip="Settings" ariaLabel="Settings" onClick={onClick}>
<TooltipButton
testId="settings-button"
tooltip="Settings"
ariaLabel="Settings"
onClick={onClick}
>
<CogTooth />
</TooltipButton>
);
Expand Down
24 changes: 17 additions & 7 deletions frontend/src/components/shared/inputs/api-key-input.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
import { Input } from "@nextui-org/react";
import { Input, Tooltip } from "@nextui-org/react";
import { useTranslation } from "react-i18next";
import { FaCheckCircle, FaExclamationCircle } from "react-icons/fa";
import { I18nKey } from "#/i18n/declaration";

interface APIKeyInputProps {
isDisabled: boolean;
defaultValue: string;
isSet: boolean;
}

export function APIKeyInput({ isDisabled, defaultValue }: APIKeyInputProps) {
export function APIKeyInput({ isDisabled, isSet }: APIKeyInputProps) {
const { t } = useTranslation();

return (
<fieldset data-testid="api-key-input" className="flex flex-col gap-2">
<label htmlFor="api-key" className="font-[500] text-[#A3A3A3] text-xs">
{t(I18nKey.SETTINGS_FORM$API_KEY_LABEL)}
</label>
<Tooltip content={isSet ? "API Key is set" : "API Key is not set"}>
<label
htmlFor="api-key"
className="font-[500] text-[#A3A3A3] text-xs flex items-center gap-1 self-start"
>
{isSet && <FaCheckCircle className="text-[#00D1B2] inline-block" />}
{!isSet && (
<FaExclamationCircle className="text-[#FF3860] inline-block" />
)}
{t(I18nKey.SETTINGS_FORM$API_KEY_LABEL)}
</label>
</Tooltip>
<Input
isDisabled={isDisabled}
id="api-key"
name="api-key"
aria-label="API Key"
type="password"
defaultValue={defaultValue}
defaultValue=""
classNames={{
inputWrapper: "bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import { ModalBody } from "../modal-body";
import { AvailableLanguages } from "#/i18n";
import { I18nKey } from "#/i18n/declaration";
import { useAuth } from "#/context/auth-context";
import { useSettings } from "#/context/settings-context";
import { handleCaptureConsent } from "#/utils/handle-capture-consent";
import { ModalButton } from "../../buttons/modal-button";
import { CustomInput } from "../../custom-input";
import { FormFieldset } from "../../form-fieldset";
import { useConfig } from "#/hooks/query/use-config";
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";

interface AccountSettingsFormProps {
onClose: () => void;
Expand All @@ -30,7 +30,7 @@ export function AccountSettingsForm({
}: AccountSettingsFormProps) {
const { gitHubToken, setGitHubToken, logout } = useAuth();
const { data: config } = useConfig();
const { saveSettings } = useSettings();
const { mutate: saveSettings } = useSaveSettings();
const { t } = useTranslation();

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useSettings } from "#/context/settings-context";
import { useGitHubUser } from "#/hooks/query/use-github-user";
import { useSettings } from "#/hooks/query/use-settings";
import { ModalBackdrop } from "../modal-backdrop";
import { AccountSettingsForm } from "./account-settings-form";

Expand All @@ -9,7 +9,7 @@ interface AccountSettingsModalProps {

export function AccountSettingsModal({ onClose }: AccountSettingsModalProps) {
const user = useGitHubUser();
const { settings } = useSettings();
const { data: settings } = useSettings();

// FIXME: Bad practice to use localStorage directly
const analyticsConsent = localStorage.getItem("analytics-consent");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,20 @@ export function BaseModalDescription({
}

interface BaseModalProps {
testId?: string;
title: string;
description: string;
buttons: ButtonConfig[];
}

export function BaseModal({ title, description, buttons }: BaseModalProps) {
export function BaseModal({
testId,
title,
description,
buttons,
}: BaseModalProps) {
return (
<ModalBody>
<ModalBody testID={testId}>
<div className="flex flex-col gap-2 self-start">
<BaseModalTitle title={title} />
<BaseModalDescription description={description} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BaseModal } from "./base-modal";

interface DangerModalProps {
testId?: string;

title: string;
description: string;

Expand All @@ -10,9 +12,15 @@ interface DangerModalProps {
};
}

export function DangerModal({ title, description, buttons }: DangerModalProps) {
export function DangerModal({
testId,
title,
description,
buttons,
}: DangerModalProps) {
return (
<BaseModal
testId={testId}
title={title}
description={description}
buttons={[
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/shared/modals/settings/model-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export function ModelSelector({
LLM Provider
</label>
<Autocomplete
data-testid="llm-provider"
isRequired
isVirtualized={false}
name="llm-provider"
Expand All @@ -91,7 +92,11 @@ export function ModelSelector({
{Object.keys(models)
.filter((provider) => VERIFIED_PROVIDERS.includes(provider))
.map((provider) => (
<AutocompleteItem key={provider} value={provider}>
<AutocompleteItem
data-testid={`provider-item-${provider}`}
key={provider}
value={provider}
>
{mapProvider(provider)}
</AutocompleteItem>
))}
Expand All @@ -113,6 +118,7 @@ export function ModelSelector({
LLM Model
</label>
<Autocomplete
data-testid="llm-model"
isRequired
isVirtualized={false}
name="llm-model"
Expand Down Expand Up @@ -144,7 +150,11 @@ export function ModelSelector({
{models[selectedProvider || ""]?.models
.filter((model) => !VERIFIED_MODELS.includes(model))
.map((model) => (
<AutocompleteItem key={model} value={model}>
<AutocompleteItem
data-testid={`model-item-${model}`}
key={model}
value={model}
>
{model}
</AutocompleteItem>
))}
Expand Down
15 changes: 6 additions & 9 deletions frontend/src/components/shared/modals/settings/settings-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { DangerModal } from "../confirmation-modals/danger-modal";
import { I18nKey } from "#/i18n/declaration";
import { extractSettings, saveSettingsView } from "#/utils/settings-utils";
import { useEndSession } from "#/hooks/use-end-session";
import { useSettings } from "#/context/settings-context";
import { ModalButton } from "../../buttons/modal-button";
import { AdvancedOptionSwitch } from "../../inputs/advanced-option-switch";
import { AgentInput } from "../../inputs/agent-input";
Expand All @@ -20,6 +19,7 @@ import { CustomModelInput } from "../../inputs/custom-model-input";
import { SecurityAnalyzerInput } from "../../inputs/security-analyzers-input";
import { ModalBackdrop } from "../modal-backdrop";
import { ModelSelector } from "./model-selector";
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";

interface SettingsFormProps {
disabled?: boolean;
Expand All @@ -38,7 +38,7 @@ export function SettingsForm({
securityAnalyzers,
onClose,
}: SettingsFormProps) {
const { saveSettings } = useSettings();
const { mutateAsync: saveSettings } = useSaveSettings();
const endSession = useEndSession();

const location = useLocation();
Expand Down Expand Up @@ -82,7 +82,6 @@ export function SettingsForm({
const resetOngoingSession = () => {
if (location.pathname.startsWith("/conversations/")) {
endSession();
onClose();
}
};

Expand All @@ -92,7 +91,7 @@ export function SettingsForm({
const newSettings = extractSettings(formData);

saveSettingsView(isUsingAdvancedOptions ? "advanced" : "basic");
await saveSettings(newSettings);
await saveSettings(newSettings, { onSuccess: onClose });
resetOngoingSession();

posthog.capture("settings_saved", {
Expand All @@ -102,11 +101,9 @@ export function SettingsForm({
};

const handleConfirmResetSettings = async () => {
await saveSettings(getDefaultSettings());
await saveSettings(getDefaultSettings(), { onSuccess: onClose });
resetOngoingSession();
posthog.capture("settings_reset");

onClose();
};

const handleConfirmEndSession = () => {
Expand All @@ -122,7 +119,6 @@ export function SettingsForm({
setConfirmEndSessionModalOpen(true);
} else {
handleFormSubmission(formData);
onClose();
}
};

Expand Down Expand Up @@ -165,7 +161,7 @@ export function SettingsForm({

<APIKeyInput
isDisabled={!!disabled}
defaultValue={settings.LLM_API_KEY || ""}
isSet={settings.LLM_API_KEY === "SET"}
/>

{showAdvancedOptions && (
Expand Down Expand Up @@ -221,6 +217,7 @@ export function SettingsForm({
{confirmResetDefaultsModalOpen && (
<ModalBackdrop>
<DangerModal
testId="reset-defaults-modal"
title={t(I18nKey.SETTINGS_FORM$ARE_YOU_SURE_LABEL)}
description={t(
I18nKey.SETTINGS_FORM$ALL_INFORMATION_WILL_BE_DELETED_MESSAGE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useSettings } from "#/context/settings-context";
import { useAIConfigOptions } from "#/hooks/query/use-ai-config-options";
import { Settings } from "#/services/settings";
import { LoadingSpinner } from "../../loading-spinner";
import { ModalBackdrop } from "../modal-backdrop";
import { SettingsForm } from "./settings-form";

interface SettingsModalProps {
settings: Settings;
onClose: () => void;
}

export function SettingsModal({ onClose }: SettingsModalProps) {
const { settings } = useSettings();
export function SettingsModal({ onClose, settings }: SettingsModalProps) {
const aiConfigOptions = useAIConfigOptions();

return (
Expand Down
Loading

0 comments on commit 5b197b4

Please sign in to comment.