fix: chat page render performance (#2784)

* fix: chat page render performance

* fix: ts
This commit is contained in:
Archer
2024-09-24 18:05:27 +08:00
committed by GitHub
parent afd2c394d8
commit 7aa75f8ee0
7 changed files with 244 additions and 202 deletions

View File

@@ -103,3 +103,4 @@ weight: 813
17. 修复 - 知识库选择权限问题。 17. 修复 - 知识库选择权限问题。
18. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。 18. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。
19. 修复 - createDataset 接口intro 为赋值。 19. 修复 - createDataset 接口intro 为赋值。
20. 修复 - 对话框渲染性能问题。

View File

@@ -11,12 +11,12 @@ import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
import { getChatRecords } from '@/web/core/chat/api'; import { getChatRecords } from '@/web/core/chat/api';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants'; import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { GetChatRecordsProps } from '@/global/core/chat/api';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import { PaginationResponse } from '../../../../../../../packages/web/common/fetch/type'; import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
import type { getPaginationRecordsBody } from '@/pages/api/core/chat/getPaginationRecords'; import type { getPaginationRecordsBody } from '@/pages/api/core/chat/getPaginationRecords';
import { GetChatTypeEnum } from '@/global/core/chat/constants';
export const useChat = () => { export const useChat = (params?: { chatId?: string; appId: string; type?: GetChatTypeEnum }) => {
const ChatBoxRef = useRef<ChatComponentRef>(null); const ChatBoxRef = useRef<ChatComponentRef>(null);
const variablesForm = useForm<ChatBoxInputFormType>(); const variablesForm = useForm<ChatBoxInputFormType>();
// plugin // plugin
@@ -49,38 +49,41 @@ export const useChat = () => {
ChatBoxRef.current?.restartChat?.(); ChatBoxRef.current?.restartChat?.();
}, [variablesForm]); }, [variablesForm]);
const useChatScrollData = useCallback((params: GetChatRecordsProps) => { const {
return useScrollPagination( data: chatRecords,
async (data: getPaginationRecordsBody): Promise<PaginationResponse<ChatSiteItemType>> => { ScrollData,
const res = await getChatRecords(data); setData: setChatRecords,
total: totalRecordsCount
} = useScrollPagination(
async (data: getPaginationRecordsBody): Promise<PaginationResponse<ChatSiteItemType>> => {
const res = await getChatRecords(data);
// First load scroll to bottom // First load scroll to bottom
if (data.offset === 0) { if (data.offset === 0) {
function scrollToBottom() { function scrollToBottom() {
requestAnimationFrame( requestAnimationFrame(
ChatBoxRef?.current ? () => ChatBoxRef?.current?.scrollToBottom?.() : scrollToBottom ChatBoxRef?.current ? () => ChatBoxRef?.current?.scrollToBottom?.() : scrollToBottom
); );
}
scrollToBottom();
} }
scrollToBottom();
return {
...res,
list: res.list.map((item) => ({
...item,
dataId: item.dataId || getNanoid(),
status: ChatStatusEnum.finish
}))
};
},
{
pageSize: 10,
refreshDeps: [params],
params,
scrollLoadType: 'top'
} }
);
}, []); return {
...res,
list: res.list.map((item) => ({
...item,
dataId: item.dataId || getNanoid(),
status: ChatStatusEnum.finish
}))
};
},
{
pageSize: 10,
refreshDeps: [params],
params,
scrollLoadType: 'top'
}
);
return { return {
ChatBoxRef, ChatBoxRef,
@@ -89,7 +92,10 @@ export const useChat = () => {
setPluginRunTab, setPluginRunTab,
clearChatRecords, clearChatRecords,
resetVariables, resetVariables,
useChatScrollData chatRecords,
ScrollData,
setChatRecords,
totalRecordsCount
}; };
}; };

View File

@@ -47,14 +47,11 @@ const DetailLogsModal = ({
pluginRunTab, pluginRunTab,
setPluginRunTab, setPluginRunTab,
resetVariables, resetVariables,
useChatScrollData chatRecords,
} = useChat();
const {
data: chatRecords,
ScrollData, ScrollData,
setData: setChatRecords, setChatRecords,
total: totalRecordsCount totalRecordsCount
} = useChatScrollData(params); } = useChat(params);
const { data: chat, isFetching } = useQuery( const { data: chat, isFetching } = useQuery(
['getChatDetail', chatId], ['getChatDetail', chatId],

View File

@@ -164,4 +164,4 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
); );
}; };
export default SliderApps; export default React.memo(SliderApps);

View File

@@ -77,14 +77,11 @@ const Chat = ({
pluginRunTab, pluginRunTab,
setPluginRunTab, setPluginRunTab,
resetVariables, resetVariables,
useChatScrollData chatRecords,
} = useChat();
const {
data: chatRecords,
ScrollData, ScrollData,
setData: setChatRecords, setChatRecords,
total: totalRecordsCount totalRecordsCount
} = useChatScrollData(params); } = useChat(params);
// get chat app info // get chat app info
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData); const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
@@ -166,6 +163,57 @@ const Chat = ({
); );
const loading = isLoading; const loading = isLoading;
const RenderHistorySlider = useMemo(() => {
const Children = (
<ChatHistorySlider
confirmClearText={t('common:core.chat.Confirm to clear history')}
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
onDelHistory={(e) => onDelHistory({ ...e, appId })}
onClearHistory={() => {
onClearHistories({ appId });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({ ...e, appId });
}}
onSetCustomTitle={async (e) => {
onUpdateHistory({
appId,
chatId: e.chatId,
customTitle: e.title
});
}}
/>
);
return isPc || !appId ? (
<SideBar>{Children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer>
);
}, [
appId,
chatData.app.avatar,
chatData.app.name,
isOpenSlider,
isPc,
onClearHistories,
onCloseSlider,
onDelHistory,
onUpdateHistory,
t
]);
return ( return (
<Flex h={'100%'}> <Flex h={'100%'}>
<NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead> <NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
@@ -179,43 +227,7 @@ const Chat = ({
<PageContainer isLoading={loading} 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']}> <Flex h={'100%'} flexDirection={['column', 'row']}>
{/* pc always show history. */} {/* pc always show history. */}
{((children: React.ReactNode) => { {RenderHistorySlider}
return isPc || !appId ? (
<SideBar>{children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'}>{children}</DrawerContent>
</Drawer>
);
})(
<ChatHistorySlider
confirmClearText={t('common:core.chat.Confirm to clear history')}
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
onDelHistory={(e) => onDelHistory({ ...e, appId })}
onClearHistory={() => {
onClearHistories({ appId });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({ ...e, appId });
}}
onSetCustomTitle={async (e) => {
onUpdateHistory({
appId,
chatId: e.chatId,
customTitle: e.title
});
}}
/>
)}
{/* chat container */} {/* chat container */}
<Flex <Flex
position={'relative'} position={'relative'}

View File

@@ -4,11 +4,7 @@ import { Box, Flex, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/reac
import { streamFetch } from '@/web/common/api/fetch'; import { streamFetch } from '@/web/common/api/fetch';
import SideBar from '@/components/SideBar'; import SideBar from '@/components/SideBar';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWSYZ1234567890_',
24
);
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox'; import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type'; import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
@@ -19,7 +15,6 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { delChatRecordById, getInitOutLinkChatInfo } from '@/web/core/chat/api'; import { delChatRecordById, getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils'; import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type'; import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
import { addLog } from '@fastgpt/service/common/system/log'; import { addLog } from '@fastgpt/service/common/system/log';
@@ -87,14 +82,6 @@ const OutLink = ({
onChangeChatId onChangeChatId
} = useContextSelector(ChatContext, (v) => v); } = useContextSelector(ChatContext, (v) => v);
const {
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
useChatScrollData
} = useChat();
const params = useMemo(() => { const params = useMemo(() => {
return { return {
chatId, chatId,
@@ -105,11 +92,16 @@ const OutLink = ({
}; };
}, [chatData.appId, chatId, outLinkUid, shareId]); }, [chatData.appId, chatId, outLinkUid, shareId]);
const { const {
data: chatRecords, ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
chatRecords,
ScrollData, ScrollData,
setData: setChatRecords, setChatRecords,
total: totalRecordsCount totalRecordsCount
} = useChatScrollData(params); } = useChat(params);
const startChat = useCallback( const startChat = useCallback(
async ({ async ({
@@ -233,6 +225,73 @@ const OutLink = ({
useMount(() => { useMount(() => {
setIdEmbed(window !== top); setIdEmbed(window !== top);
}); });
const RenderHistoryList = useMemo(() => {
const Children = (
<ChatHistorySlider
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('common:core.chat.Confirm to clear share chat history')}
onDelHistory={({ chatId }) =>
onDelHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
}
onClearHistory={() => {
onClearHistories({ shareId, outLinkUid });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({
...e,
appId: chatData.appId,
shareId,
outLinkUid
});
}}
onSetCustomTitle={(e) => {
onUpdateHistory({
appId: chatData.appId,
chatId: e.chatId,
customTitle: e.title,
shareId,
outLinkUid
});
}}
/>
);
if (showHistory !== '1') return null;
return isPc ? (
<SideBar>{Children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
{Children}
</DrawerContent>
</Drawer>
);
}, [
chatData.app.avatar,
chatData.app.name,
chatData.appId,
isOpenSlider,
isPc,
onClearHistories,
onCloseSlider,
onDelHistory,
onUpdateHistory,
outLinkUid,
shareId,
showHistory,
t
]);
const loading = isLoading; const loading = isLoading;
return ( return (
@@ -244,54 +303,7 @@ const OutLink = ({
: { p: [0, 5] })} : { p: [0, 5] })}
> >
<Flex h={'100%'} flexDirection={['column', 'row']}> <Flex h={'100%'} flexDirection={['column', 'row']}>
{showHistory === '1' && {RenderHistoryList}
((children: React.ReactNode) => {
return isPc ? (
<SideBar>{children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
{children}
</DrawerContent>
</Drawer>
);
})(
<ChatHistorySlider
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('common:core.chat.Confirm to clear share chat history')}
onDelHistory={({ chatId }) =>
onDelHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
}
onClearHistory={() => {
onClearHistories({ shareId, outLinkUid });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({
...e,
appId: chatData.appId,
shareId,
outLinkUid
});
}}
onSetCustomTitle={(e) => {
onUpdateHistory({
appId: chatData.appId,
chatId: e.chatId,
customTitle: e.title,
shareId,
outLinkUid
});
}}
/>
)}
{/* chat container */} {/* chat container */}
<Flex <Flex

View File

@@ -67,14 +67,6 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
onChangeChatId onChangeChatId
} = useContextSelector(ChatContext, (v) => v); } = useContextSelector(ChatContext, (v) => v);
const {
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
useChatScrollData
} = useChat();
const params = useMemo(() => { const params = useMemo(() => {
return { return {
appId, appId,
@@ -85,11 +77,16 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
}; };
}, [appId, chatId, teamId, teamToken]); }, [appId, chatId, teamId, teamToken]);
const { const {
data: chatRecords, ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
chatRecords,
ScrollData, ScrollData,
setData: setChatRecords, setChatRecords,
total: totalRecordsCount totalRecordsCount
} = useChatScrollData(params); } = useChat(params);
const startChat = useCallback( const startChat = useCallback(
async ({ async ({
@@ -181,6 +178,61 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
} }
); );
const RenderHistoryList = useMemo(() => {
const Children = (
<ChatHistorySlider
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('common:core.chat.Confirm to clear history')}
onDelHistory={(e) => onDelHistory({ ...e, appId, teamId, teamToken })}
onClearHistory={() => {
onClearHistories({ appId, teamId, teamToken });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({ ...e, teamId, teamToken, appId });
}}
onSetCustomTitle={async (e) => {
onUpdateHistory({
appId,
chatId: e.chatId,
customTitle: e.title,
teamId,
teamToken
});
}}
/>
);
return isPc || !appId ? (
<SideBar>{Children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer>
);
}, [
appId,
chatData.app.avatar,
chatData.app.name,
isOpenSlider,
isPc,
onClearHistories,
onCloseSlider,
onDelHistory,
onUpdateHistory,
t,
teamId,
teamToken
]);
const loading = isLoading; const loading = isLoading;
return ( return (
@@ -195,45 +247,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
<PageContainer isLoading={loading} 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'}> <Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{((children: React.ReactNode) => { {RenderHistoryList}
return isPc || !appId ? (
<SideBar>{children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'}>{children}</DrawerContent>
</Drawer>
);
})(
<ChatHistorySlider
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('common:core.chat.Confirm to clear history')}
onDelHistory={(e) => onDelHistory({ ...e, appId, teamId, teamToken })}
onClearHistory={() => {
onClearHistories({ appId, teamId, teamToken });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({ ...e, teamId, teamToken, appId });
}}
onSetCustomTitle={async (e) => {
onUpdateHistory({
appId,
chatId: e.chatId,
customTitle: e.title,
teamId,
teamToken
});
}}
/>
)}
{/* chat container */} {/* chat container */}
<Flex <Flex
position={'relative'} position={'relative'}