chore: Remove settings local storage logic (#6504)

This commit is contained in:
sp.wack
2025-01-29 15:42:20 +04:00
committed by GitHub
parent eb760f32c7
commit b987f33a67
20 changed files with 56 additions and 266 deletions

View File

@@ -135,27 +135,6 @@ describe("Sidebar", () => {
});
describe("Settings Modal", () => {
it("should open the settings modal if the settings version is out of date", async () => {
const user = userEvent.setup();
localStorage.clear();
const { rerender } = renderSidebar();
const settingsModal = await screen.findByTestId("ai-config-modal");
expect(settingsModal).toBeInTheDocument();
const saveSettingsButton = await within(settingsModal).findByTestId(
"save-settings-button",
);
await user.click(saveSettingsButton);
expect(screen.queryByTestId("ai-config-modal")).not.toBeInTheDocument();
rerender(<RouterStub />);
expect(screen.queryByTestId("ai-config-modal")).not.toBeInTheDocument();
});
it("should open the settings modal if the user clicks the settings button", async () => {
const user = userEvent.setup();
renderSidebar();

View File

@@ -1,28 +0,0 @@
import { beforeEach, describe, expect, it, vi, type Mock } from "vitest";
import { getCachedConfig } from "../../src/utils/storage";
describe("getCachedConfig", () => {
beforeEach(() => {
// Clear all instances and calls to constructor and all methods
Storage.prototype.getItem = vi.fn();
});
it("should return an empty object when local storage is null or undefined", () => {
(Storage.prototype.getItem as Mock).mockReturnValue(null);
expect(getCachedConfig()).toEqual({});
(Storage.prototype.getItem as Mock).mockReturnValue(undefined);
expect(getCachedConfig()).toEqual({});
});
it("should return an empty object when local storage has invalid JSON", () => {
(Storage.prototype.getItem as Mock).mockReturnValue("invalid JSON");
expect(getCachedConfig()).toEqual({});
});
it("should return parsed object when local storage has valid JSON", () => {
const validJSON = '{"key":"value"}';
(Storage.prototype.getItem as Mock).mockReturnValue(validJSON);
expect(getCachedConfig()).toEqual({ key: "value" });
});
});

View File

@@ -13,7 +13,7 @@ import {
GetTrajectoryResponse,
} from "./open-hands.types";
import { openHands } from "./open-hands-axios";
import { ApiSettings } from "#/services/settings";
import { ApiSettings } from "#/types/settings";
class OpenHands {
/**

View File

@@ -30,9 +30,7 @@ export function Sidebar() {
const { data: config } = useConfig();
const { data: settings, isError: settingsError } = useSettings();
const { mutateAsync: logout } = useLogout();
const { saveUserSettings, isUpToDate: settingsAreUpToDate } =
useCurrentSettings();
const { saveUserSettings } = useCurrentSettings();
const [accountSettingsModalOpen, setAccountSettingsModalOpen] =
React.useState(false);
@@ -63,8 +61,6 @@ export function Sidebar() {
posthog.reset();
};
const showSettingsModal = !settingsAreUpToDate || settingsModalIsOpen;
return (
<>
<aside className="h-[40px] md:h-auto px-1 flex flex-row md:flex-col gap-1">
@@ -109,7 +105,7 @@ export function Sidebar() {
{accountSettingsModalOpen && (
<AccountSettingsModal onClose={handleAccountSettingsModalClose} />
)}
{(settingsError || showSettingsModal) && (
{(settingsError || settingsModalIsOpen) && (
<SettingsModal
settings={settings}
onClose={() => setSettingsModalIsOpen(false)}

View File

@@ -13,8 +13,8 @@ import { ModalButton } from "../../buttons/modal-button";
import { FormFieldset } from "../../form-fieldset";
import { useConfig } from "#/hooks/query/use-config";
import { useCurrentSettings } from "#/context/settings-context";
import { PostSettings } from "#/services/settings";
import { GitHubTokenInput } from "./github-token-input";
import { PostSettings } from "#/types/settings";
interface AccountSettingsFormProps {
onClose: () => void;

View File

@@ -4,10 +4,10 @@ import React from "react";
import posthog from "posthog-js";
import { I18nKey } from "#/i18n/declaration";
import { organizeModelsAndProviders } from "#/utils/organize-models-and-providers";
import { getDefaultSettings, Settings } from "#/services/settings";
import { getDefaultSettings } from "#/services/settings";
import { extractModelAndProvider } from "#/utils/extract-model-and-provider";
import { DangerModal } from "../confirmation-modals/danger-modal";
import { extractSettings, saveSettingsView } from "#/utils/settings-utils";
import { extractSettings } from "#/utils/settings-utils";
import { useEndSession } from "#/hooks/use-end-session";
import { ModalButton } from "../../buttons/modal-button";
import { AdvancedOptionSwitch } from "../../inputs/advanced-option-switch";
@@ -24,6 +24,7 @@ import { RuntimeSizeSelector } from "./runtime-size-selector";
import { useConfig } from "#/hooks/query/use-config";
import { useCurrentSettings } from "#/context/settings-context";
import { MEMORY_CONDENSER } from "#/utils/feature-flags";
import { Settings } from "#/types/settings";
interface SettingsFormProps {
disabled?: boolean;
@@ -93,14 +94,11 @@ export function SettingsForm({
};
const handleFormSubmission = async (formData: FormData) => {
const keys = Array.from(formData.keys());
const isUsingAdvancedOptions = keys.includes("use-advanced-options");
const newSettings = extractSettings(formData);
// Inject the condenser config from the current feature flag value
newSettings.ENABLE_DEFAULT_CONDENSER = MEMORY_CONDENSER;
saveSettingsView(isUsingAdvancedOptions ? "advanced" : "basic");
await saveUserSettings(newSettings);
onClose();
resetOngoingSession();

View File

@@ -1,10 +1,10 @@
import { useTranslation } from "react-i18next";
import { useAIConfigOptions } from "#/hooks/query/use-ai-config-options";
import { Settings } from "#/services/settings";
import { I18nKey } from "#/i18n/declaration";
import { LoadingSpinner } from "../../loading-spinner";
import { ModalBackdrop } from "../modal-backdrop";
import { SettingsForm } from "./settings-form";
import { Settings } from "#/types/settings";
interface SettingsModalProps {
settings: Settings;

View File

@@ -1,16 +1,9 @@
import React from "react";
import {
LATEST_SETTINGS_VERSION,
PostSettings,
Settings,
settingsAreUpToDate,
} from "#/services/settings";
import { useSettings } from "#/hooks/query/use-settings";
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";
import { PostSettings, Settings } from "#/types/settings";
interface SettingsContextType {
isUpToDate: boolean;
setIsUpToDate: (value: boolean) => void;
saveUserSettings: (newSettings: Partial<PostSettings>) => Promise<void>;
settings: Settings | undefined;
}
@@ -27,8 +20,6 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
const { data: userSettings } = useSettings();
const { mutateAsync: saveSettings } = useSaveSettings();
const [isUpToDate, setIsUpToDate] = React.useState(settingsAreUpToDate());
const saveUserSettings = async (newSettings: Partial<PostSettings>) => {
const updatedSettings: Partial<PostSettings> = {
...userSettings,
@@ -39,27 +30,15 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
delete updatedSettings.LLM_API_KEY;
}
await saveSettings(updatedSettings, {
onSuccess: () => {
if (!isUpToDate) {
localStorage.setItem(
"SETTINGS_VERSION",
LATEST_SETTINGS_VERSION.toString(),
);
setIsUpToDate(true);
}
},
});
await saveSettings(updatedSettings);
};
const value = React.useMemo(
() => ({
isUpToDate,
setIsUpToDate,
saveUserSettings,
settings: userSettings,
}),
[isUpToDate, setIsUpToDate, saveUserSettings, userSettings],
[saveUserSettings, userSettings],
);
return <SettingsContext value={value}>{children}</SettingsContext>;

View File

@@ -1,10 +1,7 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import {
DEFAULT_SETTINGS,
PostApiSettings,
PostSettings,
} from "#/services/settings";
import { DEFAULT_SETTINGS } from "#/services/settings";
import OpenHands from "#/api/open-hands";
import { PostSettings, PostApiSettings } from "#/types/settings";
const saveSettingsMutationFn = async (settings: Partial<PostSettings>) => {
const apiSettings: Partial<PostApiSettings> = {

View File

@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import React from "react";
import posthog from "posthog-js";
import { AxiosError } from "axios";
import { DEFAULT_SETTINGS, getLocalStorageSettings } from "#/services/settings";
import { DEFAULT_SETTINGS } from "#/services/settings";
import OpenHands from "#/api/open-hands";
import { useAuth } from "#/context/auth-context";
@@ -26,7 +26,7 @@ const getSettingsQueryFn = async () => {
};
}
return getLocalStorageSettings();
return DEFAULT_SETTINGS;
} catch (error) {
if (error instanceof AxiosError) {
if (error.response?.status === 404) {

View File

@@ -1,52 +0,0 @@
// Sometimes we ship major changes, like a new default agent.
import React from "react";
import { useCurrentSettings } from "#/context/settings-context";
import {
getCurrentSettingsVersion,
DEFAULT_SETTINGS,
getLocalStorageSettings,
} from "#/services/settings";
import { useSaveSettings } from "./mutation/use-save-settings";
// In this case, we may want to override a previous choice made by the user.
export const useMaybeMigrateSettings = () => {
const { mutateAsync: saveSettings } = useSaveSettings();
const { isUpToDate } = useCurrentSettings();
const maybeMigrateSettings = async () => {
const currentVersion = getCurrentSettingsVersion();
if (currentVersion < 1) {
localStorage.setItem("AGENT", DEFAULT_SETTINGS.AGENT);
}
if (currentVersion < 2) {
const customModel = localStorage.getItem("CUSTOM_LLM_MODEL");
if (customModel) {
localStorage.setItem("LLM_MODEL", customModel);
}
localStorage.removeItem("CUSTOM_LLM_MODEL");
localStorage.removeItem("USING_CUSTOM_MODEL");
}
if (currentVersion < 3) {
localStorage.removeItem("token");
}
if (currentVersion < 4) {
// We used to log out here, but it's breaking things
}
// Only save settings if user already previously saved settings
// That way we avoid setting defaults for new users too early
if (currentVersion !== 0 && currentVersion < 5) {
const localSettings = getLocalStorageSettings();
await saveSettings(localSettings);
}
};
React.useEffect(() => {
if (!isUpToDate) {
maybeMigrateSettings();
}
}, []);
};

View File

@@ -4,11 +4,8 @@ import {
Conversation,
ResultSet,
} from "#/api/open-hands.types";
import {
ApiSettings,
DEFAULT_SETTINGS,
PostApiSettings,
} from "#/services/settings";
import { DEFAULT_SETTINGS } from "#/services/settings";
import { ApiSettings, PostApiSettings } from "#/types/settings";
export const MOCK_DEFAULT_USER_SETTINGS: ApiSettings | PostApiSettings = {
llm_model: DEFAULT_SETTINGS.LLM_MODEL,

View File

@@ -8,7 +8,6 @@ import { Sidebar } from "#/components/features/sidebar/sidebar";
import { WaitlistModal } from "#/components/features/waitlist/waitlist-modal";
import { AnalyticsConsentFormModal } from "#/components/features/analytics/analytics-consent-form-modal";
import { useSettings } from "#/hooks/query/use-settings";
import { useMaybeMigrateSettings } from "#/hooks/use-maybe-migrate-settings";
import { useAuth } from "#/context/auth-context";
export function ErrorBoundary() {
@@ -44,8 +43,6 @@ export function ErrorBoundary() {
}
export default function MainApp() {
useMaybeMigrateSettings();
const { githubTokenIsSet } = useAuth();
const { data: settings } = useSettings();

View File

@@ -1,41 +1,7 @@
import { Settings } from "#/types/settings";
export const LATEST_SETTINGS_VERSION = 5;
export type Settings = {
LLM_MODEL: string;
LLM_BASE_URL: string;
AGENT: string;
LANGUAGE: string;
LLM_API_KEY: string | null;
CONFIRMATION_MODE: boolean;
SECURITY_ANALYZER: string;
REMOTE_RUNTIME_RESOURCE_FACTOR: number;
GITHUB_TOKEN_IS_SET: boolean;
ENABLE_DEFAULT_CONDENSER: boolean;
};
export type ApiSettings = {
llm_model: string;
llm_base_url: string;
agent: string;
language: string;
llm_api_key: string | null;
confirmation_mode: boolean;
security_analyzer: string;
remote_runtime_resource_factor: number;
github_token_is_set: boolean;
enable_default_condenser: boolean;
};
export type PostSettings = Settings & {
github_token: string;
unset_github_token: boolean;
};
export type PostApiSettings = ApiSettings & {
github_token: string;
unset_github_token: boolean;
};
export const DEFAULT_SETTINGS: Settings = {
LLM_MODEL: "anthropic/claude-3-5-sonnet-20241022",
LLM_BASE_URL: "",
@@ -49,58 +15,7 @@ export const DEFAULT_SETTINGS: Settings = {
ENABLE_DEFAULT_CONDENSER: false,
};
export const getCurrentSettingsVersion = () => {
const settingsVersion = localStorage.getItem("SETTINGS_VERSION");
if (!settingsVersion) return 0;
try {
return parseInt(settingsVersion, 10);
} catch (e) {
return 0;
}
};
export const settingsAreUpToDate = () =>
getCurrentSettingsVersion() === LATEST_SETTINGS_VERSION;
// TODO: localStorage settings are deprecated. Remove this after 1/31/2025
/**
* Get the settings from local storage
* @returns the settings from local storage
* @deprecated
*/
export const getLocalStorageSettings = (): Settings => {
const llmModel = localStorage.getItem("LLM_MODEL");
const baseUrl = localStorage.getItem("LLM_BASE_URL");
const agent = localStorage.getItem("AGENT");
const language = localStorage.getItem("LANGUAGE");
const llmApiKey = localStorage.getItem("LLM_API_KEY");
const confirmationMode = localStorage.getItem("CONFIRMATION_MODE") === "true";
const securityAnalyzer = localStorage.getItem("SECURITY_ANALYZER");
const enableDefaultCondenser =
localStorage.getItem("ENABLE_DEFAULT_CONDENSER") === "true";
return {
LLM_MODEL: llmModel || DEFAULT_SETTINGS.LLM_MODEL,
LLM_BASE_URL: baseUrl || DEFAULT_SETTINGS.LLM_BASE_URL,
AGENT: agent || DEFAULT_SETTINGS.AGENT,
LANGUAGE: language || DEFAULT_SETTINGS.LANGUAGE,
LLM_API_KEY: llmApiKey || DEFAULT_SETTINGS.LLM_API_KEY,
CONFIRMATION_MODE: confirmationMode || DEFAULT_SETTINGS.CONFIRMATION_MODE,
SECURITY_ANALYZER: securityAnalyzer || DEFAULT_SETTINGS.SECURITY_ANALYZER,
REMOTE_RUNTIME_RESOURCE_FACTOR:
DEFAULT_SETTINGS.REMOTE_RUNTIME_RESOURCE_FACTOR,
GITHUB_TOKEN_IS_SET: DEFAULT_SETTINGS.GITHUB_TOKEN_IS_SET,
ENABLE_DEFAULT_CONDENSER:
enableDefaultCondenser || DEFAULT_SETTINGS.ENABLE_DEFAULT_CONDENSER,
};
};
/**
* Get the default settings
*/
export const getDefaultSettings = (): Settings => DEFAULT_SETTINGS;
/**
* Get the current settings, either from local storage or defaults
*/
export const getSettings = (): Settings => getLocalStorageSettings();

View File

@@ -0,0 +1,35 @@
export type Settings = {
LLM_MODEL: string;
LLM_BASE_URL: string;
AGENT: string;
LANGUAGE: string;
LLM_API_KEY: string | null;
CONFIRMATION_MODE: boolean;
SECURITY_ANALYZER: string;
REMOTE_RUNTIME_RESOURCE_FACTOR: number;
GITHUB_TOKEN_IS_SET: boolean;
ENABLE_DEFAULT_CONDENSER: boolean;
};
export type ApiSettings = {
llm_model: string;
llm_base_url: string;
agent: string;
language: string;
llm_api_key: string | null;
confirmation_mode: boolean;
security_analyzer: string;
remote_runtime_resource_factor: number;
github_token_is_set: boolean;
enable_default_condenser: boolean;
};
export type PostSettings = Settings & {
github_token: string;
unset_github_token: boolean;
};
export type PostApiSettings = ApiSettings & {
github_token: string;
unset_github_token: boolean;
};

View File

@@ -1,4 +1,4 @@
import { Settings } from "#/services/settings";
import { Settings } from "#/types/settings";
const extractBasicFormData = (formData: FormData) => {
const provider = formData.get("llm-provider")?.toString();
@@ -44,7 +44,7 @@ const extractAdvancedFormData = (formData: FormData) => {
};
};
const extractSettings = (formData: FormData): Partial<Settings> => {
export const extractSettings = (formData: FormData): Partial<Settings> => {
const { LLM_MODEL, LLM_API_KEY, AGENT, LANGUAGE } =
extractBasicFormData(formData);
@@ -65,12 +65,3 @@ const extractSettings = (formData: FormData): Partial<Settings> => {
SECURITY_ANALYZER,
};
};
const saveSettingsView = (view: "basic" | "advanced") => {
localStorage.setItem(
"use-advanced-options",
view === "advanced" ? "true" : "false",
);
};
export { extractSettings, saveSettingsView };

View File

@@ -1,11 +0,0 @@
const getCachedConfig = (): { [key: string]: string } => {
const config = localStorage.getItem("ALL_SETTINGS");
if (config === null || config === undefined) return {};
try {
return JSON.parse(config);
} catch (e) {
return {};
}
};
export { getCachedConfig };

View File

@@ -34,7 +34,6 @@ test.beforeEach(async ({ page }) => {
await page.evaluate(() => {
localStorage.setItem("FEATURE_MULTI_CONVERSATION_UI", "true");
localStorage.setItem("analytics-consent", "true");
localStorage.setItem("SETTINGS_VERSION", "5");
});
});

View File

@@ -9,7 +9,6 @@ test.beforeEach(async ({ page }) => {
await page.goto("/");
await page.evaluate(() => {
localStorage.setItem("analytics-consent", "true");
localStorage.setItem("SETTINGS_VERSION", "5");
});
});

View File

@@ -4,7 +4,6 @@ test.beforeEach(async ({ page }) => {
await page.goto("/");
await page.evaluate(() => {
localStorage.setItem("analytics-consent", "true");
localStorage.setItem("SETTINGS_VERSION", "4");
});
});