Bladeren bron

feat(pricing): 定价数据源切换到渠道表,新增缓存价格展示

- 后端 pricing API 从 channel_pricings 表获取实际定价,选取最便宜渠道
- 提取 applyGlobalDefault 辅助函数消除全局回退逻辑重复
- price.go 重构扩展比率为局部变量,简化回退逻辑
- 定价卡片新增缓存读取/创建价格,改为两行布局防止溢出
- ChannelPricingCard 缓存列显示实际价格而非倍率
- 修复移动端 hero 区域 padding 过大
- 默认标签页标题改为 Loading...
- 新增缓存读取/创建 i18n 翻译(7 语言)

Co-Authored-By: Claude <noreply@anthropic.com>
master
fengsilin 2 weken geleden
bovenliggende
commit
387f6c1ae0
15 gewijzigde bestanden met toevoegingen van 323 en 57 verwijderingen
  1. +90
    -10
      model/pricing.go
  2. +122
    -0
      model/pricing_test.go
  3. +39
    -32
      relay/helper/price.go
  4. +1
    -1
      web/index.html
  5. +13
    -4
      web/src/components/table/model-pricing/modal/components/ChannelPricingCard.jsx
  6. +1
    -1
      web/src/components/table/model-pricing/view/card/PricingCardView.jsx
  7. +42
    -8
      web/src/helpers/utils.jsx
  8. +2
    -0
      web/src/i18n/locales/en.json
  9. +2
    -0
      web/src/i18n/locales/fr.json
  10. +2
    -0
      web/src/i18n/locales/ja.json
  11. +2
    -0
      web/src/i18n/locales/ru.json
  12. +2
    -0
      web/src/i18n/locales/vi.json
  13. +2
    -0
      web/src/i18n/locales/zh-CN.json
  14. +2
    -0
      web/src/i18n/locales/zh-TW.json
  15. +1
    -1
      web/src/pages/Home/components/HeroSection.jsx

+ 90
- 10
model/pricing.go Bestand weergeven

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

+ 122
- 0
model/pricing_test.go Bestand weergeven

@@ -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, "应选最便宜的按次渠道")
}

+ 39
- 32
relay/helper/price.go Bestand weergeven

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


+ 1
- 1
web/index.html Bestand weergeven

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


+ 13
- 4
web/src/components/table/model-pricing/modal/components/ChannelPricingCard.jsx Bestand weergeven

@@ -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,
},
]
: [];


+ 1
- 1
web/src/components/table/model-pricing/view/card/PricingCardView.jsx Bestand weergeven

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


+ 42
- 8
web/src/helpers/utils.jsx Bestand weergeven

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



+ 2
- 0
web/src/i18n/locales/en.json Bestand weergeven

@@ -2260,6 +2260,8 @@
"缓存目录": "Cache Directory",
"缓存目录磁盘空间": "Cache Directory Disk Space",
"缓存读": "Cache Read",
"缓存读取": "Cache Read",
"缓存创建": "Cache Creation",
"编辑": "Edit",
"编辑API": "Edit API",
"编辑产品": "Edit Product",


+ 2
- 0
web/src/i18n/locales/fr.json Bestand weergeven

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


+ 2
- 0
web/src/i18n/locales/ja.json Bestand weergeven

@@ -2122,6 +2122,8 @@
"缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Cache creation ratio 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}",
"缓存创建倍率 {{cacheCreationRatio}}": "Cache creation ratio {{cacheCreationRatio}}",
"缓存读": "キャッシュ読取",
"缓存读取": "キャッシュ読取",
"缓存创建": "キャッシュ作成",
"编辑": "編集",
"编辑API": "API編集",
"编辑产品": "Edit Product",


+ 2
- 0
web/src/i18n/locales/ru.json Bestand weergeven

@@ -2152,6 +2152,8 @@
"缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Коэффициент создания кэша 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}",
"缓存创建倍率 {{cacheCreationRatio}}": "Коэффициент создания кэша {{cacheCreationRatio}}",
"缓存读": "Чтение кэша",
"缓存读取": "Чтение кэша",
"缓存创建": "Создание кэша",
"编辑": "Редактировать",
"编辑API": "Редактировать API",
"编辑产品": "Редактировать продукт",


+ 2
- 0
web/src/i18n/locales/vi.json Bestand weergeven

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


+ 2
- 0
web/src/i18n/locales/zh-CN.json Bestand weergeven

@@ -2242,6 +2242,8 @@
"缓存目录": "缓存目录",
"缓存目录磁盘空间": "缓存目录磁盘空间",
"缓存读": "缓存读",
"缓存读取": "缓存读取",
"缓存创建": "缓存创建",
"编辑": "编辑",
"编辑API": "编辑API",
"编辑产品": "编辑产品",


+ 2
- 0
web/src/i18n/locales/zh-TW.json Bestand weergeven

@@ -2180,6 +2180,8 @@
"缓存创建倍率 {{cacheCreationRatio}}": "快取建立倍率 {{cacheCreationRatio}}",
"缓存目录": "快取目錄",
"缓存目录磁盘空间": "快取目錄磁碟空間",
"缓存读取": "快取讀取",
"缓存创建": "快取建立",
"编辑": "編輯",
"编辑API": "編輯API",
"编辑产品": "編輯產品",


+ 1
- 1
web/src/pages/Home/components/HeroSection.jsx Bestand weergeven

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


Laden…
Annuleren
Opslaan