From 5a040c0b118e971e9f4dcc6861ce75650ad7b86a Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 6 Oct 2026 14:34:29 -0700 Subject: [PATCH] fix(lens): refresh recorded content with trace details --- .../TraceConversation.integration.test.tsx | 21 +++++++++++++++++++ .../conversation/useConversationDetails.ts | 2 +- .../lens/traces/detail/run/RunView.tsx | 9 +++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/components/lens/traces/detail/conversation/TraceConversation.integration.test.tsx b/ui/litellm-dashboard/src/components/lens/traces/detail/conversation/TraceConversation.integration.test.tsx index a77b443ac9e..85df5f5255b 100644 --- a/ui/litellm-dashboard/src/components/lens/traces/detail/conversation/TraceConversation.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/lens/traces/detail/conversation/TraceConversation.integration.test.tsx @@ -46,6 +46,27 @@ describe("TraceConversation", () => { .mockImplementation(async (_token, _trace, id) => (id === "root" ? rootDetail : { ...toolDetail, span_id: id })); }); + it.each([false, true])("refreshes unchanged spans without hiding loaded content on failure (%s)", async (failed) => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + await user.click(await screen.findByRole("tab", { name: "Conversation" })); + expect(await screen.findByText("The release is ready")).toBeVisible(); + vi.mocked(agentTraceSpanCall).mockImplementation(async (_token, _trace, id) => { + if (failed) throw new Error("content refresh failed"); + return id === "root" ? { ...rootDetail, output: "Updated final answer" } : toolDetail; + }); + await user.click(screen.getByRole("button", { name: "Refresh run" })); + if (failed) { + expect(await screen.findAllByRole("button", { name: "Retry step" })).toHaveLength(2); + expect(screen.getByText("The release is ready")).toBeVisible(); + } else { + expect(await screen.findByText("Updated final answer")).toBeVisible(); + expect(screen.queryByText("The release is ready")).not.toBeInTheDocument(); + } + }); + it("switches to a readable transcript and opens the exact tool step from it", async () => { const user = userEvent.setup(); renderWithProviders( diff --git a/ui/litellm-dashboard/src/components/lens/traces/detail/conversation/useConversationDetails.ts b/ui/litellm-dashboard/src/components/lens/traces/detail/conversation/useConversationDetails.ts index e94bb82cd98..e13b663a421 100644 --- a/ui/litellm-dashboard/src/components/lens/traces/detail/conversation/useConversationDetails.ts +++ b/ui/litellm-dashboard/src/components/lens/traces/detail/conversation/useConversationDetails.ts @@ -22,7 +22,7 @@ export function useConversationDetails(trace: Trace, accessToken: string) { [traceId, traceRef, accessToken, traces], ); const queries = useQueries({ queries: visible.map(spanQuery) }); - const unresolvedIndex = queries.findIndex((query) => !query.isSuccess); + const unresolvedIndex = queries.findIndex((query) => query.data === undefined); const loadedCount = unresolvedIndex < 0 ? queries.length : unresolvedIndex; const details = new Map( queries.slice(0, loadedCount).map((query, index) => [visible[index].span_id, query.data!] as const), diff --git a/ui/litellm-dashboard/src/components/lens/traces/detail/run/RunView.tsx b/ui/litellm-dashboard/src/components/lens/traces/detail/run/RunView.tsx index feaa9ba2c8c..f383fc46b3e 100644 --- a/ui/litellm-dashboard/src/components/lens/traces/detail/run/RunView.tsx +++ b/ui/litellm-dashboard/src/components/lens/traces/detail/run/RunView.tsx @@ -114,7 +114,14 @@ function LoadedRun({ setManualRead(true); void read().finally(() => setManualRead(false)); }; - const refreshRun = () => readManually(() => traceQuery.refetch()); + const refreshRun = () => + readManually(async () => { + await traceQuery.refetch(); + await queryClient.invalidateQueries({ + queryKey: ["agentTraceSpan", traceId, traceRef], + predicate: (query) => query.queryKey.at(-1) === accessToken, + }); + }); const toggleLive = () => { if (live && !manualRead && !traceQuery.isFetchingNextPage) { void queryClient.cancelQueries({ queryKey, exact: true });