This commit is contained in:
archer
2023-06-10 14:01:00 +08:00
parent e19ac56fe5
commit 6fd49b0955
11 changed files with 161 additions and 119 deletions

1
.gitignore vendored
View File

@@ -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
View 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

View File

@@ -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 && (

View File

@@ -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,

View File

@@ -40,3 +40,8 @@ export const InformTypeMap = {
label: '系统通知' label: '系统通知'
} }
}; };
export enum MyModelsTypeEnum {
my = 'my',
collection = 'collection'
}

View File

@@ -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);
} }

View File

@@ -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;

View File

@@ -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={() => {

View File

@@ -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>
)} )}

View File

@@ -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 {

View File

@@ -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"