diff --git a/README.md b/README.md index 821f0a41..8eeb0673 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,10 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
- 程序下载 - · 部署教程 · + 使用方法 + · 意见反馈 · 截图展示 @@ -158,11 +158,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。 diff --git a/controller/relay.go b/controller/relay.go index 228fb71c..4db55b75 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -30,27 +30,27 @@ const ( // https://platform.openai.com/docs/api-reference/chat type GeneralOpenAIRequest struct { - Model string `json:"model"` - Messages []Message `json:"messages"` - Prompt string `json:"prompt"` - Stream bool `json:"stream"` + Model string `json:"model"` + Messages []Message `json:"messages"` + Prompt any `json:"prompt"` + Stream bool `json:"stream"` //MaxTokens int `json:"max_tokens"` - Temperature float64 `json:"temperature"` - TopP float64 `json:"top_p"` - N int `json:"n"` - Input any `json:"input"` + Temperature float64 `json:"temperature"` + TopP float64 `json:"top_p"` + N int `json:"n"` + Input any `json:"input"` } type ChatRequest struct { - Model string `json:"model"` - Messages []Message `json:"messages"` + Model string `json:"model"` + Messages []Message `json:"messages"` //MaxTokens int `json:"max_tokens"` } type TextRequest struct { - Model string `json:"model"` - Messages []Message `json:"messages"` - Prompt string `json:"prompt"` + Model string `json:"model"` + Messages []Message `json:"messages"` + Prompt string `json:"prompt"` //MaxTokens int `json:"max_tokens"` //Stream bool `json:"stream"` } @@ -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) } @@ -260,7 +260,10 @@ 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)) + 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()) + } +} 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'); }} > - 剩余额度 + 统计信息