diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 87440dd7232..f423bdb2c78 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -46,7 +46,8 @@ "src/components/activity_metrics.tsx", "src/components/skill_hub_table_columns.tsx", "src/components/common_components/PassThroughSecuritySection.tsx", - "src/components/common_components/DeleteResourceModal.tsx" + "src/components/common_components/DeleteResourceModal.tsx", + "src/components/common_components/PremiumLoggingSettings.tsx" ], "rules": { "litellm-ui/no-banned-ui-imports": "off", diff --git a/ui/litellm-dashboard/src/components/common_components/ModelSelector.tsx b/ui/litellm-dashboard/src/components/common_components/ModelSelector.tsx index 24d6696dec6..3f6db4734ff 100644 --- a/ui/litellm-dashboard/src/components/common_components/ModelSelector.tsx +++ b/ui/litellm-dashboard/src/components/common_components/ModelSelector.tsx @@ -1,8 +1,18 @@ -import React, { useState, useEffect, useRef } from "react"; -import { TextInput, Text } from "@tremor/react"; -import { Select } from "antd"; -import { RobotOutlined } from "@ant-design/icons"; -import { fetchAvailableModels, ModelGroup } from "../playground/llm_calls/fetch_models"; +import React, { useState, useEffect, useRef, useMemo } from "react"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Bot } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { + fetchAvailableModels, + ModelGroup, +} from "../playground/llm_calls/fetch_models"; interface ModelSelectorProps { accessToken: string; @@ -28,7 +38,8 @@ const ModelSelector: React.FC = ({ labelText = "Select Model", }) => { const [selectedModel, setSelectedModel] = useState(value); - const [showCustomModelInput, setShowCustomModelInput] = useState(false); + const [showCustomModelInput, setShowCustomModelInput] = + useState(false); const [modelInfo, setModelInfo] = useState([]); const customModelTimeout = useRef(null); @@ -43,10 +54,7 @@ const ModelSelector: React.FC = ({ try { const uniqueModels = await fetchAvailableModels(accessToken); console.log("Fetched models for selector:", uniqueModels); - - if (uniqueModels.length > 0) { - setModelInfo(uniqueModels); - } + if (uniqueModels.length > 0) setModelInfo(uniqueModels); } catch (error) { console.error("Error fetching model info:", error); } @@ -55,62 +63,62 @@ const ModelSelector: React.FC = ({ loadModels(); }, [accessToken]); - const onModelChange = (value: string) => { - if (value === "custom") { + const onModelChange = (v: string) => { + if (v === "custom") { setShowCustomModelInput(true); setSelectedModel(undefined); } else { setShowCustomModelInput(false); - setSelectedModel(value); - if (onChange) { - onChange(value); - } + setSelectedModel(v); + onChange?.(v); } }; - const handleCustomModelChange = (value: string) => { - // Using setTimeout to create a simple debounce effect - if (customModelTimeout.current) { - clearTimeout(customModelTimeout.current); - } - + const handleCustomModelChange = (v: string) => { + if (customModelTimeout.current) clearTimeout(customModelTimeout.current); customModelTimeout.current = setTimeout(() => { - setSelectedModel(value); - if (onChange) { - onChange(value); - } - }, 500); // 500ms delay after typing stops + setSelectedModel(v); + onChange?.(v); + }, 500); }; + const uniqueModels = useMemo( + () => Array.from(new Set(modelInfo.map((o) => o.model_group))), + [modelInfo], + ); + return (
{showLabel && ( - - {labelText} - +
+ {labelText} +
)} {showCustomModelInput && ( - handleCustomModelChange(e.target.value)} disabled={disabled} /> )} diff --git a/ui/litellm-dashboard/src/components/common_components/PassThroughRoutesSelector.tsx b/ui/litellm-dashboard/src/components/common_components/PassThroughRoutesSelector.tsx index d80e3790c36..b6f0073ea09 100644 --- a/ui/litellm-dashboard/src/components/common_components/PassThroughRoutesSelector.tsx +++ b/ui/litellm-dashboard/src/components/common_components/PassThroughRoutesSelector.tsx @@ -1,5 +1,13 @@ -import React, { useEffect, useState } from "react"; -import { Select } from "antd"; +import React, { useEffect, useMemo, useState } from "react"; +import { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { X } from "lucide-react"; +import { cn } from "@/lib/utils"; import { getPassThroughEndpointsCall } from "../networking"; interface PassThroughRoutesSelectorProps { @@ -26,8 +34,12 @@ const PassThroughRoutesSelector: React.FC = ({ disabled = false, teamId, }) => { - const [passThroughRoutes, setPassThroughRoutes] = useState>([]); + const [passThroughRoutes, setPassThroughRoutes] = useState< + Array<{ label: string; value: string }> + >([]); const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(""); useEffect(() => { const fetchPassThroughRoutes = async () => { @@ -37,24 +49,21 @@ const PassThroughRoutesSelector: React.FC = ({ try { const response = await getPassThroughEndpointsCall(accessToken, teamId); if (response.endpoints) { - const routes = response.endpoints.flatMap((endpoint: PassThroughEndpoint) => { - const path = endpoint.path; - const methods = endpoint.methods; - - // If methods are specified, create one entry per method - if (methods && methods.length > 0) { - return methods.map((method) => ({ - label: `${method} ${path}`, - value: path, // Keep value as path for backward compatibility - })); - } - - // If no methods specified, show just the path (all methods supported) - return [{ - label: path, - value: path, - }]; - }); + const routes = response.endpoints.flatMap( + (endpoint: PassThroughEndpoint) => { + const path = endpoint.path; + const methods = endpoint.methods; + + if (methods && methods.length > 0) { + return methods.map((method) => ({ + label: `${method} ${path}`, + value: path, + })); + } + + return [{ label: path, value: path }]; + }, + ); setPassThroughRoutes(routes); } } catch (error) { @@ -67,23 +76,111 @@ const PassThroughRoutesSelector: React.FC = ({ fetchPassThroughRoutes(); }, [accessToken, teamId]); + const selected = value ?? []; + const filteredOptions = useMemo( + () => + passThroughRoutes + .filter((o) => !selected.includes(o.value)) + .filter((o) => + query ? o.label.toLowerCase().includes(query.toLowerCase()) : true, + ), + [passThroughRoutes, selected, query], + ); + + const labelFor = (v: string) => + passThroughRoutes.find((o) => o.value === v)?.label ?? v; + + const addFreeform = () => { + const v = query.trim(); + if (!v) return; + if (!selected.includes(v)) onChange([...selected, v]); + setQuery(""); + }; + return ( - setQuery(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + addFreeform(); + } + }} + className="h-8 mb-2" + /> +
+ {filteredOptions.length === 0 ? ( +
+ {query ? ( + + Press Enter to add {query} + + ) : ( + "No matches" + )} +
+ ) : ( + filteredOptions.map((opt) => ( + + )) + )} +
+ + ); }; export default PassThroughRoutesSelector; - diff --git a/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.tsx b/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.tsx index 91ce01350e7..7823121f65a 100644 --- a/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.tsx +++ b/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.tsx @@ -1,9 +1,10 @@ import React from "react"; -import { Text } from "@tremor/react"; import LoggingSettings from "../team/LoggingSettings"; interface PremiumLoggingSettingsProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any onChange: (settings: any[]) => void; premiumUser?: boolean; disabledCallbacks?: string[]; @@ -21,22 +22,28 @@ export function PremiumLoggingSettings({ return (
-
+
✨ langfuse-logging
-
+
✨ datadog-logging
-
- - Setting Key/Team logging settings is a LiteLLM Enterprise feature. Global Logging Settings are available for - all free users. Get a trial key{" "} - +
);