mirror of
https://github.com/linux-do/new-api.git
synced 2025-09-23 18:36:37 +08:00
Merge branch 'main' into refactor-settings-operation
This commit is contained in:
commit
0ddb67f9a2
@ -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{
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 (
|
|||||||
|
|
||||||
// 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() {
|
|||||||
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() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
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() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
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() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
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() {
|
|||||||
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) {
|
|||||||
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) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
37
dto/pricing.go
Normal file
37
dto/pricing.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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) {
|
|||||||
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 {
|
|||||||
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"
|
||||||
|
72
model/pricing.go
Normal file
72
model/pricing.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
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,
|
|||||||
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 {
|
|||||||
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 (
|
|||||||
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 {
|
|||||||
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
|
|||||||
requestBody = c.Request.Body
|
requestBody = c.Request.Body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modelPrice, success := common.GetModelPrice(imageRequest.Model, true)
|
||||||
|
if !success {
|
||||||
modelRatio := common.GetModelRatio(imageRequest.Model)
|
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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|||||||
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 {
|
|||||||
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
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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
|
|||||||
|
|
||||||
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
|
|||||||
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) {
|
|||||||
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';
|
|||||||
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'));
|
||||||
@ -219,6 +220,14 @@ function App() {
|
|||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path='/pricing'
|
||||||
|
element={
|
||||||
|
<Suspense fallback={<Loading></Loading>}>
|
||||||
|
<Pricing />
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path='/about'
|
path='/about'
|
||||||
element={
|
element={
|
||||||
|
@ -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 = () => {
|
|||||||
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 = () => {
|
|||||||
}
|
}
|
||||||
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 = () => {
|
|||||||
<Paragraph
|
<Paragraph
|
||||||
ellipsis={{
|
ellipsis={{
|
||||||
rows: 2,
|
rows: 2,
|
||||||
showTooltip: {
|
|
||||||
type: 'popover',
|
|
||||||
opts: { style: { width: 240 } },
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
style={{ maxWidth: 240 }}
|
style={{ maxWidth: 240 }}
|
||||||
>
|
>
|
||||||
|
229
web/src/components/ModelPricing.js
Normal file
229
web/src/components/ModelPricing.js
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
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 {
|
|||||||
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 = () => {
|
|||||||
about: '/about',
|
about: '/about',
|
||||||
chat: '/chat',
|
chat: '/chat',
|
||||||
detail: '/detail',
|
detail: '/detail',
|
||||||
|
pricing: '/pricing',
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerButtons = useMemo(
|
const headerButtons = useMemo(
|
||||||
@ -100,6 +103,12 @@ const SiderBar = () => {
|
|||||||
to: '/topup',
|
to: '/topup',
|
||||||
icon: <IconCreditCard />,
|
icon: <IconCreditCard />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: '模型价格',
|
||||||
|
itemKey: 'pricing',
|
||||||
|
to: '/pricing',
|
||||||
|
icon: <IconPriceTag />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: '用户管理',
|
text: '用户管理',
|
||||||
itemKey: 'user',
|
itemKey: 'user',
|
||||||
@ -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('无法正常连接至服务器!');
|
||||||
}
|
}
|
||||||
|
33
web/src/helpers/data.js
Normal file
33
web/src/helpers/data.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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));
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
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) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function renderModelPrice(
|
export function renderModelPrice(
|
||||||
|
inputTokens,
|
||||||
|
completionTokens,
|
||||||
modelRatio,
|
modelRatio,
|
||||||
modelPrice = -1,
|
modelPrice = -1,
|
||||||
completionRatio,
|
completionRatio,
|
||||||
@ -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) => {
|
|||||||
'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) => {
|
|||||||
'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) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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); // 添加到模型列表
|
||||||
|
localModelOptions.push({ // 添加到下拉选项
|
||||||
|
key: model,
|
||||||
|
text: model,
|
||||||
|
value: model,
|
||||||
});
|
});
|
||||||
setModelOptions((modelOptions) => {
|
} else if (model) {
|
||||||
return [...modelOptions, ...localModelOptions];
|
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) => {
|
|||||||
</Space>
|
</Space>
|
||||||
<Input
|
<Input
|
||||||
addonAfter={
|
addonAfter={
|
||||||
<Button type='primary' onClick={addCustomModel}>
|
<Button type='primary' onClick={addCustomModels}>
|
||||||
填入
|
填入
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
10
web/src/pages/Pricing/index.js
Normal file
10
web/src/pages/Pricing/index.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ModelPricing from '../../components/ModelPricing.js';
|
||||||
|
|
||||||
|
const Pricing = () => (
|
||||||
|
<>
|
||||||
|
<ModelPricing />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Pricing;
|
Loading…
Reference in New Issue
Block a user