mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-15 09:12:18 +00:00
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>
This commit is contained in:
parent
ff3983c1f6
commit
db48411c0f
2 changed files with 61 additions and 25 deletions
|
|
@ -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<LogEntry> = {
|
||||
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<LogEntry> = {
|
||||
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<LogEntry> = {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -301,28 +301,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 <span className="text-muted-foreground">-</span>;
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
{provider && isSingleModel && (
|
||||
<img
|
||||
src={getLogoUrl(log, provider)}
|
||||
alt=""
|
||||
className="w-4 h-4"
|
||||
onError={(event) => {
|
||||
event.currentTarget.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="flex min-w-0 items-center">
|
||||
<CellTooltip
|
||||
content={modelLabel}
|
||||
content={
|
||||
<div className="flex max-w-[280px] flex-col gap-0.5">
|
||||
{models.map((model) => (
|
||||
<span key={model}>{model}</span>
|
||||
))}
|
||||
{log.session_models_truncated === true && (
|
||||
<span className="text-muted-foreground">More models not shown</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
trigger={
|
||||
<span className={isSingleModel ? "max-w-[15ch] truncate block" : "min-w-0 truncate block"}>
|
||||
{modelLabel}
|
||||
<span className="inline-flex min-w-0 items-center gap-1.5">
|
||||
{log.custom_llm_provider && (
|
||||
<img
|
||||
src={getLogoUrl(log, log.custom_llm_provider)}
|
||||
alt=""
|
||||
className="w-4 h-4 shrink-0"
|
||||
onError={(event) => {
|
||||
event.currentTarget.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<span className="max-w-[22ch] truncate">{latestModel}</span>
|
||||
{otherModels.length > 0 && (
|
||||
<span className="shrink-0 rounded-full border border-border px-1 text-[10px] leading-4 text-muted-foreground">
|
||||
+{otherModels.length}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue