Skip to content

Commit

Permalink
feat: migrate settings storage from localStorage to server API (#5703)
Browse files Browse the repository at this point in the history
Co-authored-by: openhands <[email protected]>
  • Loading branch information
tofarr and openhands-agent authored Dec 26, 2024
1 parent 8975fcd commit c195e46
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 235 deletions.
124 changes: 0 additions & 124 deletions frontend/__tests__/services/settings.test.ts

This file was deleted.

4 changes: 2 additions & 2 deletions frontend/src/components/features/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import { useLocation } from "react-router";
import { useAuth } from "#/context/auth-context";
import { useUserPrefs } from "#/context/user-prefs-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 @@ -21,7 +21,7 @@ export function Sidebar() {
const { data: isAuthed } = useIsAuthed();

const { logout } = useAuth();
const { settingsAreUpToDate } = useUserPrefs();
const { settingsAreUpToDate } = useSettings();

const [accountSettingsModalOpen, setAccountSettingsModalOpen] =
React.useState(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ModalBody } from "../modal-body";
import { AvailableLanguages } from "#/i18n";
import { I18nKey } from "#/i18n/declaration";
import { useAuth } from "#/context/auth-context";
import { useUserPrefs } from "#/context/user-prefs-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";
Expand All @@ -30,7 +30,7 @@ export function AccountSettingsForm({
}: AccountSettingsFormProps) {
const { gitHubToken, setGitHubToken, logout } = useAuth();
const { data: config } = useConfig();
const { saveSettings } = useUserPrefs();
const { saveSettings } = useSettings();
const { t } = useTranslation();

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useUserPrefs } from "#/context/user-prefs-context";
import { useSettings } from "#/context/settings-context";
import { useGitHubUser } from "#/hooks/query/use-github-user";
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 } = useUserPrefs();
const { settings } = useSettings();

// FIXME: Bad practice to use localStorage directly
const analyticsConsent = localStorage.getItem("analytics-consent");
Expand Down
52 changes: 5 additions & 47 deletions frontend/src/components/shared/modals/settings/settings-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
updateSettingsVersion,
} from "#/utils/settings-utils";
import { useEndSession } from "#/hooks/use-end-session";
import { useUserPrefs } from "#/context/user-prefs-context";
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 Down Expand Up @@ -43,7 +43,7 @@ export function SettingsForm({
securityAnalyzers,
onClose,
}: SettingsFormProps) {
const { saveSettings } = useUserPrefs();
const { saveSettings } = useSettings();
const endSession = useEndSession();
const { logout } = useAuth();

Expand Down Expand Up @@ -84,7 +84,6 @@ export function SettingsForm({
React.useState(false);
const [confirmEndSessionModalOpen, setConfirmEndSessionModalOpen] =
React.useState(false);
const [showWarningModal, setShowWarningModal] = React.useState(false);

const resetOngoingSession = () => {
if (location.pathname.startsWith("/conversations/")) {
Expand Down Expand Up @@ -125,38 +124,15 @@ export function SettingsForm({
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const apiKey = formData.get("api-key");

if (!apiKey) {
setShowWarningModal(true);
} else if (location.pathname.startsWith("/conversations/")) {
if (location.pathname.startsWith("/conversations/")) {
setConfirmEndSessionModalOpen(true);
} else {
handleFormSubmission(formData);
onClose();
}
};

const handleCloseClick = () => {
const formData = new FormData(formRef.current ?? undefined);
const apiKey = formData.get("api-key");

if (!apiKey) setShowWarningModal(true);
else onClose();
};

const handleWarningConfirm = () => {
setShowWarningModal(false);
const formData = new FormData(formRef.current ?? undefined);
formData.set("api-key", ""); // Set null value for API key
handleFormSubmission(formData);
onClose();
};

const handleWarningCancel = () => {
setShowWarningModal(false);
};

return (
<div>
<form
Expand Down Expand Up @@ -196,7 +172,7 @@ export function SettingsForm({

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

{showAdvancedOptions && (
Expand Down Expand Up @@ -234,7 +210,7 @@ export function SettingsForm({
<ModalButton
text={t(I18nKey.SETTINGS_FORM$CLOSE_LABEL)}
className="bg-[#737373] w-full"
onClick={handleCloseClick}
onClick={onClose}
/>
</div>
<ModalButton
Expand Down Expand Up @@ -289,24 +265,6 @@ export function SettingsForm({
/>
</ModalBackdrop>
)}
{showWarningModal && (
<ModalBackdrop>
<DangerModal
title="Are you sure?"
description="You haven't set an API key. Without an API key, you won't be able to use the AI features. Are you sure you want to close the settings?"
buttons={{
danger: {
text: "Yes, close settings",
onClick: handleWarningConfirm,
},
cancel: {
text: "Cancel",
onClick: handleWarningCancel,
},
}}
/>
</ModalBackdrop>
)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useUserPrefs } from "#/context/user-prefs-context";
import { useSettings } from "#/context/settings-context";
import { useAIConfigOptions } from "#/hooks/query/use-ai-config-options";
import { LoadingSpinner } from "../../loading-spinner";
import { ModalBackdrop } from "../modal-backdrop";
Expand All @@ -9,7 +9,7 @@ interface SettingsModalProps {
}

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

return (
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/shared/task-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from "#/state/initial-query-slice";
import OpenHands from "#/api/open-hands";
import { useAuth } from "#/context/auth-context";
import { useUserPrefs } from "#/context/user-prefs-context";
import { useSettings } from "#/context/settings-context";

import { SuggestionBubble } from "#/components/features/suggestions/suggestion-bubble";
import { SUGGESTIONS } from "#/utils/suggestions";
Expand All @@ -28,7 +28,7 @@ export const TaskForm = React.forwardRef<HTMLFormElement>((_, ref) => {
const navigation = useNavigation();
const navigate = useNavigate();
const { gitHubToken } = useAuth();
const { settings } = useUserPrefs();
const { settings } = useSettings();

const { selectedRepository, files } = useSelector(
(state: RootState) => state.initialQuery,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
import React from "react";
import posthog from "posthog-js";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
getSettings,
Settings,
saveSettings as updateAndSaveSettingsToLocalStorage,
settingsAreUpToDate as checkIfSettingsAreUpToDate,
DEFAULT_SETTINGS,
} from "#/services/settings";

interface UserPrefsContextType {
interface SettingsContextType {
settings: Settings;
settingsAreUpToDate: boolean;
saveSettings: (settings: Partial<Settings>) => void;
}

const UserPrefsContext = React.createContext<UserPrefsContextType | undefined>(
const SettingsContext = React.createContext<SettingsContextType | undefined>(
undefined,
);

function UserPrefsProvider({ children }: React.PropsWithChildren) {
const [settings, setSettings] = React.useState(getSettings());
const SETTINGS_QUERY_KEY = ["settings"];

function SettingsProvider({ children }: React.PropsWithChildren) {
const { data: settings } = useQuery({
queryKey: SETTINGS_QUERY_KEY,
queryFn: getSettings,
initialData: DEFAULT_SETTINGS,
});

const [settingsAreUpToDate, setSettingsAreUpToDate] = React.useState(
checkIfSettingsAreUpToDate(),
);
const queryClient = useQueryClient();

const saveSettings = (newSettings: Partial<Settings>) => {
updateAndSaveSettingsToLocalStorage(newSettings);
setSettings(getSettings());
queryClient.invalidateQueries({ queryKey: SETTINGS_QUERY_KEY });
setSettingsAreUpToDate(checkIfSettingsAreUpToDate());
};

React.useEffect(() => {
if (settings.LLM_API_KEY) {
if (settings?.LLM_API_KEY) {
posthog.capture("user_activated");
}
}, [settings.LLM_API_KEY]);
}, [settings?.LLM_API_KEY]);

const value = React.useMemo(
() => ({
Expand All @@ -45,18 +55,18 @@ function UserPrefsProvider({ children }: React.PropsWithChildren) {
);

return (
<UserPrefsContext.Provider value={value}>
<SettingsContext.Provider value={value}>
{children}
</UserPrefsContext.Provider>
</SettingsContext.Provider>
);
}

function useUserPrefs() {
const context = React.useContext(UserPrefsContext);
function useSettings() {
const context = React.useContext(SettingsContext);
if (context === undefined) {
throw new Error("useUserPrefs must be used within a UserPrefsProvider");
throw new Error("useSettings must be used within a SettingsProvider");
}
return context;
}

export { UserPrefsProvider, useUserPrefs };
export { SettingsProvider, useSettings };
Loading

0 comments on commit c195e46

Please sign in to comment.