From ff06e6d4eab214cb9d6a95f2f92828251a872999 Mon Sep 17 00:00:00 2001 From: Achintya Rajan Date: Fri, 10 Oct 2025 15:02:38 -0700 Subject: [PATCH] extracted AllModelsTab, eliminating props --- .../components/AllModelsTab.tsx | 373 +++++++++++++++++ .../models-and-endpoints}/model_dashboard.tsx | 389 ++---------------- .../(dashboard)/models-and-endpoints/page.tsx | 4 +- ui/litellm-dashboard/src/app/page.tsx | 4 +- 4 files changed, 411 insertions(+), 359 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx rename ui/litellm-dashboard/src/{components/templates => app/(dashboard)/models-and-endpoints}/model_dashboard.tsx (74%) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx new file mode 100644 index 0000000000..389023c6fb --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx @@ -0,0 +1,373 @@ +import { Grid, Select, SelectItem, TabPanel, Text } from "@tremor/react"; +import { InfoCircleOutlined } from "@ant-design/icons"; +import { ModelDataTable } from "@/components/model_dashboard/table"; +import { columns } from "@/components/molecules/models/columns"; +import { getDisplayModelName } from "@/components/view_model/model_name_display"; +import React, { useEffect, useMemo, useRef, useState } from "react"; +import useTeams from "@/app/(dashboard)/hooks/useTeams"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { Table as TableInstance, PaginationState } from "@tanstack/react-table"; + +type ModelViewMode = "all" | "current_team"; + +interface AllModelsTabProps { + modelNameSearch: string; + setModelNameSearch: (nameSearch: string) => void; + selectedModelGroup: string | null; + setSelectedModelGroup: (selectedModelGroup: string) => void; + availableModelGroups: string[]; + selectedModelAccessGroupFilter: string | null; + setSelectedModelAccessGroupFilter: (selectedModelAccessGroupFilter: string | null) => void; + availableModelAccessGroups: string[]; + setSelectedModelId: (id: string) => void; + setSelectedTeamId: (id: string) => void; + setEditModel: (edit: boolean) => void; + modelData: any; +} + +const AllModelsTab = ({ + modelNameSearch, + setModelNameSearch, + selectedModelGroup, + setSelectedModelGroup, + availableModelGroups, + selectedModelAccessGroupFilter, + setSelectedModelAccessGroupFilter, + availableModelAccessGroups, + setSelectedModelId, + setSelectedTeamId, + setEditModel, + modelData, +}: AllModelsTabProps) => { + const { userId, userRole, premiumUser } = useAuthorized(); + const { teams } = useTeams(); + + const [modelViewMode, setModelViewMode] = useState("current_team"); + const [currentTeam, setCurrentTeam] = useState("personal"); // 'personal' or team_id + const [showFilters, setShowFilters] = useState(false); + const [expandedRows, setExpandedRows] = useState>(new Set()); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 50, + }); + const tableRef = useRef>(null); + + const filteredData = useMemo(() => { + if (!modelData || !modelData.data || modelData.data.length === 0) { + return []; + } + + return modelData.data.filter((model: any) => { + const searchMatch = + modelNameSearch === "" || model.model_name.toLowerCase().includes(modelNameSearch.toLowerCase()); + + const modelNameMatch = + selectedModelGroup === "all" || + model.model_name === selectedModelGroup || + !selectedModelGroup || + (selectedModelGroup === "wildcard" && model.model_name?.includes("*")); + + const accessGroupMatch = + selectedModelAccessGroupFilter === "all" || + model.model_info["access_groups"]?.includes(selectedModelAccessGroupFilter) || + !selectedModelAccessGroupFilter; + + let teamAccessMatch = true; + if (modelViewMode === "current_team") { + if (currentTeam === "personal") { + teamAccessMatch = model.model_info?.direct_access === true; + } else { + teamAccessMatch = model.model_info?.access_via_team_ids?.includes(currentTeam) === true; + } + } + + return searchMatch && modelNameMatch && accessGroupMatch && teamAccessMatch; + }); + }, [modelData, modelNameSearch, selectedModelGroup, selectedModelAccessGroupFilter, currentTeam, modelViewMode]); + + const paginatedData = useMemo(() => { + const startIndex = pagination.pageIndex * pagination.pageSize; + const endIndex = startIndex + pagination.pageSize; + return filteredData.slice(startIndex, endIndex); + }, [filteredData, pagination.pageIndex, pagination.pageSize]); + + useEffect(() => { + setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); + }, [modelNameSearch, selectedModelGroup, selectedModelAccessGroupFilter, currentTeam, modelViewMode]); + + const resetFilters = () => { + setModelNameSearch(""); + setSelectedModelGroup("all"); + setSelectedModelAccessGroupFilter(null); + setCurrentTeam("personal"); + setModelViewMode("current_team"); + setPagination({ pageIndex: 0, pageSize: 50 }); + }; + + return ( + + +
+
+ {/* Current Team and View Mode Selector - Prominent Section */} +
+
+
+ Current Team: + +
+ +
+ View: + +
+
+ + {modelViewMode === "current_team" && ( +
+ +
+ {currentTeam === "personal" ? ( + + To access these models: Create a Virtual Key without selecting a team on the{" "} + + Virtual Keys page + + + ) : ( + + To access these models: Create a Virtual Key and select Team as " + {currentTeam}" on the{" "} + + Virtual Keys page + + + )} +
+
+ )} +
+ + {/* Search and Filter Controls */} +
+
+ {/* Search and Filter Controls */} +
+ {/* Model Name Search */} +
+ setModelNameSearch(e.target.value)} + /> + + + +
+ + {/* Filter Button */} + + + {/* Reset Filters Button */} + +
+ + {/* Additional Filters */} + {showFilters && ( +
+ {/* Model Name Filter */} +
+ +
+ + {/* Model Access Group Filter */} +
+ +
+
+ )} + + {/* Results Count and Pagination Controls */} +
+ + {filteredData.length > 0 + ? `Showing ${pagination.pageIndex * pagination.pageSize + 1} - ${Math.min( + (pagination.pageIndex + 1) * pagination.pageSize, + filteredData.length, + )} of ${filteredData.length} results` + : "Showing 0 results"} + + + {/* Pagination Controls */} + {filteredData.length > pagination.pageSize && ( +
+ + + +
+ )} +
+
+
+ + {}, + () => {}, + setEditModel, + expandedRows, + setExpandedRows, + )} + data={paginatedData} + isLoading={false} + table={tableRef} + /> +
+
+
+
+ ); +}; + +export default AllModelsTab; diff --git a/ui/litellm-dashboard/src/components/templates/model_dashboard.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/model_dashboard.tsx similarity index 74% rename from ui/litellm-dashboard/src/components/templates/model_dashboard.tsx rename to ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/model_dashboard.tsx index 23bde8fcc3..65b1353205 100644 --- a/ui/litellm-dashboard/src/components/templates/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/model_dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef, useMemo } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Card, Title, @@ -13,15 +13,15 @@ import { Grid, Col, } from "@tremor/react"; -import { CredentialItem, credentialListCall, CredentialsResponse } from "../networking"; +import { CredentialItem, credentialListCall, CredentialsResponse } from "../../../components/networking"; -import { handleAddModelSubmit } from "../add_model/handle_add_model_submit"; +import { handleAddModelSubmit } from "../../../components/add_model/handle_add_model_submit"; import CredentialsPanel from "@/components/model_add/credentials"; -import { getDisplayModelName } from "../view_model/model_name_display"; +import { getDisplayModelName } from "../../../components/view_model/model_name_display"; import { TabPanel, TabPanels, TabGroup, TabList, Tab, Icon } from "@tremor/react"; import { Select, SelectItem, DateRangePickerValue } from "@tremor/react"; -import UsageDatePicker from "../shared/usage_date_picker"; +import UsageDatePicker from "../../../components/shared/usage_date_picker"; import { modelInfoCall, modelCostMap, @@ -36,30 +36,27 @@ import { adminGlobalActivityExceptions, adminGlobalActivityExceptionsPerDeployment, allEndUsersCall, -} from "../networking"; +} from "../../../components/networking"; import { BarChart, AreaChart } from "@tremor/react"; import { Popover, Form, InputNumber } from "antd"; import { Button } from "@tremor/react"; import { Typography } from "antd"; import { RefreshIcon, FilterIcon } from "@heroicons/react/outline"; -import { InfoCircleOutlined } from "@ant-design/icons"; import type { UploadProps } from "antd"; -import TimeToFirstToken from "../model_metrics/time_to_first_token"; -import { Team } from "../key_team_helpers/key_list"; -import TeamInfoView from "../team/team_info"; -import { Providers, provider_map, getPlaceholder, getProviderModels } from "../provider_info_helpers"; -import ModelInfoView from "../model_info_view"; -import AddModelTab from "../add_model/add_model_tab"; +import TimeToFirstToken from "../../../components/model_metrics/time_to_first_token"; +import { Team } from "../../../components/key_team_helpers/key_list"; +import TeamInfoView from "../../../components/team/team_info"; +import { Providers, provider_map, getPlaceholder, getProviderModels } from "../../../components/provider_info_helpers"; +import ModelInfoView from "../../../components/model_info_view"; +import AddModelTab from "../../../components/add_model/add_model_tab"; -import { ModelDataTable } from "../model_dashboard/table"; -import { columns } from "../molecules/models/columns"; -import PriceDataReload from "../price_data_reload"; -import HealthCheckComponent from "../model_dashboard/HealthCheckComponent"; -import PassThroughSettings from "../pass_through_settings"; -import ModelGroupAliasSettings from "../model_group_alias_settings"; +import PriceDataReload from "../../../components/price_data_reload"; +import HealthCheckComponent from "../../../components/model_dashboard/HealthCheckComponent"; +import PassThroughSettings from "../../../components/pass_through_settings"; +import ModelGroupAliasSettings from "../../../components/model_group_alias_settings"; import { all_admin_roles } from "@/utils/roles"; -import { Table as TableInstance, PaginationState } from "@tanstack/react-table"; -import NotificationsManager from "../molecules/notifications_manager"; +import NotificationsManager from "../../../components/molecules/notifications_manager"; +import AllModelsTab from "@/app/(dashboard)/models-and-endpoints/components/AllModelsTab"; interface ModelDashboardProps { accessToken: string | null; @@ -109,7 +106,7 @@ const retry_policy_map: Record = { "InternalServerError (500)": "InternalServerErrorRetries", }; -const ModelDashboard: React.FC = ({ +const ModelsAndEndpointsView: React.FC = ({ accessToken, token, userRole, @@ -184,25 +181,14 @@ const ModelDashboard: React.FC = ({ const [modelNameSearch, setModelNameSearch] = useState(""); - // Add new state for current team and model view mode - const [currentTeam, setCurrentTeam] = useState("personal"); // 'personal' or team_id - const [modelViewMode, setModelViewMode] = useState<"current_team" | "all">("current_team"); - // Add state for showing/hiding filters const [showFilters, setShowFilters] = useState(false); const [showColumnDropdown, setShowColumnDropdown] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const [expandedRows, setExpandedRows] = useState>(new Set()); const dropdownRef = useRef(null); - const tableRef = useRef>(null); - // Pagination state - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 50, - }); const [selectedTabIndex, setSelectedTabIndex] = useState(0); const handleCreateNewModelClick = () => { @@ -212,61 +198,6 @@ const ModelDashboard: React.FC = ({ setSelectedTabIndex(1); }; - const resetFilters = () => { - setModelNameSearch(""); - setSelectedModelGroup("all"); - setSelectedModelAccessGroupFilter(null); - setCurrentTeam("personal"); - setModelViewMode("current_team"); - setPagination({ pageIndex: 0, pageSize: 50 }); - }; - - // Memoize filtered data to prevent unnecessary re-calculations - const filteredData = useMemo(() => { - if (!modelData || !modelData.data || modelData.data.length === 0) { - return []; - } - - return modelData.data.filter((model: any) => { - const searchMatch = - modelNameSearch === "" || model.model_name.toLowerCase().includes(modelNameSearch.toLowerCase()); - - const modelNameMatch = - selectedModelGroup === "all" || - model.model_name === selectedModelGroup || - !selectedModelGroup || - (selectedModelGroup === "wildcard" && model.model_name?.includes("*")); - - const accessGroupMatch = - selectedModelAccessGroupFilter === "all" || - model.model_info["access_groups"]?.includes(selectedModelAccessGroupFilter) || - !selectedModelAccessGroupFilter; - - let teamAccessMatch = true; - if (modelViewMode === "current_team") { - if (currentTeam === "personal") { - teamAccessMatch = model.model_info?.direct_access === true; - } else { - teamAccessMatch = model.model_info?.access_via_team_ids?.includes(currentTeam) === true; - } - } - - return searchMatch && modelNameMatch && accessGroupMatch && teamAccessMatch; - }); - }, [modelData, modelNameSearch, selectedModelGroup, selectedModelAccessGroupFilter, currentTeam, modelViewMode]); - - // Memoize paginated data - const paginatedData = useMemo(() => { - const startIndex = pagination.pageIndex * pagination.pageSize; - const endIndex = startIndex + pagination.pageSize; - return filteredData.slice(startIndex, endIndex); - }, [filteredData, pagination.pageIndex, pagination.pageSize]); - - // Reset pagination when filters change - useEffect(() => { - setPagination((prev) => ({ ...prev, pageIndex: 0 })); - }, [modelNameSearch, selectedModelGroup, selectedModelAccessGroupFilter, currentTeam, modelViewMode]); - const setProviderModelsFn = (provider: Providers) => { const _providerModels = getProviderModels(provider, modelMap); setProviderModels(_providerModels); @@ -1065,272 +996,20 @@ const ModelDashboard: React.FC = ({ - - -
-
- {/* Current Team and View Mode Selector - Prominent Section */} -
-
-
- Current Team: - -
- -
- View: - -
-
- - {modelViewMode === "current_team" && ( -
- -
- {currentTeam === "personal" ? ( - - To access these models: Create a Virtual Key without selecting a team on the{" "} - - Virtual Keys page - - - ) : ( - - To access these models: Create a Virtual Key and select Team as " - {currentTeam}" on the{" "} - - Virtual Keys page - - - )} -
-
- )} -
- - {/* Search and Filter Controls */} -
-
- {/* Search and Filter Controls */} -
- {/* Model Name Search */} -
- setModelNameSearch(e.target.value)} - /> - - - -
- - {/* Filter Button */} - - - {/* Reset Filters Button */} - -
- - {/* Additional Filters */} - {showFilters && ( -
- {/* Model Name Filter */} -
- -
- - {/* Model Access Group Filter */} -
- -
-
- )} - - {/* Results Count and Pagination Controls */} -
- - {filteredData.length > 0 - ? `Showing ${pagination.pageIndex * pagination.pageSize + 1} - ${Math.min( - (pagination.pageIndex + 1) * pagination.pageSize, - filteredData.length, - )} of ${filteredData.length} results` - : "Showing 0 results"} - - - {/* Pagination Controls */} - {filteredData.length > pagination.pageSize && ( -
- - - -
- )} -
-
-
- - -
-
-
-
+ = ({ ); }; -export default ModelDashboard; +export default ModelsAndEndpointsView; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/page.tsx index b661e4de3e..4283693d1b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/page.tsx @@ -1,9 +1,9 @@ "use client"; -import ModelDashboard from "@/components/templates/model_dashboard"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import useTeams from "@/app/(dashboard)/hooks/useTeams"; import { useState } from "react"; +import ModelsAndEndpointsView from "@/app/(dashboard)/models-and-endpoints/model_dashboard"; const ModelsAndEndpointsPage = () => { const { token, accessToken, userRole, userId, premiumUser } = useAuthorized(); @@ -12,7 +12,7 @@ const ModelsAndEndpointsPage = () => { const { teams } = useTeams(); return ( - ) : page == "models" ? ( -