From afa9c650fe8950c1af9aac19524b0a8583cc1678 Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Thu, 17 Jul 2025 19:07:11 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=B1=20feat(model-pricing):=20add=20cur?= =?UTF-8?q?rency=20(USD/CNY)=20&=20token=20unit=20(M/K)=20toggles=20to=20M?= =?UTF-8?q?odel=20Pricing=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduced a currency switch to toggle prices between USD and CNY. * CNY prices are calculated by multiplying USD prices with the site-wide `price` rate from `/api/status`. * Added a second switch to display prices per 1 M tokens or per 1 K tokens. * When “K” is selected, prices are divided by 1 000 and labels are updated accordingly. * Extended component state with `currency` and `tokenUnit` variables. * Integrated `StatusContext` to retrieve and memoize the current exchange rate. * Updated price rendering logic and labels to reflect selected currency and token unit. * Minor UI tweaks: kept Switch components compact and aligned with the table header. No breaking changes. --- web/src/components/table/ModelPricing.js | 59 +++++++++++++++++++----- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/web/src/components/table/ModelPricing.js b/web/src/components/table/ModelPricing.js index cf498e70..6e7dab87 100644 --- a/web/src/components/table/ModelPricing.js +++ b/web/src/components/table/ModelPricing.js @@ -16,7 +16,8 @@ import { Card, Tabs, TabPane, - Empty + Empty, + Switch } from '@douyinfe/semi-ui'; import { IllustrationNoResult, @@ -32,6 +33,7 @@ import { } from '@douyinfe/semi-icons'; import { UserContext } from '../../context/User/index.js'; import { AlertCircle } from 'lucide-react'; +import { StatusContext } from '../../context/Status/index.js'; const ModelPricing = () => { const { t } = useTranslation(); @@ -44,6 +46,13 @@ const ModelPricing = () => { const [activeKey, setActiveKey] = useState('all'); const [pageSize, setPageSize] = useState(10); + const [currency, setCurrency] = useState('USD'); + const [tokenUnit, setTokenUnit] = useState('M'); + const [statusState] = useContext(StatusContext); + const priceRate = useMemo(() => { + return statusState?.status?.price || 1; + }, [statusState]); + const rowSelection = useMemo( () => ({ onChange: (selectedRowKeys, selectedRows) => { @@ -245,33 +254,61 @@ const ModelPricing = () => { }, }, { - title: t('模型价格'), + title: ( +
+ {t('模型价格')} + {/* 货币切换 */} + setCurrency(checked ? 'RMB' : 'USD')} + checkedText="¥" + uncheckedText="$" + /> + {/* 计费单位切换 */} + setTokenUnit(checked ? 'K' : 'M')} + checkedText="K" + uncheckedText="M" + /> +
+ ), dataIndex: 'model_price', render: (text, record, index) => { let content = text; if (record.quota_type === 0) { - let inputRatioPrice = - record.model_ratio * 2 * groupRatio[selectedGroup]; + let inputRatioPrice = record.model_ratio * 2 * groupRatio[selectedGroup]; let completionRatioPrice = - record.model_ratio * - record.completion_ratio * - 2 * - groupRatio[selectedGroup]; + record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup]; + + if (currency === 'RMB') { + inputRatioPrice = inputRatioPrice * priceRate; + completionRatioPrice = completionRatioPrice * priceRate; + } + + const unitDivisor = tokenUnit === 'K' ? 1000 : 1; + const unitLabel = tokenUnit === 'K' ? 'K' : 'M'; + inputRatioPrice = inputRatioPrice / unitDivisor; + completionRatioPrice = completionRatioPrice / unitDivisor; content = (
- {t('提示')} ${inputRatioPrice.toFixed(3)} / 1M tokens + {t('提示')} {currency === 'USD' ? '$' : '¥'}{inputRatioPrice.toFixed(3)} / 1{unitLabel} tokens
- {t('补全')} ${completionRatioPrice.toFixed(3)} / 1M tokens + {t('补全')} {currency === 'USD' ? '$' : '¥'}{completionRatioPrice.toFixed(3)} / 1{unitLabel} tokens
); } else { let price = parseFloat(text) * groupRatio[selectedGroup]; + + if (currency === 'RMB') { + price = price * priceRate; + } content = (
- {t('模型价格')}:${price.toFixed(3)} + {t('模型价格')}:{currency === 'USD' ? '$' : '¥'}{price.toFixed(3)}
); }