@mitodl/smoot-design 0.0.0-a2e4e1e → 0.0.0-bafccd3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +4 -0
  2. package/dist/bundles/{remoteTutorDrawer.es.js → aiDrawerManager.es.js} +17612 -17240
  3. package/dist/bundles/aiDrawerManager.es.js.map +1 -0
  4. package/dist/bundles/aiDrawerManager.umd.js +245 -0
  5. package/dist/bundles/aiDrawerManager.umd.js.map +1 -0
  6. package/dist/cjs/VERSION.d.ts +12 -0
  7. package/dist/cjs/VERSION.js +15 -0
  8. package/dist/cjs/ai.d.ts +3 -3
  9. package/dist/cjs/ai.js +5 -1
  10. package/dist/cjs/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.d.ts → AiDrawer/AiDrawer.d.ts} +11 -13
  11. package/dist/cjs/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.js → AiDrawer/AiDrawer.js} +27 -41
  12. package/dist/cjs/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.stories.d.ts → AiDrawer/AiDrawer.stories.d.ts} +4 -3
  13. package/dist/cjs/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.stories.js → AiDrawer/AiDrawer.stories.js} +32 -51
  14. package/dist/cjs/bundles/AiDrawer/AiDrawerManager.d.ts +12 -0
  15. package/dist/cjs/bundles/AiDrawer/AiDrawerManager.js +51 -0
  16. package/dist/cjs/bundles/AiDrawer/AiDrawerManager.stories.d.ts +6 -0
  17. package/dist/cjs/bundles/AiDrawer/AiDrawerManager.stories.js +267 -0
  18. package/dist/cjs/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.test.js → AiDrawer/AiDrawerManager.test.js} +22 -18
  19. package/dist/cjs/bundles/{RemoteTutorDrawer → AiDrawer}/FlashcardsScreen.js +1 -1
  20. package/dist/cjs/bundles/aiDrawerManager.d.ts +6 -0
  21. package/dist/cjs/bundles/aiDrawerManager.js +44 -0
  22. package/dist/cjs/components/AiChat/AiChat.d.ts +3 -3
  23. package/dist/cjs/components/AiChat/AiChat.js +65 -53
  24. package/dist/cjs/components/AiChat/AiChat.stories.d.ts +0 -4
  25. package/dist/cjs/components/AiChat/AiChat.stories.js +5 -54
  26. package/dist/cjs/components/AiChat/AiChatContext.d.ts +26 -0
  27. package/dist/cjs/components/AiChat/AiChatContext.js +106 -0
  28. package/dist/cjs/components/AiChat/AiChatContext.stories.d.ts +14 -0
  29. package/dist/cjs/components/AiChat/AiChatContext.stories.js +75 -0
  30. package/dist/cjs/components/AiChat/AiChatMarkdown.stories.d.ts +15 -0
  31. package/dist/cjs/components/AiChat/AiChatMarkdown.stories.js +282 -0
  32. package/dist/cjs/components/AiChat/Markdown.d.ts +7 -0
  33. package/dist/cjs/components/AiChat/Markdown.js +14 -0
  34. package/dist/cjs/components/AiChat/test-utils/api.js +40 -1
  35. package/dist/cjs/components/AiChat/types.d.ts +25 -11
  36. package/dist/cjs/components/AiChat/types.js +1 -1
  37. package/dist/cjs/components/AiChat/utils.d.ts +1 -1
  38. package/dist/cjs/components/AiChat/utils.js +1 -1
  39. package/dist/cjs/components/LinkAdapter/LinkAdapter.js +1 -1
  40. package/dist/cjs/components/TabButtons/TabButtonList.js +1 -1
  41. package/dist/cjs/index.d.ts +1 -0
  42. package/dist/cjs/index.js +3 -1
  43. package/dist/cjs/utils/retryingFetch.d.ts +19 -0
  44. package/dist/cjs/utils/retryingFetch.js +98 -0
  45. package/dist/cjs/utils/retryingFetch.test.js +48 -0
  46. package/dist/esm/VERSION.d.ts +12 -0
  47. package/dist/esm/VERSION.js +12 -0
  48. package/dist/esm/ai.d.ts +3 -3
  49. package/dist/esm/ai.js +2 -1
  50. package/dist/esm/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.d.ts → AiDrawer/AiDrawer.d.ts} +11 -13
  51. package/dist/esm/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.js → AiDrawer/AiDrawer.js} +26 -40
  52. package/dist/esm/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.stories.d.ts → AiDrawer/AiDrawer.stories.d.ts} +4 -3
  53. package/dist/esm/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.stories.js → AiDrawer/AiDrawer.stories.js} +31 -50
  54. package/dist/esm/bundles/AiDrawer/AiDrawerManager.d.ts +12 -0
  55. package/dist/esm/bundles/AiDrawer/AiDrawerManager.js +48 -0
  56. package/dist/esm/bundles/AiDrawer/AiDrawerManager.stories.d.ts +6 -0
  57. package/dist/esm/bundles/AiDrawer/AiDrawerManager.stories.js +264 -0
  58. package/dist/esm/bundles/AiDrawer/AiDrawerManager.test.d.ts +1 -0
  59. package/dist/esm/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.test.js → AiDrawer/AiDrawerManager.test.js} +22 -18
  60. package/dist/esm/bundles/{RemoteTutorDrawer → AiDrawer}/FlashcardsScreen.js +1 -1
  61. package/dist/esm/bundles/aiDrawerManager.d.ts +6 -0
  62. package/dist/esm/bundles/aiDrawerManager.js +41 -0
  63. package/dist/esm/components/AiChat/AiChat.d.ts +3 -3
  64. package/dist/esm/components/AiChat/AiChat.js +65 -54
  65. package/dist/esm/components/AiChat/AiChat.stories.d.ts +0 -4
  66. package/dist/esm/components/AiChat/AiChat.stories.js +4 -53
  67. package/dist/esm/components/AiChat/AiChatContext.d.ts +26 -0
  68. package/dist/esm/components/AiChat/AiChatContext.js +102 -0
  69. package/dist/esm/components/AiChat/AiChatContext.stories.d.ts +14 -0
  70. package/dist/esm/components/AiChat/AiChatContext.stories.js +72 -0
  71. package/dist/esm/components/AiChat/AiChatMarkdown.stories.d.ts +15 -0
  72. package/dist/esm/components/AiChat/AiChatMarkdown.stories.js +279 -0
  73. package/dist/esm/components/AiChat/Markdown.d.ts +7 -0
  74. package/dist/esm/components/AiChat/Markdown.js +12 -0
  75. package/dist/esm/components/AiChat/test-utils/api.js +40 -1
  76. package/dist/esm/components/AiChat/types.d.ts +25 -11
  77. package/dist/esm/components/AiChat/types.js +1 -1
  78. package/dist/esm/components/AiChat/utils.d.ts +1 -1
  79. package/dist/esm/components/AiChat/utils.js +1 -1
  80. package/dist/esm/components/LinkAdapter/LinkAdapter.js +1 -1
  81. package/dist/esm/components/TabButtons/TabButtonList.js +1 -1
  82. package/dist/esm/index.d.ts +1 -0
  83. package/dist/esm/index.js +1 -0
  84. package/dist/esm/utils/retryingFetch.d.ts +19 -0
  85. package/dist/esm/utils/retryingFetch.js +96 -0
  86. package/dist/esm/utils/retryingFetch.test.d.ts +1 -0
  87. package/dist/esm/utils/retryingFetch.test.js +46 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/package.json +11 -7
  90. package/dist/bundles/remoteTutorDrawer.umd.js +0 -207
  91. package/dist/cjs/bundles/remoteTutorDrawer.d.ts +0 -7
  92. package/dist/cjs/bundles/remoteTutorDrawer.js +0 -40
  93. package/dist/esm/bundles/remoteTutorDrawer.d.ts +0 -7
  94. package/dist/esm/bundles/remoteTutorDrawer.js +0 -37
  95. /package/dist/cjs/bundles/{RemoteTutorDrawer/RemoteTutorDrawer.test.d.ts → AiDrawer/AiDrawerManager.test.d.ts} +0 -0
  96. /package/dist/cjs/bundles/{RemoteTutorDrawer → AiDrawer}/FlashcardsScreen.d.ts +0 -0
  97. /package/dist/{esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.test.d.ts → cjs/utils/retryingFetch.test.d.ts} +0 -0
  98. /package/dist/esm/bundles/{RemoteTutorDrawer → AiDrawer}/FlashcardsScreen.d.ts +0 -0
@@ -10,21 +10,21 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import * as React from "react";
13
- import { useEffect, useRef, useState, useMemo } from "react";
13
+ import { useEffect, useRef, useState } from "react";
14
14
  import styled from "@emotion/styled";
15
15
  import Typography from "@mui/material/Typography";
16
16
  import classNames from "classnames";
17
17
  import { RiSendPlaneFill, RiStopFill, RiMoreFill } from "@remixicon/react";
18
18
  import { Input, AdornmentButton } from "../Input/Input";
19
19
  import { EntryScreen } from "./EntryScreen";
20
- import Markdown from "react-markdown";
21
20
  import { ScrollSnap } from "../ScrollSnap/ScrollSnap";
22
21
  import { SrAnnouncer } from "../SrAnnouncer/SrAnnouncer";
23
22
  import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden";
24
23
  import { Alert } from "../Alert/Alert";
25
24
  import { ChatTitle } from "./ChatTitle";
26
- import { useAiChat } from "./utils";
25
+ import { AiChatProvider, useAiChat } from "./AiChatContext";
27
26
  import { useScrollSnap } from "../ScrollSnap/useScrollSnap";
27
+ import Markdown from "./Markdown";
28
28
  const classes = {
29
29
  root: "MitAiChat--root",
30
30
  title: "MitAiChat--title",
@@ -62,6 +62,9 @@ const MessagesContainer = styled(ScrollSnap)(({ externalScroll }) => ({
62
62
  padding: "14px 0",
63
63
  overflow: externalScroll ? "visible" : "auto",
64
64
  gap: "16px",
65
+ [`> .${classes.messageRowAssistant}:first-child`]: {
66
+ marginTop: "16px",
67
+ },
65
68
  }));
66
69
  const MessageRow = styled.div({
67
70
  display: "flex",
@@ -75,25 +78,45 @@ const MessageRow = styled.div({
75
78
  },
76
79
  position: "relative",
77
80
  });
78
- const Message = styled.div(({ theme }) => (Object.assign(Object.assign({ color: theme.custom.colors.darkGray2, backgroundColor: theme.custom.colors.white, padding: "12px 16px" }, theme.typography.body2), { "p:first-of-type": {
81
+ const Message = styled.div(({ theme }) => (Object.assign(Object.assign({ color: theme.custom.colors.darkGray2, backgroundColor: theme.custom.colors.white }, theme.typography.body2), { "p:first-of-type": {
79
82
  marginTop: 0,
80
83
  }, "p:last-of-type": {
81
84
  marginBottom: 0,
82
- }, "ol, ul": {
85
+ }, "ol,ul": {
83
86
  paddingInlineStart: "16px",
84
- marginLeft: "6px",
85
- "ol, ul": {
86
- marginLeft: 0,
87
+ margin: "12px 0 12px 4px",
88
+ }, "ol > li, ul > li": {
89
+ margin: "12px 0",
90
+ "ol,ul": {
91
+ margin: "12px 0 12px 4px",
87
92
  },
88
93
  li: {
89
- margin: "16px 0",
94
+ margin: "6px 0",
95
+ },
96
+ }, ul: {
97
+ paddingInlineStart: 0,
98
+ "> li": {
99
+ listStyleType: "none",
100
+ position: "relative",
101
+ "&::before": {
102
+ content: '"–"',
103
+ position: "absolute",
104
+ left: 0,
105
+ color: "#888",
106
+ marginRight: "8px",
107
+ },
108
+ paddingLeft: "16px",
109
+ },
110
+ }, "ol + ul": {
111
+ marginLeft: "24px",
112
+ li: {
113
+ margin: "6px 0",
90
114
  },
91
115
  }, a: {
92
116
  color: theme.custom.colors.red,
93
117
  fontWeight: "normal",
94
- }, borderRadius: "12px", [`.${classes.messageRowAssistant} &`]: {
95
- padding: "12px 16px 12px 0",
96
- }, [`.${classes.messageRowUser} &`]: {
118
+ }, borderRadius: "12px", [`.${classes.messageRowUser} &`]: {
119
+ padding: "12px 16px",
97
120
  borderRadius: "8px 0px 8px 8px",
98
121
  backgroundColor: theme.custom.colors.lightGray1,
99
122
  } })));
@@ -128,47 +151,32 @@ const Disclaimer = styled(Typography)(({ theme }) => ({
128
151
  marginTop: "16px",
129
152
  textAlign: "center",
130
153
  }));
131
- const AiChat = (_a) => {
154
+ const AiChatDisplay = (_a) => {
132
155
  var _b, _c;
133
- var { entryScreenTitle, entryScreenEnabled = true, conversationStarters, initialMessages: _initialMessages, askTimTitle, requestOpts, parseContent, srLoadingMessages, placeholder = "", className, scrollElement, chatId, ref } = _a, others = __rest(_a, ["entryScreenTitle", "entryScreenEnabled", "conversationStarters", "initialMessages", "askTimTitle", "requestOpts", "parseContent", "srLoadingMessages", "placeholder", "className", "scrollElement", "chatId", "ref"]) // Could contain data attributes
156
+ var { conversationStarters, askTimTitle, entryScreenEnabled = true, entryScreenTitle, srLoadingMessages, placeholder = "", className, scrollElement, ref, useMathJax = false } = _a, others = __rest(_a, ["conversationStarters", "askTimTitle", "entryScreenEnabled", "entryScreenTitle", "srLoadingMessages", "placeholder", "className", "scrollElement", "ref", "useMathJax"]) // Could contain data attributes
134
157
  ;
135
158
  const containerRef = useRef(null);
136
159
  const messagesContainerRef = useRef(null);
137
- const [showEntryScreen, setShowEntryScreen] = useState(entryScreenEnabled);
138
160
  const chatScreenRef = useRef(null);
139
- const [initialMessages, setInitialMessages] = useState();
140
161
  const promptInputRef = useRef(null);
141
- const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, stop, error, } = useAiChat(requestOpts, {
142
- initialMessages,
143
- id: chatId,
144
- });
162
+ const { messages, input, handleInputChange, handleSubmit, append, isLoading, stop, error, initialMessages, status, } = useAiChat();
145
163
  useScrollSnap({
146
164
  scrollElement: scrollElement || messagesContainerRef.current,
147
165
  contentElement: scrollElement ? messagesContainerRef.current : null,
148
166
  threshold: 200,
149
167
  });
150
- useEffect(() => {
151
- if (_initialMessages) {
152
- const prefix = Math.random().toString().slice(2);
153
- setInitialMessages(_initialMessages.map((m, i) => (Object.assign(Object.assign({}, m), { id: `initial-${prefix}-${i}` }))));
154
- }
155
- }, [_initialMessages]);
168
+ const [showEntryScreen, setShowEntryScreen] = useState(entryScreenEnabled);
156
169
  useEffect(() => {
157
170
  var _a, _b;
158
171
  if (!showEntryScreen) {
159
172
  (_b = (_a = promptInputRef.current) === null || _a === void 0 ? void 0 : _a.querySelector("input")) === null || _b === void 0 ? void 0 : _b.focus();
160
173
  }
161
174
  }, [showEntryScreen]);
162
- const messages = useMemo(() => {
163
- const initial = initialMessages === null || initialMessages === void 0 ? void 0 : initialMessages.map((m) => m.id);
164
- return unparsed.map((m) => {
165
- if (m.role === "assistant" && !(initial === null || initial === void 0 ? void 0 : initial.includes(m.id))) {
166
- const content = parseContent ? parseContent(m.content) : m.content;
167
- return Object.assign(Object.assign({}, m), { content });
168
- }
169
- return m;
170
- });
171
- }, [parseContent, unparsed, initialMessages]);
175
+ useEffect(() => {
176
+ if (messages.some((m) => m.role === "user" || ["submitted", "streaming"].includes(status))) {
177
+ setShowEntryScreen(false);
178
+ }
179
+ }, [messages, status]);
172
180
  const showStarters = messages.length === ((initialMessages === null || initialMessages === void 0 ? void 0 : initialMessages.length) || 0);
173
181
  const waiting = !showStarters && !error && ((_b = messages[messages.length - 1]) === null || _b === void 0 ? void 0 : _b.role) === "user";
174
182
  const stoppable = isLoading && ((_c = messages[messages.length - 1]) === null || _c === void 0 ? void 0 : _c.role) !== "user";
@@ -181,16 +189,7 @@ const AiChat = (_a) => {
181
189
  };
182
190
  const lastMsg = messages[messages.length - 1];
183
191
  const externalScroll = !!scrollElement;
184
- return (React.createElement(Container, { className: className, ref: containerRef,
185
- /**
186
- * Changing the `useChat` chatId seems to persist some state between
187
- * hook calls. This can cause strange effects like loading API responses
188
- * for previous chatId into new chatId.
189
- *
190
- * To avoid this, let's change the key, this will force React to make a new component
191
- * not sharing any of the old state.
192
- */
193
- key: chatId }, showEntryScreen ? (React.createElement(EntryScreen, { className: classes.entryScreenContainer, title: entryScreenTitle, conversationStarters: conversationStarters, onPromptSubmit: (prompt) => {
192
+ return (React.createElement(Container, { className: className, ref: containerRef }, showEntryScreen ? (React.createElement(EntryScreen, { className: classes.entryScreenContainer, title: entryScreenTitle, conversationStarters: conversationStarters, onPromptSubmit: (prompt) => {
194
193
  if (prompt.trim() === "") {
195
194
  return;
196
195
  }
@@ -200,13 +199,20 @@ const AiChat = (_a) => {
200
199
  React.createElement(ChatContainer, Object.assign({ className: classNames(className, classes.root), externalScroll: externalScroll }, others),
201
200
  React.createElement(ChatTitle, { askTimTitle: askTimTitle, externalScroll: externalScroll, className: classNames(className, classes.title) }),
202
201
  React.createElement(MessagesContainer, { className: classes.messagesContainer, externalScroll: externalScroll, ref: messagesContainerRef },
203
- messages.map((m) => (React.createElement(MessageRow, { key: m.id, "data-chat-role": m.role, className: classNames(classes.messageRow, {
204
- [classes.messageRowUser]: m.role === "user",
205
- [classes.messageRowAssistant]: m.role === "assistant",
206
- }) },
207
- React.createElement(Message, { className: classes.message },
208
- React.createElement(VisuallyHidden, { as: m.role === "user" ? "h5" : "h6" }, m.role === "user" ? "You said: " : "Assistant said: "),
209
- React.createElement(Markdown, { skipHtml: true }, m.content))))),
202
+ messages.map((m, i) => {
203
+ // Our Markdown+Mathjax has issues when rendering streaming display math
204
+ // Force a re-render of the last (streaming) message when it's done loading.
205
+ const key = i === messages.length - 1 && isLoading
206
+ ? `isLoading-${m.id}`
207
+ : m.id;
208
+ return (React.createElement(MessageRow, { key: key, "data-chat-role": m.role, className: classNames(classes.messageRow, {
209
+ [classes.messageRowUser]: m.role === "user",
210
+ [classes.messageRowAssistant]: m.role === "assistant",
211
+ }) },
212
+ React.createElement(Message, { className: classes.message },
213
+ React.createElement(VisuallyHidden, { as: m.role === "user" ? "h5" : "h6" }, m.role === "user" ? "You said: " : "Assistant said: "),
214
+ React.createElement(Markdown, { enableMathjax: useMathJax }, m.content))));
215
+ }),
210
216
  showStarters ? (React.createElement(StarterContainer, null, conversationStarters === null || conversationStarters === void 0 ? void 0 : conversationStarters.map((m) => (React.createElement(Starter, { className: classes.conversationStarter, key: m.content, onClick: () => {
211
217
  scrollToBottom();
212
218
  append({ role: "user", content: m.content });
@@ -241,4 +247,9 @@ const AiChat = (_a) => {
241
247
  React.createElement(Disclaimer, { variant: "body3" }, "AI-generated content may be incorrect.")),
242
248
  React.createElement(SrAnnouncer, { isLoading: isLoading, loadingMessages: srLoadingMessages, message: (lastMsg === null || lastMsg === void 0 ? void 0 : lastMsg.role) === "assistant" ? lastMsg === null || lastMsg === void 0 ? void 0 : lastMsg.content : "" }))))));
243
249
  };
244
- export { AiChat };
250
+ const AiChat = (_a) => {
251
+ var { requestOpts, initialMessages, chatId, parseContent } = _a, displayProps = __rest(_a, ["requestOpts", "initialMessages", "chatId", "parseContent"]);
252
+ return (React.createElement(AiChatProvider, { requestOpts: requestOpts, chatId: chatId, initialMessages: initialMessages, parseContent: parseContent },
253
+ React.createElement(AiChatDisplay, Object.assign({}, displayProps))));
254
+ };
255
+ export { AiChatDisplay, AiChat };
@@ -9,10 +9,6 @@ export declare const StreamingResponses: Story;
9
9
  * to text via `parseContent`.
10
10
  */
11
11
  export declare const JsonResponses: Story;
12
- /**
13
- * This story shows the component's builtin markdown styling.
14
- */
15
- export declare const MarkdownStyling: Story;
16
12
  /**
17
13
  * Where a scrollable container exists in the including component, it can be passed in
18
14
  * for the AiChat component to use in place of its own height constrained message container
@@ -3,6 +3,7 @@ import { AiChat } from "./AiChat";
3
3
  import styled from "@emotion/styled";
4
4
  import { handlers } from "./test-utils/api";
5
5
  import { useEffect, useRef, useState } from "react";
6
+ import { MathJaxContext } from "better-react-mathjax";
6
7
  const TEST_API_STREAMING = "http://localhost:4567/streaming";
7
8
  const TEST_API_JSON = "http://localhost:4567/json";
8
9
  const INITIAL_MESSAGES = [
@@ -36,8 +37,9 @@ const meta = {
36
37
  },
37
38
  render: (args) => React.createElement(AiChat, Object.assign({}, args)),
38
39
  decorators: (Story, context) => {
39
- return (React.createElement(Container, null,
40
- React.createElement(Story, { key: String(context.args.entryScreenEnabled) })));
40
+ return (React.createElement(MathJaxContext, null,
41
+ React.createElement(Container, null,
42
+ React.createElement(Story, { key: String(context.args.entryScreenEnabled) }))));
41
43
  },
42
44
  args: {
43
45
  entryScreenTitle: "What do you want to learn from MIT?",
@@ -74,57 +76,6 @@ export const JsonResponses = {
74
76
  },
75
77
  },
76
78
  };
77
- const DEMO_MARKDOWN = `This shows default markdown styling. Here's are some lists:
78
- - Item 1
79
- - Item 2 - an unordered list (bullets)
80
- - Point A
81
- - Point B
82
- - Item 3 - an ordered list (numbers)
83
- 1. Item 3.1
84
- 2. Item 3.2
85
- 3. Item 3.3
86
-
87
- Sometimes, unordered lists are nested within ordered lists:
88
- 1. Item 1
89
- - Item 1.1
90
- - Item 1.2
91
- 2. Item 2
92
- - Item 2.1
93
- - Item 2.2
94
-
95
- Sometimes, ordered lists are nested within unordered lists:
96
- - Item 1
97
- 1. Item 1.1
98
- 2. Item 1.2
99
- - Item 2
100
- 1. Item 2.1
101
- 2. Item 2.2
102
-
103
- Here is a longer paragraph and **bold text** and *italic text*. Lorem ipsum dolor sit amet, consectetur adipiscing elit
104
- sed do eiusmod tempor [incididunt](https://mit.edu) ut labore et dolore magna aliqua. Ut enim ad minim veniam.
105
-
106
- And some inline code, \`\`<inline></inline>\`\` and code block:
107
- \`\`\`
108
- def f(x):
109
- print(x)
110
- \`\`\`
111
- `;
112
- /**
113
- * This story shows the component's builtin markdown styling.
114
- */
115
- export const MarkdownStyling = {
116
- args: {
117
- requestOpts: { apiUrl: TEST_API_STREAMING },
118
- entryScreenEnabled: false,
119
- conversationStarters: [],
120
- initialMessages: [
121
- {
122
- role: "assistant",
123
- content: DEMO_MARKDOWN,
124
- },
125
- ],
126
- },
127
- };
128
79
  const ScrollComponent = (args) => {
129
80
  const ref = useRef(null);
130
81
  const [scrollElement, setScrollElement] = useState(null);
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import { UseChatHelpers } from "@ai-sdk/react";
3
+ import type { AiChatMessage, AiChatContextProps } from "./types";
4
+ /**
5
+ * All of `@ai-sdk/react`'s [`useChat`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat)
6
+ * results, plus the initial messages.
7
+ */
8
+ type AiChatContextResult = UseChatHelpers & {
9
+ initialMessages: AiChatMessage[] | null;
10
+ };
11
+ /**
12
+ * Provides AiChatContext to its children. Within this provider, you can consume
13
+ * the AiChatContext using the `useAiChat` hook.
14
+ */
15
+ declare const AiChatProvider: React.FC<AiChatContextProps>;
16
+ /**
17
+ * Returns the AiChatContext, which includes all results from `@ai-sdk/react`'s
18
+ * [`useChat`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) hook as
19
+ * well as the initial messages.
20
+ *
21
+ * In addition to customizing the fetcher, using a context allows us to avoid
22
+ * this issue https://github.com/vercel/ai/issues/3266 since the caller no
23
+ * longer needs to provide the initial messages.
24
+ */
25
+ declare const useAiChat: () => AiChatContextResult;
26
+ export { useAiChat, AiChatProvider };
@@ -0,0 +1,102 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __rest = (this && this.__rest) || function (s, e) {
11
+ var t = {};
12
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
13
+ t[p] = s[p];
14
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
15
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
16
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
17
+ t[p[i]] = s[p[i]];
18
+ }
19
+ return t;
20
+ };
21
+ import * as React from "react";
22
+ import { useChat } from "@ai-sdk/react";
23
+ import { useMemo, createContext } from "react";
24
+ import retryingFetch from "../../utils/retryingFetch";
25
+ const identity = (x) => x;
26
+ const getFetcher = (requestOpts) => (url, opts) => __awaiter(void 0, void 0, void 0, function* () {
27
+ var _a, _b;
28
+ if (typeof (opts === null || opts === void 0 ? void 0 : opts.body) !== "string") {
29
+ console.error("Unexpected body type.");
30
+ return retryingFetch(url, opts);
31
+ }
32
+ const messages = JSON.parse(opts === null || opts === void 0 ? void 0 : opts.body).messages;
33
+ const transformBody = (_a = requestOpts.transformBody) !== null && _a !== void 0 ? _a : identity;
34
+ const options = Object.assign(Object.assign(Object.assign(Object.assign({}, opts), { body: JSON.stringify(transformBody(messages)) }), requestOpts.fetchOpts), { headers: Object.assign(Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts.headers), { "Content-Type": "application/json" }), (_b = requestOpts.fetchOpts) === null || _b === void 0 ? void 0 : _b.headers) });
35
+ return retryingFetch(url, options);
36
+ });
37
+ const AiChatContext = createContext(null);
38
+ /**
39
+ * Provides AiChatContext to its children. Within this provider, you can consume
40
+ * the AiChatContext using the `useAiChat` hook.
41
+ */
42
+ const AiChatProvider = ({ initialMessages: _initialMessages, requestOpts, chatId, parseContent, children, }) => {
43
+ const initialMessages = useMemo(() => {
44
+ var _a;
45
+ return ((_a = _initialMessages === null || _initialMessages === void 0 ? void 0 : _initialMessages.map((message, i) => (Object.assign(Object.assign({}, message), { id: `initial-${i}` })))) !== null && _a !== void 0 ? _a : []);
46
+ }, [_initialMessages]);
47
+ const fetcher = useMemo(() => getFetcher(requestOpts), [requestOpts]);
48
+ const _a = useChat({
49
+ api: requestOpts.apiUrl,
50
+ streamProtocol: "text",
51
+ fetch: fetcher,
52
+ onFinish: (message) => {
53
+ var _a;
54
+ if (!requestOpts.onFinish)
55
+ return;
56
+ if (message.role === "assistant" || message.role === "user") {
57
+ (_a = requestOpts.onFinish) === null || _a === void 0 ? void 0 : _a.call(requestOpts, message);
58
+ }
59
+ else {
60
+ console.info("Unexpected message role.", message);
61
+ }
62
+ },
63
+ initialMessages,
64
+ id: chatId,
65
+ }), { messages: unparsed } = _a, others = __rest(_a, ["messages"]);
66
+ const messages = useMemo(() => {
67
+ const initial = initialMessages === null || initialMessages === void 0 ? void 0 : initialMessages.map((m) => m.id);
68
+ return unparsed.map((m) => {
69
+ if (m.role === "assistant" && !(initial === null || initial === void 0 ? void 0 : initial.includes(m.id))) {
70
+ const content = parseContent ? parseContent(m.content) : m.content;
71
+ return Object.assign(Object.assign({}, m), { content });
72
+ }
73
+ return m;
74
+ });
75
+ }, [parseContent, unparsed, initialMessages]);
76
+ return (React.createElement(AiChatContext.Provider
77
+ /**
78
+ * Ensure that child state is reset when chatId changes.
79
+ */
80
+ , {
81
+ /**
82
+ * Ensure that child state is reset when chatId changes.
83
+ */
84
+ key: chatId, value: Object.assign({ initialMessages, messages }, others) }, children));
85
+ };
86
+ /**
87
+ * Returns the AiChatContext, which includes all results from `@ai-sdk/react`'s
88
+ * [`useChat`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) hook as
89
+ * well as the initial messages.
90
+ *
91
+ * In addition to customizing the fetcher, using a context allows us to avoid
92
+ * this issue https://github.com/vercel/ai/issues/3266 since the caller no
93
+ * longer needs to provide the initial messages.
94
+ */
95
+ const useAiChat = () => {
96
+ const context = React.useContext(AiChatContext);
97
+ if (!context) {
98
+ throw new Error("useAiChatContext must be used within an AiChatProvider");
99
+ }
100
+ return context;
101
+ };
102
+ export { useAiChat, AiChatProvider };
@@ -0,0 +1,14 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { AiChatProvider } from "./AiChatContext";
3
+ /**
4
+ * AiChatProvider provides state and functions for managing chat. The higher-level
5
+ * `AiChat` component is a wrapper around this provider and the `AiChatDisplay`,
6
+ * roughly.
7
+ *
8
+ * If you need to access chat state outside of the chat display, you can use
9
+ * `AiChatProvider` directly.
10
+ */
11
+ declare const meta: Meta<typeof AiChatProvider>;
12
+ export default meta;
13
+ type Story = StoryObj<typeof AiChatProvider>;
14
+ export declare const StreamingResponses: Story;
@@ -0,0 +1,72 @@
1
+ import * as React from "react";
2
+ import { AiChatDisplay } from "./AiChat";
3
+ import { AiChatProvider, useAiChat } from "./AiChatContext";
4
+ import styled from "@emotion/styled";
5
+ import { handlers } from "./test-utils/api";
6
+ import Typography from "@mui/material/Typography";
7
+ const TEST_API_STREAMING = "http://localhost:4567/streaming";
8
+ const INITIAL_MESSAGES = [
9
+ {
10
+ content: "Hi! What are you interested in learning about?",
11
+ role: "assistant",
12
+ },
13
+ ];
14
+ const STARTERS = [
15
+ { content: "I'm interested in quantum computing" },
16
+ { content: "I want to understand global warming. " },
17
+ { content: "I am curious about AI applications for business" },
18
+ ];
19
+ const Container = styled.div({
20
+ width: "100%",
21
+ height: "400px",
22
+ position: "relative",
23
+ });
24
+ const MessageCounter = () => {
25
+ const { messages } = useAiChat();
26
+ return (React.createElement(Typography, { variant: "subtitle1" },
27
+ "Message count: ",
28
+ messages.length,
29
+ " (Provided by ",
30
+ React.createElement("code", null, "AiChatContext"),
31
+ ")"));
32
+ };
33
+ /**
34
+ * AiChatProvider provides state and functions for managing chat. The higher-level
35
+ * `AiChat` component is a wrapper around this provider and the `AiChatDisplay`,
36
+ * roughly.
37
+ *
38
+ * If you need to access chat state outside of the chat display, you can use
39
+ * `AiChatProvider` directly.
40
+ */
41
+ const meta = {
42
+ title: "smoot-design/AI/AiChatContext",
43
+ component: AiChatProvider,
44
+ parameters: {
45
+ msw: { handlers },
46
+ },
47
+ render: (args) => {
48
+ return (React.createElement(AiChatProvider, Object.assign({}, args),
49
+ React.createElement(MessageCounter, null),
50
+ React.createElement(Container, null,
51
+ React.createElement(AiChatDisplay, { entryScreenEnabled: false, conversationStarters: STARTERS, placeholder: "Type your message here", askTimTitle: "Ask TIM" }))));
52
+ },
53
+ decorators: (Story) => {
54
+ return (React.createElement(Container, null,
55
+ React.createElement(Story, null)));
56
+ },
57
+ args: {
58
+ requestOpts: { apiUrl: TEST_API_STREAMING },
59
+ initialMessages: INITIAL_MESSAGES,
60
+ },
61
+ argTypes: {
62
+ initialMessages: {
63
+ control: { type: "object", disable: true },
64
+ },
65
+ requestOpts: {
66
+ control: { type: "object", disable: true },
67
+ table: { readonly: true }, // See above
68
+ },
69
+ },
70
+ };
71
+ export default meta;
72
+ export const StreamingResponses = {};
@@ -0,0 +1,15 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { AiChat } from "./AiChat";
3
+ declare const meta: Meta<typeof AiChat>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof AiChat>;
6
+ export declare const Typical: Story;
7
+ export declare const Textual: Story;
8
+ export declare const Math: Story;
9
+ export declare const SimpleOrderedList: Story;
10
+ export declare const SimpleUnorderedList: Story;
11
+ export declare const NestedOrderedList: Story;
12
+ export declare const NestedUnorderedList: Story;
13
+ export declare const NestedOrderedUnorderedList: Story;
14
+ export declare const NestedUnorderedOrderedList: Story;
15
+ export declare const UnexpectedList: Story;