mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
feat(ui): drop the Lens preview header and keep setup within the page
The sample card already shows the match count, so the preview header repeated it. On wide screens the setup no longer scrolls as a page: the preview table and, when needed, the steps column scroll on their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
284f12f522
commit
fcf977b522
5 changed files with 18 additions and 53 deletions
|
|
@ -189,7 +189,7 @@ export function InvestigationsView({ readOnly = false }: InvestigationsViewProps
|
|||
return (
|
||||
<section
|
||||
aria-label="Investigations"
|
||||
className={cn("flex w-full min-w-0 flex-1 flex-col", !browsing && "gap-3 p-4")}
|
||||
className={cn("flex min-h-0 w-full min-w-0 flex-1 flex-col", !browsing && "gap-3 p-4")}
|
||||
>
|
||||
<Banners
|
||||
error={bannerError}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,6 @@ describe("Investigation setup", () => {
|
|||
await user.type(screen.getByRole("textbox", { name: "Check 1" }), "Find incomplete reports");
|
||||
await user.click(screen.getByRole("button", { name: "Continue" }));
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Run and monitor" })).toBeEnabled());
|
||||
expect(screen.getByText("1 matching run")).toBeInTheDocument();
|
||||
expect(screen.getByText("Research report")).toBeInTheDocument();
|
||||
await user.click(screen.getByRole("button", { name: "Run and monitor" }));
|
||||
expect(save).toHaveBeenCalledWith(
|
||||
|
|
@ -340,9 +339,9 @@ it.each(["new", "duplicate"] as const)("blocks a %s investigation until its mode
|
|||
: Promise.resolve(gatewayResponse(path, {})),
|
||||
);
|
||||
renderWithProviders(<InvestigationSetup initial={settings} mode={mode} onClose={vi.fn()} onSave={save} />);
|
||||
expect(await screen.findByText("100% of 1")).toBeInTheDocument();
|
||||
await user.click(screen.getByRole("button", { name: "Continue" }));
|
||||
await user.click(screen.getByRole("button", { name: "Continue" }));
|
||||
expect(await screen.findByText("1 matching run")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: mode === "new" ? "Run and monitor" : "Run investigation" })).toBeDisabled();
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -393,14 +392,14 @@ it("appends the next preview page as the list scrolls near its end, then stops a
|
|||
renderWithProviders(<InvestigationSetup mode="edit" initial={settings} onClose={vi.fn()} onSave={vi.fn()} />);
|
||||
expect(await screen.findByText("Run one")).toBeVisible();
|
||||
expect(screen.getByText(/Showing 1 of 2/)).toBeVisible();
|
||||
expect(screen.getByRole("status")).toHaveTextContent("2 matching runs");
|
||||
expect(screen.getByText("100% of 2")).toBeVisible();
|
||||
const nextPageCalls = () =>
|
||||
proxy.post.mock.calls.filter(([, options]) => (options?.body as { cursor: string }).cursor === "next");
|
||||
expect(nextPageCalls()).toHaveLength(0);
|
||||
act(() => mockAllIsIntersecting(true));
|
||||
await waitFor(() => expect(nextPageCalls()).toHaveLength(1));
|
||||
expect(screen.getByText("Run one")).toBeVisible();
|
||||
expect(screen.getByRole("status")).toHaveTextContent("2 matching runs");
|
||||
expect(screen.getByText("100% of 2")).toBeVisible();
|
||||
act(() => finishSecondPage());
|
||||
expect(await screen.findByText("Run two")).toBeVisible();
|
||||
expect(screen.getByText("Run one")).toBeVisible();
|
||||
|
|
@ -423,7 +422,7 @@ it("fetches one preview for two keystrokes inside the debounce window", async ()
|
|||
await vi.advanceTimersByTimeAsync(1000);
|
||||
await waitFor(() => expect(previewsFor("ab")).toHaveLength(1));
|
||||
expect(previewsFor("a")).toHaveLength(0);
|
||||
expect(await screen.findByText("1 matching run")).toBeVisible();
|
||||
expect(await screen.findByText(/% of 1$/)).toBeVisible();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ function SetupEditor({
|
|||
const saveLabel = saveLabelFor(mode, repeat);
|
||||
const offline = !ready && mode !== "edit";
|
||||
return (
|
||||
<section aria-label={TITLES[mode]} className="flex min-w-0 flex-1 flex-col">
|
||||
<section aria-label={TITLES[mode]} className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<header className="flex items-center gap-1 border-b pb-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -194,8 +194,13 @@ function SetupEditor({
|
|||
The worker or trace storage is unavailable. Your draft is safe; you can start when it reconnects.
|
||||
</p>
|
||||
)}
|
||||
<div className="grid min-w-0 flex-1 items-start gap-8 pt-6 lg:grid-cols-[minmax(0,26rem)_minmax(0,1fr)] xl:gap-10">
|
||||
<SetupSteps aria-label="Investigation setup" current={step} onOpen={setStep}>
|
||||
<div className="grid min-h-0 min-w-0 flex-1 items-start gap-8 pt-6 lg:grid-cols-[minmax(0,26rem)_minmax(0,1fr)] lg:grid-rows-[minmax(0,1fr)] xl:gap-10">
|
||||
<SetupSteps
|
||||
aria-label="Investigation setup"
|
||||
current={step}
|
||||
onOpen={setStep}
|
||||
className="lg:-m-1 lg:max-h-full lg:overflow-y-auto lg:p-1"
|
||||
>
|
||||
<SetupStep id="activity" heading="Activity" description="Which runs to review" summary={<ActivitySummary />}>
|
||||
<ScopeFields {...activity.scope} />
|
||||
<SampleFields eligible={activity.preview.page.eligible} />
|
||||
|
|
@ -232,7 +237,7 @@ function SetupEditor({
|
|||
noun="run"
|
||||
storageKey={FINDING_PANEL_WIDTH_KEY}
|
||||
>
|
||||
<MatchingActivityPreview {...activity.preview} className="min-w-0 lg:sticky lg:top-0" />
|
||||
<MatchingActivityPreview {...activity.preview} className="min-w-0 lg:max-h-full" />
|
||||
<Inspector.Panel label="Run details" testId="run-panel">
|
||||
{(run: TraceRef) => (
|
||||
<TraceEvidence
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { memo, useMemo, type ComponentProps } from "react";
|
||||
import { RotateCw } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/cva.config";
|
||||
|
||||
|
|
@ -72,31 +71,14 @@ export function MatchingActivityPreview({
|
|||
[pickedIds, togglePick],
|
||||
);
|
||||
const shown = status.ready || status.stale;
|
||||
const showTable = status.loading || (shown && !status.error && runs.length > 0);
|
||||
return (
|
||||
<section
|
||||
aria-label="Matching activity"
|
||||
data-slot="matching-activity-preview"
|
||||
className={cn("self-start overflow-hidden rounded-lg border bg-card", className)}
|
||||
className={cn("flex flex-col self-start overflow-hidden rounded-lg border bg-card", className)}
|
||||
{...props}
|
||||
>
|
||||
<header className="flex items-start justify-between gap-3 border-b px-4 py-3">
|
||||
<div className="grid min-w-0 gap-0.5">
|
||||
<p className="text-sm font-semibold tabular-nums" role="status">
|
||||
{status.title}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">{status.windowLabel} · Previewing is free</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
className="-mr-1.5 text-muted-foreground"
|
||||
aria-label="Refresh matching activity"
|
||||
onClick={status.refresh}
|
||||
disabled={!status.ready}
|
||||
>
|
||||
<RotateCw className={cn("size-3.5", (status.loading || status.stale) && "motion-safe:animate-spin")} />
|
||||
</Button>
|
||||
</header>
|
||||
{status.ready && status.error && (
|
||||
<p role="alert" className="px-4 py-3 text-sm text-destructive">
|
||||
{status.error.message}{" "}
|
||||
|
|
@ -113,8 +95,8 @@ export function MatchingActivityPreview({
|
|||
</p>
|
||||
</div>
|
||||
)}
|
||||
{(status.loading || (shown && !status.error && runs.length > 0)) && (
|
||||
<div className="flex max-h-[calc(100dvh-16rem)] min-h-0 flex-col">
|
||||
{showTable && (
|
||||
<div className="flex max-h-[calc(100dvh-16rem)] min-h-0 flex-1 flex-col lg:max-h-none">
|
||||
<PreviewTable
|
||||
traces={runs}
|
||||
isLoading={status.loading}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
|
|||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
import { lensKeys, lensQueries } from "../data/queries";
|
||||
import { useLensApi } from "../data/LensServices";
|
||||
import { durationLabel } from "../model/format";
|
||||
import type { Execution, Sample } from "../model/types";
|
||||
import type { TraceSummary } from "../traces/types";
|
||||
import { useDebouncedValue } from "./useDebouncedValue";
|
||||
|
|
@ -26,8 +25,6 @@ export interface ScopeOptions {
|
|||
}
|
||||
|
||||
export interface PreviewStatus {
|
||||
readonly title: string;
|
||||
readonly windowLabel: string;
|
||||
readonly ready: boolean;
|
||||
/** No results yet for any search: the table shows skeleton rows. */
|
||||
readonly loading: boolean;
|
||||
|
|
@ -79,27 +76,11 @@ function validScope(scope: Selection): boolean {
|
|||
return validWindow(scope) && validSampling;
|
||||
}
|
||||
|
||||
function previewTitle(
|
||||
state: { pending: boolean; validWindow: boolean; valid: boolean },
|
||||
data: PreviewPageData | undefined,
|
||||
): string {
|
||||
if (!state.validWindow) return "Choose a history window between 1 hour and 365 days";
|
||||
if (!state.valid) return "Complete your sampling settings to preview matches";
|
||||
if (state.pending) return "Finding matching runs…";
|
||||
if (!data) return "Preview unavailable";
|
||||
return `${data.eligible.toLocaleString()} matching run${data.eligible === 1 ? "" : "s"}`;
|
||||
}
|
||||
|
||||
function manualSelectedCount(selection: Selection): number {
|
||||
const sampled = Math.ceil((selection.execution_ids.length * (selection.sample_percent ?? 100)) / 100);
|
||||
return Math.min(sampled, selection.sample_size ?? Infinity);
|
||||
}
|
||||
|
||||
function windowLabel(selection: Selection): string {
|
||||
if (!validWindow(selection)) return "Choose a valid history window";
|
||||
return `Last ${durationLabel(selection.lookback_hours ?? 24, "hours")}`;
|
||||
}
|
||||
|
||||
function scopeOptions(selection: Selection, asOf: string, executions: readonly Execution[]): ScopeOptions {
|
||||
const endMs = Date.parse(asOf) || Date.now();
|
||||
return {
|
||||
|
|
@ -161,8 +142,6 @@ export function useMatchingActivity(): MatchingActivity {
|
|||
scope: scopeOptions(selection, asOf, executions),
|
||||
preview: {
|
||||
status: {
|
||||
title: previewTitle({ pending, validWindow: windowValid, valid }, firstPage),
|
||||
windowLabel: windowLabel(selection),
|
||||
ready,
|
||||
loading,
|
||||
stale,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue