Files
FastGPT/projects/app/src/components/support/apikey/Table.tsx
Archer e812ad6e84 feat: chunk index independent config (#4271)
* sync collection

* remove lock

* feat: chunk index independent config

* feat: add max chunksize to split chunk function

* remove log

* update doc

* remove

* remove log
2025-03-27 10:05:31 +08:00

399 lines
12 KiB
TypeScript

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 dayjs from 'dayjs';
import { AddIcon } from '@chakra-ui/icons';
import { useCopyData } from '@fastgpt/web/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, useRequest2 } 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 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 theme = useTheme();
const { copyData } = useCopyData();
const { feConfigs } = useSystemStore();
const [baseUrl, setBaseUrl] = useState('https://tryfastgpt.ai/api');
const [editData, setEditData] = useState<EditProps>();
const [apiKey, setApiKey] = useState('');
const { ConfirmModal, openConfirm } = useConfirm({
type: 'delete',
content: t('common:delete_api')
});
const { runAsync: onclickRemove } = useRequest2(delOpenApiById, {
onSuccess() {
refetch();
}
});
const {
data: apiKeys = [],
loading: isGetting,
run: refetch
} = useRequest2(() => getOpenApiKeys({ appId }), {
manual: false,
refreshDeps: [appId]
});
useEffect(() => {
setBaseUrl(feConfigs?.customApiDomain || `${location.origin}/api`);
}, [feConfigs?.customApiDomain]);
return (
<MyBox
isLoading={isGetting}
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="keyPrimary"
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'}
userSelect={'all'}
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 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: t('workflow:create_link_error'),
onSuccess: onCreate
});
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
mutationFn: (e: EditProps) => {
//@ts-ignore
return putOpenApiKey(e);
},
errorToast: t('workflow:update_link_error'),
onSuccess: onEdit
});
return (
<MyModal
isOpen={true}
iconSrc="keyPrimary"
title={isEdit ? t('publish:edit_api_key') : t('publish:create_api_key')}
>
<ModalBody>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 90px'}>{t('common:Name')}</FormLabel>
<Input
placeholder={t('publish:key_alias') || 'key_alias'}
maxLength={100}
{...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>
);
}