From 16c65387d80bdfd4d44abc1f80d7948969f0be8c Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 29 Aug 2026 11:42:04 -0700 Subject: [PATCH] Merge pull request #38778 from BerriAI/litellm_/json-readability-logs-661421 fix(ui): make the logs JSON viewer follow the theme in dark mode (cherry picked from commit 207893db9cf0d09d54dc82531b8aab018ae3482b) --- .../LogDetailsDrawer/JsonViewer.test.tsx | 32 +++++++++++++++++-- .../view_logs/LogDetailsDrawer/JsonViewer.tsx | 9 ++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.test.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.test.tsx index 055e400167b..a0ccb7f5323 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.test.tsx @@ -1,17 +1,26 @@ import { render, screen } from "@testing-library/react"; +import { ThemeProvider } from "next-themes"; +import { darkStyles, defaultStyles } from "react-json-view-lite"; import { describe, expect, it } from "vitest"; import { JsonViewer } from "./JsonViewer"; +const renderWithTheme = (theme: "light" | "dark", data: unknown) => + render( + + + , + ); + describe("JsonViewer", () => { it("should render a placeholder and no tree when the log entry carries no payload", () => { - render(); + renderWithTheme("light", null); expect(screen.getByText("No data")).toBeInTheDocument(); expect(screen.queryByRole("tree")).not.toBeInTheDocument(); }); it("should render the payload as a tree exposing its keys", () => { - render(); + renderWithTheme("light", { model: "claude-opus-4-5", stream: true }); expect(screen.getByRole("tree")).toBeInTheDocument(); expect(screen.getByText(/model/)).toBeInTheDocument(); @@ -20,9 +29,26 @@ describe("JsonViewer", () => { }); it("should treat an empty payload as data rather than showing the placeholder", () => { - render(); + renderWithTheme("light", {}); expect(screen.getByRole("tree")).toBeInTheDocument(); expect(screen.queryByText("No data")).not.toBeInTheDocument(); }); + + it("should style the tree with the light palette when the dashboard theme is light", () => { + renderWithTheme("light", { model: "claude-opus-4-5" }); + + expect(screen.getByRole("tree")).toHaveClass(...defaultStyles.container.split(" ")); + }); + + it("should style the tree with the dark palette when the dashboard theme is dark", () => { + renderWithTheme("dark", { model: "claude-opus-4-5" }); + + const tree = screen.getByRole("tree"); + expect(tree).toHaveClass(...darkStyles.container.split(" ")); + defaultStyles.container + .split(" ") + .filter((className) => !darkStyles.container.split(" ").includes(className)) + .forEach((lightOnlyClassName) => expect(tree).not.toHaveClass(lightOnlyClassName)); + }); }); diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx index 86b897f526f..980573a65cc 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx @@ -1,4 +1,5 @@ -import { JsonView, defaultStyles } from "react-json-view-lite"; +import { useTheme } from "next-themes"; +import { JsonView, darkStyles, defaultStyles } from "react-json-view-lite"; import "react-json-view-lite/dist/index.css"; import { JSON_MAX_HEIGHT, SPACING_LARGE } from "./constants"; @@ -12,6 +13,8 @@ interface JsonViewerProps { * Uses an interactive tree component for easy navigation. */ export function JsonViewer({ data }: JsonViewerProps) { + const { resolvedTheme } = useTheme(); + if (!data) return No data; return ( @@ -24,8 +27,8 @@ export function JsonViewer({ data }: JsonViewerProps) { borderRadius: 4, }} > -
- +
+
);