From b342f32f558db213ea97bfc7e08ba3ab88bdc5f0 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 25 Apr 2025 11:22:55 -0700 Subject: [PATCH] feat(filter.tsx): initial commit making filter component more generic - same style as user table filters --- ...odel_prices_and_context_window_backup.json | 24 +- .../components/common_components/filter.tsx | 256 ++++-------------- 2 files changed, 72 insertions(+), 208 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 760a7c7842c..eb4b9f721e3 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/common_components/filter.tsx b/ui/litellm-dashboard/src/components/common_components/filter.tsx index cca4f70b001..f706d662db3 100644 --- a/ui/litellm-dashboard/src/components/common_components/filter.tsx +++ b/ui/litellm-dashboard/src/components/common_components/filter.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect, useCallback } from 'react'; -import { Button, Input, Dropdown, MenuProps, Select, Spin } from 'antd'; +import { Button, Input, Dropdown, MenuProps, Select, Spin, Space } from 'antd'; import { Card, Button as TremorButton } from '@tremor/react'; import { FilterIcon, @@ -38,121 +38,44 @@ const FilterComponent: React.FC = ({ buttonLabel = "Filter", }) => { const [showFilters, setShowFilters] = useState(false); - const [selectedFilter, setSelectedFilter] = useState(options[0]?.name || ''); const [filterValues, setFilterValues] = useState(initialValues); const [tempValues, setTempValues] = useState(initialValues); - const [dropdownOpen, setDropdownOpen] = useState(false); - const [searchOptions, setSearchOptions] = useState>([]); - const [searchLoading, setSearchLoading] = useState(false); - const [searchInputValue, setSearchInputValue] = useState(''); + const [searchOptionsMap, setSearchOptionsMap] = useState<{ [key: string]: Array<{ label: string; value: string }> }>({}); + const [searchLoadingMap, setSearchLoadingMap] = useState<{ [key: string]: boolean }>({}); + const [searchInputValueMap, setSearchInputValueMap] = useState<{ [key: string]: string }>({}); const filtersRef = useRef(null); - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - const target = event.target as HTMLElement; - if (filtersRef.current && - !filtersRef.current.contains(target) && - !target.closest('.ant-dropdown') && - !target.closest('.ant-select-dropdown')) { - setShowFilters(false); - } - }; - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - useEffect(() => { - if (options.length > 0 && options[0].isSearchable && options[0].searchFn) { - loadInitialOptions(options[0]); - } - }, []); - - const loadInitialOptions = async (option: FilterOption) => { - if (!option.isSearchable || !option.searchFn) return; - - setSearchLoading(true); - try { - const results = await option.searchFn(''); - setSearchOptions(results); - } catch (error) { - console.error('Error loading initial options:', error); - setSearchOptions([]); - } finally { - setSearchLoading(false); - } - }; - - useEffect(() => { - if (showFilters && currentOption?.isSearchable && currentOption?.searchFn) { - loadInitialOptions(currentOption); - } - }, [showFilters, selectedFilter]); - - const handleFilterSelect = (key: string) => { - setSelectedFilter(key); - setDropdownOpen(false); - - const newOption = options.find(opt => opt.name === key); - if (newOption?.isSearchable && newOption?.searchFn) { - loadInitialOptions(newOption); - } else { - setSearchOptions([]); - } - }; const debouncedSearch = useCallback( debounce(async (value: string, option: FilterOption) => { if (!option.isSearchable || !option.searchFn) return; - setSearchLoading(true); + setSearchLoadingMap(prev => ({ ...prev, [option.name]: true })); try { const results = await option.searchFn(value); - setSearchOptions(results); + setSearchOptionsMap(prev => ({ ...prev, [option.name]: results })); } catch (error) { console.error('Error searching:', error); - setSearchOptions([]); + setSearchOptionsMap(prev => ({ ...prev, [option.name]: [] })); } finally { - setSearchLoading(false); + setSearchLoadingMap(prev => ({ ...prev, [option.name]: false })); } }, 300), [] ); - - const handleFilterChange = (value: string) => { + + const handleFilterChange = (name: string, value: string) => { setTempValues(prev => ({ ...prev, - [selectedFilter]: value + [name]: value })); }; - - const clearFilters = () => { - const emptyValues: FilterValues = {}; - options.forEach(option => { - emptyValues[option.name] = ''; - }); - setTempValues(emptyValues); - }; - + const handleApplyFilters = () => { setFilterValues(tempValues); onApplyFilters(tempValues); setShowFilters(false); }; - - const dropdownItems: MenuProps['items'] = options.map(option => ({ - key: option.name, - label: ( -
- {selectedFilter === option.name && ( - - )} - {option.label || option.name} -
- ), - })); - - const currentOption = options.find(option => option.name === selectedFilter); const resetFilters = () => { const emptyValues: FilterValues = {}; @@ -161,11 +84,11 @@ const FilterComponent: React.FC = ({ }); setTempValues(emptyValues); setFilterValues(emptyValues); - setSearchInputValue(''); - setSearchOptions([]); - onResetFilters(); // Call the parent's reset function + setSearchInputValueMap({}); + setSearchOptionsMap({}); + onResetFilters(); }; - + return (
= ({ {buttonLabel} {showFilters && ( - -
-
- Where - - handleFilterSelect(key), - style: { minWidth: '200px' } - }} - onOpenChange={setDropdownOpen} - open={dropdownOpen} - trigger={['click']} - > - - - - {currentOption?.isSearchable ? ( - handleFilterChange(e.target.value)} - className="px-3 py-1.5 border rounded-md text-sm flex-1 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" - suffix={ - tempValues[selectedFilter] ? ( - { - e.stopPropagation(); - handleFilterChange(''); - }} - /> - ) : null - } - /> - )} -
- - -
- - -
-
-
+
+ + {options.map((option) => ( +
+ {option.isSearchable ? ( + handleFilterChange(option.name, e.target.value)} + allowClear + /> + )} +
+ ))} + + + + +
+
)}
);