mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-01 07:36:39 +08:00
feat: export as image
This commit is contained in:
parent
278ed92717
commit
420f8511a1
@ -218,6 +218,7 @@
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.chat-body-title {
|
||||
|
@ -20,6 +20,7 @@ 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 ExportImage from "../icons/export-image.svg";
|
||||
|
||||
import { Message, SubmitKey, useChatStore, ChatSession } from "../store";
|
||||
import { showModal, showToast } from "./ui-lib";
|
||||
@ -36,6 +37,7 @@ import dynamic from "next/dynamic";
|
||||
import { REPO_URL } from "../constant";
|
||||
import { ControllerPool } from "../requests";
|
||||
import { Prompt, usePromptStore } from "../store/prompt";
|
||||
import { toPng } from "html-to-image";
|
||||
|
||||
export function Loading(props: { noLogo?: boolean }) {
|
||||
return (
|
||||
@ -341,6 +343,9 @@ export function Chat(props: {
|
||||
}, 500);
|
||||
});
|
||||
|
||||
//export image
|
||||
const [exportImageLoading, setExportImageLoading] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={styles.chat} key={session.id}>
|
||||
<div className={styles["window-header"]}>
|
||||
@ -394,10 +399,28 @@ export function Chat(props: {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["window-action-button"]}>
|
||||
{exportImageLoading ? (
|
||||
<IconButton
|
||||
icon={<LoadingIcon />}
|
||||
bordered
|
||||
title={Locale.Chat.Actions.GeneratingImage}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
icon={<ExportImage />}
|
||||
bordered
|
||||
title={Locale.Chat.Actions.ExportImage}
|
||||
onClick={() => {
|
||||
exportImage(session.topic, setExportImageLoading);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles["chat-body"]}>
|
||||
<div className={styles["chat-body"]} id="chat-body">
|
||||
{messages.map((message, i) => {
|
||||
const isUser = message.role === "user";
|
||||
|
||||
@ -584,6 +607,30 @@ function showMemoryPrompt(session: ChatSession) {
|
||||
});
|
||||
}
|
||||
|
||||
async function exportImage(
|
||||
topic: string,
|
||||
setExportImageLoading: (loading: boolean) => void,
|
||||
) {
|
||||
setExportImageLoading(true);
|
||||
const element = document.querySelector("#chat-body") as HTMLElement;
|
||||
try {
|
||||
const dataURL = await toPng(element, {
|
||||
width: element.scrollWidth,
|
||||
height: element.scrollHeight,
|
||||
});
|
||||
let link = document.createElement("a");
|
||||
link.download = `${topic}-${new Date().toLocaleString()}.png`;
|
||||
link.href = dataURL;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
setExportImageLoading(false);
|
||||
} catch (error) {
|
||||
showToast(Locale.Export.Failed);
|
||||
setExportImageLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const useHasHydrated = () => {
|
||||
const [hasHydrated, setHasHydrated] = useState<boolean>(false);
|
||||
|
||||
|
1
app/icons/export-Image.svg
Normal file
1
app/icons/export-Image.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1680342009123" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3081" id="mx_n_1680342009124" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M859.3 191.8H164.2c-55 0-100 45-100 100V765c0 55 45 100 100 100h695.1c55 0 100-45 100-100V291.8c0-55-45-100-100-100z m36 573.2c0 19.5-16.5 36-36 36H164.2c-19.5 0-36-16.5-36-36V291.8c0-19.5 16.5-36 36-36h695.1c19.5 0 36 16.5 36 36V765z" fill="#333333" p-id="3082"></path><path d="M64.21 638.816l197.238-126.841 34.617 53.83L98.827 692.646zM368.923 723.565l378.68-281.275 38.164 51.38-378.68 281.274z" fill="#333333" p-id="3083"></path><path d="M295.644 489.41L456.4 739.385l-53.83 34.618-160.756-249.975zM901.4 778.2L705.8 474l51.2-38.7 198.2 308.3zM661 277.8c-42.4 0-76.8 34.4-76.8 76.8s34.4 76.8 76.8 76.8 76.8-34.4 76.8-76.8-34.4-76.8-76.8-76.8z m33.1 109.8c-18.2 18.2-47.9 18.2-66.1 0-18.2-18.2-18.2-47.9 0-66.1s47.9-18.2 66.1 0 18.2 47.9 0 66.1z" fill="#333333" p-id="3084"></path></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -60,6 +60,7 @@ export default function RootLayout({
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap"
|
||||
rel="stylesheet"
|
||||
crossOrigin="anonymous"
|
||||
></link>
|
||||
<script src="/serviceWorkerRegister.js" defer></script>
|
||||
</head>
|
||||
|
@ -17,6 +17,8 @@ const cn = {
|
||||
Copy: "复制",
|
||||
Stop: "停止",
|
||||
Retry: "重试",
|
||||
ExportImage: "导出图片",
|
||||
GeneratingImage: "正在生成图片",
|
||||
},
|
||||
Rename: "重命名对话",
|
||||
Typing: "正在输入…",
|
||||
@ -33,6 +35,8 @@ const cn = {
|
||||
Title: "导出聊天记录为 Markdown",
|
||||
Copy: "全部复制",
|
||||
Download: "下载文件",
|
||||
Image: "导出图片",
|
||||
Failed: "导出失败",
|
||||
},
|
||||
Memory: {
|
||||
Title: "上下文记忆 Prompt",
|
||||
|
@ -19,6 +19,8 @@ const en: LocaleType = {
|
||||
Copy: "Copy",
|
||||
Stop: "Stop",
|
||||
Retry: "Retry",
|
||||
ExportImage: "Export All Messages as Image",
|
||||
GeneratingImage: "Generating Image",
|
||||
},
|
||||
Rename: "Rename Chat",
|
||||
Typing: "Typing…",
|
||||
@ -35,6 +37,8 @@ const en: LocaleType = {
|
||||
Title: "All Messages",
|
||||
Copy: "Copy All",
|
||||
Download: "Download",
|
||||
Image: "Export Image",
|
||||
Failed: "Export Failed",
|
||||
},
|
||||
Memory: {
|
||||
Title: "Memory Prompt",
|
||||
|
@ -19,6 +19,8 @@ const es: LocaleType = {
|
||||
Copy: "Copiar",
|
||||
Stop: "Detener",
|
||||
Retry: "Reintentar",
|
||||
ExportImage: "Exportar imagen",
|
||||
GeneratingImage: "Generando imagen",
|
||||
},
|
||||
Rename: "Renombrar chat",
|
||||
Typing: "Escribiendo...",
|
||||
@ -35,6 +37,8 @@ const es: LocaleType = {
|
||||
Title: "Todos los mensajes",
|
||||
Copy: "Copiar todo",
|
||||
Download: "Descargar",
|
||||
Image: "Exportar imagen",
|
||||
Failed: "Exportación fallida",
|
||||
},
|
||||
Memory: {
|
||||
Title: "Historial de memoria",
|
||||
@ -143,12 +147,14 @@ const es: LocaleType = {
|
||||
Summarize:
|
||||
"Resuma nuestra discusión brevemente en 50 caracteres o menos para usarlo como un recordatorio para futuros contextos.",
|
||||
},
|
||||
ConfirmClearAll: "¿Confirmar para borrar todos los datos de chat y configuración?",
|
||||
ConfirmClearAll:
|
||||
"¿Confirmar para borrar todos los datos de chat y configuración?",
|
||||
},
|
||||
Copy: {
|
||||
Success: "Copiado al portapapeles",
|
||||
Failed: "La copia falló, por favor concede permiso para acceder al portapapeles",
|
||||
Failed:
|
||||
"La copia falló, por favor concede permiso para acceder al portapapeles",
|
||||
},
|
||||
};
|
||||
|
||||
export default es;
|
||||
export default es;
|
||||
|
@ -18,6 +18,8 @@ const tw: LocaleType = {
|
||||
Copy: "複製",
|
||||
Stop: "停止",
|
||||
Retry: "重試",
|
||||
ExportImage: "匯出圖片",
|
||||
GeneratingImage: "正在生成圖片",
|
||||
},
|
||||
Rename: "重命名對話",
|
||||
Typing: "正在輸入…",
|
||||
@ -34,6 +36,8 @@ const tw: LocaleType = {
|
||||
Title: "匯出聊天記錄為 Markdown",
|
||||
Copy: "複製全部",
|
||||
Download: "下載檔案",
|
||||
Image: "匯出圖片",
|
||||
Failed: "匯出失敗",
|
||||
},
|
||||
Memory: {
|
||||
Title: "上下文記憶 Prompt",
|
||||
|
@ -17,15 +17,16 @@
|
||||
"emoji-picker-react": "^4.4.7",
|
||||
"eventsource-parser": "^0.1.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"html-to-image": "^1.11.11",
|
||||
"next": "^13.2.3",
|
||||
"node-fetch": "^3.3.1",
|
||||
"openai": "^3.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^8.0.5",
|
||||
"remark-breaks": "^3.0.2",
|
||||
"rehype-katex": "^6.0.2",
|
||||
"rehype-prism-plus": "^1.5.1",
|
||||
"remark-breaks": "^3.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"sass": "^1.59.2",
|
||||
|
@ -2890,6 +2890,11 @@ hastscript@^7.0.0:
|
||||
property-information "^6.0.0"
|
||||
space-separated-tokens "^2.0.0"
|
||||
|
||||
html-to-image@^1.11.11:
|
||||
version "1.11.11"
|
||||
resolved "https://registry.npmmirror.com/html-to-image/-/html-to-image-1.11.11.tgz#c0f8a34dc9e4b97b93ff7ea286eb8562642ebbea"
|
||||
integrity sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==
|
||||
|
||||
human-signals@^4.3.0:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2"
|
||||
|
Loading…
Reference in New Issue
Block a user