diff --git a/ui/litellm-dashboard/src/components/entity_usage.tsx b/ui/litellm-dashboard/src/components/entity_usage.tsx index 22c5e5602b2..2a4229d692b 100644 --- a/ui/litellm-dashboard/src/components/entity_usage.tsx +++ b/ui/litellm-dashboard/src/components/entity_usage.tsx @@ -29,6 +29,7 @@ import { formatNumberWithCommas } from "@/utils/dataUtils"; import { valueFormatterSpend } from "./usage/utils/value_formatters"; import { getProviderLogoAndName } from "./provider_info_helpers"; import { UsageExportHeader } from "./EntityUsageExport"; +import TopModelView from "./top_model_view"; interface EntityMetrics { metrics: { @@ -550,17 +551,7 @@ const EntityUsage: React.FC = ({ Top Models - `$${formatNumberWithCommas(value, 2)}`} - layout="vertical" - yAxisWidth={200} - showLegend={false} - /> + diff --git a/ui/litellm-dashboard/src/components/top_key_view.test.tsx b/ui/litellm-dashboard/src/components/top_key_view.test.tsx new file mode 100644 index 00000000000..c918fdbd586 --- /dev/null +++ b/ui/litellm-dashboard/src/components/top_key_view.test.tsx @@ -0,0 +1,110 @@ +import { render } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import TopKeyView from "./top_key_view"; + +describe("TopKeyView", () => { + it("should render", () => { + const { container } = render( + , + ); + expect(container).toBeTruthy(); + }); + + it("should have a table view button", () => { + const { getByText } = render( + , + ); + expect(getByText("Table View")).toBeInTheDocument(); + }); + + it("should have a chart view", () => { + const { getByText } = render( + , + ); + expect(getByText("Chart View")).toBeInTheDocument(); + }); + + ["Key ID", "Key Alias", "Spend (USD)"].forEach((header) => { + it(`should have a ${header} column`, () => { + const { getByText } = render( + , + ); + expect(getByText(header)).toBeInTheDocument(); + }); + }); + + it("should have a Tags column when showTags is true", () => { + const { getByText } = render( + , + ); + expect(getByText("Tags")).toBeInTheDocument(); + }); + + it("should show the key's information on the table", () => { + const { getByText } = render( + , + ); + expect(getByText("Test Key")).toBeInTheDocument(); + expect(getByText(/tag-1/)).toBeInTheDocument(); + expect(getByText(/tag-2/)).toBeInTheDocument(); + expect(getByText("$100.00")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx index b84aa16008b..565dc03a38a 100644 --- a/ui/litellm-dashboard/src/components/top_key_view.tsx +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -216,9 +216,8 @@ const TopKeyView: React.FC = ({ yAxisWidth={120} tickGap={5} layout="vertical" - showXAxis={false} showLegend={false} - valueFormatter={(value) => (value ? `$${formatNumberWithCommas(value, 2)}` : "No Key Alias")} + valueFormatter={(value) => `$${formatNumberWithCommas(value, 2)}`} onValueChange={(item) => handleKeyClick(item)} showTooltip={true} customTooltip={(props) => { diff --git a/ui/litellm-dashboard/src/components/top_model_view.test.tsx b/ui/litellm-dashboard/src/components/top_model_view.test.tsx new file mode 100644 index 00000000000..d25055c3b91 --- /dev/null +++ b/ui/litellm-dashboard/src/components/top_model_view.test.tsx @@ -0,0 +1,48 @@ +import { render } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import TopModelView from "./top_model_view"; + +describe("TopModelView", () => { + it("should render", () => { + const { container } = render(); + expect(container).toBeTruthy(); + }); + + it("should have a table view button", () => { + const { getByText } = render(); + expect(getByText("Table View")).toBeInTheDocument(); + }); + + it("should have a chart view", () => { + const { getByText } = render(); + expect(getByText("Chart View")).toBeInTheDocument(); + }); + + ["Model", "Spend (USD)", "Successful", "Failed", "Tokens"].forEach((header) => { + it(`should have a ${header} column`, () => { + const { getByText } = render(); + expect(getByText(header)).toBeInTheDocument(); + }); + }); + + it("should show the model's information on the table", () => { + const { getByText } = render( + , + ); + expect(getByText("gpt-4")).toBeInTheDocument(); + expect(getByText("$150.50")).toBeInTheDocument(); + expect(getByText("100")).toBeInTheDocument(); + expect(getByText("5")).toBeInTheDocument(); + expect(getByText("50,000")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/top_model_view.tsx b/ui/litellm-dashboard/src/components/top_model_view.tsx new file mode 100644 index 00000000000..f090d2da5cb --- /dev/null +++ b/ui/litellm-dashboard/src/components/top_model_view.tsx @@ -0,0 +1,95 @@ +import { BarChart } from "@tremor/react"; +import { formatNumberWithCommas } from "../utils/dataUtils"; +import { useState } from "react"; +import { DataTable } from "./view_logs/table"; + +interface TopModel { + key: string; + spend: number; + successful_requests: number; + failed_requests: number; + tokens: number; +} + +interface TopModelViewProps { + topModels: TopModel[]; +} + +export default function TopModelView({ topModels }: TopModelViewProps) { + const [modelViewMode, setModelViewMode] = useState<"chart" | "table">("table"); + + const columns = [ + { + header: "Model", + accessorKey: "key", + cell: (info: any) => info.getValue() || "-", + }, + { + header: "Spend (USD)", + accessorKey: "spend", + cell: (info: any) => { + const value = info.getValue(); + return `$${formatNumberWithCommas(value, 2)}`; + }, + }, + { + header: "Successful", + accessorKey: "successful_requests", + cell: (info: any) => {info.getValue()?.toLocaleString() || 0}, + }, + { + header: "Failed", + accessorKey: "failed_requests", + cell: (info: any) => {info.getValue()?.toLocaleString() || 0}, + }, + { + header: "Tokens", + accessorKey: "tokens", + cell: (info: any) => info.getValue()?.toLocaleString() || 0, + }, + ]; + return ( + <> +
+
+ + +
+
+ {modelViewMode === "chart" ? ( +
+ `$${formatNumberWithCommas(value, 2)}`} + layout="vertical" + yAxisWidth={200} + showLegend={false} + /> +
+ ) : ( +
+ <>} + getRowCanExpand={() => false} + /> +
+ )} + + ); +}