Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func SetTheme(t string) {

// var ChatLink = ""
// var ChatLink2 = ""
var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens
var QuotaPerUnit = 500 * 1000.0 // ¥0.002 / 1K tokens(人民币基准)
// 保留旧变量以兼容历史逻辑,实际展示由 general_setting.quota_display_type 控制
var DisplayInCurrencyEnabled = true
var DisplayTokenStatEnabled = true
Expand Down
15 changes: 7 additions & 8 deletions controller/billing.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,17 @@ func GetSubscription(c *gin.Context) {
}
quota := remainQuota + usedQuota
amount := float64(quota)
// OpenAI 兼容接口中的 *_USD 字段含义保持“额度单位”对应值:
// 我们将其解释为以“站点展示类型”为准:
// - USD: 直接除以 QuotaPerUnit
// - CNY: 先转 USD 再乘汇率
// 计价基准为人民币:quota / QuotaPerUnit 直接得到 CNY
// - CNY: 直接除以 QuotaPerUnit
// - USD: 除以 QuotaPerUnit 再除以汇率
// - TOKENS: 直接使用 tokens 数量
switch operation_setting.GetQuotaDisplayType() {
case operation_setting.QuotaDisplayTypeCNY:
amount = amount / common.QuotaPerUnit * operation_setting.USDExchangeRate
amount = amount / common.QuotaPerUnit
case operation_setting.QuotaDisplayTypeTokens:
// amount 保持 tokens 数值
default:
amount = amount / common.QuotaPerUnit
amount = amount / common.QuotaPerUnit / operation_setting.USDExchangeRate
}
Comment on lines 47 to 54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check for USDExchangeRate validation in operation_setting

# Search for USDExchangeRate assignments and validation
rg -nP -C5 'USDExchangeRate\s*=' --type=go

# Search for validation patterns (> 0, != 0, etc.)
rg -nP 'USDExchangeRate.*[>!]=\s*0' --type=go

Repository: QuantumNous/new-api

Length of output: 860


🏁 Script executed:

# Check the operation_setting package structure
find . -name "*operation_setting*" -type f | grep -E "\.(go|proto)$"

Repository: QuantumNous/new-api

Length of output: 112


🏁 Script executed:

# Examine billing.go to see the actual code at lines 47-54 and around line 99
rg -n "GetQuotaDisplayType|GetUsage" --type=go -B2 -A10 | head -100

Repository: QuantumNous/new-api

Length of output: 6307


🏁 Script executed:

# Check how USDExchangeRate is initialized and used in operation_setting
rg -n "USDExchangeRate" --type=go -B3 -A3

Repository: QuantumNous/new-api

Length of output: 3586


🏁 Script executed:

# Read the operation_setting.go file to check its contents
cat -n setting/operation_setting/operation_setting.go | head -100

Repository: QuantumNous/new-api

Length of output: 1081


🏁 Script executed:

# Check if USDExchangeRate has any getter function with validation
rg -n "func.*USDExchangeRate|USDExchangeRate\s*>" setting/operation_setting/ --type=go

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

# Check payment_setting_old.go and any other payment-related files
cat -n setting/operation_setting/payment_setting_old.go | head -50

Repository: QuantumNous/new-api

Length of output: 1474


🏁 Script executed:

# Check the context around the USDExchangeRate assignment in model/option.go
sed -n '365,375p' model/option.go

Repository: QuantumNous/new-api

Length of output: 469


🏁 Script executed:

# Search for any validation or guards around USDExchangeRate usage
rg -n "USDExchangeRate" --type=go -B2 -A2 | grep -E "(<=|>=|>|<|!=|==|if|validate|check|guard)" -B2 -A2

Repository: QuantumNous/new-api

Length of output: 305


🏁 Script executed:

# Check if there's any initialization or validation logic when loading settings
rg -n "LoadOptions|InitOptions|USDExchangeRate" --type=go -B5 -A5 | head -80

Repository: QuantumNous/new-api

Length of output: 4805


🏁 Script executed:

# Check if there are any guards around divisions by USDExchangeRate
rg -n "USDExchangeRate" controller/billing.go logger/logger.go --type=go -B5 | grep -E "(if|guard|validate|<=|>=)"

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

# Verify the entire context of controller/billing.go around the issue
sed -n '45,60p' controller/billing.go
sed -n '92,105p' controller/billing.go

Repository: QuantumNous/new-api

Length of output: 1027


Validate USDExchangeRate to prevent division by zero.

The default case divides by operation_setting.USDExchangeRate at lines 53 and 99 without validation. If the exchange rate is zero or becomes zero (e.g., from a failed strconv.ParseFloat in model/option.go:369), this will cause a runtime panic. Additional unguarded divisions exist in logger/logger.go at lines 142 and 168.

🛡️ Proposed fix to add validation
 	switch operation_setting.GetQuotaDisplayType() {
 	case operation_setting.QuotaDisplayTypeCNY:
 		amount = amount / common.QuotaPerUnit
 	case operation_setting.QuotaDisplayTypeTokens:
 		// amount 保持 tokens 数值
 	default:
+		if operation_setting.USDExchangeRate <= 0 {
+			operation_setting.USDExchangeRate = 1.0 // fallback to 1:1 if invalid
+		}
 		amount = amount / common.QuotaPerUnit / operation_setting.USDExchangeRate
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
switch operation_setting.GetQuotaDisplayType() {
case operation_setting.QuotaDisplayTypeCNY:
amount = amount / common.QuotaPerUnit * operation_setting.USDExchangeRate
amount = amount / common.QuotaPerUnit
case operation_setting.QuotaDisplayTypeTokens:
// amount 保持 tokens 数值
default:
amount = amount / common.QuotaPerUnit
amount = amount / common.QuotaPerUnit / operation_setting.USDExchangeRate
}
switch operation_setting.GetQuotaDisplayType() {
case operation_setting.QuotaDisplayTypeCNY:
amount = amount / common.QuotaPerUnit
case operation_setting.QuotaDisplayTypeTokens:
// amount 保持 tokens 数值
default:
if operation_setting.USDExchangeRate <= 0 {
operation_setting.USDExchangeRate = 1.0 // fallback to 1:1 if invalid
}
amount = amount / common.QuotaPerUnit / operation_setting.USDExchangeRate
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/billing.go` around lines 47 - 54, The default branch in
controller.billing uses operation_setting.USDExchangeRate to divide amount (and
similar divisions exist in logger code); add a guard that checks
operation_setting.USDExchangeRate != 0 before performing the division (e.g.,
capture into a local safeRate := operation_setting.USDExchangeRate, if safeRate
== 0 then log a warning and either return/skip conversion or use a defined
fallback rate), update the default case in the switch around
operation_setting.GetQuotaDisplayType() to use safeRate for dividing amount, and
apply the same validation pattern to any logger functions that divide by
operation_setting.USDExchangeRate so no unguarded division can panic.

if token != nil && token.UnlimitedQuota {
amount = 100000000
Expand Down Expand Up @@ -93,11 +92,11 @@ func GetUsage(c *gin.Context) {
amount := float64(quota)
switch operation_setting.GetQuotaDisplayType() {
case operation_setting.QuotaDisplayTypeCNY:
amount = amount / common.QuotaPerUnit * operation_setting.USDExchangeRate
amount = amount / common.QuotaPerUnit
case operation_setting.QuotaDisplayTypeTokens:
// tokens 保持原值
default:
amount = amount / common.QuotaPerUnit
amount = amount / common.QuotaPerUnit / operation_setting.USDExchangeRate
}
usage := OpenAIUsageResponse{
Object: "list",
Expand Down
30 changes: 24 additions & 6 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Frontend Development - Backend built from local source
# Frontend Development - Backend + Frontend in Docker
#
# Usage:
# 1. docker compose -f docker-compose.dev.yml up -d
# 2. cd web && bun install && bun run dev
# 3. Open http://localhost:3001 (Rsbuild dev server, API auto-proxied to :3000)
# docker compose -f docker-compose.dev.yml up -d
# Open http://localhost:5173 (Rsbuild dev server, API auto-proxied to :3000)
#
# Rebuild backend after Go code changes:
# docker compose -f docker-compose.dev.yml up -d --build new-api
Expand Down Expand Up @@ -39,15 +38,33 @@ services:
networks:
- dev-network

frontend:
image: oven/bun:1
container_name: new-api-frontend
restart: unless-stopped
working_dir: /app
ports:
- "5173:3000"
volumes:
- ./web/default:/app
- frontend_node_modules:/app/node_modules
environment:
- VITE_REACT_APP_SERVER_URL=http://new-api-dev:3000
command: sh -c "bun install && bun run dev --host 0.0.0.0"
depends_on:
- new-api
networks:
- dev-network

redis:
image: redis:7-alpine
image: redis:latest
container_name: new-api-dev-redis
restart: unless-stopped
networks:
- dev-network

postgres:
image: postgres:15-alpine
image: postgres:15
container_name: new-api-dev-pg
restart: unless-stopped
environment:
Expand All @@ -67,6 +84,7 @@ services:
volumes:
dev_data:
dev_pg_data:
frontend_node_modules:

networks:
dev-network:
Expand Down
24 changes: 12 additions & 12 deletions logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,14 @@ func logHelper(ctx context.Context, level string, msg string) {
}

func LogQuota(quota int) string {
// 新逻辑:根据额度展示类型输出
// 计价基准为人民币,直接除以 QuotaPerUnit 即为 CNY
q := float64(quota)
switch operation_setting.GetQuotaDisplayType() {
case operation_setting.QuotaDisplayTypeCNY:
usd := q / common.QuotaPerUnit
cny := usd * operation_setting.USDExchangeRate
cny := q / common.QuotaPerUnit
return fmt.Sprintf("¥%.6f 额度", cny)
case operation_setting.QuotaDisplayTypeCustom:
usd := q / common.QuotaPerUnit
base := q / common.QuotaPerUnit
rate := operation_setting.GetGeneralSetting().CustomCurrencyExchangeRate
symbol := operation_setting.GetGeneralSetting().CustomCurrencySymbol
if symbol == "" {
Expand All @@ -135,24 +134,24 @@ func LogQuota(quota int) string {
if rate <= 0 {
rate = 1
}
v := usd * rate
v := base * rate
return fmt.Sprintf("%s%.6f 额度", symbol, v)
case operation_setting.QuotaDisplayTypeTokens:
return fmt.Sprintf("%d 点额度", quota)
default: // USD
return fmt.Sprintf("$%.6f 额度", q/common.QuotaPerUnit)
usd := q / common.QuotaPerUnit / operation_setting.USDExchangeRate
return fmt.Sprintf("$%.6f 额度", usd)
}
Comment on lines 141 to 144

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate USDExchangeRate before division.

Line 142 (and line 168 in FormatQuota) divides by operation_setting.USDExchangeRate without validation. If the exchange rate is zero or negative, this will cause a runtime panic or produce nonsensical results. The custom currency case has proper validation (lines 134-136), but the USD case does not.

🛡️ Proposed fix to add validation
 	default: // USD
+		exchangeRate := operation_setting.USDExchangeRate
+		if exchangeRate <= 0 {
+			exchangeRate = 1.0
+		}
-		usd := q / common.QuotaPerUnit / operation_setting.USDExchangeRate
+		usd := q / common.QuotaPerUnit / exchangeRate
 		return fmt.Sprintf("$%.6f 额度", usd)
 	}

Apply a similar fix to FormatQuota at lines 167-169.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@logger/logger.go` around lines 141 - 144, FormatQuota currently divides by
operation_setting.USDExchangeRate in the default (USD) branch without
validation; replicate the same validation used in the custom currency branch
(check that operation_setting.USDExchangeRate > 0) before doing the division in
the default/USD case inside FormatQuota, and if the rate is invalid return a
safe fallback message or handle the error similarly to the custom currency path
to avoid divide-by-zero or nonsensical results.

}

func FormatQuota(quota int) string {
q := float64(quota)
switch operation_setting.GetQuotaDisplayType() {
case operation_setting.QuotaDisplayTypeCNY:
usd := q / common.QuotaPerUnit
cny := usd * operation_setting.USDExchangeRate
cny := q / common.QuotaPerUnit
return fmt.Sprintf("¥%.6f", cny)
case operation_setting.QuotaDisplayTypeCustom:
usd := q / common.QuotaPerUnit
base := q / common.QuotaPerUnit
rate := operation_setting.GetGeneralSetting().CustomCurrencyExchangeRate
symbol := operation_setting.GetGeneralSetting().CustomCurrencySymbol
if symbol == "" {
Expand All @@ -161,12 +160,13 @@ func FormatQuota(quota int) string {
if rate <= 0 {
rate = 1
}
v := usd * rate
v := base * rate
return fmt.Sprintf("%s%.6f", symbol, v)
case operation_setting.QuotaDisplayTypeTokens:
return fmt.Sprintf("%d", quota)
default:
return fmt.Sprintf("$%.6f", q/common.QuotaPerUnit)
default: // USD
usd := q / common.QuotaPerUnit / operation_setting.USDExchangeRate
return fmt.Sprintf("$%.6f", usd)
}
}

Expand Down
21 changes: 20 additions & 1 deletion model/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,27 @@ func RecordConsumeLog(c *gin.Context, userId int, params RecordConsumeLogParams)
logger.LogError(c, "failed to record log: "+err.Error())
}
if common.DataExportEnabled {
// Extract cache token counts from the Other map before spawning goroutine
cacheTokens := 0
cacheCreationTokens := 0
if v, ok := params.Other["cache_tokens"]; ok {
switch n := v.(type) {
case int:
cacheTokens = n
case float64:
cacheTokens = int(n)
}
}
if v, ok := params.Other["cache_creation_tokens"]; ok {
switch n := v.(type) {
case int:
cacheCreationTokens = n
case float64:
cacheCreationTokens = int(n)
}
}
gopool.Go(func() {
LogQuotaData(userId, username, params.ModelName, params.Quota, common.GetTimestamp(), params.PromptTokens+params.CompletionTokens)
LogQuotaData(userId, username, params.ModelName, params.Quota, common.GetTimestamp(), params.PromptTokens+params.CompletionTokens, cacheTokens, cacheCreationTokens)
})
}
}
Expand Down
58 changes: 33 additions & 25 deletions model/usedata.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import (

// QuotaData 柱状图数据
type QuotaData struct {
Id int `json:"id"`
UserID int `json:"user_id" gorm:"index"`
Username string `json:"username" gorm:"index:idx_qdt_model_user_name,priority:2;size:64;default:''"`
ModelName string `json:"model_name" gorm:"index:idx_qdt_model_user_name,priority:1;size:64;default:''"`
CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_qdt_created_at,priority:2"`
TokenUsed int `json:"token_used" gorm:"default:0"`
Count int `json:"count" gorm:"default:0"`
Quota int `json:"quota" gorm:"default:0"`
Id int `json:"id"`
UserID int `json:"user_id" gorm:"index"`
Username string `json:"username" gorm:"index:idx_qdt_model_user_name,priority:2;size:64;default:''"`
ModelName string `json:"model_name" gorm:"index:idx_qdt_model_user_name,priority:1;size:64;default:''"`
CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_qdt_created_at,priority:2"`
TokenUsed int `json:"token_used" gorm:"default:0"`
Count int `json:"count" gorm:"default:0"`
Quota int `json:"quota" gorm:"default:0"`
CacheTokens int `json:"cache_tokens" gorm:"default:0"` // tokens read from cache (prompt cache hits)
CacheCreationTokens int `json:"cache_creation_tokens" gorm:"default:0"` // tokens written to cache
}

func UpdateQuotaData() {
Expand All @@ -34,34 +36,38 @@ func UpdateQuotaData() {
var CacheQuotaData = make(map[string]*QuotaData)
var CacheQuotaDataLock = sync.Mutex{}

func logQuotaDataCache(userId int, username string, modelName string, quota int, createdAt int64, tokenUsed int) {
func logQuotaDataCache(userId int, username string, modelName string, quota int, createdAt int64, tokenUsed int, cacheTokens int, cacheCreationTokens int) {
key := fmt.Sprintf("%d-%s-%s-%d", userId, username, modelName, createdAt)
quotaData, ok := CacheQuotaData[key]
if ok {
quotaData.Count += 1
quotaData.Quota += quota
quotaData.TokenUsed += tokenUsed
quotaData.CacheTokens += cacheTokens
quotaData.CacheCreationTokens += cacheCreationTokens
} else {
quotaData = &QuotaData{
UserID: userId,
Username: username,
ModelName: modelName,
CreatedAt: createdAt,
Count: 1,
Quota: quota,
TokenUsed: tokenUsed,
UserID: userId,
Username: username,
ModelName: modelName,
CreatedAt: createdAt,
Count: 1,
Quota: quota,
TokenUsed: tokenUsed,
CacheTokens: cacheTokens,
CacheCreationTokens: cacheCreationTokens,
}
}
CacheQuotaData[key] = quotaData
}

func LogQuotaData(userId int, username string, modelName string, quota int, createdAt int64, tokenUsed int) {
func LogQuotaData(userId int, username string, modelName string, quota int, createdAt int64, tokenUsed int, cacheTokens int, cacheCreationTokens int) {
// 只精确到小时
createdAt = createdAt - (createdAt % 3600)

CacheQuotaDataLock.Lock()
defer CacheQuotaDataLock.Unlock()
logQuotaDataCache(userId, username, modelName, quota, createdAt, tokenUsed)
logQuotaDataCache(userId, username, modelName, quota, createdAt, tokenUsed, cacheTokens, cacheCreationTokens)
}

func SaveQuotaDataCache() {
Expand All @@ -80,7 +86,7 @@ func SaveQuotaDataCache() {
//quotaDataDB.Count += quotaData.Count
//quotaDataDB.Quota += quotaData.Quota
//DB.Table("quota_data").Save(quotaDataDB)
increaseQuotaData(quotaData.UserID, quotaData.Username, quotaData.ModelName, quotaData.Count, quotaData.Quota, quotaData.CreatedAt, quotaData.TokenUsed)
increaseQuotaData(quotaData.UserID, quotaData.Username, quotaData.ModelName, quotaData.Count, quotaData.Quota, quotaData.CreatedAt, quotaData.TokenUsed, quotaData.CacheTokens, quotaData.CacheCreationTokens)
} else {
DB.Table("quota_data").Create(quotaData)
}
Expand All @@ -89,12 +95,14 @@ func SaveQuotaDataCache() {
common.SysLog(fmt.Sprintf("保存数据看板数据成功,共保存%d条数据", size))
}

func increaseQuotaData(userId int, username string, modelName string, count int, quota int, createdAt int64, tokenUsed int) {
func increaseQuotaData(userId int, username string, modelName string, count int, quota int, createdAt int64, tokenUsed int, cacheTokens int, cacheCreationTokens int) {
err := DB.Table("quota_data").Where("user_id = ? and username = ? and model_name = ? and created_at = ?",
userId, username, modelName, createdAt).Updates(map[string]interface{}{
"count": gorm.Expr("count + ?", count),
"quota": gorm.Expr("quota + ?", quota),
"token_used": gorm.Expr("token_used + ?", tokenUsed),
"count": gorm.Expr("count + ?", count),
"quota": gorm.Expr("quota + ?", quota),
"token_used": gorm.Expr("token_used + ?", tokenUsed),
"cache_tokens": gorm.Expr("cache_tokens + ?", cacheTokens),
"cache_creation_tokens": gorm.Expr("cache_creation_tokens + ?", cacheCreationTokens),
}).Error
if err != nil {
common.SysLog(fmt.Sprintf("increaseQuotaData error: %s", err))
Expand All @@ -118,7 +126,7 @@ func GetQuotaDataByUserId(userId int, startTime int64, endTime int64) (quotaData
func GetQuotaDataGroupByUser(startTime int64, endTime int64) (quotaData []*QuotaData, err error) {
var quotaDatas []*QuotaData
err = DB.Table("quota_data").
Select("username, created_at, sum(count) as count, sum(quota) as quota, sum(token_used) as token_used").
Select("username, created_at, sum(count) as count, sum(quota) as quota, sum(token_used) as token_used, sum(cache_tokens) as cache_tokens, sum(cache_creation_tokens) as cache_creation_tokens").
Where("created_at >= ? and created_at <= ?", startTime, endTime).
Group("username, created_at").
Find(&quotaDatas).Error
Expand All @@ -133,6 +141,6 @@ func GetAllQuotaDates(startTime int64, endTime int64, username string) (quotaDat
// 从quota_data表中查询数据
// only select model_name, sum(count) as count, sum(quota) as quota, model_name, created_at from quota_data group by model_name, created_at;
//err = DB.Table("quota_data").Where("created_at >= ? and created_at <= ?", startTime, endTime).Find(&quotaDatas).Error
err = DB.Table("quota_data").Select("model_name, sum(count) as count, sum(quota) as quota, sum(token_used) as token_used, created_at").Where("created_at >= ? and created_at <= ?", startTime, endTime).Group("model_name, created_at").Find(&quotaDatas).Error
err = DB.Table("quota_data").Select("model_name, sum(count) as count, sum(quota) as quota, sum(token_used) as token_used, sum(cache_tokens) as cache_tokens, sum(cache_creation_tokens) as cache_creation_tokens, created_at").Where("created_at >= ? and created_at <= ?", startTime, endTime).Group("model_name, created_at").Find(&quotaDatas).Error
return quotaDatas, err
}
2 changes: 1 addition & 1 deletion setting/operation_setting/general_setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var generalSetting = GeneralSetting{
DocsLink: "https://docs.newapi.pro",
PingIntervalEnabled: false,
PingIntervalSeconds: 60,
QuotaDisplayType: QuotaDisplayTypeUSD,
QuotaDisplayType: QuotaDisplayTypeCNY,
CustomCurrencySymbol: "¤",
CustomCurrencyExchangeRate: 1.0,
}
Expand Down
18 changes: 7 additions & 11 deletions setting/ratio_setting/model_ratio.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,16 @@ import (
"github.com/QuantumNous/new-api/types"
)

// from songquanpeng/one-api
// 计价基准:人民币 (CNY)
const (
USD2RMB = 7.3 // 暂定 1 USD = 7.3 RMB
USD = 500 // $0.002 = 1 -> $1 = 500
RMB = USD / USD2RMB
USD2RMB = 7.3 // 暂定 1 USD = 7.3 RMB
RMB = 500 // ¥0.002 = 1 -> ¥1 = 500(基准货币)
USD = RMB * USD2RMB // $1 = 3650(由汇率换算,仅供参考)
)

// modelRatio
// https://platform.openai.com/docs/models/model-endpoint-compatibility
// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Blfmc9dlf
// https://openai.com/pricing
// TODO: when a new api is enabled, check the pricing here
// 1 === $0.002 / 1K tokens
// 1 === ¥0.014 / 1k tokens
// modelRatio — 以人民币为基准
// 1 === ¥0.002 / 1K tokens === ¥2 / 1M tokens
// 1 === $0.000274 / 1K tokens(按 7.3 汇率换算)

var defaultModelRatio = map[string]float64{
//"midjourney": 50,
Expand Down
Loading