diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 760a7c7842..eb4b9f721e 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -356,7 +356,8 @@ "supports_vision": true, "supports_prompt_caching": true, "supports_system_messages": true, - "supports_tool_choice": true + "supports_tool_choice": true, + "deprecation_date": "2025-07-14" }, "gpt-4o-audio-preview": { "max_tokens": 16384, @@ -1509,6 +1510,8 @@ }, "gpt-4o-transcribe": { "mode": "audio_transcription", + "max_input_tokens": 16000, + "max_output_tokens": 2000, "input_cost_per_token": 0.0000025, "input_cost_per_audio_token": 0.000006, "output_cost_per_token": 0.00001, @@ -1517,6 +1520,8 @@ }, "gpt-4o-mini-transcribe": { "mode": "audio_transcription", + "max_input_tokens": 16000, + "max_output_tokens": 2000, "input_cost_per_token": 0.00000125, "input_cost_per_audio_token": 0.000003, "output_cost_per_token": 0.000005, @@ -2439,7 +2444,8 @@ "supports_response_schema": true, "supports_vision": true, "supports_prompt_caching": true, - "supports_tool_choice": true + "supports_tool_choice": true, + "deprecation_date": "2025-08-20" }, "azure/us/gpt-4o-2024-08-06": { "max_tokens": 16384, @@ -2479,13 +2485,15 @@ "max_output_tokens": 16384, "input_cost_per_token": 0.0000025, "output_cost_per_token": 0.000010, + "cache_read_input_token_cost": 0.00000125, "litellm_provider": "azure", "mode": "chat", "supports_function_calling": true, "supports_parallel_function_calling": true, "supports_response_schema": true, "supports_vision": true, - "supports_tool_choice": true + "supports_tool_choice": true, + "deprecation_date": "2025-12-20" }, "azure/global-standard/gpt-4o-mini": { "max_tokens": 16384, @@ -5349,14 +5357,14 @@ "input_cost_per_image": 0, "input_cost_per_video_per_second": 0, "input_cost_per_audio_per_second": 0, - "input_cost_per_token": 0, + "input_cost_per_token": 0.00000015, "input_cost_per_character": 0, "input_cost_per_token_above_128k_tokens": 0, "input_cost_per_character_above_128k_tokens": 0, "input_cost_per_image_above_128k_tokens": 0, "input_cost_per_video_per_second_above_128k_tokens": 0, "input_cost_per_audio_per_second_above_128k_tokens": 0, - "output_cost_per_token": 0, + "output_cost_per_token": 0.0000006, "output_cost_per_character": 0, "output_cost_per_token_above_128k_tokens": 0, "output_cost_per_character_above_128k_tokens": 0, @@ -5395,7 +5403,8 @@ "supports_tool_choice": true, "supported_modalities": ["text", "image", "audio", "video"], "supported_output_modalities": ["text", "image"], - "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing" + "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing", + "deprecation_date": "2026-02-05" }, "gemini-2.0-flash-thinking-exp": { "max_tokens": 8192, @@ -5599,7 +5608,8 @@ "supported_modalities": ["text", "image", "audio", "video"], "supported_output_modalities": ["text"], "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash", - "supports_tool_choice": true + "supports_tool_choice": true, + "deprecation_date": "2026-02-25" }, "gemini-2.5-pro-preview-03-25": { "max_tokens": 65536, diff --git a/ui/litellm-dashboard/src/components/model_dashboard.tsx b/ui/litellm-dashboard/src/components/model_dashboard.tsx index 43b3b4f47b..e7697a8d03 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Card, Title, @@ -95,6 +95,7 @@ import { FilterIcon, ChevronUpIcon, ChevronDownIcon, + TableIcon, } from "@heroicons/react/outline"; import DeleteModelButton from "./delete_model_button"; const { Title: Title2, Link } = Typography; @@ -112,6 +113,7 @@ import AddModelTab from "./add_model/add_model_tab"; import { ModelDataTable } from "./model_dashboard/table"; import { columns } from "./model_dashboard/columns"; import { all_admin_roles } from "@/utils/roles"; +import { Table as TableInstance } from '@tanstack/react-table'; interface ModelDashboardProps { accessToken: string | null; @@ -246,6 +248,15 @@ const ModelDashboard: React.FC = ({ const [editModel, setEditModel] = useState(false); const [selectedTeamId, setSelectedTeamId] = useState(null); + const [selectedTeam, setSelectedTeam] = useState(null); + + const [selectedTeamFilter, setSelectedTeamFilter] = useState(null); + + const [showColumnDropdown, setShowColumnDropdown] = useState(false); + + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const dropdownRef = useRef(null); + const tableRef = useRef>(null); const setProviderModelsFn = (provider: Providers) => { const _providerModels = getProviderModels(provider, modelMap); @@ -268,7 +279,7 @@ const ModelDashboard: React.FC = ({ "endTime:", endTime ); - setSelectedModelGroup(modelGroup); // If you want to store the selected model group in state + setSelectedModelGroup(modelGroup); let selected_token = selectedAPIKey?.token; if (selected_token === undefined) { @@ -289,7 +300,6 @@ const ModelDashboard: React.FC = ({ endTime.setMinutes(59); endTime.setSeconds(59); - try { const modelMetricsResponse = await modelMetricsCall( accessToken, @@ -394,7 +404,18 @@ const ModelDashboard: React.FC = ({ dateValue.from, dateValue.to ); - }, [selectedAPIKey, selectedCustomer]); + }, [selectedAPIKey, selectedCustomer, selectedTeam]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsDropdownOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); function formatCreatedAt(createdAt: string | null) { if (createdAt) { @@ -642,7 +663,7 @@ const ModelDashboard: React.FC = ({ } handleRefreshClick(); - }, [accessToken, token, userRole, userID, modelMap, lastRefreshed]); + }, [accessToken, token, userRole, userID, modelMap, lastRefreshed, selectedTeam]); if (!modelData) { return
Loading...
; @@ -812,125 +833,95 @@ const ModelDashboard: React.FC = ({ ); } - return null; // Add this line to handle the case when the condition is not met + return null; })} + + Select Customer Name + + + - - Select Customer Name - - - setSelectedTeamFilter(value === "all" ? null : value)} + > + All Teams + {teams?.filter(team => team.team_id).map((team) => ( { - setSelectedCustomer(user); - }} + key={team.team_id} + value={team.team_id} > - {user} + {team.team_alias + ? `${team.team_alias} (${team.team_id.slice(0, 8)}...)` + : `Team ${team.team_id.slice(0, 8)}...` + } - ); - }) - } - - + ))} + ): (
- - setSelectedTeamFilter(value === "all" ? null : value)} + > + All Teams + {teams?.filter(team => team.team_id).map((team) => ( { - setSelectedAPIKey(null); - }} + key={team.team_id} + value={team.team_id} > - All Keys + {team.team_alias + ? `${team.team_alias} (${team.team_id.slice(0, 8)}...)` + : `Team ${team.team_id.slice(0, 8)}...` + } - {keys?.map((key: any, index: number) => { - if ( - key && - key["key_alias"] !== null && - key["key_alias"].length > 0 - ) { - return ( - - { - setSelectedAPIKey(key); - }} - > - ✨ {key["key_alias"]} (Enterprise only Feature) - - ); - } - return null; // Add this line to handle the case when the condition is not met - })} - - - - - Select Customer Name - - - - + ))} +
) } - - - - - - - + ); const customTooltip = (props: any) => { @@ -1088,63 +1079,146 @@ const ModelDashboard: React.FC = ({ -
- {/* Left side - Title and description */} -
- Model Management - {!all_admin_roles.includes(userRole) ? ( - - Add models for teams you are an admin for. - - ) : ( - - Add and manage models for the proxy - - )} -
+
+
+
+ Model Management + {!all_admin_roles.includes(userRole) ? ( + + Add models for teams you are an admin for. + + ) : ( + + Add and manage models for the proxy + + )} +
+
- {/* Right side - Filter */} -
- Filter by Public Model Name: - +
+
+
+ {/* Search and Filter Controls */} +
+
+ {/* Model Name Filter */} +
+ Filter by Public Model Name: + +
+ + {/* Team Filter */} +
+ Filter by Team: + +
+
+ + {/* Column Selector will be rendered here */} +
+ + {isDropdownOpen && tableRef.current && ( +
+
+ {tableRef.current.getAllLeafColumns().map((column: any) => { + if (column.id === 'actions') return null; + return ( +
column.toggleVisibility()} + > + column.toggleVisibility()} + className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" + /> + + {typeof column.columnDef.header === 'string' + ? column.columnDef.header + : column.columnDef.header?.props?.children || column.id} + +
+ ); + })} +
+
+ )} +
+
+ + {/* Results Count */} +
+ + Showing {modelData && modelData.data.length > 0 ? modelData.data.length : 0} results + +
+
+
+ + ( + (selectedModelGroup === "all" || model.model_name === selectedModelGroup || !selectedModelGroup) && + (selectedTeamFilter === "all" || model.team_id === selectedTeamFilter || !selectedTeamFilter) + ) + )} + isLoading={false} + table={tableRef} + /> +
-
- - selectedModelGroup === "all" || - model.model_name === selectedModelGroup || - !selectedModelGroup - )} - isLoading={false} // Add loading state if needed - /> diff --git a/ui/litellm-dashboard/src/components/model_dashboard/model_dashboard.tsx b/ui/litellm-dashboard/src/components/model_dashboard/model_dashboard.tsx new file mode 100644 index 0000000000..1f8146aa10 --- /dev/null +++ b/ui/litellm-dashboard/src/components/model_dashboard/model_dashboard.tsx @@ -0,0 +1,63 @@ +import { useState } from 'react'; +import { Select, SelectItem, Text } from "@tremor/react"; + +interface ModelData { + team_id: string; + team_name: string; + // Add other properties as needed +} + +interface ModelDashboardProps { + modelData: ModelData[]; +} + +export default function ModelDashboard({ modelData }: ModelDashboardProps) { + const [selectedTeam, setSelectedTeam] = useState(null); + + const getTeamName = (teamId: string): string => { + const team = modelData.find(item => item.team_id === teamId); + return team?.team_name || 'Unknown Team'; + }; + + return ( +
+
+
+ Model Management + Add and manage models for the proxy +
+ +
+
+ Filter by Public Model Name: + +
+ +
+ Filter by Team: + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/model_dashboard/table.tsx b/ui/litellm-dashboard/src/components/model_dashboard/table.tsx index 48661ad15e..35bcef6111 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard/table.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard/table.tsx @@ -24,12 +24,14 @@ interface ModelDataTableProps { data: TData[]; columns: ColumnDef[]; isLoading?: boolean; + table: any; // Add table prop to access column visibility controls } export function ModelDataTable({ data = [], columns, isLoading = false, + table }: ModelDataTableProps) { const [sorting, setSorting] = React.useState([ { id: "model_info.created_at", desc: true } @@ -37,21 +39,8 @@ export function ModelDataTable({ const [columnResizeMode] = React.useState("onChange"); const [columnSizing, setColumnSizing] = React.useState({}); const [columnVisibility, setColumnVisibility] = React.useState({}); - const [isDropdownOpen, setIsDropdownOpen] = React.useState(false); - const dropdownRef = React.useRef(null); - React.useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setIsDropdownOpen(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - const table = useReactTable({ + const tableInstance = useReactTable({ data, columns, state: { @@ -73,6 +62,13 @@ export function ModelDataTable({ }, }); + // Expose table instance to parent + React.useEffect(() => { + if (table) { + table.current = tableInstance; + } + }, [tableInstance, table]); + const getHeaderText = (header: any): string => { if (typeof header === 'string') { return header; @@ -93,145 +89,106 @@ export function ModelDataTable({ }; return ( -
-
-
- - {isDropdownOpen && ( -
-
- {table.getAllLeafColumns().map((column) => { - if (column.id === 'actions') return null; - return ( -
column.toggleVisibility()} +
+
+
+ + + {tableInstance.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + - column.toggleVisibility()} - className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" - /> - {getHeaderText(column.columnDef.header)} +
+
+ {header.isPlaceholder ? null : ( + flexRender( + header.column.columnDef.header, + header.getContext() + ) + )} +
+ {header.id !== 'actions' && ( +
+ {header.column.getIsSorted() ? ( + { + asc: , + desc: + }[header.column.getIsSorted() as string] + ) : ( + + )} +
+ )} +
+ {header.column.getCanResize() && ( +
+ )} + + ))} + + ))} + + + {isLoading ? ( + + +
+

🚅 Loading models...

- ); - })} -
- - )} - - -
-
-
-
- - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - + + ) : tableInstance.getRowModel().rows.length > 0 ? ( + tableInstance.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + -
-
- {header.isPlaceholder ? null : ( - flexRender( - header.column.columnDef.header, - header.getContext() - ) - )} -
- {header.id !== 'actions' && ( -
- {header.column.getIsSorted() ? ( - { - asc: , - desc: - }[header.column.getIsSorted() as string] - ) : ( - - )} -
- )} -
- {header.column.getCanResize() && ( -
- )} - + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} - ))} - - - {isLoading ? ( - - -
-

🚅 Loading models...

-
-
-
- ) : table.getRowModel().rows.length > 0 ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - )) - ) : ( - - -
-

No models found

-
-
-
- )} -
-
-
+ )) + ) : ( + + +
+

No models found

+
+
+
+ )} + +