mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 08:25:07 +00:00
perf: my models
This commit is contained in:
@@ -1,76 +0,0 @@
|
|||||||
import React, { useRef, useEffect, useMemo } from 'react';
|
|
||||||
import type { BoxProps } from '@chakra-ui/react';
|
|
||||||
import { Box } from '@chakra-ui/react';
|
|
||||||
import { throttle } from 'lodash';
|
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
|
||||||
|
|
||||||
interface Props extends BoxProps {
|
|
||||||
nextPage: () => void;
|
|
||||||
isLoadAll: boolean;
|
|
||||||
requesting: boolean;
|
|
||||||
children: React.ReactNode;
|
|
||||||
initRequesting?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ScrollData = ({
|
|
||||||
children,
|
|
||||||
nextPage,
|
|
||||||
isLoadAll,
|
|
||||||
requesting,
|
|
||||||
initRequesting,
|
|
||||||
...props
|
|
||||||
}: Props) => {
|
|
||||||
const { Loading } = useLoading({ defaultLoading: true });
|
|
||||||
const elementRef = useRef<HTMLDivElement>(null);
|
|
||||||
const loadText = useMemo(() => {
|
|
||||||
if (requesting) return '请求中……';
|
|
||||||
if (isLoadAll) return '已加载全部';
|
|
||||||
return '点击加载更多';
|
|
||||||
}, [isLoadAll, requesting]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!elementRef.current) return;
|
|
||||||
|
|
||||||
const scrolling = throttle((e: Event) => {
|
|
||||||
const element = e.target as HTMLDivElement;
|
|
||||||
if (!element) return;
|
|
||||||
// 当前滚动位置
|
|
||||||
const scrollTop = element.scrollTop;
|
|
||||||
// 可视高度
|
|
||||||
const clientHeight = element.clientHeight;
|
|
||||||
// 内容总高度
|
|
||||||
const scrollHeight = element.scrollHeight;
|
|
||||||
// 判断是否滚动到底部
|
|
||||||
if (scrollTop + clientHeight + 100 >= scrollHeight) {
|
|
||||||
nextPage();
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
elementRef.current.addEventListener('scroll', scrolling);
|
|
||||||
return () => {
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
elementRef.current?.removeEventListener('scroll', scrolling);
|
|
||||||
};
|
|
||||||
}, [elementRef, nextPage]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box {...props} ref={elementRef} overflowY={'auto'} position={'relative'}>
|
|
||||||
{children}
|
|
||||||
<Box
|
|
||||||
mt={2}
|
|
||||||
fontSize={'xs'}
|
|
||||||
color={'blackAlpha.500'}
|
|
||||||
textAlign={'center'}
|
|
||||||
cursor={loadText === '点击加载更多' ? 'pointer' : 'default'}
|
|
||||||
onClick={() => {
|
|
||||||
if (loadText !== '点击加载更多') return;
|
|
||||||
nextPage();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{loadText}
|
|
||||||
</Box>
|
|
||||||
{initRequesting && <Loading fixed={false} />}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ScrollData;
|
|
@@ -1,21 +1,27 @@
|
|||||||
import { useState, useCallback, useMemo, useEffect } from 'react';
|
import { useRef, useState, useCallback, useLayoutEffect, useMemo, useEffect } from 'react';
|
||||||
import type { PagingData } from '../types/index';
|
import type { PagingData } from '../types/index';
|
||||||
import { IconButton, Flex, Box, Input } from '@chakra-ui/react';
|
import { IconButton, Flex, Box, Input } from '@chakra-ui/react';
|
||||||
import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons';
|
import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { useToast } from './useToast';
|
import { useToast } from './useToast';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
|
const thresholdVal = 100;
|
||||||
|
|
||||||
export const usePagination = <T = any,>({
|
export const usePagination = <T = any,>({
|
||||||
api,
|
api,
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
params = {},
|
params = {},
|
||||||
defaultRequest = true
|
defaultRequest = true,
|
||||||
|
type = 'button'
|
||||||
}: {
|
}: {
|
||||||
api: (data: any) => any;
|
api: (data: any) => any;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
params?: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
defaultRequest?: boolean;
|
defaultRequest?: boolean;
|
||||||
|
type?: 'button' | 'scroll';
|
||||||
}) => {
|
}) => {
|
||||||
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [pageNum, setPageNum] = useState(1);
|
const [pageNum, setPageNum] = useState(1);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
@@ -108,6 +114,60 @@ export const usePagination = <T = any,>({
|
|||||||
);
|
);
|
||||||
}, [isLoading, maxPage, mutate, pageNum]);
|
}, [isLoading, maxPage, mutate, pageNum]);
|
||||||
|
|
||||||
|
const ScrollData = useCallback(
|
||||||
|
({ children, ...props }: { children: React.ReactNode }) => {
|
||||||
|
const loadText = useMemo(() => {
|
||||||
|
if (isLoading) return '请求中……';
|
||||||
|
if (total <= data.length) return '已加载全部';
|
||||||
|
return '点击加载更多';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box {...props} ref={elementRef} overflow={'overlay'}>
|
||||||
|
{children}
|
||||||
|
<Box
|
||||||
|
mt={2}
|
||||||
|
fontSize={'xs'}
|
||||||
|
color={'blackAlpha.500'}
|
||||||
|
textAlign={'center'}
|
||||||
|
cursor={loadText === '点击加载更多' ? 'pointer' : 'default'}
|
||||||
|
onClick={() => {
|
||||||
|
if (loadText !== '点击加载更多') return;
|
||||||
|
mutate(pageNum + 1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loadText}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[data.length, isLoading, mutate, pageNum, total]
|
||||||
|
);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!elementRef.current || type !== 'scroll') return;
|
||||||
|
|
||||||
|
const scrolling = throttle((e: Event) => {
|
||||||
|
const element = e.target as HTMLDivElement;
|
||||||
|
if (!element) return;
|
||||||
|
// 当前滚动位置
|
||||||
|
const scrollTop = element.scrollTop;
|
||||||
|
// 可视高度
|
||||||
|
const clientHeight = element.clientHeight;
|
||||||
|
// 内容总高度
|
||||||
|
const scrollHeight = element.scrollHeight;
|
||||||
|
// 判断是否滚动到底部
|
||||||
|
if (scrollTop + clientHeight + thresholdVal >= scrollHeight) {
|
||||||
|
mutate(pageNum + 1);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
elementRef.current.addEventListener('scroll', scrolling);
|
||||||
|
return () => {
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
elementRef.current?.removeEventListener('scroll', scrolling);
|
||||||
|
};
|
||||||
|
}, [elementRef, mutate, pageNum, type]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
defaultRequest && mutate(1);
|
defaultRequest && mutate(1);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -119,6 +179,7 @@ export const usePagination = <T = any,>({
|
|||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
Pagination,
|
Pagination,
|
||||||
|
ScrollData,
|
||||||
getData: mutate
|
getData: mutate
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -24,8 +24,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
const authCount = await Model.countDocuments({
|
const authCount = await Model.countDocuments({
|
||||||
userId
|
userId
|
||||||
});
|
});
|
||||||
if (authCount >= 30) {
|
if (authCount >= 50) {
|
||||||
throw new Error('上限 30 个应用');
|
throw new Error('上限 50 个应用');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建模型
|
// 创建模型
|
||||||
|
@@ -18,14 +18,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
{
|
{
|
||||||
userId
|
userId
|
||||||
},
|
},
|
||||||
'_id avatar name chat.systemPrompt'
|
'_id avatar name intro'
|
||||||
).sort({
|
).sort({
|
||||||
updateTime: -1
|
updateTime: -1
|
||||||
}),
|
}),
|
||||||
Collection.find({ userId })
|
Collection.find({ userId })
|
||||||
.populate({
|
.populate({
|
||||||
path: 'modelId',
|
path: 'modelId',
|
||||||
select: '_id avatar name chat.systemPrompt',
|
select: '_id avatar name intro',
|
||||||
match: { 'share.isShare': true }
|
match: { 'share.isShare': true }
|
||||||
})
|
})
|
||||||
.then((res) => res.filter((item) => item.modelId))
|
.then((res) => res.filter((item) => item.modelId))
|
||||||
@@ -37,14 +37,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
_id: item._id,
|
_id: item._id,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
avatar: item.avatar,
|
avatar: item.avatar,
|
||||||
systemPrompt: item.chat.systemPrompt
|
intro: item.intro
|
||||||
})),
|
})),
|
||||||
myCollectionModels: myCollections
|
myCollectionModels: myCollections
|
||||||
.map((item: any) => ({
|
.map((item: any) => ({
|
||||||
_id: item.modelId?._id,
|
_id: item.modelId?._id,
|
||||||
name: item.modelId?.name,
|
name: item.modelId?.name,
|
||||||
avatar: item.modelId?.avatar,
|
avatar: item.modelId?.avatar,
|
||||||
systemPrompt: item.modelId?.chat.systemPrompt
|
intro: item.modelId?.intro
|
||||||
}))
|
}))
|
||||||
.filter((item) => !myModels.find((model) => String(model._id) === String(item._id))) // 去重
|
.filter((item) => !myModels.find((model) => String(model._id) === String(item._id))) // 去重
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { Box, Flex, Input, IconButton, Tooltip, Tab, useTheme } from '@chakra-ui/react';
|
import { Box, Flex, Input, IconButton, Tooltip, useTheme } from '@chakra-ui/react';
|
||||||
import { AddIcon } from '@chakra-ui/icons';
|
import { AddIcon } from '@chakra-ui/icons';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
@@ -55,14 +55,12 @@ const ModelList = ({ modelId }: { modelId: string }) => {
|
|||||||
const currentModels = useMemo(() => {
|
const currentModels = useMemo(() => {
|
||||||
const map = {
|
const map = {
|
||||||
[MyModelsTypeEnum.my]: {
|
[MyModelsTypeEnum.my]: {
|
||||||
list: myModels.filter((item) =>
|
list: myModels.filter((item) => new RegExp(searchText, 'ig').test(item.name + item.intro)),
|
||||||
new RegExp(searchText, 'ig').test(item.name + item.systemPrompt)
|
|
||||||
),
|
|
||||||
emptyText: '还没有 AI 应用~\n快来创建一个吧'
|
emptyText: '还没有 AI 应用~\n快来创建一个吧'
|
||||||
},
|
},
|
||||||
[MyModelsTypeEnum.collection]: {
|
[MyModelsTypeEnum.collection]: {
|
||||||
list: myCollectionModels.filter((item) =>
|
list: myCollectionModels.filter((item) =>
|
||||||
new RegExp(searchText, 'ig').test(item.name + item.systemPrompt)
|
new RegExp(searchText, 'ig').test(item.name + item.intro)
|
||||||
),
|
),
|
||||||
emptyText: '收藏的 AI 应用为空~\n快去市场找一个吧'
|
emptyText: '收藏的 AI 应用为空~\n快去市场找一个吧'
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,8 @@ const Settings = ({ modelId }: { modelId: string }) => {
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { Loading, setIsLoading } = useLoading();
|
const { Loading, setIsLoading } = useLoading();
|
||||||
const { userInfo, modelDetail, loadModelDetail, refreshModel, setLastModelId } = useUserStore();
|
const { userInfo, modelDetail, myModels, loadModelDetail, refreshModel, setLastModelId } =
|
||||||
|
useUserStore();
|
||||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||||
fileType: '.jpg,.png',
|
fileType: '.jpg,.png',
|
||||||
multiple: false
|
multiple: false
|
||||||
@@ -119,7 +120,7 @@ const Settings = ({ modelId }: { modelId: string }) => {
|
|||||||
status: 'success'
|
status: 'success'
|
||||||
});
|
});
|
||||||
refreshModel.removeModelDetail(modelDetail._id);
|
refreshModel.removeModelDetail(modelDetail._id);
|
||||||
router.replace('/model');
|
router.replace(`/model?modelId=${myModels[1]?._id}`);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast({
|
toast({
|
||||||
title: err?.message || '删除失败',
|
title: err?.message || '删除失败',
|
||||||
@@ -127,7 +128,7 @@ const Settings = ({ modelId }: { modelId: string }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [modelDetail, setIsLoading, toast, refreshModel, router]);
|
}, [modelDetail, setIsLoading, toast, refreshModel, router, myModels]);
|
||||||
|
|
||||||
const onSelectFile = useCallback(
|
const onSelectFile = useCallback(
|
||||||
async (e: File[]) => {
|
async (e: File[]) => {
|
||||||
|
2
client/src/types/model.d.ts
vendored
2
client/src/types/model.d.ts
vendored
@@ -5,7 +5,7 @@ export type ModelListItemType = {
|
|||||||
_id: string;
|
_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
systemPrompt: string;
|
intro: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ModelUpdateParams {
|
export interface ModelUpdateParams {
|
||||||
|
Reference in New Issue
Block a user