Skip to content
This repository has been archived by the owner on Sep 15, 2024. It is now read-only.

Commit

Permalink
[Cherry Pick] [Head Repo] Add Image Viewer (#286)
Browse files Browse the repository at this point in the history
* feat: add image viewer

* feat: add image viewer

* add alt attribute to image viewer

* fix: prompt-toast obscuring the image viewer

---------

Co-authored-by: TheRamU <[email protected]>
  • Loading branch information
H0llyW00dzZ and TheRamU authored Feb 25, 2024
1 parent 548f666 commit 76b1155
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 84 deletions.
7 changes: 1 addition & 6 deletions app/client/platforms/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,6 @@ export class GeminiProApi implements LLMApi {
chatConfig.useMaxTokens,
);

// if (visionModel && messages.length > 1) {
// options.onError?.(new Error("Multiturn chat is not enabled for models/gemini-pro-vision"));
// }
const modelConfig = {
...useAppConfig.getState().modelConfig,
...useChatStore.getState().currentSession().mask.modelConfig,
Expand Down Expand Up @@ -217,9 +214,7 @@ export class GeminiProApi implements LLMApi {
const controller = new AbortController();
options.onController?.(controller);
try {
let googleChatPath = visionModel
? Google.VisionChatPath
: Google.ChatPath;
let googleChatPath = multimodal ? Google.VisionChatPath : Google.ChatPath;
let chatPath = this.path(googleChatPath);

// let baseUrl = accessStore.googleUrl;
Expand Down
63 changes: 62 additions & 1 deletion app/components/chat.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
}

.attach-image {
cursor: default;
cursor: pointer;
width: 64px;
height: 64px;
border: rgba($color: #888, $alpha: 0.2) 1px solid;
Expand Down Expand Up @@ -39,6 +39,12 @@
border-radius: 5px;
float: right;
background-color: var(--white);
opacity: 0.8;
transition: all ease 0.3s;
}

.delete-image:hover {
opacity: 1;
}
}

Expand Down Expand Up @@ -422,6 +428,10 @@
transition: all ease 0.3s;
}

.chat-message-item img {
cursor: pointer;
}

.chat-message-item-image {
width: 100%;
margin-top: 10px;
Expand Down Expand Up @@ -624,4 +634,55 @@
.chat-input-send {
bottom: 30px;
}
}

@keyframes slide-in {
from {
transform: translateY(10px);
opacity: 0;
}

to {
transform: translateY(0);
opacity: 1;
}
}

.image-box {
background-color: rgba($color: #000000, $alpha: 0.5);
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
z-index: 999;
}

.image-box>img {
animation: slide-in ease 0.3s;
width: 100%;
height: 100%;
object-fit: contain;
}

.image-box-close-button {
cursor: pointer;
box-sizing: border-box;
position: fixed;
top: 20px;
right: 20px;
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
border-radius: 10px;
border: rgba($color: #888, $alpha: 0.2) 1px solid;
background-color: var(--white);
transition: all ease 0.3s;
}

.image-box-close-button:hover {
border-color: var(--primary);
filter: brightness(0.9);
}
133 changes: 91 additions & 42 deletions app/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import ChatGptIcon from "../icons/chatgpt.png";
import EyeOnIcon from "../icons/eye.svg";
import EyeOffIcon from "../icons/eye-off.svg";
import { debounce, escapeRegExp } from "lodash";
import CloseIcon from "../icons/close.svg";

import {
ChatMessage,
Expand Down Expand Up @@ -838,12 +839,38 @@ function useDebouncedEffect(effect: () => void, deps: any[], delay: number) {

export function DeleteImageButton(props: { deleteImage: () => void }) {
return (
<div className={styles["delete-image"]} onClick={props.deleteImage}>
<div
className={styles["delete-image"]}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
props.deleteImage();
}}
>
<DeleteIcon />
</div>
);
}

export function ImageBox(props: {
showImageBox: boolean;
data: { src: string; alt: string };
closeImageBox: () => void;
}) {
return (
<div
className={styles["image-box"]}
style={{ display: props.showImageBox ? "block" : "none" }}
onClick={props.closeImageBox}
>
<img src={props.data.src} alt={props.data.alt} />
<div className={styles["image-box-close-button"]}>
<CloseIcon />
</div>
</div>
);
}

function _Chat() {
type RenderMessage = ChatMessage & { preview?: boolean };

Expand All @@ -866,6 +893,8 @@ function _Chat() {
const isApp = getClientConfig()?.isApp;
const [attachImages, setAttachImages] = useState<string[]>([]);
const [uploading, setUploading] = useState(false);
const [showImageBox, setShowImageBox] = useState(false);
const [imageBoxData, setImageBoxData] = useState({ src: "", alt: "" });

// prompt hints
const promptStore = usePromptStore();
Expand Down Expand Up @@ -1456,48 +1485,58 @@ function _Chat() {
}, [session.id]);

async function uploadImage() {
const images: string[] = [];
images.push(...attachImages);

images.push(
...(await new Promise<string[]>((res, rej) => {
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept =
"image/png, image/jpeg, image/webp, image/heic, image/heif";
fileInput.multiple = true;
fileInput.onchange = (event: any) => {
setUploading(true);
const files = event.target.files;
const imagesData: string[] = [];
for (let i = 0; i < files.length; i++) {
const file = event.target.files[i];
compressImage(file, 256 * 1024)
.then((dataUrl) => {
imagesData.push(dataUrl);
if (
imagesData.length === 3 ||
imagesData.length === files.length
) {
setUploading(false);
res(imagesData);
}
})
.catch((e) => {
const maxImages = 3;
if (uploading) return;
new Promise<string[]>((res, rej) => {
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept =
"image/png, image/jpeg, image/webp, image/heic, image/heif";
fileInput.multiple = true;
fileInput.onchange = (event: any) => {
setUploading(true);
const files = event.target.files;
const imagesData: string[] = [];
for (let i = 0; i < files.length; i++) {
const file = event.target.files[i];
compressImage(file, 256 * 1024)
.then((dataUrl) => {
imagesData.push(dataUrl);
if (
imagesData.length + attachImages.length >= maxImages ||
imagesData.length === files.length
) {
setUploading(false);
rej(e);
});
}
};
fileInput.click();
})),
);
res(imagesData);
}
})
.catch((e) => {
rej(e);
});
}
};
fileInput.click();
})
.then((imagesData) => {
const images: string[] = [];
images.push(...attachImages);
images.push(...imagesData);
setAttachImages(images);
const imagesLength = images.length;
if (imagesLength > maxImages) {
images.splice(maxImages, imagesLength - maxImages);
}
setAttachImages(images);
})
.catch(() => {
setUploading(false);
});
}

const imagesLength = images.length;
if (imagesLength > 3) {
images.splice(3, imagesLength - 3);
}
setAttachImages(images);
function openImageBox(src: string, alt?: string) {
alt = alt ?? "";
setImageBoxData({ src, alt });
setShowImageBox(true);
}

// this now better
Expand Down Expand Up @@ -1589,7 +1628,11 @@ function _Chat() {
setShowModal={setShowPromptModal}
/>
</div>

<ImageBox
showImageBox={showImageBox}
data={imageBoxData}
closeImageBox={() => setShowImageBox(false)}
></ImageBox>
<div
className={styles["chat-body"]}
ref={scrollRef}
Expand Down Expand Up @@ -1744,6 +1787,7 @@ function _Chat() {
fontSize={fontSize}
parentRef={scrollRef}
defaultShow={i >= messages.length - 6}
openImageBox={openImageBox}
/>
{getMessageImages(message).length == 1 && (
// this fix when uploading
Expand All @@ -1753,6 +1797,9 @@ function _Chat() {
className={styles["chat-message-item-image"]}
src={getMessageImages(message)[0]}
alt=""
onClick={() =>
openImageBox(getMessageImages(message)[0])
}
/>
)}
{getMessageImages(message).length > 1 && (
Expand All @@ -1774,6 +1821,7 @@ function _Chat() {
src={image}
alt=""
layout="responsive"
onClick={() => openImageBox(image)}
/>
);
})}
Expand Down Expand Up @@ -1852,6 +1900,7 @@ function _Chat() {
key={index}
className={styles["attach-image"]}
style={{ backgroundImage: `url("${image}")` }}
onClick={() => openImageBox(image)}
>
<div className={styles["attach-image-mask"]}>
<DeleteImageButton
Expand Down
18 changes: 18 additions & 0 deletions app/components/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,27 @@ export function Markdown(
fontSize?: number;
parentRef?: RefObject<HTMLDivElement>;
defaultShow?: boolean;
openImageBox?: (src: string, alt: string) => void;
} & React.DOMAttributes<HTMLDivElement>,
) {
const mdRef = useRef<HTMLDivElement>(null);
const { parentRef, openImageBox } = props;

useEffect(() => {
if (!parentRef || !openImageBox) {
return;
}
const imgs = mdRef.current?.querySelectorAll("img");
if (imgs) {
imgs.forEach((img) => {
const src = img.getAttribute("src");
const alt = img.getAttribute("alt") ?? "";
if (src) {
img.onclick = () => openImageBox(src, alt);
}
});
}
}, [mdRef, parentRef, openImageBox]);

return (
<div
Expand Down
Loading

0 comments on commit 76b1155

Please sign in to comment.