### 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)tags/v0.3.0
| import SyntaxHighlighter from 'react-syntax-highlighter'; | import SyntaxHighlighter from 'react-syntax-highlighter'; | ||||
| import remarkGfm from 'remark-gfm'; | import remarkGfm from 'remark-gfm'; | ||||
| const SharedMarkdown = ({ content }: { content: string }) => { | |||||
| const HightLightMarkdown = ({ | |||||
| children, | |||||
| }: { | |||||
| children: string | null | undefined; | |||||
| }) => { | |||||
| return ( | return ( | ||||
| <Markdown | <Markdown | ||||
| remarkPlugins={[remarkGfm]} | remarkPlugins={[remarkGfm]} | ||||
| } as any | } as any | ||||
| } | } | ||||
| > | > | ||||
| {content} | |||||
| {children} | |||||
| </Markdown> | </Markdown> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default SharedMarkdown; | |||||
| export default HightLightMarkdown; |
| IStats, | IStats, | ||||
| IToken, | IToken, | ||||
| } from '@/interfaces/database/chat'; | } from '@/interfaces/database/chat'; | ||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import { useCallback } from 'react'; | |||||
| import { useDispatch, useSelector } from 'umi'; | import { useDispatch, useSelector } from 'umi'; | ||||
| export const useFetchDialogList = () => { | export const useFetchDialogList = () => { | ||||
| return completeSharedConversation; | 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 | //#endregion |
| .pointerCursor() { | .pointerCursor() { | ||||
| cursor: pointer; | cursor: pointer; | ||||
| } | } | ||||
| .clearCardBody() { | |||||
| :global { | |||||
| .ant-card-body { | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| } | |||||
| } | |||||
| } |
| 'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).', | 'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).', | ||||
| quote: 'Show Quote', | quote: 'Show Quote', | ||||
| quoteTip: 'Should the source of the original text be displayed?', | quoteTip: 'Should the source of the original text be displayed?', | ||||
| overview: 'Overview', | |||||
| overview: 'API', | |||||
| pv: 'Number of messages', | pv: 'Number of messages', | ||||
| uv: 'Active user number', | uv: 'Active user number', | ||||
| speed: 'Token output speed', | speed: 'Token output speed', | ||||
| createNewKey: 'Create new key', | createNewKey: 'Create new key', | ||||
| created: 'Created', | created: 'Created', | ||||
| action: 'Action', | 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: { | setting: { | ||||
| profile: 'Profile', | profile: 'Profile', |
| '這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。', | '這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。', | ||||
| quote: '顯示引文', | quote: '顯示引文', | ||||
| quoteTip: '是否應該顯示原文出處?', | quoteTip: '是否應該顯示原文出處?', | ||||
| overview: '概覽', | |||||
| overview: 'API', | |||||
| pv: '消息數', | pv: '消息數', | ||||
| uv: '活躍用戶數', | uv: '活躍用戶數', | ||||
| speed: 'Token 輸出速度', | speed: 'Token 輸出速度', | ||||
| createNewKey: '創建新密鑰', | createNewKey: '創建新密鑰', | ||||
| created: '創建於', | created: '創建於', | ||||
| action: '操作', | action: '操作', | ||||
| embedModalTitle: '嵌入網站', | |||||
| comingSoon: '即將推出', | |||||
| fullScreenTitle: '全屏嵌入', | |||||
| fullScreenDescription: '將以下iframe嵌入您的網站處於所需位置', | |||||
| partialTitle: '部分嵌入', | |||||
| extensionTitle: 'Chrome 插件', | |||||
| tokenError: '請先創建 Api Token!', | |||||
| }, | }, | ||||
| setting: { | setting: { | ||||
| profile: '概述', | profile: '概述', |
| '这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。', | '这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。', | ||||
| quote: '显示引文', | quote: '显示引文', | ||||
| quoteTip: '是否应该显示原文出处?', | quoteTip: '是否应该显示原文出处?', | ||||
| overview: '概览', | |||||
| overview: 'API', | |||||
| pv: '消息数', | pv: '消息数', | ||||
| uv: '活跃用户数', | uv: '活跃用户数', | ||||
| speed: 'Token 输出速度', | speed: 'Token 输出速度', | ||||
| createNewKey: '创建新密钥', | createNewKey: '创建新密钥', | ||||
| created: '创建于', | created: '创建于', | ||||
| action: '操作', | action: '操作', | ||||
| embedModalTitle: '嵌入网站', | |||||
| comingSoon: '即将推出', | |||||
| fullScreenTitle: '全屏嵌入', | |||||
| fullScreenDescription: '将以下iframe嵌入您的网站处于所需位置', | |||||
| partialTitle: '部分嵌入', | |||||
| extensionTitle: 'Chrome 插件', | |||||
| tokenError: '请先创建 Api Token!', | |||||
| }, | }, | ||||
| setting: { | setting: { | ||||
| profile: '概要', | profile: '概要', |
| import CopyToClipboard from '@/components/copy-to-clipboard'; | |||||
| import LineChart from '@/components/line-chart'; | import LineChart from '@/components/line-chart'; | ||||
| import { useCreatePublicUrlToken } from '@/hooks/chatHooks'; | |||||
| import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; | import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; | ||||
| import { IModalProps } from '@/interfaces/common'; | import { IModalProps } from '@/interfaces/common'; | ||||
| import { IDialog, IStats } from '@/interfaces/database/chat'; | import { IDialog, IStats } from '@/interfaces/database/chat'; | ||||
| import { ReloadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Card, DatePicker, Flex, Modal, Space, Typography } from 'antd'; | import { Button, Card, DatePicker, Flex, Modal, Space, Typography } from 'antd'; | ||||
| import { RangePickerProps } from 'antd/es/date-picker'; | import { RangePickerProps } from 'antd/es/date-picker'; | ||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import camelCase from 'lodash/camelCase'; | import camelCase from 'lodash/camelCase'; | ||||
| import { Link } from 'umi'; | |||||
| import ChatApiKeyModal from '../chat-api-key-modal'; | 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'; | import styles from './index.less'; | ||||
| const { Paragraph } = Typography; | const { Paragraph } = Typography; | ||||
| }: IModalProps<any> & { dialog: IDialog }) => { | }: IModalProps<any> & { dialog: IDialog }) => { | ||||
| const { t } = useTranslate('chat'); | const { t } = useTranslate('chat'); | ||||
| const chartList = useSelectChartStatsList(); | const chartList = useSelectChartStatsList(); | ||||
| const { urlWithToken, createUrlToken, token } = useCreatePublicUrlToken( | |||||
| dialog.id, | |||||
| visible, | |||||
| ); | |||||
| const { | const { | ||||
| visible: apiKeyVisible, | visible: apiKeyVisible, | ||||
| hideModal: hideApiKeyModal, | hideModal: hideApiKeyModal, | ||||
| showModal: showApiKeyModal, | showModal: showApiKeyModal, | ||||
| } = useSetModalState(); | } = useSetModalState(); | ||||
| const { | |||||
| embedVisible, | |||||
| hideEmbedModal, | |||||
| showEmbedModal, | |||||
| embedToken, | |||||
| errorContextHolder, | |||||
| } = useShowEmbedModal(dialog.id); | |||||
| const { pickerValue, setPickerValue } = useFetchStatsOnMount(visible); | const { pickerValue, setPickerValue } = useFetchStatsOnMount(visible); | ||||
| return current && current > dayjs().endOf('day'); | return current && current > dayjs().endOf('day'); | ||||
| }; | }; | ||||
| const { handlePreview, contextHolder } = usePreviewChat(dialog.id); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Modal | <Modal | ||||
| width={'100vw'} | width={'100vw'} | ||||
| > | > | ||||
| <Flex vertical gap={'middle'}> | <Flex vertical gap={'middle'}> | ||||
| <Card title={dialog.name}> | |||||
| <Flex gap={8} vertical> | |||||
| {t('publicUrl')} | |||||
| <Flex className={styles.linkText} gap={10}> | |||||
| <span>{urlWithToken}</span> | |||||
| <CopyToClipboard text={urlWithToken}></CopyToClipboard> | |||||
| <ReloadOutlined onClick={createUrlToken} /> | |||||
| </Flex> | |||||
| <Space size={'middle'}> | |||||
| <Button> | |||||
| <Link to={`/chat/share?shared_id=${token}`} target="_blank"> | |||||
| {t('preview')} | |||||
| </Link> | |||||
| </Button> | |||||
| <Button>{t('embedded')}</Button> | |||||
| </Space> | |||||
| </Flex> | |||||
| </Card> | |||||
| <Card title={t('backendServiceApi')}> | <Card title={t('backendServiceApi')}> | ||||
| <Flex gap={8} vertical> | <Flex gap={8} vertical> | ||||
| {t('serviceApiEndpoint')} | {t('serviceApiEndpoint')} | ||||
| <Paragraph copyable className={styles.linkText}> | <Paragraph copyable className={styles.linkText}> | ||||
| This is a copyable text. | |||||
| https://demo.ragflow.io/v1/api/ | |||||
| </Paragraph> | </Paragraph> | ||||
| </Flex> | </Flex> | ||||
| <Space size={'middle'}> | <Space size={'middle'}> | ||||
| <Button onClick={showApiKeyModal}>{t('apiKey')}</Button> | <Button onClick={showApiKeyModal}>{t('apiKey')}</Button> | ||||
| <Button>{t('apiReference')}</Button> | |||||
| <a | |||||
| href={ | |||||
| 'https://github.com/infiniflow/ragflow/blob/main/docs/conversation_api.md' | |||||
| } | |||||
| target="_blank" | |||||
| rel="noreferrer" | |||||
| > | |||||
| <Button>{t('apiReference')}</Button> | |||||
| </a> | |||||
| </Space> | </Space> | ||||
| </Card> | </Card> | ||||
| <Card title={dialog.name}> | |||||
| <Flex gap={8} vertical> | |||||
| {t('publicUrl')} | |||||
| {/* <Flex className={styles.linkText} gap={10}> | |||||
| <span>{urlWithToken}</span> | |||||
| <CopyToClipboard text={urlWithToken}></CopyToClipboard> | |||||
| <ReloadOutlined onClick={createUrlToken} /> | |||||
| </Flex> */} | |||||
| <Space size={'middle'}> | |||||
| <Button onClick={handlePreview}>{t('preview')}</Button> | |||||
| <Button onClick={showEmbedModal}>{t('embedded')}</Button> | |||||
| </Space> | |||||
| </Flex> | |||||
| </Card> | |||||
| <Space> | <Space> | ||||
| <b>{t('dateRange')}</b> | <b>{t('dateRange')}</b> | ||||
| <RangePicker | <RangePicker | ||||
| hideModal={hideApiKeyModal} | hideModal={hideApiKeyModal} | ||||
| dialogId={dialog.id} | dialogId={dialog.id} | ||||
| ></ChatApiKeyModal> | ></ChatApiKeyModal> | ||||
| <EmbedModal | |||||
| token={embedToken} | |||||
| visible={embedVisible} | |||||
| hideModal={hideEmbedModal} | |||||
| ></EmbedModal> | |||||
| {contextHolder} | |||||
| {errorContextHolder} | |||||
| </Modal> | </Modal> | ||||
| </> | </> | ||||
| ); | ); |
| .codeCard { | |||||
| .clearCardBody(); | |||||
| } | |||||
| .codeText { | |||||
| padding: 10px; | |||||
| background-color: #e8e8ea; | |||||
| } |
| 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<any> & { token: string }) => { | |||||
| const { t } = useTranslate('chat'); | |||||
| const text = ` | |||||
| ~~~ html | |||||
| <iframe | |||||
| src="https://demo.ragflow.io/chat/share?shared_id=${token}" | |||||
| style="width: 100%; height: 100%; min-height: 600px" | |||||
| frameborder="0" | |||||
| > | |||||
| </iframe> | |||||
| ~~~ | |||||
| `; | |||||
| const items: TabsProps['items'] = [ | |||||
| { | |||||
| key: '1', | |||||
| label: t('fullScreenTitle'), | |||||
| children: ( | |||||
| <Card | |||||
| title={t('fullScreenDescription')} | |||||
| extra={<CopyToClipboard text={text}></CopyToClipboard>} | |||||
| className={styles.codeCard} | |||||
| > | |||||
| <HightLightMarkdown>{text}</HightLightMarkdown> | |||||
| </Card> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: t('partialTitle'), | |||||
| children: t('comingSoon'), | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: t('extensionTitle'), | |||||
| children: t('comingSoon'), | |||||
| }, | |||||
| ]; | |||||
| const onChange = (key: string) => { | |||||
| console.log(key); | |||||
| }; | |||||
| return ( | |||||
| <Modal | |||||
| title={t('embedModalTitle')} | |||||
| open={visible} | |||||
| style={{ top: 300 }} | |||||
| width={'50vw'} | |||||
| onOk={hideModal} | |||||
| onCancel={hideModal} | |||||
| > | |||||
| <Tabs defaultActiveKey="1" items={items} onChange={onChange} /> | |||||
| </Modal> | |||||
| ); | |||||
| }; | |||||
| export default EmbedModal; |
| useRemoveToken, | useRemoveToken, | ||||
| useSelectConversationList, | useSelectConversationList, | ||||
| useSelectDialogList, | useSelectDialogList, | ||||
| useSelectStats, | |||||
| useSelectTokenList, | useSelectTokenList, | ||||
| useSetDialog, | useSetDialog, | ||||
| useUpdateConversation, | useUpdateConversation, | ||||
| } from '@/hooks/chatHooks'; | } from '@/hooks/chatHooks'; | ||||
| import { useSetModalState, useShowDeleteConfirm } from '@/hooks/commonHooks'; | |||||
| import { | |||||
| useSetModalState, | |||||
| useShowDeleteConfirm, | |||||
| useTranslate, | |||||
| } from '@/hooks/commonHooks'; | |||||
| import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; | import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; | ||||
| import { IConversation, IDialog, IStats } from '@/interfaces/database/chat'; | import { IConversation, IDialog, IStats } from '@/interfaces/database/chat'; | ||||
| import { IChunk } from '@/interfaces/database/knowledge'; | import { IChunk } from '@/interfaces/database/knowledge'; | ||||
| import { getFileExtension } from '@/utils'; | import { getFileExtension } from '@/utils'; | ||||
| import { message } from 'antd'; | |||||
| import dayjs, { Dayjs } from 'dayjs'; | import dayjs, { Dayjs } from 'dayjs'; | ||||
| import omit from 'lodash/omit'; | import omit from 'lodash/omit'; | ||||
| import { | import { | ||||
| }; | }; | ||||
| export const useSelectChartStatsList = (): 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) => { | return Object.keys(stats).reduce((pre, cur) => { | ||||
| const item = stats[cur as keyof IStats]; | const item = stats[cur as keyof IStats]; | ||||
| }, {} as 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 | //#endregion |
| import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg'; | import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg'; | ||||
| import RenameModal from '@/components/rename-modal'; | import RenameModal from '@/components/rename-modal'; | ||||
| import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons'; | |||||
| import { | |||||
| CloudOutlined, | |||||
| DeleteOutlined, | |||||
| EditOutlined, | |||||
| FormOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { | import { | ||||
| Avatar, | Avatar, | ||||
| Button, | Button, | ||||
| ), | ), | ||||
| }, | }, | ||||
| { type: 'divider' }, | { type: 'divider' }, | ||||
| // { | |||||
| // key: '3', | |||||
| // onClick: handleShowOverviewModal(dialog), | |||||
| // label: ( | |||||
| // <Space> | |||||
| // <ProfileOutlined /> | |||||
| // {t('overview')} | |||||
| // </Space> | |||||
| // ), | |||||
| // }, | |||||
| { | |||||
| key: '3', | |||||
| onClick: handleShowOverviewModal(dialog), | |||||
| label: ( | |||||
| <Space> | |||||
| <CloudOutlined /> | |||||
| {t('overview')} | |||||
| </Space> | |||||
| ), | |||||
| }, | |||||
| ]; | ]; | ||||
| return appItems; | return appItems; |
| payload: data.data, | payload: data.data, | ||||
| }); | }); | ||||
| } | } | ||||
| return data.retcode; | |||||
| return data; | |||||
| }, | }, | ||||
| *removeToken({ payload }, { call, put }) { | *removeToken({ payload }, { call, put }) { | ||||
| const { data } = yield call( | const { data } = yield call( |
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useSelectConversationLoading } from '../hooks'; | import { useSelectConversationLoading } from '../hooks'; | ||||
| import HightLightMarkdown from '@/components/highlight-markdown'; | |||||
| import React, { ChangeEventHandler, forwardRef } from 'react'; | import React, { ChangeEventHandler, forwardRef } from 'react'; | ||||
| import { IClientConversation } from '../interface'; | import { IClientConversation } from '../interface'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import SharedMarkdown from './shared-markdown'; | |||||
| const MessageItem = ({ item }: { item: Message }) => { | const MessageItem = ({ item }: { item: Message }) => { | ||||
| const isAssistant = item.role === MessageType.Assistant; | const isAssistant = item.role === MessageType.Assistant; | ||||
| <b>{isAssistant ? '' : 'You'}</b> | <b>{isAssistant ? '' : 'You'}</b> | ||||
| <div className={styles.messageText}> | <div className={styles.messageText}> | ||||
| {item.content !== '' ? ( | {item.content !== '' ? ( | ||||
| <SharedMarkdown content={item.content}></SharedMarkdown> | |||||
| <HightLightMarkdown>{item.content}</HightLightMarkdown> | |||||
| ) : ( | ) : ( | ||||
| <Skeleton active className={styles.messageEmpty} /> | <Skeleton active className={styles.messageEmpty} /> | ||||
| )} | )} |
| url, | url, | ||||
| options: { | options: { | ||||
| ...options, | ...options, | ||||
| // data, | |||||
| // params, | |||||
| data, | |||||
| params, | |||||
| headers: { | headers: { | ||||
| ...(options.skipToken ? undefined : { [Authorization]: authorization }), | ...(options.skipToken ? undefined : { [Authorization]: authorization }), | ||||
| ...options.headers, | ...options.headers, |