feat: sync org from wecom, pref: member list pagination (#3549)

* feat: sync org

* chore: fe

* chore: loading

* chore: type

* pref: team member list change to pagination. Edit a sort of list apis.

* feat: member update avatar

* chore: user avatar move to tmb

* chore: init scripts move user avatar

* chore: sourceMember

* fix: list api sourceMember

* fix: member sync

* fix: pagination

* chore: adjust code

* chore: move changeOwner to pro

* chore: init v4819 script

* chore: adjust code

* chore: UserBox
This commit is contained in:
Finley Ge
2025-01-13 11:22:20 +08:00
committed by archer
parent a8d456f448
commit ec0cef09a2
73 changed files with 883 additions and 757 deletions

View File

@@ -54,7 +54,7 @@ const InputGuideConfig = ({
onChange: (e: ChatInputGuideConfigType) => void;
}) => {
const { t } = useTranslation();
const { chatT, commonT } = useI18n();
const { chatT } = useI18n();
const { isOpen, onOpen, onClose } = useDisclosure();
const {
isOpen: isOpenLexiconConfig,
@@ -220,7 +220,7 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () =>
});
const { run: createNewData, loading: isCreating } = useRequest2(
(textList: string[]) => {
async (textList: string[]) => {
if (textList.filter(Boolean).length === 0) {
return Promise.resolve();
}

View File

@@ -1,4 +1,4 @@
import { useUserStore } from '@/web/support/user/useUserStore';
import { getTeamMembers } from '@/web/support/user/team/api';
import {
Box,
Flex,
@@ -15,6 +15,7 @@ import Icon from '@fastgpt/web/components/common/Icon';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTag from '@fastgpt/web/components/common/Tag';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import { useTranslation } from 'next-i18next';
import React, { useState } from 'react';
@@ -31,13 +32,12 @@ export function ChangeOwnerModal({
onChangeOwner
}: ChangeOwnerModalProps & { onClose: () => void }) {
const { t } = useTranslation();
const { loadAndGetTeamMembers } = useUserStore();
const [inputValue, setInputValue] = React.useState('');
const { data: teamMembers = [] } = useRequest2(loadAndGetTeamMembers, {
manual: false
const { data: teamMembers, ScrollData } = useScrollPagination(getTeamMembers, {
pageSize: 15
});
const memberList = teamMembers.filter((item) => {
return item.memberName.includes(inputValue);
});
@@ -101,11 +101,6 @@ export function ChangeOwnerModal({
onOpenMemberListMenu();
setSelectedMember(null);
}}
// onBlur={() => {
// setTimeout(() => {
// onCloseMemberListMenu();
// }, 10);
// }}
{...(selectedMember && { pl: '10' })}
/>
</Flex>
@@ -123,26 +118,28 @@ export function ChangeOwnerModal({
maxH={'300px'}
overflow={'auto'}
>
{memberList.map((item) => (
<Box
key={item.tmbId}
p="2"
_hover={{ bg: 'myGray.100' }}
mx="1"
borderRadius="md"
cursor={'pointer'}
onClickCapture={() => {
setInputValue(item.memberName);
setSelectedMember(item);
onCloseMemberListMenu();
}}
>
<Flex align="center">
<Avatar src={item.avatar} w="1.25rem" />
<Box ml="2">{item.memberName}</Box>
</Flex>
</Box>
))}
<ScrollData>
{memberList.map((item) => (
<Box
key={item.tmbId}
p="2"
_hover={{ bg: 'myGray.100' }}
mx="1"
borderRadius="md"
cursor={'pointer'}
onClickCapture={() => {
setInputValue(item.memberName);
setSelectedMember(item);
onCloseMemberListMenu();
}}
>
<Flex align="center">
<Avatar src={item.avatar} w="1.25rem" />
<Box ml="2">{item.memberName}</Box>
</Flex>
</Box>
))}
</ScrollData>
</Flex>
)}

View File

@@ -33,6 +33,10 @@ import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
import { useContextSelector } from 'use-context-selector';
import { CollaboratorContext } from './context';
import { getTeamMembers } from '@/web/support/user/team/api';
import { getGroupList } from '@/web/support/user/team/group/api';
import { getOrgList } from '@/web/support/user/team/org/api';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
const HoverBoxStyle = {
bgColor: 'myGray.50',
@@ -47,30 +51,27 @@ function MemberModal({
addPermissionOnly?: boolean;
}) {
const { t } = useTranslation();
const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, loadAndGetOrgs } = useUserStore();
const { userInfo, myGroups } = useUserStore();
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
const [searchText, setSearchText] = useState<string>('');
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
const { data: members, ScrollData } = useScrollPagination(getTeamMembers, {
pageSize: 15
});
const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } =
useRequest2(
async () => {
if (!userInfo?.team?.teamId) return [[], []];
return Promise.all([
loadAndGetTeamMembers(true),
loadAndGetGroups(true),
loadAndGetOrgs(true)
]);
},
{
manual: false,
refreshDeps: [userInfo?.team?.teamId]
}
);
const { data: [groups = [], orgs = []] = [], loading: loadingGroupsAndOrgs } = useRequest2(
async () => {
if (!userInfo?.team?.teamId) return [[], []];
return Promise.all([getGroupList(), getOrgList()]);
},
{
manual: false,
refreshDeps: [userInfo?.team?.teamId]
}
);
const [parentPath, setParentPath] = useState('');
const paths = useMemo(() => {
const splitPath = parentPath.split('/').filter(Boolean);
return splitPath
@@ -212,7 +213,7 @@ function MemberModal({
h={'100%'}
maxH={'90vh'}
isCentered
isLoading={loadingMembersAndGroups}
isLoading={loadingGroupsAndOrgs}
>
<ModalBody flex={'1'}>
<Grid
@@ -299,140 +300,128 @@ function MemberModal({
)}
<Flex flexDirection={'column'} gap={1} userSelect={'none'}>
{filterMembers.map((member) => {
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
const disabled = addOnly && collaborator !== undefined;
const onChange = () => {
if (disabled) return;
setSelectedMembers((state) => {
if (state.includes(member.tmbId)) {
return state.filter((v) => v !== member.tmbId);
}
return [...state, member.tmbId];
});
};
return (
<HStack
justifyContent="space-between"
key={member.tmbId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isDisabled={disabled}
isChecked={disabled || selectedMemberIdList.includes(member.tmbId)}
pointerEvents="none"
/>
<MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2">
{member.memberName}
</Box>
<PermissionTags
permission={addOnly ? undefined : collaborator?.permission.value}
/>
</HStack>
);
})}
{filterOrgs.map((org) => {
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
const disabled = addOnly && collaborator !== undefined;
const onChange = () => {
if (disabled) return;
setSelectedOrgIdList((state) => {
if (state.includes(org._id)) {
return state.filter((v) => v !== org._id);
}
return [...state, org._id];
});
};
return (
<HStack
justifyContent="space-between"
key={org._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isDisabled={disabled}
isChecked={disabled || 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>
<ScrollData>
{filterMembers.map((member) => {
const onChange = () => {
setSelectedMembers((state) => {
if (state.includes(member.tmbId)) {
return state.filter((v) => v !== member.tmbId);
}
return [...state, member.tmbId];
});
};
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%'} />
<Box w="full" ml="2">
{member.memberName}
</Box>
<PermissionTags permission={collaborator?.permission.value} />
</HStack>
);
})}
{filterOrgs.map((org) => {
const onChange = () => {
setSelectedOrgIdList((state) => {
if (state.includes(org._id)) {
return state.filter((v) => v !== org._id);
}
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 && (
<>
<Tag size="sm" my="auto">
{org.count}
</Tag>
</>
)}
</HStack>
<PermissionTags permission={collaborator?.permission.value} />
{org.count && (
<Tag size="sm" my="auto">
{org.count}
</Tag>
<MyIcon
name="core/chat/chevronRight"
w="16px"
p="4px"
rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={() => {
setParentPath(getOrgChildrenPath(org));
}}
/>
)}
</HStack>
<PermissionTags
permission={addOnly ? undefined : collaborator?.permission.value}
/>
{org.count && (
<MyIcon
name="core/chat/chevronRight"
w="16px"
p="4px"
rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={(e) => {
e.stopPropagation();
setParentPath(getOrgChildrenPath(org));
}}
);
})}
{filterGroups.map((group) => {
const onChange = () => {
setSelectedGroupIdList((state) => {
if (state.includes(group._id)) {
return state.filter((v) => v !== group._id);
}
return [...state, group._id];
});
};
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
return (
<HStack
justifyContent="space-between"
key={group._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isChecked={selectedGroupIdList.includes(group._id)}
pointerEvents="none"
/>
)}
</HStack>
);
})}
{filterGroups.map((group) => {
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
const disabled = addOnly && collaborator !== undefined;
const onChange = () => {
if (disabled) return;
setSelectedGroupIdList((state) => {
if (state.includes(group._id)) {
return state.filter((v) => v !== group._id);
}
return [...state, group._id];
});
};
return (
<HStack
justifyContent="space-between"
key={group._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isDisabled={disabled}
isChecked={disabled || selectedGroupIdList.includes(group._id)}
pointerEvents="none"
/>
<MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
</Box>
<PermissionTags
permission={addOnly ? undefined : collaborator?.permission.value}
/>
</HStack>
);
})}
<MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
</Box>
<PermissionTags permission={collaborator?.permission.value} />
</HStack>
);
})}
</ScrollData>
</Flex>
</Flex>
</Flex>

View File

@@ -1,7 +1,7 @@
import { RequestPaging } from '@/types';
import { PaginationProps } from '@fastgpt/web/common/fetch/type';
export type GetAppChatLogsParams = RequestPaging & {
export type GetAppChatLogsParams = PaginationProps<{
appId: string;
dateStart: Date;
dateEnd: Date;
};
}>;

View File

@@ -3,24 +3,24 @@ import {
DatasetCollectionTypeEnum,
DatasetTypeEnum
} from '@fastgpt/global/core/dataset/constants';
import type { RequestPaging } from '@/types';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import type { SearchTestItemType } from '@/types/core/dataset';
import { UploadChunkItemType } from '@fastgpt/global/core/dataset/type';
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { PaginationProps } from '@fastgpt/web/common/fetch/type';
/* ===== dataset ===== */
/* ======= collections =========== */
export type GetDatasetCollectionsProps = RequestPaging & {
export type GetDatasetCollectionsProps = PaginationProps<{
datasetId: string;
parentId?: string;
searchText?: string;
filterTags?: string[];
simple?: boolean;
selectFolder?: boolean;
};
}>;
/* ==== data ===== */

View File

@@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import React, { useState, useMemo, useEffect } from 'react';
import {
Button,
Table,
@@ -25,7 +25,6 @@ import {
billStatusMap,
billTypeMap
} from '@fastgpt/global/support/wallet/bill/constants';
// import { usePagination } from '@/web/common/hooks/usePagination';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
@@ -33,25 +32,23 @@ import MySelect from '@fastgpt/web/components/common/MySelect';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { usePagination } from '@fastgpt/web/hooks/usePagination';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { useI18n } from '@/web/context/I18n';
const BillTable = () => {
const { t } = useTranslation();
const { commonT } = useI18n();
const { toast } = useToast();
const [billType, setBillType] = useState<BillTypeEnum | ''>('');
const [billType, setBillType] = useState<BillTypeEnum | undefined>(undefined);
const [billDetail, setBillDetail] = useState<BillSchemaType>();
const billTypeList = useMemo(
() =>
[
{ label: t('account_bill:all'), value: '' },
{ label: t('account_bill:all'), value: undefined },
...Object.entries(billTypeMap).map(([key, value]) => ({
label: t(value.label as any),
value: key
}))
] as {
label: string;
value: BillTypeEnum | '';
value: BillTypeEnum | undefined;
}[],
[t]
);
@@ -62,8 +59,7 @@ const BillTable = () => {
Pagination,
getData,
total
} = usePagination({
api: getBills,
} = usePagination(getBills, {
pageSize: 20,
params: {
type: billType
@@ -110,7 +106,7 @@ const BillTable = () => {
<Tr>
<Th>#</Th>
<Th>
<MySelect<BillTypeEnum | ''>
<MySelect
list={billTypeList}
value={billType}
size={'sm'}
@@ -181,7 +177,6 @@ export default BillTable;
function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: () => void }) {
const { t } = useTranslation();
const { commonT } = useI18n();
return (
<MyModal
isOpen={true}

View File

@@ -1,7 +1,7 @@
import { getInvoiceRecords } from '@/web/support/wallet/bill/invoice/api';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useTranslation } from 'next-i18next';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import {
Box,
Button,
@@ -30,8 +30,7 @@ const InvoiceTable = () => {
isLoading,
Pagination,
total
} = usePagination({
api: getInvoiceRecords,
} = usePagination(getInvoiceRecords, {
pageSize: 20
});

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useRef } from 'react';
import React, { useCallback, useMemo } from 'react';
import {
Box,
Flex,
@@ -160,6 +160,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
color: 'myGray.900'
};
const isSyncMember = feConfigs.register_method?.includes('sync');
return (
<Box>
{/* user info */}
@@ -224,6 +225,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
<Box {...labelStyles}>{t('account_info:member_name')}:&nbsp;</Box>
<Input
flex={'1 0 0'}
disabled={isSyncMember}
defaultValue={userInfo?.team?.memberName || 'Member'}
title={t('account_info:click_modify_nickname')}
borderColor={'transparent'}
@@ -590,11 +592,6 @@ const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
const { feConfigs } = useSystemStore();
const { t } = useTranslation();
const { isPc } = useSystem();
const { userInfo, updateUserInfo } = useUserStore();
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
});
return (
<Box>

View File

@@ -1,13 +1,12 @@
import React from 'react';
import { Box, Button, Flex, useTheme } from '@chakra-ui/react';
import { getInforms, readInform } from '@/web/support/user/inform/api';
import type { UserInformSchema } from '@fastgpt/global/support/user/inform/type';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { usePagination } from '@fastgpt/web/hooks/usePagination';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { useTranslation } from 'next-i18next';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import AccountContainer, { TabEnum } from './components/AccountContainer';
import AccountContainer from './components/AccountContainer';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
const InformTable = () => {
@@ -23,8 +22,7 @@ const InformTable = () => {
Pagination,
getData,
pageNum
} = usePagination<UserInformSchema>({
api: getInforms,
} = usePagination(getInforms, {
pageSize: 20
});

View File

@@ -25,7 +25,7 @@ import { usePagination } from '@fastgpt/web/hooks/usePagination';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import AccountContainer, { TabEnum } from './components/AccountContainer';
import AccountContainer from './components/AccountContainer';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
const Promotion = () => {
@@ -41,8 +41,7 @@ const Promotion = () => {
total,
pageSize,
Pagination
} = usePagination({
api: getPromotionRecords,
} = usePagination(getPromotionRecords, {
pageSize: 20
});

View File

@@ -13,7 +13,6 @@ import {
Tr,
useDisclosure
} from '@chakra-ui/react';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { useTranslation } from 'next-i18next';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
@@ -30,6 +29,8 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { delLeaveTeam } from '@/web/support/user/team/api';
import { postSyncMembers } from '@/web/support/user/api';
import MyLoading from '@fastgpt/web/components/common/MyLoading';
const InviteModal = dynamic(() => import('./InviteModal'));
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
@@ -40,8 +41,16 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const { userInfo, teamPlanStatus } = useUserStore();
const { feConfigs, setNotSufficientModalType } = useSystemStore();
const { groups, refetchGroups, myTeams, refetchTeams, members, refetchMembers, onSwitchTeam } =
useContextSelector(TeamContext, (v) => v);
const {
groups,
refetchGroups,
myTeams,
refetchTeams,
members,
refetchMembers,
onSwitchTeam,
MemberScrollData
} = useContextSelector(TeamContext, (v) => v);
const {
isOpen: isOpenTeamTagsAsync,
@@ -54,6 +63,8 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
type: 'delete'
});
const isSyncMember = feConfigs.register_method?.includes('sync');
const { runAsync: onLeaveTeam } = useRequest2(
async () => {
const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0];
@@ -72,8 +83,17 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
content: t('account_team:confirm_leave_team')
});
const { runAsync: onSyncMember, loading: isSyncing } = useRequest2(postSyncMembers, {
onSuccess() {
refetchMembers();
},
successToast: t('account_team:sync_member_success'),
errorToast: t('account_team:sync_member_failed')
});
return (
<>
{isSyncing && <MyLoading />}
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
{Tabs}
<HStack>
@@ -91,7 +111,21 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
{t('account_team:label_sync')}
</Button>
)}
{userInfo?.team.permission.hasManagePer && (
{userInfo?.team.permission.hasManagePer && isSyncMember && (
<Button
variant={'primary'}
size="md"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name="common/retryLight" w={'16px'} color={'white'} />}
onClick={() => {
onSyncMember();
}}
>
{t('account_team:sync_immediately')}
</Button>
)}
{userInfo?.team.permission.hasManagePer && !isSyncMember && (
<Button
variant={'primary'}
size="md"
@@ -135,76 +169,84 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
<Box flex={'1 0 0'} overflow={'auto'}>
<TableContainer overflow={'unset'} fontSize={'sm'}>
<Table overflow={'unset'}>
<Thead>
<Tr bgColor={'white !important'}>
<Th borderLeftRadius="6px" bgColor="myGray.100">
{t('account_team:user_name')}
</Th>
<Th bgColor="myGray.100">{t('account_team:member_group')}</Th>
<Th borderRightRadius="6px" bgColor="myGray.100">
{t('common:common.Action')}
</Th>
</Tr>
</Thead>
<Tbody>
{members?.map((item) => (
<Tr key={item.userId} overflow={'unset'}>
<Td>
<HStack>
<Avatar src={item.avatar} w={['18px', '22px']} borderRadius={'50%'} />
<Box className={'textEllipsis'}>
{item.memberName}
{item.status === 'waiting' && (
<Tag ml="2" colorSchema="yellow">
{t('account_team:waiting')}
</Tag>
)}
</Box>
</HStack>
</Td>
<Td maxW={'300px'}>
<GroupTags
names={groups
?.filter((group) => group.members.map((m) => m.tmbId).includes(item.tmbId))
.map((g) => g.name)}
max={3}
/>
</Td>
<Td>
{userInfo?.team.permission.hasManagePer &&
item.role !== TeamMemberRoleEnum.owner &&
item.tmbId !== userInfo?.team.tmbId && (
<Icon
name={'common/trash'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'red.600',
bgColor: 'myGray.100'
}}
onClick={() => {
openRemoveMember(
() =>
delRemoveMember(item.tmbId).then(() =>
Promise.all([refetchGroups(), refetchMembers()])
),
undefined,
t('account_team:remove_tip', {
username: item.memberName
})
)();
}}
{MemberScrollData && (
<MemberScrollData>
<Table overflow={'unset'}>
<Thead>
<Tr bgColor={'white !important'}>
<Th borderLeftRadius="6px" bgColor="myGray.100">
{t('account_team:user_name')}
</Th>
<Th bgColor="myGray.100">{t('account_team:member_group')}</Th>
{!isSyncMember && (
<Th borderRightRadius="6px" bgColor="myGray.100">
{t('common:common.Action')}
</Th>
)}
</Tr>
</Thead>
<Tbody>
{members?.map((item) => (
<Tr key={item.userId} overflow={'unset'}>
<Td>
<HStack>
<Avatar src={item.avatar} w={['18px', '22px']} borderRadius={'50%'} />
<Box className={'textEllipsis'}>
{item.memberName}
{item.status === 'waiting' && (
<Tag ml="2" colorSchema="yellow">
{t('account_team:waiting')}
</Tag>
)}
</Box>
</HStack>
</Td>
<Td maxW={'300px'}>
<GroupTags
names={groups
?.filter((group) =>
group.members.map((m) => m.tmbId).includes(item.tmbId)
)
.map((g) => g.name)}
max={3}
/>
</Td>
{!isSyncMember && (
<Td>
userInfo?.team.permission.hasManagePer && item.role !==
TeamMemberRoleEnum.owner && item.tmbId !== userInfo?.team.tmbId && (
<Icon
name={'common/trash'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'red.600',
bgColor: 'myGray.100'
}}
onClick={() => {
openRemoveMember(
() =>
delRemoveMember(item.tmbId).then(() =>
Promise.all([refetchGroups(), refetchMembers()])
),
undefined,
t('account_team:remove_tip', {
username: item.memberName
})
)();
}}
/>
)
</Td>
)}
</Td>
</Tr>
))}
</Tbody>
</Table>
</Tr>
))}
</Tbody>
</Table>
</MemberScrollData>
)}
<ConfirmRemoveMemberModal />
</TableContainer>
</Box>

View File

@@ -1,4 +1,3 @@
import { deleteOrg, deleteOrgMember } from '@/web/support/user/team/org/api';
import { useUserStore } from '@/web/support/user/useUserStore';
import {
Box,
@@ -27,7 +26,7 @@ import { useMemo, useState } from 'react';
import { useContextSelector } from 'use-context-selector';
import MemberTag from '@/components/support/user/team/Info/MemberTag';
import { TeamContext } from '../context';
import { getOrgList } from '@/web/support/user/team/org/api';
import { deleteOrg, deleteOrgMember, getOrgList } from '@/web/support/user/team/org/api';
import IconButton from './IconButton';
import { defaultOrgForm, type OrgFormType } from './OrgInfoModal';
@@ -37,6 +36,7 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
import Path from '@/components/common/folder/Path';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
import { useSystemStore } from '@/web/common/system/useSystemStore';
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal'));
@@ -76,7 +76,9 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
const { userInfo, isTeamAdmin } = useUserStore();
const { members } = useContextSelector(TeamContext, (v) => v);
const { feConfigs } = useSystemStore();
const isSyncMember = feConfigs.register_method?.includes('sync');
const [parentPath, setParentPath] = useState('');
const {
data: orgs = [],
@@ -174,9 +176,11 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
<Th bg="myGray.100" borderLeftRadius="6px">
{t('common:Name')}
</Th>
<Th bg="myGray.100" borderRightRadius="6px">
{t('common:common.Action')}
</Th>
{!isSyncMember && (
<Th bg="myGray.100" borderRightRadius="6px">
{t('common:common.Action')}
</Th>
)}
</Tr>
</Thead>
<Tbody>
@@ -197,8 +201,8 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
/>
</HStack>
</Td>
<Td w={'6rem'}>
{isTeamAdmin && (
{isTeamAdmin && !isSyncMember && (
<Td w={'6rem'}>
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
@@ -225,8 +229,8 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
}
]}
/>
)}
</Td>
</Td>
)}
</Tr>
))}
{currentOrg?.members.map((member) => {
@@ -239,7 +243,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
</Td>
<Td w={'6rem'}>
{isTeamAdmin && (
{isTeamAdmin && !isSyncMember && (
<MyMenu
trigger={'hover'}
Button={<IconButton name="more" />}
@@ -268,57 +272,59 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
</Table>
</TableContainer>
{/* Slider */}
<VStack w={'180px'} alignItems={'start'}>
<HStack gap={'6px'}>
<Avatar src={currentOrg?.avatar} w={'1rem'} h={'1rem'} rounded={'xs'} />
<Box fontWeight={500} color={'myGray.900'}>
{currentOrg?.name}
</Box>
{currentOrg?.path !== '' && (
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
)}
</HStack>
<Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
<Divider my={'20px'} />
<Box fontWeight={500} fontSize="sm" color="myGray.900">
{t('common:common.Action')}
</Box>
{currentOrg && isTeamAdmin && (
<VStack gap="13px" w="100%">
<ActionButton
icon="common/add2"
text={t('account_team:create_sub_org')}
onClick={() => {
setEditOrg({
...defaultOrgForm,
parentId: currentOrg?._id
});
}}
/>
<ActionButton
icon="common/administrator"
text={t('account_team:manage_member')}
onClick={() => setManageMemberOrg(currentOrg)}
/>
{!isSyncMember && (
<VStack w={'180px'} alignItems={'start'}>
<HStack gap={'6px'}>
<Avatar src={currentOrg?.avatar} w={'1rem'} h={'1rem'} rounded={'xs'} />
<Box fontWeight={500} color={'myGray.900'}>
{currentOrg?.name}
</Box>
{currentOrg?.path !== '' && (
<>
<ActionButton
icon="common/file/move"
text={t('account_team:move_org')}
onClick={() => setMovingOrg(currentOrg)}
/>
<ActionButton
icon="delete"
text={t('account_team:delete_org')}
onClick={() => deleteOrgHandler(currentOrg._id)}
/>
</>
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
)}
</VStack>
)}
</VStack>
</HStack>
<Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
<Divider my={'20px'} />
<Box fontWeight={500} fontSize="sm" color="myGray.900">
{t('common:common.Action')}
</Box>
{currentOrg && isTeamAdmin && (
<VStack gap="13px" w="100%">
<ActionButton
icon="common/add2"
text={t('account_team:create_sub_org')}
onClick={() => {
setEditOrg({
...defaultOrgForm,
parentId: currentOrg?._id
});
}}
/>
<ActionButton
icon="common/administrator"
text={t('account_team:manage_member')}
onClick={() => setManageMemberOrg(currentOrg)}
/>
{currentOrg?.path !== '' && (
<>
<ActionButton
icon="common/file/move"
text={t('account_team:move_org')}
onClick={() => setMovingOrg(currentOrg)}
/>
<ActionButton
icon="delete"
text={t('account_team:delete_org')}
onClick={() => deleteOrgHandler(currentOrg._id)}
/>
</>
)}
</VStack>
)}
</VStack>
)}
</Flex>
{!!editOrg && (

View File

@@ -2,7 +2,7 @@ import React, { ReactNode, useState } from 'react';
import { createContext } from 'use-context-selector';
import type { EditTeamFormDataType } from './EditInfoModal';
import dynamic from 'next/dynamic';
import { getTeamList, putSwitchTeam } from '@/web/support/user/team/api';
import { getTeamList, getTeamMembers, putSwitchTeam } from '@/web/support/user/team/api';
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
import { useUserStore } from '@/web/support/user/useUserStore';
import type { TeamTmbItemType, TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
@@ -10,7 +10,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { getGroupList } from '@/web/support/user/team/group/api';
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
const EditInfoModal = dynamic(() => import('./EditInfoModal'));
@@ -26,6 +26,7 @@ type TeamModalContextType = {
refetchTeams: () => void;
refetchGroups: () => void;
teamSize: number;
MemberScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
};
export const TeamContext = createContext<TeamModalContextType>({
@@ -49,13 +50,14 @@ export const TeamContext = createContext<TeamModalContextType>({
throw new Error('Function not implemented.');
},
teamSize: 0
teamSize: 0,
MemberScrollData: undefined
});
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
const { t } = useTranslation();
const [editTeamData, setEditTeamData] = useState<EditTeamFormDataType>();
const { userInfo, initUserInfo, loadAndGetTeamMembers } = useUserStore();
const { userInfo, initUserInfo } = useUserStore();
const {
data: myTeams = [],
@@ -69,18 +71,11 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
// member action
const {
data: members = [],
runAsync: refetchMembers,
loading: loadingMembers
} = useRequest2(
() => {
if (!userInfo?.team?.teamId) return Promise.resolve([]);
return loadAndGetTeamMembers(true);
},
{
manual: false,
refreshDeps: [userInfo?.team?.teamId]
}
);
isLoading: loadingMembers,
refreshList: refetchMembers,
total: memberTotal,
ScrollData: MemberScrollData
} = useScrollPagination(getTeamMembers, {});
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
async (teamId: string) => {
@@ -115,7 +110,8 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
refetchMembers,
groups,
refetchGroups,
teamSize: members.length
teamSize: memberTotal,
MemberScrollData
};
return (

View File

@@ -1,6 +1,6 @@
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import AccountContainer from '../components/AccountContainer';
import { Box, Flex, useDisclosure } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import Icon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import TeamSelector from '../components/TeamSelector';
@@ -13,11 +13,10 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { TeamContext, TeamModalContextProvider } from './components/context';
import dynamic from 'next/dynamic';
import MemberTable from './components/MemberTable';
const MemberTable = dynamic(() => import('./components/MemberTable'));
const PermissionManage = dynamic(() => import('./components/PermissionManage/index'));
const GroupManage = dynamic(() => import('./components/GroupManage/index'));
const OrgManage = dynamic(() => import('./components/OrgManage/index'));
export enum TeamTabEnum {
@@ -34,7 +33,7 @@ const Team = () => {
const { t } = useTranslation();
const { userInfo } = useUserStore();
const { setEditTeamData, teamSize, isLoading } = useContextSelector(TeamContext, (v) => v);
const { setEditTeamData, isLoading, teamSize } = useContextSelector(TeamContext, (v) => v);
const Tabs = useMemo(
() => (

View File

@@ -23,15 +23,16 @@ import DateRangePicker, {
import { addDays } from 'date-fns';
import dynamic from 'next/dynamic';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MySelect from '@fastgpt/web/components/common/MySelect';
import { formatNumber } from '@fastgpt/global/common/math/tools';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import AccountContainer, { TabEnum } from '../components/AccountContainer';
import AccountContainer from '../components/AccountContainer';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import { getTeamMembers } from '@/web/support/user/team/api';
const UsageDetail = dynamic(() => import('./UsageDetail'));
@@ -44,7 +45,7 @@ const UsageTable = () => {
});
const [usageSource, setUsageSource] = useState<UsageSourceEnum | ''>('');
const { isPc } = useSystem();
const { userInfo, loadAndGetTeamMembers } = useUserStore();
const { userInfo } = useUserStore();
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
const sourceList = useMemo(
@@ -63,10 +64,7 @@ const UsageTable = () => {
);
const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId);
const { data: members = [] } = useQuery(['getMembers', userInfo?.team?.teamId], () => {
if (!userInfo?.team?.teamId) return [];
return loadAndGetTeamMembers();
});
const { data: members, ScrollData } = useScrollPagination(getTeamMembers, {});
const tmbList = useMemo(
() =>
members.map((item) => ({
@@ -86,14 +84,13 @@ const UsageTable = () => {
isLoading,
Pagination,
getData
} = usePagination<UsageItemType>({
api: getUserUsages,
} = usePagination(getUserUsages, {
pageSize: isPc ? 20 : 10,
params: {
dateStart: dateRange.from || new Date(),
dateEnd: addDays(dateRange.to || new Date(), 1),
source: usageSource,
teamMemberId: selectTmbId
source: usageSource as UsageSourceEnum,
teamMemberId: selectTmbId ?? ''
},
defaultRequest: false
});
@@ -120,6 +117,7 @@ const UsageTable = () => {
<MySelect
size={'sm'}
minW={'100px'}
ScrollData={ScrollData}
list={tmbList}
value={selectTmbId}
onchange={setSelectTmbId}

View File

@@ -5,6 +5,8 @@ import { jiebaSplit } from '@fastgpt/service/common/string/jieba';
import { MongoDatasetDataText } from '@fastgpt/service/core/dataset/data/dataTextSchema';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { NextApiRequest, NextApiResponse } from 'next';
/*
@@ -14,6 +16,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
2. 执行升级脚本,不要删除 MongoDatasetData 里的数据。
3. 切换正式版镜像,让 MongoDatasetDataText 生效。
4. 删除 MongoDatasetData 里的索引和多余字段。4819 再删
5. 移动 User 表中的 avatar 字段到 TeamMember 表中。
*/
let success = 0;
async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -109,15 +112,26 @@ const initData = async (batchSize: number) => {
}
};
const batchUpdateFields = async (batchSize = 2000) => {
// Update in batches
await MongoDatasetData.updateMany(
{ initFullText: { $exists: true } },
{
$unset: {
initFullText: 1,
fullTextToken: 1
}
}
);
};
// const batchUpdateFields = async (batchSize = 2000) => {
// // Find documents that still have these fields
// const documents = await MongoDatasetData.find({ initFullText: { $exists: true } }, '_id')
// .limit(batchSize)
// .lean();
// if (documents.length === 0) return;
// // Update in batches
// await MongoDatasetData.updateMany(
// { _id: { $in: documents.map((doc) => doc._id) } },
// {
// $unset: {
// initFullText: 1
// // fullTextToken: 1
// }
// }
// );
// success += documents.length;
// console.log('Delete success:', success);
// await batchUpdateFields(batchSize);
// };

View File

@@ -0,0 +1,37 @@
import { NextAPI } from '@/service/middleware/entry';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { NextApiRequest, NextApiResponse } from 'next';
/*
简单版迁移:直接升级到最新镜像,会去除 MongoDatasetData 里的索引。直接执行这个脚本。
无缝迁移:
1. 移动 User 表中的 avatar 字段到 TeamMember 表中。
*/
async function handler(req: NextApiRequest, res: NextApiResponse) {
await authCert({ req, authRoot: true });
await moveUserAvatar();
return { success: true };
}
export default NextAPI(handler);
const moveUserAvatar = async () => {
try {
const users = await MongoUser.find({});
for await (const user of users) {
await MongoTeamMember.updateOne(
{
_id: user._id
},
{
avatar: (user as any).avatar // 删除 avatar 字段, 因为 Type 改了,所以这里不能直接写 user.avatar
}
);
}
console.log('Move avatar success:', users.length);
} catch (error) {
console.error(error);
}
};

View File

@@ -1,6 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import type { PagingData } from '@/types';
import { AppLogsListItemType } from '@/types/app';
import { Types } from '@fastgpt/service/common/mongo';
import { addDays } from 'date-fns';
@@ -10,19 +9,22 @@ import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchem
import { NextAPI } from '@/service/middleware/entry';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
import { TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant';
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
async function handler(
req: NextApiRequest,
_res: NextApiResponse
): Promise<PagingData<AppLogsListItemType>> {
): Promise<PaginationResponse<AppLogsListItemType>> {
const {
pageNum = 1,
pageSize = 20,
appId,
dateStart = addDays(new Date(), -7),
dateEnd = new Date()
} = req.body as GetAppChatLogsParams;
const { pageSize = 20, offset } = parsePaginationRequest(req);
if (!appId) {
throw new Error('缺少参数');
}
@@ -39,7 +41,7 @@ async function handler(
}
};
const [data, total] = await Promise.all([
const [list, total] = await Promise.all([
MongoChat.aggregate(
[
{ $match: where },
@@ -51,7 +53,7 @@ async function handler(
updateTime: -1
}
},
{ $skip: (pageNum - 1) * pageSize },
{ $skip: offset },
{ $limit: pageSize },
{
$lookup: {
@@ -80,6 +82,14 @@ async function handler(
as: 'chatitems'
}
},
{
$lookup: {
from: TeamMemberCollectionName,
localField: 'tmbId',
foreignField: '_id',
as: 'member'
}
},
{
$addFields: {
userGoodFeedbackCount: {
@@ -133,7 +143,12 @@ async function handler(
customFeedbacksCount: 1,
markCount: 1,
outLinkUid: 1,
tmbId: 1
tmbId: 1,
sourceMember: {
name: '$member.name',
avatar: '$member.avatar',
status: '$member.status'
}
}
}
],
@@ -145,9 +160,7 @@ async function handler(
]);
return {
pageNum,
pageSize,
data,
list,
total
};
}

View File

@@ -18,6 +18,7 @@ import { replaceRegChars } from '@fastgpt/global/common/string/tools';
import { concatPer } from '@fastgpt/service/support/permission/controller';
import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGroup/controllers';
import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
export type ListAppBody = {
parentId?: ParentIdType;
@@ -201,19 +202,33 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
})
.filter((app) => app.permission.hasReadPer);
return formatApps.map((app) => ({
_id: app._id,
tmbId: app.tmbId,
avatar: app.avatar,
type: app.type,
name: app.name,
intro: app.intro,
updateTime: app.updateTime,
permission: app.permission,
pluginData: app.pluginData,
inheritPermission: app.inheritPermission ?? true,
private: app.privateApp
}));
// get member info
const memberInfo = await MongoTeamMember.find(
{ _id: { $in: formatApps.map((app) => app.tmbId) } },
'_id name avatar status'
).lean();
return formatApps.map((app) => {
const member = memberInfo.find((item) => String(item._id) === String(app.tmbId))!;
return {
_id: app._id,
tmbId: app.tmbId,
avatar: app.avatar,
type: app.type,
name: app.name,
intro: app.intro,
updateTime: app.updateTime,
permission: app.permission,
pluginData: app.pluginData,
inheritPermission: app.inheritPermission ?? true,
private: app.privateApp,
sourceMember: {
name: member.name,
avatar: member.avatar,
status: member.status
}
};
});
}
export default NextAPI(handler);

View File

@@ -6,6 +6,8 @@ import { ApiRequestProps } from '@fastgpt/service/type/next';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { VersionListItemType } from '@fastgpt/global/core/app/version';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
export type versionListBody = PaginationProps<{
appId: string;
@@ -15,24 +17,44 @@ export type versionListResponse = PaginationResponse<VersionListItemType>;
async function handler(
req: ApiRequestProps<versionListBody>,
res: NextApiResponse<any>
_res: NextApiResponse<any>
): Promise<versionListResponse> {
const { offset, pageSize, appId } = req.body;
const { appId } = req.body;
const { offset, pageSize } = parsePaginationRequest(req);
await authApp({ appId, req, per: WritePermissionVal, authToken: true });
const [result, total] = await Promise.all([
MongoAppVersion.find(
{
(async () => {
const versions = await MongoAppVersion.find({
appId
},
'_id appId versionName time isPublish tmbId'
)
.sort({
time: -1
})
.skip(offset)
.limit(pageSize),
.sort({
time: -1
})
.skip(offset)
.limit(pageSize)
.lean();
const memberList = await MongoTeamMember.find(
{
_id: { $in: versions.map((item) => item.tmbId) }
},
'_id name avatar status'
).lean();
return versions.map((item) => {
const member = memberList.find((member) => String(member._id) === String(item.tmbId));
return {
...item,
sourceMember: {
name: member?.name || '',
avatar: member?.avatar || '',
status: member?.status || ''
}
};
});
})(),
MongoAppVersion.countDocuments({ appId })
]);
@@ -43,7 +65,8 @@ async function handler(
versionName: item.versionName,
time: item.time,
isPublish: item.isPublish,
tmbId: item.tmbId
tmbId: item.tmbId,
sourceMember: item.sourceMember
};
});

View File

@@ -12,7 +12,6 @@ describe('发布应用版本测试', () => {
nodes: [],
edges: [],
chatConfig: {},
type: AppTypeEnum.simple,
isPublish: false,
versionName: '1'
};

View File

@@ -7,6 +7,7 @@ import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { GetHistoriesProps } from '@/global/core/chat/api';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
import { addMonths } from 'date-fns';
export type getHistoriesQuery = {};
@@ -17,9 +18,10 @@ export type getHistoriesResponse = {};
async function handler(
req: ApiRequestProps<getHistoriesBody, getHistoriesQuery>,
res: ApiResponseType<any>
_res: ApiResponseType<any>
): Promise<PaginationResponse<getHistoriesResponse>> {
const { appId, shareId, outLinkUid, teamId, teamToken, offset, pageSize, source } = req.body;
const { appId, shareId, outLinkUid, teamId, teamToken, source } = req.body;
const { offset, pageSize } = parsePaginationRequest(req);
const match = await (async () => {
if (shareId && outLinkUid) {

View File

@@ -13,6 +13,7 @@ import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
import { GetChatTypeEnum } from '@/global/core/chat/constants';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
export type getPaginationRecordsQuery = {};
@@ -22,16 +23,11 @@ export type getPaginationRecordsResponse = PaginationResponse<ChatItemType>;
async function handler(
req: ApiRequestProps<getPaginationRecordsBody, getPaginationRecordsQuery>,
res: ApiResponseType<any>
_res: ApiResponseType<any>
): Promise<getPaginationRecordsResponse> {
const {
appId,
chatId,
offset,
pageSize = 10,
loadCustomFeedbacks,
type = GetChatTypeEnum.normal
} = req.body;
const { appId, chatId, loadCustomFeedbacks, type = GetChatTypeEnum.normal } = req.body;
const { offset, pageSize } = parsePaginationRequest(req);
if (!appId || !chatId) {
return {

View File

@@ -6,6 +6,7 @@ import { ApiRequestProps } from '@fastgpt/service/type/next';
import { ChatInputGuideSchemaType } from '@fastgpt/global/core/chat/inputGuide/type';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
export type ChatInputGuideProps = PaginationProps<{
appId: string;
@@ -17,7 +18,8 @@ async function handler(
req: ApiRequestProps<ChatInputGuideProps>,
res: NextApiResponse<any>
): Promise<ChatInputGuideResponse> {
const { appId, pageSize, offset, searchKey } = req.body;
const { appId, searchKey } = req.body;
const { offset, pageSize } = parsePaginationRequest(req);
await authApp({ req, appId, authToken: true, per: ReadPermissionVal });

View File

@@ -10,14 +10,15 @@ import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/sc
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
import { NextAPI } from '@/service/middleware/entry';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { PagingData } from '@/types';
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
import { collectionTagsToTagLabel } from '@fastgpt/service/core/dataset/collection/utils';
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectionsListItemType>> {
async function handler(
req: NextApiRequest
): Promise<PaginationResponse<DatasetCollectionsListItemType>> {
let {
pageNum = 1,
pageSize = 10,
datasetId,
parentId = null,
searchText = '',
@@ -25,8 +26,9 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
filterTags = [],
simple = false
} = req.body as GetDatasetCollectionsProps;
searchText = searchText?.replace(/'/g, '');
let { pageSize, offset } = parsePaginationRequest(req);
pageSize = Math.min(pageSize, 30);
searchText = searchText?.replace(/'/g, '');
// auth dataset and get my role
const { teamId, permission } = await authDataset({
@@ -78,9 +80,7 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
.lean();
return {
pageNum,
pageSize,
data: await Promise.all(
list: await Promise.all(
collections.map(async (item) => ({
...item,
tags: await collectionTagsToTagLabel({
@@ -105,7 +105,7 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
$sort: { updateTime: -1 }
},
{
$skip: (pageNum - 1) * pageSize
$skip: offset
},
{
$limit: pageSize
@@ -167,7 +167,7 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
})
]);
const data = await Promise.all(
const list = await Promise.all(
collections.map(async (item) => ({
...item,
tags: await collectionTagsToTagLabel({
@@ -178,15 +178,13 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
}))
);
if (data.find((item) => item.trainingAmount > 0)) {
if (list.find((item) => item.trainingAmount > 0)) {
startTrainingQueue();
}
// count collections
return {
pageNum,
pageSize,
data,
list,
total
};
}

View File

@@ -10,6 +10,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
export type GetScrollCollectionsProps = PaginationProps<{
datasetId: string;
@@ -25,8 +26,6 @@ async function handler(
): Promise<PaginationResponse<DatasetCollectionsListItemType>> {
let {
datasetId,
pageSize = 10,
offset,
parentId = null,
searchText = '',
selectFolder = false,
@@ -36,6 +35,7 @@ async function handler(
if (!datasetId) {
return Promise.reject(CommonErrEnum.missingParams);
}
let { offset, pageSize } = parsePaginationRequest(req);
searchText = searchText?.replace(/'/g, '');
pageSize = Math.min(pageSize, 30);

View File

@@ -3,19 +3,21 @@ import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
import { NextAPI } from '@/service/middleware/entry';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { PagingData, RequestPaging } from '@/types';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { DatasetDataListItemType } from '@/global/core/dataset/type';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
export type GetDatasetDataListProps = RequestPaging & {
export type GetDatasetDataListProps = {
searchText?: string;
collectionId: string;
};
async function handler(
req: ApiRequestProps<GetDatasetDataListProps>
): Promise<PagingData<DatasetDataListItemType>> {
let { pageNum = 1, pageSize = 10, searchText = '', collectionId } = req.body;
): Promise<PaginationResponse<DatasetDataListItemType>> {
let { searchText = '', collectionId } = req.body;
let { offset, pageSize } = parsePaginationRequest(req);
pageSize = Math.min(pageSize, 30);
@@ -40,19 +42,17 @@ async function handler(
: {})
};
const [data, total] = await Promise.all([
const [list, total] = await Promise.all([
MongoDatasetData.find(match, '_id datasetId collectionId q a chunkIndex')
.sort({ chunkIndex: 1, updateTime: -1 })
.skip((pageNum - 1) * pageSize)
.skip(offset)
.limit(pageSize)
.lean(),
MongoDatasetData.countDocuments(match)
]);
return {
pageNum,
pageSize,
data,
list,
total
};
}

View File

@@ -6,6 +6,7 @@ import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { DatasetDataListItemType } from '@/global/core/dataset/type';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
export type GetDatasetDataListProps = PaginationProps & {
searchText?: string;
@@ -16,7 +17,8 @@ export type GetDatasetDataListRes = PaginationResponse<DatasetDataListItemType>;
async function handler(
req: ApiRequestProps<GetDatasetDataListProps>
): Promise<GetDatasetDataListRes> {
let { offset, pageSize = 10, searchText = '', collectionId } = req.body;
let { searchText = '', collectionId } = req.body;
let { offset, pageSize } = parsePaginationRequest(req);
pageSize = Math.min(pageSize, 30);

View File

@@ -2,7 +2,6 @@ import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { getVectorModel } from '@fastgpt/service/core/ai/model';
import { NextAPI } from '@/service/middleware/entry';
import { DatasetPermission } from '@fastgpt/global/support/permission/dataset/controller';
import {
@@ -19,6 +18,7 @@ import { replaceRegChars } from '@fastgpt/global/common/string/tools';
import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGroup/controllers';
import { concatPer } from '@fastgpt/service/support/permission/controller';
import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
export type GetDatasetListBody = {
parentId: ParentIdType;
@@ -174,19 +174,20 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
})
.filter((app) => app.permission.hasReadPer);
const data = formatDatasets.map<DatasetListItemType>((item) => ({
_id: item._id,
avatar: item.avatar,
name: item.name,
intro: item.intro,
type: item.type,
permission: item.permission,
vectorModel: getVectorModel(item.vectorModel),
inheritPermission: item.inheritPermission,
tmbId: item.tmbId,
updateTime: item.updateTime,
private: item.privateDataset
}));
const tmbIds = formatDatasets.map((item) => item.tmbId);
const memberInfo = await MongoTeamMember.find({ _id: { $in: tmbIds } }, '_id name avatar').lean();
const data = formatDatasets.map((item) => {
const member = memberInfo.find((member) => String(member._id) === String(item.tmbId));
return {
...item,
sourceMember: {
name: member!.name,
avatar: member!.avatar
}
};
});
return data;
}

View File

@@ -1,17 +1,18 @@
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { UserUpdateParams } from '@/types/user';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
/* update user info */
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { getUserDetail } from '@fastgpt/service/support/user/controller';
import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
export type UserAccountUpdateQuery = {};
export type UserAccountUpdateBody = UserUpdateParams;
export type UserAccountUpdateResponse = {};
async function handler(
req: ApiRequestProps<UserAccountUpdateBody, UserAccountUpdateQuery>,
_res: ApiResponseType<any>
@@ -19,21 +20,33 @@ async function handler(
const { avatar, timezone } = req.body;
const { tmbId } = await authCert({ req, authToken: true });
const user = await getUserDetail({ tmbId });
// const user = await getUserDetail({ tmbId });
// 更新对应的记录
await mongoSessionRun(async (session) => {
await MongoUser.updateOne(
{
_id: user._id
},
{
...(avatar && { avatar }),
...(timezone && { timezone })
}
).session(session);
await refreshSourceAvatar(avatar, user.avatar, session);
const tmb = await MongoTeamMember.findById(tmbId).session(session);
if (timezone) {
await MongoUser.updateOne(
{
_id: tmb?.userId
},
{
timezone
}
).session(session);
}
// if avatar, update team member avatar
if (avatar) {
await MongoTeamMember.updateOne(
{
_id: tmbId
},
{
avatar
}
).session(session);
await refreshSourceAvatar(avatar, tmb?.avatar, session);
}
});
return {};

View File

@@ -13,7 +13,7 @@ import {
ModalBody,
HStack
} from '@chakra-ui/react';
import Avatar from '@fastgpt/web/components/common/Avatar';
import UserBox from '@fastgpt/web/components/common/UserBox';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import { getAppChatLogs } from '@/web/core/app/api';
@@ -30,8 +30,6 @@ import { cardStyles } from '../constants';
import dynamic from 'next/dynamic';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useMount } from 'ahooks';
const DetailLogsModal = dynamic(() => import('./DetailLogsModal'));
@@ -40,17 +38,11 @@ const Logs = () => {
const { isPc } = useSystem();
const appId = useContextSelector(AppContext, (v) => v.appId);
const { teamMembers, loadAndGetTeamMembers } = useUserStore();
useMount(() => {
loadAndGetTeamMembers();
});
const [dateRange, setDateRange] = useState<DateRangeType>({
from: addDays(new Date(), -7),
to: new Date()
});
const {
isOpen: isOpenMarkDesc,
onOpen: onOpenMarkDesc,
@@ -63,8 +55,7 @@ const Logs = () => {
Pagination,
getData,
pageNum
} = usePagination({
api: getAppChatLogs,
} = usePagination(getAppChatLogs, {
pageSize: 20,
params: {
appId,
@@ -139,15 +130,7 @@ const Logs = () => {
{!!item.outLinkUid ? (
item.outLinkUid
) : (
<HStack>
<Avatar
src={teamMembers?.find((v) => v.tmbId === item.tmbId)?.avatar}
w="1.25rem"
/>
<Box fontSize={'sm'} ml={1}>
{teamMembers?.find((v) => v.tmbId === item.tmbId)?.memberName}
</Box>
</HStack>
<UserBox sourceMember={item.sourceMember} />
)}
</Box>
</Td>

View File

@@ -18,12 +18,11 @@ import Tag from '@fastgpt/web/components/common/Tag';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyPopover from '@fastgpt/web/components/common/MyPopover';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import type { AppVersionSchemaType, VersionListItemType } from '@fastgpt/global/core/app/version';
import type { SimpleAppSnapshotType } from './SimpleApp/useSnapshots';
import UserBox from '@fastgpt/web/components/common/UserBox';
const PublishHistoriesSlider = <T extends SimpleAppSnapshotType | WorkflowSnapshotsType>({
onClose,
@@ -183,10 +182,8 @@ const TeamCloud = ({
}) => {
const { t } = useTranslation();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const { loadAndGetTeamMembers } = useUserStore();
const { feConfigs } = useSystemStore();
const { scrollDataList, ScrollList, isLoading, fetchData, setData } = useVirtualScrollPagination(
const { scrollDataList, ScrollList, isLoading, setData } = useVirtualScrollPagination(
getWorkflowVersionList,
{
itemHeight: 40,
@@ -198,9 +195,6 @@ const TeamCloud = ({
}
}
);
const { data: members = [] } = useRequest2(loadAndGetTeamMembers, {
manual: !feConfigs.isPlus
});
const [editIndex, setEditIndex] = useState<number | undefined>(undefined);
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(undefined);
@@ -241,7 +235,6 @@ const TeamCloud = ({
{scrollDataList.map((data, index) => {
const item = data.data;
const firstPublishedIndex = scrollDataList.findIndex((data) => data.data.isPublish);
const tmb = members.find((member) => member.tmbId === item.tmbId);
return (
<Flex
@@ -266,22 +259,20 @@ const TeamCloud = ({
h={'72px'}
Trigger={
<Box>
<Avatar src={tmb?.avatar} borderRadius={'50%'} w={'24px'} h={'24px'} />
<Avatar
src={data.data.sourceMember.avatar}
borderRadius={'50%'}
w={'24px'}
h={'24px'}
/>
</Box>
}
>
{({ onClose }) => (
{() => (
<Flex alignItems={'center'} h={'full'} pl={5} gap={3}>
<Box>
<Avatar src={tmb?.avatar} borderRadius={'50%'} w={'36px'} h={'36px'} />
</Box>
<Box>
<Box fontSize={'14px'} color={'myGray.900'}>
{tmb?.memberName}
</Box>
<Box fontSize={'12px'} color={'myGray.500'}>
{formatTime2YMDHMS(item.time)}
</Box>
<UserBox sourceMember={data.data.sourceMember} avatarSize="36px" fontSize="sm" />
<Box fontSize={'12px'} color={'myGray.500'}>
{formatTime2YMDHMS(item.time)}
</Box>
</Flex>
)}

View File

@@ -50,7 +50,6 @@ import { useWorkflowUtils } from './hooks/useUtils';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import { cloneDeep } from 'lodash';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useUserStore } from '@/web/support/user/useUserStore';
import { LoopStartNode } from '@fastgpt/global/core/workflow/template/system/loop/loopStart';
import { LoopEndNode } from '@fastgpt/global/core/workflow/template/system/loop/loopEnd';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
@@ -89,8 +88,6 @@ enum TemplateTypeEnum {
const sliderWidth = 460;
const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
const { loadAndGetTeamMembers } = useUserStore();
const [parentId, setParentId] = useState<ParentIdType>('');
const [searchKey, setSearchKey] = useState('');
const { feConfigs } = useSystemStore();
@@ -99,10 +96,6 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const appId = useContextSelector(WorkflowContext, (v) => v.appId);
const { data: members = [] } = useRequest2(loadAndGetTeamMembers, {
manual: !feConfigs.isPlus
});
const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic);
const { data: basicNodes } = useRequest2(
@@ -162,19 +155,10 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
searchVal?: string;
}) => {
if (type === TemplateTypeEnum.teamPlugin) {
const teamApps = await getTeamPlugTemplates({
return getTeamPlugTemplates({
parentId,
searchKey: searchVal
}).then((res) => res.filter((app) => app.id !== appId));
return teamApps.map<NodeTemplateListItemType>((app) => {
const member = members.find((member) => member.tmbId === app.tmbId);
return {
...app,
author: member?.memberName,
authorAvatar: member?.avatar
};
});
}
if (type === TemplateTypeEnum.systemPlugin) {
return getSystemPlugTemplates({
@@ -188,7 +172,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
setParentId(parentId);
setTemplateType(type);
},
refreshDeps: [members, searchKey, templateType]
refreshDeps: [searchKey, templateType]
}
);
@@ -420,7 +404,6 @@ const RenderList = React.memo(function RenderList({
templates,
type,
onClose,
parentId,
setParentId
}: RenderListProps) {
const { t } = useTranslation();

View File

@@ -34,8 +34,8 @@ import { postCopyApp } from '@/web/core/app/api/app';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { useUserStore } from '@/web/support/user/useUserStore';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
import UserBox from '@fastgpt/web/components/common/UserBox';
const HttpEditModal = dynamic(() => import('./HttpPluginEditModal'));
const ListItem = () => {
@@ -44,8 +44,6 @@ const ListItem = () => {
const { parentId = null } = router.query;
const { isPc } = useSystem();
const { loadAndGetTeamMembers } = useUserStore();
const { openConfirm: openMoveConfirm, ConfirmModal: MoveConfirmModal } = useConfirm({
type: 'common',
title: t('common:move.confirm'),
@@ -115,10 +113,6 @@ const ListItem = () => {
successToast: t('app:create_copy_success')
});
const { data: members = [] } = useRequest2(loadAndGetTeamMembers, {
manual: false
});
const { runAsync: onResumeInheritPermission } = useRequest2(
() => {
return resumeInheritPer(editPerApp!._id);
@@ -145,7 +139,6 @@ const ListItem = () => {
alignItems={'stretch'}
>
{myApps.map((app, index) => {
const owner = members.find((v) => v.tmbId === app.tmbId);
return (
<MyTooltip
key={app._id}
@@ -229,15 +222,7 @@ const ListItem = () => {
color={'myGray.500'}
>
<HStack spacing={3.5}>
{owner && (
<HStack spacing={1}>
<Avatar src={owner.avatar} w={'0.875rem'} borderRadius={'50%'} />
<Box maxW={'150px'} className="textEllipsis">
{owner.memberName}
</Box>
</HStack>
)}
<UserBox sourceMember={app.sourceMember} fontSize="xs" avatarSize="1.25rem" />
<PermissionIconText
private={app.private}
color={'myGray.500'}

View File

@@ -111,8 +111,7 @@ const CollectionPageContextProvider = ({ children }: { children: ReactNode }) =>
isLoading: isGetting,
pageNum,
pageSize
} = usePagination<DatasetCollectionsListItemType>({
api: getDatasetCollections,
} = usePagination(getDatasetCollections, {
pageSize: 20,
params: {
datasetId,

View File

@@ -28,10 +28,10 @@ import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useFolderDrag } from '@/components/common/folder/useFolderDrag';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useTranslation } from 'next-i18next';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import SideTag from './SideTag';
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
import UserBox from '@fastgpt/web/components/common/UserBox';
const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal'));
@@ -39,7 +39,6 @@ function List() {
const { setLoading } = useSystemStore();
const { isPc } = useSystem();
const { t } = useTranslation();
const { loadAndGetTeamMembers } = useUserStore();
const {
loadMyDatasets,
setMoveDatasetId,
@@ -81,10 +80,6 @@ function List() {
}
});
const { data: members = [] } = useRequest2(loadAndGetTeamMembers, {
manual: false
});
const editPerDataset = useMemo(
() => (editPerDatasetIndex !== undefined ? myDatasets[editPerDatasetIndex] : undefined),
[editPerDatasetIndex, myDatasets]
@@ -156,7 +151,6 @@ function List() {
alignItems={'stretch'}
>
{formatDatasets.map((dataset, index) => {
const owner = members.find((v) => v.tmbId === dataset.tmbId);
const vectorModelAvatar = getModelProvider(dataset.vectorModel.provider)?.avatar;
return (
@@ -265,14 +259,11 @@ function List() {
color={'myGray.500'}
>
<HStack spacing={3.5}>
{owner && (
<HStack spacing={1}>
<Avatar src={owner.avatar} w={'0.875rem'} borderRadius={'50%'} />
<Box maxW={'150px'} className="textEllipsis" fontSize={'mini'}>
{owner.memberName}
</Box>
</HStack>
)}
<UserBox
sourceMember={dataset.sourceMember}
fontSize="xs"
avatarSize="1.25rem"
/>
<PermissionIconText
flexShrink={0}
private={dataset.private}

View File

@@ -13,6 +13,8 @@ import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/no
import type { ChatSchema } from '@fastgpt/global/core/chat/type';
import type { AppSchema } from '@fastgpt/global/core/app/type';
import { ChatModelType } from '@/constants/model';
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
import { SourceMember } from '@fastgpt/global/support/user/type';
export interface ShareAppItem {
_id: string;
@@ -45,4 +47,5 @@ export type AppLogsListItemType = {
markCount: number;
outLinkUid?: string;
tmbId: string;
sourceMember: SourceMember;
};

View File

@@ -10,15 +10,6 @@ import {
import { TrackEventName } from '@/web/common/system/constants';
import { SubPlanType } from '@fastgpt/global/support/wallet/sub/type';
export type PagingData<T> = {
pageNum: number;
pageSize: number;
data: T[];
total?: number;
};
export type RequestPaging = { pageNum: number; pageSize: number; [key]: any };
declare global {
var qaQueueLen: number;
var vectorQueueLen: number;

View File

@@ -5,7 +5,7 @@ import { AppUpdateParams, AppChangeOwnerBody } from '@/global/core/app/api';
import type { CreateAppBody } from '@/pages/api/core/app/create';
import type { ListAppBody } from '@/pages/api/core/app/list';
import { AppLogsListItemType } from '@/types/app';
import { PagingData } from '@/types';
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
/**
* 获取应用列表
@@ -39,7 +39,7 @@ export const putAppById = (id: string, data: AppUpdateParams) =>
// =================== chat logs
export const getAppChatLogs = (data: GetAppChatLogsParams) =>
POST<PagingData<AppLogsListItemType>>(`/core/app/getChatLogs`, data);
POST<PaginationResponse<AppLogsListItemType>>(`/core/app/getChatLogs`, data);
export const resumeInheritPer = (appId: string) =>
GET(`/core/app/resumeInheritPermission`, { appId });

View File

@@ -35,7 +35,8 @@ export const getTeamPlugTemplates = (data?: ListAppBody) =>
intro: app.intro,
showStatus: false,
version: app.pluginData?.nodeVersion || defaultNodeVersion,
isTool: true
isTool: true,
sourceMember: app.sourceMember
}))
);

View File

@@ -1,5 +1,5 @@
import { PostPublishAppProps } from '@/global/core/app/api';
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import { GET, POST } from '@/web/common/api/request';
import type { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import { PaginationProps } from '@fastgpt/web/common/fetch/type';
import type {

View File

@@ -2,7 +2,7 @@ import { getPaginationRecordsBody } from '@/pages/api/core/chat/getPaginationRec
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
import React, { ReactNode, useMemo, useState } from 'react';
import { createContext, useContextSelector } from 'use-context-selector';
import { ChatItemContext } from './chatItemContext';
import { getChatRecords } from '../api';
@@ -68,7 +68,7 @@ const ChatRecordContextProvider = ({
const res = await getChatRecords(data);
// First load scroll to bottom
if (data.offset === 0) {
if (Number(data.offset) === 0) {
function scrollToBottom() {
requestAnimationFrame(
ChatBoxRef?.current ? () => ChatBoxRef?.current?.scrollToBottom?.() : scrollToBottom

View File

@@ -37,7 +37,6 @@ import type { DatasetCollectionItemType } from '@fastgpt/global/core/dataset/typ
import { DatasetCollectionSyncResultEnum } from '@fastgpt/global/core/dataset/constants';
import type { DatasetDataItemType } from '@fastgpt/global/core/dataset/type';
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
import { PagingData } from '@/types';
import type { getDatasetTrainingQueueResponse } from '@/pages/api/core/dataset/training/getDatasetTrainingQueue';
import type { rebuildEmbeddingBody } from '@/pages/api/core/dataset/training/rebuildEmbedding';
import type {
@@ -66,8 +65,6 @@ import type {
listExistIdQuery,
listExistIdResponse
} from '@/pages/api/core/dataset/apiDataset/listExistId';
import { FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
/* ======================== dataset ======================= */
export const getDatasets = (data: GetDatasetListBody) =>
@@ -110,7 +107,7 @@ export const postSearchText = (data: SearchTestProps) =>
/* ============================= collections ==================================== */
export const getDatasetCollections = (data: GetDatasetCollectionsProps) =>
POST<PagingData<DatasetCollectionsListItemType>>(`/core/dataset/collection/list`, data);
POST<PaginationResponse<DatasetCollectionsListItemType>>(`/core/dataset/collection/list`, data);
export const getDatasetCollectionPathById = (parentId: string) =>
GET<ParentTreePathItemType[]>(`/core/dataset/collection/paths`, { parentId });
export const getDatasetCollectionById = (id: string) =>

View File

@@ -1,7 +1,7 @@
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyModal from '@fastgpt/web/components/common/MyModal';
import ParentPaths from '@/components/common/ParentPaths';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getDatasetCollectionPathById, getDatasetCollections } from '@/web/core/dataset/api';
import { Box, Flex, ModalFooter, Button, useTheme, Grid, Card, ModalBody } from '@chakra-ui/react';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
@@ -48,20 +48,24 @@ const SelectCollections = ({
useQuery(['loadDatasetDetail', datasetId], () => loadDatasetDetail(datasetId));
const { data, isLoading } = useQuery(['getDatasetCollections', parentId], () =>
getDatasetCollections({
datasetId,
parentId,
selectFolder: type === 'folder',
simple: true,
pageNum: 1,
pageSize: 50
})
const { data, loading: isLoading } = useRequest2(
() =>
getDatasetCollections({
datasetId,
parentId,
selectFolder: type === 'folder',
simple: true,
pageNum: 1,
pageSize: 50
}),
{
manual: false,
refreshDeps: [datasetId, parentId, type]
}
);
const formatCollections = useMemo(
() =>
data?.data.map((collection) => {
data?.list.map((collection) => {
const icon = getCollectionIcon(collection.type, collection.name);
return {
@@ -111,7 +115,7 @@ const SelectCollections = ({
title={
<Box>
<ParentPaths
paths={paths.map((path, i) => ({
paths={paths.map((path) => ({
parentId: path.parentId,
parentName: path.parentName
}))}

View File

@@ -1,6 +1,6 @@
import { GET, POST, PUT } from '@/web/common/api/request';
import { GET, POST } from '@/web/common/api/request';
import type { PromotionRecordType } from '@/global/support/api/userRes.d';
import { PagingData, type RequestPaging } from '@/types';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
/* get promotion init data */
export const getPromotionInitData = () =>
@@ -10,5 +10,8 @@ export const getPromotionInitData = () =>
}>('/proApi/support/activity/promotion/getPromotionData');
/* promotion records */
export const getPromotionRecords = (data: RequestPaging) =>
POST<PagingData<PromotionRecordType>>(`/proApi/support/activity/promotion/getPromotions`, data);
export const getPromotionRecords = (data: PaginationProps) =>
POST<PaginationResponse<PromotionRecordType>>(
`/proApi/support/activity/promotion/getPromotions`,
data
);

View File

@@ -90,3 +90,5 @@ export const getCaptchaPic = (username: string) =>
GET<{
captchaImage: string;
}>('/proApi/support/user/account/captcha/getImgCaptcha', { username });
export const postSyncMembers = () => POST('/proApi/support/user/team/org/sync');

View File

@@ -1,10 +1,10 @@
import { GET, POST, PUT } from '@/web/common/api/request';
import type { PagingData, RequestPaging } from '@/types';
import { GET, POST } from '@/web/common/api/request';
import type { UserInformSchema } from '@fastgpt/global/support/user/inform/type';
import { SystemMsgModalValueType } from '@fastgpt/service/support/user/inform/type';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
export const getInforms = (data: RequestPaging) =>
POST<PagingData<UserInformSchema>>(`/proApi/support/user/inform/list`, data);
export const getInforms = (data: PaginationProps) =>
POST<PaginationResponse<UserInformSchema>>(`/proApi/support/user/inform/list`, data);
export const getUnreadCount = () =>
GET<{

View File

@@ -19,6 +19,7 @@ import {
} from '@fastgpt/global/support/user/team/type.d';
import { FeTeamPlanStatusType, TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
import { TeamInvoiceHeaderType } from '@fastgpt/global/support/user/team/type';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
/* --------------- team ---------------- */
export const getTeamList = (status: `${TeamMemberSchema['status']}`) =>
@@ -30,8 +31,8 @@ export const putSwitchTeam = (teamId: string) =>
PUT<string>(`/proApi/support/user/team/switch`, { teamId });
/* --------------- team member ---------------- */
export const getTeamMembers = () =>
GET<TeamMemberItemType[]>(`/proApi/support/user/team/member/list`);
export const getTeamMembers = (props: PaginationProps) =>
GET<PaginationResponse<TeamMemberItemType>>(`/proApi/support/user/team/member/list`, props);
export const postInviteTeamMember = (data: InviteMemberProps) =>
POST<InviteMemberResponse>(`/proApi/support/user/team/member/invite`, data);
export const putUpdateMemberName = (name: string) =>

View File

@@ -30,9 +30,6 @@ type State = {
teamPlanStatus: FeTeamPlanStatusType | null;
initTeamPlanStatus: () => Promise<any>;
teamMembers: TeamMemberItemType[];
loadAndGetTeamMembers: (init?: boolean) => Promise<TeamMemberItemType[]>;
teamMemberGroups: MemberGroupListType;
myGroups: MemberGroupListType;
loadAndGetGroups: (init?: boolean) => Promise<MemberGroupListType>;
@@ -102,7 +99,7 @@ export const useUserStore = create<State>()(
},
// team
teamPlanStatus: null,
initTeamPlanStatus() {
async initTeamPlanStatus() {
return getTeamPlanStatus().then((res) => {
set((state) => {
state.teamPlanStatus = res;
@@ -110,21 +107,6 @@ export const useUserStore = create<State>()(
return res;
});
},
teamMembers: [],
loadAndGetTeamMembers: async (init = false) => {
if (!useSystemStore.getState()?.feConfigs?.isPlus) return [];
const randomRefresh = Math.random() > 0.7;
if (!randomRefresh && !init && get().teamMembers?.length)
return Promise.resolve(get().teamMembers);
const res = await getTeamMembers();
set((state) => {
state.teamMembers = res;
});
return res;
},
teamMemberGroups: [],
teamOrgs: [],
myGroups: [],

View File

@@ -1,14 +1,14 @@
import { PagingData, RequestPaging } from '@/types';
import { GET, POST } from '@/web/common/api/request';
import { CreateBillProps, CreateBillResponse } from '@fastgpt/global/support/wallet/bill/api';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import type { BillSchemaType } from '@fastgpt/global/support/wallet/bill/type.d';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
export const getBills = (
data: RequestPaging & {
data: PaginationProps<{
type?: BillTypeEnum;
}
) => POST<PagingData<BillSchemaType>>(`/proApi/support/wallet/bill/list`, data);
}>
) => POST<PaginationResponse<BillSchemaType>>(`/proApi/support/wallet/bill/list`, data);
export const getWxPayQRCode = (data: CreateBillProps) =>
POST<CreateBillResponse>(`/proApi/support/wallet/bill/create`, data);

View File

@@ -1,8 +1,8 @@
import { PagingData, RequestPaging } from '@/types';
import { GET, POST } from '@/web/common/api/request';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { InvoiceType } from '@fastgpt/global/support/wallet/bill/type';
import { InvoiceSchemaType } from '@fastgpt/global/support/wallet/bill/type';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
export type invoiceBillDataType = {
type: BillTypeEnum;
price: number;
@@ -16,5 +16,5 @@ export const getInvoiceBillsList = () =>
export const submitInvoice = (data: InvoiceType) =>
POST(`/proApi/support/wallet/bill/invoice/submit`, data);
export const getInvoiceRecords = (data: RequestPaging) =>
POST<PagingData<InvoiceSchemaType>>(`/proApi/support/wallet/bill/invoice/records`, data);
export const getInvoiceRecords = (data: PaginationProps) =>
POST<PaginationResponse<InvoiceSchemaType>>(`/proApi/support/wallet/bill/invoice/records`, data);

View File

@@ -1,10 +1,17 @@
import { GET, POST } from '@/web/common/api/request';
import { POST } from '@/web/common/api/request';
import { CreateTrainingUsageProps } from '@fastgpt/global/support/wallet/usage/api.d';
import type { PagingData, RequestPaging } from '@/types';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
export const getUserUsages = (data: RequestPaging) =>
POST<PagingData<UsageItemType>>(`/proApi/support/wallet/usage/getUsage`, data);
export const getUserUsages = (
data: PaginationProps<{
dateStart: Date;
dateEnd: Date;
source?: UsageSourceEnum;
teamMemberId: string;
}>
) => POST<PaginationResponse<UsageItemType>>(`/proApi/support/wallet/usage/getUsage`, data);
export const postCreateTrainingUsage = (data: CreateTrainingUsageProps) =>
POST<string>(`/support/wallet/usage/createTrainingUsage`, data);