Commit 07fa9205 by 1808837298@qq.com

feat: playground

parent 85dfe503
...@@ -38,6 +38,46 @@ func relayHandler(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode ...@@ -38,6 +38,46 @@ func relayHandler(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode
return err return err
} }
func Playground(c *gin.Context) {
var openaiErr *dto.OpenAIErrorWithStatusCode
defer func() {
if openaiErr != nil {
c.JSON(openaiErr.StatusCode, gin.H{
"error": openaiErr.Error,
})
}
}()
playgroundRequest := &dto.PlayGroundRequest{}
err := common.UnmarshalBodyReusable(c, playgroundRequest)
if err != nil {
openaiErr = service.OpenAIErrorWrapperLocal(err, "unmarshal_request_failed", http.StatusBadRequest)
return
}
if playgroundRequest.Model == "" {
openaiErr = service.OpenAIErrorWrapperLocal(errors.New("请选择模型"), "model_required", http.StatusBadRequest)
return
}
c.Set("original_model", playgroundRequest.Model)
group := playgroundRequest.Group
if group == "" {
group = c.GetString("group")
} else {
c.Set("group", group)
}
log.Printf("group: %s", group)
log.Printf("model: %s", playgroundRequest.Model)
channel, err := model.CacheGetRandomSatisfiedChannel(group, playgroundRequest.Model, 0)
if err != nil {
openaiErr = service.OpenAIErrorWrapperLocal(err, "get_playground_channel_failed", http.StatusInternalServerError)
return
}
middleware.SetupContextForSelectedChannel(c, channel, playgroundRequest.Model)
Relay(c)
}
func Relay(c *gin.Context) { func Relay(c *gin.Context) {
relayMode := constant.Path2RelayMode(c.Request.URL.Path) relayMode := constant.Path2RelayMode(c.Request.URL.Path)
requestId := c.GetString(common.RequestIdKey) requestId := c.GetString(common.RequestIdKey)
......
...@@ -68,6 +68,7 @@ func setupLogin(user *model.User, c *gin.Context) { ...@@ -68,6 +68,7 @@ func setupLogin(user *model.User, c *gin.Context) {
session.Set("username", user.Username) session.Set("username", user.Username)
session.Set("role", user.Role) session.Set("role", user.Role)
session.Set("status", user.Status) session.Set("status", user.Status)
session.Set("group", user.Group)
err := session.Save() err := session.Save()
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
......
package dto
type PlayGroundRequest struct {
Model string `json:"model,omitempty"`
Group string `json:"group,omitempty"`
}
...@@ -121,6 +121,7 @@ func authHelper(c *gin.Context, minRole int) { ...@@ -121,6 +121,7 @@ func authHelper(c *gin.Context, minRole int) {
c.Set("username", username) c.Set("username", username)
c.Set("role", role) c.Set("role", role)
c.Set("id", id) c.Set("id", id)
c.Set("group", session.Get("group"))
c.Next() c.Next()
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"one-api/common" "one-api/common"
"one-api/constant" "one-api/constant"
relaycommon "one-api/relay/common"
"strconv" "strconv"
"strings" "strings"
) )
...@@ -257,52 +258,57 @@ func decreaseTokenQuota(id int, quota int) (err error) { ...@@ -257,52 +258,57 @@ func decreaseTokenQuota(id int, quota int) (err error) {
return err return err
} }
func PreConsumeTokenQuota(tokenId int, quota int) (userQuota int, err error) { func PreConsumeTokenQuota(relayInfo *relaycommon.RelayInfo, quota int) (userQuota int, err error) {
if quota < 0 { if quota < 0 {
return 0, errors.New("quota 不能为负数!") return 0, errors.New("quota 不能为负数!")
} }
token, err := GetTokenById(tokenId) if !relayInfo.IsPlayground {
token, err := GetTokenById(relayInfo.TokenId)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if !token.UnlimitedQuota && token.RemainQuota < quota { if !token.UnlimitedQuota && token.RemainQuota < quota {
return 0, errors.New("令牌额度不足") return 0, errors.New("令牌额度不足")
} }
userQuota, err = GetUserQuota(token.UserId) }
userQuota, err = GetUserQuota(relayInfo.UserId)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if userQuota < quota { if userQuota < quota {
return 0, errors.New(fmt.Sprintf("用户额度不足,剩余额度为 %d", userQuota)) return 0, errors.New(fmt.Sprintf("用户额度不足,剩余额度为 %d", userQuota))
} }
err = DecreaseTokenQuota(tokenId, quota) if !relayInfo.IsPlayground {
err = DecreaseTokenQuota(relayInfo.TokenId, quota)
if err != nil { if err != nil {
return 0, err return 0, err
} }
err = DecreaseUserQuota(token.UserId, quota) }
err = DecreaseUserQuota(relayInfo.UserId, quota)
return userQuota - quota, err return userQuota - quota, err
} }
func PostConsumeTokenQuota(tokenId int, userQuota int, quota int, preConsumedQuota int, sendEmail bool) (err error) { func PostConsumeTokenQuota(relayInfo *relaycommon.RelayInfo, userQuota int, quota int, preConsumedQuota int, sendEmail bool) (err error) {
token, err := GetTokenById(tokenId)
if quota > 0 { if quota > 0 {
err = DecreaseUserQuota(token.UserId, quota) err = DecreaseUserQuota(relayInfo.UserId, quota)
} else { } else {
err = IncreaseUserQuota(token.UserId, -quota) err = IncreaseUserQuota(relayInfo.UserId, -quota)
} }
if err != nil { if err != nil {
return err return err
} }
if !relayInfo.IsPlayground {
if quota > 0 { if quota > 0 {
err = DecreaseTokenQuota(tokenId, quota) err = DecreaseTokenQuota(relayInfo.TokenId, quota)
} else { } else {
err = IncreaseTokenQuota(tokenId, -quota) err = IncreaseTokenQuota(relayInfo.TokenId, -quota)
} }
if err != nil { if err != nil {
return err return err
} }
}
if sendEmail { if sendEmail {
if (quota + preConsumedQuota) != 0 { if (quota + preConsumedQuota) != 0 {
...@@ -310,7 +316,7 @@ func PostConsumeTokenQuota(tokenId int, userQuota int, quota int, preConsumedQuo ...@@ -310,7 +316,7 @@ func PostConsumeTokenQuota(tokenId int, userQuota int, quota int, preConsumedQuo
noMoreQuota := userQuota-(quota+preConsumedQuota) <= 0 noMoreQuota := userQuota-(quota+preConsumedQuota) <= 0
if quotaTooLow || noMoreQuota { if quotaTooLow || noMoreQuota {
go func() { go func() {
email, err := GetUserEmail(token.UserId) email, err := GetUserEmail(relayInfo.UserId)
if err != nil { if err != nil {
common.SysError("failed to fetch user email: " + err.Error()) common.SysError("failed to fetch user email: " + err.Error())
} }
......
...@@ -20,6 +20,7 @@ type RelayInfo struct { ...@@ -20,6 +20,7 @@ type RelayInfo struct {
setFirstResponse bool setFirstResponse bool
ApiType int ApiType int
IsStream bool IsStream bool
IsPlayground bool
RelayMode int RelayMode int
UpstreamModelName string UpstreamModelName string
OriginModelName string OriginModelName string
...@@ -65,6 +66,11 @@ func GenRelayInfo(c *gin.Context) *RelayInfo { ...@@ -65,6 +66,11 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
ApiKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "), ApiKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "),
Organization: c.GetString("channel_organization"), Organization: c.GetString("channel_organization"),
} }
if strings.HasPrefix(c.Request.URL.Path, "/pg") {
info.IsPlayground = true
info.RequestURLPath = strings.TrimPrefix(info.RequestURLPath, "/pg")
info.RequestURLPath = "/v1" + info.RequestURLPath
}
if info.BaseUrl == "" { if info.BaseUrl == "" {
info.BaseUrl = common.ChannelBaseURLs[channelType] info.BaseUrl = common.ChannelBaseURLs[channelType]
} }
...@@ -146,3 +152,20 @@ func GenTaskRelayInfo(c *gin.Context) *TaskRelayInfo { ...@@ -146,3 +152,20 @@ func GenTaskRelayInfo(c *gin.Context) *TaskRelayInfo {
} }
return info return info
} }
func (info *TaskRelayInfo) ToRelayInfo() *RelayInfo {
return &RelayInfo{
ChannelType: info.ChannelType,
ChannelId: info.ChannelId,
TokenId: info.TokenId,
UserId: info.UserId,
Group: info.Group,
StartTime: info.StartTime,
ApiType: info.ApiType,
RelayMode: info.RelayMode,
UpstreamModelName: info.UpstreamModelName,
RequestURLPath: info.RequestURLPath,
ApiKey: info.ApiKey,
BaseUrl: info.BaseUrl,
}
}
...@@ -42,7 +42,7 @@ const ( ...@@ -42,7 +42,7 @@ const (
func Path2RelayMode(path string) int { func Path2RelayMode(path string) int {
relayMode := RelayModeUnknown relayMode := RelayModeUnknown
if strings.HasPrefix(path, "/v1/chat/completions") { if strings.HasPrefix(path, "/v1/chat/completions") || strings.HasPrefix(path, "/pg/chat/completions") {
relayMode = RelayModeChatCompletions relayMode = RelayModeChatCompletions
} else if strings.HasPrefix(path, "/v1/completions") { } else if strings.HasPrefix(path, "/v1/completions") {
relayMode = RelayModeCompletions relayMode = RelayModeCompletions
......
...@@ -87,7 +87,7 @@ func AudioHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { ...@@ -87,7 +87,7 @@ func AudioHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
preConsumedQuota = 0 preConsumedQuota = 0
} }
if preConsumedQuota > 0 { if preConsumedQuota > 0 {
userQuota, err = model.PreConsumeTokenQuota(relayInfo.TokenId, preConsumedQuota) userQuota, err = model.PreConsumeTokenQuota(relayInfo, preConsumedQuota)
if err != nil { if err != nil {
return service.OpenAIErrorWrapperLocal(err, "pre_consume_token_quota_failed", http.StatusForbidden) return service.OpenAIErrorWrapperLocal(err, "pre_consume_token_quota_failed", http.StatusForbidden)
} }
...@@ -126,7 +126,7 @@ func AudioHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { ...@@ -126,7 +126,7 @@ func AudioHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
statusCodeMappingStr := c.GetString("status_code_mapping") statusCodeMappingStr := c.GetString("status_code_mapping")
if resp != nil { if resp != nil {
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
returnPreConsumedQuota(c, relayInfo.TokenId, userQuota, preConsumedQuota) returnPreConsumedQuota(c, relayInfo, userQuota, preConsumedQuota)
openaiErr := service.RelayErrorHandler(resp) openaiErr := service.RelayErrorHandler(resp)
// reset status code 重置状态码 // reset status code 重置状态码
service.ResetStatusCode(openaiErr, statusCodeMappingStr) service.ResetStatusCode(openaiErr, statusCodeMappingStr)
...@@ -136,7 +136,7 @@ func AudioHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { ...@@ -136,7 +136,7 @@ func AudioHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
usage, openaiErr := adaptor.DoResponse(c, resp, relayInfo) usage, openaiErr := adaptor.DoResponse(c, resp, relayInfo)
if openaiErr != nil { if openaiErr != nil {
returnPreConsumedQuota(c, relayInfo.TokenId, userQuota, preConsumedQuota) returnPreConsumedQuota(c, relayInfo, userQuota, preConsumedQuota)
// reset status code 重置状态码 // reset status code 重置状态码
service.ResetStatusCode(openaiErr, statusCodeMappingStr) service.ResetStatusCode(openaiErr, statusCodeMappingStr)
return openaiErr return openaiErr
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"one-api/constant" "one-api/constant"
"one-api/dto" "one-api/dto"
"one-api/model" "one-api/model"
relaycommon "one-api/relay/common"
relayconstant "one-api/relay/constant" relayconstant "one-api/relay/constant"
"one-api/service" "one-api/service"
"strconv" "strconv"
...@@ -146,6 +147,7 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse { ...@@ -146,6 +147,7 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse {
userId := c.GetInt("id") userId := c.GetInt("id")
group := c.GetString("group") group := c.GetString("group")
channelId := c.GetInt("channel_id") channelId := c.GetInt("channel_id")
relayInfo := relaycommon.GenRelayInfo(c)
var swapFaceRequest dto.SwapFaceRequest var swapFaceRequest dto.SwapFaceRequest
err := common.UnmarshalBodyReusable(c, &swapFaceRequest) err := common.UnmarshalBodyReusable(c, &swapFaceRequest)
if err != nil { if err != nil {
...@@ -191,7 +193,7 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse { ...@@ -191,7 +193,7 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse {
} }
defer func(ctx context.Context) { defer func(ctx context.Context) {
if mjResp.StatusCode == 200 && mjResp.Response.Code == 1 { if mjResp.StatusCode == 200 && mjResp.Response.Code == 1 {
err := model.PostConsumeTokenQuota(tokenId, userQuota, quota, 0, true) err := model.PostConsumeTokenQuota(relayInfo, userQuota, quota, 0, true)
if err != nil { if err != nil {
common.SysError("error consuming token remain quota: " + err.Error()) common.SysError("error consuming token remain quota: " + err.Error())
} }
...@@ -356,6 +358,7 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons ...@@ -356,6 +358,7 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons
userId := c.GetInt("id") userId := c.GetInt("id")
group := c.GetString("group") group := c.GetString("group")
channelId := c.GetInt("channel_id") channelId := c.GetInt("channel_id")
relayInfo := relaycommon.GenRelayInfo(c)
consumeQuota := true consumeQuota := true
var midjRequest dto.MidjourneyRequest var midjRequest dto.MidjourneyRequest
err := common.UnmarshalBodyReusable(c, &midjRequest) err := common.UnmarshalBodyReusable(c, &midjRequest)
...@@ -495,7 +498,7 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons ...@@ -495,7 +498,7 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons
defer func(ctx context.Context) { defer func(ctx context.Context) {
if consumeQuota && midjResponseWithStatus.StatusCode == 200 { if consumeQuota && midjResponseWithStatus.StatusCode == 200 {
err := model.PostConsumeTokenQuota(tokenId, userQuota, quota, 0, true) err := model.PostConsumeTokenQuota(relayInfo, userQuota, quota, 0, true)
if err != nil { if err != nil {
common.SysError("error consuming token remain quota: " + err.Error()) common.SysError("error consuming token remain quota: " + err.Error())
} }
......
...@@ -178,7 +178,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { ...@@ -178,7 +178,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
if resp != nil { if resp != nil {
relayInfo.IsStream = relayInfo.IsStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") relayInfo.IsStream = relayInfo.IsStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream")
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
returnPreConsumedQuota(c, relayInfo.TokenId, userQuota, preConsumedQuota) returnPreConsumedQuota(c, relayInfo, userQuota, preConsumedQuota)
openaiErr := service.RelayErrorHandler(resp) openaiErr := service.RelayErrorHandler(resp)
// reset status code 重置状态码 // reset status code 重置状态码
service.ResetStatusCode(openaiErr, statusCodeMappingStr) service.ResetStatusCode(openaiErr, statusCodeMappingStr)
...@@ -188,7 +188,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { ...@@ -188,7 +188,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
usage, openaiErr := adaptor.DoResponse(c, resp, relayInfo) usage, openaiErr := adaptor.DoResponse(c, resp, relayInfo)
if openaiErr != nil { if openaiErr != nil {
returnPreConsumedQuota(c, relayInfo.TokenId, userQuota, preConsumedQuota) returnPreConsumedQuota(c, relayInfo, userQuota, preConsumedQuota)
// reset status code 重置状态码 // reset status code 重置状态码
service.ResetStatusCode(openaiErr, statusCodeMappingStr) service.ResetStatusCode(openaiErr, statusCodeMappingStr)
return openaiErr return openaiErr
...@@ -266,7 +266,7 @@ func preConsumeQuota(c *gin.Context, preConsumedQuota int, relayInfo *relaycommo ...@@ -266,7 +266,7 @@ func preConsumeQuota(c *gin.Context, preConsumedQuota int, relayInfo *relaycommo
} }
} }
if preConsumedQuota > 0 { if preConsumedQuota > 0 {
userQuota, err = model.PreConsumeTokenQuota(relayInfo.TokenId, preConsumedQuota) userQuota, err = model.PreConsumeTokenQuota(relayInfo, preConsumedQuota)
if err != nil { if err != nil {
return 0, 0, service.OpenAIErrorWrapperLocal(err, "pre_consume_token_quota_failed", http.StatusForbidden) return 0, 0, service.OpenAIErrorWrapperLocal(err, "pre_consume_token_quota_failed", http.StatusForbidden)
} }
...@@ -274,11 +274,11 @@ func preConsumeQuota(c *gin.Context, preConsumedQuota int, relayInfo *relaycommo ...@@ -274,11 +274,11 @@ func preConsumeQuota(c *gin.Context, preConsumedQuota int, relayInfo *relaycommo
return preConsumedQuota, userQuota, nil return preConsumedQuota, userQuota, nil
} }
func returnPreConsumedQuota(c *gin.Context, tokenId int, userQuota int, preConsumedQuota int) { func returnPreConsumedQuota(c *gin.Context, relayInfo *relaycommon.RelayInfo, userQuota int, preConsumedQuota int) {
if preConsumedQuota != 0 { if preConsumedQuota != 0 {
go func(ctx context.Context) { go func(ctx context.Context) {
// return pre-consumed quota // return pre-consumed quota
err := model.PostConsumeTokenQuota(tokenId, userQuota, -preConsumedQuota, 0, false) err := model.PostConsumeTokenQuota(relayInfo, userQuota, -preConsumedQuota, 0, false)
if err != nil { if err != nil {
common.SysError("error return pre-consumed quota: " + err.Error()) common.SysError("error return pre-consumed quota: " + err.Error())
} }
...@@ -336,7 +336,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelN ...@@ -336,7 +336,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelN
//} //}
quotaDelta := quota - preConsumedQuota quotaDelta := quota - preConsumedQuota
if quotaDelta != 0 { if quotaDelta != 0 {
err := model.PostConsumeTokenQuota(relayInfo.TokenId, userQuota, quotaDelta, preConsumedQuota, true) err := model.PostConsumeTokenQuota(relayInfo, userQuota, quotaDelta, preConsumedQuota, true)
if err != nil { if err != nil {
common.LogError(ctx, "error consuming token remain quota: "+err.Error()) common.LogError(ctx, "error consuming token remain quota: "+err.Error())
} }
......
...@@ -101,7 +101,7 @@ func RerankHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode ...@@ -101,7 +101,7 @@ func RerankHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode
} }
if resp != nil { if resp != nil {
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
returnPreConsumedQuota(c, relayInfo.TokenId, userQuota, preConsumedQuota) returnPreConsumedQuota(c, relayInfo, userQuota, preConsumedQuota)
openaiErr := service.RelayErrorHandler(resp) openaiErr := service.RelayErrorHandler(resp)
// reset status code 重置状态码 // reset status code 重置状态码
service.ResetStatusCode(openaiErr, statusCodeMappingStr) service.ResetStatusCode(openaiErr, statusCodeMappingStr)
...@@ -111,7 +111,7 @@ func RerankHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode ...@@ -111,7 +111,7 @@ func RerankHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode
usage, openaiErr := adaptor.DoResponse(c, resp, relayInfo) usage, openaiErr := adaptor.DoResponse(c, resp, relayInfo)
if openaiErr != nil { if openaiErr != nil {
returnPreConsumedQuota(c, relayInfo.TokenId, userQuota, preConsumedQuota) returnPreConsumedQuota(c, relayInfo, userQuota, preConsumedQuota)
// reset status code 重置状态码 // reset status code 重置状态码
service.ResetStatusCode(openaiErr, statusCodeMappingStr) service.ResetStatusCode(openaiErr, statusCodeMappingStr)
return openaiErr return openaiErr
......
...@@ -111,7 +111,8 @@ func RelayTaskSubmit(c *gin.Context, relayMode int) (taskErr *dto.TaskError) { ...@@ -111,7 +111,8 @@ func RelayTaskSubmit(c *gin.Context, relayMode int) (taskErr *dto.TaskError) {
defer func(ctx context.Context) { defer func(ctx context.Context) {
// release quota // release quota
if relayInfo.ConsumeQuota && taskErr == nil { if relayInfo.ConsumeQuota && taskErr == nil {
err := model.PostConsumeTokenQuota(relayInfo.TokenId, userQuota, quota, 0, true)
err := model.PostConsumeTokenQuota(relayInfo.ToRelayInfo(), userQuota, quota, 0, true)
if err != nil { if err != nil {
common.SysError("error consuming token remain quota: " + err.Error()) common.SysError("error consuming token remain quota: " + err.Error())
} }
......
...@@ -16,6 +16,11 @@ func SetRelayRouter(router *gin.Engine) { ...@@ -16,6 +16,11 @@ func SetRelayRouter(router *gin.Engine) {
modelsRouter.GET("", controller.ListModels) modelsRouter.GET("", controller.ListModels)
modelsRouter.GET("/:model", controller.RetrieveModel) modelsRouter.GET("/:model", controller.RetrieveModel)
} }
playgroundRouter := router.Group("/pg")
playgroundRouter.Use(middleware.UserAuth())
{
playgroundRouter.POST("/chat/completions", controller.Playground)
}
relayV1Router := router.Group("/v1") relayV1Router := router.Group("/v1")
relayV1Router.Use(middleware.TokenAuth(), middleware.Distribute()) relayV1Router.Use(middleware.TokenAuth(), middleware.Distribute())
{ {
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
"private": true, "private": true,
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@douyinfe/semi-icons": "^2.46.1", "@douyinfe/semi-icons": "^2.63.1",
"@douyinfe/semi-ui": "^2.55.3", "@douyinfe/semi-ui": "^2.63.1",
"@visactor/react-vchart": "~1.8.8", "@visactor/react-vchart": "~1.8.8",
"@visactor/vchart": "~1.8.8", "@visactor/vchart": "~1.8.8",
"@visactor/vchart-semi-theme": "~1.8.8", "@visactor/vchart-semi-theme": "~1.8.8",
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
"react-toastify": "^9.0.8", "react-toastify": "^9.0.8",
"react-turnstile": "^1.0.5", "react-turnstile": "^1.0.5",
"semantic-ui-offline": "^2.5.0", "semantic-ui-offline": "^2.5.0",
"semantic-ui-react": "^2.1.3" "semantic-ui-react": "^2.1.3",
"sse": "github:mpetazzoni/sse.js"
}, },
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -25,6 +25,7 @@ import { Layout } from '@douyinfe/semi-ui'; ...@@ -25,6 +25,7 @@ import { Layout } from '@douyinfe/semi-ui';
import Midjourney from './pages/Midjourney'; import Midjourney from './pages/Midjourney';
import Pricing from './pages/Pricing/index.js'; import Pricing from './pages/Pricing/index.js';
import Task from "./pages/Task/index.js"; import Task from "./pages/Task/index.js";
import Playground from './components/Playground.js';
const Home = lazy(() => import('./pages/Home')); const Home = lazy(() => import('./pages/Home'));
const Detail = lazy(() => import('./pages/Detail')); const Detail = lazy(() => import('./pages/Detail'));
...@@ -101,6 +102,14 @@ function App() { ...@@ -101,6 +102,14 @@ function App() {
} }
/> />
<Route <Route
path='/playground'
element={
<PrivateRoute>
<Playground />
</PrivateRoute>
}
/>
<Route
path='/redemption' path='/redemption'
element={ element={
<PrivateRoute> <PrivateRoute>
......
...@@ -39,10 +39,10 @@ let buttons = [ ...@@ -39,10 +39,10 @@ let buttons = [
// icon: <IconHomeStroked />, // icon: <IconHomeStroked />,
}, },
// { // {
// text: '模型价格', // text: 'Playground',
// itemKey: 'pricing', // itemKey: 'playground',
// to: '/pricing', // to: '/playground',
// icon: <IconNoteMoneyStroked />, // // icon: <IconNoteMoneyStroked />,
// }, // },
]; ];
......
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { UserContext } from '../context/User';
import { API, getUserIdFromLocalStorage, showError } from '../helpers';
import { Card, Chat, Input, Layout, Select, Slider, TextArea, Typography } from '@douyinfe/semi-ui';
import { SSE } from 'sse';
const defaultMessage = [
{
role: 'user',
id: '2',
createAt: 1715676751919,
content: "你好",
},
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: "你好,请问有什么可以帮助您的吗?",
}
];
let id = 4;
function getId() {
return `${id++}`
}
const Playground = () => {
const [inputs, setInputs] = useState({
model: 'gpt-4o-mini',
group: '',
max_tokens: 0,
temperature: 0,
});
const [searchParams, setSearchParams] = useSearchParams();
const [userState, userDispatch] = useContext(UserContext);
const [status, setStatus] = useState({});
const [systemPrompt, setSystemPrompt] = useState('You are a helpful assistant. You can help me by answering my questions. You can also ask me questions.');
const [message, setMessage] = useState(defaultMessage);
const [models, setModels] = useState([]);
const [groups, setGroups] = useState([]);
const handleInputChange = (name, value) => {
setInputs((inputs) => ({ ...inputs, [name]: value }));
};
useEffect(() => {
if (searchParams.get('expired')) {
showError('未登录或登录已过期,请重新登录!');
}
let status = localStorage.getItem('status');
if (status) {
status = JSON.parse(status);
setStatus(status);
}
loadModels();
loadGroups();
}, []);
const loadModels = async () => {
let res = await API.get(`/api/user/models`);
const { success, message, data } = res.data;
if (success) {
let localModelOptions = data.map((model) => ({
label: model,
value: model,
}));
setModels(localModelOptions);
} else {
showError(message);
}
};
const loadGroups = async () => {
let res = await API.get(`/api/user/groups`);
const { success, message, data } = res.data;
if (success) {
// return data is a map, key is group name, value is group description
// label is group description, value is group name
let localGroupOptions = Object.keys(data).map((group) => ({
label: data[group],
value: group,
}));
// handleInputChange('group', localGroupOptions[0].value);
if (localGroupOptions.length > 0) {
} else {
localGroupOptions = [{
label: '用户分组',
value: '',
}];
setGroups(localGroupOptions);
}
setGroups(localGroupOptions);
handleInputChange('group', localGroupOptions[0].value);
} else {
showError(message);
}
};
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
margin: '0px 8px',
}
const getSystemMessage = () => {
if (systemPrompt !== '') {
return {
role: 'system',
id: '1',
createAt: 1715676751919,
content: systemPrompt,
}
}
}
let handleSSE = (payload) => {
let source = new SSE('/pg/chat/completions', {
headers: {
"Content-Type": "application/json",
"New-Api-User": getUserIdFromLocalStorage(),
},
method: "POST",
payload: JSON.stringify(payload),
});
source.addEventListener("message", (e) => {
if (e.data !== "[DONE]") {
let payload = JSON.parse(e.data);
// console.log("Payload: ", payload);
if (payload.choices.length === 0) {
source.close();
completeMessage();
} else {
let text = payload.choices[0].delta.content;
generateMockResponse(text);
}
} else {
completeMessage();
}
});
source.addEventListener("error", (e) => {
generateMockResponse(e.data)
completeMessage();
});
source.addEventListener("readystatechange", (e) => {
if (e.readyState >= 2) {
if (source.status === undefined) {
source.close();
completeMessage();
}
}
});
source.stream();
}
const onMessageSend = useCallback((content, attachment) => {
console.log("attachment: ", attachment);
setMessage((prevMessage) => {
const newMessage = [
...prevMessage,
{
role: 'user',
content: content,
createAt: Date.now(),
id: getId()
}
];
// 将 getPayload 移到这里
const getPayload = () => {
let systemMessage = getSystemMessage();
let messages = newMessage.map((item) => {
return {
role: item.role,
content: item.content,
}
});
if (systemMessage) {
messages.unshift(systemMessage);
}
return {
messages: messages,
stream: true,
model: inputs.model,
group: inputs.group,
max_tokens: inputs.max_tokens,
temperature: inputs.temperature,
};
};
// 使用更新后的消息状态调用 handleSSE
handleSSE(getPayload());
newMessage.push({
role: 'assistant',
content: '',
createAt: Date.now(),
id: getId(),
status: 'loading'
});
return newMessage;
});
}, [getSystemMessage]);
const completeMessage = useCallback(() => {
setMessage((prevMessage) => {
const lastMessage = prevMessage[prevMessage.length - 1];
return [
...prevMessage.slice(0, -1),
{ ...lastMessage, status: 'complete' }
];
});
}, [])
const generateMockResponse = useCallback((content) => {
// console.log("Generate Mock Response: ", content);
setMessage((message) => {
const lastMessage = message[message.length - 1];
let newMessage = {...lastMessage};
if (lastMessage.status === 'loading' || lastMessage.status === 'incomplete') {
newMessage = {
...newMessage,
content: (lastMessage.content || '') + content,
status: 'incomplete'
}
}
return [ ...message.slice(0, -1), newMessage ]
})
}, []);
return (
<Layout style={{height: '100%'}}>
<Layout.Sider>
<Card style={commonOuterStyle}>
<div style={{ marginTop: 10 }}>
<Typography.Text strong>分组:</Typography.Text>
</div>
<Select
placeholder={'请选择分组'}
name='group'
required
selection
onChange={(value) => {
handleInputChange('group', value);
}}
value={inputs.group}
autoComplete='new-password'
optionList={groups}
/>
<div style={{ marginTop: 10 }}>
<Typography.Text strong>模型:</Typography.Text>
</div>
<Select
placeholder={'请选择模型'}
name='model'
required
selection
filter
onChange={(value) => {
handleInputChange('model', value);
}}
value={inputs.model}
autoComplete='new-password'
optionList={models}
/>
<div style={{ marginTop: 10 }}>
<Typography.Text strong>Temperature</Typography.Text>
</div>
<Slider
step={0.1}
min={0.1}
max={1}
value={inputs.temperature}
onChange={(value) => {
handleInputChange('temperature', value);
}}
/>
<div style={{ marginTop: 10 }}>
<Typography.Text strong>MaxTokens</Typography.Text>
</div>
<Input
placeholder='MaxTokens'
name='max_tokens'
required
autoComplete='new-password'
defaultValue={0}
value={inputs.max_tokens}
onChange={(value) => {
handleInputChange('max_tokens', value);
}}
/>
<div style={{ marginTop: 10 }}>
<Typography.Text strong>System</Typography.Text>
</div>
<TextArea
placeholder='System Prompt'
name='system'
required
autoComplete='new-password'
autosize
defaultValue={systemPrompt}
// value={systemPrompt}
onChange={(value) => {
setSystemPrompt(value);
}}
/>
</Card>
</Layout.Sider>
<Layout.Content>
<div style={{height: '100%'}}>
<Chat
chatBoxRenderConfig={{
renderChatBoxAction: () => {
return <div></div>
}
}}
style={commonOuterStyle}
chats={message}
onMessageSend={onMessageSend}
showClearContext
onClear={() => {
setMessage([]);
}}
/>
</div>
</Layout.Content>
</Layout>
);
};
export default Playground;
...@@ -15,7 +15,7 @@ import '../index.css'; ...@@ -15,7 +15,7 @@ import '../index.css';
import { import {
IconCalendarClock, IconChecklistStroked, IconCalendarClock, IconChecklistStroked,
IconComment, IconComment, IconCommentStroked,
IconCreditCard, IconCreditCard,
IconGift, IconHelpCircle, IconGift, IconHelpCircle,
IconHistogram, IconHistogram,
...@@ -63,6 +63,7 @@ const SiderBar = () => { ...@@ -63,6 +63,7 @@ const SiderBar = () => {
detail: '/detail', detail: '/detail',
pricing: '/pricing', pricing: '/pricing',
task: '/task', task: '/task',
playground: '/playground',
}; };
const headerButtons = useMemo( const headerButtons = useMemo(
...@@ -74,6 +75,12 @@ const SiderBar = () => { ...@@ -74,6 +75,12 @@ const SiderBar = () => {
// icon: <IconHome />, // icon: <IconHome />,
// }, // },
{ {
text: 'Playground',
itemKey: 'playground',
to: '/playground',
icon: <IconCommentStroked />,
},
{
text: '模型价格', text: '模型价格',
itemKey: 'pricing', itemKey: 'pricing',
to: '/pricing', to: '/pricing',
......
...@@ -59,6 +59,12 @@ body { ...@@ -59,6 +59,12 @@ body {
display: revert; display: revert;
} }
.semi-chat {
padding-top: 0 !important;
padding-bottom: 0 !important;
height: 100%;
}
.tableHiddle { .tableHiddle {
display: none !important; display: none !important;
} }
......
...@@ -55,6 +55,10 @@ export default defineConfig({ ...@@ -55,6 +55,10 @@ export default defineConfig({
target: 'http://localhost:3000', target: 'http://localhost:3000',
changeOrigin: true, changeOrigin: true,
}, },
'/pg': {
target: 'http://localhost:3000',
changeOrigin: true,
},
}, },
}, },
}); });
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