diff --git a/frontend/src/components/modals/settings/AutocompleteCombobox.test.tsx b/frontend/src/components/modals/settings/AutocompleteCombobox.test.tsx
index e8002ac42a..108ac25871 100644
--- a/frontend/src/components/modals/settings/AutocompleteCombobox.test.tsx
+++ b/frontend/src/components/modals/settings/AutocompleteCombobox.test.tsx
@@ -1,6 +1,7 @@
-import { render, screen, act } from "@testing-library/react";
-import React from "react";
+import { act, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
+import React from "react";
+import { vi } from "vitest";
import { AutocompleteCombobox } from "./AutocompleteCombobox";
const onChangeMock = vi.fn();
@@ -15,6 +16,7 @@ const renderComponent = () =>
{ value: "m3", label: "model3" },
]}
defaultKey="m1"
+ tooltip="tooltip"
onChange={onChangeMock}
/>,
);
@@ -61,4 +63,6 @@ describe("AutocompleteCombobox", () => {
expect(modelInput).toHaveValue("model2");
expect(onChangeMock).toHaveBeenCalledWith("model2");
});
+
+ it.todo("should show a tooltip after 0.5 seconds of focus");
});
diff --git a/frontend/src/components/modals/settings/AutocompleteCombobox.tsx b/frontend/src/components/modals/settings/AutocompleteCombobox.tsx
index 353ea1d407..c4cdc7af17 100644
--- a/frontend/src/components/modals/settings/AutocompleteCombobox.tsx
+++ b/frontend/src/components/modals/settings/AutocompleteCombobox.tsx
@@ -1,7 +1,7 @@
-import { Autocomplete, AutocompleteItem } from "@nextui-org/react";
+import { I18nKey } from "#/i18n/declaration";
+import { Autocomplete, AutocompleteItem, Tooltip } from "@nextui-org/react";
import React from "react";
import { useTranslation } from "react-i18next";
-import { I18nKey } from "#/i18n/declaration";
type Label = "model" | "agent" | "language";
@@ -27,7 +27,9 @@ interface AutocompleteComboboxProps {
items: AutocompleteItemType[];
defaultKey: string;
onChange: (key: string) => void;
+ tooltip: string;
allowCustomValue?: boolean;
+ disabled?: boolean;
}
export function AutocompleteCombobox({
@@ -35,29 +37,43 @@ export function AutocompleteCombobox({
items,
defaultKey,
onChange,
+ tooltip,
allowCustomValue = false,
+ disabled = false,
}: AutocompleteComboboxProps) {
const { t } = useTranslation();
return (
- {
- onChange(value);
- }}
+
- {(item) => (
- {item.label}
- )}
-
+ {
+ onChange(value);
+ }}
+ >
+ {(item) => (
+ {item.label}
+ )}
+
+
);
}
AutocompleteCombobox.defaultProps = {
allowCustomValue: false,
+ disabled: false,
};
diff --git a/frontend/src/components/modals/settings/SettingsForm.test.tsx b/frontend/src/components/modals/settings/SettingsForm.test.tsx
index 212d6aa35b..a144c06426 100644
--- a/frontend/src/components/modals/settings/SettingsForm.test.tsx
+++ b/frontend/src/components/modals/settings/SettingsForm.test.tsx
@@ -1,6 +1,8 @@
-import React from "react";
-import { act, render, screen } from "@testing-library/react";
+import { act, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
+import React from "react";
+import { renderWithProviders } from "test-utils";
+import AgentTaskState from "#/types/AgentTaskState";
import SettingsForm from "./SettingsForm";
const onModelChangeMock = vi.fn();
@@ -8,7 +10,7 @@ const onAgentChangeMock = vi.fn();
const onLanguageChangeMock = vi.fn();
const renderSettingsForm = (settings: Partial) => {
- render(
+ renderWithProviders(
{
expect(languageInput).toHaveValue("Español");
});
+ it("should disable settings while task is running", () => {
+ renderWithProviders(
+ ,
+ { preloadedState: { agent: { curTaskState: AgentTaskState.RUNNING } } },
+ );
+ const modelInput = screen.getByRole("combobox", { name: "model" });
+ const agentInput = screen.getByRole("combobox", { name: "agent" });
+ const languageInput = screen.getByRole("combobox", { name: "language" });
+
+ expect(modelInput).toBeDisabled();
+ expect(agentInput).toBeDisabled();
+ expect(languageInput).toBeDisabled();
+ });
+
describe("onChange handlers", () => {
it("should call the onModelChange handler when the model changes", () => {
renderSettingsForm({});
diff --git a/frontend/src/components/modals/settings/SettingsForm.tsx b/frontend/src/components/modals/settings/SettingsForm.tsx
index 71cbde9150..e5a82053d8 100644
--- a/frontend/src/components/modals/settings/SettingsForm.tsx
+++ b/frontend/src/components/modals/settings/SettingsForm.tsx
@@ -1,5 +1,10 @@
-import React from "react";
-import { AvailableLanguages } from "#/i18n";
+import React, { useEffect } from "react";
+import { useTranslation } from "react-i18next";
+import { useSelector } from "react-redux";
+import { AvailableLanguages } from "../../../i18n";
+import { I18nKey } from "../../../i18n/declaration";
+import { RootState } from "../../../store";
+import AgentTaskState from "../../../types/AgentTaskState";
import { AutocompleteCombobox } from "./AutocompleteCombobox";
interface SettingsFormProps {
@@ -20,6 +25,21 @@ function SettingsForm({
onAgentChange,
onLanguageChange,
}: SettingsFormProps) {
+ const { t } = useTranslation();
+ const { curTaskState } = useSelector((state: RootState) => state.agent);
+ const [disabled, setDisabled] = React.useState(false);
+
+ useEffect(() => {
+ if (
+ curTaskState === AgentTaskState.RUNNING ||
+ curTaskState === AgentTaskState.PAUSED
+ ) {
+ setDisabled(true);
+ } else {
+ setDisabled(false);
+ }
+ }, [curTaskState, setDisabled]);
+
return (
<>
({ value: model, label: model }))}
defaultKey={settings.LLM_MODEL || models[0]}
onChange={onModelChange}
+ tooltip={t(I18nKey.SETTINGS$MODEL_TOOLTIP)}
allowCustomValue // user can type in a custom LLM model that is not in the list
+ disabled={disabled}
/>
({ value: agent, label: agent }))}
defaultKey={settings.AGENT || agents[0]}
onChange={onAgentChange}
+ tooltip={t(I18nKey.SETTINGS$AGENT_TOOLTIP)}
+ disabled={disabled}
/>
>
);
diff --git a/frontend/src/components/modals/settings/SettingsModal.test.tsx b/frontend/src/components/modals/settings/SettingsModal.test.tsx
index b9f6a62d14..fdc91ecd59 100644
--- a/frontend/src/components/modals/settings/SettingsModal.test.tsx
+++ b/frontend/src/components/modals/settings/SettingsModal.test.tsx
@@ -1,13 +1,14 @@
-import { waitFor, screen, act, render } from "@testing-library/react";
+import {
+ fetchAgents,
+ fetchModels,
+ getCurrentSettings,
+ saveSettings,
+} from "#/services/settingsService";
+import { act, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
+import { renderWithProviders } from "test-utils";
import { Mock } from "vitest";
-import {
- fetchModels,
- fetchAgents,
- saveSettings,
- getCurrentSettings,
-} from "../../../services/settingsService";
import SettingsModal from "./SettingsModal";
vi.mock("../../services/settingsService", async (importOriginal) => ({
@@ -28,7 +29,7 @@ describe("SettingsModal", () => {
});
it("should fetch existing agents and models from the API", async () => {
- render();
+ renderWithProviders();
await waitFor(() => {
expect(fetchModels).toHaveBeenCalledTimes(1);
@@ -43,7 +44,7 @@ describe("SettingsModal", () => {
it("should close the modal when the cancel button is clicked", async () => {
const onOpenChange = vi.fn();
await act(async () =>
- render(),
+ renderWithProviders(),
);
const cancelButton = screen.getByRole("button", {
@@ -60,7 +61,9 @@ describe("SettingsModal", () => {
it("should call saveSettings (and close) with the new values", async () => {
const onOpenChangeMock = vi.fn();
await act(async () =>
- render(),
+ renderWithProviders(
+ ,
+ ),
);
const saveButton = screen.getByRole("button", { name: /save/i });
@@ -97,7 +100,7 @@ describe("SettingsModal", () => {
});
const onOpenChange = vi.fn();
- const { rerender } = render(
+ const { rerender } = renderWithProviders(
,
);
diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json
index 915f429b78..f794193636 100644
--- a/frontend/src/i18n/translation.json
+++ b/frontend/src/i18n/translation.json
@@ -308,5 +308,21 @@
"ar": "مساعد",
"fr": "Assistant",
"tr": "Gönder"
+ },
+ "SETTINGS$MODEL_TOOLTIP": {
+ "en": "Select the language model to use.",
+ "de": "Wähle das zu verwendende Model."
+ },
+ "SETTINGS$AGENT_TOOLTIP": {
+ "en": "Select the agent to use.",
+ "de": "Wähle den zu verwendenden Agent."
+ },
+ "SETTINGS$LANGUAGE_TOOLTIP": {
+ "en": "Select the language for the UI.",
+ "de": "Wähle die Sprache für das Interface."
+ },
+ "SETTINGS$DISABLED_RUNNING": {
+ "en": "Cannot be changed while the agent is running.",
+ "de": "Kann nicht geändert werden während ein Task ausgeführt wird."
}
}