4.8.6 merge (#1943)

* Dataset collection forbid (#1885)

* perf: tool call support same id

* feat: collection forbid

* feat: collection forbid

* Inheritance Permission for apps (#1897)

* feat: app schema define

chore: references of authapp

* feat: authApp method inheritance

* feat: create and update api

* feat: update

* feat: inheritance Permission controller for app.

* feat: abstract version of inheritPermission

* feat: ancestorId for apps

* chore: update app

* fix: inheritPermission abstract version

* feat: update folder defaultPermission

* feat: app update api

* chore: inheritance frontend

* chore: app list api

* feat: update defaultPermission in app deatil

* feat: backend api finished

* feat: app inheritance permission fe

* fix: app update defaultpermission causes collaborator miss

* fix: ts error

* chore: adjust the codes

* chore: i18n

chore: i18n

* chore: fe adjust and i18n

* chore: adjust the code

* feat: resume api;
chore: rewrite update api and inheritPermission methods

* chore: something

* chore: fe code adjusting

* feat: frontend adjusting

* chore: fe code adjusting

* chore: adjusting the code

* perf: fe loading

* format

* Inheritance fix (#1908)

* fix: SlideCard

* fix: authapp did not return parent app for inheritance app

* fix: fe adjusting

* feat: fe adjusing

* perf: inherit per ux

* doc

* fix: ts errors (#1916)

* perf: inherit permission

* fix: permission inherit

* Workflow type (#1938)

* perf: workflow type

tmp workflow

perf: workflow type

feat: custom field config

* perf: dynamic input

* perf: node classify

* perf: node classify

* perf: node classify

* perf: node classify

* fix: workflow custom input

* feat: text editor and customFeedback move to basic nodes

* feat: community system plugin

* fix: ts

* feat: exprEval plugin

* perf: workflow type

* perf: plugin important

* fix: default templates

* perf: markdown hr css

* lock

* perf: fetch url

* perf: new plugin version

* fix: chat histories update

* fix: collection paths invalid

* perf: app card ui

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
Archer
2024-07-04 17:42:09 +08:00
committed by GitHub
parent babf03c218
commit a9cdece341
303 changed files with 18883 additions and 13149 deletions

View File

@@ -41,7 +41,7 @@ const Header = ({}: {}) => {
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
const router = useRouter();
const { parentId = '' } = router.query as { parentId: string; datasetId: string };
const { parentId = '' } = router.query as { parentId: string };
const { isPc } = useSystemStore();
const lastSearch = useRef('');

View File

@@ -9,7 +9,8 @@ import {
Th,
Td,
Tbody,
MenuButton
MenuButton,
Switch
} from '@chakra-ui/react';
import {
delDatasetCollectionById,
@@ -20,8 +21,7 @@ import { useQuery } from '@tanstack/react-query';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import dayjs from 'dayjs';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useRouter } from 'next/router';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
@@ -33,7 +33,6 @@ import {
import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils';
import { TabEnum } from '../../index';
import dynamic from 'next/dynamic';
import { useDrag } from '@/web/common/hooks/useDrag';
import SelectCollections from '@/web/core/dataset/components/SelectCollections';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
@@ -42,6 +41,14 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
import { useContextSelector } from 'use-context-selector';
import { CollectionPageContext } from './Context';
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
import { useI18n } from '@/web/context/I18n';
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import {
checkCollectionIsFolder,
getTrainingTypeLabel
} from '@fastgpt/global/core/dataset/collection/utils';
import { useFolderDrag } from '@/components/common/folder/useFolderDrag';
const Header = dynamic(() => import('./Header'));
const EmptyCollectionTip = dynamic(() => import('./EmptyCollectionTip'));
@@ -51,6 +58,7 @@ const CollectionCard = () => {
const router = useRouter();
const { toast } = useToast();
const { t } = useTranslation();
const { datasetT } = useI18n();
const { datasetDetail, loadDatasetDetail } = useContextSelector(DatasetPageContext, (v) => v);
const { openConfirm: openDeleteConfirm, ConfirmModal: ConfirmDeleteModal } = useConfirm({
@@ -70,8 +78,6 @@ const CollectionCard = () => {
const { collections, Pagination, total, getData, isGetting, pageNum, pageSize } =
useContextSelector(CollectionPageContext, (v) => v);
const { dragStartId, setDragStartId, dragTargetId, setDragTargetId } = useDrag();
// Ad file status icon
const formatCollections = useMemo(
() =>
@@ -83,16 +89,12 @@ const CollectionCard = () => {
statusText: t('dataset.collections.Collection Embedding', {
total: collection.trainingAmount
}),
color: 'myGray.600',
bg: 'myGray.50',
borderColor: 'borderColor.low'
colorSchema: 'gray'
};
}
return {
statusText: t('core.dataset.collection.status.active'),
color: 'green.600',
bg: 'green.50',
borderColor: 'green.300'
colorSchema: 'green'
};
})();
@@ -105,20 +107,15 @@ const CollectionCard = () => {
[collections, t]
);
const { mutate: onUpdateCollectionName } = useRequest({
mutationFn: ({ collectionId, name }: { collectionId: string; name: string }) => {
return putDatasetCollectionById({
id: collectionId,
name
});
},
onSuccess() {
getData(pageNum);
},
successToast: t('common.Rename Success'),
errorToast: t('common.Rename Failed')
});
const { runAsync: onUpdateCollection, loading: isUpdating } = useRequest2(
putDatasetCollectionById,
{
onSuccess() {
getData(pageNum);
},
successToast: t('common.Update Success')
}
);
const { mutate: onDelCollection, isLoading: isDeleting } = useRequest({
mutationFn: (collectionId: string) => {
return delDatasetCollectionById({
@@ -150,10 +147,6 @@ const CollectionCard = () => {
() => !!formatCollections.find((item) => item.trainingAmount > 0),
[formatCollections]
);
const isLoading = useMemo(
() => isDeleting || isSyncing || (isGetting && collections.length === 0),
[collections.length, isDeleting, isGetting, isSyncing]
);
useQuery(
['refreshCollection'],
@@ -170,6 +163,24 @@ const CollectionCard = () => {
}
);
const { getBoxProps, isDropping } = useFolderDrag({
activeStyles: {
bg: 'primary.100'
},
onDrop: async (dragId: string, targetId: string) => {
try {
await putDatasetCollectionById({
id: dragId,
parentId: targetId
});
getData(pageNum);
} catch (error) {}
}
});
const isLoading =
isUpdating || isDeleting || isSyncing || (isGetting && collections.length === 0) || isDropping;
return (
<MyBox isLoading={isLoading} h={'100%'} py={[2, 4]}>
<Flex ref={BoxRef} flexDirection={'column'} py={[1, 3]} h={'100%'}>
@@ -177,75 +188,41 @@ const CollectionCard = () => {
<Header />
{/* collection table */}
<TableContainer
px={[2, 6]}
mt={[0, 3]}
position={'relative'}
flex={'1 0 0'}
overflowY={'auto'}
fontSize={'sm'}
>
<TableContainer px={[2, 6]} mt={[0, 3]} flex={'1 0 0'} overflowY={'auto'} fontSize={'sm'}>
<Table variant={'simple'} draggable={false}>
<Thead draggable={false}>
<Tr>
<Th py={4}>#</Th>
<Th py={4}>{t('common.Name')}</Th>
<Th py={4}>{datasetT('collection.Training type')}</Th>
<Th py={4}>{t('dataset.collections.Data Amount')}</Th>
<Th py={4}>{t('core.dataset.Sync Time')}</Th>
<Th py={4}>{datasetT('collection.Create update time')}</Th>
<Th py={4}>{t('common.Status')}</Th>
<Th py={4}>{datasetT('Enable')}</Th>
<Th py={4} />
</Tr>
</Thead>
<Tbody>
<Tr h={'10px'} />
{formatCollections.map((collection, index) => (
<Tr h={'5px'} />
{formatCollections.map((collection) => (
<Tr
key={collection._id}
_hover={{ bg: 'myGray.50' }}
cursor={'pointer'}
data-drag-id={
collection.type === DatasetCollectionTypeEnum.folder
? collection._id
: undefined
}
bg={dragTargetId === collection._id ? 'primary.100' : ''}
userSelect={'none'}
onDragStart={() => {
setDragStartId(collection._id);
}}
onDragOver={(e) => {
e.preventDefault();
const targetId = e.currentTarget.getAttribute('data-drag-id');
if (!targetId) return;
DatasetCollectionTypeEnum.folder && setDragTargetId(targetId);
}}
onDragLeave={(e) => {
e.preventDefault();
setDragTargetId(undefined);
}}
onDrop={async (e) => {
e.preventDefault();
if (!dragTargetId || !dragStartId || dragTargetId === dragStartId) return;
// update parentId
try {
await putDatasetCollectionById({
id: dragStartId,
parentId: dragTargetId
});
getData(pageNum);
} catch (error) {}
setDragTargetId(undefined);
}}
{...getBoxProps({
dataId: collection._id,
isFolder: collection.type === DatasetCollectionTypeEnum.folder
})}
draggable={false}
onClick={() => {
if (collection.type === DatasetCollectionTypeEnum.folder) {
router.replace({
router.push({
query: {
...router.query,
parentId: collection._id
}
});
} else {
router.replace({
router.push({
query: {
...router.query,
collectionId: collection._id,
@@ -255,8 +232,7 @@ const CollectionCard = () => {
}
}}
>
<Td w={'50px'}>{index + 1}</Td>
<Td minW={'150px'} maxW={['200px', '300px']} draggable>
<Td minW={'150px'} maxW={['200px', '300px']} draggable py={2}>
<Flex alignItems={'center'}>
<MyIcon name={collection.icon as any} w={'16px'} mr={2} />
<MyTooltip label={t('common.folder.Drag Tip')} shouldWrapChildren={false}>
@@ -266,33 +242,36 @@ const CollectionCard = () => {
</MyTooltip>
</Flex>
</Td>
<Td>{collection.dataAmount || '-'}</Td>
<Td>{dayjs(collection.updateTime).format('YYYY/MM/DD HH:mm')}</Td>
<Td>
<Box
display={'inline-flex'}
alignItems={'center'}
w={'auto'}
color={collection.color}
bg={collection.bg}
borderWidth={'1px'}
borderColor={collection.borderColor}
px={3}
py={1}
borderRadius={'md'}
_before={{
content: '""',
w: '6px',
h: '6px',
mr: 2,
borderRadius: 'lg',
bg: collection.color
}}
>
{t(collection.statusText)}
</Box>
<Td py={2}>
{!checkCollectionIsFolder(collection.type) ? (
<>{t(getTrainingTypeLabel(collection.trainingType) || '-')}</>
) : (
'-'
)}
</Td>
<Td onClick={(e) => e.stopPropagation()}>
<Td py={2}>{collection.dataAmount || '-'}</Td>
<Td fontSize={'xs'} py={2} color={'myGray.500'}>
<Box>{formatTime2YMDHM(collection.createTime)}</Box>
<Box>{formatTime2YMDHM(collection.updateTime)}</Box>
</Td>
<Td py={2}>
<MyTag showDot colorSchema={collection.colorSchema as any} type={'borderFill'}>
{t(collection.statusText)}
</MyTag>
</Td>
<Td py={2} onClick={(e) => e.stopPropagation()}>
<Switch
isChecked={!collection.forbid}
size={'sm'}
onChange={(e) =>
onUpdateCollection({
id: collection._id,
forbid: !e.target.checked
})
}
/>
</Td>
<Td py={2} onClick={(e) => e.stopPropagation()}>
{collection.permission.hasWritePer && (
<MyMenu
width={100}
@@ -360,12 +339,11 @@ const CollectionCard = () => {
onClick: () =>
onOpenEditTitleModal({
defaultVal: collection.name,
onSuccess: (newName) => {
onUpdateCollectionName({
collectionId: collection._id,
onSuccess: (newName) =>
onUpdateCollection({
id: collection._id,
name: newName
});
}
})
})
}
]

View File

@@ -13,12 +13,15 @@ import {
DrawerHeader,
DrawerOverlay,
DrawerContent,
useDisclosure
useDisclosure,
HStack,
Switch
} from '@chakra-ui/react';
import {
getDatasetDataList,
delOneDatasetDataById,
getDatasetCollectionById
getDatasetCollectionById,
putDatasetDataById
} from '@/web/core/dataset/api';
import { DeleteIcon } from '@chakra-ui/icons';
import { useQuery } from '@tanstack/react-query';
@@ -47,6 +50,9 @@ import { useI18n } from '@/web/context/I18n';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
import { useContextSelector } from 'use-context-selector';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import MyBox from '@fastgpt/web/components/common/MyBox';
const DataCard = () => {
const BoxRef = useRef<HTMLDivElement>(null);
@@ -60,7 +66,6 @@ const DataCard = () => {
};
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
const { Loading, setIsLoading } = useLoading({ defaultLoading: true });
const { t } = useTranslation();
const { datasetT } = useI18n();
const [searchText, setSearchText] = useState('');
@@ -78,16 +83,17 @@ const DataCard = () => {
total,
getData,
pageNum,
pageSize
pageSize,
isLoading: isRequesting
} = usePagination<DatasetDataListItemType>({
api: getDatasetDataList,
pageSize: 24,
defaultRequest: false,
params: {
collectionId,
searchText
},
onChange() {
setIsLoading(false);
if (BoxRef.current) {
BoxRef.current.scrollTop = 0;
}
@@ -97,12 +103,16 @@ const DataCard = () => {
const [editDataId, setEditDataId] = useState<string>();
// get first page data
const getFirstData = useCallback(
debounce(() => {
useRequest2(
async () => {
getData(1);
lastSearch.current = searchText;
}, 300),
[searchText]
},
{
manual: false,
debounceWait: 300,
refreshDeps: [searchText]
}
);
// get file info
@@ -182,9 +192,18 @@ const DataCard = () => {
];
}, [collection, datasetT, t]);
const { run: onUpdate, loading } = useRequest2(putDatasetDataById, {
onSuccess() {
getData(pageNum);
}
});
const isLoading = isRequesting || loading;
return (
<Box position={'relative'} py={[1, 5]} h={'100%'}>
<MyBox isLoading={isLoading} position={'relative'} py={[1, 5]} h={'100%'}>
<Flex ref={BoxRef} flexDirection={'column'} h={'100%'}>
{/* Header */}
<Flex alignItems={'center'} px={5}>
<IconButton
mr={3}
@@ -270,20 +289,10 @@ const DataCard = () => {
value={searchText}
onChange={(e) => {
setSearchText(e.target.value);
getFirstData();
}}
onBlur={() => {
if (searchText === lastSearch.current) return;
getFirstData();
}}
onKeyDown={(e) => {
if (searchText === lastSearch.current) return;
if (e.key === 'Enter') {
getFirstData();
}
}}
/>
</Flex>
{/* data */}
<Box flex={'1 0 0'} overflow={'auto'} px={5}>
<Grid
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']}
@@ -304,29 +313,58 @@ const DataCard = () => {
borderColor: 'myGray.200',
boxShadow: 'lg',
bg: 'white',
'& .footer': { h: 'auto', p: 3 }
'& .footer': { h: 'auto', p: 3 },
'& .forbid-switch': { display: 'flex' }
}}
onClick={() => {
if (!collection) return;
setEditDataId(item._id);
}}
>
<Flex zIndex={1} alignItems={'center'} justifyContent={'space-between'}>
<Flex zIndex={1} alignItems={'center'}>
<MyTag type="borderFill"># {item.chunkIndex ?? '-'}</MyTag>
<Box
borderWidth={'1px'}
borderColor={'primary.200'}
bg={'primary.50'}
color={'primary.600'}
px={2}
fontSize={'sm'}
mr={1}
borderRadius={'md'}
className={'textEllipsis'}
flex={'1 0 0'}
w="0"
fontSize={'mini'}
textAlign={'right'}
>
# {item.chunkIndex ?? '-'}
</Box>
<Box className={'textEllipsis'} fontSize={'xs'}>
ID:{item._id}
</Box>
{/* {item.forbid ? (
<MyTag colorSchema="gray" bg={'transparent'} px={1} showDot>
{datasetT('Disabled')}
</MyTag>
) : (
<MyTag colorSchema="green" bg={'transparent'} px={1} showDot>
{datasetT('Enabled')}
</MyTag>
)}
<HStack
borderLeftWidth={'1.5px'}
className="forbid-switch"
display={['flex', 'none']}
borderLeftColor={'myGray.200'}
pl={1}
onClick={(e) => {
e.stopPropagation();
}}
h={'12px'}
>
<Switch
size={'sm'}
isChecked={!item.forbid}
onChange={(e) => {
e.stopPropagation();
onUpdate({
dataId: item._id,
forbid: !e.target.checked
});
}}
/>
</HStack> */}
</Flex>
<Box
maxH={'135px'}
@@ -335,13 +373,14 @@ const DataCard = () => {
wordBreak={'break-all'}
pt={1}
pb={3}
fontSize={'13px'}
fontSize={'sm'}
>
<Box color={'black'} mb={1}>
{item.q}
</Box>
<Box color={'myGray.700'}>{item.a}</Box>
{/* Mask */}
<Flex
className="footer"
position={'absolute'}
@@ -351,39 +390,43 @@ const DataCard = () => {
right={0}
h={'0'}
overflow={'hidden'}
p={0}
bg={'linear-gradient(to top, white,white 20%, rgba(255,255,255,0) 60%)'}
alignItems={'flex-end'}
fontSize={'sm'}
fontSize={'mini'}
>
<Flex alignItems={'center'}>
<MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} />
{item.q.length + (item.a?.length || 0)}
</Flex>
<Box flex={1} />
{canWrite && (
<IconButton
display={'flex'}
icon={<DeleteIcon />}
variant={'whiteDanger'}
size={'xsSquare'}
aria-label={'delete'}
onClick={(e) => {
e.stopPropagation();
openConfirm(async () => {
try {
await delOneDatasetDataById(item._id);
getData(pageNum);
} catch (error) {
toast({
title: getErrText(error),
status: 'error'
});
}
})();
}}
/>
)}
<HStack p={0} flex={1}>
<Flex alignItems={'center'}>
<MyIcon name="common/text/t" w={'0.8rem'} mr={1} color={'myGray.500'} />
<Box>{item.q.length + (item.a?.length || 0)}</Box>
</Flex>
<Box flex={1}></Box>
{/* <Box className={'textEllipsis'} flex={'1 0 0'} w="0">
ID:{item._id}
</Box> */}
{canWrite && (
<IconButton
display={'flex'}
icon={<DeleteIcon />}
variant={'whiteDanger'}
size={'xsSquare'}
aria-label={'delete'}
onClick={(e) => {
e.stopPropagation();
openConfirm(async () => {
try {
await delOneDatasetDataById(item._id);
getData(pageNum);
} catch (error) {
toast({
title: getErrText(error),
status: 'error'
});
}
})();
}}
/>
)}
</HStack>
</Flex>
</Box>
</Card>
@@ -440,8 +483,7 @@ const DataCard = () => {
/>
)}
<ConfirmModal />
<Loading fixed={false} />
</Box>
</MyBox>
);
};

View File

@@ -14,7 +14,7 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { getDefaultIndex } from '@fastgpt/global/core/dataset/utils';
import { DatasetDataIndexItemType } from '@fastgpt/global/core/dataset/type';
@@ -183,13 +183,14 @@ const InputDataModal = ({
errorToast: t('common.error.unKnow')
});
// update
const { mutate: onUpdateData, isLoading: isUpdating } = useRequest({
mutationFn: async (e: InputDataType) => {
if (!dataId) return e;
const { runAsync: onUpdateData, loading: isUpdating } = useRequest2(
async (e: InputDataType) => {
if (!dataId) return Promise.reject(t('common.error.unKnow'));
// not exactly same
await putDatasetDataById({
id: dataId,
dataId,
...e,
indexes:
e.indexes?.map((index) =>
@@ -202,13 +203,14 @@ const InputDataModal = ({
...e
};
},
successToast: t('dataset.data.Update Success Tip'),
errorToast: t('common.error.unKnow'),
onSuccess(data) {
onSuccess(data);
onClose();
{
successToast: t('dataset.data.Update Success Tip'),
onSuccess(data) {
onSuccess(data);
onClose();
}
}
});
);
// delete
const { mutate: onDeleteData, isLoading: isDeleting } = useRequest({
mutationFn: () => {
@@ -224,10 +226,7 @@ const InputDataModal = ({
errorToast: t('common.error.unKnow')
});
const isLoading = useMemo(
() => isImporting || isUpdating || isFetchingData || isDeleting,
[isImporting, isUpdating, isFetchingData, isDeleting]
);
const isLoading = isFetchingData || isDeleting;
return (
<MyModal isOpen={true} isCentered w={'90vw'} maxW={'1440px'} h={'90vh'}>
@@ -370,6 +369,7 @@ const InputDataModal = ({
>
<Button
isDisabled={!collection.permission.hasWritePer}
isLoading={isImporting || isUpdating}
// @ts-ignore
onClick={handleSubmit(dataId ? onUpdateData : sureImportData)}
>

View File

@@ -47,7 +47,7 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
(tab: TabEnum) => {
router.replace({
query: {
...query,
datasetId: query.datasetId,
currentTab: tab
}
});

View File

@@ -159,7 +159,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
>
{/* header */}
<Flex alignItems={'center'} justifyContent={'space-between'}>
<MySelect
<MySelect<'text' | 'file'>
size={'sm'}
w={'150px'}
list={[