Skip to content

Commit

Permalink
Rework chat layout UI (#2741)
Browse files Browse the repository at this point in the history
The `QueueInfo` is removed for now. I will add it back in follow up PR.
Most of the change are UI code, there is no logic here, it safe to merge
Desktop 
<details>
  <summary>Light mode</summary>
  

![image](https://user-images.githubusercontent.com/33456881/233029668-d04702cd-9f3b-4315-a4e2-8d99d78c68aa.png)
</details>
<details>
  <summary>Dark mode</summary>


![image](https://user-images.githubusercontent.com/33456881/233029734-eaf0b350-43c8-487f-a1a4-13972280bb57.png)
</details>

Mobile
<details>
  <summary>Light mode</summary>
  

![image](https://user-images.githubusercontent.com/33456881/233031042-45d5fd2c-ca70-48c4-b41a-abb6c70ac502.png)</details>
<details>
  <summary>Dark mode</summary>


![image](https://user-images.githubusercontent.com/33456881/233031086-508eabc4-1910-4d81-b079-0a5720d7e67d.png)</details>

---------

Co-authored-by: AbdBarho <ka70911@gmail.com>
  • Loading branch information
notmd and AbdBarho committed Apr 21, 2023
1 parent aca65a3 commit acc88e3
Show file tree
Hide file tree
Showing 24 changed files with 432 additions and 284 deletions.
3 changes: 2 additions & 1 deletion website/public/locales/en/chat.json
Expand Up @@ -23,5 +23,6 @@
"top_p": "Top P",
"typical_p": "Typical P",
"you_are_logged_in": "You are logged in to the chat service",
"your_chats": "Your Chats"
"your_chats": "Your Chats",
"input_placeholder": "Ask the assistant anything"
}
38 changes: 38 additions & 0 deletions website/src/components/Chat/ChatConfigDesktop.tsx
@@ -0,0 +1,38 @@
import { Box } from "@chakra-ui/react";
import { useTranslation } from "next-i18next";
import { memo } from "react";
import SimpleBar from "simplebar-react";

import { ChatConfigForm } from "./ChatConfigForm";

export const ChatConfigDesktop = memo(function ChatConfigDesktop() {
const { t } = useTranslation("chat");
return (
<Box
py="4"
pl="4"
gap="1"
height="full"
minH="0"
flexDirection="column"
w="270px"
display={{ base: "none", xl: "flex" }}
_dark={{
bg: "blackAlpha.400",
}}
>
<Box fontSize="xl" borderBottomWidth="1px" mb="4" pb="4">
{t("config_title")}
</Box>

<SimpleBar
style={{ maxHeight: "100%", height: "100%", minHeight: "0" }}
classNames={{
contentEl: "mr-4 flex flex-col overflow-y-auto items-center",
}}
>
<ChatConfigForm></ChatConfigForm>
</SimpleBar>
</Box>
);
});
@@ -1,14 +1,7 @@
import {
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerHeader,
DrawerOverlay,
Flex,
FormControl,
FormLabel,
IconButton,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
Expand All @@ -22,38 +15,14 @@ import {
Stack,
Switch,
Tooltip,
useDisclosure,
} from "@chakra-ui/react";
import { Settings } from "lucide-react";
import { useTranslation } from "next-i18next";
import { ChangeEvent, memo, useCallback, useState } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { ChatConfigFormData, SamplingParameters } from "src/types/Chat";

import { useChatContext } from "./ChatContext";
import { areParametersEqual } from "./WorkParameters";

export const ChatConfigDrawer = memo(function ChatConfigDrawer() {
const { isOpen, onOpen, onClose } = useDisclosure();

const { t } = useTranslation("chat");
return (
<>
<IconButton aria-label={t("config_title")} icon={<Settings />} onClick={onOpen} size="lg" borderRadius="xl" />
<Drawer placement="right" onClose={onClose} isOpen={isOpen}>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader borderBottomWidth="1px">{t("config_title")}</DrawerHeader>
<DrawerBody>
<ChatConfigForm />
</DrawerBody>
</DrawerContent>
</Drawer>
</>
);
});

const sliderItems: Readonly<
Array<{
key: keyof SamplingParameters;
Expand Down Expand Up @@ -112,7 +81,7 @@ const parameterLabel: Record<keyof SamplingParameters, string> = {
typical_p: "Typical P",
};

const ChatConfigForm = () => {
export const ChatConfigForm = memo(function ChatConfigForm() {
const { t } = useTranslation("chat");
const { modelInfos } = useChatContext();

Expand Down Expand Up @@ -178,7 +147,7 @@ const ChatConfigForm = () => {
))}
</Stack>
);
};
});

type NumberInputSliderProps = {
max?: number;
Expand Down Expand Up @@ -216,7 +185,9 @@ const ChatParameterField = memo(function ChatParameterField(props: NumberInputSl
<FormControl isDisabled={isDisabled}>
<Flex justifyContent="space-between" mb="2">
<FormLabel mb="0">
<Tooltip label={description}>{label}</Tooltip>
<Tooltip label={description} placement="left">
{label}
</Tooltip>
</FormLabel>
<Switch isChecked={showSlider} onChange={handleShowSliderChange}></Switch>
</Flex>
Expand Down
42 changes: 42 additions & 0 deletions website/src/components/Chat/ChatConfigMobile.tsx
@@ -0,0 +1,42 @@
import {
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerHeader,
DrawerOverlay,
useDisclosure,
} from "@chakra-ui/react";
import { Settings } from "lucide-react";
import { useTranslation } from "next-i18next";
import { memo } from "react";

import { ChatConfigForm } from "./ChatConfigForm";
import { ChatInputIconButton } from "./ChatInputIconButton";

export const ChatConfigDrawer = memo(function ChatConfigDrawer() {
const { isOpen, onOpen, onClose } = useDisclosure();

const { t } = useTranslation("chat");
return (
<>
<ChatInputIconButton
aria-label={t("config_title")}
icon={Settings}
onClick={onOpen}
borderRadius="xl"
display={{ base: "flex", xl: "none" }}
/>
<Drawer placement="right" onClose={onClose} isOpen={isOpen}>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader borderBottomWidth="1px">{t("config_title")}</DrawerHeader>
<DrawerBody>
<ChatConfigForm />
</DrawerBody>
</DrawerContent>
</Drawer>
</>
);
});
9 changes: 6 additions & 3 deletions website/src/components/Chat/ChatConfigSaver.tsx
Expand Up @@ -4,11 +4,14 @@ import { ChatConfigFormData } from "src/types/Chat";
import { setConfigCache } from "src/utils/chat";

export const ChatConfigSaver = () => {
const { watch } = useFormContext<ChatConfigFormData>();
const { watch, formState } = useFormContext<ChatConfigFormData>();
const config = watch();
useEffect(() => {
setConfigCache(config);
}, [config]);
// only update when form is changed
if (formState.isDirty) {
setConfigCache(config);
}
}, [config, formState.isDirty]);

return null;
};
48 changes: 35 additions & 13 deletions website/src/components/Chat/ChatConversation.tsx
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Flex, useBoolean, useToast } from "@chakra-ui/react";
import { Box, useBoolean, useToast } from "@chakra-ui/react";
import { memo, useCallback, useRef, useState } from "react";
import { UseFormGetValues } from "react-hook-form";
import SimpleBar from "simplebar-react";
import { useMessageVote } from "src/hooks/chat/useMessageVote";
import { get, post } from "src/lib/api";
import { handleChatEventStream, QueueInfo } from "src/lib/chat_stream";
Expand Down Expand Up @@ -200,18 +201,39 @@ export const ChatConversation = memo(function ChatConversation({ chatId, getConf
);

return (
<Flex flexDir="column" gap={4}>
<ChatConversationTree
messages={messages}
onVote={handleOnVote}
onRetry={handleOnRetry}
isSending={isSending}
retryingParentId={retryingParentId}
onEditPromtp={handleEditPrompt}
></ChatConversationTree>
{isSending && streamedResponse && <PendingMessageEntry isAssistant content={streamedResponse} />}

<Box
pt="4"
px="2"
gap="1"
height="full"
minH="0"
display="flex"
flexDirection="column"
flexGrow="1"
_light={{
bg: "gray.50",
}}
_dark={{
bg: "blackAlpha.300",
}}
>
<SimpleBar
style={{ padding: "4px 0", maxHeight: "100%", height: "100%", minHeight: "0" }}
classNames={{
contentEl: "space-y-4 mx-4 flex flex-col overflow-y-auto items-center",
}}
>
<ChatConversationTree
messages={messages}
onVote={handleOnVote}
onRetry={handleOnRetry}
isSending={isSending}
retryingParentId={retryingParentId}
onEditPromtp={handleEditPrompt}
></ChatConversationTree>
{isSending && streamedResponse && <PendingMessageEntry isAssistant content={streamedResponse} />}
</SimpleBar>
<ChatForm ref={inputRef} isSending={isSending} onSubmit={sendPrompterMessage} queueInfo={queueInfo}></ChatForm>
</Flex>
</Box>
);
});
73 changes: 43 additions & 30 deletions website/src/components/Chat/ChatForm.tsx
@@ -1,11 +1,12 @@
import { Button, Grid, Textarea } from "@chakra-ui/react";
import { Box, CircularProgress, Flex, Textarea } from "@chakra-ui/react";
import { Send } from "lucide-react";
import { useTranslation } from "next-i18next";
import { forwardRef, KeyboardEvent, SyntheticEvent, useCallback } from "react";
import TextareaAutosize from "react-textarea-autosize";
import { QueueInfo } from "src/lib/chat_stream";

import { ChatConfigDrawer } from "./ChatConfigDrawer";
import { QueueInfoMessage } from "./QueueInfoMessage";
import { ChatConfigDrawer } from "./ChatConfigMobile";
import { ChatInputIconButton } from "./ChatInputIconButton";

type ChatFormProps = {
isSending: boolean;
Expand All @@ -16,7 +17,7 @@ type ChatFormProps = {
// eslint-disable-next-line react/display-name
export const ChatForm = forwardRef<HTMLTextAreaElement, ChatFormProps>((props, ref) => {
const { isSending, onSubmit: onSubmit, queueInfo } = props;
const { t } = useTranslation("common");
const { t } = useTranslation("chat");
const handleSubmit = useCallback(
(e: SyntheticEvent) => {
e.preventDefault();
Expand All @@ -34,31 +35,43 @@ export const ChatForm = forwardRef<HTMLTextAreaElement, ChatFormProps>((props, r
[onSubmit]
);
return (
<form onSubmit={handleSubmit}>
<Textarea
as={TextareaAutosize}
ref={ref}
bg="gray.100"
borderRadius="xl"
onKeyDown={handleKeydown}
_dark={{
bg: "gray.800",
}}
/>
<Grid gridTemplateColumns="1fr 50px" gap={2} mt="4">
<Button
type="submit"
onClick={handleSubmit}
isLoading={isSending}
overflow="hidden"
spinner={queueInfo ? <QueueInfoMessage info={queueInfo} /> : undefined}
size="lg"
borderRadius="xl"
>
{t("submit")}
</Button>
<ChatConfigDrawer />
</Grid>
</form>
<Box
as="form"
maxWidth={{ base: "3xl", "2xl": "4xl" }}
onSubmit={handleSubmit}
className="pt-2 pb-4 w-full mx-auto"
>
<div className="relative">
<Textarea
as={TextareaAutosize}
ref={ref}
bg="gray.200"
borderRadius="md"
rows={1}
maxRows={10}
py={{ base: 2, md: 3 }}
onKeyDown={handleKeydown}
placeholder={t("input_placeholder")}
_dark={{
bg: "whiteAlpha.100",
}}
border="0"
outline="none"
_focus={{
outline: "none !important",
boxShadow: "none",
}}
style={{ resize: "none" }}
/>
<Flex position="absolute" zIndex="10" className="ltr:right-0 rtl:left-0 top-0 h-full items-center px-4" gap="2">
<ChatConfigDrawer />
{isSending ? (
<CircularProgress isIndeterminate size="20px" />
) : (
<ChatInputIconButton icon={Send} onClick={handleSubmit}></ChatInputIconButton>
)}
</Flex>
</div>
</Box>
);
});
19 changes: 19 additions & 0 deletions website/src/components/Chat/ChatInputIconButton.tsx
@@ -0,0 +1,19 @@
import { Box, BoxProps } from "@chakra-ui/react";
import { LucideIcon } from "lucide-react";

export const ChatInputIconButton = ({ icon: Icon, ...props }: { icon: LucideIcon } & BoxProps) => (
<Box
as="button"
color="gray.500"
_light={{
_hover: { color: "gray.700" },
}}
_dark={{
_hover: { color: "white" },
}}
{...props}
type="button"
>
<Icon size="20px"></Icon>
</Box>
);

0 comments on commit acc88e3

Please sign in to comment.