diff --git a/app/components/home.module.scss b/app/components/home.module.scss index 15cae9080..58ed64a29 100644 --- a/app/components/home.module.scss +++ b/app/components/home.module.scss @@ -451,3 +451,10 @@ height: 100%; width: 100%; } + +.image-body { + height: 40vh; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/app/components/home.tsx b/app/components/home.tsx index 14efeae9d..2acb0f73a 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -37,7 +37,6 @@ 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 ( @@ -60,6 +59,14 @@ const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, { loading: () => , }); +const HtmlToImage = dynamic( + async () => (await import("./html-to-image")).HtmlToImage, + { + loading: () => , + ssr: false, + }, +); + export function Avatar(props: { role: Message["role"] }) { const config = useChatStore((state) => state.config); @@ -343,8 +350,34 @@ export function Chat(props: { }, 500); }); - //export image - const [exportImageLoading, setExportImageLoading] = useState(false); + // export image + const dataUrl = useRef(""); + + async function exportImage(topic: string) { + showModal({ + title: Locale.Export.Image, + children: ( +
+ { + dataUrl.current = url; + }} + /> +
+ ), + actions: [ + } + bordered + text={Locale.Chat.Actions.ExportImage} + onClick={() => { + dataUrl.current && exportPng(topic, dataUrl.current); + }} + />, + ], + }); + } return (
@@ -400,22 +433,14 @@ export function Chat(props: { />
- {exportImageLoading ? ( - } - bordered - title={Locale.Chat.Actions.GeneratingImage} - /> - ) : ( - } - bordered - title={Locale.Chat.Actions.ExportImage} - onClick={() => { - exportImage(session.topic, setExportImageLoading); - }} - /> - )} + } + bordered + title={Locale.Chat.Actions.ExportImage} + onClick={() => { + exportImage(session.topic); + }} + />
@@ -607,28 +632,11 @@ 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); - } +function exportPng(topic: string, dataURL: string) { + const a = document.createElement("a"); + a.href = dataURL; + a.download = `${topic}-${new Date().toLocaleString()}.jpg`; + a.click(); } const useHasHydrated = () => { diff --git a/app/components/html-to-image.module.scss b/app/components/html-to-image.module.scss new file mode 100644 index 000000000..456db3ca6 --- /dev/null +++ b/app/components/html-to-image.module.scss @@ -0,0 +1,7 @@ +.image-wrap { + height: 100%; + > img { + max-width: 100%; + height: auto; + } +} diff --git a/app/components/html-to-image.tsx b/app/components/html-to-image.tsx new file mode 100644 index 000000000..b3f7c80aa --- /dev/null +++ b/app/components/html-to-image.tsx @@ -0,0 +1,35 @@ +import React, { useState, useEffect } from "react"; +import { toJpeg } from "html-to-image"; +import LoadingIcon from "../icons/three-dots.svg"; +import styles from "./html-to-image.module.scss"; + +export function HtmlToImage(props: { getDataUrl: (url: string) => void }) { + const [imageUrl, setImageUrl] = useState(""); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const element = document.querySelector("#chat-body") as HTMLElement; + if (element) { + toJpeg(element, { + width: element.scrollWidth, + height: element.scrollHeight, + }) + .then((dataUrl) => { + setImageUrl(dataUrl); + props?.getDataUrl(dataUrl); + }) + .finally(() => setLoading(false)); + } + }, []); + return ( + <> + {loading ? ( + + ) : ( +
+ +
+ )} + + ); +} diff --git a/app/icons/export-Image.svg b/app/icons/export-Image.svg index 69181889b..6e916fab5 100644 --- a/app/icons/export-Image.svg +++ b/app/icons/export-Image.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file