mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-18 17:51:24 +00:00
fix: save chat
This commit is contained in:
@@ -7,6 +7,7 @@ const Avatar = ({ w = '30px', ...props }: ImageProps) => {
|
|||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
fallbackSrc={LOGO_ICON}
|
fallbackSrc={LOGO_ICON}
|
||||||
|
fallbackStrategy={'onError'}
|
||||||
borderRadius={'50%'}
|
borderRadius={'50%'}
|
||||||
objectFit={'cover'}
|
objectFit={'cover'}
|
||||||
alt=""
|
alt=""
|
||||||
|
@@ -8,7 +8,7 @@ import React, {
|
|||||||
ForwardedRef
|
ForwardedRef
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import { ChatSiteItemType } from '@/types/chat';
|
import { ChatItemType, ChatSiteItemType, ExportChatType } from '@/types/chat';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { useCopyData, voiceBroadcast, hasVoiceApi } from '@/utils/tools';
|
import { useCopyData, voiceBroadcast, hasVoiceApi } from '@/utils/tools';
|
||||||
import { Box, Card, Flex, Input, Textarea, Button, useTheme } from '@chakra-ui/react';
|
import { Box, Card, Flex, Input, Textarea, Button, useTheme } from '@chakra-ui/react';
|
||||||
@@ -28,6 +28,8 @@ import MySelect from '@/components/Select';
|
|||||||
import { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
|
import { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
import MyTooltip from '../MyTooltip';
|
import MyTooltip from '../MyTooltip';
|
||||||
|
import { fileDownload } from '@/utils/file';
|
||||||
|
import { htmlTemplate } from '@/constants/common';
|
||||||
|
|
||||||
const textareaMinH = '22px';
|
const textareaMinH = '22px';
|
||||||
export type StartChatFnProps = {
|
export type StartChatFnProps = {
|
||||||
@@ -607,3 +609,76 @@ const ChatBox = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(forwardRef(ChatBox));
|
export default React.memo(forwardRef(ChatBox));
|
||||||
|
|
||||||
|
export const useChatBox = () => {
|
||||||
|
const onExportChat = useCallback(
|
||||||
|
({ type, history }: { type: ExportChatType; history: ChatItemType[] }) => {
|
||||||
|
const getHistoryHtml = () => {
|
||||||
|
const historyDom = document.getElementById('history');
|
||||||
|
if (!historyDom) return;
|
||||||
|
const dom = Array.from(historyDom.children).map((child, i) => {
|
||||||
|
const avatar = `<img src="${
|
||||||
|
child.querySelector<HTMLImageElement>('.avatar')?.src
|
||||||
|
}" alt="" />`;
|
||||||
|
|
||||||
|
const chatContent = child.querySelector<HTMLDivElement>('.markdown');
|
||||||
|
|
||||||
|
if (!chatContent) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatContentClone = chatContent.cloneNode(true) as HTMLDivElement;
|
||||||
|
|
||||||
|
const codeHeader = chatContentClone.querySelectorAll('.code-header');
|
||||||
|
codeHeader.forEach((childElement: any) => {
|
||||||
|
childElement.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
return `<div class="chat-item">
|
||||||
|
${avatar}
|
||||||
|
${chatContentClone.outerHTML}
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const html = htmlTemplate.replace('{{CHAT_CONTENT}}', dom.join('\n'));
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
const map: Record<ExportChatType, () => void> = {
|
||||||
|
md: () => {
|
||||||
|
fileDownload({
|
||||||
|
text: history.map((item) => item.value).join('\n\n'),
|
||||||
|
type: 'text/markdown',
|
||||||
|
filename: 'chat.md'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
html: () => {
|
||||||
|
const html = getHistoryHtml();
|
||||||
|
html &&
|
||||||
|
fileDownload({
|
||||||
|
text: html,
|
||||||
|
type: 'text/html',
|
||||||
|
filename: '聊天记录.html'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
pdf: () => {
|
||||||
|
const html = getHistoryHtml();
|
||||||
|
|
||||||
|
html &&
|
||||||
|
// @ts-ignore
|
||||||
|
html2pdf(html, {
|
||||||
|
margin: 0,
|
||||||
|
filename: `聊天记录.pdf`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
map[type]();
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
onExportChat
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@@ -124,6 +124,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
historyId,
|
historyId,
|
||||||
newHistoryId,
|
newHistoryId,
|
||||||
appId,
|
appId,
|
||||||
|
variables,
|
||||||
prompts: [
|
prompts: [
|
||||||
prompt,
|
prompt,
|
||||||
{
|
{
|
||||||
|
30
client/src/pages/chat/components/ToolMenu.tsx
Normal file
30
client/src/pages/chat/components/ToolMenu.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useChatBox } from '@/components/ChatBox';
|
||||||
|
import { ChatItemType } from '@/types/chat';
|
||||||
|
import { Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
|
||||||
|
const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
|
||||||
|
const { onExportChat } = useChatBox();
|
||||||
|
return (
|
||||||
|
<Menu autoSelect={false} isLazy>
|
||||||
|
<MenuButton
|
||||||
|
_hover={{ bg: 'myWhite.600 ' }}
|
||||||
|
cursor={'pointer'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MyIcon name={'more'} w={'14px'} p={2} />
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList color={'myGray.700'} minW={`90px !important`}>
|
||||||
|
<MenuItem onClick={() => onExportChat({ type: 'html', history })}>导出HTML格式</MenuItem>
|
||||||
|
<MenuItem onClick={() => onExportChat({ type: 'pdf', history })}>导出PDF格式</MenuItem>
|
||||||
|
<MenuItem onClick={() => onExportChat({ type: 'md', history })}>导出Markdown格式</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToolMenu;
|
@@ -32,12 +32,13 @@ import { useChatStore } from '@/store/chat';
|
|||||||
import { useLoading } from '@/hooks/useLoading';
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
|
|
||||||
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
|
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
|
||||||
|
import { ChatHistoryItemType } from '@/types/chat';
|
||||||
import PageContainer from '@/components/PageContainer';
|
import PageContainer from '@/components/PageContainer';
|
||||||
import SideBar from '@/components/SideBar';
|
import SideBar from '@/components/SideBar';
|
||||||
import ChatHistorySlider from './components/ChatHistorySlider';
|
import ChatHistorySlider from './components/ChatHistorySlider';
|
||||||
import SliderApps from './components/SliderApps';
|
import SliderApps from './components/SliderApps';
|
||||||
import Tag from '@/components/Tag';
|
import Tag from '@/components/Tag';
|
||||||
import { ChatHistoryItemType } from '@/types/chat';
|
import ToolMenu from './components/ToolMenu';
|
||||||
|
|
||||||
const Chat = () => {
|
const Chat = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -316,6 +317,8 @@ const Chat = () => {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<Box flex={1} />
|
||||||
|
<ToolMenu history={chatData.history} />
|
||||||
</Flex>
|
</Flex>
|
||||||
{/* chat box */}
|
{/* chat box */}
|
||||||
<Box flex={1}>
|
<Box flex={1}>
|
||||||
|
Reference in New Issue
Block a user