This commit is contained in:
Archer
2023-11-09 09:46:57 +08:00
committed by GitHub
parent 661ee79943
commit 8bb5588305
402 changed files with 9899 additions and 5967 deletions

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Image } from '@chakra-ui/react';
import type { ImageProps } from '@chakra-ui/react';
import { LOGO_ICON } from '@/constants/chat';
import { LOGO_ICON } from '@fastgpt/global/core/chat/constants';
const Avatar = ({ w = '30px', ...props }: ImageProps) => {
return (

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import { ChatItemType } from '@/types/chat';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
import MyModal from '../MyModal';
const ContextModal = ({

View File

@@ -16,6 +16,7 @@ import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/ty
import MyTooltip from '../MyTooltip';
import NextLink from 'next/link';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useUserStore } from '@/web/support/user/useUserStore';
const QuoteModal = ({
rawSearch = [],
@@ -28,14 +29,15 @@ const QuoteModal = ({
const { isPc } = useSystemStore();
const theme = useTheme();
const router = useRouter();
const { userInfo } = useUserStore();
const { toast } = useToast();
const { setIsLoading, Loading } = useLoading();
const [editInputData, setEditInputData] = useState<InputDataType>();
const [editInputData, setEditInputData] = useState<InputDataType & { datasetId: string }>();
const isShare = useMemo(() => router.pathname === '/chat/share', [router.pathname]);
/**
* click edit, get new kbDataItem
* click edit, get new DataItem
*/
const onclickEdit = useCallback(
async (item: InputDataType) => {
@@ -181,6 +183,7 @@ const QuoteModal = ({
</MyModal>
{editInputData && editInputData.id && (
<InputDataModal
canWrite={userInfo?.team?.canWrite || false}
onClose={() => setEditInputData(undefined)}
onSuccess={() => {
console.log('更新引用成功');

View File

@@ -1,5 +1,6 @@
import React, { useMemo, useState } from 'react';
import { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { Flex, BoxProps, useDisclosure, Image, useTheme } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';

View File

@@ -6,7 +6,7 @@ import MyIcon from '@/components/Icon';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import DatasetSelectModal, { useDatasetSelect } from '@/components/core/dataset/SelectModal';
import dynamic from 'next/dynamic';
import { MarkDataType } from '@/global/core/dataset/type';
import { AdminFbkType } from '@fastgpt/global/core/chat/type.d';
import SelectCollections from '@/web/core/dataset/components/SelectCollections';
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
@@ -28,7 +28,7 @@ const SelectMarkCollection = ({
adminMarkData: AdminMarkType;
setAdminMarkData: (e: AdminMarkType) => void;
onClose: () => void;
onSuccess: (adminFeedback: MarkDataType) => void;
onSuccess: (adminFeedback: AdminFbkType) => void;
}) => {
const { t } = useTranslation();
const theme = useTheme();
@@ -166,12 +166,12 @@ const SelectMarkCollection = ({
datasetId={adminMarkData.datasetId}
defaultValues={{
id: adminMarkData.dataId,
datasetId: adminMarkData.datasetId,
collectionId: adminMarkData.collectionId,
sourceName: '手动标注',
q: adminMarkData.q,
a: adminMarkData.a
}}
canWrite
onSuccess={(data) => {
if (!data.q || !adminMarkData.datasetId || !adminMarkData.collectionId || !data.id) {
return onClose();

View File

@@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react';
import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@/types/chat';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import { useTranslation } from 'react-i18next';
import { ModuleTemplatesFlat } from '@/constants/flow/ModuleTemplate';
import Tabs from '../Tabs';
@@ -8,7 +8,7 @@ import Tabs from '../Tabs';
import MyModal from '../MyModal';
import MyTooltip from '../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { formatPrice } from '@fastgpt/global/common/bill/tools';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
function Row({ label, value }: { label: string; value?: string | number | React.ReactNode }) {
const theme = useTheme();

View File

@@ -10,12 +10,9 @@ import React, {
} from 'react';
import Script from 'next/script';
import { throttle } from 'lodash';
import {
ChatHistoryItemResType,
ChatItemType,
ChatSiteItemType,
ExportChatType
} from '@/types/chat';
import type { ExportChatType } from '@/types/chat.d';
import type { ChatItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import { useToast } from '@/web/common/hooks/useToast';
import { useAudioPlay } from '@/web/common/utils/voice';
import { getErrText } from '@fastgpt/global/common/error/utils';
@@ -38,12 +35,12 @@ import { useMarkdown } from '@/web/common/hooks/useMarkdown';
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { VariableInputEnum } from '@/constants/app';
import { useForm } from 'react-hook-form';
import type { MessageItemType } from '@/types/core/chat/type';
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
import { fileDownload } from '@/web/common/file/utils';
import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { useTranslation } from 'react-i18next';
import { customAlphabet } from 'nanoid';
import { adminUpdateChatFeedback, userUpdateChatFeedback } from '@/web/core/chat/api';
@@ -64,6 +61,7 @@ const SelectMarkCollection = dynamic(() => import('./SelectMarkCollection'));
import styles from './index.module.scss';
import { postQuestionGuide } from '@/web/core/ai/api';
import { splitGuideModule } from '@/global/core/app/modules/utils';
import { AppTTSConfigType } from '@/types/app';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
@@ -73,7 +71,7 @@ type generatingMessageProps = { text?: string; name?: string; status?: 'running'
export type StartChatFnProps = {
chatList: ChatSiteItemType[];
messages: MessageItemType[];
messages: ChatMessageItemType[];
controller: AbortController;
variables: Record<string, any>;
generatingMessage: (e: generatingMessageProps) => void;
@@ -158,7 +156,7 @@ const ChatBox = (
[chatHistory]
);
const { welcomeText, variableModules, questionGuide } = useMemo(
const { welcomeText, variableModules, questionGuide, ttsConfig } = useMemo(
() => splitGuideModule(userGuideModule),
[userGuideModule]
);
@@ -206,32 +204,28 @@ const ChatBox = (
[]
);
// eslint-disable-next-line react-hooks/exhaustive-deps
const generatingMessage = useCallback(
// concat text to end of message
({ text = '', status, name }: generatingMessageProps) => {
setChatHistory((state) =>
state.map((item, index) => {
if (index !== state.length - 1) return item;
return {
...item,
...(text
? {
value: item.value + text
}
: {}),
...(status && name
? {
status,
moduleName: name
}
: {})
};
})
);
generatingScroll();
},
[generatingScroll, setChatHistory]
);
const generatingMessage = ({ text = '', status, name }: generatingMessageProps) => {
setChatHistory((state) =>
state.map((item, index) => {
if (index !== state.length - 1) return item;
return {
...item,
...(text
? {
value: item.value + text
}
: {}),
...(status && name
? {
status,
moduleName: name
}
: {})
};
})
);
generatingScroll();
};
// 重置输入内容
const resetInputVal = useCallback((val: string) => {
@@ -489,7 +483,7 @@ const ChatBox = (
return {
bg: colorMap[chatContent.status] || colorMap.loading,
name: t(chatContent.moduleName || 'common.Loading')
name: chatContent.moduleName || t('common.Loading')
};
}, [chatHistory, isChatting, t]);
/* style end */
@@ -654,8 +648,10 @@ const ChatBox = (
<ChatController
ml={2}
chat={item}
setChatHistory={setChatHistory}
display={index === chatHistory.length - 1 && isChatting ? 'none' : 'flex'}
showVoiceIcon={showVoiceIcon}
ttsConfig={ttsConfig}
onDelete={
onDelMessage
? () => {
@@ -801,13 +797,20 @@ const ChatBox = (
</Box>
{/* message input */}
{onStartChat && variableIsFinish && active ? (
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(750px, 100%)']} px={[0, 5]}>
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(800px, 100%)']} px={[0, 5]}>
<Box
py={'18px'}
position={'relative'}
boxShadow={`0 0 10px rgba(0,0,0,0.2)`}
borderTop={['1px solid', 0]}
borderTopColor={'myGray.200'}
{...(isPc
? {
border: '1px solid',
borderColor: 'rgba(0,0,0,0.12)'
}
: {
borderTop: '1px solid',
borderTopColor: 'rgba(0,0,0,0.15)'
})}
borderRadius={['none', 'md']}
backgroundColor={'white'}
>
@@ -836,6 +839,7 @@ const ChatBox = (
const textarea = e.target;
textarea.style.height = textareaMinH;
textarea.style.height = `${textarea.scrollHeight}px`;
setRefresh((state) => !state);
}}
onKeyDown={(e) => {
// enter send.(pc or iframe && enter and unPress shift)
@@ -852,11 +856,14 @@ const ChatBox = (
<Flex
alignItems={'center'}
justifyContent={'center'}
h={'25px'}
w={'25px'}
h={['26px', '32px']}
w={['26px', '32px']}
position={'absolute'}
right={['12px', '20px']}
bottom={'15px'}
right={['12px', '14px']}
bottom={['15px', '13px']}
borderRadius={'md'}
bg={TextareaDom.current?.value ? 'myBlue.600' : ''}
lineHeight={1}
>
{isChatting ? (
<MyIcon
@@ -869,16 +876,18 @@ const ChatBox = (
onClick={() => chatController.current?.abort('stop')}
/>
) : (
<MyIcon
name={'chatSend'}
width={['18px', '20px']}
height={['18px', '20px']}
cursor={'pointer'}
color={'gray.500'}
onClick={() => {
handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))();
}}
/>
<MyTooltip label={t('core.chat.Send Message')}>
<MyIcon
name={'core/chat/sendFill'}
width={'16px'}
height={'16px'}
cursor={'pointer'}
color={TextareaDom.current?.value ? 'white' : 'myBlue.600'}
onClick={() => {
handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))();
}}
/>
</MyTooltip>
)}
</Flex>
</Box>
@@ -1106,8 +1115,10 @@ function Empty() {
function ChatController({
chat,
setChatHistory,
display,
showVoiceIcon,
ttsConfig,
onReadFeedback,
onMark,
onRetry,
@@ -1117,7 +1128,9 @@ function ChatController({
mr
}: {
chat: ChatSiteItemType;
setChatHistory?: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
showVoiceIcon?: boolean;
ttsConfig?: AppTTSConfigType;
onRetry?: () => void;
onDelete?: () => void;
onMark?: () => void;
@@ -1127,7 +1140,9 @@ function ChatController({
const theme = useTheme();
const { t } = useTranslation();
const { copyData } = useCopyData();
const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({});
const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({
ttsConfig
});
const controlIconStyle = {
w: '14px',
cursor: 'pointer',
@@ -1198,7 +1213,24 @@ function ChatController({
{...controlIconStyle}
name={'voice'}
_hover={{ color: '#E74694' }}
onClick={() => playAudio(chat.value)}
onClick={async () => {
const buffer = await playAudio({
buffer: chat.ttsBuffer,
chatItemId: chat.dataId,
text: chat.value
});
if (!setChatHistory) return;
setChatHistory((state) =>
state.map((item) =>
item.dataId === chat.dataId
? {
...item,
ttsBuffer: buffer
}
: item
)
);
}}
/>
</MyTooltip>
))}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698673802976"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2726"
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
<path
d="M496.68187246 465.60374835c121.95586149 0 220.9345317-98.9786702 220.9345317-220.9345317s-98.9786702-220.9345317-220.9345317-220.93453167-220.9345317 98.9786702-220.93453167 220.93453167 98.9786702 220.9345317 220.93453167 220.9345317z m-125.04894492-345.98347662c33.43475913-33.28746944 77.76895517-51.69868042 125.04894492-51.69868042 47.27998978 0 91.61418581 18.41121097 125.04894495 51.69868042 33.43475913 33.43475913 51.69868042 77.76895517 51.69868041 125.04894492 0 47.27998978-18.41121097 91.61418581-51.69868041 125.04894495-33.43475913 33.43475913-77.76895517 51.69868042-125.04894495 51.69868042-47.27998978 0-91.61418581-18.41121097-125.04894492-51.69868042-33.43475913-33.43475913-51.69868042-77.76895517-51.69868041-125.04894495 0-47.27998978 18.41121097-91.61418581 51.69868041-125.04894492zM511.41084125 892.59655326h-213.5700473c-81.15661796 0-147.2896878-66.13306981-147.28968778-147.2896878 0-39.32634663 15.31812754-76.29605827 43.1558785-104.13380927 27.83775099-27.83775099 64.80746262-43.15587853 104.13380928-43.15587853h397.68215704c52.14054948 0 100.89343614 27.98504069 127.25829025 73.05568516 6.1861669 10.60485751 19.73681815 14.13981003 30.19438599 7.95364313 10.60485751-6.1861669 14.13981003-19.73681815 7.95364314-30.19438599-34.31849726-58.47400606-97.65306301-94.85455893-165.40631938-94.85455893h-397.68215704c-51.10952167 0-99.12595989 19.88410785-135.35922308 56.11737104-36.2332632 36.0859735-56.11737105 84.24970142-56.11737105 135.35922308 0 105.60670615 85.86988799 191.47659413 191.47659413 191.47659413h213.5700473c12.22504409 0 22.09345317-10.01569877 22.09345316-22.24074285s-9.86840908-22.09345317-22.09345316-22.09345317z"
p-id="2727"></path>
<path
d="M873.74347321 830.88217408h-103.10278144v-103.10278145c0-12.22504409-9.86840908-22.09345317-22.09345318-22.09345318s-22.09345317 9.86840908-22.09345316 22.09345318v103.10278145h-103.10278146c-12.22504409 0-22.09345317 9.86840908-22.09345318 22.09345315s9.86840908 22.09345317 22.09345318 22.09345318h103.10278146v103.10278146c0 12.22504409 9.86840908 22.09345317 22.09345316 22.09345315s22.09345317-9.86840908 22.09345318-22.09345315v-103.10278146h103.10278144c12.22504409 0 22.09345317-9.86840908 22.09345318-22.09345318s-9.86840908-22.09345317-22.09345318-22.09345315z"
p-id="2728"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1699444463613" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2759" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M512 58.02666667c250.31111111 0 453.97333333 203.66222222 453.97333333 453.97333333S762.31111111 965.97333333 512 965.97333333 58.02666667 762.31111111 58.02666667 512s203.66222222-453.97333333 453.97333333-453.97333333m0-47.78666667C234.83733333 10.24 10.24 234.83733333 10.24 512s224.59733333 501.76 501.76 501.76 501.76-224.59733333 501.76-501.76S789.16266667 10.24 512 10.24z" p-id="2760"></path><path d="M430.42133333 723.17155555c-34.01955555 0-61.78133333-27.648-61.78133333-61.78133333V362.496c0-34.01955555 27.76177778-61.78133333 61.78133333-61.78133333 10.69511111 0 21.27644445 2.84444445 30.72 8.30577778L720.09955555 458.52444445c19.34222222 11.15022222 30.83377778 31.17511111 30.83377778 53.47555555s-11.49155555 42.32533333-30.83377778 53.47555555L461.25511111 714.86577778c-9.55733333 5.46133333-20.13866667 8.30577778-30.83377778 8.30577777z m0-374.55644444c-6.71288889 0-13.99466667 5.34755555-13.99466666 13.99466667v298.89422222c0 11.71911111 12.40177778 16.95288889 20.93511111 12.06044445l258.84444444-149.504c6.25777778-3.64088889 6.94044445-9.55733333 6.94044445-12.06044445s-0.68266667-8.41955555-6.94044445-12.06044445L437.36177778 350.43555555c-2.27555555-1.25155555-4.55111111-1.82044445-6.94044445-1.82044444z" p-id="2761"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698673301556" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4065" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M843.693959 293.609061 425.255869 712.056362 186.145026 472.947566 66.579883 592.504522 425.255869 951.165158 963.260126 413.174204Z" p-id="4066"></path></svg>

After

Width:  |  Height:  |  Size: 492 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1699434372634" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4041" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M189.184 349.952c14.336 0 27.904 5.632 38.144 15.872 9.984 9.984 15.872 23.808 15.872 38.144v215.04c0 19.2-10.24 36.864-26.88 46.592-16.64 9.728-37.12 9.728-53.76 0S135.68 638.208 135.68 619.008v-215.04c0-14.336 5.632-27.904 15.872-38.144 9.728-10.24 23.552-15.872 37.632-15.872zM404.48 134.656c29.696 0 53.76 24.064 53.76 53.76v645.376c0 19.2-10.24 36.864-26.88 46.592-16.64 9.728-37.12 9.728-53.76 0S350.72 852.992 350.72 833.792V188.416c0-14.336 5.632-27.904 15.872-38.144 9.984-9.984 23.552-15.616 37.888-15.616z m215.04 134.4c29.696 0 53.76 24.064 53.76 53.76v376.576c0 19.2-10.24 36.864-26.88 46.592-16.64 9.728-37.12 9.728-53.76 0S565.76 718.592 565.76 699.392v-376.32c0-29.696 24.064-54.016 53.76-54.016z m215.296 80.896c29.696 0 53.76 24.064 53.76 53.76v242.176c0 29.696-24.064 53.76-53.76 53.76s-53.76-24.064-53.76-53.76v-242.176c0-29.696 24.064-53.76 53.76-53.76z m0 0" fill="#E67E22" p-id="4042"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" class="icon-sm m-1 md:m-0">
<path
d="M.5 1.163A1 1 0 0 1 1.97.28l12.868 6.837a1 1 0 0 1 0 1.766L1.969 15.72A1 1 0 0 1 .5 14.836V10.33a1 1 0 0 1 .816-.983L8.5 8 1.316 6.653A1 1 0 0 1 .5 5.67V1.163Z"
fill="currentColor"></path>
</svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@@ -1 +1,8 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691412437336" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2312" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 14.8973037a497.1026963 497.1026963 0 1 0 497.1026963 497.1026963A497.1026963 497.1026963 0 0 0 512 14.8973037z m165.70089876 581.8863224l-207.12612345 124.27567408L346.29910124 796.45320912V255.16360691l125.38034629 75.39390894 207.12612347 124.27567407 118.47614261 70.97521873z" p-id="2313"></path></svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691412437336"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2312"
xmlns:xlink="http://www.w3.org/1999/xlink">
<path
d="M512 14.8973037a497.1026963 497.1026963 0 1 0 497.1026963 497.1026963A497.1026963 497.1026963 0 0 0 512 14.8973037z m165.70089876 581.8863224l-207.12612345 124.27567408L346.29910124 796.45320912V255.16360691l125.38034629 75.39390894 207.12612347 124.27567407 118.47614261 70.97521873z"
p-id="2313"></path>
</svg>

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 650 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698892598531"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4968"
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
<path
d="M696.917333 85.333333H327.04C182.101333 85.333333 85.333333 189.141333 85.333333 337.749333v348.501334C85.333333 835.029333 181.845333 938.666667 327.04 938.666667h369.834667C842.154667 938.666667 938.666667 834.986667 938.666667 686.250667V337.749333C938.666667 189.013333 842.154667 85.333333 696.917333 85.333333zM327.04 149.333333h369.92c108.8 0 177.706667 74.026667 177.706667 188.416v348.501334c0 114.389333-68.906667 188.416-177.792 188.416H327.04c-108.8 0-177.706667-73.984-177.706667-188.416V337.749333C149.333333 223.573333 218.453333 149.333333 327.04 149.333333zM377.173333 400.981333a111.018667 111.018667 0 1 0 106.325334 143.018667h89.6v47.018667l0.256 4.309333a32 32 0 0 0 63.744-4.309333V544h56.661333v47.018667l0.298667 4.352a32 32 0 0 0 63.701333-4.352V512l-0.298667-4.309333a32 32 0 0 0-31.701333-27.690667h-119.466667a36.138667 36.138667 0 0 0-2.389333 0h-120.405333A111.104 111.104 0 0 0 377.173333 400.981333z m0 64a47.018667 47.018667 0 1 1 0 94.08 47.018667 47.018667 0 0 1 0-94.08z"
p-id="4969"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698895784986" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6347" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M655.39072 491.8784l-19.01568-7.552 16.128-12.58496c55.53152-43.34592 87.38304-108.11904 87.38304-177.70496 0-124.6464-102.1184-226.05312-227.6352-226.05312-125.52192 0-227.64032 101.40672-227.64032 226.05312 0 69.58592 31.85664 134.35904 87.39328 177.70496l16.128 12.58496-19.01568 7.552c-148.26496 58.85952-244.06528 198.97856-244.06528 356.98176v79.24736c0 15.39072 12.63104 27.91424 28.16 27.91424h718.08c15.52384 0 28.16-12.52352 28.16-27.91424v-79.24736c0-157.99808-95.80032-298.12736-244.06016-356.98176zM335.49824 293.72416c0-96.85504 79.29344-175.65696 176.75264-175.65696 97.46944 0 176.75776 78.80192 176.75776 175.65696s-79.28832 175.65696-176.75776 175.65696c-97.4592 0-176.75264-78.80192-176.75264-175.65696z m514.45248 611.10784H174.52032V852.224c0-187.84768 148.69504-340.66944 337.76128-340.66944 189.01504 0 337.66912 152.82176 337.66912 340.66944v52.608z" p-id="6348"></path><path d="M920.54528 837.1456c-0.5376-17.92512-2.28352-32.57856-5.3248-50.08384h54.63552v-45.52192c0-122.48576-77.85472-228.96128-189.33248-258.93888l-7.36768-1.90464c-12.09344-4.50048-20.18816-15.6416-20.18816-27.85792 0-11.97568 7.59808-22.784 19.35872-27.53024l0.68608-0.30208c46.87872-22.66112 77.16864-72.67328 77.16864-127.41632 0-59.74016-52.08064-110.87872-103.06048-130.72384a271.58528 271.58528 0 0 0-33.82272-68.69504c97.9456 6.61504 175.72352 93.54752 175.72352 199.45472 0 49.45408-17.34144 97.0496-48.82944 134.00064l-10.33728 12.12928 13.62944 7.51104c101.92896 56.2176 165.21216 167.43936 165.15584 290.25792v65.70496c0 16.49152-12.63616 29.91104-28.15488 29.91104h-59.93984zM38.4 837.14048c-15.52384 0-28.16-13.40928-28.16-29.88544v-65.61792c0-122.69568 63.3088-233.79456 165.2224-289.94048l13.62944-7.50592-10.33216-12.11392c-31.488-36.91008-48.83456-84.44928-48.83456-133.85728 0-105.75872 77.77792-192.57856 175.7184-199.1936a272.31232 272.31232 0 0 0-33.8432 68.62336c-48.8192 19.17952-96.41984 68.90496-96.41984 130.57024 0 54.66624 30.28992 104.62208 77.16864 127.26784l0.68096 0.30208c11.75552 4.75648 19.3536 15.54944 19.3536 27.50464 0 12.2112-8.0896 23.34208-20.1728 27.83744l-7.26016 1.85856c-111.60064 29.952-189.45536 136.30464-189.45536 258.65216v45.48096h47.9744c-3.0464 17.56672-4.79744 32.21504-5.33504 50.02752H38.4z" p-id="6349"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698724877512" class="icon" viewBox="0 0 1252 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4699" xmlns:xlink="http://www.w3.org/1999/xlink" width="156.5" height="128"><path d="M1164.136052 546.068492h-143.393656a199.609195 199.609195 0 0 1-70.816241-4.544965 36.927843 36.927843 0 0 1-16.305063-19.884224l-1.619144-8.52181a35.166669 35.166669 0 0 1 29.400245-37.495963c16.049409 0 32.070411 0 48.11982-0.284061h159.698719c41.955711 0 74.395401-5.681207 80.729946 31.246637a31.587509 31.587509 0 0 1-4.033657 20.168283c-13.095181 25.56543-44.767908 19.316103-81.780969 19.316103z m60.135572-199.126293a125.838727 125.838727 0 0 1-29.655899 1.136242H878.71223a100.670982 100.670982 0 0 1-33.405495-2.840604 36.586971 36.586971 0 0 1-18.463921-19.600163c-0.710151-2.556543-1.420302-5.397146-2.130453-8.237749a34.996233 34.996233 0 0 1 28.406033-39.200326 131.179061 131.179061 0 0 1 28.065161-0.568121h337.293238a35.393917 35.393917 0 0 1 5.823237 69.310721z m-618.228906 233.213533A437.736971 437.736971 0 0 1 874.905822 982.669222a41.330778 41.330778 0 0 1-82.633151 0 354.223234 354.223234 0 0 0-344.621994-353.371053h-20.395532A354.223234 354.223234 0 0 0 82.633151 982.669222a41.330778 41.330778 0 0 1-82.633151 0 437.736971 437.736971 0 0 1 267.755269-402.51349 314.710442 314.710442 0 1 1 338.287449 0zM434.015781 83.902332a231.622795 231.622795 0 0 0-6.760636 463.018341h20.395532a231.622795 231.622795 0 0 0-13.634896-463.018341zM1040.797056 682.985572a162.596134 162.596134 0 0 1 66.81099-4.829026h66.697365c40.535409 0 69.594781-3.976845 75.645267 31.246637a32.922592 32.922592 0 0 1-4.033657 20.452343c-12.157782 23.292947-37.80843 19.032042-72.435385 19.032043h-79.224426a158.647695 158.647695 0 0 1-50.534333-3.124664 35.59276 35.59276 0 0 1-18.151455-19.316103l-2.158859-8.521809A35.905226 35.905226 0 0 1 1040.797056 682.985572z" fill="#333333" p-id="4700"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -98,7 +98,15 @@ const iconPaths = {
'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
'core/module/previewLight': () => import('./icons/core/module/previewLight.svg'),
'core/chat/quoteFill': () => import('./icons/core/chat/quoteFill.svg'),
'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg')
'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg'),
'common/tickFill': () => import('./icons/common/tickFill.svg'),
'common/inviteLight': () => import('./icons/common/inviteLight.svg'),
'support/team/memberLight': () => import('./icons/support/team/memberLight.svg'),
'support/permission/privateLight': () => import('./icons/support/permission/privateLight.svg'),
'support/permission/publicLight': () => import('./icons/support/permission/publicLight.svg'),
'core/app/ttsFill': () => import('./icons/core/app/ttsFill.svg'),
'common/playLight': () => import('./icons/common/playLight.svg'),
'core/chat/sendFill': () => import('./icons/core/chat/sendFill.svg')
};
export type IconName = keyof typeof iconPaths;

View File

@@ -4,12 +4,19 @@ import { useRouter } from 'next/router';
import { useLoading } from '@/web/common/hooks/useLoading';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { throttle } from 'lodash';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getUnreadCount } from '@/web/support/user/inform/api';
import { feConfigs } from '@/web/common/system/staticData';
import dynamic from 'next/dynamic';
import Auth from './auth';
import Navbar from './navbar';
import NavbarPhone from './navbarPhone';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getUnreadCount } from '@/web/support/user/api';
const UpdateInviteModal = dynamic(
() => import('@/components/support/user/team/UpdateInviteModal'),
{ ssr: false }
);
const pcUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
@@ -60,7 +67,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
}, [loadGitStar, setScreenWidth]);
const { data: unread = 0 } = useQuery(['getUnreadCount'], getUnreadCount, {
enabled: !!userInfo,
enabled: !!userInfo && feConfigs.isPlus,
refetchInterval: 10000
});
@@ -103,6 +110,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
)}
</Box>
<Loading loading={loading} zIndex={9999} />
{!!userInfo && <UpdateInviteModal />}
</>
);
};

View File

@@ -3,7 +3,7 @@ import { Box, Flex, Link } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/storeChat';
import { HUMAN_ICON } from '@/constants/chat';
import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants';
import { feConfigs } from '@/web/common/system/staticData';
import NextLink from 'next/link';
import Badge from '../Badge';

View File

@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { Box, useTheme } from '@chakra-ui/react';
import { getFileAndOpen } from '@/web/common/file/utils';
import { getFileAndOpen } from '@/web/core/dataset/utils';
import { useToast } from '@/web/common/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';

View File

@@ -91,7 +91,7 @@ const MermaidBlock = ({ code }: { code: string }) => {
a.download = 'mermaid.jpg';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
document.body?.removeChild(a);
};
img.onerror = (e) => {
console.log(e);

View File

@@ -41,7 +41,8 @@ const MyMenu = ({ width, offset = [0, 10], Button, menuList }: Props) => {
e.stopPropagation();
item.onClick && item.onClick();
}}
color={item.isActive ? 'hover.blue' : ''}
color={item.isActive ? 'myBlue.600' : ''}
whiteSpace={'pre-wrap'}
>
{item.child}
</MenuItem>

View File

@@ -10,6 +10,7 @@ const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ...
const { isPc } = useSystemStore();
return isPc || forceShow ? (
<Tooltip
className="tooltip"
bg={'white'}
arrowShadowColor={' rgba(0,0,0,0.05)'}
hasArrow

View File

@@ -69,7 +69,7 @@ const MyRadio = ({
onClick={() => onChange(item.value)}
>
{!!item.icon && <MyIcon mr={'14px'} name={item.icon as any} w={iconSize} />}
<Box>
<Box pr={2}>
<Box>{item.title}</Box>
{!!item.desc && (
<Box fontSize={'sm'} color={'myGray.500'}>

View File

@@ -1,4 +1,4 @@
import React, { useRef, forwardRef } from 'react';
import React, { useRef, forwardRef, useMemo } from 'react';
import {
Menu,
Box,
@@ -14,10 +14,11 @@ interface Props extends ButtonProps {
value?: string;
placeholder?: string;
list: {
label: string;
alias?: string;
label: string | React.ReactNode;
value: string;
}[];
onchange?: (val: string) => void;
onchange?: (val: any) => void;
}
const MySelect = (
@@ -36,6 +37,7 @@ const MySelect = (
}
};
const { isOpen, onOpen, onClose } = useDisclosure();
const selectItem = useMemo(() => list.find((item) => item.value === value), [list, value]);
useOutsideClick({
ref: SelectRef,
@@ -72,7 +74,7 @@ const MySelect = (
: {})}
{...props}
>
{list.find((item) => item.value === value)?.label || placeholder}
{selectItem?.alias || selectItem?.label || placeholder}
<Box flex={1} />
<ChevronDownIcon />
</Button>
@@ -103,7 +105,8 @@ const MySelect = (
{...menuItemStyles}
{...(value === item.value
? {
color: 'myBlue.600'
color: 'myBlue.600',
bg: 'myWhite.300'
}
: {})}
onClick={() => {
@@ -111,6 +114,7 @@ const MySelect = (
onchange(item.value);
}
}}
whiteSpace={'pre-wrap'}
>
{item.label}
</MenuItem>

View File

@@ -0,0 +1,99 @@
import React, { useCallback, useRef, useState } from 'react';
import {
Box,
BoxProps,
Flex,
Input,
Tag,
TagCloseButton,
TagLabel,
useTheme
} from '@chakra-ui/react';
import { useToast } from '@/web/common/hooks/useToast';
import { useTranslation } from 'react-i18next';
type Props = BoxProps & { defaultValues: string[]; onUpdate: (e: string[]) => void };
const TagTextarea = ({ defaultValues, onUpdate, ...props }: Props) => {
const theme = useTheme();
const InputRef = useRef<HTMLInputElement>(null);
const { t } = useTranslation();
const { toast } = useToast();
const [focus, setFocus] = useState(false);
const [tags, setTags] = useState<string[]>(defaultValues);
const onUpdateValue = useCallback(
(value?: string) => {
setFocus(false);
if (!value || !InputRef.current?.value) {
return;
}
if (tags.includes(value)) {
toast({
status: 'warning',
title: t('common.input.Repeat Value')
});
}
setTags([...tags, value]);
onUpdate([...tags, value]);
InputRef.current.value = '';
},
[onUpdate, t, tags, toast]
);
return (
<Box
w={'100%'}
minH={'200px'}
borderRadius={'md'}
border={theme.borders.base}
p={2}
fontSize={'sm'}
bg={'myWhite.600'}
{...(focus && {
boxShadow: '0px 0px 4px #A8DBFF',
borderColor: 'myBlue.600'
})}
{...props}
onClick={() => {
if (!focus) {
InputRef.current?.focus();
setFocus(true);
}
}}
>
<Flex alignItems={'center'} gap={2} flexWrap={'wrap'}>
{tags.map((tag, i) => (
<Tag key={tag} colorScheme="blue" onClick={(e) => e.stopPropagation()}>
<TagLabel>{tag}</TagLabel>
<TagCloseButton
onClick={() => {
const val = tags.filter((_, index) => index !== i);
setTags(val);
onUpdate(val);
}}
/>
</Tag>
))}
<Input
ref={InputRef}
variant={'unstyled'}
display={'inline-block'}
w="auto"
onBlur={(e) => {
const value = e.target.value;
onUpdateValue(value);
}}
onKeyDown={(e) => {
if (e.keyCode === 13) {
e.preventDefault();
onUpdateValue(InputRef.current?.value);
}
}}
/>
</Flex>
</Box>
);
};
export default TagTextarea;

View File

@@ -49,7 +49,7 @@ const AIChatSettingsModal = ({
}>();
const tokenLimit = useMemo(() => {
return chatModelList.find((item) => item.model === getValues('model'))?.maxToken || 4000;
return chatModelList.find((item) => item.model === getValues('model'))?.maxResponse || 4000;
}, [getValues, refresh]);
const LabelStyles: BoxProps = {

View File

@@ -1,5 +1,5 @@
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { AppSchema } from '@/types/mongoSchema';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import React, {
useMemo,
useCallback,
@@ -47,7 +47,7 @@ const ChatTest = (
// 流请求,获取数据
const { responseText, responseData } = await streamFetch({
url: '/api/chat/chatTest',
url: '/api/core/chat/chatTest',
data: {
history,
prompt: chatList[chatList.length - 2].value,

View File

@@ -7,6 +7,7 @@ import Avatar from '@/components/Avatar';
import { useTranslation } from 'react-i18next';
import { useLoading } from '@/web/common/hooks/useLoading';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useAppStore } from '@/web/core/app/store/useAppStore';
const SelectAppModal = ({
defaultApps = [],
@@ -26,7 +27,7 @@ const SelectAppModal = ({
const theme = useTheme();
const [selectedApps, setSelectedApps] = React.useState<string[]>(defaultApps);
/* 加载模型 */
const { myApps, loadMyApps } = useUserStore();
const { myApps, loadMyApps } = useAppStore();
const { isLoading } = useQuery(['loadMyApos'], () => loadMyApps());
const apps = useMemo(

View File

@@ -9,7 +9,7 @@ import {
Switch,
Input
} from '@chakra-ui/react';
import type { ContextExtractAgentItemType } from '@/types/app';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
import { useForm } from 'react-hook-form';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);

View File

@@ -6,7 +6,7 @@ import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
import type { ClassifyQuestionAgentItemType } from '@/types/app';
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/type.d';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
import MyIcon from '@/components/Icon';

View File

@@ -8,7 +8,7 @@ import Container from '../modules/Container';
import { AddIcon } from '@chakra-ui/icons';
import RenderInput from '../render/RenderInput';
import Divider from '../modules/Divider';
import { ContextExtractAgentItemType } from '@/types/app';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
import RenderOutput from '../render/RenderOutput';
import MyIcon from '@/components/Icon';
import ExtractFieldModal from '../modules/ExtractFieldModal';

View File

@@ -15,9 +15,9 @@ import {
Switch
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { SystemInputEnum } from '@/constants/app';
import { welcomeTextTip, variableTip, questionGuideTip } from '@/constants/flow/ModuleTemplate';
import { welcomeTextTip, variableTip } from '@/constants/flow/ModuleTemplate';
import { onChangeNode } from '../../FlowProvider';
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
@@ -26,18 +26,24 @@ import MyTooltip from '@/components/MyTooltip';
import Container from '../modules/Container';
import NodeCard from '../modules/NodeCard';
import { VariableItemType } from '@/types/app';
import QGSwitch from '@/pages/app/detail/components/QGSwitch';
import TTSSelect from '@/pages/app/detail/components/TTSSelect';
import { splitGuideModule } from '@/global/core/app/modules/utils';
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
const theme = useTheme();
return (
<>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<Container className="nodrag" borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<WelcomeText data={data} />
<Box pt={4} pb={2}>
<ChatStartVariable data={data} />
</Box>
<Box pt={3} borderTop={theme.borders.base}>
<TTSGuide data={data} />
</Box>
<Box mt={3} pt={3} borderTop={theme.borders.base}>
<QuestionGuide data={data} />
</Box>
</Container>
@@ -215,29 +221,43 @@ function QuestionGuide({ data }: { data: FlowModuleItemType }) {
);
return (
<Flex alignItems={'center'}>
<MyIcon name={'questionGuide'} mr={2} w={'16px'} />
<Box></Box>
<MyTooltip label={questionGuideTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Switch
isChecked={questionGuide}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
onChangeNode({
moduleId,
key: SystemInputEnum.questionGuide,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === SystemInputEnum.questionGuide),
value
}
});
}}
/>
</Flex>
<QGSwitch
isChecked={questionGuide}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
onChangeNode({
moduleId,
key: SystemInputEnum.questionGuide,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === SystemInputEnum.questionGuide),
value
}
});
}}
/>
);
}
function TTSGuide({ data }: { data: FlowModuleItemType }) {
const { inputs, moduleId } = data;
const { ttsConfig } = splitGuideModule({ inputs } as ModuleItemType);
return (
<TTSSelect
value={ttsConfig}
onChange={(e) => {
onChangeNode({
moduleId,
key: SystemInputEnum.tts,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === SystemInputEnum.tts),
value: e
}
});
}}
/>
);
}

View File

@@ -29,11 +29,11 @@ import MyIcon from '@/components/Icon';
import { useTranslation } from 'react-i18next';
import { AIChatProps } from '@/types/core/aiChat';
import { chatModelList } from '@/web/common/system/staticData';
import { formatPrice } from '@fastgpt/global/common/bill/tools';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { SelectedDatasetType } from '@/types/core/dataset';
import { useQuery } from '@tanstack/react-query';
import { LLMModelItemType } from '@/types/model';
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
@@ -406,7 +406,7 @@ var MaxTokenRender = React.memo(function MaxTokenRender({
}: RenderProps) {
const model = inputs.find((item) => item.key === 'model')?.value;
const modelData = chatModelList.find((item) => item.model === model);
const maxToken = modelData ? modelData.maxToken : 4000;
const maxToken = modelData ? modelData.maxResponse : 4000;
const markList = [
{ label: '100', value: 100 },
{ label: `${maxToken}`, value: maxToken }
@@ -468,10 +468,10 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
...inputs.find((input) => input.key === 'maxToken'),
markList: [
{ label: '100', value: 100 },
{ label: `${model.maxToken}`, value: model.maxToken }
{ label: `${model.maxResponse}`, value: model.maxResponse }
],
max: model.maxToken,
value: model.maxToken / 2
max: model.maxResponse,
value: model.maxResponse / 2
}
});
}

View File

@@ -26,7 +26,7 @@ import {
delOpenApiById,
putOpenApiKey
} from '@/web/support/openapi/api';
import type { EditApiKeyProps } from '@/global/support/api/openapiReq';
import type { EditApiKeyProps } from '@/global/support/openapi/api.d';
import { useQuery, useMutation } from '@tanstack/react-query';
import { useLoading } from '@/web/common/hooks/useLoading';
import dayjs from 'dayjs';

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant';
import { Box, Flex, FlexProps } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import { useTranslation } from 'react-i18next';
const PermissionIconText = ({
permission,
...props
}: { permission: `${PermissionTypeEnum}` } & FlexProps) => {
const { t } = useTranslation();
return PermissionTypeMap[permission] ? (
<Flex alignItems={'center'} {...props}>
<MyIcon name={PermissionTypeMap[permission]?.iconLight as any} w={'14px'} />
<Box ml={'1px'}>{t(PermissionTypeMap[permission]?.label)}</Box>
</Flex>
) : null;
};
export default PermissionIconText;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import MyRadio from '@/components/Radio';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { useTranslation } from 'react-i18next';
const PermissionRadio = ({
value,
onChange
}: {
value: `${PermissionTypeEnum}`;
onChange: (e: `${PermissionTypeEnum}`) => void;
}) => {
const { t } = useTranslation();
return (
<MyRadio
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)']}
list={[
{
icon: 'support/permission/privateLight',
title: t('permission.Private'),
desc: t('permission.Private Tip'),
value: PermissionTypeEnum.private
},
{
icon: 'support/permission/publicLight',
title: t('permission.Public'),
desc: t('permission.Public Tip'),
value: PermissionTypeEnum.public
}
]}
value={value}
onChange={(e) => onChange(e as `${PermissionTypeEnum}`)}
/>
);
};
export default PermissionRadio;

View File

@@ -0,0 +1,159 @@
import React, { useCallback, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgAndUpload } from '@/web/common/file/controller';
import { useToast } from '@/web/common/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useRequest } from '@/web/common/hooks/useRequest';
import MyModal from '@/components/MyModal';
import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import Avatar from '@/components/Avatar';
import { postCreateTeam, putUpdateTeam } from '@/web/support/user/team/api';
import { CreateTeamProps } from '@fastgpt/global/support/user/team/controller.d';
export type FormDataType = CreateTeamProps & {
id?: string;
};
export const defaultForm = {
name: '',
avatar: '/icon/logo.svg'
};
function EditModal({
defaultData = defaultForm,
onClose,
onSuccess
}: {
defaultData?: FormDataType;
onClose: () => void;
onSuccess: () => void;
}) {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
const { toast } = useToast();
const { register, setValue, getValues, handleSubmit } = useForm<CreateTeamProps>({
defaultValues: defaultData
});
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png,.svg',
multiple: false
});
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
if (!file) return;
try {
const src = await compressImgAndUpload({
file,
maxW: 100,
maxH: 100
});
setValue('avatar', src);
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: getErrText(err, t('common.Select File Failed')),
status: 'warning'
});
}
},
[setValue, t, toast]
);
const { mutate: onclickCreate, isLoading: creating } = useRequest({
mutationFn: async (data: CreateTeamProps) => {
return postCreateTeam(data);
},
onSuccess() {
onSuccess();
onClose();
},
successToast: t('common.Create Success'),
errorToast: t('common.Create Failed')
});
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
mutationFn: async (data: FormDataType) => {
if (!data.id) return Promise.resolve('');
return putUpdateTeam({
teamId: data.id,
name: data.name,
avatar: data.avatar
});
},
onSuccess() {
onSuccess();
onClose();
},
successToast: t('common.Update Success'),
errorToast: t('common.Update Failed')
});
return (
<MyModal
isOpen
onClose={onClose}
title={defaultData.id ? t('user.team.Update Team') : t('user.team.Create Team')}
>
<ModalBody>
<Box color={'myGray.800'} fontWeight={'bold'}>
{t('user.team.Set Name')}
</Box>
<Flex mt={3} alignItems={'center'}>
<MyTooltip label={t('common.Set Avatar')}>
<Avatar
flexShrink={0}
src={getValues('avatar')}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
onClick={onOpenSelectFile}
/>
</MyTooltip>
<Input
flex={1}
ml={4}
autoFocus
bg={'myWhite.600'}
maxLength={20}
placeholder={t('user.team.Team Name')}
{...register('name', {
required: t('common.Please Input Name')
})}
/>
</Flex>
</ModalBody>
<ModalFooter>
{!!defaultData.id ? (
<>
<Box flex={1} />
<Button variant={'base'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={updating} onClick={handleSubmit((data) => onclickUpdate(data))}>
{t('common.Confirm Update')}
</Button>
</>
) : (
<Button
w={'100%'}
isLoading={creating}
onClick={handleSubmit((data) => onclickCreate(data))}
>
{t('common.Confirm Create')}
</Button>
)}
</ModalFooter>
<File onSelect={onSelectFile} />
</MyModal>
);
}
export default React.memo(EditModal);

View File

@@ -0,0 +1,106 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next';
import { ModalCloseButton, ModalBody, Box, ModalFooter, Button } from '@chakra-ui/react';
import TagTextarea from '@/components/common/Textarea/TagTextarea';
import MySelect from '@/components/Select';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { useRequest } from '@/web/common/hooks/useRequest';
import { postInviteTeamMember } from '@/web/support/user/team/api';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import type { InviteMemberResponse } from '@fastgpt/global/support/user/team/controller.d';
const InviteModal = ({
teamId,
onClose,
onSuccess
}: {
teamId: string;
onClose: () => void;
onSuccess: () => void;
}) => {
const { t } = useTranslation();
const { ConfirmModal, openConfirm } = useConfirm({
title: t('user.team.Invite Member Result Tip'),
showCancel: false
});
const [inviteUsernames, setInviteUsernames] = useState<string[]>([]);
const inviteTypes = useMemo(
() => [
{
alias: t('user.team.Invite Role Visitor Alias'),
label: t('user.team.Invite Role Visitor Tip'),
value: TeamMemberRoleEnum.visitor
},
{
alias: t('user.team.Invite Role Admin Alias'),
label: t('user.team.Invite Role Admin Tip'),
value: TeamMemberRoleEnum.admin
}
],
[t]
);
const [selectedInviteType, setSelectInviteType] = useState(inviteTypes[0].value);
const { mutate: onInvite, isLoading } = useRequest({
mutationFn: () => {
return postInviteTeamMember({
teamId,
usernames: inviteUsernames,
role: selectedInviteType
});
},
onSuccess(res: InviteMemberResponse) {
onSuccess();
openConfirm(
() => onClose(),
undefined,
t('user.team.Invite Member Success Tip', {
success: res.invite.length,
inValid: res.inValid.join(', '),
inTeam: res.inTeam.join(', ')
})
)();
},
errorToast: t('user.team.Invite Member Failed Tip')
});
return (
<MyModal
isOpen
title={
<>
<Box>{t('user.team.Invite Member')}</Box>
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
{t('user.team.Invite Member Tips')}
</Box>
</>
}
maxW={['90vw', '400px']}
overflow={'unset'}
>
<ModalCloseButton onClick={onClose} />
<ModalBody>
<Box mb={2}>{t('common.Username')}</Box>
<TagTextarea defaultValues={inviteUsernames} onUpdate={setInviteUsernames} />
<Box mt={4}>
<MySelect list={inviteTypes} value={selectedInviteType} onchange={setSelectInviteType} />
</Box>
</ModalBody>
<ModalFooter>
<Button
w={'100%'}
h={'34px'}
isDisabled={inviteUsernames.length === 0}
isLoading={isLoading}
onClick={onInvite}
>
{t('user.team.Confirm Invite')}
</Button>
</ModalFooter>
<ConfirmModal />
</MyModal>
);
};
export default InviteModal;

View File

@@ -0,0 +1,436 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import {
getTeamList,
getTeamMembers,
putSwitchTeam,
putUpdateMember,
delRemoveMember,
delLeaveTeam
} from '@/web/support/user/team/api';
import {
Box,
Button,
Flex,
IconButton,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
useTheme,
useDisclosure,
MenuButton
} from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
import { useUserStore } from '@/web/support/user/useUserStore';
import {
TeamMemberRoleEnum,
TeamMemberRoleMap,
TeamMemberStatusEnum,
TeamMemberStatusMap
} from '@fastgpt/global/support/user/team/constant';
import dynamic from 'next/dynamic';
import { useRequest } from '@/web/common/hooks/useRequest';
import { setToken } from '@/web/support/user/auth';
import { useLoading } from '@/web/common/hooks/useLoading';
import { FormDataType, defaultForm } from './EditModal';
import MyMenu from '@/components/MyMenu';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { useToast } from '@/web/common/hooks/useToast';
const EditModal = dynamic(() => import('./EditModal'));
const InviteModal = dynamic(() => import('./InviteModal'));
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
const theme = useTheme();
const { t } = useTranslation();
const { Loading } = useLoading();
const { toast } = useToast();
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
content: t('user.team.member.Confirm Leave')
});
const { userInfo, initUserInfo } = useUserStore();
const [editTeamData, setEditTeamData] = useState<FormDataType>();
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
const {
data: myTeams = [],
isLoading: isLoadingTeams,
refetch: refetchTeam
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
const defaultTeam = useMemo(
() => myTeams.find((item) => item.defaultTeam) || myTeams[0],
[myTeams]
);
const { mutate: onSwitchTeam, isLoading: isSwitchTeam } = useRequest({
mutationFn: async (teamId: string) => {
const token = await putSwitchTeam(teamId);
setToken(token);
return initUserInfo();
},
errorToast: t('user.team.Switch Team Failed')
});
// member action
const { data: members = [], refetch: refetchMembers } = useQuery(
['getMembers', userInfo?.team?.teamId],
() => {
if (!userInfo?.team?.teamId) return [];
return getTeamMembers(userInfo.team.teamId);
}
);
const { mutate: onUpdateMember, isLoading: isLoadingUpdateMember } = useRequest({
mutationFn: putUpdateMember,
onSuccess() {
refetchMembers();
}
});
const { mutate: onRemoveMember, isLoading: isLoadingRemoveMember } = useRequest({
mutationFn: delRemoveMember,
onSuccess() {
refetchMembers();
}
});
const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({
mutationFn: async (teamId?: string) => {
if (!teamId) return;
// change to personal team
await onSwitchTeam(defaultTeam.teamId);
return delLeaveTeam(teamId);
},
onSuccess() {
refetchTeam();
},
errorToast: t('user.team.Leave Team Failed')
});
return !!userInfo?.team ? (
<>
<MyModal
isOpen
onClose={onClose}
maxW={['90vw', '1000px']}
w={'100%'}
h={'550px'}
isCentered
bg={'myWhite.600'}
overflow={'hidden'}
>
<Box display={['block', 'flex']} flex={1} position={'relative'} overflow={'auto'}>
{/* teams */}
<Flex
flexDirection={'column'}
w={['auto', '270px']}
h={['auto', '100%']}
pt={3}
px={5}
mb={[2, 0]}
>
<Flex
alignItems={'center'}
py={2}
h={'40px'}
borderBottom={'1.5px solid rgba(0, 0, 0, 0.05)'}
>
<Box flex={['0 0 auto', 1]} fontWeight={'bold'} fontSize={['md', 'lg']}>
{t('common.Team')}
</Box>
{myTeams.length < 1 && (
<IconButton
variant={'ghost'}
border={'none'}
icon={
<MyIcon
name={'addCircle'}
w={['16px', '18px']}
color={'myBlue.600'}
cursor={'pointer'}
/>
}
aria-label={''}
onClick={() => setEditTeamData(defaultForm)}
/>
)}
</Flex>
<Box flex={['auto', '1 0 0']} overflow={'auto'}>
{myTeams.map((team) => (
<Flex
key={team.teamId}
alignItems={'center'}
mt={3}
borderRadius={'md'}
p={3}
cursor={'default'}
gap={3}
{...(userInfo?.team?.teamId === team.teamId
? {
bg: 'myBlue.300'
}
: {
_hover: {
bg: 'myGray.100'
}
})}
>
<Avatar src={team.avatar} w={['18px', '22px']} />
<Box
flex={'1 0 0'}
w={0}
{...(team.role === TeamMemberRoleEnum.owner
? {
fontWeight: 'bold'
}
: {})}
>
{team.teamName}
</Box>
{userInfo?.team?.teamId === team.teamId ? (
<MyIcon name={'common/tickFill'} w={'16px'} color={'myBlue.600'} />
) : (
<Button size={'xs'} variant={'base'} onClick={() => onSwitchTeam(team.teamId)}>
{t('user.team.Check Team')}
</Button>
)}
</Flex>
))}
</Box>
</Flex>
{/* team card */}
<Flex
flexDirection={'column'}
flex={'1'}
h={['auto', '100%']}
bg={'white'}
minH={['50vh', 'auto']}
borderRadius={['8px 8px 0 0', '8px 0 0 8px']}
>
<Flex
alignItems={'center'}
px={5}
py={4}
borderBottom={'1.5px solid'}
borderBottomColor={'myGray.100'}
mb={3}
>
<Box fontSize={['lg', 'xl']} fontWeight={'bold'}>
{userInfo.team.teamName}
</Box>
{userInfo.team.role === TeamMemberRoleEnum.owner && (
<MyIcon
name="edit"
w={'14px'}
ml={2}
cursor={'pointer'}
_hover={{
color: 'myBlue.600'
}}
onClick={() => {
if (!userInfo?.team) return;
setEditTeamData({
id: userInfo.team.teamId,
name: userInfo.team.teamName,
avatar: userInfo.team.avatar
});
}}
/>
)}
</Flex>
<Flex px={5} alignItems={'center'}>
<MyIcon name="support/team/memberLight" w={'14px'} />
<Box ml={1}>{t('user.team.Member')}</Box>
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
{members.length}
</Box>
{userInfo.team.role === TeamMemberRoleEnum.owner && (
<Button
variant={'base'}
size="sm"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'myBlue.600'} />}
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>
)}
<Box flex={1} />
{userInfo.team.role !== TeamMemberRoleEnum.owner && (
<Button
variant={'base'}
size="sm"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name={'loginoutLight'} w={'14px'} color={'myBlue.600'} />}
onClick={() => {
openLeaveConfirm(() => onLeaveTeam(userInfo?.team?.teamId))();
}}
>
{t('user.team.Leave Team')}
</Button>
)}
</Flex>
<Box mt={3} flex={'1 0 0'} overflow={'auto'}>
<TableContainer overflow={'unset'}>
<Table overflow={'unset'}>
<Thead bg={'myWhite.400'}>
<Tr>
<Th>{t('common.Username')}</Th>
<Th>{t('user.team.Role')}</Th>
<Th>{t('common.Status')}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{members.map((item) => (
<Tr key={item.userId} overflow={'unset'}>
<Td display={'flex'} alignItems={'center'}>
<Avatar src={item.avatar} w={['18px', '22px']} />
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
{item.memberUsername}
</Box>
</Td>
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
<Td color={TeamMemberStatusMap[item.status].color}>
{t(TeamMemberStatusMap[item.status]?.label || '')}
</Td>
<Td>
{userInfo?.team?.role === TeamMemberRoleEnum.owner &&
item.role !== TeamMemberRoleEnum.owner && (
<MyMenu
width={20}
Button={
<MenuButton
_hover={{
bg: 'myWhite.600'
}}
px={2}
py={1}
lineHeight={1}
>
<MyIcon
name={'edit'}
cursor={'pointer'}
w="14px"
_hover={{ color: 'myBlue.600' }}
/>
</MenuButton>
}
menuList={[
{
isActive: item.role === TeamMemberRoleEnum.visitor,
child: t('user.team.Invite Role Visitor Tip'),
onClick: () => {
onUpdateMember({
teamId: item.teamId,
memberId: item.tmbId,
role: TeamMemberRoleEnum.visitor
});
}
},
{
isActive: item.role === TeamMemberRoleEnum.admin,
child: t('user.team.Invite Role Admin Tip'),
onClick: () => {
onUpdateMember({
teamId: item.teamId,
memberId: item.tmbId,
role: TeamMemberRoleEnum.admin
});
}
},
...(item.status === TeamMemberStatusEnum.reject
? [
{
child: t('user.team.Reinvite'),
onClick: () => {
onUpdateMember({
teamId: item.teamId,
memberId: item.tmbId,
status: TeamMemberStatusEnum.waiting
});
}
}
]
: []),
{
child: t('user.team.Remove Member Tip'),
onClick: () =>
openRemoveMember(
() =>
onRemoveMember({
teamId: item.teamId,
memberId: item.tmbId
}),
undefined,
t('user.team.Remove Member Confirm Tip', {
username: item.memberUsername
})
)()
}
]}
/>
)}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</Flex>
<Loading
loading={
isSwitchTeam ||
isLoadingTeams ||
isLoadingUpdateMember ||
isLoadingRemoveMember ||
isLoadingLeaveTeam
}
fixed={false}
/>
</Box>
</MyModal>
{!!editTeamData && (
<EditModal
defaultData={editTeamData}
onClose={() => setEditTeamData(undefined)}
onSuccess={() => {
refetchTeam();
initUserInfo();
}}
/>
)}
{isOpenInvite && userInfo?.team?.teamId && (
<InviteModal
teamId={userInfo.team.teamId}
onClose={onCloseInvite}
onSuccess={refetchMembers}
/>
)}
<ConfirmRemoveMemberModal />
<ConfirmLeaveTeamModal />
</>
) : null;
};
export default React.memo(TeamManageModal);

View File

@@ -0,0 +1,65 @@
import React from 'react';
import { Box, Button, Flex, Image, useDisclosure, useTheme } from '@chakra-ui/react';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useTranslation } from 'react-i18next';
import MyTooltip from '@/components/MyTooltip';
import dynamic from 'next/dynamic';
import { feConfigs } from '@/web/common/system/staticData';
import { useToast } from '@/web/common/hooks/useToast';
const TeamManageModal = dynamic(() => import('../TeamManageModal'));
const TeamMenu = () => {
const theme = useTheme();
const { t } = useTranslation();
const { userInfo } = useUserStore();
const { toast } = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<Button
variant={'base'}
userSelect={'none'}
w={'100%'}
display={'block'}
h={'34px'}
px={3}
css={{
'& span': {
display: 'block'
}
}}
transform={'none !important'}
onClick={() => {
if (feConfigs.isPlus) {
onOpen();
} else {
toast({
status: 'warning',
title: t('common.Business edition features')
});
}
}}
>
<MyTooltip label={t('user.team.Select Team')}>
<Flex w={'100%'} alignItems={'center'}>
{userInfo?.team ? (
<>
<Image src={userInfo.team.avatar} alt={''} w={'16px'} />
<Box ml={2}>{userInfo.team.teamName}</Box>
</>
) : (
<>
<Box w={'8px'} h={'8px'} mr={3} borderRadius={'50%'} bg={'#67c13b'} />
{t('user.team.Personal Team')}
</>
)}
</Flex>
</MyTooltip>
{isOpen && <TeamManageModal onClose={onClose} />}
</Button>
);
};
export default TeamMenu;

View File

@@ -0,0 +1,133 @@
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import MyModal from '@/components/MyModal';
import {
Button,
ModalFooter,
useDisclosure,
ModalBody,
Flex,
Box,
useTheme
} from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { getTeamList, updateInviteResult } from '@/web/support/user/team/api';
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
import Avatar from '@/components/Avatar';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useToast } from '@/web/common/hooks/useToast';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { feConfigs } from '@/web/common/system/staticData';
const UpdateInviteModal = () => {
const { t } = useTranslation();
const theme = useTheme();
const { toast } = useToast();
const { ConfirmModal, openConfirm } = useConfirm({});
const { data: inviteList = [], refetch } = useQuery(['getInviteList'], () =>
feConfigs.isPlus ? getTeamList(TeamMemberStatusEnum.waiting) : []
);
const { mutate: onAccept, isLoading: isLoadingAccept } = useRequest({
mutationFn: updateInviteResult,
onSuccess() {
toast({
status: 'success',
title: t('user.team.invite.Accepted')
});
refetch();
}
});
const { mutate: onReject, isLoading: isLoadingReject } = useRequest({
mutationFn: updateInviteResult,
onSuccess() {
toast({
status: 'success',
title: t('user.team.invite.Reject')
});
refetch();
}
});
return (
<MyModal
isOpen={inviteList.length > 0}
title={
<>
<Box>{t('user.team.Processing invitations')}</Box>
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'}>
{t('user.team.Processing invitations Tips', { amount: inviteList.length })}
</Box>
</>
}
maxW={['90vw', '500px']}
>
<ModalBody>
{inviteList.map((item) => (
<Flex
key={item.teamId}
alignItems={'center'}
border={theme.borders.base}
borderRadius={'md'}
px={3}
py={2}
_notFirst={{
mt: 3
}}
>
<Avatar src={item.avatar} w={['16px', '23px']} />
<Box mx={2}>{item.teamName}</Box>
<Box flex={1} />
<Button
size="sm"
variant={'solid'}
colorScheme="green"
isLoading={isLoadingAccept}
onClick={() => {
openConfirm(
() =>
onAccept({
tmbId: item.tmbId,
status: TeamMemberStatusEnum.active
}),
undefined,
t('user.team.invite.Accept Confirm')
)();
}}
>
{t('user.team.invite.accept')}
</Button>
<Button
size="sm"
ml={2}
variant={'solid'}
colorScheme="red"
isLoading={isLoadingReject}
onClick={() => {
openConfirm(
() =>
onReject({
tmbId: item.tmbId,
status: TeamMemberStatusEnum.reject
}),
undefined,
t('user.team.invite.Reject Confirm')
)();
}}
>
{t('user.team.invite.reject')}
</Button>
</Flex>
))}
</ModalBody>
<ModalFooter justifyContent={'center'}>
<Box>{t('user.team.invite.Deal Width Footer Tip')}</Box>
</ModalFooter>
<ConfirmModal />
</MyModal>
);
};
export default React.memo(UpdateInviteModal);