mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for shared date pickers and created key display
This commit is contained in:
parent
f737f979d7
commit
a851b90d4b
5 changed files with 148 additions and 102 deletions
|
|
@ -1,34 +1,30 @@
|
|||
import React, { useState } from "react";
|
||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||
import { Button } from "antd";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import MessageManager from "@/components/molecules/message_manager";
|
||||
|
||||
interface CreatedKeyDisplayProps {
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared component for displaying a newly-created virtual key.
|
||||
* Used on the Virtual Keys page and in the Add Agent wizard.
|
||||
*/
|
||||
const CreatedKeyDisplay: React.FC<CreatedKeyDisplayProps> = ({ apiKey }) => {
|
||||
const { t } = useTranslation();
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
setCopied(true);
|
||||
MessageManager.success("Key copied to clipboard");
|
||||
MessageManager.success(t("shared.createdKeyDisplay.copiedToClipboard"));
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="mb-2">
|
||||
Please save this secret key somewhere safe and accessible. For security reasons,{" "}
|
||||
<b>you will not be able to view it again</b> through your LiteLLM account. If you lose this secret key, you will
|
||||
need to generate a new one.
|
||||
<Trans i18nKey="shared.createdKeyDisplay.saveWarning" components={{ b: <b /> }} />
|
||||
</p>
|
||||
|
||||
<p className="text-sm text-gray-600 mt-3 mb-1">Virtual Key:</p>
|
||||
<p className="text-sm text-gray-600 mt-3 mb-1">{t("shared.createdKeyDisplay.virtualKeyLabel")}</p>
|
||||
<div
|
||||
style={{
|
||||
background: "#f8f8f8",
|
||||
|
|
@ -42,7 +38,7 @@ const CreatedKeyDisplay: React.FC<CreatedKeyDisplayProps> = ({ apiKey }) => {
|
|||
|
||||
<CopyToClipboard text={apiKey} onCopy={handleCopy}>
|
||||
<Button type="primary" style={{ marginTop: 12 }}>
|
||||
{copied ? "Copied!" : "Copy Virtual Key"}
|
||||
{copied ? t("common.copied") : t("shared.createdKeyDisplay.copyButton")}
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { CalendarOutlined, ClockCircleOutlined } from "@ant-design/icons";
|
||||
import { Button, DateRangePickerValue, Text } from "@tremor/react";
|
||||
import moment from "moment";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TFunction } from "i18next";
|
||||
|
||||
interface AdvancedDatePickerProps {
|
||||
value: DateRangePickerValue;
|
||||
|
|
@ -17,42 +19,42 @@ interface RelativeTimeOption {
|
|||
getValue: () => { from: Date; to: Date };
|
||||
}
|
||||
|
||||
const relativeTimeOptions: RelativeTimeOption[] = [
|
||||
const getRelativeTimeOptions = (t: TFunction): RelativeTimeOption[] => [
|
||||
{
|
||||
label: "Today",
|
||||
shortLabel: "today",
|
||||
label: t("shared.advancedDatePicker.today"),
|
||||
shortLabel: t("shared.advancedDatePicker.todayShort"),
|
||||
getValue: () => ({
|
||||
from: moment().startOf("day").toDate(),
|
||||
to: moment().endOf("day").toDate(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: "Last 7 days",
|
||||
shortLabel: "7d",
|
||||
label: t("shared.advancedDatePicker.last7Days"),
|
||||
shortLabel: t("shared.advancedDatePicker.last7DaysShort"),
|
||||
getValue: () => ({
|
||||
from: moment().subtract(7, "days").startOf("day").toDate(),
|
||||
to: moment().endOf("day").toDate(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: "Last 30 days",
|
||||
shortLabel: "30d",
|
||||
label: t("shared.advancedDatePicker.last30Days"),
|
||||
shortLabel: t("shared.advancedDatePicker.last30DaysShort"),
|
||||
getValue: () => ({
|
||||
from: moment().subtract(30, "days").startOf("day").toDate(),
|
||||
to: moment().endOf("day").toDate(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: "Month to date",
|
||||
shortLabel: "MTD",
|
||||
label: t("shared.advancedDatePicker.monthToDate"),
|
||||
shortLabel: t("shared.advancedDatePicker.monthToDateShort"),
|
||||
getValue: () => ({
|
||||
from: moment().startOf("month").toDate(),
|
||||
to: moment().endOf("day").toDate(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: "Year to date",
|
||||
shortLabel: "YTD",
|
||||
label: t("shared.advancedDatePicker.yearToDate"),
|
||||
shortLabel: t("shared.advancedDatePicker.yearToDateShort"),
|
||||
getValue: () => ({
|
||||
from: moment().startOf("year").toDate(),
|
||||
to: moment().endOf("day").toDate(),
|
||||
|
|
@ -60,51 +62,50 @@ const relativeTimeOptions: RelativeTimeOption[] = [
|
|||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Advanced Date Range Picker with dropdown, relative times, and custom inputs
|
||||
*/
|
||||
const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
||||
value,
|
||||
onValueChange,
|
||||
label = "Select Time Range",
|
||||
label,
|
||||
showTimeRange = true,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const relativeTimeOptions = useMemo(() => getRelativeTimeOptions(t), [t]);
|
||||
const resolvedLabel = label !== undefined ? label : t("shared.advancedDatePicker.selectTimeRange");
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [tempValue, setTempValue] = useState<DateRangePickerValue>(value);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
|
||||
// Custom date inputs only - removed time inputs
|
||||
const [startDate, setStartDate] = useState("");
|
||||
const [endDate, setEndDate] = useState("");
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Function to check if current value matches a relative time option
|
||||
const getMatchingOption = useCallback((currentValue: DateRangePickerValue): string | null => {
|
||||
if (!currentValue.from || !currentValue.to) return null;
|
||||
const getMatchingOption = useCallback(
|
||||
(currentValue: DateRangePickerValue): string | null => {
|
||||
if (!currentValue.from || !currentValue.to) return null;
|
||||
|
||||
for (const option of relativeTimeOptions) {
|
||||
const optionRange = option.getValue();
|
||||
for (const option of relativeTimeOptions) {
|
||||
const optionRange = option.getValue();
|
||||
|
||||
// Compare dates with some tolerance (to account for time differences)
|
||||
const fromMatches = moment(currentValue.from).isSame(moment(optionRange.from), "day");
|
||||
const toMatches = moment(currentValue.to).isSame(moment(optionRange.to), "day");
|
||||
const fromMatches = moment(currentValue.from).isSame(moment(optionRange.from), "day");
|
||||
const toMatches = moment(currentValue.to).isSame(moment(optionRange.to), "day");
|
||||
|
||||
if (fromMatches && toMatches) {
|
||||
return option.shortLabel;
|
||||
if (fromMatches && toMatches) {
|
||||
return option.shortLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}, []);
|
||||
return null;
|
||||
},
|
||||
[relativeTimeOptions],
|
||||
);
|
||||
|
||||
// Update selected option when value changes
|
||||
useEffect(() => {
|
||||
const matchingOption = getMatchingOption(value);
|
||||
setSelectedOption(matchingOption);
|
||||
}, [value, getMatchingOption]);
|
||||
|
||||
// Validation logic - simplified for dates only
|
||||
const validateDateRange = useCallback(() => {
|
||||
if (!startDate || !endDate) {
|
||||
return { isValid: true, error: "" };
|
||||
|
|
@ -114,19 +115,18 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
const end = moment(endDate, "YYYY-MM-DD");
|
||||
|
||||
if (!start.isValid() || !end.isValid()) {
|
||||
return { isValid: false, error: "Invalid date format" };
|
||||
return { isValid: false, error: t("shared.advancedDatePicker.invalidDateFormat") };
|
||||
}
|
||||
|
||||
if (end.isBefore(start)) {
|
||||
return { isValid: false, error: "End date cannot be before start date" };
|
||||
return { isValid: false, error: t("shared.advancedDatePicker.endBeforeStart") };
|
||||
}
|
||||
|
||||
return { isValid: true, error: "" };
|
||||
}, [startDate, endDate]);
|
||||
}, [startDate, endDate, t]);
|
||||
|
||||
const validation = validateDateRange();
|
||||
|
||||
// Initialize form inputs when component mounts or value changes
|
||||
useEffect(() => {
|
||||
if (value.from) {
|
||||
setStartDate(moment(value.from).format("YYYY-MM-DD"));
|
||||
|
|
@ -137,7 +137,6 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
setTempValue(value);
|
||||
}, [value]);
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
|
|
@ -154,17 +153,19 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const formatDisplayRange = useCallback((from: Date | undefined, to: Date | undefined) => {
|
||||
if (!from || !to) return "Select date range";
|
||||
const formatDisplayRange = useCallback(
|
||||
(from: Date | undefined, to: Date | undefined) => {
|
||||
if (!from || !to) return t("shared.advancedDatePicker.selectDateRange");
|
||||
|
||||
const formatDateTime = (date: Date) => {
|
||||
return moment(date).format("D MMM, HH:mm");
|
||||
};
|
||||
const formatDateTime = (date: Date) => {
|
||||
return moment(date).format("D MMM, HH:mm");
|
||||
};
|
||||
|
||||
return `${formatDateTime(from)} - ${formatDateTime(to)}`;
|
||||
}, []);
|
||||
return `${formatDateTime(from)} - ${formatDateTime(to)}`;
|
||||
},
|
||||
[t],
|
||||
);
|
||||
|
||||
// CRITICAL: Apply the same date adjustment logic as the original component
|
||||
const adjustDateRange = useCallback((newValue: DateRangePickerValue): DateRangePickerValue => {
|
||||
if (!newValue.from) return newValue;
|
||||
|
||||
|
|
@ -178,15 +179,8 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
adjustedEndTime = new Date(newValue.from);
|
||||
}
|
||||
|
||||
const isSameDay = adjustedStartTime.toDateString() === adjustedEndTime.toDateString();
|
||||
|
||||
if (isSameDay) {
|
||||
adjustedStartTime.setHours(0, 0, 0, 0);
|
||||
adjustedEndTime.setHours(23, 59, 59, 999);
|
||||
} else {
|
||||
adjustedStartTime.setHours(0, 0, 0, 0);
|
||||
adjustedEndTime.setHours(23, 59, 59, 999);
|
||||
}
|
||||
adjustedStartTime.setHours(0, 0, 0, 0);
|
||||
adjustedEndTime.setHours(23, 59, 59, 999);
|
||||
|
||||
adjustedValue.from = adjustedStartTime;
|
||||
adjustedValue.to = adjustedEndTime;
|
||||
|
|
@ -198,21 +192,16 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
const { from, to } = option.getValue();
|
||||
const newValue = { from, to };
|
||||
|
||||
// Update local state to reflect the selection (don't apply immediately)
|
||||
setTempValue(newValue);
|
||||
setSelectedOption(option.shortLabel);
|
||||
|
||||
// Update the form inputs to reflect the selection
|
||||
setStartDate(moment(from).format("YYYY-MM-DD"));
|
||||
setEndDate(moment(to).format("YYYY-MM-DD"));
|
||||
|
||||
// Don't close the dropdown - let user click Apply to confirm
|
||||
};
|
||||
|
||||
const updateTempValueFromInputs = useCallback(() => {
|
||||
try {
|
||||
if (startDate && endDate && validation.isValid) {
|
||||
// Set times to start and end of day
|
||||
const from = moment(startDate, "YYYY-MM-DD").startOf("day");
|
||||
const to = moment(endDate, "YYYY-MM-DD").endOf("day");
|
||||
|
||||
|
|
@ -220,7 +209,6 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
const newValue = { from: from.toDate(), to: to.toDate() };
|
||||
setTempValue(newValue);
|
||||
|
||||
// Check if this matches any preset option
|
||||
const matchingOption = getMatchingOption(newValue);
|
||||
setSelectedOption(matchingOption);
|
||||
}
|
||||
|
|
@ -230,17 +218,14 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
}
|
||||
}, [startDate, endDate, validation.isValid, getMatchingOption]);
|
||||
|
||||
// Update tempValue when inputs change
|
||||
useEffect(() => {
|
||||
updateTempValueFromInputs();
|
||||
}, [updateTempValueFromInputs]);
|
||||
|
||||
const handleApply = () => {
|
||||
if (tempValue.from && tempValue.to && validation.isValid) {
|
||||
// First call with immediate value for UI responsiveness
|
||||
onValueChange(tempValue);
|
||||
|
||||
// Then do the same background adjustment logic as the original component
|
||||
requestIdleCallback(
|
||||
() => {
|
||||
const adjustedValue = adjustDateRange(tempValue);
|
||||
|
|
@ -254,10 +239,8 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
// Reset to original value
|
||||
setTempValue(value);
|
||||
|
||||
// Reset form inputs
|
||||
if (value.from) {
|
||||
setStartDate(moment(value.from).format("YYYY-MM-DD"));
|
||||
}
|
||||
|
|
@ -265,7 +248,6 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
setEndDate(moment(value.to).format("YYYY-MM-DD"));
|
||||
}
|
||||
|
||||
// Reset selected option
|
||||
const matchingOption = getMatchingOption(value);
|
||||
setSelectedOption(matchingOption);
|
||||
|
||||
|
|
@ -274,9 +256,8 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
{label && <Text className="text-sm font-medium text-gray-700 whitespace-nowrap">{label}</Text>}
|
||||
{resolvedLabel && <Text className="text-sm font-medium text-gray-700 whitespace-nowrap">{resolvedLabel}</Text>}
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
{/* Main input display */}
|
||||
<div
|
||||
className="w-[300px] px-3 py-2 text-sm border border-gray-300 rounded-md bg-white cursor-pointer hover:border-gray-400 focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
|
|
@ -297,21 +278,21 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown panel */}
|
||||
{isOpen && (
|
||||
<div className="absolute top-full right-0 z-[9999] min-w-[600px] mt-1 bg-white border border-gray-200 rounded-lg shadow-xl">
|
||||
<div className="flex">
|
||||
{/* Left side - Relative time options */}
|
||||
<div className="w-1/2 border-r border-gray-200">
|
||||
<div className="p-3 border-b border-gray-200">
|
||||
<span className="text-sm font-semibold text-gray-900">Relative time</span>
|
||||
<span className="text-sm font-semibold text-gray-900">
|
||||
{t("shared.advancedDatePicker.relativeTime")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-[350px] overflow-y-auto">
|
||||
{relativeTimeOptions.map((option) => {
|
||||
const isSelected = selectedOption === option.shortLabel;
|
||||
return (
|
||||
<div
|
||||
key={option.label}
|
||||
key={option.shortLabel}
|
||||
className={`flex items-center justify-between px-5 py-4 cursor-pointer border-b border-gray-100 transition-colors ${
|
||||
isSelected ? "bg-blue-50 hover:bg-blue-100 border-blue-200" : "hover:bg-gray-50"
|
||||
}`}
|
||||
|
|
@ -333,19 +314,21 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Custom date selection */}
|
||||
<div className="w-1/2 relative">
|
||||
<div className="p-3.5 border-b border-gray-200">
|
||||
<div className="flex items-center gap-2">
|
||||
<CalendarOutlined className="text-gray-600" />
|
||||
<span className="text-sm font-semibold text-gray-900">Start and end dates</span>
|
||||
<span className="text-sm font-semibold text-gray-900">
|
||||
{t("shared.advancedDatePicker.startAndEndDates")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6 pb-20">
|
||||
{/* Start date */}
|
||||
<div>
|
||||
<label className="text-sm text-gray-700 mb-1 block">Start date</label>
|
||||
<label className="text-sm text-gray-700 mb-1 block">
|
||||
{t("shared.advancedDatePicker.startDate")}
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={startDate}
|
||||
|
|
@ -358,9 +341,8 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* End date */}
|
||||
<div>
|
||||
<label className="text-sm text-gray-700 mb-1 block">End date</label>
|
||||
<label className="text-sm text-gray-700 mb-1 block">{t("shared.advancedDatePicker.endDate")}</label>
|
||||
<input
|
||||
type="date"
|
||||
value={endDate}
|
||||
|
|
@ -373,7 +355,6 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* Error message */}
|
||||
{!validation.isValid && validation.error && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-md p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -390,15 +371,14 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Current selection time range */}
|
||||
{tempValue.from && tempValue.to && validation.isValid && (
|
||||
<div className="bg-blue-50 p-3 rounded-md space-y-1">
|
||||
<div className="text-xs text-blue-800">
|
||||
<span className="font-medium">From:</span>{" "}
|
||||
<span className="font-medium">{t("shared.advancedDatePicker.from")}</span>{" "}
|
||||
{moment(tempValue.from).format("MMM D, YYYY [at] HH:mm:ss")}
|
||||
</div>
|
||||
<div className="text-xs text-blue-800">
|
||||
<span className="font-medium">To:</span>{" "}
|
||||
<span className="font-medium">{t("shared.advancedDatePicker.to")}</span>{" "}
|
||||
{moment(tempValue.to).format("MMM D, YYYY [at] HH:mm:ss")}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -408,10 +388,10 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
<div className="absolute bottom-4 right-4">
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" onClick={handleCancel}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleApply} disabled={!tempValue.from || !tempValue.to || !validation.isValid}>
|
||||
Apply
|
||||
{t("common.apply")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useCallback, useState, useRef } from "react";
|
||||
import { DateRangePicker, DateRangePickerValue, Text } from "@tremor/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface UsageDatePickerProps {
|
||||
value: DateRangePickerValue;
|
||||
|
|
@ -15,10 +16,12 @@ interface UsageDatePickerProps {
|
|||
const UsageDatePicker: React.FC<UsageDatePickerProps> = ({
|
||||
value,
|
||||
onValueChange,
|
||||
label = "Select Time Range",
|
||||
label,
|
||||
className = "",
|
||||
showTimeRange = true,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const resolvedLabel = label !== undefined ? label : t("shared.usageDatePicker.selectTimeRange");
|
||||
const [showSelectedFeedback, setShowSelectedFeedback] = useState(false);
|
||||
const datePickerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
@ -110,22 +113,20 @@ const UsageDatePicker: React.FC<UsageDatePickerProps> = ({
|
|||
|
||||
return (
|
||||
<div className={className}>
|
||||
{label && <Text className="mb-2">{label}</Text>}
|
||||
{resolvedLabel && <Text className="mb-2">{resolvedLabel}</Text>}
|
||||
|
||||
{/* Container with relative positioning for absolute placement */}
|
||||
<div className="relative w-fit">
|
||||
<div ref={datePickerRef}>
|
||||
<DateRangePicker
|
||||
enableSelect={true}
|
||||
value={value}
|
||||
onValueChange={handleDateChange} // Only triggers on actual selection
|
||||
placeholder="Select date range"
|
||||
onValueChange={handleDateChange}
|
||||
placeholder={t("shared.usageDatePicker.selectDateRange")}
|
||||
enableClear={false}
|
||||
style={{ zIndex: 100 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ONLY SHOW AFTER ACTUAL SELECTION IS COMPLETED */}
|
||||
{showSelectedFeedback && (
|
||||
<div
|
||||
className="absolute top-1/2 animate-pulse"
|
||||
|
|
@ -139,13 +140,12 @@ const UsageDatePicker: React.FC<UsageDatePickerProps> = ({
|
|||
<div className="w-3 h-3 bg-green-500 text-white rounded-full flex items-center justify-center text-xs">
|
||||
✓
|
||||
</div>
|
||||
<span className="text-xs">Selected</span>
|
||||
<span className="text-xs">{t("shared.usageDatePicker.selected")}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Time range display */}
|
||||
{showTimeRange && value.from && value.to && (
|
||||
<Text className="mt-2 text-xs text-gray-500">{formatTimeRange(value.from, value.to)}</Text>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,41 @@
|
|||
"adminUiDisabledBody": "The Admin UI has been disabled by the administrator. To re-enable it, please update the following environment variable:",
|
||||
"ssoEnabledNotice": "Single Sign-On (SSO) is enabled. LiteLLM no longer automatically redirects to the SSO login flow upon loading this page. To re-enable auto-redirect-to-SSO, set <code>AUTO_REDIRECT_UI_LOGIN_TO_SSO=true</code> in your environment configuration."
|
||||
},
|
||||
"shared": {
|
||||
"createdKeyDisplay": {
|
||||
"copiedToClipboard": "Key copied to clipboard",
|
||||
"saveWarning": "Please save this secret key somewhere safe and accessible. For security reasons, <b>you will not be able to view it again</b> through your LiteLLM account. If you lose this secret key, you will need to generate a new one.",
|
||||
"virtualKeyLabel": "Virtual Key:",
|
||||
"copyButton": "Copy Virtual Key"
|
||||
},
|
||||
"advancedDatePicker": {
|
||||
"selectTimeRange": "Select Time Range",
|
||||
"selectDateRange": "Select date range",
|
||||
"relativeTime": "Relative time",
|
||||
"startAndEndDates": "Start and end dates",
|
||||
"startDate": "Start date",
|
||||
"endDate": "End date",
|
||||
"invalidDateFormat": "Invalid date format",
|
||||
"endBeforeStart": "End date cannot be before start date",
|
||||
"from": "From:",
|
||||
"to": "To:",
|
||||
"today": "Today",
|
||||
"todayShort": "today",
|
||||
"last7Days": "Last 7 days",
|
||||
"last7DaysShort": "7d",
|
||||
"last30Days": "Last 30 days",
|
||||
"last30DaysShort": "30d",
|
||||
"monthToDate": "Month to date",
|
||||
"monthToDateShort": "MTD",
|
||||
"yearToDate": "Year to date",
|
||||
"yearToDateShort": "YTD"
|
||||
},
|
||||
"usageDatePicker": {
|
||||
"selectTimeRange": "Select Time Range",
|
||||
"selectDateRange": "Select date range",
|
||||
"selected": "Selected"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
|
|
|
|||
|
|
@ -98,6 +98,41 @@
|
|||
"adminUiDisabledBody": "管理员已禁用管理界面。如需重新启用,请更新以下环境变量:",
|
||||
"ssoEnabledNotice": "单点登录(SSO)已启用。LiteLLM 不再在加载此页面时自动跳转到 SSO 登录流程。如需重新启用自动跳转,请在环境配置中设置 <code>AUTO_REDIRECT_UI_LOGIN_TO_SSO=true</code>。"
|
||||
},
|
||||
"shared": {
|
||||
"createdKeyDisplay": {
|
||||
"copiedToClipboard": "密钥已复制到剪贴板",
|
||||
"saveWarning": "请将此密钥保存在安全且易于访问的地方。出于安全原因,<b>您将无法通过 LiteLLM 账户再次查看它</b>。如果丢失此密钥,您需要重新生成一个。",
|
||||
"virtualKeyLabel": "虚拟密钥:",
|
||||
"copyButton": "复制虚拟密钥"
|
||||
},
|
||||
"advancedDatePicker": {
|
||||
"selectTimeRange": "选择时间范围",
|
||||
"selectDateRange": "选择日期范围",
|
||||
"relativeTime": "相对时间",
|
||||
"startAndEndDates": "开始和结束日期",
|
||||
"startDate": "开始日期",
|
||||
"endDate": "结束日期",
|
||||
"invalidDateFormat": "日期格式无效",
|
||||
"endBeforeStart": "结束日期不能早于开始日期",
|
||||
"from": "从:",
|
||||
"to": "至:",
|
||||
"today": "今天",
|
||||
"todayShort": "今天",
|
||||
"last7Days": "过去 7 天",
|
||||
"last7DaysShort": "7天",
|
||||
"last30Days": "过去 30 天",
|
||||
"last30DaysShort": "30天",
|
||||
"monthToDate": "本月至今",
|
||||
"monthToDateShort": "MTD",
|
||||
"yearToDate": "今年至今",
|
||||
"yearToDateShort": "YTD"
|
||||
},
|
||||
"usageDatePicker": {
|
||||
"selectTimeRange": "选择时间范围",
|
||||
"selectDateRange": "选择日期范围",
|
||||
"selected": "已选择"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue