mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 05:12:39 +00:00
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:
@@ -9,13 +9,18 @@
|
||||
"Chat Logs Tips": "Logs will record online, shared and API (chatId required) conversation records for this app",
|
||||
"Chat logs": "Chat Logs",
|
||||
"Confirm Del App Tip": "Confirm to delete this app and all its chat records?",
|
||||
"Confirm delete folder tip": "Are you sure to delete this folder? All the following applications and corresponding chat records will be deleted, please confirm!",
|
||||
"Connection is invalid": "Connection is invalid",
|
||||
"Connection type is different": "Connection type is different",
|
||||
"Copy Module Config": "Copy Config",
|
||||
"Create bot": "App",
|
||||
"Create one ai app": "Create AI app",
|
||||
"Dataset Quote Template": "Knowledge Base QA Mode",
|
||||
"Edit app": "Edit app",
|
||||
"Export Config Successful": "Config copied, please check for important data",
|
||||
"Export Configs": "Export Configs",
|
||||
"Feedback Count": "User Feedback",
|
||||
"Go to chat": "To chat",
|
||||
"Import Configs": "Import Configs",
|
||||
"Import Configs Failed": "Failed to import configs, please ensure configs are valid!",
|
||||
"Input Field Settings": "Input Field Settings",
|
||||
@@ -25,6 +30,7 @@
|
||||
"Logs Time": "Time",
|
||||
"Logs Title": "Title",
|
||||
"Mark Count": "Marked Answer Count",
|
||||
"Move app": "Move app",
|
||||
"My Apps": "My Apps",
|
||||
"Output Field Settings": "Output Field Settings",
|
||||
"Paste Config": "Paste Config",
|
||||
|
@@ -49,6 +49,7 @@
|
||||
"Delete Success": "Delete Success",
|
||||
"Delete Tip": "Delete Tip",
|
||||
"Delete Warning": "Delete Warning",
|
||||
"Delete folder": "Delete",
|
||||
"Detail": "Detail",
|
||||
"Documents": "Documents",
|
||||
"Done": "Done",
|
||||
@@ -64,6 +65,8 @@
|
||||
"Import failed": "Import failed",
|
||||
"Import success": "Import success",
|
||||
"Input": "Input",
|
||||
"Input folder description": "Folder description",
|
||||
"Input name": "Folder name",
|
||||
"Intro": "Intro",
|
||||
"Invalid Json": "Invalid JSON format, please check.",
|
||||
"Last Step": "Last Step",
|
||||
@@ -71,6 +74,7 @@
|
||||
"Load Failed": "Load Failed",
|
||||
"Loading": "Loading...",
|
||||
"More settings": "More settings",
|
||||
"Move": "Move",
|
||||
"MultipleRowSelect": {
|
||||
"No data": "No data available"
|
||||
},
|
||||
@@ -84,6 +88,7 @@
|
||||
"OK": "OK",
|
||||
"Open": "Open",
|
||||
"Opened": "Opened",
|
||||
"Operation": "Operation",
|
||||
"Other": "Other",
|
||||
"Output": "Output",
|
||||
"Params": "Params",
|
||||
@@ -161,7 +166,9 @@
|
||||
"folder": {
|
||||
"Drag Tip": "Drag me",
|
||||
"Move Success": "Move successful",
|
||||
"Move to": "Move to",
|
||||
"No Folder": "No subdirectories, place here",
|
||||
"Open folder": "Open folder",
|
||||
"Root Path": "Root directory",
|
||||
"empty": "This directory has nothing selectable~"
|
||||
},
|
||||
@@ -1212,8 +1219,12 @@
|
||||
"Tools": "Tools"
|
||||
},
|
||||
"permission": {
|
||||
"Collaborator": "",
|
||||
"Default permission": "Default permission",
|
||||
"Manage": "Manage",
|
||||
"Not collaborator": "Not collaborator",
|
||||
"Permission": "Permission",
|
||||
"Permission config": "Permission config",
|
||||
"Private": "Private",
|
||||
"Private Tip": "Only available to oneself",
|
||||
"Public": "Team",
|
||||
@@ -1292,6 +1303,9 @@
|
||||
"Response Quote tips": "Return quote content in the share link, but will not allow users to download the original document"
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"Permission": "Permission"
|
||||
},
|
||||
"standard": {
|
||||
"AI Bonus Points": "AI points",
|
||||
"Expired Time": "End time",
|
||||
|
@@ -8,13 +8,18 @@
|
||||
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录",
|
||||
"Chat logs": "对话日志",
|
||||
"Confirm Del App Tip": "确认删除该应用及其所有聊天记录?",
|
||||
"Confirm delete folder tip": "确认删除该文件夹?将会删除它下面所有应用及对应的聊天记录,请确认!",
|
||||
"Connection is invalid": "连接无效",
|
||||
"Connection type is different": "连接的类型不一致",
|
||||
"Copy Module Config": "复制配置",
|
||||
"Create bot": "应用",
|
||||
"Create one ai app": "创建一个AI应用",
|
||||
"Dataset Quote Template": "知识库问答模式",
|
||||
"Edit app": "编辑应用",
|
||||
"Export Config Successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
|
||||
"Export Configs": "导出配置",
|
||||
"Feedback Count": "用户反馈",
|
||||
"Go to chat": "去对话",
|
||||
"Import Configs": "导入配置",
|
||||
"Import Configs Failed": "导入配置失败,请确保配置正常!",
|
||||
"Input Field Settings": "输入字段编辑",
|
||||
@@ -24,6 +29,7 @@
|
||||
"Logs Time": "时间",
|
||||
"Logs Title": "标题",
|
||||
"Mark Count": "标注答案数量",
|
||||
"Move app": "移动应用",
|
||||
"My Apps": "我的应用",
|
||||
"Output Field Settings": "输出字段编辑",
|
||||
"Paste Config": "粘贴配置",
|
||||
|
@@ -49,6 +49,7 @@
|
||||
"Delete Success": "删除成功",
|
||||
"Delete Tip": "删除提示",
|
||||
"Delete Warning": "删除警告",
|
||||
"Delete folder": "删除文件夹",
|
||||
"Detail": "详情",
|
||||
"Documents": "文档",
|
||||
"Done": "完成",
|
||||
@@ -64,6 +65,8 @@
|
||||
"Import failed": "导入失败",
|
||||
"Import success": "导入成功",
|
||||
"Input": "输入",
|
||||
"Input folder description": "文件夹描述",
|
||||
"Input name": "取个名字",
|
||||
"Intro": "介绍",
|
||||
"Invalid Json": "无效的JSON格式,请注意检查。",
|
||||
"Last Step": "上一步",
|
||||
@@ -71,6 +74,7 @@
|
||||
"Load Failed": "加载失败",
|
||||
"Loading": "加载中...",
|
||||
"More settings": "更多设置",
|
||||
"Move": "移动",
|
||||
"MultipleRowSelect": {
|
||||
"No data": "没有可选值"
|
||||
},
|
||||
@@ -84,6 +88,7 @@
|
||||
"OK": "好的",
|
||||
"Open": "打开",
|
||||
"Opened": "已开启",
|
||||
"Operation": "操作",
|
||||
"Other": "其他",
|
||||
"Output": "输出",
|
||||
"Params": "参数",
|
||||
@@ -162,7 +167,9 @@
|
||||
"folder": {
|
||||
"Drag Tip": "点我可拖动",
|
||||
"Move Success": "移动成功",
|
||||
"Move to": "移动到",
|
||||
"No Folder": "没有子目录了,就放这里吧",
|
||||
"Open folder": "打开文件夹",
|
||||
"Root Path": "根目录",
|
||||
"empty": "这个目录已经没东西可选了~"
|
||||
},
|
||||
@@ -532,7 +539,6 @@
|
||||
"Go Dataset": "前往知识库",
|
||||
"Intro Placeholder": "这个知识库还没有介绍~",
|
||||
"Manual collection": "手动数据集",
|
||||
"externalFile": "外部文件库",
|
||||
"My Dataset": "我的知识库",
|
||||
"Name": "知识库名称",
|
||||
"Query extension intro": "开启问题优化功能,可以提高提高连续对话时,知识库搜索的精度。开启该功能后,在进行知识库搜索时,会根据对话记录,利用 AI 补全问题缺失的信息。",
|
||||
@@ -586,7 +592,8 @@
|
||||
"success": "开始同步"
|
||||
}
|
||||
},
|
||||
"training": {}
|
||||
"training": {
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"Auxiliary Data": "辅助数据",
|
||||
@@ -619,6 +626,7 @@
|
||||
"unCreateCollection": "无权操作该数据",
|
||||
"unLinkCollection": "不是网络链接集合"
|
||||
},
|
||||
"externalFile": "外部文件库",
|
||||
"file": "文件",
|
||||
"folder": "目录",
|
||||
"import": {
|
||||
@@ -1219,8 +1227,12 @@
|
||||
"Tools": "工具"
|
||||
},
|
||||
"permission": {
|
||||
"Collaborator": "协作者",
|
||||
"Default permission": "默认权限",
|
||||
"Manage": "管理",
|
||||
"Not collaborator": "暂无协作者",
|
||||
"Permission": "权限",
|
||||
"Permission config": "权限配置",
|
||||
"Private": "私有",
|
||||
"Private Tip": "仅自己可用",
|
||||
"Public": "团队",
|
||||
@@ -1299,6 +1311,9 @@
|
||||
"Response Quote tips": "在分享链接中返回引用内容,但不会允许用户下载原文档"
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"Permission": "权限"
|
||||
},
|
||||
"standard": {
|
||||
"AI Bonus Points": "AI 积分",
|
||||
"Expired Time": "结束时间",
|
||||
|
@@ -1 +1,14 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1700746780241" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="67557" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M768 320v128H576V256h128L512 64 320 256h128v192H256V320L64 512l64 64 128 128V576h192v192H320l192 192 64-64 128-128H576V576h192v128l192-192-192-192z" p-id="67558" fill="#13227a"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path
|
||||
d="M10.5898 1.90814C10.4269 1.74528 10.2134 1.66392 9.99998 1.66406C9.78652 1.66392 9.57302 1.74528 9.41016 1.90814C9.40148 1.91682 9.39303 1.92565 9.38481 1.93461L7.64351 3.67591C7.31807 4.00134 7.31807 4.52898 7.64351 4.85442C7.96895 5.17986 8.49659 5.17986 8.82202 4.85442L9.1672 4.50924V7.49791C9.1672 7.95815 9.5403 8.33125 10.0005 8.33125C10.4608 8.33125 10.8339 7.95815 10.8339 7.49791V4.51036L11.1779 4.85442C11.5034 5.17986 12.031 5.17986 12.3564 4.85442C12.6819 4.52898 12.6819 4.00134 12.3564 3.67591L10.6151 1.93461C10.6069 1.92565 10.5985 1.91682 10.5898 1.90814Z"
|
||||
fill="#3370FF" />
|
||||
<path
|
||||
d="M9.1672 15.4907V12.5048C9.1672 12.0446 9.5403 11.6715 10.0005 11.6715C10.4608 11.6715 10.8339 12.0446 10.8339 12.5048V15.4896L11.1779 15.1455C11.5034 14.8201 12.031 14.8201 12.3564 15.1455C12.6819 15.471 12.6819 15.9986 12.3564 16.324L10.6151 18.0653C10.6069 18.0743 10.5985 18.0831 10.5898 18.0918C10.4269 18.2547 10.2134 18.336 9.99998 18.3359C9.78652 18.336 9.57302 18.2547 9.41016 18.0918C9.40148 18.0831 9.39303 18.0743 9.38481 18.0653L7.64351 16.324C7.31807 15.9986 7.31807 15.471 7.64351 15.1455C7.96895 14.8201 8.49659 14.8201 8.82202 15.1455L9.1672 15.4907Z"
|
||||
fill="#3370FF" />
|
||||
<path
|
||||
d="M7.58068 9.1672C8.04092 9.1672 8.41402 9.5403 8.41402 10.0005C8.41402 10.4608 8.04092 10.8339 7.58068 10.8339H4.51036L4.85442 11.1779C5.17986 11.5034 5.17986 12.031 4.85442 12.3564C4.52898 12.6819 4.00134 12.6819 3.67591 12.3564L1.93461 10.6151C1.92565 10.6069 1.91682 10.5985 1.90814 10.5898C1.74527 10.4269 1.66391 10.2134 1.66406 9.99995C1.66393 9.7865 1.74529 9.57301 1.90814 9.41016C1.91682 9.40148 1.92565 9.39303 1.93461 9.38481L3.67591 7.64351C4.00134 7.31807 4.52898 7.31807 4.85442 7.64351C5.17986 7.96895 5.17986 8.49659 4.85442 8.82202L4.50924 9.1672H7.58068Z"
|
||||
fill="#3370FF" />
|
||||
<path
|
||||
d="M16.3241 7.64351L18.0654 9.38481C18.0743 9.39303 18.0832 9.40148 18.0918 9.41016C18.2547 9.57302 18.3361 9.78652 18.3359 9.99998C18.3361 10.2134 18.2547 10.4269 18.0918 10.5898C18.0832 10.5985 18.0743 10.6069 18.0654 10.6151L16.3241 12.3564C15.9986 12.6819 15.471 12.6819 15.1456 12.3564C14.8201 12.031 14.8201 11.5034 15.1456 11.1779L15.4888 10.8347H12.4119C11.9517 10.8347 11.5786 10.4616 11.5786 10.0014C11.5786 9.54114 11.9517 9.16804 12.4119 9.16804H15.4916L15.1456 8.82202C14.8201 8.49659 14.8201 7.96895 15.1456 7.64351C15.471 7.31807 15.9986 7.31807 16.3241 7.64351Z"
|
||||
fill="#3370FF" />
|
||||
</svg>
|
Before Width: | Height: | Size: 524 B After Width: | Height: | Size: 2.5 KiB |
@@ -39,6 +39,7 @@ export default function InputGuideBox({
|
||||
);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [text],
|
||||
throttleWait: 300
|
||||
}
|
||||
|
@@ -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}
|
||||
|
116
projects/app/src/components/common/Modal/EditResourceModal.tsx
Normal file
116
projects/app/src/components/common/Modal/EditResourceModal.tsx
Normal 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;
|
@@ -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>
|
||||
))}
|
||||
|
184
projects/app/src/components/common/folder/MoveModal.tsx
Normal file
184
projects/app/src/components/common/folder/MoveModal.tsx
Normal 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;
|
68
projects/app/src/components/common/folder/Path.tsx
Normal file
68
projects/app/src/components/common/folder/Path.tsx
Normal 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);
|
140
projects/app/src/components/common/folder/SelectOneResource.tsx
Normal file
140
projects/app/src/components/common/folder/SelectOneResource.tsx
Normal 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;
|
178
projects/app/src/components/common/folder/SlideCard.tsx
Normal file
178
projects/app/src/components/common/folder/SlideCard.tsx
Normal 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;
|
54
projects/app/src/components/common/folder/useFolderDrag.tsx
Normal file
54
projects/app/src/components/common/folder/useFolderDrag.tsx
Normal 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
|
||||
};
|
||||
};
|
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@@ -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;
|
@@ -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>
|
||||
);
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
);
|
||||
|
@@ -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;
|
@@ -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 (
|
||||
|
@@ -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">
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
@@ -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);
|
@@ -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>
|
||||
</>
|
||||
) : (
|
||||
|
14
projects/app/src/global/core/app/api.d.ts
vendored
14
projects/app/src/global/core/app/api.d.ts
vendored
@@ -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'];
|
||||
|
@@ -72,7 +72,7 @@ export type SearchTestResponse = {
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
usingReRank: boolean;
|
||||
similarity: number;
|
||||
usingQueryExtension: boolean;
|
||||
queryExtensionModel?: string;
|
||||
};
|
||||
|
||||
/* =========== training =========== */
|
||||
|
@@ -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';
|
||||
|
@@ -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,
|
||||
|
@@ -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 }
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
40
projects/app/src/pages/api/core/app/folder/create.ts
Normal file
40
projects/app/src/pages/api/core/app/folder/create.ts
Normal 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);
|
41
projects/app/src/pages/api/core/app/folder/path.ts
Normal file
41
projects/app/src/pages/api/core/app/folder/path.ts
Normal 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;
|
||||
}
|
@@ -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
|
||||
}));
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
}
|
||||
});
|
||||
|
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
@@ -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) {
|
||||
|
@@ -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>
|
||||
)}
|
||||
|
@@ -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'),
|
||||
|
294
projects/app/src/pages/app/list/component/List.tsx
Normal file
294
projects/app/src/pages/app/list/component/List.tsx
Normal 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;
|
138
projects/app/src/pages/app/list/component/context.tsx
Normal file
138
projects/app/src/pages/app/list/component/context.tsx
Normal 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;
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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];
|
||||
}
|
||||
}
|
||||
|
@@ -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 获取模型
|
||||
|
11
projects/app/src/web/core/app/api/app.ts
Normal file
11
projects/app/src/web/core/app/api/app.ts
Normal 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 });
|
@@ -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(),
|
||||
|
@@ -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;
|
||||
}
|
||||
)
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
@@ -11,7 +11,7 @@ import {
|
||||
export const appTemplates: (AppItemType & {
|
||||
avatar: string;
|
||||
intro: string;
|
||||
type: `${AppTypeEnum}`;
|
||||
type: AppTypeEnum;
|
||||
})[] = [
|
||||
{
|
||||
id: 'simpleChat',
|
||||
|
@@ -15,7 +15,7 @@ export type SearchTestStoreItemType = {
|
||||
limit: number;
|
||||
usingReRank: boolean;
|
||||
similarity: number;
|
||||
usingQueryExtension: boolean;
|
||||
queryExtensionModel?: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
Reference in New Issue
Block a user