refactor(site/src/pages/AgentsPage): use model selector for personal overrides (#27229)

This commit is contained in:
Danielle Maywood
2026-07-16 11:22:48 +01:00
committed by GitHub
parent 35ade9e3d2
commit 9d1e881093
5 changed files with 272 additions and 281 deletions
@@ -70,6 +70,16 @@ const claudeModelConfig = buildModelConfig({
context_limit: 200_000,
});
const reasoningModelConfig = buildModelConfig({
id: "model-gpt-5",
model: "gpt-5",
display_name: "GPT-5",
model_config: {
reasoning_effort: { default: "medium", max: "high" },
},
reasoning_efforts: ["none", "minimal", "low", "medium", "high"],
});
const disabledModelConfig = buildModelConfig({
id: "model-disabled",
model: "gpt-4.1-legacy",
@@ -87,10 +97,21 @@ const inaccessibleModelConfig = buildModelConfig({
const modelConfigs = [
defaultModelConfig,
claudeModelConfig,
reasoningModelConfig,
disabledModelConfig,
inaccessibleModelConfig,
];
const reasoningModelOption: ModelSelectorOption = {
id: reasoningModelConfig.id,
provider: "openai",
model: reasoningModelConfig.model,
displayName: reasoningModelConfig.display_name,
contextLimit: reasoningModelConfig.context_limit,
reasoningEffortDefault: "medium",
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],
};
const modelOptions: ModelSelectorOption[] = [
{
id: defaultModelConfig.id,
@@ -106,6 +127,7 @@ const modelOptions: ModelSelectorOption[] = [
displayName: claudeModelConfig.display_name,
contextLimit: claudeModelConfig.context_limit,
},
reasoningModelOption,
];
const buildOverridesResponse = (
@@ -168,14 +190,16 @@ const getSection = async (
const selectOption = async (
section: HTMLElement,
canvasElement: HTMLElement,
comboboxName: string | RegExp,
comboboxName: string,
optionName: string | RegExp,
) => {
await userEvent.click(
within(section).getByRole("combobox", { name: comboboxName }),
);
const combobox = within(section).getByRole("combobox", {
name: comboboxName,
});
await userEvent.click(combobox);
const body = within(canvasElement.ownerDocument.body);
await userEvent.click(await body.findByRole("option", { name: optionName }));
return combobox;
};
const meta = {
@@ -236,10 +260,20 @@ export const EnabledWithSavedValues: Story = {
}),
play: async ({ canvasElement, args }) => {
const rootSection = await getSection(canvasElement, "Root agent model");
const exploreSection = await getSection(
canvasElement,
"Explore subagent model",
);
expect(
within(exploreSection).getByRole("combobox", {
name: "Explore subagent model behavior, Claude Sonnet 4",
}),
).toHaveTextContent("Claude Sonnet 4");
await selectOption(
rootSection,
canvasElement,
"Root agent model behavior",
"Root agent model behavior, Chat default: GPT 4.1 Mini",
/Claude Sonnet 4/i,
);
const rootSaveButton = within(rootSection).getByRole("button", {
@@ -263,12 +297,13 @@ export const EnabledWithSavedValues: Story = {
await selectOption(
generalSection,
canvasElement,
"General subagent model behavior",
"General subagent model behavior, Deployment default: Claude Sonnet 4",
/Chat default/i,
);
await userEvent.click(
within(generalSection).getByRole("button", { name: "Save" }),
);
await waitFor(() => {
expect(args.onSaveGeneralModelOverride).toHaveBeenCalledWith(
{ mode: "chat_default", model_config_id: "" },
@@ -278,6 +313,112 @@ export const EnabledWithSavedValues: Story = {
},
};
export const SavedReasoningModel: Story = {
args: buildArgs({
modelOptions: [
{
id: defaultModelConfig.id,
provider: "openai",
model: defaultModelConfig.model,
displayName: defaultModelConfig.display_name,
contextLimit: defaultModelConfig.context_limit,
},
{
id: reasoningModelConfig.id,
provider: "openai",
model: reasoningModelConfig.model,
displayName: reasoningModelConfig.display_name,
contextLimit: reasoningModelConfig.context_limit,
reasoningEffortDefault: "medium",
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],
},
],
overridesData: buildOverridesResponse({
root: buildOverride("root", {
mode: "model",
model_config_id: defaultModelConfig.id,
is_set: true,
}),
}),
}),
play: async ({ canvasElement, args }) => {
const rootSection = await getSection(canvasElement, "Root agent model");
const modelPicker = await selectOption(
rootSection,
canvasElement,
"Root agent model behavior, GPT 4.1 Mini",
/GPT-5/i,
);
const body = within(canvasElement.ownerDocument.body);
expect(modelPicker).toHaveAttribute("aria-expanded", "true");
expect(await body.findByRole("listbox")).toBeVisible();
const slider = await body.findByRole("slider");
expect(slider).toBeVisible();
expect(slider).toHaveAttribute("aria-valuenow", "3");
expect(body.getByText("Medium")).toBeVisible();
const infoTrigger = body.getByRole("button", {
name: "About reasoning effort",
});
await userEvent.tab();
expect(infoTrigger).toHaveFocus();
await userEvent.tab();
expect(slider).toHaveFocus();
await userEvent.keyboard("{ArrowRight}");
await waitFor(() => {
expect(slider).toHaveAttribute("aria-valuenow", "4");
});
expect(body.getByText("High")).toBeVisible();
await userEvent.keyboard("{Escape}");
await userEvent.click(
within(rootSection).getByRole("button", { name: "Save" }),
);
await waitFor(() => {
expect(args.onSaveRootModelOverride).toHaveBeenCalledWith(
{
mode: "model",
model_config_id: reasoningModelConfig.id,
reasoning_effort: "high",
},
expect.anything(),
);
});
},
};
export const SavedLowReasoningEffort: Story = {
args: buildArgs({
modelOptions: [reasoningModelOption],
overridesData: buildOverridesResponse({
root: buildOverride("root", {
mode: "model",
model_config_id: reasoningModelConfig.id,
reasoning_effort: "low",
is_set: true,
}),
}),
}),
play: async ({ canvasElement }) => {
const rootSection = await getSection(canvasElement, "Root agent model");
const modelPicker = within(rootSection).getByRole("combobox", {
name: "Root agent model behavior, GPT-5",
});
expect(modelPicker).toHaveTextContent("GPT-5");
await userEvent.click(modelPicker);
const body = within(canvasElement.ownerDocument.body);
expect(modelPicker).toHaveAttribute("aria-expanded", "true");
const slider = await body.findByRole("slider");
expect(slider).toHaveAttribute("aria-valuenow", "2");
await waitFor(() => {
expect(body.getByText("Low")).toBeVisible();
});
},
};
export const MalformedSavedValues: Story = {
args: buildArgs({
overridesData: buildOverridesResponse({
@@ -460,19 +601,19 @@ export const ModelConfigsError: Story = {
await selectOption(
rootSection,
canvasElement,
"Root agent model behavior",
"Root agent model behavior, Claude Sonnet 4",
/Chat default/i,
);
await selectOption(
generalSection,
canvasElement,
"General subagent model behavior",
"General subagent model behavior, Claude Sonnet 4",
/Deployment default/i,
);
await selectOption(
exploreSection,
canvasElement,
"Explore subagent model behavior",
"Explore subagent model behavior, Claude Sonnet 4",
/Chat default/i,
);
@@ -493,7 +634,7 @@ export const LoadingState: Story = {
const rootSection = await getSection(canvasElement, "Root agent model");
expect(
within(rootSection).getByRole("combobox", {
name: "Root agent model behavior",
name: "Root agent model behavior, Chat default: GPT 4.1 Mini",
}),
).toBeDisabled();
expect(
@@ -576,7 +717,7 @@ export const AdminDisabledReadOnly: Story = {
const rootSection = await getSection(canvasElement, "Root agent model");
expect(
within(rootSection).getByRole("combobox", {
name: "Root agent model behavior",
name: "Root agent model behavior, GPT 4.1 Mini",
}),
).toBeDisabled();
expect(
@@ -609,7 +750,7 @@ export const InvalidRootDeploymentDefault: Story = {
await selectOption(
rootSection,
canvasElement,
"Root agent model behavior",
"Root agent model behavior, Invalid deployment default",
/Chat default/i,
);
await userEvent.click(
@@ -92,6 +92,21 @@ export const WithSelectedValue: Story = {
},
};
export const CustomTriggerLabel: Story = {
args: {
options: openAIModels,
value: "openai/gpt-4o",
triggerAriaLabel: "Agent model behavior",
},
play: async ({ canvasElement }) => {
expect(
within(canvasElement).getByRole("combobox", {
name: "Agent model behavior, GPT-4o",
}),
).toBeInTheDocument();
},
};
export const CustomPlaceholder: Story = {
args: {
placeholder: "Choose a model…",
@@ -197,6 +212,7 @@ export const SelectsModel: Story = {
options: openAIModels,
value: "",
onValueChange: fn(),
onReasoningEffortChange: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
@@ -204,10 +220,20 @@ export const SelectsModel: Story = {
const trigger = canvas.getByRole("combobox");
await userEvent.click(trigger);
const listbox = await within(document.body).findByRole("listbox");
const body = within(document.body);
const listbox = await body.findByRole("listbox");
const search = body.getByPlaceholderText("Search...");
await userEvent.type(search, "mini");
await userEvent.click(within(listbox).getByText("GPT-4o Mini"));
expect(args.onValueChange).toHaveBeenCalledWith("openai/gpt-4o-mini");
await waitFor(() => {
expect(trigger).toHaveAttribute("aria-expanded", "false");
expect(body.queryByRole("listbox")).not.toBeInTheDocument();
});
await userEvent.click(trigger);
expect(await body.findByPlaceholderText("Search...")).toHaveValue("");
},
};
@@ -298,16 +324,22 @@ export const EffortRowHiddenWithoutConfig: Story = {
};
const EffortRowStory = ({
onValueChange,
onReasoningEffortChange,
}: {
onValueChange: (value: string) => void;
onReasoningEffortChange: (value: string) => void;
}) => {
const [model, setModel] = useState("openai/gpt-4o");
const [effort, setEffort] = useState("medium");
return (
<ModelSelector
options={[...openAIModels, effortModel]}
value="openai/gpt-5"
onValueChange={fn()}
value={model}
onValueChange={(value) => {
onValueChange(value);
setModel(value);
}}
reasoningEffort={effort}
onReasoningEffortChange={(value) => {
onReasoningEffortChange(value);
@@ -323,6 +355,7 @@ export const EffortRow: Story = {
},
render: (args) => (
<EffortRowStory
onValueChange={args.onValueChange}
onReasoningEffortChange={(value) => args.onReasoningEffortChange?.(value)}
/>
),
@@ -330,11 +363,20 @@ export const EffortRow: Story = {
const canvas = within(canvasElement);
const body = within(document.body);
await userEvent.click(canvas.getByRole("combobox", { name: "GPT-5" }));
await body.findByRole("listbox");
const trigger = canvas.getByRole("combobox", { name: "GPT-4o" });
await userEvent.click(trigger);
const listbox = await body.findByRole("listbox");
const search = body.getByPlaceholderText("Search...");
await userEvent.type(search, "gpt-5");
await userEvent.click(
within(listbox).getByRole("option", { name: /GPT-5/ }),
);
// The row is visible with one discrete step per selectable effort.
expect(args.onValueChange).toHaveBeenCalledWith("openai/gpt-5");
await waitFor(() => {
expect(trigger).toHaveAttribute("aria-expanded", "true");
expect(listbox).toBeVisible();
expect(search).toHaveValue("");
expect(body.getByText("Effort")).toBeVisible();
});
const slider = await body.findByRole("slider");
@@ -1,39 +0,0 @@
import { screen } from "@testing-library/react";
import { render } from "#/testHelpers/renderHelpers";
import { ModelSelector, type ModelSelectorOption } from "./ModelSelector";
import { MockModelSelectorOption } from "./modelSelectorFixtures";
const mockModelOptions: readonly ModelSelectorOption[] = [
{
...MockModelSelectorOption,
id: "gpt-4o-mini",
model: "gpt-4o-mini",
displayName: "GPT-4o mini",
},
{
...MockModelSelectorOption,
id: "claude-opus",
provider: "anthropic",
model: "claude-opus-4-1",
displayName: "Claude Opus 4.1",
contextLimit: 1_000_000,
},
];
test("suppresses mouse-focus ring but keeps keyboard-focus ring on model selector trigger", () => {
render(
<ModelSelector
options={mockModelOptions}
value="gpt-4o-mini"
onValueChange={vi.fn()}
/>,
);
const trigger = screen.getByRole("combobox");
// Mouse-focus ring should be suppressed.
expect(trigger.className).toContain("focus:ring-0");
// Keyboard-focus ring should remain.
expect(trigger.className).toContain("focus-visible:ring-2");
expect(trigger.className).not.toContain("focus-visible:ring-0");
});
@@ -43,6 +43,11 @@ interface ModelSelectorProps {
options: readonly ModelSelectorOption[];
value: string;
onValueChange: (value: string) => void;
/**
* When set, the trigger's accessible name is this contextual label followed
* by the selected model's display name or the placeholder.
*/
triggerAriaLabel?: string;
disabled?: boolean;
placeholder?: string;
/**
@@ -92,6 +97,7 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
options,
value,
onValueChange,
triggerAriaLabel,
disabled = false,
placeholder = "Select model",
unsetLabel,
@@ -115,6 +121,7 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
setOpen(nextOpen);
};
const selectedModel = options.find((option) => option.id === value);
const triggerLabel = selectedModel?.displayName ?? placeholder;
// With an unset option the selector stays usable even when no model
// options exist, so a saved override can still be switched back.
const isDisabled = disabled || (options.length === 0 && !unsetLabel);
@@ -144,7 +151,11 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
<Popover open={open} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild disabled={isDisabled}>
<Button
aria-label={selectedModel ? selectedModel.displayName : placeholder}
aria-label={
triggerAriaLabel
? `${triggerAriaLabel}, ${triggerLabel}`
: triggerLabel
}
aria-expanded={open}
aria-haspopup="listbox"
disabled={isDisabled}
@@ -157,9 +168,7 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
)}
onTouchStart={onTriggerTouchStart}
>
<span className="truncate">
{selectedModel ? selectedModel.displayName : placeholder}
</span>
<span className="truncate">{triggerLabel}</span>
<ChevronDownIcon open={open} className="size-icon-sm" />
</Button>
</PopoverTrigger>
@@ -263,7 +272,13 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
isSelected={option.id === value}
onSelect={() => {
onValueChange(option.id);
handleOpenChange(false);
setSearch("");
if (
!option.reasoningEfforts?.length ||
!onReasoningEffortChange
) {
handleOpenChange(false);
}
}}
/>
))}
@@ -1,23 +1,10 @@
import { useFormik } from "formik";
import { Select as SelectPrimitive } from "radix-ui";
import type { FC } from "react";
import type * as TypesGen from "#/api/typesGenerated";
import { Alert, AlertDescription } from "#/components/Alert/Alert";
import { Button } from "#/components/Button/Button";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "#/components/Select/Select";
import { Slider } from "#/components/Slider/Slider";
import {
formatReasoningEffort,
pickReasoningEffort,
} from "../utils/reasoningEffort";
import type { ModelSelectorOption } from "./ChatElements";
import { pickReasoningEffort } from "../utils/reasoningEffort";
import { ModelSelector, type ModelSelectorOption } from "./ChatElements";
import { ModelOverrideAlerts } from "./ModelOverrideAlerts";
import { SectionHeader } from "./SectionHeader";
@@ -107,10 +94,6 @@ const getModelConfigLabel = (modelConfig: TypesGen.ChatModelConfig): string => {
return modelConfig.display_name.trim() || modelConfig.model || modelConfig.id;
};
const getModelOptionLabel = (option: ModelSelectorOption): string => {
return option.displayName.trim() || option.model || option.id;
};
const getModelConfigLabelByID = (
modelConfigID: string,
modelConfigs: readonly TypesGen.ChatModelConfig[],
@@ -173,61 +156,12 @@ const getDeploymentDefaultDescription = (
);
};
const getSelectionLabel = ({
context,
deploymentDefault,
isInvalidRootDeploymentDefault,
modelConfigs,
modelOptions,
values,
}: {
context: PersonalOverrideContext;
deploymentDefault?: TypesGen.ChatModelOverrideResponse;
isInvalidRootDeploymentDefault: boolean;
modelConfigs: readonly TypesGen.ChatModelConfig[];
modelOptions: readonly ModelSelectorOption[];
values: PersonalOverrideFormValues;
}): string => {
if (isInvalidRootDeploymentDefault) {
return "Invalid deployment default";
}
switch (values.mode) {
case "chat_default":
return `Chat default: ${getChatDefaultDescription(context, modelConfigs)}`;
case "deployment_default":
return `Deployment default: ${getDeploymentDefaultDescription(
deploymentDefault,
modelConfigs,
)}`;
case "model": {
const modelConfigID = values.model_config_id.trim();
const modelOption = modelOptions.find(
(option) => option.id === modelConfigID,
);
if (modelOption) {
return getModelOptionLabel(modelOption);
}
return modelConfigID === ""
? "Select..."
: getUnavailableModelLabel(modelConfigID, modelConfigs);
}
}
};
const isDefaultModeOption = (
value: string,
): value is Exclude<PersonalOverrideMode, "model"> => {
return value === "chat_default" || value === "deployment_default";
};
// Local separator for use inside SelectContent. Defined here instead of
// in the core Select component so the styling stays scoped to this
// feature until a shared design lands.
const SelectSeparator: FC = () => (
<SelectPrimitive.Separator className="-mx-1 my-1 h-px bg-border" />
);
export const PersonalModelOverrideRow: FC<PersonalModelOverrideRowProps> = ({
context,
title,
@@ -259,7 +193,21 @@ export const PersonalModelOverrideRow: FC<PersonalModelOverrideRowProps> = ({
disabled || isSaving || isLoading || !hasLoadedOverride;
const canSave =
hasLoadedOverride && !disabled && (form.dirty || isMalformedOverride);
const defaultModeOptions = getDefaultModeOptions(context);
const defaultModeOptions = getDefaultModeOptions(context).map((mode) => {
const label =
mode === "deployment_default" ? "Deployment default" : "Chat default";
const modeDescription =
mode === "deployment_default"
? getDeploymentDefaultDescription(deploymentDefault, modelConfigs)
: getChatDefaultDescription(context, modelConfigs);
return {
id: mode,
provider: "defaults",
providerLabel: "Defaults",
model: mode,
displayName: `${label}: ${modeDescription}`,
};
});
const isInvalidRootDeploymentDefault =
context === "root" && overrideData?.mode === "deployment_default";
const isUnavailableSavedModel =
@@ -286,14 +234,6 @@ export const PersonalModelOverrideRow: FC<PersonalModelOverrideRowProps> = ({
selectedModelOption.reasoningEffortDefault,
)
: undefined;
const selectionLabel = getSelectionLabel({
context,
deploymentDefault,
isInvalidRootDeploymentDefault,
modelConfigs,
modelOptions,
values: form.values,
});
const canSaveSelection =
canSave &&
(form.values.mode !== "model" ||
@@ -304,7 +244,8 @@ export const PersonalModelOverrideRow: FC<PersonalModelOverrideRowProps> = ({
<section aria-label={title} className="flex flex-col gap-3">
<SectionHeader label={title} description={description} level="section" />
<form className="flex flex-col gap-3" onSubmit={form.handleSubmit}>
<Select
<ModelSelector
options={[...defaultModeOptions, ...modelOptions]}
value={selectionValue}
onValueChange={(value) => {
if (isDefaultModeOption(value)) {
@@ -316,83 +257,47 @@ export const PersonalModelOverrideRow: FC<PersonalModelOverrideRowProps> = ({
return;
}
const option = modelOptions.find((option) => option.id === value);
const reasoningEffortDefault = option
? option.reasoningEffortDefault
: undefined;
let reasoningEffort = "";
if (option) {
reasoningEffort =
pickReasoningEffort(
"",
option.reasoningEfforts ?? [],
option.reasoningEffortDefault,
) ?? "";
}
void form.setValues({
mode: "model",
model_config_id: value,
reasoning_effort:
pickReasoningEffort(
"",
option?.reasoningEfforts ?? [],
reasoningEffortDefault,
) ?? "",
reasoning_effort: reasoningEffort,
});
}}
disabled={isFormDisabled}
>
<SelectTrigger
aria-label={`${title} behavior`}
className="h-10 w-full justify-between rounded-md border border-border border-solid bg-transparent px-3 text-sm shadow-sm md:w-[18rem]"
>
<SelectValue placeholder="Select...">{selectionLabel}</SelectValue>
</SelectTrigger>
<SelectContent className="min-w-[18rem]">
{isInvalidRootDeploymentDefault && (
<>
<SelectItem value="deployment_default" disabled>
Invalid deployment default
</SelectItem>
<SelectSeparator />
</>
)}
<SelectGroup>
{defaultModeOptions.map((mode) => (
<DefaultModeSelectItem
key={mode}
mode={mode}
context={context}
deploymentDefault={deploymentDefault}
modelConfigs={modelConfigs}
/>
))}
</SelectGroup>
<SelectSeparator />
{isUnavailableSelectedModel && (
<>
<SelectItem value={form.values.model_config_id} disabled>
{getUnavailableModelLabel(
placeholder={
isInvalidRootDeploymentDefault
? "Invalid deployment default"
: isUnavailableSelectedModel
? getUnavailableModelLabel(
form.values.model_config_id,
modelConfigs,
)}
</SelectItem>
<SelectSeparator />
</>
)}
<SelectGroup>
{modelOptions.map((option) => (
<SelectItem key={option.id} value={option.id}>
{getModelOptionLabel(option)}
</SelectItem>
))}
{modelOptions.length === 0 && (
<SelectItem value="__empty_models__" disabled>
{isLoading ? "Loading models..." : "No enabled models found."}
</SelectItem>
)}
</SelectGroup>
</SelectContent>
</Select>
{selectedReasoningEffort !== undefined && selectedModelOption && (
<PersonalReasoningEffortRow
option={selectedModelOption}
value={selectedReasoningEffort}
onChange={(value) =>
void form.setFieldValue("reasoning_effort", value)
}
/>
)
: "Select..."
}
triggerAriaLabel={`${title} behavior`}
emptyMessage="No matching models found."
className="h-10 w-full justify-between rounded-md border border-border border-solid bg-transparent px-3 text-sm shadow-sm md:w-[18rem]"
contentClassName="min-w-[18rem]"
reasoningEffort={selectedReasoningEffort}
onReasoningEffortChange={(value) =>
void form.setFieldValue("reasoning_effort", value)
}
/>
{modelOptions.length === 0 && (
<p role="status" className="m-0 text-xs text-content-secondary">
{isLoading ? "Loading models..." : "No enabled models found."}
</p>
)}
<ModelOverrideAlerts
isUnavailableSavedModel={isUnavailableSavedModel}
unavailableMessage="The saved model is unavailable and will be ignored until you choose a valid model override."
@@ -428,76 +333,3 @@ export const PersonalModelOverrideRow: FC<PersonalModelOverrideRowProps> = ({
</section>
);
};
interface PersonalReasoningEffortRowProps {
option: ModelSelectorOption;
value: string;
onChange: (value: string) => void;
}
const PersonalReasoningEffortRow: FC<PersonalReasoningEffortRowProps> = ({
option,
value,
onChange,
}) => {
const selectableEfforts = option.reasoningEfforts ?? [];
if (selectableEfforts.length === 0) {
return null;
}
const valueIndex = selectableEfforts.indexOf(value);
const effortIndex = valueIndex >= 0 ? valueIndex : 0;
return (
<div className="flex items-center gap-3 md:w-[18rem]">
<span className="shrink-0 text-content-secondary text-sm">Effort</span>
<Slider
aria-label={`${option.displayName} reasoning effort`}
value={[effortIndex]}
onValueChange={([index]) => {
const nextEffort = selectableEfforts[index];
if (nextEffort && nextEffort !== value) {
onChange(nextEffort);
}
}}
min={0}
max={selectableEfforts.length - 1}
step={1}
/>
<span className="shrink-0 rounded bg-surface-secondary px-1.5 py-0.5 text-content-secondary text-xs font-medium leading-[18px]">
{formatReasoningEffort(value)}
</span>
</div>
);
};
interface DefaultModeSelectItemProps {
mode: Exclude<PersonalOverrideMode, "model">;
context: PersonalOverrideContext;
deploymentDefault?: TypesGen.ChatModelOverrideResponse;
modelConfigs: readonly TypesGen.ChatModelConfig[];
}
const DefaultModeSelectItem: FC<DefaultModeSelectItemProps> = ({
mode,
context,
deploymentDefault,
modelConfigs,
}) => {
const label =
mode === "deployment_default" ? "Deployment default" : "Chat default";
const description =
mode === "deployment_default"
? getDeploymentDefaultDescription(deploymentDefault, modelConfigs)
: getChatDefaultDescription(context, modelConfigs);
return (
<SelectItem value={mode}>
<span className="flex min-w-0 flex-col">
<span className="truncate text-content-primary">{label}</span>
<span className="truncate text-content-secondary text-xs leading-tight">
{description}
</span>
</span>
</SelectItem>
);
};