perf: load members;perf: yuque load;fix: workflow llm params cannot close (#3594)

* chat openapi doc

* feat: dataset openapi doc

* perf: load members

* perf: member load code

* perf: yuque load

* fix: workflow llm params cannot close
This commit is contained in:
Archer 2025-01-14 21:14:25 +08:00 committed by archer
parent 68f5afeba0
commit 80e670600b
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
31 changed files with 346 additions and 318 deletions

View File

@ -686,7 +686,7 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getHistories
- appId - 应用 Id - appId - 应用 Id
- offset - 偏移量,即从第几条数据开始取 - offset - 偏移量,即从第几条数据开始取
- pageSize - 记录数量 - pageSize - 记录数量
- source - 对话源 - source - 对话源。source=api表示获取通过 API 创建的对话(不会获取到页面上的对话记录)
{{% /alert %}} {{% /alert %}}
{{< /markdownify >}} {{< /markdownify >}}

View File

@ -733,6 +733,21 @@ data 为集合的 ID。
{{< tab tabName="请求示例" >}} {{< tab tabName="请求示例" >}}
{{< markdownify >}} {{< markdownify >}}
**4.8.19+**
```bash
curl --location --request POST 'http://localhost:3000/api/core/dataset/collection/listv2' \
--header 'Authorization: Bearer {{authorization}}' \
--header 'Content-Type: application/json' \
--data-raw '{
"offset":0,
"pageSize": 10,
"datasetId":"6593e137231a2be9c5603ba7",
"parentId": null,
"searchText":""
}'
```
**4.8.19-(不再维护)**
```bash ```bash
curl --location --request POST 'http://localhost:3000/api/core/dataset/collection/list' \ curl --location --request POST 'http://localhost:3000/api/core/dataset/collection/list' \
--header 'Authorization: Bearer {{authorization}}' \ --header 'Authorization: Bearer {{authorization}}' \
@ -753,7 +768,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio
{{< markdownify >}} {{< markdownify >}}
{{% alert icon=" " context="success" %}} {{% alert icon=" " context="success" %}}
- pageNum: 页码(选填) - offset: 偏移量
- pageSize: 每页数量最大30选填 - pageSize: 每页数量最大30选填
- datasetId: 知识库的ID(必填) - datasetId: 知识库的ID(必填)
- parentId: 父级Id选填 - parentId: 父级Id选填
@ -773,9 +788,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio
"statusText": "", "statusText": "",
"message": "", "message": "",
"data": { "data": {
"pageNum": 1, "list": [
"pageSize": 10,
"data": [
{ {
"_id": "6593e137231a2be9c5603ba9", "_id": "6593e137231a2be9c5603ba9",
"parentId": null, "parentId": null,

View File

@ -10,4 +10,8 @@ weight: 806
## 完整更新内容 ## 完整更新内容
1. 新增 - 工作流知识库检索支持按知识库权限进行过滤 1. 新增 - 工作流知识库检索支持按知识库权限进行过滤。
2. 优化 - 成员列表分页加载。
3. 优化 - 统一分页加载代码。
4. 修复 - 语雀文件库导入时,嵌套文件内容无法展开的问题。
5. 修复 - 工作流编排中LLM 参数无法关闭问题。

View File

@ -5,7 +5,7 @@ export type APIFileItem = {
type: 'file' | 'folder'; type: 'file' | 'folder';
updateTime: Date; updateTime: Date;
createTime: Date; createTime: Date;
canEnter?: boolean; hasChild?: boolean;
}; };
export type APIFileServer = { export type APIFileServer = {

View File

@ -13,6 +13,7 @@ type OrgSchemaType = {
}; };
type OrgMemberSchemaType = { type OrgMemberSchemaType = {
_id: string;
teamId: string; teamId: string;
orgId: string; orgId: string;
tmbId: string; tmbId: string;
@ -20,6 +21,6 @@ type OrgMemberSchemaType = {
type OrgType = Omit<OrgSchemaType, 'avatar'> & { type OrgType = Omit<OrgSchemaType, 'avatar'> & {
avatar: string; avatar: string;
members: OrgMemberSchemaType[];
permission: TeamPermission; permission: TeamPermission;
members: OrgMemberSchemaType[];
}; };

View File

@ -102,7 +102,7 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }
const formattedFiles = files.map((file) => ({ const formattedFiles = files.map((file) => ({
...file, ...file,
canEnter: file.type === 'folder' hasChild: file.type === 'folder'
})); }));
return formattedFiles; return formattedFiles;

View File

@ -47,14 +47,11 @@ export const OrgSchema = new Schema(
OrgSchema.virtual('members', { OrgSchema.virtual('members', {
ref: OrgMemberCollectionName, ref: OrgMemberCollectionName,
localField: '_id', localField: '_id',
foreignField: 'orgId' foreignField: 'orgId',
match: function (this: OrgSchemaType) {
return { teamId: this.teamId };
}
}); });
// OrgSchema.virtual('permission', {
// ref: ResourcePermissionCollectionName,
// localField: '_id',
// foreignField: 'orgId',
// justOne: true
// });
try { try {
OrgSchema.index({ OrgSchema.index({

View File

@ -23,7 +23,7 @@ function AvatarGroup({
<Flex position="relative"> <Flex position="relative">
{avatars.slice(0, max).map((avatar, index) => ( {avatars.slice(0, max).map((avatar, index) => (
<Avatar <Avatar
key={avatar + groupId} key={index}
src={avatar} src={avatar}
position={index > 0 ? 'absolute' : 'relative'} position={index > 0 ? 'absolute' : 'relative'}
left={index > 0 ? `${index * 15}px` : 0} left={index > 0 ? `${index * 15}px` : 0}

View File

@ -16,7 +16,7 @@ import MyBox from '../components/common/MyBox';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
type ItemHeight<T> = (index: number, data: T) => number; type ItemHeight<T> = (index: number, data: T) => number;
const thresholdVal = 200; const thresholdVal = 100;
export type ScrollListType = ({ export type ScrollListType = ({
children, children,

View File

@ -1,6 +1,6 @@
{ {
"name": "app", "name": "app",
"version": "4.8.18", "version": "4.8.19",
"private": false, "private": false,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",

View File

@ -237,6 +237,7 @@ function MemberModal({
/> />
<Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}> <Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}>
{/* Entry */}
{!searchText && !filterClass && ( {!searchText && !filterClass && (
<> <>
{entryList.current.map((item) => { {entryList.current.map((item) => {
@ -299,133 +300,135 @@ function MemberModal({
</Box> </Box>
)} )}
<ScrollData {filterClass && (
flexDirection={'column'} <ScrollData
gap={1} flexDirection={'column'}
userSelect={'none'} gap={1}
height={'fit-content'} userSelect={'none'}
> height={'fit-content'}
{filterMembers.map((member) => { >
const onChange = () => { {filterOrgs.map((org) => {
setSelectedMembers((state) => { const onChange = () => {
if (state.includes(member.tmbId)) { setSelectedOrgIdList((state) => {
return state.filter((v) => v !== member.tmbId); if (state.includes(org._id)) {
} return state.filter((v) => v !== org._id);
return [...state, member.tmbId]; }
}); return [...state, org._id];
}; });
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId); };
return ( const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
<HStack return (
justifyContent="space-between" <HStack
key={member.tmbId} justifyContent="space-between"
py="2" key={org._id}
px="3" py="2"
borderRadius="sm" px="3"
alignItems="center" borderRadius="sm"
_hover={HoverBoxStyle} alignItems="center"
onClick={onChange} _hover={HoverBoxStyle}
> onClick={onChange}
<Checkbox >
isChecked={selectedMemberIdList.includes(member.tmbId)} <Checkbox
pointerEvents="none" isChecked={selectedOrgIdList.includes(org._id)}
/> pointerEvents="none"
<MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} /> />
<Box w="full" ml="2"> <MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
{member.memberName} <HStack ml="2" w="full" gap="5px">
</Box> <Text>{org.name}</Text>
<PermissionTags permission={collaborator?.permission.value} /> {org.count && (
</HStack> <>
); <Tag size="sm" my="auto">
})} {org.count}
{filterOrgs.map((org) => { </Tag>
const onChange = () => { </>
setSelectedOrgIdList((state) => { )}
if (state.includes(org._id)) { </HStack>
return state.filter((v) => v !== org._id); <PermissionTags permission={collaborator?.permission.value} />
}
return [...state, org._id];
});
};
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
return (
<HStack
justifyContent="space-between"
key={org._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isChecked={selectedOrgIdList.includes(org._id)}
pointerEvents="none"
/>
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
<HStack ml="2" w="full" gap="5px">
<Text>{org.name}</Text>
{org.count && ( {org.count && (
<> <MyIcon
<Tag size="sm" my="auto"> name="core/chat/chevronRight"
{org.count} w="16px"
</Tag> p="4px"
</> rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={() => {
setParentPath(getOrgChildrenPath(org));
}}
/>
)} )}
</HStack> </HStack>
<PermissionTags permission={collaborator?.permission.value} /> );
{org.count && ( })}
<MyIcon {filterMembers.map((member) => {
name="core/chat/chevronRight" const onChange = () => {
w="16px" setSelectedMembers((state) => {
p="4px" if (state.includes(member.tmbId)) {
rounded={'6px'} return state.filter((v) => v !== member.tmbId);
_hover={{ }
bgColor: 'myGray.200' return [...state, member.tmbId];
}} });
onClick={() => { };
setParentPath(getOrgChildrenPath(org)); const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
}} return (
<HStack
justifyContent="space-between"
key={member.tmbId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isChecked={selectedMemberIdList.includes(member.tmbId)}
pointerEvents="none"
/> />
)} <MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
</HStack> <Box w="full" ml="2">
); {member.memberName}
})} </Box>
{filterGroups.map((group) => { <PermissionTags permission={collaborator?.permission.value} />
const onChange = () => { </HStack>
setSelectedGroupIdList((state) => { );
if (state.includes(group._id)) { })}
return state.filter((v) => v !== group._id); {filterGroups.map((group) => {
} const onChange = () => {
return [...state, group._id]; setSelectedGroupIdList((state) => {
}); if (state.includes(group._id)) {
}; return state.filter((v) => v !== group._id);
const collaborator = collaboratorList?.find((v) => v.groupId === group._id); }
return ( return [...state, group._id];
<HStack });
justifyContent="space-between" };
key={group._id} const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
py="2" return (
px="3" <HStack
borderRadius="sm" justifyContent="space-between"
alignItems="center" key={group._id}
_hover={HoverBoxStyle} py="2"
onClick={onChange} px="3"
> borderRadius="sm"
<Checkbox alignItems="center"
isChecked={selectedGroupIdList.includes(group._id)} _hover={HoverBoxStyle}
pointerEvents="none" onClick={onChange}
/> >
<MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} /> <Checkbox
<Box ml="2" w="full"> isChecked={selectedGroupIdList.includes(group._id)}
{group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} pointerEvents="none"
</Box> />
<PermissionTags permission={collaborator?.permission.value} /> <MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
</HStack> <Box ml="2" w="full">
); {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
})} </Box>
</ScrollData> <PermissionTags permission={collaborator?.permission.value} />
</HStack>
);
})}
</ScrollData>
)}
</Flex> </Flex>
</Flex> </Flex>

View File

@ -55,7 +55,7 @@ function GroupInfoModal({ onClose, editGroupId }: { onClose: () => void; editGro
} }
); );
const { run: onCreate, loading: isLoadingCreate } = useRequest2( const { runAsync: onCreate, loading: isLoadingCreate } = useRequest2(
(data: GroupFormType) => { (data: GroupFormType) => {
return postCreateGroup({ return postCreateGroup({
name: data.name, name: data.name,
@ -67,7 +67,7 @@ function GroupInfoModal({ onClose, editGroupId }: { onClose: () => void; editGro
} }
); );
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2( const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
async (data: GroupFormType) => { async (data: GroupFormType) => {
if (!editGroupId) return; if (!editGroupId) return;
return putUpdateGroup({ return putUpdateGroup({

View File

@ -32,27 +32,26 @@ export type GroupFormType = {
}[]; }[];
}; };
// 1. Owner can not be deleted, toast
// 2. Owner/Admin can manage members
// 3. Owner can add/remove admins
function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGroupId?: string }) { function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGroupId?: string }) {
// 1. Owner can not be deleted, toast
// 2. Owner/Admin can manage members
// 3. Owner can add/remove admins
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const { toast } = useToast(); const { toast } = useToast();
const [hoveredMemberId, setHoveredMemberId] = useState<string | undefined>(undefined);
const {
members: allMembers,
refetchGroups,
groups,
refetchMembers,
MemberScrollData
} = useContextSelector(TeamContext, (v) => v);
const groups = useContextSelector(TeamContext, (v) => v.groups);
const refetchGroups = useContextSelector(TeamContext, (v) => v.refetchGroups);
const group = useMemo(() => { const group = useMemo(() => {
return groups.find((item) => item._id === editGroupId); return groups.find((item) => item._id === editGroupId);
}, [editGroupId, groups]); }, [editGroupId, groups]);
const allMembers = useContextSelector(TeamContext, (v) => v.members);
const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers);
const MemberScrollData = useContextSelector(TeamContext, (v) => v.MemberScrollData);
const [hoveredMemberId, setHoveredMemberId] = useState<string>();
const [members, setMembers] = useState(group?.members || []); const [members, setMembers] = useState(group?.members || []);
const [searchKey, setSearchKey] = useState(''); const [searchKey, setSearchKey] = useState('');
const filtered = useMemo(() => { const filtered = useMemo(() => {
return [ return [
@ -63,7 +62,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
]; ];
}, [searchKey, allMembers]); }, [searchKey, allMembers]);
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2( const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
async () => { async () => {
if (!editGroupId || !members.length) return; if (!editGroupId || !members.length) return;
return putUpdateGroup({ return putUpdateGroup({
@ -156,39 +155,37 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
setSearchKey(e.target.value); setSearchKey(e.target.value);
}} }}
/> />
<Flex flexDirection="column" mt={3} flexGrow="1" overflow={'auto'} maxH={'400px'}> <MemberScrollData mt={3} flex={'1 0 0'} h={0}>
<MemberScrollData> {filtered.map((member) => {
{filtered.map((member) => { return (
return ( <HStack
<HStack py="2"
py="2" px={3}
px={3} borderRadius={'md'}
borderRadius={'md'} alignItems="center"
alignItems="center" key={member.tmbId}
key={member.tmbId} cursor={'pointer'}
cursor={'pointer'} _hover={{
_hover={{ bg: 'myGray.50',
bg: 'myGray.50', ...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {})
...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {}) }}
}} _notLast={{ mb: 2 }}
_notLast={{ mb: 2 }} onClick={() => handleToggleSelect(member.tmbId)}
onClick={() => handleToggleSelect(member.tmbId)} >
> <Checkbox
<Checkbox isChecked={!!isSelected(member.tmbId)}
isChecked={!!isSelected(member.tmbId)} icon={<MyIcon name={'common/check'} w={'12px'} />}
icon={<MyIcon name={'common/check'} w={'12px'} />} />
/> <Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
<Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} /> <Box>{member.memberName}</Box>
<Box>{member.memberName}</Box> </HStack>
</HStack> );
); })}
})} </MemberScrollData>
</MemberScrollData>
</Flex>
</Flex> </Flex>
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}> <Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
<Box mt={2}>{t('common:chosen') + ': ' + members.length}</Box> <Box mt={2}>{t('common:chosen') + ': ' + members.length}</Box>
<Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'400px'}> <MemberScrollData mt={3} flex={'1 0 0'} h={0}>
{members.map((member) => { {members.map((member) => {
return ( return (
<HStack <HStack
@ -265,11 +262,14 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
</HStack> </HStack>
); );
})} })}
</Flex> </MemberScrollData>
</Flex> </Flex>
</Grid> </Grid>
</ModalBody> </ModalBody>
<ModalFooter alignItems="flex-end"> <ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button isLoading={isLoading} onClick={onUpdate}> <Button isLoading={isLoading} onClick={onUpdate}>
{t('common:common.Save')} {t('common:common.Save')}
</Button> </Button>

View File

@ -24,50 +24,43 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { deleteGroup } from '@/web/support/user/team/group/api'; import { deleteGroup } from '@/web/support/user/team/group/api';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import MemberTag from '../../../../../components/support/user/team/Info/MemberTag'; import MemberTag from '../../../../components/support/user/team/Info/MemberTag';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useState } from 'react'; import { useState } from 'react';
import IconButton from '../OrgManage/IconButton'; import IconButton from '../OrgManage/IconButton';
import { MemberGroupType } from '@fastgpt/global/support/permission/memberGroup/type';
const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal')); const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal'));
const GroupInfoModal = dynamic(() => import('./GroupInfoModal')); const GroupInfoModal = dynamic(() => import('./GroupInfoModal'));
const ManageGroupMemberModal = dynamic(() => import('./GroupManageMember')); const GroupManageMember = dynamic(() => import('./GroupManageMember'));
function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const [editGroupId, setEditGroupId] = useState<string>(); const { groups, refetchGroups, members, refetchMembers } = useContextSelector(
TeamContext,
(v) => v
);
const [editGroup, setEditGroup] = useState<MemberGroupType>();
const { const {
isOpen: isOpenGroupInfo, isOpen: isOpenGroupInfo,
onOpen: onOpenGroupInfo, onOpen: onOpenGroupInfo,
onClose: onCloseGroupInfo onClose: onCloseGroupInfo
} = useDisclosure(); } = useDisclosure();
const {
isOpen: isOpenManageGroupMember, const onEditGroupInfo = (e: MemberGroupType) => {
onOpen: onOpenManageGroupMember, setEditGroup(e);
onClose: onCloseManageGroupMember
} = useDisclosure();
const onEditGroup = (groupId: string) => {
setEditGroupId(groupId);
onOpenGroupInfo(); onOpenGroupInfo();
}; };
const onManageMember = (groupId: string) => {
setEditGroupId(groupId);
onOpenManageGroupMember();
};
const { ConfirmModal: ConfirmDeleteGroupModal, openConfirm: openDeleteGroupModal } = useConfirm({ const { ConfirmModal: ConfirmDeleteGroupModal, openConfirm: openDeleteGroupModal } = useConfirm({
type: 'delete', type: 'delete',
content: t('account_team:confirm_delete_group') content: t('account_team:confirm_delete_group')
}); });
const { groups, refetchGroups, members, refetchMembers } = useContextSelector(
TeamContext,
(v) => v
);
const { runAsync: delDeleteGroup } = useRequest2(deleteGroup, { const { runAsync: delDeleteGroup } = useRequest2(deleteGroup, {
onSuccess: () => { onSuccess: () => {
refetchGroups(); refetchGroups();
@ -75,12 +68,21 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
} }
}); });
const {
isOpen: isOpenManageGroupMember,
onOpen: onOpenManageGroupMember,
onClose: onCloseManageGroupMember
} = useDisclosure();
const onManageMember = (e: MemberGroupType) => {
setEditGroup(e);
onOpenManageGroupMember();
};
const hasGroupManagePer = (group: (typeof groups)[0]) => const hasGroupManagePer = (group: (typeof groups)[0]) =>
userInfo?.team.permission.hasManagePer || userInfo?.team.permission.hasManagePer ||
['admin', 'owner'].includes( ['admin', 'owner'].includes(
group.members.find((item) => item.tmbId === userInfo?.team.tmbId)?.role ?? '' group.members.find((item) => item.tmbId === userInfo?.team.tmbId)?.role ?? ''
); );
const isGroupOwner = (group: (typeof groups)[0]) => const isGroupOwner = (group: (typeof groups)[0]) =>
userInfo?.team.permission.hasManagePer || userInfo?.team.permission.hasManagePer ||
group.members.find((item) => item.role === 'owner')?.tmbId === userInfo?.team.tmbId; group.members.find((item) => item.role === 'owner')?.tmbId === userInfo?.team.tmbId;
@ -90,8 +92,8 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
onOpen: onOpenChangeOwner, onOpen: onOpenChangeOwner,
onClose: onCloseChangeOwner onClose: onCloseChangeOwner
} = useDisclosure(); } = useDisclosure();
const onChangeOwner = (groupId: string) => { const onChangeOwner = (e: MemberGroupType) => {
setEditGroupId(groupId); setEditGroup(e);
onOpenChangeOwner(); onOpenChangeOwner();
}; };
@ -173,7 +175,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
<AvatarGroup avatars={members.map((v) => v.avatar)} groupId={group._id} /> <AvatarGroup avatars={members.map((v) => v.avatar)} groupId={group._id} />
) : hasGroupManagePer(group) ? ( ) : hasGroupManagePer(group) ? (
<MyTooltip label={t('account_team:manage_member')}> <MyTooltip label={t('account_team:manage_member')}>
<Box cursor="pointer" onClick={() => onManageMember(group._id)}> <Box cursor="pointer" onClick={() => onManageMember(group)}>
<AvatarGroup <AvatarGroup
avatars={group.members.map( avatars={group.members.map(
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' (v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
@ -202,14 +204,14 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
label: t('account_team:edit_info'), label: t('account_team:edit_info'),
icon: 'edit', icon: 'edit',
onClick: () => { onClick: () => {
onEditGroup(group._id); onEditGroupInfo(group);
} }
}, },
{ {
label: t('account_team:manage_member'), label: t('account_team:manage_member'),
icon: 'support/team/group', icon: 'support/team/group',
onClick: () => { onClick: () => {
onManageMember(group._id); onManageMember(group);
} }
}, },
...(isGroupOwner(group) ...(isGroupOwner(group)
@ -218,7 +220,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
label: t('account_team:transfer_ownership'), label: t('account_team:transfer_ownership'),
icon: 'modal/changePer', icon: 'modal/changePer',
onClick: () => { onClick: () => {
onChangeOwner(group._id); onChangeOwner(group);
}, },
type: 'primary' as MenuItemType type: 'primary' as MenuItemType
}, },
@ -246,25 +248,25 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
</MyBox> </MyBox>
<ConfirmDeleteGroupModal /> <ConfirmDeleteGroupModal />
{isOpenChangeOwner && editGroupId && ( {isOpenChangeOwner && editGroup && (
<ChangeOwnerModal groupId={editGroupId} onClose={onCloseChangeOwner} /> <ChangeOwnerModal groupId={editGroup._id} onClose={onCloseChangeOwner} />
)} )}
{isOpenGroupInfo && ( {isOpenGroupInfo && (
<GroupInfoModal <GroupInfoModal
onClose={() => { onClose={() => {
onCloseGroupInfo(); onCloseGroupInfo();
setEditGroupId(undefined); setEditGroup(undefined);
}} }}
editGroupId={editGroupId} editGroupId={editGroup?._id}
/> />
)} )}
{isOpenManageGroupMember && ( {isOpenManageGroupMember && editGroup && (
<ManageGroupMemberModal <GroupManageMember
onClose={() => { onClose={() => {
onCloseManageGroupMember(); onCloseManageGroupMember();
setEditGroupId(undefined); setEditGroup(undefined);
}} }}
editGroupId={editGroupId} editGroupId={editGroup._id}
/> />
)} )}
</> </>

View File

@ -97,7 +97,6 @@ function OrgMemberManageModal({
return ( return (
<MyModal <MyModal
onClose={onClose}
isOpen isOpen
title={t('user:team.group.manage_member')} title={t('user:team.group.manage_member')}
iconSrc={currentOrg?.avatar} iconSrc={currentOrg?.avatar}
@ -186,7 +185,10 @@ function OrgMemberManageModal({
</Flex> </Flex>
</Grid> </Grid>
</ModalBody> </ModalBody>
<ModalFooter alignItems="flex-end"> <ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button isLoading={isLoading} onClick={onUpdate}> <Button isLoading={isLoading} onClick={onUpdate}>
{t('common:common.Save')} {t('common:common.Save')}
</Button> </Button>

View File

@ -75,7 +75,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo, isTeamAdmin } = useUserStore(); const { userInfo, isTeamAdmin } = useUserStore();
const { members } = useContextSelector(TeamContext, (v) => v); const { members, MemberScrollData } = useContextSelector(TeamContext, (v) => v);
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
const isSyncMember = feConfigs.register_method?.includes('sync'); const isSyncMember = feConfigs.register_method?.includes('sync');
@ -174,109 +174,112 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} /> <Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
</Box> </Box>
<Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}> <Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}>
{/* Table */} <MemberScrollData h={'100%'} fontSize={'sm'} flexGrow={1}>
<TableContainer h={'100%'} overflowY={'auto'} fontSize={'sm'} flexGrow={1}> {/* Table */}
<Table> <TableContainer>
<Thead> <Table>
<Tr bg={'white !important'}> <Thead>
<Th bg="myGray.100" borderLeftRadius="6px"> <Tr bg={'white !important'}>
{t('common:Name')} <Th bg="myGray.100" borderLeftRadius="6px">
</Th> {t('common:Name')}
{!isSyncMember && (
<Th bg="myGray.100" borderRightRadius="6px">
{t('common:common.Action')}
</Th> </Th>
)} {!isSyncMember && (
</Tr> <Th bg="myGray.100" borderRightRadius="6px">
</Thead> {t('common:common.Action')}
<Tbody> </Th>
{currentOrgs.map((org) => (
<Tr key={org._id} overflow={'unset'}>
<Td>
<HStack
cursor={'pointer'}
onClick={() => setParentPath(getOrgChildrenPath(org))}
>
<MemberTag name={org.name} avatar={org.avatar} />
<Tag size="sm">{org.count}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
</Td>
{isTeamAdmin && !isSyncMember && (
<Td w={'6rem'}>
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'edit',
label: t('account_team:edit_info'),
onClick: () => setEditOrg(org)
},
{
icon: 'common/file/move',
label: t('common:Move'),
onClick: () => setMovingOrg(org)
},
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () => deleteOrgHandler(org._id)
}
]
}
]}
/>
</Td>
)} )}
</Tr> </Tr>
))} </Thead>
{currentOrg?.members.map((member) => { <Tbody>
const memberInfo = members.find((m) => m.tmbId === member.tmbId); {currentOrgs.map((org) => (
if (!memberInfo) return null; <Tr key={org._id} overflow={'unset'}>
return (
<Tr key={member.tmbId}>
<Td> <Td>
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} /> <HStack
cursor={'pointer'}
onClick={() => setParentPath(getOrgChildrenPath(org))}
>
<MemberTag name={org.name} avatar={org.avatar} />
<Tag size="sm">{org.count}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
</Td> </Td>
<Td w={'6rem'}> {isTeamAdmin && !isSyncMember && (
{isTeamAdmin && !isSyncMember && ( <Td w={'6rem'}>
<MyMenu <MyMenu
trigger={'hover'} trigger="hover"
Button={<IconButton name="more" />} Button={<IconButton name="more" />}
menuList={[ menuList={[
{ {
children: [ children: [
{
icon: 'edit',
label: t('account_team:edit_info'),
onClick: () => setEditOrg(org)
},
{
icon: 'common/file/move',
label: t('common:Move'),
onClick: () => setMovingOrg(org)
},
{ {
icon: 'delete', icon: 'delete',
label: t('account_team:delete'), label: t('account_team:delete'),
type: 'danger', type: 'danger',
onClick: () => onClick: () => deleteOrgHandler(org._id)
openDeleteMemberModal(() =>
deleteMemberReq(currentOrg._id, member.tmbId)
)()
} }
] ]
} }
]} ]}
/> />
)} </Td>
</Td> )}
</Tr> </Tr>
); ))}
})} {currentOrg?.members.map((member) => {
</Tbody> const memberInfo = members.find((m) => m.tmbId === member.tmbId);
</Table> if (!memberInfo) return null;
</TableContainer>
return (
<Tr key={member.tmbId}>
<Td>
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
</Td>
<Td w={'6rem'}>
{isTeamAdmin && !isSyncMember && (
<MyMenu
trigger={'hover'}
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () =>
openDeleteMemberModal(() =>
deleteMemberReq(currentOrg._id, member.tmbId)
)()
}
]
}
]}
/>
)}
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
</MemberScrollData>
{/* Slider */} {/* Slider */}
{!isSyncMember && ( {!isSyncMember && (
<VStack w={'180px'} alignItems={'start'}> <VStack w={'180px'} alignItems={'start'}>

View File

@ -24,7 +24,7 @@ import {
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import Avatar from '@fastgpt/web/components/common/Avatar'; import Avatar from '@fastgpt/web/components/common/Avatar';
import MemberTag from '../../../../../components/support/user/team/Info/MemberTag'; import MemberTag from '../../../../components/support/user/team/Info/MemberTag';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import { import {
TeamManagePermissionVal, TeamManagePermissionVal,

View File

@ -11,13 +11,15 @@ import { useRouter } from 'next/router';
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { TeamContext, TeamModalContextProvider } from './components/context'; import { TeamContext, TeamModalContextProvider } from '@/pageComponents/account/team/context';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
const MemberTable = dynamic(() => import('./components/MemberTable')); const MemberTable = dynamic(() => import('@/pageComponents/account/team/MemberTable'));
const PermissionManage = dynamic(() => import('./components/PermissionManage/index')); const PermissionManage = dynamic(
const GroupManage = dynamic(() => import('./components/GroupManage/index')); () => import('@/pageComponents/account/team/PermissionManage/index')
const OrgManage = dynamic(() => import('./components/OrgManage/index')); );
const GroupManage = dynamic(() => import('@/pageComponents/account/team/GroupManage/index'));
const OrgManage = dynamic(() => import('@/pageComponents/account/team/OrgManage/index'));
export enum TeamTabEnum { export enum TeamTabEnum {
member = 'member', member = 'member',

View File

@ -8,7 +8,8 @@ async function handler(req: ApiRequestProps<{}, { bufferId?: string }>, res: Nex
// If bufferId is the same as the current bufferId, return directly // If bufferId is the same as the current bufferId, return directly
if (bufferId && global.systemInitBufferId && global.systemInitBufferId === bufferId) { if (bufferId && global.systemInitBufferId && global.systemInitBufferId === bufferId) {
return { return {
bufferId: global.systemInitBufferId bufferId: global.systemInitBufferId,
systemVersion: global.systemVersion || '0.0.0'
}; };
} }

View File

@ -127,7 +127,7 @@ const CustomAPIFileInput = () => {
const handleItemClick = useCallback( const handleItemClick = useCallback(
(item: APIFileItem) => { (item: APIFileItem) => {
if (item.canEnter) { if (item.hasChild) {
setPaths((state) => [...state, { parentId: item.id, parentName: item.name }]); setPaths((state) => [...state, { parentId: item.id, parentName: item.name }]);
return setParent({ return setParent({
parentId: item.id, parentId: item.id,
@ -250,7 +250,7 @@ const CustomAPIFileInput = () => {
<Box fontSize={'sm'} fontWeight={'medium'} color={'myGray.900'}> <Box fontSize={'sm'} fontWeight={'medium'} color={'myGray.900'}>
{item.name} {item.name}
</Box> </Box>
{item.canEnter && <MyIcon name="core/chat/chevronRight" w={'18px'} ml={2} />} {item.hasChild && <MyIcon name="core/chat/chevronRight" w={'18px'} ml={2} />}
</Flex> </Flex>
); );
})} })}

View File

@ -118,7 +118,7 @@ export const storeNode2FlowNode = ({
toolDescription: t(templateInput.toolDescription ?? (storeInput.toolDescription as any)), toolDescription: t(templateInput.toolDescription ?? (storeInput.toolDescription as any)),
selectedTypeIndex: storeInput.selectedTypeIndex ?? templateInput.selectedTypeIndex, selectedTypeIndex: storeInput.selectedTypeIndex ?? templateInput.selectedTypeIndex,
value: storeInput.value ?? templateInput.value, value: storeInput.value,
valueType: storeInput.valueType ?? templateInput.valueType, valueType: storeInput.valueType ?? templateInput.valueType,
label: storeInput.label ?? templateInput.label label: storeInput.label ?? templateInput.label
}; };