Commit 09804935 by QuentinHsu

Merge branch 'main' into refactor-settings-operation

parents 031bfc9d fc017799
...@@ -61,8 +61,6 @@ var DefaultModelRatio = map[string]float64{ ...@@ -61,8 +61,6 @@ var DefaultModelRatio = map[string]float64{
"text-search-ada-doc-001": 10, "text-search-ada-doc-001": 10,
"text-moderation-stable": 0.1, "text-moderation-stable": 0.1,
"text-moderation-latest": 0.1, "text-moderation-latest": 0.1,
"dall-e-2": 8,
"dall-e-3": 16,
"claude-instant-1": 0.4, // $0.8 / 1M tokens "claude-instant-1": 0.4, // $0.8 / 1M tokens
"claude-2.0": 4, // $8 / 1M tokens "claude-2.0": 4, // $8 / 1M tokens
"claude-2.1": 4, // $8 / 1M tokens "claude-2.1": 4, // $8 / 1M tokens
...@@ -117,6 +115,8 @@ var DefaultModelRatio = map[string]float64{ ...@@ -117,6 +115,8 @@ var DefaultModelRatio = map[string]float64{
} }
var DefaultModelPrice = map[string]float64{ var DefaultModelPrice = map[string]float64{
"dall-e-2": 0.02,
"dall-e-3": 0.04,
"gpt-4-gizmo-*": 0.1, "gpt-4-gizmo-*": 0.1,
"mj_imagine": 0.1, "mj_imagine": 0.1,
"mj_variation": 0.1, "mj_variation": 0.1,
...@@ -160,7 +160,8 @@ func UpdateModelPriceByJSONString(jsonStr string) error { ...@@ -160,7 +160,8 @@ func UpdateModelPriceByJSONString(jsonStr string) error {
return json.Unmarshal([]byte(jsonStr), &modelPrice) return json.Unmarshal([]byte(jsonStr), &modelPrice)
} }
func GetModelPrice(name string, printErr bool) float64 { // GetModelPrice 返回模型的价格,如果模型不存在则返回-1,false
func GetModelPrice(name string, printErr bool) (float64, bool) {
if modelPrice == nil { if modelPrice == nil {
modelPrice = DefaultModelPrice modelPrice = DefaultModelPrice
} }
...@@ -172,9 +173,16 @@ func GetModelPrice(name string, printErr bool) float64 { ...@@ -172,9 +173,16 @@ func GetModelPrice(name string, printErr bool) float64 {
if printErr { if printErr {
SysError("model price not found: " + name) SysError("model price not found: " + name)
} }
return -1 return -1, false
} }
return price return price, true
}
func GetModelPrices() map[string]float64 {
if modelPrice == nil {
modelPrice = DefaultModelPrice
}
return modelPrice
} }
func ModelRatio2JSONString() string { func ModelRatio2JSONString() string {
...@@ -208,6 +216,13 @@ func GetModelRatio(name string) float64 { ...@@ -208,6 +216,13 @@ func GetModelRatio(name string) float64 {
return ratio return ratio
} }
func GetModelRatios() map[string]float64 {
if modelRatio == nil {
modelRatio = DefaultModelRatio
}
return modelRatio
}
func CompletionRatio2JSONString() string { func CompletionRatio2JSONString() string {
if CompletionRatio == nil { if CompletionRatio == nil {
CompletionRatio = DefaultCompletionRatio CompletionRatio = DefaultCompletionRatio
...@@ -281,3 +296,10 @@ func GetCompletionRatio(name string) float64 { ...@@ -281,3 +296,10 @@ func GetCompletionRatio(name string) float64 {
} }
return 1 return 1
} }
func GetCompletionRatios() map[string]float64 {
if CompletionRatio == nil {
CompletionRatio = DefaultCompletionRatio
}
return CompletionRatio
}
...@@ -250,3 +250,11 @@ func MapToJsonStr(m map[string]interface{}) string { ...@@ -250,3 +250,11 @@ func MapToJsonStr(m map[string]interface{}) string {
} }
return string(bytes) return string(bytes)
} }
func MapToJsonStrFloat(m map[string]float64) string {
bytes, err := json.Marshal(m)
if err != nil {
return ""
}
return string(bytes)
}
...@@ -53,7 +53,7 @@ func testChannel(channel *model.Channel, testModel string) (err error, openaiErr ...@@ -53,7 +53,7 @@ func testChannel(channel *model.Channel, testModel string) (err error, openaiErr
} }
meta := relaycommon.GenRelayInfo(c) meta := relaycommon.GenRelayInfo(c)
apiType := constant.ChannelType2APIType(channel.Type) apiType, _ := constant.ChannelType2APIType(channel.Type)
adaptor := relay.GetAdaptor(apiType) adaptor := relay.GetAdaptor(apiType)
if adaptor == nil { if adaptor == nil {
return fmt.Errorf("invalid api type: %d, adaptor is nil", apiType), nil return fmt.Errorf("invalid api type: %d, adaptor is nil", apiType), nil
......
...@@ -18,38 +18,13 @@ import ( ...@@ -18,38 +18,13 @@ import (
// https://platform.openai.com/docs/api-reference/models/list // https://platform.openai.com/docs/api-reference/models/list
type OpenAIModelPermission struct { var openAIModels []dto.OpenAIModels
Id string `json:"id"` var openAIModelsMap map[string]dto.OpenAIModels
Object string `json:"object"`
Created int `json:"created"`
AllowCreateEngine bool `json:"allow_create_engine"`
AllowSampling bool `json:"allow_sampling"`
AllowLogprobs bool `json:"allow_logprobs"`
AllowSearchIndices bool `json:"allow_search_indices"`
AllowView bool `json:"allow_view"`
AllowFineTuning bool `json:"allow_fine_tuning"`
Organization string `json:"organization"`
Group *string `json:"group"`
IsBlocking bool `json:"is_blocking"`
}
type OpenAIModels struct {
Id string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
OwnedBy string `json:"owned_by"`
Permission []OpenAIModelPermission `json:"permission"`
Root string `json:"root"`
Parent *string `json:"parent"`
}
var openAIModels []OpenAIModels
var openAIModelsMap map[string]OpenAIModels
var channelId2Models map[int][]string var channelId2Models map[int][]string
func getPermission() []OpenAIModelPermission { func getPermission() []dto.OpenAIModelPermission {
var permission []OpenAIModelPermission var permission []dto.OpenAIModelPermission
permission = append(permission, OpenAIModelPermission{ permission = append(permission, dto.OpenAIModelPermission{
Id: "modelperm-LwHkVFn8AcMItP432fKKDIKJ", Id: "modelperm-LwHkVFn8AcMItP432fKKDIKJ",
Object: "model_permission", Object: "model_permission",
Created: 1626777600, Created: 1626777600,
...@@ -77,7 +52,7 @@ func init() { ...@@ -77,7 +52,7 @@ func init() {
channelName := adaptor.GetChannelName() channelName := adaptor.GetChannelName()
modelNames := adaptor.GetModelList() modelNames := adaptor.GetModelList()
for _, modelName := range modelNames { for _, modelName := range modelNames {
openAIModels = append(openAIModels, OpenAIModels{ openAIModels = append(openAIModels, dto.OpenAIModels{
Id: modelName, Id: modelName,
Object: "model", Object: "model",
Created: 1626777600, Created: 1626777600,
...@@ -89,7 +64,7 @@ func init() { ...@@ -89,7 +64,7 @@ func init() {
} }
} }
for _, modelName := range ai360.ModelList { for _, modelName := range ai360.ModelList {
openAIModels = append(openAIModels, OpenAIModels{ openAIModels = append(openAIModels, dto.OpenAIModels{
Id: modelName, Id: modelName,
Object: "model", Object: "model",
Created: 1626777600, Created: 1626777600,
...@@ -100,7 +75,7 @@ func init() { ...@@ -100,7 +75,7 @@ func init() {
}) })
} }
for _, modelName := range moonshot.ModelList { for _, modelName := range moonshot.ModelList {
openAIModels = append(openAIModels, OpenAIModels{ openAIModels = append(openAIModels, dto.OpenAIModels{
Id: modelName, Id: modelName,
Object: "model", Object: "model",
Created: 1626777600, Created: 1626777600,
...@@ -111,7 +86,7 @@ func init() { ...@@ -111,7 +86,7 @@ func init() {
}) })
} }
for _, modelName := range lingyiwanwu.ModelList { for _, modelName := range lingyiwanwu.ModelList {
openAIModels = append(openAIModels, OpenAIModels{ openAIModels = append(openAIModels, dto.OpenAIModels{
Id: modelName, Id: modelName,
Object: "model", Object: "model",
Created: 1626777600, Created: 1626777600,
...@@ -122,7 +97,7 @@ func init() { ...@@ -122,7 +97,7 @@ func init() {
}) })
} }
for modelName, _ := range constant.MidjourneyModel2Action { for modelName, _ := range constant.MidjourneyModel2Action {
openAIModels = append(openAIModels, OpenAIModels{ openAIModels = append(openAIModels, dto.OpenAIModels{
Id: modelName, Id: modelName,
Object: "model", Object: "model",
Created: 1626777600, Created: 1626777600,
...@@ -132,14 +107,14 @@ func init() { ...@@ -132,14 +107,14 @@ func init() {
Parent: nil, Parent: nil,
}) })
} }
openAIModelsMap = make(map[string]OpenAIModels) openAIModelsMap = make(map[string]dto.OpenAIModels)
for _, model := range openAIModels { for _, model := range openAIModels {
openAIModelsMap[model.Id] = model openAIModelsMap[model.Id] = model
} }
channelId2Models = make(map[int][]string) channelId2Models = make(map[int][]string)
for i := 1; i <= common.ChannelTypeDummy; i++ { for i := 1; i <= common.ChannelTypeDummy; i++ {
apiType := relayconstant.ChannelType2APIType(i) apiType, success := relayconstant.ChannelType2APIType(i)
if apiType == -1 || apiType == relayconstant.APITypeAIProxyLibrary { if !success || apiType == relayconstant.APITypeAIProxyLibrary {
continue continue
} }
meta := &relaycommon.RelayInfo{ChannelType: i} meta := &relaycommon.RelayInfo{ChannelType: i}
...@@ -160,17 +135,17 @@ func ListModels(c *gin.Context) { ...@@ -160,17 +135,17 @@ func ListModels(c *gin.Context) {
return return
} }
models := model.GetGroupModels(user.Group) models := model.GetGroupModels(user.Group)
userOpenAiModels := make([]OpenAIModels, 0) userOpenAiModels := make([]dto.OpenAIModels, 0)
permission := getPermission() permission := getPermission()
for _, s := range models { for _, s := range models {
if _, ok := openAIModelsMap[s]; ok { if _, ok := openAIModelsMap[s]; ok {
userOpenAiModels = append(userOpenAiModels, openAIModelsMap[s]) userOpenAiModels = append(userOpenAiModels, openAIModelsMap[s])
} else { } else {
userOpenAiModels = append(userOpenAiModels, OpenAIModels{ userOpenAiModels = append(userOpenAiModels, dto.OpenAIModels{
Id: s, Id: s,
Object: "model", Object: "model",
Created: 1626777600, Created: 1626777600,
OwnedBy: "openai", OwnedBy: "custom",
Permission: permission, Permission: permission,
Root: s, Root: s,
Parent: nil, Parent: nil,
...@@ -213,3 +188,18 @@ func RetrieveModel(c *gin.Context) { ...@@ -213,3 +188,18 @@ func RetrieveModel(c *gin.Context) {
}) })
} }
} }
func GetPricing(c *gin.Context) {
userId := c.GetInt("id")
user, _ := model.GetUserById(userId, true)
groupRatio := common.GetGroupRatio("default")
if user != nil {
groupRatio = common.GetGroupRatio(user.Group)
}
pricing := model.GetPricing(user, openAIModels)
c.JSON(200, gin.H{
"success": true,
"data": pricing,
"group_ratio": groupRatio,
})
}
package dto
type OpenAIModelPermission struct {
Id string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
AllowCreateEngine bool `json:"allow_create_engine"`
AllowSampling bool `json:"allow_sampling"`
AllowLogprobs bool `json:"allow_logprobs"`
AllowSearchIndices bool `json:"allow_search_indices"`
AllowView bool `json:"allow_view"`
AllowFineTuning bool `json:"allow_fine_tuning"`
Organization string `json:"organization"`
Group *string `json:"group"`
IsBlocking bool `json:"is_blocking"`
}
type OpenAIModels struct {
Id string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
OwnedBy string `json:"owned_by"`
Permission []OpenAIModelPermission `json:"permission"`
Root string `json:"root"`
Parent *string `json:"parent"`
}
type ModelPricing struct {
Available bool `json:"available"`
ModelName string `json:"model_name"`
QuotaType int `json:"quota_type"`
ModelRatio float64 `json:"model_ratio"`
ModelPrice float64 `json:"model_price"`
OwnerBy string `json:"owner_by"`
CompletionRatio float64 `json:"completion_ratio"`
EnableGroup []string `json:"enable_group,omitempty"`
}
...@@ -64,6 +64,17 @@ func authHelper(c *gin.Context, minRole int) { ...@@ -64,6 +64,17 @@ func authHelper(c *gin.Context, minRole int) {
c.Next() c.Next()
} }
func TryUserAuth() func(c *gin.Context) {
return func(c *gin.Context) {
session := sessions.Default(c)
id := session.Get("id")
if id != nil {
c.Set("id", id)
}
c.Next()
}
}
func UserAuth() func(c *gin.Context) { func UserAuth() func(c *gin.Context) {
return func(c *gin.Context) { return func(c *gin.Context) {
authHelper(c, common.RoleCommonUser) authHelper(c, common.RoleCommonUser)
......
...@@ -29,6 +29,13 @@ func GetGroupModels(group string) []string { ...@@ -29,6 +29,13 @@ func GetGroupModels(group string) []string {
return models return models
} }
func GetEnabledModels() []string {
var models []string
// Find distinct models
DB.Table("abilities").Where("enabled = ?", true).Distinct("model").Pluck("model", &models)
return models
}
func getPriority(group string, model string, retry int) (int, error) { func getPriority(group string, model string, retry int) (int, error) {
groupCol := "`group`" groupCol := "`group`"
trueVal := "1" trueVal := "1"
......
package model
import (
"one-api/common"
"one-api/dto"
"sync"
"time"
)
var (
pricingMap []dto.ModelPricing
lastGetPricingTime time.Time
updatePricingLock sync.Mutex
)
func GetPricing(user *User, openAIModels []dto.OpenAIModels) []dto.ModelPricing {
updatePricingLock.Lock()
defer updatePricingLock.Unlock()
if time.Since(lastGetPricingTime) > time.Minute*1 || len(pricingMap) == 0 {
updatePricing(openAIModels)
}
if user != nil {
userPricingMap := make([]dto.ModelPricing, 0)
models := GetGroupModels(user.Group)
for _, pricing := range pricingMap {
if !common.StringsContains(models, pricing.ModelName) {
pricing.Available = false
}
userPricingMap = append(userPricingMap, pricing)
}
return userPricingMap
}
return pricingMap
}
func updatePricing(openAIModels []dto.OpenAIModels) {
modelRatios := common.GetModelRatios()
enabledModels := GetEnabledModels()
allModels := make(map[string]string)
for _, openAIModel := range openAIModels {
if common.StringsContains(enabledModels, openAIModel.Id) {
allModels[openAIModel.Id] = openAIModel.OwnedBy
}
}
for model, _ := range modelRatios {
if common.StringsContains(enabledModels, model) {
if _, ok := allModels[model]; !ok {
allModels[model] = "custom"
}
}
}
pricingMap = make([]dto.ModelPricing, 0)
for model, ownerBy := range allModels {
pricing := dto.ModelPricing{
Available: true,
ModelName: model,
OwnerBy: ownerBy,
}
modelPrice, findPrice := common.GetModelPrice(model, false)
if findPrice {
pricing.ModelPrice = modelPrice
pricing.QuotaType = 1
} else {
pricing.ModelRatio = common.GetModelRatio(model)
pricing.CompletionRatio = common.GetCompletionRatio(model)
pricing.QuotaType = 0
}
pricingMap = append(pricingMap, pricing)
}
lastGetPricingTime = time.Now()
}
...@@ -45,6 +45,7 @@ func logQuotaDataCache(userId int, username string, modelName string, quota int, ...@@ -45,6 +45,7 @@ func logQuotaDataCache(userId int, username string, modelName string, quota int,
if ok { if ok {
quotaData.Count += 1 quotaData.Count += 1
quotaData.Quota += quota quotaData.Quota += quota
quotaData.TokenUsed += tokenUsed
} else { } else {
quotaData = &QuotaData{ quotaData = &QuotaData{
UserID: userId, UserID: userId,
......
...@@ -38,7 +38,7 @@ func GenRelayInfo(c *gin.Context) *RelayInfo { ...@@ -38,7 +38,7 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
tokenUnlimited := c.GetBool("token_unlimited_quota") tokenUnlimited := c.GetBool("token_unlimited_quota")
startTime := time.Now() startTime := time.Now()
apiType := constant.ChannelType2APIType(channelType) apiType, _ := constant.ChannelType2APIType(channelType)
info := &RelayInfo{ info := &RelayInfo{
RelayMode: constant.Path2RelayMode(c.Request.URL.Path), RelayMode: constant.Path2RelayMode(c.Request.URL.Path),
......
...@@ -24,19 +24,11 @@ const ( ...@@ -24,19 +24,11 @@ const (
APITypeDummy // this one is only for count, do not add any channel after this APITypeDummy // this one is only for count, do not add any channel after this
) )
func ChannelType2APIType(channelType int) int { func ChannelType2APIType(channelType int) (int, bool) {
apiType := -1 apiType := -1
switch channelType { switch channelType {
case common.ChannelTypeOpenAI: case common.ChannelTypeOpenAI:
apiType = APITypeOpenAI apiType = APITypeOpenAI
case common.ChannelTypeAzure:
apiType = APITypeOpenAI
case common.ChannelTypeMoonshot:
apiType = APITypeOpenAI
case common.ChannelTypeLingYiWanWu:
apiType = APITypeOpenAI
case common.ChannelType360:
apiType = APITypeOpenAI
case common.ChannelTypeAnthropic: case common.ChannelTypeAnthropic:
apiType = APITypeAnthropic apiType = APITypeAnthropic
case common.ChannelTypeBaidu: case common.ChannelTypeBaidu:
...@@ -66,5 +58,8 @@ func ChannelType2APIType(channelType int) int { ...@@ -66,5 +58,8 @@ func ChannelType2APIType(channelType int) int {
case common.ChannelTypeCohere: case common.ChannelTypeCohere:
apiType = APITypeCohere apiType = APITypeCohere
} }
return apiType if apiType == -1 {
return APITypeOpenAI, false
}
return apiType, true
} }
...@@ -106,21 +106,26 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC ...@@ -106,21 +106,26 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC
requestBody = c.Request.Body requestBody = c.Request.Body
} }
modelRatio := common.GetModelRatio(imageRequest.Model) modelPrice, success := common.GetModelPrice(imageRequest.Model, true)
if !success {
modelRatio := common.GetModelRatio(imageRequest.Model)
// modelRatio 16 = modelPrice $0.04
// per 1 modelRatio = $0.04 / 16
modelPrice = 0.0025 * modelRatio
}
groupRatio := common.GetGroupRatio(group) groupRatio := common.GetGroupRatio(group)
ratio := modelRatio * groupRatio
userQuota, err := model.CacheGetUserQuota(userId) userQuota, err := model.CacheGetUserQuota(userId)
sizeRatio := 1.0 sizeRatio := 1.0
// Size // Size
if imageRequest.Size == "256x256" { if imageRequest.Size == "256x256" {
sizeRatio = 1 sizeRatio = 0.4
} else if imageRequest.Size == "512x512" { } else if imageRequest.Size == "512x512" {
sizeRatio = 1.125 sizeRatio = 0.45
} else if imageRequest.Size == "1024x1024" { } else if imageRequest.Size == "1024x1024" {
sizeRatio = 1.25 sizeRatio = 1
} else if imageRequest.Size == "1024x1792" || imageRequest.Size == "1792x1024" { } else if imageRequest.Size == "1024x1792" || imageRequest.Size == "1792x1024" {
sizeRatio = 2.5 sizeRatio = 2
} }
qualityRatio := 1.0 qualityRatio := 1.0
...@@ -131,7 +136,7 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC ...@@ -131,7 +136,7 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC
} }
} }
quota := int(ratio*sizeRatio*qualityRatio*1000) * imageRequest.N quota := int(modelPrice*groupRatio*common.QuotaPerUnit*sizeRatio*qualityRatio) * imageRequest.N
if userQuota-quota < 0 { if userQuota-quota < 0 {
return service.OpenAIErrorWrapper(errors.New("user quota is not enough"), "insufficient_user_quota", http.StatusForbidden) return service.OpenAIErrorWrapper(errors.New("user quota is not enough"), "insufficient_user_quota", http.StatusForbidden)
...@@ -190,9 +195,9 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC ...@@ -190,9 +195,9 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC
if imageRequest.Quality == "hd" { if imageRequest.Quality == "hd" {
quality = "hd" quality = "hd"
} }
logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f, 大小 %s, 品质 %s", modelRatio, groupRatio, imageRequest.Size, quality) logContent := fmt.Sprintf("模型价格 %.2f,分组倍率 %.2f, 大小 %s, 品质 %s", modelPrice, groupRatio, imageRequest.Size, quality)
other := make(map[string]interface{}) other := make(map[string]interface{})
other["model_ratio"] = modelRatio other["model_price"] = modelPrice
other["group_ratio"] = groupRatio other["group_ratio"] = groupRatio
model.RecordConsumeLog(ctx, userId, channelId, 0, 0, imageRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false, other) model.RecordConsumeLog(ctx, userId, channelId, 0, 0, imageRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false, other)
model.UpdateUserUsedQuotaAndRequestCount(userId, quota) model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
......
...@@ -155,9 +155,9 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse { ...@@ -155,9 +155,9 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse {
return service.MidjourneyErrorWrapper(constant.MjRequestError, "sour_base64_and_target_base64_is_required") return service.MidjourneyErrorWrapper(constant.MjRequestError, "sour_base64_and_target_base64_is_required")
} }
modelName := service.CoverActionToModelName(constant.MjActionSwapFace) modelName := service.CoverActionToModelName(constant.MjActionSwapFace)
modelPrice := common.GetModelPrice(modelName, true) modelPrice, success := common.GetModelPrice(modelName, true)
// 如果没有配置价格,则使用默认价格 // 如果没有配置价格,则使用默认价格
if modelPrice == -1 { if !success {
defaultPrice, ok := common.DefaultModelPrice[modelName] defaultPrice, ok := common.DefaultModelPrice[modelName]
if !ok { if !ok {
modelPrice = 0.1 modelPrice = 0.1
...@@ -454,9 +454,9 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons ...@@ -454,9 +454,9 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons
fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL) fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL)
modelName := service.CoverActionToModelName(midjRequest.Action) modelName := service.CoverActionToModelName(midjRequest.Action)
modelPrice := common.GetModelPrice(modelName, true) modelPrice, success := common.GetModelPrice(modelName, true)
// 如果没有配置价格,则使用默认价格 // 如果没有配置价格,则使用默认价格
if modelPrice == -1 { if !success {
defaultPrice, ok := common.DefaultModelPrice[modelName] defaultPrice, ok := common.DefaultModelPrice[modelName]
if !ok { if !ok {
modelPrice = 0.1 modelPrice = 0.1
......
...@@ -91,7 +91,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { ...@@ -91,7 +91,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
} }
} }
relayInfo.UpstreamModelName = textRequest.Model relayInfo.UpstreamModelName = textRequest.Model
modelPrice := common.GetModelPrice(textRequest.Model, false) modelPrice, success := common.GetModelPrice(textRequest.Model, false)
groupRatio := common.GetGroupRatio(relayInfo.Group) groupRatio := common.GetGroupRatio(relayInfo.Group)
var preConsumedQuota int var preConsumedQuota int
...@@ -108,7 +108,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { ...@@ -108,7 +108,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
return service.OpenAIErrorWrapper(err, "count_token_messages_failed", http.StatusInternalServerError) return service.OpenAIErrorWrapper(err, "count_token_messages_failed", http.StatusInternalServerError)
} }
if modelPrice == -1 { if !success {
preConsumedTokens := common.PreConsumedQuota preConsumedTokens := common.PreConsumedQuota
if textRequest.MaxTokens != 0 { if textRequest.MaxTokens != 0 {
preConsumedTokens = promptTokens + int(textRequest.MaxTokens) preConsumedTokens = promptTokens + int(textRequest.MaxTokens)
...@@ -178,7 +178,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { ...@@ -178,7 +178,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
service.ResetStatusCode(openaiErr, statusCodeMappingStr) service.ResetStatusCode(openaiErr, statusCodeMappingStr)
return openaiErr return openaiErr
} }
postConsumeQuota(c, relayInfo, *textRequest, usage, ratio, preConsumedQuota, userQuota, modelRatio, groupRatio, modelPrice) postConsumeQuota(c, relayInfo, *textRequest, usage, ratio, preConsumedQuota, userQuota, modelRatio, groupRatio, modelPrice, success)
return nil return nil
} }
...@@ -257,7 +257,7 @@ func returnPreConsumedQuota(c *gin.Context, tokenId int, userQuota int, preConsu ...@@ -257,7 +257,7 @@ func returnPreConsumedQuota(c *gin.Context, tokenId int, userQuota int, preConsu
func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRequest dto.GeneralOpenAIRequest, func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRequest dto.GeneralOpenAIRequest,
usage *dto.Usage, ratio float64, preConsumedQuota int, userQuota int, modelRatio float64, groupRatio float64, usage *dto.Usage, ratio float64, preConsumedQuota int, userQuota int, modelRatio float64, groupRatio float64,
modelPrice float64) { modelPrice float64, usePrice bool) {
useTimeSeconds := time.Now().Unix() - relayInfo.StartTime.Unix() useTimeSeconds := time.Now().Unix() - relayInfo.StartTime.Unix()
promptTokens := usage.PromptTokens promptTokens := usage.PromptTokens
...@@ -267,9 +267,9 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRe ...@@ -267,9 +267,9 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRe
completionRatio := common.GetCompletionRatio(textRequest.Model) completionRatio := common.GetCompletionRatio(textRequest.Model)
quota := 0 quota := 0
if modelPrice == -1 { if !usePrice {
quota = promptTokens + int(float64(completionTokens)*completionRatio) quota = promptTokens + int(math.Round(float64(completionTokens)*completionRatio))
quota = int(float64(quota) * ratio) quota = int(math.Round(float64(quota) * ratio))
if ratio != 0 && quota <= 0 { if ratio != 0 && quota <= 0 {
quota = 1 quota = 1
} }
......
...@@ -20,6 +20,7 @@ func SetApiRouter(router *gin.Engine) { ...@@ -20,6 +20,7 @@ func SetApiRouter(router *gin.Engine) {
apiRouter.GET("/about", controller.GetAbout) apiRouter.GET("/about", controller.GetAbout)
//apiRouter.GET("/midjourney", controller.GetMidjourney) //apiRouter.GET("/midjourney", controller.GetMidjourney)
apiRouter.GET("/home_page_content", controller.GetHomePageContent) apiRouter.GET("/home_page_content", controller.GetHomePageContent)
apiRouter.GET("/pricing", middleware.CriticalRateLimit(), middleware.TryUserAuth(), controller.GetPricing)
apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification)
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
......
...@@ -22,6 +22,7 @@ import Log from './pages/Log'; ...@@ -22,6 +22,7 @@ import Log from './pages/Log';
import Chat from './pages/Chat'; import Chat from './pages/Chat';
import { Layout } from '@douyinfe/semi-ui'; import { Layout } from '@douyinfe/semi-ui';
import Midjourney from './pages/Midjourney'; import Midjourney from './pages/Midjourney';
import Pricing from './pages/Pricing/index.js';
// import Detail from './pages/Detail'; // import Detail from './pages/Detail';
const Home = lazy(() => import('./pages/Home')); const Home = lazy(() => import('./pages/Home'));
...@@ -220,6 +221,14 @@ function App() { ...@@ -220,6 +221,14 @@ function App() {
} }
/> />
<Route <Route
path='/pricing'
element={
<Suspense fallback={<Loading></Loading>}>
<Pricing />
</Suspense>
}
/>
<Route
path='/about' path='/about'
element={ element={
<Suspense fallback={<Loading></Loading>}> <Suspense fallback={<Loading></Loading>}>
......
...@@ -19,6 +19,7 @@ import TelegramLoginButton from 'react-telegram-login'; ...@@ -19,6 +19,7 @@ import TelegramLoginButton from 'react-telegram-login';
import { IconGithubLogo } from '@douyinfe/semi-icons'; import { IconGithubLogo } from '@douyinfe/semi-icons';
import WeChatIcon from './WeChatIcon'; import WeChatIcon from './WeChatIcon';
import { setUserData } from '../helpers/data.js';
const LoginForm = () => { const LoginForm = () => {
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
...@@ -99,7 +100,7 @@ const LoginForm = () => { ...@@ -99,7 +100,7 @@ const LoginForm = () => {
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
userDispatch({ type: 'login', payload: data }); userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data)); setUserData(data);
showSuccess('登录成功!'); showSuccess('登录成功!');
if (username === 'root' && password === '123456') { if (username === 'root' && password === '123456') {
Modal.error({ Modal.error({
......
...@@ -316,6 +316,8 @@ const LogsTable = () => { ...@@ -316,6 +316,8 @@ const LogsTable = () => {
} }
let other = JSON.parse(record.other); let other = JSON.parse(record.other);
let content = renderModelPrice( let content = renderModelPrice(
record.prompt_tokens,
record.completion_tokens,
other.model_ratio, other.model_ratio,
other.model_price, other.model_price,
other.completion_ratio, other.completion_ratio,
...@@ -326,10 +328,6 @@ const LogsTable = () => { ...@@ -326,10 +328,6 @@ const LogsTable = () => {
<Paragraph <Paragraph
ellipsis={{ ellipsis={{
rows: 2, rows: 2,
showTooltip: {
type: 'popover',
opts: { style: { width: 240 } },
},
}} }}
style={{ maxWidth: 240 }} style={{ maxWidth: 240 }}
> >
......
import React, { useContext, useEffect, useState } from 'react';
import { API, copy, showError, showSuccess } from '../helpers';
import { Banner, Layout, Modal, Table, Tag, Tooltip } from '@douyinfe/semi-ui';
import { stringToColor } from '../helpers/render.js';
import { UserContext } from '../context/User/index.js';
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
function renderQuotaType(type) {
// Ensure all cases are string literals by adding quotes.
switch (type) {
case 1:
return (
<Tag color='green' size='large'>
按次计费
</Tag>
);
case 0:
return (
<Tag color='blue' size='large'>
按量计费
</Tag>
);
default:
return (
<Tag color='white' size='large'>
未知
</Tag>
);
}
}
function renderAvailable(available) {
return available ? (
<Tag color='green' size='large'>
可用
</Tag>
) : (
<Tooltip content='您所在的分组不可用'>
<Tag color='red' size='large'>
不可用
</Tag>
</Tooltip>
);
}
const ModelPricing = () => {
const columns = [
{
title: '可用性',
dataIndex: 'available',
render: (text, record, index) => {
return renderAvailable(text);
},
},
{
title: '提供者',
dataIndex: 'owner_by',
render: (text, record, index) => {
return (
<>
<Tag color={stringToColor(text)} size='large'>
{text}
</Tag>
</>
);
},
},
{
title: '模型名称',
dataIndex: 'model_name', // 以finish_time作为dataIndex
render: (text, record, index) => {
return (
<>
<Tag
color={stringToColor(record.owner_by)}
size='large'
onClick={() => {
copyText(text);
}}
>
{text}
</Tag>
</>
);
},
},
{
title: '计费类型',
dataIndex: 'quota_type',
render: (text, record, index) => {
return renderQuotaType(parseInt(text));
},
},
{
title: '模型倍率',
dataIndex: 'model_ratio',
render: (text, record, index) => {
return <div>{record.quota_type === 0 ? text : 'N/A'}</div>;
},
},
{
title: '补全倍率',
dataIndex: 'completion_ratio',
render: (text, record, index) => {
let ratio = parseFloat(text.toFixed(3));
return <div>{record.quota_type === 0 ? ratio : 'N/A'}</div>;
},
},
{
title: '模型价格',
dataIndex: 'model_price',
render: (text, record, index) => {
let content = text;
if (record.quota_type === 0) {
let inputRatioPrice = record.model_ratio * 2.0 * record.group_ratio;
let completionRatioPrice =
record.model_ratio *
record.completion_ratio *
2.0 *
record.group_ratio;
content = (
<>
<Text>提示 ${inputRatioPrice} / 1M tokens</Text>
<br />
<Text>补全 ${completionRatioPrice} / 1M tokens</Text>
</>
);
} else {
let price = parseFloat(text) * record.group_ratio;
content = <>模型价格:${price}</>;
}
return <div>{content}</div>;
},
},
];
const [models, setModels] = useState([]);
const [loading, setLoading] = useState(true);
const [userState, userDispatch] = useContext(UserContext);
const [groupRatio, setGroupRatio] = useState(1);
const setModelsFormat = (models, groupRatio) => {
for (let i = 0; i < models.length; i++) {
models[i].key = i;
models[i].group_ratio = groupRatio;
}
// sort by quota_type
models.sort((a, b) => {
return a.quota_type - b.quota_type;
});
// sort by owner_by, openai is max, other use localeCompare
models.sort((a, b) => {
if (a.owner_by === 'openai') {
return -1;
} else if (b.owner_by === 'openai') {
return 1;
} else {
return a.owner_by.localeCompare(b.owner_by);
}
});
setModels(models);
};
const loadPricing = async () => {
setLoading(true);
let url = '';
url = `/api/pricing`;
const res = await API.get(url);
const { success, message, data, group_ratio } = res.data;
if (success) {
setGroupRatio(group_ratio);
setModelsFormat(data, group_ratio);
} else {
showError(message);
}
setLoading(false);
};
const refresh = async () => {
await loadPricing();
};
const copyText = async (text) => {
if (await copy(text)) {
showSuccess('已复制:' + text);
} else {
// setSearchKeyword(text);
Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
}
};
useEffect(() => {
refresh().then();
}, []);
return (
<>
<Layout>
{userState.user ? (
<Banner
type='info'
description={`您的分组为:${userState.user.group},分组倍率为:${groupRatio}`}
/>
) : (
<Banner
type='warning'
description={`您还未登陆,显示的价格为默认分组倍率: ${groupRatio}`}
/>
)}
<Table
style={{ marginTop: 5 }}
columns={columns}
dataSource={models}
loading={loading}
pagination={{
pageSize: models.length,
showSizeChanger: false,
}}
/>
</Layout>
</>
);
};
export default ModelPricing;
...@@ -23,10 +23,12 @@ import { ...@@ -23,10 +23,12 @@ import {
IconImage, IconImage,
IconKey, IconKey,
IconLayers, IconLayers,
IconPriceTag,
IconSetting, IconSetting,
IconUser, IconUser,
} from '@douyinfe/semi-icons'; } from '@douyinfe/semi-icons';
import { Layout, Nav } from '@douyinfe/semi-ui'; import { Layout, Nav } from '@douyinfe/semi-ui';
import { setStatusData } from '../helpers/data.js';
// HeaderBar Buttons // HeaderBar Buttons
...@@ -55,6 +57,7 @@ const SiderBar = () => { ...@@ -55,6 +57,7 @@ const SiderBar = () => {
about: '/about', about: '/about',
chat: '/chat', chat: '/chat',
detail: '/detail', detail: '/detail',
pricing: '/pricing',
}; };
const headerButtons = useMemo( const headerButtons = useMemo(
...@@ -101,6 +104,12 @@ const SiderBar = () => { ...@@ -101,6 +104,12 @@ const SiderBar = () => {
icon: <IconCreditCard />, icon: <IconCreditCard />,
}, },
{ {
text: '模型价格',
itemKey: 'pricing',
to: '/pricing',
icon: <IconPriceTag />,
},
{
text: '用户管理', text: '用户管理',
itemKey: 'user', itemKey: 'user',
to: '/user', to: '/user',
...@@ -161,34 +170,8 @@ const SiderBar = () => { ...@@ -161,34 +170,8 @@ const SiderBar = () => {
} }
const { success, data } = res.data; const { success, data } = res.data;
if (success) { if (success) {
localStorage.setItem('status', JSON.stringify(data));
statusDispatch({ type: 'set', payload: data }); statusDispatch({ type: 'set', payload: data });
localStorage.setItem('system_name', data.system_name); setStatusData(data);
localStorage.setItem('logo', data.logo);
localStorage.setItem('footer_html', data.footer_html);
localStorage.setItem('quota_per_unit', data.quota_per_unit);
localStorage.setItem('display_in_currency', data.display_in_currency);
localStorage.setItem('enable_drawing', data.enable_drawing);
localStorage.setItem('enable_data_export', data.enable_data_export);
localStorage.setItem(
'data_export_default_time',
data.data_export_default_time,
);
localStorage.setItem(
'default_collapse_sidebar',
data.default_collapse_sidebar,
);
localStorage.setItem('mj_notify_enabled', data.mj_notify_enabled);
if (data.chat_link) {
localStorage.setItem('chat_link', data.chat_link);
} else {
localStorage.removeItem('chat_link');
}
if (data.chat_link2) {
localStorage.setItem('chat_link2', data.chat_link2);
} else {
localStorage.removeItem('chat_link2');
}
} else { } else {
showError('无法正常连接至服务器!'); showError('无法正常连接至服务器!');
} }
......
export function setStatusData(data) {
localStorage.setItem('status', JSON.stringify(data));
localStorage.setItem('system_name', data.system_name);
localStorage.setItem('logo', data.logo);
localStorage.setItem('footer_html', data.footer_html);
localStorage.setItem('quota_per_unit', data.quota_per_unit);
localStorage.setItem('display_in_currency', data.display_in_currency);
localStorage.setItem('enable_drawing', data.enable_drawing);
localStorage.setItem('enable_data_export', data.enable_data_export);
localStorage.setItem(
'data_export_default_time',
data.data_export_default_time,
);
localStorage.setItem(
'default_collapse_sidebar',
data.default_collapse_sidebar,
);
localStorage.setItem('mj_notify_enabled', data.mj_notify_enabled);
if (data.chat_link) {
localStorage.setItem('chat_link', data.chat_link);
} else {
localStorage.removeItem('chat_link');
}
if (data.chat_link2) {
localStorage.setItem('chat_link2', data.chat_link2);
} else {
localStorage.removeItem('chat_link2');
}
}
export function setUserData(data) {
localStorage.setItem('user', JSON.stringify(data));
}
import { Label } from 'semantic-ui-react';
import { Tag } from '@douyinfe/semi-ui'; import { Tag } from '@douyinfe/semi-ui';
export function renderText(text, limit) { export function renderText(text, limit) {
...@@ -136,6 +135,8 @@ export function renderQuota(quota, digits = 2) { ...@@ -136,6 +135,8 @@ export function renderQuota(quota, digits = 2) {
} }
export function renderModelPrice( export function renderModelPrice(
inputTokens,
completionTokens,
modelRatio, modelRatio,
modelPrice = -1, modelPrice = -1,
completionRatio, completionRatio,
...@@ -148,15 +149,24 @@ export function renderModelPrice( ...@@ -148,15 +149,24 @@ export function renderModelPrice(
if (completionRatio === undefined) { if (completionRatio === undefined) {
completionRatio = 0; completionRatio = 0;
} }
let inputRatioPrice = modelRatio * 0.002 * groupRatio; let inputRatioPrice = modelRatio * 2.0 * groupRatio;
let completionRatioPrice = let completionRatioPrice = modelRatio * completionRatio * 2.0 * groupRatio;
modelRatio * completionRatio * 0.002 * groupRatio; let price =
(inputTokens / 1000000) * inputRatioPrice +
(completionTokens / 1000000) * completionRatioPrice;
return ( return (
'输入:$' + <>
inputRatioPrice.toFixed(3) + <article>
'/1K tokens,补全:$' + <p>提示 ${inputRatioPrice} / 1M tokens</p>
completionRatioPrice.toFixed(3) + <p>补全 ${completionRatioPrice} / 1M tokens</p>
'/1K tokens' <p></p>
<p>
提示 {inputTokens} tokens / 1M tokens * ${inputRatioPrice} + 补全{' '}
{completionTokens} tokens / 1M tokens * ${completionRatioPrice} = $
{price.toFixed(6)}
</p>
</article>
</>
); );
} }
} }
......
...@@ -99,6 +99,7 @@ const EditChannel = (props) => { ...@@ -99,6 +99,7 @@ const EditChannel = (props) => {
'mj_blend', 'mj_blend',
'mj_upscale', 'mj_upscale',
'mj_describe', 'mj_describe',
'mj_uploads',
]; ];
break; break;
case 5: case 5:
...@@ -118,6 +119,7 @@ const EditChannel = (props) => { ...@@ -118,6 +119,7 @@ const EditChannel = (props) => {
'mj_high_variation', 'mj_high_variation',
'mj_low_variation', 'mj_low_variation',
'mj_pan', 'mj_pan',
'mj_uploads',
]; ];
break; break;
default: default:
...@@ -296,24 +298,39 @@ const EditChannel = (props) => { ...@@ -296,24 +298,39 @@ const EditChannel = (props) => {
} }
}; };
const addCustomModel = () => { const addCustomModels = () => {
if (customModel.trim() === '') return; if (customModel.trim() === '') return;
if (inputs.models.includes(customModel)) return showError('该模型已存在!'); // 使用逗号分隔字符串,然后去除每个模型名称前后的空格
const modelArray = customModel.split(',').map(model => model.trim());
let localModels = [...inputs.models]; let localModels = [...inputs.models];
localModels.push(customModel); let localModelOptions = [...modelOptions];
let localModelOptions = []; let hasError = false;
localModelOptions.push({
key: customModel, modelArray.forEach(model => {
text: customModel, // 检查模型是否已存在,且模型名称非空
value: customModel, if (model && !localModels.includes(model)) {
}); localModels.push(model); // 添加到模型列表
setModelOptions((modelOptions) => { localModelOptions.push({ // 添加到下拉选项
return [...modelOptions, ...localModelOptions]; key: model,
text: model,
value: model,
});
} else if (model) {
showError('某些模型已存在!');
hasError = true;
}
}); });
if (hasError) return; // 如果有错误则终止操作
// 更新状态值
setModelOptions(localModelOptions);
setCustomModel(''); setCustomModel('');
handleInputChange('models', localModels); handleInputChange('models', localModels);
}; };
return ( return (
<> <>
<SideSheet <SideSheet
...@@ -540,7 +557,7 @@ const EditChannel = (props) => { ...@@ -540,7 +557,7 @@ const EditChannel = (props) => {
</Space> </Space>
<Input <Input
addonAfter={ addonAfter={
<Button type='primary' onClick={addCustomModel}> <Button type='primary' onClick={addCustomModels}>
填入 填入
</Button> </Button>
} }
......
import React from 'react';
import ModelPricing from '../../components/ModelPricing.js';
const Pricing = () => (
<>
<ModelPricing />
</>
);
export default Pricing;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment