diff --git a/web/src/pages/chat/share/shared-markdown.tsx b/web/src/components/highlight-markdown/index.tsx similarity index 84% rename from web/src/pages/chat/share/shared-markdown.tsx rename to web/src/components/highlight-markdown/index.tsx index 2c1a3c040..7c393fa5c 100644 --- a/web/src/pages/chat/share/shared-markdown.tsx +++ b/web/src/components/highlight-markdown/index.tsx @@ -2,7 +2,11 @@ import Markdown from 'react-markdown'; import SyntaxHighlighter from 'react-syntax-highlighter'; import remarkGfm from 'remark-gfm'; -const SharedMarkdown = ({ content }: { content: string }) => { +const HightLightMarkdown = ({ + children, +}: { + children: string | null | undefined; +}) => { return ( { } as any } > - {content} + {children} ); }; -export default SharedMarkdown; +export default HightLightMarkdown; diff --git a/web/src/hooks/chatHooks.ts b/web/src/hooks/chatHooks.ts index 3f33f6b0a..34e0d74b0 100644 --- a/web/src/hooks/chatHooks.ts +++ b/web/src/hooks/chatHooks.ts @@ -4,7 +4,7 @@ import { IStats, IToken, } from '@/interfaces/database/chat'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback } from 'react'; import { useDispatch, useSelector } from 'umi'; export const useFetchDialogList = () => { @@ -299,27 +299,4 @@ export const useCompleteSharedConversation = () => { return completeSharedConversation; }; -export const useCreatePublicUrlToken = (dialogId: string, visible: boolean) => { - const [token, setToken] = useState(); - const createToken = useCreateToken(dialogId); - const { protocol, host } = window.location; - - const urlWithToken = `${protocol}//${host}/chat/share?shared_id=${token}`; - - const createUrlToken = useCallback(async () => { - if (visible) { - const data = await createToken(); - const urlToken = data.data?.token; - if (urlToken) { - setToken(urlToken); - } - } - }, [createToken, visible]); - - useEffect(() => { - createUrlToken(); - }, [createUrlToken]); - - return { token, createUrlToken, urlWithToken }; -}; //#endregion diff --git a/web/src/less/mixins.less b/web/src/less/mixins.less index b5256f7e0..ba363ec45 100644 --- a/web/src/less/mixins.less +++ b/web/src/less/mixins.less @@ -33,3 +33,12 @@ .pointerCursor() { cursor: pointer; } + +.clearCardBody() { + :global { + .ant-card-body { + padding: 0; + margin: 0; + } + } +} diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index dafe2a07a..ea3f507b2 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -349,7 +349,7 @@ export default { 'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).', quote: 'Show Quote', quoteTip: 'Should the source of the original text be displayed?', - overview: 'Overview', + overview: 'API', pv: 'Number of messages', uv: 'Active user number', speed: 'Token output speed', @@ -367,6 +367,14 @@ export default { createNewKey: 'Create new key', created: 'Created', action: 'Action', + embedModalTitle: 'Embed into website', + comingSoon: 'Coming Soon', + fullScreenTitle: 'Full Embed', + fullScreenDescription: + 'Embed the following iframe into your website at the desired location', + partialTitle: 'Partial Embed', + extensionTitle: 'Chrome Extension', + tokenError: 'Please create API Token first!', }, setting: { profile: 'Profile', diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts index f469759ca..73c150aab 100644 --- a/web/src/locales/zh-traditional.ts +++ b/web/src/locales/zh-traditional.ts @@ -321,7 +321,7 @@ export default { '這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。', quote: '顯示引文', quoteTip: '是否應該顯示原文出處?', - overview: '概覽', + overview: 'API', pv: '消息數', uv: '活躍用戶數', speed: 'Token 輸出速度', @@ -339,6 +339,13 @@ export default { createNewKey: '創建新密鑰', created: '創建於', action: '操作', + embedModalTitle: '嵌入網站', + comingSoon: '即將推出', + fullScreenTitle: '全屏嵌入', + fullScreenDescription: '將以下iframe嵌入您的網站處於所需位置', + partialTitle: '部分嵌入', + extensionTitle: 'Chrome 插件', + tokenError: '請先創建 Api Token!', }, setting: { profile: '概述', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 6d662342f..b3793030c 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -338,7 +338,7 @@ export default { '这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。', quote: '显示引文', quoteTip: '是否应该显示原文出处?', - overview: '概览', + overview: 'API', pv: '消息数', uv: '活跃用户数', speed: 'Token 输出速度', @@ -356,6 +356,13 @@ export default { createNewKey: '创建新密钥', created: '创建于', action: '操作', + embedModalTitle: '嵌入网站', + comingSoon: '即将推出', + fullScreenTitle: '全屏嵌入', + fullScreenDescription: '将以下iframe嵌入您的网站处于所需位置', + partialTitle: '部分嵌入', + extensionTitle: 'Chrome 插件', + tokenError: '请先创建 Api Token!', }, setting: { profile: '概要', diff --git a/web/src/pages/chat/chat-overview-modal/index.tsx b/web/src/pages/chat/chat-overview-modal/index.tsx index c8a1c4126..6381d6561 100644 --- a/web/src/pages/chat/chat-overview-modal/index.tsx +++ b/web/src/pages/chat/chat-overview-modal/index.tsx @@ -1,17 +1,19 @@ -import CopyToClipboard from '@/components/copy-to-clipboard'; import LineChart from '@/components/line-chart'; -import { useCreatePublicUrlToken } from '@/hooks/chatHooks'; import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; import { IModalProps } from '@/interfaces/common'; import { IDialog, IStats } from '@/interfaces/database/chat'; -import { ReloadOutlined } from '@ant-design/icons'; 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 { Link } from 'umi'; import ChatApiKeyModal from '../chat-api-key-modal'; -import { useFetchStatsOnMount, useSelectChartStatsList } from '../hooks'; +import EmbedModal from '../embed-modal'; +import { + useFetchStatsOnMount, + usePreviewChat, + useSelectChartStatsList, + useShowEmbedModal, +} from '../hooks'; import styles from './index.less'; const { Paragraph } = Typography; @@ -24,16 +26,18 @@ const ChatOverviewModal = ({ }: IModalProps & { dialog: IDialog }) => { const { t } = useTranslate('chat'); const chartList = useSelectChartStatsList(); - const { urlWithToken, createUrlToken, token } = useCreatePublicUrlToken( - dialog.id, - visible, - ); - const { visible: apiKeyVisible, hideModal: hideApiKeyModal, showModal: showApiKeyModal, } = useSetModalState(); + const { + embedVisible, + hideEmbedModal, + showEmbedModal, + embedToken, + errorContextHolder, + } = useShowEmbedModal(dialog.id); const { pickerValue, setPickerValue } = useFetchStatsOnMount(visible); @@ -41,6 +45,8 @@ const ChatOverviewModal = ({ return current && current > dayjs().endOf('day'); }; + const { handlePreview, contextHolder } = usePreviewChat(dialog.id); + return ( <> - - - {t('publicUrl')} - - {urlWithToken} - - - - - - - - - {t('serviceApiEndpoint')} - This is a copyable text. + https://demo.ragflow.io/v1/api/ - + + + + + + {t('publicUrl')} + {/* + {urlWithToken} + + + */} + + + + + + + {t('dateRange')} + + {contextHolder} + {errorContextHolder} ); diff --git a/web/src/pages/chat/embed-modal/index.less b/web/src/pages/chat/embed-modal/index.less new file mode 100644 index 000000000..5e807d852 --- /dev/null +++ b/web/src/pages/chat/embed-modal/index.less @@ -0,0 +1,8 @@ +.codeCard { + .clearCardBody(); +} + +.codeText { + padding: 10px; + background-color: #e8e8ea; +} diff --git a/web/src/pages/chat/embed-modal/index.tsx b/web/src/pages/chat/embed-modal/index.tsx new file mode 100644 index 000000000..44d396765 --- /dev/null +++ b/web/src/pages/chat/embed-modal/index.tsx @@ -0,0 +1,70 @@ +import CopyToClipboard from '@/components/copy-to-clipboard'; +import HightLightMarkdown from '@/components/highlight-markdown'; +import { useTranslate } from '@/hooks/commonHooks'; +import { IModalProps } from '@/interfaces/common'; +import { Card, Modal, Tabs, TabsProps } from 'antd'; +import styles from './index.less'; + +const EmbedModal = ({ + visible, + hideModal, + token = '', +}: IModalProps & { token: string }) => { + const { t } = useTranslate('chat'); + + const text = ` + ~~~ html + +~~~ + `; + + const items: TabsProps['items'] = [ + { + key: '1', + label: t('fullScreenTitle'), + children: ( + } + className={styles.codeCard} + > + {text} + + ), + }, + { + key: '2', + label: t('partialTitle'), + children: t('comingSoon'), + }, + { + key: '3', + label: t('extensionTitle'), + children: t('comingSoon'), + }, + ]; + + const onChange = (key: string) => { + console.log(key); + }; + + return ( + + + + ); +}; + +export default EmbedModal; diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts index cf5c85831..e44155dea 100644 --- a/web/src/pages/chat/hooks.ts +++ b/web/src/pages/chat/hooks.ts @@ -14,15 +14,21 @@ import { useRemoveToken, useSelectConversationList, useSelectDialogList, + useSelectStats, useSelectTokenList, useSetDialog, useUpdateConversation, } from '@/hooks/chatHooks'; -import { useSetModalState, useShowDeleteConfirm } from '@/hooks/commonHooks'; +import { + useSetModalState, + useShowDeleteConfirm, + useTranslate, +} from '@/hooks/commonHooks'; import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; import { IConversation, IDialog, IStats } from '@/interfaces/database/chat'; import { IChunk } from '@/interfaces/database/knowledge'; import { getFileExtension } from '@/utils'; +import { message } from 'antd'; import dayjs, { Dayjs } from 'dayjs'; import omit from 'lodash/omit'; import { @@ -777,35 +783,35 @@ type ChartStatsType = { }; export const useSelectChartStatsList = (): ChartStatsType => { - // const stats: IStats = useSelectStats(); - const stats = { - pv: [ - ['2024-06-01', 1], - ['2024-07-24', 3], - ['2024-09-01', 10], - ], - uv: [ - ['2024-02-01', 0], - ['2024-03-01', 99], - ['2024-05-01', 3], - ], - speed: [ - ['2024-09-01', 2], - ['2024-09-01', 3], - ], - tokens: [ - ['2024-09-01', 1], - ['2024-09-01', 3], - ], - round: [ - ['2024-09-01', 0], - ['2024-09-01', 3], - ], - thumb_up: [ - ['2024-09-01', 3], - ['2024-09-01', 9], - ], - }; + const stats: IStats = useSelectStats(); + // const stats = { + // pv: [ + // ['2024-06-01', 1], + // ['2024-07-24', 3], + // ['2024-09-01', 10], + // ], + // uv: [ + // ['2024-02-01', 0], + // ['2024-03-01', 99], + // ['2024-05-01', 3], + // ], + // speed: [ + // ['2024-09-01', 2], + // ['2024-09-01', 3], + // ], + // tokens: [ + // ['2024-09-01', 1], + // ['2024-09-01', 3], + // ], + // round: [ + // ['2024-09-01', 0], + // ['2024-09-01', 3], + // ], + // thumb_up: [ + // ['2024-09-01', 3], + // ['2024-09-01', 9], + // ], + // }; return Object.keys(stats).reduce((pre, cur) => { const item = stats[cur as keyof IStats]; @@ -819,4 +825,93 @@ export const useSelectChartStatsList = (): ChartStatsType => { }, {} as ChartStatsType); }; +export const useShowTokenEmptyError = () => { + const [messageApi, contextHolder] = message.useMessage(); + const { t } = useTranslate('chat'); + + const showTokenEmptyError = useCallback(() => { + messageApi.error(t('tokenError')); + }, [messageApi, t]); + return { showTokenEmptyError, contextHolder }; +}; + +const getUrlWithToken = (token: string) => { + const { protocol, host } = window.location; + return `${protocol}//${host}/chat/share?shared_id=${token}`; +}; + +const useFetchTokenListBeforeOtherStep = (dialogId: string) => { + const { showTokenEmptyError, contextHolder } = useShowTokenEmptyError(); + + const listToken = useListToken(); + const tokenList = useSelectTokenList(); + + const token = + Array.isArray(tokenList) && tokenList.length > 0 ? tokenList[0].token : ''; + + const handleOperate = useCallback(async () => { + const data = await listToken(dialogId); + const list = data.data; + if (data.retcode === 0 && Array.isArray(list) && list.length > 0) { + return list[0]?.token; + } else { + showTokenEmptyError(); + return false; + } + }, [dialogId, listToken, showTokenEmptyError]); + + return { + token, + contextHolder, + handleOperate, + }; +}; + +export const useShowEmbedModal = (dialogId: string) => { + const { + visible: embedVisible, + hideModal: hideEmbedModal, + showModal: showEmbedModal, + } = useSetModalState(); + + const { handleOperate, token, contextHolder } = + useFetchTokenListBeforeOtherStep(dialogId); + + const handleShowEmbedModal = useCallback(async () => { + const succeed = await handleOperate(); + if (succeed) { + showEmbedModal(); + } + }, [handleOperate, showEmbedModal]); + + return { + showEmbedModal: handleShowEmbedModal, + hideEmbedModal, + embedVisible, + embedToken: token, + errorContextHolder: contextHolder, + }; +}; + +export const usePreviewChat = (dialogId: string) => { + const { handleOperate, contextHolder } = + useFetchTokenListBeforeOtherStep(dialogId); + + const open = useCallback((t: string) => { + window.open(getUrlWithToken(t), '_blank'); + }, []); + + const handlePreview = useCallback(async () => { + const token = await handleOperate(); + if (token) { + open(token); + } + }, [handleOperate, open]); + + return { + handlePreview, + contextHolder, + }; +}; + //#endregion diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx index b07897bc1..a6499d53d 100644 --- a/web/src/pages/chat/index.tsx +++ b/web/src/pages/chat/index.tsx @@ -1,6 +1,11 @@ import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg'; import RenameModal from '@/components/rename-modal'; -import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons'; +import { + CloudOutlined, + DeleteOutlined, + EditOutlined, + FormOutlined, +} from '@ant-design/icons'; import { Avatar, Button, @@ -185,16 +190,16 @@ const Chat = () => { ), }, { type: 'divider' }, - // { - // key: '3', - // onClick: handleShowOverviewModal(dialog), - // label: ( - // - // - // {t('overview')} - // - // ), - // }, + { + key: '3', + onClick: handleShowOverviewModal(dialog), + label: ( + + + {t('overview')} + + ), + }, ]; return appItems; diff --git a/web/src/pages/chat/model.ts b/web/src/pages/chat/model.ts index 5c302a272..e1a6122f4 100644 --- a/web/src/pages/chat/model.ts +++ b/web/src/pages/chat/model.ts @@ -202,7 +202,7 @@ const model: DvaModel = { payload: data.data, }); } - return data.retcode; + return data; }, *removeToken({ payload }, { call, put }) { const { data } = yield call( diff --git a/web/src/pages/chat/share/large.tsx b/web/src/pages/chat/share/large.tsx index 1e510af66..1a5ba4525 100644 --- a/web/src/pages/chat/share/large.tsx +++ b/web/src/pages/chat/share/large.tsx @@ -6,10 +6,10 @@ import { Avatar, Button, Flex, Input, Skeleton, Spin } from 'antd'; import classNames from 'classnames'; import { useSelectConversationLoading } from '../hooks'; +import HightLightMarkdown from '@/components/highlight-markdown'; import React, { ChangeEventHandler, forwardRef } from 'react'; import { IClientConversation } from '../interface'; import styles from './index.less'; -import SharedMarkdown from './shared-markdown'; const MessageItem = ({ item }: { item: Message }) => { const isAssistant = item.role === MessageType.Assistant; @@ -46,7 +46,7 @@ const MessageItem = ({ item }: { item: Message }) => { {isAssistant ? '' : 'You'}
{item.content !== '' ? ( - + {item.content} ) : ( )} diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts index 39d30da42..267831db6 100644 --- a/web/src/utils/request.ts +++ b/web/src/utils/request.ts @@ -98,8 +98,8 @@ request.interceptors.request.use((url: string, options: any) => { url, options: { ...options, - // data, - // params, + data, + params, headers: { ...(options.skipToken ? undefined : { [Authorization]: authorization }), ...options.headers,