balibabu
commited on
Commit
·
7ba250b
1
Parent(s):
8f4f7c1
feat: translate EmbedModal #345 (#455)
Browse files### What problem does this PR solve?
Embed the chat window into other websites through iframe
#345
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
- web/src/{pages/chat/share/shared-markdown.tsx → components/highlight-markdown/index.tsx} +7 -3
- web/src/hooks/chatHooks.ts +1 -24
- web/src/less/mixins.less +9 -0
- web/src/locales/en.ts +9 -1
- web/src/locales/zh-traditional.ts +8 -1
- web/src/locales/zh.ts +8 -1
- web/src/pages/chat/chat-overview-modal/index.tsx +48 -30
- web/src/pages/chat/embed-modal/index.less +8 -0
- web/src/pages/chat/embed-modal/index.tsx +70 -0
- web/src/pages/chat/hooks.ts +125 -30
- web/src/pages/chat/index.tsx +16 -11
- web/src/pages/chat/model.ts +1 -1
- web/src/pages/chat/share/large.tsx +2 -2
- web/src/utils/request.ts +2 -2
web/src/{pages/chat/share/shared-markdown.tsx → components/highlight-markdown/index.tsx}
RENAMED
|
@@ -2,7 +2,11 @@ import Markdown from 'react-markdown';
|
|
| 2 |
import SyntaxHighlighter from 'react-syntax-highlighter';
|
| 3 |
import remarkGfm from 'remark-gfm';
|
| 4 |
|
| 5 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
return (
|
| 7 |
<Markdown
|
| 8 |
remarkPlugins={[remarkGfm]}
|
|
@@ -24,9 +28,9 @@ const SharedMarkdown = ({ content }: { content: string }) => {
|
|
| 24 |
} as any
|
| 25 |
}
|
| 26 |
>
|
| 27 |
-
{
|
| 28 |
</Markdown>
|
| 29 |
);
|
| 30 |
};
|
| 31 |
|
| 32 |
-
export default
|
|
|
|
| 2 |
import SyntaxHighlighter from 'react-syntax-highlighter';
|
| 3 |
import remarkGfm from 'remark-gfm';
|
| 4 |
|
| 5 |
+
const HightLightMarkdown = ({
|
| 6 |
+
children,
|
| 7 |
+
}: {
|
| 8 |
+
children: string | null | undefined;
|
| 9 |
+
}) => {
|
| 10 |
return (
|
| 11 |
<Markdown
|
| 12 |
remarkPlugins={[remarkGfm]}
|
|
|
|
| 28 |
} as any
|
| 29 |
}
|
| 30 |
>
|
| 31 |
+
{children}
|
| 32 |
</Markdown>
|
| 33 |
);
|
| 34 |
};
|
| 35 |
|
| 36 |
+
export default HightLightMarkdown;
|
web/src/hooks/chatHooks.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
| 4 |
IStats,
|
| 5 |
IToken,
|
| 6 |
} from '@/interfaces/database/chat';
|
| 7 |
-
import { useCallback
|
| 8 |
import { useDispatch, useSelector } from 'umi';
|
| 9 |
|
| 10 |
export const useFetchDialogList = () => {
|
|
@@ -299,27 +299,4 @@ export const useCompleteSharedConversation = () => {
|
|
| 299 |
return completeSharedConversation;
|
| 300 |
};
|
| 301 |
|
| 302 |
-
export const useCreatePublicUrlToken = (dialogId: string, visible: boolean) => {
|
| 303 |
-
const [token, setToken] = useState();
|
| 304 |
-
const createToken = useCreateToken(dialogId);
|
| 305 |
-
const { protocol, host } = window.location;
|
| 306 |
-
|
| 307 |
-
const urlWithToken = `${protocol}//${host}/chat/share?shared_id=${token}`;
|
| 308 |
-
|
| 309 |
-
const createUrlToken = useCallback(async () => {
|
| 310 |
-
if (visible) {
|
| 311 |
-
const data = await createToken();
|
| 312 |
-
const urlToken = data.data?.token;
|
| 313 |
-
if (urlToken) {
|
| 314 |
-
setToken(urlToken);
|
| 315 |
-
}
|
| 316 |
-
}
|
| 317 |
-
}, [createToken, visible]);
|
| 318 |
-
|
| 319 |
-
useEffect(() => {
|
| 320 |
-
createUrlToken();
|
| 321 |
-
}, [createUrlToken]);
|
| 322 |
-
|
| 323 |
-
return { token, createUrlToken, urlWithToken };
|
| 324 |
-
};
|
| 325 |
//#endregion
|
|
|
|
| 4 |
IStats,
|
| 5 |
IToken,
|
| 6 |
} from '@/interfaces/database/chat';
|
| 7 |
+
import { useCallback } from 'react';
|
| 8 |
import { useDispatch, useSelector } from 'umi';
|
| 9 |
|
| 10 |
export const useFetchDialogList = () => {
|
|
|
|
| 299 |
return completeSharedConversation;
|
| 300 |
};
|
| 301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
//#endregion
|
web/src/less/mixins.less
CHANGED
|
@@ -33,3 +33,12 @@
|
|
| 33 |
.pointerCursor() {
|
| 34 |
cursor: pointer;
|
| 35 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
.pointerCursor() {
|
| 34 |
cursor: pointer;
|
| 35 |
}
|
| 36 |
+
|
| 37 |
+
.clearCardBody() {
|
| 38 |
+
:global {
|
| 39 |
+
.ant-card-body {
|
| 40 |
+
padding: 0;
|
| 41 |
+
margin: 0;
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
}
|
web/src/locales/en.ts
CHANGED
|
@@ -349,7 +349,7 @@ export default {
|
|
| 349 |
'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).',
|
| 350 |
quote: 'Show Quote',
|
| 351 |
quoteTip: 'Should the source of the original text be displayed?',
|
| 352 |
-
overview: '
|
| 353 |
pv: 'Number of messages',
|
| 354 |
uv: 'Active user number',
|
| 355 |
speed: 'Token output speed',
|
|
@@ -367,6 +367,14 @@ export default {
|
|
| 367 |
createNewKey: 'Create new key',
|
| 368 |
created: 'Created',
|
| 369 |
action: 'Action',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
},
|
| 371 |
setting: {
|
| 372 |
profile: 'Profile',
|
|
|
|
| 349 |
'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).',
|
| 350 |
quote: 'Show Quote',
|
| 351 |
quoteTip: 'Should the source of the original text be displayed?',
|
| 352 |
+
overview: 'API',
|
| 353 |
pv: 'Number of messages',
|
| 354 |
uv: 'Active user number',
|
| 355 |
speed: 'Token output speed',
|
|
|
|
| 367 |
createNewKey: 'Create new key',
|
| 368 |
created: 'Created',
|
| 369 |
action: 'Action',
|
| 370 |
+
embedModalTitle: 'Embed into website',
|
| 371 |
+
comingSoon: 'Coming Soon',
|
| 372 |
+
fullScreenTitle: 'Full Embed',
|
| 373 |
+
fullScreenDescription:
|
| 374 |
+
'Embed the following iframe into your website at the desired location',
|
| 375 |
+
partialTitle: 'Partial Embed',
|
| 376 |
+
extensionTitle: 'Chrome Extension',
|
| 377 |
+
tokenError: 'Please create API Token first!',
|
| 378 |
},
|
| 379 |
setting: {
|
| 380 |
profile: 'Profile',
|
web/src/locales/zh-traditional.ts
CHANGED
|
@@ -321,7 +321,7 @@ export default {
|
|
| 321 |
'這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。',
|
| 322 |
quote: '顯示引文',
|
| 323 |
quoteTip: '是否應該顯示原文出處?',
|
| 324 |
-
overview: '
|
| 325 |
pv: '消息數',
|
| 326 |
uv: '活躍用戶數',
|
| 327 |
speed: 'Token 輸出速度',
|
|
@@ -339,6 +339,13 @@ export default {
|
|
| 339 |
createNewKey: '創建新密鑰',
|
| 340 |
created: '創建於',
|
| 341 |
action: '操作',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
},
|
| 343 |
setting: {
|
| 344 |
profile: '概述',
|
|
|
|
| 321 |
'這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。',
|
| 322 |
quote: '顯示引文',
|
| 323 |
quoteTip: '是否應該顯示原文出處?',
|
| 324 |
+
overview: 'API',
|
| 325 |
pv: '消息數',
|
| 326 |
uv: '活躍用戶數',
|
| 327 |
speed: 'Token 輸出速度',
|
|
|
|
| 339 |
createNewKey: '創建新密鑰',
|
| 340 |
created: '創建於',
|
| 341 |
action: '操作',
|
| 342 |
+
embedModalTitle: '嵌入網站',
|
| 343 |
+
comingSoon: '即將推出',
|
| 344 |
+
fullScreenTitle: '全屏嵌入',
|
| 345 |
+
fullScreenDescription: '將以下iframe嵌入您的網站處於所需位置',
|
| 346 |
+
partialTitle: '部分嵌入',
|
| 347 |
+
extensionTitle: 'Chrome 插件',
|
| 348 |
+
tokenError: '請先創建 Api Token!',
|
| 349 |
},
|
| 350 |
setting: {
|
| 351 |
profile: '概述',
|
web/src/locales/zh.ts
CHANGED
|
@@ -338,7 +338,7 @@ export default {
|
|
| 338 |
'这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。',
|
| 339 |
quote: '显示引文',
|
| 340 |
quoteTip: '是否应该显示原文出处?',
|
| 341 |
-
overview: '
|
| 342 |
pv: '消息数',
|
| 343 |
uv: '活跃用户数',
|
| 344 |
speed: 'Token 输出速度',
|
|
@@ -356,6 +356,13 @@ export default {
|
|
| 356 |
createNewKey: '创建新密钥',
|
| 357 |
created: '创建于',
|
| 358 |
action: '操作',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
},
|
| 360 |
setting: {
|
| 361 |
profile: '概要',
|
|
|
|
| 338 |
'这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。',
|
| 339 |
quote: '显示引文',
|
| 340 |
quoteTip: '是否应该显示原文出处?',
|
| 341 |
+
overview: 'API',
|
| 342 |
pv: '消息数',
|
| 343 |
uv: '活跃用户数',
|
| 344 |
speed: 'Token 输出速度',
|
|
|
|
| 356 |
createNewKey: '创建新密钥',
|
| 357 |
created: '创建于',
|
| 358 |
action: '操作',
|
| 359 |
+
embedModalTitle: '嵌入网站',
|
| 360 |
+
comingSoon: '即将推出',
|
| 361 |
+
fullScreenTitle: '全屏嵌入',
|
| 362 |
+
fullScreenDescription: '将以下iframe嵌入您的网站处于所需位置',
|
| 363 |
+
partialTitle: '部分嵌入',
|
| 364 |
+
extensionTitle: 'Chrome 插件',
|
| 365 |
+
tokenError: '请先创建 Api Token!',
|
| 366 |
},
|
| 367 |
setting: {
|
| 368 |
profile: '概要',
|
web/src/pages/chat/chat-overview-modal/index.tsx
CHANGED
|
@@ -1,17 +1,19 @@
|
|
| 1 |
-
import CopyToClipboard from '@/components/copy-to-clipboard';
|
| 2 |
import LineChart from '@/components/line-chart';
|
| 3 |
-
import { useCreatePublicUrlToken } from '@/hooks/chatHooks';
|
| 4 |
import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
|
| 5 |
import { IModalProps } from '@/interfaces/common';
|
| 6 |
import { IDialog, IStats } from '@/interfaces/database/chat';
|
| 7 |
-
import { ReloadOutlined } from '@ant-design/icons';
|
| 8 |
import { Button, Card, DatePicker, Flex, Modal, Space, Typography } from 'antd';
|
| 9 |
import { RangePickerProps } from 'antd/es/date-picker';
|
| 10 |
import dayjs from 'dayjs';
|
| 11 |
import camelCase from 'lodash/camelCase';
|
| 12 |
-
import { Link } from 'umi';
|
| 13 |
import ChatApiKeyModal from '../chat-api-key-modal';
|
| 14 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
import styles from './index.less';
|
| 16 |
|
| 17 |
const { Paragraph } = Typography;
|
|
@@ -24,16 +26,18 @@ const ChatOverviewModal = ({
|
|
| 24 |
}: IModalProps<any> & { dialog: IDialog }) => {
|
| 25 |
const { t } = useTranslate('chat');
|
| 26 |
const chartList = useSelectChartStatsList();
|
| 27 |
-
const { urlWithToken, createUrlToken, token } = useCreatePublicUrlToken(
|
| 28 |
-
dialog.id,
|
| 29 |
-
visible,
|
| 30 |
-
);
|
| 31 |
-
|
| 32 |
const {
|
| 33 |
visible: apiKeyVisible,
|
| 34 |
hideModal: hideApiKeyModal,
|
| 35 |
showModal: showApiKeyModal,
|
| 36 |
} = useSetModalState();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
const { pickerValue, setPickerValue } = useFetchStatsOnMount(visible);
|
| 39 |
|
|
@@ -41,6 +45,8 @@ const ChatOverviewModal = ({
|
|
| 41 |
return current && current > dayjs().endOf('day');
|
| 42 |
};
|
| 43 |
|
|
|
|
|
|
|
| 44 |
return (
|
| 45 |
<>
|
| 46 |
<Modal
|
|
@@ -50,36 +56,41 @@ const ChatOverviewModal = ({
|
|
| 50 |
width={'100vw'}
|
| 51 |
>
|
| 52 |
<Flex vertical gap={'middle'}>
|
| 53 |
-
<Card title={dialog.name}>
|
| 54 |
-
<Flex gap={8} vertical>
|
| 55 |
-
{t('publicUrl')}
|
| 56 |
-
<Flex className={styles.linkText} gap={10}>
|
| 57 |
-
<span>{urlWithToken}</span>
|
| 58 |
-
<CopyToClipboard text={urlWithToken}></CopyToClipboard>
|
| 59 |
-
<ReloadOutlined onClick={createUrlToken} />
|
| 60 |
-
</Flex>
|
| 61 |
-
<Space size={'middle'}>
|
| 62 |
-
<Button>
|
| 63 |
-
<Link to={`/chat/share?shared_id=${token}`} target="_blank">
|
| 64 |
-
{t('preview')}
|
| 65 |
-
</Link>
|
| 66 |
-
</Button>
|
| 67 |
-
<Button>{t('embedded')}</Button>
|
| 68 |
-
</Space>
|
| 69 |
-
</Flex>
|
| 70 |
-
</Card>
|
| 71 |
<Card title={t('backendServiceApi')}>
|
| 72 |
<Flex gap={8} vertical>
|
| 73 |
{t('serviceApiEndpoint')}
|
| 74 |
<Paragraph copyable className={styles.linkText}>
|
| 75 |
-
|
| 76 |
</Paragraph>
|
| 77 |
</Flex>
|
| 78 |
<Space size={'middle'}>
|
| 79 |
<Button onClick={showApiKeyModal}>{t('apiKey')}</Button>
|
| 80 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
</Space>
|
| 82 |
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
<Space>
|
| 84 |
<b>{t('dateRange')}</b>
|
| 85 |
<RangePicker
|
|
@@ -103,6 +114,13 @@ const ChatOverviewModal = ({
|
|
| 103 |
hideModal={hideApiKeyModal}
|
| 104 |
dialogId={dialog.id}
|
| 105 |
></ChatApiKeyModal>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
</Modal>
|
| 107 |
</>
|
| 108 |
);
|
|
|
|
|
|
|
| 1 |
import LineChart from '@/components/line-chart';
|
|
|
|
| 2 |
import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
|
| 3 |
import { IModalProps } from '@/interfaces/common';
|
| 4 |
import { IDialog, IStats } from '@/interfaces/database/chat';
|
|
|
|
| 5 |
import { Button, Card, DatePicker, Flex, Modal, Space, Typography } from 'antd';
|
| 6 |
import { RangePickerProps } from 'antd/es/date-picker';
|
| 7 |
import dayjs from 'dayjs';
|
| 8 |
import camelCase from 'lodash/camelCase';
|
|
|
|
| 9 |
import ChatApiKeyModal from '../chat-api-key-modal';
|
| 10 |
+
import EmbedModal from '../embed-modal';
|
| 11 |
+
import {
|
| 12 |
+
useFetchStatsOnMount,
|
| 13 |
+
usePreviewChat,
|
| 14 |
+
useSelectChartStatsList,
|
| 15 |
+
useShowEmbedModal,
|
| 16 |
+
} from '../hooks';
|
| 17 |
import styles from './index.less';
|
| 18 |
|
| 19 |
const { Paragraph } = Typography;
|
|
|
|
| 26 |
}: IModalProps<any> & { dialog: IDialog }) => {
|
| 27 |
const { t } = useTranslate('chat');
|
| 28 |
const chartList = useSelectChartStatsList();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
const {
|
| 30 |
visible: apiKeyVisible,
|
| 31 |
hideModal: hideApiKeyModal,
|
| 32 |
showModal: showApiKeyModal,
|
| 33 |
} = useSetModalState();
|
| 34 |
+
const {
|
| 35 |
+
embedVisible,
|
| 36 |
+
hideEmbedModal,
|
| 37 |
+
showEmbedModal,
|
| 38 |
+
embedToken,
|
| 39 |
+
errorContextHolder,
|
| 40 |
+
} = useShowEmbedModal(dialog.id);
|
| 41 |
|
| 42 |
const { pickerValue, setPickerValue } = useFetchStatsOnMount(visible);
|
| 43 |
|
|
|
|
| 45 |
return current && current > dayjs().endOf('day');
|
| 46 |
};
|
| 47 |
|
| 48 |
+
const { handlePreview, contextHolder } = usePreviewChat(dialog.id);
|
| 49 |
+
|
| 50 |
return (
|
| 51 |
<>
|
| 52 |
<Modal
|
|
|
|
| 56 |
width={'100vw'}
|
| 57 |
>
|
| 58 |
<Flex vertical gap={'middle'}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
<Card title={t('backendServiceApi')}>
|
| 60 |
<Flex gap={8} vertical>
|
| 61 |
{t('serviceApiEndpoint')}
|
| 62 |
<Paragraph copyable className={styles.linkText}>
|
| 63 |
+
https://demo.ragflow.io/v1/api/
|
| 64 |
</Paragraph>
|
| 65 |
</Flex>
|
| 66 |
<Space size={'middle'}>
|
| 67 |
<Button onClick={showApiKeyModal}>{t('apiKey')}</Button>
|
| 68 |
+
<a
|
| 69 |
+
href={
|
| 70 |
+
'https://github.com/infiniflow/ragflow/blob/main/docs/conversation_api.md'
|
| 71 |
+
}
|
| 72 |
+
target="_blank"
|
| 73 |
+
rel="noreferrer"
|
| 74 |
+
>
|
| 75 |
+
<Button>{t('apiReference')}</Button>
|
| 76 |
+
</a>
|
| 77 |
</Space>
|
| 78 |
</Card>
|
| 79 |
+
<Card title={dialog.name}>
|
| 80 |
+
<Flex gap={8} vertical>
|
| 81 |
+
{t('publicUrl')}
|
| 82 |
+
{/* <Flex className={styles.linkText} gap={10}>
|
| 83 |
+
<span>{urlWithToken}</span>
|
| 84 |
+
<CopyToClipboard text={urlWithToken}></CopyToClipboard>
|
| 85 |
+
<ReloadOutlined onClick={createUrlToken} />
|
| 86 |
+
</Flex> */}
|
| 87 |
+
<Space size={'middle'}>
|
| 88 |
+
<Button onClick={handlePreview}>{t('preview')}</Button>
|
| 89 |
+
<Button onClick={showEmbedModal}>{t('embedded')}</Button>
|
| 90 |
+
</Space>
|
| 91 |
+
</Flex>
|
| 92 |
+
</Card>
|
| 93 |
+
|
| 94 |
<Space>
|
| 95 |
<b>{t('dateRange')}</b>
|
| 96 |
<RangePicker
|
|
|
|
| 114 |
hideModal={hideApiKeyModal}
|
| 115 |
dialogId={dialog.id}
|
| 116 |
></ChatApiKeyModal>
|
| 117 |
+
<EmbedModal
|
| 118 |
+
token={embedToken}
|
| 119 |
+
visible={embedVisible}
|
| 120 |
+
hideModal={hideEmbedModal}
|
| 121 |
+
></EmbedModal>
|
| 122 |
+
{contextHolder}
|
| 123 |
+
{errorContextHolder}
|
| 124 |
</Modal>
|
| 125 |
</>
|
| 126 |
);
|
web/src/pages/chat/embed-modal/index.less
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.codeCard {
|
| 2 |
+
.clearCardBody();
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
.codeText {
|
| 6 |
+
padding: 10px;
|
| 7 |
+
background-color: #e8e8ea;
|
| 8 |
+
}
|
web/src/pages/chat/embed-modal/index.tsx
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import CopyToClipboard from '@/components/copy-to-clipboard';
|
| 2 |
+
import HightLightMarkdown from '@/components/highlight-markdown';
|
| 3 |
+
import { useTranslate } from '@/hooks/commonHooks';
|
| 4 |
+
import { IModalProps } from '@/interfaces/common';
|
| 5 |
+
import { Card, Modal, Tabs, TabsProps } from 'antd';
|
| 6 |
+
import styles from './index.less';
|
| 7 |
+
|
| 8 |
+
const EmbedModal = ({
|
| 9 |
+
visible,
|
| 10 |
+
hideModal,
|
| 11 |
+
token = '',
|
| 12 |
+
}: IModalProps<any> & { token: string }) => {
|
| 13 |
+
const { t } = useTranslate('chat');
|
| 14 |
+
|
| 15 |
+
const text = `
|
| 16 |
+
~~~ html
|
| 17 |
+
<iframe
|
| 18 |
+
src="https://demo.ragflow.io/chat/share?shared_id=${token}"
|
| 19 |
+
style="width: 100%; height: 100%; min-height: 600px"
|
| 20 |
+
frameborder="0"
|
| 21 |
+
>
|
| 22 |
+
</iframe>
|
| 23 |
+
~~~
|
| 24 |
+
`;
|
| 25 |
+
|
| 26 |
+
const items: TabsProps['items'] = [
|
| 27 |
+
{
|
| 28 |
+
key: '1',
|
| 29 |
+
label: t('fullScreenTitle'),
|
| 30 |
+
children: (
|
| 31 |
+
<Card
|
| 32 |
+
title={t('fullScreenDescription')}
|
| 33 |
+
extra={<CopyToClipboard text={text}></CopyToClipboard>}
|
| 34 |
+
className={styles.codeCard}
|
| 35 |
+
>
|
| 36 |
+
<HightLightMarkdown>{text}</HightLightMarkdown>
|
| 37 |
+
</Card>
|
| 38 |
+
),
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
key: '2',
|
| 42 |
+
label: t('partialTitle'),
|
| 43 |
+
children: t('comingSoon'),
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
key: '3',
|
| 47 |
+
label: t('extensionTitle'),
|
| 48 |
+
children: t('comingSoon'),
|
| 49 |
+
},
|
| 50 |
+
];
|
| 51 |
+
|
| 52 |
+
const onChange = (key: string) => {
|
| 53 |
+
console.log(key);
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
return (
|
| 57 |
+
<Modal
|
| 58 |
+
title={t('embedModalTitle')}
|
| 59 |
+
open={visible}
|
| 60 |
+
style={{ top: 300 }}
|
| 61 |
+
width={'50vw'}
|
| 62 |
+
onOk={hideModal}
|
| 63 |
+
onCancel={hideModal}
|
| 64 |
+
>
|
| 65 |
+
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
|
| 66 |
+
</Modal>
|
| 67 |
+
);
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
export default EmbedModal;
|
web/src/pages/chat/hooks.ts
CHANGED
|
@@ -14,15 +14,21 @@ import {
|
|
| 14 |
useRemoveToken,
|
| 15 |
useSelectConversationList,
|
| 16 |
useSelectDialogList,
|
|
|
|
| 17 |
useSelectTokenList,
|
| 18 |
useSetDialog,
|
| 19 |
useUpdateConversation,
|
| 20 |
} from '@/hooks/chatHooks';
|
| 21 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
| 23 |
import { IConversation, IDialog, IStats } from '@/interfaces/database/chat';
|
| 24 |
import { IChunk } from '@/interfaces/database/knowledge';
|
| 25 |
import { getFileExtension } from '@/utils';
|
|
|
|
| 26 |
import dayjs, { Dayjs } from 'dayjs';
|
| 27 |
import omit from 'lodash/omit';
|
| 28 |
import {
|
|
@@ -777,35 +783,35 @@ type ChartStatsType = {
|
|
| 777 |
};
|
| 778 |
|
| 779 |
export const useSelectChartStatsList = (): ChartStatsType => {
|
| 780 |
-
|
| 781 |
-
const stats = {
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
};
|
| 809 |
|
| 810 |
return Object.keys(stats).reduce((pre, cur) => {
|
| 811 |
const item = stats[cur as keyof IStats];
|
|
@@ -819,4 +825,93 @@ export const useSelectChartStatsList = (): ChartStatsType => {
|
|
| 819 |
}, {} as ChartStatsType);
|
| 820 |
};
|
| 821 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 822 |
//#endregion
|
|
|
|
| 14 |
useRemoveToken,
|
| 15 |
useSelectConversationList,
|
| 16 |
useSelectDialogList,
|
| 17 |
+
useSelectStats,
|
| 18 |
useSelectTokenList,
|
| 19 |
useSetDialog,
|
| 20 |
useUpdateConversation,
|
| 21 |
} from '@/hooks/chatHooks';
|
| 22 |
+
import {
|
| 23 |
+
useSetModalState,
|
| 24 |
+
useShowDeleteConfirm,
|
| 25 |
+
useTranslate,
|
| 26 |
+
} from '@/hooks/commonHooks';
|
| 27 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
| 28 |
import { IConversation, IDialog, IStats } from '@/interfaces/database/chat';
|
| 29 |
import { IChunk } from '@/interfaces/database/knowledge';
|
| 30 |
import { getFileExtension } from '@/utils';
|
| 31 |
+
import { message } from 'antd';
|
| 32 |
import dayjs, { Dayjs } from 'dayjs';
|
| 33 |
import omit from 'lodash/omit';
|
| 34 |
import {
|
|
|
|
| 783 |
};
|
| 784 |
|
| 785 |
export const useSelectChartStatsList = (): ChartStatsType => {
|
| 786 |
+
const stats: IStats = useSelectStats();
|
| 787 |
+
// const stats = {
|
| 788 |
+
// pv: [
|
| 789 |
+
// ['2024-06-01', 1],
|
| 790 |
+
// ['2024-07-24', 3],
|
| 791 |
+
// ['2024-09-01', 10],
|
| 792 |
+
// ],
|
| 793 |
+
// uv: [
|
| 794 |
+
// ['2024-02-01', 0],
|
| 795 |
+
// ['2024-03-01', 99],
|
| 796 |
+
// ['2024-05-01', 3],
|
| 797 |
+
// ],
|
| 798 |
+
// speed: [
|
| 799 |
+
// ['2024-09-01', 2],
|
| 800 |
+
// ['2024-09-01', 3],
|
| 801 |
+
// ],
|
| 802 |
+
// tokens: [
|
| 803 |
+
// ['2024-09-01', 1],
|
| 804 |
+
// ['2024-09-01', 3],
|
| 805 |
+
// ],
|
| 806 |
+
// round: [
|
| 807 |
+
// ['2024-09-01', 0],
|
| 808 |
+
// ['2024-09-01', 3],
|
| 809 |
+
// ],
|
| 810 |
+
// thumb_up: [
|
| 811 |
+
// ['2024-09-01', 3],
|
| 812 |
+
// ['2024-09-01', 9],
|
| 813 |
+
// ],
|
| 814 |
+
// };
|
| 815 |
|
| 816 |
return Object.keys(stats).reduce((pre, cur) => {
|
| 817 |
const item = stats[cur as keyof IStats];
|
|
|
|
| 825 |
}, {} as ChartStatsType);
|
| 826 |
};
|
| 827 |
|
| 828 |
+
export const useShowTokenEmptyError = () => {
|
| 829 |
+
const [messageApi, contextHolder] = message.useMessage();
|
| 830 |
+
const { t } = useTranslate('chat');
|
| 831 |
+
|
| 832 |
+
const showTokenEmptyError = useCallback(() => {
|
| 833 |
+
messageApi.error(t('tokenError'));
|
| 834 |
+
}, [messageApi, t]);
|
| 835 |
+
return { showTokenEmptyError, contextHolder };
|
| 836 |
+
};
|
| 837 |
+
|
| 838 |
+
const getUrlWithToken = (token: string) => {
|
| 839 |
+
const { protocol, host } = window.location;
|
| 840 |
+
return `${protocol}//${host}/chat/share?shared_id=${token}`;
|
| 841 |
+
};
|
| 842 |
+
|
| 843 |
+
const useFetchTokenListBeforeOtherStep = (dialogId: string) => {
|
| 844 |
+
const { showTokenEmptyError, contextHolder } = useShowTokenEmptyError();
|
| 845 |
+
|
| 846 |
+
const listToken = useListToken();
|
| 847 |
+
const tokenList = useSelectTokenList();
|
| 848 |
+
|
| 849 |
+
const token =
|
| 850 |
+
Array.isArray(tokenList) && tokenList.length > 0 ? tokenList[0].token : '';
|
| 851 |
+
|
| 852 |
+
const handleOperate = useCallback(async () => {
|
| 853 |
+
const data = await listToken(dialogId);
|
| 854 |
+
const list = data.data;
|
| 855 |
+
if (data.retcode === 0 && Array.isArray(list) && list.length > 0) {
|
| 856 |
+
return list[0]?.token;
|
| 857 |
+
} else {
|
| 858 |
+
showTokenEmptyError();
|
| 859 |
+
return false;
|
| 860 |
+
}
|
| 861 |
+
}, [dialogId, listToken, showTokenEmptyError]);
|
| 862 |
+
|
| 863 |
+
return {
|
| 864 |
+
token,
|
| 865 |
+
contextHolder,
|
| 866 |
+
handleOperate,
|
| 867 |
+
};
|
| 868 |
+
};
|
| 869 |
+
|
| 870 |
+
export const useShowEmbedModal = (dialogId: string) => {
|
| 871 |
+
const {
|
| 872 |
+
visible: embedVisible,
|
| 873 |
+
hideModal: hideEmbedModal,
|
| 874 |
+
showModal: showEmbedModal,
|
| 875 |
+
} = useSetModalState();
|
| 876 |
+
|
| 877 |
+
const { handleOperate, token, contextHolder } =
|
| 878 |
+
useFetchTokenListBeforeOtherStep(dialogId);
|
| 879 |
+
|
| 880 |
+
const handleShowEmbedModal = useCallback(async () => {
|
| 881 |
+
const succeed = await handleOperate();
|
| 882 |
+
if (succeed) {
|
| 883 |
+
showEmbedModal();
|
| 884 |
+
}
|
| 885 |
+
}, [handleOperate, showEmbedModal]);
|
| 886 |
+
|
| 887 |
+
return {
|
| 888 |
+
showEmbedModal: handleShowEmbedModal,
|
| 889 |
+
hideEmbedModal,
|
| 890 |
+
embedVisible,
|
| 891 |
+
embedToken: token,
|
| 892 |
+
errorContextHolder: contextHolder,
|
| 893 |
+
};
|
| 894 |
+
};
|
| 895 |
+
|
| 896 |
+
export const usePreviewChat = (dialogId: string) => {
|
| 897 |
+
const { handleOperate, contextHolder } =
|
| 898 |
+
useFetchTokenListBeforeOtherStep(dialogId);
|
| 899 |
+
|
| 900 |
+
const open = useCallback((t: string) => {
|
| 901 |
+
window.open(getUrlWithToken(t), '_blank');
|
| 902 |
+
}, []);
|
| 903 |
+
|
| 904 |
+
const handlePreview = useCallback(async () => {
|
| 905 |
+
const token = await handleOperate();
|
| 906 |
+
if (token) {
|
| 907 |
+
open(token);
|
| 908 |
+
}
|
| 909 |
+
}, [handleOperate, open]);
|
| 910 |
+
|
| 911 |
+
return {
|
| 912 |
+
handlePreview,
|
| 913 |
+
contextHolder,
|
| 914 |
+
};
|
| 915 |
+
};
|
| 916 |
+
|
| 917 |
//#endregion
|
web/src/pages/chat/index.tsx
CHANGED
|
@@ -1,6 +1,11 @@
|
|
| 1 |
import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
|
| 2 |
import RenameModal from '@/components/rename-modal';
|
| 3 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import {
|
| 5 |
Avatar,
|
| 6 |
Button,
|
|
@@ -185,16 +190,16 @@ const Chat = () => {
|
|
| 185 |
),
|
| 186 |
},
|
| 187 |
{ type: 'divider' },
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
];
|
| 199 |
|
| 200 |
return appItems;
|
|
|
|
| 1 |
import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
|
| 2 |
import RenameModal from '@/components/rename-modal';
|
| 3 |
+
import {
|
| 4 |
+
CloudOutlined,
|
| 5 |
+
DeleteOutlined,
|
| 6 |
+
EditOutlined,
|
| 7 |
+
FormOutlined,
|
| 8 |
+
} from '@ant-design/icons';
|
| 9 |
import {
|
| 10 |
Avatar,
|
| 11 |
Button,
|
|
|
|
| 190 |
),
|
| 191 |
},
|
| 192 |
{ type: 'divider' },
|
| 193 |
+
{
|
| 194 |
+
key: '3',
|
| 195 |
+
onClick: handleShowOverviewModal(dialog),
|
| 196 |
+
label: (
|
| 197 |
+
<Space>
|
| 198 |
+
<CloudOutlined />
|
| 199 |
+
{t('overview')}
|
| 200 |
+
</Space>
|
| 201 |
+
),
|
| 202 |
+
},
|
| 203 |
];
|
| 204 |
|
| 205 |
return appItems;
|
web/src/pages/chat/model.ts
CHANGED
|
@@ -202,7 +202,7 @@ const model: DvaModel<ChatModelState> = {
|
|
| 202 |
payload: data.data,
|
| 203 |
});
|
| 204 |
}
|
| 205 |
-
return data
|
| 206 |
},
|
| 207 |
*removeToken({ payload }, { call, put }) {
|
| 208 |
const { data } = yield call(
|
|
|
|
| 202 |
payload: data.data,
|
| 203 |
});
|
| 204 |
}
|
| 205 |
+
return data;
|
| 206 |
},
|
| 207 |
*removeToken({ payload }, { call, put }) {
|
| 208 |
const { data } = yield call(
|
web/src/pages/chat/share/large.tsx
CHANGED
|
@@ -6,10 +6,10 @@ import { Avatar, Button, Flex, Input, Skeleton, Spin } from 'antd';
|
|
| 6 |
import classNames from 'classnames';
|
| 7 |
import { useSelectConversationLoading } from '../hooks';
|
| 8 |
|
|
|
|
| 9 |
import React, { ChangeEventHandler, forwardRef } from 'react';
|
| 10 |
import { IClientConversation } from '../interface';
|
| 11 |
import styles from './index.less';
|
| 12 |
-
import SharedMarkdown from './shared-markdown';
|
| 13 |
|
| 14 |
const MessageItem = ({ item }: { item: Message }) => {
|
| 15 |
const isAssistant = item.role === MessageType.Assistant;
|
|
@@ -46,7 +46,7 @@ const MessageItem = ({ item }: { item: Message }) => {
|
|
| 46 |
<b>{isAssistant ? '' : 'You'}</b>
|
| 47 |
<div className={styles.messageText}>
|
| 48 |
{item.content !== '' ? (
|
| 49 |
-
<
|
| 50 |
) : (
|
| 51 |
<Skeleton active className={styles.messageEmpty} />
|
| 52 |
)}
|
|
|
|
| 6 |
import classNames from 'classnames';
|
| 7 |
import { useSelectConversationLoading } from '../hooks';
|
| 8 |
|
| 9 |
+
import HightLightMarkdown from '@/components/highlight-markdown';
|
| 10 |
import React, { ChangeEventHandler, forwardRef } from 'react';
|
| 11 |
import { IClientConversation } from '../interface';
|
| 12 |
import styles from './index.less';
|
|
|
|
| 13 |
|
| 14 |
const MessageItem = ({ item }: { item: Message }) => {
|
| 15 |
const isAssistant = item.role === MessageType.Assistant;
|
|
|
|
| 46 |
<b>{isAssistant ? '' : 'You'}</b>
|
| 47 |
<div className={styles.messageText}>
|
| 48 |
{item.content !== '' ? (
|
| 49 |
+
<HightLightMarkdown>{item.content}</HightLightMarkdown>
|
| 50 |
) : (
|
| 51 |
<Skeleton active className={styles.messageEmpty} />
|
| 52 |
)}
|
web/src/utils/request.ts
CHANGED
|
@@ -98,8 +98,8 @@ request.interceptors.request.use((url: string, options: any) => {
|
|
| 98 |
url,
|
| 99 |
options: {
|
| 100 |
...options,
|
| 101 |
-
|
| 102 |
-
|
| 103 |
headers: {
|
| 104 |
...(options.skipToken ? undefined : { [Authorization]: authorization }),
|
| 105 |
...options.headers,
|
|
|
|
| 98 |
url,
|
| 99 |
options: {
|
| 100 |
...options,
|
| 101 |
+
data,
|
| 102 |
+
params,
|
| 103 |
headers: {
|
| 104 |
...(options.skipToken ? undefined : { [Authorization]: authorization }),
|
| 105 |
...options.headers,
|