- calculateModelPrice 新增缓存读取/创建价格计算逻辑 - ModelPricingTable 条件显示缓存价格列(仅 cache_ratio !== 1 时) - deploy.sh 添加镜像拉取超时(120s)和3次重试机制master
| @@ -127,7 +127,18 @@ echo " [1/4] 更新 image tag..." | |||||
| sed -i "s|${REGISTRY}:.*|${REGISTRY}:${TAG}|" docker-compose.yml | sed -i "s|${REGISTRY}:.*|${REGISTRY}:${TAG}|" docker-compose.yml | ||||
| echo " [2/4] 拉取新镜像..." | echo " [2/4] 拉取新镜像..." | ||||
| docker compose pull new-api | |||||
| PULL_TIMEOUT=120 | |||||
| for i in 1 2 3; do | |||||
| if timeout "${PULL_TIMEOUT}" docker compose pull --quiet new-api; then | |||||
| break | |||||
| fi | |||||
| if [ "$i" -eq 3 ]; then | |||||
| echo " [2/4] 拉取失败,已重试 3 次(每次 ${PULL_TIMEOUT}s 超时)" | |||||
| exit 1 | |||||
| fi | |||||
| echo " [2/4] 拉取超时或失败(${PULL_TIMEOUT}s),第 $((i+1)) 次重试..." | |||||
| sleep 5 | |||||
| done | |||||
| echo " [3/4] 重启服务..." | echo " [3/4] 重启服务..." | ||||
| docker compose up -d new-api | docker compose up -d new-api | ||||
| @@ -79,6 +79,14 @@ const ModelPricingTable = ({ | |||||
| modelData?.quota_type === 0 | modelData?.quota_type === 0 | ||||
| ? priceData.completionPrice || priceData.outputPrice | ? priceData.completionPrice || priceData.outputPrice | ||||
| : '-', | : '-', | ||||
| cachePrice: | |||||
| modelData?.quota_type === 0 && priceData.cacheRatio !== 1 | |||||
| ? priceData.cachePrice | |||||
| : '-', | |||||
| cacheCreationPrice: | |||||
| modelData?.quota_type === 0 && priceData.cacheRatio !== 1 | |||||
| ? priceData.cacheCreationPrice | |||||
| : '-', | |||||
| fixedPrice: modelData?.quota_type === 1 ? priceData.price : '-', | fixedPrice: modelData?.quota_type === 1 ? priceData.price : '-', | ||||
| }; | }; | ||||
| }); | }); | ||||
| @@ -126,6 +134,9 @@ const ModelPricingTable = ({ | |||||
| }, | }, | ||||
| }); | }); | ||||
| // 检查是否有缓存价格数据(模型支持缓存时同时显示缓存读取和缓存创建) | |||||
| const hasCachePricing = tableData.some((row) => row.cachePrice !== '-'); | |||||
| // 根据计费类型添加价格列 | // 根据计费类型添加价格列 | ||||
| if (modelData?.quota_type === 0) { | if (modelData?.quota_type === 0) { | ||||
| // 按量计费 | // 按量计费 | ||||
| @@ -155,6 +166,36 @@ const ModelPricingTable = ({ | |||||
| ), | ), | ||||
| }, | }, | ||||
| ); | ); | ||||
| // 缓存读取价格列(仅在有缓存倍率时显示) | |||||
| if (hasCachePricing) { | |||||
| columns.push({ | |||||
| title: t('缓存读取'), | |||||
| dataIndex: 'cachePrice', | |||||
| render: (text) => ( | |||||
| <> | |||||
| <div className='font-semibold text-orange-600'>{text}</div> | |||||
| <div className='text-xs text-gray-500'> | |||||
| / {tokenUnit === 'K' ? '1K' : '1M'} tokens | |||||
| </div> | |||||
| </> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| // 缓存创建价格列(仅在有缓存价格数据时显示,与缓存读取列同时出现) | |||||
| if (hasCachePricing) { | |||||
| columns.push({ | |||||
| title: t('缓存创建'), | |||||
| dataIndex: 'cacheCreationPrice', | |||||
| render: (text) => ( | |||||
| <> | |||||
| <div className='font-semibold text-orange-600'>{text}</div> | |||||
| <div className='text-xs text-gray-500'> | |||||
| / {tokenUnit === 'K' ? '1K' : '1M'} tokens | |||||
| </div> | |||||
| </> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| } else { | } else { | ||||
| // 按次计费 | // 按次计费 | ||||
| columns.push({ | columns.push({ | ||||
| @@ -652,16 +652,29 @@ export const calculateModelPrice = ({ | |||||
| const completionRatioPriceUSD = | const completionRatioPriceUSD = | ||||
| record.model_ratio * record.completion_ratio * 2 * usedGroupRatio; | record.model_ratio * record.completion_ratio * 2 * usedGroupRatio; | ||||
| // 缓存读取价格 = 输入价格 × 缓存倍率(默认 1 表示与输入价格相同) | |||||
| const cacheRatio = record.cache_ratio ?? 1; | |||||
| const cacheRatioPriceUSD = inputRatioPriceUSD * cacheRatio; | |||||
| // 缓存创建价格 = 输入价格 × 缓存创建倍率(默认 1.25) | |||||
| const cacheCreationRatio = record.cache_creation_ratio ?? 1.25; | |||||
| const cacheCreationRatioPriceUSD = inputRatioPriceUSD * cacheCreationRatio; | |||||
| const unitDivisor = tokenUnit === 'K' ? 1000 : 1; | const unitDivisor = tokenUnit === 'K' ? 1000 : 1; | ||||
| const unitLabel = tokenUnit === 'K' ? 'K' : 'M'; | const unitLabel = tokenUnit === 'K' ? 'K' : 'M'; | ||||
| const rawDisplayInput = displayPrice(inputRatioPriceUSD); | const rawDisplayInput = displayPrice(inputRatioPriceUSD); | ||||
| const rawDisplayCompletion = displayPrice(completionRatioPriceUSD); | const rawDisplayCompletion = displayPrice(completionRatioPriceUSD); | ||||
| const rawDisplayCache = displayPrice(cacheRatioPriceUSD); | |||||
| const rawDisplayCacheCreation = displayPrice(cacheCreationRatioPriceUSD); | |||||
| const numInput = | const numInput = | ||||
| parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor; | parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor; | ||||
| const numCompletion = | const numCompletion = | ||||
| parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor; | parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor; | ||||
| const numCache = | |||||
| parseFloat(rawDisplayCache.replace(/[^0-9.]/g, '')) / unitDivisor; | |||||
| const numCacheCreation = | |||||
| parseFloat(rawDisplayCacheCreation.replace(/[^0-9.]/g, '')) / unitDivisor; | |||||
| let symbol = '$'; | let symbol = '$'; | ||||
| if (currency === 'CNY') { | if (currency === 'CNY') { | ||||
| @@ -683,6 +696,10 @@ export const calculateModelPrice = ({ | |||||
| return { | return { | ||||
| inputPrice: `${symbol}${numInput.toFixed(precision)}`, | inputPrice: `${symbol}${numInput.toFixed(precision)}`, | ||||
| completionPrice: `${symbol}${numCompletion.toFixed(precision)}`, | completionPrice: `${symbol}${numCompletion.toFixed(precision)}`, | ||||
| cachePrice: `${symbol}${numCache.toFixed(precision)}`, | |||||
| cacheCreationPrice: `${symbol}${numCacheCreation.toFixed(precision)}`, | |||||
| cacheRatio, | |||||
| cacheCreationRatio, | |||||
| unitLabel, | unitLabel, | ||||
| isPerToken: true, | isPerToken: true, | ||||
| usedGroup, | usedGroup, | ||||