mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(ui): show Lens run reloads as a search spinner and a blurred body
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5e7b0afd5c
commit
03bec959bf
9 changed files with 33 additions and 37 deletions
|
|
@ -469,12 +469,3 @@
|
|||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rows-sweep {
|
||||
from {
|
||||
left: -25%;
|
||||
}
|
||||
to {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -671,12 +671,12 @@ describe("AgentTracesPage", () => {
|
|||
}),
|
||||
);
|
||||
fireEvent.click(screen.getByTestId(`sort-header-${key}`));
|
||||
expect(await screen.findByRole("progressbar", { name: "Updating rows" })).toBeInTheDocument();
|
||||
expect(await screen.findByRole("status", { name: "Loading results" })).toBeInTheDocument();
|
||||
expect(screen.getAllByTestId("agent-trace-row")).toHaveLength(rowsBefore.length);
|
||||
expect(listRequest(-1)).toMatchObject({ order: { key, descending: true }, page: { cursor: null } });
|
||||
|
||||
act(release);
|
||||
await waitFor(() => expect(screen.queryByRole("progressbar")).not.toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.queryByRole("status", { name: "Loading results" })).not.toBeInTheDocument());
|
||||
expect(screen.getAllByTestId("agent-trace-row")[0]).toHaveTextContent(topAfter);
|
||||
expect(lastUrl(onUrlUpdate).get("sort_by")).toBe(key);
|
||||
expect(lastUrl(onUrlUpdate).has("sort_dir")).toBe(false);
|
||||
|
|
|
|||
|
|
@ -171,7 +171,14 @@ export function AgentTracesSection({
|
|||
/>
|
||||
)}
|
||||
</Inspector.Panel>
|
||||
<RunsToolbar query={query} onQueryChange={setQuery} runs={traces.traces} range={zoom ?? window} order={order}>
|
||||
<RunsToolbar
|
||||
query={query}
|
||||
onQueryChange={setQuery}
|
||||
runs={traces.traces}
|
||||
range={zoom ?? window}
|
||||
order={order}
|
||||
busy={traces.isPlaceholder}
|
||||
>
|
||||
{timeControls && (
|
||||
<TimeRangeControls
|
||||
range={range}
|
||||
|
|
|
|||
|
|
@ -236,7 +236,6 @@ describe("AgentTracesTable sorting", () => {
|
|||
),
|
||||
);
|
||||
expect(screen.getAllByTestId("agent-trace-row")).toHaveLength(runs.length);
|
||||
expect(screen.getByRole("progressbar", { name: "Updating rows" })).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("runs-placeholder")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("status")).not.toBeInTheDocument();
|
||||
expect(onLoadMore).not.toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -287,10 +287,10 @@ export function AgentTracesTable({
|
|||
<InspectorTable.Root table={table} data-testid="runs-table">
|
||||
<ScrollToTop order={order} />
|
||||
<InspectorTable.Grid aria-label="Agent runs" aria-busy={isFetching} className="min-w-[900px] text-xs">
|
||||
<InspectorTable.Header busy={isPlaceholder} />
|
||||
<InspectorTable.Header />
|
||||
<InspectorTable.Body<TraceSummary>
|
||||
rowHeight={() => ROW_HEIGHT}
|
||||
className={cn(isPlaceholder && "opacity-60 transition-opacity motion-reduce:transition-none")}
|
||||
className={cn("transition-[filter]", isPlaceholder && "blur-[1.5px]")}
|
||||
after={
|
||||
<>
|
||||
{isLoading && SKELETON_ROWS.map((row) => <PlaceholderRow key={row} index={row} />)}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@ interface RunSearchProps {
|
|||
/** The range and order the list shows; the copied query follows both. */
|
||||
range?: TimeWindow;
|
||||
order?: RunOrder;
|
||||
busy?: boolean;
|
||||
}
|
||||
|
||||
/** The runs list query box: free text plus `key:value` filters over run fields, copyable as a trace query. */
|
||||
export function RunSearch({ value, onChange, runs, range, order = NEWEST }: RunSearchProps) {
|
||||
export function RunSearch({ value, onChange, runs, range, order = NEWEST, busy = false }: RunSearchProps) {
|
||||
const command = useMemo(() => runQueryCommand(range, order), [range, order]);
|
||||
return (
|
||||
<SearchBox.Root
|
||||
|
|
@ -37,6 +38,7 @@ export function RunSearch({ value, onChange, runs, range, order = NEWEST }: RunS
|
|||
>
|
||||
<SearchBox.Input
|
||||
className="h-full rounded-none border-0 px-3 focus-within:bg-muted/40 focus-within:ring-0 dark:bg-transparent"
|
||||
busy={busy}
|
||||
placeholder="Search runs, or filter like agent:researcher status:error"
|
||||
/>
|
||||
<SearchBox.Suggestions>
|
||||
|
|
|
|||
|
|
@ -13,14 +13,16 @@ interface RunsToolbarProps {
|
|||
/** The range and order the list shows, for the copied query. */
|
||||
range?: TimeWindow;
|
||||
order?: RunOrder;
|
||||
/** Shows a spinner in the search box while the list reloads for a new query or order. */
|
||||
busy?: boolean;
|
||||
/** Extra controls (time range, live tail) rendered on the right. */
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function RunsToolbar({ query, onQueryChange, runs, range, order, children }: RunsToolbarProps) {
|
||||
export function RunsToolbar({ query, onQueryChange, runs, range, order, busy, children }: RunsToolbarProps) {
|
||||
return (
|
||||
<div className="flex h-10 shrink-0 items-stretch border-b border-border bg-card">
|
||||
<RunSearch value={query} onChange={onQueryChange} runs={runs} range={range} order={order} />
|
||||
<RunSearch value={query} onChange={onQueryChange} runs={runs} range={range} order={order} busy={busy} />
|
||||
{children && <div className="flex shrink-0 items-stretch">{children}</div>}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -73,11 +73,9 @@ function ariaSort<T>(column: Column<T>): "ascending" | "descending" | "none" | u
|
|||
|
||||
interface HeaderProps {
|
||||
readonly hidden?: boolean;
|
||||
/** Shows a progress line under the headings while the rows below are being replaced. */
|
||||
readonly busy?: boolean;
|
||||
}
|
||||
|
||||
function Header({ hidden = false, busy = false }: HeaderProps) {
|
||||
function Header({ hidden = false }: HeaderProps) {
|
||||
const { table } = useInspectorTable();
|
||||
return (
|
||||
<TableHeader
|
||||
|
|
@ -100,19 +98,6 @@ function Header({ hidden = false, busy = false }: HeaderProps) {
|
|||
))}
|
||||
</tr>
|
||||
))}
|
||||
{busy && (
|
||||
<tr className="border-0">
|
||||
<th colSpan={table.getVisibleLeafColumns().length} className="relative h-0 p-0">
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-label="Updating rows"
|
||||
className="absolute inset-x-0 bottom-0 h-0.5 overflow-hidden bg-[#3b5bfd]/10"
|
||||
>
|
||||
<div className="absolute inset-y-0 w-1/4 bg-[#3b5bfd]/60 motion-safe:animate-[rows-sweep_1.2s_ease-in-out_infinite] motion-reduce:hidden" />
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
)}
|
||||
</TableHeader>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import "prosemirror-view/style/prosemirror.css";
|
|||
|
||||
import { useDebouncedCallback } from "@tanstack/react-pacer/debouncer";
|
||||
import { ProseMirror, ProseMirrorDoc, reactKeys, useEditorEventCallback } from "@handlewithcare/react-prosemirror";
|
||||
import { Check, Copy, CornerDownLeft, type LucideIcon, Search } from "lucide-react";
|
||||
import { Check, Copy, CornerDownLeft, Loader2, type LucideIcon, Search } from "lucide-react";
|
||||
import { Schema } from "prosemirror-model";
|
||||
import { EditorState, Plugin, TextSelection, type Transaction } from "prosemirror-state";
|
||||
import { Decoration, DecorationSet, type EditorView } from "prosemirror-view";
|
||||
|
|
@ -231,10 +231,12 @@ function Root<F extends string>({
|
|||
|
||||
export type SearchBoxInputProps = ComponentProps<"div"> & {
|
||||
placeholder: string;
|
||||
/** Swaps the search icon for a spinner while results for the current query load. */
|
||||
busy?: boolean;
|
||||
};
|
||||
|
||||
/** The bordered field holding the editor; shows `placeholder` while the query is empty. */
|
||||
function Input({ placeholder, className, ...props }: SearchBoxInputProps) {
|
||||
function Input({ placeholder, busy = false, className, ...props }: SearchBoxInputProps) {
|
||||
const { text } = useSearchBox();
|
||||
return (
|
||||
<div
|
||||
|
|
@ -245,7 +247,15 @@ function Input({ placeholder, className, ...props }: SearchBoxInputProps) {
|
|||
)}
|
||||
{...props}
|
||||
>
|
||||
<Search className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
{busy ? (
|
||||
<Loader2
|
||||
role="status"
|
||||
aria-label="Loading results"
|
||||
className="size-3.5 shrink-0 animate-spin text-muted-foreground"
|
||||
/>
|
||||
) : (
|
||||
<Search className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
)}
|
||||
<div className="relative flex min-w-0 flex-1">
|
||||
<ProseMirrorDoc />
|
||||
{!text && (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue