feat: 分组特殊倍率
This commit is contained in:
@@ -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)))
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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' ||
|
||||||
|
|||||||
@@ -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 }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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}}', {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user