perf: 懒加载和动态加载优化

This commit is contained in:
archer
2023-03-05 21:16:19 +08:00
parent 78903baefa
commit 52a752dab5
14 changed files with 203 additions and 191 deletions

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17" height="12" viewBox="0 0 17 12" fill="none"><g opacity="1" transform="translate(0.70001220703125 0.2001953125) rotate(0 7.5 5.5)"><path id="Path" style="stroke:#A0A5BA; stroke-width:1.4; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(0 5) rotate(0 7.5 0.5)" d="M0,0.5L15,0.5 " /><path id="Path" style="stroke:#A0A5BA; stroke-width:1.4; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(0 0) rotate(0 7.5 0.5)" d="M0,0.5L15,0.5 " /><path id="Path" style="stroke:#A0A5BA; stroke-width:1.4; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(7 10) rotate(0 4 0.5)" d="M0,0.5L8,0.5 " /></g></svg>

Before

Width:  |  Height:  |  Size: 728 B

View File

@@ -25,10 +25,10 @@ const Auth = ({ children }: { children: JSX.Element }) => {
useQuery( useQuery(
[router.pathname, userInfo], [router.pathname, userInfo],
() => { () => {
setLoading(true);
if (unAuthPage[router.pathname] === true || userInfo) { if (unAuthPage[router.pathname] === true || userInfo) {
return setLoading(false); return setLoading(false);
} else { } else {
setLoading(true);
return getTokenLogin(); return getTokenLogin();
} }
}, },

View File

@@ -43,15 +43,13 @@ const navbarList = [
const Layout = ({ children }: { children: JSX.Element }) => { const Layout = ({ children }: { children: JSX.Element }) => {
const { isPc } = useScreen(); const { isPc } = useScreen();
const router = useRouter(); const router = useRouter();
const { Loading } = useLoading({ const { Loading } = useLoading({ defaultLoading: true });
defaultLoading: true
});
const { loading } = useGlobalStore(); const { loading } = useGlobalStore();
return ( return (
<> <>
{!unShowLayoutRoute[router.pathname] ? ( {!unShowLayoutRoute[router.pathname] ? (
<Box data-test="ss" h={'100%'} backgroundColor={'gray.100'} overflow={'auto'}> <Box h={'100%'} backgroundColor={'gray.100'} overflow={'auto'}>
{isPc ? ( {isPc ? (
<> <>
<Box h={'100%'} position={'fixed'} left={0} top={0} w={'80px'}> <Box h={'100%'} position={'fixed'} left={0} top={0} w={'80px'}>

View File

@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, memo } from 'react';
import { Spinner, Flex } from '@chakra-ui/react'; import { Spinner, Flex } from '@chakra-ui/react';
export const useLoading = (props?: { defaultLoading: boolean }) => { export const useLoading = (props?: { defaultLoading: boolean }) => {
@@ -31,6 +31,6 @@ export const useLoading = (props?: { defaultLoading: boolean }) => {
return { return {
isLoading, isLoading,
setIsLoading, setIsLoading,
Loading Loading: memo(Loading)
}; };
}; };

View File

@@ -11,6 +11,6 @@ export function useScreen() {
isPc, isPc,
mediaLgMd: useMemo(() => (isPc ? 'lg' : 'md'), [isPc]), mediaLgMd: useMemo(() => (isPc ? 'lg' : 'md'), [isPc]),
mediaMdSm: useMemo(() => (isPc ? 'md' : 'sm'), [isPc]), mediaMdSm: useMemo(() => (isPc ? 'md' : 'sm'), [isPc]),
media: (pc: number | string, phone: number | string) => (isPc ? pc : phone) media: (pc: any, phone: any) => (isPc ? pc : phone)
}; };
} }

View File

@@ -38,6 +38,7 @@ export default function App({ Component, pageProps }: AppProps) {
/> />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Script src="/iconfont.js" strategy="afterInteractive"></Script>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}> <ChakraProvider theme={theme}>
<Layout> <Layout>
@@ -45,7 +46,6 @@ export default function App({ Component, pageProps }: AppProps) {
</Layout> </Layout>
</ChakraProvider> </ChakraProvider>
</QueryClientProvider> </QueryClientProvider>
<Script src="/iconfont.js"></Script>
</> </>
); );
} }

View File

@@ -13,10 +13,13 @@ import { Textarea, Box, Flex, Button } from '@chakra-ui/react';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import Icon from '@/components/Icon'; import Icon from '@/components/Icon';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import Markdown from '@/components/Markdown';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading'; import { useLoading } from '@/hooks/useLoading';
import { OpenAiModelEnum } from '@/constants/model'; import { OpenAiModelEnum } from '@/constants/model';
import dynamic from 'next/dynamic';
import { useGlobalStore } from '@/store/global';
const Markdown = dynamic(() => import('@/components/Markdown'));
const textareaMinH = '22px'; const textareaMinH = '22px';
@@ -34,7 +37,7 @@ const Chat = () => {
const isChatting = useMemo(() => chatList[chatList.length - 1]?.status === 'loading', [chatList]); const isChatting = useMemo(() => chatList[chatList.length - 1]?.status === 'loading', [chatList]);
const lastWordHuman = useMemo(() => chatList[chatList.length - 1]?.obj === 'Human', [chatList]); const lastWordHuman = useMemo(() => chatList[chatList.length - 1]?.obj === 'Human', [chatList]);
const { Loading, setIsLoading } = useLoading({ defaultLoading: true }); const { setLoading } = useGlobalStore();
// 滚动到底部 // 滚动到底部
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {
@@ -49,7 +52,14 @@ const Chat = () => {
}, []); }, []);
// 初始化聊天框 // 初始化聊天框
useQuery([chatId, windowId], () => (chatId ? getInitChatSiteInfo(chatId, windowId) : null), { useQuery(
[chatId, windowId],
() => {
if (!chatId) return null;
setLoading(true);
return getInitChatSiteInfo(chatId, windowId);
},
{
cacheTime: 5 * 60 * 1000, cacheTime: 5 * 60 * 1000,
onSuccess(res) { onSuccess(res) {
if (!res) return; if (!res) return;
@@ -63,7 +73,7 @@ const Chat = () => {
})) }))
); );
scrollToBottom(); scrollToBottom();
setIsLoading(false); setLoading(false);
}, },
onError() { onError() {
toast({ toast({
@@ -72,9 +82,10 @@ const Chat = () => {
isClosable: true, isClosable: true,
duration: 5000 duration: 5000
}); });
setIsLoading(false); setLoading(false);
} }
}); }
);
// gpt3 方法 // gpt3 方法
const gpt3ChatPrompt = useCallback( const gpt3ChatPrompt = useCallback(
@@ -293,7 +304,7 @@ const Chat = () => {
alt="/imgs/modelAvatar.png" alt="/imgs/modelAvatar.png"
width={30} width={30}
height={30} height={30}
></Image> />
</Box> </Box>
<Box flex={'1 0 0'} w={0} overflowX={'auto'}> <Box flex={'1 0 0'} w={0} overflowX={'auto'}>
{item.obj === 'AI' ? ( {item.obj === 'AI' ? (
@@ -393,7 +404,6 @@ const Chat = () => {
</Box> </Box>
)} )}
</Box> </Box>
<Loading />
</Flex> </Flex>
); );
}; };

View File

@@ -1,12 +1,9 @@
import React, { useEffect } from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { Card } from '@chakra-ui/react';
import { Card, Text, Box, Heading, Flex } from '@chakra-ui/react';
import Markdown from '@/components/Markdown'; import Markdown from '@/components/Markdown';
import { introPage } from '@/constants/common'; import { introPage } from '@/constants/common';
const Home = () => { const Home = () => {
const router = useRouter();
return ( return (
<Card p={5} lineHeight={2}> <Card p={5} lineHeight={2}>
<Markdown source={introPage} isChatting={false} /> <Markdown source={introPage} isChatting={false} />

View File

@@ -1,15 +1,17 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback, useMemo } from 'react';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { Box, Flex, Image } from '@chakra-ui/react'; import { Box, Flex, Image } from '@chakra-ui/react';
import { PageTypeEnum } from '@/constants/user'; import { PageTypeEnum } from '@/constants/user';
import LoginForm from './components/LoginForm';
import RegisterForm from './components/RegisterForm';
import ForgetPasswordForm from './components/ForgetPasswordForm';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import type { ResLogin } from '@/api/response/user'; import type { ResLogin } from '@/api/response/user';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
import dynamic from 'next/dynamic';
const LoginForm = dynamic(() => import('./components/LoginForm'));
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
const Login = () => { const Login = () => {
const router = useRouter(); const router = useRouter();
const { isPc } = useScreen(); const { isPc } = useScreen();
@@ -24,21 +26,18 @@ const Login = () => {
[router, setUserInfo] [router, setUserInfo]
); );
const map = { function DynamicComponent({ type }: { type: `${PageTypeEnum}` }) {
[PageTypeEnum.login]: { const TypeMap = {
Component: <LoginForm setPageType={setPageType} loginSuccess={loginSuccess} />, [PageTypeEnum.login]: LoginForm,
img: '/icon/loginLeft.svg' [PageTypeEnum.register]: RegisterForm,
}, [PageTypeEnum.forgetPassword]: ForgetPasswordForm
[PageTypeEnum.register]: {
Component: <RegisterForm setPageType={setPageType} loginSuccess={loginSuccess} />,
img: '/icon/loginLeft.svg'
},
[PageTypeEnum.forgetPassword]: {
Component: <ForgetPasswordForm setPageType={setPageType} loginSuccess={loginSuccess} />,
img: '/icon/loginLeft.svg'
}
}; };
const Component = TypeMap[type];
return <Component setPageType={setPageType} loginSuccess={loginSuccess} />;
}
return ( return (
<Box className={styles.loginPage} h={'100%'} p={isPc ? '10vh 10vw' : 0}> <Box className={styles.loginPage} h={'100%'} p={isPc ? '10vh 10vw' : 0}>
<Flex <Flex
@@ -54,7 +53,7 @@ const Login = () => {
> >
{isPc && ( {isPc && (
<Image <Image
src={map[pageType].img} src={'/icon/loginLeft.svg'}
order={pageType === PageTypeEnum.login ? 0 : 2} order={pageType === PageTypeEnum.login ? 0 : 2}
flex={'1 0 0'} flex={'1 0 0'}
w="0" w="0"
@@ -76,7 +75,7 @@ const Login = () => {
px={10} px={10}
borderRadius={isPc ? 'md' : 'none'} borderRadius={isPc ? 'md' : 'none'}
> >
{map[pageType].Component} <DynamicComponent type={pageType} />
</Box> </Box>
</Flex> </Flex>
</Box> </Box>

View File

@@ -25,11 +25,9 @@ interface CreateFormType {
} }
const CreateModel = ({ const CreateModel = ({
isOpen,
setCreateModelOpen, setCreateModelOpen,
onSuccess onSuccess
}: { }: {
isOpen: boolean;
setCreateModelOpen: Dispatch<boolean>; setCreateModelOpen: Dispatch<boolean>;
onSuccess: Dispatch<ModelType>; onSuccess: Dispatch<ModelType>;
}) => { }) => {
@@ -72,7 +70,7 @@ const CreateModel = ({
return ( return (
<> <>
<Modal isOpen={isOpen} onClose={() => setCreateModelOpen(false)}> <Modal isOpen={true} onClose={() => setCreateModelOpen(false)}>
<ModalOverlay /> <ModalOverlay />
<ModalContent> <ModalContent>
<ModalHeader></ModalHeader> <ModalHeader></ModalHeader>

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import { Grid, Box, Card, Flex, Button, FormControl, Input, Textarea } from '@chakra-ui/react'; import { Grid, Box, Card, Flex, Button, FormControl, Input, Textarea } from '@chakra-ui/react';
import type { ModelType } from '@/types/model'; import type { ModelType } from '@/types/model';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -7,17 +7,17 @@ import { putModelById } from '@/api/model';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
const ModelEditForm = ({ model }: { model: ModelType }) => { const ModelEditForm = ({ model }: { model?: ModelType }) => {
const isInit = useRef(false);
const { const {
register, register,
handleSubmit, handleSubmit,
reset,
formState: { errors } formState: { errors }
} = useForm<ModelType>({ } = useForm<ModelType>();
defaultValues: model
});
const { setLoading } = useGlobalStore(); const { setLoading } = useGlobalStore();
const { toast } = useToast(); const { toast } = useToast();
const { isPc } = useScreen(); const { media } = useScreen();
const onclickSave = useCallback( const onclickSave = useCallback(
async (data: ModelType) => { async (data: ModelType) => {
@@ -61,8 +61,16 @@ const ModelEditForm = ({ model }: { model: ModelType }) => {
}); });
}, [errors, toast]); }, [errors, toast]);
/* model 只会改变一次 */
useEffect(() => {
if (model && !isInit.current) {
reset(model);
isInit.current = true;
}
}, [model, reset]);
return ( return (
<Grid gridTemplateColumns={isPc ? '1fr 1fr' : '1fr'} gridGap={5}> <Grid gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
<Card p={4}> <Card p={4}>
<Flex justifyContent={'space-between'} alignItems={'center'}> <Flex justifyContent={'space-between'} alignItems={'center'}>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'} fontSize={'lg'}>
@@ -83,7 +91,7 @@ const ModelEditForm = ({ model }: { model: ModelType }) => {
<FormControl mt={5}> <FormControl mt={5}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box flex={'0 0 80px'}>:</Box> <Box flex={'0 0 80px'}>:</Box>
<Box>{model.service.modelName}</Box> <Box>{model?.service.modelName}</Box>
</Flex> </Flex>
</FormControl> </FormControl>
<FormControl mt={5}> <FormControl mt={5}>

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useCallback, useState } from 'react'; import React, { useEffect, useCallback, useState } from 'react';
import { Box, Card, TableContainer, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react'; import { Box, TableContainer, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react';
import { ModelType } from '@/types/model'; import { ModelType } from '@/types/model';
import { getModelTrainings } from '@/api/model'; import { getModelTrainings } from '@/api/model';
import type { TrainingItemType } from '@/types/training'; import type { TrainingItemType } from '@/types/training';
@@ -38,7 +38,7 @@ const Training = ({ model }: { model: ModelType }) => {
}, [loadTrainingRecords, model]); }, [loadTrainingRecords, model]);
return ( return (
<Card p={4} h={'100%'}> <>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'} fontSize={'lg'}>
: {model.trainingTimes} : {model.trainingTimes}
</Box> </Box>
@@ -63,7 +63,7 @@ const Training = ({ model }: { model: ModelType }) => {
</Tbody> </Tbody>
</Table> </Table>
</TableContainer> </TableContainer>
</Card> </>
); );
}; };

View File

@@ -11,12 +11,14 @@ import { useGlobalStore } from '@/store/global';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import ModelEditForm from './components/ModelEditForm'; import ModelEditForm from './components/ModelEditForm';
import Icon from '@/components/Icon'; import Icon from '@/components/Icon';
import Training from './components/Training'; import dynamic from 'next/dynamic';
const Training = dynamic(() => import('./components/Training'));
const ModelDetail = () => { const ModelDetail = () => {
const { toast } = useToast(); const { toast } = useToast();
const router = useRouter(); const router = useRouter();
const { isPc } = useScreen(); const { isPc, media } = useScreen();
const { setLoading } = useGlobalStore(); const { setLoading } = useGlobalStore();
const { openConfirm, ConfirmChild } = useConfirm({ const { openConfirm, ConfirmChild } = useConfirm({
content: '确认删除该模型?' content: '确认删除该模型?'
@@ -127,16 +129,15 @@ const ModelDetail = () => {
}, [setLoading, loadModel, model]); }, [setLoading, loadModel, model]);
return ( return (
<>
{!!model && (
<> <>
{/* 头部 */} {/* 头部 */}
<Card px={6} py={3}> <Card px={6} py={3}>
{isPc ? ( {isPc ? (
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box fontSize={'xl'} fontWeight={'bold'}> <Box fontSize={'xl'} fontWeight={'bold'}>
{model.name} {model?.name || '模型'}
</Box> </Box>
{!!model && (
<Tag <Tag
ml={2} ml={2}
variant="solid" variant="solid"
@@ -146,6 +147,7 @@ const ModelDetail = () => {
> >
{formatModelStatus[model.status].text} {formatModelStatus[model.status].text}
</Tag> </Tag>
)}
<Box flex={1} /> <Box flex={1} />
<Button variant={'outline'} onClick={handlePreviewChat}> <Button variant={'outline'} onClick={handlePreviewChat}>
@@ -155,11 +157,13 @@ const ModelDetail = () => {
<> <>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}> <Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}>
{model.name} {model?.name || '模型'}
</Box> </Box>
{!!model && (
<Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}> <Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}>
{formatModelStatus[model.status].text} {formatModelStatus[model.status].text}
</Tag> </Tag>
)}
</Flex> </Flex>
<Box mt={4} textAlign={'right'}> <Box mt={4} textAlign={'right'}>
<Button variant={'outline'} onClick={handlePreviewChat}> <Button variant={'outline'} onClick={handlePreviewChat}>
@@ -174,9 +178,9 @@ const ModelDetail = () => {
<ModelEditForm model={model} /> <ModelEditForm model={model} />
</Box> </Box>
{/* 其他配置 */} {/* 其他配置 */}
<Grid mt={5} gridTemplateColumns={isPc ? '1fr 1fr' : '1fr'} gridGap={5}> <Grid mt={5} gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
<Training model={model} /> <Card p={4}>{!!model && <Training model={model} />}</Card>
<Card h={'100%'} p={4}> <Card p={4}>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'} fontSize={'lg'}>
</Box> </Box>
@@ -234,8 +238,6 @@ const ModelDetail = () => {
</Flex> </Flex>
</Card> </Card>
</Grid> </Grid>
</>
)}
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}> <Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
<input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} /> <input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} />
</Box> </Box>

View File

@@ -1,15 +1,17 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { Box, Button, Flex, Card } from '@chakra-ui/react'; import { Box, Button, Flex, Card } from '@chakra-ui/react';
import { getMyModels } from '@/api/model'; import { getMyModels } from '@/api/model';
import { getChatSiteId } from '@/api/chat'; import { getChatSiteId } from '@/api/chat';
import { ModelType } from '@/types/model'; import { ModelType } from '@/types/model';
import CreateModel from './components/CreateModel';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import ModelTable from './components/ModelTable'; import ModelTable from './components/ModelTable';
import ModelPhoneList from './components/ModelPhoneList'; import ModelPhoneList from './components/ModelPhoneList';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading'; import { useLoading } from '@/hooks/useLoading';
import dynamic from 'next/dynamic';
const CreateModel = dynamic(() => import('./components/CreateModel'));
const ModelList = () => { const ModelList = () => {
const { isPc } = useScreen(); const { isPc } = useScreen();
@@ -72,11 +74,10 @@ const ModelList = () => {
)} )}
</Box> </Box>
{/* 创建弹窗 */} {/* 创建弹窗 */}
<CreateModel {openCreateModel && (
isOpen={openCreateModel} <CreateModel setCreateModelOpen={setOpenCreateModel} onSuccess={createModelSuccess} />
setCreateModelOpen={setOpenCreateModel} )}
onSuccess={createModelSuccess}
/>
<Loading loading={isLoading} /> <Loading loading={isLoading} />
</Box> </Box>
); );