- 后端 pricing API 从 channel_pricings 表获取实际定价,选取最便宜渠道 - 提取 applyGlobalDefault 辅助函数消除全局回退逻辑重复 - price.go 重构扩展比率为局部变量,简化回退逻辑 - 定价卡片新增缓存读取/创建价格,改为两行布局防止溢出 - ChannelPricingCard 缓存列显示实际价格而非倍率 - 修复移动端 hero 区域 padding 过大 - 默认标签页标题改为 Loading... - 新增缓存读取/创建 i18n 翻译(7 语言) Co-Authored-By: Claude <noreply@anthropic.com>master
| @@ -25,6 +25,8 @@ type Pricing struct { | |||||
| ModelPrice float64 `json:"model_price"` | ModelPrice float64 `json:"model_price"` | ||||
| OwnerBy string `json:"owner_by"` | OwnerBy string `json:"owner_by"` | ||||
| CompletionRatio float64 `json:"completion_ratio"` | CompletionRatio float64 `json:"completion_ratio"` | ||||
| CacheRatio float64 `json:"cache_ratio"` | |||||
| CacheCreationRatio float64 `json:"cache_creation_ratio"` | |||||
| EnableGroup []string `json:"enable_groups"` | EnableGroup []string `json:"enable_groups"` | ||||
| SupportedEndpointTypes []constant.EndpointType `json:"supported_endpoint_types"` | SupportedEndpointTypes []constant.EndpointType `json:"supported_endpoint_types"` | ||||
| PricingVersion string `json:"pricing_version,omitempty"` | PricingVersion string `json:"pricing_version,omitempty"` | ||||
| @@ -269,6 +271,18 @@ func updatePricing() { | |||||
| } | } | ||||
| } | } | ||||
| // 从渠道定价表加载实际定价数据(仅启用渠道) | |||||
| var allCPs []ChannelPricing | |||||
| DB.Table("channel_pricings"). | |||||
| Select("channel_pricings.*"). | |||||
| Joins("JOIN channels ON channel_pricings.channel_id = channels.id"). | |||||
| Where("channels.status = 1 AND channel_pricings.deleted_at IS NULL"). | |||||
| Find(&allCPs) | |||||
| cpMap := make(map[string][]ChannelPricing) | |||||
| for i := range allCPs { | |||||
| cpMap[allCPs[i].ModelName] = append(cpMap[allCPs[i].ModelName], allCPs[i]) | |||||
| } | |||||
| pricingMap = make([]Pricing, 0) | pricingMap = make([]Pricing, 0) | ||||
| for model, groups := range modelGroupsMap { | for model, groups := range modelGroupsMap { | ||||
| pricing := Pricing{ | pricing := Pricing{ | ||||
| @@ -289,16 +303,10 @@ func updatePricing() { | |||||
| pricing.VendorID = meta.VendorID | pricing.VendorID = meta.VendorID | ||||
| pricing.Type = meta.Type | pricing.Type = meta.Type | ||||
| } | } | ||||
| modelPrice, findPrice := ratio_setting.GetModelPrice(model, false) | |||||
| if findPrice { | |||||
| pricing.ModelPrice = modelPrice | |||||
| pricing.QuotaType = 1 | |||||
| } else { | |||||
| modelRatio, _, _ := ratio_setting.GetModelRatio(model) | |||||
| pricing.ModelRatio = modelRatio | |||||
| pricing.CompletionRatio = ratio_setting.GetCompletionRatio(model) | |||||
| pricing.QuotaType = 0 | |||||
| } | |||||
| // 使用渠道定价表中的实际数据,选取最便宜的渠道 | |||||
| applyBestChannelPricing(&pricing, cpMap[model], model) | |||||
| pricingMap = append(pricingMap, pricing) | pricingMap = append(pricingMap, pricing) | ||||
| } | } | ||||
| @@ -324,3 +332,75 @@ func updatePricing() { | |||||
| func GetSupportedEndpointMap() map[string]common.EndpointInfo { | func GetSupportedEndpointMap() map[string]common.EndpointInfo { | ||||
| return supportedEndpointMap | return supportedEndpointMap | ||||
| } | } | ||||
| // applyGlobalDefault 用全局默认值填充 Pricing(无渠道定价时的回退) | |||||
| func applyGlobalDefault(pricing *Pricing, model string) { | |||||
| modelPrice, findPrice := ratio_setting.GetModelPrice(model, false) | |||||
| if findPrice { | |||||
| pricing.ModelPrice = modelPrice | |||||
| pricing.QuotaType = 1 | |||||
| } else { | |||||
| modelRatio, _, _ := ratio_setting.GetModelRatio(model) | |||||
| pricing.ModelRatio = modelRatio | |||||
| pricing.CompletionRatio = ratio_setting.GetCompletionRatio(model) | |||||
| pricing.QuotaType = 0 | |||||
| } | |||||
| pricing.CacheRatio, _ = ratio_setting.GetCacheRatio(model) | |||||
| pricing.CacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(model) | |||||
| } | |||||
| // applyBestChannelPricing 从渠道定价中选取最优(最便宜)的价格填充 Pricing | |||||
| // 优先选按量计费(quota_type=0)的渠道,因为缓存价格仅对按量计费有意义 | |||||
| // 扩展比率字段(cache_ratio 等)为 0 表示未设置,需回退到全局默认值 | |||||
| func applyBestChannelPricing(pricing *Pricing, cps []ChannelPricing, model string) { | |||||
| if len(cps) == 0 { | |||||
| applyGlobalDefault(pricing, model) | |||||
| return | |||||
| } | |||||
| // 优先选按量计费 (quota_type=0) 中 model_ratio 最低的渠道 | |||||
| var bestPerToken *ChannelPricing | |||||
| for i := range cps { | |||||
| cp := &cps[i] | |||||
| if cp.QuotaType == 0 { | |||||
| if bestPerToken == nil || cp.ModelRatio < bestPerToken.ModelRatio { | |||||
| bestPerToken = cp | |||||
| } | |||||
| } | |||||
| } | |||||
| if bestPerToken != nil { | |||||
| pricing.QuotaType = 0 | |||||
| pricing.ModelRatio = bestPerToken.ModelRatio | |||||
| pricing.CompletionRatio = bestPerToken.CompletionRatio | |||||
| if bestPerToken.CacheRatio > 0 { | |||||
| pricing.CacheRatio = bestPerToken.CacheRatio | |||||
| } else { | |||||
| pricing.CacheRatio, _ = ratio_setting.GetCacheRatio(model) | |||||
| } | |||||
| if bestPerToken.CacheCreationRatio > 0 { | |||||
| pricing.CacheCreationRatio = bestPerToken.CacheCreationRatio | |||||
| } else { | |||||
| pricing.CacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(model) | |||||
| } | |||||
| return | |||||
| } | |||||
| // 没有按量渠道,选按次计费 (quota_type=1) 中 model_price 最低的渠道 | |||||
| var bestPerCall *ChannelPricing | |||||
| for i := range cps { | |||||
| cp := &cps[i] | |||||
| if cp.QuotaType == 1 { | |||||
| if bestPerCall == nil || cp.ModelPrice < bestPerCall.ModelPrice { | |||||
| bestPerCall = cp | |||||
| } | |||||
| } | |||||
| } | |||||
| if bestPerCall != nil { | |||||
| pricing.QuotaType = 1 | |||||
| pricing.ModelPrice = bestPerCall.ModelPrice | |||||
| return | |||||
| } | |||||
| applyGlobalDefault(pricing, model) | |||||
| } | |||||
| @@ -0,0 +1,122 @@ | |||||
| package model | |||||
| import ( | |||||
| "testing" | |||||
| "github.com/QuantumNous/new-api/setting/ratio_setting" | |||||
| "github.com/glebarez/sqlite" | |||||
| "github.com/stretchr/testify/require" | |||||
| "gorm.io/gorm" | |||||
| ) | |||||
| const testPricingModel = "test-pricing-model-apply" | |||||
| func setupPricingTest(t *testing.T) { | |||||
| t.Helper() | |||||
| db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) | |||||
| require.NoError(t, err) | |||||
| sqlDB, _ := db.DB() | |||||
| sqlDB.SetMaxOpenConns(1) | |||||
| origDB := DB | |||||
| DB = db | |||||
| require.NoError(t, db.AutoMigrate(&ChannelPricing{})) | |||||
| // 全局默认定价 | |||||
| require.NoError(t, ratio_setting.UpdateModelRatioByJSONString(`{"`+testPricingModel+`":10}`)) | |||||
| require.NoError(t, ratio_setting.UpdateCompletionRatioByJSONString(`{"`+testPricingModel+`":3}`)) | |||||
| require.NoError(t, ratio_setting.UpdateCacheRatioByJSONString(`{"`+testPricingModel+`":0.5}`)) | |||||
| require.NoError(t, ratio_setting.UpdateCreateCacheRatioByJSONString(`{"`+testPricingModel+`":0.75}`)) | |||||
| t.Cleanup(func() { | |||||
| DB = origDB | |||||
| sqlDB.Close() | |||||
| ratio_setting.UpdateModelRatioByJSONString(`{}`) | |||||
| ratio_setting.UpdateCompletionRatioByJSONString(`{}`) | |||||
| ratio_setting.UpdateCacheRatioByJSONString(`{}`) | |||||
| ratio_setting.UpdateCreateCacheRatioByJSONString(`{}`) | |||||
| ratio_setting.UpdateModelPriceByJSONString(`{}`) | |||||
| }) | |||||
| } | |||||
| func TestApplyBestChannelPricing_NoChannelPricing(t *testing.T) { | |||||
| setupPricingTest(t) | |||||
| p := &Pricing{} | |||||
| applyBestChannelPricing(p, nil, testPricingModel) | |||||
| require.Equal(t, 0, p.QuotaType, "应使用全局按量计费") | |||||
| require.Equal(t, 10.0, p.ModelRatio, "应使用全局 model_ratio") | |||||
| require.Equal(t, 3.0, p.CompletionRatio, "应使用全局 completion_ratio") | |||||
| require.Equal(t, 0.5, p.CacheRatio, "应使用全局 cache_ratio") | |||||
| require.Equal(t, 0.75, p.CacheCreationRatio, "应使用全局 cache_creation_ratio") | |||||
| } | |||||
| func TestApplyBestChannelPricing_PerTokenCheapest(t *testing.T) { | |||||
| setupPricingTest(t) | |||||
| // 渠道A: model_ratio=8(更便宜) | |||||
| require.NoError(t, (&ChannelPricing{ | |||||
| ModelName: testPricingModel, ChannelId: 1, | |||||
| QuotaType: QuotaTypeByTokens, ModelRatio: 8, CompletionRatio: 2, | |||||
| CacheRatio: 0.3, CacheCreationRatio: 0.6, | |||||
| }).Insert()) | |||||
| // 渠道B: model_ratio=12(更贵) | |||||
| require.NoError(t, (&ChannelPricing{ | |||||
| ModelName: testPricingModel, ChannelId: 2, | |||||
| QuotaType: QuotaTypeByTokens, ModelRatio: 12, CompletionRatio: 4, | |||||
| CacheRatio: 0.8, CacheCreationRatio: 1.0, | |||||
| }).Insert()) | |||||
| cps := []ChannelPricing{ | |||||
| {ModelRatio: 8, CompletionRatio: 2, CacheRatio: 0.3, CacheCreationRatio: 0.6}, | |||||
| {ModelRatio: 12, CompletionRatio: 4, CacheRatio: 0.8, CacheCreationRatio: 1.0}, | |||||
| } | |||||
| p := &Pricing{} | |||||
| applyBestChannelPricing(p, cps, testPricingModel) | |||||
| require.Equal(t, 0, p.QuotaType) | |||||
| require.Equal(t, 8.0, p.ModelRatio, "应选最便宜的渠道A") | |||||
| require.Equal(t, 2.0, p.CompletionRatio) | |||||
| require.Equal(t, 0.3, p.CacheRatio) | |||||
| require.Equal(t, 0.6, p.CacheCreationRatio) | |||||
| } | |||||
| func TestApplyBestChannelPricing_ExtendedRatioZeroFallback(t *testing.T) { | |||||
| setupPricingTest(t) | |||||
| // 渠道定价中扩展比率为 0,应回退到全局值 | |||||
| require.NoError(t, (&ChannelPricing{ | |||||
| ModelName: testPricingModel, ChannelId: 1, | |||||
| QuotaType: QuotaTypeByTokens, ModelRatio: 5, CompletionRatio: 1, | |||||
| CacheRatio: 0, CacheCreationRatio: 0, // 0 = 未设置 | |||||
| }).Insert()) | |||||
| cps := []ChannelPricing{ | |||||
| {ModelRatio: 5, CompletionRatio: 1, CacheRatio: 0, CacheCreationRatio: 0}, | |||||
| } | |||||
| p := &Pricing{} | |||||
| applyBestChannelPricing(p, cps, testPricingModel) | |||||
| require.Equal(t, 0.5, p.CacheRatio, "cache_ratio=0 应回退全局 0.5") | |||||
| require.Equal(t, 0.75, p.CacheCreationRatio, "cache_creation_ratio=0 应回退全局 0.75") | |||||
| } | |||||
| func TestApplyBestChannelPricing_PerCallCheapest(t *testing.T) { | |||||
| setupPricingTest(t) | |||||
| require.NoError(t, ratio_setting.UpdateModelPriceByJSONString(`{"`+testPricingModel+`":0.8}`)) | |||||
| // 只有按次计费的渠道 | |||||
| cps := []ChannelPricing{ | |||||
| {QuotaType: QuotaTypeByCall, ModelPrice: 0.3}, | |||||
| {QuotaType: QuotaTypeByCall, ModelPrice: 0.5}, | |||||
| } | |||||
| p := &Pricing{} | |||||
| applyBestChannelPricing(p, cps, testPricingModel) | |||||
| require.Equal(t, 1, p.QuotaType) | |||||
| require.Equal(t, 0.3, p.ModelPrice, "应选最便宜的按次渠道") | |||||
| } | |||||
| @@ -49,6 +49,7 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens | |||||
| var modelRatio float64 | var modelRatio float64 | ||||
| var completionRatio float64 | var completionRatio float64 | ||||
| var channelPricingFound bool | var channelPricingFound bool | ||||
| var cacheRatio, cacheCreationRatio, imageRatio, audioRatio, audioCompletionRatio float64 | |||||
| // 尝试获取渠道定价(优先于全局定价) | // 尝试获取渠道定价(优先于全局定价) | ||||
| channelMetaAvailable := info != nil && info.ChannelMeta != nil && info.ChannelId > 0 | channelMetaAvailable := info != nil && info.ChannelMeta != nil && info.ChannelId > 0 | ||||
| @@ -60,6 +61,12 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens | |||||
| modelPrice = cp.ModelPrice | modelPrice = cp.ModelPrice | ||||
| usePrice = cp.QuotaType == model.QuotaTypeByCall | usePrice = cp.QuotaType == model.QuotaTypeByCall | ||||
| channelPricingFound = true | channelPricingFound = true | ||||
| // 渠道定价的扩展比率(非零值直接使用) | |||||
| cacheRatio = cp.CacheRatio | |||||
| cacheCreationRatio = cp.CacheCreationRatio | |||||
| imageRatio = cp.ImageRatio | |||||
| audioRatio = cp.AudioRatio | |||||
| audioCompletionRatio = cp.AudioCompletionRatio | |||||
| if common.DebugEnabled { | if common.DebugEnabled { | ||||
| println(fmt.Sprintf("[ChannelPricing] hit: model=%s channel=%d source=cache", info.OriginModelName, info.ChannelId)) | println(fmt.Sprintf("[ChannelPricing] hit: model=%s channel=%d source=cache", info.OriginModelName, info.ChannelId)) | ||||
| } | } | ||||
| @@ -105,6 +112,24 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens | |||||
| preConsumedQuota = int(modelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio) | preConsumedQuota = int(modelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio) | ||||
| } | } | ||||
| // 全局比率作为回退(仅当渠道未设置、值为 0 时生效) | |||||
| // 必须放在 usePrice 判断之外,因为 UpdatePriceDataForChannelPricing 可能改变 UsePrice | |||||
| if cacheRatio == 0 { | |||||
| cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName) | |||||
| } | |||||
| if cacheCreationRatio == 0 { | |||||
| cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName) | |||||
| } | |||||
| if imageRatio == 0 { | |||||
| imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) | |||||
| } | |||||
| if audioRatio == 0 { | |||||
| audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName) | |||||
| } | |||||
| if audioCompletionRatio == 0 { | |||||
| audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName) | |||||
| } | |||||
| // check if free model pre-consume is disabled | // check if free model pre-consume is disabled | ||||
| if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume { | if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume { | ||||
| // if model price or ratio is 0, do not pre-consume quota | // if model price or ratio is 0, do not pre-consume quota | ||||
| @@ -125,38 +150,20 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens | |||||
| } | } | ||||
| priceData := types.PriceData{ | priceData := types.PriceData{ | ||||
| FreeModel: freeModel, | |||||
| ModelPrice: modelPrice, | |||||
| ModelRatio: modelRatio, | |||||
| CompletionRatio: completionRatio, | |||||
| GroupRatioInfo: groupRatioInfo, | |||||
| UsePrice: usePrice, | |||||
| QuotaToPreConsume: preConsumedQuota, | |||||
| } | |||||
| // 应用渠道定价的扩展比率(非零值覆盖),然后回退全局比率 | |||||
| if channelMetaAvailable { | |||||
| if cp, found := model.GetEffectivePricing(info.OriginModelName, info.ChannelId); found { | |||||
| priceData.ApplyChannelPricingRatios(cp.CacheRatio, cp.CacheCreationRatio, cp.ImageRatio, cp.AudioRatio, cp.AudioCompletionRatio) | |||||
| } | |||||
| } | |||||
| // 全局比率作为回退(仅当对应字段仍为 0 时生效) | |||||
| if priceData.CacheRatio == 0 { | |||||
| priceData.CacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName) | |||||
| } | |||||
| if priceData.CacheCreationRatio == 0 { | |||||
| priceData.CacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName) | |||||
| priceData.CacheCreation5mRatio = priceData.CacheCreationRatio | |||||
| priceData.CacheCreation1hRatio = priceData.CacheCreationRatio * types.ClaudeCacheCreation1hMultiplier | |||||
| } | |||||
| if priceData.ImageRatio == 0 { | |||||
| priceData.ImageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) | |||||
| } | |||||
| if priceData.AudioRatio == 0 { | |||||
| priceData.AudioRatio = ratio_setting.GetAudioRatio(info.OriginModelName) | |||||
| } | |||||
| if priceData.AudioCompletionRatio == 0 { | |||||
| priceData.AudioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName) | |||||
| FreeModel: freeModel, | |||||
| ModelPrice: modelPrice, | |||||
| ModelRatio: modelRatio, | |||||
| CompletionRatio: completionRatio, | |||||
| GroupRatioInfo: groupRatioInfo, | |||||
| UsePrice: usePrice, | |||||
| QuotaToPreConsume: preConsumedQuota, | |||||
| CacheRatio: cacheRatio, | |||||
| ImageRatio: imageRatio, | |||||
| AudioRatio: audioRatio, | |||||
| AudioCompletionRatio: audioCompletionRatio, | |||||
| CacheCreationRatio: cacheCreationRatio, | |||||
| CacheCreation5mRatio: cacheCreationRatio, | |||||
| CacheCreation1hRatio: cacheCreationRatio * types.ClaudeCacheCreation1hMultiplier, | |||||
| } | } | ||||
| if common.DebugEnabled { | if common.DebugEnabled { | ||||
| @@ -10,7 +10,7 @@ | |||||
| content="OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用" | content="OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用" | ||||
| /> | /> | ||||
| <meta name="generator" content="new-api" /> | <meta name="generator" content="new-api" /> | ||||
| <title>New API</title> | |||||
| <title>Loading...</title> | |||||
| <!--umami--> | <!--umami--> | ||||
| <!--Google Analytics--> | <!--Google Analytics--> | ||||
| </head> | </head> | ||||
| @@ -212,17 +212,26 @@ const ChannelPricingCard = ({ | |||||
| (item) => item.cacheRatio > 0 || item.cacheCreationRatio > 0 | (item) => item.cacheRatio > 0 || item.cacheCreationRatio > 0 | ||||
| ); | ); | ||||
| const renderCachePrice = (v, record) => { | |||||
| if (record.quotaType !== 0 || v <= 0) return '-'; | |||||
| return ( | |||||
| <div className='font-semibold text-orange-600'> | |||||
| {formatPrice(record.modelRatio * v * 2)} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| const advancedColumns = hasAdvancedPricing | const advancedColumns = hasAdvancedPricing | ||||
| ? [ | ? [ | ||||
| { | { | ||||
| title: t('缓存读取'), | |||||
| title: t('缓存读取') + ` / ${tokenUnit === 'K' ? '1K' : '1M'} tokens`, | |||||
| dataIndex: 'cacheRatio', | dataIndex: 'cacheRatio', | ||||
| render: (v) => (v > 0 ? v : '-'), | |||||
| render: renderCachePrice, | |||||
| }, | }, | ||||
| { | { | ||||
| title: t('缓存创建'), | |||||
| title: t('缓存创建') + ` / ${tokenUnit === 'K' ? '1K' : '1M'} tokens`, | |||||
| dataIndex: 'cacheCreationRatio', | dataIndex: 'cacheCreationRatio', | ||||
| render: (v) => (v > 0 ? v : '-'), | |||||
| render: renderCachePrice, | |||||
| }, | }, | ||||
| ] | ] | ||||
| : []; | : []; | ||||
| @@ -265,7 +265,7 @@ const PricingCardView = ({ | |||||
| <h3 className='text-lg font-bold text-gray-900 truncate'> | <h3 className='text-lg font-bold text-gray-900 truncate'> | ||||
| {model.model_name} | {model.model_name} | ||||
| </h3> | </h3> | ||||
| <div className='flex items-center gap-3 text-xs mt-1'> | |||||
| <div className='text-xs mt-1'> | |||||
| {formatPriceInfo(priceData, t)} | {formatPriceInfo(priceData, t)} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -679,9 +679,27 @@ export const calculateModelPrice = ({ | |||||
| symbol = '¤'; | symbol = '¤'; | ||||
| } | } | ||||
| } | } | ||||
| const cacheReadRatio = record.cache_ratio || 0; | |||||
| const cacheCreationRatio = record.cache_creation_ratio || 0; | |||||
| let cacheReadPrice = null; | |||||
| let cacheCreationPrice = null; | |||||
| if (cacheReadRatio > 0) { | |||||
| const cacheReadUSD = record.model_ratio * cacheReadRatio * 2 * usedGroupRatio; | |||||
| const numCacheRead = parseFloat(displayPrice(cacheReadUSD).replace(/[^0-9.]/g, '')) / unitDivisor; | |||||
| cacheReadPrice = `${symbol}${numCacheRead.toFixed(precision)}`; | |||||
| } | |||||
| if (cacheCreationRatio > 0) { | |||||
| const cacheCreationUSD = record.model_ratio * cacheCreationRatio * 2 * usedGroupRatio; | |||||
| const numCacheCreation = parseFloat(displayPrice(cacheCreationUSD).replace(/[^0-9.]/g, '')) / unitDivisor; | |||||
| cacheCreationPrice = `${symbol}${numCacheCreation.toFixed(precision)}`; | |||||
| } | |||||
| return { | return { | ||||
| inputPrice: `${symbol}${numInput.toFixed(precision)}`, | inputPrice: `${symbol}${numInput.toFixed(precision)}`, | ||||
| completionPrice: `${symbol}${numCompletion.toFixed(precision)}`, | completionPrice: `${symbol}${numCompletion.toFixed(precision)}`, | ||||
| cacheReadPrice, | |||||
| cacheCreationPrice, | |||||
| unitLabel, | unitLabel, | ||||
| isPerToken: true, | isPerToken: true, | ||||
| usedGroup, | usedGroup, | ||||
| @@ -715,14 +733,30 @@ export const calculateModelPrice = ({ | |||||
| export const formatPriceInfo = (priceData, t) => { | export const formatPriceInfo = (priceData, t) => { | ||||
| if (priceData.isPerToken) { | if (priceData.isPerToken) { | ||||
| return ( | return ( | ||||
| <> | |||||
| <span style={{ color: 'var(--semi-color-text-1)' }}> | |||||
| {t('输入')} {priceData.inputPrice}/{priceData.unitLabel} | |||||
| </span> | |||||
| <span style={{ color: 'var(--semi-color-text-1)' }}> | |||||
| {t('输出')} {priceData.completionPrice}/{priceData.unitLabel} | |||||
| </span> | |||||
| </> | |||||
| <div className='flex flex-col gap-0.5'> | |||||
| <div className='flex items-center gap-2 flex-wrap'> | |||||
| <span style={{ color: 'var(--semi-color-text-1)' }}> | |||||
| {t('输入')} {priceData.inputPrice}/{priceData.unitLabel} | |||||
| </span> | |||||
| <span style={{ color: 'var(--semi-color-text-1)' }}> | |||||
| {t('输出')} {priceData.completionPrice}/{priceData.unitLabel} | |||||
| </span> | |||||
| </div> | |||||
| {(priceData.cacheReadPrice || priceData.cacheCreationPrice) && ( | |||||
| <div className='flex items-center gap-2 flex-wrap'> | |||||
| {priceData.cacheReadPrice && ( | |||||
| <span style={{ color: 'var(--semi-color-text-2)' }}> | |||||
| {t('缓存读取')} {priceData.cacheReadPrice}/{priceData.unitLabel} | |||||
| </span> | |||||
| )} | |||||
| {priceData.cacheCreationPrice && ( | |||||
| <span style={{ color: 'var(--semi-color-text-2)' }}> | |||||
| {t('缓存创建')} {priceData.cacheCreationPrice}/{priceData.unitLabel} | |||||
| </span> | |||||
| )} | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -2260,6 +2260,8 @@ | |||||
| "缓存目录": "Cache Directory", | "缓存目录": "Cache Directory", | ||||
| "缓存目录磁盘空间": "Cache Directory Disk Space", | "缓存目录磁盘空间": "Cache Directory Disk Space", | ||||
| "缓存读": "Cache Read", | "缓存读": "Cache Read", | ||||
| "缓存读取": "Cache Read", | |||||
| "缓存创建": "Cache Creation", | |||||
| "编辑": "Edit", | "编辑": "Edit", | ||||
| "编辑API": "Edit API", | "编辑API": "Edit API", | ||||
| "编辑产品": "Edit Product", | "编辑产品": "Edit Product", | ||||
| @@ -2139,6 +2139,8 @@ | |||||
| "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Ratio de création de cache 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Ratio de création de cache 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | ||||
| "缓存创建倍率 {{cacheCreationRatio}}": "Ratio de création de cache {{cacheCreationRatio}}", | "缓存创建倍率 {{cacheCreationRatio}}": "Ratio de création de cache {{cacheCreationRatio}}", | ||||
| "缓存读": "Lecture cache", | "缓存读": "Lecture cache", | ||||
| "缓存读取": "Lecture cache", | |||||
| "缓存创建": "Création cache", | |||||
| "编辑": "Modifier", | "编辑": "Modifier", | ||||
| "编辑API": "Modifier l'API", | "编辑API": "Modifier l'API", | ||||
| "编辑产品": "Modifier le produit", | "编辑产品": "Modifier le produit", | ||||
| @@ -2122,6 +2122,8 @@ | |||||
| "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Cache creation ratio 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Cache creation ratio 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | ||||
| "缓存创建倍率 {{cacheCreationRatio}}": "Cache creation ratio {{cacheCreationRatio}}", | "缓存创建倍率 {{cacheCreationRatio}}": "Cache creation ratio {{cacheCreationRatio}}", | ||||
| "缓存读": "キャッシュ読取", | "缓存读": "キャッシュ読取", | ||||
| "缓存读取": "キャッシュ読取", | |||||
| "缓存创建": "キャッシュ作成", | |||||
| "编辑": "編集", | "编辑": "編集", | ||||
| "编辑API": "API編集", | "编辑API": "API編集", | ||||
| "编辑产品": "Edit Product", | "编辑产品": "Edit Product", | ||||
| @@ -2152,6 +2152,8 @@ | |||||
| "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Коэффициент создания кэша 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Коэффициент создания кэша 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | ||||
| "缓存创建倍率 {{cacheCreationRatio}}": "Коэффициент создания кэша {{cacheCreationRatio}}", | "缓存创建倍率 {{cacheCreationRatio}}": "Коэффициент создания кэша {{cacheCreationRatio}}", | ||||
| "缓存读": "Чтение кэша", | "缓存读": "Чтение кэша", | ||||
| "缓存读取": "Чтение кэша", | |||||
| "缓存创建": "Создание кэша", | |||||
| "编辑": "Редактировать", | "编辑": "Редактировать", | ||||
| "编辑API": "Редактировать API", | "编辑API": "Редактировать API", | ||||
| "编辑产品": "Редактировать продукт", | "编辑产品": "Редактировать продукт", | ||||
| @@ -2423,6 +2423,8 @@ | |||||
| "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Cache creation ratio 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Cache creation ratio 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | ||||
| "缓存创建倍率 {{cacheCreationRatio}}": "Cache creation ratio {{cacheCreationRatio}}", | "缓存创建倍率 {{cacheCreationRatio}}": "Cache creation ratio {{cacheCreationRatio}}", | ||||
| "缓存读": "Đọc bộ nhớ đệm", | "缓存读": "Đọc bộ nhớ đệm", | ||||
| "缓存读取": "Đọc bộ nhớ đệm", | |||||
| "缓存创建": "Tạo bộ nhớ đệm", | |||||
| "编辑": "Chỉnh sửa", | "编辑": "Chỉnh sửa", | ||||
| "编辑API": "Chỉnh sửa API", | "编辑API": "Chỉnh sửa API", | ||||
| "编辑产品": "Chỉnh sửa sản phẩm", | "编辑产品": "Chỉnh sửa sản phẩm", | ||||
| @@ -2242,6 +2242,8 @@ | |||||
| "缓存目录": "缓存目录", | "缓存目录": "缓存目录", | ||||
| "缓存目录磁盘空间": "缓存目录磁盘空间", | "缓存目录磁盘空间": "缓存目录磁盘空间", | ||||
| "缓存读": "缓存读", | "缓存读": "缓存读", | ||||
| "缓存读取": "缓存读取", | |||||
| "缓存创建": "缓存创建", | |||||
| "编辑": "编辑", | "编辑": "编辑", | ||||
| "编辑API": "编辑API", | "编辑API": "编辑API", | ||||
| "编辑产品": "编辑产品", | "编辑产品": "编辑产品", | ||||
| @@ -2180,6 +2180,8 @@ | |||||
| "缓存创建倍率 {{cacheCreationRatio}}": "快取建立倍率 {{cacheCreationRatio}}", | "缓存创建倍率 {{cacheCreationRatio}}": "快取建立倍率 {{cacheCreationRatio}}", | ||||
| "缓存目录": "快取目錄", | "缓存目录": "快取目錄", | ||||
| "缓存目录磁盘空间": "快取目錄磁碟空間", | "缓存目录磁盘空间": "快取目錄磁碟空間", | ||||
| "缓存读取": "快取讀取", | |||||
| "缓存创建": "快取建立", | |||||
| "编辑": "編輯", | "编辑": "編輯", | ||||
| "编辑API": "編輯API", | "编辑API": "編輯API", | ||||
| "编辑产品": "編輯產品", | "编辑产品": "編輯產品", | ||||
| @@ -81,7 +81,7 @@ const HeroSection = () => { | |||||
| <div className="hero-blur-ball-1" /> | <div className="hero-blur-ball-1" /> | ||||
| <div className="hero-blur-ball-2" /> | <div className="hero-blur-ball-2" /> | ||||
| <div className="relative z-10 mx-auto w-full max-w-6xl px-5 pt-28 pb-16 md:px-6 md:pt-32 md:pb-20 lg:px-8 lg:pt-36 lg:pb-24"> | |||||
| <div className="relative z-10 mx-auto w-full max-w-6xl px-5 pt-14 pb-8 md:px-6 md:pt-32 md:pb-20 lg:px-8 lg:pt-36 lg:pb-24"> | |||||
| <div className="grid items-center gap-12 hero-grid lg:grid-cols-[1.1fr_0.9fr]"> | <div className="grid items-center gap-12 hero-grid lg:grid-cols-[1.1fr_0.9fr]"> | ||||
| {/* 左侧内容 */} | {/* 左侧内容 */} | ||||
| <div className="flex flex-col gap-8"> | <div className="flex flex-col gap-8"> | ||||