mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Reset the reset (#8079)
This commit is contained in:
parent
4deffa3907
commit
9b1aaa53fe
67
docs/static/openapi.json
vendored
67
docs/static/openapi.json
vendored
@ -1646,6 +1646,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/reset-settings": {
|
||||
"post": {
|
||||
"summary": "Reset settings (Deprecated)",
|
||||
"description": "This endpoint is deprecated and will return a 410 Gone error. Reset functionality has been removed.",
|
||||
"operationId": "resetSettings",
|
||||
"deprecated": true,
|
||||
"responses": {
|
||||
"410": {
|
||||
"description": "Feature removed",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string",
|
||||
"example": "Reset settings functionality has been removed."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/unset-settings-tokens": {
|
||||
"post": {
|
||||
"summary": "Unset settings tokens",
|
||||
@ -1685,45 +1711,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/reset-settings": {
|
||||
"post": {
|
||||
"summary": "Reset settings",
|
||||
"description": "Reset user settings to defaults",
|
||||
"operationId": "resetSettings",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Settings reset successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Error resetting settings",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/options/models": {
|
||||
"get": {
|
||||
"summary": "Get models",
|
||||
@ -2095,4 +2082,4 @@
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,6 @@ const mock_provider_tokens_are_set: Record<Provider, boolean> = {
|
||||
describe("Settings Screen", () => {
|
||||
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
|
||||
const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings");
|
||||
const resetSettingsSpy = vi.spyOn(OpenHands, "resetSettings");
|
||||
const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
|
||||
|
||||
const { handleLogoutMock } = vi.hoisted(() => ({
|
||||
@ -67,7 +66,6 @@ describe("Settings Screen", () => {
|
||||
// Use queryAllByText to handle multiple elements with the same text
|
||||
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");
|
||||
});
|
||||
});
|
||||
@ -542,54 +540,6 @@ describe("Settings Screen", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("resetting settings with no changes but having advanced enabled should hide the advanced items", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
getSettingsSpy.mockResolvedValueOnce({
|
||||
...MOCK_DEFAULT_USER_SETTINGS,
|
||||
});
|
||||
|
||||
renderSettingsScreen();
|
||||
|
||||
await toggleAdvancedSettings(user);
|
||||
|
||||
const resetButton = screen.getByText("BUTTON$RESET_TO_DEFAULTS");
|
||||
await user.click(resetButton);
|
||||
|
||||
// show modal
|
||||
const modal = await screen.findByTestId("reset-modal");
|
||||
expect(modal).toBeInTheDocument();
|
||||
|
||||
// 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,
|
||||
llm_base_url: "",
|
||||
confirmation_mode: false,
|
||||
security_analyzer: "",
|
||||
});
|
||||
|
||||
// confirm reset
|
||||
const confirmButton = within(modal).getByText("Reset");
|
||||
await user.click(confirmButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByTestId("llm-custom-model-input"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId("base-url-input"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("agent-input")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId("security-analyzer-input"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId("enable-confirmation-mode-switch"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should save if only confirmation mode is enabled", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderSettingsScreen();
|
||||
@ -762,81 +712,6 @@ 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);
|
||||
|
||||
renderSettingsScreen();
|
||||
|
||||
const languageInput = await screen.findByTestId("language-input");
|
||||
await user.click(languageInput);
|
||||
|
||||
const norskOption = await screen.findByText("Norsk");
|
||||
await user.click(norskOption);
|
||||
|
||||
expect(languageInput).toHaveValue("Norsk");
|
||||
|
||||
const resetButton = screen.getByText("BUTTON$RESET_TO_DEFAULTS");
|
||||
await user.click(resetButton);
|
||||
|
||||
expect(saveSettingsSpy).not.toHaveBeenCalled();
|
||||
|
||||
// show modal
|
||||
const modal = await screen.findByTestId("reset-modal");
|
||||
expect(modal).toBeInTheDocument();
|
||||
|
||||
// confirm reset
|
||||
const confirmButton = within(modal).getByText("Reset");
|
||||
await user.click(confirmButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(resetSettingsSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Mock the settings response after reset
|
||||
getSettingsSpy.mockResolvedValueOnce({
|
||||
...MOCK_DEFAULT_USER_SETTINGS,
|
||||
llm_base_url: "",
|
||||
confirmation_mode: false,
|
||||
security_analyzer: "",
|
||||
});
|
||||
|
||||
// Wait for the mutation to complete and the modal to be removed
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("reset-modal")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId("llm-custom-model-input"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("base-url-input")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("agent-input")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId("security-analyzer-input"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId("enable-confirmation-mode-switch"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should cancel the reset when the 'Cancel' button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS);
|
||||
|
||||
renderSettingsScreen();
|
||||
|
||||
const resetButton = await screen.findByText("BUTTON$RESET_TO_DEFAULTS");
|
||||
await user.click(resetButton);
|
||||
|
||||
const modal = await screen.findByTestId("reset-modal");
|
||||
expect(modal).toBeInTheDocument();
|
||||
|
||||
const cancelButton = within(modal).getByText("Cancel");
|
||||
await user.click(cancelButton);
|
||||
|
||||
expect(saveSettingsSpy).not.toHaveBeenCalled();
|
||||
expect(screen.queryByTestId("reset-modal")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call handleCaptureConsent with true if the save is successful", async () => {
|
||||
const user = userEvent.setup();
|
||||
const handleCaptureConsentSpy = vi.spyOn(
|
||||
@ -1044,18 +919,5 @@ describe("Settings Screen", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should not submit the unwanted fields when resetting", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderSettingsScreen();
|
||||
|
||||
const resetButton = await screen.findByText("BUTTON$RESET_TO_DEFAULTS");
|
||||
await user.click(resetButton);
|
||||
|
||||
const modal = await screen.findByTestId("reset-modal");
|
||||
const confirmButton = within(modal).getByText("Reset");
|
||||
await user.click(confirmButton);
|
||||
expect(saveSettingsSpy).not.toHaveBeenCalled();
|
||||
expect(resetSettingsSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -199,14 +199,6 @@ class OpenHands {
|
||||
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",
|
||||
|
||||
@ -4,15 +4,7 @@ import OpenHands from "#/api/open-hands";
|
||||
import { PostSettings, PostApiSettings } from "#/types/settings";
|
||||
import { useSettings } from "../query/use-settings";
|
||||
|
||||
const saveSettingsMutationFn = async (
|
||||
settings: Partial<PostSettings> | null,
|
||||
) => {
|
||||
// If settings is null, we're resetting
|
||||
if (settings === null) {
|
||||
await OpenHands.resetSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
const saveSettingsMutationFn = async (settings: Partial<PostSettings>) => {
|
||||
const apiSettings: Partial<PostApiSettings> = {
|
||||
llm_model: settings.LLM_MODEL,
|
||||
llm_base_url: settings.LLM_BASE_URL,
|
||||
@ -39,12 +31,7 @@ export const useSaveSettings = () => {
|
||||
const { data: currentSettings } = useSettings();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (settings: Partial<PostSettings> | null) => {
|
||||
if (settings === null) {
|
||||
await saveSettingsMutationFn(null);
|
||||
return;
|
||||
}
|
||||
|
||||
mutationFn: async (settings: Partial<PostSettings>) => {
|
||||
const newSettings = { ...currentSettings, ...settings };
|
||||
await saveSettingsMutationFn(newSettings);
|
||||
},
|
||||
|
||||
@ -82,7 +82,6 @@ export enum I18nKey {
|
||||
API$DONT_KNOW_KEY = "API$DONT_KNOW_KEY",
|
||||
BUTTON$SAVE = "BUTTON$SAVE",
|
||||
BUTTON$CLOSE = "BUTTON$CLOSE",
|
||||
BUTTON$RESET_TO_DEFAULTS = "BUTTON$RESET_TO_DEFAULTS",
|
||||
MODAL$CONFIRM_RESET_TITLE = "MODAL$CONFIRM_RESET_TITLE",
|
||||
MODAL$CONFIRM_RESET_MESSAGE = "MODAL$CONFIRM_RESET_MESSAGE",
|
||||
MODAL$END_SESSION_TITLE = "MODAL$END_SESSION_TITLE",
|
||||
@ -321,7 +320,6 @@ export enum I18nKey {
|
||||
SETTINGS_FORM$ENABLE_CONFIRMATION_MODE_LABEL = "SETTINGS_FORM$ENABLE_CONFIRMATION_MODE_LABEL",
|
||||
SETTINGS_FORM$SAVE_LABEL = "SETTINGS_FORM$SAVE_LABEL",
|
||||
SETTINGS_FORM$CLOSE_LABEL = "SETTINGS_FORM$CLOSE_LABEL",
|
||||
SETTINGS_FORM$RESET_TO_DEFAULTS_LABEL = "SETTINGS_FORM$RESET_TO_DEFAULTS_LABEL",
|
||||
SETTINGS_FORM$CANCEL_LABEL = "SETTINGS_FORM$CANCEL_LABEL",
|
||||
SETTINGS_FORM$END_SESSION_LABEL = "SETTINGS_FORM$END_SESSION_LABEL",
|
||||
SETTINGS_FORM$CHANGING_WORKSPACE_WARNING_MESSAGE = "SETTINGS_FORM$CHANGING_WORKSPACE_WARNING_MESSAGE",
|
||||
|
||||
@ -1231,21 +1231,6 @@
|
||||
"tr": "Kapat",
|
||||
"de": "Schließen"
|
||||
},
|
||||
"BUTTON$RESET_TO_DEFAULTS": {
|
||||
"en": "Reset to defaults",
|
||||
"ja": "デフォルトにリセット",
|
||||
"zh-CN": "重置为默认值",
|
||||
"zh-TW": "還原為預設值",
|
||||
"ko-KR": "기본값으로 재설정",
|
||||
"no": "Tilbakestill til standard",
|
||||
"it": "Ripristina valori predefiniti",
|
||||
"pt": "Restaurar padrões",
|
||||
"es": "Restablecer valores predeterminados",
|
||||
"ar": "إعادة التعيين إلى الإعدادات الافتراضية",
|
||||
"fr": "Réinitialiser aux valeurs par défaut",
|
||||
"tr": "Varsayılanlara sıfırla",
|
||||
"de": "Auf Standardwerte zurücksetzen"
|
||||
},
|
||||
"MODAL$CONFIRM_RESET_TITLE": {
|
||||
"en": "Are you sure?",
|
||||
"ja": "本当によろしいですか?",
|
||||
@ -4541,21 +4526,6 @@
|
||||
"pt": "Fechar",
|
||||
"tr": "Kapat"
|
||||
},
|
||||
"SETTINGS_FORM$RESET_TO_DEFAULTS_LABEL": {
|
||||
"en": "Reset to defaults",
|
||||
"es": "Reiniciar valores por defect",
|
||||
"zh-CN": "重置为默认值",
|
||||
"zh-TW": "還原為預設值",
|
||||
"ko-KR": "기본값으로 재설정",
|
||||
"ja": "デフォルトに戻す",
|
||||
"no": "Tilbakestill til standardverdier",
|
||||
"ar": "إعادة التعيين إلى الإعدادات الافتراضية",
|
||||
"de": "Auf Standardwerte zurücksetzen",
|
||||
"fr": "Réinitialiser aux valeurs par défaut",
|
||||
"it": "Ripristina valori predefiniti",
|
||||
"pt": "Restaurar padrões",
|
||||
"tr": "Varsayılanlara sıfırla"
|
||||
},
|
||||
"SETTINGS_FORM$CANCEL_LABEL": {
|
||||
"en": "Cancel",
|
||||
"es": "Cancelar",
|
||||
|
||||
@ -9,7 +9,6 @@ import { SettingsDropdownInput } from "#/components/features/settings/settings-d
|
||||
import { SettingsInput } from "#/components/features/settings/settings-input";
|
||||
import { SettingsSwitch } from "#/components/features/settings/settings-switch";
|
||||
import { LoadingSpinner } from "#/components/shared/loading-spinner";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { ModelSelector } from "#/components/shared/modals/settings/model-selector";
|
||||
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";
|
||||
import { useAIConfigOptions } from "#/hooks/query/use-ai-config-options";
|
||||
@ -95,8 +94,6 @@ function AccountSettings() {
|
||||
>(isAdvancedSettingsSet ? "advanced" : "basic");
|
||||
const [confirmationModeIsEnabled, setConfirmationModeIsEnabled] =
|
||||
React.useState(!!settings?.SECURITY_ANALYZER);
|
||||
const [resetSettingsModalIsOpen, setResetSettingsModalIsOpen] =
|
||||
React.useState(false);
|
||||
|
||||
const formRef = React.useRef<HTMLFormElement>(null);
|
||||
|
||||
@ -180,16 +177,6 @@ function AccountSettings() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
saveSettings(null, {
|
||||
onSuccess: () => {
|
||||
displaySuccessToast(t(I18nKey.SETTINGS$RESET));
|
||||
setResetSettingsModalIsOpen(false);
|
||||
setLlmConfigMode("basic");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
// If settings is still loading by the time the state is set, it will always
|
||||
// default to basic settings. This is a workaround to ensure the correct
|
||||
@ -527,13 +514,6 @@ function AccountSettings() {
|
||||
</form>
|
||||
|
||||
<footer className="flex gap-6 p-6 justify-end border-t border-t-tertiary">
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => setResetSettingsModalIsOpen(true)}
|
||||
>
|
||||
{t(I18nKey.BUTTON$RESET_TO_DEFAULTS)}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
@ -544,40 +524,6 @@ function AccountSettings() {
|
||||
{t(I18nKey.BUTTON$SAVE)}
|
||||
</BrandButton>
|
||||
</footer>
|
||||
|
||||
{resetSettingsModalIsOpen && (
|
||||
<ModalBackdrop>
|
||||
<div
|
||||
data-testid="reset-modal"
|
||||
className="bg-base-secondary p-4 rounded-xl flex flex-col gap-4 border border-tertiary"
|
||||
>
|
||||
<p>{t(I18nKey.SETTINGS$RESET_CONFIRMATION)}</p>
|
||||
<div className="w-full flex gap-2">
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
className="grow"
|
||||
onClick={() => {
|
||||
handleReset();
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</BrandButton>
|
||||
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
className="grow"
|
||||
onClick={() => {
|
||||
setResetSettingsModalIsOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBackdrop>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -135,7 +135,6 @@ def event_to_dict(event: 'Event') -> dict:
|
||||
k: (v.value if isinstance(v, Enum) else _convert_pydantic_to_dict(v))
|
||||
for k, v in props.items()
|
||||
}
|
||||
logger.debug(f'extras data in event_to_dict: {d["extras"]}')
|
||||
# Include success field for CmdOutputObservation
|
||||
if hasattr(event, 'success'):
|
||||
d['success'] = event.success
|
||||
|
||||
@ -713,7 +713,7 @@ class LLM(RetryMixin, DebugMixin):
|
||||
completion_response=response, **extra_kwargs
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Error getting cost from litellm: {e}')
|
||||
logger.debug(f'Error getting cost from litellm: {e}')
|
||||
|
||||
if cost is None:
|
||||
_model_name = '/'.join(self.config.model.split('/')[1:])
|
||||
|
||||
@ -207,54 +207,15 @@ async def unset_settings_tokens(request: Request) -> JSONResponse:
|
||||
@app.post('/reset-settings', response_model=dict[str, str])
|
||||
async def reset_settings(request: Request) -> JSONResponse:
|
||||
"""
|
||||
Resets user settings.
|
||||
Resets user settings. (Deprecated)
|
||||
"""
|
||||
try:
|
||||
settings_store = await SettingsStoreImpl.get_instance(
|
||||
config, get_user_id(request)
|
||||
)
|
||||
|
||||
existing_settings: Settings = await settings_store.load()
|
||||
settings = Settings(
|
||||
language='en',
|
||||
agent='CodeActAgent',
|
||||
security_analyzer='',
|
||||
confirmation_mode=False,
|
||||
llm_model='anthropic/claude-3-5-sonnet-20241022',
|
||||
llm_api_key=SecretStr(''),
|
||||
llm_base_url=None,
|
||||
remote_runtime_resource_factor=1,
|
||||
enable_default_condenser=True,
|
||||
enable_sound_notifications=False,
|
||||
user_consents_to_analytics=existing_settings.user_consents_to_analytics
|
||||
if existing_settings
|
||||
else False,
|
||||
secrets_store=existing_settings.secrets_store
|
||||
)
|
||||
|
||||
server_config_values = server_config.get_config()
|
||||
is_hide_llm_settings_enabled = server_config_values.get(
|
||||
'FEATURE_FLAGS', {}
|
||||
).get('HIDE_LLM_SETTINGS', False)
|
||||
# We don't want the user to be able to modify these settings in SaaS
|
||||
if server_config.app_mode == AppMode.SAAS and is_hide_llm_settings_enabled:
|
||||
if existing_settings:
|
||||
settings.llm_api_key = existing_settings.llm_api_key
|
||||
settings.llm_base_url = existing_settings.llm_base_url
|
||||
settings.llm_model = existing_settings.llm_model
|
||||
|
||||
await settings_store.store(settings)
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content={'message': 'Settings stored'},
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f'Something went wrong resetting settings: {e}')
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={'error': 'Something went wrong resetting settings'},
|
||||
)
|
||||
logger.warning(
|
||||
f"Deprecated endpoint /api/reset-settings called by user"
|
||||
)
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_410_GONE,
|
||||
content={'error': 'Reset settings functionality has been removed.'},
|
||||
)
|
||||
|
||||
|
||||
async def check_provider_tokens(request: Request, settings: POSTSettingsModel) -> str:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user