jingyang 982325d066
fix i18next.d.ts (#2064)
* fix i18next.d.ts

* feat: packages web i18n

* delete file
2024-07-17 15:27:51 +08:00

402 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useMemo, useState } from 'react';
import {
Box,
Button,
Flex,
ModalFooter,
ModalBody,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
useTheme,
Link,
Input,
IconButton
} from '@chakra-ui/react';
import {
getOpenApiKeys,
createAOpenApiKey,
delOpenApiById,
putOpenApiKey
} from '@/web/support/openapi/api';
import type { EditApiKeyProps } from '@/global/support/openapi/api.d';
import { useQuery, useMutation } from '@tanstack/react-query';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import dayjs from 'dayjs';
import { AddIcon } from '@chakra-ui/icons';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useForm } from 'react-hook-form';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { getDocPath } from '@/web/common/system/doc';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useI18n } from '@/web/context/I18n';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import MyBox from '@fastgpt/web/components/common/MyBox';
type EditProps = EditApiKeyProps & { _id?: string };
const defaultEditData: EditProps = {
name: '',
limit: {
maxUsagePoints: -1
}
};
const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
const { t } = useTranslation();
const { Loading } = useLoading();
const theme = useTheme();
const { copyData } = useCopyData();
const { feConfigs } = useSystemStore();
const [baseUrl, setBaseUrl] = useState('https://fastgpt.in/api');
const [editData, setEditData] = useState<EditProps>();
const [apiKey, setApiKey] = useState('');
const { ConfirmModal, openConfirm } = useConfirm({
type: 'delete',
content: '确认删除该API密钥删除后该密钥立即失效对应的对话日志不会删除请确认'
});
const { mutate: onclickRemove, isLoading: isDeleting } = useMutation({
mutationFn: async (id: string) => {
return delOpenApiById(id);
},
onSuccess() {
refetch();
}
});
const {
data: apiKeys = [],
isLoading: isGetting,
refetch
} = useQuery(['getOpenApiKeys', appId], () => getOpenApiKeys({ appId }));
useEffect(() => {
setBaseUrl(feConfigs?.customApiDomain || `${location.origin}/api`);
}, []);
return (
<MyBox
isLoading={isGetting || isDeleting}
display={'flex'}
flexDirection={'column'}
h={'100%'}
position={'relative'}
>
<Box display={['block', 'flex']} alignItems={'center'}>
<Box flex={1}>
<Flex alignItems={'flex-end'}>
<Box color={'myGray.900'} fontSize={'lg'}>
{t('common:support.openapi.Api manager')}
</Box>
{feConfigs?.docUrl && (
<Link
href={feConfigs.openAPIDocUrl || getDocPath('/docs/development/openapi')}
target={'_blank'}
ml={1}
color={'primary.500'}
fontSize={'sm'}
>
{t('common:common.Read document')}
</Link>
)}
</Flex>
<Box fontSize={'mini'} color={'myGray.600'}>
{tips}
</Box>
</Box>
<Flex
mt={[2, 0]}
bg={'myGray.100'}
py={2}
px={4}
borderRadius={'md'}
cursor={'pointer'}
userSelect={'none'}
onClick={() => copyData(baseUrl, t('common:support.openapi.Copy success'))}
>
<Box border={theme.borders.md} px={2} borderRadius={'md'} fontSize={'xs'}>
{t('common:support.openapi.Api baseurl')}
</Box>
<Box ml={2} fontSize={'sm'}>
{baseUrl}
</Box>
</Flex>
<Box mt={[2, 0]} textAlign={'right'}>
<Button
ml={3}
leftIcon={<AddIcon fontSize={'md'} />}
variant={'whitePrimary'}
onClick={() =>
setEditData({
...defaultEditData,
appId
})
}
>
{t('common:New Create')}
</Button>
</Box>
</Box>
<TableContainer mt={3} position={'relative'} minH={'300px'}>
<Table>
<Thead>
<Tr>
<Th>{t('common:Name')}</Th>
<Th>Api Key</Th>
<Th>{t('common:support.outlink.Usage points')}</Th>
{feConfigs?.isPlus && (
<>
<Th>{t('common:common.Expired Time')}</Th>
</>
)}
<Th>{t('common:common.Create Time')}</Th>
<Th>{t('common:common.Last use time')}</Th>
<Th />
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{apiKeys.map(({ _id, name, usagePoints, limit, apiKey, createTime, lastUsedTime }) => (
<Tr key={_id}>
<Td>{name}</Td>
<Td>{apiKey}</Td>
<Td>
{Math.round(usagePoints)}/
{feConfigs?.isPlus && limit?.maxUsagePoints && limit?.maxUsagePoints > -1
? `${limit?.maxUsagePoints}`
: t('common:common.Unlimited')}
</Td>
{feConfigs?.isPlus && (
<>
<Td whiteSpace={'pre-wrap'}>
{limit?.expiredTime
? dayjs(limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
: '-'}
</Td>
</>
)}
<Td whiteSpace={'pre-wrap'}>{dayjs(createTime).format('YYYY/MM/DD\nHH:mm:ss')}</Td>
<Td whiteSpace={'pre-wrap'}>
{lastUsedTime
? dayjs(lastUsedTime).format('YYYY/MM/DD\nHH:mm:ss')
: t('common:common.Un used')}
</Td>
<Td>
<MyMenu
offset={[-50, 5]}
Button={
<IconButton
icon={<MyIcon name={'more'} w={'14px'} />}
name={'more'}
variant={'whitePrimary'}
size={'sm'}
aria-label={''}
/>
}
menuList={[
{
children: [
{
label: t('common:common.Edit'),
icon: 'edit',
onClick: () =>
setEditData({
_id,
name,
limit,
appId
})
},
{
label: t('common:common.Delete'),
icon: 'delete',
type: 'danger',
onClick: () => openConfirm(() => onclickRemove(_id))()
}
]
}
]}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{!!editData && (
<EditKeyModal
defaultData={editData}
onClose={() => setEditData(undefined)}
onCreate={(id) => {
setApiKey(id);
refetch();
setEditData(undefined);
}}
onEdit={() => {
refetch();
setEditData(undefined);
}}
/>
)}
<ConfirmModal />
<MyModal
isOpen={!!apiKey}
w={['400px', '600px']}
iconSrc="/imgs/modal/key.svg"
title={
<Box>
<Box fontWeight={'bold'}>{t('common:support.openapi.New api key')}</Box>
<Box fontSize={'xs'} color={'myGray.600'}>
{t('common:support.openapi.New api key tip')}
</Box>
</Box>
}
onClose={() => setApiKey('')}
>
<ModalBody pt={5}>
<Flex
bg={'myGray.100'}
px={3}
py={2}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
cursor={'pointer'}
borderRadius={'md'}
onClick={() => copyData(apiKey)}
>
<Box flex={1}>{apiKey}</Box>
<MyIcon ml={1} name={'copy'} w={'16px'}></MyIcon>
</Flex>
</ModalBody>
<ModalFooter>
<Button variant="whiteBase" onClick={() => setApiKey('')}>
{t('common:common.OK')}
</Button>
</ModalFooter>
</MyModal>
</MyBox>
);
};
export default React.memo(ApiKeyTable);
// edit link modal
function EditKeyModal({
defaultData,
onClose,
onCreate,
onEdit
}: {
defaultData: EditProps;
onClose: () => void;
onCreate: (id: string) => void;
onEdit: () => void;
}) {
const { t } = useTranslation();
const { publishT } = useI18n();
const isEdit = useMemo(() => !!defaultData._id, [defaultData]);
const { feConfigs } = useSystemStore();
const {
register,
setValue,
handleSubmit: submitShareChat
} = useForm({
defaultValues: defaultData
});
const { mutate: onclickCreate, isLoading: creating } = useRequest({
mutationFn: async (e: EditProps) => createAOpenApiKey(e),
errorToast: '创建链接异常',
onSuccess: onCreate
});
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
mutationFn: (e: EditProps) => {
//@ts-ignore
return putOpenApiKey(e);
},
errorToast: '更新链接异常',
onSuccess: onEdit
});
return (
<MyModal
isOpen={true}
iconSrc="/imgs/modal/key.svg"
title={isEdit ? publishT('Edit API Key') : publishT('Create API Key')}
>
<ModalBody>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 90px'}>{t('common:Name')}</FormLabel>
<Input
placeholder={publishT('key alias') || 'key alias'}
maxLength={20}
{...register('name', {
required: t('common:common.name_is_empty') || 'name_is_empty'
})}
/>
</Flex>
{feConfigs?.isPlus && (
<>
<Flex alignItems={'center'} mt={4}>
<FormLabel display={'flex'} flex={'0 0 90px'} alignItems={'center'}>
{t('common:support.outlink.Max usage points')}
<QuestionTip
ml={1}
label={t('common:support.outlink.Max usage points tip')}
></QuestionTip>
</FormLabel>
<Input
{...register('limit.maxUsagePoints', {
min: -1,
max: 10000000,
valueAsNumber: true,
required: true
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<FormLabel flex={'0 0 90px'}>{t('common:common.Expired Time')}</FormLabel>
<Input
type="datetime-local"
defaultValue={
defaultData.limit?.expiredTime
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
: ''
}
onChange={(e) => {
setValue('limit.expiredTime', new Date(e.target.value));
}}
/>
</Flex>
</>
)}
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button
isLoading={creating || updating}
onClick={submitShareChat((data) => (isEdit ? onclickUpdate(data) : onclickCreate(data)))}
>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
}