feat: share chat page

This commit is contained in:
archer
2023-05-14 21:37:26 +08:00
parent d3e7923040
commit d31bdf0ee0
23 changed files with 2047 additions and 65 deletions

View File

@@ -0,0 +1,130 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authShareChat } from '@/service/utils/auth';
import { modelServiceToolMap } from '@/service/utils/chat';
import { ChatItemSimpleType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
import { pushChatBill, updateShareChatBill } from '@/service/events/pushBill';
import { resStreamResponse } from '@/service/utils/chat';
import { searchKb } from '@/service/plugins/searchKb';
import { ChatRoleEnum } from '@/constants/chat';
/* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
let step = 0; // step=1 时,表示开始了流响应
const stream = new PassThrough();
stream.on('error', () => {
console.log('error: ', 'stream error');
stream.destroy();
});
res.on('close', () => {
stream.destroy();
});
res.on('error', () => {
console.log('error: ', 'request error');
stream.destroy();
});
try {
const { shareId, password, historyId, prompts } = req.body as {
prompts: ChatItemSimpleType[];
password: string;
shareId: string;
historyId: string;
};
if (!historyId || !prompts) {
throw new Error('分享链接无效');
}
await connectToDatabase();
let startTime = Date.now();
const { model, showModelDetail, userOpenAiKey, systemAuthKey, userId } = await authShareChat({
shareId,
password
});
const modelConstantsData = ChatModelMap[model.chat.chatModel];
// 使用了知识库搜索
if (model.chat.useKb) {
const { code, searchPrompts } = await searchKb({
userOpenAiKey,
prompts,
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity,
model,
userId
});
// search result is empty
if (code === 201) {
return res.send(searchPrompts[0]?.value);
}
prompts.splice(prompts.length - 3, 0, ...searchPrompts);
} else {
// 没有用知识库搜索,仅用系统提示词
model.chat.systemPrompt &&
prompts.splice(prompts.length - 3, 0, {
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
});
}
// 计算温度
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
2
);
// 发出请求
const { streamResponse } = await modelServiceToolMap[model.chat.chatModel].chatCompletion({
apiKey: userOpenAiKey || systemAuthKey,
temperature: +temperature,
messages: prompts,
stream: true,
res,
chatId: historyId
});
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
step = 1;
const { totalTokens, finishMessages } = await resStreamResponse({
model: model.chat.chatModel,
res,
stream,
chatResponse: streamResponse,
prompts,
systemPrompt: ''
});
/* bill */
pushChatBill({
isPay: !userOpenAiKey,
chatModel: model.chat.chatModel,
userId,
textLen: finishMessages.map((item) => item.value).join('').length,
tokens: totalTokens
});
updateShareChatBill({
shareId,
tokens: totalTokens
});
} catch (err: any) {
if (step === 1) {
// 直接结束流
console.log('error结束');
stream.destroy();
} else {
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
}
}
}

View File

@@ -0,0 +1,39 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authModel, authToken } from '@/service/utils/auth';
import type { ShareChatEditType } from '@/types/model';
/* create a shareChat */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { modelId, name, maxContext, password } = req.body as ShareChatEditType & {
modelId: string;
};
await connectToDatabase();
const userId = await authToken(req);
await authModel({
modelId,
userId
});
const { _id } = await ShareChat.create({
userId,
modelId,
name,
password,
maxContext
});
jsonRes(res, {
data: _id
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,29 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
/* delete a shareChat by shareChatId */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { id } = req.query as {
id: string;
};
await connectToDatabase();
const userId = await authToken(req);
await ShareChat.findOneAndRemove({
_id: id,
userId
});
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,59 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import type { InitShareChatResponse } from '@/api/response/chat';
import { authModel } from '@/service/utils/auth';
import { hashPassword } from '@/service/utils/tools';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
let { shareId, password = '' } = req.query as {
shareId: string;
password: string;
};
if (!shareId) {
throw new Error('params is error');
}
await connectToDatabase();
// get shareChat
const shareChat = await ShareChat.findById(shareId);
if (!shareChat) {
throw new Error('分享链接已失效');
}
if (shareChat.password !== hashPassword(password)) {
return jsonRes(res, {
code: 501,
message: '密码不正确'
});
}
// 校验使用权限
const { model } = await authModel({
modelId: shareChat.modelId,
userId: String(shareChat.userId)
});
jsonRes<InitShareChatResponse>(res, {
data: {
maxContext: shareChat.maxContext,
model: {
name: model.name,
avatar: model.avatar,
intro: model.share.intro
},
chatModel: model.chat.chatModel
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,43 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { hashPassword } from '@/service/utils/tools';
/* get shareChat list by modelId */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { modelId } = req.query as {
modelId: string;
};
await connectToDatabase();
const userId = await authToken(req);
const data = await ShareChat.find({
modelId,
userId
}).sort({
_id: -1
});
const blankPassword = hashPassword('');
jsonRes(res, {
data: data.map((item) => ({
_id: item._id,
name: item.name,
password: item.password === blankPassword ? '' : '1',
tokens: item.tokens,
maxContext: item.maxContext,
lastTime: item.lastTime
}))
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}