mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-06 22:59:07 +08:00
feat: add export to .md button
This commit is contained in:
@@ -328,4 +328,8 @@
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.export-content {
|
||||
white-space: break-spaces;
|
||||
}
|
@@ -23,9 +23,13 @@ import DeleteIcon from "../icons/delete.svg";
|
||||
import LoadingIcon from "../icons/three-dots.svg";
|
||||
import MenuIcon from "../icons/menu.svg";
|
||||
import CloseIcon from "../icons/close.svg";
|
||||
import CopyIcon from "../icons/copy.svg";
|
||||
import DownloadIcon from "../icons/download.svg";
|
||||
|
||||
import { Message, SubmitKey, useChatStore, Theme } from "../store";
|
||||
import { Settings } from "./settings";
|
||||
import { showModal } from "./ui-lib";
|
||||
import { copyToClipboard, downloadAs } from "../utils";
|
||||
|
||||
export function Markdown(props: { content: string }) {
|
||||
return (
|
||||
@@ -208,7 +212,10 @@ export function Chat(props: { showSideBar?: () => void }) {
|
||||
<IconButton
|
||||
icon={<ExportIcon />}
|
||||
bordered
|
||||
title="导出聊天记录为 Markdown(开发中)"
|
||||
title="导出聊天记录"
|
||||
onClick={() => {
|
||||
exportMessages(session.messages, session.topic)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,6 +301,22 @@ function useSwitchTheme() {
|
||||
}, [config.theme]);
|
||||
}
|
||||
|
||||
function exportMessages(messages: Message[], topic: string) {
|
||||
const mdText = `# ${topic}\n\n` + messages.map(m => {
|
||||
return m.role === 'user' ? `## ${m.content}` : m.content.trim()
|
||||
}).join('\n\n')
|
||||
const filename = `${topic}.md`
|
||||
|
||||
showModal({
|
||||
title: "导出聊天记录为 Markdown", children: <div className="markdown-body">
|
||||
<pre className={styles['export-content']}>{mdText}</pre>
|
||||
</div>, actions: [
|
||||
<IconButton icon={<CopyIcon />} bordered text="全部复制" onClick={() => copyToClipboard(mdText)} />,
|
||||
<IconButton icon={<DownloadIcon />} bordered text="下载文件" onClick={() => downloadAs(mdText, filename)} />
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export function Home() {
|
||||
const [createNewSession] = useChatStore((state) => [state.newSession]);
|
||||
const loading = !useChatStore?.persist?.hasHydrated();
|
||||
|
@@ -29,6 +29,7 @@
|
||||
transform: translateY(10px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
@@ -56,3 +57,68 @@
|
||||
.list .list-item:last-child {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.modal-container {
|
||||
box-shadow: var(--card-shadow);
|
||||
background-color: var(--white);
|
||||
border-radius: 12px;
|
||||
width: 50vw;
|
||||
|
||||
--modal-padding: 20px;
|
||||
|
||||
.modal-header {
|
||||
padding: var(--modal-padding);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: var(--border-in-light);
|
||||
|
||||
.modal-title {
|
||||
font-weight: bolder;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.modal-close-btn {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
height: 40vh;
|
||||
padding: var(--modal-padding);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: var(--modal-padding);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.modal-action {
|
||||
&:not(:last-child) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.modal-container {
|
||||
width: 90vw;
|
||||
|
||||
.modal-content {
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,8 @@
|
||||
import styles from "./ui-lib.module.scss";
|
||||
import LoadingIcon from "../icons/three-dots.svg";
|
||||
import CloseIcon from "../icons/close.svg";
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { IconButton } from "./button";
|
||||
|
||||
export function Popover(props: {
|
||||
children: JSX.Element;
|
||||
@@ -46,4 +49,43 @@ export function Loading() {
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}><LoadingIcon /></div>
|
||||
}
|
||||
|
||||
interface ModalProps {
|
||||
title: string,
|
||||
children?: JSX.Element,
|
||||
actions?: JSX.Element[],
|
||||
onClose?: () => void,
|
||||
}
|
||||
export function Modal(props: ModalProps) {
|
||||
return <div className={styles['modal-container']}>
|
||||
<div className={styles['modal-header']}>
|
||||
<div className={styles['modal-title']}>{props.title}</div>
|
||||
|
||||
<div className={styles['modal-close-btn']} onClick={props.onClose}>
|
||||
<CloseIcon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles['modal-content']}>{props.children}</div>
|
||||
|
||||
<div className={styles['modal-footer']}>
|
||||
<div className={styles['modal-actions']}>
|
||||
{props.actions?.map(action => <div className={styles['modal-action']}>{action}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function showModal(props: ModalProps) {
|
||||
const div = document.createElement('div')
|
||||
div.className = "modal-mask";
|
||||
document.body.appendChild(div)
|
||||
|
||||
const root = createRoot(div)
|
||||
root.render(<Modal {...props} onClose={() => {
|
||||
props.onClose?.();
|
||||
root.unmount();
|
||||
div.remove();
|
||||
}}></Modal>)
|
||||
}
|
Reference in New Issue
Block a user