diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index aa8402dba6..9e40e73d2b 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -143,6 +143,7 @@ "version": "0.54.0", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.54.0.tgz", "integrity": "sha512-xyoCtHJnt/qg5GG6IgK+UJEndz8h8ljzt/caKXmq3LfBF81nC/BW6E4x2rOWCZcvsLyVW+e8U5mtIr6UCE/kJw==", + "license": "MIT", "bin": { "anthropic-ai-sdk": "bin/cli" } diff --git a/ui/litellm-dashboard/src/components/cache_dashboard.tsx b/ui/litellm-dashboard/src/components/cache_dashboard.tsx index 37875cb7f9..2db37622d9 100644 --- a/ui/litellm-dashboard/src/components/cache_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/cache_dashboard.tsx @@ -22,6 +22,7 @@ import { Icon, Text, } from "@tremor/react"; +import UsageDatePicker from "./shared/usage_date_picker"; import { Button as Button2, @@ -161,12 +162,6 @@ const CacheDashboard: React.FC = ({ return; } - // the endTime put it to the last hour of the selected date - endTime.setHours(23, 59, 59, 999); - - // startTime put it to the first hour of the selected date - startTime.setHours(0, 0, 0, 0); - let new_cache_data = await adminGlobalCacheActivity( accessToken, formatDateWithoutTZ(startTime), @@ -349,14 +344,12 @@ const runCachingHealthCheck = async () => { - { setDateValue(value); updateCachingData(value.from, value.to); }} - selectPlaceholder="Select date range" /> diff --git a/ui/litellm-dashboard/src/components/entity_usage.tsx b/ui/litellm-dashboard/src/components/entity_usage.tsx index ade70abaab..a17bc66f7e 100644 --- a/ui/litellm-dashboard/src/components/entity_usage.tsx +++ b/ui/litellm-dashboard/src/components/entity_usage.tsx @@ -6,6 +6,7 @@ import { DonutChart, TabPanel, TabGroup, TabList, Tab, TabPanels } from "@tremor/react"; +import UsageDatePicker from "./shared/usage_date_picker"; import { Select } from 'antd'; import { ActivityMetrics, processActivityData } from './activity_metrics'; import { DailyData, KeyMetricWithMetadata, EntityMetricWithMetadata } from './usage/types'; @@ -94,8 +95,9 @@ const EntityUsage: React.FC = ({ const fetchSpendData = async () => { if (!accessToken || !dateValue.from || !dateValue.to) return; - const startTime = dateValue.from; - const endTime = dateValue.to; + // Create new Date objects to avoid mutating the original dates + const startTime = new Date(dateValue.from); + const endTime = new Date(dateValue.to); if (entityType === 'tag') { const data = await tagDailyActivityCall( @@ -287,9 +289,7 @@ const EntityUsage: React.FC = ({
- Select Time Range - diff --git a/ui/litellm-dashboard/src/components/model_dashboard.tsx b/ui/litellm-dashboard/src/components/model_dashboard.tsx index 61f9353bb0..4ae965484a 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard.tsx @@ -43,6 +43,7 @@ import { MultiSelectItem, DateRangePickerValue, } from "@tremor/react"; +import UsageDatePicker from "./shared/usage_date_picker"; import { modelInfoCall, userGetRequesedtModelsCall, @@ -306,15 +307,6 @@ const ModelDashboard: React.FC = ({ selected_customer = null; } - // make startTime and endTime to last hour of the day - startTime.setHours(0); - startTime.setMinutes(0); - startTime.setSeconds(0); - - endTime.setHours(23); - endTime.setMinutes(59); - endTime.setSeconds(59); - try { const modelMetricsResponse = await modelMetricsCall( accessToken, @@ -1387,9 +1379,7 @@ const ModelDashboard: React.FC = ({ - Select Time Range - { @@ -1398,7 +1388,7 @@ const ModelDashboard: React.FC = ({ selectedModelGroup, value.from, value.to - ); // Call updateModelMetrics with the new date range + ); }} /> diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 2ec8642458..085fe5cfe9 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -19,7 +19,7 @@ import { } from "./email_events/types"; const isLocal = process.env.NODE_ENV === "development"; -export const defaultProxyBaseUrl = isLocal ? "http://localhost:4000" : null; +export const defaultProxyBaseUrl = isLocal ? "http://localhost:43845" : null; const defaultServerRootPath = "/"; export let serverRootPath = defaultServerRootPath; export let proxyBaseUrl = defaultProxyBaseUrl; @@ -1333,8 +1333,15 @@ export const userDailyActivityCall = async ( ? `${proxyBaseUrl}/user/daily/activity` : `/user/daily/activity`; const queryParams = new URLSearchParams(); - queryParams.append("start_date", startTime.toISOString()); - queryParams.append("end_date", endTime.toISOString()); + // Format dates as YYYY-MM-DD for the API + const formatDate = (date: Date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + }; + queryParams.append("start_date", formatDate(startTime)); + queryParams.append("end_date", formatDate(endTime)); queryParams.append("page_size", "1000"); queryParams.append("page", page.toString()); const queryString = queryParams.toString(); @@ -1379,8 +1386,15 @@ export const tagDailyActivityCall = async ( ? `${proxyBaseUrl}/tag/daily/activity` : `/tag/daily/activity`; const queryParams = new URLSearchParams(); - queryParams.append("start_date", startTime.toISOString()); - queryParams.append("end_date", endTime.toISOString()); + // Format dates as YYYY-MM-DD for the API + const formatDate = (date: Date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + }; + queryParams.append("start_date", formatDate(startTime)); + queryParams.append("end_date", formatDate(endTime)); queryParams.append("page_size", "1000"); queryParams.append("page", page.toString()); if (tags) { @@ -1428,8 +1442,15 @@ export const teamDailyActivityCall = async ( ? `${proxyBaseUrl}/team/daily/activity` : `/team/daily/activity`; const queryParams = new URLSearchParams(); - queryParams.append("start_date", startTime.toISOString()); - queryParams.append("end_date", endTime.toISOString()); + // Format dates as YYYY-MM-DD for the API + const formatDate = (date: Date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + }; + queryParams.append("start_date", formatDate(startTime)); + queryParams.append("end_date", formatDate(endTime)); queryParams.append("page_size", "1000"); queryParams.append("page", page.toString()); if (teamIds) { diff --git a/ui/litellm-dashboard/src/components/new_usage.tsx b/ui/litellm-dashboard/src/components/new_usage.tsx index 1bd395a254..3434f08c45 100644 --- a/ui/litellm-dashboard/src/components/new_usage.tsx +++ b/ui/litellm-dashboard/src/components/new_usage.tsx @@ -15,6 +15,7 @@ import { TableHeaderCell, TableBody, TableCell, Subtitle, DateRangePicker, DateRangePickerValue } from "@tremor/react"; +import UsageDatePicker from "./shared/usage_date_picker"; import { AreaChart } from "@tremor/react"; import { userDailyActivityCall, tagListCall } from "./networking"; @@ -211,8 +212,9 @@ const NewUsagePage: React.FC = ({ const fetchUserSpendData = async () => { if (!accessToken || !dateValue.from || !dateValue.to) return; - const startTime = dateValue.from; - const endTime = dateValue.to; + // Create new Date objects to avoid mutating the original dates + const startTime = new Date(dateValue.from); + const endTime = new Date(dateValue.to); try { // Get first page @@ -281,9 +283,7 @@ const NewUsagePage: React.FC = ({ - Select Time Range - { setDateValue(value); diff --git a/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx new file mode 100644 index 0000000000..2d9bba75d5 --- /dev/null +++ b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx @@ -0,0 +1,121 @@ +import React from "react"; +import { DateRangePicker, DateRangePickerValue, Text } from "@tremor/react"; + +interface UsageDatePickerProps { + value: DateRangePickerValue; + onValueChange: (value: DateRangePickerValue) => void; + label?: string; + className?: string; + showTimeRange?: boolean; +} + +/** + * Reusable date picker component for usage dashboards. + * Handles proper time boundaries for date ranges, especially "Today" selections. + * Addresses timezone issues by ensuring UTC time boundaries are set correctly. + */ +const UsageDatePicker: React.FC = ({ + value, + onValueChange, + label = "Select Time Range", + className = "", + showTimeRange = true +}) => { + const handleDateChange = (newValue: DateRangePickerValue) => { + // Handle the case where "Today" or same-day selection is made + if (newValue.from) { + const adjustedValue = { ...newValue }; + + // Create new Date objects to avoid mutating the original dates + const adjustedStartTime = new Date(newValue.from); + let adjustedEndTime: Date; + + if (newValue.to) { + adjustedEndTime = new Date(newValue.to); + } else { + // If no end date is provided (like "Today" from dropdown), use the same date + adjustedEndTime = new Date(newValue.from); + } + + // Check if it's the same day (Today selection or single day selection) + const isSameDay = + adjustedStartTime.toDateString() === adjustedEndTime.toDateString(); + + if (isSameDay) { + // For same-day selections, set proper time boundaries + // Use local timezone boundaries that will be converted to UTC properly + adjustedStartTime.setHours(0, 0, 0, 0); // Start of day in local time + adjustedEndTime.setHours(23, 59, 59, 999); // End of day in local time + } else { + // For multi-day ranges, set start to beginning of first day and end to end of last day + adjustedStartTime.setHours(0, 0, 0, 0); + adjustedEndTime.setHours(23, 59, 59, 999); + } + + adjustedValue.from = adjustedStartTime; + adjustedValue.to = adjustedEndTime; + + onValueChange(adjustedValue); + } else { + // If no from date, pass through as-is + onValueChange(newValue); + } + }; + + const formatTimeRange = (from: Date | undefined, to: Date | undefined) => { + if (!from || !to) return ""; + + const formatDateTime = (date: Date) => { + return date.toLocaleString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: true, + timeZoneName: 'short' + }); + }; + + const isSameDay = from.toDateString() === to.toDateString(); + + if (isSameDay) { + const dateStr = from.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }); + const startTime = from.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + hour12: true + }); + const endTime = to.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + hour12: true, + timeZoneName: 'short' + }); + return `${dateStr}: ${startTime} - ${endTime}`; + } else { + return `${formatDateTime(from)} - ${formatDateTime(to)}`; + } + }; + + return ( +
+ {label && {label}} + + {showTimeRange && value.from && value.to && ( + + {formatTimeRange(value.from, value.to)} + + )} +
+ ); +}; + +export default UsageDatePicker; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 6779ba7f94..74a411fcbc 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -4,6 +4,7 @@ import React, { useState, useEffect } from "react"; import ViewUserSpend from "./view_user_spend"; import { ProxySettings } from "./user_dashboard"; +import UsageDatePicker from "./shared/usage_date_picker"; import { Grid, Col, Text, LineChart, TabPanel, TabPanels, @@ -213,12 +214,6 @@ const UsagePage: React.FC = ({ return; } - // the endTime put it to the last hour of the selected date - endTime.setHours(23, 59, 59, 999); - - // startTime put it to the first hour of the selected date - startTime.setHours(0, 0, 0, 0); - console.log("uiSelectedKey", uiSelectedKey); let newTopUserData = await adminTopEndUsersCall( @@ -245,12 +240,6 @@ const UsagePage: React.FC = ({ return; // Don't run expensive DB queries - return out when SpendLogs has more than 1M rows } - // the endTime put it to the last hour of the selected date - endTime.setHours(23, 59, 59, 999); - - // startTime put it to the first hour of the selected date - startTime.setHours(0, 0, 0, 0); - let top_tags = await tagsSpendLogsCall( accessToken, startTime.toISOString(), @@ -835,14 +824,11 @@ const UsagePage: React.FC = ({

Customers of your LLM API calls. Tracked when a `user` param is passed in your LLM calls docs here

- Select Time Range - - { setDateValue(value); - updateEndUserData(value.from, value.to, null); // Call updateModelMetrics with the new date range + updateEndUserData(value.from, value.to, null); }} /> @@ -916,13 +902,12 @@ const UsagePage: React.FC = ({ - { setDateValue(value); - updateTagSpendData(value.from, value.to); // Call updateModelMetrics with the new date range + updateTagSpendData(value.from, value.to); }} />