diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json
index a629bf4cc4a..0b5b1e1f97a 100644
--- a/ui/litellm-dashboard/.eslintrc.json
+++ b/ui/litellm-dashboard/.eslintrc.json
@@ -33,7 +33,8 @@
"src/components/DebugWarningBanner.tsx",
"src/components/SCIM.tsx",
"src/components/logging_settings_view.tsx",
- "src/components/model_group_alias_settings.tsx"
+ "src/components/model_group_alias_settings.tsx",
+ "src/components/cache_dashboard.tsx"
],
"rules": {
"litellm-ui/no-banned-ui-imports": "off",
diff --git a/ui/litellm-dashboard/src/components/cache_dashboard.tsx b/ui/litellm-dashboard/src/components/cache_dashboard.tsx
index 874cb43276e..d7d19e336f6 100644
--- a/ui/litellm-dashboard/src/components/cache_dashboard.tsx
+++ b/ui/litellm-dashboard/src/components/cache_dashboard.tsx
@@ -1,28 +1,28 @@
-import {
- BarChart,
- Card,
- Col,
- DateRangePickerValue,
- Grid,
- Icon,
- MultiSelect,
- MultiSelectItem,
- Subtitle,
- Tab,
- TabGroup,
- TabList,
- TabPanel,
- TabPanels,
- Text,
-} from "@tremor/react";
+import { BarChart } from "@tremor/react";
+import type { DateRangePickerValue } from "@tremor/react";
import React, { useEffect, useState } from "react";
+import { Card } from "@/components/ui/card";
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from "@/components/ui/tabs";
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Badge } from "@/components/ui/badge";
+import { RefreshCw, X } from "lucide-react";
import NotificationsManager from "./molecules/notifications_manager";
import UsageDatePicker from "./shared/usage_date_picker";
-import { RefreshIcon } from "@heroicons/react/outline";
import { adminGlobalCacheActivity, cachingHealthCheckCall } from "./networking";
-// Import the new component
import { CacheHealthTab } from "./cache_health";
import CacheSettings from "./cache_settings";
@@ -37,7 +37,6 @@ function valueFormatterNumbers(number: number) {
notation: "compact",
compactDisplay: "short",
});
-
return formatter.format(number);
}
@@ -57,8 +56,6 @@ interface cacheDataItem {
total_rows: number;
generated_completion_tokens: number;
call_type: string;
-
- // Add other properties as needed
}
interface uiData {
@@ -69,34 +66,72 @@ interface uiData {
"Generated Completion Tokens": number;
}
-interface CacheHealthResponse {
- status?: string;
- cache_type?: string;
- ping_response?: boolean;
- set_cache_response?: string;
- litellm_cache_params?: string;
- error?: {
- message: string;
- type: string;
- param: string;
- code: string;
- };
+/**
+ * Multi-select chips wrapper around shadcn Select. Used for the API-key /
+ * model filter rows.
+ */
+function ChipMultiSelect({
+ placeholder,
+ options,
+ value,
+ onChange,
+}: {
+ placeholder: string;
+ options: string[];
+ value: string[];
+ onChange: (next: string[]) => void;
+}) {
+ const remaining = options.filter((o) => !value.includes(o));
+ return (
+
+
+ {value.length > 0 && (
+
+ {value.map((v) => (
+
+ {v}
+
+
+ ))}
+
+ )}
+
+ );
}
-// Helper function to deep-parse a JSON string if possible
-const deepParse = (input: any) => {
- let parsed = input;
- if (typeof parsed === "string") {
- try {
- parsed = JSON.parse(parsed);
- } catch {
- return parsed;
- }
- }
- return parsed;
-};
-
-const CacheDashboard: React.FC = ({ accessToken, token, userRole, userID, premiumUser }) => {
+const CacheDashboard: React.FC = ({
+ accessToken,
+ userRole,
+ userID,
+}) => {
const [filteredData, setFilteredData] = useState([]);
const [selectedApiKeys, setSelectedApiKeys] = useState([]);
const [selectedModels, setSelectedModels] = useState([]);
@@ -111,12 +146,11 @@ const CacheDashboard: React.FC = ({ accessToken, token, userRole
});
const [lastRefreshed, setLastRefreshed] = useState("");
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const [healthCheckResponse, setHealthCheckResponse] = useState("");
useEffect(() => {
- if (!accessToken || !dateValue) {
- return;
- }
+ if (!accessToken || !dateValue) return;
const fetchData = async () => {
const response = await adminGlobalCacheActivity(
accessToken,
@@ -126,87 +160,64 @@ const CacheDashboard: React.FC = ({ accessToken, token, userRole
setData(response);
};
fetchData();
-
- const currentDate = new Date();
- setLastRefreshed(currentDate.toLocaleString());
+ setLastRefreshed(new Date().toLocaleString());
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [accessToken]);
- const uniqueApiKeys = Array.from(new Set(data.map((item) => item?.api_key ?? "")));
- const uniqueModels = Array.from(new Set(data.map((item) => item?.model ?? "")));
- const uniqueCallTypes = Array.from(new Set(data.map((item) => item?.call_type ?? "")));
+ const uniqueApiKeys = Array.from(
+ new Set(data.map((item) => item?.api_key ?? "")),
+ );
+ const uniqueModels = Array.from(
+ new Set(data.map((item) => item?.model ?? "")),
+ );
- const updateCachingData = async (startTime: Date | undefined, endTime: Date | undefined) => {
- if (!startTime || !endTime || !accessToken) {
- return;
- }
-
- let new_cache_data = await adminGlobalCacheActivity(
+ const updateCachingData = async (
+ startTime: Date | undefined,
+ endTime: Date | undefined,
+ ) => {
+ if (!startTime || !endTime || !accessToken) return;
+ const new_cache_data = await adminGlobalCacheActivity(
accessToken,
formatDateWithoutTZ(startTime),
formatDateWithoutTZ(endTime),
);
-
setData(new_cache_data);
};
useEffect(() => {
- console.log("DATA IN CACHE DASHBOARD", data);
let newData: cacheDataItem[] = data;
if (selectedApiKeys.length > 0) {
- newData = newData.filter((item) => selectedApiKeys.includes(item.api_key));
+ newData = newData.filter((item) =>
+ selectedApiKeys.includes(item.api_key),
+ );
}
-
if (selectedModels.length > 0) {
newData = newData.filter((item) => selectedModels.includes(item.model));
}
- /*
- Data looks like this
- [{"api_key":"sk-test-mock-key-001","call_type":"acompletion","model":"llama3-8b-8192","total_rows":13,"cache_hit_true_rows":0},
- {"api_key":"sk-test-mock-key-002","call_type":"None","model":"chatgpt-v-2","total_rows":1,"cache_hit_true_rows":0},
- {"api_key":"sk-test-mock-key-123","call_type":"acompletion","model":"gpt-3.5-turbo","total_rows":19,"cache_hit_true_rows":0},
- {"api_key":"sk-test-mock-key-123","call_type":"aimage_generation","model":"","total_rows":3,"cache_hit_true_rows":0},
- {"api_key":"sk-test-mock-key-003","call_type":"None","model":"chatgpt-v-2","total_rows":1,"cache_hit_true_rows":0},
- {"api_key":"sk-test-mock-key-004","call_type":"","model":"chatgpt-v-2","total_rows":1,"cache_hit_true_rows":0},
- {"api_key":"sk-test-mock-key-005","call_type":"","model":"chatgpt-v-2","total_rows":1,"cache_hit_true_rows":0},
- */
-
- // What data we need for bar chat
- // ui_data = [
- // {
- // name: "Call Type",
- // Cache hit: 20,
- // LLM API requests: 10,
- // }
- // ]
-
- console.log("before processed data in cache dashboard", newData);
-
let llm_api_requests = 0;
let cache_hits = 0;
let cached_tokens = 0;
const processedData = newData.reduce((acc: uiData[], item) => {
- console.log("Processing item:", item);
-
- if (!item.call_type) {
- console.log("Item has no call_type:", item);
- item.call_type = "Unknown";
- }
-
- llm_api_requests += (item.total_rows || 0) - (item.cache_hit_true_rows || 0);
+ if (!item.call_type) item.call_type = "Unknown";
+ llm_api_requests +=
+ (item.total_rows || 0) - (item.cache_hit_true_rows || 0);
cache_hits += item.cache_hit_true_rows || 0;
cached_tokens += item.cached_completion_tokens || 0;
-
const existingItem = acc.find((i) => i.name === item.call_type);
if (existingItem) {
- existingItem["LLM API requests"] += (item.total_rows || 0) - (item.cache_hit_true_rows || 0);
+ existingItem["LLM API requests"] +=
+ (item.total_rows || 0) - (item.cache_hit_true_rows || 0);
existingItem["Cache hit"] += item.cache_hit_true_rows || 0;
- existingItem["Cached Completion Tokens"] += item.cached_completion_tokens || 0;
- existingItem["Generated Completion Tokens"] += item.generated_completion_tokens || 0;
+ existingItem["Cached Completion Tokens"] +=
+ item.cached_completion_tokens || 0;
+ existingItem["Generated Completion Tokens"] +=
+ item.generated_completion_tokens || 0;
} else {
acc.push({
name: item.call_type,
- "LLM API requests": (item.total_rows || 0) - (item.cache_hit_true_rows || 0),
+ "LLM API requests":
+ (item.total_rows || 0) - (item.cache_hit_true_rows || 0),
"Cache hit": item.cache_hit_true_rows || 0,
"Cached Completion Tokens": item.cached_completion_tokens || 0,
"Generated Completion Tokens": item.generated_completion_tokens || 0,
@@ -215,48 +226,38 @@ const CacheDashboard: React.FC = ({ accessToken, token, userRole
return acc;
}, []);
- // set header cache statistics
setCachedResponses(valueFormatterNumbers(cache_hits));
setCachedTokens(valueFormatterNumbers(cached_tokens));
- let allRequests = cache_hits + llm_api_requests;
- if (allRequests > 0) {
- let cache_hit_ratio = ((cache_hits / allRequests) * 100).toFixed(2);
- setCacheHitRatio(cache_hit_ratio);
- } else {
- setCacheHitRatio("0");
- }
-
+ const allRequests = cache_hits + llm_api_requests;
+ setCacheHitRatio(
+ allRequests > 0 ? ((cache_hits / allRequests) * 100).toFixed(2) : "0",
+ );
setFilteredData(processedData);
-
- console.log("PROCESSED DATA IN CACHE DASHBOARD", processedData);
}, [selectedApiKeys, selectedModels, dateValue, data]);
const handleRefreshClick = () => {
- // Update the 'lastRefreshed' state to the current date and time
- const currentDate = new Date();
- setLastRefreshed(currentDate.toLocaleString());
+ setLastRefreshed(new Date().toLocaleString());
};
const runCachingHealthCheck = async () => {
try {
NotificationsManager.info("Running cache health check...");
setHealthCheckResponse("");
- const response = await cachingHealthCheckCall(accessToken !== null ? accessToken : "");
- console.log("CACHING HEALTH CHECK RESPONSE", response);
+ const response = await cachingHealthCheckCall(
+ accessToken !== null ? accessToken : "",
+ );
setHealthCheckResponse(response);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
console.error("Error running health check:", error);
- let errorData;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let errorData: any;
if (error && error.message) {
try {
- // Parse the error message which may contain a nested error layer.
let parsedData = JSON.parse(error.message);
- // If the parsed object is wrapped (e.g. { error: { ... } }), unwrap it.
- if (parsedData.error) {
- parsedData = parsedData.error;
- }
+ if (parsedData.error) parsedData = parsedData.error;
errorData = parsedData;
- } catch (e) {
+ } catch {
errorData = { message: error.message };
}
} else {
@@ -267,133 +268,144 @@ const CacheDashboard: React.FC = ({ accessToken, token, userRole
};
return (
-
-
-
- Cache Analytics
- Cache Health
- Cache Settings
-
+
+
+
+ Cache Analytics
+ Cache Health
+ Cache Settings
+
- {lastRefreshed && Last Refreshed: {lastRefreshed}}
-
+ Last Refreshed: {lastRefreshed}
+
+ )}
+
+ aria-label="Refresh"
+ >
+
+
-
-
-
-
-
-
-
- {uniqueApiKeys.map((key) => (
-
- {key}
-
- ))}
-
-
-
-
- {uniqueModels.map((model) => (
-
- {model}
-
- ))}
-
-
-
- {
- setDateValue(value);
- updateCachingData(value.from, value.to);
- }}
- />
-
-
+
-
-
-
- Cache Hit Ratio
-
-
-
-
-
- Cache Hits
-
-
-
- {cachedResponses}
-
-
-
-
-
-
- Cached Tokens
-
-
-
+
+
+
+
+
+
+
+
+
+ {
+ setDateValue(value);
+ updateCachingData(value.from, value.to);
+ }}
+ />
+
+
- Cache Hits vs API Requests
-
+
+
+
+ Cache Hit Ratio
+
+
+
+
+
+ Cache Hits
+
+
+
+ {cachedResponses}
+
+
+
+
+
+ Cached Tokens
+
+
+
+
- Cached Completion Tokens vs Generated Completion Tokens
-
-
-
-
-
+ Cache Hits vs API Requests
+
+
-
-
-
-
-
-
+
+
+ Cached Completion Tokens vs Generated Completion Tokens
+
+
+
+
+
+
+
+
+
+
+
);
};