refactor: export image

This commit is contained in:
Dogtiti 2023-04-01 23:44:03 +08:00
parent f429a5d2a8
commit 7d446cc7f8
5 changed files with 99 additions and 42 deletions

View File

@ -451,3 +451,10 @@
height: 100%;
width: 100%;
}
.image-body {
height: 40vh;
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -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: () => <LoadingIcon />,
});
const HtmlToImage = dynamic(
async () => (await import("./html-to-image")).HtmlToImage,
{
loading: () => <LoadingIcon />,
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: (
<div className={styles["image-body"]}>
<HtmlToImage
getDataUrl={(url) => {
dataUrl.current = url;
}}
/>
</div>
),
actions: [
<IconButton
key="exportPng"
icon={<ExportImage />}
bordered
text={Locale.Chat.Actions.ExportImage}
onClick={() => {
dataUrl.current && exportPng(topic, dataUrl.current);
}}
/>,
],
});
}
return (
<div className={styles.chat} key={session.id}>
@ -400,22 +433,14 @@ 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);
}}
/>
)}
<IconButton
icon={<ExportImage />}
bordered
title={Locale.Chat.Actions.ExportImage}
onClick={() => {
exportImage(session.topic);
}}
/>
</div>
</div>
</div>
@ -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 = () => {

View File

@ -0,0 +1,7 @@
.image-wrap {
height: 100%;
> img {
max-width: 100%;
height: auto;
}
}

View File

@ -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 ? (
<LoadingIcon />
) : (
<div className={styles["image-wrap"]}>
<img src={imageUrl} alt="" />
</div>
)}
</>
);
}

View File

@ -1 +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>
<?xml version="1.0" encoding="UTF-8"?><svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M44 24C44 22.8954 43.1046 22 42 22C40.8954 22 40 22.8954 40 24H44ZM24 8C25.1046 8 26 7.10457 26 6C26 4.89543 25.1046 4 24 4V8ZM39 40H9V44H39V40ZM8 39V9H4V39H8ZM40 24V39H44V24H40ZM9 8H24V4H9V8ZM9 40C8.44772 40 8 39.5523 8 39H4C4 41.7614 6.23857 44 9 44V40ZM39 44C41.7614 44 44 41.7614 44 39H40C40 39.5523 39.5523 40 39 40V44ZM8 9C8 8.44772 8.44771 8 9 8V4C6.23858 4 4 6.23857 4 9H8Z" fill="#333"/><path d="M6 35L16.6931 25.198C17.4389 24.5143 18.5779 24.4953 19.3461 25.1538L32 36" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 31L32.7735 26.2265C33.4772 25.5228 34.5914 25.4436 35.3877 26.0408L42 31" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M32 13L37 18L42 13" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M37 6L37 18" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB