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",