From d7965788800946829c23f090221df38cd252ae36 Mon Sep 17 00:00:00 2001 From: CaIon Date: Fri, 6 Mar 2026 22:33:51 +0800 Subject: [PATCH] fix: unify pricing labels and expand marketplace pricing display Keep the model pricing editor wording aligned with the new price-based UI while exposing cache, image, and audio pricing in the marketplace so users can see the full configured pricing model. --- model/pricing.go | 24 +++- .../modal/components/ModelPricingTable.jsx | 65 +++------ .../view/card/PricingCardView.jsx | 2 +- .../view/table/PricingTableColumns.jsx | 29 ++-- web/src/helpers/utils.jsx | 134 ++++++++++++++---- web/src/i18n/locales/en.json | 7 +- web/src/i18n/locales/fr.json | 7 +- web/src/i18n/locales/ja.json | 7 +- web/src/i18n/locales/ru.json | 7 +- web/src/i18n/locales/vi.json | 7 +- web/src/i18n/locales/zh-CN.json | 3 +- web/src/i18n/locales/zh-TW.json | 3 +- .../Ratio/components/ModelPricingEditor.jsx | 2 +- 13 files changed, 183 insertions(+), 114 deletions(-) diff --git a/model/pricing.go b/model/pricing.go index cb687d04a..54ae98451 100644 --- a/model/pricing.go +++ b/model/pricing.go @@ -25,6 +25,11 @@ type Pricing struct { ModelPrice float64 `json:"model_price"` OwnerBy string `json:"owner_by"` CompletionRatio float64 `json:"completion_ratio"` + CacheRatio *float64 `json:"cache_ratio,omitempty"` + CreateCacheRatio *float64 `json:"create_cache_ratio,omitempty"` + ImageRatio *float64 `json:"image_ratio,omitempty"` + AudioRatio *float64 `json:"audio_ratio,omitempty"` + AudioCompletionRatio *float64 `json:"audio_completion_ratio,omitempty"` EnableGroup []string `json:"enable_groups"` SupportedEndpointTypes []constant.EndpointType `json:"supported_endpoint_types"` PricingVersion string `json:"pricing_version,omitempty"` @@ -297,12 +302,29 @@ func updatePricing() { pricing.CompletionRatio = ratio_setting.GetCompletionRatio(model) pricing.QuotaType = 0 } + if cacheRatio, ok := ratio_setting.GetCacheRatio(model); ok { + pricing.CacheRatio = &cacheRatio + } + if createCacheRatio, ok := ratio_setting.GetCreateCacheRatio(model); ok { + pricing.CreateCacheRatio = &createCacheRatio + } + if imageRatio, ok := ratio_setting.GetImageRatio(model); ok { + pricing.ImageRatio = &imageRatio + } + if ratio_setting.ContainsAudioRatio(model) { + audioRatio := ratio_setting.GetAudioRatio(model) + pricing.AudioRatio = &audioRatio + } + if ratio_setting.ContainsAudioCompletionRatio(model) { + audioCompletionRatio := ratio_setting.GetAudioCompletionRatio(model) + pricing.AudioCompletionRatio = &audioCompletionRatio + } pricingMap = append(pricingMap, pricing) } // 防止大更新后数据不通用 if len(pricingMap) > 0 { - pricingMap[0].PricingVersion = "82c4a357505fff6fee8462c3f7ec8a645bb95532669cb73b2cabee6a416ec24f" + pricingMap[0].PricingVersion = "5a90f2b86c08bd983a9a2e6d66c255f4eaef9c4bc934386d2b6ae84ef0ff1f1f" } // 刷新缓存映射,供高并发快速查询 diff --git a/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx b/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx index 266bbf947..d4b923d01 100644 --- a/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx +++ b/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx @@ -20,7 +20,7 @@ For commercial licensing, please contact support@quantumnous.com import React from 'react'; import { Card, Avatar, Typography, Table, Tag } from '@douyinfe/semi-ui'; import { IconCoinMoneyStroked } from '@douyinfe/semi-icons'; -import { calculateModelPrice } from '../../../../../helpers'; +import { calculateModelPrice, getModelPriceItems } from '../../../../../helpers'; const { Text } = Typography; @@ -74,12 +74,7 @@ const ModelPricingTable = ({ : modelData?.quota_type === 1 ? t('按次计费') : '-', - inputPrice: modelData?.quota_type === 0 ? priceData.inputPrice : '-', - outputPrice: - modelData?.quota_type === 0 - ? priceData.completionPrice || priceData.outputPrice - : '-', - fixedPrice: modelData?.quota_type === 1 ? priceData.price : '-', + priceItems: getModelPriceItems(priceData, t), }; }); @@ -126,48 +121,22 @@ const ModelPricingTable = ({ }, }); - // 根据计费类型添加价格列 - if (modelData?.quota_type === 0) { - // 按量计费 - columns.push( - { - title: t('提示'), - dataIndex: 'inputPrice', - render: (text) => ( - <> -
{text}
-
- / {tokenUnit === 'K' ? '1K' : '1M'} tokens + columns.push({ + title: t('价格摘要'), + dataIndex: 'priceItems', + render: (items) => ( +
+ {items.map((item) => ( +
+
+ {item.label} {item.value}
- - ), - }, - { - title: t('补全'), - dataIndex: 'outputPrice', - render: (text) => ( - <> -
{text}
-
- / {tokenUnit === 'K' ? '1K' : '1M'} tokens -
- - ), - }, - ); - } else { - // 按次计费 - columns.push({ - title: t('价格'), - dataIndex: 'fixedPrice', - render: (text) => ( - <> -
{text}
-
/ 次
- - ), - }); - } +
{item.suffix}
+
+ ))} +
+ ), + }); return ( {model.model_name} -
+
{formatPriceInfo(priceData, t)}
diff --git a/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx b/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx index 5ab8af9f4..362bf5a44 100644 --- a/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx +++ b/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx @@ -24,6 +24,7 @@ import { renderModelTag, stringToColor, calculateModelPrice, + getModelPriceItems, getLobeHubIcon, } from '../../../../../helpers'; import { @@ -231,26 +232,18 @@ export const getPricingTableColumns = ({ ...(isMobile ? {} : { fixed: 'right' }), render: (text, record, index) => { const priceData = getPriceData(record); + const priceItems = getModelPriceItems(priceData, t); - if (priceData.isPerToken) { - return ( -
-
- {t('输入')} {priceData.inputPrice} / 1{priceData.unitLabel} tokens + return ( +
+ {priceItems.map((item) => ( +
+ {item.label} {item.value} + {item.suffix}
-
- {t('输出')} {priceData.completionPrice} / 1{priceData.unitLabel}{' '} - tokens -
-
- ); - } else { - return ( -
- {t('模型价格')}:{priceData.price} -
- ); - } + ))} +
+ ); }, }; diff --git a/web/src/helpers/utils.jsx b/web/src/helpers/utils.jsx index 5ce83e678..6fea484d5 100644 --- a/web/src/helpers/utils.jsx +++ b/web/src/helpers/utils.jsx @@ -648,20 +648,9 @@ export const calculateModelPrice = ({ if (record.quota_type === 0) { // 按量计费 const inputRatioPriceUSD = record.model_ratio * 2 * usedGroupRatio; - const completionRatioPriceUSD = - record.model_ratio * record.completion_ratio * 2 * usedGroupRatio; - const unitDivisor = tokenUnit === 'K' ? 1000 : 1; const unitLabel = tokenUnit === 'K' ? 'K' : 'M'; - const rawDisplayInput = displayPrice(inputRatioPriceUSD); - const rawDisplayCompletion = displayPrice(completionRatioPriceUSD); - - const numInput = - parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor; - const numCompletion = - parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor; - let symbol = '$'; if (currency === 'CNY') { symbol = '¥'; @@ -678,9 +667,48 @@ export const calculateModelPrice = ({ symbol = '¤'; } } + + const formatTokenPrice = (priceUSD) => { + const rawDisplayPrice = displayPrice(priceUSD); + const numericPrice = + parseFloat(rawDisplayPrice.replace(/[^0-9.]/g, '')) / unitDivisor; + return `${symbol}${numericPrice.toFixed(precision)}`; + }; + + const hasRatioValue = (value) => + value !== undefined && + value !== null && + value !== '' && + Number.isFinite(Number(value)); + + const inputPrice = formatTokenPrice(inputRatioPriceUSD); + const audioInputPrice = hasRatioValue(record.audio_ratio) + ? formatTokenPrice(inputRatioPriceUSD * Number(record.audio_ratio)) + : null; + return { - inputPrice: `${symbol}${numInput.toFixed(precision)}`, - completionPrice: `${symbol}${numCompletion.toFixed(precision)}`, + inputPrice, + completionPrice: formatTokenPrice( + inputRatioPriceUSD * Number(record.completion_ratio), + ), + cachePrice: hasRatioValue(record.cache_ratio) + ? formatTokenPrice(inputRatioPriceUSD * Number(record.cache_ratio)) + : null, + createCachePrice: hasRatioValue(record.create_cache_ratio) + ? formatTokenPrice(inputRatioPriceUSD * Number(record.create_cache_ratio)) + : null, + imagePrice: hasRatioValue(record.image_ratio) + ? formatTokenPrice(inputRatioPriceUSD * Number(record.image_ratio)) + : null, + audioInputPrice, + audioOutputPrice: + audioInputPrice && hasRatioValue(record.audio_completion_ratio) + ? formatTokenPrice( + inputRatioPriceUSD * + Number(record.audio_ratio) * + Number(record.audio_completion_ratio), + ) + : null, unitLabel, isPerToken: true, usedGroup, @@ -710,26 +738,76 @@ export const calculateModelPrice = ({ }; }; -// 格式化价格信息(用于卡片视图) -export const formatPriceInfo = (priceData, t) => { +export const getModelPriceItems = (priceData, t) => { if (priceData.isPerToken) { - return ( - <> - - {t('输入')} {priceData.inputPrice}/{priceData.unitLabel} - - - {t('输出')} {priceData.completionPrice}/{priceData.unitLabel} - - - ); + const unitSuffix = ` / 1${priceData.unitLabel} Tokens`; + return [ + { + key: 'input', + label: t('输入价格'), + value: priceData.inputPrice, + suffix: unitSuffix, + }, + { + key: 'completion', + label: t('补全价格'), + value: priceData.completionPrice, + suffix: unitSuffix, + }, + { + key: 'cache', + label: t('缓存读取价格'), + value: priceData.cachePrice, + suffix: unitSuffix, + }, + { + key: 'create-cache', + label: t('缓存创建价格'), + value: priceData.createCachePrice, + suffix: unitSuffix, + }, + { + key: 'image', + label: t('图片输入价格'), + value: priceData.imagePrice, + suffix: unitSuffix, + }, + { + key: 'audio-input', + label: t('音频输入价格'), + value: priceData.audioInputPrice, + suffix: unitSuffix, + }, + { + key: 'audio-output', + label: t('音频补全价格'), + value: priceData.audioOutputPrice, + suffix: unitSuffix, + }, + ].filter((item) => item.value !== null && item.value !== undefined && item.value !== ''); } + return [ + { + key: 'fixed', + label: t('模型价格'), + value: priceData.price, + suffix: ` / ${t('次')}`, + }, + ].filter((item) => item.value !== null && item.value !== undefined && item.value !== ''); +}; + +// 格式化价格信息(用于卡片视图) +export const formatPriceInfo = (priceData, t) => { + const items = getModelPriceItems(priceData, t); return ( <> - - {t('模型价格')} {priceData.price} - + {items.map((item) => ( + + {item.label} {item.value} + {item.suffix} + + ))} ); }; diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 948ae78a9..a4d1060b8 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1398,7 +1398,8 @@ "按价格设置": "Set by price", "按倍率类型筛选": "Filter by ratio type", "按倍率设置": "Set by ratio", - "按次计费": "Pay per view", + "按次": "Per request", + "按次计费": "Pay per request", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "Enter in the format: AccessKey|SecretAccessKey|Region", "按量计费": "Pay as you go", "按顺序替换content中的变量占位符": "Replace variable placeholders in content in order", @@ -1846,7 +1847,7 @@ "模板应用失败": "", "模板示例": "Template example", "模糊搜索模型名称": "Fuzzy search model name", - "次": "times", + "次": "request", "欢迎使用,请完成以下设置以开始使用系统": "Welcome! Please complete the following settings to start using the system", "欧元": "EUR", "正在加载可用部署位置...": "Loading available deployment locations...", @@ -3223,7 +3224,7 @@ "扩展价格": "Additional Pricing", "额外价格项": "Additional price items", "补全价格": "Completion Price", - "提示缓存价格": "Input Cache Read Price", + "缓存读取价格": "Input Cache Read Price", "缓存创建价格": "Input Cache Creation Price", "图片输入价格": "Image Input Price", "音频输入价格": "Audio Input Price", diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index 891a70ba3..6f2a6b4e6 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -1398,7 +1398,8 @@ "按价格设置": "Définir par prix", "按倍率类型筛选": "Filtrer par type de ratio", "按倍率设置": "Définir par ratio", - "按次计费": "Paiement à la séance", + "按次": "Par requête", + "按次计费": "Paiement par requête", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "Entrez au format : AccessKey|SecretAccessKey|Region", "按量计费": "Paiement à l'utilisation", "按顺序替换content中的变量占位符": "Remplacer les espaces réservés de variable dans le contenu dans l'ordre", @@ -1835,7 +1836,7 @@ "模板应用失败": "", "模板示例": "Exemple de modèle", "模糊搜索模型名称": "Recherche floue de nom de modèle", - "次": "Fois", + "次": "requête", "欢迎使用,请完成以下设置以开始使用系统": "Bienvenue, veuillez compléter les paramètres suivants pour commencer à utiliser le système", "欧元": "Euro", "正在加载可用部署位置...": "Loading available deployment locations...", @@ -3192,7 +3193,7 @@ "扩展价格": "Prix supplémentaires", "额外价格项": "Éléments de prix supplémentaires", "补全价格": "Prix de complétion", - "提示缓存价格": "Prix de lecture du cache d'entrée", + "缓存读取价格": "Prix de lecture du cache d'entrée", "缓存创建价格": "Prix de création du cache d'entrée", "图片输入价格": "Prix d'entrée image", "音频输入价格": "Prix d'entrée audio", diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index 6aa49d928..5bdd764f6 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -1381,7 +1381,8 @@ "按价格设置": "料金設定", "按倍率类型筛选": "倍率タイプで絞り込み", "按倍率设置": "倍率設定", - "按次计费": "リクエスト課金", + "按次": "リクエストごと", + "按次计费": "リクエストごとの課金", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "Enter in the format: AccessKey|SecretAccessKey|Region", "按量计费": "従量課金", "按顺序替换content中的变量占位符": "content内の変数プレースホルダーを順番に置換します", @@ -1818,7 +1819,7 @@ "模板应用失败": "", "模板示例": "テンプレートサンプル", "模糊搜索模型名称": "モデル名であいまい検索", - "次": "回", + "次": "リクエスト", "欢迎使用,请完成以下设置以开始使用系统": "ようこそ。システムを利用開始するには、以下の設定を完了してください", "欧元": "EUR", "正在加载可用部署位置...": "Loading available deployment locations...", @@ -3173,7 +3174,7 @@ "扩展价格": "追加価格", "额外价格项": "追加価格項目", "补全价格": "補完価格", - "提示缓存价格": "入力キャッシュ読み取り価格", + "缓存读取价格": "入力キャッシュ読み取り価格", "缓存创建价格": "入力キャッシュ作成価格", "图片输入价格": "画像入力価格", "音频输入价格": "音声入力価格", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index f003d0a98..f89fe0809 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -1410,7 +1410,8 @@ "按价格设置": "Настроить по цене", "按倍率类型筛选": "Фильтровать по типу коэффициента", "按倍率设置": "Настроить по множителю", - "按次计费": "Оплата за использование", + "按次": "За запрос", + "按次计费": "Оплата за запрос", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "Введите в формате: AccessKey|SecretAccessKey|Region", "按量计费": "Оплата по объему", "按顺序替换content中的变量占位符": "Последовательно заменять переменные-заполнители в content", @@ -1847,7 +1848,7 @@ "模板应用失败": "", "模板示例": "Пример шаблона", "模糊搜索模型名称": "Нечеткий поиск по названию модели", - "次": "раз", + "次": "запрос", "欢迎使用,请完成以下设置以开始使用系统": "Добро пожаловать, пожалуйста, выполните следующие настройки, чтобы начать использовать систему", "欧元": "Евро", "正在加载可用部署位置...": "Loading available deployment locations...", @@ -3206,7 +3207,7 @@ "扩展价格": "Дополнительные цены", "额外价格项": "Дополнительные ценовые позиции", "补全价格": "Цена завершения", - "提示缓存价格": "Цена чтения входного кеша", + "缓存读取价格": "Цена чтения входного кеша", "缓存创建价格": "Цена создания входного кеша", "图片输入价格": "Цена входного изображения", "音频输入价格": "Цена входного аудио", diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index 207064264..34d4a4c8f 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -1382,7 +1382,8 @@ "按价格设置": "Đặt theo giá", "按倍率类型筛选": "Lọc theo loại tỷ lệ", "按倍率设置": "Đặt theo tỷ lệ", - "按次计费": "Trả tiền cho mỗi lần xem", + "按次": "Theo lượt gọi", + "按次计费": "Tính phí theo lượt gọi", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "Enter in the format: AccessKey|SecretAccessKey|Region", "按量计费": "Trả tiền theo mức sử dụng", "按顺序替换content中的变量占位符": "Thay thế các trình giữ chỗ biến trong nội dung theo thứ tự", @@ -1839,7 +1840,7 @@ "模板示例": "Ví dụ mẫu", "模糊匹配": "Khớp mờ", "模糊搜索模型名称": "Tìm kiếm mờ tên mô hình", - "次": "lần", + "次": "lượt", "欢迎使用,请完成以下设置以开始使用系统": "Chào mừng! Vui lòng hoàn tất các cài đặt sau để bắt đầu sử dụng hệ thống", "欢迎回来": "Chào mừng trở lại", "欢迎回来!": "Chào mừng trở lại!", @@ -3745,7 +3746,7 @@ "扩展价格": "Giá mở rộng", "额外价格项": "Mục giá bổ sung", "补全价格": "Giá hoàn thành", - "提示缓存价格": "Giá đọc bộ nhớ đệm đầu vào", + "缓存读取价格": "Giá đọc bộ nhớ đệm đầu vào", "缓存创建价格": "Giá tạo bộ nhớ đệm đầu vào", "图片输入价格": "Giá đầu vào hình ảnh", "音频输入价格": "Giá đầu vào âm thanh", diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index 7e5cec50f..a7af51e19 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -1103,6 +1103,7 @@ "按价格设置": "按价格设置", "按倍率类型筛选": "按倍率类型筛选", "按倍率设置": "按倍率设置", + "按次": "按次", "按次计费": "按次计费", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "按照如下格式输入:AccessKey|SecretAccessKey|Region", "按量计费": "按量计费", @@ -2850,7 +2851,7 @@ "扩展价格": "扩展价格", "额外价格项": "额外价格项", "补全价格": "补全价格", - "提示缓存价格": "提示缓存价格", + "缓存读取价格": "缓存读取价格", "缓存创建价格": "缓存创建价格", "图片输入价格": "图片输入价格", "音频输入价格": "音频输入价格", diff --git a/web/src/i18n/locales/zh-TW.json b/web/src/i18n/locales/zh-TW.json index a709bcf96..5567ea8f1 100644 --- a/web/src/i18n/locales/zh-TW.json +++ b/web/src/i18n/locales/zh-TW.json @@ -1106,6 +1106,7 @@ "按价格设置": "按價格設定", "按倍率类型筛选": "按倍率類型篩選", "按倍率设置": "按倍率設定", + "按次": "按次", "按次计费": "按次計費", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "按照如下格式輸入:AccessKey|SecretAccessKey|Region", "按量计费": "按量計費", @@ -2843,7 +2844,7 @@ "扩展价格": "擴展價格", "额外价格项": "額外價格項", "补全价格": "補全價格", - "提示缓存价格": "提示快取價格", + "缓存读取价格": "快取讀取價格", "缓存创建价格": "快取建立價格", "图片输入价格": "圖片輸入價格", "音频输入价格": "音訊輸入價格", diff --git a/web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx b/web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx index d4c2ff662..5028a3ffd 100644 --- a/web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx +++ b/web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx @@ -491,7 +491,7 @@ export default function ModelPricingEditor({ } /> handleNumericFieldChange('cachePrice', value)}