Skip to content

Commit

Permalink
fix: 5xx error when open index page before login (#440)
Browse files Browse the repository at this point in the history
* feat: add usage limit

新增了聊天数量限制功能

* feat: add usage limit fix readme & code comments

回滚了中英文README,更新了新增的代码注释

* feat: add usage limit refine comments

新增了聊天数量限制功能,完善了代码注释和新增数据库注释

* feat: add usage limit refine comments

新增了聊天数量限制功能,完善代码和数据库注释

* fix: issue #435 #436 #438

add usage limit switch #435
fix error when loading page #436
maybe fixed #438

* fix: avoid amount minus in null case

MongoServerError Cannot apply inc to a value of non-numeric type

---------

Signed-off-by: ccjaread <[email protected]>
  • Loading branch information
ccjaread authored Mar 2, 2024
1 parent 804897c commit 745e5a2
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 27 deletions.
6 changes: 2 additions & 4 deletions service/src/chatgpt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { getCacheApiKeys, getCacheConfig, getOriginConfig } from '../storage/con
import { sendResponse } from '../utils'
import { hasAnyRole, isNotEmptyString } from '../utils/is'
import type { ChatContext, ChatGPTUnofficialProxyAPIOptions, JWT, ModelConfig } from '../types'
import { getChatByMessageId, updateAmountMinusOne, updateRoomAccountId } from '../storage/mongo'
import { getChatByMessageId, updateRoomAccountId } from '../storage/mongo'
import type { RequestOptions } from './types'

const { HttpsProxyAgent } = httpsProxyAgent
Expand Down Expand Up @@ -119,7 +119,7 @@ async function chatReplyProcess(options: RequestOptions) {
const maxContextCount = options.user.advanced.maxContextCount ?? 20
const messageId = options.messageId
if (key == null || key === undefined)
throw new Error('没有可用的配置。请再试一次 | No available configuration. Please try again.')
throw new Error('没有对应的apikeys配置。请再试一次 | No available apikeys configuration. Please try again.')

if (key.keyModel === 'ChatGPTUnofficialProxyAPI') {
if (!options.room.accountId)
Expand Down Expand Up @@ -159,8 +159,6 @@ async function chatReplyProcess(options: RequestOptions) {
process?.(partialResponse)
},
})
// update personal useAmount
await updateAmountMinusOne(userId)
return sendResponse({ type: 'Success', data: response })
}
catch (error: any) {
Expand Down
48 changes: 38 additions & 10 deletions service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
insertChat,
insertChatUsage,
renameChatRoom,
updateAmountMinusOne,
updateApiKeyStatus,
updateChat,
updateConfig,
Expand Down Expand Up @@ -380,14 +381,19 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
let lastResponse
let result
let message: ChatInfo
let user = await getUserById(userId)
try {
const config = await getCacheConfig()
const userId = req.headers.userId.toString()
const user = await getUserById(userId)
// 在调用前判断对话额度是否够用
const useAmount = user.useAmount ?? 0
// report if useamount is 0
if (Number(useAmount) <= 0) {
const useAmount = user ? (user.useAmount ?? 0) : 0

// if use the fixed fakeuserid(some probability of duplicated with real ones), redefine user which is send to chatReplyProcess
if (userId === '6406d8c50aedd633885fa16f')
user = { _id: userId, roles: [UserRole.User], useAmount: 999, advanced: { maxContextCount: 999 }, limit_switch: false } as UserInfo

// report if useamount is 0, 6406d8c50aedd633885fa16f is the fakeuserid in nologin situation
if (userId !== '6406d8c50aedd633885fa16f' && Number(useAmount) <= 0 && user.limit_switch) {
res.send({ status: 'Fail', message: '提问次数用完啦 | Question limit reached', data: null })
return
}
Expand Down Expand Up @@ -488,6 +494,10 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
result.data.id,
result.data.detail?.usage as UsageResponse)
}
// update personal useAmount moved here
// if not fakeuserid, and has valid user info and valid useAmount set by admin nut null and limit is enabled
if (userId !== '6406d8c50aedd633885fa16f' && user && user.useAmount && user.limit_switch)
await updateAmountMinusOne(userId)
}
catch (error) {
globalThis.console.error(error)
Expand Down Expand Up @@ -662,6 +672,21 @@ router.post('/session', async (req, res) => {
value: c.key,
})
})

res.send({
status: 'Success',
message: '',
data: {
auth: hasAuth,
allowRegister,
model: config.apiModel,
title: config.siteConfig.siteTitle,
chatModels,
allChatModels: chatModelOptions,
userInfo,
},
})
return
}

res.send({
Expand All @@ -672,7 +697,7 @@ router.post('/session', async (req, res) => {
allowRegister,
model: config.apiModel,
title: config.siteConfig.siteTitle,
chatModels,
chatModels: chatModelOptions, // if userId is null which means in nologin mode, open all model options, otherwise user can only choose gpt-3.5-turbo
allChatModels: chatModelOptions,
userInfo,
},
Expand Down Expand Up @@ -805,7 +830,10 @@ router.get('/user-getamtinfo', auth, async (req, res) => {
try {
const userId = req.headers.userId as string
const user = await getUserById(userId)
res.send({ status: 'Success', message: null, data: user.useAmount })
if (user.limit_switch)
res.send({ status: 'Success', message: null, data: user.useAmount })
else
res.send({ status: 'Success', message: null, data: 99999 })
}
catch (error) {
console.error(error)
Expand Down Expand Up @@ -881,16 +909,16 @@ router.post('/user-status', rootAuth, async (req, res) => {
}
})

// 函数中加入useAmount
// 函数中加入useAmount limit_switch
router.post('/user-edit', rootAuth, async (req, res) => {
try {
const { userId, email, password, roles, remark, useAmount } = req.body as { userId?: string; email: string; password: string; roles: UserRole[]; remark?: string; useAmount?: number }
const { userId, email, password, roles, remark, useAmount, limit_switch } = req.body as { userId?: string; email: string; password: string; roles: UserRole[]; remark?: string; useAmount?: number; limit_switch?: boolean }
if (userId) {
await updateUser(userId, roles, password, remark, Number(useAmount))
await updateUser(userId, roles, password, remark, Number(useAmount), limit_switch)
}
else {
const newPassword = md5(password)
const user = await createUser(email, newPassword, roles, remark, Number(useAmount))
const user = await createUser(email, newPassword, roles, remark, Number(useAmount), limit_switch)
await updateUserStatus(user._id.toString(), Status.Normal)
}
res.send({ status: 'Success', message: '更新成功 | Update successfully' })
Expand Down
3 changes: 3 additions & 0 deletions service/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ async function auth(req, res, next) {
async function getUserId(req: Request): Promise<string | undefined> {
let token: string
try {
// no Authorization info is received withput login
if (!(req.header('Authorization') as string))
return null // '6406d8c50aedd633885fa16f'
token = req.header('Authorization').replace('Bearer ', '')
const config = await getCacheConfig()
const info = jwt.verify(token, config.siteConfig.loginSalt.trim()) as AuthJwtPayload
Expand Down
2 changes: 2 additions & 0 deletions service/src/storage/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class UserInfo {
secretKey?: string // 2fa
advanced?: AdvancedConfig
useAmount?: number // chat usage amount
limit_switch?: boolean // chat amount limit switch
constructor(email: string, password: string) {
this.name = email
this.email = email
Expand All @@ -64,6 +65,7 @@ export class UserInfo {
this.roles = [UserRole.User]
this.remark = null
this.useAmount = null
this.limit_switch = true
}
}

Expand Down
14 changes: 8 additions & 6 deletions service/src/storage/mongo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,17 @@ export async function deleteChat(roomId: number, uuid: number, inversion: boolea
}
await chatCol.updateOne(query, update)
}
// createUser、updateUserInfo中加入useAmount
export async function createUser(email: string, password: string, roles?: UserRole[], remark?: string, useAmount?: number): Promise<UserInfo> {

// createUser、updateUserInfo中加入useAmount limit_switch
export async function createUser(email: string, password: string, roles?: UserRole[], remark?: string, useAmount?: number, limit_switch?: boolean): Promise<UserInfo> {
email = email.toLowerCase()
const userInfo = new UserInfo(email, password)
if (roles && roles.includes(UserRole.Admin))
userInfo.status = Status.Normal
userInfo.roles = roles
userInfo.remark = remark
userInfo.useAmount = useAmount
userInfo.limit_switch = limit_switch
await userCol.insertOne(userInfo)
return userInfo
}
Expand Down Expand Up @@ -355,16 +357,16 @@ export async function updateUserStatus(userId: string, status: Status) {
await userCol.updateOne({ _id: new ObjectId(userId) }, { $set: { status, verifyTime: new Date().toLocaleString() } })
}

// 增加了useAmount信息
export async function updateUser(userId: string, roles: UserRole[], password: string, remark?: string, useAmount?: number) {
// 增加了useAmount信息 and limit_switch
export async function updateUser(userId: string, roles: UserRole[], password: string, remark?: string, useAmount?: number, limit_switch?: boolean) {
const user = await getUserById(userId)
const query = { _id: new ObjectId(userId) }
if (user.password !== password && user.password) {
const newPassword = md5(password)
await userCol.updateOne(query, { $set: { roles, verifyTime: new Date().toLocaleString(), password: newPassword, remark, useAmount } })
await userCol.updateOne(query, { $set: { roles, verifyTime: new Date().toLocaleString(), password: newPassword, remark, useAmount, limit_switch } })
}
else {
await userCol.updateOne(query, { $set: { roles, verifyTime: new Date().toLocaleString(), remark, useAmount } })
await userCol.updateOne(query, { $set: { roles, verifyTime: new Date().toLocaleString(), remark, useAmount, limit_switch } })
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,11 @@ export function fetchUpdateUserStatus<T = any>(userId: string, status: Status) {
})
}

// 增加useAmount信息
// 增加useAmount信息 limit_switch
export function fetchUpdateUser<T = any>(userInfo: UserInfo) {
return post<T>({
url: '/user-edit',
data: { userId: userInfo._id, roles: userInfo.roles, email: userInfo.email, password: userInfo.password, remark: userInfo.remark, useAmount: userInfo.useAmount },
data: { userId: userInfo._id, roles: userInfo.roles, email: userInfo.email, password: userInfo.password, remark: userInfo.remark, useAmount: userInfo.useAmount, limit_switch: userInfo.limit_switch },
})
}

Expand Down
53 changes: 50 additions & 3 deletions src/components/common/Setting/User.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { h, onMounted, reactive, ref } from 'vue'
import { NButton, NDataTable, NInput, NInputNumber, NModal, NSelect, NSpace, NTag, useDialog, useMessage } from 'naive-ui'
import { NButton, NDataTable, NInput, NInputNumber, NModal, NSelect, NSpace, NSwitch, NTag, useDialog, useMessage } from 'naive-ui'
import { Status, UserInfo, UserRole, userRoleOptions } from './model'
import { fetchDisableUser2FAByAdmin, fetchGetUsers, fetchUpdateUser, fetchUpdateUserStatus } from '@/api'
import { t } from '@/locales'
Expand All @@ -21,23 +21,32 @@ const columns = [
key: 'email',
resizable: true,
width: 200,
minWidth: 100,
minWidth: 80,
maxWidth: 200,
},
{
title: 'Register Time',
key: 'createTime',
resizable: true,
width: 200,
minWidth: 80,
maxWidth: 200,
},
{
title: 'Verify Time',
key: 'verifyTime',
resizable: true,
width: 200,
minWidth: 80,
maxWidth: 200,
},
{
title: 'Roles',
key: 'status',
resizable: true,
width: 200,
minWidth: 80,
maxWidth: 200,
render(row: any) {
const roles = row.roles.map((role: UserRole) => {
return h(
Expand Down Expand Up @@ -68,7 +77,35 @@ const columns = [
{
title: 'Remark',
key: 'remark',
width: 220,
resizable: true,
width: 200,
minWidth: 80,
maxWidth: 200,
},
// 新增额度信息
{
title: 'Amts',
key: 'useAmount',
resizable: true,
width: 80,
minWidth: 30,
maxWidth: 100,
},
// switch off amt limit
{
title: 'limit switch',
key: 'limit_switch',
resizable: true,
width: 80,
minWidth: 30,
maxWidth: 100,
render(row: any) {
return h(NSwitch, {
defaultValue: row.limit_switch,
round: false,
disabled: true,
})
},
},
// 新增额度信息
{
Expand Down Expand Up @@ -313,6 +350,16 @@ onMounted(async () => {
/>
</div>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.limit_switch') }}</span>
<div class="flex-1">
<NSwitch
v-model:value="userRef.limit_switch"
:round="false"
@update:value="(val) => { if (userRef) userRef.limit_switch = val }"
/>
</div>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]" />
<NButton type="primary" :loading="handleSaving" @click="handleUpdateUser()">
Expand Down
3 changes: 2 additions & 1 deletion src/components/common/Setting/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ export class UserInfo {
roles: UserRole[]
remark?: string
useAmount?: number
// 配合改造,增加额度信息
// 配合改造,增加额度信息 and it's switch
limit_switch?: boolean
constructor(roles: UserRole[]) {
this.roles = roles
}
Expand Down
1 change: 1 addition & 0 deletions src/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export default {
disable2FAConfirm: 'Are you sure to disable 2FA for this user?',
},
setting: {
limit_switch: 'Open Usage Limitation',
redeemCardNo: 'Redeem CardNo',
useAmount: 'No. of questions',
setting: 'Setting',
Expand Down
1 change: 1 addition & 0 deletions src/locales/ko-KR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export default {
disable2FAConfirm: 'Are you sure to disable 2FA for this user?',
},
setting: {
limit_switch: '오픈 횟수 제한',
redeemCardNo: '질문 허용 횟수',
useAmount: '질문 허용 횟수',
setting: '설정',
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export default {
disable2FAConfirm: '您确定要为此用户禁用两步验证吗??',
},
setting: {
limit_switch: '打开次数限制',
redeemCardNo: '兑换码卡号',
useAmount: '可提问次数',
setting: '设置',
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export default {
disable2FAConfirm: '您确定要为此用户禁用两步验证吗??',
},
setting: {
limit_switch: '開啟次數限制',
redeemCardNo: '兌換碼卡號',
useAmount: '可提問次數',
setting: '設定',
Expand Down
3 changes: 2 additions & 1 deletion src/store/modules/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ export const useUserStore = defineStore('user-store', {
async updateUserInfo(update: boolean, userInfo: Partial<UserInfo>) {
this.userInfo = { ...this.userInfo, ...userInfo }
this.recordState()
if (update)
if (update) {
await fetchUpdateUserInfo(userInfo.name ?? '', userInfo.avatar ?? '', userInfo.description ?? '')
// 更新用户信息和额度写一起了,如果传了额度则更新
if (userInfo.useAmount)
await fetchUpdateUserAmt(userInfo.useAmount)
}
},
async updateSetting(sync: boolean) {
await fetchUpdateAdvanced(sync, this.userInfo.advanced)
Expand Down

0 comments on commit 745e5a2

Please sign in to comment.