Skip to content
This repository was archived by the owner on Jul 28, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
01638e2
Remove unused package
1orZero Aug 5, 2023
e19d9a6
Add Image Gallery draft
1orZero Aug 5, 2023
33f99af
Update buttons
1orZero Aug 8, 2023
e667c3f
Update carousel style
1orZero Aug 8, 2023
4531495
Add black cover to non-active thumbnail
1orZero Aug 8, 2023
8cdb3c8
Update Dot style
1orZero Aug 9, 2023
261be52
Add a separate component for the animated slide
1orZero Aug 9, 2023
641104c
Fix animation always goes to the same direction & remove comments
1orZero Aug 9, 2023
02b6cac
Fix thumbnail on click has invalid direction
1orZero Aug 9, 2023
716b105
improve component performance
1orZero Aug 9, 2023
7724667
Remove the testing code
1orZero Aug 9, 2023
da78722
Code Refactor
1orZero Aug 9, 2023
0dcf9cd
Fix The carousel component is being re-rendered multiple times
1orZero Aug 9, 2023
87df75c
Add RearrangeList component
1orZero Aug 9, 2023
72878bf
Test image tag
1orZero Aug 9, 2023
bb66282
Revert "Test image tag"
1orZero Aug 9, 2023
49b10e4
Switch over to Azure for GPT-4
thejackwu Aug 6, 2023
20e2e2c
First attempt in PWA
thejackwu Jul 13, 2023
c2a4d87
Update background color
thejackwu Jul 13, 2023
0c95a26
Update icon assets
thejackwu Aug 2, 2023
6aa160e
Change display value
thejackwu Aug 2, 2023
fa4eb11
Update theme color
thejackwu Aug 2, 2023
222b0f9
Test without background color
thejackwu Aug 2, 2023
f9f4967
Testing with other background color
thejackwu Aug 2, 2023
cbcd5ae
Update icon assets
thejackwu Aug 2, 2023
38aa0ce
Update background color to match theme color
thejackwu Aug 2, 2023
263acb5
Update icon assets
thejackwu Aug 2, 2023
92d96d7
Update icons
1orZero Aug 3, 2023
dba2ef9
Add splash screens
thejackwu Aug 3, 2023
f1bd3e6
Disable overscroll in iOS
thejackwu Aug 4, 2023
1807a7c
make page not draggable on ios safari
1orZero Aug 4, 2023
58a5132
Move viewport meta tag from _document to home.tsx
thejackwu Aug 4, 2023
977a0c9
[iOS] Fix after rotating from landscape will make the screen cut in half
1orZero Aug 8, 2023
cc802ec
Update icons
1orZero Aug 8, 2023
cd41adf
Add orientationBlock component
1orZero Aug 8, 2023
d94863c
how orientation blocker on mobile only
1orZero Aug 8, 2023
e00e0c8
Add translation
1orZero Aug 8, 2023
e171b45
add test trigger
1orZero Aug 8, 2023
42cd8e3
add test trigger
1orZero Aug 8, 2023
c3bd3da
Add test trigger
1orZero Aug 8, 2023
601a343
use orientationchange event instead of resize to detect the landscape
1orZero Aug 8, 2023
223d708
Merge branch 'chat-everywhere' into derek/20230805-image-gallery
1orZero Aug 11, 2023
ca4c0eb
Dont show image gallery if no ai image
1orZero Aug 14, 2023
cdddea1
Show Gallery thumbnail & arrow button only when length > 1
1orZero Aug 14, 2023
f4a378e
Merge branch 'chat-everywhere' into derek/20230805-image-gallery
1orZero Aug 17, 2023
325cf28
Improve performance & Make the latest ai image as the default selection
1orZero Aug 17, 2023
7e5da5d
Upgrade to use CSE and gpt-4-32k for online mode
thejackwu Aug 7, 2023
d54d914
Upgrade notion npm package
thejackwu Aug 7, 2023
358ec1c
Fix type error
1orZero Aug 8, 2023
3070cc8
remove console.log
1orZero Aug 8, 2023
1efb924
Fix type error, fix package conflict
1orZero Aug 8, 2023
876ca36
add footnote syntax rules
1orZero Aug 9, 2023
61f2084
Update a tag target if it's local link
thejackwu Aug 13, 2023
a092e79
Refactor online mode codebase and improve reliability
thejackwu Aug 16, 2023
0c7a995
Fix stream object being destory during the session
thejackwu Aug 18, 2023
2bd893d
Add new chat conversation to the top of the list instead of bottom
1orZero Aug 14, 2023
51822cf
Shorten message history in online mode
thejackwu Aug 18, 2023
7c866bd
Clean up
thejackwu Aug 18, 2023
4524648
Merge branch 'chat-everywhere' into derek/20230805-image-gallery
thejackwu Sep 21, 2023
4eaddbf
Fix merge conflict
thejackwu Sep 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 19 additions & 35 deletions components/Chat/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useTranslation } from 'next-i18next';
import { event } from 'nextjs-google-analytics';

import { updateConversation } from '@/utils/app/conversation';
import { getMjImageTags } from '@/utils/app/mjImage';
import { getPluginIcon } from '@/utils/app/ui';
import { modifyParagraphs } from '@/utils/data/onlineOutputModifier';

Expand All @@ -28,8 +29,7 @@ import { PluginID } from '@/types/plugin';

import HomeContext from '@/pages/api/home/home.context';

import { ImageGenerationComponent } from './components/ImageGenerationComponent';
import MjImageComponent from './components/MjImageComponent';
import MemoizedImageGallery from './components/ImageGallery/MemoizedImageGallery';
import TokenCounter from './components/TokenCounter';

import { CodeBlock } from '../Markdown/CodeBlock';
Expand Down Expand Up @@ -190,22 +190,11 @@ export const ChatMessage: FC<Props> = memo(
const ImgComponent = useMemo(() => {
const Component = ({
src,
title,
alt,
node,
}: React.DetailedHTMLProps<
React.ImgHTMLAttributes<HTMLImageElement> & { node?: any },
HTMLImageElement
>) => {
const aiImageButtons =
node?.properties?.dataAiImageButtons &&
(node?.properties?.dataAiImageButtons).split(',');
const aiImagePrompt =
node?.properties?.dataAiImagePrompt &&
(node?.properties?.dataAiImagePrompt).split(',');
const aiImageButtonMessageId =
node?.properties?.dataAiImageButtonMessageId;

const isValidUrl = (url: string) => {
try {
new URL(url);
Expand All @@ -218,36 +207,19 @@ export const ChatMessage: FC<Props> = memo(
if (!src) return <></>;
if (!isValidUrl(src)) return <b>{`{InValid IMAGE URL}`}</b>;

if (message.pluginId !== PluginID.IMAGE_GEN) {
if (message.pluginId === PluginID.IMAGE_GEN) {
// The IMAGE_GEN plugin will render by Image Gallery
return <></>;
} else {
return (
// eslint-disable-next-line @next/next/no-img-element
<img src={src} alt="" className="w-full" />
);
}
if (aiImageButtons) {
return (
// eslint-disable-next-line @next/next/no-img-element
<MjImageComponent
src={src}
buttons={aiImageButtons}
buttonMessageId={aiImageButtonMessageId}
prompt={aiImagePrompt}
/>
);
}

return (
<ImageGenerationComponent
src={src}
title={title}
messageIndex={messageIndex}
generationPrompt={alt || ''}
/>
);
};
Component.displayName = 'ImgComponent';
return Component;
}, [message.pluginId, messageIndex]);
}, [message.pluginId]);

const CodeComponent = useMemo(() => {
const Component: React.FC<any> = ({
Expand Down Expand Up @@ -279,6 +251,15 @@ export const ChatMessage: FC<Props> = memo(
() => modifyParagraphs(message.content),
[message.content],
);
const aiImageListString = useMemo(() => {
return JSON.stringify(getMjImageTags(formattedMessage));
}, [formattedMessage]);

// This is a workaround for the issue that the Carousel will be rendered multiple times
const aiImageList = useMemo(() => {
return JSON.parse(aiImageListString);
}, [aiImageListString]);

return (
<div
className={`group px-4 ${
Expand Down Expand Up @@ -408,6 +389,9 @@ export const ChatMessage: FC<Props> = memo(
</div>
) : (
<div className="flex w-full flex-col md:justify-between">
{aiImageList.length > 0 && (
<MemoizedImageGallery aiImageList={aiImageList} />
)}
<div className="flex flex-row justify-between">
<MemoizedReactMarkdown
className="prose dark:prose-invert min-w-full"
Expand Down
105 changes: 105 additions & 0 deletions components/Chat/components/Carousel/AnimatedSlide.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useEffect, useState } from 'react';

type AnimatedSlideProps = {
direction: 'left' | 'right' | null;
children: React.ReactNode;
};

const AnimatedSlide: React.FC<AnimatedSlideProps> = ({
direction,
children,
}) => {
const [animationClass, setAnimationClass] = useState('');
const [previousChildren, setPreviousChildren] =
useState<React.ReactNode>(children);
const [mounted, setMounted] = useState(false);

useEffect(() => {
if (mounted) {
if (direction === 'left') {
setAnimationClass('slide-out-right');
setTimeout(() => {
setPreviousChildren(children);
setAnimationClass('slide-in-left');
}, 500);
} else if (direction === 'right') {
setAnimationClass('slide-out-left');
setTimeout(() => {
setPreviousChildren(children);
setAnimationClass('slide-in-right');
}, 500);
}
} else {
setMounted(true);
}
}, [direction, children, mounted]);

return (
<div className={`${animationClass}`}>
<style jsx>{`
.slide-in-right {
animation: slide-in-right 0.5s forwards;
}

.slide-out-left {
animation: slide-out-left 0.5s forwards;
}

.slide-in-left {
animation: slide-in-left 0.5s forwards;
}

.slide-out-right {
animation: slide-out-right 0.5s forwards;
}

@keyframes slide-in-right {
0% {
transform: translateX(100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}

@keyframes slide-out-left {
0% {
transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(-100%);
opacity: 0;
}
}

@keyframes slide-in-left {
0% {
transform: translateX(-100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}

@keyframes slide-out-right {
0% {
transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(100%);
opacity: 0;
}
}
`}</style>
{previousChildren}
</div>
);
};

export default AnimatedSlide;
110 changes: 110 additions & 0 deletions components/Chat/components/Carousel/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { IconCaretLeft, IconCaretRight } from '@tabler/icons-react';
import React, {
ReactNode,
useContext,
useEffect,
useMemo,
useState,
} from 'react';

import HomeContext from '@/pages/api/home/home.context';

import AnimatedSlide from './AnimatedSlide';
import CarouselThumbnails from './CarouselThumbnails';

type CarouselProps = {
children: ReactNode[];
};

const Carousel: React.FC<CarouselProps> = ({ children }) => {
const [currentIndex, setCurrentIndex] = useState(0);
const [direction, setDirection] = useState<'left' | 'right' | null>(null);

const {
state: { selectedConversation },
} = useContext(HomeContext);
// reset index on conversation change
const selectedConversationId = useMemo(
() => selectedConversation?.id,
[selectedConversation],
);
useEffect(() => {
setCurrentIndex(0);
}, [selectedConversationId]);

const nextSlide = () => {
setDirection('right');
const newIndex = currentIndex + 1;
setCurrentIndex(newIndex >= children.length ? 0 : newIndex);
};

const prevSlide = () => {
setDirection('left');
const newIndex = currentIndex - 1;
setCurrentIndex(newIndex < 0 ? children.length - 1 : newIndex);
};

const currentIndexChildren = useMemo(() => {
return children[currentIndex];
}, [children, currentIndex]);

const handleThumbnailClick = (index: number) => (event: React.MouseEvent) => {
setDirection(index > currentIndex ? 'right' : 'left');
event.stopPropagation();
setCurrentIndex(index);
};
return (
<div className="flex flex-col items-center w-full max-w-[80dvw] mobile:max-w-[70dvw] gap-2">
<div className="relative flex justify-between w-full">
<AnimatedSlide direction={direction}>
{currentIndexChildren}
</AnimatedSlide>

{/* Main Display */}
{children.length > 1 && (
<>
<button
onClick={prevSlide}
className="absolute left-[-4rem] mobile:left-[-3rem] top-[50%] translate-y-[-50%] p-4 cursor-pointer text-white"
>
<IconCaretLeft height={`20dvw`} />
</button>
<button
onClick={nextSlide}
className="absolute right-[-4rem] mobile:right-[-3rem] top-[50%] translate-y-[-50%] p-4 cursor-pointer text-white"
>
<IconCaretRight height={`20dvw`} />
</button>
</>
)}
</div>

{/* Dots */}
{children.length > 1 && (
<div className="flex justify-center space-x-2 my-2">
{children.map((_, index) => (
<span
key={index}
className={`h-2 w-2 rounded-full ${
currentIndex === index
? 'bg-gray-800 dark:bg-white'
: 'bg-gray-300 dark:bg-gray-500'
}`}
/>
))}
</div>
)}
{/* Thumbnails */}
{children.length > 1 && (
<CarouselThumbnails
currentIndex={currentIndex}
handleThumbnailClick={handleThumbnailClick}
>
{children}
</CarouselThumbnails>
)}
</div>
);
};

export default Carousel;
53 changes: 53 additions & 0 deletions components/Chat/components/Carousel/CarouselThumbnails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { ReactElement, ReactNode, useMemo } from 'react';

interface CarouselThumbnailsProps {
children: ReactNode[];
currentIndex: number;
handleThumbnailClick: (index: number) => (event: React.MouseEvent) => void;
}

const CarouselThumbnails: React.FC<CarouselThumbnailsProps> = ({
children,
currentIndex,
handleThumbnailClick,
}) => {
const rearrangedChildren = useMemo(() => {
// if children length is less or equal then 2, no need to rearrange
if (children.length <= 2) {
return children.map((child, index) => ({
child,
originalIndex: index,
}));
}
const halfLength = Math.floor(children.length / 2);
const start = (currentIndex + 1 + halfLength) % children.length;
const rearranged = [...children.slice(start), ...children.slice(0, start)];
return rearranged.map((child, index) => ({
child,
originalIndex: (start + index) % children.length,
}));
}, [children, currentIndex]);

return (
<div className="flex overflow-x-auto gap-2">
{rearrangedChildren.map(({ child, originalIndex }, index) => (
<div
key={index}
onClick={handleThumbnailClick(originalIndex)}
className="relative w-16 h-16 cursor-pointer"
>
{React.cloneElement(child as ReactElement<any>, {
className: 'child-no-margin child-no-click',
})}
<div
className={`w-full h-full absolute top-0 left-0 ${
currentIndex === originalIndex ? 'bg-transparent' : 'bg-black/70 '
}`}
></div>
</div>
))}
</div>
);
};

export default CarouselThumbnails;
6 changes: 6 additions & 0 deletions components/Chat/components/Carousel/MemoizedCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { memo } from 'react';

import Carousel from './Carousel';

const MemoizedCarousel = memo(Carousel);
export default MemoizedCarousel;
5 changes: 5 additions & 0 deletions components/Chat/components/CustomImageComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default function CustomImageComponent() {
return <div>CustomImageComponent</div>;
}
Loading