v4.6.9-alpha (#918)

Co-authored-by: Mufei <327958099@qq.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-03-04 00:05:25 +08:00
committed by GitHub
parent f9f0b4bffd
commit 42a8184ea0
153 changed files with 4906 additions and 4307 deletions

View File

@@ -13,6 +13,7 @@ import { IMG_BLOCK_KEY } from '@fastgpt/global/core/chat/constants';
import { addDays } from 'date-fns';
import { useRequest } from '@/web/common/hooks/useRequest';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
enum FileTypeEnum {
@@ -35,8 +36,12 @@ const MessageInput = ({
isChatting,
TextareaDom,
showFileSelector = false,
resetInputVal
}: {
resetInputVal,
shareId,
outLinkUid,
teamId,
teamToken
}: OutLinkChatAuthProps & {
onChange?: (e: string) => void;
onSendMessage: (e: string) => void;
onStop: () => void;
@@ -47,7 +52,6 @@ const MessageInput = ({
}) => {
const [, startSts] = useTransition();
const { shareId } = useRouter().query as { shareId?: string };
const {
isSpeaking,
isTransCription,
@@ -56,7 +60,7 @@ const MessageInput = ({
speakingTimeString,
renderAudioGraph,
stream
} = useSpeech({ shareId });
} = useSpeech({ shareId, outLinkUid, teamId, teamToken });
const { isPc } = useSystemStore();
const canvasRef = useRef<HTMLCanvasElement>(null);
const { t } = useTranslation();
@@ -82,7 +86,10 @@ const MessageInput = ({
maxSize: 1024 * 1024 * 5,
// 30 day expired.
expiredTime: addDays(new Date(), 7),
shareId
shareId,
outLinkUid,
teamId,
teamToken
});
setFileList((state) =>
state.map((item) =>
@@ -320,7 +327,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
rows={1}
height={'22px'}
lineHeight={'22px'}
maxHeight={'150px'}
maxHeight={'50vh'}
maxLength={-1}
overflowY={'auto'}
whiteSpace={'pre-wrap'}

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import MyModal from '../MyModal';
@@ -10,12 +10,12 @@ import RawSourceBox from '../core/dataset/RawSourceBox';
const QuoteModal = ({
rawSearch = [],
onClose,
isShare,
showDetail,
metadata
}: {
rawSearch: SearchDataResponseItemType[];
onClose: () => void;
isShare: boolean;
showDetail: boolean;
metadata?: {
collectionId: string;
sourceId?: string;
@@ -57,7 +57,7 @@ const QuoteModal = ({
}
>
<ModalBody>
<QuoteList rawSearch={filterResults} isShare={isShare} />
<QuoteList rawSearch={filterResults} showDetail={showDetail} />
</ModalBody>
</MyModal>
</>
@@ -68,10 +68,10 @@ export default QuoteModal;
export const QuoteList = React.memo(function QuoteList({
rawSearch = [],
isShare
showDetail
}: {
rawSearch: SearchDataResponseItemType[];
isShare: boolean;
showDetail: boolean;
}) {
const theme = useTheme();
@@ -88,7 +88,7 @@ export const QuoteList = React.memo(function QuoteList({
_hover={{ '& .hover-data': { display: 'flex' } }}
bg={i % 2 === 0 ? 'white' : 'myWhite.500'}
>
<QuoteItem quoteItem={item} canViewSource={!isShare} linkToDataset={!isShare} />
<QuoteItem quoteItem={item} canViewSource={showDetail} linkToDataset={showDetail} />
</Box>
))}
</>

View File

@@ -1,7 +1,7 @@
import React, { useMemo, useState } from 'react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { Flex, BoxProps, useDisclosure, Image, useTheme, Box } from '@chakra-ui/react';
import { Flex, BoxProps, useDisclosure, useTheme, Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
@@ -20,10 +20,10 @@ const WholeResponseModal = dynamic(() => import('./WholeResponseModal'), { ssr:
const ResponseTags = ({
responseData = [],
isShare
showDetail
}: {
responseData?: ChatHistoryItemResType[];
isShare: boolean;
showDetail: boolean;
}) => {
const theme = useTheme();
const { isPc } = useSystemStore();
@@ -76,13 +76,13 @@ const ResponseTags = ({
sourceName: item.sourceName,
sourceId: item.sourceId,
icon: getSourceNameIcon({ sourceId: item.sourceId, sourceName: item.sourceName }),
canReadQuote: !isShare || strIsLink(item.sourceId),
canReadQuote: showDetail || strIsLink(item.sourceId),
collectionId: item.collectionId
})),
historyPreview: chatData?.historyPreview,
runningTime: +responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0).toFixed(2)
};
}, [isShare, responseData]);
}, [showDetail, responseData]);
const TagStyles: BoxProps = {
mr: 2,
@@ -134,7 +134,7 @@ const ResponseTags = ({
</Flex>
</>
)}
{!isShare && (
{showDetail && (
<Flex alignItems={'center'} mt={3} flexWrap={'wrap'}>
{quoteList.length > 0 && (
<MyTooltip label="查看引用">
@@ -187,7 +187,7 @@ const ResponseTags = ({
{!!quoteModalData && (
<QuoteModal
{...quoteModalData}
isShare={isShare}
showDetail={showDetail}
onClose={() => setQuoteModalData(undefined)}
/>
)}
@@ -195,7 +195,11 @@ const ResponseTags = ({
<ContextModal context={contextModalData} onClose={() => setContextModalData(undefined)} />
)}
{isOpenWholeModal && (
<WholeResponseModal response={responseData} isShare={isShare} onClose={onCloseWholeModal} />
<WholeResponseModal
response={responseData}
showDetail={showDetail}
onClose={onCloseWholeModal}
/>
)}
</>
);

View File

@@ -51,11 +51,11 @@ function Row({
const WholeResponseModal = ({
response,
isShare,
showDetail,
onClose
}: {
response: ChatHistoryItemResType[];
isShare: boolean;
showDetail: boolean;
onClose: () => void;
}) => {
const { t } = useTranslation();
@@ -78,7 +78,7 @@ const WholeResponseModal = ({
}
>
<Flex h={'100%'} flexDirection={'column'}>
<ResponseBox response={response} isShare={isShare} />
<ResponseBox response={response} showDetail={showDetail} />
</Flex>
</MyModal>
);
@@ -88,10 +88,10 @@ export default WholeResponseModal;
const ResponseBox = React.memo(function ResponseBox({
response,
isShare
showDetail
}: {
response: ChatHistoryItemResType[];
isShare: boolean;
showDetail: boolean;
}) {
const theme = useTheme();
const { t } = useTranslation();
@@ -142,10 +142,7 @@ const ResponseBox = React.memo(function ResponseBox({
value={`${activeModule?.runningTime || 0}s`}
/>
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
<Row
label={t('support.wallet.usage.Chars length')}
value={`${activeModule?.charsLength}`}
/>
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
<Row
label={t('core.chat.response.context total length')}
@@ -188,7 +185,7 @@ const ResponseBox = React.memo(function ResponseBox({
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
<Row
label={t('core.chat.response.module quoteList')}
rawDom={<QuoteList isShare={isShare} rawSearch={activeModule.quoteList} />}
rawDom={<QuoteList showDetail={showDetail} rawSearch={activeModule.quoteList} />}
/>
)}
</>
@@ -280,7 +277,7 @@ const ResponseBox = React.memo(function ResponseBox({
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
<Row
label={t('core.chat.response.Plugin Resonse Detail')}
rawDom={<ResponseBox response={activeModule.pluginDetail} isShare={isShare} />}
rawDom={<ResponseBox response={activeModule.pluginDetail} showDetail={showDetail} />}
/>
)}
</>

View File

@@ -68,6 +68,7 @@ import type { AppTTSConfigType, VariableItemType } from '@fastgpt/global/core/mo
import MessageInput from './MessageInput';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import ChatBoxDivider from '../core/chat/Divider';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
@@ -106,7 +107,7 @@ const MessageCardStyle: BoxProps = {
maxW: ['calc(100% - 25px)', 'calc(100% - 40px)']
};
type Props = {
type Props = OutLinkChatAuthProps & {
feedbackType?: `${FeedbackTypeEnum}`;
showMarkIcon?: boolean; // admin mark dataset
showVoiceIcon?: boolean;
@@ -120,9 +121,6 @@ type Props = {
// not chat test params
appId?: string;
chatId?: string;
shareId?: string;
shareTeamId?: string;
outLinkUid?: string;
onUpdateVariable?: (e: Record<string, any>) => void;
onStartChat?: (e: StartChatFnProps) => Promise<{
@@ -147,8 +145,9 @@ const ChatBox = (
appId,
chatId,
shareId,
shareTeamId,
outLinkUid,
teamId,
teamToken,
onUpdateVariable,
onStartChat,
onDelMessage
@@ -288,7 +287,10 @@ const ChatBox = (
const result = await postQuestionGuide(
{
messages: adaptChat2GptMessages({ messages: history, reserveId: false }).slice(-6),
shareId
shareId,
outLinkUid,
teamId,
teamToken
},
abortSignal
);
@@ -300,7 +302,7 @@ const ChatBox = (
}
} catch (error) {}
},
[questionGuide, shareId]
[questionGuide, shareId, outLinkUid, teamId, teamToken]
);
/**
@@ -398,22 +400,20 @@ const ChatBox = (
};
})
);
if (!shareTeamId) {
setTimeout(() => {
createQuestionGuide({
history: newChatList.map((item, i) =>
i === newChatList.length - 1
? {
...item,
value: responseText
}
: item
)
});
generatingScroll();
isPc && TextareaDom.current?.focus();
}, 100);
}
setTimeout(() => {
createQuestionGuide({
history: newChatList.map((item, i) =>
i === newChatList.length - 1
? {
...item,
value: responseText
}
: item
)
});
generatingScroll();
isPc && TextareaDom.current?.focus();
}, 100);
} catch (err: any) {
toast({
title: t(getErrText(err, 'core.chat.error.Chat error')),
@@ -622,6 +622,7 @@ const ChatBox = (
{/* control icon */}
<Flex w={'100%'} alignItems={'center'} justifyContent={'flex-end'}>
<ChatControllerComponent
isChatting={isChatting}
chat={item}
onDelete={
onDelMessage
@@ -654,12 +655,17 @@ const ChatBox = (
<ChatAvatar src={appAvatar} type={'AI'} />
{/* control icon */}
<ChatControllerComponent
isChatting={isChatting}
ml={2}
chat={item}
setChatHistory={setChatHistory}
display={index === chatHistory.length - 1 && isChatting ? 'none' : 'flex'}
showVoiceIcon={showVoiceIcon}
ttsConfig={ttsConfig}
shareId={shareId}
outLinkUid={outLinkUid}
teamId={teamId}
teamToken={teamToken}
onDelete={
onDelMessage
? () => {
@@ -829,7 +835,10 @@ const ChatBox = (
isChatting={index === chatHistory.length - 1 && isChatting}
/>
<ResponseTags responseData={item.responseData} isShare={!!shareId} />
<ResponseTags
responseData={item.responseData}
showDetail={!shareId && !teamId}
/>
{/* custom feedback */}
{item.customFeedbacks && item.customFeedbacks.length > 0 && (
@@ -909,6 +918,10 @@ const ChatBox = (
TextareaDom={TextareaDom}
resetInputVal={resetInputVal}
showFileSelector={showFileSelector}
shareId={shareId}
outLinkUid={outLinkUid}
teamId={teamId}
teamToken={teamToken}
/>
)}
{/* user feedback modal */}
@@ -1236,6 +1249,7 @@ function Empty() {
}
const ChatControllerComponent = React.memo(function ChatControllerComponent({
isChatting,
chat,
setChatHistory,
display,
@@ -1249,8 +1263,13 @@ const ChatControllerComponent = React.memo(function ChatControllerComponent({
onAddUserDislike,
onAddUserLike,
ml,
mr
}: {
mr,
shareId,
outLinkUid,
teamId,
teamToken
}: OutLinkChatAuthProps & {
isChatting: boolean;
chat: ChatSiteItemType;
setChatHistory?: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
showVoiceIcon?: boolean;
@@ -1267,7 +1286,11 @@ const ChatControllerComponent = React.memo(function ChatControllerComponent({
const { t } = useTranslation();
const { copyData } = useCopyData();
const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({
ttsConfig
ttsConfig,
shareId,
outLinkUid,
teamId,
teamToken
});
const controlIconStyle = {
w: '14px',
@@ -1296,7 +1319,7 @@ const ChatControllerComponent = React.memo(function ChatControllerComponent({
onClick={() => copyData(chat.value)}
/>
</MyTooltip>
{!!onDelete && (
{!!onDelete && !isChatting && (
<>
{onRetry && (
<MyTooltip label={t('core.chat.retry')}>

View File

@@ -1,103 +0,0 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
Menu,
MenuButton,
MenuList,
MenuItemOption,
MenuOptionGroup,
Flex,
TagLabel,
TagCloseButton,
HStack,
Tag,
Input
} from '@chakra-ui/react';
import type { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
const TagEdit = ({
defaultValues,
teamsTags,
setSelectedTags
}: {
defaultValues: [];
teamsTags: Array<TeamTagsSchema>;
setSelectedTags: (item: Array<string>) => void;
}) => {
const [teamTagsOptions, setTeamTagsOptions] = useState(teamsTags);
const setSelectTeamsTags = (item: any) => {
setSelectedTags(item);
};
useMemo(() => {
setTeamTagsOptions(teamsTags);
}, [teamsTags]);
return (
<>
<Menu closeOnSelect={false}>
<MenuButton className="menu-btn" maxHeight={'250'} minWidth={'80%'}>
<HStack
style={{
border: 'solid 2px #f3f3f3',
borderRadius: '5px',
padding: '3px',
flexWrap: 'wrap',
minHeight: '40px'
}}
>
{teamsTags.map((item: TeamTagsSchema, index: number) => {
const key: string = item?.key;
if (defaultValues.indexOf(key as never) > -1) {
return (
<Tag
key={index}
size={'md'}
colorScheme="red"
// maxWidth={"100px"}
borderRadius="full"
>
<TagLabel> {item.label}</TagLabel>
<TagCloseButton />
</Tag>
);
}
})}
</HStack>
</MenuButton>
<MenuList style={{ height: '300px', overflow: 'scroll' }}>
<Input
style={{ border: 'none', borderBottom: 'solid 1px #f6f6f6' }}
placeholder="pleace "
onChange={(e: any) => {
// 对用户输入的搜索文本进行小写转换,以实现不区分大小写的搜索
const searchLower: string = e?.nativeEvent?.data || '';
// 使用filter方法来过滤列表只返回包含搜索文本的项
const resultList = teamsTags.filter((item) => {
const searchValue = item.label || '';
// 对列表中的每一项也进行小写转换
return searchValue.includes(searchLower);
});
!searchLower ? setTeamTagsOptions(teamsTags) : setTeamTagsOptions(resultList);
}}
/>
<MenuOptionGroup
defaultValue={defaultValues}
type="checkbox"
style={{ height: '300px', overflow: 'scroll' }}
onChange={(e) => {
setSelectTeamsTags(e);
}}
>
{teamTagsOptions.map((item, index) => {
return (
<MenuItemOption key={index} value={item.key}>
{item?.label}
</MenuItemOption>
);
})}
</MenuOptionGroup>
</MenuList>
</Menu>
</>
);
};
export default TagEdit;

View File

@@ -8,7 +8,7 @@ import { DatasetTypeMap } from '@fastgpt/global/core/dataset/constants';
const DatasetTypeTag = ({ type, ...props }: { type: `${DatasetTypeEnum}` } & FlexProps) => {
const { t } = useTranslation();
const item = DatasetTypeMap[type];
const item = DatasetTypeMap[type] || DatasetTypeMap['dataset'];
return (
<Flex

View File

@@ -301,10 +301,22 @@ function RenderHttpProps({
headers &&
jsonBody &&
{
[TabEnum.params]: <RenderForm moduleId={moduleId} input={params} variables={variables} />,
[TabEnum.params]: (
<RenderForm
moduleId={moduleId}
input={params}
variables={variables}
tabType={TabEnum.params}
/>
),
[TabEnum.body]: <RenderJson moduleId={moduleId} variables={variables} input={jsonBody} />,
[TabEnum.headers]: (
<RenderForm moduleId={moduleId} input={headers} variables={variables} />
<RenderForm
moduleId={moduleId}
input={headers}
variables={variables}
tabType={TabEnum.headers}
/>
)
}[selectedTab]}
</Box>
@@ -313,11 +325,13 @@ function RenderHttpProps({
const RenderForm = ({
moduleId,
input,
variables
variables,
tabType
}: {
moduleId: string;
input: FlowNodeInputItemType;
variables: EditorVariablePickerType[];
tabType?: TabEnum;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
@@ -327,11 +341,52 @@ const RenderForm = ({
const [shouldUpdateNode, setShouldUpdateNode] = useState(false);
const leftVariables = useMemo(() => {
return variables.filter((variable) => {
const HttpHeaders = [
{ key: 'A-IM', label: 'A-IM' },
{ key: 'Accept', label: 'Accept' },
{ key: 'Accept-Charset', label: 'Accept-Charset' },
{ key: 'Accept-Encoding', label: 'Accept-Encoding' },
{ key: 'Accept-Language', label: 'Accept-Language' },
{ key: 'Accept-Datetime', label: 'Accept-Datetime' },
{ key: 'Access-Control-Request-Method', label: 'Access-Control-Request-Method' },
{ key: 'Access-Control-Request-Headers', label: 'Access-Control-Request-Headers' },
{ key: 'Authorization', label: 'Authorization' },
{ key: 'Cache-Control', label: 'Cache-Control' },
{ key: 'Connection', label: 'Connection' },
{ key: 'Content-Length', label: 'Content-Length' },
{ key: 'Content-Type', label: 'Content-Type' },
{ key: 'Cookie', label: 'Cookie' },
{ key: 'Date', label: 'Date' },
{ key: 'Expect', label: 'Expect' },
{ key: 'Forwarded', label: 'Forwarded' },
{ key: 'From', label: 'From' },
{ key: 'Host', label: 'Host' },
{ key: 'If-Match', label: 'If-Match' },
{ key: 'If-Modified-Since', label: 'If-Modified-Since' },
{ key: 'If-None-Match', label: 'If-None-Match' },
{ key: 'If-Range', label: 'If-Range' },
{ key: 'If-Unmodified-Since', label: 'If-Unmodified-Since' },
{ key: 'Max-Forwards', label: 'Max-Forwards' },
{ key: 'Origin', label: 'Origin' },
{ key: 'Pragma', label: 'Pragma' },
{ key: 'Proxy-Authorization', label: 'Proxy-Authorization' },
{ key: 'Range', label: 'Range' },
{ key: 'Referer', label: 'Referer' },
{ key: 'TE', label: 'TE' },
{ key: 'User-Agent', label: 'User-Agent' },
{ key: 'Upgrade', label: 'Upgrade' },
{ key: 'Via', label: 'Via' },
{ key: 'Warning', label: 'Warning' },
{ key: 'Dnt', label: 'Dnt' },
{ key: 'X-Requested-With', label: 'X-Requested-With' },
{ key: 'X-CSRF-Token', label: 'X-CSRF-Token' }
];
return (tabType === TabEnum.headers ? HttpHeaders : variables).filter((variable) => {
const existVariables = list.map((item) => item.key);
return !existVariables.includes(variable.key);
});
}, [list, variables]);
}, [list, tabType, variables]);
useEffect(() => {
setList(input.value || []);
@@ -378,16 +433,23 @@ const RenderForm = ({
};
const handleAddNewProps = (key: string, value: string = '') => {
const checkExist = list.find((item) => item.key === key);
if (checkExist) {
return toast({
status: 'warning',
title: t('core.module.http.Key already exists')
});
}
if (!key) return;
setList((prevList) => {
if (!key) {
return prevList;
}
const checkExist = prevList.find((item) => item.key === key);
if (checkExist) {
setUpdateTrigger((prev) => !prev);
toast({
status: 'warning',
title: t('core.module.http.Key already exists')
});
return prevList;
}
return [...prevList, { key, type: 'string', value }];
});
setList((prevList) => [...prevList, { key, type: 'string', value }]);
setShouldUpdateNode(true);
};
@@ -406,7 +468,7 @@ const RenderForm = ({
<Td p={0} w={'150px'}>
<HttpInput
hasVariablePlugin={false}
hasDropDownPlugin={true}
hasDropDownPlugin={tabType === TabEnum.headers}
setDropdownValue={(value) => {
handleKeyChange(index, value);
setUpdateTrigger((prev) => !prev);
@@ -450,16 +512,19 @@ const RenderForm = ({
<Tr>
<Td p={0} w={'150px'}>
<HttpInput
hasDropDownPlugin={true}
hasVariablePlugin={false}
hasDropDownPlugin={tabType === TabEnum.headers}
setDropdownValue={(val) => {
handleAddNewProps(val);
setUpdateTrigger((prev) => !prev);
}}
placeholder={t('core.module.http.Add props')}
value={''}
h={40}
variables={leftVariables}
updateTrigger={updateTrigger}
onBlur={(val) => {
handleAddNewProps(val);
setUpdateTrigger((prev) => !prev);
}}
/>
</Td>
@@ -490,7 +555,7 @@ const RenderJson = ({
<Box mt={1}>
<JSONEditor
bg={'myGray.50'}
height={200}
defaultHeight={200}
resize
value={input.value}
placeholder={t('core.module.template.http body placeholder')}

View File

@@ -9,9 +9,7 @@ import {
putSwitchTeam,
putUpdateMember,
delRemoveMember,
delLeaveTeam,
getTeamsTags,
insertTeamsTags
delLeaveTeam
} from '@/web/support/user/team/api';
import {
Box,
@@ -49,7 +47,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
const EditModal = dynamic(() => import('./EditModal'));
const InviteModal = dynamic(() => import('./InviteModal'));
const TeamTagsAsync = dynamic(() => import('../TeamTagsAsync'));
const TeamTagModal = dynamic(() => import('../TeamTagModal'));
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
@@ -57,7 +55,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
const { toast } = useToast();
const { teamPlanStatus } = useUserStore();
const { feConfigs } = useSystemStore();
const [teamsTags, setTeamTags] = useState<any>();
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
@@ -87,8 +84,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
mutationFn: async (teamId: string) => {
const token = await putSwitchTeam(teamId);
token && setToken(token);
// get team tags
await getTeamsTags(teamId);
return initUserInfo();
},
errorToast: t('user.team.Switch Team Failed')
@@ -99,11 +94,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
['getMembers', userInfo?.team?.teamId],
() => {
if (!userInfo?.team?.teamId) return [];
// get team tags
getTeamsTags(userInfo.team.teamId).then((res: any) => {
setTeamTags(res);
});
return getTeamMembers(userInfo.team.teamId);
}
);
@@ -217,17 +207,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
: {})}
>
{team.teamName}
{/* {userInfo?.team?.teamId === team.teamId && (
<HStack spacing={1}>
{teamsTags.slice(0, 3).map((item: any, index) => {
return (
<Tag key={index} size={'sm'} variant="outline" colorScheme="blue">
{item.label}
</Tag>
);
})}
</HStack>
)} */}
</Box>
{userInfo?.team?.teamId === team.teamId ? (
<MyIcon name={'common/tickFill'} w={'16px'} color={'primary.500'} />
@@ -290,31 +269,32 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
{members.length}
</Box>
{userInfo.team.role === TeamMemberRoleEnum.owner &&
teamPlanStatus?.standardConstants &&
teamPlanStatus.standardConstants.maxTeamMember > members.length && (
<Button
variant={'whitePrimary'}
size="sm"
borderRadius={'md'}
ml={3}
leftIcon={
<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />
{userInfo.team.role === TeamMemberRoleEnum.owner && (
<Button
variant={'whitePrimary'}
size="sm"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />}
onClick={() => {
if (
teamPlanStatus?.standardConstants?.maxTeamMember &&
teamPlanStatus.standardConstants.maxTeamMember <= members.length
) {
toast({
status: 'warning',
title: t('user.team.Over Max Member Tip', {
max: teamPlanStatus.standardConstants.maxTeamMember
})
});
} else {
onOpenInvite();
}
onClick={() => {
if (userInfo.team.maxSize <= members.length) {
toast({
status: 'warning',
title: t('user.team.Over Max Member Tip', { max: userInfo.team.maxSize })
});
} else {
onOpenInvite();
}
}}
>
{t('user.team.Invite Member')}
</Button>
)}
}}
>
{t('user.team.Invite Member')}
</Button>
)}
{userInfo.team.role === TeamMemberRoleEnum.owner && feConfigs?.show_team_chat && (
<Button
variant={'whitePrimary'}
@@ -323,14 +303,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
ml={3}
leftIcon={<DragHandleIcon w={'14px'} color={'primary.500'} />}
onClick={() => {
if (userInfo.team.maxSize <= members.length) {
toast({
status: 'warning',
title: t('user.team.Team Tags Async', { max: userInfo.team.maxSize })
});
} else {
onOpenTeamTagsAsync();
}
onOpenTeamTagsAsync();
}}
>
{t('user.team.Team Tags Async')}
@@ -492,13 +465,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
onSuccess={refetchMembers}
/>
)}
{isOpenTeamTagsAsync && (
<TeamTagsAsync
teamInfo={teamsTags?.tagsUrl}
teamsTags={teamsTags?.list || []}
onClose={onCloseTeamTagsAsync}
/>
)}
{isOpenTeamTagsAsync && <TeamTagModal onClose={onCloseTeamTagsAsync} />}
<ConfirmRemoveMemberModal />
<ConfirmLeaveTeamModal />
</>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React from 'react';
import MyModal from '@/components/MyModal';
import {
Box,
@@ -11,61 +11,74 @@ import {
HStack,
Avatar
} from '@chakra-ui/react';
import { AttachmentIcon, CopyIcon, DragHandleIcon } from '@chakra-ui/icons';
import { putUpdateTeamTags, updateTags } from '@/web/support/user/team/api';
import { useForm } from 'react-hook-form';
import { putUpdateTeam } from '@/web/support/user/team/api';
import { useFieldArray, useForm } from 'react-hook-form';
import { useTranslation } from 'next-i18next';
import type { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
import type { TeamTagItemType } from '@fastgpt/global/support/user/team/type';
import { useRequest } from '@/web/common/hooks/useRequest';
import { RepeatIcon } from '@chakra-ui/icons';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useQuery } from '@tanstack/react-query';
import { getTeamsTags, loadTeamTagsByDomain } from '@/web/support/user/team/api';
const TeamTagsAsync = ({
teamsTags,
teamInfo,
onClose
}: {
teamsTags: Array<TeamTagsSchema>;
teamInfo: any;
onClose: () => void;
}) => {
type FormType = {
teamDomain: string;
tags: TeamTagItemType[];
};
const TeamTagsAsync = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { toast } = useToast();
const [_teamsTags, setTeamTags] = useState<Array<TeamTagsSchema>>(teamsTags);
const { register, setValue, getValues, handleSubmit } = useForm<any>({
defaultValues: { ...teamInfo }
});
const { userInfo, initUserInfo } = useUserStore();
const { copyData } = useCopyData();
const teamInfo = userInfo?.team;
if (!teamInfo) {
onClose();
return null;
}
const { register, control, handleSubmit } = useForm<FormType>({
defaultValues: {
teamDomain: teamInfo.teamDomain,
tags: []
}
});
const { fields: teamTags, replace: replaceTeamTags } = useFieldArray({
control,
name: 'tags'
});
const baseUrl = global.feConfigs?.customSharePageDomain || location?.origin;
const linkUrl = `${baseUrl}/chat/team?shareTeamId=${teamInfo?._id}${
getValues('showHistory') ? '' : '&showHistory=0'
}`;
const linkUrl = `${baseUrl}/chat/team?teamId=${teamInfo.teamId}&teamToken=`;
// tags Async
const { mutate: onclickAsync, isLoading: creating } = useRequest({
mutationFn: async (data: any) => {
return putUpdateTeamTags({ tagsUrl: data.tagsUrl, teamId: teamInfo?._id });
const { mutate: onclickUpdate, isLoading: isUpdating } = useRequest({
mutationFn: async (data: FormType) => {
return putUpdateTeam({ teamDomain: data.teamDomain, teamId: teamInfo?.teamId });
},
onSuccess(id: string) {
onSuccess() {
initUserInfo();
onClose();
},
successToast: t('user.team.Team Tags Async Success'),
errorToast: t('common.Create Failed')
});
const asyncTags = async () => {
console.log('getValues', getValues());
const res: Array<TeamTagsSchema> = await updateTags(teamInfo?._id, getValues().tagsUrl);
setTeamTags(res);
toast({ status: 'success', title: '团队标签同步成功' });
};
useEffect(() => {
console.log('teamInfo', teamInfo);
}, []);
const { mutate: onclickTagAsync, isLoading: isSyncing } = useRequest({
mutationFn: (data: FormType) => loadTeamTagsByDomain(data.teamDomain),
onSuccess(res) {
replaceTeamTags(res);
},
successToast: t('support.user.team.Team Tags Async Success')
});
useQuery(['getTeamsTags'], getTeamsTags, {
onSuccess: (data) => {
replaceTeamTags(data);
}
});
// 获取
return (
<>
<MyModal
@@ -80,7 +93,7 @@ const TeamTagsAsync = ({
overflow={'hidden'}
title={
<Box>
<Box>{teamInfo?.name}</Box>
<Box>{teamInfo?.teamName}</Box>
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
{'填写标签同步链接,点击同步按钮即可同步'}
</Box>
@@ -98,8 +111,8 @@ const TeamTagsAsync = ({
autoFocus
bg={'myWhite.600'}
placeholder="请输入同步标签"
{...register('tagsUrl', {
required: t('core.app.error.App name can not be empty')
{...register('teamDomain', {
required: true
})}
/>
</Flex>
@@ -146,7 +159,7 @@ const TeamTagsAsync = ({
}}
spacing={1}
>
{_teamsTags.map((item, index) => {
{teamTags.map((item, index) => {
return (
<Tag key={index} mt={2} size={'md'} colorScheme="red" borderRadius="full">
<Avatar
@@ -161,7 +174,13 @@ const TeamTagsAsync = ({
);
})}
</HStack>
<Button ml={4} size="md" leftIcon={<RepeatIcon />} onClick={asyncTags}>
<Button
isLoading={isSyncing}
ml={4}
size="md"
leftIcon={<RepeatIcon />}
onClick={handleSubmit((data) => onclickTagAsync(data))}
>
</Button>
</Flex>
@@ -170,7 +189,7 @@ const TeamTagsAsync = ({
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={creating} onClick={handleSubmit((data) => onclickAsync(data))}>
<Button isLoading={isUpdating} onClick={handleSubmit((data) => onclickUpdate(data))}>
{t('user.team.Tags Async')}
</Button>
</ModalFooter>