Compare commits

...

6 Commits

Author SHA1 Message Date
amanape b1d46cf20f Merge 2025-04-08 22:55:41 +04:00
amanape 872f5ae8ae Add comments 2025-04-08 22:13:33 +04:00
amanape 0a06af5d17 Refactor reset settings 2025-03-29 01:00:05 +04:00
amanape e672a438df Refactor save settings 2025-03-29 00:34:01 +04:00
amanape f1335dab04 Refactor handlers 2025-03-28 23:16:38 +04:00
amanape 1285404349 Refactor get settings 2025-03-28 23:01:36 +04:00
28 changed files with 312 additions and 337 deletions
@@ -3,14 +3,14 @@ import { describe, expect, it, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AnalyticsConsentFormModal } from "#/components/features/analytics/analytics-consent-form-modal";
import OpenHands from "#/api/open-hands";
import { AuthProvider } from "#/context/auth-context";
import { SettingsService } from "#/api/settings-service/settings-service.api";
describe("AnalyticsConsentFormModal", () => {
it("should call saveUserSettings with consent", async () => {
const user = userEvent.setup();
const onCloseMock = vi.fn();
const saveUserSettingsSpy = vi.spyOn(OpenHands, "saveSettings");
const saveUserSettingsSpy = vi.spyOn(SettingsService, "saveSettings");
render(<AnalyticsConsentFormModal onClose={onCloseMock} />, {
wrapper: ({ children }) => (
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { renderWithProviders } from "test-utils";
import { createRoutesStub } from "react-router";
import { Sidebar } from "#/components/features/sidebar/sidebar";
import OpenHands from "#/api/open-hands";
import { SettingsService } from "#/api/settings-service/settings-service.api";
// These tests will now fail because the conversation panel is rendered through a portal
// and technically not a child of the Sidebar component.
@@ -18,7 +18,7 @@ const renderSidebar = () =>
renderWithProviders(<RouterStub initialEntries={["/conversation/123"]} />);
describe("Sidebar", () => {
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
afterEach(() => {
vi.clearAllMocks();
@@ -3,20 +3,20 @@ import { describe, expect, it, vi } from "vitest";
import { renderWithProviders } from "test-utils";
import { createRoutesStub } from "react-router";
import { screen } from "@testing-library/react";
import OpenHands from "#/api/open-hands";
import { SettingsForm } from "#/components/shared/modals/settings/settings-form";
import { DEFAULT_SETTINGS } from "#/services/settings";
import { SettingsService } from "#/api/settings-service/settings-service.api";
describe("SettingsForm", () => {
const onCloseMock = vi.fn();
const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings");
const saveSettingsSpy = vi.spyOn(SettingsService, "saveSettings");
const RouteStub = createRoutesStub([
{
Component: () => (
<SettingsForm
settings={DEFAULT_SETTINGS}
models={[DEFAULT_SETTINGS.LLM_MODEL]}
models={[DEFAULT_SETTINGS.llm_model]}
onClose={onCloseMock}
/>
),
@@ -33,7 +33,7 @@ describe("SettingsForm", () => {
expect(saveSettingsSpy).toHaveBeenCalledWith(
expect.objectContaining({
llm_model: DEFAULT_SETTINGS.LLM_MODEL,
llm_model: DEFAULT_SETTINGS.llm_model,
}),
);
});
@@ -1,13 +1,13 @@
import { renderHook, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import OpenHands from "#/api/open-hands";
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";
import { AuthProvider } from "#/context/auth-context";
import { SettingsService } from "#/api/settings-service/settings-service.api";
describe("useSaveSettings", () => {
it("should send an empty string for llm_api_key if an empty string is passed, otherwise undefined", async () => {
const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings");
const saveSettingsSpy = vi.spyOn(SettingsService, "saveSettings");
const { result } = renderHook(() => useSaveSettings(), {
wrapper: ({ children }) => (
<AuthProvider>
+2 -1
View File
@@ -7,6 +7,7 @@ import MainApp from "#/routes/root-layout";
import i18n from "#/i18n";
import * as CaptureConsent from "#/utils/handle-capture-consent";
import OpenHands from "#/api/open-hands";
import { SettingsService } from "#/api/settings-service/settings-service.api";
describe("frontend/routes/_oh", () => {
const RouteStub = createRoutesStub([{ Component: MainApp, path: "/" }]);
@@ -59,7 +60,7 @@ describe("frontend/routes/_oh", () => {
it.skip("should render and capture the user's consent if oss mode", async () => {
const user = userEvent.setup();
const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
const handleCaptureConsentSpy = vi.spyOn(
CaptureConsent,
"handleCaptureConsent",
+2 -1
View File
@@ -8,6 +8,7 @@ import MainApp from "#/routes/root-layout";
import SettingsScreen from "#/routes/settings";
import Home from "#/routes/home";
import OpenHands from "#/api/open-hands";
import { SettingsService } from "#/api/settings-service/settings-service.api";
const createAxiosNotFoundErrorObject = () =>
new AxiosError(
@@ -25,7 +26,7 @@ const createAxiosNotFoundErrorObject = () =>
},
);
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
const RouterStub = createRoutesStub([
{
+34 -35
View File
@@ -7,25 +7,26 @@ import OpenHands from "#/api/open-hands";
import { AuthProvider } from "#/context/auth-context";
import SettingsScreen from "#/routes/settings";
import * as AdvancedSettingsUtlls from "#/utils/has-advanced-settings-set";
import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers";
import * as ConsentHandlers from "#/utils/handle-capture-consent";
import AccountSettings from "#/routes/account-settings";
import { Provider } from "#/types/settings";
import { DEFAULT_SETTINGS } from "#/services/settings";
import { SettingsService } from "#/api/settings-service/settings-service.api";
import { GitProvider } from "#/api/settings-service/settings-service.types";
const toggleAdvancedSettings = async (user: UserEvent) => {
const advancedSwitch = await screen.findByTestId("advanced-settings-switch");
await user.click(advancedSwitch);
};
const mock_provider_tokens_are_set: Record<Provider, boolean> = {
const MOCK_PROVIDER_TOKENS_ARE_SET: Record<GitProvider, boolean> = {
github: true,
gitlab: false,
};
describe("Settings Screen", () => {
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings");
const resetSettingsSpy = vi.spyOn(OpenHands, "resetSettings");
const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
const saveSettingsSpy = vi.spyOn(SettingsService, "saveSettings");
const resetSettingsSpy = vi.spyOn(SettingsService, "resetSettings");
const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
const { handleLogoutMock } = vi.hoisted(() => ({
@@ -65,7 +66,9 @@ describe("Settings Screen", () => {
await waitFor(() => {
// Use queryAllByText to handle multiple elements with the same text
expect(screen.queryAllByText("SETTINGS$LLM_SETTINGS")).not.toHaveLength(0);
expect(screen.queryAllByText("SETTINGS$LLM_SETTINGS")).not.toHaveLength(
0,
);
screen.getByText("ACCOUNT_SETTINGS$ADDITIONAL_SETTINGS");
screen.getByText("BUTTON$RESET_TO_DEFAULTS");
screen.getByText("BUTTON$SAVE");
@@ -98,9 +101,7 @@ describe("Settings Screen", () => {
// TODO: Set a better unset indicator
it.skip("should render an indicator if the GitHub token is not set", async () => {
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
});
getSettingsSpy.mockResolvedValue(DEFAULT_SETTINGS);
renderSettingsScreen();
@@ -119,8 +120,8 @@ describe("Settings Screen", () => {
it("should set '<hidden>' placeholder if the GitHub token is set", async () => {
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
provider_tokens_set: mock_provider_tokens_are_set,
...DEFAULT_SETTINGS,
provider_tokens_set: MOCK_PROVIDER_TOKENS_ARE_SET,
});
renderSettingsScreen();
@@ -133,8 +134,8 @@ describe("Settings Screen", () => {
it("should render an indicator if the GitHub token is set", async () => {
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
provider_tokens_set: mock_provider_tokens_are_set,
...DEFAULT_SETTINGS,
provider_tokens_set: MOCK_PROVIDER_TOKENS_ARE_SET,
});
renderSettingsScreen();
@@ -150,8 +151,6 @@ describe("Settings Screen", () => {
}
});
// Tests for DISCONNECT_FROM_GITHUB button removed as the button is no longer included in main
it("should not render the 'Configure GitHub Repositories' button if OSS mode", async () => {
getConfigSpy.mockResolvedValue({
APP_MODE: "oss",
@@ -210,7 +209,7 @@ describe("Settings Screen", () => {
it.skip("should not reset LLM Provider and Model if GitHub token is invalid", async () => {
const user = userEvent.setup();
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
llm_model: "anthropic/claude-3-5-sonnet-20241022",
});
saveSettingsSpy.mockRejectedValueOnce(new Error("Invalid GitHub token"));
@@ -331,8 +330,8 @@ describe("Settings Screen", () => {
// TODO: Set a better unset indicator
it.skip("should render an indicator if the LLM API key is not set", async () => {
getSettingsSpy.mockResolvedValueOnce({
...MOCK_DEFAULT_USER_SETTINGS,
llm_api_key: null,
...DEFAULT_SETTINGS,
llm_api_key_set: null,
});
renderSettingsScreen();
@@ -352,7 +351,7 @@ describe("Settings Screen", () => {
it("should render an indicator if the LLM API key is set", async () => {
getSettingsSpy.mockResolvedValueOnce({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
llm_api_key_set: true,
});
@@ -373,7 +372,7 @@ describe("Settings Screen", () => {
it("should set '<hidden>' placeholder if the LLM API key is set", async () => {
getSettingsSpy.mockResolvedValueOnce({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
llm_api_key_set: true,
});
@@ -388,7 +387,7 @@ describe("Settings Screen", () => {
describe("Basic Model Selector", () => {
it("should set the provider and model", async () => {
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
llm_model: "anthropic/claude-3-5-sonnet-20241022",
});
@@ -455,7 +454,7 @@ describe("Settings Screen", () => {
});
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
remote_runtime_resource_factor: 1,
});
@@ -495,7 +494,7 @@ describe("Settings Screen", () => {
});
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
});
renderSettingsScreen();
@@ -546,7 +545,7 @@ describe("Settings Screen", () => {
const user = userEvent.setup();
getSettingsSpy.mockResolvedValueOnce({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
});
renderSettingsScreen();
@@ -563,7 +562,7 @@ describe("Settings Screen", () => {
// Mock the settings that will be returned after reset
// This should be the default settings with no advanced settings enabled
getSettingsSpy.mockResolvedValueOnce({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
llm_base_url: "",
confirmation_mode: false,
security_analyzer: "",
@@ -614,7 +613,7 @@ describe("Settings Screen", () => {
it("should toggle advanced if user had set a custom model", async () => {
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
llm_model: "some/custom-model",
});
renderSettingsScreen();
@@ -649,7 +648,7 @@ describe("Settings Screen", () => {
it("should have confirmation mode enabled if the user previously had it enabled", async () => {
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
confirmation_mode: true,
});
@@ -666,7 +665,7 @@ describe("Settings Screen", () => {
// FIXME: security analyzer is not found for some reason...
it.skip("should have the values set if the user previously had them set", async () => {
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
language: "no",
user_consents_to_analytics: true,
llm_base_url: "https://test.com",
@@ -701,7 +700,7 @@ describe("Settings Screen", () => {
it("should save the settings when the 'Save Changes' button is clicked", async () => {
const user = userEvent.setup();
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
});
renderSettingsScreen();
@@ -728,7 +727,7 @@ describe("Settings Screen", () => {
it("should properly save basic LLM model settings", async () => {
const user = userEvent.setup();
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
});
renderSettingsScreen();
@@ -764,7 +763,7 @@ describe("Settings Screen", () => {
it("should reset the settings when the 'Reset to defaults' button is clicked", async () => {
const user = userEvent.setup();
getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS);
getSettingsSpy.mockResolvedValue(DEFAULT_SETTINGS);
renderSettingsScreen();
@@ -795,7 +794,7 @@ describe("Settings Screen", () => {
// Mock the settings response after reset
getSettingsSpy.mockResolvedValueOnce({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
llm_base_url: "",
confirmation_mode: false,
security_analyzer: "",
@@ -820,7 +819,7 @@ describe("Settings Screen", () => {
it("should cancel the reset when the 'Cancel' button is clicked", async () => {
const user = userEvent.setup();
getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS);
getSettingsSpy.mockResolvedValue(DEFAULT_SETTINGS);
renderSettingsScreen();
@@ -928,7 +927,7 @@ describe("Settings Screen", () => {
it("should not send an empty LLM API Key if the user submits an empty string but already has it set", async () => {
const user = userEvent.setup();
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
...DEFAULT_SETTINGS,
llm_api_key_set: true,
});
@@ -12,7 +12,7 @@ describe("hasAdvancedSettingsSet", () => {
expect(
hasAdvancedSettingsSet({
...DEFAULT_SETTINGS,
LLM_BASE_URL: "test",
llm_base_url: "test",
}),
).toBe(true);
});
@@ -21,7 +21,7 @@ describe("hasAdvancedSettingsSet", () => {
expect(
hasAdvancedSettingsSet({
...DEFAULT_SETTINGS,
AGENT: "test",
agent: "test",
}),
).toBe(true);
});
@@ -30,7 +30,7 @@ describe("hasAdvancedSettingsSet", () => {
expect(
hasAdvancedSettingsSet({
...DEFAULT_SETTINGS,
REMOTE_RUNTIME_RESOURCE_FACTOR: 999,
remote_runtime_resource_factor: 999,
}),
).toBe(true);
});
@@ -39,7 +39,7 @@ describe("hasAdvancedSettingsSet", () => {
expect(
hasAdvancedSettingsSet({
...DEFAULT_SETTINGS,
CONFIRMATION_MODE: true,
confirmation_mode: true,
}),
).toBe(true);
});
@@ -48,7 +48,7 @@ describe("hasAdvancedSettingsSet", () => {
expect(
hasAdvancedSettingsSet({
...DEFAULT_SETTINGS,
SECURITY_ANALYZER: "test",
security_analyzer: "test",
}),
).toBe(true);
});
-28
View File
@@ -10,7 +10,6 @@ import {
GetTrajectoryResponse,
} from "./open-hands.types";
import { openHands } from "./open-hands-axios";
import { ApiSettings, PostApiSettings } from "#/types/settings";
import { GitUser, GitRepository } from "#/types/git";
class OpenHands {
@@ -178,33 +177,6 @@ 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<PostApiSettings>,
): Promise<boolean> {
const data = await openHands.post("/api/settings", settings);
return data.status === 200;
}
/**
* Reset user settings in server
*/
static async resetSettings(): Promise<boolean> {
const response = await openHands.post("/api/reset-settings");
return response.status === 200;
}
static async createCheckoutSession(amount: number): Promise<string> {
const { data } = await openHands.post(
"/api/billing/create-checkout-session",
@@ -0,0 +1,29 @@
import { openHands } from "../open-hands-axios";
import { UserSettings } from "./settings-service.types";
export class SettingsService {
/**
* Get the user's settings
*/
static async getSettings(): Promise<UserSettings> {
const { data } = await openHands.get<UserSettings>("/api/settings");
return data;
}
/**
* Save valid settings to the server
* @param settings - The settings to save
*/
static async saveSettings(settings: Partial<UserSettings>): Promise<boolean> {
const data = await openHands.post("/api/settings", settings);
return data.status === 200;
}
/**
* Reset the user's settings
*/
static async resetSettings(): Promise<boolean> {
const response = await openHands.post("/api/settings/reset");
return response.status === 200;
}
}
@@ -0,0 +1,27 @@
export type GitProvider = "github" | "gitlab";
export interface UserSettings {
llm_model: string;
llm_base_url: string;
agent: string;
language: string;
llm_api_key_set: boolean;
confirmation_mode: boolean;
security_analyzer: string;
remote_runtime_resource_factor: number | null;
github_token_is_set: boolean;
enable_default_condenser: boolean;
enable_sound_notifications: boolean;
user_consents_to_analytics: boolean | null;
provider_tokens_set: Record<GitProvider, boolean>;
}
// These are settings that are only used on the client side and should not be sent to the server
export interface ClientUserSettings extends UserSettings {
is_new_user: boolean;
}
// These are settings that are used on the server side and should be sent to the server
export interface ServerUserSettings extends UserSettings {
github_token: string | null;
}
@@ -9,15 +9,16 @@ import { extractSettings } from "#/utils/settings-utils";
import { useEndSession } from "#/hooks/use-end-session";
import { ModalBackdrop } from "../modal-backdrop";
import { ModelSelector } from "./model-selector";
import { Settings } from "#/types/settings";
import { BrandButton } from "#/components/features/settings/brand-button";
import { KeyStatusIcon } from "#/components/features/settings/key-status-icon";
import { SettingsInput } from "#/components/features/settings/settings-input";
import { HelpLink } from "#/components/features/settings/help-link";
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";
import { UserSettings } from "#/api/settings-service/settings-service.types";
import { DEFAULT_SETTINGS } from "#/services/settings";
interface SettingsFormProps {
settings: Settings;
settings: UserSettings | undefined;
models: string[];
onClose: () => void;
}
@@ -42,17 +43,16 @@ export function SettingsForm({ settings, models, onClose }: SettingsFormProps) {
const handleFormSubmission = async (formData: FormData) => {
const newSettings = extractSettings(formData);
await saveUserSettings(newSettings, {
saveUserSettings(newSettings, {
onSuccess: () => {
onClose();
resetOngoingSession();
posthog.capture("settings_saved", {
LLM_MODEL: newSettings.LLM_MODEL,
LLM_API_KEY_SET: newSettings.LLM_API_KEY_SET ? "SET" : "UNSET",
LLM_MODEL: newSettings.llm_model,
LLM_API_KEY: newSettings.llm_api_key_set ? "SET" : "UNSET",
REMOTE_RUNTIME_RESOURCE_FACTOR:
newSettings.REMOTE_RUNTIME_RESOURCE_FACTOR,
newSettings.remote_runtime_resource_factor,
});
},
});
@@ -74,7 +74,7 @@ export function SettingsForm({ settings, models, onClose }: SettingsFormProps) {
}
};
const isLLMKeySet = settings.LLM_API_KEY_SET;
const isLLMKeySet = !!settings?.llm_api_key_set;
return (
<div>
@@ -87,7 +87,7 @@ export function SettingsForm({ settings, models, onClose }: SettingsFormProps) {
<div className="flex flex-col gap-4">
<ModelSelector
models={organizeModelsAndProviders(models)}
currentModel={settings.LLM_MODEL}
currentModel={settings?.llm_model || DEFAULT_SETTINGS.llm_model}
/>
<SettingsInput
@@ -5,11 +5,10 @@ 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";
import { DEFAULT_SETTINGS } from "#/services/settings";
import { UserSettings } from "#/api/settings-service/settings-service.types";
interface SettingsModalProps {
settings?: Settings;
settings: UserSettings | undefined;
onClose: () => void;
}
@@ -47,7 +46,7 @@ export function SettingsModal({ onClose, settings }: SettingsModalProps) {
)}
{aiConfigOptions.data && (
<SettingsForm
settings={settings || DEFAULT_SETTINGS}
settings={settings}
models={aiConfigOptions.data?.models}
onClose={onClose}
/>
@@ -1,37 +1,29 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { DEFAULT_SETTINGS } from "#/services/settings";
import OpenHands from "#/api/open-hands";
import { PostSettings, PostApiSettings } from "#/types/settings";
import { useSettings } from "../query/use-settings";
import { UserSettings } from "#/api/settings-service/settings-service.types";
import { SettingsService } from "#/api/settings-service/settings-service.api";
const saveSettingsMutationFn = async (
settings: Partial<PostSettings> | null,
settings: Partial<UserSettings> | null,
) => {
// If settings is null, we're resetting
if (settings === null) {
await OpenHands.resetSettings();
await SettingsService.resetSettings();
return;
}
const apiSettings: Partial<PostApiSettings> = {
llm_model: settings.LLM_MODEL,
llm_base_url: settings.LLM_BASE_URL,
agent: settings.AGENT || DEFAULT_SETTINGS.AGENT,
language: settings.LANGUAGE || DEFAULT_SETTINGS.LANGUAGE,
confirmation_mode: settings.CONFIRMATION_MODE,
security_analyzer: settings.SECURITY_ANALYZER,
llm_api_key:
settings.llm_api_key === ""
const safeSettings: Partial<UserSettings> = {
...settings,
agent: settings.agent || DEFAULT_SETTINGS.agent,
language: settings.language || DEFAULT_SETTINGS.language,
llm_api_key_set:
settings.llm_api_key_set === ""
? ""
: settings.llm_api_key?.trim() || undefined,
remote_runtime_resource_factor: settings.REMOTE_RUNTIME_RESOURCE_FACTOR,
enable_default_condenser: settings.ENABLE_DEFAULT_CONDENSER,
enable_sound_notifications: settings.ENABLE_SOUND_NOTIFICATIONS,
user_consents_to_analytics: settings.user_consents_to_analytics,
provider_tokens: settings.provider_tokens,
: settings.llm_api_key_set?.trim() || undefined,
};
await OpenHands.saveSettings(apiSettings);
await SettingsService.saveSettings(safeSettings);
};
export const useSaveSettings = () => {
@@ -39,7 +31,7 @@ export const useSaveSettings = () => {
const { data: currentSettings } = useSettings();
return useMutation({
mutationFn: async (settings: Partial<PostSettings> | null) => {
mutationFn: async (settings: Partial<UserSettings> | null) => {
if (settings === null) {
await saveSettingsMutationFn(null);
return;
+15 -36
View File
@@ -1,29 +1,14 @@
import { useQuery } from "@tanstack/react-query";
import React from "react";
import posthog from "posthog-js";
import OpenHands from "#/api/open-hands";
import { useAuth } from "#/context/auth-context";
import { SettingsService } from "#/api/settings-service/settings-service.api";
import { ClientUserSettings } from "#/api/settings-service/settings-service.types";
import { DEFAULT_SETTINGS } from "#/services/settings";
const getSettingsQueryFn = async () => {
const apiSettings = await OpenHands.getSettings();
return {
LLM_MODEL: apiSettings.llm_model,
LLM_BASE_URL: apiSettings.llm_base_url,
AGENT: apiSettings.agent,
LANGUAGE: apiSettings.language,
CONFIRMATION_MODE: apiSettings.confirmation_mode,
SECURITY_ANALYZER: apiSettings.security_analyzer,
LLM_API_KEY_SET: apiSettings.llm_api_key_set,
REMOTE_RUNTIME_RESOURCE_FACTOR: apiSettings.remote_runtime_resource_factor,
PROVIDER_TOKENS_SET: apiSettings.provider_tokens_set,
ENABLE_DEFAULT_CONDENSER: apiSettings.enable_default_condenser,
ENABLE_SOUND_NOTIFICATIONS: apiSettings.enable_sound_notifications,
USER_CONSENTS_TO_ANALYTICS: apiSettings.user_consents_to_analytics,
PROVIDER_TOKENS: apiSettings.provider_tokens,
IS_NEW_USER: false,
};
const settingsQueryFn = async (): Promise<ClientUserSettings> => {
const settings = await SettingsService.getSettings();
return { ...settings, is_new_user: false };
};
export const useSettings = () => {
@@ -32,7 +17,7 @@ export const useSettings = () => {
const query = useQuery({
queryKey: ["settings", providerTokensSet],
queryFn: getSettingsQueryFn,
queryFn: settingsQueryFn,
// Only retry if the error is not a 404 because we
// would want to show the modal immediately if the
// settings are not found
@@ -45,42 +30,36 @@ export const useSettings = () => {
});
React.useEffect(() => {
if (query.isFetched && query.data?.LLM_API_KEY_SET) {
if (query.isFetched && query.data?.llm_api_key_set) {
posthog.capture("user_activated");
}
}, [query.data?.LLM_API_KEY_SET, query.isFetched]);
}, [query.data?.llm_api_key_set, query.isFetched]);
React.useEffect(() => {
if (query.data?.PROVIDER_TOKENS_SET) {
const providers = query.data.PROVIDER_TOKENS_SET;
if (query.isFetched && query.data?.provider_tokens_set) {
const providers = query.data.provider_tokens_set;
const setProviders = (
Object.keys(providers) as Array<keyof typeof providers>
).filter((key) => providers[key]);
setProviderTokensSet(setProviders);
const atLeastOneSet = Object.values(query.data.PROVIDER_TOKENS_SET).some(
const atLeastOneSet = Object.values(query.data.provider_tokens_set).some(
(value) => value,
);
setProvidersAreSet(atLeastOneSet);
}
}, [query.data?.PROVIDER_TOKENS_SET, query.isFetched]);
}, [query.data?.provider_tokens_set, query.isFetched]);
// We want to return the defaults if the settings aren't found so the user can still see the
// options to make their initial save. We don't set the defaults in `initialData` above because
// that would prepopulate the data to the cache and mess with expectations. Read more:
// https://tanstack.com/query/latest/docs/framework/react/guides/initial-query-data#using-initialdata-to-prepopulate-a-query
if (query.error?.status === 404) {
// Create a new object with only the properties we need, avoiding rest destructuring
// Object rest destructuring on a query will observe all changes to the query, leading to excessive re-renders.
// Only return the specific properties we need to avoid this.
return {
data: DEFAULT_SETTINGS,
error: query.error,
isError: query.isError,
isLoading: query.isLoading,
isFetching: query.isFetching,
isFetched: query.isFetched,
isFetching: query.isFetching,
isSuccess: query.isSuccess,
status: query.status,
fetchStatus: query.fetchStatus,
refetch: query.refetch,
};
}
@@ -15,7 +15,7 @@ export const useMigrateUserConsent = () => {
if (userAnalyticsConsent) {
args?.handleAnalyticsWasPresentInLocalStorage();
await saveUserSettings(
saveUserSettings(
{ user_consents_to_analytics: userAnalyticsConsent === "true" },
{
onSuccess: () => {
+2 -2
View File
@@ -26,7 +26,7 @@ export const useNotification = () => {
// 4. Not a settings-related notification
if (
options?.playSound === true && // Must be explicitly true
settings?.ENABLE_SOUND_NOTIFICATIONS &&
settings?.enable_sound_notifications &&
audioRef.current &&
!title.includes("BUTTON$") // Don't play for button/settings actions
) {
@@ -49,7 +49,7 @@ export const useNotification = () => {
return undefined;
},
[settings?.ENABLE_SOUND_NOTIFICATIONS],
[settings?.enable_sound_notifications],
);
return { notify };
+2 -66
View File
@@ -4,36 +4,11 @@ import {
Conversation,
ResultSet,
} from "#/api/open-hands.types";
import { DEFAULT_SETTINGS } from "#/services/settings";
import { STRIPE_BILLING_HANDLERS } from "./billing-handlers";
import { ApiSettings, PostApiSettings } from "#/types/settings";
import { SETTINGS_HANDLERS } from "./settings-handlers";
import { FILE_SERVICE_HANDLERS } from "./file-service-handlers";
import { GitUser } from "#/types/git";
export const MOCK_DEFAULT_USER_SETTINGS: ApiSettings | PostApiSettings = {
llm_model: DEFAULT_SETTINGS.LLM_MODEL,
llm_base_url: DEFAULT_SETTINGS.LLM_BASE_URL,
llm_api_key: null,
llm_api_key_set: DEFAULT_SETTINGS.LLM_API_KEY_SET,
agent: DEFAULT_SETTINGS.AGENT,
language: DEFAULT_SETTINGS.LANGUAGE,
confirmation_mode: DEFAULT_SETTINGS.CONFIRMATION_MODE,
security_analyzer: DEFAULT_SETTINGS.SECURITY_ANALYZER,
remote_runtime_resource_factor:
DEFAULT_SETTINGS.REMOTE_RUNTIME_RESOURCE_FACTOR,
provider_tokens_set: DEFAULT_SETTINGS.PROVIDER_TOKENS_SET,
enable_default_condenser: DEFAULT_SETTINGS.ENABLE_DEFAULT_CONDENSER,
enable_sound_notifications: DEFAULT_SETTINGS.ENABLE_SOUND_NOTIFICATIONS,
user_consents_to_analytics: DEFAULT_SETTINGS.USER_CONSENTS_TO_ANALYTICS,
provider_tokens: DEFAULT_SETTINGS.PROVIDER_TOKENS,
};
const MOCK_USER_PREFERENCES: {
settings: ApiSettings | PostApiSettings | null;
} = {
settings: null,
};
const conversations: Conversation[] = [
{
conversation_id: "1",
@@ -104,6 +79,7 @@ const openHandsHandlers = [
export const handlers = [
...STRIPE_BILLING_HANDLERS,
...SETTINGS_HANDLERS,
...FILE_SERVICE_HANDLERS,
...openHandsHandlers,
http.get("/api/user/repositories", () =>
@@ -146,40 +122,6 @@ export const handlers = [
return HttpResponse.json(config);
}),
http.get("/api/settings", async () => {
await delay();
const { settings } = MOCK_USER_PREFERENCES;
if (!settings) return HttpResponse.json(null, { status: 404 });
if (Object.keys(settings.provider_tokens_set).length > 0)
settings.provider_tokens_set = { github: false, gitlab: false };
return HttpResponse.json(settings);
}),
http.post("/api/settings", async ({ request }) => {
const body = await request.json();
if (body) {
let newSettings: Partial<PostApiSettings> = {};
if (typeof body === "object") {
newSettings = { ...body };
}
const fullSettings = {
...MOCK_DEFAULT_USER_SETTINGS,
...MOCK_USER_PREFERENCES.settings,
...newSettings,
};
MOCK_USER_PREFERENCES.settings = fullSettings;
return HttpResponse.json(null, { status: 200 });
}
return HttpResponse.json(null, { status: 400 });
}),
http.post("/api/authenticate", async () =>
HttpResponse.json({ message: "Authenticated" }),
),
@@ -260,10 +202,4 @@ export const handlers = [
}),
http.post("/api/logout", () => HttpResponse.json(null, { status: 200 })),
http.post("/api/reset-settings", async () => {
await delay();
MOCK_USER_PREFERENCES.settings = { ...MOCK_DEFAULT_USER_SETTINGS };
return HttpResponse.json(null, { status: 200 });
}),
];
+52
View File
@@ -0,0 +1,52 @@
import { delay, http, HttpResponse } from "msw";
import { UserSettings } from "#/api/settings-service/settings-service.types";
import { DEFAULT_SETTINGS } from "#/services/settings";
const MOCK_USER_PREFERENCES: {
settings: UserSettings | null;
} = {
settings: null,
};
export const SETTINGS_HANDLERS = [
http.get("/api/settings", async () => {
await delay();
const { settings } = MOCK_USER_PREFERENCES;
if (!settings) return HttpResponse.json(null, { status: 404 });
if (Object.keys(settings.provider_tokens_set).length > 0)
settings.github_token_is_set = true;
return HttpResponse.json(settings);
}),
http.post("/api/settings", async ({ request }) => {
const body = await request.json();
if (body) {
let newSettings: Partial<UserSettings> = {};
if (typeof body === "object") {
newSettings = { ...body };
}
const fullSettings: UserSettings = {
...DEFAULT_SETTINGS,
...MOCK_USER_PREFERENCES.settings,
...newSettings,
};
MOCK_USER_PREFERENCES.settings = fullSettings;
return HttpResponse.json(null, { status: 200 });
}
return HttpResponse.json(null, { status: 400 });
}),
http.post("/api/settings/reset", async () => {
await delay();
MOCK_USER_PREFERENCES.settings = { ...DEFAULT_SETTINGS };
return HttpResponse.json(null, { status: 200 });
}),
];
+34 -29
View File
@@ -27,6 +27,7 @@ import {
displayErrorToast,
displaySuccessToast,
} from "#/utils/custom-toast-handlers";
import { ServerUserSettings } from "#/api/settings-service/settings-service.types";
import { ProviderOptions } from "#/types/settings";
import { useAuth } from "#/context/auth-context";
@@ -67,9 +68,10 @@ function AccountSettings() {
if (isSuccess) {
return (
isCustomModel(resources.models, settings.LLM_MODEL) ||
isCustomModel(resources.models, settings.llm_model) ||
hasAdvancedSettingsSet({
...settings,
provider_tokens_set: settings.provider_tokens_set || {},
})
);
}
@@ -78,12 +80,12 @@ function AccountSettings() {
};
const hasAppSlug = !!config?.APP_SLUG;
const isLLMKeySet = settings?.llm_api_key_set === "**********";
const isAnalyticsEnabled = settings?.user_consents_to_analytics;
const isGitHubTokenSet =
providerTokensSet.includes(ProviderOptions.github) || false;
const isGitLabTokenSet =
providerTokensSet.includes(ProviderOptions.gitlab) || false;
const isLLMKeySet = settings?.LLM_API_KEY_SET;
const isAnalyticsEnabled = settings?.USER_CONSENTS_TO_ANALYTICS;
const isAdvancedSettingsSet = determineWhetherToToggleAdvancedSettings();
const modelsAndProviders = organizeModelsAndProviders(
@@ -94,7 +96,7 @@ function AccountSettings() {
"basic" | "advanced"
>(isAdvancedSettingsSet ? "advanced" : "basic");
const [confirmationModeIsEnabled, setConfirmationModeIsEnabled] =
React.useState(!!settings?.SECURITY_ANALYZER);
React.useState(!!settings?.security_analyzer);
const [resetSettingsModalIsOpen, setResetSettingsModalIsOpen] =
React.useState(false);
@@ -117,6 +119,10 @@ function AccountSettings() {
const remoteRuntimeResourceFactor = REMOTE_RUNTIME_OPTIONS.find(
({ label }) => label === rawRemoteRuntimeResourceFactor,
)?.key;
const remoteRuntimeResourceFactorValue = parseInt(
remoteRuntimeResourceFactor || "",
10,
);
const userConsentsToAnalytics =
formData.get("enable-analytics-switch")?.toString() === "on";
@@ -142,7 +148,7 @@ function AccountSettings() {
: llmBaseUrl;
const finalLlmApiKey = shouldHandleSpecialSaasCase ? undefined : llmApiKey;
const newSettings = {
const newSettings: Partial<ServerUserSettings> = {
provider_tokens:
githubToken || gitlabToken
? {
@@ -150,21 +156,20 @@ function AccountSettings() {
gitlab: gitlabToken || "",
}
: undefined,
LANGUAGE: languageValue,
language: languageValue,
user_consents_to_analytics: userConsentsToAnalytics,
ENABLE_DEFAULT_CONDENSER: enableMemoryCondenser,
ENABLE_SOUND_NOTIFICATIONS: enableSoundNotifications,
LLM_MODEL: finalLlmModel,
LLM_BASE_URL: finalLlmBaseUrl,
llm_api_key: finalLlmApiKey,
AGENT: formData.get("agent-input")?.toString(),
SECURITY_ANALYZER:
enable_default_condenser: enableMemoryCondenser,
enable_sound_notifications: enableSoundNotifications,
llm_model: finalLlmModel,
llm_base_url: finalLlmBaseUrl,
llm_api_key_set: finalLlmApiKey,
agent: formData.get("agent-input")?.toString(),
security_analyzer:
formData.get("security-analyzer-input")?.toString() || "",
REMOTE_RUNTIME_RESOURCE_FACTOR:
remoteRuntimeResourceFactor !== null
? Number(remoteRuntimeResourceFactor)
: DEFAULT_SETTINGS.REMOTE_RUNTIME_RESOURCE_FACTOR,
CONFIRMATION_MODE: confirmationModeIsEnabled,
remote_runtime_resource_factor:
remoteRuntimeResourceFactorValue ||
DEFAULT_SETTINGS.remote_runtime_resource_factor,
confirmation_mode: confirmationModeIsEnabled,
};
saveSettings(newSettings, {
@@ -205,7 +210,7 @@ function AccountSettings() {
setLlmConfigMode(isToggled ? "advanced" : "basic");
if (!isToggled) {
// reset advanced state
setConfirmationModeIsEnabled(!!settings?.SECURITY_ANALYZER);
setConfirmationModeIsEnabled(!!settings?.security_analyzer);
}
};
@@ -249,7 +254,7 @@ function AccountSettings() {
{llmConfigMode === "basic" && !shouldHandleSpecialSaasCase && (
<ModelSelector
models={modelsAndProviders}
currentModel={settings.LLM_MODEL}
currentModel={settings.llm_model}
/>
)}
@@ -258,7 +263,7 @@ function AccountSettings() {
testId="llm-custom-model-input"
name="llm-custom-model-input"
label={t(I18nKey.SETTINGS$CUSTOM_MODEL)}
defaultValue={settings.LLM_MODEL}
defaultValue={settings.llm_model}
placeholder="anthropic/claude-3-5-sonnet-20241022"
type="text"
className="w-[680px]"
@@ -269,7 +274,7 @@ function AccountSettings() {
testId="base-url-input"
name="base-url-input"
label={t(I18nKey.SETTINGS$BASE_URL)}
defaultValue={settings.LLM_BASE_URL}
defaultValue={settings.llm_base_url}
placeholder="https://api.openai.com"
type="text"
className="w-[680px]"
@@ -310,7 +315,7 @@ function AccountSettings() {
label: agent,
})) || []
}
defaultSelectedKey={settings.AGENT}
defaultSelectedKey={settings.agent}
isClearable={false}
/>
)}
@@ -329,7 +334,7 @@ function AccountSettings() {
</>
}
items={REMOTE_RUNTIME_OPTIONS}
defaultSelectedKey={settings.REMOTE_RUNTIME_RESOURCE_FACTOR?.toString()}
defaultSelectedKey={settings.remote_runtime_resource_factor?.toString()}
isDisabled
isClearable={false}
/>
@@ -339,7 +344,7 @@ function AccountSettings() {
<SettingsSwitch
testId="enable-confirmation-mode-switch"
onToggle={setConfirmationModeIsEnabled}
defaultIsToggled={!!settings.CONFIRMATION_MODE}
defaultIsToggled={!!settings.confirmation_mode}
isBeta
>
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
@@ -350,7 +355,7 @@ function AccountSettings() {
<SettingsSwitch
testId="enable-memory-condenser-switch"
name="enable-memory-condenser-switch"
defaultIsToggled={!!settings.ENABLE_DEFAULT_CONDENSER}
defaultIsToggled={!!settings.enable_default_condenser}
>
{t(I18nKey.SETTINGS$ENABLE_MEMORY_CONDENSATION)}
</SettingsSwitch>
@@ -368,7 +373,7 @@ function AccountSettings() {
label: analyzer,
})) || []
}
defaultSelectedKey={settings.SECURITY_ANALYZER}
defaultSelectedKey={settings.security_analyzer}
isClearable
showOptionalTag
/>
@@ -501,7 +506,7 @@ function AccountSettings() {
key: language.value,
label: language.label,
}))}
defaultSelectedKey={settings.LANGUAGE}
defaultSelectedKey={settings.language}
isClearable={false}
/>
@@ -516,7 +521,7 @@ function AccountSettings() {
<SettingsSwitch
testId="enable-sound-notifications-switch"
name="enable-sound-notifications-switch"
defaultIsToggled={!!settings.ENABLE_SOUND_NOTIFICATIONS}
defaultIsToggled={!!settings.enable_sound_notifications}
>
{t(I18nKey.SETTINGS$SOUND_NOTIFICATIONS)}
</SettingsSwitch>
+2 -2
View File
@@ -198,13 +198,13 @@ function AppContent() {
<Controls
setSecurityOpen={onSecurityModalOpen}
showSecurityLock={!!settings?.SECURITY_ANALYZER}
showSecurityLock={!!settings?.security_analyzer}
/>
{settings && (
<Security
isOpen={securityModalIsOpen}
onOpenChange={onSecurityModalOpenChange}
securityAnalyzer={settings.SECURITY_ANALYZER}
securityAnalyzer={settings.security_analyzer}
/>
)}
</div>
+7 -7
View File
@@ -60,8 +60,8 @@ export default function MainApp() {
const navigate = useNavigate();
const { pathname } = useLocation();
const [searchParams] = useSearchParams();
const { data: settings, isFetched: settingsIsFetched } = useSettings();
const { providersAreSet } = useAuth();
const { data: settings } = useSettings();
const { error, isFetching } = useBalance();
const { migrateUserConsent } = useMigrateUserConsent();
const { t } = useTranslation();
@@ -81,17 +81,17 @@ export default function MainApp() {
const [consentFormIsOpen, setConsentFormIsOpen] = React.useState(false);
React.useEffect(() => {
if (settings?.LANGUAGE) {
i18n.changeLanguage(settings.LANGUAGE);
if (settingsIsFetched && settings?.language) {
i18n.changeLanguage(settings.language);
}
}, [settings?.LANGUAGE]);
}, [settingsIsFetched, settings?.language]);
React.useEffect(() => {
const consentFormModalIsOpen =
settings?.USER_CONSENTS_TO_ANALYTICS === null;
settingsIsFetched && settings?.user_consents_to_analytics === null;
setConsentFormIsOpen(consentFormModalIsOpen);
}, [settings]);
}, [settingsIsFetched, settings?.user_consents_to_analytics]);
React.useEffect(() => {
// Migrate user consent to the server if it was previously stored in localStorage
@@ -148,7 +148,7 @@ export default function MainApp() {
{config.data?.FEATURE_FLAGS.ENABLE_BILLING &&
config.data?.APP_MODE === "saas" &&
settings?.IS_NEW_USER && <SetupPaymentModal />}
settings?.is_new_user && <SetupPaymentModal />}
</div>
);
}
+19 -19
View File
@@ -1,28 +1,28 @@
import { Settings } from "#/types/settings";
import { ClientUserSettings } from "#/api/settings-service/settings-service.types";
export const LATEST_SETTINGS_VERSION = 5;
export const DEFAULT_SETTINGS: Settings = {
LLM_MODEL: "anthropic/claude-3-5-sonnet-20241022",
LLM_BASE_URL: "",
AGENT: "CodeActAgent",
LANGUAGE: "en",
LLM_API_KEY_SET: false,
CONFIRMATION_MODE: false,
SECURITY_ANALYZER: "",
REMOTE_RUNTIME_RESOURCE_FACTOR: 1,
PROVIDER_TOKENS_SET: { github: false, gitlab: false },
ENABLE_DEFAULT_CONDENSER: true,
ENABLE_SOUND_NOTIFICATIONS: false,
USER_CONSENTS_TO_ANALYTICS: false,
PROVIDER_TOKENS: {
github: "",
gitlab: "",
export const DEFAULT_SETTINGS: ClientUserSettings = {
llm_model: "anthropic/claude-3-5-sonnet-20241022",
llm_base_url: "",
agent: "CodeActAgent",
language: "en",
llm_api_key_set: null,
confirmation_mode: false,
security_analyzer: "",
remote_runtime_resource_factor: 1,
github_token_is_set: false,
enable_default_condenser: true,
enable_sound_notifications: false,
user_consents_to_analytics: false,
provider_tokens_set: {
github: false,
gitlab: false,
},
IS_NEW_USER: true,
is_new_user: true,
};
/**
* Get the default settings
*/
export const getDefaultSettings = (): Settings => DEFAULT_SETTINGS;
export const getDefaultSettings = () => DEFAULT_SETTINGS;
-38
View File
@@ -1,38 +0,0 @@
import { Provider } from "#/types/settings";
interface GitHubErrorReponse {
message: string;
documentation_url: string;
status: number;
}
interface GitUser {
id: number;
login: string;
avatar_url: string;
company: string | null;
name: string | null;
email: string | null;
}
interface GitRepository {
id: number;
full_name: string;
git_provider: Provider;
stargazers_count?: number;
link_header?: string;
}
interface GitHubCommit {
html_url: string;
sha: string;
commit: {
author: {
date: string; // ISO 8601
};
};
}
interface GithubAppInstallation {
installations: { id: number }[];
}
+24
View File
@@ -0,0 +1,24 @@
import { GitProvider } from "#/api/settings-service/settings-service.types";
export interface GitHubErrorReponse {
message: string;
documentation_url: string;
status: number;
}
export interface GitUser {
id: number;
login: string;
avatar_url: string;
company: string | null;
name: string | null;
email: string | null;
}
export interface GitRepository {
id: number;
full_name: string;
git_provider: GitProvider;
stargazers_count?: number;
link_header?: string;
}
@@ -1,10 +1,10 @@
import { UserSettings } from "#/api/settings-service/settings-service.types";
import { DEFAULT_SETTINGS } from "#/services/settings";
import { Settings } from "#/types/settings";
export const hasAdvancedSettingsSet = (settings: Partial<Settings>): boolean =>
!!settings.LLM_BASE_URL ||
settings.AGENT !== DEFAULT_SETTINGS.AGENT ||
settings.REMOTE_RUNTIME_RESOURCE_FACTOR !==
DEFAULT_SETTINGS.REMOTE_RUNTIME_RESOURCE_FACTOR ||
settings.CONFIRMATION_MODE ||
!!settings.SECURITY_ANALYZER;
export const hasAdvancedSettingsSet = (settings: UserSettings): boolean =>
!!settings.llm_base_url ||
settings.agent !== DEFAULT_SETTINGS.agent ||
settings.remote_runtime_resource_factor !==
DEFAULT_SETTINGS.remote_runtime_resource_factor ||
settings.confirmation_mode ||
!!settings.security_analyzer;
+11 -14
View File
@@ -1,4 +1,4 @@
import { Settings } from "#/types/settings";
import { UserSettings } from "#/api/settings-service/settings-service.types";
const extractBasicFormData = (formData: FormData) => {
const provider = formData.get("llm-provider-input")?.toString();
@@ -47,9 +47,7 @@ const extractAdvancedFormData = (formData: FormData) => {
};
};
export const extractSettings = (
formData: FormData,
): Partial<Settings> & { llm_api_key?: string | null } => {
export const extractSettings = (formData: FormData): Partial<UserSettings> => {
const { LLM_MODEL, LLM_API_KEY, AGENT, LANGUAGE } =
extractBasicFormData(formData);
@@ -74,15 +72,14 @@ export const extractSettings = (
}
return {
LLM_MODEL: CUSTOM_LLM_MODEL || LLM_MODEL,
LLM_API_KEY_SET: !!LLM_API_KEY,
AGENT,
LANGUAGE,
LLM_BASE_URL,
CONFIRMATION_MODE,
SECURITY_ANALYZER,
ENABLE_DEFAULT_CONDENSER,
PROVIDER_TOKENS: providerTokens,
llm_api_key: LLM_API_KEY,
llm_model: CUSTOM_LLM_MODEL || LLM_MODEL,
llm_api_key_set: LLM_API_KEY,
agent: AGENT,
language: LANGUAGE,
llm_base_url: LLM_BASE_URL,
confirmation_mode: CONFIRMATION_MODE,
security_analyzer: SECURITY_ANALYZER,
enable_default_condenser: ENABLE_DEFAULT_CONDENSER,
provider_tokens_set: providerTokens,
};
};
+1 -1
View File
@@ -82,7 +82,7 @@ async def unset_settings_tokens(request: Request) -> JSONResponse:
)
@app.post('/reset-settings', response_model=dict[str, str])
@app.post('/settings/reset', response_model=dict[str, str])
async def reset_settings(request: Request) -> JSONResponse:
"""
Resets user settings.