From fb90747c23373bce14e92abb823f08387c005aea Mon Sep 17 00:00:00 2001 From: JustSong Date: Thu, 4 Apr 2024 18:53:42 +0800 Subject: [PATCH 01/41] fix: fix /v1/models return null data when no models available --- controller/model.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/model.go b/controller/model.go index 53649391..43e73c6c 100644 --- a/controller/model.go +++ b/controller/model.go @@ -135,7 +135,7 @@ func ListModels(c *gin.Context) { for _, availableModel := range availableModels { modelSet[availableModel] = true } - var availableOpenAIModels []OpenAIModels + availableOpenAIModels := make([]OpenAIModels, 0) for _, model := range openAIModels { if _, ok := modelSet[model.Id]; ok { modelSet[model.Id] = false From 6f036bd0c937afc9e477d421dd8c3113424f313b Mon Sep 17 00:00:00 2001 From: Yang Fei Date: Thu, 4 Apr 2024 23:32:59 +0800 Subject: [PATCH 02/41] feat: add embedding-2 support for zhipu (#1273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 增加对智谱embedding-2模型的支持 * fix: fix usage & ratio --------- Co-authored-by: yangfei Co-authored-by: JustSong --- common/model-ratio.go | 1 + relay/channel/zhipu/adaptor.go | 44 ++++++++++++++++++++++-------- relay/channel/zhipu/constants.go | 2 +- relay/channel/zhipu/main.go | 47 ++++++++++++++++++++++++++++++++ relay/channel/zhipu/model.go | 18 ++++++++++++ 5 files changed, 100 insertions(+), 12 deletions(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index aa75042e..d8356dc2 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -91,6 +91,7 @@ var ModelRatio = map[string]float64{ "glm-4": 0.1 * RMB, "glm-4v": 0.1 * RMB, "glm-3-turbo": 0.005 * RMB, + "embedding-2": 0.0005 * RMB, "chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens "chatglm_pro": 0.7143, // ¥0.01 / 1k tokens "chatglm_std": 0.3572, // ¥0.005 / 1k tokens diff --git a/relay/channel/zhipu/adaptor.go b/relay/channel/zhipu/adaptor.go index 0ca23d59..7b570e71 100644 --- a/relay/channel/zhipu/adaptor.go +++ b/relay/channel/zhipu/adaptor.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/relay/channel" "github.com/songquanpeng/one-api/relay/channel/openai" + "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/util" "io" @@ -35,6 +36,9 @@ func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { if a.APIVersion == "v4" { return fmt.Sprintf("%s/api/paas/v4/chat/completions", meta.BaseURL), nil } + if meta.Mode == constant.RelayModeEmbeddings { + return fmt.Sprintf("%s/api/paas/v4/embeddings", meta.BaseURL), nil + } method := "invoke" if meta.IsStream { method = "sse-invoke" @@ -53,18 +57,24 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G if request == nil { return nil, errors.New("request is nil") } - // TopP (0.0, 1.0) - request.TopP = math.Min(0.99, request.TopP) - request.TopP = math.Max(0.01, request.TopP) + switch relayMode { + case constant.RelayModeEmbeddings: + baiduEmbeddingRequest := ConvertEmbeddingRequest(*request) + return baiduEmbeddingRequest, nil + default: + // TopP (0.0, 1.0) + request.TopP = math.Min(0.99, request.TopP) + request.TopP = math.Max(0.01, request.TopP) - // Temperature (0.0, 1.0) - request.Temperature = math.Min(0.99, request.Temperature) - request.Temperature = math.Max(0.01, request.Temperature) - a.SetVersionByModeName(request.Model) - if a.APIVersion == "v4" { - return request, nil + // Temperature (0.0, 1.0) + request.Temperature = math.Min(0.99, request.Temperature) + request.Temperature = math.Max(0.01, request.Temperature) + a.SetVersionByModeName(request.Model) + if a.APIVersion == "v4" { + return request, nil + } + return ConvertRequest(*request), nil } - return ConvertRequest(*request), nil } func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { @@ -84,14 +94,26 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *util.Rel if a.APIVersion == "v4" { return a.DoResponseV4(c, resp, meta) } + if meta.IsStream { err, usage = StreamHandler(c, resp) } else { - err, usage = Handler(c, resp) + if meta.Mode == constant.RelayModeEmbeddings { + err, usage = EmbeddingsHandler(c, resp) + } else { + err, usage = Handler(c, resp) + } } return } +func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *EmbeddingRequest { + return &EmbeddingRequest{ + Model: "embedding-2", + Input: request.Input.(string), + } +} + func (a *Adaptor) GetModelList() []string { return ModelList } diff --git a/relay/channel/zhipu/constants.go b/relay/channel/zhipu/constants.go index 1655a59d..2daeb19c 100644 --- a/relay/channel/zhipu/constants.go +++ b/relay/channel/zhipu/constants.go @@ -2,5 +2,5 @@ package zhipu var ModelList = []string{ "chatglm_turbo", "chatglm_pro", "chatglm_std", "chatglm_lite", - "glm-4", "glm-4v", "glm-3-turbo", + "glm-4", "glm-4v", "glm-3-turbo", "embedding-2", } diff --git a/relay/channel/zhipu/main.go b/relay/channel/zhipu/main.go index a46fd537..f54e0504 100644 --- a/relay/channel/zhipu/main.go +++ b/relay/channel/zhipu/main.go @@ -254,3 +254,50 @@ func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, * _, err = c.Writer.Write(jsonResponse) return nil, &fullTextResponse.Usage } + +func EmbeddingsHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *model.Usage) { + var zhipuResponse EmbeddingRespone + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil + } + err = resp.Body.Close() + if err != nil { + return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil + } + err = json.Unmarshal(responseBody, &zhipuResponse) + if err != nil { + return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil + } + fullTextResponse := embeddingResponseZhipu2OpenAI(&zhipuResponse) + jsonResponse, err := json.Marshal(fullTextResponse) + if err != nil { + return openai.ErrorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil + } + c.Writer.Header().Set("Content-Type", "application/json") + c.Writer.WriteHeader(resp.StatusCode) + _, err = c.Writer.Write(jsonResponse) + return nil, &fullTextResponse.Usage +} + +func embeddingResponseZhipu2OpenAI(response *EmbeddingRespone) *openai.EmbeddingResponse { + openAIEmbeddingResponse := openai.EmbeddingResponse{ + Object: "list", + Data: make([]openai.EmbeddingResponseItem, 0, len(response.Embeddings)), + Model: response.Model, + Usage: model.Usage{ + PromptTokens: response.PromptTokens, + CompletionTokens: response.CompletionTokens, + TotalTokens: response.Usage.TotalTokens, + }, + } + + for _, item := range response.Embeddings { + openAIEmbeddingResponse.Data = append(openAIEmbeddingResponse.Data, openai.EmbeddingResponseItem{ + Object: `embedding`, + Index: item.Index, + Embedding: item.Embedding, + }) + } + return &openAIEmbeddingResponse +} diff --git a/relay/channel/zhipu/model.go b/relay/channel/zhipu/model.go index b63e1d6f..3c3a7443 100644 --- a/relay/channel/zhipu/model.go +++ b/relay/channel/zhipu/model.go @@ -44,3 +44,21 @@ type tokenData struct { Token string ExpiryTime time.Time } + +type EmbeddingRequest struct { + Model string `json:"model"` + Input string `json:"input"` +} + +type EmbeddingRespone struct { + Model string `json:"model"` + Object string `json:"object"` + Embeddings []EmbeddingData `json:"data"` + model.Usage `json:"usage"` +} + +type EmbeddingData struct { + Index int `json:"index"` + Object string `json:"object"` + Embedding []float64 `json:"embedding"` +} From f73f2e51dfcf6f15f3d26dd045ad9ae283f25760 Mon Sep 17 00:00:00 2001 From: manjieqi <40858189+manjieqi@users.noreply.github.com> Date: Fri, 5 Apr 2024 00:02:15 +0800 Subject: [PATCH 03/41] feat: update baidu model name & ratio (#1253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修正百度模型名称 * 更新百度模型名称,并保留旧版兼容以及修正单价 * chore: add more model and adjust order --------- Co-authored-by: JustSong --- common/model-ratio.go | 24 ++++++++++++++++-------- relay/channel/baidu/adaptor.go | 24 +++++++++++++++++------- relay/channel/baidu/constants.go | 17 ++++++++++++----- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index d8356dc2..94607c92 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -72,14 +72,22 @@ var ModelRatio = map[string]float64{ "claude-3-sonnet-20240229": 3.0 / 1000 * USD, "claude-3-opus-20240229": 15.0 / 1000 * USD, // https://cloud.baidu.com/doc/WENXINWORKSHOP/s/hlrk4akp7 - "ERNIE-Bot": 0.8572, // ¥0.012 / 1k tokens - "ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens - "ERNIE-Bot-4": 0.12 * RMB, // ¥0.12 / 1k tokens - "ERNIE-Bot-8K": 0.024 * RMB, - "Embedding-V1": 0.1429, // ¥0.002 / 1k tokens - "bge-large-zh": 0.002 * RMB, - "bge-large-en": 0.002 * RMB, - "bge-large-8k": 0.002 * RMB, + "ERNIE-4.0-8K": 0.120 * RMB, + "ERNIE-Bot-8K-0922": 0.024 * RMB, + "ERNIE-3.5-8K": 0.012 * RMB, + "ERNIE-Lite-8K-0922": 0.008 * RMB, + "ERNIE-Speed-8K": 0.004 * RMB, + "ERNIE-3.5-4K-0205": 0.012 * RMB, + "ERNIE-3.5-8K-0205": 0.024 * RMB, + "ERNIE-3.5-8K-1222": 0.012 * RMB, + "ERNIE-Lite-8K": 0.003 * RMB, + "ERNIE-Speed-128K": 0.004 * RMB, + "ERNIE-Tiny-8K": 0.001 * RMB, + "BLOOMZ-7B": 0.004 * RMB, + "Embedding-V1": 0.002 * RMB, + "bge-large-zh": 0.002 * RMB, + "bge-large-en": 0.002 * RMB, + "tao-8k": 0.002 * RMB, // https://ai.google.dev/pricing "PaLM-2": 1, "gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens diff --git a/relay/channel/baidu/adaptor.go b/relay/channel/baidu/adaptor.go index 2d2e24f6..72302fdf 100644 --- a/relay/channel/baidu/adaptor.go +++ b/relay/channel/baidu/adaptor.go @@ -38,16 +38,26 @@ func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { suffix += "completions_pro" case "ERNIE-Bot-4": suffix += "completions_pro" - case "ERNIE-3.5-8K": - suffix += "completions" - case "ERNIE-Bot-8K": - suffix += "ernie_bot_8k" case "ERNIE-Bot": suffix += "completions" - case "ERNIE-Speed": - suffix += "ernie_speed" case "ERNIE-Bot-turbo": suffix += "eb-instant" + case "ERNIE-Speed": + suffix += "ernie_speed" + case "ERNIE-Bot-8K": + suffix += "ernie_bot_8k" + case "ERNIE-4.0-8K": + suffix += "completions_pro" + case "ERNIE-3.5-8K": + suffix += "completions" + case "ERNIE-Speed-8K": + suffix += "ernie_speed" + case "ERNIE-Speed-128K": + suffix += "ernie-speed-128k" + case "ERNIE-Lite-8K": + suffix += "ernie-lite-8k" + case "ERNIE-Tiny-8K": + suffix += "ernie-tiny-8k" case "BLOOMZ-7B": suffix += "bloomz_7b1" case "Embedding-V1": @@ -59,7 +69,7 @@ func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { case "tao-8k": suffix += "tao_8k" default: - suffix += meta.ActualModelName + suffix += strings.ToLower(meta.ActualModelName) } fullRequestURL := fmt.Sprintf("%s/rpc/2.0/ai_custom/v1/wenxinworkshop/%s", meta.BaseURL, suffix) var accessToken string diff --git a/relay/channel/baidu/constants.go b/relay/channel/baidu/constants.go index 45a4e901..ccdc25c3 100644 --- a/relay/channel/baidu/constants.go +++ b/relay/channel/baidu/constants.go @@ -1,11 +1,18 @@ package baidu var ModelList = []string{ - "ERNIE-Bot-4", - "ERNIE-Bot-8K", - "ERNIE-Bot", - "ERNIE-Speed", - "ERNIE-Bot-turbo", + "ERNIE-4.0-8K", + "ERNIE-Bot-8K-0922", + "ERNIE-3.5-8K", + "ERNIE-Lite-8K-0922", + "ERNIE-Speed-8K", + "ERNIE-3.5-4K-0205", + "ERNIE-3.5-8K-0205", + "ERNIE-3.5-8K-1222", + "ERNIE-Lite-8K", + "ERNIE-Speed-128K", + "ERNIE-Tiny-8K", + "BLOOMZ-7B", "Embedding-V1", "bge-large-zh", "bge-large-en", From 1f80b0a39fb728776fdfa635d17fbc90de56baef Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 5 Apr 2024 00:13:37 +0800 Subject: [PATCH 04/41] chore: add omitempty for xunfei functions --- relay/channel/xunfei/model.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay/channel/xunfei/model.go b/relay/channel/xunfei/model.go index e9cc59a6..97a43154 100644 --- a/relay/channel/xunfei/model.go +++ b/relay/channel/xunfei/model.go @@ -28,7 +28,7 @@ type ChatRequest struct { } `json:"message"` Functions struct { Text []model.Function `json:"text,omitempty"` - } `json:"functions"` + } `json:"functions,omitempty"` } `json:"payload"` } From 1994256bac48dc7d55d22f9a43577651eb187693 Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 5 Apr 2024 00:18:26 +0800 Subject: [PATCH 05/41] chore: disable channel when error message contain quota --- relay/util/common.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/relay/util/common.go b/relay/util/common.go index 535ef680..0bb76909 100644 --- a/relay/util/common.go +++ b/relay/util/common.go @@ -46,6 +46,9 @@ func ShouldDisableChannel(err *relaymodel.Error, statusCode int) bool { } else if strings.HasPrefix(err.Message, "This organization has been disabled.") { return true } + if strings.Contains(err.Message, "quota") { + return true + } return false } From 76569bb0b64d470aee3e970fe8e82557c8931cde Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 5 Apr 2024 00:31:41 +0800 Subject: [PATCH 06/41] chore: disable channel when error message contain credit or balance --- relay/util/common.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/relay/util/common.go b/relay/util/common.go index 0bb76909..d1f79a26 100644 --- a/relay/util/common.go +++ b/relay/util/common.go @@ -49,6 +49,12 @@ func ShouldDisableChannel(err *relaymodel.Error, statusCode int) bool { if strings.Contains(err.Message, "quota") { return true } + if strings.Contains(err.Message, "credit") { + return true + } + if strings.Contains(err.Message, "balance") { + return true + } return false } From 054b00b7250853f9ba345dc5756ae345ec5666bf Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 5 Apr 2024 00:40:48 +0800 Subject: [PATCH 07/41] docs: add API docs --- README.md | 1 + docs/API.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 docs/API.md diff --git a/README.md b/README.md index 2dcdbd4f..53847b45 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 + 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。 23. 支持主题切换,设置环境变量 `THEME` 即可,默认为 `default`,欢迎 PR 更多主题,具体参考[此处](./web/README.md)。 24. 配合 [Message Pusher](https://github.com/songquanpeng/message-pusher) 可将报警信息推送到多种 App 上。 +25. 支持**扩展**,详情请参考此处 [API 文档](./docs/API.md)。 ## 部署 ### 基于 Docker 进行部署 diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 00000000..9fc350ef --- /dev/null +++ b/docs/API.md @@ -0,0 +1,17 @@ +# 使用 API 操控 & 扩展 One API +> 欢迎提交 PR 在此放上你的拓展项目。 + +## 鉴权 +One API 支持两种鉴权方式:Cookie 和 Token,对于 Token,参照下图获取: + +![image](https://github.com/songquanpeng/songquanpeng.github.io/assets/39998050/c15281a7-83ed-47cb-a1f6-913cb6bf4a7c) + +之后,将 Token 作为请求头的 Authorization 字段的值即可,例如下面使用 Token 调用测试渠道的 API: +![image](https://github.com/songquanpeng/songquanpeng.github.io/assets/39998050/1273b7ae-cb60-4c0d-93a6-b1cbc039c4f8) + +## API 列表 +> 当前 API 列表不全,请自行通过浏览器抓取前端请求 + +欢迎此处 PR 补充。 + +如果现有的 API 没有办法满足你的需求,欢迎提交 issue 讨论。 \ No newline at end of file From 0a37aa4cbd322e7ff44446ae5f130a43a90a630e Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 5 Apr 2024 01:10:30 +0800 Subject: [PATCH 08/41] docs: add API docs --- controller/user.go | 77 ++++++++++++++++++++++++++++++++------------ docs/API.md | 31 ++++++++++++++++-- model/log.go | 15 +++++++++ router/api-router.go | 1 + 4 files changed, 101 insertions(+), 23 deletions(-) diff --git a/controller/user.go b/controller/user.go index 8b614e5d..61055878 100644 --- a/controller/user.go +++ b/controller/user.go @@ -180,27 +180,27 @@ func Register(c *gin.Context) { } func GetAllUsers(c *gin.Context) { - p, _ := strconv.Atoi(c.Query("p")) - if p < 0 { - p = 0 - } - - order := c.DefaultQuery("order", "") - users, err := model.GetAllUsers(p*config.ItemsPerPage, config.ItemsPerPage, order) - - 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": users, - }) + p, _ := strconv.Atoi(c.Query("p")) + if p < 0 { + p = 0 + } + + order := c.DefaultQuery("order", "") + users, err := model.GetAllUsers(p*config.ItemsPerPage, config.ItemsPerPage, order) + + 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": users, + }) } func SearchUsers(c *gin.Context) { @@ -770,3 +770,38 @@ func TopUp(c *gin.Context) { }) return } + +type adminTopUpRequest struct { + UserId int `json:"user_id"` + Quota int `json:"quota"` + Remark string `json:"remark"` +} + +func AdminTopUp(c *gin.Context) { + req := adminTopUpRequest{} + err := c.ShouldBindJSON(&req) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + err = model.IncreaseUserQuota(req.UserId, int64(req.Quota)) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + if req.Remark == "" { + req.Remark = fmt.Sprintf("通过 API 充值 %s", common.LogQuota(int64(req.Quota))) + } + model.RecordTopupLog(req.UserId, req.Remark, req.Quota) + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + }) + return +} diff --git a/docs/API.md b/docs/API.md index 9fc350ef..72ae7d91 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,6 +1,10 @@ # 使用 API 操控 & 扩展 One API > 欢迎提交 PR 在此放上你的拓展项目。 +例如,虽然 One API 本身没有直接支持支付,但是你可以通过系统扩展的 API 来实现支付功能。 + +又或者你想自定义渠道管理策略,也可以通过 API 来实现渠道的禁用与启用。 + ## 鉴权 One API 支持两种鉴权方式:Cookie 和 Token,对于 Token,参照下图获取: @@ -9,9 +13,32 @@ One API 支持两种鉴权方式:Cookie 和 Token,对于 Token,参照下 之后,将 Token 作为请求头的 Authorization 字段的值即可,例如下面使用 Token 调用测试渠道的 API: ![image](https://github.com/songquanpeng/songquanpeng.github.io/assets/39998050/1273b7ae-cb60-4c0d-93a6-b1cbc039c4f8) +## 请求格式与响应格式 +One API 使用 JSON 格式进行请求和响应。 + +对于响应体,一般格式如下: +```json +{ + "message": "请求信息", + "success": true, + "data": {} +} +``` + ## API 列表 > 当前 API 列表不全,请自行通过浏览器抓取前端请求 -欢迎此处 PR 补充。 +如果现有的 API 没有办法满足你的需求,欢迎提交 issue 讨论。 -如果现有的 API 没有办法满足你的需求,欢迎提交 issue 讨论。 \ No newline at end of file +### 获取当前登录用户信息 +**GET** `/api/user/self` + +### 为给定用户充值额度 +**POST** `/api/topup` +```json +{ + "user_id": 1, + "quota": 100000, + "remark": "充值 100000 额度" +} +``` \ No newline at end of file diff --git a/model/log.go b/model/log.go index 4409f73e..6b679c36 100644 --- a/model/log.go +++ b/model/log.go @@ -51,6 +51,21 @@ func RecordLog(userId int, logType int, content string) { } } +func RecordTopupLog(userId int, content string, quota int) { + log := &Log{ + UserId: userId, + Username: GetUsernameById(userId), + CreatedAt: helper.GetTimestamp(), + Type: LogTypeTopup, + Content: content, + Quota: quota, + } + err := LOG_DB.Create(log).Error + if err != nil { + logger.SysError("failed to record log: " + err.Error()) + } +} + func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int64, content string) { logger.Info(ctx, fmt.Sprintf("record consume log: userId=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content)) if !config.LogConsumeEnabled { diff --git a/router/api-router.go b/router/api-router.go index 4aa6d830..1558640f 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -26,6 +26,7 @@ func SetApiRouter(router *gin.Engine) { apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth) apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.WeChatBind) apiRouter.GET("/oauth/email/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.EmailBind) + apiRouter.POST("/topup", middleware.AdminAuth(), controller.AdminTopUp) userRoute := apiRouter.Group("/user") { From f8cc63f00b47a2279091e122f8815050262a31e2 Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 5 Apr 2024 01:23:11 +0800 Subject: [PATCH 09/41] feat: add user info to topup link --- web/default/src/pages/TopUp/index.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/web/default/src/pages/TopUp/index.js b/web/default/src/pages/TopUp/index.js index f52cb8d5..2fcf0eae 100644 --- a/web/default/src/pages/TopUp/index.js +++ b/web/default/src/pages/TopUp/index.js @@ -8,6 +8,7 @@ const TopUp = () => { const [topUpLink, setTopUpLink] = useState(''); const [userQuota, setUserQuota] = useState(0); const [isSubmitting, setIsSubmitting] = useState(false); + const [user, setUser] = useState({}); const topUp = async () => { if (redemptionCode === '') { @@ -41,7 +42,14 @@ const TopUp = () => { showError('超级管理员未设置充值链接!'); return; } - window.open(topUpLink, '_blank'); + let url = new URL(topUpLink); + let username = user.username; + let user_id = user.id; + // add username and user_id to the topup link + url.searchParams.append('username', username); + url.searchParams.append('user_id', user_id); + url.searchParams.append('transaction_id', crypto.randomUUID()); + window.open(url.toString(), '_blank'); }; const getUserQuota = async ()=>{ @@ -49,6 +57,7 @@ const TopUp = () => { const {success, message, data} = res.data; if (success) { setUserQuota(data.quota); + setUser(data); } else { showError(message); } @@ -80,7 +89,7 @@ const TopUp = () => { }} /> + ) : ( + <> + )} {status.wechat_login ? ( ) } + { + status.lark_client_id && ( + + ) + } - ) : ( - <> - )} - {status.wechat_login ? ( -