From f700e21e3262928d82ceb1be9013473b11ec581f Mon Sep 17 00:00:00 2001 From: Rick <26716961+Bytechoreographer@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:05:02 +0800 Subject: [PATCH] fix(ui): keep the request log model cell to one model (#49619) * fix(ui): keep the request log model cell to one model A session row rendered every model the conversation ever used, comma joined. A long conversation produced a huge string that the auto-layout table turned into a very wide Model column, pushing the rest of the columns off screen. Render only the model of the row's own call (the newest call in the session) and show an overflow count. The full model list stays in the hover popup, including a note when the server capped the list. Co-Authored-By: AaronHowell <111272993+AaronHowell@users.noreply.github.com> * fix(ui): use the shared Badge and an i18n key in the request log model cell The "+N" overflow marker now uses the shadcn Badge instead of a hand-styled span, and the "More models not shown" note reads from en.json so the dashboard's i18next/no-literal-string lint rule passes. --------- Co-authored-by: AaronHowell <111272993+AaronHowell@users.noreply.github.com> Co-authored-by: Yuneng Jiang --- .../request/RequestLogsTableColumns.test.tsx | 36 ++++++++++--- .../logs/request/RequestLogsTableColumns.tsx | 51 ++++++++++++------- ui/litellm-dashboard/src/i18n/locales/en.json | 1 + 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/ui/litellm-dashboard/src/components/logs/request/RequestLogsTableColumns.test.tsx b/ui/litellm-dashboard/src/components/logs/request/RequestLogsTableColumns.test.tsx index 2f2246a3878..7aef0f81411 100644 --- a/ui/litellm-dashboard/src/components/logs/request/RequestLogsTableColumns.test.tsx +++ b/ui/litellm-dashboard/src/components/logs/request/RequestLogsTableColumns.test.tsx @@ -368,7 +368,24 @@ describe("Request ID column", () => { }); describe("Model column", () => { - it("lists every model used across a conversation, not only the representative call's model", () => { + it("keeps a long conversation's inline cell to the latest call's model", () => { + const conversationCall: Partial = { + request_id: "req-session", + model: "gpt-5.6", + session_id: "sess-1", + session_total_count: 4, + session_models: ["claude-sonnet-5", "gemini-3-pro", "gpt-5.6", "o4-mini"], + }; + renderRows([logEntry(conversationCall)]); + + expect(screen.getByText("gpt-5.6")).toBeInTheDocument(); + for (const otherModel of ["claude-sonnet-5", "gemini-3-pro", "o4-mini"]) { + expect(screen.queryByText(otherModel)).not.toBeInTheDocument(); + } + }); + + it("reveals the conversation's other models in the hover popup", async () => { + const user = userEvent.setup(); const conversationCall: Partial = { request_id: "req-session", model: "gpt-5.6", @@ -378,12 +395,14 @@ describe("Model column", () => { }; renderRows([logEntry(conversationCall)]); - expect(screen.getByText("claude-sonnet-5, gpt-5.6")).toBeInTheDocument(); - expect(screen.queryByText("gpt-5.6")).not.toBeInTheDocument(); + await user.hover(screen.getByText("+1")); + + expect(await screen.findByText("claude-sonnet-5")).toBeInTheDocument(); }); - it("marks a conversation whose model list was capped by the server", () => { - const cappedCall = { + it("flags a conversation whose model list was capped by the server", async () => { + const user = userEvent.setup(); + const cappedCall: Partial = { request_id: "req-capped", model: "gpt-5.6", session_id: "sess-2", @@ -393,13 +412,16 @@ describe("Model column", () => { }; renderRows([logEntry(cappedCall)]); - expect(screen.getByText("claude-sonnet-5, gpt-5.6, ...")).toBeInTheDocument(); + await user.hover(screen.getByText("+1")); + + expect(await screen.findByText("More models not shown")).toBeInTheDocument(); }); - it("keeps a single call's own model", () => { + it("keeps a single call's own model and shows no overflow marker", () => { renderRows([logEntry({ request_id: "req-single", model: "gpt-5.6" })]); expect(screen.getByText("gpt-5.6")).toBeInTheDocument(); + expect(screen.queryByText(/^\+\d+$/)).not.toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/components/logs/request/RequestLogsTableColumns.tsx b/ui/litellm-dashboard/src/components/logs/request/RequestLogsTableColumns.tsx index 87eeaa58709..6d3ca7bd0f8 100644 --- a/ui/litellm-dashboard/src/components/logs/request/RequestLogsTableColumns.tsx +++ b/ui/litellm-dashboard/src/components/logs/request/RequestLogsTableColumns.tsx @@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next"; import { DataTableSortHeader } from "@/components/shared/DataTable"; import { CellTooltip, DateCell, IdCell, MoneyCell, StatusBadge } from "@/components/shared/table_cells"; +import { Badge } from "@/components/ui/badge"; import i18n from "@/i18n/i18n"; import { copyToClipboard, getSpendString } from "@/utils/dataUtils"; @@ -329,28 +330,42 @@ export const getRequestLogsTableColumns = ( enableSorting: true, cell: ({ row }) => { const log = row.original; - const provider = log.custom_llm_provider; const sessionModels = log.session_models ?? []; - const modelNames = sessionModels.length > 0 ? sessionModels : [log.model ?? ""]; - const modelLabel = log.session_models_truncated ? `${modelNames.join(", ")}, ...` : modelNames.join(", "); - const isSingleModel = modelNames.length === 1; + const latestModel = log.model || sessionModels[0] || ""; + const otherModels = sessionModels.filter((model) => model !== latestModel); + const models = [latestModel, ...otherModels].filter((model) => model !== ""); + if (models.length === 0) return -; return ( -
- {provider && isSingleModel && ( - { - event.currentTarget.style.display = "none"; - }} - /> - )} +
+ {models.map((model) => ( + {model} + ))} + {log.session_models_truncated === true && ( + {t("logs.request.columns.moreModelsNotShown")} + )} +
+ } trigger={ - - {modelLabel} + + {log.custom_llm_provider && ( + { + event.currentTarget.style.display = "none"; + }} + /> + )} + {latestModel} + {otherModels.length > 0 && ( + + +{otherModels.length} + + )} } /> diff --git a/ui/litellm-dashboard/src/i18n/locales/en.json b/ui/litellm-dashboard/src/i18n/locales/en.json index a0660d82df1..d4ba9c14998 100644 --- a/ui/litellm-dashboard/src/i18n/locales/en.json +++ b/ui/litellm-dashboard/src/i18n/locales/en.json @@ -14887,6 +14887,7 @@ "keyHash": "Key Hash", "keyAlias": "Key Alias", "model": "Model", + "moreModelsNotShown": "More models not shown", "tokens": "Tokens", "internalUser": "Internal User", "endUser": "End User",