From 277494085179b73da587afabe579b792e42210d0 Mon Sep 17 00:00:00 2001
From: archer <545436317@qq.com>
Date: Sun, 23 Apr 2023 16:06:16 +0800
Subject: [PATCH] feat: history chat
---
src/api/chat.ts | 11 ++
src/components/Icon/icons/dbModel.svg | 1 +
src/components/Icon/icons/history.svg | 1 +
src/components/Icon/index.tsx | 4 +-
src/constants/model.ts | 3 +
src/pages/api/chat/getHistory.ts | 31 +++++
src/pages/api/chat/removeHistory.ts | 27 +++++
src/pages/api/chat/saveChat.ts | 3 +-
src/pages/chat/components/SlideBar.tsx | 151 +++++++++++++------------
src/pages/chat/index.tsx | 23 +---
src/service/models/chat.ts | 4 +
src/store/chat.ts | 50 --------
12 files changed, 162 insertions(+), 147 deletions(-)
create mode 100644 src/components/Icon/icons/dbModel.svg
create mode 100644 src/components/Icon/icons/history.svg
create mode 100644 src/pages/api/chat/getHistory.ts
create mode 100644 src/pages/api/chat/removeHistory.ts
delete mode 100644 src/store/chat.ts
diff --git a/src/api/chat.ts b/src/api/chat.ts
index b788ac57f..8367d3614 100644
--- a/src/api/chat.ts
+++ b/src/api/chat.ts
@@ -26,6 +26,17 @@ export const postGPT3SendPrompt = ({
}))
});
+/**
+ * 获取历史记录
+ */
+export const getChatHistory = () =>
+ GET<{ _id: string; title: string; modelId: string }[]>('/chat/getHistory');
+
+/**
+ * 删除一条历史记录
+ */
+export const delChatHistoryById = (id: string) => GET(`/chat/removeHistory?id=${id}`);
+
/**
* 存储一轮对话
*/
diff --git a/src/components/Icon/icons/dbModel.svg b/src/components/Icon/icons/dbModel.svg
new file mode 100644
index 000000000..b0fe76aa6
--- /dev/null
+++ b/src/components/Icon/icons/dbModel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/Icon/icons/history.svg b/src/components/Icon/icons/history.svg
new file mode 100644
index 000000000..6f9bbaa66
--- /dev/null
+++ b/src/components/Icon/icons/history.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx
index a9ba353d4..7db6b50bd 100644
--- a/src/components/Icon/index.tsx
+++ b/src/components/Icon/index.tsx
@@ -16,7 +16,9 @@ const map = {
chatting: require('./icons/chatting.svg').default,
promotion: require('./icons/promotion.svg').default,
delete: require('./icons/delete.svg').default,
- withdraw: require('./icons/withdraw.svg').default
+ withdraw: require('./icons/withdraw.svg').default,
+ dbModel: require('./icons/dbModel.svg').default,
+ history: require('./icons/history.svg').default
};
export type IconName = keyof typeof map;
diff --git a/src/constants/model.ts b/src/constants/model.ts
index 90610fde8..6956bbbf5 100644
--- a/src/constants/model.ts
+++ b/src/constants/model.ts
@@ -18,6 +18,7 @@ export const ChatModelNameMap = {
};
export type ModelConstantsData = {
+ icon: 'model' | 'dbModel';
name: string;
model: `${ChatModelNameEnum}`;
trainName: string; // 空字符串代表不能训练
@@ -29,6 +30,7 @@ export type ModelConstantsData = {
export const modelList: ModelConstantsData[] = [
{
+ icon: 'model',
name: 'chatGPT',
model: ChatModelNameEnum.GPT35,
trainName: '',
@@ -38,6 +40,7 @@ export const modelList: ModelConstantsData[] = [
price: 3
},
{
+ icon: 'dbModel',
name: '知识库',
model: ChatModelNameEnum.VECTOR_GPT,
trainName: 'vector',
diff --git a/src/pages/api/chat/getHistory.ts b/src/pages/api/chat/getHistory.ts
new file mode 100644
index 000000000..298177a82
--- /dev/null
+++ b/src/pages/api/chat/getHistory.ts
@@ -0,0 +1,31 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { jsonRes } from '@/service/response';
+import { connectToDatabase, Chat } from '@/service/mongo';
+import { authToken } from '@/service/utils/tools';
+
+/* 获取历史记录 */
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const userId = await authToken(req.headers.authorization);
+
+ await connectToDatabase();
+
+ const data = await Chat.find(
+ {
+ userId
+ },
+ '_id title modelId'
+ )
+ .sort({ updateTime: -1 })
+ .limit(20);
+
+ jsonRes(res, {
+ data
+ });
+ } catch (err) {
+ jsonRes(res, {
+ code: 500,
+ error: err
+ });
+ }
+}
diff --git a/src/pages/api/chat/removeHistory.ts b/src/pages/api/chat/removeHistory.ts
new file mode 100644
index 000000000..27dca27f0
--- /dev/null
+++ b/src/pages/api/chat/removeHistory.ts
@@ -0,0 +1,27 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { jsonRes } from '@/service/response';
+import { ChatItemType } from '@/types/chat';
+import { connectToDatabase, Chat } from '@/service/mongo';
+import { authToken } from '@/service/utils/tools';
+
+/* 获取历史记录 */
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const { id } = req.query;
+ const userId = await authToken(req.headers.authorization);
+
+ await connectToDatabase();
+
+ await Chat.findOneAndRemove({
+ _id: id,
+ userId
+ });
+
+ jsonRes(res);
+ } catch (err) {
+ jsonRes(res, {
+ code: 500,
+ error: err
+ });
+ }
+}
diff --git a/src/pages/api/chat/saveChat.ts b/src/pages/api/chat/saveChat.ts
index bb62e144f..1dbc11a24 100644
--- a/src/pages/api/chat/saveChat.ts
+++ b/src/pages/api/chat/saveChat.ts
@@ -33,7 +33,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { _id } = await Chat.create({
userId,
modelId,
- content
+ content,
+ title: content[0].value.slice(0, 20)
});
return jsonRes(res, {
data: _id
diff --git a/src/pages/chat/components/SlideBar.tsx b/src/pages/chat/components/SlideBar.tsx
index d8ad06513..6aa2c9093 100644
--- a/src/pages/chat/components/SlideBar.tsx
+++ b/src/pages/chat/components/SlideBar.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useRef, useEffect } from 'react';
import { AddIcon, ChatIcon, DeleteIcon, MoonIcon, SunIcon } from '@chakra-ui/icons';
import {
Box,
@@ -16,14 +16,13 @@ import {
useColorModeValue
} from '@chakra-ui/react';
import { useUserStore } from '@/store/user';
-import { useChatStore } from '@/store/chat';
-import { useQuery } from '@tanstack/react-query';
+import { useMutation, useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { getToken } from '@/utils/user';
import MyIcon from '@/components/Icon';
-import { useCopyData } from '@/utils/tools';
import WxConcat from '@/components/WxConcat';
-import { useMarkdown } from '@/hooks/useMarkdown';
+import { getChatHistory, delChatHistoryById } from '@/api/chat';
+import { modelList } from '@/constants/model';
const SlideBar = ({
chatId,
@@ -38,27 +37,34 @@ const SlideBar = ({
}) => {
const router = useRouter();
const { colorMode, toggleColorMode } = useColorMode();
- const { copyData } = useCopyData();
const { myModels, getMyModels } = useUserStore();
- const { chatHistory, removeChatHistoryByWindowId } = useChatStore();
- const [hasReady, setHasReady] = useState(false);
- const { isOpen: isOpenShare, onOpen: onOpenShare, onClose: onCloseShare } = useDisclosure();
const { isOpen: isOpenWx, onOpen: onOpenWx, onClose: onCloseWx } = useDisclosure();
- const { data: shareHint } = useMarkdown({ url: '/chatProblem.md' });
+ const preChatId = useRef('chatId'); // 用于校验上一次chatId的情况,判断是否需要刷新历史记录
- const { isSuccess } = useQuery(['init'], getMyModels, {
+ const { isSuccess } = useQuery(['getMyModels'], getMyModels, {
cacheTime: 5 * 60 * 1000
});
+ const { data: chatHistory = [], mutate: loadChatHistory } = useMutation({
+ mutationFn: getChatHistory
+ });
+
useEffect(() => {
- setHasReady(true);
- }, []);
+ if (chatId && preChatId.current === '') {
+ loadChatHistory();
+ }
+ preChatId.current = chatId;
+ }, [chatId, loadChatHistory]);
+
+ useEffect(() => {
+ loadChatHistory();
+ }, [loadChatHistory]);
const RenderHistory = () => (
<>
{chatHistory.map((item) => (
{
- if (item.chatId === chatId) return;
- resetChat(modelId, item.chatId);
+ if (item._id === chatId) return;
+ preChatId.current = 'chatId';
+ resetChat(item.modelId, item._id);
onClose();
}}
>
@@ -91,12 +98,14 @@ const SlideBar = ({
variant={'unstyled'}
aria-label={'edit'}
size={'xs'}
- onClick={(e) => {
- removeChatHistoryByWindowId(item.chatId);
- if (item.chatId === chatId) {
+ onClick={async (e) => {
+ e.stopPropagation();
+
+ await delChatHistoryById(item._id);
+ loadChatHistory();
+ if (item._id === chatId) {
resetChat();
}
- e.stopPropagation();
}}
/>
@@ -151,53 +160,55 @@ const SlideBar = ({
新对话
)}
-
{/* 我的模型 & 历史记录 折叠框*/}
-
- {isSuccess && (
-
-
-
- 其他模型
-
-
-
-
- {myModels.map((item) => (
- {
- if (item._id === modelId) return;
- resetChat(item._id);
- onClose();
- }}
- >
-
-
- {item.name}
-
-
- ))}
-
-
- )}
+ {isSuccess && (
+ <>
+
+ {myModels.map((item) => (
+ {
+ if (item._id === modelId) return;
+ resetChat(item._id);
+ onClose();
+ }}
+ >
+ model.model === item.service.modelName)?.icon ||
+ 'model'
+ }
+ mr={2}
+ color={'white'}
+ w={'16px'}
+ h={'16px'}
+ />
+
+ {item.name}
+
+
+ ))}
+
+ >
+ )}
+
@@ -206,7 +217,7 @@ const SlideBar = ({
- {hasReady && }
+
@@ -221,12 +232,6 @@ const SlideBar = ({
>
- {/*
- <>
-
- 分享
- >
- */}
router.push('/number/setting')}>
<>
diff --git a/src/pages/chat/index.tsx b/src/pages/chat/index.tsx
index 44220e597..c2fa7f387 100644
--- a/src/pages/chat/index.tsx
+++ b/src/pages/chat/index.tsx
@@ -24,7 +24,6 @@ import { useQuery } from '@tanstack/react-query';
import { ChatModelNameEnum } from '@/constants/model';
import dynamic from 'next/dynamic';
import { useGlobalStore } from '@/store/global';
-import { useChatStore } from '@/store/chat';
import { useCopyData } from '@/utils/tools';
import { streamFetch } from '@/api/fetch';
import Icon from '@/components/Icon';
@@ -72,7 +71,6 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
const { copyData } = useCopyData();
const { isPc, media } = useScreen();
const { setLoading } = useGlobalStore();
- const { pushChatHistory } = useChatStore();
// 滚动到底部
const scrollToBottom = useCallback((behavior: 'smooth' | 'auto' = 'smooth') => {
@@ -311,15 +309,6 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
try {
await gptChatPrompt(newChatList[newChatList.length - 2]);
-
- // 如果是 Human 第一次发送,插入历史记录
- const humanChat = newChatList.filter((item) => item.obj === 'Human');
- if (humanChat.length === 1) {
- pushChatHistory({
- chatId,
- title: humanChat[0].value
- });
- }
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : err?.message || '聊天出错了~',
@@ -335,17 +324,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
history: newChatList.slice(0, newChatList.length - 2)
}));
}
- }, [
- isChatting,
- inputVal,
- chatData.history,
- resetInputVal,
- toast,
- scrollToBottom,
- gptChatPrompt,
- pushChatHistory,
- chatId
- ]);
+ }, [isChatting, inputVal, chatData.history, resetInputVal, toast, scrollToBottom, gptChatPrompt]);
// 删除一句话
const delChatRecord = useCallback(
diff --git a/src/service/models/chat.ts b/src/service/models/chat.ts
index f2cd829f0..c6fca5d5a 100644
--- a/src/service/models/chat.ts
+++ b/src/service/models/chat.ts
@@ -26,6 +26,10 @@ const ChatSchema = new Schema({
type: Date,
default: () => new Date()
},
+ title: {
+ type: String,
+ default: '历史记录'
+ },
content: {
type: [
{
diff --git a/src/store/chat.ts b/src/store/chat.ts
deleted file mode 100644
index b06147056..000000000
--- a/src/store/chat.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { create } from 'zustand';
-import { devtools, persist } from 'zustand/middleware';
-import { immer } from 'zustand/middleware/immer';
-import type { HistoryItem } from '@/types/chat';
-
-type Props = {
- chatHistory: HistoryItem[];
- pushChatHistory: (e: HistoryItem) => void;
- updateChatHistory: (chatId: string, title: string) => void;
- removeChatHistoryByWindowId: (chatId: string) => void;
- clearHistory: () => void;
-};
-export const useChatStore = create()(
- devtools(
- persist(
- immer((set, get) => ({
- chatHistory: [],
- pushChatHistory(item: HistoryItem) {
- set((state) => {
- if (state.chatHistory.find((history) => history.chatId === item.chatId)) return;
- state.chatHistory = [item, ...state.chatHistory].slice(0, 20);
- });
- },
- updateChatHistory(chatId: string, title: string) {
- set((state) => {
- state.chatHistory = state.chatHistory.map((item) => ({
- ...item,
- title: item.chatId === chatId ? title : item.title
- }));
- });
- },
- removeChatHistoryByWindowId(chatId: string) {
- set((state) => {
- state.chatHistory = state.chatHistory.filter((item) => item.chatId !== chatId);
- });
- },
- clearHistory() {
- set((state) => {
- state.chatHistory = [];
- });
- }
- })),
- {
- name: 'chatHistory'
- // serialize: JSON.stringify,
- // deserialize: (data) => (data ? JSON.parse(data) : []),
- }
- )
- )
-);