mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(ui): always show the Lens Settings tab
The tab only rendered once GET /lens succeeded for a configuring session, so a slow or failing list, or a read-only session, silently hid it. Settings now always renders, and the worker section shows only where it can be used Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
461a58c40a
commit
60e7726116
4 changed files with 55 additions and 42 deletions
|
|
@ -57,15 +57,15 @@ export function LensModeSwitch({
|
|||
setup?: string;
|
||||
}) {
|
||||
const connected = useWorkerConnected(workers);
|
||||
const settingsTitle = connected ? "Worker connected" : "Connect worker";
|
||||
const tabs = Object.entries(LENS_TABS).filter(([view]) => view !== "settings" || workers);
|
||||
const workerTitle = connected ? "Worker connected" : "Connect worker";
|
||||
const settingsTitle = workers === null ? LENS_TABS.settings : workerTitle;
|
||||
return (
|
||||
<div className={frameTab({ session: demo ? "demo" : "live" })}>
|
||||
<NotchCorner side="left" demo={demo} />
|
||||
<NotchCorner side="right" demo={demo} />
|
||||
<TabsPrimitive.List aria-label="Lens" className="relative inline-flex h-9 items-center p-1">
|
||||
<TabsPrimitive.Indicator className="absolute top-1 bottom-1 left-(--active-tab-left) w-(--active-tab-width) rounded-full bg-muted transition-[left,width] duration-300 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none" />
|
||||
{tabs.map(([view, label]) => {
|
||||
{Object.entries(LENS_TABS).map(([view, label]) => {
|
||||
const Icon = MODE_ICONS[view as LensTab];
|
||||
const workerDisconnected = view === "settings" && workers !== null && !connected;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -280,8 +280,8 @@ describe("Lens interactive demo", () => {
|
|||
});
|
||||
renderWithProviders(<LensWorkspace accessToken="live-token" userRole="Admin" readOnly={false} />, { onUrlUpdate });
|
||||
const tabs = within(screen.getByRole("tablist", { name: "Lens" }));
|
||||
const settings = await tabs.findByRole("tab", { name: "Settings" });
|
||||
expect(settings).toHaveAttribute("title", "Worker connected");
|
||||
const settings = tabs.getByRole("tab", { name: "Settings" });
|
||||
await waitFor(() => expect(settings).toHaveAttribute("title", "Worker connected"));
|
||||
await user.click(settings);
|
||||
await expectUrl(onUrlUpdate, (url) => expect(url.get("tab")).toBe("settings"));
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
|
|
@ -357,7 +357,7 @@ describe("Lens interactive demo", () => {
|
|||
onUrlUpdate,
|
||||
});
|
||||
const panel = within(await screen.findByRole("region", { name: "Settings" }));
|
||||
await user.click(panel.getByText("Advanced options"));
|
||||
await user.click(await panel.findByText("Advanced options"));
|
||||
await user.click(panel.getByRole("switch", { name: "Use an existing virtual key" }));
|
||||
await user.click(panel.getByRole("combobox", { name: "Charge analysis to" }));
|
||||
await user.click(await screen.findByRole("option", { name: "Analysis" }));
|
||||
|
|
@ -378,18 +378,28 @@ describe("Lens interactive demo", () => {
|
|||
expect(await screen.findByRole("region", { name: "New investigation" })).toBeVisible();
|
||||
});
|
||||
|
||||
it("hides the Settings tab for read-only sessions", async () => {
|
||||
renderWithProviders(<LensWorkspace accessToken="live-token" userRole="Admin" readOnly />);
|
||||
expect(await screen.findByRole("tablist", { name: "Lens" })).toBeVisible();
|
||||
await waitFor(() => expect(network).toHaveBeenCalled());
|
||||
expect(screen.queryByRole("tab", { name: "Settings" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("sends read-only sessions following a Settings link to the default tab", async () => {
|
||||
it("opens Settings for read-only sessions without the worker controls", async () => {
|
||||
renderWithProviders(<LensWorkspace accessToken="live-token" userRole="Admin" readOnly />, {
|
||||
searchParams: "?tab=settings",
|
||||
});
|
||||
expect(await screen.findByRole("tab", { name: "Traces", selected: true })).toBeVisible();
|
||||
expect(await screen.findByRole("tab", { name: "Settings", selected: true })).toBeVisible();
|
||||
const panel = within(screen.getByRole("region", { name: "Settings" }));
|
||||
expect(await panel.findByRole("status")).toHaveTextContent("Tracing is not enabled");
|
||||
expect(panel.getByRole("switch", { name: "Show the introduction on each new session" })).toBeVisible();
|
||||
expect(panel.queryByRole("heading", { name: "Analysis worker" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the Settings tab before /lens answers and when it fails", async () => {
|
||||
network.mockImplementation(async (input) =>
|
||||
requestPath(input) === "/lens"
|
||||
? Response.json({ detail: "boom" }, { status: 500 })
|
||||
: Response.json({ data: [], traces: true, requests: false }),
|
||||
);
|
||||
renderWithProviders(<LensWorkspace accessToken="live-token" userRole="Admin" readOnly={false} />);
|
||||
const tabs = within(screen.getByRole("tablist", { name: "Lens" }));
|
||||
expect(tabs.getByRole("tab", { name: "Settings" })).toHaveAttribute("title", "Settings");
|
||||
await waitFor(() => expect(network.mock.calls.some(([input]) => requestPath(input) === "/lens")).toBe(true));
|
||||
expect(tabs.getByRole("tab", { name: "Settings" })).toBeVisible();
|
||||
});
|
||||
|
||||
it("turns the worker health dot off once heartbeats expire even when polling returns unchanged data", async () => {
|
||||
|
|
@ -412,8 +422,8 @@ describe("Lens interactive demo", () => {
|
|||
});
|
||||
renderWithProviders(<LensWorkspace accessToken="live-token" userRole="Admin" readOnly={false} />);
|
||||
const tabs = within(screen.getByRole("tablist", { name: "Lens" }));
|
||||
const settings = await tabs.findByRole("tab", { name: "Settings" });
|
||||
expect(settings).toHaveAttribute("title", "Worker connected");
|
||||
const settings = tabs.getByRole("tab", { name: "Settings" });
|
||||
await waitFor(() => expect(settings).toHaveAttribute("title", "Worker connected"));
|
||||
await vi.advanceTimersByTimeAsync(130000);
|
||||
await waitFor(() => expect(settings).toHaveAttribute("title", "Connect worker"));
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function LensContent({ userRole, readOnly }: Omit<WorkspaceProps, "accessToken">
|
|||
const isAdmin = isProxyAdminRole(userRole);
|
||||
const canConfigure = canViewInvestigations && !readOnly;
|
||||
const defaultTab = lensId ? "investigations" : "traces";
|
||||
const activeTab = tab === "settings" && !canConfigure ? defaultTab : tab ?? defaultTab;
|
||||
const activeTab = tab ?? defaultTab;
|
||||
const intro = useLensIntro({ demo, settingUp });
|
||||
const { activity, list } = useLensOverview(
|
||||
canViewInvestigations,
|
||||
|
|
@ -192,21 +192,20 @@ function LensContent({ userRole, readOnly }: Omit<WorkspaceProps, "accessToken">
|
|||
)}
|
||||
</LensPreviewContext.Provider>
|
||||
</TabsContent>
|
||||
{workers && list && (
|
||||
<TabsContent value="settings" keepMounted className={cn(PANEL, "p-6")}>
|
||||
<LensSettings
|
||||
list={list}
|
||||
workerReadyAction={
|
||||
list.lenses.length === 0 ? (
|
||||
<Button className="w-full" onClick={startFirstInvestigation}>
|
||||
New investigation
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
onOpenTraces={() => setTab("traces")}
|
||||
/>
|
||||
</TabsContent>
|
||||
)}
|
||||
<TabsContent value="settings" keepMounted className={cn(PANEL, "p-6")}>
|
||||
<LensSettings
|
||||
tracingEnabled={list?.tracing_enabled}
|
||||
workers={workers}
|
||||
workerReadyAction={
|
||||
list?.lenses.length === 0 ? (
|
||||
<Button className="w-full" onClick={startFirstInvestigation}>
|
||||
New investigation
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
onOpenTraces={() => setTab("traces")}
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
<LensIntroDialog
|
||||
|
|
|
|||
|
|
@ -60,24 +60,28 @@ function IntroductionSection() {
|
|||
}
|
||||
|
||||
export function LensSettings({
|
||||
list,
|
||||
tracingEnabled,
|
||||
workers,
|
||||
workerReadyAction,
|
||||
onOpenTraces,
|
||||
}: {
|
||||
list: LensList;
|
||||
tracingEnabled: boolean | undefined;
|
||||
workers: LensList["workers"] | null;
|
||||
/** Replaces the worker install card's Done button once the new worker connects. */
|
||||
workerReadyAction?: ReactNode;
|
||||
onOpenTraces: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div aria-label="Settings" role="region" className="flex w-full flex-col divide-y divide-border">
|
||||
<TracingSection enabled={list.tracing_enabled} onOpenTraces={onOpenTraces} />
|
||||
<SettingsSection
|
||||
heading="Analysis worker"
|
||||
description="Runs investigations on your server and bills model usage to an analysis key."
|
||||
>
|
||||
<WorkerSettings workers={list.workers} readyAction={workerReadyAction} />
|
||||
</SettingsSection>
|
||||
{tracingEnabled !== undefined && <TracingSection enabled={tracingEnabled} onOpenTraces={onOpenTraces} />}
|
||||
{workers && (
|
||||
<SettingsSection
|
||||
heading="Analysis worker"
|
||||
description="Runs investigations on your server and bills model usage to an analysis key."
|
||||
>
|
||||
<WorkerSettings workers={workers} readyAction={workerReadyAction} />
|
||||
</SettingsSection>
|
||||
)}
|
||||
<IntroductionSection />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue