fix(frontend): llm settings view resets to basic after saving (#12097)

This commit is contained in:
Hiep Le 2025-12-22 23:00:57 +07:00 committed by GitHub
parent 5553d3ca2e
commit 10edb28729
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 271 additions and 3 deletions

View File

@ -910,6 +910,162 @@ describe("Form submission", () => {
});
});
describe("View persistence after saving advanced settings", () => {
it("should remain on Advanced view after saving when memory condenser is disabled", async () => {
// Arrange: Start with default settings (basic view)
const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
});
const saveSettingsSpy = vi.spyOn(SettingsService, "saveSettings");
saveSettingsSpy.mockResolvedValue(true);
renderLlmSettingsScreen();
await screen.findByTestId("llm-settings-screen");
// Verify we start in basic view
expect(screen.getByTestId("llm-settings-form-basic")).toBeInTheDocument();
// Act: User manually switches to Advanced view
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
await userEvent.click(advancedSwitch);
await screen.findByTestId("llm-settings-form-advanced");
// User disables memory condenser (advanced-only setting)
const condenserSwitch = screen.getByTestId(
"enable-memory-condenser-switch",
);
expect(condenserSwitch).toBeChecked();
await userEvent.click(condenserSwitch);
expect(condenserSwitch).not.toBeChecked();
// Mock the updated settings that will be returned after save
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
enable_default_condenser: false, // Now disabled
});
// User saves settings
const submitButton = screen.getByTestId("submit-button");
await userEvent.click(submitButton);
// Assert: View should remain on Advanced after save
await waitFor(() => {
expect(
screen.getByTestId("llm-settings-form-advanced"),
).toBeInTheDocument();
expect(
screen.queryByTestId("llm-settings-form-basic"),
).not.toBeInTheDocument();
expect(advancedSwitch).toBeChecked();
});
});
it("should remain on Advanced view after saving when condenser max size is customized", async () => {
// Arrange: Start with default settings
const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
});
const saveSettingsSpy = vi.spyOn(SettingsService, "saveSettings");
saveSettingsSpy.mockResolvedValue(true);
renderLlmSettingsScreen();
await screen.findByTestId("llm-settings-screen");
// Act: User manually switches to Advanced view
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
await userEvent.click(advancedSwitch);
await screen.findByTestId("llm-settings-form-advanced");
// User sets custom condenser max size (advanced-only setting)
const condenserMaxSizeInput = screen.getByTestId(
"condenser-max-size-input",
);
await userEvent.clear(condenserMaxSizeInput);
await userEvent.type(condenserMaxSizeInput, "200");
// Mock the updated settings that will be returned after save
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
condenser_max_size: 200, // Custom value
});
// User saves settings
const submitButton = screen.getByTestId("submit-button");
await userEvent.click(submitButton);
// Assert: View should remain on Advanced after save
await waitFor(() => {
expect(
screen.getByTestId("llm-settings-form-advanced"),
).toBeInTheDocument();
expect(
screen.queryByTestId("llm-settings-form-basic"),
).not.toBeInTheDocument();
expect(advancedSwitch).toBeChecked();
});
});
it("should remain on Advanced view after saving when search API key is set", async () => {
// Arrange: Start with default settings (non-SaaS mode to show search API key field)
const getConfigSpy = vi.spyOn(OptionService, "getConfig");
getConfigSpy.mockResolvedValue({
APP_MODE: "oss",
GITHUB_CLIENT_ID: "fake-github-client-id",
POSTHOG_CLIENT_KEY: "fake-posthog-client-key",
FEATURE_FLAGS: {
ENABLE_BILLING: false,
HIDE_LLM_SETTINGS: false,
ENABLE_JIRA: false,
ENABLE_JIRA_DC: false,
ENABLE_LINEAR: false,
},
});
const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
search_api_key: "", // Default empty value
});
const saveSettingsSpy = vi.spyOn(SettingsService, "saveSettings");
saveSettingsSpy.mockResolvedValue(true);
renderLlmSettingsScreen();
await screen.findByTestId("llm-settings-screen");
// Act: User manually switches to Advanced view
const advancedSwitch = screen.getByTestId("advanced-settings-switch");
await userEvent.click(advancedSwitch);
await screen.findByTestId("llm-settings-form-advanced");
// User sets search API key (advanced-only setting)
const searchApiKeyInput = screen.getByTestId("search-api-key-input");
await userEvent.type(searchApiKeyInput, "test-search-api-key");
// Mock the updated settings that will be returned after save
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
search_api_key: "test-search-api-key", // Now set
});
// User saves settings
const submitButton = screen.getByTestId("submit-button");
await userEvent.click(submitButton);
// Assert: View should remain on Advanced after save
await waitFor(() => {
expect(
screen.getByTestId("llm-settings-form-advanced"),
).toBeInTheDocument();
expect(
screen.queryByTestId("llm-settings-form-basic"),
).not.toBeInTheDocument();
expect(advancedSwitch).toBeChecked();
});
});
});
describe("Status toasts", () => {
describe("Basic form", () => {
it("should call displaySuccessToast when the settings are saved", async () => {

View File

@ -29,5 +29,75 @@ describe("hasAdvancedSettingsSet", () => {
}),
).toBe(true);
});
test("enable_default_condenser is disabled", () => {
// Arrange
const settings = {
...DEFAULT_SETTINGS,
enable_default_condenser: false,
};
// Act
const result = hasAdvancedSettingsSet(settings);
// Assert
expect(result).toBe(true);
});
test("condenser_max_size is customized above default", () => {
// Arrange
const settings = {
...DEFAULT_SETTINGS,
condenser_max_size: 200,
};
// Act
const result = hasAdvancedSettingsSet(settings);
// Assert
expect(result).toBe(true);
});
test("condenser_max_size is customized below default", () => {
// Arrange
const settings = {
...DEFAULT_SETTINGS,
condenser_max_size: 50,
};
// Act
const result = hasAdvancedSettingsSet(settings);
// Assert
expect(result).toBe(true);
});
test("search_api_key is set to non-empty value", () => {
// Arrange
const settings = {
...DEFAULT_SETTINGS,
search_api_key: "test-api-key-123",
};
// Act
const result = hasAdvancedSettingsSet(settings);
// Assert
expect(result).toBe(true);
});
test("search_api_key with whitespace is treated as set", () => {
// Arrange
const settings = {
...DEFAULT_SETTINGS,
search_api_key: " test-key ",
};
// Act
const result = hasAdvancedSettingsSet(settings);
// Assert
expect(result).toBe(true);
});
});
});

View File

@ -1,6 +1,48 @@
import { DEFAULT_SETTINGS } from "#/services/settings";
import { Settings } from "#/types/settings";
export const hasAdvancedSettingsSet = (settings: Partial<Settings>): boolean =>
Object.keys(settings).length > 0 &&
(!!settings.llm_base_url || settings.agent !== DEFAULT_SETTINGS.agent);
/**
* Determines if any advanced-only settings are configured.
* Advanced-only settings are those that appear only in the Advanced Settings view
* and not in the Basic Settings view.
*
* Advanced-only fields:
* - llm_base_url: Custom base URL for LLM API
* - agent: Custom agent selection (when not using default)
* - enable_default_condenser: Memory condenser toggle (when disabled, as default is enabled)
* - condenser_max_size: Custom condenser size (when different from default)
* - search_api_key: Search API key (when set)
*/
export const hasAdvancedSettingsSet = (
settings: Partial<Settings>,
): boolean => {
if (Object.keys(settings).length === 0) {
return false;
}
// Check for advanced-only settings that differ from defaults
const hasBaseUrl =
!!settings.llm_base_url && settings.llm_base_url.trim() !== "";
const hasCustomAgent =
settings.agent !== undefined && settings.agent !== DEFAULT_SETTINGS.agent;
// Default is true, so only check if explicitly disabled
const hasDisabledCondenser = settings.enable_default_condenser === false;
// Check if condenser size differs from default (default is 120)
const hasCustomCondenserSize =
settings.condenser_max_size !== undefined &&
settings.condenser_max_size !== null &&
settings.condenser_max_size !== DEFAULT_SETTINGS.condenser_max_size;
// Check if search API key is set (non-empty string)
const hasSearchApiKey =
settings.search_api_key !== undefined &&
settings.search_api_key !== null &&
settings.search_api_key.trim() !== "";
return (
hasBaseUrl ||
hasCustomAgent ||
hasDisabledCondenser ||
hasCustomCondenserSize ||
hasSearchApiKey
);
};