Skip to content

Commit c8b1bdd

Browse files
authored
perf: persistent prompts and add chat record (#540)
* update actions * chore(gitignore): update ignored files * feat(*): add chat record * refactor: add chat record * refactor: add chat record * refactor: add chat record * perf: chat record * perf: chat record * perf: remove Shift+Enter line breaks from text copied to the clipboard * perf: chat record * feat: the prompt word store is saved to the database * perf: the prompt word * perf: the prompt word * feat: persistent prompts * Update README.md * fix: the typewriter animation * fix: the typewriter animation * fix: fix the max tokens for gpt-4-turbo 128K * feat: add custom system role * feat: add GeminiPro option * fix: chat record * perf: persistent prompts * style: update default systemMessage * chore: merge code * build: v2.17.0 * test: build * perf: history logs plus model * style: remove redundant code * chore: restore main branch * revert modifications to unrelated files such as gitignore and readme. * revert lint format * chore: update actions * Revert "chore: update actions" This reverts commit 9dda341. * Revert "default chatModels" * perf: chat record 1.remove redundant files 2.the content of the chat record uses "@/views/chat/components/Message"
1 parent df84c4e commit c8b1bdd

File tree

20 files changed

+722
-80
lines changed

20 files changed

+722
-80
lines changed

service/src/chatgpt/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { getCacheApiKeys, getCacheConfig, getOriginConfig } from '../storage/con
1414
import { sendResponse } from '../utils'
1515
import { hasAnyRole, isNotEmptyString } from '../utils/is'
1616
import type { ChatContext, ChatGPTUnofficialProxyAPIOptions, JWT, ModelConfig } from '../types'
17-
import { getChatByMessageId, updateRoomAccountId } from '../storage/mongo'
17+
import { getChatByMessageId, updateRoomAccountId, updateRoomChatModel } from '../storage/mongo'
1818
import type { RequestOptions } from './types'
1919

2020
const { HttpsProxyAgent } = httpsProxyAgent
@@ -124,13 +124,14 @@ async function chatReplyProcess(options: RequestOptions) {
124124
if (!options.room.accountId)
125125
updateRoomAccountId(userId, options.room.roomId, getAccountId(key.key))
126126

127-
if (options.lastContext && ((options.lastContext.conversationId && !options.lastContext.parentMessageId)
128-
|| (!options.lastContext.conversationId && options.lastContext.parentMessageId)))
127+
if (options.lastContext && ((options.lastContext.conversationId && !options.lastContext.parentMessageId) || (!options.lastContext.conversationId && options.lastContext.parentMessageId)))
129128
throw new Error('无法在一个房间同时使用 AccessToken 以及 Api,请联系管理员,或新开聊天室进行对话 | Unable to use AccessToken and Api at the same time in the same room, please contact the administrator or open a new chat room for conversation')
130129
}
131130

132-
const { message, uploadFileKeys, lastContext, process, systemMessage, temperature, top_p } = options
131+
// Add Chat Record
132+
updateRoomChatModel(userId, options.room.roomId, model)
133133

134+
const { message, uploadFileKeys, lastContext, process, systemMessage, temperature, top_p } = options
134135
let content: string | {
135136
type: string
136137
text?: string

service/src/index.ts

Lines changed: 146 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,31 @@ import type { ChatMessage } from './chatgpt'
1111
import { abortChatProcess, chatConfig, chatReplyProcess, containsSensitiveWords, initAuditService } from './chatgpt'
1212
import { auth, getUserId } from './middleware/auth'
1313
import { clearApiKeyCache, clearConfigCache, getApiKeys, getCacheApiKeys, getCacheConfig, getOriginConfig } from './storage/config'
14-
import type { AnnounceConfig, AuditConfig, ChatInfo, ChatOptions, Config, GiftCard, KeyConfig, MailConfig, SiteConfig, UserConfig, UserInfo } from './storage/model'
14+
import type { AnnounceConfig, AuditConfig, ChatInfo, ChatOptions, Config, GiftCard, KeyConfig, MailConfig, SiteConfig, UserConfig, UserInfo, UserPrompt } from './storage/model'
1515
import { AdvancedConfig, Status, UsageResponse, UserRole } from './storage/model'
1616
import {
1717
clearChat,
18+
clearUserPrompt,
1819
createChatRoom,
1920
createUser,
2021
deleteAllChatRooms,
2122
deleteChat,
2223
deleteChatRoom,
24+
deleteUserPrompt,
2325
disableUser2FA,
2426
existsChatRoom,
2527
getAmtByCardNo,
2628
getChat,
2729
getChatRoom,
2830
getChatRooms,
31+
getChatRoomsCount,
2932
getChats,
3033
getUser,
3134
getUserById,
35+
getUserPromptList,
3236
getUserStatisticsByDay,
3337
getUsers,
38+
importUserPrompt,
3439
insertChat,
3540
insertChatUsage,
3641
renameChatRoom,
@@ -53,6 +58,7 @@ import {
5358
updateUserPasswordWithVerifyOld,
5459
updateUserStatus,
5560
upsertKey,
61+
upsertUserPrompt,
5662
verifyUser,
5763
} from './storage/mongo'
5864
import { authLimiter, limiter } from './middleware/limiter'
@@ -102,6 +108,43 @@ router.get('/chatrooms', auth, async (req, res) => {
102108
}
103109
})
104110

111+
function formatTimestamp(timestamp: number) {
112+
const date = new Date(timestamp)
113+
const year = date.getFullYear()
114+
const month = String(date.getMonth() + 1).padStart(2, '0')
115+
const day = String(date.getDate()).padStart(2, '0')
116+
const hours = String(date.getHours()).padStart(2, '0')
117+
const minutes = String(date.getMinutes()).padStart(2, '0')
118+
const seconds = String(date.getSeconds()).padStart(2, '0')
119+
120+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
121+
}
122+
123+
router.get('/chatrooms-count', auth, async (req, res) => {
124+
try {
125+
const userId = req.query.userId as string
126+
const page = +req.query.page
127+
const size = +req.query.size
128+
const rooms = await getChatRoomsCount(userId, page, size)
129+
const result = []
130+
rooms.data.forEach((r) => {
131+
result.push({
132+
uuid: r.roomId,
133+
title: r.title,
134+
userId: r.userId,
135+
name: r.username,
136+
lastTime: formatTimestamp(r.dateTime),
137+
chatCount: r.chatCount,
138+
})
139+
})
140+
res.send({ status: 'Success', message: null, data: { data: result, total: rooms.total } })
141+
}
142+
catch (error) {
143+
console.error(error)
144+
res.send({ status: 'Fail', message: 'Load error', data: [] })
145+
}
146+
})
147+
105148
router.post('/room-create', auth, async (req, res) => {
106149
try {
107150
const userId = req.headers.userId as string
@@ -204,17 +247,38 @@ router.get('/chat-history', auth, async (req, res) => {
204247
const userId = req.headers.userId as string
205248
const roomId = +req.query.roomId
206249
const lastId = req.query.lastId as string
207-
if (!roomId || !await existsChatRoom(userId, roomId)) {
250+
const all = req.query.all as string
251+
if ((!roomId || !await existsChatRoom(userId, roomId)) && (all === null || all === 'undefined' || all === undefined || all.trim().length === 0)) {
208252
res.send({ status: 'Success', message: null, data: [] })
209253
return
210254
}
211-
const chats = await getChats(roomId, !isNotEmptyString(lastId) ? null : Number.parseInt(lastId))
212255

256+
if (all !== null && all !== 'undefined' && all !== undefined && all.trim().length !== 0) {
257+
const config = await getCacheConfig()
258+
if (config.siteConfig.loginEnabled) {
259+
try {
260+
const user = await getUserById(userId)
261+
if (user == null || user.status !== Status.Normal || !user.roles.includes(UserRole.Admin)) {
262+
res.send({ status: 'Fail', message: '无权限 | No permission.', data: null })
263+
return
264+
}
265+
}
266+
catch (error) {
267+
res.send({ status: 'Unauthorized', message: error.message ?? 'Please authenticate.', data: null })
268+
}
269+
}
270+
else {
271+
res.send({ status: 'Fail', message: '无权限 | No permission.', data: null })
272+
}
273+
}
274+
275+
const chats = await getChats(roomId, !isNotEmptyString(lastId) ? null : Number.parseInt(lastId), all)
213276
const result = []
214277
chats.forEach((c) => {
215278
if (c.status !== Status.InversionDeleted) {
216279
result.push({
217280
uuid: c.uuid,
281+
model: c.model,
218282
dateTime: new Date(c.dateTime).toLocaleString(),
219283
text: c.prompt,
220284
images: c.images,
@@ -413,10 +477,7 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
413477
return
414478
}
415479
}
416-
417-
message = regenerate
418-
? await getChat(roomId, uuid)
419-
: await insertChat(uuid, prompt, uploadFileKeys, roomId, options as ChatOptions)
480+
message = regenerate ? await getChat(roomId, uuid) : await insertChat(uuid, prompt, uploadFileKeys, roomId, model, options as ChatOptions)
420481
let firstChunk = true
421482
result = await chatReplyProcess({
422483
message: prompt,
@@ -476,7 +537,7 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
476537
}
477538

478539
if (result.data === undefined)
479-
// eslint-disable-next-line no-unsafe-finally
540+
// eslint-disable-next-line no-unsafe-finally
480541
return
481542

482543
if (regenerate && message.options.messageId) {
@@ -486,6 +547,7 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
486547
result.data.text,
487548
result.data.id,
488549
result.data.conversationId,
550+
model,
489551
result.data.detail?.usage as UsageResponse,
490552
previousResponse as [])
491553
}
@@ -494,6 +556,7 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
494556
result.data.text,
495557
result.data.id,
496558
result.data.conversationId,
559+
model,
497560
result.data.detail?.usage as UsageResponse)
498561
}
499562

@@ -527,7 +590,7 @@ router.post('/chat-abort', [auth, limiter], async (req, res) => {
527590
text,
528591
messageId,
529592
conversationId,
530-
null)
593+
null, null)
531594
res.send({ status: 'Success', message: 'OK', data: null })
532595
}
533596
catch (error) {
@@ -1352,6 +1415,80 @@ router.post('/statistics/by-day', auth, async (req, res) => {
13521415
app.use('', uploadRouter)
13531416
app.use('/api', uploadRouter)
13541417

1418+
router.get('/prompt-list', auth, async (req, res) => {
1419+
try {
1420+
const userId = req.headers.userId as string
1421+
const prompts = await getUserPromptList(userId)
1422+
const result = []
1423+
prompts.data.forEach((p) => {
1424+
result.push({
1425+
_id: p._id,
1426+
title: p.title,
1427+
value: p.value,
1428+
})
1429+
})
1430+
res.send({ status: 'Success', message: null, data: { data: result, total: prompts.total } })
1431+
}
1432+
catch (error) {
1433+
res.send({ status: 'Fail', message: error.message, data: null })
1434+
}
1435+
})
1436+
1437+
router.post('/prompt-upsert', auth, async (req, res) => {
1438+
try {
1439+
const userId = req.headers.userId as string
1440+
const userPrompt = req.body as UserPrompt
1441+
if (userPrompt._id !== undefined)
1442+
userPrompt._id = new ObjectId(userPrompt._id)
1443+
userPrompt.userId = userId
1444+
const newUserPrompt = await upsertUserPrompt(userPrompt)
1445+
res.send({ status: 'Success', message: '成功 | Successfully', data: { _id: newUserPrompt._id.toHexString() } })
1446+
}
1447+
catch (error) {
1448+
res.send({ status: 'Fail', message: error.message, data: null })
1449+
}
1450+
})
1451+
1452+
router.post('/prompt-delete', auth, async (req, res) => {
1453+
try {
1454+
const { id } = req.body as { id: string }
1455+
await deleteUserPrompt(id)
1456+
res.send({ status: 'Success', message: '成功 | Successfully' })
1457+
}
1458+
catch (error) {
1459+
res.send({ status: 'Fail', message: error.message, data: null })
1460+
}
1461+
})
1462+
1463+
router.post('/prompt-clear', auth, async (req, res) => {
1464+
try {
1465+
const userId = req.headers.userId as string
1466+
await clearUserPrompt(userId)
1467+
res.send({ status: 'Success', message: '成功 | Successfully' })
1468+
}
1469+
catch (error) {
1470+
res.send({ status: 'Fail', message: error.message, data: null })
1471+
}
1472+
})
1473+
1474+
router.post('/prompt-import', auth, async (req, res) => {
1475+
try {
1476+
const userId = req.headers.userId as string
1477+
const userPrompt = req.body as UserPrompt[]
1478+
const updatedUserPrompt = userPrompt.map((prompt) => {
1479+
return {
1480+
...prompt,
1481+
userId,
1482+
}
1483+
})
1484+
await importUserPrompt(updatedUserPrompt)
1485+
res.send({ status: 'Success', message: '成功 | Successfully' })
1486+
}
1487+
catch (error) {
1488+
res.send({ status: 'Fail', message: error.message, data: null })
1489+
}
1490+
})
1491+
13551492
app.use('', router)
13561493
app.use('/api', router)
13571494

service/src/storage/config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ export async function getOriginConfig() {
118118

119119
if (!isNotEmptyString(config.siteConfig.chatModels))
120120
config.siteConfig.chatModels = 'gpt-3.5-turbo,gpt-4-turbo-preview,gpt-4-vision-preview'
121-
122121
return config
123122
}
124123

service/src/storage/model.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export class previousResponse {
118118
export class ChatInfo {
119119
_id: ObjectId
120120
roomId: number
121+
model: string
121122
uuid: number
122123
dateTime: number
123124
prompt: string
@@ -126,8 +127,9 @@ export class ChatInfo {
126127
status: Status = Status.Normal
127128
options: ChatOptions
128129
previousResponse?: previousResponse[]
129-
constructor(roomId: number, uuid: number, prompt: string, images: string[], options: ChatOptions) {
130+
constructor(roomId: number, uuid: number, prompt: string, images: string[], model: string, options: ChatOptions) {
130131
this.roomId = roomId
132+
this.model = model
131133
this.uuid = uuid
132134
this.prompt = prompt
133135
this.images = images
@@ -273,4 +275,16 @@ export class KeyConfig {
273275
}
274276
}
275277

278+
export class UserPrompt {
279+
_id: ObjectId
280+
userId: string
281+
title: string
282+
value: string
283+
constructor(userId: string, title: string, value: string) {
284+
this.userId = userId
285+
this.title = title
286+
this.value = value
287+
}
288+
}
289+
276290
export type APIMODEL = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI'

0 commit comments

Comments
 (0)