diff --git a/ui/litellm-dashboard/src/components/add_fallbacks.tsx b/ui/litellm-dashboard/src/components/add_fallbacks.tsx index ad27c7df1e..081f597303 100644 --- a/ui/litellm-dashboard/src/components/add_fallbacks.tsx +++ b/ui/litellm-dashboard/src/components/add_fallbacks.tsx @@ -6,7 +6,7 @@ import React, { useState, useEffect } from "react"; import { Button, TextInput, Grid, Col } from "@tremor/react"; -import { Select, SelectItem, MultiSelect, MultiSelectItem } from "@tremor/react"; +import { Select, SelectItem, MultiSelect, MultiSelectItem, SearchSelect, SearchSelectItem } from "@tremor/react"; import { setCallbacksCall } from "./networking"; import { Modal, @@ -155,7 +155,7 @@ const AddFallbacks: React.FC = ({ rules={[{ required: true, message: 'Please select the primary model that needs fallbacks' }]} className="!mb-0" > - +

This is the primary model that users will request

@@ -223,7 +223,7 @@ const AddFallbacks: React.FC = ({ )} {/* Model selector */} - +

Order matters: Models will be tried in the order shown above (1st, 2nd, 3rd, etc.) diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx index 0f7858968c..4715b3b642 100644 --- a/ui/litellm-dashboard/src/components/leftnav.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.tsx @@ -1,7 +1,8 @@ import { Layout, Menu } from "antd"; import Link from "next/link"; import { List } from "postcss/lib/list"; -import { Text } from "@tremor/react"; +import { Text, Button } from "@tremor/react"; +import { useState } from "react"; import { KeyOutlined, PlayCircleOutlined, @@ -24,6 +25,8 @@ import { ToolOutlined, TagsOutlined, BgColorsOutlined, + MenuFoldOutlined, + MenuUnfoldOutlined, } from '@ant-design/icons'; import { old_admin_roles, v2_admin_role_names, all_admin_roles, rolesAllowedToSeeUsage, rolesWithWriteAccess, internalUserRoles, isAdminRole } from '../utils/roles'; import UsageIndicator from './usage_indicator'; @@ -55,6 +58,11 @@ const Sidebar: React.FC = ({ userRole, defaultSelectedKey, }) => { + const [collapsed, setCollapsed] = useState(false); + + const toggleCollapse = () => { + setCollapsed(!collapsed); + }; // Note: If a menu item does not have a role, it is visible to all roles. const menuItems: MenuItem[] = [ { key: "1", page: "api-keys", label: "Virtual Keys", icon: }, @@ -150,41 +158,116 @@ const Sidebar: React.FC = ({ return ( - +

({ - key: item.key, - icon: item.icon, - label: item.label, - children: item.children?.map(child => ({ - key: child.key, - icon: child.icon, - label: child.label, - onClick: () => { + items={[ + // Expand button row when collapsed, or first menu item when expanded + collapsed ? { + key: 'expand-toggle', + icon: , + label: '', + onClick: toggleCollapse, + style: { + cursor: 'pointer', + }, + title: "Expand navigation sidebar" + } : { + key: filteredMenuItems[0]?.key, + icon: filteredMenuItems[0]?.icon, + label: ( +
+ {filteredMenuItems[0]?.label} +
{ + e.stopPropagation(); + toggleCollapse(); + }} + style={{ + cursor: 'pointer', + padding: '8px 12px', + borderRadius: '4px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: '12px', + color: '#888', + transition: 'all 0.2s', + marginLeft: '8px', + marginRight: '-8px', + }} + onMouseEnter={(e) => { + e.currentTarget.style.background = 'rgba(0, 0, 0, 0.04)'; + e.currentTarget.style.color = '#666'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.background = 'transparent'; + e.currentTarget.style.color = '#888'; + }} + title="Collapse navigation sidebar" + > + +
+
+ ), + onClick: !filteredMenuItems[0]?.children ? () => { const newSearchParams = new URLSearchParams(window.location.search); - newSearchParams.set('page', child.page); + newSearchParams.set('page', filteredMenuItems[0]?.page || ''); window.history.pushState(null, '', `?${newSearchParams.toString()}`); - setPage(child.page); - } - })), - onClick: !item.children ? () => { - const newSearchParams = new URLSearchParams(window.location.search); - newSearchParams.set('page', item.page); - window.history.pushState(null, '', `?${newSearchParams.toString()}`); - setPage(item.page); - } : undefined - }))} + setPage(filteredMenuItems[0]?.page || ''); + } : undefined + }, + // Rest of the menu items (or all items when collapsed) + ...(collapsed ? filteredMenuItems : filteredMenuItems.slice(1)).map(item => ({ + key: item.key, + icon: item.icon, + label: item.label, + children: item.children?.map(child => ({ + key: child.key, + icon: child.icon, + label: child.label, + onClick: () => { + const newSearchParams = new URLSearchParams(window.location.search); + newSearchParams.set('page', child.page); + window.history.pushState(null, '', `?${newSearchParams.toString()}`); + setPage(child.page); + } + })), + onClick: !item.children ? () => { + const newSearchParams = new URLSearchParams(window.location.search); + newSearchParams.set('page', item.page); + window.history.pushState(null, '', `?${newSearchParams.toString()}`); + setPage(item.page); + } : undefined + })) + ]} /> { - isAdminRole(userRole) && ( + isAdminRole(userRole) && !collapsed && ( ) }