fix: improve agent model picker layout (#2941)
This commit is contained in:
@@ -183,6 +183,8 @@ export type ModelConfigSelectorProps = {
|
||||
placeholder?: string;
|
||||
ariaLabel?: string;
|
||||
variant?: "compact" | "field";
|
||||
/** Aligns the picker with the trigger edge for context-specific settings surfaces. */
|
||||
popoverAlign?: "start" | "center" | "end";
|
||||
popoverSide?: "top" | "bottom";
|
||||
triggerClassName?: string;
|
||||
triggerSummary?: "all" | "changed";
|
||||
@@ -274,6 +276,7 @@ export const ModelConfigSelector = memo(function ModelConfigSelector({
|
||||
placeholder,
|
||||
ariaLabel,
|
||||
variant = "field",
|
||||
popoverAlign = "end",
|
||||
popoverSide = "bottom",
|
||||
triggerClassName: customTriggerClassName,
|
||||
triggerSummary = "all",
|
||||
@@ -333,7 +336,7 @@ export const ModelConfigSelector = memo(function ModelConfigSelector({
|
||||
variant={variant}
|
||||
/>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
align={popoverAlign}
|
||||
side={popoverSide}
|
||||
className="w-[min(24rem,calc(100vw-1rem))] max-h-[min(32rem,calc(100vh-1rem))] gap-2 overflow-hidden p-2"
|
||||
>
|
||||
|
||||
@@ -198,6 +198,25 @@ describe("ProfileFormFields no-silent-model-fallback rows", () => {
|
||||
});
|
||||
|
||||
describe("ProfileFormFields model options", () => {
|
||||
it("constrains a single start model field on desktop", () => {
|
||||
renderForm(formData());
|
||||
|
||||
const row = screen.getByTestId("profile-capabilities-model-row");
|
||||
expect(row.firstElementChild?.className).toContain("md:max-w-xl");
|
||||
});
|
||||
|
||||
it("keeps the model and mode fields balanced when modes are available", () => {
|
||||
renderForm(formData({ mode: "default" }), {
|
||||
...modelConfig,
|
||||
available_modes: [{ id: "default", name: "Default" }],
|
||||
current_mode_id: "default",
|
||||
});
|
||||
|
||||
const row = screen.getByTestId("profile-capabilities-model-row");
|
||||
expect(row.firstElementChild?.className).toContain("flex-1");
|
||||
expect(screen.getByTestId("profile-mode-field")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("loads model-specific options in the profile model selector", async () => {
|
||||
const dynamicModelConfig: ModelConfig = {
|
||||
default_model: "model-a",
|
||||
|
||||
@@ -338,7 +338,7 @@ function CapabilitiesRowContent({
|
||||
<div className={gapCls}>
|
||||
<div className="flex items-end gap-2" data-testid="profile-capabilities-model-row">
|
||||
<div
|
||||
className={`flex-1 min-w-0 ${gapCls}`}
|
||||
className={`${hasModes ? "flex-1" : "w-full md:max-w-xl"} min-w-0 ${gapCls}`}
|
||||
data-settings-dirty={profileModelIsDirty(profile, baselineProfile)}
|
||||
data-settings-dirty-level="container"
|
||||
>
|
||||
|
||||
@@ -109,6 +109,7 @@ export function ModelPicker({
|
||||
}
|
||||
placeholder={placeholder ?? t("settings:selectAModel")}
|
||||
ariaLabel={ariaLabel}
|
||||
popoverAlign="start"
|
||||
disabled={disabled}
|
||||
configOptionsLoading={configOptionsLoading}
|
||||
keepOpenOnModelChange={keepOpenOnModelChange}
|
||||
|
||||
@@ -52,6 +52,44 @@ test.describe("Agent profile — ACP-first", () => {
|
||||
await expect(testPage.getByTestId("profile-mode-field")).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("profile model picker opens from the field start edge", async ({ testPage, apiClient }) => {
|
||||
test.setTimeout(60_000);
|
||||
|
||||
const { agents } = await apiClient.listAgents();
|
||||
const agent = agents.find((item) => item.name === "mock-agent") ?? agents[0];
|
||||
const profile = agent.profiles[0];
|
||||
|
||||
await testPage.goto(`/settings/agents/${agent.name}/profiles/${profile.id}`);
|
||||
|
||||
const profileSettingsPanel = testPage.locator(
|
||||
'[data-settings-target^="setting-agent-profile-"][data-settings-target$="-profile-settings"]',
|
||||
);
|
||||
const selector = profileSettingsPanel.getByRole("button", {
|
||||
name: "Profile start model settings",
|
||||
});
|
||||
await expect(selector).toBeVisible({ timeout: 15_000 });
|
||||
await selector.click();
|
||||
|
||||
const popoverContent = testPage.locator('[data-slot="popover-content"]:visible');
|
||||
await expect(popoverContent).toBeVisible();
|
||||
await popoverContent.evaluate(async (element) => {
|
||||
await Promise.all(
|
||||
element.getAnimations().map((animation) => animation.finished.catch(() => undefined)),
|
||||
);
|
||||
});
|
||||
|
||||
const [selectorBox, popoverBox] = await Promise.all([
|
||||
selector.boundingBox(),
|
||||
popoverContent.boundingBox(),
|
||||
]);
|
||||
|
||||
expect(selectorBox).not.toBeNull();
|
||||
expect(popoverBox).not.toBeNull();
|
||||
// Radix may nudge the content by a few pixels to satisfy collision padding;
|
||||
// the regression is the old end alignment, which placed it ~50px inward.
|
||||
expect(Math.abs(popoverBox!.x - selectorBox!.x)).toBeLessThanOrEqual(12);
|
||||
});
|
||||
|
||||
test("profile name edits persist across reload", async ({ testPage, apiClient }) => {
|
||||
test.setTimeout(60_000);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user