mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### 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:
@ -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>
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
@ -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) => {
|
||||
|
||||
@ -1,4 +1,19 @@
|
||||
.text {
|
||||
white-space: pre-wrap; // https://stackoverflow.com/questions/60332183/new-line-with-react-markdown
|
||||
.chunkText;
|
||||
font-size: 16px;
|
||||
li {
|
||||
padding: 4px 0px;
|
||||
}
|
||||
p {
|
||||
white-space: pre-wrap; // https://stackoverflow.com/questions/60332183/new-line-with-react-markdown
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
padding: 3px 6px 6px;
|
||||
margin: 0;
|
||||
white-space: break-spaces;
|
||||
background-color: rgba(129, 139, 152, 0.12);
|
||||
border-radius: 4px;
|
||||
color: rgb(31, 35, 40);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import Markdown from 'react-markdown';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
@ -22,11 +22,16 @@ const HightLightMarkdown = ({
|
||||
const { children, className, node, ...rest } = props;
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return match ? (
|
||||
<SyntaxHighlighter {...rest} PreTag="div" language={match[1]}>
|
||||
<SyntaxHighlighter
|
||||
{...rest}
|
||||
PreTag="div"
|
||||
language={match[1]}
|
||||
// style={dark}
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code {...rest} className={className}>
|
||||
<code {...rest} className={`${className} ${styles.code}`}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user