mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-24 22:03:54 +00:00
V4.9.1 feature (#4206)
* fix: remove DefaultTeam (#4037) * fix :Get application bound knowledge base information logical rewrite (#4057) * fix :Get application bound knowledge base information logical rewrite * fix :Get application bound knowledge base information logical rewrite * fix :Get application bound knowledge base information logical rewrite * fix :Get application bound knowledge base information logical rewrite * update package * fix: import dataset step error;perf: ai proxy avatar (#4074) * perf: pg config params * perf: ai proxy avatar * fix: import dataset step error * feat: data input ux * perf: app dataset rewite * fix: 文本提取不支持arrayString,arrayNumber等jsonSchema (#4079) * update doc ;perf: model test (#4098) * perf: extract array * update doc * perf: model test * perf: model test * perf: think tag parse (#4102) * chat quote reader (#3912) * init chat quote full text reader * linked structure * dataset data linked * optimize code * fix ts build * test finish * delete log * fix * fix ts * fix ts * remove nextId * initial scroll * fix * fix * perf: chunk read (#4109) * package * perf: chunk read * feat: api dataset support pdf parse;fix: chunk reader auth (#4117) * feat: api dataset support pdf parse * fix: chunk reader auth * feat: invitation link (#3979) * feat: invitation link schema and apis * feat: add invitation link * feat: member status: active, leave, forbidden * fix: expires show hours and minutes * feat: invalid invitation link hint * fix: typo * chore: fix typo & i18n * fix * pref: fe * feat: add ttl index for 30-day-clean-up * perf: invite member code (#4118) * perf: invite member code * fix: ts * fix: model test channel id;fix: quote reader (#4123) * fix: model test channel id * fix: quote reader * fix chat quote reader (#4125) * perf: model test;perf: sidebar trigger (#4127) * fix: import dataset step error;perf: ai proxy avatar (#4074) * perf: pg config params * perf: ai proxy avatar * fix: import dataset step error * feat: data input ux * perf: app dataset rewite * perf: model test * perf: sidebar trigger * lock * update nanoid version * fix: select component ux * fix: ts * fix: vitest * remove test * fix: prompt toolcall ui (#4139) * load log error adapt * fix: prompt toolcall ui * perf: commercial function tip * update package * pref: copy link (#4147) * fix(i18n): namespace (#4143) * hiden dataset source (#4152) * hiden dataset source * perf: reader * chore: move all tests into a single folder (#4160) * fix modal close scroll (#4162) * fix modal close scroll * update refresh * feat: rerank modal select and weight (#4164) * fix loadInitData refresh (#4169) * fix * fix * form input number default & api dataset max token * feat: mix search weight (#4170) * feat: mix search weight * feat: svg render * fix: avatar error remove (#4173) * fix: avatar error remove * fix: index * fix: guide * fix: auth * update package;fix: input data model ui (#4181) * update package * fix: ts * update config * update jieba package * add type sign * fix: input data ui * fix: page title refresh (#4186) * fix: ts * update jieba package * fix: page title refresh * fix: remove member length check when opening invite create modal (#4193) * add env to check internal ip (#4187) * fix: ts * update jieba package * add env to check internal ip * package * fix: jieba * reset package * update config * fix: jieba package * init shell * init version * change team reload * update jieba package (#4200) * update jieba package * package * update package * remove invalid code * action * package (#4201) * package * update package * remove invalid code * package * remove i18n tip (#4202) * doc (#4205) * fix: i18n (#4208) * fix: next config (#4207) * reset package * i18n * update config * i18n * remove log --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: gggaaallleee <91131304+gggaaallleee@users.noreply.github.com> Co-authored-by: shilin <39396378+shilin66@users.noreply.github.com> Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, ButtonProps, Flex } from '@chakra-ui/react';
|
||||
import { Box, ButtonProps } from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { getTeamList, putSwitchTeam } from '@/web/support/user/team/api';
|
||||
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const TeamSelector = ({
|
||||
@@ -21,7 +19,7 @@ const TeamSelector = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
const { userInfo } = useUserStore();
|
||||
const { setLoading } = useSystemStore();
|
||||
|
||||
const { data: myTeams = [] } = useRequest2(() => getTeamList(TeamMemberStatusEnum.active), {
|
||||
@@ -33,12 +31,11 @@ const TeamSelector = ({
|
||||
async (teamId: string) => {
|
||||
setLoading(true);
|
||||
await putSwitchTeam(teamId);
|
||||
return initUserInfo();
|
||||
},
|
||||
{
|
||||
onFinally: () => {
|
||||
router.reload();
|
||||
setLoading(false);
|
||||
onChange?.();
|
||||
},
|
||||
errorToast: t('common:user.team.Switch Team Failed')
|
||||
}
|
||||
@@ -46,48 +43,21 @@ const TeamSelector = ({
|
||||
|
||||
const teamList = useMemo(() => {
|
||||
return myTeams.map((team) => ({
|
||||
label: (
|
||||
<Flex
|
||||
key={team.teamId}
|
||||
alignItems={'center'}
|
||||
borderRadius={'md'}
|
||||
cursor={'default'}
|
||||
gap={3}
|
||||
onClick={() => onSwitchTeam(team.teamId)}
|
||||
_hover={{
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Avatar src={team.avatar} w={['1.25rem', '1.375rem']} />
|
||||
<Box flex={'1 0 0'} w={0} className="textEllipsis" fontSize={'sm'}>
|
||||
{team.teamName}
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
icon: team.avatar,
|
||||
iconSize: '1.25rem',
|
||||
label: team.teamName,
|
||||
value: team.teamId
|
||||
}));
|
||||
}, [myTeams, onSwitchTeam]);
|
||||
}, [myTeams]);
|
||||
|
||||
const formatTeamList = useMemo(() => {
|
||||
return [
|
||||
...(showManage
|
||||
? [
|
||||
{
|
||||
label: (
|
||||
<Flex
|
||||
key={'manage'}
|
||||
alignItems={'center'}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
gap={3}
|
||||
onClick={() => router.push('/account/team')}
|
||||
>
|
||||
<MyIcon name="common/setting" w={['1.25rem', '1.375rem']} />
|
||||
<Box flex={'1 0 0'} w={0} className="textEllipsis" fontSize={'sm'}>
|
||||
{t('user:manage_team')}
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
icon: 'common/setting',
|
||||
iconSize: '1.25rem',
|
||||
label: t('user:manage_team'),
|
||||
value: 'manage',
|
||||
showBorder: true
|
||||
}
|
||||
@@ -95,11 +65,24 @@ const TeamSelector = ({
|
||||
: []),
|
||||
...teamList
|
||||
];
|
||||
}, [showManage, t, teamList, router]);
|
||||
}, [showManage, t, teamList]);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
if (value === 'manage') {
|
||||
router.push('/account/team');
|
||||
} else {
|
||||
onSwitchTeam(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box w={'100%'}>
|
||||
<MySelect {...props} value={userInfo?.team?.teamId} list={formatTeamList} />
|
||||
<MySelect
|
||||
{...props}
|
||||
value={userInfo?.team?.teamId}
|
||||
list={formatTeamList}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -111,7 +111,7 @@ const BillTable = () => {
|
||||
list={billTypeList}
|
||||
value={billType}
|
||||
size={'sm'}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setBillType(e);
|
||||
}}
|
||||
w={'130px'}
|
||||
@@ -220,13 +220,7 @@ function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: ()
|
||||
{bill.metadata.payWay === 'balance' ? (
|
||||
t('user:bill.not_need_invoice')
|
||||
) : (
|
||||
<Box>
|
||||
{
|
||||
(bill.metadata.payWay = bill.hasInvoice
|
||||
? t('account_bill:yes')
|
||||
: t('account_bill:no'))
|
||||
}
|
||||
</Box>
|
||||
<Box>{bill.hasInvoice ? t('account_bill:yes') : t('account_bill:no')}</Box>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
|
@@ -213,7 +213,7 @@ export const ModelEditModal = ({
|
||||
<Td textAlign={'right'}>
|
||||
<MySelect
|
||||
value={provider}
|
||||
onchange={(value) => setValue('provider', value)}
|
||||
onChange={(value) => setValue('provider', value)}
|
||||
list={providerList.current}
|
||||
{...InputStyles}
|
||||
/>
|
||||
|
@@ -79,7 +79,7 @@ const EditChannelModal = ({
|
||||
order: provider.order,
|
||||
defaultBaseUrl: value.defaultBaseUrl,
|
||||
keyHelp: value.keyHelp,
|
||||
icon: provider.avatar,
|
||||
icon: mapData?.avatar ?? provider.avatar,
|
||||
label: t(mapData.label as any),
|
||||
value: Number(key)
|
||||
};
|
||||
@@ -90,6 +90,7 @@ const EditChannelModal = ({
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const selectedProvider = useMemo(() => {
|
||||
const res = providerList.find((item) => item.value === providerType);
|
||||
return res;
|
||||
@@ -193,7 +194,7 @@ const EditChannelModal = ({
|
||||
placeholder={t('account_model:select_provider_placeholder')}
|
||||
value={providerType}
|
||||
isSearch
|
||||
onchange={(val) => {
|
||||
onChange={(val) => {
|
||||
setValue('type', val);
|
||||
}}
|
||||
/>
|
||||
@@ -332,6 +333,8 @@ const MultipleSelect = ({ value = [], list = [], onSelect }: SelectProps) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const onclickItem = useCallback(
|
||||
(val: string) => {
|
||||
if (value.includes(val)) {
|
||||
@@ -342,12 +345,11 @@ const MultipleSelect = ({ value = [], list = [], onSelect }: SelectProps) => {
|
||||
top: BoxRef.current.scrollHeight
|
||||
});
|
||||
}
|
||||
setSearch('');
|
||||
},
|
||||
[value, onSelect]
|
||||
);
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const filterUnSelected = useMemo(() => {
|
||||
return list
|
||||
.filter((item) => !value.includes(item.value))
|
||||
|
@@ -25,6 +25,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { batchRun } from '@fastgpt/global/common/system/utils';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
type ModelTestItem = {
|
||||
label: React.ReactNode;
|
||||
@@ -34,7 +35,15 @@ type ModelTestItem = {
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void }) => {
|
||||
const ModelTest = ({
|
||||
channelId,
|
||||
models,
|
||||
onClose
|
||||
}: {
|
||||
channelId: number;
|
||||
models: string[];
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [testModelList, setTestModelList] = useState<ModelTestItem[]>([]);
|
||||
@@ -57,6 +66,7 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
colorSchema: 'red'
|
||||
}
|
||||
});
|
||||
|
||||
const { loading: loadingModels } = useRequest2(getSystemModelList, {
|
||||
manual: false,
|
||||
refreshDeps: [models],
|
||||
@@ -95,7 +105,7 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
);
|
||||
const start = Date.now();
|
||||
try {
|
||||
await getTestModel(model);
|
||||
await getTestModel({ model, channelId });
|
||||
const duration = Date.now() - start;
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
@@ -134,13 +144,47 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
refreshDeps: [testModelList]
|
||||
}
|
||||
);
|
||||
const { runAsync: onTestOneModel, loading: testingOneModel } = useRequest2(
|
||||
async (model: string) => {
|
||||
const start = Date.now();
|
||||
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model ? { ...item, status: 'running', message: '' } : item
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
await getTestModel({ model, channelId });
|
||||
const duration = Date.now() - start;
|
||||
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model ? { ...item, status: 'success', duration: duration / 1000 } : item
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model ? { ...item, status: 'error', message: getErrText(error) } : item
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true
|
||||
}
|
||||
);
|
||||
|
||||
const isTestLoading = testingOneModel || isTesting;
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={'core/chat/sendLight'}
|
||||
isLoading={loadingModels}
|
||||
title={t('account_model:model_test')}
|
||||
w={'600px'}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '1090px']}
|
||||
isOpen
|
||||
>
|
||||
<ModalBody>
|
||||
@@ -148,8 +192,10 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('account_model:model')}</Th>
|
||||
<Th>{t('account_model:model_name')}</Th>
|
||||
<Th>{t('account:model.model_id')}</Th>
|
||||
<Th>{t('account_model:channel_status')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -158,6 +204,7 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
return (
|
||||
<Tr key={item.model}>
|
||||
<Td>{item.label}</Td>
|
||||
<Td>{item.model}</Td>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyTag mr={1} type="borderSolid" colorSchema={data.colorSchema as any}>
|
||||
@@ -173,6 +220,16 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>
|
||||
<MyIconButton
|
||||
isLoading={isTestLoading}
|
||||
icon={'core/chat/sendLight'}
|
||||
tip={t('account:model.test_model')}
|
||||
onClick={() => {
|
||||
onTestOneModel(item.model);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
@@ -184,7 +241,7 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
<Button mr={4} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={isTesting} variant={'primary'} onClick={onStartTest}>
|
||||
<Button isLoading={isTestLoading} variant={'primary'} onClick={onStartTest}>
|
||||
{t('account_model:start_test', { num: testModelList.length })}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
@@ -74,7 +74,7 @@ const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
}
|
||||
});
|
||||
|
||||
const [testModels, setTestModels] = useState<string[]>();
|
||||
const [modelTestData, setTestModelData] = useState<{ channelId: number; models: string[] }>();
|
||||
|
||||
const isLoading =
|
||||
loadingChannelList ||
|
||||
@@ -165,7 +165,11 @@ const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
{
|
||||
icon: 'core/chat/sendLight',
|
||||
label: t('account_model:model_test'),
|
||||
onClick: () => setTestModels(item.models)
|
||||
onClick: () =>
|
||||
setTestModelData({
|
||||
channelId: item.id,
|
||||
models: item.models
|
||||
})
|
||||
},
|
||||
...(item.status === ChannelStatusEnum.ChannelStatusEnabled
|
||||
? [
|
||||
@@ -222,7 +226,9 @@ const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
onSuccess={refreshChannelList}
|
||||
/>
|
||||
)}
|
||||
{!!testModels && <ModelTest models={testModels} onClose={() => setTestModels(undefined)} />}
|
||||
{!!modelTestData && (
|
||||
<ModelTest {...modelTestData} onClose={() => setTestModelData(undefined)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -206,7 +206,7 @@ const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
list={channelList}
|
||||
placeholder={t('account_model:select_channel')}
|
||||
value={filterProps.channelId}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, channelId: val })}
|
||||
onChange={(val) => setFilterProps({ ...filterProps, channelId: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
@@ -219,7 +219,7 @@ const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
list={modelList}
|
||||
placeholder={t('account_model:select_model')}
|
||||
value={filterProps.model}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, model: val })}
|
||||
onChange={(val) => setFilterProps({ ...filterProps, model: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
@@ -234,7 +234,7 @@ const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
{ label: t('common:common.failed'), value: 'error' }
|
||||
]}
|
||||
value={filterProps.code_type}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, code_type: val })}
|
||||
onChange={(val) => setFilterProps({ ...filterProps, code_type: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
@@ -298,11 +298,15 @@ const LogDetail = ({ data, onClose }: { data: LogDetailType; onClose: () => void
|
||||
const { data: detailData } = useRequest2(
|
||||
async () => {
|
||||
if (data.code === 200) return data;
|
||||
const res = await getLogDetail(data.id);
|
||||
return {
|
||||
...res,
|
||||
...data
|
||||
};
|
||||
try {
|
||||
const res = await getLogDetail(data.id);
|
||||
return {
|
||||
...res,
|
||||
...data
|
||||
};
|
||||
} catch (error) {
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: false
|
||||
|
@@ -280,6 +280,10 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
|
||||
isCustom: true,
|
||||
isActive: true,
|
||||
|
||||
isDefault: false,
|
||||
isDefaultDatasetTextModel: false,
|
||||
isDefaultDatasetImageModel: false,
|
||||
// @ts-ignore
|
||||
type
|
||||
});
|
||||
@@ -326,7 +330,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
w={'200px'}
|
||||
bg={'myGray.50'}
|
||||
value={provider}
|
||||
onchange={setProvider}
|
||||
onChange={setProvider}
|
||||
list={filterProviderList}
|
||||
/>
|
||||
</HStack>
|
||||
@@ -338,7 +342,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
w={'150px'}
|
||||
bg={'myGray.50'}
|
||||
value={modelType}
|
||||
onchange={setModelType}
|
||||
onChange={setModelType}
|
||||
list={selectModelTypeList.current}
|
||||
/>
|
||||
</HStack>
|
||||
@@ -436,7 +440,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
<MyIconButton
|
||||
icon={'core/chat/sendLight'}
|
||||
tip={t('account:model.test_model')}
|
||||
onClick={() => onTestModel(item.model)}
|
||||
onClick={() => onTestModel({ model: item.model })}
|
||||
/>
|
||||
<MyIconButton
|
||||
icon={'common/settingLight'}
|
||||
@@ -597,7 +601,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
llm: llmModelList.find((item) => item.model === e)
|
||||
@@ -616,7 +620,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
embedding: embeddingModelList.find((item) => item.model === e)
|
||||
@@ -635,7 +639,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
tts: ttsModelList.find((item) => item.model === e)
|
||||
@@ -654,7 +658,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
stt: sttModelList.find((item) => item.model === e)
|
||||
@@ -673,7 +677,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
rerank: reRankModelList.find((item) => item.model === e)
|
||||
@@ -696,7 +700,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
datasetTextLLM: datasetModelList.find((item) => item.model === e)
|
||||
@@ -718,7 +722,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
datasetImageLLM: vlmModelList.find((item) => item.model === e)
|
||||
|
@@ -142,7 +142,7 @@ function EditModal({
|
||||
width={'fit-content'}
|
||||
>
|
||||
<Icon name="common/info" w="1rem" />
|
||||
<Box width="fit-content">{t('account_info:please_bind_contact')}</Box>
|
||||
<Box width="fit-content">{t('account_team:please_bind_contact')}</Box>
|
||||
</HStack>
|
||||
);
|
||||
})()}
|
||||
|
@@ -172,7 +172,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
</Td>
|
||||
<Td>
|
||||
{group.name === DefaultGroupName ? (
|
||||
<AvatarGroup avatars={members.map((v) => v.avatar)} groupId={group._id} />
|
||||
<AvatarGroup avatars={members.map((v) => v.avatar)} />
|
||||
) : hasGroupManagePer(group) ? (
|
||||
<MyTooltip label={t('account_team:manage_member')}>
|
||||
<Box cursor="pointer" onClick={() => onManageMember(group)}>
|
||||
@@ -180,7 +180,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
avatars={group.members.map(
|
||||
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
|
||||
)}
|
||||
groupId={group._id}
|
||||
/>
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
@@ -189,7 +188,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
avatars={group.members.map(
|
||||
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
|
||||
)}
|
||||
groupId={group._id}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
|
@@ -0,0 +1,105 @@
|
||||
import { postCreateInvitationLink } from '@/web/support/user/team/api';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Grid,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Input,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalFooter,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
InvitationLinkCreateType,
|
||||
InvitationLinkExpiresType
|
||||
} from '@fastgpt/service/support/user/team/invitationLink/type';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
function CreateInvitationModal({ onClose }: { onClose: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const expiresOptions: Array<{ label: string; value: InvitationLinkExpiresType }> = [
|
||||
{ label: t('account_team:30mins'), value: '30m' }, // 30 mins
|
||||
{ label: t('account_team:7days'), value: '7d' }, // 7 days
|
||||
{ label: t('account_team:1year'), value: '1y' } // 1 year
|
||||
];
|
||||
|
||||
const { register, handleSubmit, watch, setValue } = useForm<InvitationLinkCreateType>({
|
||||
defaultValues: {
|
||||
description: '',
|
||||
expires: expiresOptions[1].value,
|
||||
usedTimesLimit: 1
|
||||
}
|
||||
});
|
||||
|
||||
const expires = watch('expires');
|
||||
const usedTimesLimit = watch('usedTimesLimit');
|
||||
|
||||
const { runAsync: createInvitationLink, loading } = useRequest2(postCreateInvitationLink, {
|
||||
manual: true,
|
||||
successToast: t('common:common.Create Success'),
|
||||
errorToast: t('common:common.Create Failed'),
|
||||
onFinally: () => onClose()
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="common/addLight"
|
||||
iconColor="primary.500"
|
||||
title={<Box>{t('account_team:create_invitation_link')}</Box>}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Grid gap={6} templateColumns="max-content 1fr" alignItems="center">
|
||||
<>
|
||||
<FormLabel required={true}>{t('account_team:invitation_link_description')}</FormLabel>
|
||||
<Input
|
||||
placeholder={t('account_team:invitation_link_description')}
|
||||
{...register('description', { required: true })}
|
||||
/>
|
||||
</>
|
||||
|
||||
<>
|
||||
<FormLabel required={true}>{t('account_team:expires')}</FormLabel>
|
||||
<MySelect
|
||||
list={expiresOptions}
|
||||
value={expires}
|
||||
onChange={(val) => setValue('expires', val)}
|
||||
minW="120px"
|
||||
/>
|
||||
</>
|
||||
|
||||
<>
|
||||
<FormLabel required={true}>{t('account_team:used_times_limit')}</FormLabel>
|
||||
<RadioGroup
|
||||
onChange={(val: '1' | '-1') => setValue('usedTimesLimit', Number(val) as 1 | -1)}
|
||||
value={String(usedTimesLimit)}
|
||||
>
|
||||
<HStack gap={6}>
|
||||
<Radio value="1">{t('account_team:1person')}</Radio>
|
||||
<Radio value="-1">{t('account_team:unlimited')}</Radio>
|
||||
</HStack>
|
||||
</RadioGroup>
|
||||
</>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button isLoading={loading} onClick={onClose} variant="outline">
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={loading} onClick={handleSubmit(createInvitationLink)} ml="4">
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateInvitationModal;
|
@@ -0,0 +1,77 @@
|
||||
import { getInvitationInfo, postAcceptInvitationLink } from '@/web/support/user/team/api';
|
||||
import { Box, Button, Flex, ModalBody, ModalCloseButton } from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '../context';
|
||||
|
||||
function Invite({ invitelinkid }: { invitelinkid: string }) {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { onSwitchTeam } = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
const onClose = () => {
|
||||
router.push('/account/team');
|
||||
};
|
||||
|
||||
const { data: invitationInfo } = useRequest2(() => getInvitationInfo(invitelinkid), {
|
||||
manual: false,
|
||||
onError: onClose
|
||||
});
|
||||
|
||||
const { runAsync: acceptInvitation, loading: accepting } = useRequest2(
|
||||
() => postAcceptInvitationLink(invitelinkid),
|
||||
{
|
||||
manual: true,
|
||||
successToast: t('common:common.Success'),
|
||||
onSuccess: async () => {
|
||||
onSwitchTeam(invitationInfo!.teamId);
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return invitationInfo ? (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="support/user/usersLight"
|
||||
title={t('account_team:handle_invitation')}
|
||||
iconColor={'primary.600'}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Flex
|
||||
key={invitationInfo._id}
|
||||
alignItems={'center'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
borderRadius={'md'}
|
||||
px={3}
|
||||
py={2}
|
||||
>
|
||||
<Avatar src={invitationInfo.teamAvatar} w={['16px', '23px']} />
|
||||
<Box mx={2}>{invitationInfo.teamName}</Box>
|
||||
<Box flex={1} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={'solid'}
|
||||
colorScheme="green"
|
||||
onClick={acceptInvitation}
|
||||
isLoading={accepting}
|
||||
>
|
||||
{t('account_team:accept')}
|
||||
</Button>
|
||||
<Button size="sm" ml={2} variant="outline" onClick={onClose} isLoading={accepting}>
|
||||
{t('account_team:ignore')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export default Invite;
|
@@ -0,0 +1,278 @@
|
||||
import MemberTag from '@/components/support/user/team/Info/MemberTag';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getInvitationLinkList, putUpdateInvitationInfo } from '@/web/support/user/team/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Flex,
|
||||
Grid,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import AvatarGroup from '@fastgpt/web/components/common/Avatar/AvatarGroup';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import format from 'date-fns/format';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const CreateInvitationModal = dynamic(() => import('./CreateInvitationModal'));
|
||||
|
||||
const InviteModal = ({
|
||||
teamId,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
teamId: string;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data: invitationLinkList,
|
||||
loading: isLoadingLink,
|
||||
runAsync: refetchInvitationLinkList
|
||||
} = useRequest2(() => getInvitationLinkList(), {
|
||||
manual: false
|
||||
});
|
||||
|
||||
const { isOpen: isOpenCreate, onOpen: onOpenCreate, onClose: onCloseCreate } = useDisclosure();
|
||||
|
||||
const isLoading = isLoadingLink;
|
||||
const { copyData } = useCopyData();
|
||||
const { userInfo } = useUserStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const onCopy = useCallback(
|
||||
(linkId: string) => {
|
||||
const url = location.origin + `/account/team?invitelinkid=${linkId}`;
|
||||
const teamName = userInfo?.team.teamName;
|
||||
const systemName = feConfigs.systemTitle;
|
||||
const userName = userInfo?.team.memberName;
|
||||
copyData(
|
||||
t('account_team:invitation_copy_link', {
|
||||
teamName,
|
||||
systemName,
|
||||
userName,
|
||||
url
|
||||
})
|
||||
);
|
||||
},
|
||||
[copyData]
|
||||
);
|
||||
|
||||
const { runAsync: onForbid, loading: forbiding } = useRequest2(
|
||||
(linkId: string) =>
|
||||
putUpdateInvitationInfo({
|
||||
linkId,
|
||||
forbidden: true
|
||||
}),
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: refetchInvitationLinkList,
|
||||
successToast: t('account_team:forbid_success')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isLoading={isLoading}
|
||||
isOpen
|
||||
iconSrc="common/inviteLight"
|
||||
iconColor="primary.600"
|
||||
title={t('account_team:invite_member')}
|
||||
overflow={'unset'}
|
||||
onClose={onClose}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '820px']}
|
||||
>
|
||||
<ModalBody maxH="500px">
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'} mb={4}>
|
||||
<HStack>
|
||||
<Icon name="common/list" w="16px" />
|
||||
<Box ml="6px" fontSize="md">
|
||||
{t('account_team:invitation_link_list')}
|
||||
</Box>
|
||||
</HStack>
|
||||
<Button onClick={onOpenCreate}>{t('account_team:create_invitation_link')}</Button>
|
||||
</Flex>
|
||||
<TableContainer overflowY={'auto'}>
|
||||
<Table fontSize={'sm'} overflow={'unset'}>
|
||||
<Thead>
|
||||
<Tr bgColor={'white !important'}>
|
||||
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||
{t('account_team:invitation_link_description')}
|
||||
</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:expires')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:used_times_limit')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:invited')}</Th>
|
||||
<Th bgColor="myGray.100" borderRightRadius="6px">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
{!!invitationLinkList?.length && (
|
||||
<Tbody overflow={'unset'}>
|
||||
{invitationLinkList?.map((item) => {
|
||||
const isForbidden = item.forbidden || new Date(item.expires) < new Date();
|
||||
return (
|
||||
<Tr key={item._id} overflow={'unset'}>
|
||||
<Td maxW="200px" minW="100px">
|
||||
{item.description}
|
||||
</Td>
|
||||
<Td>
|
||||
{isForbidden ? (
|
||||
<Tag colorSchema="gray">{t('account_team:has_forbidden')}</Tag>
|
||||
) : (
|
||||
format(new Date(item.expires), 'yyyy-MM-dd HH:mm')
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{item.usedTimesLimit === -1
|
||||
? t('account_team:unlimited')
|
||||
: item.usedTimesLimit}
|
||||
</Td>
|
||||
<Td>
|
||||
{item.members.length > 0 && (
|
||||
<MyPopover
|
||||
w="fit-content"
|
||||
Trigger={
|
||||
<Box
|
||||
borderRadius="md"
|
||||
cursor="pointer"
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
p="1.5"
|
||||
w="fit-content"
|
||||
>
|
||||
<AvatarGroup max={3} avatars={item.members.map((i) => i.avatar)} />
|
||||
</Box>
|
||||
}
|
||||
trigger="click"
|
||||
closeOnBlur={true}
|
||||
>
|
||||
{() => (
|
||||
<Box py="4" maxH="200px" w="fit-content">
|
||||
<Flex mx="4" justifyContent="center" alignItems={'center'}>
|
||||
<Box>{t('account_team:has_invited')}</Box>
|
||||
<Box
|
||||
ml="auto"
|
||||
bg="myGray.200"
|
||||
px="2"
|
||||
borderRadius="md"
|
||||
fontSize="sm"
|
||||
>
|
||||
{item.members.length}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Divider my="2" mx="4" />
|
||||
<Grid
|
||||
w="fit-content"
|
||||
mt="2"
|
||||
gridRowGap="4"
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
overflow="auto"
|
||||
alignItems="center"
|
||||
mx="4"
|
||||
>
|
||||
{item.members.map((member) => (
|
||||
<Box key={member.tmbId} justifySelf="start">
|
||||
<MemberTag name={member.name} avatar={member.avatar} />
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{!isForbidden && (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onCopy(item._id)}
|
||||
color="myGray.900"
|
||||
>
|
||||
<Icon name="common/link" w="16px" mr="1" />
|
||||
{t('account_team:copy_link')}
|
||||
</Button>
|
||||
<MyPopover
|
||||
placement="bottom-end"
|
||||
Trigger={
|
||||
<Button variant="outline" ml="10px" size="sm" color="myGray.900">
|
||||
<Icon name="common/lineStop" w="16px" mr="1" />
|
||||
{t('account_team:forbidden')}
|
||||
</Button>
|
||||
}
|
||||
closeOnBlur={true}
|
||||
>
|
||||
{({ onClose: onClosePopover }) => (
|
||||
<Box p={4}>
|
||||
<Box fontWeight={400} whiteSpace="pre-wrap">
|
||||
{t('account_team:forbid_hint')}
|
||||
</Box>
|
||||
<Flex gap={2} mt={2} justifyContent={'flex-end'}>
|
||||
<Button variant="outline" onClick={onClosePopover}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={forbiding}
|
||||
variant="outline"
|
||||
colorScheme="red"
|
||||
onClick={() => {
|
||||
onForbid(item._id);
|
||||
onClosePopover();
|
||||
}}
|
||||
>
|
||||
{t('account_team:confirm_forbidden')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
</>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
)}
|
||||
</Table>
|
||||
{!invitationLinkList?.length && <EmptyTip />}
|
||||
</TableContainer>
|
||||
</ModalBody>
|
||||
<ModalFooter justifyContent={'flex-start'}>
|
||||
<Tag colorSchema="blue" marginBlock="2">
|
||||
<Box>{t('account_team:invitation_link_auto_clean_hint')}</Box>
|
||||
</Tag>
|
||||
</ModalFooter>
|
||||
{isOpenCreate && (
|
||||
<CreateInvitationModal
|
||||
onClose={() => Promise.all([onCloseCreate(), refetchInvitationLinkList()])}
|
||||
/>
|
||||
)}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default InviteModal;
|
@@ -1,90 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModalCloseButton, ModalBody, Box, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import TagTextarea from '@/components/common/Textarea/TagTextarea';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { postInviteTeamMember } from '@/web/support/user/team/api';
|
||||
import { useConfirm } from '@fastgpt/web/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 { runAsync: onInvite, loading: isLoading } = useRequest2(
|
||||
() =>
|
||||
postInviteTeamMember({
|
||||
teamId,
|
||||
usernames: inviteUsernames
|
||||
}),
|
||||
{
|
||||
onSuccess(res: InviteMemberResponse) {
|
||||
onSuccess();
|
||||
openConfirm(
|
||||
() => onClose(),
|
||||
undefined,
|
||||
<Box whiteSpace={'pre-wrap'}>
|
||||
{t('user:team.Invite Member Success Tip', {
|
||||
success: res.invite.length,
|
||||
inValid: res.inValid.map((item) => item.username).join(', '),
|
||||
inTeam: res.inTeam.map((item) => item.username).join(', ')
|
||||
})}
|
||||
</Box>
|
||||
)();
|
||||
},
|
||||
errorToast: t('user:team.Invite Member Failed Tip')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="common/inviteLight"
|
||||
iconColor="primary.600"
|
||||
title={
|
||||
<Box>
|
||||
<Box>{t('common:user.team.Invite Member')}</Box>
|
||||
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
|
||||
{t('common:user.team.Invite Member Tips')}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
maxW={['90vw', '400px']}
|
||||
overflow={'unset'}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Box mb={2}>{t('common:user.Account')}</Box>
|
||||
<TagTextarea defaultValues={inviteUsernames} onUpdate={setInviteUsernames} />
|
||||
</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;
|
@@ -17,7 +17,7 @@ import {
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { delRemoveMember, updateStatus } from '@/web/support/user/team/api';
|
||||
import { delRemoveMember, postRestoreMember } from '@/web/support/user/team/api';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
@@ -41,7 +41,7 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { useState } from 'react';
|
||||
import { downloadFetch } from '@/web/common/system/utils';
|
||||
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
const InviteModal = dynamic(() => import('./Invite/InviteModal'));
|
||||
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
||||
|
||||
function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
@@ -118,7 +118,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
errorToast: t('account_team:sync_member_failed')
|
||||
});
|
||||
|
||||
const { runAsync: onRestore, loading: isUpdateInvite } = useRequest2(updateStatus, {
|
||||
const { runAsync: onRestore, loading: isUpdateInvite } = useRequest2(postRestoreMember, {
|
||||
onSuccess() {
|
||||
refetchMembers();
|
||||
},
|
||||
@@ -175,22 +175,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name="common/inviteLight" w={'16px'} color={'white'} />}
|
||||
onClick={() => {
|
||||
if (
|
||||
teamPlanStatus?.standardConstants?.maxTeamMember &&
|
||||
teamPlanStatus.standardConstants.maxTeamMember <= members.length
|
||||
) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common:user.team.Over Max Member Tip', {
|
||||
max: teamPlanStatus.standardConstants.maxTeamMember
|
||||
})
|
||||
});
|
||||
setNotSufficientModalType(TeamErrEnum.teamMemberOverSize);
|
||||
} else {
|
||||
onOpenInvite();
|
||||
}
|
||||
}}
|
||||
onClick={onOpenInvite}
|
||||
>
|
||||
{t('account_team:user_team_invite_member')}
|
||||
</Button>
|
||||
@@ -236,7 +221,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||
{t('account_team:user_name')}
|
||||
</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:contact')}</Th>
|
||||
<Th bgColor="myGray.100">{t('common:contact_way')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:org')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:join_update_time')}</Th>
|
||||
<Th borderRightRadius="6px" bgColor="myGray.100">
|
||||
@@ -253,12 +238,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
<Avatar src={member.avatar} w={['18px', '22px']} borderRadius={'50%'} />
|
||||
<Box className={'textEllipsis'}>
|
||||
{member.memberName}
|
||||
{member.status === 'waiting' && (
|
||||
<Tag ml="2" colorSchema="yellow">
|
||||
{t('account_team:waiting')}
|
||||
</Tag>
|
||||
)}
|
||||
{member.status === 'leave' && (
|
||||
{member.status !== 'active' && (
|
||||
<Tag ml="2" colorSchema="gray">
|
||||
{t('account_team:leave')}
|
||||
</Tag>
|
||||
@@ -295,7 +275,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
{userInfo?.team.permission.hasManagePer &&
|
||||
member.role !== TeamMemberRoleEnum.owner &&
|
||||
member.tmbId !== userInfo?.team.tmbId &&
|
||||
(member.status !== TeamMemberStatusEnum.leave ? (
|
||||
(member.status === TeamMemberStatusEnum.active ? (
|
||||
<Icon
|
||||
name={'common/trash'}
|
||||
cursor={'pointer'}
|
||||
@@ -320,30 +300,28 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name={'common/confirm/restoreTip'}
|
||||
cursor={'pointer'}
|
||||
w="1rem"
|
||||
p="1"
|
||||
borderRadius="sm"
|
||||
_hover={{
|
||||
color: 'primary.500',
|
||||
bgColor: 'myGray.100'
|
||||
}}
|
||||
onClick={() => {
|
||||
openRestoreMember(
|
||||
() =>
|
||||
onRestore({
|
||||
tmbId: member.tmbId,
|
||||
status: TeamMemberStatusEnum.active
|
||||
}),
|
||||
undefined,
|
||||
t('account_team:restore_tip', {
|
||||
username: member.memberName
|
||||
})
|
||||
)();
|
||||
}}
|
||||
/>
|
||||
member.status === TeamMemberStatusEnum.forbidden && (
|
||||
<Icon
|
||||
name={'common/confirm/restoreTip'}
|
||||
cursor={'pointer'}
|
||||
w="1rem"
|
||||
p="1"
|
||||
borderRadius="sm"
|
||||
_hover={{
|
||||
color: 'primary.500',
|
||||
bgColor: 'myGray.100'
|
||||
}}
|
||||
onClick={() => {
|
||||
openRestoreMember(
|
||||
() => onRestore(member.tmbId),
|
||||
undefined,
|
||||
t('account_team:restore_tip', {
|
||||
username: member.memberName
|
||||
})
|
||||
)();
|
||||
}}
|
||||
/>
|
||||
)
|
||||
))}
|
||||
</Td>
|
||||
</Tr>
|
||||
|
@@ -101,6 +101,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
||||
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
|
||||
async (teamId: string) => {
|
||||
await putSwitchTeam(teamId);
|
||||
refetchMembers();
|
||||
return initUserInfo();
|
||||
},
|
||||
{
|
||||
|
Reference in New Issue
Block a user