Merge branch 'songquanpeng:main' into test
This commit is contained in:
commit
d9b7b6076e
@ -4,12 +4,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/songquanpeng/one-api/common"
|
"github.com/songquanpeng/one-api/common"
|
||||||
|
"github.com/songquanpeng/one-api/model"
|
||||||
"github.com/songquanpeng/one-api/relay/channel/openai"
|
"github.com/songquanpeng/one-api/relay/channel/openai"
|
||||||
"github.com/songquanpeng/one-api/relay/constant"
|
"github.com/songquanpeng/one-api/relay/constant"
|
||||||
"github.com/songquanpeng/one-api/relay/helper"
|
"github.com/songquanpeng/one-api/relay/helper"
|
||||||
relaymodel "github.com/songquanpeng/one-api/relay/model"
|
relaymodel "github.com/songquanpeng/one-api/relay/model"
|
||||||
"github.com/songquanpeng/one-api/relay/util"
|
"github.com/songquanpeng/one-api/relay/util"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://platform.openai.com/docs/api-reference/models/list
|
// https://platform.openai.com/docs/api-reference/models/list
|
||||||
@ -120,9 +122,41 @@ func DashboardListModels(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ListModels(c *gin.Context) {
|
func ListModels(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
var availableModels []string
|
||||||
|
if c.GetString("available_models") != "" {
|
||||||
|
availableModels = strings.Split(c.GetString("available_models"), ",")
|
||||||
|
} else {
|
||||||
|
userId := c.GetInt("id")
|
||||||
|
userGroup, _ := model.CacheGetUserGroup(userId)
|
||||||
|
availableModels, _ = model.CacheGetGroupModels(ctx, userGroup)
|
||||||
|
}
|
||||||
|
modelSet := make(map[string]bool)
|
||||||
|
for _, availableModel := range availableModels {
|
||||||
|
modelSet[availableModel] = true
|
||||||
|
}
|
||||||
|
var availableOpenAIModels []OpenAIModels
|
||||||
|
for _, model := range openAIModels {
|
||||||
|
if _, ok := modelSet[model.Id]; ok {
|
||||||
|
modelSet[model.Id] = false
|
||||||
|
availableOpenAIModels = append(availableOpenAIModels, model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for modelName, ok := range modelSet {
|
||||||
|
if ok {
|
||||||
|
availableOpenAIModels = append(availableOpenAIModels, OpenAIModels{
|
||||||
|
Id: modelName,
|
||||||
|
Object: "model",
|
||||||
|
Created: 1626777600,
|
||||||
|
OwnedBy: "custom",
|
||||||
|
Root: modelName,
|
||||||
|
Parent: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"object": "list",
|
"object": "list",
|
||||||
"data": openAIModels,
|
"data": availableOpenAIModels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,3 +176,30 @@ func RetrieveModel(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserAvailableModels(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
id := c.GetInt("id")
|
||||||
|
userGroup, err := model.CacheGetUserGroup(id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models, err := model.CacheGetGroupModels(ctx, userGroup)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": models,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -130,6 +130,7 @@ func AddToken(c *gin.Context) {
|
|||||||
ExpiredTime: token.ExpiredTime,
|
ExpiredTime: token.ExpiredTime,
|
||||||
RemainQuota: token.RemainQuota,
|
RemainQuota: token.RemainQuota,
|
||||||
UnlimitedQuota: token.UnlimitedQuota,
|
UnlimitedQuota: token.UnlimitedQuota,
|
||||||
|
Models: token.Models,
|
||||||
}
|
}
|
||||||
err = cleanToken.Insert()
|
err = cleanToken.Insert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -216,6 +217,7 @@ func UpdateToken(c *gin.Context) {
|
|||||||
cleanToken.ExpiredTime = token.ExpiredTime
|
cleanToken.ExpiredTime = token.ExpiredTime
|
||||||
cleanToken.RemainQuota = token.RemainQuota
|
cleanToken.RemainQuota = token.RemainQuota
|
||||||
cleanToken.UnlimitedQuota = token.UnlimitedQuota
|
cleanToken.UnlimitedQuota = token.UnlimitedQuota
|
||||||
|
cleanToken.Models = token.Models
|
||||||
}
|
}
|
||||||
err = cleanToken.Update()
|
err = cleanToken.Update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/songquanpeng/one-api/common"
|
"github.com/songquanpeng/one-api/common"
|
||||||
@ -107,6 +108,19 @@ func TokenAuth() func(c *gin.Context) {
|
|||||||
abortWithMessage(c, http.StatusForbidden, "用户已被封禁")
|
abortWithMessage(c, http.StatusForbidden, "用户已被封禁")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
requestModel, err := getRequestModel(c)
|
||||||
|
if err != nil {
|
||||||
|
abortWithMessage(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Set("request_model", requestModel)
|
||||||
|
if token.Models != nil && *token.Models != "" {
|
||||||
|
c.Set("available_models", *token.Models)
|
||||||
|
if requestModel != "" && !isModelInList(requestModel, *token.Models) {
|
||||||
|
abortWithMessage(c, http.StatusForbidden, fmt.Sprintf("该令牌无权使用模型:%s", requestModel))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
c.Set("id", token.UserId)
|
c.Set("id", token.UserId)
|
||||||
c.Set("token_id", token.Id)
|
c.Set("token_id", token.Id)
|
||||||
c.Set("token_name", token.Name)
|
c.Set("token_name", token.Name)
|
||||||
|
@ -2,14 +2,12 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/songquanpeng/one-api/common"
|
"github.com/songquanpeng/one-api/common"
|
||||||
"github.com/songquanpeng/one-api/common/logger"
|
"github.com/songquanpeng/one-api/common/logger"
|
||||||
"github.com/songquanpeng/one-api/model"
|
"github.com/songquanpeng/one-api/model"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ModelRequest struct {
|
type ModelRequest struct {
|
||||||
@ -40,37 +38,11 @@ func Distribute() func(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Select a channel for the user
|
requestModel := c.GetString("request_model")
|
||||||
var modelRequest ModelRequest
|
var err error
|
||||||
err := common.UnmarshalBodyReusable(c, &modelRequest)
|
channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, requestModel, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
abortWithMessage(c, http.StatusBadRequest, "无效的请求")
|
message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", userGroup, requestModel)
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(c.Request.URL.Path, "/v1/moderations") {
|
|
||||||
if modelRequest.Model == "" {
|
|
||||||
modelRequest.Model = "text-moderation-stable"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(c.Request.URL.Path, "embeddings") {
|
|
||||||
if modelRequest.Model == "" {
|
|
||||||
modelRequest.Model = c.Param("model")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(c.Request.URL.Path, "/v1/images/generations") {
|
|
||||||
if modelRequest.Model == "" {
|
|
||||||
modelRequest.Model = "dall-e-2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(c.Request.URL.Path, "/v1/audio/transcriptions") || strings.HasPrefix(c.Request.URL.Path, "/v1/audio/translations") {
|
|
||||||
if modelRequest.Model == "" {
|
|
||||||
modelRequest.Model = "whisper-1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestModel = modelRequest.Model
|
|
||||||
channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, modelRequest.Model, false)
|
|
||||||
if err != nil {
|
|
||||||
message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", userGroup, modelRequest.Model)
|
|
||||||
if channel != nil {
|
if channel != nil {
|
||||||
logger.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
|
logger.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
|
||||||
message = "数据库一致性已被破坏,请联系管理员"
|
message = "数据库一致性已被破坏,请联系管理员"
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/songquanpeng/one-api/common"
|
||||||
"github.com/songquanpeng/one-api/common/helper"
|
"github.com/songquanpeng/one-api/common/helper"
|
||||||
"github.com/songquanpeng/one-api/common/logger"
|
"github.com/songquanpeng/one-api/common/logger"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func abortWithMessage(c *gin.Context, statusCode int, message string) {
|
func abortWithMessage(c *gin.Context, statusCode int, message string) {
|
||||||
@ -16,3 +19,42 @@ func abortWithMessage(c *gin.Context, statusCode int, message string) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
logger.Error(c.Request.Context(), message)
|
logger.Error(c.Request.Context(), message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRequestModel(c *gin.Context) (string, error) {
|
||||||
|
var modelRequest ModelRequest
|
||||||
|
err := common.UnmarshalBodyReusable(c, &modelRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("common.UnmarshalBodyReusable failed: %w", err)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(c.Request.URL.Path, "/v1/moderations") {
|
||||||
|
if modelRequest.Model == "" {
|
||||||
|
modelRequest.Model = "text-moderation-stable"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(c.Request.URL.Path, "embeddings") {
|
||||||
|
if modelRequest.Model == "" {
|
||||||
|
modelRequest.Model = c.Param("model")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(c.Request.URL.Path, "/v1/images/generations") {
|
||||||
|
if modelRequest.Model == "" {
|
||||||
|
modelRequest.Model = "dall-e-2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(c.Request.URL.Path, "/v1/audio/transcriptions") || strings.HasPrefix(c.Request.URL.Path, "/v1/audio/translations") {
|
||||||
|
if modelRequest.Model == "" {
|
||||||
|
modelRequest.Model = "whisper-1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modelRequest.Model, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isModelInList(modelName string, models string) bool {
|
||||||
|
modelList := strings.Split(models, ",")
|
||||||
|
for _, model := range modelList {
|
||||||
|
if modelName == model {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"github.com/songquanpeng/one-api/common"
|
"github.com/songquanpeng/one-api/common"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -88,3 +90,19 @@ func (channel *Channel) UpdateAbilities() error {
|
|||||||
func UpdateAbilityStatus(channelId int, status bool) error {
|
func UpdateAbilityStatus(channelId int, status bool) error {
|
||||||
return DB.Model(&Ability{}).Where("channel_id = ?", channelId).Select("enabled").Update("enabled", status).Error
|
return DB.Model(&Ability{}).Where("channel_id = ?", channelId).Select("enabled").Update("enabled", status).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetGroupModels(ctx context.Context, group string) ([]string, error) {
|
||||||
|
groupCol := "`group`"
|
||||||
|
trueVal := "1"
|
||||||
|
if common.UsingPostgreSQL {
|
||||||
|
groupCol = `"group"`
|
||||||
|
trueVal = "true"
|
||||||
|
}
|
||||||
|
var models []string
|
||||||
|
err := DB.Model(&Ability{}).Distinct("model").Where(groupCol+" = ? and enabled = "+trueVal, group).Pluck("model", &models).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Strings(models)
|
||||||
|
return models, err
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ var (
|
|||||||
UserId2GroupCacheSeconds = config.SyncFrequency
|
UserId2GroupCacheSeconds = config.SyncFrequency
|
||||||
UserId2QuotaCacheSeconds = config.SyncFrequency
|
UserId2QuotaCacheSeconds = config.SyncFrequency
|
||||||
UserId2StatusCacheSeconds = config.SyncFrequency
|
UserId2StatusCacheSeconds = config.SyncFrequency
|
||||||
|
GroupModelsCacheSeconds = config.SyncFrequency
|
||||||
)
|
)
|
||||||
|
|
||||||
func CacheGetTokenByKey(key string) (*Token, error) {
|
func CacheGetTokenByKey(key string) (*Token, error) {
|
||||||
@ -146,6 +147,25 @@ func CacheIsUserEnabled(userId int) (bool, error) {
|
|||||||
return userEnabled, err
|
return userEnabled, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CacheGetGroupModels(ctx context.Context, group string) ([]string, error) {
|
||||||
|
if !common.RedisEnabled {
|
||||||
|
return GetGroupModels(ctx, group)
|
||||||
|
}
|
||||||
|
modelsStr, err := common.RedisGet(fmt.Sprintf("group_models:%s", group))
|
||||||
|
if err == nil {
|
||||||
|
return strings.Split(modelsStr, ","), nil
|
||||||
|
}
|
||||||
|
models, err := GetGroupModels(ctx, group)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = common.RedisSet(fmt.Sprintf("group_models:%s", group), strings.Join(models, ","), time.Duration(GroupModelsCacheSeconds)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
logger.SysError("Redis set group models error: " + err.Error())
|
||||||
|
}
|
||||||
|
return models, nil
|
||||||
|
}
|
||||||
|
|
||||||
var group2model2channels map[string]map[string][]*Channel
|
var group2model2channels map[string]map[string][]*Channel
|
||||||
var channelSyncLock sync.RWMutex
|
var channelSyncLock sync.RWMutex
|
||||||
|
|
||||||
|
@ -12,24 +12,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
UserId int `json:"user_id"`
|
UserId int `json:"user_id"`
|
||||||
Key string `json:"key" gorm:"type:char(48);uniqueIndex"`
|
Key string `json:"key" gorm:"type:char(48);uniqueIndex"`
|
||||||
Status int `json:"status" gorm:"default:1"`
|
Status int `json:"status" gorm:"default:1"`
|
||||||
Name string `json:"name" gorm:"index" `
|
Name string `json:"name" gorm:"index" `
|
||||||
CreatedTime int64 `json:"created_time" gorm:"bigint"`
|
CreatedTime int64 `json:"created_time" gorm:"bigint"`
|
||||||
AccessedTime int64 `json:"accessed_time" gorm:"bigint"`
|
AccessedTime int64 `json:"accessed_time" gorm:"bigint"`
|
||||||
ExpiredTime int64 `json:"expired_time" gorm:"bigint;default:-1"` // -1 means never expired
|
ExpiredTime int64 `json:"expired_time" gorm:"bigint;default:-1"` // -1 means never expired
|
||||||
RemainQuota int64 `json:"remain_quota" gorm:"bigint;default:0"`
|
RemainQuota int64 `json:"remain_quota" gorm:"bigint;default:0"`
|
||||||
UnlimitedQuota bool `json:"unlimited_quota" gorm:"default:false"`
|
UnlimitedQuota bool `json:"unlimited_quota" gorm:"default:false"`
|
||||||
UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"` // used quota
|
UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"` // used quota
|
||||||
|
Models *string `json:"models" gorm:"default:''"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllUserTokens(userId int, startIdx int, num int, order string) ([]*Token, error) {
|
func GetAllUserTokens(userId int, startIdx int, num int, order string) ([]*Token, error) {
|
||||||
var tokens []*Token
|
var tokens []*Token
|
||||||
var err error
|
var err error
|
||||||
query := DB.Where("user_id = ?", userId)
|
query := DB.Where("user_id = ?", userId)
|
||||||
|
|
||||||
switch order {
|
switch order {
|
||||||
case "remain_quota":
|
case "remain_quota":
|
||||||
query = query.Order("unlimited_quota desc, remain_quota desc")
|
query = query.Order("unlimited_quota desc, remain_quota desc")
|
||||||
@ -38,7 +39,7 @@ func GetAllUserTokens(userId int, startIdx int, num int, order string) ([]*Token
|
|||||||
default:
|
default:
|
||||||
query = query.Order("id desc")
|
query = query.Order("id desc")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = query.Limit(num).Offset(startIdx).Find(&tokens).Error
|
err = query.Limit(num).Offset(startIdx).Find(&tokens).Error
|
||||||
return tokens, err
|
return tokens, err
|
||||||
}
|
}
|
||||||
@ -121,7 +122,7 @@ func (token *Token) Insert() error {
|
|||||||
// Update Make sure your token's fields is completed, because this will update non-zero values
|
// Update Make sure your token's fields is completed, because this will update non-zero values
|
||||||
func (token *Token) Update() error {
|
func (token *Token) Update() error {
|
||||||
var err error
|
var err error
|
||||||
err = DB.Model(token).Select("name", "status", "expired_time", "remain_quota", "unlimited_quota").Updates(token).Error
|
err = DB.Model(token).Select("name", "status", "expired_time", "remain_quota", "unlimited_quota", "models").Updates(token).Error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,8 +50,8 @@ func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest {
|
|||||||
TopP: request.TopP,
|
TopP: request.TopP,
|
||||||
TopK: request.TopK,
|
TopK: request.TopK,
|
||||||
ResultFormat: "message",
|
ResultFormat: "message",
|
||||||
|
Tools: request.Tools,
|
||||||
},
|
},
|
||||||
Tools: request.Tools,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,21 +16,21 @@ type Input struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Parameters struct {
|
type Parameters struct {
|
||||||
TopP float64 `json:"top_p,omitempty"`
|
TopP float64 `json:"top_p,omitempty"`
|
||||||
TopK int `json:"top_k,omitempty"`
|
TopK int `json:"top_k,omitempty"`
|
||||||
Seed uint64 `json:"seed,omitempty"`
|
Seed uint64 `json:"seed,omitempty"`
|
||||||
EnableSearch bool `json:"enable_search,omitempty"`
|
EnableSearch bool `json:"enable_search,omitempty"`
|
||||||
IncrementalOutput bool `json:"incremental_output,omitempty"`
|
IncrementalOutput bool `json:"incremental_output,omitempty"`
|
||||||
MaxTokens int `json:"max_tokens,omitempty"`
|
MaxTokens int `json:"max_tokens,omitempty"`
|
||||||
Temperature float64 `json:"temperature,omitempty"`
|
Temperature float64 `json:"temperature,omitempty"`
|
||||||
ResultFormat string `json:"result_format,omitempty"`
|
ResultFormat string `json:"result_format,omitempty"`
|
||||||
|
Tools []model.Tool `json:"tools,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatRequest struct {
|
type ChatRequest struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Input Input `json:"input"`
|
Input Input `json:"input"`
|
||||||
Parameters Parameters `json:"parameters,omitempty"`
|
Parameters Parameters `json:"parameters,omitempty"`
|
||||||
Tools []model.Tool `json:"tools,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmbeddingRequest struct {
|
type EmbeddingRequest struct {
|
||||||
|
@ -43,6 +43,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
selfRoute.GET("/token", controller.GenerateAccessToken)
|
selfRoute.GET("/token", controller.GenerateAccessToken)
|
||||||
selfRoute.GET("/aff", controller.GetAffCode)
|
selfRoute.GET("/aff", controller.GetAffCode)
|
||||||
selfRoute.POST("/topup", controller.TopUp)
|
selfRoute.POST("/topup", controller.TopUp)
|
||||||
|
selfRoute.GET("/available_models", controller.GetUserAvailableModels)
|
||||||
}
|
}
|
||||||
|
|
||||||
adminRoute := userRoute.Group("/")
|
adminRoute := userRoute.Group("/")
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Header, Message, Segment } from 'semantic-ui-react';
|
import { Button, Form, Header, Message, Segment } from 'semantic-ui-react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { API, showError, showSuccess, timestamp2string } from '../../helpers';
|
import { API, copy, showError, showSuccess, timestamp2string } from '../../helpers';
|
||||||
import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render';
|
import { renderQuotaWithPrompt } from '../../helpers/render';
|
||||||
|
|
||||||
const EditToken = () => {
|
const EditToken = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const tokenId = params.id;
|
const tokenId = params.id;
|
||||||
const isEdit = tokenId !== undefined;
|
const isEdit = tokenId !== undefined;
|
||||||
const [loading, setLoading] = useState(isEdit);
|
const [loading, setLoading] = useState(isEdit);
|
||||||
|
const [modelOptions, setModelOptions] = useState([]);
|
||||||
const originInputs = {
|
const originInputs = {
|
||||||
name: '',
|
name: '',
|
||||||
remain_quota: isEdit ? 0 : 500000,
|
remain_quota: isEdit ? 0 : 500000,
|
||||||
expired_time: -1,
|
expired_time: -1,
|
||||||
unlimited_quota: false
|
unlimited_quota: false,
|
||||||
|
models: []
|
||||||
};
|
};
|
||||||
const [inputs, setInputs] = useState(originInputs);
|
const [inputs, setInputs] = useState(originInputs);
|
||||||
const { name, remain_quota, expired_time, unlimited_quota } = inputs;
|
const { name, remain_quota, expired_time, unlimited_quota } = inputs;
|
||||||
@ -22,8 +24,8 @@ const EditToken = () => {
|
|||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
};
|
};
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
navigate("/token");
|
navigate('/token');
|
||||||
}
|
};
|
||||||
const setExpiredTime = (month, day, hour, minute) => {
|
const setExpiredTime = (month, day, hour, minute) => {
|
||||||
let now = new Date();
|
let now = new Date();
|
||||||
let timestamp = now.getTime() / 1000;
|
let timestamp = now.getTime() / 1000;
|
||||||
@ -50,6 +52,11 @@ const EditToken = () => {
|
|||||||
if (data.expired_time !== -1) {
|
if (data.expired_time !== -1) {
|
||||||
data.expired_time = timestamp2string(data.expired_time);
|
data.expired_time = timestamp2string(data.expired_time);
|
||||||
}
|
}
|
||||||
|
if (data.models === '') {
|
||||||
|
data.models = [];
|
||||||
|
} else {
|
||||||
|
data.models = data.models.split(',');
|
||||||
|
}
|
||||||
setInputs(data);
|
setInputs(data);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
@ -60,8 +67,26 @@ const EditToken = () => {
|
|||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
loadToken().then();
|
loadToken().then();
|
||||||
}
|
}
|
||||||
|
loadAvailableModels().then();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const loadAvailableModels = async () => {
|
||||||
|
let res = await API.get(`/api/user/available_models`);
|
||||||
|
const { success, message, data } = res.data;
|
||||||
|
if (success) {
|
||||||
|
let options = data.map((model) => {
|
||||||
|
return {
|
||||||
|
key: model,
|
||||||
|
text: model,
|
||||||
|
value: model
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setModelOptions(options);
|
||||||
|
} else {
|
||||||
|
showError(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (!isEdit && inputs.name === '') return;
|
if (!isEdit && inputs.name === '') return;
|
||||||
let localInputs = inputs;
|
let localInputs = inputs;
|
||||||
@ -74,6 +99,7 @@ const EditToken = () => {
|
|||||||
}
|
}
|
||||||
localInputs.expired_time = Math.ceil(time / 1000);
|
localInputs.expired_time = Math.ceil(time / 1000);
|
||||||
}
|
}
|
||||||
|
localInputs.models = localInputs.models.join(',');
|
||||||
let res;
|
let res;
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
res = await API.put(`/api/token/`, { ...localInputs, id: parseInt(tokenId) });
|
res = await API.put(`/api/token/`, { ...localInputs, id: parseInt(tokenId) });
|
||||||
@ -109,6 +135,24 @@ const EditToken = () => {
|
|||||||
required={!isEdit}
|
required={!isEdit}
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<Form.Dropdown
|
||||||
|
label='模型范围'
|
||||||
|
placeholder={'请选择允许使用的模型,留空则不进行限制'}
|
||||||
|
name='models'
|
||||||
|
fluid
|
||||||
|
multiple
|
||||||
|
search
|
||||||
|
onLabelClick={(e, { value }) => {
|
||||||
|
copy(value).then();
|
||||||
|
}}
|
||||||
|
selection
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={inputs.models}
|
||||||
|
autoComplete='new-password'
|
||||||
|
options={modelOptions}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='过期时间'
|
label='过期时间'
|
||||||
|
Loading…
Reference in New Issue
Block a user