mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for cost tracking settings
This commit is contained in:
parent
6a21cb423b
commit
65f3306f46
9 changed files with 443 additions and 127 deletions
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { TextInput, Button } from "@tremor/react";
|
||||
import { Select as AntdSelect, Form, Tooltip, Radio } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Providers, provider_map, providerLogoMap } from "../provider_info_helpers";
|
||||
import { MarginConfig } from "./types";
|
||||
import { handleImageError } from "./provider_display_helpers";
|
||||
|
|
@ -31,22 +32,24 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
onFixedAmountChange,
|
||||
onAddProvider,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
Provider
|
||||
<Tooltip title="Select 'Global' to apply margin to all providers, or select a specific provider">
|
||||
{t("costTracking.addMarginForm.providerLabel")}
|
||||
<Tooltip title={t("costTracking.addMarginForm.providerTooltip")}>
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
rules={[{ required: true, message: "Please select a provider" }]}
|
||||
rules={[{ required: true, message: t("costTracking.addMarginForm.providerRequired") }]}
|
||||
>
|
||||
<AntdSelect
|
||||
showSearch
|
||||
placeholder="Select provider or 'Global'"
|
||||
placeholder={t("costTracking.addMarginForm.providerPlaceholder")}
|
||||
value={selectedProvider}
|
||||
onChange={onProviderChange}
|
||||
style={{ width: "100%" }}
|
||||
|
|
@ -58,9 +61,9 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
.includes(input.toLowerCase())
|
||||
}
|
||||
>
|
||||
<AntdSelect.Option key="global" value="global" label="Global (All Providers)">
|
||||
<AntdSelect.Option key="global" value="global" label={t("costTracking.addMarginForm.globalOption")}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="font-medium">Global (All Providers)</span>
|
||||
<span className="font-medium">{t("costTracking.addMarginForm.globalOption")}</span>
|
||||
</div>
|
||||
</AntdSelect.Option>
|
||||
{Object.entries(Providers).map(([providerEnum, providerDisplayName]) => {
|
||||
|
|
@ -89,17 +92,17 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
Margin Type
|
||||
<Tooltip title="Choose how to apply the margin: percentage-based or fixed amount">
|
||||
{t("costTracking.addMarginForm.marginTypeLabel")}
|
||||
<Tooltip title={t("costTracking.addMarginForm.marginTypeTooltip")}>
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
rules={[{ required: true, message: "Please select a margin type" }]}
|
||||
rules={[{ required: true, message: t("costTracking.addMarginForm.marginTypeRequired") }]}
|
||||
>
|
||||
<Radio.Group value={marginType} onChange={(e) => onMarginTypeChange(e.target.value)} className="w-full">
|
||||
<Radio value="percentage">Percentage-based</Radio>
|
||||
<Radio value="fixed">Fixed Amount</Radio>
|
||||
<Radio value="percentage">{t("costTracking.addMarginForm.percentageBased")}</Radio>
|
||||
<Radio value="fixed">{t("costTracking.addMarginForm.fixedAmount")}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
|
|
@ -107,22 +110,22 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
Margin Percentage
|
||||
<Tooltip title="Enter a percentage value (e.g., 10 for 10% margin)">
|
||||
{t("costTracking.addMarginForm.marginPercentageLabel")}
|
||||
<Tooltip title={t("costTracking.addMarginForm.marginPercentageTooltip")}>
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
rules={[
|
||||
{ required: true, message: "Please enter a margin percentage" },
|
||||
{ required: true, message: t("costTracking.addMarginForm.percentageRequired") },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error("Please enter a margin percentage"));
|
||||
return Promise.reject(new Error(t("costTracking.addMarginForm.percentageRequired")));
|
||||
}
|
||||
const numValue = parseFloat(value);
|
||||
if (isNaN(numValue) || numValue < 0 || numValue > 1000) {
|
||||
return Promise.reject(new Error("Percentage must be between 0 and 1000"));
|
||||
return Promise.reject(new Error(t("costTracking.addMarginForm.percentageRangeError")));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
@ -145,22 +148,22 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
Fixed Margin Amount
|
||||
<Tooltip title="Enter a fixed amount in USD (e.g., 0.001 for $0.001 per request)">
|
||||
{t("costTracking.addMarginForm.fixedAmountLabel")}
|
||||
<Tooltip title={t("costTracking.addMarginForm.fixedAmountTooltip")}>
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
rules={[
|
||||
{ required: true, message: "Please enter a fixed amount" },
|
||||
{ required: true, message: t("costTracking.addMarginForm.fixedAmountRequired") },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error("Please enter a fixed amount"));
|
||||
return Promise.reject(new Error(t("costTracking.addMarginForm.fixedAmountRequired")));
|
||||
}
|
||||
const numValue = parseFloat(value);
|
||||
if (isNaN(numValue) || numValue < 0) {
|
||||
return Promise.reject(new Error("Fixed amount must be non-negative"));
|
||||
return Promise.reject(new Error(t("costTracking.addMarginForm.fixedAmountNonNegative")));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
@ -189,7 +192,7 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
(marginType === "fixed" && !fixedAmountValue)
|
||||
}
|
||||
>
|
||||
Add Provider Margin
|
||||
{t("costTracking.addMarginForm.addButton")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { TextInput, Button } from "@tremor/react";
|
||||
import { Select as AntdSelect, Form, Tooltip } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Providers, provider_map, providerLogoMap } from "../provider_info_helpers";
|
||||
import { DiscountConfig } from "./types";
|
||||
import { handleImageError } from "./provider_display_helpers";
|
||||
|
|
@ -23,22 +24,24 @@ const AddProviderForm: React.FC<AddProviderFormProps> = ({
|
|||
onDiscountChange,
|
||||
onAddProvider,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
Provider
|
||||
<Tooltip title="Select the LLM provider you want to configure a discount for">
|
||||
{t("costTracking.addProviderForm.providerLabel")}
|
||||
<Tooltip title={t("costTracking.addProviderForm.providerTooltip")}>
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
rules={[{ required: true, message: "Please select a provider" }]}
|
||||
rules={[{ required: true, message: t("costTracking.addProviderForm.providerRequired") }]}
|
||||
>
|
||||
<AntdSelect
|
||||
showSearch
|
||||
placeholder="Select provider"
|
||||
placeholder={t("costTracking.addProviderForm.providerPlaceholder")}
|
||||
value={selectedProvider}
|
||||
onChange={onProviderChange}
|
||||
style={{ width: "100%" }}
|
||||
|
|
@ -76,13 +79,13 @@ const AddProviderForm: React.FC<AddProviderFormProps> = ({
|
|||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
Discount Percentage
|
||||
<Tooltip title="Enter a percentage value (e.g., 5 for 5% discount)">
|
||||
{t("costTracking.addProviderForm.discountPercentageLabel")}
|
||||
<Tooltip title={t("costTracking.addProviderForm.discountPercentageTooltip")}>
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
rules={[{ required: true, message: "Please enter a discount percentage" }]}
|
||||
rules={[{ required: true, message: t("costTracking.addProviderForm.discountRequired") }]}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<TextInput
|
||||
|
|
@ -97,7 +100,7 @@ const AddProviderForm: React.FC<AddProviderFormProps> = ({
|
|||
|
||||
<div className="flex items-center justify-end space-x-3 pt-6 border-t border-gray-100">
|
||||
<Button variant="primary" onClick={onAddProvider} disabled={!selectedProvider || !newDiscount}>
|
||||
Add Provider Discount
|
||||
{t("costTracking.addProviderForm.addButton")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
TabPanel,
|
||||
} from "@tremor/react";
|
||||
import { Modal, Form } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CostTrackingSettingsProps } from "./types";
|
||||
import ProviderDiscountTable from "./provider_discount_table";
|
||||
import AddProviderForm from "./add_provider_form";
|
||||
|
|
@ -32,6 +33,7 @@ const DOCS_LINKS = [
|
|||
];
|
||||
|
||||
const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, userRole, accessToken }) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedProvider, setSelectedProvider] = useState<string | undefined>(undefined);
|
||||
const [newDiscount, setNewDiscount] = useState<string>("");
|
||||
const [isFetching, setIsFetching] = useState(true);
|
||||
|
|
@ -106,12 +108,12 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
|
||||
const handleRemoveProvider = async (provider: string, providerDisplayName: string) => {
|
||||
modal.confirm({
|
||||
title: "Remove Provider Discount",
|
||||
title: t("costTracking.costTrackingSettings.removeDiscountTitle"),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: `Are you sure you want to remove the discount for ${providerDisplayName}?`,
|
||||
okText: "Remove",
|
||||
content: t("costTracking.costTrackingSettings.removeDiscountContent", { providerDisplayName }),
|
||||
okText: t("costTracking.costTrackingSettings.removeOkText"),
|
||||
okType: "danger",
|
||||
cancelText: "Cancel",
|
||||
cancelText: t("common.cancel"),
|
||||
onOk: () => removeProvider(provider),
|
||||
});
|
||||
};
|
||||
|
|
@ -143,12 +145,12 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
|
||||
const handleRemoveMargin = async (provider: string, providerDisplayName: string) => {
|
||||
modal.confirm({
|
||||
title: "Remove Provider Margin",
|
||||
title: t("costTracking.costTrackingSettings.removeMarginTitle"),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: `Are you sure you want to remove the margin for ${providerDisplayName}?`,
|
||||
okText: "Remove",
|
||||
content: t("costTracking.costTrackingSettings.removeMarginContent", { providerDisplayName }),
|
||||
okText: t("costTracking.costTrackingSettings.removeOkText"),
|
||||
okType: "danger",
|
||||
cancelText: "Cancel",
|
||||
cancelText: t("common.cancel"),
|
||||
onOk: () => removeMargin(provider),
|
||||
});
|
||||
};
|
||||
|
|
@ -165,12 +167,10 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
<div className="flex flex-col md:flex-row items-start md:items-center justify-between mb-6">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Title>Cost Tracking Settings</Title>
|
||||
<Title>{t("costTracking.costTrackingSettings.pageTitle")}</Title>
|
||||
<DocsMenu items={DOCS_LINKS} />
|
||||
</div>
|
||||
<Text className="text-gray-500 mt-1">
|
||||
Configure cost discounts and margins for different LLM providers. Changes are saved automatically.
|
||||
</Text>
|
||||
<Text className="text-gray-500 mt-1">{t("costTracking.costTrackingSettings.pageSubtitle")}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -181,27 +181,31 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
<Accordion>
|
||||
<AccordionHeader className="px-6 py-4">
|
||||
<div className="flex flex-col items-start w-full">
|
||||
<Text className="text-lg font-semibold text-gray-900">Provider Discounts</Text>
|
||||
<Text className="text-lg font-semibold text-gray-900">
|
||||
{t("costTracking.costTrackingSettings.providerDiscountsTitle")}
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-500 mt-1">
|
||||
Apply percentage-based discounts to reduce costs for specific providers
|
||||
{t("costTracking.costTrackingSettings.providerDiscountsSubtitle")}
|
||||
</Text>
|
||||
</div>
|
||||
</AccordionHeader>
|
||||
<AccordionBody className="px-0">
|
||||
<TabGroup>
|
||||
<TabList className="px-6 pt-4">
|
||||
<Tab>Discounts</Tab>
|
||||
<Tab>Test It</Tab>
|
||||
<Tab>{t("costTracking.costTrackingSettings.discountsTab")}</Tab>
|
||||
<Tab>{t("costTracking.costTrackingSettings.testItTab")}</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<div className="p-6">
|
||||
<div className="flex justify-end mb-4">
|
||||
<Button onClick={() => setIsModalVisible(true)}>+ Add Provider Discount</Button>
|
||||
<Button onClick={() => setIsModalVisible(true)}>
|
||||
{t("costTracking.costTrackingSettings.addProviderDiscountButton")}
|
||||
</Button>
|
||||
</div>
|
||||
{isFetching ? (
|
||||
<div className="py-12 text-center">
|
||||
<Text className="text-gray-500">Loading configuration...</Text>
|
||||
<Text className="text-gray-500">{t("common.loading")}</Text>
|
||||
</div>
|
||||
) : Object.keys(discountConfig).length > 0 ? (
|
||||
<ProviderDiscountTable
|
||||
|
|
@ -224,9 +228,11 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Text className="text-gray-700 font-medium mb-2">No provider discounts configured</Text>
|
||||
<Text className="text-gray-700 font-medium mb-2">
|
||||
{t("costTracking.costTrackingSettings.noDiscountsConfigured")}
|
||||
</Text>
|
||||
<Text className="text-gray-500 text-sm">
|
||||
Click "Add Provider Discount" to get started
|
||||
{t("costTracking.costTrackingSettings.noDiscountsHint")}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -248,20 +254,24 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
<Accordion>
|
||||
<AccordionHeader className="px-6 py-4">
|
||||
<div className="flex flex-col items-start w-full">
|
||||
<Text className="text-lg font-semibold text-gray-900">Fee/Price Margin</Text>
|
||||
<Text className="text-lg font-semibold text-gray-900">
|
||||
{t("costTracking.costTrackingSettings.feePriceMarginTitle")}
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-500 mt-1">
|
||||
Add fees or margins to LLM costs for internal billing and cost recovery
|
||||
{t("costTracking.costTrackingSettings.feePriceMarginSubtitle")}
|
||||
</Text>
|
||||
</div>
|
||||
</AccordionHeader>
|
||||
<AccordionBody className="px-0">
|
||||
<div className="p-6">
|
||||
<div className="flex justify-end mb-4">
|
||||
<Button onClick={() => setIsMarginModalVisible(true)}>+ Add Provider Margin</Button>
|
||||
<Button onClick={() => setIsMarginModalVisible(true)}>
|
||||
{t("costTracking.costTrackingSettings.addProviderMarginButton")}
|
||||
</Button>
|
||||
</div>
|
||||
{isFetching ? (
|
||||
<div className="py-12 text-center">
|
||||
<Text className="text-gray-500">Loading configuration...</Text>
|
||||
<Text className="text-gray-500">{t("common.loading")}</Text>
|
||||
</div>
|
||||
) : Object.keys(marginConfig).length > 0 ? (
|
||||
<ProviderMarginTable
|
||||
|
|
@ -284,8 +294,12 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Text className="text-gray-700 font-medium mb-2">No provider margins configured</Text>
|
||||
<Text className="text-gray-500 text-sm">Click "Add Provider Margin" to get started</Text>
|
||||
<Text className="text-gray-700 font-medium mb-2">
|
||||
{t("costTracking.costTrackingSettings.noMarginsConfigured")}
|
||||
</Text>
|
||||
<Text className="text-gray-500 text-sm">
|
||||
{t("costTracking.costTrackingSettings.noMarginsHint")}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -297,9 +311,11 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
<Accordion defaultOpen={true}>
|
||||
<AccordionHeader className="px-6 py-4">
|
||||
<div className="flex flex-col items-start w-full">
|
||||
<Text className="text-lg font-semibold text-gray-900">Pricing Calculator</Text>
|
||||
<Text className="text-lg font-semibold text-gray-900">
|
||||
{t("costTracking.costTrackingSettings.pricingCalculatorTitle")}
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-500 mt-1">
|
||||
Estimate LLM costs based on expected token usage and request volume
|
||||
{t("costTracking.costTrackingSettings.pricingCalculatorSubtitle")}
|
||||
</Text>
|
||||
</div>
|
||||
</AccordionHeader>
|
||||
|
|
@ -314,7 +330,9 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
<Modal
|
||||
title={
|
||||
<div className="flex items-center space-x-3 pb-4 border-b border-gray-100">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Add Provider Discount</h2>
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
{t("costTracking.costTrackingSettings.addDiscountModalTitle")}
|
||||
</h2>
|
||||
</div>
|
||||
}
|
||||
open={isModalVisible}
|
||||
|
|
@ -329,8 +347,7 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
>
|
||||
<div className="mt-6">
|
||||
<Text className="text-sm text-gray-600 mb-6">
|
||||
Select a provider and set its discount percentage. Enter a value between 0% and 100% (e.g., 5 for a 5%
|
||||
discount).
|
||||
{t("costTracking.costTrackingSettings.addDiscountModalDesc")}
|
||||
</Text>
|
||||
<Form form={form} onFinish={handleFormSubmit} layout="vertical" className="space-y-6">
|
||||
<AddProviderForm
|
||||
|
|
@ -348,7 +365,9 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
<Modal
|
||||
title={
|
||||
<div className="flex items-center space-x-3 pb-4 border-b border-gray-100">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Add Provider Margin</h2>
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
{t("costTracking.costTrackingSettings.addMarginModalTitle")}
|
||||
</h2>
|
||||
</div>
|
||||
}
|
||||
open={isMarginModalVisible}
|
||||
|
|
@ -363,8 +382,7 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
|
|||
>
|
||||
<div className="mt-6">
|
||||
<Text className="text-sm text-gray-600 mb-6">
|
||||
Select a provider (or "Global" for all providers) and configure the margin. You can use
|
||||
percentage-based or fixed amount.
|
||||
{t("costTracking.costTrackingSettings.addMarginModalDesc")}
|
||||
</Text>
|
||||
<Form form={marginForm} layout="vertical" className="space-y-6">
|
||||
<AddMarginForm
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import React, { useState, useMemo } from "react";
|
||||
import { Text, TextInput } from "@tremor/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CodeBlock from "@/app/(dashboard)/api-reference/components/CodeBlock";
|
||||
|
||||
const HowItWorks: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [responseCost, setResponseCost] = useState("");
|
||||
const [discountAmount, setDiscountAmount] = useState("");
|
||||
|
||||
|
|
@ -28,30 +30,30 @@ const HowItWorks: React.FC = () => {
|
|||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<div>
|
||||
<Text className="font-medium text-gray-900 text-sm mb-1">Cost Calculation</Text>
|
||||
<Text className="font-medium text-gray-900 text-sm mb-1">
|
||||
{t("costTracking.howItWorks.costCalculationTitle")}
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-600">
|
||||
Discounts are applied to provider costs:{" "}
|
||||
{t("costTracking.howItWorks.costCalculationDesc")}{" "}
|
||||
<code className="bg-gray-100 px-1.5 py-0.5 rounded text-xs">
|
||||
final_cost = base_cost × (1 - discount%/100)
|
||||
</code>
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium text-gray-900 text-sm mb-1">Example</Text>
|
||||
<Text className="text-xs text-gray-600">
|
||||
A 5% discount on a $10.00 request results in: $10.00 × (1 - 0.05) = $9.50
|
||||
</Text>
|
||||
<Text className="font-medium text-gray-900 text-sm mb-1">{t("costTracking.howItWorks.exampleTitle")}</Text>
|
||||
<Text className="text-xs text-gray-600">{t("costTracking.howItWorks.exampleDesc")}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium text-gray-900 text-sm mb-1">Valid Range</Text>
|
||||
<Text className="text-xs text-gray-600">Discount percentages must be between 0% and 100%</Text>
|
||||
<Text className="font-medium text-gray-900 text-sm mb-1">{t("costTracking.howItWorks.validRangeTitle")}</Text>
|
||||
<Text className="text-xs text-gray-600">{t("costTracking.howItWorks.validRangeDesc")}</Text>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
<Text className="font-medium text-gray-900 text-sm mb-2">Validating Discounts</Text>
|
||||
<Text className="text-xs text-gray-600 mb-3">
|
||||
Make a test request and check the response headers to verify discounts are applied:
|
||||
<Text className="font-medium text-gray-900 text-sm mb-2">
|
||||
{t("costTracking.howItWorks.validatingDiscountsTitle")}
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-600 mb-3">{t("costTracking.howItWorks.validatingDiscountsDesc")}</Text>
|
||||
<CodeBlock
|
||||
language="bash"
|
||||
code={`curl -X POST -i http://your-proxy:4000/chat/completions \\
|
||||
|
|
@ -62,38 +64,38 @@ const HowItWorks: React.FC = () => {
|
|||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
}'`}
|
||||
/>
|
||||
<Text className="text-xs text-gray-600 mt-3 mb-2">Look for these headers in the response:</Text>
|
||||
<Text className="text-xs text-gray-600 mt-3 mb-2">{t("costTracking.howItWorks.lookForHeaders")}</Text>
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-start gap-3">
|
||||
<code className="bg-gray-100 px-2 py-1 rounded text-xs font-mono text-gray-800 whitespace-nowrap">
|
||||
x-litellm-response-cost
|
||||
</code>
|
||||
<Text className="text-xs text-gray-600">Final cost after discount</Text>
|
||||
<Text className="text-xs text-gray-600">{t("costTracking.howItWorks.headerResponseCost")}</Text>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<code className="bg-gray-100 px-2 py-1 rounded text-xs font-mono text-gray-800 whitespace-nowrap">
|
||||
x-litellm-response-cost-original
|
||||
</code>
|
||||
<Text className="text-xs text-gray-600">Original cost before discount</Text>
|
||||
<Text className="text-xs text-gray-600">{t("costTracking.howItWorks.headerOriginalCost")}</Text>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<code className="bg-gray-100 px-2 py-1 rounded text-xs font-mono text-gray-800 whitespace-nowrap">
|
||||
x-litellm-response-cost-discount-amount
|
||||
</code>
|
||||
<Text className="text-xs text-gray-600">Amount discounted</Text>
|
||||
<Text className="text-xs text-gray-600">{t("costTracking.howItWorks.headerDiscountAmount")}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
<Text className="font-medium text-gray-900 text-sm mb-3">Discount Calculator</Text>
|
||||
<Text className="text-xs text-gray-600 mb-3">
|
||||
Enter values from your response headers to verify the discount:
|
||||
<Text className="font-medium text-gray-900 text-sm mb-3">
|
||||
{t("costTracking.howItWorks.discountCalculatorTitle")}
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-600 mb-3">{t("costTracking.howItWorks.discountCalculatorDesc")}</Text>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||
Response Cost (x-litellm-response-cost)
|
||||
{t("costTracking.howItWorks.responseCostLabel")}
|
||||
</label>
|
||||
<TextInput
|
||||
placeholder="0.0171938125"
|
||||
|
|
@ -104,7 +106,7 @@ const HowItWorks: React.FC = () => {
|
|||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||
Discount Amount (x-litellm-response-cost-discount-amount)
|
||||
{t("costTracking.howItWorks.discountAmountLabel")}
|
||||
</label>
|
||||
<TextInput
|
||||
placeholder="0.0009049375"
|
||||
|
|
@ -117,22 +119,26 @@ const HowItWorks: React.FC = () => {
|
|||
|
||||
{calculatedDiscount && (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<Text className="text-sm font-medium text-blue-900 mb-2">Calculated Results</Text>
|
||||
<Text className="text-sm font-medium text-blue-900 mb-2">
|
||||
{t("costTracking.howItWorks.calculatedResultsTitle")}
|
||||
</Text>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Text className="text-xs text-blue-800">Original Cost:</Text>
|
||||
<Text className="text-xs text-blue-800">{t("costTracking.howItWorks.originalCostLabel")}</Text>
|
||||
<code className="text-xs font-mono text-blue-900">${calculatedDiscount.originalCost}</code>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Text className="text-xs text-blue-800">Final Cost:</Text>
|
||||
<Text className="text-xs text-blue-800">{t("costTracking.howItWorks.finalCostLabel")}</Text>
|
||||
<code className="text-xs font-mono text-blue-900">${calculatedDiscount.finalCost}</code>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Text className="text-xs text-blue-800">Discount Amount:</Text>
|
||||
<Text className="text-xs text-blue-800">{t("costTracking.howItWorks.discountAmountResultLabel")}</Text>
|
||||
<code className="text-xs font-mono text-blue-900">${calculatedDiscount.discountAmount}</code>
|
||||
</div>
|
||||
<div className="flex items-center justify-between pt-2 border-t border-blue-300">
|
||||
<Text className="text-xs font-semibold text-blue-900">Discount Applied:</Text>
|
||||
<Text className="text-xs font-semibold text-blue-900">
|
||||
{t("costTracking.howItWorks.discountAppliedLabel")}
|
||||
</Text>
|
||||
<Text className="text-sm font-bold text-blue-900">{calculatedDiscount.discountPercentage}%</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useCallback } from "react";
|
||||
import { Table, Select, InputNumber, Button, Radio } from "antd";
|
||||
import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PricingCalculatorProps, ModelEntry } from "./types";
|
||||
import MultiCostResults from "./multi_cost_results";
|
||||
import { useMultiCostEstimate } from "./use_multi_cost_estimate";
|
||||
|
|
@ -19,6 +20,7 @@ const createDefaultEntry = (): ModelEntry => ({
|
|||
});
|
||||
|
||||
const PricingCalculator: React.FC<PricingCalculatorProps> = ({ accessToken, models }) => {
|
||||
const { t } = useTranslation();
|
||||
const [entries, setEntries] = useState<ModelEntry[]>([createDefaultEntry()]);
|
||||
const [timePeriod, setTimePeriod] = useState<TimePeriod>("month");
|
||||
const { debouncedFetchForEntry, removeEntry, getMultiModelResult } = useMultiCostEstimate(accessToken);
|
||||
|
|
@ -65,14 +67,14 @@ const PricingCalculator: React.FC<PricingCalculatorProps> = ({ accessToken, mode
|
|||
|
||||
const columns = [
|
||||
{
|
||||
title: "Model",
|
||||
title: t("costTracking.pricingCalculator.colModel"),
|
||||
dataIndex: "model",
|
||||
key: "model",
|
||||
width: "35%",
|
||||
render: (_: string, record: ModelEntry) => (
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Select a model"
|
||||
placeholder={t("costTracking.pricingCalculator.modelPlaceholder")}
|
||||
value={record.model || undefined}
|
||||
onChange={(value) => handleEntryChange(record.id, "model", value)}
|
||||
optionFilterProp="label"
|
||||
|
|
@ -91,7 +93,7 @@ const PricingCalculator: React.FC<PricingCalculatorProps> = ({ accessToken, mode
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Input Tokens",
|
||||
title: t("costTracking.pricingCalculator.colInputTokens"),
|
||||
dataIndex: "input_tokens",
|
||||
key: "input_tokens",
|
||||
width: "18%",
|
||||
|
|
@ -107,7 +109,7 @@ const PricingCalculator: React.FC<PricingCalculatorProps> = ({ accessToken, mode
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Output Tokens",
|
||||
title: t("costTracking.pricingCalculator.colOutputTokens"),
|
||||
dataIndex: "output_tokens",
|
||||
key: "output_tokens",
|
||||
width: "18%",
|
||||
|
|
@ -123,7 +125,11 @@ const PricingCalculator: React.FC<PricingCalculatorProps> = ({ accessToken, mode
|
|||
),
|
||||
},
|
||||
{
|
||||
title: `Requests/${timePeriod === "day" ? "Day" : "Month"}`,
|
||||
title: t(
|
||||
timePeriod === "day"
|
||||
? "costTracking.pricingCalculator.colRequestsPerDay"
|
||||
: "costTracking.pricingCalculator.colRequestsPerMonth",
|
||||
),
|
||||
dataIndex: timePeriod === "day" ? "num_requests_per_day" : "num_requests_per_month",
|
||||
key: "num_requests",
|
||||
width: "20%",
|
||||
|
|
@ -172,8 +178,8 @@ const PricingCalculator: React.FC<PricingCalculatorProps> = ({ accessToken, mode
|
|||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
>
|
||||
<Radio.Button value="day">Per Day</Radio.Button>
|
||||
<Radio.Button value="month">Per Month</Radio.Button>
|
||||
<Radio.Button value="day">{t("costTracking.pricingCalculator.perDay")}</Radio.Button>
|
||||
<Radio.Button value="month">{t("costTracking.pricingCalculator.perMonth")}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
|
|
@ -185,7 +191,7 @@ const PricingCalculator: React.FC<PricingCalculatorProps> = ({ accessToken, mode
|
|||
size="small"
|
||||
footer={() => (
|
||||
<Button type="dashed" onClick={handleAddEntry} icon={<PlusOutlined />} className="w-full">
|
||||
Add Another Model
|
||||
{t("costTracking.pricingCalculator.addAnotherModel")}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||
import { Text, Button } from "@tremor/react";
|
||||
import { Card, Statistic, Row, Col, Divider, Spin, Table, Tag } from "antd";
|
||||
import { LoadingOutlined, DownOutlined, RightOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CostEstimateResponse } from "../types";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { MultiModelResult } from "./types";
|
||||
|
|
@ -30,7 +31,9 @@ const SingleModelBreakdown: React.FC<{
|
|||
loading: boolean;
|
||||
timePeriod: "day" | "month";
|
||||
}> = ({ result, loading, timePeriod }) => {
|
||||
const periodLabel = timePeriod === "day" ? "Daily" : "Monthly";
|
||||
const { t } = useTranslation();
|
||||
const periodLabel =
|
||||
timePeriod === "day" ? t("costTracking.multiCostResults.daily") : t("costTracking.multiCostResults.monthly");
|
||||
const periodCost = timePeriod === "day" ? result.daily_cost : result.monthly_cost;
|
||||
const periodInputCost = timePeriod === "day" ? result.daily_input_cost : result.monthly_input_cost;
|
||||
const periodOutputCost = timePeriod === "day" ? result.daily_output_cost : result.monthly_output_cost;
|
||||
|
|
@ -42,25 +45,25 @@ const SingleModelBreakdown: React.FC<{
|
|||
{loading && (
|
||||
<div className="flex items-center gap-2 text-gray-500 text-sm">
|
||||
<Spin indicator={<LoadingOutlined spin />} size="small" />
|
||||
<span>Updating...</span>
|
||||
<span>{t("costTracking.multiCostResults.updating")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500 block">Total/Request</Text>
|
||||
<Text className="text-xs text-gray-500 block">{t("costTracking.multiCostResults.totalPerRequest")}</Text>
|
||||
<Text className="text-base font-semibold text-blue-600">{formatCost(result.cost_per_request)}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500 block">Input Cost</Text>
|
||||
<Text className="text-xs text-gray-500 block">{t("costTracking.multiCostResults.inputCost")}</Text>
|
||||
<Text className="text-sm">{formatCost(result.input_cost_per_request)}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500 block">Output Cost</Text>
|
||||
<Text className="text-xs text-gray-500 block">{t("costTracking.multiCostResults.outputCost")}</Text>
|
||||
<Text className="text-sm">{formatCost(result.output_cost_per_request)}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500 block">Margin Fee</Text>
|
||||
<Text className="text-xs text-gray-500 block">{t("costTracking.multiCostResults.marginFee")}</Text>
|
||||
<Text className={`text-sm ${result.margin_cost_per_request > 0 ? "text-amber-600" : ""}`}>
|
||||
{formatCost(result.margin_cost_per_request)}
|
||||
</Text>
|
||||
|
|
@ -71,22 +74,31 @@ const SingleModelBreakdown: React.FC<{
|
|||
<div className="grid grid-cols-4 gap-4 pt-2 border-t border-gray-200">
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500 block">
|
||||
{periodLabel} Total ({formatRequests(periodRequests)} req)
|
||||
{t("costTracking.multiCostResults.periodTotal", {
|
||||
periodLabel,
|
||||
requests: formatRequests(periodRequests),
|
||||
})}
|
||||
</Text>
|
||||
<Text className={`text-base font-semibold ${timePeriod === "day" ? "text-green-600" : "text-purple-600"}`}>
|
||||
{formatCost(periodCost)}
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500 block">{periodLabel} Input</Text>
|
||||
<Text className="text-xs text-gray-500 block">
|
||||
{t("costTracking.multiCostResults.periodInput", { periodLabel })}
|
||||
</Text>
|
||||
<Text className="text-sm">{formatCost(periodInputCost)}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500 block">{periodLabel} Output</Text>
|
||||
<Text className="text-xs text-gray-500 block">
|
||||
{t("costTracking.multiCostResults.periodOutput", { periodLabel })}
|
||||
</Text>
|
||||
<Text className="text-sm">{formatCost(periodOutputCost)}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500 block">{periodLabel} Margin Fee</Text>
|
||||
<Text className="text-xs text-gray-500 block">
|
||||
{t("costTracking.multiCostResults.periodMarginFee", { periodLabel })}
|
||||
</Text>
|
||||
<Text className={`text-sm ${(periodMarginCost ?? 0) > 0 ? "text-amber-600" : ""}`}>
|
||||
{formatCost(periodMarginCost)}
|
||||
</Text>
|
||||
|
|
@ -96,13 +108,21 @@ const SingleModelBreakdown: React.FC<{
|
|||
|
||||
{(result.input_cost_per_token || result.output_cost_per_token) && (
|
||||
<div className="text-xs text-gray-400 pt-2 border-t border-gray-200">
|
||||
Token Pricing:{" "}
|
||||
{t("costTracking.multiCostResults.tokenPricingLabel")}{" "}
|
||||
{result.input_cost_per_token && (
|
||||
<span>Input ${formatNumberWithCommas(result.input_cost_per_token * 1_000_000, 2)}/1M</span>
|
||||
<span>
|
||||
{t("costTracking.multiCostResults.inputPricingPerM", {
|
||||
cost: formatNumberWithCommas(result.input_cost_per_token * 1_000_000, 2),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
{result.input_cost_per_token && result.output_cost_per_token && " | "}
|
||||
{result.output_cost_per_token && (
|
||||
<span>Output ${formatNumberWithCommas(result.output_cost_per_token * 1_000_000, 2)}/1M</span>
|
||||
<span>
|
||||
{t("costTracking.multiCostResults.outputPricingPerM", {
|
||||
cost: formatNumberWithCommas(result.output_cost_per_token * 1_000_000, 2),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -111,6 +131,7 @@ const SingleModelBreakdown: React.FC<{
|
|||
};
|
||||
|
||||
const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePeriod }) => {
|
||||
const { t } = useTranslation();
|
||||
const [expandedModels, setExpandedModels] = useState<Set<string>>(new Set());
|
||||
|
||||
const validEntries = multiResult.entries.filter((e) => e.result !== null);
|
||||
|
|
@ -124,7 +145,7 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
|
|||
if (!hasAnyResult && !isAnyLoading && !hasAnyError) {
|
||||
return (
|
||||
<div className="py-6 text-center border border-dashed border-gray-300 rounded-lg bg-gray-50">
|
||||
<Text className="text-gray-500">Select models above to see cost estimates</Text>
|
||||
<Text className="text-gray-500">{t("costTracking.multiCostResults.selectModelsHint")}</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -134,7 +155,7 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
|
|||
return (
|
||||
<div className="py-6 text-center">
|
||||
<Spin indicator={<LoadingOutlined spin />} />
|
||||
<Text className="text-gray-500 block mt-2">Calculating costs...</Text>
|
||||
<Text className="text-gray-500 block mt-2">{t("costTracking.multiCostResults.calculatingCosts")}</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -145,13 +166,15 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
|
|||
<div className="space-y-4">
|
||||
<Divider className="my-4" />
|
||||
<div className="flex items-center justify-between">
|
||||
<Text className="text-base font-semibold text-gray-900">Cost Estimates</Text>
|
||||
<Text className="text-base font-semibold text-gray-900">
|
||||
{t("costTracking.multiCostResults.costEstimatesTitle")}
|
||||
</Text>
|
||||
{isAnyLoading && <Spin indicator={<LoadingOutlined spin />} size="small" />}
|
||||
</div>
|
||||
{/* Error Messages */}
|
||||
{errorEntries.map((e) => (
|
||||
<div key={e.entry.id} className="text-sm text-red-600 bg-red-50 p-3 rounded-lg border border-red-200">
|
||||
<span className="font-medium">{e.entry.model || "Unknown model"}: </span>
|
||||
<span className="font-medium">{e.entry.model || t("costTracking.multiCostResults.unknownModel")}: </span>
|
||||
{e.error}
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -173,12 +196,13 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
|
|||
|
||||
const hasMargin = multiResult.totals.margin_per_request > 0;
|
||||
|
||||
const periodLabel = timePeriod === "day" ? "Daily" : "Monthly";
|
||||
const periodLabel =
|
||||
timePeriod === "day" ? t("costTracking.multiCostResults.daily") : t("costTracking.multiCostResults.monthly");
|
||||
const periodCostKey = timePeriod === "day" ? "daily_cost" : "monthly_cost";
|
||||
|
||||
const summaryColumns = [
|
||||
{
|
||||
title: "Model",
|
||||
title: t("costTracking.multiCostResults.colModel"),
|
||||
dataIndex: "model",
|
||||
key: "model",
|
||||
render: (
|
||||
|
|
@ -204,14 +228,14 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
|
|||
{record.error && <div className="text-xs text-red-600 bg-red-50 px-2 py-1 rounded">⚠️ {record.error}</div>}
|
||||
{record.hasZeroCost && !record.error && (
|
||||
<div className="text-xs text-amber-600 bg-amber-50 px-2 py-1 rounded">
|
||||
⚠️ No pricing data found for this model. Set base_model in config.
|
||||
⚠️ {t("costTracking.multiCostResults.noPricingData")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Per Request",
|
||||
title: t("costTracking.multiCostResults.colPerRequest"),
|
||||
dataIndex: "cost_per_request",
|
||||
key: "cost_per_request",
|
||||
align: "right" as const,
|
||||
|
|
@ -223,7 +247,7 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Margin Fee",
|
||||
title: t("costTracking.multiCostResults.colMarginFee"),
|
||||
dataIndex: "margin_cost_per_request",
|
||||
key: "margin_cost_per_request",
|
||||
align: "right" as const,
|
||||
|
|
@ -287,7 +311,9 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
|
|||
<Divider className="my-4" />
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Text className="text-base font-semibold text-gray-900">Cost Estimates</Text>
|
||||
<Text className="text-base font-semibold text-gray-900">
|
||||
{t("costTracking.multiCostResults.costEstimatesTitle")}
|
||||
</Text>
|
||||
<div className="flex items-center gap-2">
|
||||
{isAnyLoading && <Spin indicator={<LoadingOutlined spin />} size="small" />}
|
||||
<MultiExportDropdown multiResult={multiResult} />
|
||||
|
|
@ -299,14 +325,14 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
|
|||
<Row gutter={[16, 8]}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Statistic
|
||||
title={<span className="text-xs">Total Per Request</span>}
|
||||
title={<span className="text-xs">{t("costTracking.multiCostResults.totalPerRequest")}</span>}
|
||||
value={formatCost(multiResult.totals.cost_per_request)}
|
||||
valueStyle={{ color: "#1890ff", fontSize: "18px", fontFamily: "monospace" }}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<Statistic
|
||||
title={<span className="text-xs">Total {periodLabel}</span>}
|
||||
title={<span className="text-xs">{t("costTracking.multiCostResults.totalPeriod", { periodLabel })}</span>}
|
||||
value={formatCost(timePeriod === "day" ? multiResult.totals.daily_cost : multiResult.totals.monthly_cost)}
|
||||
valueStyle={{
|
||||
color: timePeriod === "day" ? "#52c41a" : "#722ed1",
|
||||
|
|
@ -319,13 +345,15 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
|
|||
{hasMargin && (
|
||||
<Row gutter={[16, 8]} className="mt-3 pt-3 border-t border-slate-200">
|
||||
<Col xs={24} sm={12}>
|
||||
<div className="text-xs text-gray-500">Margin Fee/Request</div>
|
||||
<div className="text-xs text-gray-500">{t("costTracking.multiCostResults.marginFeePerRequest")}</div>
|
||||
<div className="text-sm font-mono text-amber-600">
|
||||
{formatCost(multiResult.totals.margin_per_request)}
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<div className="text-xs text-gray-500">{periodLabel} Margin Fee</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{t("costTracking.multiCostResults.periodMarginFeeTotal", { periodLabel })}
|
||||
</div>
|
||||
<div className="text-sm font-mono text-amber-600">
|
||||
{formatCost(timePeriod === "day" ? multiResult.totals.daily_margin : multiResult.totals.monthly_margin)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { Button } from "@tremor/react";
|
||||
import { DownloadOutlined, FilePdfOutlined, FileExcelOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MultiModelResult } from "./types";
|
||||
import { exportMultiToPDF, exportMultiToCSV } from "./multi_export_utils";
|
||||
|
||||
|
|
@ -9,6 +10,7 @@ interface MultiExportDropdownProps {
|
|||
}
|
||||
|
||||
const MultiExportDropdown: React.FC<MultiExportDropdownProps> = ({ multiResult }) => {
|
||||
const { t } = useTranslation();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
@ -37,7 +39,7 @@ const MultiExportDropdown: React.FC<MultiExportDropdownProps> = ({ multiResult }
|
|||
return (
|
||||
<div className="relative inline-block" ref={menuRef}>
|
||||
<Button size="xs" variant="secondary" icon={DownloadOutlined} onClick={() => setIsOpen(!isOpen)}>
|
||||
Export
|
||||
{t("common.export")}
|
||||
</Button>
|
||||
|
||||
{isOpen && (
|
||||
|
|
@ -50,7 +52,7 @@ const MultiExportDropdown: React.FC<MultiExportDropdownProps> = ({ multiResult }
|
|||
}}
|
||||
>
|
||||
<FilePdfOutlined className="mr-3 text-red-500" />
|
||||
Export as PDF
|
||||
{t("costTracking.multiExportDropdown.exportAsPdf")}
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
|
|
@ -60,7 +62,7 @@ const MultiExportDropdown: React.FC<MultiExportDropdownProps> = ({ multiResult }
|
|||
}}
|
||||
>
|
||||
<FileExcelOutlined className="mr-3 text-green-600" />
|
||||
Export as CSV
|
||||
{t("costTracking.multiExportDropdown.exportAsCsv")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4669,5 +4669,130 @@
|
|||
"baseModelHelp": "The actual model your azure deployment uses. Used for accurate cost tracking. Select name from",
|
||||
"baseModelHelpLink": "here"
|
||||
}
|
||||
},
|
||||
"costTracking": {
|
||||
"addMarginForm": {
|
||||
"providerLabel": "Provider",
|
||||
"providerTooltip": "Select 'Global' to apply margin to all providers, or select a specific provider",
|
||||
"providerRequired": "Please select a provider",
|
||||
"providerPlaceholder": "Select provider or 'Global'",
|
||||
"globalOption": "Global (All Providers)",
|
||||
"marginTypeLabel": "Margin Type",
|
||||
"marginTypeTooltip": "Choose how to apply the margin: percentage-based or fixed amount",
|
||||
"marginTypeRequired": "Please select a margin type",
|
||||
"percentageBased": "Percentage-based",
|
||||
"fixedAmount": "Fixed Amount",
|
||||
"marginPercentageLabel": "Margin Percentage",
|
||||
"marginPercentageTooltip": "Enter a percentage value (e.g., 10 for 10% margin)",
|
||||
"percentageRequired": "Please enter a margin percentage",
|
||||
"percentageRangeError": "Percentage must be between 0 and 1000",
|
||||
"fixedAmountLabel": "Fixed Margin Amount",
|
||||
"fixedAmountTooltip": "Enter a fixed amount in USD (e.g., 0.001 for $0.001 per request)",
|
||||
"fixedAmountRequired": "Please enter a fixed amount",
|
||||
"fixedAmountNonNegative": "Fixed amount must be non-negative",
|
||||
"addButton": "Add Provider Margin"
|
||||
},
|
||||
"addProviderForm": {
|
||||
"providerLabel": "Provider",
|
||||
"providerTooltip": "Select the LLM provider you want to configure a discount for",
|
||||
"providerRequired": "Please select a provider",
|
||||
"providerPlaceholder": "Select provider",
|
||||
"discountPercentageLabel": "Discount Percentage",
|
||||
"discountPercentageTooltip": "Enter a percentage value (e.g., 5 for 5% discount)",
|
||||
"discountRequired": "Please enter a discount percentage",
|
||||
"addButton": "Add Provider Discount"
|
||||
},
|
||||
"costTrackingSettings": {
|
||||
"pageTitle": "Cost Tracking Settings",
|
||||
"pageSubtitle": "Configure cost discounts and margins for different LLM providers. Changes are saved automatically.",
|
||||
"providerDiscountsTitle": "Provider Discounts",
|
||||
"providerDiscountsSubtitle": "Apply percentage-based discounts to reduce costs for specific providers",
|
||||
"discountsTab": "Discounts",
|
||||
"testItTab": "Test It",
|
||||
"addProviderDiscountButton": "+ Add Provider Discount",
|
||||
"noDiscountsConfigured": "No provider discounts configured",
|
||||
"noDiscountsHint": "Click \"Add Provider Discount\" to get started",
|
||||
"feePriceMarginTitle": "Fee/Price Margin",
|
||||
"feePriceMarginSubtitle": "Add fees or margins to LLM costs for internal billing and cost recovery",
|
||||
"addProviderMarginButton": "+ Add Provider Margin",
|
||||
"noMarginsConfigured": "No provider margins configured",
|
||||
"noMarginsHint": "Click \"Add Provider Margin\" to get started",
|
||||
"pricingCalculatorTitle": "Pricing Calculator",
|
||||
"pricingCalculatorSubtitle": "Estimate LLM costs based on expected token usage and request volume",
|
||||
"removeDiscountTitle": "Remove Provider Discount",
|
||||
"removeDiscountContent": "Are you sure you want to remove the discount for {{providerDisplayName}}?",
|
||||
"removeMarginTitle": "Remove Provider Margin",
|
||||
"removeMarginContent": "Are you sure you want to remove the margin for {{providerDisplayName}}?",
|
||||
"removeOkText": "Remove",
|
||||
"addDiscountModalTitle": "Add Provider Discount",
|
||||
"addDiscountModalDesc": "Select a provider and set its discount percentage. Enter a value between 0% and 100% (e.g., 5 for a 5% discount).",
|
||||
"addMarginModalTitle": "Add Provider Margin",
|
||||
"addMarginModalDesc": "Select a provider (or \"Global\" for all providers) and configure the margin. You can use percentage-based or fixed amount."
|
||||
},
|
||||
"howItWorks": {
|
||||
"costCalculationTitle": "Cost Calculation",
|
||||
"costCalculationDesc": "Discounts are applied to provider costs:",
|
||||
"exampleTitle": "Example",
|
||||
"exampleDesc": "A 5% discount on a $10.00 request results in: $10.00 × (1 - 0.05) = $9.50",
|
||||
"validRangeTitle": "Valid Range",
|
||||
"validRangeDesc": "Discount percentages must be between 0% and 100%",
|
||||
"validatingDiscountsTitle": "Validating Discounts",
|
||||
"validatingDiscountsDesc": "Make a test request and check the response headers to verify discounts are applied:",
|
||||
"lookForHeaders": "Look for these headers in the response:",
|
||||
"headerResponseCost": "Final cost after discount",
|
||||
"headerOriginalCost": "Original cost before discount",
|
||||
"headerDiscountAmount": "Amount discounted",
|
||||
"discountCalculatorTitle": "Discount Calculator",
|
||||
"discountCalculatorDesc": "Enter values from your response headers to verify the discount:",
|
||||
"responseCostLabel": "Response Cost (x-litellm-response-cost)",
|
||||
"discountAmountLabel": "Discount Amount (x-litellm-response-cost-discount-amount)",
|
||||
"calculatedResultsTitle": "Calculated Results",
|
||||
"originalCostLabel": "Original Cost:",
|
||||
"finalCostLabel": "Final Cost:",
|
||||
"discountAmountResultLabel": "Discount Amount:",
|
||||
"discountAppliedLabel": "Discount Applied:"
|
||||
},
|
||||
"pricingCalculator": {
|
||||
"colModel": "Model",
|
||||
"colInputTokens": "Input Tokens",
|
||||
"colOutputTokens": "Output Tokens",
|
||||
"colRequestsPerDay": "Requests/Day",
|
||||
"colRequestsPerMonth": "Requests/Month",
|
||||
"modelPlaceholder": "Select a model",
|
||||
"perDay": "Per Day",
|
||||
"perMonth": "Per Month",
|
||||
"addAnotherModel": "Add Another Model"
|
||||
},
|
||||
"multiCostResults": {
|
||||
"selectModelsHint": "Select models above to see cost estimates",
|
||||
"calculatingCosts": "Calculating costs...",
|
||||
"costEstimatesTitle": "Cost Estimates",
|
||||
"unknownModel": "Unknown model",
|
||||
"noPricingData": "No pricing data found for this model. Set base_model in config.",
|
||||
"colModel": "Model",
|
||||
"colPerRequest": "Per Request",
|
||||
"colMarginFee": "Margin Fee",
|
||||
"totalPerRequest": "Total Per Request",
|
||||
"totalPeriod": "Total {{periodLabel}}",
|
||||
"marginFeePerRequest": "Margin Fee/Request",
|
||||
"periodMarginFeeTotal": "{{periodLabel}} Margin Fee",
|
||||
"updating": "Updating...",
|
||||
"daily": "Daily",
|
||||
"monthly": "Monthly",
|
||||
"inputCost": "Input Cost",
|
||||
"outputCost": "Output Cost",
|
||||
"marginFee": "Margin Fee",
|
||||
"periodTotal": "{{periodLabel}} Total ({{requests}} req)",
|
||||
"periodInput": "{{periodLabel}} Input",
|
||||
"periodOutput": "{{periodLabel}} Output",
|
||||
"periodMarginFee": "{{periodLabel}} Margin Fee",
|
||||
"tokenPricingLabel": "Token Pricing:",
|
||||
"inputPricingPerM": "Input ${{cost}}/1M",
|
||||
"outputPricingPerM": "Output ${{cost}}/1M"
|
||||
},
|
||||
"multiExportDropdown": {
|
||||
"exportAsPdf": "Export as PDF",
|
||||
"exportAsCsv": "Export as CSV"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4669,5 +4669,130 @@
|
|||
"baseModelHelp": "您的 Azure 部署实际使用的模型。用于精确的成本追踪。请从",
|
||||
"baseModelHelpLink": "此处"
|
||||
}
|
||||
},
|
||||
"costTracking": {
|
||||
"addMarginForm": {
|
||||
"providerLabel": "提供商",
|
||||
"providerTooltip": "选择「全局」以对所有提供商应用利润率,或选择特定提供商",
|
||||
"providerRequired": "请选择提供商",
|
||||
"providerPlaceholder": "选择提供商或「全局」",
|
||||
"globalOption": "全局(所有提供商)",
|
||||
"marginTypeLabel": "利润率类型",
|
||||
"marginTypeTooltip": "选择利润率的应用方式:百分比或固定金额",
|
||||
"marginTypeRequired": "请选择利润率类型",
|
||||
"percentageBased": "百分比",
|
||||
"fixedAmount": "固定金额",
|
||||
"marginPercentageLabel": "利润率百分比",
|
||||
"marginPercentageTooltip": "输入百分比值(例如:10 表示 10% 利润率)",
|
||||
"percentageRequired": "请输入利润率百分比",
|
||||
"percentageRangeError": "百分比必须在 0 到 1000 之间",
|
||||
"fixedAmountLabel": "固定利润率金额",
|
||||
"fixedAmountTooltip": "输入以美元为单位的固定金额(例如:0.001 表示每次请求 $0.001)",
|
||||
"fixedAmountRequired": "请输入固定金额",
|
||||
"fixedAmountNonNegative": "固定金额不能为负数",
|
||||
"addButton": "添加提供商利润率"
|
||||
},
|
||||
"addProviderForm": {
|
||||
"providerLabel": "提供商",
|
||||
"providerTooltip": "选择要配置折扣的 LLM 提供商",
|
||||
"providerRequired": "请选择提供商",
|
||||
"providerPlaceholder": "选择提供商",
|
||||
"discountPercentageLabel": "折扣百分比",
|
||||
"discountPercentageTooltip": "输入百分比值(例如:5 表示 5% 折扣)",
|
||||
"discountRequired": "请输入折扣百分比",
|
||||
"addButton": "添加提供商折扣"
|
||||
},
|
||||
"costTrackingSettings": {
|
||||
"pageTitle": "成本追踪设置",
|
||||
"pageSubtitle": "为不同 LLM 提供商配置成本折扣和利润率,更改将自动保存。",
|
||||
"providerDiscountsTitle": "提供商折扣",
|
||||
"providerDiscountsSubtitle": "应用百分比折扣以降低特定提供商的成本",
|
||||
"discountsTab": "折扣",
|
||||
"testItTab": "测试",
|
||||
"addProviderDiscountButton": "+ 添加提供商折扣",
|
||||
"noDiscountsConfigured": "未配置提供商折扣",
|
||||
"noDiscountsHint": "点击「添加提供商折扣」开始配置",
|
||||
"feePriceMarginTitle": "费用/价格利润率",
|
||||
"feePriceMarginSubtitle": "在 LLM 成本上增加费用或利润率,用于内部计费和成本回收",
|
||||
"addProviderMarginButton": "+ 添加提供商利润率",
|
||||
"noMarginsConfigured": "未配置提供商利润率",
|
||||
"noMarginsHint": "点击「添加提供商利润率」开始配置",
|
||||
"pricingCalculatorTitle": "价格计算器",
|
||||
"pricingCalculatorSubtitle": "根据预期的 Token 用量和请求量估算 LLM 成本",
|
||||
"removeDiscountTitle": "移除提供商折扣",
|
||||
"removeDiscountContent": "确定要移除 {{providerDisplayName}} 的折扣吗?",
|
||||
"removeMarginTitle": "移除提供商利润率",
|
||||
"removeMarginContent": "确定要移除 {{providerDisplayName}} 的利润率吗?",
|
||||
"removeOkText": "移除",
|
||||
"addDiscountModalTitle": "添加提供商折扣",
|
||||
"addDiscountModalDesc": "选择提供商并设置折扣百分比,输入 0% 到 100% 之间的值(例如:5 表示 5% 折扣)。",
|
||||
"addMarginModalTitle": "添加提供商利润率",
|
||||
"addMarginModalDesc": "选择提供商(或选择「全局」以覆盖所有提供商)并配置利润率,可使用百分比或固定金额。"
|
||||
},
|
||||
"howItWorks": {
|
||||
"costCalculationTitle": "成本计算",
|
||||
"costCalculationDesc": "折扣应用于提供商成本:",
|
||||
"exampleTitle": "示例",
|
||||
"exampleDesc": "对 $10.00 的请求应用 5% 折扣后:$10.00 × (1 - 0.05) = $9.50",
|
||||
"validRangeTitle": "有效范围",
|
||||
"validRangeDesc": "折扣百分比必须在 0% 到 100% 之间",
|
||||
"validatingDiscountsTitle": "验证折扣",
|
||||
"validatingDiscountsDesc": "发送测试请求并检查响应头以验证折扣是否已应用:",
|
||||
"lookForHeaders": "在响应中查找以下响应头:",
|
||||
"headerResponseCost": "折扣后的最终成本",
|
||||
"headerOriginalCost": "折扣前的原始成本",
|
||||
"headerDiscountAmount": "已折扣金额",
|
||||
"discountCalculatorTitle": "折扣计算器",
|
||||
"discountCalculatorDesc": "输入响应头中的值以验证折扣:",
|
||||
"responseCostLabel": "响应成本 (x-litellm-response-cost)",
|
||||
"discountAmountLabel": "折扣金额 (x-litellm-response-cost-discount-amount)",
|
||||
"calculatedResultsTitle": "计算结果",
|
||||
"originalCostLabel": "原始成本:",
|
||||
"finalCostLabel": "最终成本:",
|
||||
"discountAmountResultLabel": "折扣金额:",
|
||||
"discountAppliedLabel": "已应用折扣:"
|
||||
},
|
||||
"pricingCalculator": {
|
||||
"colModel": "模型",
|
||||
"colInputTokens": "输入 Token",
|
||||
"colOutputTokens": "输出 Token",
|
||||
"colRequestsPerDay": "请求数/天",
|
||||
"colRequestsPerMonth": "请求数/月",
|
||||
"modelPlaceholder": "选择模型",
|
||||
"perDay": "每天",
|
||||
"perMonth": "每月",
|
||||
"addAnotherModel": "添加另一个模型"
|
||||
},
|
||||
"multiCostResults": {
|
||||
"selectModelsHint": "在上方选择模型以查看成本估算",
|
||||
"calculatingCosts": "正在计算成本...",
|
||||
"costEstimatesTitle": "成本估算",
|
||||
"unknownModel": "未知模型",
|
||||
"noPricingData": "未找到此模型的定价数据,请在配置中设置 base_model。",
|
||||
"colModel": "模型",
|
||||
"colPerRequest": "每次请求",
|
||||
"colMarginFee": "利润率费用",
|
||||
"totalPerRequest": "每次请求总计",
|
||||
"totalPeriod": "{{periodLabel}}总计",
|
||||
"marginFeePerRequest": "利润率费用/请求",
|
||||
"periodMarginFeeTotal": "{{periodLabel}}利润率费用",
|
||||
"updating": "更新中...",
|
||||
"daily": "每日",
|
||||
"monthly": "每月",
|
||||
"inputCost": "输入成本",
|
||||
"outputCost": "输出成本",
|
||||
"marginFee": "利润率费用",
|
||||
"periodTotal": "{{periodLabel}}总计({{requests}} 次请求)",
|
||||
"periodInput": "{{periodLabel}}输入",
|
||||
"periodOutput": "{{periodLabel}}输出",
|
||||
"periodMarginFee": "{{periodLabel}}利润率费用",
|
||||
"tokenPricingLabel": "Token 定价:",
|
||||
"inputPricingPerM": "输入 ${{cost}}/1M",
|
||||
"outputPricingPerM": "输出 ${{cost}}/1M"
|
||||
},
|
||||
"multiExportDropdown": {
|
||||
"exportAsPdf": "导出为 PDF",
|
||||
"exportAsCsv": "导出为 CSV"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue