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