- 后端 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"` | |||
| OwnerBy string `json:"owner_by"` | |||
| CompletionRatio float64 `json:"completion_ratio"` | |||
| CacheRatio float64 `json:"cache_ratio"` | |||
| CacheCreationRatio float64 `json:"cache_creation_ratio"` | |||
| EnableGroup []string `json:"enable_groups"` | |||
| SupportedEndpointTypes []constant.EndpointType `json:"supported_endpoint_types"` | |||
| 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) | |||
| for model, groups := range modelGroupsMap { | |||
| pricing := Pricing{ | |||
| @@ -289,16 +303,10 @@ func updatePricing() { | |||
| pricing.VendorID = meta.VendorID | |||
| 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) | |||
| } | |||
| @@ -324,3 +332,75 @@ func updatePricing() { | |||
| func GetSupportedEndpointMap() map[string]common.EndpointInfo { | |||
| 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 completionRatio float64 | |||
| var channelPricingFound bool | |||
| var cacheRatio, cacheCreationRatio, imageRatio, audioRatio, audioCompletionRatio float64 | |||
| // 尝试获取渠道定价(优先于全局定价) | |||
| 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 | |||
| usePrice = cp.QuotaType == model.QuotaTypeByCall | |||
| channelPricingFound = true | |||
| // 渠道定价的扩展比率(非零值直接使用) | |||
| cacheRatio = cp.CacheRatio | |||
| cacheCreationRatio = cp.CacheCreationRatio | |||
| imageRatio = cp.ImageRatio | |||
| audioRatio = cp.AudioRatio | |||
| audioCompletionRatio = cp.AudioCompletionRatio | |||
| if common.DebugEnabled { | |||
| 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) | |||
| } | |||
| // 全局比率作为回退(仅当渠道未设置、值为 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 | |||
| if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume { | |||
| // 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{ | |||
| 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 { | |||
| @@ -10,7 +10,7 @@ | |||
| content="OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用" | |||
| /> | |||
| <meta name="generator" content="new-api" /> | |||
| <title>New API</title> | |||
| <title>Loading...</title> | |||
| <!--umami--> | |||
| <!--Google Analytics--> | |||
| </head> | |||
| @@ -212,17 +212,26 @@ const ChannelPricingCard = ({ | |||
| (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 | |||
| ? [ | |||
| { | |||
| title: t('缓存读取'), | |||
| title: t('缓存读取') + ` / ${tokenUnit === 'K' ? '1K' : '1M'} tokens`, | |||
| dataIndex: 'cacheRatio', | |||
| render: (v) => (v > 0 ? v : '-'), | |||
| render: renderCachePrice, | |||
| }, | |||
| { | |||
| title: t('缓存创建'), | |||
| title: t('缓存创建') + ` / ${tokenUnit === 'K' ? '1K' : '1M'} tokens`, | |||
| 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'> | |||
| {model.model_name} | |||
| </h3> | |||
| <div className='flex items-center gap-3 text-xs mt-1'> | |||
| <div className='text-xs mt-1'> | |||
| {formatPriceInfo(priceData, t)} | |||
| </div> | |||
| </div> | |||
| @@ -679,9 +679,27 @@ export const calculateModelPrice = ({ | |||
| 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 { | |||
| inputPrice: `${symbol}${numInput.toFixed(precision)}`, | |||
| completionPrice: `${symbol}${numCompletion.toFixed(precision)}`, | |||
| cacheReadPrice, | |||
| cacheCreationPrice, | |||
| unitLabel, | |||
| isPerToken: true, | |||
| usedGroup, | |||
| @@ -715,14 +733,30 @@ export const calculateModelPrice = ({ | |||
| export const formatPriceInfo = (priceData, t) => { | |||
| if (priceData.isPerToken) { | |||
| 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 Disk Space", | |||
| "缓存读": "Cache Read", | |||
| "缓存读取": "Cache Read", | |||
| "缓存创建": "Cache Creation", | |||
| "编辑": "Edit", | |||
| "编辑API": "Edit API", | |||
| "编辑产品": "Edit Product", | |||
| @@ -2139,6 +2139,8 @@ | |||
| "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Ratio de création de cache 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | |||
| "缓存创建倍率 {{cacheCreationRatio}}": "Ratio de création de cache {{cacheCreationRatio}}", | |||
| "缓存读": "Lecture cache", | |||
| "缓存读取": "Lecture cache", | |||
| "缓存创建": "Création cache", | |||
| "编辑": "Modifier", | |||
| "编辑API": "Modifier l'API", | |||
| "编辑产品": "Modifier le produit", | |||
| @@ -2122,6 +2122,8 @@ | |||
| "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Cache creation ratio 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | |||
| "缓存创建倍率 {{cacheCreationRatio}}": "Cache creation ratio {{cacheCreationRatio}}", | |||
| "缓存读": "キャッシュ読取", | |||
| "缓存读取": "キャッシュ読取", | |||
| "缓存创建": "キャッシュ作成", | |||
| "编辑": "編集", | |||
| "编辑API": "API編集", | |||
| "编辑产品": "Edit Product", | |||
| @@ -2152,6 +2152,8 @@ | |||
| "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Коэффициент создания кэша 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | |||
| "缓存创建倍率 {{cacheCreationRatio}}": "Коэффициент создания кэша {{cacheCreationRatio}}", | |||
| "缓存读": "Чтение кэша", | |||
| "缓存读取": "Чтение кэша", | |||
| "缓存创建": "Создание кэша", | |||
| "编辑": "Редактировать", | |||
| "编辑API": "Редактировать API", | |||
| "编辑产品": "Редактировать продукт", | |||
| @@ -2423,6 +2423,8 @@ | |||
| "缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Cache creation ratio 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", | |||
| "缓存创建倍率 {{cacheCreationRatio}}": "Cache creation ratio {{cacheCreationRatio}}", | |||
| "缓存读": "Đọc bộ nhớ đệm", | |||
| "缓存读取": "Đọc bộ nhớ đệm", | |||
| "缓存创建": "Tạo bộ nhớ đệm", | |||
| "编辑": "Chỉnh sửa", | |||
| "编辑API": "Chỉnh sửa API", | |||
| "编辑产品": "Chỉnh sửa sản phẩm", | |||
| @@ -2242,6 +2242,8 @@ | |||
| "缓存目录": "缓存目录", | |||
| "缓存目录磁盘空间": "缓存目录磁盘空间", | |||
| "缓存读": "缓存读", | |||
| "缓存读取": "缓存读取", | |||
| "缓存创建": "缓存创建", | |||
| "编辑": "编辑", | |||
| "编辑API": "编辑API", | |||
| "编辑产品": "编辑产品", | |||
| @@ -2180,6 +2180,8 @@ | |||
| "缓存创建倍率 {{cacheCreationRatio}}": "快取建立倍率 {{cacheCreationRatio}}", | |||
| "缓存目录": "快取目錄", | |||
| "缓存目录磁盘空间": "快取目錄磁碟空間", | |||
| "缓存读取": "快取讀取", | |||
| "缓存创建": "快取建立", | |||
| "编辑": "編輯", | |||
| "编辑API": "編輯API", | |||
| "编辑产品": "編輯產品", | |||
| @@ -81,7 +81,7 @@ const HeroSection = () => { | |||
| <div className="hero-blur-ball-1" /> | |||
| <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="flex flex-col gap-8"> | |||