mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-18 10:03:55 +00:00
perf: ui
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -28,5 +28,4 @@ next-env.d.ts
|
|||||||
platform.json
|
platform.json
|
||||||
testApi/
|
testApi/
|
||||||
local/
|
local/
|
||||||
.husky/
|
|
||||||
dist/
|
dist/
|
6
.husky/pre-commit
Executable file
6
.husky/pre-commit
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
if command -v npx >/dev/null 2>&1; then
|
||||||
|
npx lint-staged
|
||||||
|
fi
|
@@ -27,7 +27,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
activeLink: ['/chat']
|
activeLink: ['/chat']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '我的应用',
|
label: '应用',
|
||||||
icon: 'model',
|
icon: 'model',
|
||||||
link: `/model?modelId=${lastModelId}`,
|
link: `/model?modelId=${lastModelId}`,
|
||||||
activeLink: ['/model']
|
activeLink: ['/model']
|
||||||
@@ -39,7 +39,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
activeLink: ['/kb']
|
activeLink: ['/kb']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '应用市场',
|
label: '市场',
|
||||||
icon: 'appStore',
|
icon: 'appStore',
|
||||||
link: '/model/share',
|
link: '/model/share',
|
||||||
activeLink: ['/model/share']
|
activeLink: ['/model/share']
|
||||||
@@ -61,14 +61,15 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const itemStyles: any = {
|
const itemStyles: any = {
|
||||||
mb: 3,
|
my: 3,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
w: '60px',
|
w: '54px',
|
||||||
h: '45px',
|
h: '54px',
|
||||||
|
borderRadius: 'md',
|
||||||
_hover: {
|
_hover: {
|
||||||
color: '#ffffff'
|
color: '#ffffff'
|
||||||
}
|
}
|
||||||
@@ -79,7 +80,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
flexDirection={'column'}
|
flexDirection={'column'}
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
pt={6}
|
pt={6}
|
||||||
backgroundColor={'#465069'}
|
backgroundImage={'linear-gradient(to bottom right,#465069,#000000)'}
|
||||||
h={'100%'}
|
h={'100%'}
|
||||||
w={'100%'}
|
w={'100%'}
|
||||||
boxShadow={'4px 0px 4px 0px rgba(43, 45, 55, 0.01)'}
|
boxShadow={'4px 0px 4px 0px rgba(43, 45, 55, 0.01)'}
|
||||||
@@ -99,30 +100,26 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
{/* 导航列表 */}
|
{/* 导航列表 */}
|
||||||
<Box flex={1}>
|
<Box flex={1}>
|
||||||
{navbarList.map((item) => (
|
{navbarList.map((item) => (
|
||||||
<Tooltip
|
<Link
|
||||||
label={item.label}
|
key={item.link}
|
||||||
key={item.label}
|
as={NextLink}
|
||||||
placement={'right'}
|
href={item.link}
|
||||||
openDelay={100}
|
{...itemStyles}
|
||||||
gutter={-10}
|
{...(item.activeLink.includes(router.pathname)
|
||||||
|
? {
|
||||||
|
color: '#ffffff ',
|
||||||
|
backgroundImage: 'linear-gradient(to bottom right, #2152d9 0%, #4e83fd 100%)'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
color: '#9096a5',
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<Link
|
<MyIcon name={item.icon as any} width={'20px'} height={'20px'} />
|
||||||
as={NextLink}
|
<Box fontSize={'12px'} transform={'scale(0.9)'} mt={'5px'} lineHeight={1}>
|
||||||
href={item.link}
|
{item.label}
|
||||||
{...itemStyles}
|
</Box>
|
||||||
{...(item.activeLink.includes(router.pathname)
|
</Link>
|
||||||
? {
|
|
||||||
color: '#ffffff ',
|
|
||||||
backgroundImage: 'linear-gradient(270deg,#4e83fd,#3370ff)'
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
color: '#9096a5',
|
|
||||||
backgroundColor: 'transparent'
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<MyIcon name={item.icon as any} width={'22px'} height={'22px'} />
|
|
||||||
</Link>
|
|
||||||
</Tooltip>
|
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
{unread > 0 && (
|
{unread > 0 && (
|
||||||
|
@@ -232,6 +232,10 @@ export const theme = extendTheme({
|
|||||||
xl: '1800px',
|
xl: '1800px',
|
||||||
'2xl': '2100px'
|
'2xl': '2100px'
|
||||||
},
|
},
|
||||||
|
active: {
|
||||||
|
activeBlueGradient: 'linear-gradient(120deg, #d6e8ff 0%, #f0f7ff 100%)',
|
||||||
|
hoverBlueGradient: 'linear-gradient(60deg, #f0f7ff 0%, #d6e8ff 100%)'
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
Modal: ModalTheme,
|
Modal: ModalTheme,
|
||||||
Button,
|
Button,
|
||||||
|
@@ -40,3 +40,8 @@ export const InformTypeMap = {
|
|||||||
label: '系统通知'
|
label: '系统通知'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum MyModelsTypeEnum {
|
||||||
|
my = 'my',
|
||||||
|
collection = 'collection'
|
||||||
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import { withNextCors } from '@/service/utils/tools';
|
|||||||
import { TrainingModeEnum } from '@/constants/plugin';
|
import { TrainingModeEnum } from '@/constants/plugin';
|
||||||
import { startQueue } from '@/service/utils/tools';
|
import { startQueue } from '@/service/utils/tools';
|
||||||
import { PgClient } from '@/service/pg';
|
import { PgClient } from '@/service/pg';
|
||||||
|
import { modelToolMap } from '@/utils/plugin';
|
||||||
|
|
||||||
type DateItemType = { a: string; q: string; source?: string };
|
type DateItemType = { a: string; q: string; source?: string };
|
||||||
|
|
||||||
@@ -21,6 +22,11 @@ export type Response = {
|
|||||||
insertLen: number;
|
insertLen: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const modeMaxToken = {
|
||||||
|
[TrainingModeEnum.index]: 700,
|
||||||
|
[TrainingModeEnum.qa]: 3300
|
||||||
|
};
|
||||||
|
|
||||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
try {
|
try {
|
||||||
const { kbId, data, mode, prompt } = req.body as Props;
|
const { kbId, data, mode, prompt } = req.body as Props;
|
||||||
@@ -68,7 +74,15 @@ export async function pushDataToKb({
|
|||||||
|
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
const text = item.q + item.a;
|
const text = item.q + item.a;
|
||||||
if (!set.has(text)) {
|
|
||||||
|
// count token
|
||||||
|
const token = modelToolMap['gpt-3.5-turbo'].countTokens({
|
||||||
|
messages: [{ obj: 'System', value: item.q }]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mode === TrainingModeEnum.qa && token > modeMaxToken[TrainingModeEnum.qa]) {
|
||||||
|
console.log('q is too long');
|
||||||
|
} else if (!set.has(text)) {
|
||||||
filterData.push(item);
|
filterData.push(item);
|
||||||
set.add(text);
|
set.add(text);
|
||||||
}
|
}
|
||||||
|
@@ -146,31 +146,26 @@ const PcSliderBar = ({
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* chat history */}
|
{/* chat history */}
|
||||||
<Box flex={'1 0 0'} h={0} overflow={'overlay'}>
|
<Box flex={'1 0 0'} h={0} overflow={'overlay'} userSelect={'none'}>
|
||||||
{history.map((item) => (
|
{history.map((item) => (
|
||||||
<Flex
|
<Flex
|
||||||
position={'relative'}
|
|
||||||
key={item._id}
|
key={item._id}
|
||||||
|
position={'relative'}
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
py={3}
|
p={3}
|
||||||
pr={[0, 3]}
|
mb={[2, 0]}
|
||||||
pl={[6, 3]}
|
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
transition={'background-color .2s ease-in'}
|
transition={'background-color .2s ease-in'}
|
||||||
borderLeft={['none', '5px solid transparent']}
|
|
||||||
userSelect={'none'}
|
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: ['', '#dee0e3']
|
backgroundImage: ['', theme.active.hoverBlueGradient]
|
||||||
}}
|
}}
|
||||||
{...(item._id === chatId
|
{...(item._id === chatId
|
||||||
? {
|
? {
|
||||||
bg: 'myGray.100 !important',
|
backgroundImage: `${theme.active.activeBlueGradient}`
|
||||||
borderLeftColor: 'myBlue.600 !important'
|
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
bg: item.top ? 'myBlue.200' : ''
|
bg: item.top ? 'myGray.200' : ''
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item._id === chatId) return;
|
if (item._id === chatId) return;
|
||||||
|
@@ -87,7 +87,7 @@ const KbList = ({ kbId }: { kbId: string }) => {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box flex={'1 0 0'} h={0} overflow={'overlay'}>
|
<Box flex={'1 0 0'} h={0} overflow={'overlay'} userSelect={'none'}>
|
||||||
{kbs.map((item) => (
|
{kbs.map((item) => (
|
||||||
<Flex
|
<Flex
|
||||||
key={item._id}
|
key={item._id}
|
||||||
@@ -97,14 +97,12 @@ const KbList = ({ kbId }: { kbId: string }) => {
|
|||||||
mb={[2, 0]}
|
mb={[2, 0]}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
transition={'background-color .2s ease-in'}
|
transition={'background-color .2s ease-in'}
|
||||||
borderLeft={['', '5px solid transparent']}
|
|
||||||
_hover={{
|
_hover={{
|
||||||
backgroundColor: ['', '#dee0e3']
|
backgroundImage: ['', theme.active.hoverBlueGradient]
|
||||||
}}
|
}}
|
||||||
{...(kbId === item._id
|
{...(kbId === item._id
|
||||||
? {
|
? {
|
||||||
backgroundColor: '#eff0f1',
|
backgroundImage: `${theme.active.activeBlueGradient} !important`
|
||||||
borderLeftColor: 'myBlue.600 !important'
|
|
||||||
}
|
}
|
||||||
: {})}
|
: {})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@@ -1,5 +1,15 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { Box, Flex, useTheme, Input, IconButton, Tooltip } from '@chakra-ui/react';
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
Tabs,
|
||||||
|
TabList,
|
||||||
|
Tab,
|
||||||
|
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';
|
||||||
@@ -9,8 +19,15 @@ import { useToast } from '@/hooks/useToast';
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useUserStore } from '@/store/user';
|
import { useUserStore } from '@/store/user';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
|
import { MyModelsTypeEnum } from '@/constants/user';
|
||||||
|
|
||||||
const ModelList = ({ modelId }: { modelId: string }) => {
|
const ModelList = ({ modelId }: { modelId: string }) => {
|
||||||
|
const tabs = useRef([
|
||||||
|
{ label: '我的', value: MyModelsTypeEnum.my },
|
||||||
|
{ label: '收藏', value: MyModelsTypeEnum.collection }
|
||||||
|
]);
|
||||||
|
const [currentTab, setCurrentTab] = useState(MyModelsTypeEnum.my);
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@@ -42,29 +59,23 @@ const ModelList = ({ modelId }: { modelId: string }) => {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [myModels.length, refreshModel, router, setIsLoading, toast]);
|
}, [myModels.length, refreshModel, router, setIsLoading, toast]);
|
||||||
|
|
||||||
const models = useMemo(
|
const currentModels = useMemo(() => {
|
||||||
() =>
|
const map = {
|
||||||
[
|
[MyModelsTypeEnum.my]: {
|
||||||
{
|
list: myModels.filter((item) =>
|
||||||
label: '我的',
|
new RegExp(searchText, 'ig').test(item.name + item.systemPrompt)
|
||||||
list: myModels.filter((item) =>
|
),
|
||||||
new RegExp(searchText, 'ig').test(item.name + item.systemPrompt)
|
emptyText: '还没有 AI 应用~\n快来创建一个吧'
|
||||||
)
|
},
|
||||||
},
|
[MyModelsTypeEnum.collection]: {
|
||||||
{
|
list: myCollectionModels.filter((item) =>
|
||||||
label: '收藏',
|
new RegExp(searchText, 'ig').test(item.name + item.systemPrompt)
|
||||||
list: myCollectionModels.filter((item) =>
|
),
|
||||||
new RegExp(searchText, 'ig').test(item.name + item.systemPrompt)
|
emptyText: '收藏的 AI 应用为空~\n快去市场找一个吧'
|
||||||
)
|
}
|
||||||
}
|
};
|
||||||
].filter((item) => item.list.length > 0),
|
return map[currentTab];
|
||||||
[myCollectionModels, myModels, searchText]
|
}, [currentTab, myCollectionModels, myModels, searchText]);
|
||||||
);
|
|
||||||
|
|
||||||
const totalModels = useMemo(
|
|
||||||
() => models.reduce((sum, item) => sum + item.list.length, 0),
|
|
||||||
[models]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@@ -107,55 +118,69 @@ const ModelList = ({ modelId }: { modelId: string }) => {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box flex={'1 0 0'} h={0} overflow={'overlay'}>
|
<Flex mb={4} userSelect={'none'}>
|
||||||
{models.map((item) => (
|
<Box flex={1}></Box>
|
||||||
<Box key={item.label} _notFirst={{ mt: 5 }}>
|
<Tabs
|
||||||
<Box fontWeight={'bold'} pl={5}>
|
variant="unstyled"
|
||||||
{item.label}
|
defaultIndex={tabs.current.findIndex((item) => item.value === currentTab)}
|
||||||
</Box>
|
onChange={(i) => setCurrentTab(tabs.current[i].value)}
|
||||||
{item.list.map((item) => (
|
>
|
||||||
<Flex
|
<TabList whiteSpace={'nowrap'}>
|
||||||
key={item._id}
|
{tabs.current.map((item) => (
|
||||||
position={'relative'}
|
<Tab
|
||||||
alignItems={['flex-start', 'center']}
|
key={item.value}
|
||||||
p={3}
|
py={'2px'}
|
||||||
mb={[2, 0]}
|
px={4}
|
||||||
cursor={'pointer'}
|
borderRadius={'sm'}
|
||||||
transition={'background-color .2s ease-in'}
|
mr={2}
|
||||||
borderLeft={['', '5px solid transparent']}
|
transition={'none'}
|
||||||
_hover={{
|
_selected={{ color: 'white', bg: 'myBlue.600' }}
|
||||||
backgroundColor: ['', '#dee0e3']
|
|
||||||
}}
|
|
||||||
{...(modelId === item._id
|
|
||||||
? {
|
|
||||||
backgroundColor: '#eff0f1',
|
|
||||||
borderLeftColor: 'myBlue.600 !important'
|
|
||||||
}
|
|
||||||
: {})}
|
|
||||||
onClick={() => {
|
|
||||||
if (item._id === modelId) return;
|
|
||||||
router.push(`/model?modelId=${item._id}`);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Avatar src={item.avatar} w={'34px'} h={'34px'} />
|
{item.label}
|
||||||
<Box flex={'1 0 0'} w={0} ml={3}>
|
</Tab>
|
||||||
<Box className="textEllipsis" color={'myGray.1000'}>
|
|
||||||
{item.name}
|
|
||||||
</Box>
|
|
||||||
<Box className="textEllipsis" color={'myGray.400'} fontSize={'sm'}>
|
|
||||||
{item.systemPrompt || '这个 应用 没有设置提示词~'}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
))}
|
))}
|
||||||
</Box>
|
</TabList>
|
||||||
|
</Tabs>
|
||||||
|
</Flex>
|
||||||
|
<Box flex={'1 0 0'} h={0} overflow={'overlay'} userSelect={'none'}>
|
||||||
|
{currentModels.list.map((item) => (
|
||||||
|
<Flex
|
||||||
|
key={item._id}
|
||||||
|
position={'relative'}
|
||||||
|
alignItems={['flex-start', 'center']}
|
||||||
|
p={3}
|
||||||
|
mb={[2, 0]}
|
||||||
|
cursor={'pointer'}
|
||||||
|
transition={'background-color .2s ease-in'}
|
||||||
|
_hover={{
|
||||||
|
backgroundImage: ['', theme.active.hoverBlueGradient]
|
||||||
|
}}
|
||||||
|
{...(modelId === item._id
|
||||||
|
? {
|
||||||
|
backgroundImage: `${theme.active.activeBlueGradient} !important`
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
onClick={() => {
|
||||||
|
if (item._id === modelId) return;
|
||||||
|
router.push(`/model?modelId=${item._id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar src={item.avatar} w={'34px'} h={'34px'} />
|
||||||
|
<Box flex={'1 0 0'} w={0} ml={3}>
|
||||||
|
<Box className="textEllipsis" color={'myGray.1000'}>
|
||||||
|
{item.name}
|
||||||
|
</Box>
|
||||||
|
<Box className="textEllipsis" color={'myGray.400'} fontSize={'sm'}>
|
||||||
|
{item.systemPrompt || '这个 应用 没有设置提示词~'}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
))}
|
))}
|
||||||
|
{!isFetching && currentModels.list.length === 0 && (
|
||||||
{!isFetching && totalModels === 0 && (
|
|
||||||
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'30vh'}>
|
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'30vh'}>
|
||||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||||
<Box mt={2} color={'myGray.500'}>
|
<Box mt={2} color={'myGray.500'}>
|
||||||
还没有 AI 应用~
|
{currentModels.emptyText}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
@@ -44,7 +44,7 @@ svg {
|
|||||||
div {
|
div {
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
transition: 1s;
|
transition: background 1s;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
|
@@ -6,14 +6,13 @@
|
|||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"format": "prettier --config \"./.prettierrc.js\" --write \"./**/src/**/*.{ts,tsx,scss}\""
|
"format": "prettier --config \"./.prettierrc.js\" --write \"./**/src/**/*.{ts,tsx,scss}\""
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^13.2.1",
|
"lint-staged": "^13.2.1",
|
||||||
"prettier": "^2.8.7"
|
"prettier": "^2.8.7"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"./**/*.{ts,tsx,scss}": "npm run format"
|
"./**/**/*.{ts,tsx,scss}": "npm run format"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
|
Reference in New Issue
Block a user