From 630156dc0a937396e81a3be92ae64a73f235240e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=9F=8E=E9=93=AD?= Date: Fri, 16 Jun 2023 14:20:25 +0800 Subject: [PATCH 1/6] fix: the prompt field can be array type now (close #166, #167) * fix: the prompt field can be array type now (close #166) * fix: fix prompt type --------- Co-authored-by: JustSong --- controller/relay.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/relay.go b/controller/relay.go index aeb36da7..3462409f 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -32,7 +32,7 @@ const ( type GeneralOpenAIRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` - Prompt string `json:"prompt"` + Prompt any `json:"prompt"` Stream bool `json:"stream"` MaxTokens int `json:"max_tokens"` Temperature float64 `json:"temperature"` @@ -188,7 +188,7 @@ func relayHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { case RelayModeChatCompletions: promptTokens = countTokenMessages(textRequest.Messages, textRequest.Model) case RelayModeCompletions: - promptTokens = countTokenText(textRequest.Prompt, textRequest.Model) + promptTokens = countTokenInput(textRequest.Prompt, textRequest.Model) case RelayModeModeration: promptTokens = countTokenInput(textRequest.Input, textRequest.Model) } From 58fb18aaceccfdcbaf7aa9d247958a382f4c6f47 Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 16 Jun 2023 14:24:16 +0800 Subject: [PATCH 2/6] fix: do not record completion ratio anymore --- controller/relay.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/relay.go b/controller/relay.go index 3462409f..2033476f 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -260,7 +260,7 @@ func relayHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { common.SysError("Error consuming token remain quota: " + err.Error()) } userId := c.GetInt("id") - model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("使用模型 %s 消耗 %d 点额度(模型倍率 %.2f,分组倍率 %.2f,补全倍率 %.2f)", textRequest.Model, quota, modelRatio, groupRatio, completionRatio)) + model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("使用模型 %s 消耗 %d 点额度(模型倍率 %.2f,分组倍率 %.2f)", textRequest.Model, quota, modelRatio, groupRatio)) } }() From 760183a97026a9b40aa4288da702c016bb554e9d Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 16 Jun 2023 15:20:06 +0800 Subject: [PATCH 3/6] feat: record used quota & request count (close #102, #165) --- controller/relay.go | 1 + model/user.go | 14 ++++++++++++++ web/src/components/UsersTable.js | 10 +++++++--- web/src/helpers/render.js | 30 +++++++++++++++++++++--------- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/controller/relay.go b/controller/relay.go index 2033476f..bb519258 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -261,6 +261,7 @@ func relayHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } userId := c.GetInt("id") model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("使用模型 %s 消耗 %d 点额度(模型倍率 %.2f,分组倍率 %.2f)", textRequest.Model, quota, modelRatio, groupRatio)) + model.UpdateUserUsedQuotaAndRequestCount(userId, quota) } }() diff --git a/model/user.go b/model/user.go index 60278a07..a8fb7842 100644 --- a/model/user.go +++ b/model/user.go @@ -23,6 +23,8 @@ type User struct { VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database! AccessToken string `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management Quota int `json:"quota" gorm:"type:int;default:0"` + UsedQuota int `json:"used_quota" gorm:"type:int;default:0;column:used_quota"` // used quota + RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number Group string `json:"group" gorm:"type:varchar(32);default:'default'"` } @@ -262,3 +264,15 @@ func GetRootUserEmail() (email string) { DB.Model(&User{}).Where("role = ?", common.RoleRootUser).Select("email").Find(&email) return email } + +func UpdateUserUsedQuotaAndRequestCount(id int, quota int) { + 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), + }, + ).Error + if err != nil { + common.SysError("Failed to update user used quota and request count: " + err.Error()) + } +} diff --git a/web/src/components/UsersTable.js b/web/src/components/UsersTable.js index f2e8521e..d64c69af 100644 --- a/web/src/components/UsersTable.js +++ b/web/src/components/UsersTable.js @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import { API, showError, showSuccess } from '../helpers'; import { ITEMS_PER_PAGE } from '../constants'; -import { renderGroup, renderText } from '../helpers/render'; +import { renderGroup, renderNumber, renderText } from '../helpers/render'; function renderRole(role) { switch (role) { @@ -197,7 +197,7 @@ const UsersTable = () => { sortUser('quota'); }} > - 剩余额度 + 统计信息 { {renderGroup(user.group)} {user.email ? renderText(user.email, 30) : '无'} - {user.quota} + + {renderNumber(user.quota)}} /> + {renderNumber(user.used_quota)}} /> + {renderNumber(user.request_count)}} /> + {renderRole(user.role)} {renderStatus(user.status)} diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index c09a79ad..11096194 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -8,19 +8,31 @@ export function renderText(text, limit) { } export function renderGroup(group) { - if (group === "") { - return + if (group === '') { + return ; } - let groups = group.split(","); + let groups = group.split(','); groups.sort(); return <> {groups.map((group) => { - if (group === "vip" || group === "pro") { - return - } else if (group === "svip" || group === "premium") { - return + if (group === 'vip' || group === 'pro') { + return ; + } else if (group === 'svip' || group === 'premium') { + return ; } - return + return ; })} - + ; +} + +export function renderNumber(num) { + if (num >= 1000000000) { + return (num / 1000000000).toFixed(1) + 'B'; + } else if (num >= 1000000) { + return (num / 1000000).toFixed(1) + 'M'; + } else if (num >= 10000) { + return (num / 1000).toFixed(1) + 'k'; + } else { + return num; + } } \ No newline at end of file From 0cdab80a6e05441d265646d297f22d22da4a4aeb Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 16 Jun 2023 16:02:00 +0800 Subject: [PATCH 4/6] feat: record channel's used quota (close #137) --- controller/relay.go | 2 ++ model/channel.go | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/controller/relay.go b/controller/relay.go index bb519258..8e7073f7 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -262,6 +262,8 @@ func relayHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { userId := c.GetInt("id") model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("使用模型 %s 消耗 %d 点额度(模型倍率 %.2f,分组倍率 %.2f)", textRequest.Model, quota, modelRatio, groupRatio)) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) + channelId := c.GetInt("channel_id") + model.UpdateChannelUsedQuota(channelId, quota) } }() diff --git a/model/channel.go b/model/channel.go index fdc89eb9..e53efc20 100644 --- a/model/channel.go +++ b/model/channel.go @@ -1,6 +1,7 @@ package model import ( + "gorm.io/gorm" "one-api/common" ) @@ -20,6 +21,7 @@ type Channel struct { BalanceUpdatedTime int64 `json:"balance_updated_time" gorm:"bigint"` Models string `json:"models"` Group string `json:"group" gorm:"type:varchar(32);default:'default'"` + UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"` } func GetAllChannels(startIdx int, num int, selectAll bool) ([]*Channel, error) { @@ -136,3 +138,10 @@ func UpdateChannelStatusById(id int, status int) { common.SysError("failed to update channel status: " + err.Error()) } } + +func UpdateChannelUsedQuota(id int, quota int) { + 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()) + } +} From 549e944b95a1ac0aba088d8fa5d8dedbe4c3b8cc Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 16 Jun 2023 16:40:07 +0800 Subject: [PATCH 5/6] docs: update README --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 821f0a41..1c055b88 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用 · 部署教程 · + 使用方法 + · 意见反馈 · 截图展示 @@ -158,11 +160,24 @@ sudo service nginx restart 等到系统启动后,使用 `root` 用户登录系统并做进一步的配置。 -## 使用方式 -在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增一个访问令牌。 +## 使用方法 +在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增访问令牌。 之后就可以使用你的令牌访问 One API 了,使用方式与 [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) 一致。 +你需要在各种用到 OpenAI API 的地方设置 API Base 为你的 One API 的部署地址,例如:`https://openai.justsong.cn`,API Key 则为你在 One API 中生成的令牌。 + +注意,具体的 API Base 的格式取决于你所使用的客户端。 + +```mermaid +graph LR + A(用户) + A --->|请求| B(One API) + B -->|中继请求| C(OpenAI) + B -->|中继请求| D(Azure) + B -->|中继请求| E(其他下游渠道) +``` + 可以通过在令牌后面添加渠道 ID 的方式指定使用哪一个渠道处理本次请求,例如:`Authorization: Bearer ONE_API_KEY-CHANNEL_ID`。 注意,需要是管理员用户创建的令牌才能指定渠道 ID。 From 57b213a0359bb2bd29755219e3d8241b03f7ffd2 Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 16 Jun 2023 16:40:54 +0800 Subject: [PATCH 6/6] docs: update README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 1c055b88..8eeb0673 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,6 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用

- 程序下载 - · 部署教程 · 使用方法