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:
Archer
2024-06-11 10:16:24 +08:00
committed by GitHub
parent b20d075d35
commit bc6864c3dc
89 changed files with 2495 additions and 695 deletions

View File

@@ -39,6 +39,7 @@ export default function InputGuideBox({
);
},
{
manual: false,
refreshDeps: [text],
throttleWait: 300
}

View File

@@ -155,12 +155,13 @@ ${JSON.stringify(questionGuides)}`;
borderColor={'myGray.200'}
boxShadow={'1'}
_hover={{
bg: 'auto',
color: 'primary.600'
bg: 'auto'
}}
>
<Avatar src={tool.toolAvatar} borderRadius={'md'} w={'14px'} mr={2} />
<Box mr={1}>{tool.toolName}</Box>
<Avatar src={tool.toolAvatar} borderRadius={'md'} w={'1rem'} mr={2} />
<Box mr={1} fontSize={'sm'}>
{tool.toolName}
</Box>
{isChatting && !tool.response && (
<MyIcon name={'common/loading'} w={'14px'} />
)}
@@ -219,7 +220,14 @@ ${toolResponse}`}
<ChatAvatar src={avatar} type={type} />
{!!chatStatusMap && statusBoxData && isLastChild && (
<Flex alignItems={'center'} px={3} py={'1.5px'} borderRadius="md" bg={chatStatusMap.bg}>
<Flex
alignItems={'center'}
px={3}
py={'1.5px'}
borderRadius="md"
bg={chatStatusMap.bg}
fontSize={'sm'}
>
<Box
className={styles.statusAnimation}
bg={chatStatusMap.color}

View File

@@ -0,0 +1,116 @@
import React, { useCallback } from 'react';
import { ModalFooter, ModalBody, Input, Button, Box, Textarea, HStack } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal/index';
import { useTranslation } from 'next-i18next';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { useForm } from 'react-hook-form';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { getErrText } from '@fastgpt/global/common/error/utils';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import Avatar from '@/components/Avatar';
import { useToast } from '@fastgpt/web/hooks/useToast';
export type EditResourceInfoFormType = {
id: string;
name: string;
avatar?: string;
intro?: string;
};
const EditResourceModal = ({
onClose,
onEdit,
title,
...defaultForm
}: EditResourceInfoFormType & {
title: string;
onClose: () => void;
onEdit: (data: EditResourceInfoFormType) => any;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const { register, watch, setValue, handleSubmit } = useForm<EditResourceInfoFormType>({
defaultValues: defaultForm
});
const avatar = watch('avatar');
const { runAsync: onSave, loading } = useRequest2(
(data: EditResourceInfoFormType) => onEdit(data),
{
onSuccess: (res) => {
onClose();
}
}
);
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
multiple: false
});
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
if (!file) return;
try {
const src = await compressImgFileAndUpload({
type: MongoImageTypeEnum.appAvatar,
file,
maxW: 300,
maxH: 300
});
setValue('avatar', src);
} catch (err: any) {
toast({
title: getErrText(err, t('common.error.Select avatar failed')),
status: 'warning'
});
}
},
[setValue, t, toast]
);
return (
<MyModal isOpen onClose={onClose} iconSrc={avatar} title={title}>
<ModalBody>
<Box>
<FormLabel mb={1}>{t('core.app.Name and avatar')}</FormLabel>
<HStack spacing={4}>
<MyTooltip label={t('common.Set Avatar')}>
<Avatar
flexShrink={0}
src={avatar}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
onClick={onOpenSelectFile}
/>
</MyTooltip>
<Input
{...register('name', { required: true })}
bg={'myGray.50'}
autoFocus
maxLength={20}
/>
</HStack>
</Box>
<Box mt={4}>
<FormLabel mb={1}>{t('common.Intro')}</FormLabel>
<Textarea {...register('intro')} bg={'myGray.50'} maxLength={200} />
</Box>
</ModalBody>
<ModalFooter>
<Button isLoading={loading} onClick={handleSubmit(onSave)}>
{t('common.Confirm')}
</Button>
</ModalFooter>
<File onSelect={onSelectFile} />
</MyModal>
);
};
export default EditResourceModal;

View File

@@ -1,4 +1,3 @@
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box, Flex } from '@chakra-ui/react';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import React, { useMemo } from 'react';
@@ -37,16 +36,19 @@ const ParentPaths = (props: {
{concatPaths.map((item, i) => (
<Flex key={item.parentId || i} alignItems={'center'}>
<Box
fontSize={['sm', fontSize || 'md']}
py={1}
px={[1, 2]}
fontSize={['sm', fontSize || 'sm']}
py={0.5}
px={1.5}
borderRadius={'md'}
{...(i === concatPaths.length - 1
? {
cursor: 'default'
cursor: 'default',
color: 'myGray.700',
fontWeight: 'bold'
}
: {
cursor: 'pointer',
color: 'myGray.600',
_hover: {
bg: 'myGray.100'
},
@@ -58,7 +60,9 @@ const ParentPaths = (props: {
{item.parentName}
</Box>
{i !== concatPaths.length - 1 && (
<MyIcon name={'common/rightArrowLight'} color={'myGray.500'} w={'14px'} />
<Box mx={1.5} color={'myGray.500'}>
/
</Box>
)}
</Flex>
))}

View File

@@ -0,0 +1,184 @@
import React, { useCallback, useState } from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { Box, Button, Flex, ModalBody, ModalFooter } from '@chakra-ui/react';
import {
GetResourceFolderListProps,
GetResourceFolderListItemResponse,
ParentIdType
} from '@fastgpt/global/common/parentFolder/type';
import { useMemoizedFn, useMount } from 'ahooks';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
type FolderItemType = {
id: string;
name: string;
open: boolean;
children?: FolderItemType[];
};
const rootId = 'root';
type Props = {
moveResourceId: string;
title: string;
server: (e: GetResourceFolderListProps) => Promise<GetResourceFolderListItemResponse[]>;
onConfirm: (id: ParentIdType) => Promise<any>;
onClose: () => void;
};
const MoveModal = ({ moveResourceId, title, server, onConfirm, onClose }: Props) => {
const { t } = useTranslation();
const [selectedId, setSelectedId] = React.useState<string>();
const [requestingIdList, setRequestingIdList] = useState<ParentIdType[]>([]);
const [folderList, setFolderList] = useState<FolderItemType[]>([]);
const { runAsync: requestServer } = useRequest2((e: GetResourceFolderListProps) => {
if (requestingIdList.includes(e.parentId)) return Promise.reject(null);
setRequestingIdList((state) => [...state, e.parentId]);
return server(e).finally(() =>
setRequestingIdList((state) => state.filter((id) => id !== e.parentId))
);
}, {});
useMount(async () => {
const data = await requestServer({ parentId: null });
setFolderList([
{
id: rootId,
name: t('common.folder.Root Path'),
open: true,
children: data.map((item) => ({
id: item.id,
name: item.name,
open: false
}))
}
]);
});
const RenderList = useMemoizedFn(
({ list, index = 0 }: { list: FolderItemType[]; index?: number }) => {
return (
<>
{list
// can not move to itself
.filter((item) => moveResourceId !== item.id)
.map((item) => (
<Box key={item.id} _notLast={{ mb: 0.5 }} userSelect={'none'}>
<Flex
alignItems={'center'}
cursor={'pointer'}
py={1}
pl={index === 0 ? '0.5rem' : `${1.75 * (index - 1) + 0.5}rem`}
pr={2}
borderRadius={'md'}
_hover={{
bg: 'myGray.100'
}}
{...(item.id === selectedId
? {
bg: 'primary.50 !important',
onClick: () => setSelectedId(undefined)
}
: {
onClick: () => setSelectedId(item.id)
})}
>
{index !== 0 && (
<Flex
alignItems={'center'}
justifyContent={'center'}
visibility={!item.children || item.children.length > 0 ? 'visible' : 'hidden'}
w={'1.25rem'}
h={'1.25rem'}
cursor={'pointer'}
borderRadius={'xs'}
_hover={{
bg: 'rgba(31, 35, 41, 0.08)'
}}
onClick={async (e) => {
e.stopPropagation();
if (requestingIdList.includes(item.id)) return;
if (!item.children) {
const data = await requestServer({ parentId: item.id });
item.children = data.map((item) => ({
id: item.id,
name: item.name,
open: false
}));
}
item.open = !item.open;
setFolderList([...folderList]);
}}
>
<MyIcon
name={
requestingIdList.includes(item.id)
? 'common/loading'
: 'common/rightArrowFill'
}
w={'1.25rem'}
color={'myGray.500'}
transform={item.open ? 'rotate(90deg)' : 'none'}
/>
</Flex>
)}
<MyIcon ml={index !== 0 ? '0.5rem' : 0} name={FolderIcon} w={'1.25rem'} />
<Box fontSize={'sm'} ml={2}>
{item.name}
</Box>
</Flex>
{item.children && item.open && (
<Box mt={0.5}>
<RenderList list={item.children} index={index + 1} />
</Box>
)}
</Box>
))}
</>
);
}
);
const { runAsync: onConfirmSelect, loading: confirming } = useRequest2(
() => {
if (selectedId) {
return onConfirm(selectedId === rootId ? null : selectedId);
}
return Promise.reject('');
},
{
onSuccess: () => {
onClose();
},
successToast: t('common.folder.Move Success')
}
);
return (
<MyModal
isLoading={folderList.length === 0}
iconSrc="/imgs/modal/move.svg"
isOpen
w={'30rem'}
title={title}
onClose={onClose}
>
<ModalBody flex={'1 0 0'} overflow={'auto'} minH={'400px'}>
<RenderList list={folderList} />
</ModalBody>
<ModalFooter>
<Button isLoading={confirming} isDisabled={!selectedId} onClick={onConfirmSelect}>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default MoveModal;

View File

@@ -0,0 +1,68 @@
import { Box, Flex } from '@chakra-ui/react';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import React, { useMemo } from 'react';
import { useTranslation } from 'next-i18next';
const FolderPath = (props: {
paths: ParentTreePathItemType[];
rootName?: string;
FirstPathDom?: React.ReactNode;
onClick: (parentId: string) => void;
fontSize?: string;
}) => {
const { t } = useTranslation();
const { paths, rootName = t('common.folder.Root Path'), FirstPathDom, onClick, fontSize } = props;
const concatPaths = useMemo(
() => [
{
parentId: '',
parentName: rootName
},
...paths
],
[rootName, paths]
);
return paths.length === 0 && !!FirstPathDom ? (
<>{FirstPathDom}</>
) : (
<Flex flex={1} ml={-1.5}>
{concatPaths.map((item, i) => (
<Flex key={item.parentId || i} alignItems={'center'}>
<Box
fontSize={['sm', fontSize || 'sm']}
py={0.5}
px={1.5}
borderRadius={'md'}
{...(i === concatPaths.length - 1
? {
cursor: 'default',
color: 'myGray.700',
fontWeight: 'bold'
}
: {
cursor: 'pointer',
color: 'myGray.600',
_hover: {
bg: 'myGray.100'
},
onClick: () => {
onClick(item.parentId);
}
})}
>
{item.parentName}
</Box>
{i !== concatPaths.length - 1 && (
<Box mx={1.5} color={'myGray.500'}>
/
</Box>
)}
</Flex>
))}
</Flex>
);
};
export default React.memo(FolderPath);

View File

@@ -0,0 +1,140 @@
import React, { useState } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import {
GetResourceFolderListProps,
GetResourceListItemResponse,
ParentIdType
} from '@fastgpt/global/common/parentFolder/type';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Loading from '@fastgpt/web/components/common/MyLoading';
import Avatar from '@/components/Avatar';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useMemoizedFn } from 'ahooks';
type ResourceItemType = GetResourceListItemResponse & {
open: boolean;
children?: ResourceItemType[];
};
const SelectOneResource = ({
server,
value,
onSelect
}: {
server: (e: GetResourceFolderListProps) => Promise<GetResourceListItemResponse[]>;
value?: ParentIdType;
onSelect: (e?: string) => any;
}) => {
const [dataList, setDataList] = useState<ResourceItemType[]>([]);
const [requestingIdList, setRequestingIdList] = useState<ParentIdType[]>([]);
const { runAsync: requestServer } = useRequest2((e: GetResourceFolderListProps) => {
if (requestingIdList.includes(e.parentId)) return Promise.reject(null);
setRequestingIdList((state) => [...state, e.parentId]);
return server(e).finally(() =>
setRequestingIdList((state) => state.filter((id) => id !== e.parentId))
);
}, {});
const { loading } = useRequest2(() => requestServer({ parentId: null }), {
manual: false,
onSuccess: (data) => {
setDataList(
data.map((item) => ({
...item,
open: false
}))
);
}
});
const Render = useMemoizedFn(
({ list, index = 0 }: { list: ResourceItemType[]; index?: number }) => {
return (
<>
{list.map((item) => (
<Box key={item.id} _notLast={{ mb: 0.5 }} userSelect={'none'}>
<Flex
alignItems={'center'}
cursor={'pointer'}
py={1}
pl={`${1.25 * index + 0.5}rem`}
pr={2}
borderRadius={'md'}
_hover={{
bg: 'myGray.100'
}}
{...(item.id === value
? {
bg: 'primary.50 !important',
onClick: () => onSelect(undefined)
}
: {
onClick: async () => {
// folder => open(request children) or close
if (item.isFolder) {
if (!item.children) {
const data = await requestServer({ parentId: item.id });
item.children = data.map((item) => ({
...item,
open: false
}));
}
item.open = !item.open;
setDataList([...dataList]);
} else {
onSelect(item.id);
}
}
})}
>
<Flex
alignItems={'center'}
justifyContent={'center'}
visibility={
item.isFolder && (!item.children || item.children.length > 0)
? 'visible'
: 'hidden'
}
w={'1.25rem'}
h={'1.25rem'}
cursor={'pointer'}
borderRadius={'xs'}
_hover={{
bg: 'rgba(31, 35, 41, 0.08)'
}}
>
<MyIcon
name={
requestingIdList.includes(item.id)
? 'common/loading'
: 'common/rightArrowFill'
}
w={'14px'}
color={'myGray.500'}
transform={item.open ? 'rotate(90deg)' : 'none'}
/>
</Flex>
<Avatar ml={index !== 0 ? '0.5rem' : 0} src={item.avatar} w={'1.25rem'} />
<Box fontSize={'sm'} ml={2}>
{item.name}
</Box>
</Flex>
{item.children && item.open && (
<Box mt={0.5}>
<Render list={item.children} index={index + 1} />
</Box>
)}
</Box>
))}
</>
);
}
);
return loading ? <Loading fixed={false} /> : <Render list={dataList} />;
};
export default SelectOneResource;

View File

@@ -0,0 +1,178 @@
import { Box, Button, Flex, HStack } from '@chakra-ui/react';
import React from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import MyDivider from '@fastgpt/web/components/common/MyDivider';
import { useTranslation } from 'next-i18next';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
import {
CollaboratorContextProvider,
MemberManagerInputPropsType
} from '../../support/permission/MemberManager/context';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
const FolderSlideCard = ({
name,
intro,
onEdit,
onMove,
deleteTip,
onDelete,
defaultPer,
managePer
}: {
name: string;
intro?: string;
onEdit: () => void;
onMove: () => void;
deleteTip: string;
onDelete: () => void;
defaultPer: {
value: PermissionValueType;
defaultValue: PermissionValueType;
onChange: (v: PermissionValueType) => Promise<any>;
};
managePer: MemberManagerInputPropsType;
}) => {
const { t } = useTranslation();
const { ConfirmModal, openConfirm } = useConfirm({
type: 'delete',
content: deleteTip
});
return (
<Box w={'13rem'}>
<Box>
<HStack>
<MyIcon name={FolderIcon} w={'1.5rem'} />
<Box color={'myGray.900'}>{name}</Box>
<MyIcon
name={'edit'}
_hover={{ color: 'primary.600' }}
w={'0.875rem'}
cursor={'pointer'}
onClick={onEdit}
/>
</HStack>
<Box mt={3} fontSize={'sm'} color={'myGray.500'} cursor={'pointer'} onClick={onEdit}>
{intro || '暂无介绍'}
</Box>
</Box>
{managePer.permission.hasManagePer && (
<>
<MyDivider my={6} />
<Box>
<FormLabel>{t('common.Operation')}</FormLabel>
<Button
variant={'transparentBase'}
pl={1}
leftIcon={<MyIcon name={'common/file/move'} w={'1rem'} />}
transform={'none !important'}
w={'100%'}
justifyContent={'flex-start'}
size={'sm'}
fontSize={'mini'}
mt={4}
onClick={onMove}
>
{t('common.Move')}
</Button>
<Button
variant={'transparentDanger'}
pl={1}
leftIcon={<MyIcon name={'delete'} w={'1rem'} />}
transform={'none !important'}
w={'100%'}
justifyContent={'flex-start'}
size={'sm'}
fontSize={'mini'}
mt={3}
onClick={() => {
openConfirm(onDelete)();
}}
>
{t('common.Delete folder')}
</Button>
</Box>
</>
)}
<MyDivider my={6} />
<Box>
<FormLabel>{t('support.permission.Permission')}</FormLabel>
{managePer.permission.hasManagePer && (
<Box mt={5}>
<Box fontSize={'sm'} color={'myGray.500'}>
{t('permission.Default permission')}
</Box>
<DefaultPermissionList
mt="1"
per={defaultPer.value}
defaultPer={defaultPer.defaultValue}
onChange={defaultPer.onChange}
/>
</Box>
)}
<Box mt={6}>
<CollaboratorContextProvider {...managePer}>
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
return (
<>
<Flex alignItems="center" justifyContent="space-between">
<Box fontSize={'sm'} color={'myGray.500'}>
{t('permission.Collaborator')}
</Box>
{managePer.permission.hasManagePer && (
<HStack spacing={3}>
<MyTooltip label={t('permission.Manage')}>
<MyIcon
w="1rem"
name="common/settingLight"
cursor={'pointer'}
_hover={{ color: 'primary.600' }}
onClick={onOpenManageModal}
/>
</MyTooltip>
<MyTooltip label={t('common.Add')}>
<MyIcon
w="1rem"
name="support/permission/collaborator"
cursor={'pointer'}
_hover={{ color: 'primary.600' }}
onClick={onOpenAddMember}
/>
</MyTooltip>
</HStack>
)}
</Flex>
<MemberListCard
mt={2}
tagStyle={{
type: 'borderSolid',
colorSchema: 'gray'
}}
/>
</>
);
}}
</CollaboratorContextProvider>
</Box>
</Box>
<ConfirmModal />
</Box>
);
};
export default FolderSlideCard;

View File

@@ -0,0 +1,54 @@
import React, { useState, DragEvent, useCallback } from 'react';
import type { BoxProps } from '@chakra-ui/react';
export const useFolderDrag = ({
onDrop,
activeStyles
}: {
onDrop: (dragId: string, targetId: string) => any;
activeStyles: BoxProps;
}) => {
const [dragId, setDragId] = useState<string>();
const [targetId, setTargetId] = useState<string>();
const getBoxProps = useCallback(
({ dataId, isFolder }: { dataId: string; isFolder: boolean }) => {
return {
draggable: true,
'data-drag-id': isFolder ? dataId : undefined,
onDragStart: (e: DragEvent<HTMLDivElement>) => {
setDragId(dataId);
},
onDragOver: (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
const targetId = e.currentTarget.getAttribute('data-drag-id');
if (!targetId) return;
setTargetId(targetId);
},
onDragLeave: (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
setTargetId(undefined);
},
onDrop: (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
if (targetId && dragId && targetId !== dragId) {
onDrop(dragId, targetId);
}
setTargetId(undefined);
setDragId(undefined);
},
...(activeStyles &&
targetId === dataId && {
...activeStyles
})
};
},
[activeStyles, dragId, onDrop, targetId]
);
return {
getBoxProps
};
};

View File

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

View File

@@ -237,7 +237,6 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () =>
});
},
{
manual: true,
onSuccess() {
setNewData(undefined);
},

View File

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

View File

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

View File

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

View File

@@ -82,7 +82,7 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
similarity={data.similarity}
limit={data.limit}
usingReRank={data.usingReRank}
usingQueryExtension={data.datasetSearchUsingExtensionQuery}
queryExtensionModel={data.datasetSearchExtensionModel}
/>
</>
);

View File

@@ -0,0 +1,96 @@
import React from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { MemberManagerInputPropsType, CollaboratorContextProvider } from '../MemberManager/context';
import { Box, Button, Flex, HStack, ModalBody } from '@chakra-ui/react';
import Avatar from '@/components/Avatar';
import DefaultPermissionList from '../DefaultPerList';
import MyIcon from '@fastgpt/web/components/common/Icon';
export type ConfigPerModalProps = {
avatar?: string;
name: string;
defaultPer: {
value: PermissionValueType;
defaultValue: PermissionValueType;
onChange: (v: PermissionValueType) => Promise<any>;
};
managePer: MemberManagerInputPropsType;
};
const ConfigPerModal = ({
avatar,
name,
defaultPer,
managePer,
onClose
}: ConfigPerModalProps & {
onClose: () => void;
}) => {
const { t } = useTranslation();
return (
<MyModal
isOpen
iconSrc="/imgs/modal/key.svg"
onClose={onClose}
title={t('permission.Permission config')}
>
<ModalBody>
<HStack>
<Avatar src={avatar} w={'1.75rem'} />
<Box fontSize={'lg'}>{name}</Box>
</HStack>
<Box mt={6}>
<Box fontSize={'sm'}>{t('permission.Default permission')}</Box>
<DefaultPermissionList
mt="1"
per={defaultPer.value}
defaultPer={defaultPer.defaultValue}
onChange={defaultPer.onChange}
/>
</Box>
<Box mt={4}>
<CollaboratorContextProvider {...managePer}>
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
return (
<>
<Flex
alignItems="center"
flexDirection="row"
justifyContent="space-between"
w="full"
>
<Box fontSize={'sm'}>{t('permission.Collaborator')}</Box>
<Flex flexDirection="row" gap="2">
<Button
size="sm"
variant="whitePrimary"
leftIcon={<MyIcon w="4" name="common/settingLight" />}
onClick={onOpenManageModal}
>
{t('permission.Manage')}
</Button>
<Button
size="sm"
variant="whitePrimary"
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
onClick={onOpenAddMember}
>
{t('common.Add')}
</Button>
</Flex>
</Flex>
<MemberListCard mt={2} p={1.5} bg="myGray.100" borderRadius="md" />
</>
);
}}
</CollaboratorContextProvider>
</Box>
</ModalBody>
</MyModal>
);
};
export default ConfigPerModal;

View File

@@ -3,6 +3,8 @@ import MySelect from '@fastgpt/web/components/common/MySelect';
import { useTranslation } from 'next-i18next';
import React from 'react';
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
export enum defaultPermissionEnum {
private = 'private',
@@ -13,16 +15,16 @@ export enum defaultPermissionEnum {
type Props = Omit<BoxProps, 'onChange'> & {
per: PermissionValueType;
defaultPer: PermissionValueType;
readPer: PermissionValueType;
writePer: PermissionValueType;
onChange: (v: PermissionValueType) => void;
readPer?: PermissionValueType;
writePer?: PermissionValueType;
onChange: (v: PermissionValueType) => Promise<any> | any;
};
const DefaultPermissionList = ({
per,
defaultPer,
readPer,
writePer,
readPer = ReadPermissionVal,
writePer = WritePermissionVal,
onChange,
...styles
}: Props) => {
@@ -33,14 +35,17 @@ const DefaultPermissionList = ({
{ label: '团队可编辑', value: writePer }
];
const { runAsync: onRequestChange, loading } = useRequest2(async (v: PermissionValueType) =>
onChange(v)
);
return (
<Box {...styles}>
<MySelect
isLoading={loading}
list={defaultPermissionSelectList}
value={per}
onchange={(v) => {
onChange(v);
}}
onchange={onRequestChange}
/>
</Box>
);

View File

@@ -23,7 +23,6 @@ import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getTeamMembers } from '@/web/support/user/team/api';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { Permission } from '@fastgpt/global/support/permission/controller';
import { ChevronDownIcon } from '@chakra-ui/icons';
import Avatar from '@/components/Avatar';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
@@ -32,11 +31,10 @@ export type AddModalPropsType = {
onClose: () => void;
};
export function AddMemberModal({ onClose }: AddModalPropsType) {
const toast = useToast();
function AddMemberModal({ onClose }: AddModalPropsType) {
const { userInfo } = useUserStore();
const { permissionList, collaboratorList, onUpdateCollaborators, getPreLabelList } =
const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList } =
useContextSelector(CollaboratorContext, (v) => v);
const [searchText, setSearchText] = useState<string>('');
const {
@@ -50,7 +48,7 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
});
const filterMembers = useMemo(() => {
return members.filter((item) => {
if (item.permission.isOwner) return false;
// if (item.permission.isOwner) return false;
if (item.tmbId === userInfo?.team?.tmbId) return false;
if (!searchText) return true;
return item.memberName.includes(searchText);
@@ -60,8 +58,8 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value);
const perLabel = useMemo(() => {
return getPreLabelList(selectedPermission).join('、');
}, [getPreLabelList, selectedPermission]);
return getPerLabelList(selectedPermission).join('、');
}, [getPerLabelList, selectedPermission]);
const { mutate: onConfirm, isLoading: isUpdating } = useRequest({
mutationFn: () => {
@@ -85,6 +83,7 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
borderColor="myGray.200"
borderRadius="0.5rem"
gridTemplateColumns="55% 45%"
fontSize={'sm'}
>
<Flex
flexDirection="column"
@@ -141,7 +140,9 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
<MyAvatar src={member.avatar} w="32px" />
<Box ml="2">{member.memberName}</Box>
</Flex>
{!!collaborator && <PermissionTags permission={collaborator.permission} />}
{!!collaborator && (
<PermissionTags permission={collaborator.permission.value} />
)}
</Flex>
</Flex>
);
@@ -210,3 +211,5 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
</MyModal>
);
}
export default AddMemberModal;

View File

@@ -18,10 +18,11 @@ import PermissionTags from './PermissionTags';
import Avatar from '@/components/Avatar';
import { CollaboratorContext } from './context';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { useUserStore } from '@/web/support/user/useUserStore';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import Loading from '@fastgpt/web/components/common/MyLoading';
export type ManageModalProps = {
onClose: () => void;
@@ -29,14 +30,12 @@ export type ManageModalProps = {
function ManageModal({ onClose }: ManageModalProps) {
const { userInfo } = useUserStore();
const { collaboratorList, onUpdateCollaborators, onDelOneCollaborator } = useContextSelector(
CollaboratorContext,
(v) => v
);
const { permission, collaboratorList, onUpdateCollaborators, onDelOneCollaborator } =
useContextSelector(CollaboratorContext, (v) => v);
const { mutate: onDelete, isLoading: isDeleting } = useRequest({
mutationFn: (tmbId: string) => onDelOneCollaborator(tmbId)
});
const { runAsync: onDelete, loading: isDeleting } = useRequest2((tmbId: string) =>
onDelOneCollaborator(tmbId)
);
const { mutate: onUpdate, isLoading: isUpdating } = useRequest({
mutationFn: ({ tmbId, per }: { tmbId: string; per: PermissionValueType }) => {
@@ -49,14 +48,7 @@ function ManageModal({ onClose }: ManageModalProps) {
const loading = isDeleting || isUpdating;
return (
<MyModal
isLoading={loading}
isOpen
onClose={onClose}
minW="600px"
title="管理协作者"
iconSrc="common/settingLight"
>
<MyModal isOpen onClose={onClose} minW="600px" title="管理协作者" iconSrc="common/settingLight">
<ModalBody>
<TableContainer borderRadius="md" minH="400px">
<Table>
@@ -86,26 +78,28 @@ function ManageModal({ onClose }: ManageModalProps) {
</Flex>
</Td>
<Td border="none">
<PermissionTags permission={item.permission} />
<PermissionTags permission={item.permission.value} />
</Td>
<Td border="none">
{item.tmbId !== userInfo?.team?.tmbId && (
<PermissionSelect
Button={
<MyIcon name={'edit'} w={'16px'} _hover={{ color: 'primary.600' }} />
}
value={item.permission}
onChange={(per) => {
onUpdate({
tmbId: item.tmbId,
per
});
}}
onDelete={() => {
onDelete(item.tmbId);
}}
/>
)}
{/* Not self; Not owner and other manager */}
{item.tmbId !== userInfo?.team?.tmbId &&
(permission.isOwner || !item.permission.hasManagePer) && (
<PermissionSelect
Button={
<MyIcon name={'edit'} w={'16px'} _hover={{ color: 'primary.600' }} />
}
value={item.permission.value}
onChange={(per) => {
onUpdate({
tmbId: item.tmbId,
per
});
}}
onDelete={() => {
onDelete(item.tmbId);
}}
/>
)}
</Td>
</Tr>
);
@@ -114,6 +108,7 @@ function ManageModal({ onClose }: ManageModalProps) {
</Table>
{collaboratorList?.length === 0 && <EmptyTip text={'暂无协作者'} />}
</TableContainer>
{loading && <Loading fixed={false} />}
</ModalBody>
</MyModal>
);

View File

@@ -0,0 +1,42 @@
import { Box, BoxProps, Flex } from '@chakra-ui/react';
import MyBox from '@fastgpt/web/components/common/MyBox';
import React from 'react';
import { useContextSelector } from 'use-context-selector';
import { CollaboratorContext } from './context';
import Tag, { TagProps } from '@fastgpt/web/components/common/Tag';
import Avatar from '@/components/Avatar';
import { useTranslation } from 'next-i18next';
export type MemberListCardProps = BoxProps & { tagStyle?: Omit<TagProps, 'children'> };
const MemberListCard = ({ tagStyle, ...props }: MemberListCardProps) => {
const { t } = useTranslation();
const { collaboratorList, isFetchingCollaborator } = useContextSelector(
CollaboratorContext,
(v) => v
);
return (
<MyBox isLoading={isFetchingCollaborator} userSelect={'none'} {...props}>
{collaboratorList?.length === 0 ? (
<Box p={3} color="myGray.600" fontSize={'xs'} textAlign={'center'}>
{t('permission.Not collaborator')}
</Box>
) : (
<Flex gap="2" flexWrap={'wrap'}>
{collaboratorList?.map((member) => {
return (
<Tag key={member.tmbId} type={'fill'} colorSchema="white" {...tagStyle}>
<Avatar src={member.avatar} w="1.25rem" />
<Box fontSize={'sm'}>{member.name}</Box>
</Tag>
);
})}
</Flex>
)}
</MyBox>
);
};
export default MemberListCard;

View File

@@ -49,7 +49,7 @@ function PermissionSelect({
...props
}: PermissionSelectProps) {
const { t } = useTranslation();
const { permissionList } = useContextSelector(CollaboratorContext, (v) => v);
const { permission, permissionList } = useContextSelector(CollaboratorContext, (v) => v);
const ref = useRef<HTMLDivElement>(null);
const closeTimer = useRef<any>();
@@ -66,10 +66,16 @@ function PermissionSelect({
});
return {
singleCheckBoxList: list.filter((item) => item.checkBoxType === 'single'),
singleCheckBoxList: list
.filter((item) => item.checkBoxType === 'single')
.filter((item) => {
if (permission.isOwner) return true;
if (item.value === permissionList['manage'].value) return false;
return true;
}),
multipleCheckBoxList: list.filter((item) => item.checkBoxType === 'multiple')
};
}, [permissionList]);
}, [permission.isOwner, permissionList]);
const selectedSingleValue = useMemo(() => {
const per = new Permission({ per: value });
@@ -88,6 +94,12 @@ function PermissionSelect({
.map((item) => item.value);
}, [permissionSelectList.multipleCheckBoxList, value]);
const onSelectPer = (per: PermissionValueType) => {
if (per === value) return;
onChange(per);
setIsOpen(false);
};
useOutsideClick({
ref: ref,
handler: () => {
@@ -151,8 +163,7 @@ function PermissionSelect({
const per = new Permission({ per: value });
per.removePer(selectedSingleValue);
per.addPer(item.value);
onChange(per.value);
setIsOpen(false);
onSelectPer(per.value);
};
return (

View File

@@ -10,9 +10,9 @@ export type PermissionTagsProp = {
};
function PermissionTags({ permission }: PermissionTagsProp) {
const { getPreLabelList } = useContextSelector(CollaboratorContext, (v) => v);
const { getPerLabelList } = useContextSelector(CollaboratorContext, (v) => v);
const perTagList = getPreLabelList(permission);
const perTagList = getPerLabelList(permission);
return (
<Flex gap="2" alignItems="center">

View File

@@ -1,3 +1,4 @@
import { BoxProps, useDisclosure } from '@chakra-ui/react';
import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
import { PermissionList } from '@fastgpt/global/support/permission/constant';
import { Permission } from '@fastgpt/global/support/permission/controller';
@@ -5,8 +6,14 @@ import { PermissionListType, PermissionValueType } from '@fastgpt/global/support
import { useQuery } from '@tanstack/react-query';
import { ReactNode, useCallback } from 'react';
import { createContext } from 'use-context-selector';
import dynamic from 'next/dynamic';
import MemberListCard, { MemberListCardProps } from './MemberListCard';
const AddMemberModal = dynamic(() => import('./AddMemberModal'));
const ManageModal = dynamic(() => import('./ManageModal'));
export type MemberManagerInputPropsType = {
permission: Permission;
onGetCollaboratorList: () => Promise<CollaboratorItemType[]>;
permissionList: PermissionListType;
onUpdateCollaborators: (tmbIds: string[], permission: PermissionValueType) => any;
@@ -16,7 +23,12 @@ export type MemberManagerPropsType = MemberManagerInputPropsType & {
collaboratorList: CollaboratorItemType[];
refetchCollaboratorList: () => void;
isFetchingCollaborator: boolean;
getPreLabelList: (per: PermissionValueType) => string[];
getPerLabelList: (per: PermissionValueType) => string[];
};
export type ChildrenProps = {
onOpenAddMember: () => void;
onOpenManageModal: () => void;
MemberListCard: (props: MemberListCardProps) => JSX.Element;
};
type CollaboratorContextType = MemberManagerPropsType & {};
@@ -30,7 +42,7 @@ export const CollaboratorContext = createContext<CollaboratorContextType>({
onDelOneCollaborator: function () {
throw new Error('Function not implemented.');
},
getPreLabelList: function (): string[] {
getPerLabelList: function (): string[] {
throw new Error('Function not implemented.');
},
refetchCollaboratorList: function (): void {
@@ -39,33 +51,36 @@ export const CollaboratorContext = createContext<CollaboratorContextType>({
onGetCollaboratorList: function (): Promise<CollaboratorItemType[]> {
throw new Error('Function not implemented.');
},
isFetchingCollaborator: false
isFetchingCollaborator: false,
permission: new Permission()
});
export const CollaboratorContextProvider = ({
permission,
onGetCollaboratorList,
permissionList,
onUpdateCollaborators,
onDelOneCollaborator,
children
}: MemberManagerInputPropsType & {
children: ReactNode;
children: (props: ChildrenProps) => ReactNode;
}) => {
const {
data: collaboratorList = [],
refetch: refetchCollaboratorList,
isLoading: isFetchingCollaborator
} = useQuery(['collaboratorList'], onGetCollaboratorList);
const onUpdateCollaboratorsThen = async (tmbIds: string[], permission: PermissionValueType) => {
await onUpdateCollaborators(tmbIds, permission);
refetchCollaboratorList();
};
const onDelOneCollaboratorThem = async (tmbId: string) => {
const onDelOneCollaboratorThen = async (tmbId: string) => {
await onDelOneCollaborator(tmbId);
refetchCollaboratorList();
};
const getPreLabelList = useCallback(
const getPerLabelList = useCallback(
(per: PermissionValueType) => {
const Per = new Permission({ per });
const labels: string[] = [];
@@ -91,17 +106,33 @@ export const CollaboratorContextProvider = ({
[permissionList]
);
const {
isOpen: isOpenAddMember,
onOpen: onOpenAddMember,
onClose: onCloseAddMember
} = useDisclosure();
const {
isOpen: isOpenManageModal,
onOpen: onOpenManageModal,
onClose: onCloseManageModal
} = useDisclosure();
const contextValue = {
permission,
onGetCollaboratorList,
collaboratorList,
refetchCollaboratorList,
isFetchingCollaborator,
permissionList,
onUpdateCollaborators: onUpdateCollaboratorsThen,
onDelOneCollaborator: onDelOneCollaboratorThem,
getPreLabelList
onDelOneCollaborator: onDelOneCollaboratorThen,
getPerLabelList
};
return (
<CollaboratorContext.Provider value={contextValue}>{children}</CollaboratorContext.Provider>
<CollaboratorContext.Provider value={contextValue}>
{children({ onOpenAddMember, onOpenManageModal, MemberListCard })}
{isOpenAddMember && <AddMemberModal onClose={onCloseAddMember} />}
{isOpenManageModal && <ManageModal onClose={onCloseManageModal} />}
</CollaboratorContext.Provider>
);
};

View File

@@ -1,99 +0,0 @@
import React, { useState } from 'react';
import { Flex, Box, Button, Tag, TagLabel, useDisclosure } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@/components/Avatar';
import { AddMemberModal } from './AddMemberModal';
import { useContextSelector } from 'use-context-selector';
import ManageModal from './ManageModal';
import {
CollaboratorContext,
CollaboratorContextProvider,
MemberManagerInputPropsType
} from './context';
import { useTranslation } from 'next-i18next';
import MyBox from '@fastgpt/web/components/common/MyBox';
function MemberManger() {
const { t } = useTranslation();
const {
isOpen: isOpenAddMember,
onOpen: onOpenAddMember,
onClose: onCloseAddMember
} = useDisclosure();
const {
isOpen: isOpenManageModal,
onOpen: onOpenManageModal,
onClose: onCloseManageModal
} = useDisclosure();
const { collaboratorList, isFetchingCollaborator } = useContextSelector(
CollaboratorContext,
(v) => v
);
return (
<>
<Flex alignItems="center" flexDirection="row" justifyContent="space-between" w="full">
<Box fontSize={'sm'}></Box>
<Flex flexDirection="row" gap="2">
<Button
size="sm"
variant="whitePrimary"
leftIcon={<MyIcon w="4" name="common/settingLight" />}
onClick={onOpenManageModal}
>
{t('permission.Manage')}
</Button>
<Button
size="sm"
variant="whitePrimary"
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
onClick={onOpenAddMember}
>
{t('common.Add')}
</Button>
</Flex>
</Flex>
{/* member list */}
<MyBox
isLoading={isFetchingCollaborator}
mt={2}
bg="myGray.100"
borderRadius="md"
size={'md'}
>
{collaboratorList?.length === 0 ? (
<Box p={3} color="myGray.600" fontSize={'xs'} textAlign={'center'}>
</Box>
) : (
<Flex gap="2" p={1.5}>
{collaboratorList?.map((member) => {
return (
<Tag px="4" py="1.5" bgColor="white" key={member.tmbId} width="fit-content">
<Flex alignItems="center">
<Avatar src={member.avatar} w="24px" />
<TagLabel mx="2">{member.name}</TagLabel>
</Flex>
</Tag>
);
})}
</Flex>
)}
</MyBox>
{isOpenAddMember && <AddMemberModal onClose={onCloseAddMember} />}
{isOpenManageModal && <ManageModal onClose={onCloseManageModal} />}
</>
);
}
function Render(props: MemberManagerInputPropsType) {
return (
<CollaboratorContextProvider {...props}>
<MemberManger />
</CollaboratorContextProvider>
);
}
export default React.memo(Render);

View File

@@ -6,6 +6,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import dynamic from 'next/dynamic';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import Avatar from '@/components/Avatar';
const TeamManageModal = dynamic(() => import('../TeamManageModal'));
@@ -46,7 +47,7 @@ const TeamMenu = () => {
<Flex w={'100%'} alignItems={'center'}>
{userInfo?.team ? (
<>
<Image src={userInfo.team.avatar} alt={''} w={'16px'} />
<Avatar src={userInfo.team.avatar} w={'1rem'} />
<Box ml={2}>{userInfo.team.teamName}</Box>
</>
) : (

View File

@@ -1,17 +1,11 @@
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppSchema } from '@fastgpt/global/core/app/type';
export type CreateAppParams = {
name?: string;
avatar?: string;
type?: `${AppTypeEnum}`;
modules: AppSchema['modules'];
edges?: AppSchema['edges'];
};
export type AppUpdateParams = {
parentId?: ParentIdType;
name?: string;
type?: `${AppTypeEnum}`;
type?: AppTypeEnum;
avatar?: string;
intro?: string;
nodes?: AppSchema['modules'];
@@ -23,7 +17,7 @@ export type AppUpdateParams = {
};
export type PostPublishAppProps = {
type: `${AppTypeEnum}`;
type: AppTypeEnum;
nodes: AppSchema['modules'];
edges: AppSchema['edges'];
chatConfig: AppSchema['chatConfig'];

View File

@@ -72,7 +72,7 @@ export type SearchTestResponse = {
searchMode: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
similarity: number;
usingQueryExtension: boolean;
queryExtensionModel?: string;
};
/* =========== training =========== */

View File

@@ -17,7 +17,6 @@ import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
import { usePagination } from '@fastgpt/web/hooks/usePagination';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import dayjs from 'dayjs';
import MyIcon from '@fastgpt/web/components/common/Icon';
import DateRangePicker, {
type DateRangeType
} from '@fastgpt/web/components/common/DateRangePicker';

View File

@@ -1,6 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import type { CreateAppParams } from '@/global/core/app/api.d';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
@@ -9,17 +8,24 @@ import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { NextAPI } from '@/service/middleware/entry';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import type { AppSchema } from '@fastgpt/global/core/app/type';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const {
name = 'APP',
avatar,
type = AppTypeEnum.advanced,
modules,
edges
} = req.body as CreateAppParams;
export type CreateAppBody = {
parentId?: ParentIdType;
name?: string;
avatar?: string;
type?: AppTypeEnum;
modules: AppSchema['modules'];
edges?: AppSchema['edges'];
};
if (!name || !Array.isArray(modules)) {
async function handler(req: ApiRequestProps<CreateAppBody>, res: NextApiResponse<any>) {
const { parentId, name, avatar, type, modules, edges } = req.body;
if (!name || !type || !Array.isArray(modules)) {
throw new Error('缺少参数');
}
@@ -34,6 +40,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const [{ _id: appId }] = await MongoApp.create(
[
{
...parseParentIdInMongo(parentId),
avatar,
name,
teamId,

View File

@@ -9,6 +9,7 @@ import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { NextAPI } from '@/service/middleware/entry';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
import { findAppAndAllChildren } from '@fastgpt/service/core/app/controller';
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { appId } = req.query as { appId: string };
@@ -17,50 +18,61 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
throw new Error('参数错误');
}
// 凭证校验
await authApp({ req, authToken: true, appId, per: OwnerPermissionVal });
// Auth owner (folder owner, can delete all apps in the folder)
const { teamId } = await authApp({ req, authToken: true, appId, per: OwnerPermissionVal });
const apps = await findAppAndAllChildren({
teamId,
appId,
fields: '_id'
});
console.log(apps);
// 删除对应的聊天
await mongoSessionRun(async (session) => {
await MongoChatItem.deleteMany(
{
appId
},
{ session }
);
await MongoChat.deleteMany(
{
appId
},
{ session }
);
// 删除分享链接
await MongoOutLink.deleteMany(
{
appId
},
{ session }
);
// delete version
await MongoAppVersion.deleteMany(
{
appId
},
{ session }
);
await MongoChatInputGuide.deleteMany(
{
appId
},
{ session }
);
// delete app
await MongoApp.deleteOne(
{
_id: appId
},
{ session }
);
for await (const app of apps) {
const appId = app._id;
// Chats
await MongoChatItem.deleteMany(
{
appId
},
{ session }
);
await MongoChat.deleteMany(
{
appId
},
{ session }
);
// 删除分享链接
await MongoOutLink.deleteMany(
{
appId
},
{ session }
);
// delete version
await MongoAppVersion.deleteMany(
{
appId
},
{ session }
);
await MongoChatInputGuide.deleteMany(
{
appId
},
{ session }
);
// delete app
await MongoApp.deleteOne(
{
_id: appId
},
{ session }
);
}
});
}

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { NextAPI } from '@/service/middleware/entry';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
/* 获取我的模型 */
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -11,7 +11,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
throw new Error('参数错误');
}
// 凭证校验
const { app } = await authApp({ req, authToken: true, appId, per: WritePermissionVal });
const { app } = await authApp({ req, authToken: true, appId, per: ReadPermissionVal });
if (!app.permission.hasWritePer) {
app.modules = [];
app.edges = [];
}
return app;
}

View File

@@ -0,0 +1,40 @@
import type { NextApiResponse } from 'next';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { FolderImgUrl } from '@fastgpt/global/common/file/image/constants';
import { NextAPI } from '@/service/middleware/entry';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
export type CreateAppFolderBody = {
parentId?: ParentIdType;
name: string;
intro?: string;
};
async function handler(req: ApiRequestProps<CreateAppFolderBody>, res: NextApiResponse<any>) {
const { name, intro, parentId } = req.body;
if (!name) {
throw new Error('缺少参数');
}
// 凭证校验
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
// Create app
await MongoApp.create({
...parseParentIdInMongo(parentId),
avatar: FolderImgUrl,
name,
intro,
teamId,
tmbId,
type: AppTypeEnum.folder
});
}
export default NextAPI(handler);

View File

@@ -0,0 +1,41 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type {
ParentIdType,
ParentTreePathItemType
} from '@fastgpt/global/common/parentFolder/type.d';
import { NextAPI } from '@/service/middleware/entry';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { MongoApp } from '@fastgpt/service/core/app/schema';
async function handler(
req: NextApiRequest,
res: NextApiResponse<any>
): Promise<ParentTreePathItemType[]> {
const { parentId } = req.query as { parentId: string };
if (!parentId) {
return [];
}
await authApp({ req, authToken: true, appId: parentId, per: ReadPermissionVal });
return await getParents(parentId);
}
export default NextAPI(handler);
async function getParents(parentId: ParentIdType): Promise<ParentTreePathItemType[]> {
if (!parentId) {
return [];
}
const parent = await MongoApp.findById(parentId, 'name parentId');
if (!parent) return [];
const paths = await getParents(parent.parentId);
paths.push({ parentId, parentName: parent.name });
return paths;
}

View File

@@ -1,4 +1,4 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { NextApiResponse } from 'next';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
@@ -9,8 +9,21 @@ import {
ReadPermissionVal
} from '@fastgpt/global/support/permission/constant';
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<AppListItemType[]> {
export type ListAppBody = {
parentId: ParentIdType;
type?: AppTypeEnum;
};
async function handler(
req: ApiRequestProps<ListAppBody>,
res: NextApiResponse<any>
): Promise<AppListItemType[]> {
// 凭证校验
const {
teamId,
@@ -22,9 +35,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
per: ReadPermissionVal
});
const { parentId, type } = req.body;
/* temp: get all apps and per */
const [myApps, rpList] = await Promise.all([
MongoApp.find({ teamId }, '_id avatar name intro tmbId defaultPermission')
MongoApp.find(
{ teamId, ...(type && { type }), ...parseParentIdInMongo(parentId) },
'_id avatar type name intro tmbId defaultPermission'
)
.sort({
updateTime: -1
})
@@ -54,10 +72,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
return filterApps.map((app) => ({
_id: app._id,
avatar: app.avatar,
type: app.type,
name: app.name,
intro: app.intro,
permission: app.permission,
defaultPermission: app.defaultPermission
defaultPermission: app.defaultPermission || AppDefaultPermissionVal
}));
}

View File

@@ -9,10 +9,12 @@ import {
WritePermissionVal,
OwnerPermissionVal
} from '@fastgpt/global/support/permission/constant';
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
/* 获取我的模型 */
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const {
parentId,
name,
avatar,
type,
@@ -49,6 +51,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
_id: appId
},
{
...parseParentIdInMongo(parentId),
name,
type,
avatar,

View File

@@ -92,7 +92,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
data: {
list: searchRes,
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`,
usingQueryExtension: !!aiExtensionResult,
queryExtensionModel: aiExtensionResult?.model,
...result
}
});

View File

@@ -20,7 +20,7 @@ import Avatar from '@/components/Avatar';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import MemberManager from '@/components/support/permission/MemberManager';
import { CollaboratorContextProvider } from '@/components/support/permission/MemberManager/context';
import {
postUpdateAppCollaborators,
deleteAppCollaborators,
@@ -29,12 +29,12 @@ import {
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '@/web/core/app/context/appContext';
import {
AppDefaultPermission,
AppDefaultPermissionVal,
AppPermissionList
} from '@fastgpt/global/support/permission/app/constant';
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
import MyIcon from '@fastgpt/web/components/common/Icon';
const InfoModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
@@ -181,25 +181,57 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
{/* role */}
{appDetail.permission.hasManagePer && (
<>
{' '}
<Box mt="4">
<Box fontSize={'sm'}>{t('permission.Default permission')}</Box>
<DefaultPermissionList
mt="2"
per={defaultPermission}
defaultPer={AppDefaultPermission}
readPer={ReadPermissionVal}
writePer={WritePermissionVal}
defaultPer={AppDefaultPermissionVal}
onChange={(v) => setValue('defaultPermission', v)}
/>
</Box>
<Box mt={6}>
<MemberManager
<CollaboratorContextProvider
permission={appDetail.permission}
onGetCollaboratorList={() => getCollaboratorList(appDetail._id)}
permissionList={AppPermissionList}
onUpdateCollaborators={onUpdateCollaborators}
onDelOneCollaborator={onDelCollaborator}
/>
>
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
return (
<>
<Flex
alignItems="center"
flexDirection="row"
justifyContent="space-between"
w="full"
>
<Box fontSize={'sm'}></Box>
<Flex flexDirection="row" gap="2">
<Button
size="sm"
variant="whitePrimary"
leftIcon={<MyIcon w="4" name="common/settingLight" />}
onClick={onOpenManageModal}
>
{t('permission.Manage')}
</Button>
<Button
size="sm"
variant="whitePrimary"
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
onClick={onOpenAddMember}
>
{t('common.Add')}
</Button>
</Flex>
</Flex>
<MemberListCard mt={2} p={1.5} bg="myGray.100" borderRadius="md" />
</>
);
}}
</CollaboratorContextProvider>
</Box>
</>
)}

View File

@@ -6,7 +6,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useRouter } from 'next/router';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { delModelById } from '@/web/core/app/api';
import { delAppById } from '@/web/core/app/api';
import { useTranslation } from 'next-i18next';
import PermissionIconText from '@/components/support/permission/IconText';
import dynamic from 'next/dynamic';
@@ -45,7 +45,7 @@ const AppCard = () => {
const { mutate: handleDelModel, isLoading } = useRequest({
mutationFn: async () => {
if (!appDetail) return null;
await delModelById(appDetail._id);
await delAppById(appDetail._id);
return 'success';
},
onSuccess(res) {

View File

@@ -297,7 +297,7 @@ const EditForm = ({
similarity={getValues('dataset.similarity')}
limit={getValues('dataset.limit')}
usingReRank={getValues('dataset.usingReRank')}
usingQueryExtension={getValues('dataset.datasetSearchUsingExtensionQuery')}
queryExtensionModel={getValues('dataset.datasetSearchExtensionModel')}
/>
</Box>
)}

View File

@@ -25,6 +25,8 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { useContextSelector } from 'use-context-selector';
import { AppListContext } from './context';
type FormType = {
avatar: string;
@@ -32,12 +34,15 @@ type FormType = {
templateId: string;
};
const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) => {
const CreateModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
const theme = useTheme();
const { isPc, feConfigs } = useSystemStore();
const { isPc } = useSystemStore();
const { register, setValue, watch, handleSubmit } = useForm<FormType>({
defaultValues: {
avatar: '',
@@ -82,6 +87,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
return Promise.reject(t('core.dataset.error.Template does not exist'));
}
return postCreateApp({
parentId,
avatar: data.avatar || template.avatar,
name: data.name,
type: template.type,
@@ -91,7 +97,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
},
onSuccess(id: string) {
router.push(`/app/detail?appId=${id}`);
onSuccess();
loadMyApps();
onClose();
},
successToast: t('common.Create Success'),

View File

@@ -0,0 +1,294 @@
import React, { useMemo, useState } from 'react';
import { Box, Grid, Flex, IconButton } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { delAppById, putAppById } from '@/web/core/app/api';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@/components/Avatar';
import PermissionIconText from '@/components/support/permission/IconText';
import { useI18n } from '@/web/context/I18n';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useTranslation } from 'next-i18next';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useContextSelector } from 'use-context-selector';
import { AppListContext } from './context';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useFolderDrag } from '@/components/common/folder/useFolderDrag';
import dynamic from 'next/dynamic';
import type { EditResourceInfoFormType } from '@/components/common/Modal/EditResourceModal';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import {
AppDefaultPermissionVal,
AppPermissionList
} from '@fastgpt/global/support/permission/app/constant';
import {
deleteAppCollaborators,
getCollaboratorList,
postUpdateAppCollaborators
} from '@/web/core/app/api/collaborator';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal'));
const ConfigPerModal = dynamic(() => import('@/components/support/permission/ConfigPerModal'));
const ListItem = () => {
const { t } = useTranslation();
const { appT } = useI18n();
const router = useRouter();
const { myApps, loadMyApps, onUpdateApp, setMoveAppId } = useContextSelector(
AppListContext,
(v) => v
);
const [loadingAppId, setLoadingAppId] = useState<string>();
const [editedApp, setEditedApp] = useState<EditResourceInfoFormType>();
const [editPerAppIndex, setEditPerAppIndex] = useState<number>();
const editPerApp = useMemo(
() => (editPerAppIndex !== undefined ? myApps[editPerAppIndex] : undefined),
[editPerAppIndex, myApps]
);
const { getBoxProps } = useFolderDrag({
activeStyles: {
borderColor: 'primary.600'
},
onDrop: async (dragId: string, targetId: string) => {
setLoadingAppId(dragId);
try {
await putAppById(dragId, { parentId: targetId });
loadMyApps();
} catch (error) {}
setLoadingAppId(undefined);
}
});
const { openConfirm, ConfirmModal } = useConfirm({
type: 'delete'
});
const { run: onclickDelApp } = useRequest2(
(id: string) => {
setLoadingAppId(id);
return delAppById(id);
},
{
onSuccess() {
loadMyApps();
},
onFinally() {
setLoadingAppId(undefined);
},
successToast: t('common.Delete Success'),
errorToast: t('common.Delete Failed')
}
);
return (
<>
<Grid
py={[4, 6]}
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']}
gridGap={5}
>
{myApps.map((app, index) => (
<MyTooltip
key={app._id}
label={
app.type === AppTypeEnum.folder
? t('common.folder.Open folder')
: app.permission.hasWritePer
? appT('Edit app')
: appT('Go to chat')
}
>
<MyBox
isLoading={loadingAppId === app._id}
lineHeight={1.5}
h={'100%'}
py={3}
px={5}
cursor={'pointer'}
borderWidth={'1.5px'}
borderColor={'borderColor.low'}
bg={'white'}
borderRadius={'md'}
userSelect={'none'}
position={'relative'}
display={'flex'}
flexDirection={'column'}
_hover={{
borderColor: 'primary.300',
boxShadow: '1.5',
'& .more': {
display: 'flex'
},
'& .chat': {
display: 'flex'
}
}}
onClick={() => {
if (app.type === AppTypeEnum.folder) {
router.push({
query: {
parentId: app._id
}
});
} else if (app.permission.hasWritePer) {
router.push(`/app/detail?appId=${app._id}`);
} else {
router.push(`/chat?appId=${app._id}`);
}
}}
{...getBoxProps({
dataId: app._id,
isFolder: app.type === AppTypeEnum.folder
})}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={app.avatar} borderRadius={'md'} w={'28px'} />
<Box ml={3}>{app.name}</Box>
{app.permission.hasManagePer && (
<Box
className="more"
position={'absolute'}
top={3.5}
right={4}
display={['', 'none']}
>
<MyMenu
Button={
<IconButton
size={'xsSquare'}
variant={'transparentBase'}
icon={<MyIcon name={'more'} w={'1rem'} />}
aria-label={''}
/>
}
menuList={[
{
children: [
{
icon: 'edit',
label: '编辑信息',
onClick: () =>
setEditedApp({
id: app._id,
avatar: app.avatar,
name: app.name,
intro: app.intro
})
},
{
icon: 'common/file/move',
label: t('common.folder.Move to'),
onClick: () => setMoveAppId(app._id)
},
...(app.permission.hasManagePer
? [
{
icon: 'support/team/key',
label: t('permission.Permission'),
onClick: () => setEditPerAppIndex(index)
}
]
: [])
]
},
...(app.permission.isOwner
? [
{
children: [
{
type: 'danger' as 'danger',
icon: 'delete',
label: t('common.Delete'),
onClick: () =>
openConfirm(
() => onclickDelApp(app._id),
undefined,
app.type === AppTypeEnum.folder
? appT('Confirm delete folder tip')
: appT('Confirm Del App Tip')
)()
}
]
}
]
: [])
]}
/>
</Box>
)}
</Flex>
<Box
flex={1}
className={'textEllipsis3'}
py={2}
wordBreak={'break-all'}
fontSize={'mini'}
color={'myGray.600'}
>
{app.intro || '还没写介绍~'}
</Box>
<Flex h={'34px'} alignItems={'flex-end'}>
<Box flex={1}>
<PermissionIconText
defaultPermission={app.defaultPermission}
color={'myGray.600'}
/>
</Box>
</Flex>
</MyBox>
</MyTooltip>
))}
</Grid>
{myApps.length === 0 && <EmptyTip text={'还没有应用,快去创建一个吧!'} pt={'30vh'} />}
<ConfirmModal />
{!!editedApp && (
<EditResourceModal
{...editedApp}
title="应用信息编辑"
onClose={() => {
setEditedApp(undefined);
}}
onEdit={({ id, ...data }) => onUpdateApp(id, data)}
/>
)}
{!!editPerApp && (
<ConfigPerModal
avatar={editPerApp.avatar}
name={editPerApp.name}
defaultPer={{
value: editPerApp.defaultPermission,
defaultValue: AppDefaultPermissionVal,
onChange: (e) => {
return onUpdateApp(editPerApp._id, { defaultPermission: e });
}
}}
managePer={{
permission: editPerApp.permission,
onGetCollaboratorList: () => getCollaboratorList(editPerApp._id),
permissionList: AppPermissionList,
onUpdateCollaborators: (tmbIds: string[], permission: number) => {
return postUpdateAppCollaborators({
tmbIds,
permission,
appId: editPerApp._id
});
},
onDelOneCollaborator: (tmbId: string) =>
deleteAppCollaborators({
appId: editPerApp._id,
tmbId
})
}}
onClose={() => setEditPerAppIndex(undefined)}
/>
)}
</>
);
};
export default ListItem;

View File

@@ -0,0 +1,138 @@
import React, { ReactNode, useCallback, useState } from 'react';
import { createContext } from 'use-context-selector';
import { useRouter } from 'next/router';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getAppDetailById, getMyApps, putAppById } from '@/web/core/app/api';
import { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type';
import { useQuery } from '@tanstack/react-query';
import { getAppFolderPath } from '@/web/core/app/api/app';
import {
GetResourceFolderListProps,
ParentIdType,
ParentTreePathItemType
} from '@fastgpt/global/common/parentFolder/type';
import { AppUpdateParams } from '@/global/core/app/api';
import dynamic from 'next/dynamic';
import { useI18n } from '@/web/context/I18n';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { delay } from '@fastgpt/global/common/system/utils';
type AppListContextType = {
parentId?: string | null;
myApps: AppListItemType[];
loadMyApps: () => void;
isFetchingApps: boolean;
folderDetail: AppDetailType | undefined | null;
paths: ParentTreePathItemType[];
onUpdateApp: (id: string, data: AppUpdateParams) => Promise<any>;
setMoveAppId: React.Dispatch<React.SetStateAction<string | undefined>>;
};
export const AppListContext = createContext<AppListContextType>({
parentId: undefined,
myApps: [],
loadMyApps: function (): void {
throw new Error('Function not implemented.');
},
isFetchingApps: false,
folderDetail: undefined,
paths: [],
onUpdateApp: function (id: string, data: AppUpdateParams): Promise<any> {
throw new Error('Function not implemented.');
},
setMoveAppId: function (value: React.SetStateAction<string | undefined>): void {
throw new Error('Function not implemented.');
}
});
const MoveModal = dynamic(() => import('@/components/common/folder/MoveModal'));
const AppListContextProvider = ({ children }: { children: ReactNode }) => {
const { appT } = useI18n();
const router = useRouter();
const { parentId = null } = router.query as { parentId?: string | null };
const {
data = [],
runAsync: loadMyApps,
loading: isFetchingApps
} = useRequest2(() => getMyApps({ parentId }), {
manual: false,
refreshOnWindowFocus: true,
refreshDeps: [parentId]
});
const { data: paths = [], runAsync: refetchPaths } = useRequest2(
() => getAppFolderPath(parentId),
{
manual: false,
refreshDeps: [parentId]
}
);
const { data: folderDetail, runAsync: refetchFolderDetail } = useRequest2(
() => {
if (parentId) return getAppDetailById(parentId);
return Promise.resolve(null);
},
{
manual: false,
refreshDeps: [parentId]
}
);
const { runAsync: onUpdateApp } = useRequest2((id: string, data: AppUpdateParams) =>
putAppById(id, data).then(async (res) => {
await Promise.all([refetchFolderDetail(), refetchPaths(), loadMyApps()]);
return res;
})
);
const [moveAppId, setMoveAppId] = useState<string>();
const onMoveApp = useCallback(
async (parentId: ParentIdType) => {
if (!moveAppId) return;
await onUpdateApp(moveAppId, { parentId });
},
[moveAppId, onUpdateApp]
);
const getAppFolderList = useCallback(({ parentId }: GetResourceFolderListProps) => {
return getMyApps({
parentId,
type: AppTypeEnum.folder
}).then((res) =>
res.map((item) => ({
id: item._id,
name: item.name
}))
);
}, []);
const contextValue: AppListContextType = {
parentId,
myApps: data,
loadMyApps,
isFetchingApps,
folderDetail,
paths,
onUpdateApp,
setMoveAppId
};
return (
<AppListContext.Provider value={contextValue}>
{children}
{!!moveAppId && (
<MoveModal
moveResourceId={moveAppId}
server={getAppFolderList}
title={appT('Move app')}
onClose={() => setMoveAppId(undefined)}
onConfirm={onMoveApp}
/>
)}
</AppListContext.Provider>
);
};
export default AppListContextProvider;

View File

@@ -1,192 +1,209 @@
import React, { useCallback } from 'react';
import { Box, Grid, Flex, IconButton, Button, useDisclosure } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import { Box, Flex, Button, useDisclosure } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { delModelById } from '@/web/core/app/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { serviceSideProps } from '@/web/common/utils/i18n';
import MyIcon from '@fastgpt/web/components/common/Icon';
import PageContainer from '@/components/PageContainer';
import Avatar from '@/components/Avatar';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import CreateModal from './component/CreateModal';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import PermissionIconText from '@/components/support/permission/IconText';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useI18n } from '@/web/context/I18n';
import { useTranslation } from 'next-i18next';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import dynamic from 'next/dynamic';
import List from './component/List';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { postCreateAppFolder } from '@/web/core/app/api/app';
import type { EditFolderFormType } from '@fastgpt/web/components/common/MyModal/EditFolderModal';
import { useContextSelector } from 'use-context-selector';
import AppListContextProvider, { AppListContext } from './component/context';
import FolderPath from '@/components/common/folder/Path';
import { useRouter } from 'next/router';
import FolderSlideCard from '@/components/common/folder/SlideCard';
import { delAppById } from '@/web/core/app/api';
import {
AppDefaultPermissionVal,
AppPermissionList
} from '@fastgpt/global/support/permission/app/constant';
import {
deleteAppCollaborators,
getCollaboratorList,
postUpdateAppCollaborators
} from '@/web/core/app/api/collaborator';
import { useSystemStore } from '@/web/common/system/useSystemStore';
const EditFolderModal = dynamic(
() => import('@fastgpt/web/components/common/MyModal/EditFolderModal')
);
const MyApps = () => {
const { t } = useTranslation();
const { toast } = useToast();
const { appT, commonT } = useI18n();
const { appT } = useI18n();
const router = useRouter();
const { isPc } = useSystemStore();
const {
paths,
parentId,
myApps,
loadMyApps,
onUpdateApp,
setMoveAppId,
isFetchingApps,
folderDetail
} = useContextSelector(AppListContext, (v) => v);
const { userInfo } = useUserStore();
const { myApps, loadMyApps } = useAppStore();
const { openConfirm, ConfirmModal } = useConfirm({
type: 'delete',
content: '确认删除该应用所有信息?'
});
const {
isOpen: isOpenCreateModal,
onOpen: onOpenCreateModal,
onClose: onCloseCreateModal
} = useDisclosure();
const [editFolder, setEditFolder] = useState<EditFolderFormType>();
/* 点击删除 */
const onclickDelApp = useCallback(
async (id: string) => {
try {
await delModelById(id);
toast({
title: '删除成功',
status: 'success'
});
loadMyApps();
} catch (err: any) {
toast({
title: err?.message || t('common.Delete Failed'),
status: 'error'
});
}
const { runAsync: onCreateFolder } = useRequest2(postCreateAppFolder, {
onSuccess() {
loadMyApps();
},
[toast, loadMyApps, t]
);
/* 加载模型 */
const { isFetching } = useQuery(['loadApps'], () => loadMyApps(), {
refetchOnMount: true
errorToast: 'Error'
});
const { runAsync: onDeleFolder } = useRequest2(delAppById, {
onSuccess() {
router.replace({
query: {
parentId: folderDetail?.parentId
}
});
},
errorToast: 'Error'
});
return (
<PageContainer isLoading={isFetching} insertProps={{ px: [5, '48px'] }}>
<Flex pt={[4, '30px']} alignItems={'center'} justifyContent={'space-between'}>
<Box letterSpacing={1} fontSize={['20px', '24px']} color={'myGray.900'}>
{appT('My Apps')}
<PageContainer
isLoading={myApps.length === 0 && isFetchingApps}
insertProps={{ px: folderDetail ? [4, 6] : [4, 10] }}
>
<Flex gap={5}>
<Box flex={'1 0 0'}>
<Flex pt={[4, 6]} alignItems={'center'} justifyContent={'space-between'}>
<FolderPath
paths={paths}
FirstPathDom={
<Box letterSpacing={1} fontSize={['md', 'lg']} color={'myGray.900'}>
{appT('My Apps')}
</Box>
}
onClick={(parentId) => {
router.push({
query: {
parentId
}
});
}}
/>
{userInfo?.team.permission.hasWritePer && (
<MyMenu
width={150}
iconSize="1.5rem"
Button={
<Button variant={'primary'} leftIcon={<AddIcon />}>
<Box>{t('common.Create New')}</Box>
</Button>
}
menuList={[
{
children: [
{
icon: 'core/app/simpleBot',
label: appT('Create bot'),
description: appT('Create one ai app'),
onClick: onOpenCreateModal
}
]
},
{
children: [
{
icon: FolderIcon,
label: t('Folder'),
onClick: () => setEditFolder({})
}
]
}
]}
/>
)}
</Flex>
<List />
</Box>
{userInfo?.team.permission.hasWritePer && (
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
{commonT('New Create')}
</Button>
{!!folderDetail && isPc && (
<Box pt={[4, 6]}>
<FolderSlideCard
name={folderDetail.name}
intro={folderDetail.intro}
onEdit={() => {
setEditFolder({
id: folderDetail._id,
name: folderDetail.name,
intro: folderDetail.intro
});
}}
onMove={() => setMoveAppId(folderDetail._id)}
deleteTip={appT('Confirm delete folder tip')}
onDelete={() => onDeleFolder(folderDetail._id)}
defaultPer={{
value: folderDetail.defaultPermission,
defaultValue: AppDefaultPermissionVal,
onChange: (e) => {
return onUpdateApp(folderDetail._id, { defaultPermission: e });
}
}}
managePer={{
permission: folderDetail.permission,
onGetCollaboratorList: () => getCollaboratorList(folderDetail._id),
permissionList: AppPermissionList,
onUpdateCollaborators: (tmbIds: string[], permission: number) => {
return postUpdateAppCollaborators({
tmbIds,
permission,
appId: folderDetail._id
});
},
onDelOneCollaborator: (tmbId: string) =>
deleteAppCollaborators({
appId: folderDetail._id,
tmbId
})
}}
/>
</Box>
)}
</Flex>
<Grid
py={[4, 6]}
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']}
gridGap={5}
>
{myApps.map((app) => (
<MyTooltip
key={app._id}
label={app.permission.hasWritePer ? appT('To Settings') : appT('To Chat')}
>
<Box
lineHeight={1.5}
h={'100%'}
py={3}
px={5}
cursor={'pointer'}
borderWidth={'1.5px'}
borderColor={'borderColor.low'}
bg={'white'}
borderRadius={'md'}
userSelect={'none'}
position={'relative'}
display={'flex'}
flexDirection={'column'}
_hover={{
borderColor: 'primary.300',
boxShadow: '1.5',
'& .delete': {
display: 'flex'
},
'& .chat': {
display: 'flex'
}
}}
onClick={() => {
if (app.permission.hasWritePer) {
router.push(`/app/detail?appId=${app._id}`);
} else {
router.push(`/chat?appId=${app._id}`);
}
}}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={app.avatar} borderRadius={'md'} w={'28px'} />
<Box ml={3}>{app.name}</Box>
{app.permission.isOwner && (
<IconButton
className="delete"
position={'absolute'}
top={4}
right={4}
size={'xsSquare'}
variant={'whiteDanger'}
icon={<MyIcon name={'delete'} w={'14px'} />}
aria-label={'delete'}
display={['', 'none']}
onClick={(e) => {
e.stopPropagation();
openConfirm(() => onclickDelApp(app._id))();
}}
/>
)}
</Flex>
<Box
flex={1}
className={'textEllipsis3'}
py={2}
wordBreak={'break-all'}
fontSize={'xs'}
color={'myGray.600'}
>
{app.intro || '这个应用还没写介绍~'}
</Box>
<Flex h={'34px'} alignItems={'flex-end'}>
<Box flex={1}>
<PermissionIconText
defaultPermission={app.defaultPermission}
color={'myGray.600'}
/>
</Box>
{app.permission.hasWritePer && (
<IconButton
className="chat"
size={'xsSquare'}
variant={'whitePrimary'}
icon={
<MyTooltip label={'去聊天'}>
<MyIcon name={'core/chat/chatLight'} w={'14px'} />
</MyTooltip>
}
aria-label={'chat'}
display={['', 'none']}
onClick={(e) => {
e.stopPropagation();
router.push(`/chat?appId=${app._id}`);
}}
/>
)}
</Flex>
</Box>
</MyTooltip>
))}
</Grid>
{myApps.length === 0 && <EmptyTip text={'还没有应用,快去创建一个吧!'} pt={'30vh'} />}
<ConfirmModal />
{isOpenCreateModal && (
<CreateModal onClose={onCloseCreateModal} onSuccess={() => loadMyApps()} />
{!!editFolder && (
<EditFolderModal
{...editFolder}
onClose={() => setEditFolder(undefined)}
onCreate={(data) => onCreateFolder({ ...data, parentId })}
onEdit={({ id, ...data }) => onUpdateApp(id, data)}
/>
)}
{isOpenCreateModal && <CreateModal onClose={onCloseCreateModal} />}
</PageContainer>
);
};
function ContextRender() {
return (
<AppListContextProvider>
<MyApps />
</AppListContextProvider>
);
}
export default ContextRender;
export async function getServerSideProps(content: any) {
return {
props: {
@@ -194,5 +211,3 @@ export async function getServerSideProps(content: any) {
}
};
}
export default MyApps;

View File

@@ -108,7 +108,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
usingReRank: res.usingReRank,
limit: res.limit,
similarity: res.similarity,
usingQueryExtension: res.usingQueryExtension
queryExtensionModel: res.queryExtensionModel
};
pushDatasetTestItem(testItem);
setDatasetTestItem(testItem);
@@ -430,7 +430,7 @@ const TestResults = React.memo(function TestResults({
similarity={datasetTestItem.similarity}
limit={datasetTestItem.limit}
usingReRank={datasetTestItem.usingReRank}
usingQueryExtension={datasetTestItem.usingQueryExtension}
queryExtensionModel={datasetTestItem.queryExtensionModel}
/>
</Box>

View File

@@ -143,7 +143,7 @@ function request(
): any {
/* 去空 */
for (const key in data) {
if (data[key] === null || data[key] === undefined) {
if (data[key] === undefined) {
delete data[key];
}
}

View File

@@ -1,28 +1,25 @@
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import type {
AppDetailType,
AppListItemType,
ChatInputGuideConfigType
} from '@fastgpt/global/core/app/type.d';
import type { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d';
import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d';
import { AppUpdateParams, CreateAppParams } from '@/global/core/app/api';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { AppUpdateParams } from '@/global/core/app/api';
import type { CreateAppBody } from '@/pages/api/core/app/create';
import type { ListAppBody } from '@/pages/api/core/app/list';
/**
* 获取模型列表
*/
export const getMyApps = () => GET<AppListItemType[]>('/core/app/list');
export const getMyApps = (data?: ListAppBody) => POST<AppListItemType[]>('/core/app/list', data);
/**
* 创建一个模型
*/
export const postCreateApp = (data: CreateAppParams) => POST<string>('/core/app/create', data);
export const postCreateApp = (data: CreateAppBody) => POST<string>('/core/app/create', data);
export const getMyAppsByTags = (data: {}) => POST(`/proApi/core/chat/team/getApps`, data);
/**
* 根据 ID 删除模型
*/
export const delModelById = (id: string) => DELETE(`/core/app/del?appId=${id}`);
export const delAppById = (id: string) => DELETE(`/core/app/del?appId=${id}`);
/**
* 根据 ID 获取模型

View File

@@ -0,0 +1,11 @@
import { DELETE, GET, POST } from '@/web/common/api/request';
import type { CreateAppFolderBody } from '@/pages/api/core/app/folder/create';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
/* folder */
export const postCreateAppFolder = (data: CreateAppFolderBody) =>
POST('/core/app/folder/create', data);
export const getAppFolderPath = (parentId: ParentIdType) =>
GET<ParentTreePathItemType[]>(`/core/app/folder/path`, { parentId });

View File

@@ -1,3 +1,4 @@
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
@@ -6,7 +7,7 @@ import { NullPermission } from '@fastgpt/global/support/permission/constant';
export const defaultApp: AppDetailType = {
_id: '',
name: '应用加载中',
type: 'simple',
type: AppTypeEnum.simple,
avatar: '/icon/logo.svg',
intro: '',
updateTime: Date.now(),

View File

@@ -1,31 +1,25 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { getMyApps } from '@/web/core/app/api';
import { AppListItemType } from '@fastgpt/global/core/app/type';
export type State = {
myApps: AppListItemType[];
loadMyApps: () => Promise<AppListItemType[]>;
loadMyApps: (...arg: Parameters<typeof getMyApps>) => Promise<AppListItemType[]>;
};
export const useAppStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
myApps: [],
async loadMyApps() {
const res = await getMyApps();
set((state) => {
state.myApps = res;
});
return res;
}
})),
{
name: 'appStore',
partialize: (state) => ({})
immer((set, get) => ({
myApps: [],
async loadMyApps(data) {
const res = await getMyApps(data);
set((state) => {
state.myApps = res;
});
return res;
}
)
}))
)
);

View File

@@ -11,7 +11,7 @@ import {
export const appTemplates: (AppItemType & {
avatar: string;
intro: string;
type: `${AppTypeEnum}`;
type: AppTypeEnum;
})[] = [
{
id: 'simpleChat',

View File

@@ -15,7 +15,7 @@ export type SearchTestStoreItemType = {
limit: number;
usingReRank: boolean;
similarity: number;
usingQueryExtension: boolean;
queryExtensionModel?: string;
};
type State = {