Skip to content
This repository was archived by the owner on Sep 29, 2025. It is now read-only.
Merged
9 changes: 5 additions & 4 deletions chat-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,15 @@
"@leafygreen-ui/text-input": "^12.1.18",
"@leafygreen-ui/toggle": "^10.0.15",
"@leafygreen-ui/typography": "^16.5.4",
"@lg-chat/chat-disclaimer": "^1.0.1",
"@lg-chat/fixed-chat-window": "^1.1.1",
"@lg-chat/message-prompts": "^1.0.1",
"@lg-chat/avatar": "^2.0.6",
"@lg-chat/chat-window": "^1.0.4",
"@lg-chat/input-bar": "^3.1.3",
"@lg-chat/message": "^2.0.8",
"@lg-chat/message-feed": "^2.0.7",
"@lg-chat/chat-disclaimer": "^2.0.0",
"@lg-chat/chat-window": "^1.0.4",
"@lg-chat/fixed-chat-window": "^1.1.1",
"@lg-chat/leafygreen-chat-provider": "^1.0.2",
"@lg-chat/message-prompts": "^1.0.2",
"@lg-chat/message-rating": "^1.1.3",
"@microsoft/fetch-event-source": "^2.0.1",
"buffer": "^6.0.3",
Expand Down
3 changes: 0 additions & 3 deletions chat-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import { Overline, Link } from "@leafygreen-ui/typography";
import Toggle from "@leafygreen-ui/toggle";
import { Chatbot as DevCenterChatbot } from "./DevCenterChatbot";

const prefersDarkMode = () =>
window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;

const prefersDarkMode = () =>
window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;

Expand Down
11 changes: 2 additions & 9 deletions chat-ui/src/Chatbot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,13 @@ const styles = {
box-sizing: border-box;
}
}`,
chatbot_input_area: css`
chatbot_input: css`
position: relative;
width: 100%;
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 1rem;
padding-left: 32px;
padding-right: 32px;
padding-top: 0.5rem;
padding-bottom: 1rem;
`,
chatbot_input_error_border: css`
> div {
Expand Down Expand Up @@ -535,10 +531,7 @@ function ChatbotModal({
})}
</MessageFeed>
) : null}
<div className={cx(
styles.chatbot_input,
styles.chatbot_input_area
)}>
<div className={cx(styles.chatbot_input, styles.chatbot_input_area)}>
{conversation.error ? (
<ErrorBanner darkMode={darkMode} message={conversation.error} />
) : null}
Expand Down
177 changes: 145 additions & 32 deletions chat-ui/src/DevCenterChatbot.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { css } from "@emotion/css";
import LeafyGreenProvider from "@leafygreen-ui/leafygreen-provider";
import LeafyGreenProvider, {
useDarkMode,
} from "@leafygreen-ui/leafygreen-provider";
import Modal from "@leafygreen-ui/modal";
import { palette } from "@leafygreen-ui/palette";
import { ParagraphSkeleton } from "@leafygreen-ui/skeleton-loader";
import { Body, Link } from "@leafygreen-ui/typography";
import { Avatar } from "@lg-chat/avatar";
import { DisclaimerText as LGDisclaimerText } from "@lg-chat/chat-disclaimer";
import { ChatWindow } from "@lg-chat/chat-window";
import { ChatTrigger } from "@lg-chat/fixed-chat-window";
import { InputBar } from "@lg-chat/input-bar";
import {
InputBar as LGInputBar,
InputBarProps as LGInputBarProps,
} from "@lg-chat/input-bar";
import { LeafyGreenChatProvider } from "@lg-chat/leafygreen-chat-provider";
import { Message as LGMessage, MessageSourceType } from "@lg-chat/message";
import { MessageFeed } from "@lg-chat/message-feed";
import { MessagePrompt, MessagePrompts } from "@lg-chat/message-prompts";
import { MessageRatingProps } from "@lg-chat/message-rating";
import { Fragment, useEffect, useState } from "react";
import { CharacterCount } from "./InputBar";
import { UserProvider } from "./UserProvider";
import { MessageData } from "./services/conversations";
import { Conversation, useConversation } from "./useConversation";
// import { DisclaimerText } from "@lg-chat/chat-disclaimer";
import { User, useUser } from "./useUser";

const styles = {
chat_trigger: css`
Expand Down Expand Up @@ -60,13 +70,10 @@ const styles = {
}
}
`,
disclaimer_text_container: css`
display: flex;
`,
disclaimer_text: css`
& p {
text-align: center;
}
text-align: center;
margin-top: 16px;
margin-bottom: 32px;
`,
chatbot_input: css`
padding-bottom: 1rem;
Expand Down Expand Up @@ -104,6 +111,26 @@ const styles = {
transition: 'opacity 300ms ease-in',
opacity: 1,
`,
chatbot_input_area: css`
position: relative;
width: 100%;
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 1rem;
padding-left: 32px;
padding-right: 32px;
padding-top: 0.5rem;
padding-bottom: 1rem;
`,
chatbot_input_error_border: css`
> div {
> div {
border-color: ${palette.red.base} !important;
border-width: 2px !important;
}
}
`,
};

export type ChatbotProps = {
Expand All @@ -112,14 +139,17 @@ export type ChatbotProps = {
shouldStream?: boolean;
suggestedPrompts?: string[];
startingMessage?: string;
user?: User;
};

export function Chatbot(props: ChatbotProps) {
const { darkMode, ...InnerChatbotProps } = props;
const { darkMode, user, ...InnerChatbotProps } = props;
// TODO: Use ConversationProvider
return (
<LeafyGreenProvider darkMode={props.darkMode}>
<InnerChatbot {...InnerChatbotProps} />
<UserProvider user={user}>
<InnerChatbot {...InnerChatbotProps} />
</UserProvider>
</LeafyGreenProvider>
);
}
Expand Down Expand Up @@ -203,17 +233,6 @@ export function InnerChatbot({
);
}

// const DisclaimerTextDescription = () => {
// return (
// <div className={styles.disclaimer_text}>
// This is a
// <Link href={"https://www.mongodb.com/legal/terms-of-use"}>
// Terms of Use
// </Link>
// </div>
// );
// };

const SUGGESTED_PROMPTS = [
"How do you deploy a free cluster in Atlas?",
"How do you import or migrate data into MongoDB Atlas?",
Expand Down Expand Up @@ -279,6 +298,10 @@ function ChatbotModal({
);
};

const promptIsTooLong = () => {
return inputBarValue.length > MAX_INPUT_CHARACTERS;
};

return (
<Modal
open={open}
Expand All @@ -293,12 +316,7 @@ function ChatbotModal({
className={styles.chat_window}
>
<MessageFeed>
{/* <DisclaimerText
className={styles.disclaimer_text_container}
title="Terms of Use"
>
{DisclaimerTextDescription()}
</DisclaimerText> */}
<DisclaimerText />
{messages.map((messageData, idx) => {
return (
<Message
Expand All @@ -310,14 +328,18 @@ function ChatbotModal({
}
suggestedPromptOnClick={handleSubmit}
isLoading={isLoading(messageData.id)}
renderRating={messageData.role === "assistant" && idx !== 0}
conversation={conversation}
/>
);
})}
</MessageFeed>
<InputBar
inputBarValue={inputBarValue}
onSubmit={() => handleSubmit(inputBarValue)}
hasError={promptIsTooLong()}
disabled={!!conversation.error}
disableSend={awaitingReply}
disableSend={awaitingReply || promptIsTooLong()}
textareaProps={{
value: inputBarValue,
onChange: (e) => {
Expand All @@ -336,12 +358,80 @@ function ChatbotModal({
);
}

const MAX_INPUT_CHARACTERS = 300;
interface InputBarProps extends LGInputBarProps {
inputBarValue: string;
hasError: boolean;
}

const InputBar = (props: InputBarProps) => {
const { inputBarValue, hasError, ...LGInputBarProps } = props;
const { darkMode } = useDarkMode();

return (
<div className={styles.chatbot_input_area}>
<LGInputBar
className={
hasError ?? false ? styles.chatbot_input_error_border : undefined
}
shouldRenderGradient={!hasError}
{...LGInputBarProps}
/>
<div
className={css`
display: flex;
justify-content: space-between;
`}
>
<Body baseFontSize={13}>
This is an experimental generative AI chatbot. All information should
be verified prior to use.
</Body>
<CharacterCount
darkMode={darkMode}
current={inputBarValue.length}
max={MAX_INPUT_CHARACTERS}
/>
</div>
</div>
);
};

const DisclaimerText = () => {
return (
<LGDisclaimerText
title="Terms and Policy"
className={styles.disclaimer_text}
>
<Body>
This is a generative AI Chatbot. By interacting with it, you agree to
MongoDB's{" "}
<Link
hideExternalIcon
href={"https://www.mongodb.com/legal/terms-of-use"}
>
Terms of Use
</Link>{" "}
and{" "}
<Link
hideExternalIcon
href={"https://www.mongodb.com/legal/acceptable-use-policy"}
>
Acceptable Use Policy.
</Link>
</Body>
</LGDisclaimerText>
);
};

type MessageProp = {
messageData: MessageData;
suggestedPrompts?: string[];
displaySuggestedPrompts: boolean;
suggestedPromptOnClick: (prompt: string) => void;
isLoading: boolean;
renderRating: boolean;
conversation: Conversation;
};

const Message = ({
Expand All @@ -350,25 +440,48 @@ const Message = ({
displaySuggestedPrompts,
suggestedPromptOnClick,
isLoading,
renderRating,
conversation,
}: MessageProp) => {
const [suggestedPromptIdx, setSuggestedPromptIdx] = useState(-1);
const user = useUser();

return (
<Fragment key={messageData.id}>
<LGMessage
baseFontSize={13}
isSender={messageData.role === "user"}
messageRatingProps={
messageData.role === "assistant"
renderRating
? {
className: styles.message_rating,
description: "How was the response?",
onChange: (e) => console.log(e),
onChange: (e) => {
const value = e.target.value as MessageRatingProps["value"];
if (!value) {
return;
}
conversation.rateMessage(
messageData.id,
value === "liked" ? true : false
);
},
value:
messageData.rating === undefined
? undefined
: messageData.rating
? "liked"
: "disliked",
}
: undefined
}
avatar={
<Avatar variant={messageData.role === "user" ? "user" : "mongo"} />
<Avatar
variant={messageData.role === "user" ? "user" : "mongo"}
name={
messageData.role === "user" && user?.name ? user?.name : undefined
}
/>
}
sourceType={isLoading ? undefined : MessageSourceType.Markdown}
markdownProps={{
Expand Down
13 changes: 13 additions & 0 deletions chat-ui/src/UserProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createContext, ReactNode } from "react";
import { User } from "./useUser";

export const UserContext = createContext<User | undefined>(undefined);

type UserProviderProps = {
children: ReactNode;
user?: User;
};

export function UserProvider({ children, user }: UserProviderProps) {
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
}
10 changes: 10 additions & 0 deletions chat-ui/src/useUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useContext } from "react";
import { UserContext } from "./UserProvider";

export type User = {
name: string;
};

export function useUser() {
return useContext(UserContext);
}
Loading