feat: API access key management #2846 (#2865)

### What problem does this PR solve?

feat: API access key management #2846
feat: Render markdown file with remark-loader #2846

### Type of change

- [ ] Bug Fix (non-breaking change which fixes an issue)
- [x] New Feature (non-breaking change which adds functionality)
- [ ] Documentation Update
- [ ] Refactoring
- [ ] Performance Improvement
- [ ] Other (please describe):
This commit is contained in:
balibabu
2024-10-16 15:57:39 +08:00
committed by GitHub
parent 4991107822
commit e5d3ab0332
32 changed files with 1303 additions and 165 deletions

View File

@ -12,9 +12,9 @@ const ChatApiKeyModal = ({
dialogId,
hideModal,
idKey,
}: IModalProps<any> & { dialogId: string; idKey: string }) => {
}: IModalProps<any> & { dialogId?: string; idKey: string }) => {
const { createToken, removeToken, tokenList, listLoading, creatingLoading } =
useOperateApiKey(dialogId, idKey);
useOperateApiKey(idKey, dialogId);
const { t } = useTranslate('chat');
const columns: TableProps<IToken>['columns'] = [
@ -36,9 +36,7 @@ const ChatApiKeyModal = ({
render: (_, record) => (
<Space size="middle">
<CopyToClipboard text={record.token}></CopyToClipboard>
<DeleteOutlined
onClick={() => removeToken(record.token, record.tenant_id)}
/>
<DeleteOutlined onClick={() => removeToken(record.token)} />
</Space>
),
},
@ -60,8 +58,13 @@ const ChatApiKeyModal = ({
dataSource={tokenList}
rowKey={'token'}
loading={listLoading}
pagination={false}
/>
<Button onClick={createToken} loading={creatingLoading}>
<Button
onClick={createToken}
loading={creatingLoading}
disabled={tokenList.length > 0}
>
{t('createNewKey')}
</Button>
</Modal>

View File

@ -0,0 +1,64 @@
import HightLightMarkdown from '@/components/highlight-markdown';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import { Button, Card, Flex, Space } from 'antd';
import apiDoc from '../../../../../docs/references/api.md';
import ChatApiKeyModal from '../chat-api-key-modal';
import EmbedModal from '../embed-modal';
import { usePreviewChat, useShowEmbedModal } from '../hooks';
import BackendServiceApi from './backend-service-api';
const ApiContent = ({
id,
idKey,
hideChatPreviewCard = false,
}: {
id?: string;
idKey: string;
hideChatPreviewCard?: boolean;
}) => {
const { t } = useTranslate('chat');
const {
visible: apiKeyVisible,
hideModal: hideApiKeyModal,
showModal: showApiKeyModal,
} = useSetModalState();
const { embedVisible, hideEmbedModal, showEmbedModal, embedToken } =
useShowEmbedModal(idKey, id);
const { handlePreview } = usePreviewChat(idKey, id);
return (
<div>
<Flex vertical gap={'middle'}>
<BackendServiceApi show={showApiKeyModal}></BackendServiceApi>
{!hideChatPreviewCard && (
<Card title={`${name} Web App`}>
<Flex gap={8} vertical>
<Space size={'middle'}>
<Button onClick={handlePreview}>{t('preview')}</Button>
<Button onClick={showEmbedModal}>{t('embedded')}</Button>
</Space>
</Flex>
</Card>
)}
<HightLightMarkdown>{apiDoc}</HightLightMarkdown>
</Flex>
{apiKeyVisible && (
<ChatApiKeyModal
hideModal={hideApiKeyModal}
dialogId={id}
idKey={idKey}
></ChatApiKeyModal>
)}
{embedVisible && (
<EmbedModal
token={embedToken}
visible={embedVisible}
hideModal={hideEmbedModal}
></EmbedModal>
)}
</div>
);
};
export default ApiContent;

View File

@ -0,0 +1,35 @@
import { Button, Card, Flex, Space, Typography } from 'antd';
import { useTranslate } from '@/hooks/common-hooks';
import styles from './index.less';
const { Paragraph } = Typography;
const BackendServiceApi = ({ show }: { show(): void }) => {
const { t } = useTranslate('chat');
return (
<Card
title={
<Space size={'large'}>
<span>RAGFlow API</span>
<Button onClick={show} type="primary">
{t('apiKey')}
</Button>
</Space>
}
>
<Flex gap={8} align="center">
<b>{t('backendServiceApi')}</b>
<Paragraph
copyable={{ text: `${location.origin}/v1/api/` }}
className={styles.apiLinkText}
>
{location.origin}/v1/api/
</Paragraph>
</Flex>
</Card>
);
};
export default BackendServiceApi;

View File

@ -13,9 +13,7 @@
padding-left: 60px;
padding-bottom: 20px;
}
.linkText {
border-radius: 6px;
padding: 6px 10px;
background-color: #eff8ff;
border: 1px;
.apiLinkText {
.linkText();
margin: 0 !important;
}

View File

@ -1,65 +1,15 @@
import LineChart from '@/components/line-chart';
import { useFetchNextStats } from '@/hooks/chat-hooks';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IStats } from '@/interfaces/database/chat';
import { formatDate } from '@/utils/date';
import { Button, Card, DatePicker, Flex, Modal, Space, Typography } from 'antd';
import { RangePickerProps } from 'antd/es/date-picker';
import dayjs from 'dayjs';
import camelCase from 'lodash/camelCase';
import ChatApiKeyModal from '../chat-api-key-modal';
import EmbedModal from '../embed-modal';
import {
usePreviewChat,
useSelectChartStatsList,
useShowEmbedModal,
} from '../hooks';
import styles from './index.less';
const { Paragraph } = Typography;
const { RangePicker } = DatePicker;
const StatsLineChart = ({ statsType }: { statsType: keyof IStats }) => {
const { t } = useTranslate('chat');
const chartList = useSelectChartStatsList();
const list =
chartList[statsType]?.map((x) => ({
...x,
xAxis: formatDate(x.xAxis),
})) ?? [];
return (
<div className={styles.chartItem}>
<b className={styles.chartLabel}>{t(camelCase(statsType))}</b>
<LineChart data={list}></LineChart>
</div>
);
};
import { Modal } from 'antd';
import ApiContent from './api-content';
const ChatOverviewModal = ({
visible,
hideModal,
id,
name = '',
idKey,
}: IModalProps<any> & { id: string; name?: string; idKey: string }) => {
const { t } = useTranslate('chat');
const {
visible: apiKeyVisible,
hideModal: hideApiKeyModal,
showModal: showApiKeyModal,
} = useSetModalState();
const { embedVisible, hideEmbedModal, showEmbedModal, embedToken } =
useShowEmbedModal(id, idKey);
const { pickerValue, setPickerValue } = useFetchNextStats();
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
return current && current > dayjs().endOf('day');
};
const { handlePreview } = usePreviewChat(id, idKey);
return (
<>
@ -72,69 +22,7 @@ const ChatOverviewModal = ({
width={'100vw'}
okText={t('close', { keyPrefix: 'common' })}
>
<Flex vertical gap={'middle'}>
<Card title={t('backendServiceApi')}>
<Flex gap={8} vertical>
{t('serviceApiEndpoint')}
<Paragraph
copyable={{ text: `${location.origin}/v1/api/` }}
className={styles.linkText}
>
{location.origin}/v1/api/
</Paragraph>
</Flex>
<Space size={'middle'}>
<Button onClick={showApiKeyModal}>{t('apiKey')}</Button>
<a
href={
'https://github.com/infiniflow/ragflow/blob/main/docs/references/api.md'
}
target="_blank"
rel="noreferrer"
>
<Button>{t('apiReference')}</Button>
</a>
</Space>
</Card>
<Card title={`${name} Web App`}>
<Flex gap={8} vertical>
<Space size={'middle'}>
<Button onClick={handlePreview}>{t('preview')}</Button>
<Button onClick={showEmbedModal}>{t('embedded')}</Button>
</Space>
</Flex>
</Card>
<Space>
<b>{t('dateRange')}</b>
<RangePicker
disabledDate={disabledDate}
value={pickerValue}
onChange={setPickerValue}
allowClear={false}
/>
</Space>
<div className={styles.chartWrapper}>
<StatsLineChart statsType={'pv'}></StatsLineChart>
<StatsLineChart statsType={'round'}></StatsLineChart>
<StatsLineChart statsType={'speed'}></StatsLineChart>
<StatsLineChart statsType={'thumb_up'}></StatsLineChart>
<StatsLineChart statsType={'tokens'}></StatsLineChart>
<StatsLineChart statsType={'uv'}></StatsLineChart>
</div>
</Flex>
{apiKeyVisible && (
<ChatApiKeyModal
hideModal={hideApiKeyModal}
dialogId={id}
idKey={idKey}
></ChatApiKeyModal>
)}
<EmbedModal
token={embedToken}
visible={embedVisible}
hideModal={hideEmbedModal}
></EmbedModal>
<ApiContent id={id} idKey={idKey}></ApiContent>
</Modal>
</>
);

View File

@ -0,0 +1,40 @@
import LineChart from '@/components/line-chart';
import { useTranslate } from '@/hooks/common-hooks';
import { IStats } from '@/interfaces/database/chat';
import { formatDate } from '@/utils/date';
import camelCase from 'lodash/camelCase';
import { useSelectChartStatsList } from '../hooks';
import styles from './index.less';
const StatsLineChart = ({ statsType }: { statsType: keyof IStats }) => {
const { t } = useTranslate('chat');
const chartList = useSelectChartStatsList();
const list =
chartList[statsType]?.map((x) => ({
...x,
xAxis: formatDate(x.xAxis),
})) ?? [];
return (
<div className={styles.chartItem}>
<b className={styles.chartLabel}>{t(camelCase(statsType))}</b>
<LineChart data={list}></LineChart>
</div>
);
};
const StatsChart = () => {
return (
<div className={styles.chartWrapper}>
<StatsLineChart statsType={'pv'}></StatsLineChart>
<StatsLineChart statsType={'round'}></StatsLineChart>
<StatsLineChart statsType={'speed'}></StatsLineChart>
<StatsLineChart statsType={'thumb_up'}></StatsLineChart>
<StatsLineChart statsType={'tokens'}></StatsLineChart>
<StatsLineChart statsType={'uv'}></StatsLineChart>
</div>
);
};
export default StatsChart;

View File

@ -1,31 +1,31 @@
import { SharedFrom } from '@/constants/chat';
import {
useCreateNextToken,
useFetchTokenList,
useRemoveNextToken,
} from '@/hooks/chat-hooks';
import {
useSetModalState,
useShowDeleteConfirm,
useTranslate,
} from '@/hooks/common-hooks';
import {
useCreateSystemToken,
useFetchSystemTokenList,
useRemoveSystemToken,
} from '@/hooks/user-setting-hooks';
import { IStats } from '@/interfaces/database/chat';
import { useQueryClient } from '@tanstack/react-query';
import { message } from 'antd';
import { useCallback } from 'react';
export const useOperateApiKey = (dialogId: string, idKey: string) => {
const { removeToken } = useRemoveNextToken();
const { createToken, loading: creatingLoading } = useCreateNextToken();
const { data: tokenList, loading: listLoading } = useFetchTokenList({
export const useOperateApiKey = (idKey: string, dialogId?: string) => {
const { removeToken } = useRemoveSystemToken();
const { createToken, loading: creatingLoading } = useCreateSystemToken();
const { data: tokenList, loading: listLoading } = useFetchSystemTokenList({
[idKey]: dialogId,
});
const showDeleteConfirm = useShowDeleteConfirm();
const onRemoveToken = (token: string, tenantId: string) => {
const onRemoveToken = (token: string) => {
showDeleteConfirm({
onOk: () => removeToken({ dialogId, tokens: [token], tenantId }),
onOk: () => removeToken(token),
});
};
@ -49,7 +49,7 @@ type ChartStatsType = {
export const useSelectChartStatsList = (): ChartStatsType => {
const queryClient = useQueryClient();
const data = queryClient.getQueriesData({ queryKey: ['fetchStats'] });
const stats: IStats = data[0][1] as IStats;
const stats: IStats = (data.length > 0 ? data[0][1] : {}) as IStats;
return Object.keys(stats).reduce((pre, cur) => {
const item = stats[cur as keyof IStats];
@ -77,10 +77,12 @@ const getUrlWithToken = (token: string, from: string = 'chat') => {
return `${protocol}//${host}/chat/share?shared_id=${token}&from=${from}`;
};
const useFetchTokenListBeforeOtherStep = (dialogId: string, idKey: string) => {
const useFetchTokenListBeforeOtherStep = (idKey: string, dialogId?: string) => {
const { showTokenEmptyError } = useShowTokenEmptyError();
const { data: tokenList, refetch } = useFetchTokenList({ [idKey]: dialogId });
const { data: tokenList, refetch } = useFetchSystemTokenList({
[idKey]: dialogId,
});
const token =
Array.isArray(tokenList) && tokenList.length > 0 ? tokenList[0].token : '';
@ -102,7 +104,7 @@ const useFetchTokenListBeforeOtherStep = (dialogId: string, idKey: string) => {
};
};
export const useShowEmbedModal = (dialogId: string, idKey: string) => {
export const useShowEmbedModal = (idKey: string, dialogId?: string) => {
const {
visible: embedVisible,
hideModal: hideEmbedModal,
@ -110,8 +112,8 @@ export const useShowEmbedModal = (dialogId: string, idKey: string) => {
} = useSetModalState();
const { handleOperate, token } = useFetchTokenListBeforeOtherStep(
dialogId,
idKey,
dialogId,
);
const handleShowEmbedModal = useCallback(async () => {
@ -129,8 +131,8 @@ export const useShowEmbedModal = (dialogId: string, idKey: string) => {
};
};
export const usePreviewChat = (dialogId: string, idKey: string) => {
const { handleOperate } = useFetchTokenListBeforeOtherStep(dialogId, idKey);
export const usePreviewChat = (idKey: string, dialogId?: string) => {
const { handleOperate } = useFetchTokenListBeforeOtherStep(idKey, dialogId);
const open = useCallback(
(t: string) => {