From 7d446cc7f885385cb7bb6caccd7fd4614a836504 Mon Sep 17 00:00:00 2001
From: Dogtiti <499960698@qq.com>
Date: Sat, 1 Apr 2023 23:44:03 +0800
Subject: [PATCH] refactor: export image
---
app/components/home.module.scss | 7 ++
app/components/home.tsx | 90 +++++++++++++-----------
app/components/html-to-image.module.scss | 7 ++
app/components/html-to-image.tsx | 35 +++++++++
app/icons/export-Image.svg | 2 +-
5 files changed, 99 insertions(+), 42 deletions(-)
create mode 100644 app/components/html-to-image.module.scss
create mode 100644 app/components/html-to-image.tsx
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