feat(ui): i18n for cost tracking settings

This commit is contained in:
吹雪 2026-06-11 00:57:29 +09:00
parent 6a21cb423b
commit 65f3306f46
9 changed files with 443 additions and 127 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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 &quot;Add Provider Discount&quot; 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 &quot;Add Provider Margin&quot; 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 &quot;Global&quot; 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

View file

@ -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>

View file

@ -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>
)}
/>

View file

@ -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>

View file

@ -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>
)}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}