feat: 分组特殊倍率

This commit is contained in:
creamlike1024
2025-06-11 23:46:59 +08:00
parent 136a46218b
commit a28ab3628a
13 changed files with 323 additions and 154 deletions

View File

@@ -166,7 +166,7 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
milliseconds := tok.Sub(tik).Milliseconds() milliseconds := tok.Sub(tik).Milliseconds()
consumedTime := float64(milliseconds) / 1000.0 consumedTime := float64(milliseconds) / 1000.0
other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatio, priceData.CompletionRatio, other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatio, priceData.CompletionRatio,
usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice) usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice, priceData.UserGroupRatio)
model.RecordConsumeLog(c, 1, channel.Id, usage.PromptTokens, usage.CompletionTokens, info.OriginModelName, "模型测试", model.RecordConsumeLog(c, 1, channel.Id, usage.PromptTokens, usage.CompletionTokens, info.OriginModelName, "模型测试",
quota, "模型测试", 0, quota, int(consumedTime), false, info.Group, other) quota, "模型测试", 0, quota, int(consumedTime), false, info.Group, other)
common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody))) common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody)))

View File

@@ -1,10 +1,11 @@
package controller package controller
import ( import (
"github.com/gin-gonic/gin"
"one-api/model" "one-api/model"
"one-api/setting" "one-api/setting"
"one-api/setting/operation_setting" "one-api/setting/operation_setting"
"github.com/gin-gonic/gin"
) )
func GetPricing(c *gin.Context) { func GetPricing(c *gin.Context) {
@@ -20,6 +21,12 @@ func GetPricing(c *gin.Context) {
user, err := model.GetUserCache(userId.(int)) user, err := model.GetUserCache(userId.(int))
if err == nil { if err == nil {
group = user.Group group = user.Group
for g := range groupRatio {
ratio, ok := setting.GetGroupGroupRatio(group, g)
if ok {
groupRatio[g] = ratio
}
}
} }
} }

View File

@@ -98,6 +98,7 @@ func InitOptionMap() {
common.OptionMap["ModelPrice"] = operation_setting.ModelPrice2JSONString() common.OptionMap["ModelPrice"] = operation_setting.ModelPrice2JSONString()
common.OptionMap["CacheRatio"] = operation_setting.CacheRatio2JSONString() common.OptionMap["CacheRatio"] = operation_setting.CacheRatio2JSONString()
common.OptionMap["GroupRatio"] = setting.GroupRatio2JSONString() common.OptionMap["GroupRatio"] = setting.GroupRatio2JSONString()
common.OptionMap["GroupGroupRatio"] = setting.GroupGroupRatio2JSONString()
common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString() common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString()
common.OptionMap["CompletionRatio"] = operation_setting.CompletionRatio2JSONString() common.OptionMap["CompletionRatio"] = operation_setting.CompletionRatio2JSONString()
common.OptionMap["TopUpLink"] = common.TopUpLink common.OptionMap["TopUpLink"] = common.TopUpLink
@@ -355,6 +356,8 @@ func updateOptionMap(key string, value string) (err error) {
err = operation_setting.UpdateModelRatioByJSONString(value) err = operation_setting.UpdateModelRatioByJSONString(value)
case "GroupRatio": case "GroupRatio":
err = setting.UpdateGroupRatioByJSONString(value) err = setting.UpdateGroupRatioByJSONString(value)
case "GroupGroupRatio":
err = setting.UpdateGroupGroupRatioByJSONString(value)
case "UserUsableGroups": case "UserUsableGroups":
err = setting.UpdateUserUsableGroupsByJSONString(value) err = setting.UpdateUserUsableGroupsByJSONString(value)
case "CompletionRatio": case "CompletionRatio":

View File

@@ -61,6 +61,7 @@ type RelayInfo struct {
TokenKey string TokenKey string
UserId int UserId int
Group string Group string
UserGroup string
TokenUnlimited bool TokenUnlimited bool
StartTime time.Time StartTime time.Time
FirstResponseTime time.Time FirstResponseTime time.Time
@@ -204,6 +205,7 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
TokenKey: tokenKey, TokenKey: tokenKey,
UserId: userId, UserId: userId,
Group: group, Group: group,
UserGroup: c.GetString(constant.ContextKeyUserGroup),
TokenUnlimited: tokenUnlimited, TokenUnlimited: tokenUnlimited,
StartTime: startTime, StartTime: startTime,
FirstResponseTime: startTime.Add(-time.Second), FirstResponseTime: startTime.Add(-time.Second),

View File

@@ -2,12 +2,13 @@ package helper
import ( import (
"fmt" "fmt"
"github.com/gin-gonic/gin"
"one-api/common" "one-api/common"
constant2 "one-api/constant" constant2 "one-api/constant"
relaycommon "one-api/relay/common" relaycommon "one-api/relay/common"
"one-api/setting" "one-api/setting"
"one-api/setting/operation_setting" "one-api/setting/operation_setting"
"github.com/gin-gonic/gin"
) )
type PriceData struct { type PriceData struct {
@@ -18,6 +19,7 @@ type PriceData struct {
CacheCreationRatio float64 CacheCreationRatio float64
ImageRatio float64 ImageRatio float64
GroupRatio float64 GroupRatio float64
UserGroupRatio float64
UsePrice bool UsePrice bool
ShouldPreConsumedQuota int ShouldPreConsumedQuota int
} }
@@ -29,6 +31,10 @@ func (p PriceData) ToSetting() string {
func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens int, maxTokens int) (PriceData, error) { func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens int, maxTokens int) (PriceData, error) {
modelPrice, usePrice := operation_setting.GetModelPrice(info.OriginModelName, false) modelPrice, usePrice := operation_setting.GetModelPrice(info.OriginModelName, false)
groupRatio := setting.GetGroupRatio(info.Group) groupRatio := setting.GetGroupRatio(info.Group)
userGroupRatio, ok := setting.GetGroupGroupRatio(info.UserGroup, info.Group)
if ok {
groupRatio = userGroupRatio
}
var preConsumedQuota int var preConsumedQuota int
var modelRatio float64 var modelRatio float64
var completionRatio float64 var completionRatio float64
@@ -69,6 +75,7 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
ModelRatio: modelRatio, ModelRatio: modelRatio,
CompletionRatio: completionRatio, CompletionRatio: completionRatio,
GroupRatio: groupRatio, GroupRatio: groupRatio,
UserGroupRatio: userGroupRatio,
UsePrice: usePrice, UsePrice: usePrice,
CacheRatio: cacheRatio, CacheRatio: cacheRatio,
ImageRatio: imageRatio, ImageRatio: imageRatio,

View File

@@ -363,6 +363,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
modelRatio := priceData.ModelRatio modelRatio := priceData.ModelRatio
groupRatio := priceData.GroupRatio groupRatio := priceData.GroupRatio
modelPrice := priceData.ModelPrice modelPrice := priceData.ModelPrice
userGroupRatio := priceData.UserGroupRatio
// Convert values to decimal for precise calculation // Convert values to decimal for precise calculation
dPromptTokens := decimal.NewFromInt(int64(promptTokens)) dPromptTokens := decimal.NewFromInt(int64(promptTokens))
@@ -510,7 +511,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
if extraContent != "" { if extraContent != "" {
logContent += ", " + extraContent logContent += ", " + extraContent
} }
other := service.GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice) other := service.GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio)
if imageTokens != 0 { if imageTokens != 0 {
other["image"] = true other["image"] = true
other["image_ratio"] = imageRatio other["image_ratio"] = imageRatio

View File

@@ -8,7 +8,7 @@ import (
) )
func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64, func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
cacheTokens int, cacheRatio float64, modelPrice float64) map[string]interface{} { cacheTokens int, cacheRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} {
other := make(map[string]interface{}) other := make(map[string]interface{})
other["model_ratio"] = modelRatio other["model_ratio"] = modelRatio
other["group_ratio"] = groupRatio other["group_ratio"] = groupRatio
@@ -16,6 +16,7 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
other["cache_tokens"] = cacheTokens other["cache_tokens"] = cacheTokens
other["cache_ratio"] = cacheRatio other["cache_ratio"] = cacheRatio
other["model_price"] = modelPrice other["model_price"] = modelPrice
other["user_group_ratio"] = userGroupRatio
other["frt"] = float64(relayInfo.FirstResponseTime.UnixMilli() - relayInfo.StartTime.UnixMilli()) other["frt"] = float64(relayInfo.FirstResponseTime.UnixMilli() - relayInfo.StartTime.UnixMilli())
if relayInfo.ReasoningEffort != "" { if relayInfo.ReasoningEffort != "" {
other["reasoning_effort"] = relayInfo.ReasoningEffort other["reasoning_effort"] = relayInfo.ReasoningEffort
@@ -30,8 +31,8 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
return other return other
} }
func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice float64) map[string]interface{} { func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice) info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio)
info["ws"] = true info["ws"] = true
info["audio_input"] = usage.InputTokenDetails.AudioTokens info["audio_input"] = usage.InputTokenDetails.AudioTokens
info["audio_output"] = usage.OutputTokenDetails.AudioTokens info["audio_output"] = usage.OutputTokenDetails.AudioTokens
@@ -42,8 +43,8 @@ func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, us
return info return info
} }
func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice float64) map[string]interface{} { func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice) info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio)
info["audio"] = true info["audio"] = true
info["audio_input"] = usage.PromptTokensDetails.AudioTokens info["audio_input"] = usage.PromptTokensDetails.AudioTokens
info["audio_output"] = usage.CompletionTokenDetails.AudioTokens info["audio_output"] = usage.CompletionTokenDetails.AudioTokens
@@ -55,8 +56,8 @@ func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
} }
func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64, func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
cacheTokens int, cacheRatio float64, cacheCreationTokens int, cacheCreationRatio float64, modelPrice float64) map[string]interface{} { cacheTokens int, cacheRatio float64, cacheCreationTokens int, cacheCreationRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice) info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio)
info["claude"] = true info["claude"] = true
info["cache_creation_tokens"] = cacheCreationTokens info["cache_creation_tokens"] = cacheCreationTokens
info["cache_creation_ratio"] = cacheCreationRatio info["cache_creation_ratio"] = cacheCreationRatio

View File

@@ -94,6 +94,10 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag
audioInputTokens := usage.InputTokenDetails.AudioTokens audioInputTokens := usage.InputTokenDetails.AudioTokens
audioOutTokens := usage.OutputTokenDetails.AudioTokens audioOutTokens := usage.OutputTokenDetails.AudioTokens
groupRatio := setting.GetGroupRatio(relayInfo.Group) groupRatio := setting.GetGroupRatio(relayInfo.Group)
userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group)
if ok {
groupRatio = userGroupRatio
}
modelRatio, _ := operation_setting.GetModelRatio(modelName) modelRatio, _ := operation_setting.GetModelRatio(modelName)
quotaInfo := QuotaInfo{ quotaInfo := QuotaInfo{
@@ -145,6 +149,11 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
audioRatio := decimal.NewFromFloat(operation_setting.GetAudioRatio(relayInfo.OriginModelName)) audioRatio := decimal.NewFromFloat(operation_setting.GetAudioRatio(relayInfo.OriginModelName))
audioCompletionRatio := decimal.NewFromFloat(operation_setting.GetAudioCompletionRatio(modelName)) audioCompletionRatio := decimal.NewFromFloat(operation_setting.GetAudioCompletionRatio(modelName))
actualGroupRatio := groupRatio
userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group)
if ok {
actualGroupRatio = userGroupRatio
}
quotaInfo := QuotaInfo{ quotaInfo := QuotaInfo{
InputDetails: TokenDetails{ InputDetails: TokenDetails{
TextTokens: textInputTokens, TextTokens: textInputTokens,
@@ -157,7 +166,7 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
ModelName: modelName, ModelName: modelName,
UsePrice: usePrice, UsePrice: usePrice,
ModelRatio: modelRatio, ModelRatio: modelRatio,
GroupRatio: groupRatio, GroupRatio: actualGroupRatio,
} }
quota := calculateAudioQuota(quotaInfo) quota := calculateAudioQuota(quotaInfo)
@@ -189,7 +198,7 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
logContent += ", " + extraContent logContent += ", " + extraContent
} }
other := GenerateWssOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio, other := GenerateWssOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio,
completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice) completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, userGroupRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.InputTokens, usage.OutputTokens, logModel, model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.InputTokens, usage.OutputTokens, logModel,
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other) tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other)
} }
@@ -207,7 +216,7 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
modelRatio := priceData.ModelRatio modelRatio := priceData.ModelRatio
groupRatio := priceData.GroupRatio groupRatio := priceData.GroupRatio
modelPrice := priceData.ModelPrice modelPrice := priceData.ModelPrice
userGroupRatio := priceData.UserGroupRatio
cacheRatio := priceData.CacheRatio cacheRatio := priceData.CacheRatio
cacheTokens := usage.PromptTokensDetails.CachedTokens cacheTokens := usage.PromptTokensDetails.CachedTokens
@@ -256,7 +265,7 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
} }
other := GenerateClaudeOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, other := GenerateClaudeOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio,
cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice) cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice, userGroupRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, modelName, model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, modelName,
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other) tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other)
} }
@@ -281,6 +290,12 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
modelPrice := priceData.ModelPrice modelPrice := priceData.ModelPrice
usePrice := priceData.UsePrice usePrice := priceData.UsePrice
actualGroupRatio := groupRatio
userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group)
if ok {
actualGroupRatio = userGroupRatio
}
quotaInfo := QuotaInfo{ quotaInfo := QuotaInfo{
InputDetails: TokenDetails{ InputDetails: TokenDetails{
TextTokens: textInputTokens, TextTokens: textInputTokens,
@@ -293,7 +308,7 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
ModelName: relayInfo.OriginModelName, ModelName: relayInfo.OriginModelName,
UsePrice: usePrice, UsePrice: usePrice,
ModelRatio: modelRatio, ModelRatio: modelRatio,
GroupRatio: groupRatio, GroupRatio: actualGroupRatio,
} }
quota := calculateAudioQuota(quotaInfo) quota := calculateAudioQuota(quotaInfo)
@@ -333,7 +348,7 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
logContent += ", " + extraContent logContent += ", " + extraContent
} }
other := GenerateAudioOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio, other := GenerateAudioOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio,
completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice) completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, userGroupRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.PromptTokens, usage.CompletionTokens, logModel, model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.PromptTokens, usage.CompletionTokens, logModel,
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other) tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other)
} }

View File

@@ -14,10 +14,16 @@ var groupRatio = map[string]float64{
} }
var groupRatioMutex sync.RWMutex var groupRatioMutex sync.RWMutex
var GroupGroupRatio = map[string]map[string]float64{
"vip": {
"edit_this": 0.9,
},
}
func GetGroupRatioCopy() map[string]float64 { func GetGroupRatioCopy() map[string]float64 {
groupRatioMutex.RLock() groupRatioMutex.RLock()
defer groupRatioMutex.RUnlock() defer groupRatioMutex.RUnlock()
groupRatioCopy := make(map[string]float64) groupRatioCopy := make(map[string]float64)
for k, v := range groupRatio { for k, v := range groupRatio {
groupRatioCopy[k] = v groupRatioCopy[k] = v
@@ -28,7 +34,7 @@ func GetGroupRatioCopy() map[string]float64 {
func ContainsGroupRatio(name string) bool { func ContainsGroupRatio(name string) bool {
groupRatioMutex.RLock() groupRatioMutex.RLock()
defer groupRatioMutex.RUnlock() defer groupRatioMutex.RUnlock()
_, ok := groupRatio[name] _, ok := groupRatio[name]
return ok return ok
} }
@@ -36,7 +42,7 @@ func ContainsGroupRatio(name string) bool {
func GroupRatio2JSONString() string { func GroupRatio2JSONString() string {
groupRatioMutex.RLock() groupRatioMutex.RLock()
defer groupRatioMutex.RUnlock() defer groupRatioMutex.RUnlock()
jsonBytes, err := json.Marshal(groupRatio) jsonBytes, err := json.Marshal(groupRatio)
if err != nil { if err != nil {
common.SysError("error marshalling model ratio: " + err.Error()) common.SysError("error marshalling model ratio: " + err.Error())
@@ -47,7 +53,7 @@ func GroupRatio2JSONString() string {
func UpdateGroupRatioByJSONString(jsonStr string) error { func UpdateGroupRatioByJSONString(jsonStr string) error {
groupRatioMutex.Lock() groupRatioMutex.Lock()
defer groupRatioMutex.Unlock() defer groupRatioMutex.Unlock()
groupRatio = make(map[string]float64) groupRatio = make(map[string]float64)
return json.Unmarshal([]byte(jsonStr), &groupRatio) return json.Unmarshal([]byte(jsonStr), &groupRatio)
} }
@@ -55,7 +61,7 @@ func UpdateGroupRatioByJSONString(jsonStr string) error {
func GetGroupRatio(name string) float64 { func GetGroupRatio(name string) float64 {
groupRatioMutex.RLock() groupRatioMutex.RLock()
defer groupRatioMutex.RUnlock() defer groupRatioMutex.RUnlock()
ratio, ok := groupRatio[name] ratio, ok := groupRatio[name]
if !ok { if !ok {
common.SysError("group ratio not found: " + name) common.SysError("group ratio not found: " + name)
@@ -64,6 +70,31 @@ func GetGroupRatio(name string) float64 {
return ratio return ratio
} }
func GetGroupGroupRatio(group, name string) (float64, bool) {
gp, ok := GroupGroupRatio[group]
if !ok {
return -1, false
}
ratio, ok := gp[name]
if !ok {
return -1, false
}
return ratio, true
}
func GroupGroupRatio2JSONString() string {
jsonBytes, err := json.Marshal(GroupGroupRatio)
if err != nil {
common.SysError("error marshalling group-group ratio: " + err.Error())
}
return string(jsonBytes)
}
func UpdateGroupGroupRatioByJSONString(jsonStr string) error {
GroupGroupRatio = make(map[string]map[string]float64)
return json.Unmarshal([]byte(jsonStr), &GroupGroupRatio)
}
func CheckGroupRatio(jsonStr string) error { func CheckGroupRatio(jsonStr string) error {
checkGroupRatio := make(map[string]float64) checkGroupRatio := make(map[string]float64)
err := json.Unmarshal([]byte(jsonStr), &checkGroupRatio) err := json.Unmarshal([]byte(jsonStr), &checkGroupRatio)

View File

@@ -30,6 +30,7 @@ const OperationSetting = () => {
CompletionRatio: '', CompletionRatio: '',
ModelPrice: '', ModelPrice: '',
GroupRatio: '', GroupRatio: '',
GroupGroupRatio: '',
UserUsableGroups: '', UserUsableGroups: '',
TopUpLink: '', TopUpLink: '',
'general_setting.docs_link': '', 'general_setting.docs_link': '',
@@ -74,6 +75,7 @@ const OperationSetting = () => {
if ( if (
item.key === 'ModelRatio' || item.key === 'ModelRatio' ||
item.key === 'GroupRatio' || item.key === 'GroupRatio' ||
item.key === 'GroupGroupRatio' ||
item.key === 'UserUsableGroups' || item.key === 'UserUsableGroups' ||
item.key === 'CompletionRatio' || item.key === 'CompletionRatio' ||
item.key === 'ModelPrice' || item.key === 'ModelPrice' ||

View File

@@ -20,7 +20,7 @@ import {
renderQuota, renderQuota,
stringToColor, stringToColor,
getLogOther, getLogOther,
renderModelTag renderModelTag,
} from '../../helpers'; } from '../../helpers';
import { import {
@@ -39,11 +39,11 @@ import {
Card, Card,
Typography, Typography,
Divider, Divider,
Form Form,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { import {
IllustrationNoResult, IllustrationNoResult,
IllustrationNoResultDark IllustrationNoResultDark,
} from '@douyinfe/semi-illustrations'; } from '@douyinfe/semi-illustrations';
import { ITEMS_PER_PAGE } from '../../constants'; import { ITEMS_PER_PAGE } from '../../constants';
import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
@@ -192,7 +192,7 @@ const LogsTable = () => {
if (!modelMapped) { if (!modelMapped) {
return renderModelTag(record.model_name, { return renderModelTag(record.model_name, {
onClick: (event) => { onClick: (event) => {
copyText(event, record.model_name).then((r) => { }); copyText(event, record.model_name).then((r) => {});
}, },
}); });
} else { } else {
@@ -209,7 +209,7 @@ const LogsTable = () => {
</Text> </Text>
{renderModelTag(record.model_name, { {renderModelTag(record.model_name, {
onClick: (event) => { onClick: (event) => {
copyText(event, record.model_name).then((r) => { }); copyText(event, record.model_name).then((r) => {});
}, },
})} })}
</div> </div>
@@ -220,7 +220,7 @@ const LogsTable = () => {
{renderModelTag(other.upstream_model_name, { {renderModelTag(other.upstream_model_name, {
onClick: (event) => { onClick: (event) => {
copyText(event, other.upstream_model_name).then( copyText(event, other.upstream_model_name).then(
(r) => { }, (r) => {},
); );
}, },
})} })}
@@ -231,7 +231,7 @@ const LogsTable = () => {
> >
{renderModelTag(record.model_name, { {renderModelTag(record.model_name, {
onClick: (event) => { onClick: (event) => {
copyText(event, record.model_name).then((r) => { }); copyText(event, record.model_name).then((r) => {});
}, },
suffixIcon: ( suffixIcon: (
<Route <Route
@@ -598,21 +598,23 @@ const LogsTable = () => {
} }
let content = other?.claude let content = other?.claude
? renderClaudeModelPriceSimple( ? renderClaudeModelPriceSimple(
other.model_ratio, other.model_ratio,
other.model_price, other.model_price,
other.group_ratio, other.group_ratio,
other.cache_tokens || 0, other?.user_group_ratio,
other.cache_ratio || 1.0, other.cache_tokens || 0,
other.cache_creation_tokens || 0, other.cache_ratio || 1.0,
other.cache_creation_ratio || 1.0, other.cache_creation_tokens || 0,
) other.cache_creation_ratio || 1.0,
)
: renderModelPriceSimple( : renderModelPriceSimple(
other.model_ratio, other.model_ratio,
other.model_price, other.model_price,
other.group_ratio, other.group_ratio,
other.cache_tokens || 0, other?.user_group_ratio,
other.cache_ratio || 1.0, other.cache_tokens || 0,
); other.cache_ratio || 1.0,
);
return ( return (
<Paragraph <Paragraph
ellipsis={{ ellipsis={{
@@ -742,7 +744,7 @@ const LogsTable = () => {
group: '', group: '',
dateRange: [ dateRange: [
timestamp2string(getTodayStartTimestamp()), timestamp2string(getTodayStartTimestamp()),
timestamp2string(now.getTime() / 1000 + 3600) timestamp2string(now.getTime() / 1000 + 3600),
], ],
logType: '0', logType: '0',
}; };
@@ -763,7 +765,11 @@ const LogsTable = () => {
let start_timestamp = timestamp2string(getTodayStartTimestamp()); let start_timestamp = timestamp2string(getTodayStartTimestamp());
let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600); let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600);
if (formValues.dateRange && Array.isArray(formValues.dateRange) && formValues.dateRange.length === 2) { if (
formValues.dateRange &&
Array.isArray(formValues.dateRange) &&
formValues.dateRange.length === 2
) {
start_timestamp = formValues.dateRange[0]; start_timestamp = formValues.dateRange[0];
end_timestamp = formValues.dateRange[1]; end_timestamp = formValues.dateRange[1];
} }
@@ -941,27 +947,28 @@ const LogsTable = () => {
key: t('日志详情'), key: t('日志详情'),
value: other?.claude value: other?.claude
? renderClaudeLogContent( ? renderClaudeLogContent(
other?.model_ratio, other?.model_ratio,
other.completion_ratio, other.completion_ratio,
other.model_price, other.model_price,
other.group_ratio, other.group_ratio,
other.cache_ratio || 1.0, other?.user_group_ratio,
other.cache_creation_ratio || 1.0, other.cache_ratio || 1.0,
) other.cache_creation_ratio || 1.0,
)
: renderLogContent( : renderLogContent(
other?.model_ratio, other?.model_ratio,
other.completion_ratio, other.completion_ratio,
other.model_price, other.model_price,
other.group_ratio, other.group_ratio,
other?.user_group_ratio, other?.user_group_ratio,
false, false,
1.0, 1.0,
undefined, undefined,
other.web_search || false, other.web_search || false,
other.web_search_call_count || 0, other.web_search_call_count || 0,
other.file_search || false, other.file_search || false,
other.file_search_call_count || 0, other.file_search_call_count || 0,
), ),
}); });
} }
if (logs[i].type === 2) { if (logs[i].type === 2) {
@@ -992,6 +999,7 @@ const LogsTable = () => {
other?.audio_ratio, other?.audio_ratio,
other?.audio_completion_ratio, other?.audio_completion_ratio,
other?.group_ratio, other?.group_ratio,
other?.user_group_ratio,
other?.cache_tokens || 0, other?.cache_tokens || 0,
other?.cache_ratio || 1.0, other?.cache_ratio || 1.0,
); );
@@ -1003,6 +1011,7 @@ const LogsTable = () => {
other.model_price, other.model_price,
other.completion_ratio, other.completion_ratio,
other.group_ratio, other.group_ratio,
other?.user_group_ratio,
other.cache_tokens || 0, other.cache_tokens || 0,
other.cache_ratio || 1.0, other.cache_ratio || 1.0,
other.cache_creation_tokens || 0, other.cache_creation_tokens || 0,
@@ -1016,6 +1025,7 @@ const LogsTable = () => {
other?.model_price, other?.model_price,
other?.completion_ratio, other?.completion_ratio,
other?.group_ratio, other?.group_ratio,
other?.user_group_ratio,
other?.cache_tokens || 0, other?.cache_tokens || 0,
other?.cache_ratio || 1.0, other?.cache_ratio || 1.0,
other?.image || false, other?.image || false,
@@ -1066,7 +1076,12 @@ const LogsTable = () => {
} = getFormValues(); } = getFormValues();
// 使用传入的 logType 或者表单中的 logType 或者状态中的 logType // 使用传入的 logType 或者表单中的 logType 或者状态中的 logType
const currentLogType = customLogType !== null ? customLogType : formLogType !== undefined ? formLogType : logType; const currentLogType =
customLogType !== null
? customLogType
: formLogType !== undefined
? formLogType
: logType;
let localStartTimestamp = Date.parse(start_timestamp) / 1000; let localStartTimestamp = Date.parse(start_timestamp) / 1000;
let localEndTimestamp = Date.parse(end_timestamp) / 1000; let localEndTimestamp = Date.parse(end_timestamp) / 1000;
@@ -1093,7 +1108,7 @@ const LogsTable = () => {
const handlePageChange = (page) => { const handlePageChange = (page) => {
setActivePage(page); setActivePage(page);
loadLogs(page, pageSize).then((r) => { }); // 不传入logType让其从表单获取最新值 loadLogs(page, pageSize).then((r) => {}); // 不传入logType让其从表单获取最新值
}; };
const handlePageSizeChange = async (size) => { const handlePageSizeChange = async (size) => {
@@ -1208,9 +1223,9 @@ const LogsTable = () => {
getFormApi={(api) => setFormApi(api)} getFormApi={(api) => setFormApi(api)}
onSubmit={refresh} onSubmit={refresh}
allowEmpty={true} allowEmpty={true}
autoComplete="off" autoComplete='off'
layout="vertical" layout='vertical'
trigger="change" trigger='change'
stopValidateWithError={false} stopValidateWithError={false}
> >
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
@@ -1294,12 +1309,24 @@ const LogsTable = () => {
}, 0); }, 0);
}} }}
> >
<Form.Select.Option value='0'>{t('全部')}</Form.Select.Option> <Form.Select.Option value='0'>
<Form.Select.Option value='1'>{t('充值')}</Form.Select.Option> {t('全部')}
<Form.Select.Option value='2'>{t('消费')}</Form.Select.Option> </Form.Select.Option>
<Form.Select.Option value='3'>{t('管理')}</Form.Select.Option> <Form.Select.Option value='1'>
<Form.Select.Option value='4'>{t('系统')}</Form.Select.Option> {t('充值')}
<Form.Select.Option value='5'>{t('错误')}</Form.Select.Option> </Form.Select.Option>
<Form.Select.Option value='2'>
{t('消费')}
</Form.Select.Option>
<Form.Select.Option value='3'>
{t('管理')}
</Form.Select.Option>
<Form.Select.Option value='4'>
{t('系统')}
</Form.Select.Option>
<Form.Select.Option value='5'>
{t('错误')}
</Form.Select.Option>
</Form.Select> </Form.Select>
</div> </div>
@@ -1351,7 +1378,8 @@ const LogsTable = () => {
{...(hasExpandableRows() && { {...(hasExpandableRows() && {
expandedRowRender: expandRowRender, expandedRowRender: expandRowRender,
expandRowByClick: true, expandRowByClick: true,
rowExpandable: (record) => expandData[record.key] && expandData[record.key].length > 0 rowExpandable: (record) =>
expandData[record.key] && expandData[record.key].length > 0,
})} })}
dataSource={logs} dataSource={logs}
rowKey='key' rowKey='key'
@@ -1361,8 +1389,12 @@ const LogsTable = () => {
size='middle' size='middle'
empty={ empty={
<Empty <Empty
image={<IllustrationNoResult style={{ width: 150, height: 150 }} />} image={
darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />} <IllustrationNoResult style={{ width: 150, height: 150 }} />
}
darkModeImage={
<IllustrationNoResultDark style={{ width: 150, height: 150 }} />
}
description={t('搜索无结果')} description={t('搜索无结果')}
style={{ padding: 30 }} style={{ padding: 30 }}
/> />

View File

@@ -30,7 +30,7 @@ import {
Dify, Dify,
Coze, Coze,
SiliconCloud, SiliconCloud,
FastGPT FastGPT,
} from '@lobehub/icons'; } from '@lobehub/icons';
import { import {
@@ -46,7 +46,7 @@ import {
Gift, Gift,
User, User,
Settings, Settings,
CircleUser CircleUser,
} from 'lucide-react'; } from 'lucide-react';
// 侧边栏图标颜色映射 // 侧边栏图标颜色映射
@@ -315,7 +315,6 @@ export const getModelCategories = (() => {
}; };
})(); })();
/** /**
* 根据渠道类型返回对应的厂商图标 * 根据渠道类型返回对应的厂商图标
* @param {number} channelType - 渠道类型值 * @param {number} channelType - 渠道类型值
@@ -868,6 +867,10 @@ export function renderQuota(quota, digits = 2) {
return renderNumber(quota); return renderNumber(quota);
} }
function isValidGroupRatio(ratio) {
return ratio !== undefined && ratio !== null && ratio !== -1;
}
export function renderModelPrice( export function renderModelPrice(
inputTokens, inputTokens,
completionTokens, completionTokens,
@@ -875,6 +878,7 @@ export function renderModelPrice(
modelPrice = -1, modelPrice = -1,
completionRatio, completionRatio,
groupRatio, groupRatio,
user_group_ratio,
cacheTokens = 0, cacheTokens = 0,
cacheRatio = 1.0, cacheRatio = 1.0,
image = false, image = false,
@@ -890,13 +894,19 @@ export function renderModelPrice(
audioInputTokens = 0, audioInputTokens = 0,
audioInputPrice = 0, audioInputPrice = 0,
) { ) {
const useUserGroupRatio = isValidGroupRatio(user_group_ratio);
const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率')
: i18next.t('分组倍率');
groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio;
if (modelPrice !== -1) { if (modelPrice !== -1) {
return i18next.t( return i18next.t(
'模型价格:${{price}} * 分组倍率{{ratio}} = ${{total}}', '模型价格:${{price}} * {{ratioType}}{{ratio}} = ${{total}}',
{ {
price: modelPrice, price: modelPrice,
ratio: groupRatio, ratio: groupRatio,
total: modelPrice * groupRatio, total: modelPrice * groupRatio,
ratioType: ratioLabel,
}, },
); );
} else { } else {
@@ -1033,11 +1043,12 @@ export function renderModelPrice(
// 构建输出部分描述 // 构建输出部分描述
const outputDesc = i18next.t( const outputDesc = i18next.t(
'输出 {{completion}} tokens / 1M tokens * ${{compPrice}}) * 分组倍率 {{ratio}}', '输出 {{completion}} tokens / 1M tokens * ${{compPrice}}) * {{ratioType}} {{ratio}}',
{ {
completion: completionTokens, completion: completionTokens,
compPrice: completionRatioPrice, compPrice: completionRatioPrice,
ratio: groupRatio, ratio: groupRatio,
ratioType: ratioLabel,
}, },
); );
@@ -1045,23 +1056,25 @@ export function renderModelPrice(
const extraServices = [ const extraServices = [
webSearch && webSearchCallCount > 0 webSearch && webSearchCallCount > 0
? i18next.t( ? i18next.t(
' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * 分组倍率 {{ratio}}', ' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{ {
count: webSearchCallCount, count: webSearchCallCount,
price: webSearchPrice, price: webSearchPrice,
ratio: groupRatio, ratio: groupRatio,
}, ratioType: ratioLabel,
) },
)
: '', : '',
fileSearch && fileSearchCallCount > 0 fileSearch && fileSearchCallCount > 0
? i18next.t( ? i18next.t(
' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * 分组倍率 {{ratio}}', ' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{ {
count: fileSearchCallCount, count: fileSearchCallCount,
price: fileSearchPrice, price: fileSearchPrice,
ratio: groupRatio, ratio: groupRatio,
}, ratioType: ratioLabel,
) },
)
: '', : '',
].join(''); ].join('');
@@ -1097,6 +1110,7 @@ export function renderLogContent(
fileSearch = false, fileSearch = false,
fileSearchCallCount = 0, fileSearchCallCount = 0,
) { ) {
useUserGroupRatio = isValidGroupRatio(user_group_ratio);
const ratioLabel = useUserGroupRatio const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率') ? i18next.t('专属倍率')
: i18next.t('分组倍率'); : i18next.t('分组倍率');
@@ -1149,14 +1163,21 @@ export function renderModelPriceSimple(
modelRatio, modelRatio,
modelPrice = -1, modelPrice = -1,
groupRatio, groupRatio,
user_group_ratio,
cacheTokens = 0, cacheTokens = 0,
cacheRatio = 1.0, cacheRatio = 1.0,
image = false, image = false,
imageRatio = 1.0, imageRatio = 1.0,
) { ) {
const useUserGroupRatio = isValidGroupRatio(user_group_ratio);
const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率')
: i18next.t('分组倍率');
groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio;
if (modelPrice !== -1) { if (modelPrice !== -1) {
return i18next.t('价格:${{price}} * 分组{{ratio}}', { return i18next.t('价格:${{price}} * {{ratioType}}{{ratio}}', {
price: modelPrice, price: modelPrice,
ratioType: ratioLabel,
ratio: groupRatio, ratio: groupRatio,
}); });
} else { } else {
@@ -1191,8 +1212,9 @@ export function renderModelPriceSimple(
}, },
); );
} else { } else {
return i18next.t('模型: {{ratio}} * 分组: {{groupRatio}}', { return i18next.t('模型: {{ratio}} * {{ratioType}}{{groupRatio}}', {
ratio: modelRatio, ratio: modelRatio,
ratioType: ratioLabel,
groupRatio: groupRatio, groupRatio: groupRatio,
}); });
} }
@@ -1210,17 +1232,24 @@ export function renderAudioModelPrice(
audioRatio, audioRatio,
audioCompletionRatio, audioCompletionRatio,
groupRatio, groupRatio,
user_group_ratio,
cacheTokens = 0, cacheTokens = 0,
cacheRatio = 1.0, cacheRatio = 1.0,
) { ) {
const useUserGroupRatio = isValidGroupRatio(user_group_ratio);
const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率')
: i18next.t('分组倍率');
groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio;
// 1 ratio = $0.002 / 1K tokens // 1 ratio = $0.002 / 1K tokens
if (modelPrice !== -1) { if (modelPrice !== -1) {
return i18next.t( return i18next.t(
'模型价格:${{price}} * 分组倍率{{ratio}} = ${{total}}', '模型价格:${{price}} * {{ratioType}}{{ratio}} = ${{total}}',
{ {
price: modelPrice, price: modelPrice,
ratio: groupRatio, ratio: groupRatio,
total: modelPrice * groupRatio, total: modelPrice * groupRatio,
ratioType: ratioLabel,
}, },
); );
} else { } else {
@@ -1245,10 +1274,10 @@ export function renderAudioModelPrice(
let audioPrice = let audioPrice =
(audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio + (audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio +
(audioCompletionTokens / 1000000) * (audioCompletionTokens / 1000000) *
inputRatioPrice * inputRatioPrice *
audioRatio * audioRatio *
audioCompletionRatio * audioCompletionRatio *
groupRatio; groupRatio;
let price = textPrice + audioPrice; let price = textPrice + audioPrice;
return ( return (
<> <>
@@ -1304,27 +1333,27 @@ export function renderAudioModelPrice(
<p> <p>
{cacheTokens > 0 {cacheTokens > 0
? i18next.t( ? i18next.t(
'文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', '文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
{ {
nonCacheInput: inputTokens - cacheTokens, nonCacheInput: inputTokens - cacheTokens,
cacheInput: cacheTokens, cacheInput: cacheTokens,
cachePrice: inputRatioPrice * cacheRatio, cachePrice: inputRatioPrice * cacheRatio,
price: inputRatioPrice, price: inputRatioPrice,
completion: completionTokens, completion: completionTokens,
compPrice: completionRatioPrice, compPrice: completionRatioPrice,
total: textPrice.toFixed(6), total: textPrice.toFixed(6),
}, },
) )
: i18next.t( : i18next.t(
'文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
{ {
input: inputTokens, input: inputTokens,
price: inputRatioPrice, price: inputRatioPrice,
completion: completionTokens, completion: completionTokens,
compPrice: completionRatioPrice, compPrice: completionRatioPrice,
total: textPrice.toFixed(6), total: textPrice.toFixed(6),
}, },
)} )}
</p> </p>
<p> <p>
{i18next.t( {i18next.t(
@@ -1374,12 +1403,17 @@ export function renderClaudeModelPrice(
modelPrice = -1, modelPrice = -1,
completionRatio, completionRatio,
groupRatio, groupRatio,
user_group_ratio,
cacheTokens = 0, cacheTokens = 0,
cacheRatio = 1.0, cacheRatio = 1.0,
cacheCreationTokens = 0, cacheCreationTokens = 0,
cacheCreationRatio = 1.0, cacheCreationRatio = 1.0,
) { ) {
const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组倍率'); const useUserGroupRatio = isValidGroupRatio(user_group_ratio);
const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率')
: i18next.t('分组倍率');
groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio;
if (modelPrice !== -1) { if (modelPrice !== -1) {
return i18next.t( return i18next.t(
@@ -1461,33 +1495,35 @@ export function renderClaudeModelPrice(
<p> <p>
{cacheTokens > 0 || cacheCreationTokens > 0 {cacheTokens > 0 || cacheCreationTokens > 0
? i18next.t( ? i18next.t(
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}', '提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
{ {
nonCacheInput: nonCachedTokens, nonCacheInput: nonCachedTokens,
cacheInput: cacheTokens, cacheInput: cacheTokens,
cacheRatio: cacheRatio, cacheRatio: cacheRatio,
cacheCreationInput: cacheCreationTokens, cacheCreationInput: cacheCreationTokens,
cacheCreationRatio: cacheCreationRatio, cacheCreationRatio: cacheCreationRatio,
cachePrice: cacheRatioPrice, cachePrice: cacheRatioPrice,
cacheCreationPrice: cacheCreationRatioPrice, cacheCreationPrice: cacheCreationRatioPrice,
price: inputRatioPrice, price: inputRatioPrice,
completion: completionTokens, completion: completionTokens,
compPrice: completionRatioPrice, compPrice: completionRatioPrice,
ratio: groupRatio, ratio: groupRatio,
total: price.toFixed(6), ratioType: ratioLabel,
}, total: price.toFixed(6),
) },
)
: i18next.t( : i18next.t(
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}', '提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
{ {
input: inputTokens, input: inputTokens,
price: inputRatioPrice, price: inputRatioPrice,
completion: completionTokens, completion: completionTokens,
compPrice: completionRatioPrice, compPrice: completionRatioPrice,
ratio: groupRatio, ratio: groupRatio,
total: price.toFixed(6), ratioType: ratioLabel,
}, total: price.toFixed(6),
)} },
)}
</p> </p>
<p>{i18next.t('仅供参考,以实际扣费为准')}</p> <p>{i18next.t('仅供参考,以实际扣费为准')}</p>
</article> </article>
@@ -1501,10 +1537,15 @@ export function renderClaudeLogContent(
completionRatio, completionRatio,
modelPrice = -1, modelPrice = -1,
groupRatio, groupRatio,
user_group_ratio,
cacheRatio = 1.0, cacheRatio = 1.0,
cacheCreationRatio = 1.0, cacheCreationRatio = 1.0,
) { ) {
const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组倍率'); const useUserGroupRatio = isValidGroupRatio(user_group_ratio);
const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率')
: i18next.t('分组倍率');
groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio;
if (modelPrice !== -1) { if (modelPrice !== -1) {
return i18next.t('模型价格 ${{price}}{{ratioType}} {{ratio}}', { return i18next.t('模型价格 ${{price}}{{ratioType}} {{ratio}}', {
@@ -1531,12 +1572,17 @@ export function renderClaudeModelPriceSimple(
modelRatio, modelRatio,
modelPrice = -1, modelPrice = -1,
groupRatio, groupRatio,
user_group_ratio,
cacheTokens = 0, cacheTokens = 0,
cacheRatio = 1.0, cacheRatio = 1.0,
cacheCreationTokens = 0, cacheCreationTokens = 0,
cacheCreationRatio = 1.0, cacheCreationRatio = 1.0,
) { ) {
const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组'); const useUserGroupRatio = isValidGroupRatio(user_group_ratio);
const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率')
: i18next.t('分组倍率');
groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio;
if (modelPrice !== -1) { if (modelPrice !== -1) {
return i18next.t('价格:${{price}} * {{ratioType}}{{ratio}}', { return i18next.t('价格:${{price}} * {{ratioType}}{{ratio}}', {

View File

@@ -16,6 +16,7 @@ export default function GroupRatioSettings(props) {
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
GroupRatio: '', GroupRatio: '',
UserUsableGroups: '', UserUsableGroups: '',
GroupGroupRatio: '',
}); });
const refForm = useRef(); const refForm = useRef();
const [inputsRow, setInputsRow] = useState(inputs); const [inputsRow, setInputsRow] = useState(inputs);
@@ -136,6 +137,27 @@ export default function GroupRatioSettings(props) {
/> />
</Col> </Col>
</Row> </Row>
<Row gutter={16}>
<Col xs={24} sm={16}>
<Form.TextArea
label={t('分组特殊倍率')}
placeholder={t('为一个 JSON 文本')}
field={'GroupGroupRatio'}
autosize={{ minRows: 6, maxRows: 12 }}
trigger='blur'
stopValidateWithError
rules={[
{
validator: (rule, value) => verifyJSON(value),
message: t('不是合法的 JSON 字符串'),
},
]}
onChange={(value) =>
setInputs({ ...inputs, GroupGroupRatio: value })
}
/>
</Col>
</Row>
</Form.Section> </Form.Section>
</Form> </Form>
<Button onClick={onSubmit}>{t('保存分组倍率设置')}</Button> <Button onClick={onSubmit}>{t('保存分组倍率设置')}</Button>