Feat: App folder and permission (#1726)
* app folder * feat: app foldere * fix: run app param error * perf: select app ux * perf: folder rerender * fix: ts * fix: parentId * fix: permission * perf: loading ux * perf: per select ux * perf: clb context * perf: query extension tip * fix: ts * perf: app detail per * perf: default per
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -68,6 +68,14 @@ const DatasetParamsModal = ({
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [currentTabType, setCurrentTabType] = useState(SearchSettingTabEnum.searchMode);
|
||||
|
||||
const chatModelSelectList = (() =>
|
||||
llmModelList
|
||||
.filter((model) => model.usedInQueryExtension)
|
||||
.map((item) => ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
})))();
|
||||
|
||||
const { register, setValue, getValues, handleSubmit, watch } = useForm<DatasetParamsProps>({
|
||||
defaultValues: {
|
||||
limit,
|
||||
@@ -75,7 +83,7 @@ const DatasetParamsModal = ({
|
||||
searchMode,
|
||||
usingReRank: !!usingReRank && teamPlanStatus?.standardConstants?.permissionReRank !== false,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel: datasetSearchExtensionModel ?? llmModelList[0]?.model,
|
||||
datasetSearchExtensionModel: datasetSearchExtensionModel || chatModelSelectList[0]?.value,
|
||||
datasetSearchExtensionBg
|
||||
}
|
||||
});
|
||||
@@ -85,14 +93,6 @@ const DatasetParamsModal = ({
|
||||
const usingReRankWatch = watch('usingReRank');
|
||||
const searchModeWatch = watch('searchMode');
|
||||
|
||||
const chatModelSelectList = (() =>
|
||||
llmModelList
|
||||
.filter((model) => model.usedInQueryExtension)
|
||||
.map((item) => ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
})))();
|
||||
|
||||
const searchModeList = useMemo(() => {
|
||||
const list = Object.values(DatasetSearchModeMap);
|
||||
return list;
|
||||
@@ -109,6 +109,15 @@ const DatasetParamsModal = ({
|
||||
return usingReRank !== undefined && reRankModelList.length > 0;
|
||||
}, [reRankModelList.length, usingReRank]);
|
||||
|
||||
useEffect(() => {
|
||||
if (datasetSearchUsingCfrForm) {
|
||||
!queryExtensionModel &&
|
||||
setValue('datasetSearchExtensionModel', chatModelSelectList[0]?.value);
|
||||
} else {
|
||||
setValue('datasetSearchExtensionModel', '');
|
||||
}
|
||||
}, [chatModelSelectList, datasetSearchUsingCfrForm, queryExtensionModel, setValue]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
@@ -270,7 +279,7 @@ const DatasetParamsModal = ({
|
||||
{t('core.dataset.Query extension intro')}
|
||||
</Box>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box flex={'1 0 0'}>{t('core.dataset.search.Using query extension')}</Box>
|
||||
<FormLabel flex={'1 0 0'}>{t('core.dataset.search.Using query extension')}</FormLabel>
|
||||
<Switch {...register('datasetSearchUsingExtensionQuery')} />
|
||||
</Flex>
|
||||
{datasetSearchUsingCfrForm === true && (
|
||||
|
||||
@@ -237,7 +237,6 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () =>
|
||||
});
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess() {
|
||||
setNewData(undefined);
|
||||
},
|
||||
|
||||
@@ -14,22 +14,31 @@ const SearchParamsTip = ({
|
||||
limit = 1500,
|
||||
responseEmptyText,
|
||||
usingReRank = false,
|
||||
usingQueryExtension = false
|
||||
queryExtensionModel
|
||||
}: {
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
similarity?: number;
|
||||
limit?: number;
|
||||
responseEmptyText?: string;
|
||||
usingReRank?: boolean;
|
||||
usingQueryExtension?: boolean;
|
||||
queryExtensionModel?: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { reRankModelList } = useSystemStore();
|
||||
const { reRankModelList, llmModelList } = useSystemStore();
|
||||
|
||||
const hasReRankModel = reRankModelList.length > 0;
|
||||
const hasEmptyResponseMode = responseEmptyText !== undefined;
|
||||
const hasSimilarityMode = usingReRank || searchMode === DatasetSearchModeEnum.embedding;
|
||||
|
||||
const extensionModelName = useMemo(
|
||||
() =>
|
||||
queryExtensionModel
|
||||
? llmModelList.find((item) => item.model === queryExtensionModel)?.name ??
|
||||
llmModelList[0]?.name
|
||||
: undefined,
|
||||
[llmModelList, queryExtensionModel]
|
||||
);
|
||||
|
||||
return (
|
||||
<TableContainer
|
||||
bg={'primary.50'}
|
||||
@@ -73,8 +82,8 @@ const SearchParamsTip = ({
|
||||
{usingReRank ? '✅' : '❌'}
|
||||
</Td>
|
||||
)}
|
||||
<Td pt={0} pb={2}>
|
||||
{usingQueryExtension ? '✅' : '❌'}
|
||||
<Td pt={0} pb={2} fontSize={'mini'}>
|
||||
{extensionModelName ? extensionModelName : '❌'}
|
||||
</Td>
|
||||
{hasEmptyResponseMode && <Th>{responseEmptyText !== '' ? '✅' : '❌'}</Th>}
|
||||
</Tr>
|
||||
|
||||
@@ -1,108 +1,78 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { ModalBody, Flex, Box, useTheme, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { ModalBody, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import SelectOneResource from '@/components/common/folder/SelectOneResource';
|
||||
import {
|
||||
GetResourceFolderListProps,
|
||||
GetResourceListItemResponse
|
||||
} from '@fastgpt/global/common/parentFolder/type';
|
||||
import { getMyApps } from '@/web/core/app/api';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
const SelectAppModal = ({
|
||||
defaultApps = [],
|
||||
value,
|
||||
filterAppIds = [],
|
||||
max = 1,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultApps: string[];
|
||||
value?: SelectAppItemType;
|
||||
filterAppIds?: string[];
|
||||
max?: number;
|
||||
onClose: () => void;
|
||||
onSuccess: (e: SelectAppItemType[]) => void;
|
||||
onSuccess: (e: SelectAppItemType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading } = useLoading();
|
||||
const theme = useTheme();
|
||||
const [selectedApps, setSelectedApps] = React.useState<string[]>(defaultApps);
|
||||
/* 加载模型 */
|
||||
const { myApps, loadMyApps } = useAppStore();
|
||||
const { isLoading } = useQuery(['loadMyApos'], () => loadMyApps());
|
||||
const [selectedApp, setSelectedApp] = useState<SelectAppItemType | undefined>(value);
|
||||
|
||||
const apps = useMemo(
|
||||
() => myApps.filter((app) => !filterAppIds.includes(app._id)),
|
||||
[myApps, filterAppIds]
|
||||
const getAppList = useCallback(
|
||||
async ({ parentId }: GetResourceFolderListProps) => {
|
||||
return getMyApps({ parentId }).then((res) =>
|
||||
res
|
||||
.filter((item) => !filterAppIds.includes(item._id))
|
||||
.map<GetResourceListItemResponse>((item) => ({
|
||||
id: item._id,
|
||||
name: item.name,
|
||||
avatar: item.avatar,
|
||||
isFolder: item.type === AppTypeEnum.folder
|
||||
}))
|
||||
);
|
||||
},
|
||||
[filterAppIds]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
title={`选择应用${max > 1 ? `(${selectedApps.length}/${max})` : ''}`}
|
||||
title={`选择应用`}
|
||||
iconSrc="/imgs/workflow/ai.svg"
|
||||
onClose={onClose}
|
||||
position={'relative'}
|
||||
w={'600px'}
|
||||
>
|
||||
<ModalBody
|
||||
display={'grid'}
|
||||
gridTemplateColumns={['1fr', 'repeat(3, minmax(0, 1fr))']}
|
||||
gridGap={4}
|
||||
>
|
||||
{apps.map((app) => (
|
||||
<Flex
|
||||
key={app._id}
|
||||
alignItems={'center'}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'md'}
|
||||
p={2}
|
||||
cursor={'pointer'}
|
||||
{...(selectedApps.includes(app._id)
|
||||
? {
|
||||
bg: 'primary.100',
|
||||
onClick: () => {
|
||||
setSelectedApps(selectedApps.filter((e) => e !== app._id));
|
||||
}
|
||||
}
|
||||
: {
|
||||
onClick: () => {
|
||||
if (max === 1) {
|
||||
setSelectedApps([app._id]);
|
||||
} else if (selectedApps.length < max) {
|
||||
setSelectedApps([...selectedApps, app._id]);
|
||||
}
|
||||
}
|
||||
})}
|
||||
>
|
||||
<Avatar src={app.avatar} w={['16px', '22px']} />
|
||||
<Box fontSize={'sm'} color={'myGray.900'} ml={1}>
|
||||
{app.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
<ModalBody flex={'1 0 0'} overflow={'auto'} minH={'400px'} position={'relative'}>
|
||||
<SelectOneResource
|
||||
value={selectedApp?.id}
|
||||
onSelect={(id) => setSelectedApp(id ? { id } : undefined)}
|
||||
server={getAppList}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
{t('common.Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
ml={2}
|
||||
isDisabled={!selectedApp}
|
||||
onClick={() => {
|
||||
onSuccess(
|
||||
apps
|
||||
.filter((app) => selectedApps.includes(app._id))
|
||||
.map((app) => ({
|
||||
id: app._id,
|
||||
name: app.name,
|
||||
logo: app.avatar
|
||||
}))
|
||||
);
|
||||
if (!selectedApp) return;
|
||||
onSuccess(selectedApp);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Box, Button, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { Box, Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import SelectAppModal from '../../../../SelectAppModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/components/core/workflow/context';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getAppDetailById } from '@/web/core/app/api';
|
||||
|
||||
const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const filterAppIds = useContextSelector(WorkflowContext, (ctx) => ctx.filterAppIds);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
@@ -21,8 +22,28 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
} = useDisclosure();
|
||||
|
||||
const value = item.value as SelectAppItemType | undefined;
|
||||
|
||||
const filterAppString = useMemo(() => filterAppIds?.join(',') || '', [filterAppIds]);
|
||||
const { data: appDetail, loading } = useRequest2(
|
||||
() => {
|
||||
if (value?.id) return getAppDetailById(value.id);
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [value?.id],
|
||||
errorToast: 'Error',
|
||||
onError() {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: 'app',
|
||||
value: {
|
||||
...item,
|
||||
value: undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
@@ -33,26 +54,22 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
{t('core.module.Select app')}
|
||||
</Button>
|
||||
) : (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'md'}
|
||||
bg={'white'}
|
||||
px={3}
|
||||
py={2}
|
||||
<Button
|
||||
isLoading={loading}
|
||||
w={'100%'}
|
||||
justifyContent={loading ? 'center' : 'flex-start'}
|
||||
variant={'whiteFlow'}
|
||||
leftIcon={<Avatar src={appDetail?.avatar} w={6} />}
|
||||
>
|
||||
<Avatar src={value?.logo} w={6} />
|
||||
<Box fontWeight={'medium'} ml={2}>
|
||||
{value?.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
{appDetail?.name}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isOpenSelectApp && (
|
||||
<SelectAppModal
|
||||
defaultApps={item.value?.id ? [item.value.id] : []}
|
||||
filterAppIds={filterAppString.split(',')}
|
||||
value={item.value}
|
||||
filterAppIds={filterAppIds}
|
||||
onClose={onCloseSelectApp}
|
||||
onSuccess={(e) => {
|
||||
onChangeNode({
|
||||
@@ -61,7 +78,7 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
key: 'app',
|
||||
value: {
|
||||
...item,
|
||||
value: e[0]
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
@@ -70,15 +87,17 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
filterAppString,
|
||||
appDetail?.avatar,
|
||||
appDetail?.name,
|
||||
filterAppIds,
|
||||
isOpenSelectApp,
|
||||
item,
|
||||
loading,
|
||||
nodeId,
|
||||
onChangeNode,
|
||||
onCloseSelectApp,
|
||||
onOpenSelectApp,
|
||||
t,
|
||||
theme.borders.base,
|
||||
value
|
||||
]);
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
|
||||
similarity={data.similarity}
|
||||
limit={data.limit}
|
||||
usingReRank={data.usingReRank}
|
||||
usingQueryExtension={data.datasetSearchUsingExtensionQuery}
|
||||
queryExtensionModel={data.datasetSearchExtensionModel}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user