Skip to content

Commit

Permalink
feat: share chat page
Browse files Browse the repository at this point in the history
  • Loading branch information
c121914yu committed May 15, 2023
1 parent d3e7923 commit d31bdf0
Show file tree
Hide file tree
Showing 23 changed files with 2,047 additions and 65 deletions.
31 changes: 30 additions & 1 deletion src/api/chat.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { GET, POST, DELETE } from './request';
import type { ChatItemType, HistoryItemType } from '@/types/chat';
import type { InitChatResponse } from './response/chat';
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
import { RequestPaging } from '../types/index';
import type { ShareChatSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/model';
import { Obj2Query } from '@/utils/tools';

/**
* 获取初始化聊天内容
Expand Down Expand Up @@ -35,3 +38,29 @@ export const postSaveChat = (data: {
*/
export const delChatRecordByIndex = (chatId: string, contentId: string) =>
DELETE(`/chat/delChatRecordByContentId?chatId=${chatId}&contentId=${contentId}`);

/**
* create a shareChat
*/
export const createShareChat = (
data: ShareChatEditType & {
modelId: string;
}
) => POST<string>(`/chat/shareChat/create`, data);

/**
* get shareChat
*/
export const getShareChatList = (modelId: string) =>
GET<ShareChatSchema[]>(`/chat/shareChat/list?modelId=${modelId}`);

/**
* delete a shareChat
*/
export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?id=${id}`);

/**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string; password: string }) =>
GET<InitShareChatResponse>(`/chat/shareChat/init?${Obj2Query(data)}`);
10 changes: 10 additions & 0 deletions src/api/response/chat.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,13 @@ export interface InitChatResponse {
chatModel: ModelSchema['chat']['chatModel']; // 对话模型名
history: ChatItemType[];
}

export interface InitShareChatResponse {
maxContext: number;
model: {
name: string;
avatar: string;
intro: string;
};
chatModel: ModelSchema['chat']['chatModel']; // 对话模型名
}
8 changes: 5 additions & 3 deletions src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import NavbarPhone from './navbarPhone';

const pcUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
'/login': true
'/login': true,
'/chat/share': true
};
const phoneUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
'/login': true
'/login': true,
'/chat/share': true
};

const Layout = ({ children, isPcDevice }: { children: JSX.Element; isPcDevice: boolean }) => {
Expand Down Expand Up @@ -67,7 +69,7 @@ const Layout = ({ children, isPcDevice }: { children: JSX.Element; isPcDevice: b
</Flex>
)}
</Box>
{loading && <Loading />}
<Loading loading={loading} />
</>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/Loading/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const Loading = ({ fixed = true }: { fixed?: boolean }) => {
return (
<Flex
position={fixed ? 'fixed' : 'absolute'}
zIndex={100}
zIndex={10000}
backgroundColor={'rgba(255,255,255,0.5)'}
top={0}
left={0}
Expand Down
7 changes: 7 additions & 0 deletions src/constants/model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getSystemModelList } from '@/api/system';
import type { ModelSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/model';

export const embeddingModel = 'text-embedding-ada-002';
export type EmbeddingModelType = 'text-embedding-ada-002';
Expand Down Expand Up @@ -161,3 +162,9 @@ export const defaultModel: ModelSchema = {
maxLoadAmount: 1
}
};

export const defaultShareChat: ShareChatEditType = {
name: '',
password: '',
maxContext: 5
};
11 changes: 3 additions & 8 deletions src/pages/_error.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
function Error({ statusCode }: { statusCode: number }) {
return (
<p>
{statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'}
</p>
);
function Error({ errStr }: { errStr: string }) {
return <p>{errStr}</p>;
}

Error.getInitialProps = ({ res, err }: { res: any; err: any }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
console.log(err);
return { statusCode };
return { errStr: JSON.stringify(err) };
};

export default Error;
130 changes: 130 additions & 0 deletions src/pages/api/chat/shareChat/chat.ts
Original file line number Diff line number Diff line change
@@ -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
});
}
}
}
39 changes: 39 additions & 0 deletions src/pages/api/chat/shareChat/create.ts
Original file line number Diff line number Diff line change
@@ -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
});
}
}
29 changes: 29 additions & 0 deletions src/pages/api/chat/shareChat/delete.ts
Original file line number Diff line number Diff line change
@@ -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
});
}
}
59 changes: 59 additions & 0 deletions src/pages/api/chat/shareChat/init.ts
Original file line number Diff line number Diff line change
@@ -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
});
}
}
Loading

0 comments on commit d31bdf0

Please sign in to comment.