mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for entity usage export
This commit is contained in:
parent
663681bc4e
commit
7ff15f970e
7 changed files with 125 additions and 32 deletions
|
|
@ -2,6 +2,7 @@ import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
|
|||
import { createTeamAliasMap } from "@/utils/teamUtils";
|
||||
import { Button, Modal, Skeleton } from "antd";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
import ExportFormatSelector from "./ExportFormatSelector";
|
||||
import ExportSummary from "./ExportSummary";
|
||||
|
|
@ -18,13 +19,14 @@ const EntityUsageExportModal: React.FC<EntityUsageExportModalProps> = ({
|
|||
selectedFilters,
|
||||
customTitle,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [exportFormat, setExportFormat] = useState<ExportFormat>("csv");
|
||||
const [exportScope, setExportScope] = useState<ExportScope>("daily");
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
const { data: teams, isLoading: isLoadingTeams } = useTeams();
|
||||
|
||||
const entityLabel = entityType.charAt(0).toUpperCase() + entityType.slice(1);
|
||||
const modalTitle = customTitle || `Export ${entityLabel} Usage`;
|
||||
const modalTitle = customTitle || t("usageExport.entityUsageExportModal.exportTitle", { entityLabel });
|
||||
|
||||
// Cache team alias map using useMemo
|
||||
const teamAliasMap = useMemo(() => createTeamAliasMap(teams), [teams]);
|
||||
|
|
@ -34,15 +36,15 @@ const EntityUsageExportModal: React.FC<EntityUsageExportModalProps> = ({
|
|||
try {
|
||||
if (formatToUse === "csv") {
|
||||
handleExportCSV(spendData, exportScope, entityLabel, entityType, teamAliasMap);
|
||||
NotificationsManager.success(`${entityLabel} usage data exported successfully as CSV`);
|
||||
NotificationsManager.success(t("usageExport.entityUsageExportModal.exportSuccessCsv", { entityLabel }));
|
||||
} else {
|
||||
handleExportJSON(spendData, exportScope, entityLabel, entityType, dateRange, selectedFilters, teamAliasMap);
|
||||
NotificationsManager.success(`${entityLabel} usage data exported successfully as JSON`);
|
||||
NotificationsManager.success(t("usageExport.entityUsageExportModal.exportSuccessJson", { entityLabel }));
|
||||
}
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error exporting data:", error);
|
||||
NotificationsManager.fromBackend("Failed to export data");
|
||||
NotificationsManager.fromBackend(t("usageExport.entityUsageExportModal.exportFailed"));
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
}
|
||||
|
|
@ -74,7 +76,7 @@ const EntityUsageExportModal: React.FC<EntityUsageExportModalProps> = ({
|
|||
) : (
|
||||
<div className="flex items-center justify-end gap-2 pt-4 border-t">
|
||||
<Button variant="outlined" onClick={onClose} disabled={isExporting}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleExport()}
|
||||
|
|
@ -82,7 +84,9 @@ const EntityUsageExportModal: React.FC<EntityUsageExportModalProps> = ({
|
|||
disabled={isExporting || isLoadingTeams}
|
||||
type="primary"
|
||||
>
|
||||
{isExporting ? "Exporting..." : `Export ${exportFormat.toUpperCase()}`}
|
||||
{isExporting
|
||||
? t("usageExport.entityUsageExportModal.exporting")
|
||||
: t("usageExport.entityUsageExportModal.exportFormat", { format: exportFormat.toUpperCase() })}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import { Select } from "antd";
|
||||
import type { ExportFormat } from "./types";
|
||||
|
||||
|
|
@ -7,25 +9,27 @@ interface ExportFormatSelectorProps {
|
|||
onChange: (value: ExportFormat) => void;
|
||||
}
|
||||
|
||||
const getFormatOptions = (t: TFunction) => [
|
||||
{
|
||||
value: "csv",
|
||||
label: t("usageExport.exportFormatSelector.optionCsv"),
|
||||
},
|
||||
{
|
||||
value: "json",
|
||||
label: t("usageExport.exportFormatSelector.optionJson"),
|
||||
},
|
||||
];
|
||||
|
||||
const ExportFormatSelector: React.FC<ExportFormatSelectorProps> = ({ value, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const formatOptions = useMemo(() => getFormatOptions(t), [t]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-700 block mb-2">Format</label>
|
||||
<Select
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="w-full"
|
||||
options={[
|
||||
{
|
||||
value: "csv",
|
||||
label: "CSV (Excel, Google Sheets)",
|
||||
},
|
||||
{
|
||||
value: "json",
|
||||
label: "JSON (includes metadata)",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<label className="text-sm font-medium text-gray-700 block mb-2">
|
||||
{t("usageExport.exportFormatSelector.label")}
|
||||
</label>
|
||||
<Select value={value} onChange={onChange} className="w-full" options={formatOptions} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { DateRangePickerValue } from "@tremor/react";
|
||||
|
||||
interface ExportSummaryProps {
|
||||
|
|
@ -7,10 +8,13 @@ interface ExportSummaryProps {
|
|||
}
|
||||
|
||||
const ExportSummary: React.FC<ExportSummaryProps> = ({ dateRange, selectedFilters }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="text-sm text-gray-500">
|
||||
{dateRange.from?.toLocaleDateString()} - {dateRange.to?.toLocaleDateString()}
|
||||
{selectedFilters.length > 0 && ` · ${selectedFilters.length} filter${selectedFilters.length > 1 ? "s" : ""}`}
|
||||
{selectedFilters.length > 0 &&
|
||||
` · ${t("usageExport.exportSummary.filterCount", { count: selectedFilters.length })}`}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Radio } from "antd";
|
||||
import type { ExportScope, EntityType } from "./types";
|
||||
|
||||
|
|
@ -9,32 +10,48 @@ interface ExportTypeSelectorProps {
|
|||
}
|
||||
|
||||
const ExportTypeSelector: React.FC<ExportTypeSelectorProps> = ({ value, onChange, entityType }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-700 block mb-2">Export type</label>
|
||||
<label className="text-sm font-medium text-gray-700 block mb-2">
|
||||
{t("usageExport.exportTypeSelector.label")}
|
||||
</label>
|
||||
<Radio.Group value={value} onChange={(e) => onChange(e.target.value)} className="w-full">
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-start p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<Radio value="daily" className="mt-0.5" />
|
||||
<div className="ml-3 flex-1">
|
||||
<div className="font-medium text-sm">Day-by-day breakdown by {entityType}</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">Daily metrics for each {entityType}</div>
|
||||
<div className="font-medium text-sm">
|
||||
{t("usageExport.exportTypeSelector.dailyTitle", { entityType })}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">
|
||||
{t("usageExport.exportTypeSelector.dailyDesc", { entityType })}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="flex items-start p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<Radio value="daily_with_keys" className="mt-0.5" />
|
||||
<div className="ml-3 flex-1">
|
||||
<div className="font-medium text-sm">Day-by-day breakdown by {entityType} and key</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">Daily metrics for each {entityType}, split by API key</div>
|
||||
<div className="font-medium text-sm">
|
||||
{t("usageExport.exportTypeSelector.dailyWithKeysTitle", { entityType })}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">
|
||||
{t("usageExport.exportTypeSelector.dailyWithKeysDesc", { entityType })}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="flex items-start p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<Radio value="daily_with_models" className="mt-0.5" />
|
||||
<div className="ml-3 flex-1">
|
||||
<div className="font-medium text-sm">Day-by-day by {entityType} and model</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">Daily metrics split by model</div>
|
||||
<div className="font-medium text-sm">
|
||||
{t("usageExport.exportTypeSelector.dailyWithModelsTitle", { entityType })}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">
|
||||
{t("usageExport.exportTypeSelector.dailyWithModelsDesc")}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { DateRangePickerValue } from "@tremor/react";
|
|||
import { Button, Text } from "@tremor/react";
|
||||
import { Select } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import EntityUsageExportModal from "./EntityUsageExportModal";
|
||||
import type { EntitySpendData, EntityType } from "./types";
|
||||
import type { Team } from "@/components/key_team_helpers/key_list";
|
||||
|
|
@ -38,6 +39,7 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
|
|||
compactLayout = false,
|
||||
teams = [],
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [isExportModalOpen, setIsExportModalOpen] = useState(false);
|
||||
|
||||
// Determine grid layout based on what's visible
|
||||
|
|
@ -92,7 +94,7 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
|
|||
</svg>
|
||||
)}
|
||||
>
|
||||
Export Data
|
||||
{t("usageExport.usageExportHeader.exportData")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7123,5 +7123,36 @@
|
|||
"timezoneLabel": "Timezone",
|
||||
"timezoneTooltip": "Timezone for date handling (defaults to UTC if not provided)"
|
||||
}
|
||||
},
|
||||
"usageExport": {
|
||||
"entityUsageExportModal": {
|
||||
"exportTitle": "Export {{entityLabel}} Usage",
|
||||
"exportSuccessCsv": "{{entityLabel}} usage data exported successfully as CSV",
|
||||
"exportSuccessJson": "{{entityLabel}} usage data exported successfully as JSON",
|
||||
"exportFailed": "Failed to export data",
|
||||
"exporting": "Exporting...",
|
||||
"exportFormat": "Export {{format}}"
|
||||
},
|
||||
"exportFormatSelector": {
|
||||
"label": "Format",
|
||||
"optionCsv": "CSV (Excel, Google Sheets)",
|
||||
"optionJson": "JSON (includes metadata)"
|
||||
},
|
||||
"exportSummary": {
|
||||
"filterCount_one": "{{count}} filter",
|
||||
"filterCount_other": "{{count}} filters"
|
||||
},
|
||||
"exportTypeSelector": {
|
||||
"label": "Export type",
|
||||
"dailyTitle": "Day-by-day breakdown by {{entityType}}",
|
||||
"dailyDesc": "Daily metrics for each {{entityType}}",
|
||||
"dailyWithKeysTitle": "Day-by-day breakdown by {{entityType}} and key",
|
||||
"dailyWithKeysDesc": "Daily metrics for each {{entityType}}, split by API key",
|
||||
"dailyWithModelsTitle": "Day-by-day by {{entityType}} and model",
|
||||
"dailyWithModelsDesc": "Daily metrics split by model"
|
||||
},
|
||||
"usageExportHeader": {
|
||||
"exportData": "Export Data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7123,5 +7123,36 @@
|
|||
"timezoneLabel": "时区",
|
||||
"timezoneTooltip": "日期处理时区(未提供时默认为 UTC)"
|
||||
}
|
||||
},
|
||||
"usageExport": {
|
||||
"entityUsageExportModal": {
|
||||
"exportTitle": "导出 {{entityLabel}} 用量",
|
||||
"exportSuccessCsv": "{{entityLabel}} 用量数据已成功导出为 CSV",
|
||||
"exportSuccessJson": "{{entityLabel}} 用量数据已成功导出为 JSON",
|
||||
"exportFailed": "导出数据失败",
|
||||
"exporting": "导出中...",
|
||||
"exportFormat": "导出 {{format}}"
|
||||
},
|
||||
"exportFormatSelector": {
|
||||
"label": "格式",
|
||||
"optionCsv": "CSV(Excel、Google Sheets)",
|
||||
"optionJson": "JSON(包含元数据)"
|
||||
},
|
||||
"exportSummary": {
|
||||
"filterCount_one": "{{count}} 个筛选器",
|
||||
"filterCount_other": "{{count}} 个筛选器"
|
||||
},
|
||||
"exportTypeSelector": {
|
||||
"label": "导出类型",
|
||||
"dailyTitle": "按 {{entityType}} 逐日明细",
|
||||
"dailyDesc": "每个 {{entityType}} 的每日指标",
|
||||
"dailyWithKeysTitle": "按 {{entityType}} 和密钥逐日明细",
|
||||
"dailyWithKeysDesc": "每个 {{entityType}} 的每日指标,按 API 密钥拆分",
|
||||
"dailyWithModelsTitle": "按 {{entityType}} 和模型逐日明细",
|
||||
"dailyWithModelsDesc": "按模型拆分的每日指标"
|
||||
},
|
||||
"usageExportHeader": {
|
||||
"exportData": "导出数据"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue