feat: disable settings while task is running (#1291)

* feat: disable settings while task is running

* test files

* tests

* test
This commit is contained in:
Alex Bäuerle 2024-04-23 14:20:49 -07:00 committed by GitHub
parent a2a86e84f0
commit 620bbb38cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 122 additions and 34 deletions

View File

@ -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");
});

View File

@ -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 (
<Autocomplete
aria-label={ariaLabel}
label={t(LABELS[ariaLabel])}
placeholder={t(PLACEHOLDERS[ariaLabel])}
defaultItems={items}
defaultSelectedKey={defaultKey}
allowsCustomValue={allowCustomValue}
onInputChange={(value) => {
onChange(value);
}}
<Tooltip
content={
disabled
? `${tooltip} ${t(I18nKey.SETTINGS$DISABLED_RUNNING)}`
: tooltip
}
closeDelay={100}
delay={500}
>
{(item) => (
<AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>
)}
</Autocomplete>
<Autocomplete
aria-label={ariaLabel}
label={t(LABELS[ariaLabel])}
placeholder={t(PLACEHOLDERS[ariaLabel])}
defaultItems={items}
defaultSelectedKey={defaultKey}
isDisabled={disabled}
allowsCustomValue={allowCustomValue}
onInputChange={(value) => {
onChange(value);
}}
>
{(item) => (
<AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>
)}
</Autocomplete>
</Tooltip>
);
}
AutocompleteCombobox.defaultProps = {
allowCustomValue: false,
disabled: false,
};

View File

@ -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<Settings>) => {
render(
renderWithProviders(
<SettingsForm
settings={settings}
models={["model1", "model2", "model3"]}
@ -49,6 +51,27 @@ describe("SettingsForm", () => {
expect(languageInput).toHaveValue("Español");
});
it("should disable settings while task is running", () => {
renderWithProviders(
<SettingsForm
settings={{}}
models={["model1", "model2", "model3"]}
agents={["agent1", "agent2", "agent3"]}
onModelChange={onModelChangeMock}
onAgentChange={onAgentChangeMock}
onLanguageChange={onLanguageChangeMock}
/>,
{ 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({});

View File

@ -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<boolean>(false);
useEffect(() => {
if (
curTaskState === AgentTaskState.RUNNING ||
curTaskState === AgentTaskState.PAUSED
) {
setDisabled(true);
} else {
setDisabled(false);
}
}, [curTaskState, setDisabled]);
return (
<>
<AutocompleteCombobox
@ -27,19 +47,25 @@ function SettingsForm({
items={models.map((model) => ({ 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}
/>
<AutocompleteCombobox
ariaLabel="agent"
items={agents.map((agent) => ({ value: agent, label: agent }))}
defaultKey={settings.AGENT || agents[0]}
onChange={onAgentChange}
tooltip={t(I18nKey.SETTINGS$AGENT_TOOLTIP)}
disabled={disabled}
/>
<AutocompleteCombobox
ariaLabel="language"
items={AvailableLanguages}
defaultKey={settings.LANGUAGE || "en"}
onChange={onLanguageChange}
tooltip={t(I18nKey.SETTINGS$LANGUAGE_TOOLTIP)}
disabled={disabled}
/>
</>
);

View File

@ -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(<SettingsModal isOpen onOpenChange={vi.fn()} />);
renderWithProviders(<SettingsModal isOpen onOpenChange={vi.fn()} />);
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(<SettingsModal isOpen onOpenChange={onOpenChange} />),
renderWithProviders(<SettingsModal isOpen onOpenChange={onOpenChange} />),
);
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(<SettingsModal isOpen onOpenChange={onOpenChangeMock} />),
renderWithProviders(
<SettingsModal isOpen onOpenChange={onOpenChangeMock} />,
),
);
const saveButton = screen.getByRole("button", { name: /save/i });
@ -97,7 +100,7 @@ describe("SettingsModal", () => {
});
const onOpenChange = vi.fn();
const { rerender } = render(
const { rerender } = renderWithProviders(
<SettingsModal isOpen onOpenChange={onOpenChange} />,
);

View File

@ -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."
}
}