4.8.19-feature (#3636)
* 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 * perf: scroll page code * perf: list data * docs:更新用户答疑 (#3576) * docs: add custom uid docs (#3572) * fix: pagination bug (#3577) * 4.8.19 test (#3584) * faet: dataset search filter * fix: scroll page * fix: collection list api old version (#3591) * fix: collection list api format * fix: type error of addSourceMemeber * fix: scroll fetch (#3592) * fix: yuque dataset file folder can enter (#3593) * 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 * fix: api dataset reference tag preview (#3600) * perf: doc * feat: chat page config * fix: http parse (#3634) * update doc * fix: http parse * fix code run node reset template (#3633) Co-authored-by: Archer <545436317@qq.com> * docs:faq (#3627) * docs:faq * docsFix * perf: sleep plugin * fix: selector --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com> Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -29,8 +29,6 @@ export type ChatProviderProps = {
|
||||
outLinkAuthData?: OutLinkChatAuthProps;
|
||||
|
||||
chatType: 'log' | 'chat' | 'share' | 'team';
|
||||
showRawSource: boolean;
|
||||
showNodeStatus: boolean;
|
||||
};
|
||||
|
||||
type useChatStoreType = ChatProviderProps & {
|
||||
@@ -132,8 +130,6 @@ const Provider = ({
|
||||
chatId,
|
||||
outLinkAuthData = {},
|
||||
chatType = 'chat',
|
||||
showRawSource,
|
||||
showNodeStatus,
|
||||
children,
|
||||
...props
|
||||
}: ChatProviderProps & {
|
||||
@@ -249,9 +245,7 @@ const Provider = ({
|
||||
chatId,
|
||||
outLinkAuthData,
|
||||
getHistoryResponseData,
|
||||
chatType,
|
||||
showRawSource,
|
||||
showNodeStatus
|
||||
chatType
|
||||
};
|
||||
|
||||
return <ChatBoxContext.Provider value={value}>{children}</ChatBoxContext.Provider>;
|
||||
|
||||
@@ -25,6 +25,7 @@ import { isEqual } from 'lodash';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
|
||||
import dayjs from 'dayjs';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
const colorMap = {
|
||||
[ChatStatusEnum.loading]: {
|
||||
@@ -139,7 +140,7 @@ const ChatItem = (props: Props) => {
|
||||
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
|
||||
const showNodeStatus = useContextSelector(ChatBoxContext, (v) => v.showNodeStatus);
|
||||
const showNodeStatus = useContextSelector(ChatItemContext, (v) => v.showNodeStatus);
|
||||
const isChatLog = chatType === 'log';
|
||||
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
@@ -9,19 +9,16 @@ import RawSourceBox from '@/components/core/dataset/RawSourceBox';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
const QuoteModal = ({
|
||||
rawSearch = [],
|
||||
onClose,
|
||||
canEditDataset,
|
||||
showRawSource,
|
||||
chatItemId,
|
||||
metadata
|
||||
}: {
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
onClose: () => void;
|
||||
canEditDataset: boolean;
|
||||
showRawSource: boolean;
|
||||
chatItemId: string;
|
||||
metadata?: {
|
||||
collectionId: string;
|
||||
@@ -47,6 +44,11 @@ const QuoteModal = ({
|
||||
chatItemId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
const showRouteToDatasetDetail = useContextSelector(
|
||||
ChatItemContext,
|
||||
(v) => v.showRouteToDatasetDetail
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -71,12 +73,7 @@ const QuoteModal = ({
|
||||
}
|
||||
>
|
||||
<ModalBody>
|
||||
<QuoteList
|
||||
rawSearch={filterResults}
|
||||
canEditDataset={canEditDataset}
|
||||
canViewSource={showRawSource}
|
||||
chatItemId={chatItemId}
|
||||
/>
|
||||
<QuoteList rawSearch={filterResults} chatItemId={chatItemId} />
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
</>
|
||||
@@ -87,14 +84,10 @@ export default QuoteModal;
|
||||
|
||||
export const QuoteList = React.memo(function QuoteList({
|
||||
chatItemId,
|
||||
rawSearch = [],
|
||||
canEditDataset,
|
||||
canViewSource
|
||||
rawSearch = []
|
||||
}: {
|
||||
chatItemId?: string;
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
canEditDataset: boolean;
|
||||
canViewSource: boolean;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -104,6 +97,11 @@ export const QuoteList = React.memo(function QuoteList({
|
||||
chatId: v.chatId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
const showRouteToDatasetDetail = useContextSelector(
|
||||
ChatItemContext,
|
||||
(v) => v.showRouteToDatasetDetail
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -120,8 +118,8 @@ export const QuoteList = React.memo(function QuoteList({
|
||||
>
|
||||
<QuoteItem
|
||||
quoteItem={item}
|
||||
canViewSource={canViewSource}
|
||||
canEditDataset={canEditDataset}
|
||||
canViewSource={showRawSource}
|
||||
canEditDataset={showRouteToDatasetDetail}
|
||||
{...RawSourceBoxProps}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -49,7 +49,6 @@ const ResponseTags = ({
|
||||
const [quoteFolded, setQuoteFolded] = useState<boolean>(true);
|
||||
|
||||
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
|
||||
const showRawSource = useContextSelector(ChatBoxContext, (v) => v.showRawSource);
|
||||
const notSharePage = useMemo(() => chatType !== 'share', [chatType]);
|
||||
|
||||
const {
|
||||
@@ -251,8 +250,6 @@ const ResponseTags = ({
|
||||
<QuoteModal
|
||||
{...quoteModalData}
|
||||
chatItemId={historyItem.dataId}
|
||||
canEditDataset={notSharePage}
|
||||
showRawSource={showRawSource}
|
||||
onClose={() => setQuoteModalData(undefined)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { Box, Checkbox } from '@chakra-ui/react';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
|
||||
@@ -249,14 +249,7 @@ export const WholeResponseContent = ({
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('common:core.chat.response.module quoteList')}
|
||||
rawDom={
|
||||
<QuoteList
|
||||
canEditDataset
|
||||
canViewSource
|
||||
chatItemId={dataId}
|
||||
rawSearch={activeModule.quoteList}
|
||||
/>
|
||||
}
|
||||
rawDom={<QuoteList chatItemId={dataId} rawSearch={activeModule.quoteList} />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useTranslation } from 'next-i18next';
|
||||
import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollectionSource';
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import type { readCollectionSourceBody } from '@/pages/api/core/dataset/collection/read';
|
||||
|
||||
type Props = BoxProps &
|
||||
@@ -33,7 +32,6 @@ const RawSourceBox = ({
|
||||
...props
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { fileT } = useI18n();
|
||||
|
||||
const canPreview = !!sourceId && canView;
|
||||
|
||||
@@ -51,7 +49,7 @@ const RawSourceBox = ({
|
||||
|
||||
return (
|
||||
<MyTooltip
|
||||
label={canPreview ? fileT('click_to_view_raw_source') : ''}
|
||||
label={canPreview ? t('file:click_to_view_raw_source') : ''}
|
||||
shouldWrapChildren={false}
|
||||
>
|
||||
<Box
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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 } = 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
|
||||
@@ -236,6 +237,7 @@ function MemberModal({
|
||||
/>
|
||||
|
||||
<Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}>
|
||||
{/* Entry */}
|
||||
{!searchText && !filterClass && (
|
||||
<>
|
||||
{entryList.current.map((item) => {
|
||||
@@ -298,142 +300,135 @@ function MemberModal({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<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>
|
||||
{filterClass && (
|
||||
<ScrollData
|
||||
flexDirection={'column'}
|
||||
gap={1}
|
||||
userSelect={'none'}
|
||||
height={'fit-content'}
|
||||
>
|
||||
{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));
|
||||
}}
|
||||
);
|
||||
})}
|
||||
{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"
|
||||
/>
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
<MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box w="full" ml="2">
|
||||
{member.memberName}
|
||||
</Box>
|
||||
<PermissionTags permission={collaborator?.permission.value} />
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
{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"
|
||||
/>
|
||||
<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>
|
||||
|
||||
|
||||
6
projects/app/src/global/core/api/appReq.d.ts
vendored
6
projects/app/src/global/core/api/appReq.d.ts
vendored
@@ -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;
|
||||
};
|
||||
}>;
|
||||
|
||||
@@ -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 ===== */
|
||||
|
||||
@@ -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) => {
|
||||
return postCreateGroup({
|
||||
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) => {
|
||||
if (!editGroupId) return;
|
||||
return putUpdateGroup({
|
||||
@@ -32,26 +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 }) {
|
||||
// 1. Owner can not be deleted, toast
|
||||
// 2. Owner/Admin can manage members
|
||||
// 3. Owner can add/remove admins
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
const { toast } = useToast();
|
||||
const [hoveredMemberId, setHoveredMemberId] = useState<string | undefined>(undefined);
|
||||
const {
|
||||
members: allMembers,
|
||||
refetchGroups,
|
||||
groups,
|
||||
refetchMembers
|
||||
} = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
const groups = useContextSelector(TeamContext, (v) => v.groups);
|
||||
const refetchGroups = useContextSelector(TeamContext, (v) => v.refetchGroups);
|
||||
const group = useMemo(() => {
|
||||
return groups.find((item) => item._id === editGroupId);
|
||||
}, [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 [searchKey, setSearchKey] = useState('');
|
||||
const filtered = useMemo(() => {
|
||||
return [
|
||||
@@ -62,7 +62,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
|
||||
];
|
||||
}, [searchKey, allMembers]);
|
||||
|
||||
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||
const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||
async () => {
|
||||
if (!editGroupId || !members.length) return;
|
||||
return putUpdateGroup({
|
||||
@@ -155,7 +155,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
|
||||
setSearchKey(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Flex flexDirection="column" mt={3} flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
||||
<MemberScrollData mt={3} flex={'1 0 0'} h={0}>
|
||||
{filtered.map((member) => {
|
||||
return (
|
||||
<HStack
|
||||
@@ -181,11 +181,11 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</MemberScrollData>
|
||||
</Flex>
|
||||
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
|
||||
<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) => {
|
||||
return (
|
||||
<HStack
|
||||
@@ -262,11 +262,14 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</MemberScrollData>
|
||||
</Flex>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter alignItems="flex-end">
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common:common.Close')}
|
||||
</Button>
|
||||
<Button isLoading={isLoading} onClick={onUpdate}>
|
||||
{t('common:common.Save')}
|
||||
</Button>
|
||||
@@ -24,50 +24,43 @@ import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { deleteGroup } from '@/web/support/user/team/group/api';
|
||||
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 dynamic from 'next/dynamic';
|
||||
import { useState } from 'react';
|
||||
import IconButton from '../OrgManage/IconButton';
|
||||
import { MemberGroupType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
|
||||
const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal'));
|
||||
const GroupInfoModal = dynamic(() => import('./GroupInfoModal'));
|
||||
const ManageGroupMemberModal = dynamic(() => import('./GroupManageMember'));
|
||||
const GroupManageMember = dynamic(() => import('./GroupManageMember'));
|
||||
|
||||
function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const [editGroupId, setEditGroupId] = useState<string>();
|
||||
const { groups, refetchGroups, members, refetchMembers } = useContextSelector(
|
||||
TeamContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
const [editGroup, setEditGroup] = useState<MemberGroupType>();
|
||||
const {
|
||||
isOpen: isOpenGroupInfo,
|
||||
onOpen: onOpenGroupInfo,
|
||||
onClose: onCloseGroupInfo
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenManageGroupMember,
|
||||
onOpen: onOpenManageGroupMember,
|
||||
onClose: onCloseManageGroupMember
|
||||
} = useDisclosure();
|
||||
const onEditGroup = (groupId: string) => {
|
||||
setEditGroupId(groupId);
|
||||
|
||||
const onEditGroupInfo = (e: MemberGroupType) => {
|
||||
setEditGroup(e);
|
||||
onOpenGroupInfo();
|
||||
};
|
||||
const onManageMember = (groupId: string) => {
|
||||
setEditGroupId(groupId);
|
||||
onOpenManageGroupMember();
|
||||
};
|
||||
|
||||
const { ConfirmModal: ConfirmDeleteGroupModal, openConfirm: openDeleteGroupModal } = useConfirm({
|
||||
type: 'delete',
|
||||
content: t('account_team:confirm_delete_group')
|
||||
});
|
||||
|
||||
const { groups, refetchGroups, members, refetchMembers } = useContextSelector(
|
||||
TeamContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
const { runAsync: delDeleteGroup } = useRequest2(deleteGroup, {
|
||||
onSuccess: () => {
|
||||
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]) =>
|
||||
userInfo?.team.permission.hasManagePer ||
|
||||
['admin', 'owner'].includes(
|
||||
group.members.find((item) => item.tmbId === userInfo?.team.tmbId)?.role ?? ''
|
||||
);
|
||||
|
||||
const isGroupOwner = (group: (typeof groups)[0]) =>
|
||||
userInfo?.team.permission.hasManagePer ||
|
||||
group.members.find((item) => item.role === 'owner')?.tmbId === userInfo?.team.tmbId;
|
||||
@@ -90,8 +92,8 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
onOpen: onOpenChangeOwner,
|
||||
onClose: onCloseChangeOwner
|
||||
} = useDisclosure();
|
||||
const onChangeOwner = (groupId: string) => {
|
||||
setEditGroupId(groupId);
|
||||
const onChangeOwner = (e: MemberGroupType) => {
|
||||
setEditGroup(e);
|
||||
onOpenChangeOwner();
|
||||
};
|
||||
|
||||
@@ -173,7 +175,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
<AvatarGroup avatars={members.map((v) => v.avatar)} groupId={group._id} />
|
||||
) : hasGroupManagePer(group) ? (
|
||||
<MyTooltip label={t('account_team:manage_member')}>
|
||||
<Box cursor="pointer" onClick={() => onManageMember(group._id)}>
|
||||
<Box cursor="pointer" onClick={() => onManageMember(group)}>
|
||||
<AvatarGroup
|
||||
avatars={group.members.map(
|
||||
(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'),
|
||||
icon: 'edit',
|
||||
onClick: () => {
|
||||
onEditGroup(group._id);
|
||||
onEditGroupInfo(group);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('account_team:manage_member'),
|
||||
icon: 'support/team/group',
|
||||
onClick: () => {
|
||||
onManageMember(group._id);
|
||||
onManageMember(group);
|
||||
}
|
||||
},
|
||||
...(isGroupOwner(group)
|
||||
@@ -218,7 +220,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
label: t('account_team:transfer_ownership'),
|
||||
icon: 'modal/changePer',
|
||||
onClick: () => {
|
||||
onChangeOwner(group._id);
|
||||
onChangeOwner(group);
|
||||
},
|
||||
type: 'primary' as MenuItemType
|
||||
},
|
||||
@@ -246,25 +248,25 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
</MyBox>
|
||||
|
||||
<ConfirmDeleteGroupModal />
|
||||
{isOpenChangeOwner && editGroupId && (
|
||||
<ChangeOwnerModal groupId={editGroupId} onClose={onCloseChangeOwner} />
|
||||
{isOpenChangeOwner && editGroup && (
|
||||
<ChangeOwnerModal groupId={editGroup._id} onClose={onCloseChangeOwner} />
|
||||
)}
|
||||
{isOpenGroupInfo && (
|
||||
<GroupInfoModal
|
||||
onClose={() => {
|
||||
onCloseGroupInfo();
|
||||
setEditGroupId(undefined);
|
||||
setEditGroup(undefined);
|
||||
}}
|
||||
editGroupId={editGroupId}
|
||||
editGroupId={editGroup?._id}
|
||||
/>
|
||||
)}
|
||||
{isOpenManageGroupMember && (
|
||||
<ManageGroupMemberModal
|
||||
{isOpenManageGroupMember && editGroup && (
|
||||
<GroupManageMember
|
||||
onClose={() => {
|
||||
onCloseManageGroupMember();
|
||||
setEditGroupId(undefined);
|
||||
setEditGroup(undefined);
|
||||
}}
|
||||
editGroupId={editGroupId}
|
||||
editGroupId={editGroup._id}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -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,9 @@ 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';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
||||
@@ -40,8 +42,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 +64,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 +84,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 +112,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 +170,83 @@ 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
|
||||
})
|
||||
)();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
<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>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
|
||||
</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>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</MemberScrollData>
|
||||
<ConfirmRemoveMemberModal />
|
||||
</TableContainer>
|
||||
</Box>
|
||||
@@ -22,7 +22,6 @@ import { useMemo, useState } from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '../context';
|
||||
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
export type GroupFormType = {
|
||||
members: {
|
||||
@@ -51,7 +50,7 @@ function OrgMemberManageModal({
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const allMembers = useContextSelector(TeamContext, (v) => v.members);
|
||||
const { members: allMembers, MemberScrollData } = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
const [selectedMembers, setSelectedMembers] = useState<string[]>(
|
||||
currentOrg.members.map((item) => item.tmbId)
|
||||
@@ -98,7 +97,6 @@ function OrgMemberManageModal({
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
onClose={onClose}
|
||||
isOpen
|
||||
title={t('user:team.group.manage_member')}
|
||||
iconSrc={currentOrg?.avatar}
|
||||
@@ -124,32 +122,34 @@ function OrgMemberManageModal({
|
||||
}}
|
||||
/>
|
||||
<Flex flexDirection="column" mt={3} flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
||||
{filterMembers.map((member) => {
|
||||
return (
|
||||
<HStack
|
||||
py="2"
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
alignItems="center"
|
||||
key={member.tmbId}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
bg: 'myGray.50',
|
||||
...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {})
|
||||
}}
|
||||
_notLast={{ mb: 2 }}
|
||||
onClick={() => handleToggleSelect(member.tmbId)}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={!!isSelected(member.tmbId)}
|
||||
icon={<CheckboxIcon name={'common/check'} />}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
<Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box>{member.memberName}</Box>
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
<MemberScrollData>
|
||||
{filterMembers.map((member) => {
|
||||
return (
|
||||
<HStack
|
||||
py="2"
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
alignItems="center"
|
||||
key={member.tmbId}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
bg: 'myGray.50',
|
||||
...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {})
|
||||
}}
|
||||
_notLast={{ mb: 2 }}
|
||||
onClick={() => handleToggleSelect(member.tmbId)}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={!!isSelected(member.tmbId)}
|
||||
icon={<CheckboxIcon name={'common/check'} />}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
<Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box>{member.memberName}</Box>
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</MemberScrollData>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
|
||||
@@ -185,7 +185,10 @@ function OrgMemberManageModal({
|
||||
</Flex>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter alignItems="flex-end">
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common:common.Close')}
|
||||
</Button>
|
||||
<Button isLoading={isLoading} onClick={onUpdate}>
|
||||
{t('common:common.Save')}
|
||||
</Button>
|
||||
369
projects/app/src/pageComponents/account/team/OrgManage/index.tsx
Normal file
369
projects/app/src/pageComponents/account/team/OrgManage/index.tsx
Normal file
@@ -0,0 +1,369 @@
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Flex,
|
||||
HStack,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tag,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
VStack
|
||||
} from '@chakra-ui/react';
|
||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
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 { deleteOrg, deleteOrgMember, getOrgList } from '@/web/support/user/team/org/api';
|
||||
|
||||
import IconButton from './IconButton';
|
||||
import { defaultOrgForm, type OrgFormType } from './OrgInfoModal';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
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'));
|
||||
const OrgMoveModal = dynamic(() => import('./OrgMoveModal'));
|
||||
|
||||
function ActionButton({
|
||||
icon,
|
||||
text,
|
||||
onClick
|
||||
}: {
|
||||
icon: IconNameType;
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<HStack
|
||||
gap={'8px'}
|
||||
w="100%"
|
||||
transition={'background 0.1s'}
|
||||
cursor={'pointer'}
|
||||
p="4px"
|
||||
rounded={'sm'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600'
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MyIcon name={icon} w="1rem" h="1rem" />
|
||||
<Box fontSize={'sm'}>{text}</Box>
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, isTeamAdmin } = useUserStore();
|
||||
|
||||
const { members, MemberScrollData } = useContextSelector(TeamContext, (v) => v);
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const isSyncMember = feConfigs.register_method?.includes('sync');
|
||||
const [parentPath, setParentPath] = useState('');
|
||||
const {
|
||||
data: orgs = [],
|
||||
loading: isLoadingOrgs,
|
||||
refresh: refetchOrgs
|
||||
} = useRequest2(getOrgList, {
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
});
|
||||
const currentOrgs = useMemo(() => {
|
||||
if (orgs.length === 0) return [];
|
||||
// Auto select the first org(root org is team)
|
||||
if (parentPath === '') {
|
||||
setParentPath(getOrgChildrenPath(orgs[0]));
|
||||
return [];
|
||||
}
|
||||
|
||||
return orgs
|
||||
.filter((org) => org.path === parentPath)
|
||||
.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
// Member + org
|
||||
count:
|
||||
item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length
|
||||
};
|
||||
});
|
||||
}, [orgs, parentPath]);
|
||||
const currentOrg = useMemo(() => {
|
||||
const splitPath = parentPath.split('/');
|
||||
const currentOrgId = splitPath[splitPath.length - 1];
|
||||
if (!currentOrgId) return;
|
||||
|
||||
return orgs.find((org) => org.pathId === currentOrgId);
|
||||
}, [orgs, parentPath]);
|
||||
|
||||
const paths = useMemo(() => {
|
||||
const splitPath = parentPath.split('/').filter(Boolean);
|
||||
return splitPath
|
||||
.map((id) => {
|
||||
const org = orgs.find((org) => org.pathId === id)!;
|
||||
|
||||
if (org.path === '') return;
|
||||
|
||||
return {
|
||||
parentId: getOrgChildrenPath(org),
|
||||
parentName: org.name
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as ParentTreePathItemType[];
|
||||
}, [parentPath, orgs]);
|
||||
|
||||
const [editOrg, setEditOrg] = useState<OrgFormType>();
|
||||
const [manageMemberOrg, setManageMemberOrg] = useState<OrgType>();
|
||||
const [movingOrg, setMovingOrg] = useState<OrgType>();
|
||||
|
||||
// Delete org
|
||||
const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({
|
||||
type: 'delete',
|
||||
content: t('account_team:confirm_delete_org')
|
||||
});
|
||||
const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))();
|
||||
const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, {
|
||||
onSuccess: () => {
|
||||
refetchOrgs();
|
||||
}
|
||||
});
|
||||
|
||||
// Delete member
|
||||
const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({
|
||||
type: 'delete',
|
||||
content: t('account_team:confirm_delete_member')
|
||||
});
|
||||
const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, {
|
||||
onSuccess: () => {
|
||||
refetchOrgs();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||
{Tabs}
|
||||
</Flex>
|
||||
<MyBox
|
||||
flex={'1 0 0'}
|
||||
h={0}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
isLoading={isLoadingOrgs}
|
||||
>
|
||||
<Box mb={3}>
|
||||
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
|
||||
</Box>
|
||||
<Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}>
|
||||
<MemberScrollData h={'100%'} fontSize={'sm'} flexGrow={1}>
|
||||
{/* Table */}
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr bg={'white !important'}>
|
||||
<Th bg="myGray.100" borderLeftRadius="6px">
|
||||
{t('common:Name')}
|
||||
</Th>
|
||||
{!isSyncMember && (
|
||||
<Th bg="myGray.100" borderRightRadius="6px">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
)}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{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>
|
||||
))}
|
||||
{currentOrg?.members.map((member) => {
|
||||
const memberInfo = members.find((m) => m.tmbId === member.tmbId);
|
||||
if (!memberInfo) return null;
|
||||
|
||||
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 */}
|
||||
{!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 !== '' && (
|
||||
<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)}
|
||||
/>
|
||||
{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>
|
||||
</MyBox>
|
||||
|
||||
{!!editOrg && (
|
||||
<OrgInfoModal
|
||||
editOrg={editOrg}
|
||||
onClose={() => setEditOrg(undefined)}
|
||||
onSuccess={refetchOrgs}
|
||||
/>
|
||||
)}
|
||||
{!!movingOrg && (
|
||||
<OrgMoveModal
|
||||
orgs={orgs}
|
||||
movingOrg={movingOrg}
|
||||
onClose={() => setMovingOrg(undefined)}
|
||||
onSuccess={refetchOrgs}
|
||||
/>
|
||||
)}
|
||||
{!!manageMemberOrg && (
|
||||
<OrgMemberManageModal
|
||||
currentOrg={manageMemberOrg}
|
||||
refetchOrgs={refetchOrgs}
|
||||
onClose={() => setManageMemberOrg(undefined)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ConfirmDeleteOrgModal />
|
||||
<ConfirmDeleteMember />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgTable;
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
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 {
|
||||
TeamManagePermissionVal,
|
||||
@@ -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: () => <></>
|
||||
});
|
||||
|
||||
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 (
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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')}: </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>
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -1,354 +0,0 @@
|
||||
import { deleteOrg, deleteOrgMember } from '@/web/support/user/team/org/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Flex,
|
||||
HStack,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tag,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
VStack
|
||||
} from '@chakra-ui/react';
|
||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
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 IconButton from './IconButton';
|
||||
import { defaultOrgForm, type OrgFormType } from './OrgInfoModal';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
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';
|
||||
|
||||
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
|
||||
const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal'));
|
||||
const OrgMoveModal = dynamic(() => import('./OrgMoveModal'));
|
||||
|
||||
function ActionButton({
|
||||
icon,
|
||||
text,
|
||||
onClick
|
||||
}: {
|
||||
icon: IconNameType;
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<HStack
|
||||
gap={'8px'}
|
||||
w="100%"
|
||||
transition={'background 0.1s'}
|
||||
cursor={'pointer'}
|
||||
p="4px"
|
||||
rounded={'sm'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600'
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MyIcon name={icon} w="1rem" h="1rem" />
|
||||
<Box fontSize={'sm'}>{text}</Box>
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, isTeamAdmin } = useUserStore();
|
||||
|
||||
const { members } = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
const [parentPath, setParentPath] = useState('');
|
||||
const {
|
||||
data: orgs = [],
|
||||
loading: isLoadingOrgs,
|
||||
refresh: refetchOrgs
|
||||
} = useRequest2(getOrgList, {
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
});
|
||||
const currentOrgs = useMemo(() => {
|
||||
if (orgs.length === 0) return [];
|
||||
// Auto select the first org(root org is team)
|
||||
if (parentPath === '') {
|
||||
setParentPath(getOrgChildrenPath(orgs[0]));
|
||||
return [];
|
||||
}
|
||||
|
||||
return orgs
|
||||
.filter((org) => org.path === parentPath)
|
||||
.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
// Member + org
|
||||
count:
|
||||
item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length
|
||||
};
|
||||
});
|
||||
}, [orgs, parentPath]);
|
||||
const currentOrg = useMemo(() => {
|
||||
const splitPath = parentPath.split('/');
|
||||
const currentOrgId = splitPath[splitPath.length - 1];
|
||||
if (!currentOrgId) return;
|
||||
|
||||
return orgs.find((org) => org.pathId === currentOrgId);
|
||||
}, [orgs, parentPath]);
|
||||
|
||||
const paths = useMemo(() => {
|
||||
const splitPath = parentPath.split('/').filter(Boolean);
|
||||
return splitPath
|
||||
.map((id) => {
|
||||
const org = orgs.find((org) => org.pathId === id)!;
|
||||
|
||||
if (org.path === '') return;
|
||||
|
||||
return {
|
||||
parentId: getOrgChildrenPath(org),
|
||||
parentName: org.name
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as ParentTreePathItemType[];
|
||||
}, [parentPath, orgs]);
|
||||
|
||||
const [editOrg, setEditOrg] = useState<OrgFormType>();
|
||||
const [manageMemberOrg, setManageMemberOrg] = useState<OrgType>();
|
||||
const [movingOrg, setMovingOrg] = useState<OrgType>();
|
||||
|
||||
// Delete org
|
||||
const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({
|
||||
type: 'delete',
|
||||
content: t('account_team:confirm_delete_org')
|
||||
});
|
||||
const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))();
|
||||
const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, {
|
||||
onSuccess: () => {
|
||||
refetchOrgs();
|
||||
}
|
||||
});
|
||||
|
||||
// Delete member
|
||||
const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({
|
||||
type: 'delete',
|
||||
content: t('account_team:confirm_delete_member')
|
||||
});
|
||||
const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, {
|
||||
onSuccess: () => {
|
||||
refetchOrgs();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||
{Tabs}
|
||||
</Flex>
|
||||
<MyBox flex={'1 0 0'} overflow={'auto'} isLoading={isLoadingOrgs}>
|
||||
<Box mb={3}>
|
||||
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
|
||||
</Box>
|
||||
<Flex w={'100%'} gap={'4'}>
|
||||
{/* Table */}
|
||||
<TableContainer overflow={'unset'} fontSize={'sm'} flexGrow={1}>
|
||||
<Table overflow={'unset'}>
|
||||
<Thead>
|
||||
<Tr bg={'white !important'}>
|
||||
<Th bg="myGray.100" borderLeftRadius="6px">
|
||||
{t('common:Name')}
|
||||
</Th>
|
||||
<Th bg="myGray.100" borderRightRadius="6px">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{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>
|
||||
<Td w={'6rem'}>
|
||||
{isTeamAdmin && (
|
||||
<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>
|
||||
))}
|
||||
{currentOrg?.members.map((member) => {
|
||||
const memberInfo = members.find((m) => m.tmbId === member.tmbId);
|
||||
if (!memberInfo) return null;
|
||||
|
||||
return (
|
||||
<Tr key={member.tmbId}>
|
||||
<Td>
|
||||
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
|
||||
</Td>
|
||||
<Td w={'6rem'}>
|
||||
{isTeamAdmin && (
|
||||
<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>
|
||||
{/* 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)}
|
||||
/>
|
||||
{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 && (
|
||||
<OrgInfoModal
|
||||
editOrg={editOrg}
|
||||
onClose={() => setEditOrg(undefined)}
|
||||
onSuccess={refetchOrgs}
|
||||
/>
|
||||
)}
|
||||
{!!movingOrg && (
|
||||
<OrgMoveModal
|
||||
orgs={orgs}
|
||||
movingOrg={movingOrg}
|
||||
onClose={() => setMovingOrg(undefined)}
|
||||
onSuccess={refetchOrgs}
|
||||
/>
|
||||
)}
|
||||
{!!manageMemberOrg && (
|
||||
<OrgMemberManageModal
|
||||
currentOrg={manageMemberOrg}
|
||||
refetchOrgs={refetchOrgs}
|
||||
onClose={() => setManageMemberOrg(undefined)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ConfirmDeleteOrgModal />
|
||||
<ConfirmDeleteMember />
|
||||
</MyBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgTable;
|
||||
@@ -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';
|
||||
@@ -11,14 +11,15 @@ import { useRouter } from 'next/router';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
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 MemberTable from './components/MemberTable';
|
||||
|
||||
const PermissionManage = dynamic(() => import('./components/PermissionManage/index'));
|
||||
const GroupManage = dynamic(() => import('./components/GroupManage/index'));
|
||||
|
||||
const OrgManage = dynamic(() => import('./components/OrgManage/index'));
|
||||
const MemberTable = dynamic(() => import('@/pageComponents/account/team/MemberTable'));
|
||||
const PermissionManage = dynamic(
|
||||
() => import('@/pageComponents/account/team/PermissionManage/index')
|
||||
);
|
||||
const GroupManage = dynamic(() => import('@/pageComponents/account/team/GroupManage/index'));
|
||||
const OrgManage = dynamic(() => import('@/pageComponents/account/team/OrgManage/index'));
|
||||
|
||||
export enum TeamTabEnum {
|
||||
member = 'member',
|
||||
@@ -34,7 +35,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(
|
||||
() => (
|
||||
@@ -62,72 +63,81 @@ const Team = () => {
|
||||
|
||||
return (
|
||||
<AccountContainer isLoading={isLoading}>
|
||||
{/* header */}
|
||||
<Flex
|
||||
w={'100%'}
|
||||
h={'3.5rem'}
|
||||
px={'1.56rem'}
|
||||
py={'0.56rem'}
|
||||
borderBottom={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'myGray.25'}
|
||||
align={'center'}
|
||||
gap={6}
|
||||
justify={'space-between'}
|
||||
>
|
||||
<Flex align={'center'}>
|
||||
<Flex gap={2} color={'myGray.900'}>
|
||||
<Icon name="support/user/usersLight" w={'1.25rem'} h={'1.25rem'} />
|
||||
<Box fontWeight={'500'} fontSize={'1rem'}>
|
||||
{t('account:team')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex align={'center'} ml={6}>
|
||||
<TeamSelector height={'28px'} />
|
||||
</Flex>
|
||||
{userInfo?.team?.role === TeamMemberRoleEnum.owner && (
|
||||
<Flex align={'center'} justify={'center'} ml={2} p={'0.44rem'}>
|
||||
<MyIcon
|
||||
name="edit"
|
||||
w="18px"
|
||||
cursor="pointer"
|
||||
_hover={{
|
||||
color: 'primary.500'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!userInfo?.team) return;
|
||||
setEditTeamData({
|
||||
id: userInfo.team.teamId,
|
||||
name: userInfo.team.teamName,
|
||||
avatar: userInfo.team.avatar
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Flex h={'100%'} flexDirection={'column'}>
|
||||
{/* header */}
|
||||
<Flex
|
||||
w={'100%'}
|
||||
h={'3.5rem'}
|
||||
px={'1.56rem'}
|
||||
py={'0.56rem'}
|
||||
borderBottom={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'myGray.25'}
|
||||
align={'center'}
|
||||
gap={6}
|
||||
justify={'space-between'}
|
||||
>
|
||||
<Flex align={'center'}>
|
||||
<Flex gap={2} color={'myGray.900'}>
|
||||
<Icon name="support/user/usersLight" w={'1.25rem'} h={'1.25rem'} />
|
||||
<Box fontWeight={'500'} fontSize={'1rem'}>
|
||||
{t('account:team')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex align={'center'} ml={6}>
|
||||
<TeamSelector height={'28px'} />
|
||||
</Flex>
|
||||
{userInfo?.team?.role === TeamMemberRoleEnum.owner && (
|
||||
<Flex align={'center'} justify={'center'} ml={2} p={'0.44rem'}>
|
||||
<MyIcon
|
||||
name="edit"
|
||||
w="18px"
|
||||
cursor="pointer"
|
||||
_hover={{
|
||||
color: 'primary.500'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!userInfo?.team) return;
|
||||
setEditTeamData({
|
||||
id: userInfo.team.teamId,
|
||||
name: userInfo.team.teamName,
|
||||
avatar: userInfo.team.avatar
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
float={'right'}
|
||||
color={'myGray.900'}
|
||||
h={'1.25rem'}
|
||||
px={'0.5rem'}
|
||||
py={'0.125rem'}
|
||||
fontSize={'0.75rem'}
|
||||
borderRadius={'1.25rem'}
|
||||
bg={'myGray.150'}
|
||||
>
|
||||
{t('account_team:total_team_members', { amount: teamSize })}
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{/* table */}
|
||||
<Box
|
||||
float={'right'}
|
||||
color={'myGray.900'}
|
||||
h={'1.25rem'}
|
||||
px={'0.5rem'}
|
||||
py={'0.125rem'}
|
||||
fontSize={'0.75rem'}
|
||||
borderRadius={'1.25rem'}
|
||||
bg={'myGray.150'}
|
||||
py={'1.5rem'}
|
||||
px={'2rem'}
|
||||
flex={'1 0 0'}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
overflow={'auto'}
|
||||
>
|
||||
{t('account_team:total_team_members', { amount: teamSize })}
|
||||
{teamTab === TeamTabEnum.member && <MemberTable Tabs={Tabs} />}
|
||||
{teamTab === TeamTabEnum.org && <OrgManage Tabs={Tabs} />}
|
||||
{teamTab === TeamTabEnum.group && <GroupManage Tabs={Tabs} />}
|
||||
{teamTab === TeamTabEnum.permission && <PermissionManage Tabs={Tabs} />}
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{/* table */}
|
||||
<Box py={'1.5rem'} px={'2rem'}>
|
||||
{teamTab === TeamTabEnum.member && <MemberTable Tabs={Tabs} />}
|
||||
{teamTab === TeamTabEnum.org && <OrgManage Tabs={Tabs} />}
|
||||
{teamTab === TeamTabEnum.group && <GroupManage Tabs={Tabs} />}
|
||||
{teamTab === TeamTabEnum.permission && <PermissionManage Tabs={Tabs} />}
|
||||
</Box>
|
||||
</AccountContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
// };
|
||||
|
||||
55
projects/app/src/pages/api/admin/initv4819.ts
Normal file
55
projects/app/src/pages/api/admin/initv4819.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
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({}, '_id avatar');
|
||||
console.log('Total users:', users.length);
|
||||
let success = 0;
|
||||
for await (const user of users) {
|
||||
// @ts-ignore
|
||||
if (!user.avatar) continue;
|
||||
try {
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoTeamMember.updateOne(
|
||||
{
|
||||
userId: user._id
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
avatar: (user as any).avatar // 删除 avatar 字段, 因为 Type 改了,所以这里不能直接写 user.avatar
|
||||
}
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
// @ts-ignore
|
||||
user.avatar = undefined;
|
||||
await user.save({ session });
|
||||
});
|
||||
success++;
|
||||
console.log('Move avatar success:', success);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
@@ -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 && global.systemInitBufferId && global.systemInitBufferId === bufferId) {
|
||||
return {
|
||||
bufferId: global.systemInitBufferId
|
||||
bufferId: global.systemInitBufferId,
|
||||
systemVersion: global.systemVersion || '0.0.0'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 { PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import { addSourceMember } from '@fastgpt/service/support/user/utils';
|
||||
|
||||
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: {
|
||||
@@ -144,10 +146,14 @@ async function handler(
|
||||
MongoChat.countDocuments(where, { ...readFromSecondary })
|
||||
]);
|
||||
|
||||
const listWithSourceMember = await addSourceMember({
|
||||
list: list
|
||||
});
|
||||
|
||||
const listWithoutTmbId = list.filter((item) => !item.tmbId);
|
||||
|
||||
return {
|
||||
pageNum,
|
||||
pageSize,
|
||||
data,
|
||||
list: listWithSourceMember.concat(listWithoutTmbId),
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 { addSourceMember } from '@fastgpt/service/support/user/utils';
|
||||
|
||||
export type ListAppBody = {
|
||||
parentId?: ParentIdType;
|
||||
@@ -201,19 +202,9 @@ 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
|
||||
}));
|
||||
return addSourceMember({
|
||||
list: formatApps
|
||||
});
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -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 { addSourceMember } from '@fastgpt/service/support/user/utils';
|
||||
|
||||
export type versionListBody = PaginationProps<{
|
||||
appId: string;
|
||||
@@ -15,41 +17,40 @@ 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();
|
||||
|
||||
return addSourceMember({
|
||||
list: versions
|
||||
}).then((list) =>
|
||||
list.map((item) => ({
|
||||
...item,
|
||||
isPublish: !!item.isPublish
|
||||
}))
|
||||
);
|
||||
})(),
|
||||
MongoAppVersion.countDocuments({ appId })
|
||||
]);
|
||||
|
||||
const versionList = result.map((item) => {
|
||||
return {
|
||||
_id: item._id,
|
||||
appId: item.appId,
|
||||
versionName: item.versionName,
|
||||
time: item.time,
|
||||
isPublish: item.isPublish,
|
||||
tmbId: item.tmbId
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
total,
|
||||
list: versionList
|
||||
list: result
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ describe('发布应用版本测试', () => {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
chatConfig: {},
|
||||
type: AppTypeEnum.simple,
|
||||
isPublish: false,
|
||||
versionName: '1'
|
||||
};
|
||||
|
||||
@@ -164,6 +164,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
runningAppInfo: {
|
||||
id: appId,
|
||||
teamId: app.teamId,
|
||||
tmbId: app.tmbId
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { NextApiRequest } from 'next';
|
||||
import { DatasetTrainingCollectionName } from '@fastgpt/service/core/dataset/training/schema';
|
||||
import { Types } from '@fastgpt/service/common/mongo';
|
||||
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
|
||||
import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
@@ -10,11 +9,10 @@ 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';
|
||||
|
||||
async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectionsListItemType>> {
|
||||
async function handler(req: NextApiRequest) {
|
||||
let {
|
||||
pageNum = 1,
|
||||
pageSize = 10,
|
||||
@@ -24,7 +22,7 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
|
||||
selectFolder = false,
|
||||
filterTags = [],
|
||||
simple = false
|
||||
} = req.body as GetDatasetCollectionsProps;
|
||||
} = req.body as any;
|
||||
searchText = searchText?.replace(/'/g, '');
|
||||
pageSize = Math.min(pageSize, 30);
|
||||
|
||||
|
||||
192
projects/app/src/pages/api/core/dataset/collection/listV2.ts
Normal file
192
projects/app/src/pages/api/core/dataset/collection/listV2.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
import { DatasetTrainingCollectionName } from '@fastgpt/service/core/dataset/training/schema';
|
||||
import { Types } from '@fastgpt/service/common/mongo';
|
||||
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
|
||||
import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
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<PaginationResponse<DatasetCollectionsListItemType>> {
|
||||
let {
|
||||
datasetId,
|
||||
parentId = null,
|
||||
searchText = '',
|
||||
selectFolder = false,
|
||||
filterTags = [],
|
||||
simple = false
|
||||
} = req.body as GetDatasetCollectionsProps;
|
||||
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({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
datasetId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
const match = {
|
||||
teamId: new Types.ObjectId(teamId),
|
||||
datasetId: new Types.ObjectId(datasetId),
|
||||
parentId: parentId ? new Types.ObjectId(parentId) : null,
|
||||
...(selectFolder ? { type: DatasetCollectionTypeEnum.folder } : {}),
|
||||
...(searchText
|
||||
? {
|
||||
name: new RegExp(searchText, 'i')
|
||||
}
|
||||
: {}),
|
||||
...(filterTags.length ? { tags: { $in: filterTags } } : {})
|
||||
};
|
||||
|
||||
const selectField = {
|
||||
_id: 1,
|
||||
parentId: 1,
|
||||
tmbId: 1,
|
||||
name: 1,
|
||||
type: 1,
|
||||
forbid: 1,
|
||||
createTime: 1,
|
||||
updateTime: 1,
|
||||
trainingType: 1,
|
||||
fileId: 1,
|
||||
rawLink: 1,
|
||||
tags: 1,
|
||||
externalFileId: 1
|
||||
};
|
||||
|
||||
// not count data amount
|
||||
if (simple) {
|
||||
const collections = await MongoDatasetCollection.find(match, undefined, {
|
||||
...readFromSecondary
|
||||
})
|
||||
.select(selectField)
|
||||
.sort({
|
||||
updateTime: -1
|
||||
})
|
||||
.lean();
|
||||
|
||||
return {
|
||||
list: await Promise.all(
|
||||
collections.map(async (item) => ({
|
||||
...item,
|
||||
tags: await collectionTagsToTagLabel({
|
||||
datasetId,
|
||||
tags: item.tags
|
||||
}),
|
||||
dataAmount: 0,
|
||||
trainingAmount: 0,
|
||||
permission
|
||||
}))
|
||||
),
|
||||
total: await MongoDatasetCollection.countDocuments(match)
|
||||
};
|
||||
}
|
||||
|
||||
const [collections, total]: [DatasetCollectionsListItemType[], number] = await Promise.all([
|
||||
MongoDatasetCollection.aggregate([
|
||||
{
|
||||
$match: match
|
||||
},
|
||||
{
|
||||
$sort: { updateTime: -1 }
|
||||
},
|
||||
{
|
||||
$skip: offset
|
||||
},
|
||||
{
|
||||
$limit: pageSize
|
||||
},
|
||||
// count training data
|
||||
{
|
||||
$lookup: {
|
||||
from: DatasetTrainingCollectionName,
|
||||
let: { id: '$_id', team_id: match.teamId, dataset_id: match.datasetId },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [{ $eq: ['$teamId', '$$team_id'] }, { $eq: ['$collectionId', '$$id'] }]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ $count: 'count' }
|
||||
],
|
||||
as: 'trainingCount'
|
||||
}
|
||||
},
|
||||
// count collection total data
|
||||
{
|
||||
$lookup: {
|
||||
from: DatasetDataCollectionName,
|
||||
let: { id: '$_id', team_id: match.teamId, dataset_id: match.datasetId },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [
|
||||
{ $eq: ['$teamId', '$$team_id'] },
|
||||
{ $eq: ['$datasetId', '$$dataset_id'] },
|
||||
{ $eq: ['$collectionId', '$$id'] }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ $count: 'count' }
|
||||
],
|
||||
as: 'dataCount'
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
...selectField,
|
||||
dataAmount: {
|
||||
$ifNull: [{ $arrayElemAt: ['$dataCount.count', 0] }, 0]
|
||||
},
|
||||
trainingAmount: {
|
||||
$ifNull: [{ $arrayElemAt: ['$trainingCount.count', 0] }, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
]),
|
||||
MongoDatasetCollection.countDocuments(match, {
|
||||
...readFromSecondary
|
||||
})
|
||||
]);
|
||||
|
||||
const list = await Promise.all(
|
||||
collections.map(async (item) => ({
|
||||
...item,
|
||||
tags: await collectionTagsToTagLabel({
|
||||
datasetId,
|
||||
tags: item.tags
|
||||
}),
|
||||
permission
|
||||
}))
|
||||
);
|
||||
|
||||
if (list.find((item) => item.trainingAmount > 0)) {
|
||||
startTrainingQueue();
|
||||
}
|
||||
|
||||
// count collections
|
||||
return {
|
||||
list,
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,8 +1,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 +17,8 @@ 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 { addSourceMember } from '@fastgpt/service/support/user/utils';
|
||||
import { getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
|
||||
export type GetDatasetListBody = {
|
||||
parentId: ParentIdType;
|
||||
@@ -167,28 +167,24 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
})();
|
||||
|
||||
return {
|
||||
...dataset,
|
||||
_id: dataset._id,
|
||||
avatar: dataset.avatar,
|
||||
name: dataset.name,
|
||||
intro: dataset.intro,
|
||||
type: dataset.type,
|
||||
vectorModel: getVectorModel(dataset.vectorModel),
|
||||
inheritPermission: dataset.inheritPermission,
|
||||
tmbId: dataset.tmbId,
|
||||
updateTime: dataset.updateTime,
|
||||
permission: Per,
|
||||
privateDataset
|
||||
};
|
||||
})
|
||||
.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
|
||||
}));
|
||||
|
||||
return data;
|
||||
return addSourceMember({
|
||||
list: formatDatasets
|
||||
});
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -45,7 +45,11 @@ async function handler(
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'debug',
|
||||
runningAppInfo: {
|
||||
id: appId,
|
||||
id: app._id,
|
||||
teamId: app.teamId,
|
||||
tmbId: app.tmbId
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -280,6 +280,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
uid: String(outLinkUserId || tmbId),
|
||||
|
||||
chatId,
|
||||
|
||||
@@ -164,8 +164,6 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
|
||||
showMarkIcon
|
||||
showVoiceIcon={false}
|
||||
chatType="log"
|
||||
showRawSource
|
||||
showNodeStatus
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
@@ -187,7 +185,12 @@ const Render = (props: Props) => {
|
||||
}, [appId, chatId]);
|
||||
|
||||
return (
|
||||
<ChatItemContextProvider>
|
||||
<ChatItemContextProvider
|
||||
showRouteToAppDetail={true}
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
showNodeStatus
|
||||
>
|
||||
<ChatRecordContextProvider params={params}>
|
||||
<DetailLogsModal {...props} />
|
||||
</ChatRecordContextProvider>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
getWorkflowVersionList,
|
||||
updateAppVersion
|
||||
} from '@/web/core/app/api/version';
|
||||
import { useVirtualScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, BoxProps, Button, Flex, Input } from '@chakra-ui/react';
|
||||
@@ -18,9 +18,7 @@ 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';
|
||||
@@ -183,23 +181,18 @@ const TeamCloud = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const { loadAndGetTeamMembers } = useUserStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { scrollDataList, ScrollList, isLoading, fetchData, setData } = useVirtualScrollPagination(
|
||||
getWorkflowVersionList,
|
||||
{
|
||||
itemHeight: 40,
|
||||
overscan: 20,
|
||||
|
||||
pageSize: 30,
|
||||
defaultParams: {
|
||||
appId: appDetail._id
|
||||
}
|
||||
}
|
||||
);
|
||||
const { data: members = [] } = useRequest2(loadAndGetTeamMembers, {
|
||||
manual: !feConfigs.isPlus
|
||||
const {
|
||||
ScrollData,
|
||||
data: scrollDataList,
|
||||
setData,
|
||||
isLoading
|
||||
} = useScrollPagination(getWorkflowVersionList, {
|
||||
pageSize: 30,
|
||||
params: {
|
||||
appId: appDetail._id
|
||||
},
|
||||
refreshDeps: [appDetail._id]
|
||||
});
|
||||
const [editIndex, setEditIndex] = useState<number | undefined>(undefined);
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(undefined);
|
||||
@@ -237,15 +230,13 @@ const TeamCloud = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<ScrollList isLoading={isLoading || isLoadingVersion} flex={'1 0 0'} px={5}>
|
||||
{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);
|
||||
<ScrollData isLoading={isLoading || isLoadingVersion} flex={'1 0 0'} px={5}>
|
||||
{scrollDataList.map((item, index) => {
|
||||
const firstPublishedIndex = scrollDataList.findIndex((data) => data.isPublish);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={data.index}
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
py={editIndex !== index ? 2 : 1}
|
||||
px={3}
|
||||
@@ -266,20 +257,33 @@ const TeamCloud = ({
|
||||
h={'72px'}
|
||||
Trigger={
|
||||
<Box>
|
||||
<Avatar src={tmb?.avatar} borderRadius={'50%'} w={'24px'} h={'24px'} />
|
||||
<Avatar
|
||||
src={item.sourceMember.avatar}
|
||||
borderRadius={'50%'}
|
||||
w={'24px'}
|
||||
h={'24px'}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
{({ onClose }) => (
|
||||
<Flex alignItems={'center'} h={'full'} pl={5} gap={3}>
|
||||
{() => (
|
||||
<Flex alignItems={'center'} h={'full'} pl={5} gap={2}>
|
||||
<Box>
|
||||
<Avatar src={tmb?.avatar} borderRadius={'50%'} w={'36px'} h={'36px'} />
|
||||
<Avatar
|
||||
src={item.sourceMember.avatar}
|
||||
borderRadius={'50%'}
|
||||
w={'36px'}
|
||||
h={'36px'}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box fontSize={'14px'} color={'myGray.900'}>
|
||||
{tmb?.memberName}
|
||||
</Box>
|
||||
<Box fontSize={'12px'} color={'myGray.500'}>
|
||||
<Flex gap={1} fontSize={'sm'} color={'myGray.900'}>
|
||||
<Box>{item.sourceMember.name}</Box>
|
||||
{item.sourceMember.status === 'leave' && (
|
||||
<Tag color="gray">{t('common:user_leaved')}</Tag>
|
||||
)}
|
||||
</Flex>
|
||||
<Box fontSize={'xs'} mt={2} color={'myGray.500'}>
|
||||
{formatTime2YMDHMS(item.time)}
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -349,6 +353,6 @@ const TeamCloud = ({
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</ScrollList>
|
||||
</ScrollData>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -91,7 +91,12 @@ const Render = ({ appForm }: Props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<ChatItemContextProvider>
|
||||
<ChatItemContextProvider
|
||||
showRouteToAppDetail={true}
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
showNodeStatus
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest appForm={appForm} />
|
||||
</ChatRecordContextProvider>
|
||||
|
||||
@@ -158,7 +158,12 @@ const Render = (Props: Props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<ChatItemContextProvider>
|
||||
<ChatItemContextProvider
|
||||
showRouteToAppDetail={true}
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
showNodeStatus
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest {...Props} />
|
||||
</ChatRecordContextProvider>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -14,22 +14,18 @@ import RenderToolInput from './render/RenderToolInput';
|
||||
import RenderOutput from './render/RenderOutput';
|
||||
import CodeEditor from '@fastgpt/web/components/common/Textarea/CodeEditor';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { getLatestNodeTemplate } from '@/web/core/workflow/utils';
|
||||
import { CodeNode } from '@fastgpt/global/core/workflow/template/system/sandbox';
|
||||
import { JS_TEMPLATE } from '@fastgpt/global/core/workflow/template/system/sandbox/constants';
|
||||
|
||||
const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { workflowT } = useI18n();
|
||||
const { nodeId, inputs, outputs } = data;
|
||||
const { splitToolInputs, onChangeNode, onResetNode } = useContextSelector(
|
||||
WorkflowContext,
|
||||
(ctx) => ctx
|
||||
);
|
||||
|
||||
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (ctx) => ctx.onChangeNode);
|
||||
|
||||
const { ConfirmModal, openConfirm } = useConfirm({
|
||||
content: workflowT('code.Reset template confirm')
|
||||
content: t('workflow:code.Reset template confirm')
|
||||
});
|
||||
|
||||
const CustomComponent = useMemo(() => {
|
||||
@@ -38,19 +34,24 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return (
|
||||
<Box mt={-3}>
|
||||
<Flex mb={2} alignItems={'flex-end'}>
|
||||
<Box flex={'1'}>{'Javascript ' + workflowT('Code')}</Box>
|
||||
<Box flex={'1'}>{'Javascript ' + t('workflow:Code')}</Box>
|
||||
<Box
|
||||
cursor={'pointer'}
|
||||
color={'primary.500'}
|
||||
fontSize={'xs'}
|
||||
onClick={openConfirm(() => {
|
||||
onResetNode({
|
||||
id: nodeId,
|
||||
node: getLatestNodeTemplate(data, CodeNode)
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: JS_TEMPLATE
|
||||
}
|
||||
});
|
||||
})}
|
||||
>
|
||||
{workflowT('code.Reset template')}
|
||||
{t('workflow:code.Reset template')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<CodeEditor
|
||||
@@ -73,37 +74,33 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [data, nodeId, onChangeNode, onResetNode, openConfirm, workflowT]);
|
||||
}, [nodeId, onChangeNode, openConfirm, t]);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
const { isTool, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
const { isTool, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} selected={selected} {...data}>
|
||||
{isTool && (
|
||||
<>
|
||||
<Container>
|
||||
<RenderToolInput nodeId={nodeId} inputs={inputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
<Container>
|
||||
<IOTitle text={t('common:common.Input')} mb={-1} />
|
||||
<RenderInput
|
||||
nodeId={nodeId}
|
||||
flowInputList={commonInputs}
|
||||
CustomComponent={CustomComponent}
|
||||
/>
|
||||
</Container>
|
||||
<Container>
|
||||
<IOTitle text={t('common:common.Output')} />
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
<ConfirmModal />
|
||||
</NodeCard>
|
||||
);
|
||||
}, [ConfirmModal, CustomComponent, data, inputs, nodeId, outputs, selected, splitToolInputs, t]);
|
||||
|
||||
return Render;
|
||||
return (
|
||||
<NodeCard minW={'400px'} selected={selected} {...data}>
|
||||
{isTool && (
|
||||
<>
|
||||
<Container>
|
||||
<RenderToolInput nodeId={nodeId} inputs={inputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
<Container>
|
||||
<IOTitle text={t('common:common.Input')} mb={-1} />
|
||||
<RenderInput
|
||||
nodeId={nodeId}
|
||||
flowInputList={commonInputs}
|
||||
CustomComponent={CustomComponent}
|
||||
/>
|
||||
</Container>
|
||||
<Container>
|
||||
<IOTitle text={t('common:common.Output')} />
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
<ConfirmModal />
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeCode);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
|
||||
@@ -10,14 +10,14 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
input: FlowNodeInputItemType;
|
||||
RightComponent?: React.JSX.Element;
|
||||
};
|
||||
|
||||
const InputLabel = ({ nodeId, input }: Props) => {
|
||||
const InputLabel = ({ nodeId, input, RightComponent }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
@@ -68,11 +68,11 @@ const InputLabel = ({ nodeId, input }: Props) => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Variable picker tip */}
|
||||
{input.renderTypeList[input.selectedTypeIndex ?? 0] === FlowNodeInputTypeEnum.textarea && (
|
||||
{/* Right Component */}
|
||||
{RightComponent && (
|
||||
<>
|
||||
<Box flex={1} />
|
||||
<VariableTip transform={'translateY(2px)'} />
|
||||
<Box flex={'1'} />
|
||||
{RightComponent}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@@ -8,71 +8,72 @@ import InputLabel from './Label';
|
||||
import type { RenderInputProps } from './type';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const RenderList: {
|
||||
types: FlowNodeInputTypeEnum[];
|
||||
Component: React.ComponentType<RenderInputProps>;
|
||||
}[] = [
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.reference],
|
||||
const RenderList: Record<
|
||||
FlowNodeInputTypeEnum,
|
||||
| {
|
||||
Component: React.ComponentType<RenderInputProps>;
|
||||
LableRightComponent?: React.ComponentType<RenderInputProps>;
|
||||
}
|
||||
| undefined
|
||||
> = {
|
||||
[FlowNodeInputTypeEnum.reference]: {
|
||||
Component: dynamic(() => import('./templates/Reference'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.fileSelect],
|
||||
[FlowNodeInputTypeEnum.fileSelect]: {
|
||||
Component: dynamic(() => import('./templates/Reference'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.select],
|
||||
[FlowNodeInputTypeEnum.select]: {
|
||||
Component: dynamic(() => import('./templates/Select'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.numberInput],
|
||||
[FlowNodeInputTypeEnum.numberInput]: {
|
||||
Component: dynamic(() => import('./templates/NumberInput'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.switch],
|
||||
[FlowNodeInputTypeEnum.switch]: {
|
||||
Component: dynamic(() => import('./templates/Switch'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectApp],
|
||||
[FlowNodeInputTypeEnum.selectApp]: {
|
||||
Component: dynamic(() => import('./templates/SelectApp'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectLLMModel],
|
||||
[FlowNodeInputTypeEnum.selectLLMModel]: {
|
||||
Component: dynamic(() => import('./templates/SelectLLMModel'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.settingLLMModel],
|
||||
[FlowNodeInputTypeEnum.settingLLMModel]: {
|
||||
Component: dynamic(() => import('./templates/SettingLLMModel'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectDataset],
|
||||
Component: dynamic(() => import('./templates/SelectDataset'))
|
||||
[FlowNodeInputTypeEnum.selectDataset]: {
|
||||
Component: dynamic(() =>
|
||||
import('./templates/SelectDataset').then((mod) => mod.SelectDatasetRender)
|
||||
),
|
||||
LableRightComponent: dynamic(() =>
|
||||
import('./templates/SelectDataset').then((mod) => mod.SwitchAuthTmb)
|
||||
)
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectDatasetParamsModal],
|
||||
[FlowNodeInputTypeEnum.selectDatasetParamsModal]: {
|
||||
Component: dynamic(() => import('./templates/SelectDatasetParams'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.addInputParam],
|
||||
[FlowNodeInputTypeEnum.addInputParam]: {
|
||||
Component: dynamic(() => import('./templates/DynamicInputs/index'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.JSONEditor],
|
||||
[FlowNodeInputTypeEnum.JSONEditor]: {
|
||||
Component: dynamic(() => import('./templates/JsonEditor'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.settingDatasetQuotePrompt],
|
||||
[FlowNodeInputTypeEnum.settingDatasetQuotePrompt]: {
|
||||
Component: dynamic(() => import('./templates/SettingQuotePrompt'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.input],
|
||||
[FlowNodeInputTypeEnum.input]: {
|
||||
Component: dynamic(() => import('./templates/TextInput'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.textarea],
|
||||
Component: dynamic(() => import('./templates/Textarea'))
|
||||
}
|
||||
];
|
||||
[FlowNodeInputTypeEnum.textarea]: {
|
||||
Component: dynamic(() => import('./templates/Textarea')),
|
||||
LableRightComponent: dynamic(() =>
|
||||
import('./templates/Textarea').then((mod) => mod.TextareaRightComponent)
|
||||
)
|
||||
},
|
||||
|
||||
[FlowNodeInputTypeEnum.customVariable]: undefined,
|
||||
[FlowNodeInputTypeEnum.hidden]: undefined,
|
||||
[FlowNodeInputTypeEnum.custom]: undefined
|
||||
};
|
||||
|
||||
const hideLabelTypeList = [FlowNodeInputTypeEnum.addInputParam];
|
||||
|
||||
@@ -101,7 +102,7 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [feConfigs?.isPlus, flowInputList]);
|
||||
}, [filterProInputs]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -110,23 +111,41 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
|
||||
|
||||
const RenderComponent = (() => {
|
||||
if (renderType === FlowNodeInputTypeEnum.custom && CustomComponent?.[input.key]) {
|
||||
return <>{CustomComponent?.[input.key]({ ...input })}</>;
|
||||
return {
|
||||
Component: <>{CustomComponent?.[input.key]({ ...input })}</>
|
||||
};
|
||||
}
|
||||
|
||||
const Component = RenderList.find((item) => item.types.includes(renderType))?.Component;
|
||||
const RenderItem = RenderList[renderType];
|
||||
|
||||
if (!Component) return null;
|
||||
return <Component inputs={filterProInputs} item={input} nodeId={nodeId} />;
|
||||
if (!RenderItem) return null;
|
||||
|
||||
return {
|
||||
Component: (
|
||||
<RenderItem.Component inputs={filterProInputs} item={input} nodeId={nodeId} />
|
||||
),
|
||||
LableRightComponent: RenderItem.LableRightComponent ? (
|
||||
<RenderItem.LableRightComponent
|
||||
inputs={filterProInputs}
|
||||
item={input}
|
||||
nodeId={nodeId}
|
||||
/>
|
||||
) : undefined
|
||||
};
|
||||
})();
|
||||
|
||||
return (
|
||||
<Box key={input.key} _notLast={{ mb }} position={'relative'}>
|
||||
{!!input.label && !hideLabelTypeList.includes(renderType) && (
|
||||
<InputLabel nodeId={nodeId} input={input} />
|
||||
<InputLabel
|
||||
nodeId={nodeId}
|
||||
input={input}
|
||||
RightComponent={RenderComponent?.LableRightComponent}
|
||||
/>
|
||||
)}
|
||||
{!!RenderComponent && (
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{RenderComponent}
|
||||
{RenderComponent.Component}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Grid, Switch, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { SelectedDatasetType } from '@fastgpt/global/core/workflow/api';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
@@ -12,12 +12,17 @@ import dynamic from 'next/dynamic';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
|
||||
|
||||
const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
export const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
inputs = [],
|
||||
item,
|
||||
nodeId
|
||||
}: RenderInputProps) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const [data, setData] = useState({
|
||||
@@ -80,8 +85,9 @@ const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) =>
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
h={10}
|
||||
border={theme.borders.base}
|
||||
borderColor={'myGray.200'}
|
||||
boxShadow={'sm'}
|
||||
bg={'white'}
|
||||
border={'base'}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
@@ -128,11 +134,47 @@ const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) =>
|
||||
onOpenDatasetSelect,
|
||||
selectedDatasets,
|
||||
selectedDatasetsValue,
|
||||
t,
|
||||
theme.borders.base
|
||||
t
|
||||
]);
|
||||
|
||||
return Render;
|
||||
};
|
||||
});
|
||||
|
||||
export default React.memo(SelectDatasetRender);
|
||||
export const SwitchAuthTmb = React.memo(function SwitchAuthTmb({
|
||||
inputs = [],
|
||||
item,
|
||||
nodeId
|
||||
}: RenderInputProps) {
|
||||
const { t } = useTranslation();
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const authTmbIdInput = useMemo(
|
||||
() => inputs.find((v) => v.key === NodeInputKeyEnum.authTmbId),
|
||||
[inputs]
|
||||
);
|
||||
|
||||
return authTmbIdInput ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontSize={'sm'}>{t('workflow:auth_tmb_id')}</Box>
|
||||
<QuestionTip label={t('workflow:auth_tmb_id_tip')} />
|
||||
<Switch
|
||||
ml={1}
|
||||
size={'sm'}
|
||||
isChecked={!!authTmbIdInput.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
key: NodeInputKeyEnum.authTmbId,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...authTmbIdInput,
|
||||
value: e.target.checked
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
) : null;
|
||||
});
|
||||
|
||||
export default SelectDatasetRender;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import { getEditorVariables } from '../../../../../utils';
|
||||
import { WorkflowNodeEdgeContext } from '../../../../../context/workflowInitContext';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip';
|
||||
|
||||
const TextareaRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -84,3 +85,10 @@ const TextareaRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
};
|
||||
|
||||
export default React.memo(TextareaRender);
|
||||
|
||||
export const TextareaRightComponent = React.memo(function TextareaRightComponent({
|
||||
item,
|
||||
nodeId
|
||||
}: RenderInputProps) {
|
||||
return <VariableTip transform={'translateY(2px)'} />;
|
||||
});
|
||||
|
||||
@@ -141,8 +141,6 @@ export const useChatTest = ({
|
||||
chatId={chatId}
|
||||
showMarkIcon
|
||||
chatType="chat"
|
||||
showRawSource
|
||||
showNodeStatus
|
||||
onStartChat={startChat}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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,12 @@ 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="1rem"
|
||||
spacing={0.5}
|
||||
/>
|
||||
<PermissionIconText
|
||||
private={app.private}
|
||||
color={'myGray.500'}
|
||||
|
||||
@@ -27,13 +27,11 @@ const ChatHeader = ({
|
||||
history,
|
||||
showHistory,
|
||||
apps,
|
||||
onRouteToAppDetail,
|
||||
totalRecordsCount
|
||||
}: {
|
||||
history: ChatItemType[];
|
||||
showHistory?: boolean;
|
||||
apps?: AppListItemType[];
|
||||
onRouteToAppDetail?: () => void;
|
||||
totalRecordsCount: number;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -71,7 +69,7 @@ const ChatHeader = ({
|
||||
)}
|
||||
|
||||
{/* control */}
|
||||
{!isPlugin && <ToolMenu history={history} onRouteToAppDetail={onRouteToAppDetail} />}
|
||||
{!isPlugin && <ToolMenu history={history} />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ type HistoryItemType = {
|
||||
const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) => {
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const isUserChatPage = router.pathname === '/chat';
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -46,6 +45,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
|
||||
|
||||
const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name);
|
||||
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar);
|
||||
const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail);
|
||||
|
||||
const concatHistory = useMemo(() => {
|
||||
const formatHistories: HistoryItemType[] = histories.map((item) => {
|
||||
@@ -77,8 +77,8 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
|
||||
});
|
||||
|
||||
const canRouteToDetail = useMemo(
|
||||
() => appId && userInfo?.team.permission.hasWritePer,
|
||||
[appId, userInfo?.team.permission.hasWritePer]
|
||||
() => appId && userInfo?.team.permission.hasWritePer && showRouteToAppDetail,
|
||||
[appId, userInfo?.team.permission.hasWritePer, showRouteToAppDetail]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -287,7 +287,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
|
||||
</ScrollData>
|
||||
|
||||
{/* exec */}
|
||||
{!isPc && isUserChatPage && (
|
||||
{!isPc && !!canRouteToDetail && (
|
||||
<Flex
|
||||
mt={2}
|
||||
borderTop={theme.borders.base}
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
} from '@fastgpt/global/common/parentFolder/type';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
const SelectOneResource = dynamic(() => import('@/components/common/folder/SelectOneResource'));
|
||||
|
||||
@@ -22,6 +24,8 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
|
||||
const router = useRouter();
|
||||
const isTeamChat = router.pathname === '/chat/team';
|
||||
|
||||
const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail);
|
||||
|
||||
const getAppList = useCallback(async ({ parentId }: GetResourceFolderListProps) => {
|
||||
return getMyApps({
|
||||
parentId,
|
||||
@@ -50,34 +54,36 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} h={'100%'}>
|
||||
<Box mt={4} px={4}>
|
||||
{!isTeamChat && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
py={2}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'myGray.200' }}
|
||||
onClick={() => router.push('/app/list')}
|
||||
>
|
||||
<IconButton
|
||||
mr={3}
|
||||
icon={<MyIcon name={'common/backFill'} w={'1rem'} color={'primary.500'} />}
|
||||
bg={'white'}
|
||||
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
|
||||
size={'smSquare'}
|
||||
borderRadius={'50%'}
|
||||
aria-label={''}
|
||||
/>
|
||||
{t('common:core.chat.Exit Chat')}
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
{showRouteToAppDetail && (
|
||||
<>
|
||||
<Box mt={4} px={4}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
py={2}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'myGray.200' }}
|
||||
onClick={() => router.push('/app/list')}
|
||||
>
|
||||
<IconButton
|
||||
mr={3}
|
||||
icon={<MyIcon name={'common/backFill'} w={'1rem'} color={'primary.500'} />}
|
||||
bg={'white'}
|
||||
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
|
||||
size={'smSquare'}
|
||||
borderRadius={'50%'}
|
||||
aria-label={''}
|
||||
/>
|
||||
{t('common:core.chat.Exit Chat')}
|
||||
</Flex>
|
||||
</Box>
|
||||
<MyDivider h={2} my={1} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isTeamChat && (
|
||||
<>
|
||||
<MyDivider h={2} my={1} />
|
||||
<HStack
|
||||
px={4}
|
||||
my={2}
|
||||
|
||||
@@ -7,20 +7,19 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const ToolMenu = ({
|
||||
history,
|
||||
onRouteToAppDetail
|
||||
}: {
|
||||
history: ChatItemType[];
|
||||
onRouteToAppDetail?: () => void;
|
||||
}) => {
|
||||
const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { onExportChat } = useChatBox();
|
||||
|
||||
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
|
||||
const chatData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
|
||||
const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail);
|
||||
|
||||
return history.length > 0 ? (
|
||||
return (
|
||||
<MyMenu
|
||||
Button={
|
||||
<IconButton
|
||||
@@ -61,14 +60,14 @@ const ToolMenu = ({
|
||||
// }
|
||||
]
|
||||
},
|
||||
...(onRouteToAppDetail
|
||||
...(showRouteToAppDetail
|
||||
? [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: 'core/app/aiLight',
|
||||
label: t('app:app_detail'),
|
||||
onClick: onRouteToAppDetail
|
||||
onClick: () => router.push(`/app/detail?appId=${chatData.appId}`)
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -76,8 +75,6 @@ const ToolMenu = ({
|
||||
: [])
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<Box w={'28px'} h={'28px'} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const { userInfo } = useUserStore();
|
||||
const { setLastChatAppId, chatId, appId, outLinkAuthData } = useChatStore();
|
||||
|
||||
@@ -186,7 +187,6 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
apps={myApps}
|
||||
history={chatRecords}
|
||||
showHistory
|
||||
onRouteToAppDetail={() => router.push(`/app/detail?appId=${appId}`)}
|
||||
/>
|
||||
|
||||
{/* chat box */}
|
||||
@@ -208,8 +208,6 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
feedbackType={'user'}
|
||||
onStartChat={onStartChat}
|
||||
chatType={'chat'}
|
||||
showRawSource
|
||||
showNodeStatus
|
||||
isReady={!loading}
|
||||
/>
|
||||
)}
|
||||
@@ -221,8 +219,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Render = (props: { appId: string }) => {
|
||||
const { appId } = props;
|
||||
const Render = (props: { appId: string; isStandalone?: string }) => {
|
||||
const { appId, isStandalone } = props;
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
@@ -276,7 +274,12 @@ const Render = (props: { appId: string }) => {
|
||||
|
||||
return source === ChatSourceEnum.online ? (
|
||||
<ChatContextProvider params={chatHistoryProviderParams}>
|
||||
<ChatItemContextProvider>
|
||||
<ChatItemContextProvider
|
||||
showRouteToAppDetail={isStandalone !== '1'}
|
||||
showRouteToDatasetDetail={isStandalone !== '1'}
|
||||
isShowReadRawSource={true}
|
||||
showNodeStatus
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<Chat myApps={myApps} />
|
||||
</ChatRecordContextProvider>
|
||||
@@ -289,6 +292,7 @@ export async function getServerSideProps(context: any) {
|
||||
return {
|
||||
props: {
|
||||
appId: context?.query?.appId || '',
|
||||
isStandalone: context?.query?.isStandalone || '',
|
||||
...(await serviceSideProps(context, ['file', 'app', 'chat', 'workflow']))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,7 +55,6 @@ type Props = {
|
||||
const OutLink = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { showRawSource, showNodeStatus } = props;
|
||||
const {
|
||||
shareId = '',
|
||||
showHistory = '1',
|
||||
@@ -287,8 +286,6 @@ const OutLink = (props: Props) => {
|
||||
feedbackType={'user'}
|
||||
onStartChat={startChat}
|
||||
chatType="share"
|
||||
showRawSource={showRawSource}
|
||||
showNodeStatus={showNodeStatus}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
@@ -340,7 +337,12 @@ const Render = (props: Props) => {
|
||||
|
||||
return source === ChatSourceEnum.share ? (
|
||||
<ChatContextProvider params={chatHistoryProviderParams}>
|
||||
<ChatItemContextProvider>
|
||||
<ChatItemContextProvider
|
||||
showRouteToAppDetail={false}
|
||||
showRouteToDatasetDetail={false}
|
||||
isShowReadRawSource={props.showRawSource}
|
||||
showNodeStatus={props.showNodeStatus}
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<OutLink {...props} />
|
||||
</ChatRecordContextProvider>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import { getTeamChatInfo } from '@/web/core/chat/api';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -20,8 +20,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { InitChatResponse } from '@/global/core/chat/api';
|
||||
import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
@@ -226,8 +225,6 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
feedbackType={'user'}
|
||||
onStartChat={startChat}
|
||||
chatType="team"
|
||||
showRawSource
|
||||
showNodeStatus
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
@@ -299,7 +296,12 @@ const Render = (props: Props) => {
|
||||
|
||||
return source === ChatSourceEnum.team ? (
|
||||
<ChatContextProvider params={contextParams}>
|
||||
<ChatItemContextProvider>
|
||||
<ChatItemContextProvider
|
||||
showRouteToAppDetail={false}
|
||||
showRouteToDatasetDetail={false}
|
||||
isShowReadRawSource={true}
|
||||
showNodeStatus
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<Chat {...props} myApps={myApps} />
|
||||
</ChatRecordContextProvider>
|
||||
|
||||
@@ -111,8 +111,7 @@ const CollectionPageContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
isLoading: isGetting,
|
||||
pageNum,
|
||||
pageSize
|
||||
} = usePagination<DatasetCollectionsListItemType>({
|
||||
api: getDatasetCollections,
|
||||
} = usePagination(getDatasetCollections, {
|
||||
pageSize: 20,
|
||||
params: {
|
||||
datasetId,
|
||||
|
||||
@@ -49,7 +49,6 @@ const CustomAPIFileInput = () => {
|
||||
parentId: '',
|
||||
parentName: ''
|
||||
});
|
||||
const [parentUuid, setParentUuid] = useState<string>('');
|
||||
const [paths, setPaths] = useState<ParentTreePathItemType[]>([]);
|
||||
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
@@ -128,7 +127,7 @@ const CustomAPIFileInput = () => {
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(item: APIFileItem) => {
|
||||
if (item.type === 'folder') {
|
||||
if (item.hasChild) {
|
||||
setPaths((state) => [...state, { parentId: item.id, parentName: item.name }]);
|
||||
return setParent({
|
||||
parentId: item.id,
|
||||
@@ -251,6 +250,7 @@ const CustomAPIFileInput = () => {
|
||||
<Box fontSize={'sm'} fontWeight={'medium'} color={'myGray.900'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
{item.hasChild && <MyIcon name="core/chat/chevronRight" w={'18px'} ml={2} />}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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,12 @@ 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="1rem"
|
||||
spacing={0.5}
|
||||
/>
|
||||
<PermissionIconText
|
||||
flexShrink={0}
|
||||
private={dataset.private}
|
||||
|
||||
@@ -65,6 +65,10 @@ export const getScheduleTriggerApp = async () => {
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
uid: String(app.tmbId),
|
||||
runtimeNodes: storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes)),
|
||||
runtimeEdges: initWorkflowEdgeStatus(edges),
|
||||
|
||||
3
projects/app/src/types/app.d.ts
vendored
3
projects/app/src/types/app.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
9
projects/app/src/types/index.d.ts
vendored
9
projects/app/src/types/index.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -9,6 +9,12 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { AppChatConfigType, VariableItemType } from '@fastgpt/global/core/app/type';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
|
||||
type ContextProps = {
|
||||
showRouteToAppDetail: boolean;
|
||||
showRouteToDatasetDetail: boolean;
|
||||
isShowReadRawSource: boolean;
|
||||
showNodeStatus: boolean;
|
||||
};
|
||||
type ChatBoxDataType = {
|
||||
appId: string;
|
||||
title?: string;
|
||||
@@ -37,7 +43,7 @@ type ChatItemContextType = {
|
||||
chatBoxData: ChatBoxDataType;
|
||||
setChatBoxData: React.Dispatch<React.SetStateAction<ChatBoxDataType>>;
|
||||
isPlugin: boolean;
|
||||
};
|
||||
} & ContextProps;
|
||||
|
||||
export const ChatItemContext = createContext<ChatItemContextType>({
|
||||
ChatBoxRef: null,
|
||||
@@ -61,7 +67,15 @@ export const ChatItemContext = createContext<ChatItemContextType>({
|
||||
/*
|
||||
Chat 对象的上下文
|
||||
*/
|
||||
const ChatItemContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const ChatItemContextProvider = ({
|
||||
children,
|
||||
showRouteToAppDetail,
|
||||
showRouteToDatasetDetail,
|
||||
isShowReadRawSource,
|
||||
showNodeStatus
|
||||
}: {
|
||||
children: ReactNode;
|
||||
} & ContextProps) => {
|
||||
const ChatBoxRef = useRef<ChatComponentRef>(null);
|
||||
const variablesForm = useForm<ChatBoxInputFormType>();
|
||||
|
||||
@@ -113,16 +127,23 @@ const ChatItemContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
resetVariables,
|
||||
clearChatRecords
|
||||
clearChatRecords,
|
||||
showRouteToAppDetail,
|
||||
showRouteToDatasetDetail,
|
||||
isShowReadRawSource,
|
||||
showNodeStatus
|
||||
};
|
||||
}, [
|
||||
chatBoxData,
|
||||
setChatBoxData,
|
||||
clearChatRecords,
|
||||
isPlugin,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
resetVariables,
|
||||
variablesForm
|
||||
clearChatRecords,
|
||||
showRouteToAppDetail,
|
||||
showRouteToDatasetDetail,
|
||||
isShowReadRawSource,
|
||||
showNodeStatus
|
||||
]);
|
||||
|
||||
return <ChatItemContext.Provider value={contextValue}>{children}</ChatItemContext.Provider>;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/listV2`, data);
|
||||
export const getDatasetCollectionPathById = (parentId: string) =>
|
||||
GET<ParentTreePathItemType[]>(`/core/dataset/collection/paths`, { parentId });
|
||||
export const getDatasetCollectionById = (id: string) =>
|
||||
|
||||
@@ -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
|
||||
}))}
|
||||
|
||||
@@ -118,7 +118,7 @@ export const storeNode2FlowNode = ({
|
||||
toolDescription: t(templateInput.toolDescription ?? (storeInput.toolDescription as any)),
|
||||
|
||||
selectedTypeIndex: storeInput.selectedTypeIndex ?? templateInput.selectedTypeIndex,
|
||||
value: storeInput.value ?? templateInput.value,
|
||||
value: storeInput.value,
|
||||
valueType: storeInput.valueType ?? templateInput.valueType,
|
||||
label: storeInput.label ?? templateInput.label
|
||||
};
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user