4.8.5 test (#1819)

This commit is contained in:
Archer
2024-06-21 18:32:05 +08:00
committed by GitHub
parent 5cc01b8509
commit 24596a6e21
40 changed files with 908 additions and 1058 deletions

View File

@@ -1,25 +1,12 @@
import React, { useCallback, useRef } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { useRouter } from 'next/router';
import { getInitChatInfo } from '@/web/core/chat/api';
import {
Box,
Flex,
useDisclosure,
Drawer,
DrawerOverlay,
DrawerContent,
useTheme
} from '@chakra-ui/react';
import { delChatRecordById, getChatHistories, getInitChatInfo } from '@/web/core/chat/api';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useQuery } from '@tanstack/react-query';
import { streamFetch } from '@/web/common/api/fetch';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { useTranslation } from 'next-i18next';
import ChatBox from '@/components/ChatBox';
@@ -29,7 +16,6 @@ import SideBar from '@/components/SideBar';
import ChatHistorySlider from './components/ChatHistorySlider';
import SliderApps from './components/SliderApps';
import ChatHeader from './components/ChatHeader';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useUserStore } from '@/web/support/user/useUserStore';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
@@ -39,40 +25,48 @@ import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { getMyApps } from '@/web/core/app/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
import { useMount } from 'ahooks';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData } from '@/global/core/chat/constants';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector';
type Props = { appId: string; chatId: string };
const Chat = ({
appId,
chatId,
myApps
}: Props & {
myApps: AppListItemType[];
}) => {
const router = useRouter();
const theme = useTheme();
const { t } = useTranslation();
const { toast } = useToast();
const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const { setLastChatAppId } = useChatStore();
const {
lastChatAppId,
setLastChatAppId,
lastChatId,
setLastChatId,
histories,
loadHistories,
pushHistory,
updateHistory,
delOneHistory,
clearHistories,
chatData,
setChatData,
delOneHistoryItem
} = useChatStore();
const { userInfo } = useUserStore();
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const { userInfo } = useUserStore();
const { isPc } = useSystemStore();
const { Loading, setIsLoading } = useLoading();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const prompts = messages.slice(-2);
const completionChatId = chatId ? chatId : nanoid();
const completionChatId = chatId ? chatId : getNanoid();
const { responseText, responseData } = await streamFetch({
data: {
@@ -89,170 +83,82 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
chatId: completionChatId,
appId
}
});
onChangeChatId(completionChatId, true);
loadHistories();
}
} else {
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle
});
onUpdateHistory({
appId,
chatId: completionChatId,
title: newTitle
});
}
// update chat window
setChatData((state) => ({
...state,
title: newTitle,
history: ChatBoxRef.current?.getChatHistories() || state.history
title: newTitle
}));
return { responseText, responseData, isNewChat: forbidRefresh.current };
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
);
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
() => getMyApps({ getRecentlyChat: true }),
{
manual: false
}
[appId, chatId, forbidLoadChat, loadHistories, onChangeChatId, onUpdateHistory]
);
// get chat app info
const loadChatInfo = useCallback(
async ({
appId,
chatId,
loading = false
}: {
appId: string;
chatId: string;
loading?: boolean;
}) => {
try {
loading && setIsLoading(true);
const res = await getInitChatInfo({ appId, chatId });
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish
}));
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { loading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
setChatData({
...res,
history
});
const res = await getInitChatInfo({ appId, chatId });
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || getNanoid(),
status: ChatStatusEnum.finish
}));
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
// reset all chat tore
const result: InitChatResponse = {
...res,
history
};
// reset chat box
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
setLastChatAppId(appId);
setChatData(result);
},
{
manual: false,
refreshDeps: [appId, chatId],
onError(e: any) {
setLastChatAppId('');
setLastChatId('');
toast({
title: getErrText(e, t('core.chat.Failed to initialize chat')),
status: 'error'
});
// reset all chat tore
if (e?.code === 501) {
router.replace('/app/list');
} else if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
setIsLoading(false);
return null;
},
[setIsLoading, setChatData, setLastChatAppId, setLastChatId, toast, t, router]
}
);
// 初始化聊天框
useQuery(['init', { appId, chatId }], () => {
// pc: redirect to latest model chat
if (!appId && lastChatAppId) {
return router.replace({
query: {
appId: lastChatAppId,
chatId: lastChatId
}
});
}
if (!appId && myApps[0]) {
return router.replace({
query: {
appId: myApps[0]._id,
chatId: lastChatId
}
});
}
if (!appId) {
(async () => {
const apps = await loadMyApps();
if (apps.length === 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
router.replace('/app/list');
} else {
router.replace({
query: {
appId: apps[0]._id,
chatId: lastChatId
}
});
}
})();
return;
}
// store id
appId && setLastChatAppId(appId);
setLastChatId(chatId);
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
}
return loadChatInfo({
appId,
chatId,
loading: appId !== chatData.appId
});
});
useQuery(['loadHistories', appId], () => (appId ? loadHistories({ appId }) : null));
return (
<Flex h={'100%'}>
<NextHead title={chatData.app.name}></NextHead>
<NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
{/* pc show myself apps */}
{isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
@@ -260,7 +166,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
</Box>
)}
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<PageContainer isLoading={loading} flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{/* pc always show history. */}
{((children: React.ReactNode) => {
@@ -285,42 +191,17 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
activeChatId={chatId}
onClose={onCloseSlider}
history={histories.map((item, i) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}))}
onChangeChat={(chatId) => {
router.replace({
query: {
chatId: chatId || '',
appId
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={(e) => delOneHistory({ ...e, appId })}
onDelHistory={(e) => onDelHistory({ ...e, appId })}
onClearHistory={() => {
clearHistories({ appId });
router.replace({
query: {
appId
}
});
onClearHistories({ appId });
}}
onSetHistoryTop={(e) => {
updateHistory({ ...e, appId });
onUpdateHistory({ ...e, appId });
}}
onSetCustomTitle={async (e) => {
updateHistory({
onUpdateHistory({
appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title
});
}}
@@ -340,7 +221,6 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appName={chatData.app.name}
history={chatData.history}
chatModels={chatData.app.chatModels}
onOpenSlider={onOpenSlider}
onRoute2AppDetail={() => router.push(`/app/detail?appId=${appId}`)}
showHistory
/>
@@ -356,19 +236,81 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'}
onStartChat={startChat}
onDelMessage={(e) => delOneHistoryItem({ ...e, appId, chatId })}
onDelMessage={({ contentId }) => delChatRecordById({ contentId, appId, chatId })}
appId={appId}
chatId={chatId}
/>
</Box>
</Flex>
</Flex>
<Loading fixed={false} />
</PageContainer>
</Flex>
);
};
const Render = (props: Props) => {
const { appId } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { lastChatAppId, lastChatId } = useChatStore();
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
() => getMyApps({ getRecentlyChat: true }),
{
manual: false
}
);
const { data: histories = [], runAsync: loadHistories } = useRequest2(
() => (appId ? getChatHistories({ appId }) : Promise.resolve([])),
{
manual: false,
refreshDeps: [appId]
}
);
// 初始化聊天框
useMount(async () => {
// pc: redirect to latest model chat
if (!appId) {
if (lastChatAppId) {
return router.replace({
query: {
...router.query,
appId: lastChatAppId,
chatId: lastChatId
}
});
}
const apps = await loadMyApps();
if (apps.length === 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
router.replace('/app/list');
} else {
router.replace({
query: {
...router.query,
appId: apps[0]._id,
chatId: ''
}
});
}
}
});
return (
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
<Chat {...props} myApps={myApps} />
</ChatContextProvider>
);
};
export async function getServerSideProps(context: any) {
return {
props: {
@@ -379,4 +321,4 @@ export async function getServerSideProps(context: any) {
};
}
export default Chat;
export default Render;