Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
eef6d751
authored
Jun 10, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
perf: kb framwork
parent
6fd49b09
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
236 additions
and
196 deletions
+236
-196
client/src/pages/kb/components/Detail.tsx
+9
-196
client/src/pages/kb/components/Info.tsx
+227
-0
No files found.
client/src/pages/kb/components/Detail.tsx
View file @
eef6d751
import
React
,
{
use
Callback
,
useState
,
use
Ref
}
from
'react'
;
import
React
,
{
useRef
}
from
'react'
;
import
{
useRouter
}
from
'next/router'
;
import
{
useRouter
}
from
'next/router'
;
import
{
import
{
Card
,
Box
}
from
'@chakra-ui/react'
;
Card
,
Box
,
Flex
,
Button
,
Tooltip
,
FormControl
,
Input
,
Tag
,
IconButton
}
from
'@chakra-ui/react'
;
import
{
QuestionOutlineIcon
,
DeleteIcon
}
from
'@chakra-ui/icons'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useForm
}
from
'react-hook-form'
;
import
{
useForm
}
from
'react-hook-form'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
delKbById
,
putKbById
}
from
'@/api/plugins/kb'
;
import
{
KbItemType
}
from
'@/types/plugin'
;
import
{
KbItemType
}
from
'@/types/plugin'
;
import
{
useSelectFile
}
from
'@/hooks/useSelectFile'
;
import
{
useConfirm
}
from
'@/hooks/useConfirm'
;
import
{
compressImg
}
from
'@/utils/file'
;
import
DataCard
from
'./DataCard'
;
import
DataCard
from
'./DataCard'
;
import
{
getErrText
}
from
'@/utils/tools'
;
import
{
getErrText
}
from
'@/utils/tools'
;
import
Avatar
from
'@/components/Avatar
'
;
import
Info
,
{
type
ComponentRef
}
from
'./Info
'
;
const
Detail
=
({
kbId
}:
{
kbId
:
string
})
=>
{
const
Detail
=
({
kbId
}:
{
kbId
:
string
})
=>
{
const
{
toast
}
=
useToast
();
const
{
toast
}
=
useToast
();
const
router
=
useRouter
();
const
router
=
useRouter
();
const
InputRef
=
useRef
<
HTMLInputElement
>
(
null
);
const
BasicInfo
=
useRef
<
ComponentRef
>
(
null
);
const
{
setLastKbId
,
kbDetail
,
getKbDetail
,
loadKbList
,
myKbList
}
=
useUserStore
();
const
{
setLastKbId
,
kbDetail
,
getKbDetail
,
loadKbList
,
myKbList
}
=
useUserStore
();
const
[
btnLoading
,
setBtnLoading
]
=
useState
(
false
);
const
[
refresh
,
setRefresh
]
=
useState
(
false
);
const
{
getValues
,
formState
,
setValue
,
reset
,
register
,
handleSubmit
}
=
useForm
<
KbItemType
>
({
const
form
=
useForm
<
KbItemType
>
({
defaultValues
:
kbDetail
defaultValues
:
kbDetail
});
});
const
{
openConfirm
,
ConfirmChild
}
=
useConfirm
({
const
{
reset
}
=
form
;
content
:
'确认删除该知识库?数据将无法恢复,请确认!'
});
const
{
File
,
onOpen
:
onOpenSelectFile
}
=
useSelectFile
({
fileType
:
'.jpg,.png'
,
multiple
:
false
});
useQuery
([
kbId
,
myKbList
],
()
=>
getKbDetail
(
kbId
),
{
useQuery
([
kbId
,
myKbList
],
()
=>
getKbDetail
(
kbId
),
{
onSuccess
(
res
)
{
onSuccess
(
res
)
{
kbId
&&
setLastKbId
(
kbId
);
kbId
&&
setLastKbId
(
kbId
);
if
(
res
)
{
if
(
res
)
{
reset
(
res
);
reset
(
res
);
if
(
InputRef
.
current
)
{
BasicInfo
.
current
?.
initInput
?.(
res
.
tags
);
InputRef
.
current
.
value
=
res
.
tags
;
}
}
}
},
},
onError
(
err
:
any
)
{
onError
(
err
:
any
)
{
...
@@ -66,176 +41,14 @@ const Detail = ({ kbId }: { kbId: string }) => {
...
@@ -66,176 +41,14 @@ const Detail = ({ kbId }: { kbId: string }) => {
}
}
});
});
/* 点击删除 */
const
onclickDelKb
=
useCallback
(
async
()
=>
{
setBtnLoading
(
true
);
try
{
await
delKbById
(
kbId
);
toast
({
title
:
'删除成功'
,
status
:
'success'
});
router
.
replace
(
`/kb?kbId=
${
myKbList
.
find
((
item
)
=>
item
.
_id
!==
kbId
)?.
_id
||
''
}
`);
await loadKbList(true);
} catch (err: any) {
toast({
title: err?.message || '删除失败',
status: 'error'
});
}
setBtnLoading(false);
}, [setBtnLoading, kbId, toast, router, myKbList, loadKbList]);
const saveSubmitSuccess = useCallback(
async (data: KbItemType) => {
setBtnLoading(true);
try {
await putKbById({
id: kbId,
...data
});
await getKbDetail(kbId, true);
toast({
title: '更新成功',
status: 'success'
});
loadKbList(true);
} catch (err: any) {
toast({
title: err?.message || '更新失败',
status: 'error'
});
}
setBtnLoading(false);
},
[getKbDetail, kbId, loadKbList, toast]
);
const saveSubmitError = useCallback(() => {
// deep search message
const deepSearch = (obj: any): string => {
if (!obj) return '提交表单错误';
if (!!obj.message) {
return obj.message;
}
return deepSearch(Object.values(obj)[0]);
};
toast({
title: deepSearch(formState.errors),
status: 'error',
duration: 4000,
isClosable: true
});
}, [formState.errors, toast]);
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
if (!file) return;
try {
const base64 = await compressImg({
file,
maxW: 100,
maxH: 100
});
setValue('avatar', base64);
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : '头像选择异常',
status: 'warning'
});
}
},
[setRefresh, setValue, toast]
);
return
(
return
(
<
Box
h=
{
'100%'
}
p=
{
5
}
overflow=
{
'overlay'
}
position=
{
'relative'
}
>
<
Box
h=
{
'100%'
}
p=
{
5
}
overflow=
{
'overlay'
}
position=
{
'relative'
}
>
<
Card
p=
{
6
}
>
<
Card
p=
{
6
}
>
<Flex>
<
Info
ref=
{
BasicInfo
}
kbId=
{
kbId
}
form=
{
form
}
/>
<Box fontWeight={'bold'} fontSize={'2xl'} flex={1}>
知识库信息
</Box>
{kbDetail._id && (
<>
<Button
isLoading={btnLoading}
mr={3}
onClick={handleSubmit(saveSubmitSuccess, saveSubmitError)}
>
保存
</Button>
<IconButton
isLoading={btnLoading}
icon={<DeleteIcon />}
aria-label={''}
variant={'solid'}
colorScheme={'red'}
onClick={openConfirm(onclickDelKb)}
/>
</>
)}
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 60px'} w={0}>
头像
</Box>
<Avatar
src={getValues('avatar')}
w={['28px', '36px']}
h={['28px', '36px']}
cursor={'pointer'}
title={'点击切换头像'}
onClick={onOpenSelectFile}
/>
</Flex>
<FormControl mt={5}>
<Flex alignItems={'center'} maxW={'350px'}>
<Box flex={'0 0 60px'} w={0}>
名称
</Box>
<Input
{...register('name', {
required: '知识库名称不能为空'
})}
/>
</Flex>
</FormControl>
<Box>
<Flex mt={5} alignItems={'center'} maxW={'350px'} flexWrap={'wrap'}>
<Box flex={'0 0 60px'} w={0}>
标签
<Tooltip label={'仅用于记忆,用空格隔开多个标签'}>
<QuestionOutlineIcon ml={1} />
</Tooltip>
</Box>
<Input
flex={1}
ref={InputRef}
placeholder={'标签,使用空格分割。'}
onChange={(e) => {
setValue('tags', e.target.value);
setRefresh(!refresh);
}}
/>
<Box pl={'60px'} mt={2} w="100%">
{getValues('tags')
.split(' ')
.filter((item) => item)
.map((item, i) => (
<Tag mr={2} mb={2} key={i} variant={'outline'} colorScheme={'blue'}>
{item}
</Tag>
))}
</Box>
</Flex>
</Box>
</
Card
>
</
Card
>
<
Card
p=
{
6
}
mt=
{
5
}
>
<
Card
p=
{
6
}
mt=
{
5
}
>
<
DataCard
kbId=
{
kbId
}
/>
<
DataCard
kbId=
{
kbId
}
/>
</
Card
>
</
Card
>
<File onSelect={onSelectFile} />
<ConfirmChild />
</
Box
>
</
Box
>
);
);
};
};
...
...
client/src/pages/kb/components/Info.tsx
0 → 100644
View file @
eef6d751
import
React
,
{
useCallback
,
useState
,
useRef
,
forwardRef
,
useImperativeHandle
,
ForwardedRef
}
from
'react'
;
import
{
useRouter
}
from
'next/router'
;
import
{
Box
,
Flex
,
Button
,
FormControl
,
IconButton
,
Tooltip
,
Input
,
Tag
}
from
'@chakra-ui/react'
;
import
{
QuestionOutlineIcon
,
DeleteIcon
}
from
'@chakra-ui/icons'
;
import
{
delKbById
,
putKbById
}
from
'@/api/plugins/kb'
;
import
{
useSelectFile
}
from
'@/hooks/useSelectFile'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
useConfirm
}
from
'@/hooks/useConfirm'
;
import
{
UseFormReturn
}
from
'react-hook-form'
;
import
{
compressImg
}
from
'@/utils/file'
;
import
type
{
KbItemType
}
from
'@/types/plugin'
;
import
Avatar
from
'@/components/Avatar'
;
export
interface
ComponentRef
{
initInput
:
(
tags
:
string
)
=>
void
;
}
const
Info
=
(
{
kbId
,
form
}:
{
kbId
:
string
;
form
:
UseFormReturn
<
KbItemType
,
any
>
},
ref
:
ForwardedRef
<
ComponentRef
>
)
=>
{
const
{
getValues
,
formState
,
setValue
,
register
,
handleSubmit
}
=
form
;
const
InputRef
=
useRef
<
HTMLInputElement
>
(
null
);
const
{
toast
}
=
useToast
();
const
router
=
useRouter
();
const
[
btnLoading
,
setBtnLoading
]
=
useState
(
false
);
const
[
refresh
,
setRefresh
]
=
useState
(
false
);
const
{
openConfirm
,
ConfirmChild
}
=
useConfirm
({
content
:
'确认删除该知识库?数据将无法恢复,请确认!'
});
const
{
File
,
onOpen
:
onOpenSelectFile
}
=
useSelectFile
({
fileType
:
'.jpg,.png'
,
multiple
:
false
});
const
{
kbDetail
,
getKbDetail
,
loadKbList
,
myKbList
}
=
useUserStore
();
/* 点击删除 */
const
onclickDelKb
=
useCallback
(
async
()
=>
{
setBtnLoading
(
true
);
try
{
await
delKbById
(
kbId
);
toast
({
title
:
'删除成功'
,
status
:
'success'
});
router
.
replace
(
`/kb?kbId=
${
myKbList
.
find
((
item
)
=>
item
.
_id
!==
kbId
)?.
_id
||
''
}
`);
await loadKbList(true);
} catch (err: any) {
toast({
title: err?.message || '删除失败',
status: 'error'
});
}
setBtnLoading(false);
}, [setBtnLoading, kbId, toast, router, myKbList, loadKbList]);
const saveSubmitSuccess = useCallback(
async (data: KbItemType) => {
setBtnLoading(true);
try {
await putKbById({
id: kbId,
...data
});
await getKbDetail(kbId, true);
toast({
title: '更新成功',
status: 'success'
});
loadKbList(true);
} catch (err: any) {
toast({
title: err?.message || '更新失败',
status: 'error'
});
}
setBtnLoading(false);
},
[getKbDetail, kbId, loadKbList, toast]
);
const saveSubmitError = useCallback(() => {
// deep search message
const deepSearch = (obj: any): string => {
if (!obj) return '提交表单错误';
if (!!obj.message) {
return obj.message;
}
return deepSearch(Object.values(obj)[0]);
};
toast({
title: deepSearch(formState.errors),
status: 'error',
duration: 4000,
isClosable: true
});
}, [formState.errors, toast]);
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
if (!file) return;
try {
const base64 = await compressImg({
file,
maxW: 100,
maxH: 100
});
setValue('avatar', base64);
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : '头像选择异常',
status: 'warning'
});
}
},
[setRefresh, setValue, toast]
);
useImperativeHandle(ref, () => ({
initInput: (tags: string) => {
if (InputRef.current) {
InputRef.current.value = tags;
}
}
}));
return (
<Box>
<Flex>
<Box fontWeight={'bold'} fontSize={'2xl'} flex={1}>
知识库信息
</Box>
{kbDetail._id && (
<>
<Button
isLoading={btnLoading}
mr={3}
onClick={handleSubmit(saveSubmitSuccess, saveSubmitError)}
>
保存
</Button>
<IconButton
isLoading={btnLoading}
icon={<DeleteIcon />}
aria-label={''}
variant={'solid'}
colorScheme={'red'}
onClick={openConfirm(onclickDelKb)}
/>
</>
)}
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 60px'} w={0}>
头像
</Box>
<Avatar
src={getValues('avatar')}
w={['28px', '36px']}
h={['28px', '36px']}
cursor={'pointer'}
title={'点击切换头像'}
onClick={onOpenSelectFile}
/>
</Flex>
<FormControl mt={5}>
<Flex alignItems={'center'} maxW={'350px'}>
<Box flex={'0 0 60px'} w={0}>
名称
</Box>
<Input
{...register('name', {
required: '知识库名称不能为空'
})}
/>
</Flex>
</FormControl>
<Box>
<Flex mt={5} alignItems={'center'} maxW={'350px'} flexWrap={'wrap'}>
<Box flex={'0 0 60px'} w={0}>
标签
<Tooltip label={'仅用于记忆,用空格隔开多个标签'}>
<QuestionOutlineIcon ml={1} />
</Tooltip>
</Box>
<Input
flex={1}
ref={InputRef}
placeholder={'标签,使用空格分割。'}
onChange={(e) => {
setValue('tags', e.target.value);
setRefresh(!refresh);
}}
/>
<Box pl={'60px'} mt={2} w="100%">
{getValues('tags')
.split(' ')
.filter((item) => item)
.map((item, i) => (
<Tag mr={2} mb={2} key={i} variant={'outline'} colorScheme={'blue'}>
{item}
</Tag>
))}
</Box>
</Flex>
</Box>
<File onSelect={onSelectFile} />
<ConfirmChild />
</Box>
);
};
export default forwardRef(Info);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment