From f429a5d2a833a1ff681f82a8184bd01284521f75 Mon Sep 17 00:00:00 2001 From: Dogtiti <499960698@qq.com> Date: Sat, 1 Apr 2023 17:57:17 +0800 Subject: [PATCH] feat: export as image --- app/components/home.module.scss | 1 + app/components/home.tsx | 49 ++++++++++++++++++++++++++++++++- app/icons/export-Image.svg | 1 + app/layout.tsx | 1 + app/locales/cn.ts | 4 +++ app/locales/en.ts | 4 +++ app/locales/es.ts | 12 ++++++-- app/locales/tw.ts | 4 +++ package.json | 3 +- yarn.lock | 5 ++++ 10 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 app/icons/export-Image.svg diff --git a/app/components/home.module.scss b/app/components/home.module.scss index 764805d80..15cae9080 100644 --- a/app/components/home.module.scss +++ b/app/components/home.module.scss @@ -218,6 +218,7 @@ flex: 1; overflow: auto; padding: 20px; + background-color: var(--white); } .chat-body-title { diff --git a/app/components/home.tsx b/app/components/home.tsx index 2f09aa273..14efeae9d 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -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 (
@@ -394,10 +399,28 @@ export function Chat(props: { }} />
+
+ {exportImageLoading ? ( + } + bordered + title={Locale.Chat.Actions.GeneratingImage} + /> + ) : ( + } + bordered + title={Locale.Chat.Actions.ExportImage} + onClick={() => { + exportImage(session.topic, setExportImageLoading); + }} + /> + )} +
-
+
{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(false); diff --git a/app/icons/export-Image.svg b/app/icons/export-Image.svg new file mode 100644 index 000000000..69181889b --- /dev/null +++ b/app/icons/export-Image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 32d3576c5..96fa1642d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -60,6 +60,7 @@ export default function RootLayout({ diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 737ccad45..a8c2b2ae6 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -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", diff --git a/app/locales/en.ts b/app/locales/en.ts index 156c03616..563574ecf 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -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", diff --git a/app/locales/es.ts b/app/locales/es.ts index 3f7ad1bc7..cffc189b6 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -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; \ No newline at end of file +export default es; diff --git a/app/locales/tw.ts b/app/locales/tw.ts index cfba7add5..7e556c4c9 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -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", diff --git a/package.json b/package.json index eb17000ed..2fce8cb82 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 9f98a244d..6c4b3453a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"