Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: persistent prompts and add chat record #540

Merged
merged 47 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
9eccfde
update actions
jamebal May 23, 2023
562b953
Merge branch 'main' into jmal
jamebal May 23, 2023
4830604
chore(gitignore): update ignored files
jamebal Aug 11, 2023
d8276a7
feat(*): add chat record
jamebal Aug 29, 2023
7414dc5
refactor: add chat record
jamebal Aug 29, 2023
7c0d42e
refactor: add chat record
jamebal Aug 29, 2023
23e7d2e
refactor: add chat record
jamebal Aug 29, 2023
38e03f1
perf: chat record
jamebal Aug 29, 2023
92a7b61
perf: chat record
jamebal Aug 31, 2023
6e2811f
perf: remove Shift+Enter line breaks from text copied to the clipboard
jamebal Aug 31, 2023
485df4d
perf: chat record
jamebal Aug 31, 2023
5809f22
feat: the prompt word store is saved to the database
jamebal Aug 31, 2023
154df7b
perf: the prompt word
jamebal Aug 31, 2023
aa5a3d3
perf: the prompt word
jamebal Aug 31, 2023
09badfd
feat: persistent prompts
jamebal Sep 1, 2023
263a1b0
Update README.md
jamebal Oct 11, 2023
e52b43b
fix: the typewriter animation
jamebal Oct 18, 2023
3256961
Merge branch 'main' into test
jamebal Nov 13, 2023
f87be7b
Merge branch 'main' into jmal
jamebal Nov 13, 2023
ecf6d4e
fix: the typewriter animation
jamebal Nov 13, 2023
f9016d2
Merge remote-tracking branch 'origin/main'
jamebal Nov 13, 2023
1dd5102
Merge branch 'main'
jamebal Nov 13, 2023
3002891
fix: fix the max tokens for gpt-4-turbo 128K
jamebal Dec 20, 2023
6800e38
feat: add custom system role
jamebal Dec 21, 2023
3d20329
feat: add GeminiPro option
jamebal Dec 21, 2023
3771a52
feat: add chat record
jamebal Jan 11, 2024
298a4ae
fix: chat record
jamebal Jan 11, 2024
a9f1ccd
perf: persistent prompts
jamebal Jan 11, 2024
b3935c0
style: update default systemMessage
jamebal Jan 11, 2024
deebf10
Merge branch 'refs/heads/master' into jmal
jamebal May 14, 2024
9090954
Merge remote-tracking branch 'refs/remotes/origin/main' into jmal
jamebal May 14, 2024
6d655d5
chore: merge code
jamebal May 14, 2024
46f057c
build: v2.17.0
jamebal May 14, 2024
129b905
test: build
jamebal May 14, 2024
0533956
perf: history logs plus model
jamebal May 14, 2024
cd71aa9
Merge remote-tracking branch 'refs/remotes/origin/main' into jmal
jamebal May 15, 2024
42e6144
style: remove redundant code
jamebal May 15, 2024
3c9bf41
chore: restore main branch
jamebal May 15, 2024
499a37d
Merge pull request #541
jamebal May 15, 2024
6886bbc
revert modifications to unrelated files such as gitignore and readme.
jamebal May 15, 2024
78be0ff
revert lint format
jamebal May 15, 2024
d751456
Merge branch 'main' into jmal
BobDu May 21, 2024
2bf0eff
Merge branch 'refs/heads/main' into jmal
jamebal May 27, 2024
9dda341
chore: update actions
jamebal May 27, 2024
73e0b94
Revert "chore: update actions"
jamebal May 27, 2024
5fafb5b
Revert "default chatModels"
jamebal May 30, 2024
b515ee6
perf: chat record
jamebal May 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions service/src/chatgpt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,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, updateRoomAccountId } from '../storage/mongo'
import { getChatByMessageId, updateRoomAccountId, updateRoomChatModel } from '../storage/mongo'
import type { RequestOptions } from './types'

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

if (options.lastContext && ((options.lastContext.conversationId && !options.lastContext.parentMessageId)
|| (!options.lastContext.conversationId && options.lastContext.parentMessageId)))
if (options.lastContext && ((options.lastContext.conversationId && !options.lastContext.parentMessageId) || (!options.lastContext.conversationId && options.lastContext.parentMessageId)))
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')
}

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

const { message, uploadFileKeys, lastContext, process, systemMessage, temperature, top_p } = options
let content: string | {
type: string
text?: string
Expand Down
155 changes: 146 additions & 9 deletions service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,31 @@ import type { ChatMessage } from './chatgpt'
import { abortChatProcess, chatConfig, chatReplyProcess, containsSensitiveWords, initAuditService } from './chatgpt'
import { auth, getUserId } from './middleware/auth'
import { clearApiKeyCache, clearConfigCache, getApiKeys, getCacheApiKeys, getCacheConfig, getOriginConfig } from './storage/config'
import type { AnnounceConfig, AuditConfig, ChatInfo, ChatOptions, Config, GiftCard, KeyConfig, MailConfig, SiteConfig, UserConfig, UserInfo } from './storage/model'
import type { AnnounceConfig, AuditConfig, ChatInfo, ChatOptions, Config, GiftCard, KeyConfig, MailConfig, SiteConfig, UserConfig, UserInfo, UserPrompt } from './storage/model'
import { AdvancedConfig, Status, UsageResponse, UserRole } from './storage/model'
import {
clearChat,
clearUserPrompt,
createChatRoom,
createUser,
deleteAllChatRooms,
deleteChat,
deleteChatRoom,
deleteUserPrompt,
disableUser2FA,
existsChatRoom,
getAmtByCardNo,
getChat,
getChatRoom,
getChatRooms,
getChatRoomsCount,
getChats,
getUser,
getUserById,
getUserPromptList,
getUserStatisticsByDay,
getUsers,
importUserPrompt,
insertChat,
insertChatUsage,
renameChatRoom,
Expand All @@ -53,6 +58,7 @@ import {
updateUserPasswordWithVerifyOld,
updateUserStatus,
upsertKey,
upsertUserPrompt,
verifyUser,
} from './storage/mongo'
import { authLimiter, limiter } from './middleware/limiter'
Expand Down Expand Up @@ -102,6 +108,43 @@ router.get('/chatrooms', auth, async (req, res) => {
}
})

function formatTimestamp(timestamp: number) {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')

return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}

router.get('/chatrooms-count', auth, async (req, res) => {
try {
const userId = req.query.userId as string
const page = +req.query.page
const size = +req.query.size
const rooms = await getChatRoomsCount(userId, page, size)
const result = []
rooms.data.forEach((r) => {
result.push({
uuid: r.roomId,
title: r.title,
userId: r.userId,
name: r.username,
lastTime: formatTimestamp(r.dateTime),
chatCount: r.chatCount,
})
})
res.send({ status: 'Success', message: null, data: { data: result, total: rooms.total } })
}
catch (error) {
console.error(error)
res.send({ status: 'Fail', message: 'Load error', data: [] })
}
})

router.post('/room-create', auth, async (req, res) => {
try {
const userId = req.headers.userId as string
Expand Down Expand Up @@ -204,17 +247,38 @@ router.get('/chat-history', auth, async (req, res) => {
const userId = req.headers.userId as string
const roomId = +req.query.roomId
const lastId = req.query.lastId as string
if (!roomId || !await existsChatRoom(userId, roomId)) {
const all = req.query.all as string
if ((!roomId || !await existsChatRoom(userId, roomId)) && (all === null || all === 'undefined' || all === undefined || all.trim().length === 0)) {
res.send({ status: 'Success', message: null, data: [] })
return
}
const chats = await getChats(roomId, !isNotEmptyString(lastId) ? null : Number.parseInt(lastId))

if (all !== null && all !== 'undefined' && all !== undefined && all.trim().length !== 0) {
const config = await getCacheConfig()
if (config.siteConfig.loginEnabled) {
try {
const user = await getUserById(userId)
if (user == null || user.status !== Status.Normal || !user.roles.includes(UserRole.Admin)) {
res.send({ status: 'Fail', message: '无权限 | No permission.', data: null })
return
}
}
catch (error) {
res.send({ status: 'Unauthorized', message: error.message ?? 'Please authenticate.', data: null })
}
}
else {
res.send({ status: 'Fail', message: '无权限 | No permission.', data: null })
}
}

const chats = await getChats(roomId, !isNotEmptyString(lastId) ? null : Number.parseInt(lastId), all)
const result = []
chats.forEach((c) => {
if (c.status !== Status.InversionDeleted) {
result.push({
uuid: c.uuid,
model: c.model,
dateTime: new Date(c.dateTime).toLocaleString(),
text: c.prompt,
images: c.images,
Expand Down Expand Up @@ -413,10 +477,7 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
return
}
}

message = regenerate
? await getChat(roomId, uuid)
: await insertChat(uuid, prompt, uploadFileKeys, roomId, options as ChatOptions)
message = regenerate ? await getChat(roomId, uuid) : await insertChat(uuid, prompt, uploadFileKeys, roomId, model, options as ChatOptions)
let firstChunk = true
result = await chatReplyProcess({
message: prompt,
Expand Down Expand Up @@ -476,7 +537,7 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
}

if (result.data === undefined)
// eslint-disable-next-line no-unsafe-finally
// eslint-disable-next-line no-unsafe-finally
return

if (regenerate && message.options.messageId) {
Expand All @@ -486,6 +547,7 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
result.data.text,
result.data.id,
result.data.conversationId,
model,
result.data.detail?.usage as UsageResponse,
previousResponse as [])
}
Expand All @@ -494,6 +556,7 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
result.data.text,
result.data.id,
result.data.conversationId,
model,
result.data.detail?.usage as UsageResponse)
}

Expand Down Expand Up @@ -527,7 +590,7 @@ router.post('/chat-abort', [auth, limiter], async (req, res) => {
text,
messageId,
conversationId,
null)
null, null)
res.send({ status: 'Success', message: 'OK', data: null })
}
catch (error) {
Expand Down Expand Up @@ -1352,6 +1415,80 @@ router.post('/statistics/by-day', auth, async (req, res) => {
app.use('', uploadRouter)
app.use('/api', uploadRouter)

router.get('/prompt-list', auth, async (req, res) => {
try {
const userId = req.headers.userId as string
const prompts = await getUserPromptList(userId)
const result = []
prompts.data.forEach((p) => {
result.push({
_id: p._id,
title: p.title,
value: p.value,
})
})
res.send({ status: 'Success', message: null, data: { data: result, total: prompts.total } })
}
catch (error) {
res.send({ status: 'Fail', message: error.message, data: null })
}
})

router.post('/prompt-upsert', auth, async (req, res) => {
try {
const userId = req.headers.userId as string
const userPrompt = req.body as UserPrompt
if (userPrompt._id !== undefined)
userPrompt._id = new ObjectId(userPrompt._id)
userPrompt.userId = userId
const newUserPrompt = await upsertUserPrompt(userPrompt)
res.send({ status: 'Success', message: '成功 | Successfully', data: { _id: newUserPrompt._id.toHexString() } })
}
catch (error) {
res.send({ status: 'Fail', message: error.message, data: null })
}
})

router.post('/prompt-delete', auth, async (req, res) => {
try {
const { id } = req.body as { id: string }
await deleteUserPrompt(id)
res.send({ status: 'Success', message: '成功 | Successfully' })
}
catch (error) {
res.send({ status: 'Fail', message: error.message, data: null })
}
})

router.post('/prompt-clear', auth, async (req, res) => {
try {
const userId = req.headers.userId as string
await clearUserPrompt(userId)
res.send({ status: 'Success', message: '成功 | Successfully' })
}
catch (error) {
res.send({ status: 'Fail', message: error.message, data: null })
}
})

router.post('/prompt-import', auth, async (req, res) => {
try {
const userId = req.headers.userId as string
const userPrompt = req.body as UserPrompt[]
const updatedUserPrompt = userPrompt.map((prompt) => {
return {
...prompt,
userId,
}
})
await importUserPrompt(updatedUserPrompt)
res.send({ status: 'Success', message: '成功 | Successfully' })
}
catch (error) {
res.send({ status: 'Fail', message: error.message, data: null })
}
})

app.use('', router)
app.use('/api', router)

Expand Down
3 changes: 1 addition & 2 deletions service/src/storage/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ export async function getOriginConfig() {
}

if (!isNotEmptyString(config.siteConfig.chatModels))
config.siteConfig.chatModels = 'gpt-3.5-turbo,gpt-4-turbo-preview,gpt-4-vision-preview'

config.siteConfig.chatModels = 'gpt-3.5-turbo,gpt-4,gpt-4-turbo,gpt-4o,gpt-4-turbo-preview,gpt-4-vision-preview,gemini-pro'
jamebal marked this conversation as resolved.
Show resolved Hide resolved
return config
}

Expand Down
16 changes: 15 additions & 1 deletion service/src/storage/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class previousResponse {
export class ChatInfo {
_id: ObjectId
roomId: number
model: string
uuid: number
dateTime: number
prompt: string
Expand All @@ -126,8 +127,9 @@ export class ChatInfo {
status: Status = Status.Normal
options: ChatOptions
previousResponse?: previousResponse[]
constructor(roomId: number, uuid: number, prompt: string, images: string[], options: ChatOptions) {
constructor(roomId: number, uuid: number, prompt: string, images: string[], model: string, options: ChatOptions) {
this.roomId = roomId
this.model = model
this.uuid = uuid
this.prompt = prompt
this.images = images
Expand Down Expand Up @@ -273,4 +275,16 @@ export class KeyConfig {
}
}

export class UserPrompt {
_id: ObjectId
userId: string
title: string
value: string
constructor(userId: string, title: string, value: string) {
this.userId = userId
this.title = title
this.value = value
}
}

export type APIMODEL = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI'
Loading
Loading