Skip to content

Commit

Permalink
Refactor chat (#81)
Browse files Browse the repository at this point in the history
* Add `code blocks` to markdown

* Refactor messages

* Add autofocus to textarea
  • Loading branch information
homanp authored Apr 24, 2023
1 parent 9c4f800 commit dfc73a1
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 123 deletions.
1 change: 1 addition & 0 deletions app/api/v1/chatbots/[chatbotId]/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export async function POST(request, { params }) {
: undefined;

const handleNewToken = async (token) => {
const match = /\r|\n/.exec(token);
await writer.ready;
await writer.write(encoder.encode(`data: ${token}\n\n`));
};
Expand Down
5 changes: 3 additions & 2 deletions components/chat/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default function Chat({ id, ...properties }) {
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState();
const [isSendingMessage, setIsSendingMessage] = useState();
const decoder = new TextDecoder();

const onSubmit = useCallback(
async (values) => {
Expand All @@ -35,8 +36,8 @@ export default function Chat({ id, ...properties }) {
message: values,
}),
async onmessage(event) {
if (event.data !== "" && event.data !== "CLOSE") {
message += event.data;
if (event.data !== "CLOSE") {
message += event.data === "" ? `${event.data} \n` : event.data;
setNewMessage(message);
}

Expand Down
15 changes: 14 additions & 1 deletion components/chat/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,33 @@ export default function ChatInput({ isLoading, onSubmit, ...properties }) {
};
}, []);

useEffect(() => {
const ref = textareaReference?.current;

if (!isLoading) {
ref.focus();
}
}, [isLoading]);

return (
<Box {...properties} bgGradient={backgroundGradient}>
<Container alignSelf="center" maxWidth="4xl">
<HStack
backgroundColor={backgroundColor}
borderWidth="1px"
padding={4}
paddingY={2}
paddingLeft={4}
paddingRight={2}
borderRadius="md"
alignItems="center"
>
<Textarea
ref={textareaReference}
isDisabled={isLoading}
autoFocus={!isLoading && true}
variant="unstyled"
value={message}
fontWeight={500}
placeholder="Send a message"
onKeyDown={handleKeyDown}
backgroundColor="transparent"
Expand Down
100 changes: 100 additions & 0 deletions components/chat/message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useRef } from "react";
import {
Avatar,
Box,
Code,
HStack,
Icon,
IconButton,
Stack,
Text,
useColorModeValue,
} from "@chakra-ui/react";
import remarkGfm from "remark-gfm";
import PropTypes from "prop-types";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { BeatLoader } from "react-spinners";
import { dracula } from "react-syntax-highlighter/dist/esm/styles/prism";
import { TbCopy } from "react-icons/tb";
import { MemoizedReactMarkdown } from "@/lib/markdown";

export default function Message({ agent, message, isLastMessage }) {
const loaderColor = useColorModeValue("gray.100", "white");
const lastMessageReference = useRef();
const unevenBackgroundColor = useColorModeValue("gray.100", "gray.700");

return (
<Box
ref={isLastMessage ? lastMessageReference : undefined}
backgroundColor={agent && unevenBackgroundColor}
padding={4}
>
<HStack spacing={6} maxWidth="4xl" marginX="auto" alignItems="center">
<Avatar
src={agent ? "/chatbot.png" : "/user.png"}
size="xs"
alignSelf="flex-start"
/>
<Stack spacing={4} fontSize="sm" flex={1}>
{message ? (
<MemoizedReactMarkdown
components={{
code({ node, inline, className, children, ...props }) {
const value = String(children).replace(/\n$/, "");
const match = /language-(\w+)/.exec(className || "");

const handleCopyCode = () => {
navigator.clipboard.writeText(value);
};

return !inline ? (
<Box position="relative">
<HStack position="absolute" top={2} right={2}>
<Text fontSize="xs">{match && match[1]}</Text>
<IconButton
size="sm"
icon={<Icon as={TbCopy} fontSize="lg" />}
onClick={() => handleCopyCode()}
/>
</HStack>
<SyntaxHighlighter
customStyle={{
fontSize: "12px",
}}
codeTagProps={{
style: {
lineHeight: "inherit",
fontSize: "inherit",
},
}}
style={dracula}
language={(match && match[1]) || ""}
>
{value}
</SyntaxHighlighter>
</Box>
) : (
<Code fontSize="sm" className={className} {...props}>
{children}
</Code>
);
},
}}
remarkPlugins={[remarkGfm]}
>
{message}
</MemoizedReactMarkdown>
) : (
<BeatLoader color={loaderColor} size={8} />
)}
</Stack>
</HStack>
</Box>
);
}

Message.propTypes = {
agent: PropTypes.string,
message: PropTypes.string,
isLastMessage: PropTypes.bool,
};
125 changes: 12 additions & 113 deletions components/chat/output.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,14 @@
import React, { useEffect, useRef } from "react";
import {
Avatar,
Box,
HStack,
Icon,
IconButton,
Stack,
useColorModeValue,
} from "@chakra-ui/react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Box, Stack } from "@chakra-ui/react";
import PropTypes from "prop-types";

import SyntaxHighlighter from "react-syntax-highlighter";
import { dracula } from "react-syntax-highlighter/dist/esm/styles/prism";
import { TbCopy } from "react-icons/tb";
import { BeatLoader } from "react-spinners";
import Message from "./message";

export default function ChatOuput({
messages,
newMessage,
isLoading,
...properties
}) {
const loaderColor = useColorModeValue("gray.100", "white");
const unevenBackgroundColor = useColorModeValue("gray.100", "gray.600");
const lastMessageReference = useRef();

useEffect(() => {
Expand All @@ -33,107 +17,22 @@ export default function ChatOuput({
}
}, [messages]);

const showAIMessage = isLoading || newMessage;

return (
<Stack flex={1} maxWidth="100%" {...properties}>
<Stack spacing={0}>
{messages.map(({ agent, data: { response } }, index) => (
<Box
ref={
index + 1 === messages.length ? lastMessageReference : undefined
}
padding={4}
<Message
key={index}
backgroundColor={index % 2 !== 0 && unevenBackgroundColor}
>
<HStack
spacing={6}
maxWidth="4xl"
marginX="auto"
alignItems="flex-start"
>
<Avatar src={agent ? "/chatbot.png" : "/user.png"} size="xs" />
<Stack spacing={4} fontSize="sm">
<ReactMarkdown
components={{
pre: (pre) => {
const codeChunk = pre.node.children[0].children[0].value;

const handleCopyCode = () => {
navigator.clipboard.writeText(codeChunk);
};

return (
<Box position="relative">
<IconButton
position="absolute"
top={4}
right={2}
size="sm"
icon={<Icon as={TbCopy} fontSize="lg" />}
onClick={() => handleCopyCode()}
/>
<SyntaxHighlighter style={dracula}>
{pre.children[0].props.children[0]}
</SyntaxHighlighter>
</Box>
);
},
}}
remarkPlugins={[remarkGfm]}
>
{response}
</ReactMarkdown>
</Stack>
</HStack>
</Box>
agent={agent}
message={response}
isLastMessage={index + 1 === messages.length}
/>
))}
{isLoading && (
<Box padding={4} backgroundColor={unevenBackgroundColor}>
<HStack
spacing={6}
maxWidth="4xl"
marginX="auto"
alignItems="flex-start"
>
<Avatar src="/chatbot.png" size="xs" />
<Stack spacing={4} fontSize="sm">
{newMessage ? (
<ReactMarkdown
components={{
pre: (pre) => {
const codeChunk =
pre.node.children[0].children[0].value;

const handleCopyCode = () => {
navigator.clipboard.writeText(codeChunk);
};

return (
<Box position="relative">
<IconButton
position="absolute"
top={4}
right={2}
size="sm"
icon={<Icon as={TbCopy} fontSize="lg" />}
onClick={() => handleCopyCode()}
/>
<SyntaxHighlighter style={dracula}>
{pre.children[0].props.children[0]}
</SyntaxHighlighter>
</Box>
);
},
}}
remarkPlugins={[remarkGfm]}
>
{newMessage}
</ReactMarkdown>
) : (
<BeatLoader color={loaderColor} size={8} />
)}
</Stack>
</HStack>
{showAIMessage && (
<Box ref={lastMessageReference}>
<Message agent="ai" message={newMessage} isLastMessage={true} />
</Box>
)}
</Stack>
Expand Down
2 changes: 1 addition & 1 deletion lib/chain.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChatOpenAI } from "langchain/chat_models";
import { ChatOpenAI } from "langchain/chat_models/openai";
import { ConversationChain } from "langchain/chains";
import { CallbackManager } from "langchain/callbacks";
import { BufferMemory, ChatMessageHistory } from "langchain/memory";
Expand Down
7 changes: 7 additions & 0 deletions lib/markdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { memo } from "react";
import ReactMarkdown from "react-markdown";

export const MemoizedReactMarkdown = memo(
ReactMarkdown,
(prevProps, nextProps) => prevProps.children === nextProps.children
);
2 changes: 2 additions & 0 deletions lib/prisma.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export const prismaClient = globalPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== "production") {
globalPrisma.prisma = prismaClient;
}

if (process.env.NODE_ENV !== "production") globalPrisma.prisma = prismaClient;
15 changes: 9 additions & 6 deletions lib/prompt-template.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
export const DEFAULT_PROMPT_TEMPLATE = `
Assistant is a large language model trained by OpenAI.
Assistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics.
As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.
Assistant is constantly learning and improving, and its capabilities are constantly evolving.
It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions.
Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.
Overall, Assistant is a powerful tool that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics.
Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.
Assistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.
Assistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.
Overall, Assistant is a powerful tool that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.
Make sure you answer in markdown and include line breaks in the output.
{history}
Human: {message}
Assitant:
Assitant answer in Markdown:
`;
Loading

1 comment on commit dfc73a1

@vercel
Copy link

@vercel vercel bot commented on dfc73a1 Apr 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

langchain-ui – ./

langchain-ui-git-main-homanp.vercel.app
langchain-ui-homanp.vercel.app
langchain-ui.vercel.app

Please sign in to comment.