mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site): move Coder Agents settings to AI settings (#26692)
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { ArrowUpRightIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import {
|
||||
Sidebar as BaseSidebar,
|
||||
@@ -43,6 +42,11 @@ const AISettingsSidebarView: FC<AISettingsSidebarViewProps> = ({
|
||||
Instructions
|
||||
</SidebarNavItem>
|
||||
)}
|
||||
{permissions.editDeploymentConfig && (
|
||||
<SidebarNavItem href="/ai/settings/coder-agents">
|
||||
Coder Agents
|
||||
</SidebarNavItem>
|
||||
)}
|
||||
{permissions.editDeploymentConfig && (
|
||||
<SidebarNavItem href="/ai/settings/lifecycle">
|
||||
Lifecycle
|
||||
@@ -58,13 +62,6 @@ const AISettingsSidebarView: FC<AISettingsSidebarViewProps> = ({
|
||||
MCP servers
|
||||
</SidebarNavItem>
|
||||
)}
|
||||
{permissions.editDeploymentConfig && (
|
||||
<SidebarNavItem href="/agents/settings/agents">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
Manage Coder Agents <ArrowUpRightIcon size={16} />
|
||||
</div>
|
||||
</SidebarNavItem>
|
||||
)}
|
||||
</div>
|
||||
</BaseSidebar>
|
||||
);
|
||||
|
||||
+6
-4
@@ -14,7 +14,8 @@ import {
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import { useAuthenticated } from "#/hooks/useAuthenticated";
|
||||
import { RequirePermission } from "#/modules/permissions/RequirePermission";
|
||||
import { AgentSettingsAgentsPageView } from "./AgentSettingsAgentsPageView";
|
||||
import { pageTitle } from "#/utils/page";
|
||||
import { CoderAgentsPageView } from "./CoderAgentsPageView";
|
||||
|
||||
const generalOverrideContext: TypesGen.ChatModelOverrideContext = "general";
|
||||
const exploreOverrideContext: TypesGen.ChatModelOverrideContext = "explore";
|
||||
@@ -45,7 +46,7 @@ const updateChatModelOverrideMutation = (
|
||||
},
|
||||
});
|
||||
|
||||
const AgentSettingsAgentsPage: FC = () => {
|
||||
const CoderAgentsPage: FC = () => {
|
||||
const { permissions } = useAuthenticated();
|
||||
const queryClient = useQueryClient();
|
||||
const canEditDeploymentConfig = permissions.editDeploymentConfig;
|
||||
@@ -85,7 +86,8 @@ const AgentSettingsAgentsPage: FC = () => {
|
||||
|
||||
return (
|
||||
<RequirePermission isFeatureVisible={canEditDeploymentConfig}>
|
||||
<AgentSettingsAgentsPageView
|
||||
<title>{pageTitle("Coder Agents", "AI Settings")}</title>
|
||||
<CoderAgentsPageView
|
||||
adminOverridesData={personalModelOverridesAdminSettingsQuery.data}
|
||||
adminOverridesError={personalModelOverridesAdminSettingsQuery.error}
|
||||
onRetryAdminOverrides={() => {
|
||||
@@ -135,4 +137,4 @@ const AgentSettingsAgentsPage: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentSettingsAgentsPage;
|
||||
export default CoderAgentsPage;
|
||||
+27
-22
@@ -3,9 +3,9 @@ import { expect, fn, userEvent, waitFor, within } from "storybook/test";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import { MockChatModelConfig } from "#/testHelpers/chatModels";
|
||||
import {
|
||||
AgentSettingsAgentsPageView,
|
||||
type AgentSettingsAgentsPageViewProps,
|
||||
} from "./AgentSettingsAgentsPageView";
|
||||
CoderAgentsPageView,
|
||||
type CoderAgentsPageViewProps,
|
||||
} from "./CoderAgentsPageView";
|
||||
|
||||
const OVERRIDE_MALFORMED_WARNING =
|
||||
"The saved override is malformed and is being treated as unset. Click Save to clear it.";
|
||||
@@ -105,8 +105,8 @@ const allModelConfigs: TypesGen.ChatModelConfig[] = [
|
||||
];
|
||||
|
||||
const buildArgs = (
|
||||
overrides: Partial<AgentSettingsAgentsPageViewProps> = {},
|
||||
): AgentSettingsAgentsPageViewProps => ({
|
||||
overrides: Partial<CoderAgentsPageViewProps> = {},
|
||||
): CoderAgentsPageViewProps => ({
|
||||
adminOverridesData: { allow_users: false },
|
||||
adminOverridesError: undefined,
|
||||
onRetryAdminOverrides: fn(),
|
||||
@@ -138,13 +138,11 @@ const getSection = async (
|
||||
): Promise<HTMLElement> => {
|
||||
const canvas = within(canvasElement);
|
||||
const heading = await canvas.findByRole("heading", { name: headingName });
|
||||
const section = heading.closest("section");
|
||||
if (!(section instanceof HTMLElement)) {
|
||||
throw new Error(
|
||||
`Expected ${headingName} heading to live inside a section.`,
|
||||
);
|
||||
const setting = heading.closest("form");
|
||||
if (!(setting instanceof HTMLElement)) {
|
||||
throw new Error(`Expected ${headingName} heading to live inside a form.`);
|
||||
}
|
||||
return section;
|
||||
return setting;
|
||||
};
|
||||
|
||||
const selectModelInSection = async (
|
||||
@@ -162,29 +160,36 @@ const selectModelInSection = async (
|
||||
};
|
||||
|
||||
const meta = {
|
||||
title: "pages/AgentsPage/AgentSettingsAgentsPageView",
|
||||
component: AgentSettingsAgentsPageView,
|
||||
title: "pages/AISettingsPage/CoderAgentsPage/CoderAgentsPageView",
|
||||
component: CoderAgentsPageView,
|
||||
args: buildArgs(),
|
||||
} satisfies Meta<typeof AgentSettingsAgentsPageView>;
|
||||
} satisfies Meta<typeof CoderAgentsPageView>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AgentSettingsAgentsPageView>;
|
||||
type Story = StoryObj<typeof CoderAgentsPageView>;
|
||||
|
||||
export const AllOverridesUnset: Story = {
|
||||
args: buildArgs(),
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText("Agents");
|
||||
expect(
|
||||
await canvas.findByRole("heading", { name: "Coder Agents" }),
|
||||
).toBeVisible();
|
||||
expect(
|
||||
canvas.getByText(
|
||||
"Configure deployment-wide defaults for Coder Agents and agent-specific capabilities.",
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
expect(canvas.getByText("Allow personal model overrides")).toBeVisible();
|
||||
const headings = await canvas.findAllByRole("heading", { level: 3 });
|
||||
expect(headings.map((heading) => heading.textContent?.trim())).toEqual([
|
||||
"Enable users to define their personal overrides",
|
||||
"General model",
|
||||
"Title generation model",
|
||||
"Explore subagent model",
|
||||
]);
|
||||
await canvas.findByText(
|
||||
"Choose a model for generated chat titles. Leave unset to use Coder's default title algorithm, which currently tries fast title models for configured providers first, for example Claude Haiku, GPT-4o mini, and Gemini Flash, then falls back to the chat's current model. When a model is selected here, Coder uses only that model for title generation. Recommended title models are fast and low cost.",
|
||||
"Leave unset to use Coder's title default, which prefers fast models from configured providers.",
|
||||
);
|
||||
|
||||
const unsetSections = [
|
||||
@@ -204,8 +209,8 @@ export const AllOverridesUnset: Story = {
|
||||
within(section).getByRole("combobox", { name: placeholder }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
within(section).getByRole("button", { name: "Save" }),
|
||||
).toBeDisabled();
|
||||
within(section).queryByRole("button", { name: "Save" }),
|
||||
).not.toBeInTheDocument();
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -217,7 +222,7 @@ export const PersonalOverridesDisabled: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const toggle = await canvas.findByRole("switch", {
|
||||
name: "Enable users to define their personal overrides",
|
||||
name: "Allow personal model overrides",
|
||||
});
|
||||
|
||||
expect(toggle).not.toBeChecked();
|
||||
@@ -231,7 +236,7 @@ export const PersonalOverridesEnabled: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const toggle = await canvas.findByRole("switch", {
|
||||
name: "Enable users to define their personal overrides",
|
||||
name: "Allow personal model overrides",
|
||||
});
|
||||
|
||||
expect(toggle).toBeChecked();
|
||||
+31
-60
@@ -1,10 +1,14 @@
|
||||
import type { FC } from "react";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "#/components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
AdminPersonalModelOverridesSettings,
|
||||
type SavePersonalModelOverridesAdminSetting,
|
||||
} from "./components/AdminPersonalModelOverridesSettings";
|
||||
import { SectionHeader } from "./components/SectionHeader";
|
||||
import {
|
||||
type MutationCallbacks,
|
||||
SubagentModelOverrideSettings,
|
||||
@@ -15,7 +19,7 @@ type SaveModelOverride = (
|
||||
options?: MutationCallbacks,
|
||||
) => void;
|
||||
|
||||
export interface AgentSettingsAgentsPageViewProps {
|
||||
export interface CoderAgentsPageViewProps {
|
||||
adminOverridesData?: TypesGen.ChatPersonalModelOverridesAdminSettings;
|
||||
adminOverridesError?: unknown;
|
||||
onRetryAdminOverrides?: () => void;
|
||||
@@ -40,9 +44,7 @@ export interface AgentSettingsAgentsPageViewProps {
|
||||
isSaveExploreModelOverrideError: boolean;
|
||||
}
|
||||
|
||||
export const AgentSettingsAgentsPageView: FC<
|
||||
AgentSettingsAgentsPageViewProps
|
||||
> = ({
|
||||
export const CoderAgentsPageView: FC<CoderAgentsPageViewProps> = ({
|
||||
adminOverridesData,
|
||||
adminOverridesError,
|
||||
onRetryAdminOverrides,
|
||||
@@ -76,30 +78,28 @@ export const AgentSettingsAgentsPageView: FC<
|
||||
isSaveGeneralModelOverrideError;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<SectionHeader
|
||||
label="Agents"
|
||||
description="Configure defaults for delegated agents and other agent-specific capabilities."
|
||||
/>
|
||||
<AdminPersonalModelOverridesSettings
|
||||
adminSettings={adminOverridesData}
|
||||
adminSettingsError={adminOverridesError}
|
||||
onRetryAdminSettings={onRetryAdminOverrides}
|
||||
isRetryingAdminSettings={isRetryingAdminOverrides}
|
||||
onSaveAdminSetting={onSaveAdminOverrides}
|
||||
isSavingAdminSetting={isSavingAdminOverrides}
|
||||
isSaveAdminSettingError={isSaveAdminOverridesError}
|
||||
/>
|
||||
{showGeneralModelSection && onSaveGeneralModelOverride && (
|
||||
<section aria-label="General model" className="flex flex-col gap-3">
|
||||
<SectionHeader
|
||||
label="General model"
|
||||
description="Deployment-wide model override for delegated subagents with write capabilities, such as editing files or running commands in the workspace."
|
||||
level="section"
|
||||
/>
|
||||
<div className="flex max-w-4xl flex-col gap-8">
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Coder Agents</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Configure deployment-wide defaults for Coder Agents and agent-specific
|
||||
capabilities.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
<div className="flex flex-col gap-6 rounded-lg border border-solid border-border px-6 py-7">
|
||||
<AdminPersonalModelOverridesSettings
|
||||
adminSettings={adminOverridesData}
|
||||
adminSettingsError={adminOverridesError}
|
||||
onRetryAdminSettings={onRetryAdminOverrides}
|
||||
isRetryingAdminSettings={isRetryingAdminOverrides}
|
||||
onSaveAdminSetting={onSaveAdminOverrides}
|
||||
isSavingAdminSetting={isSavingAdminOverrides}
|
||||
isSaveAdminSettingError={isSaveAdminOverridesError}
|
||||
/>
|
||||
{showGeneralModelSection && onSaveGeneralModelOverride && (
|
||||
<SubagentModelOverrideSettings
|
||||
title="General model"
|
||||
description="Deployment-wide model override for delegated subagents with write capabilities, such as editing files or running commands in the workspace."
|
||||
description="Used by delegated agents that can edit files or run commands."
|
||||
modelOverrideData={generalModelOverrideData}
|
||||
enabledModelConfigs={enabledModelConfigs}
|
||||
modelConfigsError={modelConfigsError}
|
||||
@@ -108,22 +108,11 @@ export const AgentSettingsAgentsPageView: FC<
|
||||
isSaving={isSavingGeneralModelOverride}
|
||||
isSaveError={isSaveGeneralModelOverrideError}
|
||||
saveErrorMessage="Failed to save general model override."
|
||||
showHeader={false}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
<section
|
||||
aria-label="Title generation model"
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
<SectionHeader
|
||||
label="Title generation model"
|
||||
description="Choose a model for generated chat titles. Leave unset to use Coder's default title algorithm, which currently tries fast title models for configured providers first, for example Claude Haiku, GPT-4o mini, and Gemini Flash, then falls back to the chat's current model. When a model is selected here, Coder uses only that model for title generation. Recommended title models are fast and low cost."
|
||||
level="section"
|
||||
/>
|
||||
)}
|
||||
<SubagentModelOverrideSettings
|
||||
title="Title generation model"
|
||||
description="Choose a model for generated chat titles."
|
||||
description="Leave unset to use Coder's title default, which prefers fast models from configured providers."
|
||||
modelOverrideData={titleGenerationModelOverrideData}
|
||||
enabledModelConfigs={enabledModelConfigs}
|
||||
modelConfigsError={modelConfigsError}
|
||||
@@ -134,27 +123,10 @@ export const AgentSettingsAgentsPageView: FC<
|
||||
saveErrorMessage="Failed to save title generation model."
|
||||
unsetPlaceholder="Use title default"
|
||||
unavailableModelWarning="The selected model is currently unavailable. Title generation will be skipped until you choose another model or clear this setting."
|
||||
showHeader={false}
|
||||
/>
|
||||
</section>
|
||||
<section
|
||||
aria-label="Explore subagent model"
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
<SectionHeader
|
||||
label="Explore subagent model"
|
||||
description="Deployment-wide model override for read-only Explore subagents."
|
||||
level="section"
|
||||
/>
|
||||
<SubagentModelOverrideSettings
|
||||
title="Explore subagent model"
|
||||
description={
|
||||
<>
|
||||
Deployment-wide model override for read-only Explore subagents
|
||||
launched through the <code>spawn_agent</code> tool with a
|
||||
<code>type=explore</code> argument.
|
||||
</>
|
||||
}
|
||||
description="Used for read-only codebase exploration before work returns to the main agent."
|
||||
modelOverrideData={exploreModelOverrideData}
|
||||
enabledModelConfigs={enabledModelConfigs}
|
||||
modelConfigsError={modelConfigsError}
|
||||
@@ -163,9 +135,8 @@ export const AgentSettingsAgentsPageView: FC<
|
||||
isSaving={isSavingExploreModelOverride}
|
||||
isSaveError={isSaveExploreModelOverrideError}
|
||||
saveErrorMessage="Failed to save Explore model override."
|
||||
showHeader={false}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+22
-16
@@ -13,7 +13,8 @@ const baseArgs = {
|
||||
};
|
||||
|
||||
const meta = {
|
||||
title: "pages/AgentsPage/components/AdminPersonalModelOverridesSettings",
|
||||
title:
|
||||
"pages/AISettingsPage/CoderAgentsPage/components/AdminPersonalModelOverridesSettings",
|
||||
component: AdminPersonalModelOverridesSettings,
|
||||
args: baseArgs,
|
||||
} satisfies Meta<typeof AdminPersonalModelOverridesSettings>;
|
||||
@@ -25,16 +26,16 @@ export const FeatureDisabled: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const toggle = await canvas.findByRole("switch", {
|
||||
name: "Enable users to define their personal overrides",
|
||||
name: "Allow personal model overrides",
|
||||
});
|
||||
|
||||
expect(
|
||||
await canvas.findByText(
|
||||
"Enable users to define their personal overrides",
|
||||
),
|
||||
await canvas.findByText("Allow personal model overrides"),
|
||||
).toBeInTheDocument();
|
||||
expect(toggle).not.toBeChecked();
|
||||
expect(canvas.getByRole("button", { name: "Save" })).toBeDisabled();
|
||||
expect(
|
||||
canvas.queryByRole("button", { name: "Save" }),
|
||||
).not.toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -50,10 +51,12 @@ export const LoadingState: Story = {
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
canvas.getByRole("switch", {
|
||||
name: "Enable users to define their personal overrides",
|
||||
name: "Allow personal model overrides",
|
||||
}),
|
||||
).toBeDisabled();
|
||||
expect(canvas.getByRole("button", { name: "Save" })).toBeDisabled();
|
||||
expect(
|
||||
canvas.queryByRole("button", { name: "Save" }),
|
||||
).not.toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -71,7 +74,9 @@ export const LoadError: Story = {
|
||||
expect(
|
||||
canvas.queryByText("Loading personal model override settings..."),
|
||||
).not.toBeInTheDocument();
|
||||
expect(canvas.getByRole("button", { name: "Save" })).toBeDisabled();
|
||||
expect(
|
||||
canvas.queryByRole("button", { name: "Save" }),
|
||||
).not.toBeInTheDocument();
|
||||
await userEvent.click(canvas.getByRole("button", { name: "Retry" }));
|
||||
expect(args.onRetryAdminSettings).toHaveBeenCalled();
|
||||
},
|
||||
@@ -84,11 +89,13 @@ export const FeatureEnabled: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const toggle = await canvas.findByRole("switch", {
|
||||
name: "Enable users to define their personal overrides",
|
||||
name: "Allow personal model overrides",
|
||||
});
|
||||
|
||||
expect(toggle).toBeChecked();
|
||||
expect(canvas.getByRole("button", { name: "Save" })).toBeDisabled();
|
||||
expect(
|
||||
canvas.queryByRole("button", { name: "Save" }),
|
||||
).not.toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -99,11 +106,11 @@ export const Saving: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const toggle = await canvas.findByRole("switch", {
|
||||
name: "Enable users to define their personal overrides",
|
||||
name: "Allow personal model overrides",
|
||||
});
|
||||
|
||||
expect(toggle).toBeDisabled();
|
||||
expect(canvas.getByRole("button", { name: "Save" })).toBeDisabled();
|
||||
expect(canvas.getByRole("button", { name: /save/i })).toBeDisabled();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -126,11 +133,10 @@ export const SavesChangedSetting: Story = {
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const toggle = await canvas.findByRole("switch", {
|
||||
name: "Enable users to define their personal overrides",
|
||||
name: "Allow personal model overrides",
|
||||
});
|
||||
const saveButton = canvas.getByRole("button", { name: "Save" });
|
||||
|
||||
await userEvent.click(toggle);
|
||||
const saveButton = await canvas.findByRole("button", { name: "Save" });
|
||||
await waitFor(() => {
|
||||
expect(saveButton).toBeEnabled();
|
||||
});
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import { useFormik } from "formik";
|
||||
import type { FC } from "react";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import { ErrorAlert } from "#/components/Alert/ErrorAlert";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import { Switch } from "#/components/Switch/Switch";
|
||||
import {
|
||||
TemporarySavedState,
|
||||
useTemporarySavedState,
|
||||
} from "#/components/TemporarySavedState/TemporarySavedState";
|
||||
|
||||
interface MutationCallbacks {
|
||||
onSuccess?: () => void;
|
||||
onError?: () => void;
|
||||
}
|
||||
|
||||
export type SavePersonalModelOverridesAdminSetting = (
|
||||
req: TypesGen.UpdateChatPersonalModelOverridesAdminSettingsRequest,
|
||||
options?: MutationCallbacks,
|
||||
) => void;
|
||||
|
||||
interface AdminPersonalModelOverridesSettingsProps {
|
||||
adminSettings: TypesGen.ChatPersonalModelOverridesAdminSettings | undefined;
|
||||
adminSettingsError?: unknown;
|
||||
onRetryAdminSettings?: () => void;
|
||||
isRetryingAdminSettings?: boolean;
|
||||
onSaveAdminSetting: SavePersonalModelOverridesAdminSetting;
|
||||
isSavingAdminSetting: boolean;
|
||||
isSaveAdminSettingError: boolean;
|
||||
}
|
||||
|
||||
export const AdminPersonalModelOverridesSettings: FC<
|
||||
AdminPersonalModelOverridesSettingsProps
|
||||
> = ({
|
||||
adminSettings,
|
||||
adminSettingsError,
|
||||
onRetryAdminSettings,
|
||||
isRetryingAdminSettings = false,
|
||||
onSaveAdminSetting,
|
||||
isSavingAdminSetting,
|
||||
isSaveAdminSettingError,
|
||||
}) => {
|
||||
const { isSavedVisible, showSavedState } = useTemporarySavedState();
|
||||
const hasLoadedAdminSettings = adminSettings !== undefined;
|
||||
const hasAdminSettingsError = adminSettingsError != null;
|
||||
const form = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
allow_users: adminSettings?.allow_users ?? false,
|
||||
},
|
||||
onSubmit: (values, { resetForm }) => {
|
||||
onSaveAdminSetting(
|
||||
{
|
||||
allow_users: values.allow_users,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
showSavedState();
|
||||
resetForm({ values });
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
const isDisabled = isSavingAdminSetting || !hasLoadedAdminSettings;
|
||||
const showSave = form.dirty || isSavingAdminSetting || isSavedVisible;
|
||||
const showStatusArea =
|
||||
hasAdminSettingsError || !hasLoadedAdminSettings || isSaveAdminSettingError;
|
||||
|
||||
return (
|
||||
<form
|
||||
aria-label="Allow personal model overrides"
|
||||
className="flex flex-col"
|
||||
onSubmit={form.handleSubmit}
|
||||
noValidate
|
||||
>
|
||||
<div className="flex min-h-8 items-start gap-2 font-sans text-sm font-normal leading-6 text-content-primary">
|
||||
<Switch
|
||||
checked={form.values.allow_users}
|
||||
onCheckedChange={(checked) => {
|
||||
void form.setFieldValue("allow_users", checked);
|
||||
}}
|
||||
aria-label="Allow personal model overrides"
|
||||
type="button"
|
||||
disabled={isDisabled}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<span>Allow personal model overrides</span>
|
||||
<span className="text-content-secondary">
|
||||
Saved user preferences are preserved but ignored while disabled.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{showSave && (
|
||||
<div className="mt-4 flex min-h-10 items-center">
|
||||
{isSavedVisible ? (
|
||||
<TemporarySavedState />
|
||||
) : (
|
||||
<Button
|
||||
size="lg"
|
||||
type="submit"
|
||||
disabled={isDisabled || !form.dirty}
|
||||
className="h-10 min-w-[88px]"
|
||||
>
|
||||
{isSavingAdminSetting && <Spinner loading className="h-4 w-4" />}
|
||||
Save
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showStatusArea && (
|
||||
<div className="text-xs">
|
||||
{hasAdminSettingsError && (
|
||||
<div className="flex flex-col gap-2 text-content-primary">
|
||||
<ErrorAlert error={adminSettingsError} />
|
||||
{onRetryAdminSettings && (
|
||||
<Button
|
||||
disabled={isRetryingAdminSettings}
|
||||
onClick={onRetryAdminSettings}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-fit"
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!hasAdminSettingsError && !hasLoadedAdminSettings && (
|
||||
<p className="m-0 text-content-secondary">
|
||||
Loading personal model override settings...
|
||||
</p>
|
||||
)}
|
||||
{isSaveAdminSettingError && (
|
||||
<p className="m-0 text-content-destructive">
|
||||
Failed to save personal model override settings.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { expect, fn, within } from "storybook/test";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { AgentSettingLayout } from "./AgentSettingLayout";
|
||||
|
||||
const meta = {
|
||||
title: "pages/AISettingsPage/CoderAgentsPage/components/AgentSettingLayout",
|
||||
component: AgentSettingLayout,
|
||||
args: {
|
||||
title: "General model",
|
||||
description:
|
||||
"Used by delegated agents that can edit files or run commands.",
|
||||
showSave: false,
|
||||
isSaving: false,
|
||||
isSavedVisible: false,
|
||||
saveDisabled: true,
|
||||
onSubmit: fn(),
|
||||
},
|
||||
} satisfies Meta<typeof AgentSettingLayout>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AgentSettingLayout>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: <Button type="button">Choose model</Button>,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(
|
||||
await canvas.findByRole("form", { name: "General model" }),
|
||||
).toBeVisible();
|
||||
expect(canvas.getByText("General model")).toBeVisible();
|
||||
expect(canvas.getByRole("button", { name: "Choose model" })).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const Saving: Story = {
|
||||
args: {
|
||||
showSave: true,
|
||||
isSaving: true,
|
||||
saveDisabled: true,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(await canvas.findByRole("button", { name: /save/i })).toBeDisabled();
|
||||
},
|
||||
};
|
||||
|
||||
export const Saved: Story = {
|
||||
args: {
|
||||
showSave: false,
|
||||
isSavedVisible: true,
|
||||
saveDisabled: false,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(await canvas.findByText("Saved")).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
error: <p className="m-0">Failed to save setting.</p>,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(await canvas.findByText("Failed to save setting.")).toBeVisible();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { FC, FormEventHandler, ReactNode } from "react";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import { TemporarySavedState } from "#/components/TemporarySavedState/TemporarySavedState";
|
||||
|
||||
interface AgentSettingLayoutProps {
|
||||
title: string;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
error?: ReactNode;
|
||||
showSave: boolean;
|
||||
isSaving: boolean;
|
||||
isSavedVisible: boolean;
|
||||
saveDisabled: boolean;
|
||||
onSubmit: FormEventHandler<HTMLFormElement>;
|
||||
}
|
||||
|
||||
export const AgentSettingLayout: FC<AgentSettingLayoutProps> = ({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
error,
|
||||
showSave,
|
||||
isSaving,
|
||||
isSavedVisible,
|
||||
saveDisabled,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const shouldShowControls =
|
||||
Boolean(children) || showSave || isSavedVisible || isSaving;
|
||||
|
||||
return (
|
||||
<form
|
||||
aria-label={title}
|
||||
className="flex flex-col"
|
||||
onSubmit={onSubmit}
|
||||
noValidate
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<h3 className="m-0 text-sm font-normal leading-6 text-content-primary">
|
||||
{title}
|
||||
</h3>
|
||||
{description && (
|
||||
<p className="mt-1 mb-0 text-sm font-normal leading-6 text-content-secondary">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{shouldShowControls && (
|
||||
<div className="mt-4 flex flex-wrap items-start gap-3">
|
||||
{children}
|
||||
<div className="flex min-h-10 items-center">
|
||||
{(showSave || isSavedVisible || isSaving) &&
|
||||
(isSavedVisible ? (
|
||||
<TemporarySavedState />
|
||||
) : (
|
||||
<Button
|
||||
size="lg"
|
||||
type="submit"
|
||||
disabled={saveDisabled}
|
||||
className="h-10 min-w-[88px]"
|
||||
>
|
||||
{isSaving && <Spinner loading className="h-4 w-4" />}
|
||||
Save
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{error && <div className="text-xs text-content-destructive">{error}</div>}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
+55
-59
@@ -2,9 +2,11 @@ import { useFormik } from "formik";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import type { ModelSelectorOption } from "./ChatElements/ModelSelector";
|
||||
import { ModelSelector } from "./ChatElements/ModelSelector";
|
||||
import { ModelOverrideAlerts } from "./ModelOverrideAlerts";
|
||||
import { useTemporarySavedState } from "#/components/TemporarySavedState/TemporarySavedState";
|
||||
import type { ModelSelectorOption } from "#/pages/AgentsPage/components/ChatElements/ModelSelector";
|
||||
import { ModelSelector } from "#/pages/AgentsPage/components/ChatElements/ModelSelector";
|
||||
import { ModelOverrideAlerts } from "#/pages/AgentsPage/components/ModelOverrideAlerts";
|
||||
import { AgentSettingLayout } from "./AgentSettingLayout";
|
||||
|
||||
export interface MutationCallbacks {
|
||||
onSuccess?: () => void;
|
||||
@@ -36,7 +38,6 @@ interface SubagentModelOverrideSettingsProps {
|
||||
saveErrorMessage: string;
|
||||
unsetPlaceholder?: string;
|
||||
unavailableModelWarning?: string;
|
||||
showHeader?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -65,9 +66,9 @@ export const SubagentModelOverrideSettings: FC<
|
||||
saveErrorMessage,
|
||||
unsetPlaceholder = "Use chat default",
|
||||
unavailableModelWarning = "The saved model is no longer enabled and will be ignored until you choose a new override.",
|
||||
showHeader = true,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const { isSavedVisible, showSavedState } = useTemporarySavedState();
|
||||
const hasLoadedModelOverride = modelOverrideData !== undefined;
|
||||
const isMalformedOverride = modelOverrideData?.is_malformed ?? false;
|
||||
const enabledModelOptions = enabledModelConfigs.map(toModelSelectorOption);
|
||||
@@ -84,6 +85,7 @@ export const SubagentModelOverrideSettings: FC<
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
showSavedState();
|
||||
resetForm({ values });
|
||||
},
|
||||
},
|
||||
@@ -102,61 +104,55 @@ export const SubagentModelOverrideSettings: FC<
|
||||
);
|
||||
|
||||
return (
|
||||
<form aria-label={title} className="space-y-2" onSubmit={form.handleSubmit}>
|
||||
{showHeader && (
|
||||
<>
|
||||
<h3 className="m-0 text-[13px] font-semibold text-content-primary">
|
||||
{title}
|
||||
</h3>
|
||||
{description && (
|
||||
<p className="!mt-0.5 m-0 text-xs text-content-secondary">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<ModelSelector
|
||||
options={enabledModelOptions}
|
||||
value={form.values.model_config_id}
|
||||
onValueChange={(value) => form.setFieldValue("model_config_id", value)}
|
||||
disabled={isFormDisabled}
|
||||
placeholder={
|
||||
isUnavailableSavedModel ? "Unavailable model" : unsetPlaceholder
|
||||
}
|
||||
emptyMessage={
|
||||
isLoading ? "Loading models..." : "No enabled models found."
|
||||
}
|
||||
className="h-10 w-full justify-between rounded-md border border-border border-solid bg-transparent px-3 text-sm shadow-sm"
|
||||
contentClassName="min-w-[18rem]"
|
||||
/>
|
||||
<ModelOverrideAlerts
|
||||
isUnavailableSavedModel={isUnavailableSavedModel}
|
||||
unavailableMessage={unavailableModelWarning}
|
||||
isMalformedOverride={isMalformedOverride}
|
||||
malformedMessage="The saved override is malformed and is being treated as unset. Click Save to clear it."
|
||||
modelConfigsError={modelConfigsError}
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
form.setFieldValue("model_config_id", "");
|
||||
}}
|
||||
<AgentSettingLayout
|
||||
title={title}
|
||||
description={description}
|
||||
showSave={canSave}
|
||||
isSaving={isSaving}
|
||||
isSavedVisible={isSavedVisible}
|
||||
saveDisabled={isFormDisabled || !canSave}
|
||||
onSubmit={form.handleSubmit}
|
||||
error={
|
||||
isSaveError ? <p className="m-0">{saveErrorMessage}</p> : undefined
|
||||
}
|
||||
>
|
||||
<div className="flex w-[22rem] max-w-full flex-col gap-2">
|
||||
<ModelSelector
|
||||
options={enabledModelOptions}
|
||||
value={form.values.model_config_id}
|
||||
onValueChange={(value) =>
|
||||
void form.setFieldValue("model_config_id", value)
|
||||
}
|
||||
disabled={isFormDisabled}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
<Button size="sm" type="submit" disabled={isFormDisabled || !canSave}>
|
||||
Save
|
||||
</Button>
|
||||
placeholder={
|
||||
isUnavailableSavedModel ? "Unavailable model" : unsetPlaceholder
|
||||
}
|
||||
emptyMessage={
|
||||
isLoading ? "Loading models..." : "No enabled models found."
|
||||
}
|
||||
className="h-10 w-full justify-between rounded-md border border-border border-solid bg-transparent px-3 text-sm"
|
||||
contentClassName="min-w-[18rem]"
|
||||
/>
|
||||
<ModelOverrideAlerts
|
||||
isUnavailableSavedModel={isUnavailableSavedModel}
|
||||
unavailableMessage={unavailableModelWarning}
|
||||
isMalformedOverride={isMalformedOverride}
|
||||
malformedMessage="The saved override is malformed and is being treated as unset. Click Save to clear it."
|
||||
modelConfigsError={modelConfigsError}
|
||||
/>
|
||||
</div>
|
||||
{isSaveError && (
|
||||
<p className="m-0 text-xs text-content-destructive">
|
||||
{saveErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void form.setFieldValue("model_config_id", "");
|
||||
}}
|
||||
disabled={isFormDisabled}
|
||||
className="h-10"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</AgentSettingLayout>
|
||||
);
|
||||
};
|
||||
@@ -27,9 +27,9 @@ import {
|
||||
withAuthProvider,
|
||||
withDashboardProvider,
|
||||
} from "#/testHelpers/storybook";
|
||||
import { CoderAgentsPageView } from "../AISettingsPage/CoderAgentsPage/CoderAgentsPageView";
|
||||
import AgentAnalyticsPage from "./AgentAnalyticsPage";
|
||||
import AgentCreatePage from "./AgentCreatePage";
|
||||
import { AgentSettingsAgentsPageView } from "./AgentSettingsAgentsPageView";
|
||||
import AgentSettingsCompactionPage from "./AgentSettingsCompactionPage";
|
||||
import AgentSettingsExperimentsPage from "./AgentSettingsExperimentsPage";
|
||||
import AgentSettingsGeneralPage from "./AgentSettingsGeneralPage";
|
||||
@@ -165,7 +165,7 @@ const buildChat = (overrides: Partial<Chat> = {}): Chat => ({
|
||||
const fixedNow = dayjs("2026-03-12T12:00:00");
|
||||
|
||||
const AgentsRouteElement = () => (
|
||||
<AgentSettingsAgentsPageView
|
||||
<CoderAgentsPageView
|
||||
adminOverridesData={{ allow_users: false }}
|
||||
onSaveAdminOverrides={fn()}
|
||||
isSavingAdminOverrides={false}
|
||||
@@ -1143,7 +1143,7 @@ export const OpensSettingsForNonAdmins: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const OpensAdminSubPanelOnMobile: Story = {
|
||||
export const OpensExperimentsFromManageAgentsOnMobile: Story = {
|
||||
args: {
|
||||
isAgentsAdmin: true,
|
||||
},
|
||||
@@ -1160,15 +1160,12 @@ export const OpensAdminSubPanelOnMobile: Story = {
|
||||
);
|
||||
|
||||
await expect(
|
||||
await screen.findByRole("link", { name: "Providers" }),
|
||||
).toBeInTheDocument();
|
||||
await expect(
|
||||
await screen.findByRole("link", { name: "Spend" }),
|
||||
await screen.findByRole("heading", { name: "Experiments" }),
|
||||
).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const SettingsViewSpendCrossLink: Story = {
|
||||
export const SettingsViewExperimentsLink: Story = {
|
||||
args: {
|
||||
isAgentsAdmin: true,
|
||||
},
|
||||
@@ -1186,9 +1183,10 @@ export const SettingsViewSpendCrossLink: Story = {
|
||||
await screen.findByRole("link", { name: "Manage agents" }),
|
||||
);
|
||||
|
||||
await userEvent.click(await screen.findByRole("link", { name: "Spend" }));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Spend limits and usage")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("Opt in to experimental features."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import { useFormik } from "formik";
|
||||
import type { FC } from "react";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import { ErrorAlert } from "#/components/Alert/ErrorAlert";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { Switch } from "#/components/Switch/Switch";
|
||||
|
||||
interface MutationCallbacks {
|
||||
onSuccess?: () => void;
|
||||
onError?: () => void;
|
||||
}
|
||||
|
||||
export type SavePersonalModelOverridesAdminSetting = (
|
||||
req: TypesGen.UpdateChatPersonalModelOverridesAdminSettingsRequest,
|
||||
options?: MutationCallbacks,
|
||||
) => void;
|
||||
|
||||
interface AdminPersonalModelOverridesSettingsProps {
|
||||
adminSettings: TypesGen.ChatPersonalModelOverridesAdminSettings | undefined;
|
||||
adminSettingsError?: unknown;
|
||||
onRetryAdminSettings?: () => void;
|
||||
isRetryingAdminSettings?: boolean;
|
||||
onSaveAdminSetting: SavePersonalModelOverridesAdminSetting;
|
||||
isSavingAdminSetting: boolean;
|
||||
isSaveAdminSettingError: boolean;
|
||||
}
|
||||
|
||||
export const AdminPersonalModelOverridesSettings: FC<
|
||||
AdminPersonalModelOverridesSettingsProps
|
||||
> = ({
|
||||
adminSettings,
|
||||
adminSettingsError,
|
||||
onRetryAdminSettings,
|
||||
isRetryingAdminSettings = false,
|
||||
onSaveAdminSetting,
|
||||
isSavingAdminSetting,
|
||||
isSaveAdminSettingError,
|
||||
}) => {
|
||||
const hasLoadedAdminSettings = adminSettings !== undefined;
|
||||
const hasAdminSettingsError = adminSettingsError != null;
|
||||
const form = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
allow_users: adminSettings?.allow_users ?? false,
|
||||
},
|
||||
onSubmit: (values, { resetForm }) => {
|
||||
onSaveAdminSetting(
|
||||
{
|
||||
allow_users: values.allow_users,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
resetForm({ values });
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
const isDisabled = isSavingAdminSetting || !hasLoadedAdminSettings;
|
||||
|
||||
return (
|
||||
<form
|
||||
aria-label="Personal model overrides"
|
||||
className="space-y-2"
|
||||
onSubmit={form.handleSubmit}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Enable users to define their personal overrides
|
||||
</h3>
|
||||
<p className="m-0 text-xs text-content-secondary">
|
||||
Lets users choose personal models for root chats, General subagents,
|
||||
and Explore subagents. When disabled, saved user settings remain
|
||||
stored but are ignored at runtime.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={form.values.allow_users}
|
||||
onCheckedChange={(checked) => {
|
||||
void form.setFieldValue("allow_users", checked);
|
||||
}}
|
||||
aria-label="Enable users to define their personal overrides"
|
||||
type="button"
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
{hasAdminSettingsError ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<ErrorAlert error={adminSettingsError} />
|
||||
{onRetryAdminSettings && (
|
||||
<Button
|
||||
disabled={isRetryingAdminSettings}
|
||||
onClick={onRetryAdminSettings}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
!hasLoadedAdminSettings && (
|
||||
<p className="m-0 text-xs text-content-secondary">
|
||||
Loading personal model override settings...
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button size="sm" type="submit" disabled={isDisabled || !form.dirty}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
{isSaveAdminSettingError && (
|
||||
<p className="m-0 text-xs text-content-destructive">
|
||||
Failed to save personal model override settings.
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -2238,7 +2238,7 @@ export const SettingsUserAgentsAdmin: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const SettingsAdminAgentsEntryPreserved: Story = {
|
||||
export const SettingsAdminCoderAgentsEntryPreserved: Story = {
|
||||
args: {
|
||||
chats: [],
|
||||
isAdmin: true,
|
||||
@@ -2251,8 +2251,13 @@ export const SettingsAdminAgentsEntryPreserved: Story = {
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const agentsLink = canvas.getByRole("link", { name: "Agents" });
|
||||
await expect(agentsLink).toHaveAttribute("aria-current", "page");
|
||||
const coderAgentsLink = canvas.getByRole("link", {
|
||||
name: "Coder Agents",
|
||||
});
|
||||
expect(coderAgentsLink).toHaveAttribute(
|
||||
"href",
|
||||
"/ai/settings/coder-agents",
|
||||
);
|
||||
expect(canvas.getByText("Manage agents")).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
ArrowUpRightIcon,
|
||||
BotIcon,
|
||||
BoxesIcon,
|
||||
ChevronRightIcon,
|
||||
CoinsIcon,
|
||||
FlaskConicalIcon,
|
||||
KeyIcon,
|
||||
@@ -147,10 +146,9 @@ export const SettingsPanel: FC<SettingsPanelProps> = ({
|
||||
<SettingsNavItem
|
||||
icon={Settings2Icon}
|
||||
label="Manage agents"
|
||||
active={false}
|
||||
to="/agents/settings/admin"
|
||||
active={settingsSection === "experiments"}
|
||||
to="/agents/settings/experiments"
|
||||
state={location.state}
|
||||
trailingIcon={ChevronRightIcon}
|
||||
/>
|
||||
)}
|
||||
</nav>
|
||||
@@ -158,10 +156,10 @@ export const SettingsPanel: FC<SettingsPanelProps> = ({
|
||||
<nav className="flex flex-col gap-0.5 px-2 py-2">
|
||||
<SettingsNavItem
|
||||
icon={BotIcon}
|
||||
label="Agents"
|
||||
active={!settingsSection || settingsSection === "agents"}
|
||||
to="/agents/settings/agents"
|
||||
state={location.state}
|
||||
label="Coder Agents"
|
||||
active={false}
|
||||
to="/ai/settings/coder-agents"
|
||||
trailingIcon={ArrowUpRightIcon}
|
||||
/>
|
||||
<SettingsNavItem
|
||||
icon={PlugIcon}
|
||||
|
||||
+11
-4
@@ -378,8 +378,8 @@ const AgentSettingsExperimentsPage = lazy(
|
||||
const AISettingsLifecyclePage = lazy(
|
||||
() => import("./pages/AISettingsPage/LifecyclePage/LifecyclePage"),
|
||||
);
|
||||
const AgentSettingsAgentsPage = lazy(
|
||||
() => import("./pages/AgentsPage/AgentSettingsAgentsPage"),
|
||||
const CoderAgentsPage = lazy(
|
||||
() => import("./pages/AISettingsPage/CoderAgentsPage/CoderAgentsPage"),
|
||||
);
|
||||
const AgentSettingsUserAgentsPage = lazy(
|
||||
() => import("./pages/AgentsPage/AgentSettingsUserAgentsPage"),
|
||||
@@ -775,6 +775,7 @@ export const router = createBrowserRouter(
|
||||
element={<AISettingsInstructionsPage />}
|
||||
/>
|
||||
<Route path="lifecycle" element={<AISettingsLifecyclePage />} />
|
||||
<Route path="coder-agents" element={<CoderAgentsPage />} />
|
||||
<Route path="templates" element={<AISettingsTemplatesPage />} />
|
||||
<Route path="models/add" element={<AISettingsAddModelPage />} />
|
||||
<Route
|
||||
@@ -879,8 +880,14 @@ export const router = createBrowserRouter(
|
||||
path="personal-skills"
|
||||
element={<AgentSettingsPersonalSkillsPage />}
|
||||
/>
|
||||
<Route path="admin" element={<AgentSettingsAgentsPage />} />
|
||||
<Route path="agents" element={<AgentSettingsAgentsPage />} />
|
||||
<Route
|
||||
path="admin"
|
||||
element={<Navigate to="/ai/settings/coder-agents" replace />}
|
||||
/>
|
||||
<Route
|
||||
path="agents"
|
||||
element={<Navigate to="/ai/settings/coder-agents" replace />}
|
||||
/>
|
||||
<Route path="api-keys" element={<AgentSettingsAPIKeysPage />} />
|
||||
<Route
|
||||
path="providers"
|
||||
|
||||
Reference in New Issue
Block a user