Skip to content
This repository was archived by the owner on Sep 29, 2025. It is now read-only.

Commit cbb0e98

Browse files
authored
(EAI-629) [UI] Switch state management to Zustand (#581)
1 parent 3f7e18d commit cbb0e98

21 files changed

+546
-774
lines changed

package-lock.json

Lines changed: 31 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/mongodb-chatbot-ui/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
},
4343
"scripts": {
4444
"build:dts": "tsc --project tsconfig.declarations.json",
45-
"build": "npm run build:component && npm run build:component:cjs && if [ \"$ENVIRONMENT\" = \"qa\" ]; then npm run build:static-site:qa && echo \"qa\"; else npm run build:static-site:staging; fi",
45+
"build": "npm run build:component && if [ \"$ENVIRONMENT\" = \"qa\" ]; then npm run build:static-site:qa && echo \"qa\"; else npm run build:static-site:staging; fi",
4646
"build:component": "npm run build:component:esm && npm run build:component:cjs && npm run build:dts",
4747
"build:component:esm": "tsc --project tsconfig.build.json && VITE_MODULE_FORMAT=es vite build --config vite.config.component.js",
4848
"build:component:cjs": "tsc --project tsconfig.build.json && VITE_MODULE_FORMAT=cjs vite build --config vite.config.component.js",
@@ -107,7 +107,8 @@
107107
"remark": "^15.0.1",
108108
"remark-parse": "^11.0.0",
109109
"unified": "^11.0.4",
110-
"unist-util-visit": "^5.0.0"
110+
"unist-util-visit": "^5.0.0",
111+
"zustand": "^5.0.2"
111112
},
112113
"devDependencies": {
113114
"@rollup/plugin-inject": "^5.0.3",

packages/mongodb-chatbot-ui/src/ChatMessageFeed.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ export function ChatMessageFeed(props: ChatMessageFeedProps) {
5959
</DisclaimerText>
6060
) : null}
6161
{messages.map((message, idx) => {
62-
const isLoading = conversation.isStreamingMessage
63-
? message.id === conversation.streamingMessage?.id &&
64-
conversation.streamingMessage?.content === ""
65-
: false;
62+
const isLoading =
63+
message.id === conversation.streamingMessageId &&
64+
conversation.getMessage(conversation.streamingMessageId ?? "")
65+
?.content === "";
6666

6767
const isInitialMessage = idx === 0;
6868

@@ -76,8 +76,7 @@ export function ChatMessageFeed(props: ChatMessageFeedProps) {
7676
message.role === "assistant" &&
7777
!isLoading &&
7878
!(
79-
awaitingReply &&
80-
conversation.streamingMessage?.id === message.id
79+
awaitingReply && conversation.streamingMessageId === message.id
8180
) &&
8281
// We don't want users to rate the initial message (and they can't because it's not in the database)
8382
!isInitialMessage

packages/mongodb-chatbot-ui/src/Chatbot.tsx

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useChatbot, OpenCloseHandlers, UseChatbotProps } from "./useChatbot";
66
import { LinkDataProvider } from "./LinkDataProvider";
77
import { type User } from "./useUser";
88
import { ChatbotProvider } from "./ChatbotProvider";
9-
import ConversationProvider from "./ConversationProvider";
9+
import { ConversationStateProvider } from "./ConversationStateProvider";
1010
import { RenameFields } from "./utils";
1111
import { HotkeyContextProvider } from "./HotkeyContext";
1212

@@ -41,34 +41,57 @@ export function Chatbot({
4141
const maxCommentCharacters =
4242
props.maxCommentCharacters ?? DEFAULT_MAX_COMMENT_CHARACTERS;
4343

44-
const chatbotData = useChatbot({
45-
chatbotName: name,
46-
serverBaseUrl,
47-
shouldStream,
48-
fetchOptions,
49-
isExperimental,
50-
maxInputCharacters,
51-
maxCommentCharacters,
52-
onOpen,
53-
onClose,
54-
sortMessageReferences,
55-
});
56-
5744
const tck = props.tck ?? "mongodb_ai_chatbot";
5845

5946
return (
6047
<LeafyGreenProvider darkMode={darkMode}>
61-
<LinkDataProvider tck={tck}>
62-
<UserProvider user={user}>
63-
<ChatbotProvider {...chatbotData}>
64-
<HotkeyContextProvider>
65-
<ConversationProvider conversation={chatbotData.conversation}>
66-
{children}
67-
</ConversationProvider>
68-
</HotkeyContextProvider>
69-
</ChatbotProvider>
70-
</UserProvider>
71-
</LinkDataProvider>
48+
<ConversationStateProvider>
49+
<LinkDataProvider tck={tck}>
50+
<UserProvider user={user}>
51+
<InnerChatbot
52+
fetchOptions={fetchOptions}
53+
isExperimental={isExperimental}
54+
maxCommentCharacters={maxCommentCharacters}
55+
maxInputCharacters={maxInputCharacters}
56+
name={name}
57+
onOpen={onOpen}
58+
onClose={onClose}
59+
serverBaseUrl={serverBaseUrl}
60+
shouldStream={shouldStream}
61+
sortMessageReferences={sortMessageReferences}
62+
>
63+
{children}
64+
</InnerChatbot>
65+
</UserProvider>
66+
</LinkDataProvider>
67+
</ConversationStateProvider>
7268
</LeafyGreenProvider>
7369
);
7470
}
71+
72+
type InnerChatbotProps = Pick<
73+
ChatbotProps,
74+
| "children"
75+
| "fetchOptions"
76+
| "isExperimental"
77+
| "maxCommentCharacters"
78+
| "maxInputCharacters"
79+
| "name"
80+
| "onOpen"
81+
| "onClose"
82+
| "serverBaseUrl"
83+
| "shouldStream"
84+
| "sortMessageReferences"
85+
>;
86+
87+
function InnerChatbot({ children, ...props }: InnerChatbotProps) {
88+
const chatbotData = useChatbot({
89+
...props,
90+
});
91+
92+
return (
93+
<ChatbotProvider {...chatbotData}>
94+
<HotkeyContextProvider>{children}</HotkeyContextProvider>
95+
</ChatbotProvider>
96+
);
97+
}

packages/mongodb-chatbot-ui/src/ChatbotProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ export type ChatbotProviderProps = ChatbotData & {
99

1010
export function ChatbotProvider({
1111
children,
12-
...linkData
12+
...chatbotData
1313
}: ChatbotProviderProps) {
1414
return (
15-
<ChatbotContext.Provider value={linkData}>
15+
<ChatbotContext.Provider value={chatbotData}>
1616
{children}
1717
</ChatbotContext.Provider>
1818
);

packages/mongodb-chatbot-ui/src/ConversationProvider.tsx

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { createContext, useState } from "react";
2+
import {
3+
ConversationStore,
4+
initialConversationState,
5+
makeConversationStore,
6+
} from "./conversationStore";
7+
8+
const ConversationStoreNotInitialized =
9+
"A conversation store was used before it was initialized.";
10+
11+
const defaultState = {
12+
...initialConversationState,
13+
api: new Proxy(
14+
{} as ReturnType<ConversationStore["getInitialState"]>["api"],
15+
{
16+
get: () => () => {
17+
throw new Error(ConversationStoreNotInitialized);
18+
},
19+
}
20+
),
21+
} as ReturnType<ConversationStore["getInitialState"]>;
22+
23+
const defaultStore = {
24+
getInitialState: () => defaultState,
25+
getState: () => defaultState,
26+
setState: () => {
27+
throw new Error(ConversationStoreNotInitialized);
28+
},
29+
subscribe: () => () => {
30+
throw new Error(ConversationStoreNotInitialized);
31+
},
32+
} satisfies ConversationStore;
33+
34+
export const ConversationStateContext =
35+
createContext<ConversationStore>(defaultStore);
36+
37+
export function ConversationStateProvider(props: {
38+
children: React.ReactNode;
39+
}) {
40+
// The useState initializer function ensures that the store is only created once per mount of this provider.
41+
// See https://tkdodo.eu/blog/use-state-for-one-time-initializations for more information.
42+
const [store] = useState(() => makeConversationStore("state"));
43+
return (
44+
<ConversationStateContext.Provider value={store}>
45+
{props.children}
46+
</ConversationStateContext.Provider>
47+
);
48+
}

packages/mongodb-chatbot-ui/src/DevCenterChatbot.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import {
44
FloatingActionButtonTrigger,
55
type FloatingActionButtonTriggerProps,
66
} from "./FloatingActionButtonTrigger";
7-
import { ModalView, ModalViewProps } from "./ModalView";
7+
import { type ModalViewProps } from "./ModalView";
88
import { MongoDbLegalDisclosure } from "./MongoDbLegal";
99
import { mongoDbVerifyInformationMessage } from "./ui-text";
1010
import { PoweredByAtlasVectorSearch } from "./PoweredByAtlasVectorSearch";
1111
import { css } from "@emotion/css";
12+
import { lazy, Suspense } from "react";
1213
import { References } from "./references";
1314

1415
export type DevCenterChatbotProps = DarkModeProps & {
@@ -17,6 +18,10 @@ export type DevCenterChatbotProps = DarkModeProps & {
1718
initialMessageReferences?: References;
1819
};
1920

21+
const ModalView = lazy(() =>
22+
import("./ModalView").then((m) => ({ default: m.ModalView }))
23+
);
24+
2025
export function DevCenterChatbot(props: DevCenterChatbotProps) {
2126
const { darkMode } = useDarkMode(props.darkMode);
2227

@@ -46,7 +51,9 @@ export function DevCenterChatbot(props: DevCenterChatbotProps) {
4651
return (
4752
<>
4853
<FloatingActionButtonTrigger {...triggerProps} />
49-
<ModalView {...viewProps} />
54+
<Suspense fallback={null}>
55+
<ModalView {...viewProps} />
56+
</Suspense>
5057
</>
5158
);
5259
}

packages/mongodb-chatbot-ui/src/DocsChatbot.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { useDarkMode } from "@leafygreen-ui/leafygreen-provider";
22
import { DarkModeProps } from "./DarkMode";
33
import { InputBarTrigger, InputBarTriggerProps } from "./InputBarTrigger";
4-
import { ModalView, ModalViewProps } from "./ModalView";
4+
import { type ModalViewProps } from "./ModalView";
55
import { MongoDbLegalDisclosure } from "./MongoDbLegal";
66
import { mongoDbVerifyInformationMessage } from "./ui-text";
7+
import { lazy } from "react";
8+
9+
const ModalView = lazy(() =>
10+
import("./ModalView").then((m) => ({ default: m.ModalView }))
11+
);
712

813
export type DocsChatbotProps = DarkModeProps & {
914
suggestedPrompts?: string[];

packages/mongodb-chatbot-ui/src/ModalView.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export function ModalView(props: ModalViewProps) {
6565

6666
const chatWindowInputBarId = inputBarId ?? "chatbot-modal-input-bar";
6767

68+
console.log("chatWindowInputBarId", chatWindowInputBarId);
69+
6870
return (
6971
<Suspense fallback={null}>
7072
{open ? (
@@ -77,12 +79,7 @@ export function ModalView(props: ModalViewProps) {
7779
}
7880
shouldClose={shouldClose}
7981
>
80-
<Suspense fallback={null}>
81-
<ChatWindow
82-
inputBarId={chatWindowInputBarId}
83-
{...chatWindowProps}
84-
/>
85-
</Suspense>
82+
<ChatWindow inputBarId={chatWindowInputBarId} {...chatWindowProps} />
8683
</Modal>
8784
) : null}
8885
</Suspense>

0 commit comments

Comments
 (0)