From 29fa94e7d28140f42bf49d95268f3c12f7c6a0ea Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Fri, 11 Aug 2023 19:27:42 +0800 Subject: [PATCH 01/24] docs: update FastGPT's description (#388) --- README.en.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.en.md b/README.en.md index 2ea6e65e..92eb567f 100644 --- a/README.en.md +++ b/README.en.md @@ -283,7 +283,7 @@ If the channel ID is not provided, load balancing will be used to distribute the + Double-check that your interface address and API Key are correct. ## Related Projects -[FastGPT](https://github.com/c121914yu/FastGPT): Build an AI knowledge base in three minutes +[FastGPT](https://github.com/labring/FastGPT): Knowledge question answering system based on the LLM ## Note This project is an open-source project. Please use it in compliance with OpenAI's [Terms of Use](https://openai.com/policies/terms-of-use) and **applicable laws and regulations**. It must not be used for illegal purposes. diff --git a/README.md b/README.md index ad90bb15..9840fa19 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ https://openai.justsong.cn + 上游通道 429 了。 ## 相关项目 -[FastGPT](https://github.com/c121914yu/FastGPT): 三分钟搭建 AI 知识库 +[FastGPT](https://github.com/labring/FastGPT): 基于 LLM 大语言模型的知识库问答系统 ## 注意 From 3b36608bbd47eadcd581e264a430290a287143c4 Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 11 Aug 2023 19:53:01 +0800 Subject: [PATCH 02/24] fix: update no route handler --- controller/relay.go | 6 +++--- router/web-router.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/controller/relay.go b/controller/relay.go index dee9c94d..617e22b8 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -207,10 +207,10 @@ func RelayNotImplemented(c *gin.Context) { func RelayNotFound(c *gin.Context) { err := OpenAIError{ - Message: fmt.Sprintf("API not found: %s:%s", c.Request.Method, c.Request.URL.Path), - Type: "one_api_error", + Message: fmt.Sprintf("Invalid URL (%s %s)", c.Request.Method, c.Request.URL.Path), + Type: "invalid_request_error", Param: "", - Code: "api_not_found", + Code: "", } c.JSON(http.StatusNotFound, gin.H{ "error": err, diff --git a/router/web-router.go b/router/web-router.go index 19fc0c04..8f9c18a2 100644 --- a/router/web-router.go +++ b/router/web-router.go @@ -18,7 +18,7 @@ func SetWebRouter(router *gin.Engine, buildFS embed.FS, indexPage []byte) { router.Use(middleware.Cache()) router.Use(static.Serve("/", common.EmbedFolder(buildFS, "web/build"))) router.NoRoute(func(c *gin.Context) { - if strings.HasPrefix(c.Request.RequestURI, "/v1") { + if strings.HasPrefix(c.Request.RequestURI, "/v1") || strings.HasPrefix(c.Request.RequestURI, "/api") { controller.RelayNotFound(c) return } From 3a18cebe3490a4a56207bf0dea1e54aaa90c2f84 Mon Sep 17 00:00:00 2001 From: glzjin Date: Fri, 11 Aug 2023 22:24:11 +0800 Subject: [PATCH 03/24] fix: update tiktoken-go's version to fix resource consumption problem (#392, close #161) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 1d061520..1d08a7d3 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.5.0 - github.com/pkoukk/tiktoken-go v0.1.1 + github.com/pkoukk/tiktoken-go v0.1.5 golang.org/x/crypto v0.9.0 gorm.io/driver/mysql v1.4.3 gorm.io/driver/sqlite v1.4.3 @@ -26,7 +26,7 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dlclark/regexp2 v1.8.1 // indirect + github.com/dlclark/regexp2 v1.10.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect diff --git a/go.sum b/go.sum index c6e4423c..b4281cb6 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= -github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= @@ -112,8 +112,8 @@ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZO github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo= -github.com/pkoukk/tiktoken-go v0.1.1/go.mod h1:boMWvk9pQCOTx11pgu0DrIdrAKgQzzJKUP6vLXaz7Rw= +github.com/pkoukk/tiktoken-go v0.1.5 h1:hAlT4dCf6Uk50x8E7HQrddhH3EWMKUN+LArExQQsQx4= +github.com/pkoukk/tiktoken-go v0.1.5/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= From 2b088a167855c49425add5ffc54b4d687e83cd01 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 09:29:29 +0800 Subject: [PATCH 04/24] fix: disable eslint when building (close #371, close #376) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4afbf100..22055553 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /build COPY ./web . COPY ./VERSION . RUN npm install -RUN REACT_APP_VERSION=$(cat VERSION) npm run build +RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build FROM golang AS builder2 From 466005de07c668e620c2539a9c96230d2534102b Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 10:05:25 +0800 Subject: [PATCH 05/24] fix: set connection limits for database --- README.md | 5 +++++ common/utils.go | 13 +++++++++++++ model/main.go | 11 ++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9840fa19..2f9d0d39 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,11 @@ graph LR + 注意需要提前建立数据库 `oneapi`,无需手动建表,程序将自动建表。 + 如果使用本地数据库:部署命令可添加 `--network="host"` 以使得容器内的程序可以访问到宿主机上的 MySQL。 + 如果使用云数据库:如果云服务器需要验证身份,需要在连接参数中添加 `?tls=skip-verify`。 + + 请根据你的数据库配置修改下列参数(或者保持默认值): + + `SQL_MAX_IDLE_CONNS`:最大空闲连接数,默认为 `10`。 + + `SQL_MAX_OPEN_CONNS`:最大打开连接数,默认为 `100`。 + + 如果报错 `Error 1040: Too many connections`,请适当减小该值。 + + `SQL_CONN_MAX_LIFETIME`:连接的最大生命周期,默认为 `60`,单位分钟。 4. `FRONTEND_BASE_URL`:设置之后将重定向页面请求到指定的地址,仅限从服务器设置。 + 例子:`FRONTEND_BASE_URL=https://openai.justsong.cn` 5. `SYNC_FREQUENCY`:设置之后将定期与数据库同步配置,单位为秒,未设置则不进行同步。 diff --git a/common/utils.go b/common/utils.go index 1329c1a0..bb9b7e0c 100644 --- a/common/utils.go +++ b/common/utils.go @@ -7,6 +7,7 @@ import ( "log" "math/rand" "net" + "os" "os/exec" "runtime" "strconv" @@ -177,3 +178,15 @@ func Max(a int, b int) int { return b } } + +func GetOrDefault(env string, defaultValue int) int { + if env == "" || os.Getenv(env) == "" { + return defaultValue + } + num, err := strconv.Atoi(os.Getenv(env)) + if err != nil { + SysError(fmt.Sprintf("failed to parse %s: %s, using default value: %d", env, err.Error(), defaultValue)) + return defaultValue + } + return num +} diff --git a/model/main.go b/model/main.go index 5bc5ce19..ddbc69aa 100644 --- a/model/main.go +++ b/model/main.go @@ -6,6 +6,7 @@ import ( "gorm.io/gorm" "one-api/common" "os" + "time" ) var DB *gorm.DB @@ -57,10 +58,18 @@ func InitDB() (err error) { common.SysLog("database connected") if err == nil { DB = db + sqlDB, err := DB.DB() + if err != nil { + return err + } + sqlDB.SetMaxIdleConns(common.GetOrDefault("SQL_MAX_IDLE_CONNS", 10)) + sqlDB.SetMaxOpenConns(common.GetOrDefault("SQL_MAX_OPEN_CONNS", 100)) + sqlDB.SetConnMaxLifetime(time.Second * time.Duration(common.GetOrDefault("SQL_MAX_LIFETIME", 60))) + if !common.IsMasterNode { return nil } - err := db.AutoMigrate(&Channel{}) + err = db.AutoMigrate(&Channel{}) if err != nil { return err } From f2159e1033a655ce9476f2d704051cdba5f6cd4d Mon Sep 17 00:00:00 2001 From: wood chen <95951386+woodchen-ink@users.noreply.github.com> Date: Sat, 12 Aug 2023 10:14:13 +0800 Subject: [PATCH 06/24] docs: update README (#374) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f9d0d39..0f7ca2a9 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,8 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR` -`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。 + +其中,`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。 数据将会保存在宿主机的 `/home/ubuntu/data/one-api` 目录,请确保该目录存在且具有写入权限,或者更改为合适的目录。 From be780462f1d559949ea040ca06752485db5df971 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 10:16:59 +0800 Subject: [PATCH 07/24] docs: update README --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0f7ca2a9..8e18d6b4 100644 --- a/README.md +++ b/README.md @@ -102,17 +102,16 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 ### 基于 Docker 进行部署 部署命令:`docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api` +其中,`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。 + +数据将会保存在宿主机的 `/home/ubuntu/data/one-api` 目录,请确保该目录存在且具有写入权限,或者更改为合适的目录。 + 如果上面的镜像无法拉取,可以尝试使用 GitHub 的 Docker 镜像,将上面的 `justsong/one-api` 替换为 `ghcr.io/songquanpeng/one-api` 即可。 如果你的并发量较大,**务必**设置 `SQL_DSN`,详见下面[环境变量](#环境变量)一节。 更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR` - -其中,`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。 - -数据将会保存在宿主机的 `/home/ubuntu/data/one-api` 目录,请确保该目录存在且具有写入权限,或者更改为合适的目录。 - Nginx 的参考配置: ``` server{ From 150d068e9f089fd9198a8523910863661a29998f Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 10:20:54 +0800 Subject: [PATCH 08/24] chore: update prompt --- controller/relay.go | 2 +- i18n/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/relay.go b/controller/relay.go index 617e22b8..e5d898f5 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -176,7 +176,7 @@ func Relay(c *gin.Context) { c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s?retry=%d", c.Request.URL.Path, retryTimes-1)) } else { if err.StatusCode == http.StatusTooManyRequests { - err.OpenAIError.Message = "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。" + err.OpenAIError.Message = "当前分组上游负载已饱和,请稍后再试" } c.JSON(err.StatusCode, gin.H{ "error": err.OpenAIError, diff --git a/i18n/en.json b/i18n/en.json index f53aad4c..9ea33c38 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -39,7 +39,7 @@ "兑换码个数必须大于0": "The number of redemption codes must be greater than 0", "一次兑换码批量生成的个数不能大于 100": "The number of redemption codes generated in a batch cannot be greater than 100", "通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)": "Using model %s with token %s consumes %s (model rate %.2f, group rate %.2f)", - "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。": "The current group load is saturated, please try again later, or upgrade your account to improve service quality.", + "当前分组上游负载已饱和,请稍后再试": "The current group load is saturated, please try again later", "令牌名称长度必须在1-20之间": "The length of the token name must be between 1-20", "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.", "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度": "The available quota of the token has been used up and cannot be enabled. Please modify the remaining quota of the token, or set it to unlimited quota", From c58f710227d5c71c606ad78f17f6f1ff19f65606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yolo=C2=B0?= <136311867+yangfan-sys@users.noreply.github.com> Date: Sat, 12 Aug 2023 10:49:30 +0800 Subject: [PATCH 09/24] feat: improve frontend (#387) * fork * fork * chore: update style --------- Co-authored-by: JustSong --- controller/token.go | 11 +++++-- i18n/en.json | 7 +++-- web/src/components/ChannelsTable.js | 4 +-- web/src/components/OtherSetting.js | 2 +- web/src/components/RedemptionsTable.js | 28 ++++++++++++------ web/src/components/SystemSetting.js | 34 +++++++++++++++++++++- web/src/helpers/utils.js | 13 +++++++-- web/src/pages/Channel/EditChannel.js | 8 ++++- web/src/pages/Home/index.js | 9 +++--- web/src/pages/Redemption/EditRedemption.js | 8 ++++- 10 files changed, 98 insertions(+), 26 deletions(-) diff --git a/controller/token.go b/controller/token.go index 5341ea3a..b05d820a 100644 --- a/controller/token.go +++ b/controller/token.go @@ -109,10 +109,10 @@ func AddToken(c *gin.Context) { }) return } - if len(token.Name) == 0 || len(token.Name) > 20 { + if len(token.Name) == 0 || len(token.Name) > 30 { c.JSON(http.StatusOK, gin.H{ "success": false, - "message": "令牌名称长度必须在1-20之间", + "message": "令牌名称过长", }) return } @@ -171,6 +171,13 @@ func UpdateToken(c *gin.Context) { }) return } + if len(token.Name) == 0 || len(token.Name) > 30 { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "令牌名称过长", + }) + return + } cleanToken, err := model.GetTokenByIds(token.Id, userId) if err != nil { c.JSON(http.StatusOK, gin.H{ diff --git a/i18n/en.json b/i18n/en.json index f53aad4c..78df1acf 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -40,7 +40,7 @@ "一次兑换码批量生成的个数不能大于 100": "The number of redemption codes generated in a batch cannot be greater than 100", "通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)": "Using model %s with token %s consumes %s (model rate %.2f, group rate %.2f)", "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。": "The current group load is saturated, please try again later, or upgrade your account to improve service quality.", - "令牌名称长度必须在1-20之间": "The length of the token name must be between 1-20", + "令牌名称过长": "Token name is too long", "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.", "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度": "The available quota of the token has been used up and cannot be enabled. Please modify the remaining quota of the token, or set it to unlimited quota", "管理员关闭了密码登录": "The administrator has turned off password login", @@ -229,7 +229,7 @@ "已是最新版本": "Is the latest version", "检查更新": "Check for updates", "公告": "Announcement", - "在此输入新的公告内容": "Enter new announcement content here", + "在此输入新的公告内容,支持 Markdown & HTML 代码": "Enter the new announcement content here, supports Markdown & HTML code", "保存公告": "Save Announcement", "个性化设置": "Personalization Settings", "系统名称": "System Name", @@ -518,5 +518,6 @@ ",图片演示。": "related image demo.", "令牌创建成功,请在列表页面点击复制获取令牌!": "Token created successfully, please click copy on the list page to get the token!", "代理": "Proxy", - "此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com" + "此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com", + "取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?": "Canceling password login will cause all users (including administrators) who have not bound other login methods to be unable to log in via password, confirm cancel?" } diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index 0459619a..072f5b90 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -447,8 +447,8 @@ const ChannelsTable = () => { - + {/* */} { { > 复制 - + } + on='click' + flowing + hoverable > - 删除 - + + + + + + } { + return
; +}; +export default HTMLToastContent; export function isAdmin() { let user = localStorage.getItem('user'); if (!user) return false; @@ -107,8 +112,12 @@ export function showInfo(message) { toast.info(message, showInfoOptions); } -export function showNotice(message) { - toast.info(message, showNoticeOptions); +export function showNotice(message, isHTML = false) { + if (isHTML) { + toast(, showNoticeOptions); + } else { + toast.info(message, showNoticeOptions); + } } export function openPage(url) { diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index 4cfec018..0d7a4a01 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Button, Form, Header, Input, Message, Segment } from 'semantic-ui-react'; -import { useParams } from 'react-router-dom'; +import { useParams, useNavigate } from 'react-router-dom'; import { API, showError, showInfo, showSuccess, verifyJSON } from '../../helpers'; import { CHANNEL_OPTIONS } from '../../constants'; @@ -12,9 +12,14 @@ const MODEL_MAPPING_EXAMPLE = { const EditChannel = () => { const params = useParams(); + const navigate = useNavigate(); const channelId = params.id; const isEdit = channelId !== undefined; const [loading, setLoading] = useState(isEdit); + const handleCancel = () => { + navigate('/channel'); + }; + const originInputs = { name: '', type: 1, @@ -381,6 +386,7 @@ const EditChannel = () => { ) } + diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index 20d42104..c9f4d445 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -14,10 +14,11 @@ const Home = () => { const { success, message, data } = res.data; if (success) { let oldNotice = localStorage.getItem('notice'); - if (data !== oldNotice && data !== '') { - showNotice(data); - localStorage.setItem('notice', data); - } + if (data !== oldNotice && data !== '') { + const htmlNotice = marked(data); + showNotice(htmlNotice, true); + localStorage.setItem('notice', data); + } } else { showError(message); } diff --git a/web/src/pages/Redemption/EditRedemption.js b/web/src/pages/Redemption/EditRedemption.js index df614ab5..7a33f770 100644 --- a/web/src/pages/Redemption/EditRedemption.js +++ b/web/src/pages/Redemption/EditRedemption.js @@ -1,11 +1,12 @@ import React, { useEffect, useState } from 'react'; import { Button, Form, Header, Segment } from 'semantic-ui-react'; -import { useParams } from 'react-router-dom'; +import { useParams, useNavigate } from 'react-router-dom'; import { API, downloadTextAsFile, showError, showSuccess } from '../../helpers'; import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render'; const EditRedemption = () => { const params = useParams(); + const navigate = useNavigate(); const redemptionId = params.id; const isEdit = redemptionId !== undefined; const [loading, setLoading] = useState(isEdit); @@ -17,6 +18,10 @@ const EditRedemption = () => { const [inputs, setInputs] = useState(originInputs); const { name, quota, count } = inputs; + const handleCancel = () => { + navigate('/redemption'); + }; + const handleInputChange = (e, { name, value }) => { setInputs((inputs) => ({ ...inputs, [name]: value })); }; @@ -113,6 +118,7 @@ const EditRedemption = () => { } + From 476a46ad7e9c25c3d4900a9e2f94ea8717491b37 Mon Sep 17 00:00:00 2001 From: igophper <34326532+igophper@users.noreply.github.com> Date: Sat, 12 Aug 2023 11:04:53 +0800 Subject: [PATCH 10/24] fix: fix finish_reason fileld not fully compatible with OpenAI (close #372, #373) * optimize:unify finish_reason field * refactor: use a global stop finish reason --------- Co-authored-by: JustSong --- controller/relay-ali.go | 5 ++++- controller/relay-baidu.go | 4 +++- controller/relay-claude.go | 5 ++++- controller/relay-palm.go | 2 +- controller/relay-utils.go | 2 ++ controller/relay-xunfei.go | 3 +++ controller/relay-zhipu.go | 3 +-- controller/relay.go | 2 +- 8 files changed, 19 insertions(+), 7 deletions(-) diff --git a/controller/relay-ali.go b/controller/relay-ali.go index e8437c27..e94abd6a 100644 --- a/controller/relay-ali.go +++ b/controller/relay-ali.go @@ -121,7 +121,10 @@ func responseAli2OpenAI(response *AliChatResponse) *OpenAITextResponse { func streamResponseAli2OpenAI(aliResponse *AliChatResponse) *ChatCompletionsStreamResponse { var choice ChatCompletionsStreamResponseChoice choice.Delta.Content = aliResponse.Output.Text - choice.FinishReason = aliResponse.Output.FinishReason + if aliResponse.Output.FinishReason != "null" { + finishReason := aliResponse.Output.FinishReason + choice.FinishReason = &finishReason + } response := ChatCompletionsStreamResponse{ Id: aliResponse.RequestId, Object: "chat.completion.chunk", diff --git a/controller/relay-baidu.go b/controller/relay-baidu.go index 7960e8ee..664bbd11 100644 --- a/controller/relay-baidu.go +++ b/controller/relay-baidu.go @@ -120,7 +120,9 @@ func responseBaidu2OpenAI(response *BaiduChatResponse) *OpenAITextResponse { func streamResponseBaidu2OpenAI(baiduResponse *BaiduChatStreamResponse) *ChatCompletionsStreamResponse { var choice ChatCompletionsStreamResponseChoice choice.Delta.Content = baiduResponse.Result - choice.FinishReason = "stop" + if baiduResponse.IsEnd { + choice.FinishReason = &stopFinishReason + } response := ChatCompletionsStreamResponse{ Id: baiduResponse.Id, Object: "chat.completion.chunk", diff --git a/controller/relay-claude.go b/controller/relay-claude.go index 1d67fa7b..052e5605 100644 --- a/controller/relay-claude.go +++ b/controller/relay-claude.go @@ -81,7 +81,10 @@ func requestOpenAI2Claude(textRequest GeneralOpenAIRequest) *ClaudeRequest { func streamResponseClaude2OpenAI(claudeResponse *ClaudeResponse) *ChatCompletionsStreamResponse { var choice ChatCompletionsStreamResponseChoice choice.Delta.Content = claudeResponse.Completion - choice.FinishReason = stopReasonClaude2OpenAI(claudeResponse.StopReason) + finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason) + if finishReason != "null" { + choice.FinishReason = &finishReason + } var response ChatCompletionsStreamResponse response.Object = "chat.completion.chunk" response.Model = claudeResponse.Model diff --git a/controller/relay-palm.go b/controller/relay-palm.go index 74624c7f..0053c9b8 100644 --- a/controller/relay-palm.go +++ b/controller/relay-palm.go @@ -94,7 +94,7 @@ func streamResponsePaLM2OpenAI(palmResponse *PaLMChatResponse) *ChatCompletionsS if len(palmResponse.Candidates) > 0 { choice.Delta.Content = palmResponse.Candidates[0].Content } - choice.FinishReason = "stop" + choice.FinishReason = &stopFinishReason var response ChatCompletionsStreamResponse response.Object = "chat.completion.chunk" response.Model = "palm2" diff --git a/controller/relay-utils.go b/controller/relay-utils.go index 2133d8be..3695e119 100644 --- a/controller/relay-utils.go +++ b/controller/relay-utils.go @@ -6,6 +6,8 @@ import ( "one-api/common" ) +var stopFinishReason = "stop" + var tokenEncoderMap = map[string]*tiktoken.Tiktoken{} func getTokenEncoder(model string) *tiktoken.Tiktoken { diff --git a/controller/relay-xunfei.go b/controller/relay-xunfei.go index 1faf3294..48472456 100644 --- a/controller/relay-xunfei.go +++ b/controller/relay-xunfei.go @@ -138,6 +138,9 @@ func streamResponseXunfei2OpenAI(xunfeiResponse *XunfeiChatResponse) *ChatComple } var choice ChatCompletionsStreamResponseChoice choice.Delta.Content = xunfeiResponse.Payload.Choices.Text[0].Content + if xunfeiResponse.Payload.Choices.Status == 2 { + choice.FinishReason = &stopFinishReason + } response := ChatCompletionsStreamResponse{ Object: "chat.completion.chunk", Created: common.GetTimestamp(), diff --git a/controller/relay-zhipu.go b/controller/relay-zhipu.go index 20a4fa42..b125f1e7 100644 --- a/controller/relay-zhipu.go +++ b/controller/relay-zhipu.go @@ -163,7 +163,6 @@ func responseZhipu2OpenAI(response *ZhipuResponse) *OpenAITextResponse { func streamResponseZhipu2OpenAI(zhipuResponse string) *ChatCompletionsStreamResponse { var choice ChatCompletionsStreamResponseChoice choice.Delta.Content = zhipuResponse - choice.FinishReason = "" response := ChatCompletionsStreamResponse{ Object: "chat.completion.chunk", Created: common.GetTimestamp(), @@ -176,7 +175,7 @@ func streamResponseZhipu2OpenAI(zhipuResponse string) *ChatCompletionsStreamResp func streamMetaResponseZhipu2OpenAI(zhipuResponse *ZhipuStreamMetaResponse) (*ChatCompletionsStreamResponse, *Usage) { var choice ChatCompletionsStreamResponseChoice choice.Delta.Content = "" - choice.FinishReason = "stop" + choice.FinishReason = &stopFinishReason response := ChatCompletionsStreamResponse{ Id: zhipuResponse.RequestId, Object: "chat.completion.chunk", diff --git a/controller/relay.go b/controller/relay.go index 617e22b8..030b27f7 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -124,7 +124,7 @@ type ChatCompletionsStreamResponseChoice struct { Delta struct { Content string `json:"content"` } `json:"delta"` - FinishReason string `json:"finish_reason,omitempty"` + FinishReason *string `json:"finish_reason"` } type ChatCompletionsStreamResponse struct { From 7e2bca7e9cc06015a3d5c0ef8c90e195a1abd2cd Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 11:30:13 +0800 Subject: [PATCH 11/24] docs: update README --- README.en.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.en.md b/README.en.md index 92eb567f..8a041da8 100644 --- a/README.en.md +++ b/README.en.md @@ -190,7 +190,7 @@ If you encounter a blank page after deployment, refer to [#97](https://github.co > Zeabur's servers are located overseas, automatically solving network issues, and the free quota is sufficient for personal usage. 1. First, fork the code. -2. Go to [Zeabur](https://zeabur.com/), log in, and enter the console. +2. Go to [Zeabur](https://zeabur.com?referralCode=songquanpeng), log in, and enter the console. 3. Create a new project. In Service -> Add Service, select Marketplace, and choose MySQL. Note down the connection parameters (username, password, address, and port). 4. Copy the connection parameters and run ```create database `one-api` ``` to create the database. 5. Then, in Service -> Add Service, select Git (authorization is required for the first use) and choose your forked repository. diff --git a/README.md b/README.md index 8e18d6b4..02127100 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://ope > Zeabur 的服务器在国外,自动解决了网络的问题,同时免费的额度也足够个人使用。 1. 首先 fork 一份代码。 -2. 进入 [Zeabur](https://zeabur.com/),登录,进入控制台。 +2. 进入 [Zeabur](https://zeabur.com?referralCode=songquanpeng),登录,进入控制台。 3. 新建一个 Project,在 Service -> Add Service 选择 Marketplace,选择 MySQL,并记下连接参数(用户名、密码、地址、端口)。 4. 复制链接参数,运行 ```create database `one-api` ``` 创建数据库。 5. 然后在 Service -> Add Service,选择 Git(第一次使用需要先授权),选择你 fork 的仓库。 From 821c559e893ac59a4a0777ab04777b8581b63223 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 12:17:01 +0800 Subject: [PATCH 12/24] docs: update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 02127100..1f3a4a5e 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,8 @@ https://openai.justsong.cn + 上游通道 429 了。 ## 相关项目 -[FastGPT](https://github.com/labring/FastGPT): 基于 LLM 大语言模型的知识库问答系统 +* [FastGPT](https://github.com/labring/FastGPT): 基于 LLM 大语言模型的知识库问答系统 +* [ChatGPT Next Web](https://github.com/Yidadaa/ChatGPT-Next-Web): 一键拥有你自己的跨平台 ChatGPT 应用 ## 注意 From e42119b73d493f3202cb4c315d2ab3c16315ebe4 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 14:18:04 +0800 Subject: [PATCH 13/24] chore: use unknown as placeholder --- web/src/pages/Home/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index c9f4d445..63d6d77a 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -65,7 +65,7 @@ const Home = () => { 系统信息总览

名称:{statusState?.status?.system_name}

-

版本:{statusState?.status?.version}

+

版本:{statusState?.status?.version ? statusState?.status?.version : "unknown"}

源码: Date: Sat, 12 Aug 2023 16:58:29 +0800 Subject: [PATCH 14/24] fix: fix token name too long --- controller/token.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/token.go b/controller/token.go index b05d820a..8642122c 100644 --- a/controller/token.go +++ b/controller/token.go @@ -109,7 +109,7 @@ func AddToken(c *gin.Context) { }) return } - if len(token.Name) == 0 || len(token.Name) > 30 { + if len(token.Name) > 30 { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "令牌名称过长", @@ -171,7 +171,7 @@ func UpdateToken(c *gin.Context) { }) return } - if len(token.Name) == 0 || len(token.Name) > 30 { + if len(token.Name) > 30 { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "令牌名称过长", From 7bddc73b963e98a69095242f1aa6f79cfc865a72 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 18:10:15 +0800 Subject: [PATCH 15/24] perf: flush response after response handled (close #364) --- common/constants.go | 2 ++ controller/relay-text.go | 4 ++-- main.go | 3 +++ model/main.go | 3 +++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/common/constants.go b/common/constants.go index eaaca803..4b9df311 100644 --- a/common/constants.go +++ b/common/constants.go @@ -55,6 +55,8 @@ var EmailDomainWhitelist = []string{ "foxmail.com", } +var DebugEnabled = os.Getenv("DEBUG") == "true" + var LogConsumeEnabled = true var SMTPServer = "" diff --git a/controller/relay-text.go b/controller/relay-text.go index 65f03bcf..a6a276f3 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -5,13 +5,12 @@ import ( "encoding/json" "errors" "fmt" + "github.com/gin-gonic/gin" "io" "net/http" "one-api/common" "one-api/model" "strings" - - "github.com/gin-gonic/gin" ) const ( @@ -308,6 +307,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { var textResponse TextResponse defer func() { + c.Writer.Flush() if consumeQuota { quota := 0 completionRatio := 1.0 diff --git a/main.go b/main.go index d6d0c75b..f4d20373 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,9 @@ func main() { if os.Getenv("GIN_MODE") != "debug" { gin.SetMode(gin.ReleaseMode) } + if common.DebugEnabled { + common.SysLog("running in debug mode") + } // Initialize SQL Database err := model.InitDB() if err != nil { diff --git a/model/main.go b/model/main.go index ddbc69aa..fcc134d8 100644 --- a/model/main.go +++ b/model/main.go @@ -57,6 +57,9 @@ func InitDB() (err error) { } common.SysLog("database connected") if err == nil { + if common.DebugEnabled { + db = db.Debug() + } DB = db sqlDB, err := DB.DB() if err != nil { From eae9b6e60779d02132c6caf7b5155c2c98f7cf4f Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 18:25:18 +0800 Subject: [PATCH 16/24] fix: add lock when update quota (close #399) --- model/channel.go | 2 +- model/token.go | 4 ++-- model/user.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/model/channel.go b/model/channel.go index 7cc9fa9b..b0d6e644 100644 --- a/model/channel.go +++ b/model/channel.go @@ -141,7 +141,7 @@ func UpdateChannelStatusById(id int, status int) { } func UpdateChannelUsedQuota(id int, quota int) { - err := DB.Model(&Channel{}).Where("id = ?", id).Update("used_quota", gorm.Expr("used_quota + ?", quota)).Error + err := DB.Set("gorm:query_option", "FOR UPDATE").Model(&Channel{}).Where("id = ?", id).Update("used_quota", gorm.Expr("used_quota + ?", quota)).Error if err != nil { common.SysError("failed to update channel used quota: " + err.Error()) } diff --git a/model/token.go b/model/token.go index 7cd226c6..0e2395ad 100644 --- a/model/token.go +++ b/model/token.go @@ -131,7 +131,7 @@ func IncreaseTokenQuota(id int, quota int) (err error) { if quota < 0 { return errors.New("quota 不能为负数!") } - err = DB.Model(&Token{}).Where("id = ?", id).Updates( + err = DB.Set("gorm:query_option", "FOR UPDATE").Model(&Token{}).Where("id = ?", id).Updates( map[string]interface{}{ "remain_quota": gorm.Expr("remain_quota + ?", quota), "used_quota": gorm.Expr("used_quota - ?", quota), @@ -144,7 +144,7 @@ func DecreaseTokenQuota(id int, quota int) (err error) { if quota < 0 { return errors.New("quota 不能为负数!") } - err = DB.Model(&Token{}).Where("id = ?", id).Updates( + err = DB.Set("gorm:query_option", "FOR UPDATE").Model(&Token{}).Where("id = ?", id).Updates( map[string]interface{}{ "remain_quota": gorm.Expr("remain_quota - ?", quota), "used_quota": gorm.Expr("used_quota + ?", quota), diff --git a/model/user.go b/model/user.go index 7c771840..c7080450 100644 --- a/model/user.go +++ b/model/user.go @@ -275,7 +275,7 @@ func IncreaseUserQuota(id int, quota int) (err error) { if quota < 0 { return errors.New("quota 不能为负数!") } - err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota + ?", quota)).Error + err = DB.Set("gorm:query_option", "FOR UPDATE").Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota + ?", quota)).Error return err } @@ -283,7 +283,7 @@ func DecreaseUserQuota(id int, quota int) (err error) { if quota < 0 { return errors.New("quota 不能为负数!") } - err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota - ?", quota)).Error + err = DB.Set("gorm:query_option", "FOR UPDATE").Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota - ?", quota)).Error return err } @@ -293,7 +293,7 @@ func GetRootUserEmail() (email string) { } func UpdateUserUsedQuotaAndRequestCount(id int, quota int) { - err := DB.Model(&User{}).Where("id = ?", id).Updates( + err := DB.Set("gorm:query_option", "FOR UPDATE").Model(&User{}).Where("id = ?", id).Updates( map[string]interface{}{ "used_quota": gorm.Expr("used_quota + ?", quota), "request_count": gorm.Expr("request_count + ?", 1), From e0b4f96b5bce6e5b9526d0a51a5509507276306a Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 19:20:12 +0800 Subject: [PATCH 17/24] feat: support PostgreSQL now --- README.md | 6 ++++-- go.mod | 6 +++++- go.sum | 10 ++++++++++ model/main.go | 41 +++++++++++++++++++++++++---------------- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 1f3a4a5e..a53c8b9d 100644 --- a/README.md +++ b/README.md @@ -276,8 +276,10 @@ graph LR + 例子:`REDIS_CONN_STRING=redis://default:redispw@localhost:49153` 2. `SESSION_SECRET`:设置之后将使用固定的会话密钥,这样系统重新启动后已登录用户的 cookie 将依旧有效。 + 例子:`SESSION_SECRET=random_string` -3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 8.0 版本。 - + 例子:`SQL_DSN=root:123456@tcp(localhost:3306)/oneapi` +3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 或 PostgreSQL。 + + 例子: + + MySQL:`SQL_DSN=root:123456@tcp(localhost:3306)/oneapi` + + PostgreSQL:`SQL_DSN=postgres://postgres:123456@localhost:5432/oneapi` + 注意需要提前建立数据库 `oneapi`,无需手动建表,程序将自动建表。 + 如果使用本地数据库:部署命令可添加 `--network="host"` 以使得容器内的程序可以访问到宿主机上的 MySQL。 + 如果使用云数据库:如果云服务器需要验证身份,需要在连接参数中添加 `?tls=skip-verify`。 diff --git a/go.mod b/go.mod index 1d08a7d3..79b01f93 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/crypto v0.9.0 gorm.io/driver/mysql v1.4.3 gorm.io/driver/sqlite v1.4.3 - gorm.io/gorm v1.24.0 + gorm.io/gorm v1.25.0 ) require ( @@ -36,6 +36,9 @@ require ( github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -54,4 +57,5 @@ require ( golang.org/x/text v0.9.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.2 // indirect ) diff --git a/go.sum b/go.sum index b4281cb6..810e7819 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,12 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= @@ -187,9 +193,13 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k= gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74= gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/model/main.go b/model/main.go index fcc134d8..213db58c 100644 --- a/model/main.go +++ b/model/main.go @@ -2,10 +2,12 @@ package model import ( "gorm.io/driver/mysql" + "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" "one-api/common" "os" + "strings" "time" ) @@ -34,28 +36,35 @@ func createRootAccountIfNeed() error { return nil } -func CountTable(tableName string) (num int64) { - DB.Table(tableName).Count(&num) - return -} - -func InitDB() (err error) { - var db *gorm.DB +func chooseDB() (*gorm.DB, error) { if os.Getenv("SQL_DSN") != "" { + dsn := os.Getenv("SQL_DSN") + if strings.HasPrefix(dsn, "postgres://") { + // Use PostgreSQL + common.SysLog("using PostgreSQL as database") + return gorm.Open(postgres.New(postgres.Config{ + DSN: dsn, + PreferSimpleProtocol: true, // disables implicit prepared statement usage + }), &gorm.Config{ + PrepareStmt: true, // precompile SQL + }) + } // Use MySQL common.SysLog("using MySQL as database") - db, err = gorm.Open(mysql.Open(os.Getenv("SQL_DSN")), &gorm.Config{ - PrepareStmt: true, // precompile SQL - }) - } else { - // Use SQLite - common.SysLog("SQL_DSN not set, using SQLite as database") - common.UsingSQLite = true - db, err = gorm.Open(sqlite.Open(common.SQLitePath), &gorm.Config{ + return gorm.Open(mysql.Open(dsn), &gorm.Config{ PrepareStmt: true, // precompile SQL }) } - common.SysLog("database connected") + // Use SQLite + common.SysLog("SQL_DSN not set, using SQLite as database") + common.UsingSQLite = true + return gorm.Open(sqlite.Open(common.SQLitePath), &gorm.Config{ + PrepareStmt: true, // precompile SQL + }) +} + +func InitDB() (err error) { + db, err := chooseDB() if err == nil { if common.DebugEnabled { db = db.Debug() From 0e9ff8825efc7668be6a2d0c7df9960d5121c26b Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 12 Aug 2023 19:36:31 +0800 Subject: [PATCH 18/24] perf: use a goroutine to handle quota post consumption (#364) --- controller/relay-text.go | 83 +++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index a6a276f3..1bb463fa 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -305,51 +305,54 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } var textResponse TextResponse + tokenName := c.GetString("token_name") + channelId := c.GetInt("channel_id") defer func() { - c.Writer.Flush() - if consumeQuota { - quota := 0 - completionRatio := 1.0 - if strings.HasPrefix(textRequest.Model, "gpt-3.5") { - completionRatio = 1.333333 - } - if strings.HasPrefix(textRequest.Model, "gpt-4") { - completionRatio = 2 - } + // c.Writer.Flush() + go func() { + if consumeQuota { + quota := 0 + completionRatio := 1.0 + if strings.HasPrefix(textRequest.Model, "gpt-3.5") { + completionRatio = 1.333333 + } + if strings.HasPrefix(textRequest.Model, "gpt-4") { + completionRatio = 2 + } - promptTokens = textResponse.Usage.PromptTokens - completionTokens = textResponse.Usage.CompletionTokens + promptTokens = textResponse.Usage.PromptTokens + completionTokens = textResponse.Usage.CompletionTokens - quota = promptTokens + int(float64(completionTokens)*completionRatio) - quota = int(float64(quota) * ratio) - if ratio != 0 && quota <= 0 { - quota = 1 + quota = promptTokens + int(float64(completionTokens)*completionRatio) + quota = int(float64(quota) * ratio) + if ratio != 0 && quota <= 0 { + quota = 1 + } + totalTokens := promptTokens + completionTokens + if totalTokens == 0 { + // in this case, must be some error happened + // we cannot just return, because we may have to return the pre-consumed quota + quota = 0 + } + quotaDelta := quota - preConsumedQuota + err := model.PostConsumeTokenQuota(tokenId, quotaDelta) + if err != nil { + common.SysError("error consuming token remain quota: " + err.Error()) + } + err = model.CacheUpdateUserQuota(userId) + if err != nil { + common.SysError("error update user quota cache: " + err.Error()) + } + if quota != 0 { + logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) + model.RecordConsumeLog(userId, promptTokens, completionTokens, textRequest.Model, tokenName, quota, logContent) + model.UpdateUserUsedQuotaAndRequestCount(userId, quota) + + model.UpdateChannelUsedQuota(channelId, quota) + } } - totalTokens := promptTokens + completionTokens - if totalTokens == 0 { - // in this case, must be some error happened - // we cannot just return, because we may have to return the pre-consumed quota - quota = 0 - } - quotaDelta := quota - preConsumedQuota - err := model.PostConsumeTokenQuota(tokenId, quotaDelta) - if err != nil { - common.SysError("error consuming token remain quota: " + err.Error()) - } - err = model.CacheUpdateUserQuota(userId) - if err != nil { - common.SysError("error update user quota cache: " + err.Error()) - } - if quota != 0 { - tokenName := c.GetString("token_name") - logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) - model.RecordConsumeLog(userId, promptTokens, completionTokens, textRequest.Model, tokenName, quota, logContent) - model.UpdateUserUsedQuotaAndRequestCount(userId, quota) - channelId := c.GetInt("channel_id") - model.UpdateChannelUsedQuota(channelId, quota) - } - } + }() }() switch apiType { case APITypeOpenAI: From ca512f6a38809f62a060391e838e750bc224be50 Mon Sep 17 00:00:00 2001 From: igophper <34326532+igophper@users.noreply.github.com> Date: Sat, 12 Aug 2023 21:55:18 +0800 Subject: [PATCH 19/24] refactor: add set event stream headers func (#402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 封装流式传输响应头的设置 --- controller/relay-ali.go | 6 +----- controller/relay-baidu.go | 6 +----- controller/relay-claude.go | 6 +----- controller/relay-openai.go | 6 +----- controller/relay-palm.go | 6 +----- controller/relay-utils.go | 9 +++++++++ controller/relay-xunfei.go | 6 +----- controller/relay-zhipu.go | 6 +----- 8 files changed, 16 insertions(+), 35 deletions(-) diff --git a/controller/relay-ali.go b/controller/relay-ali.go index e94abd6a..014f6b84 100644 --- a/controller/relay-ali.go +++ b/controller/relay-ali.go @@ -166,11 +166,7 @@ func aliStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStat } stopChan <- true }() - c.Writer.Header().Set("Content-Type", "text/event-stream") - c.Writer.Header().Set("Cache-Control", "no-cache") - c.Writer.Header().Set("Connection", "keep-alive") - c.Writer.Header().Set("Transfer-Encoding", "chunked") - c.Writer.Header().Set("X-Accel-Buffering", "no") + setEventStreamHeaders(c) lastResponseText := "" c.Stream(func(w io.Writer) bool { select { diff --git a/controller/relay-baidu.go b/controller/relay-baidu.go index 664bbd11..118e87a6 100644 --- a/controller/relay-baidu.go +++ b/controller/relay-baidu.go @@ -191,11 +191,7 @@ func baiduStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithSt } stopChan <- true }() - c.Writer.Header().Set("Content-Type", "text/event-stream") - c.Writer.Header().Set("Cache-Control", "no-cache") - c.Writer.Header().Set("Connection", "keep-alive") - c.Writer.Header().Set("Transfer-Encoding", "chunked") - c.Writer.Header().Set("X-Accel-Buffering", "no") + setEventStreamHeaders(c) c.Stream(func(w io.Writer) bool { select { case data := <-dataChan: diff --git a/controller/relay-claude.go b/controller/relay-claude.go index 052e5605..1f4a3e7b 100644 --- a/controller/relay-claude.go +++ b/controller/relay-claude.go @@ -141,11 +141,7 @@ func claudeStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithS } stopChan <- true }() - c.Writer.Header().Set("Content-Type", "text/event-stream") - c.Writer.Header().Set("Cache-Control", "no-cache") - c.Writer.Header().Set("Connection", "keep-alive") - c.Writer.Header().Set("Transfer-Encoding", "chunked") - c.Writer.Header().Set("X-Accel-Buffering", "no") + setEventStreamHeaders(c) c.Stream(func(w io.Writer) bool { select { case data := <-dataChan: diff --git a/controller/relay-openai.go b/controller/relay-openai.go index 298dbe95..6bdfbc08 100644 --- a/controller/relay-openai.go +++ b/controller/relay-openai.go @@ -66,11 +66,7 @@ func openaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*O } stopChan <- true }() - c.Writer.Header().Set("Content-Type", "text/event-stream") - c.Writer.Header().Set("Cache-Control", "no-cache") - c.Writer.Header().Set("Connection", "keep-alive") - c.Writer.Header().Set("Transfer-Encoding", "chunked") - c.Writer.Header().Set("X-Accel-Buffering", "no") + setEventStreamHeaders(c) c.Stream(func(w io.Writer) bool { select { case data := <-dataChan: diff --git a/controller/relay-palm.go b/controller/relay-palm.go index 0053c9b8..a705b318 100644 --- a/controller/relay-palm.go +++ b/controller/relay-palm.go @@ -143,11 +143,7 @@ func palmStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithSta dataChan <- string(jsonResponse) stopChan <- true }() - c.Writer.Header().Set("Content-Type", "text/event-stream") - c.Writer.Header().Set("Cache-Control", "no-cache") - c.Writer.Header().Set("Connection", "keep-alive") - c.Writer.Header().Set("Transfer-Encoding", "chunked") - c.Writer.Header().Set("X-Accel-Buffering", "no") + setEventStreamHeaders(c) c.Stream(func(w io.Writer) bool { select { case data := <-dataChan: diff --git a/controller/relay-utils.go b/controller/relay-utils.go index 3695e119..5b3e0274 100644 --- a/controller/relay-utils.go +++ b/controller/relay-utils.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "github.com/gin-gonic/gin" "github.com/pkoukk/tiktoken-go" "one-api/common" ) @@ -106,3 +107,11 @@ func shouldDisableChannel(err *OpenAIError) bool { } return false } + +func setEventStreamHeaders(c *gin.Context) { + c.Writer.Header().Set("Content-Type", "text/event-stream") + c.Writer.Header().Set("Cache-Control", "no-cache") + c.Writer.Header().Set("Connection", "keep-alive") + c.Writer.Header().Set("Transfer-Encoding", "chunked") + c.Writer.Header().Set("X-Accel-Buffering", "no") +} diff --git a/controller/relay-xunfei.go b/controller/relay-xunfei.go index 48472456..87037e34 100644 --- a/controller/relay-xunfei.go +++ b/controller/relay-xunfei.go @@ -217,11 +217,7 @@ func xunfeiStreamHandler(c *gin.Context, textRequest GeneralOpenAIRequest, appId } stopChan <- true }() - c.Writer.Header().Set("Content-Type", "text/event-stream") - c.Writer.Header().Set("Cache-Control", "no-cache") - c.Writer.Header().Set("Connection", "keep-alive") - c.Writer.Header().Set("Transfer-Encoding", "chunked") - c.Writer.Header().Set("X-Accel-Buffering", "no") + setEventStreamHeaders(c) c.Stream(func(w io.Writer) bool { select { case xunfeiResponse := <-dataChan: diff --git a/controller/relay-zhipu.go b/controller/relay-zhipu.go index b125f1e7..7a4a582d 100644 --- a/controller/relay-zhipu.go +++ b/controller/relay-zhipu.go @@ -224,11 +224,7 @@ func zhipuStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithSt } stopChan <- true }() - c.Writer.Header().Set("Content-Type", "text/event-stream") - c.Writer.Header().Set("Cache-Control", "no-cache") - c.Writer.Header().Set("Connection", "keep-alive") - c.Writer.Header().Set("Transfer-Encoding", "chunked") - c.Writer.Header().Set("X-Accel-Buffering", "no") + setEventStreamHeaders(c) c.Stream(func(w io.Writer) bool { select { case data := <-dataChan: From af20063a8ddd4f4103f8d58f7ae2f1ee9568defd Mon Sep 17 00:00:00 2001 From: igophper <34326532+igophper@users.noreply.github.com> Date: Sat, 12 Aug 2023 23:41:44 +0800 Subject: [PATCH 20/24] feat: able to refresh baidu access token automatically (#400, close #401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:baidu channel support apiKey and secretKey 添加百度文心渠道时支持填写secretKey|apiKey或者accessToken,支持自动刷新accessToken * fix * fix * fix * chore: update implementation --------- Co-authored-by: JustSong Co-authored-by: JustSong <39998050+songquanpeng@users.noreply.github.com> --- controller/relay-baidu.go | 79 +++++++++++++++++++++++++--- controller/relay-text.go | 11 +++- i18n/en.json | 3 +- web/src/pages/Channel/EditChannel.js | 2 +- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/controller/relay-baidu.go b/controller/relay-baidu.go index 118e87a6..d66391bc 100644 --- a/controller/relay-baidu.go +++ b/controller/relay-baidu.go @@ -3,22 +3,22 @@ package controller import ( "bufio" "encoding/json" + "errors" + "fmt" "github.com/gin-gonic/gin" "io" "net/http" "one-api/common" "strings" + "sync" + "time" ) // https://cloud.baidu.com/doc/WENXINWORKSHOP/s/flfmc9do2 type BaiduTokenResponse struct { - RefreshToken string `json:"refresh_token"` - ExpiresIn int `json:"expires_in"` - SessionKey string `json:"session_key"` - AccessToken string `json:"access_token"` - Scope string `json:"scope"` - SessionSecret string `json:"session_secret"` + ExpiresIn int `json:"expires_in"` + AccessToken string `json:"access_token"` } type BaiduMessage struct { @@ -73,6 +73,16 @@ type BaiduEmbeddingResponse struct { BaiduError } +type BaiduAccessToken struct { + AccessToken string `json:"access_token"` + Error string `json:"error,omitempty"` + ErrorDescription string `json:"error_description,omitempty"` + ExpiresIn int64 `json:"expires_in,omitempty"` + ExpiresAt time.Time `json:"-"` +} + +var baiduTokenStore sync.Map + func requestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduChatRequest { messages := make([]BaiduMessage, 0, len(request.Messages)) for _, message := range request.Messages { @@ -295,3 +305,60 @@ func baiduEmbeddingHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWit _, err = c.Writer.Write(jsonResponse) return nil, &fullTextResponse.Usage } + +func getBaiduAccessToken(apiKey string) (string, error) { + if val, ok := baiduTokenStore.Load(apiKey); ok { + var accessToken BaiduAccessToken + if accessToken, ok = val.(BaiduAccessToken); ok { + // soon this will expire + if time.Now().Add(time.Hour).After(accessToken.ExpiresAt) { + go func() { + _, _ = getBaiduAccessTokenHelper(apiKey) + }() + } + return accessToken.AccessToken, nil + } + } + accessToken, err := getBaiduAccessTokenHelper(apiKey) + if err != nil { + return "", err + } + if accessToken == nil { + return "", errors.New("getBaiduAccessToken return a nil token") + } + return (*accessToken).AccessToken, nil +} + +func getBaiduAccessTokenHelper(apiKey string) (*BaiduAccessToken, error) { + parts := strings.Split(apiKey, "|") + if len(parts) != 2 { + return nil, errors.New("invalid baidu apikey") + } + req, err := http.NewRequest("POST", fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s", + parts[0], parts[1]), nil) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + res, err := impatientHTTPClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var accessToken BaiduAccessToken + err = json.NewDecoder(res.Body).Decode(&accessToken) + if err != nil { + return nil, err + } + if accessToken.Error != "" { + return nil, errors.New(accessToken.Error + ": " + accessToken.ErrorDescription) + } + if accessToken.AccessToken == "" { + return nil, errors.New("getBaiduAccessTokenHelper get empty access token") + } + accessToken.ExpiresAt = time.Now().Add(time.Duration(accessToken.ExpiresIn) * time.Second) + baiduTokenStore.Store(apiKey, accessToken) + return &accessToken, nil +} diff --git a/controller/relay-text.go b/controller/relay-text.go index 1bb463fa..e8dab514 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -11,6 +11,7 @@ import ( "one-api/common" "one-api/model" "strings" + "time" ) const ( @@ -24,9 +25,13 @@ const ( ) var httpClient *http.Client +var impatientHTTPClient *http.Client func init() { httpClient = &http.Client{} + impatientHTTPClient = &http.Client{ + Timeout: 5 * time.Second, + } } func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { @@ -145,7 +150,11 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } apiKey := c.Request.Header.Get("Authorization") apiKey = strings.TrimPrefix(apiKey, "Bearer ") - fullRequestURL += "?access_token=" + apiKey // TODO: access token expire in 30 days + var err error + if apiKey, err = getBaiduAccessToken(apiKey); err != nil { + return errorWrapper(err, "invalid_baidu_config", http.StatusInternalServerError) + } + fullRequestURL += "?access_token=" + apiKey case APITypePaLM: fullRequestURL = "https://generativelanguage.googleapis.com/v1beta2/models/chat-bison-001:generateMessage" if baseURL != "" { diff --git a/i18n/en.json b/i18n/en.json index 67ce8a56..a9402419 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -519,5 +519,6 @@ "令牌创建成功,请在列表页面点击复制获取令牌!": "Token created successfully, please click copy on the list page to get the token!", "代理": "Proxy", "此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com", - "取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?": "Canceling password login will cause all users (including administrators) who have not bound other login methods to be unable to log in via password, confirm cancel?" + "取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?": "Canceling password login will cause all users (including administrators) who have not bound other login methods to be unable to log in via password, confirm cancel?", + "按照如下格式输入:": "Enter in the following format:" } diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index 0d7a4a01..b5fb524e 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -355,7 +355,7 @@ const EditChannel = () => { label='密钥' name='key' required - placeholder={inputs.type === 15 ? '请输入 access token,当前版本暂不支持自动刷新,请每 30 天更新一次' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')} + placeholder={inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')} onChange={handleInputChange} value={inputs.key} autoComplete='new-password' From 4ef5e2020ceecc534073d7e045197f7be05233ce Mon Sep 17 00:00:00 2001 From: igophper <34326532+igophper@users.noreply.github.com> Date: Sun, 13 Aug 2023 00:45:04 +0800 Subject: [PATCH 21/24] fix: fix baidu's embedding api (#398) * fix:judge baidu embeddings input type * chore: add back update all channels balance * chore: update default configuration for sql connection --------- Co-authored-by: JustSong --- README.md | 4 ++-- controller/relay-baidu.go | 8 ++++++-- model/main.go | 4 ++-- web/src/components/ChannelsTable.js | 6 +++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a53c8b9d..29f45e95 100644 --- a/README.md +++ b/README.md @@ -284,8 +284,8 @@ graph LR + 如果使用本地数据库:部署命令可添加 `--network="host"` 以使得容器内的程序可以访问到宿主机上的 MySQL。 + 如果使用云数据库:如果云服务器需要验证身份,需要在连接参数中添加 `?tls=skip-verify`。 + 请根据你的数据库配置修改下列参数(或者保持默认值): - + `SQL_MAX_IDLE_CONNS`:最大空闲连接数,默认为 `10`。 - + `SQL_MAX_OPEN_CONNS`:最大打开连接数,默认为 `100`。 + + `SQL_MAX_IDLE_CONNS`:最大空闲连接数,默认为 `100`。 + + `SQL_MAX_OPEN_CONNS`:最大打开连接数,默认为 `1000`。 + 如果报错 `Error 1040: Too many connections`,请适当减小该值。 + `SQL_CONN_MAX_LIFETIME`:连接的最大生命周期,默认为 `60`,单位分钟。 4. `FRONTEND_BASE_URL`:设置之后将重定向页面请求到指定的地址,仅限从服务器设置。 diff --git a/controller/relay-baidu.go b/controller/relay-baidu.go index d66391bc..78b4c226 100644 --- a/controller/relay-baidu.go +++ b/controller/relay-baidu.go @@ -150,8 +150,12 @@ func embeddingRequestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduEmbeddingR switch request.Input.(type) { case string: baiduEmbeddingRequest.Input = []string{request.Input.(string)} - case []string: - baiduEmbeddingRequest.Input = request.Input.([]string) + case []any: + for _, item := range request.Input.([]any) { + if str, isStr := item.(string); isStr { + baiduEmbeddingRequest.Input = append(baiduEmbeddingRequest.Input, str) + } + } } return &baiduEmbeddingRequest } diff --git a/model/main.go b/model/main.go index 213db58c..d422c4e0 100644 --- a/model/main.go +++ b/model/main.go @@ -74,8 +74,8 @@ func InitDB() (err error) { if err != nil { return err } - sqlDB.SetMaxIdleConns(common.GetOrDefault("SQL_MAX_IDLE_CONNS", 10)) - sqlDB.SetMaxOpenConns(common.GetOrDefault("SQL_MAX_OPEN_CONNS", 100)) + sqlDB.SetMaxIdleConns(common.GetOrDefault("SQL_MAX_IDLE_CONNS", 100)) + sqlDB.SetMaxOpenConns(common.GetOrDefault("SQL_MAX_OPEN_CONNS", 1000)) sqlDB.SetConnMaxLifetime(time.Second * time.Duration(common.GetOrDefault("SQL_MAX_LIFETIME", 60))) if !common.IsMasterNode { diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index 072f5b90..5eb39783 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -368,7 +368,7 @@ const ChannelsTable = () => { }} style={{ cursor: 'pointer' }}> {renderBalance(channel.type, channel.balance)} } - content="点击更新" + content='点击更新' basic /> @@ -447,8 +447,8 @@ const ChannelsTable = () => { - {/* */} + Date: Sun, 13 Aug 2023 00:50:26 +0800 Subject: [PATCH 22/24] chore: update variable name --- controller/relay-baidu.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/relay-baidu.go b/controller/relay-baidu.go index 78b4c226..ad20d6d6 100644 --- a/controller/relay-baidu.go +++ b/controller/relay-baidu.go @@ -152,7 +152,7 @@ func embeddingRequestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduEmbeddingR baiduEmbeddingRequest.Input = []string{request.Input.(string)} case []any: for _, item := range request.Input.([]any) { - if str, isStr := item.(string); isStr { + if str, ok := item.(string); ok { baiduEmbeddingRequest.Input = append(baiduEmbeddingRequest.Input, str) } } From cac61b9f66b04d70c5a9f3b90de1641fb213336a Mon Sep 17 00:00:00 2001 From: JustSong Date: Sun, 13 Aug 2023 00:51:48 +0800 Subject: [PATCH 23/24] Revert "fix: add lock when update quota (close #399)" This reverts commit eae9b6e60779d02132c6caf7b5155c2c98f7cf4f. --- model/channel.go | 2 +- model/token.go | 4 ++-- model/user.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/model/channel.go b/model/channel.go index b0d6e644..7cc9fa9b 100644 --- a/model/channel.go +++ b/model/channel.go @@ -141,7 +141,7 @@ func UpdateChannelStatusById(id int, status int) { } func UpdateChannelUsedQuota(id int, quota int) { - err := DB.Set("gorm:query_option", "FOR UPDATE").Model(&Channel{}).Where("id = ?", id).Update("used_quota", gorm.Expr("used_quota + ?", quota)).Error + err := DB.Model(&Channel{}).Where("id = ?", id).Update("used_quota", gorm.Expr("used_quota + ?", quota)).Error if err != nil { common.SysError("failed to update channel used quota: " + err.Error()) } diff --git a/model/token.go b/model/token.go index 0e2395ad..7cd226c6 100644 --- a/model/token.go +++ b/model/token.go @@ -131,7 +131,7 @@ func IncreaseTokenQuota(id int, quota int) (err error) { if quota < 0 { return errors.New("quota 不能为负数!") } - err = DB.Set("gorm:query_option", "FOR UPDATE").Model(&Token{}).Where("id = ?", id).Updates( + err = DB.Model(&Token{}).Where("id = ?", id).Updates( map[string]interface{}{ "remain_quota": gorm.Expr("remain_quota + ?", quota), "used_quota": gorm.Expr("used_quota - ?", quota), @@ -144,7 +144,7 @@ func DecreaseTokenQuota(id int, quota int) (err error) { if quota < 0 { return errors.New("quota 不能为负数!") } - err = DB.Set("gorm:query_option", "FOR UPDATE").Model(&Token{}).Where("id = ?", id).Updates( + err = DB.Model(&Token{}).Where("id = ?", id).Updates( map[string]interface{}{ "remain_quota": gorm.Expr("remain_quota - ?", quota), "used_quota": gorm.Expr("used_quota + ?", quota), diff --git a/model/user.go b/model/user.go index c7080450..7c771840 100644 --- a/model/user.go +++ b/model/user.go @@ -275,7 +275,7 @@ func IncreaseUserQuota(id int, quota int) (err error) { if quota < 0 { return errors.New("quota 不能为负数!") } - err = DB.Set("gorm:query_option", "FOR UPDATE").Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota + ?", quota)).Error + err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota + ?", quota)).Error return err } @@ -283,7 +283,7 @@ func DecreaseUserQuota(id int, quota int) (err error) { if quota < 0 { return errors.New("quota 不能为负数!") } - err = DB.Set("gorm:query_option", "FOR UPDATE").Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota - ?", quota)).Error + err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota - ?", quota)).Error return err } @@ -293,7 +293,7 @@ func GetRootUserEmail() (email string) { } func UpdateUserUsedQuotaAndRequestCount(id int, quota int) { - err := DB.Set("gorm:query_option", "FOR UPDATE").Model(&User{}).Where("id = ?", id).Updates( + err := DB.Model(&User{}).Where("id = ?", id).Updates( map[string]interface{}{ "used_quota": gorm.Expr("used_quota + ?", quota), "request_count": gorm.Expr("request_count + ?", 1), From da1d81998fd5fe7389176f52c8b8c0f93b20f7d6 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sun, 13 Aug 2023 01:16:27 +0800 Subject: [PATCH 24/24] docs: update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29f45e95..e2979961 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,7 @@ graph LR 3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 或 PostgreSQL。 + 例子: + MySQL:`SQL_DSN=root:123456@tcp(localhost:3306)/oneapi` - + PostgreSQL:`SQL_DSN=postgres://postgres:123456@localhost:5432/oneapi` + + PostgreSQL:`SQL_DSN=postgres://postgres:123456@localhost:5432/oneapi`(适配中,欢迎反馈) + 注意需要提前建立数据库 `oneapi`,无需手动建表,程序将自动建表。 + 如果使用本地数据库:部署命令可添加 `--network="host"` 以使得容器内的程序可以访问到宿主机上的 MySQL。 + 如果使用云数据库:如果云服务器需要验证身份,需要在连接参数中添加 `?tls=skip-verify`。