feat: kb ui

This commit is contained in:
archer
2023-07-14 11:49:36 +08:00
parent 358c4716f9
commit 5a96e167ee
24 changed files with 463 additions and 412 deletions

View File

@@ -1,99 +0,0 @@
import React, { useRef, useState } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex } from '@chakra-ui/react';
import { useToast } from '@/hooks/useToast';
import { useForm } from 'react-hook-form';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import { KbItemType } from '@/types/plugin';
import { useScreen } from '@/hooks/useScreen';
import { getErrText } from '@/utils/tools';
import { type ComponentRef } from './Info';
import Tabs from '@/components/Tabs';
import dynamic from 'next/dynamic';
import DataCard from './DataCard';
const Test = dynamic(() => import('./Test'), {
ssr: false
});
const Info = dynamic(() => import('./Info'), {
ssr: false
});
enum TabEnum {
data = 'data',
test = 'test',
info = 'info'
}
const Detail = ({ kbId }: { kbId: string }) => {
const { toast } = useToast();
const router = useRouter();
const { isPc } = useScreen();
const BasicInfo = useRef<ComponentRef>(null);
const { setLastKbId, kbDetail, getKbDetail, loadKbList } = useUserStore();
const [currentTab, setCurrentTab] = useState(TabEnum.data);
const form = useForm<KbItemType>({
defaultValues: kbDetail
});
const { reset } = form;
useQuery(
[kbId],
() => {
setCurrentTab(TabEnum.data);
return getKbDetail(kbId);
},
{
onSuccess(res) {
if (!res) return;
kbId && setLastKbId(kbId);
reset(res);
BasicInfo.current?.initInput?.(res.tags);
},
onError(err: any) {
loadKbList(true);
setLastKbId('');
router.replace(`/kb`);
toast({
title: getErrText(err, '获取知识库异常'),
status: 'error'
});
}
}
);
return (
<Flex
flexDirection={'column'}
bg={'#fcfcfc'}
h={'100%'}
pt={5}
overflow={'overlay'}
position={'relative'}
>
<Box mb={3}>
<Tabs
m={'auto'}
w={'260px'}
size={isPc ? 'md' : 'sm'}
list={[
{ id: TabEnum.data, label: '数据管理' },
{ id: TabEnum.test, label: '搜索测试' },
{ id: TabEnum.info, label: '基本信息' }
]}
activeId={currentTab}
onChange={(e: any) => setCurrentTab(e)}
/>
</Box>
<Box flex={'1 0 0'} overflow={'overlay'}>
{currentTab === TabEnum.data && <DataCard kbId={kbId} />}
{currentTab === TabEnum.test && <Test />}
{currentTab === TabEnum.info && <Info ref={BasicInfo} kbId={kbId} form={form} />}
</Box>
</Flex>
);
};
export default React.memo(Detail);

View File

@@ -1,152 +0,0 @@
import React, { useCallback, useState, useMemo } from 'react';
import { Box, Flex, useTheme, Input, IconButton } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { useRouter } from 'next/router';
import { postCreateKb } from '@/api/plugins/kb';
import { useLoading } from '@/hooks/useLoading';
import { useToast } from '@/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
import Tag from '@/components/Tag';
import MyTooltip from '@/components/MyTooltip';
const KbList = ({ kbId }: { kbId: string }) => {
const theme = useTheme();
const router = useRouter();
const { toast } = useToast();
const { Loading, setIsLoading } = useLoading();
const { myKbList, loadKbList } = useUserStore();
const [searchText, setSearchText] = useState('');
const kbs = useMemo(
() => myKbList.filter((item) => new RegExp(searchText, 'ig').test(item.name + item.tags)),
[myKbList, searchText]
);
/* 加载模型 */
const { isFetching } = useQuery(['loadModels'], () => loadKbList(false));
const handleCreateModel = useCallback(async () => {
setIsLoading(true);
try {
const name = `知识库${myKbList.length + 1}`;
const id = await postCreateKb({ name });
await loadKbList(true);
toast({
title: '创建成功',
status: 'success'
});
router.replace(`/kb?kbId=${id}`);
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : err.message || '出现了意外',
status: 'error'
});
}
setIsLoading(false);
}, [loadKbList, myKbList.length, router, setIsLoading, toast]);
return (
<Flex
position={'relative'}
flexDirection={'column'}
w={'100%'}
h={'100%'}
bg={'white'}
borderRight={['', theme.borders.base]}
>
<Flex w={'90%'} my={5} mx={'auto'}>
<Flex flex={1} mr={2} position={'relative'} alignItems={'center'}>
<Input
h={'32px'}
placeholder="搜索知识库"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
{searchText && (
<MyIcon
zIndex={10}
position={'absolute'}
right={3}
name={'closeSolid'}
w={'16px'}
h={'16px'}
color={'myGray.500'}
cursor={'pointer'}
onClick={() => setSearchText('')}
/>
)}
</Flex>
<MyTooltip label={'新建一个知识库'}>
<IconButton
h={'32px'}
icon={<AddIcon />}
aria-label={''}
variant={'base'}
onClick={handleCreateModel}
/>
</MyTooltip>
</Flex>
<Box flex={'1 0 0'} h={0} pl={[0, 2]} overflowY={'scroll'} userSelect={'none'}>
{kbs.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'}
borderRadius={['', 'md']}
borderBottom={['1px solid #f4f4f4', 'none']}
_hover={{
backgroundImage: ['', theme.lgColor.hoverBlueGradient]
}}
{...(kbId === item._id
? {
backgroundImage: `${theme.lgColor.activeBlueGradient} !important`
}
: {})}
onClick={() => {
if (item._id === kbId) return;
router.push(`/kb?kbId=${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>
{/* tags */}
<Box color={'myGray.400'} py={1} fontSize={'sm'}>
{!item.tags ? (
<>{item.tags || '你还没设置标签~'}</>
) : (
item.tags.split(' ').map((item, i) => (
<Tag key={i} mr={1} mb={1}>
{item}
</Tag>
))
)}
</Box>
</Box>
</Flex>
))}
{!isFetching && myKbList.length === 0 && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'30vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
~
</Box>
</Flex>
)}
</Box>
<Loading loading={isFetching} fixed={false} />
</Flex>
);
};
export default KbList;

View File

@@ -31,8 +31,8 @@ import InputModal, { FormData as InputDataType } from './InputDataModal';
import { debounce } from 'lodash';
import { getErrText } from '@/utils/tools';
const SelectFileModal = dynamic(() => import('./SelectFileModal'));
const SelectCsvModal = dynamic(() => import('./SelectCsvModal'));
const SelectFileModal = dynamic(() => import('./SelectFileModal'), { ssr: true });
const SelectCsvModal = dynamic(() => import('./SelectCsvModal'), { ssr: true });
const DataCard = ({ kbId }: { kbId: string }) => {
const lastSearch = useRef('');
@@ -140,7 +140,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
}, [kbId]);
return (
<Box position={'relative'} px={5} pb={[1, 5]}>
<Box position={'relative'} px={5} py={[1, 5]}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'} fontSize={'lg'} mr={2}>
: {total}

View File

@@ -59,7 +59,7 @@ const Info = (
status: 'success'
});
router.replace(`/kb?kbId=${myKbList.find((item) => item._id !== kbId)?._id || ''}`);
await loadKbList(true);
await loadKbList();
} catch (err: any) {
toast({
title: err?.message || '删除失败',
@@ -82,7 +82,7 @@ const Info = (
title: '更新成功',
status: 'success'
});
loadKbList(true);
loadKbList();
} catch (err: any) {
toast({
title: err?.message || '更新失败',
@@ -136,6 +136,7 @@ const Info = (
useImperativeHandle(ref, () => ({
initInput: (tags: string) => {
console.log(tags);
if (InputRef.current) {
InputRef.current.value = tags;
}
@@ -143,7 +144,7 @@ const Info = (
}));
return (
<Flex px={5} flexDirection={'column'} alignItems={'center'}>
<Flex p={5} flexDirection={'column'} alignItems={'center'}>
<Flex mt={5} w={'100%'} maxW={'350px'} alignItems={'center'}>
<Box flex={'0 0 90px'} w={0}>
@@ -200,31 +201,29 @@ const Info = (
))}
</Box>
</Flex>
{kbDetail._id && (
<Flex mt={5} w={'100%'} maxW={'350px'} alignItems={'flex-end'}>
<Box flex={'0 0 90px'} w={0}></Box>
<Button
isLoading={btnLoading}
mr={4}
w={'100px'}
onClick={handleSubmit(saveSubmitSuccess, saveSubmitError)}
>
</Button>
<IconButton
isLoading={btnLoading}
icon={<DeleteIcon />}
aria-label={''}
variant={'outline'}
size={'sm'}
_hover={{
color: 'red.600',
borderColor: 'red.600'
}}
onClick={openConfirm(onclickDelKb)}
/>
</Flex>
)}
<Flex mt={5} w={'100%'} maxW={'350px'} alignItems={'flex-end'}>
<Box flex={'0 0 90px'} w={0}></Box>
<Button
isLoading={btnLoading}
mr={4}
w={'100px'}
onClick={handleSubmit(saveSubmitSuccess, saveSubmitError)}
>
</Button>
<IconButton
isLoading={btnLoading}
icon={<DeleteIcon />}
aria-label={''}
variant={'outline'}
size={'sm'}
_hover={{
color: 'red.600',
borderColor: 'red.600'
}}
onClick={openConfirm(onclickDelKb)}
/>
</Flex>
<File onSelect={onSelectFile} />
<ConfirmChild />
</Flex>

View File

@@ -11,7 +11,10 @@ import InputDataModal, { type FormData } from './InputDataModal';
import { useGlobalStore } from '@/store/global';
import { getErrText } from '@/utils/tools';
import { useToast } from '@/hooks/useToast';
import { vectorModelList } from '@/store/static';
import { customAlphabet } from 'nanoid';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
const Test = () => {
@@ -30,7 +33,7 @@ const Test = () => {
);
const { mutate, isLoading } = useRequest({
mutationFn: () => searchText({ kbId, text: inputText.trim() }),
mutationFn: () => searchText({ model: vectorModelList[0].model, kbId, text: inputText.trim() }),
onSuccess(res) {
const testItem = {
id: nanoid(),
@@ -40,7 +43,6 @@ const Test = () => {
results: res
};
pushKbTestItem(testItem);
setInputText('');
setKbTestItem(testItem);
},
onError(err) {
@@ -59,13 +61,14 @@ const Test = () => {
<Box h={'100%'} display={['block', 'flex']}>
<Box
h={['auto', '100%']}
overflow={'overlay'}
display={['block', 'flex']}
flexDirection={'column'}
flex={1}
maxW={'500px'}
px={4}
py={4}
borderRight={['none', theme.borders.base]}
>
<Box border={'2px solid'} borderColor={'myBlue.600'} p={3} borderRadius={'md'}>
<Box border={'2px solid'} borderColor={'myBlue.600'} p={3} mx={4} borderRadius={'md'}>
<Box fontSize={'sm'} fontWeight={'bold'}>
<MyIcon mr={2} name={'text'} w={'18px'} h={'18px'} color={'myBlue.700'} />
@@ -85,13 +88,13 @@ const Test = () => {
</Button>
</Flex>
</Box>
<Box mt={5} display={['none', 'block']}>
<Box mt={5} flex={'1 0 0'} px={4} overflow={'overlay'} display={['none', 'block']}>
<Flex alignItems={'center'} color={'myGray.600'}>
<MyIcon mr={2} name={'history'} w={'16px'} h={'16px'} />
<Box fontSize={'2xl'}></Box>
</Flex>
<Box mt={2}>
<Flex py={1} fontWeight={'bold'} borderBottom={theme.borders.base}>
<Flex py={2} fontWeight={'bold'} borderBottom={theme.borders.sm}>
<Box flex={1}></Box>
<Box w={'80px'}></Box>
<Box w={'14px'}></Box>
@@ -115,26 +118,28 @@ const Test = () => {
{item.text}
</Box>
<Box w={'80px'}>{formatTimeToChatTime(item.time)}</Box>
<Box w={'14px'} h={'14px'}>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
display={'none'}
_hover={{ color: 'red.600' }}
onClick={(e) => {
e.stopPropagation();
delKbTestItemById(item.id);
kbTestItem?.id === item.id && setKbTestItem(undefined);
}}
/>
</Box>
<MyTooltip label={'删除该测试记录'}>
<Box w={'14px'} h={'14px'}>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
display={'none'}
_hover={{ color: 'red.600' }}
onClick={(e) => {
e.stopPropagation();
delKbTestItemById(item.id);
kbTestItem?.id === item.id && setKbTestItem(undefined);
}}
/>
</Box>
</MyTooltip>
</Flex>
))}
</Box>
</Box>
</Box>
<Box px={4} pb={4} mt={[8, 0]} h={['auto', '100%']} overflow={'overlay'} flex={1}>
<Box p={4} h={['auto', '100%']} overflow={'overlay'} flex={1}>
{!kbTestItem?.results || kbTestItem.results.length === 0 ? (
<Flex
mt={[10, 0]}
@@ -154,9 +159,18 @@ const Test = () => {
<Box fontSize={'3xl'} color={'myGray.600'}>
</Box>
<Box fontSize={'xs'} color={'myGray.500'} ml={1}>
QA内容可能不是最新
</Box>
<MyTooltip
label={
'根据知识库内容与测试文本的相似度进行排序,你可以根据测试结果调整对应的文本。\n注意测试记录中的数据可能已经被修改过点击某条测试数据后将展示最新的数据。'
}
>
<QuestionOutlineIcon
ml={2}
color={'myGray.600'}
cursor={'pointer'}
fontSize={'lg'}
/>
</MyTooltip>
</Flex>
<Grid
mt={1}

View File

@@ -0,0 +1,160 @@
import React, { useCallback, useMemo, useRef } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react';
import { useToast } from '@/hooks/useToast';
import { useForm } from 'react-hook-form';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import { KbItemType } from '@/types/plugin';
import { useScreen } from '@/hooks/useScreen';
import { getErrText } from '@/utils/tools';
import { type ComponentRef } from './components/Info';
import Tabs from '@/components/Tabs';
import dynamic from 'next/dynamic';
import DataCard from './components/DataCard';
import MyIcon from '@/components/Icon';
import SideTabs from '@/components/SideTabs';
import PageContainer from '@/components/PageContainer';
import Avatar from '@/components/Avatar';
import Info from './components/Info';
const Test = dynamic(() => import('./components/Test'), {
ssr: false
});
enum TabEnum {
data = 'data',
test = 'test',
info = 'info'
}
const Detail = ({ kbId, currentTab }: { kbId: string; currentTab: `${TabEnum}` }) => {
const InfoRef = useRef<ComponentRef>(null);
const theme = useTheme();
const { toast } = useToast();
const router = useRouter();
const { isPc } = useScreen();
const { kbDetail, getKbDetail } = useUserStore();
const tabList = useMemo(
() => [
{ label: '数据集', id: TabEnum.data, icon: 'overviewLight' },
{ label: '搜索测试', id: TabEnum.test, icon: 'kbTest' },
{ label: '基本信息', id: TabEnum.info, icon: 'settingLight' }
],
[]
);
const setCurrentTab = useCallback(
(tab: `${TabEnum}`) => {
router.replace({
query: {
kbId,
currentTab: tab
}
});
},
[kbId, router]
);
const form = useForm<KbItemType>({
defaultValues: kbDetail
});
useQuery([kbId], () => getKbDetail(kbId), {
onSuccess(res) {
InfoRef.current?.initInput(res.tags);
form.reset(res);
},
onError(err: any) {
router.replace(`/kb/list`);
toast({
title: getErrText(err, '获取知识库异常'),
status: 'error'
});
}
});
return (
<PageContainer>
<Box display={['block', 'flex']} h={'100%'} pt={[4, 0]}>
{/* pc tab */}
<Box
display={['none', 'flex']}
flexDirection={'column'}
p={4}
w={'200px'}
borderRight={theme.borders.base}
>
<Flex mb={4} alignItems={'center'}>
<Avatar src={kbDetail.avatar} w={'34px'} borderRadius={'lg'} />
<Box ml={2} fontWeight={'bold'}>
{kbDetail.name}
</Box>
</Flex>
<SideTabs
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
list={tabList}
activeId={currentTab}
onChange={(e: any) => {
setCurrentTab(e);
}}
/>
<Flex
alignItems={'center'}
cursor={'pointer'}
py={2}
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.100' }}
onClick={() => router.replace('/kb/list')}
>
<IconButton
mr={3}
icon={<MyIcon name={'backFill'} w={'18px'} color={'myBlue.600'} />}
bg={'white'}
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
h={'28px'}
size={'sm'}
borderRadius={'50%'}
aria-label={''}
/>
</Flex>
</Box>
<Box mb={3} display={['block', 'none']}>
<Tabs
m={'auto'}
w={'260px'}
size={isPc ? 'md' : 'sm'}
list={[
{ id: TabEnum.data, label: '数据管理' },
{ id: TabEnum.test, label: '搜索测试' },
{ id: TabEnum.info, label: '基本信息' }
]}
activeId={currentTab}
onChange={(e: any) => setCurrentTab(e)}
/>
</Box>
<Box flex={'1 0 0'} overflow={'overlay'} pb={[4, 0]}>
{currentTab === TabEnum.data && <DataCard kbId={kbId} />}
{currentTab === TabEnum.test && <Test />}
{currentTab === TabEnum.info && <Info ref={InfoRef} kbId={kbId} form={form} />}
</Box>
</Box>
</PageContainer>
);
};
export async function getServerSideProps(context: any) {
const currentTab = context?.query?.currentTab || TabEnum.data;
const kbId = context?.query?.kbId;
return {
props: { currentTab, kbId }
};
}
export default React.memo(Detail);

View File

@@ -1,40 +0,0 @@
import React, { useEffect } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { useGlobalStore } from '@/store/global';
import { useRouter } from 'next/router';
import { useUserStore } from '@/store/user';
import SideBar from '@/components/SideBar';
import KbList from './components/KbList';
import KbDetail from './components/Detail';
const Kb = () => {
const router = useRouter();
const { kbId = '' } = router.query as { kbId: string };
const { isPc } = useGlobalStore();
const { lastKbId } = useUserStore();
// redirect
useEffect(() => {
if (isPc && !kbId && lastKbId) {
router.replace(`/kb?kbId=${lastKbId}`);
}
}, [isPc, kbId, lastKbId, router]);
return (
<Flex h={'100%'} position={'relative'} overflow={'hidden'}>
{/* 模型列表 */}
{(isPc || !kbId) && (
<SideBar w={['100%', '0 0 250px', '0 0 270px', '0 0 290px']}>
<KbList kbId={kbId} />
</SideBar>
)}
{!!kbId && (
<Box flex={'1 0 0'} w={0} h={'100%'} position={'relative'}>
<KbDetail kbId={kbId} />
</Box>
)}
</Flex>
);
};
export default Kb;

View File

@@ -0,0 +1,149 @@
import React, { useCallback } from 'react';
import { Box, Card, Flex, Grid, useTheme, Button, IconButton } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useUserStore } from '@/store/user';
import PageContainer from '@/components/PageContainer';
import { useConfirm } from '@/hooks/useConfirm';
import { AddIcon } from '@chakra-ui/icons';
import { useQuery } from '@tanstack/react-query';
import { useToast } from '@/hooks/useToast';
import { delKbById, postCreateKb } from '@/api/plugins/kb';
import { useRequest } from '@/hooks/useRequest';
import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
import Tag from '@/components/Tag';
const Kb = () => {
const theme = useTheme();
const router = useRouter();
const { toast } = useToast();
const { openConfirm, ConfirmChild } = useConfirm({
title: '删除提示',
content: '确认删除该知识库?'
});
const { myKbList, loadKbList, setKbList } = useUserStore();
useQuery(['loadKbList'], () => loadKbList());
/* 点击删除 */
const onclickDelKb = useCallback(
async (id: string) => {
try {
delKbById(id);
toast({
title: '删除成功',
status: 'success'
});
setKbList(myKbList.filter((item) => item._id !== id));
} catch (err: any) {
toast({
title: err?.message || '删除失败',
status: 'error'
});
}
},
[toast, setKbList, myKbList]
);
/* create a new kb and router to it */
const { mutate: onclickCreate, isLoading } = useRequest({
mutationFn: async () => {
const name = `知识库${myKbList.length + 1}`;
const id = await postCreateKb({ name });
return id;
},
successToast: '创建成功',
errorToast: '创建知识库出现意外',
onSuccess(id) {
router.push(`/kb/detail?kbId=${id}`);
}
});
return (
<PageContainer>
<Flex pt={3} px={5} alignItems={'center'}>
<Box flex={1} className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}>
</Box>
<Button
isLoading={isLoading}
leftIcon={<AddIcon />}
variant={'base'}
onClick={onclickCreate}
>
</Button>
</Flex>
<Grid
p={5}
gridTemplateColumns={['1fr', 'repeat(3,1fr)', 'repeat(4,1fr)', 'repeat(5,1fr)']}
gridGap={5}
>
{myKbList.map((kb) => (
<Card
display={'flex'}
flexDirection={'column'}
key={kb._id}
py={4}
px={5}
cursor={'pointer'}
h={'140px'}
border={theme.borders.md}
boxShadow={'none'}
userSelect={'none'}
position={'relative'}
_hover={{
boxShadow: '1px 1px 10px rgba(0,0,0,0.2)',
borderColor: 'transparent',
'& .delete': {
display: 'block'
}
}}
onClick={() =>
router.push({
pathname: '/kb/detail',
query: {
kbId: kb._id
}
})
}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={kb.avatar} borderRadius={'lg'} w={'28px'} />
<Box ml={3}>{kb.name}</Box>
<IconButton
className="delete"
position={'absolute'}
top={4}
right={4}
size={'sm'}
icon={<MyIcon name={'delete'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
display={['', 'none']}
_hover={{
bg: 'red.100'
}}
onClick={(e) => {
e.stopPropagation();
openConfirm(() => onclickDelKb(kb._id))();
}}
/>
</Flex>
<Box flex={'1 0 0'} overflow={'hidden'} pt={2}>
{kb.tags.map((tag, i) => (
<Tag key={i} mr={2} mb={2}>
{tag}
</Tag>
))}
</Box>
</Card>
))}
</Grid>
<ConfirmChild />
</PageContainer>
);
};
export default Kb;