fix(ui): open the classifier prompt editor above the edit auto-router form (#36438)

The prompt editor is a base-ui Dialog at z-index 50. The create form houses it in
the same base-ui Dialog, so it stacks on top, but the edit form was an antd Modal
whose portal computes to z-index 1000, so the editor opened underneath it and was
neither readable nor clickable.

Move the edit form onto the Dialog the create form already uses, which puts the
whole nesting chain in one overlay layer. A dialog opened from inside another
dialog now reads as a drill-down rather than a stack: base-ui stamps
data-nested-dialog-open on the parent while a child is open, so the parent steps
aside instead of showing its own edges around a differently sized child.
This commit is contained in:
tin-berri 2026-08-10 16:28:40 -07:00 committed by GitHub
parent 20354bfcdc
commit fd66d87e46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 61 additions and 27 deletions

View file

@ -235,3 +235,10 @@
.custom-border {
border: 1px solid var(--neutral-border);
}
/* A dialog opened from inside another dialog reads as a drill-down, not a stack: base-ui stamps
this attribute on the parent while a child is open, so the parent steps aside instead of
showing its own edges around a differently sized child. */
[data-slot="dialog-content"][data-nested-dialog-open] {
visibility: hidden;
}

View file

@ -6,12 +6,19 @@ import { fireEvent, renderWithProviders, screen, waitFor, within } from "@/../te
import NotificationsManager from "@/components/molecules/notifications_manager";
import EditAutoRouterModal from "./edit_auto_router_modal";
const { modelPatchUpdateCall, modelAvailableCall } = vi.hoisted(() => ({
const { modelPatchUpdateCall, modelAvailableCall, getAutoRouterClassifierDefaultPromptCall } = vi.hoisted(() => ({
modelPatchUpdateCall: vi.fn().mockResolvedValue({}),
modelAvailableCall: vi.fn().mockResolvedValue({ data: [] }),
getAutoRouterClassifierDefaultPromptCall: vi.fn().mockResolvedValue("Classify the request into exactly one tier."),
}));
vi.mock("../networking", () => ({ modelPatchUpdateCall, modelAvailableCall }));
vi.mock("../networking", () => ({
modelPatchUpdateCall,
modelAvailableCall,
getAutoRouterClassifierDefaultPromptCall,
}));
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ default: () => ({ accessToken: "sk-test" }) }));
vi.mock("@/components/llm_calls/fetch_models", () => ({
fetchAvailableModels: vi.fn().mockResolvedValue([{ model_group: "gpt-4o-mini" }]),
@ -243,6 +250,22 @@ describe("EditAutoRouterModal classifier context window", () => {
expect(config.classifier_context_per_turn_chars).toBe(300);
});
// The prompt editor is a base-ui Dialog at z-index 50. Housing this form in an antd Modal put a
// z-index 1000 overlay between the operator and it, so the editor opened underneath and could
// not be read or typed into. jsdom does not paint, so the assertion is the invariant behind the
// stacking: both overlays come from the one Dialog primitive the create form already uses.
it("opens the classifier prompt editor in the same overlay layer as the form", async () => {
const user = userEvent.setup();
const { baseElement } = renderLlmModal();
await user.click(await screen.findByText("Advanced: Classification Method"));
await user.click(await screen.findByRole("button", { name: /prompt/i }));
expect(await screen.findByLabelText("Classifier system prompt")).toBeInTheDocument();
expect(baseElement.querySelectorAll('[data-slot="dialog-content"]')).toHaveLength(2);
expect(baseElement.querySelector(".ant-modal")).toBeNull();
});
it("persists an edited classifier context window size", async () => {
const user = userEvent.setup();
renderLlmModal();

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import { Modal, Form, Button, Select as AntdSelect, Tooltip } from "antd";
import { Text, TextInput } from "@tremor/react";
import { Form, Button, Select as AntdSelect, Tooltip } from "antd";
import { TextInput } from "@tremor/react";
import { modelAvailableCall, modelPatchUpdateCall } from "../networking";
import { fetchAvailableModels, ModelGroup } from "@/components/llm_calls/fetch_models";
import RouterConfigBuilder from "../add_model/RouterConfigBuilder";
@ -24,6 +24,14 @@ import ComplexityRouterConfig, {
DEFAULT_TIER_DISTANCE_PENALTY,
} from "../add_model/ComplexityRouterConfig";
import NotificationsManager from "../molecules/notifications_manager";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
interface EditAutoRouterModalProps {
isVisible: boolean;
@ -432,27 +440,14 @@ const EditAutoRouterModal: React.FC<EditAutoRouterModalProps> = ({
}));
return (
<Modal
title="Edit Auto Router Configuration"
open={isVisible}
onCancel={onCancel}
footer={[
<Button key="cancel" onClick={onCancel}>
Cancel
</Button>,
<Tooltip key="submit" title={submitBlockedReason}>
<Button loading={loading} disabled={submitBlockedReason !== null} onClick={handleSubmit}>
Save Changes
</Button>
</Tooltip>,
]}
width={1000}
destroyOnHidden
>
<div className="space-y-6">
<Text className="text-gray-600">
Edit the auto router configuration including routing logic, default models, and access settings.
</Text>
<Dialog open={isVisible} onOpenChange={(open) => !open && onCancel()}>
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-4xl">
<DialogHeader>
<DialogTitle>Edit Auto Router Configuration</DialogTitle>
<DialogDescription>
Edit the auto router configuration including routing logic, default models, and access settings.
</DialogDescription>
</DialogHeader>
<Form form={form} layout="vertical" className="space-y-4">
{/* Auto Router Name */}
@ -552,8 +547,17 @@ const EditAutoRouterModal: React.FC<EditAutoRouterModalProps> = ({
</Form.Item>
)}
</Form>
</div>
</Modal>
<DialogFooter>
<Button onClick={onCancel}>Cancel</Button>
<Tooltip title={submitBlockedReason}>
<Button loading={loading} disabled={submitBlockedReason !== null} onClick={handleSubmit}>
Save Changes
</Button>
</Tooltip>
</DialogFooter>
</DialogContent>
</Dialog>
);
};