@mitodl/smoot-design 0.0.0-7bc0c0f

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 (243) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +31 -0
  3. package/dist/bundles/remoteTutorDrawer.es.js +38347 -0
  4. package/dist/bundles/remoteTutorDrawer.umd.js +207 -0
  5. package/dist/cjs/ai.d.ts +3 -0
  6. package/dist/cjs/ai.js +5 -0
  7. package/dist/cjs/bundles/RemoteTutorDrawer/FlashcardsScreen.d.ts +9 -0
  8. package/dist/cjs/bundles/RemoteTutorDrawer/FlashcardsScreen.js +87 -0
  9. package/dist/cjs/bundles/RemoteTutorDrawer/RemoteTutorDrawer.d.ts +57 -0
  10. package/dist/cjs/bundles/RemoteTutorDrawer/RemoteTutorDrawer.js +276 -0
  11. package/dist/cjs/bundles/RemoteTutorDrawer/RemoteTutorDrawer.stories.d.ts +16 -0
  12. package/dist/cjs/bundles/RemoteTutorDrawer/RemoteTutorDrawer.stories.js +283 -0
  13. package/dist/cjs/bundles/RemoteTutorDrawer/RemoteTutorDrawer.test.d.ts +1 -0
  14. package/dist/cjs/bundles/RemoteTutorDrawer/RemoteTutorDrawer.test.js +241 -0
  15. package/dist/cjs/bundles/remoteTutorDrawer.d.ts +7 -0
  16. package/dist/cjs/bundles/remoteTutorDrawer.js +40 -0
  17. package/dist/cjs/components/AiChat/AiChat.d.ts +5 -0
  18. package/dist/cjs/components/AiChat/AiChat.js +247 -0
  19. package/dist/cjs/components/AiChat/AiChat.stories.d.ts +21 -0
  20. package/dist/cjs/components/AiChat/AiChat.stories.js +243 -0
  21. package/dist/cjs/components/AiChat/AiChat.test.d.ts +1 -0
  22. package/dist/cjs/components/AiChat/AiChat.test.js +211 -0
  23. package/dist/cjs/components/AiChat/ChatTitle.d.ts +8 -0
  24. package/dist/cjs/components/AiChat/ChatTitle.js +43 -0
  25. package/dist/cjs/components/AiChat/EntryScreen.d.ts +11 -0
  26. package/dist/cjs/components/AiChat/EntryScreen.js +123 -0
  27. package/dist/cjs/components/AiChat/TimLogo.d.ts +5 -0
  28. package/dist/cjs/components/AiChat/TimLogo.js +15 -0
  29. package/dist/cjs/components/AiChat/test-utils/api.d.ts +2 -0
  30. package/dist/cjs/components/AiChat/test-utils/api.js +125 -0
  31. package/dist/cjs/components/AiChat/types.d.ts +82 -0
  32. package/dist/cjs/components/AiChat/types.js +3 -0
  33. package/dist/cjs/components/AiChat/utils.d.ts +9 -0
  34. package/dist/cjs/components/AiChat/utils.js +41 -0
  35. package/dist/cjs/components/Alert/Alert.d.ts +15 -0
  36. package/dist/cjs/components/Alert/Alert.js +62 -0
  37. package/dist/cjs/components/Alert/Alert.stories.d.ts +8 -0
  38. package/dist/cjs/components/Alert/Alert.stories.js +53 -0
  39. package/dist/cjs/components/Button/ActionButton.d.ts +30 -0
  40. package/dist/cjs/components/Button/ActionButton.js +73 -0
  41. package/dist/cjs/components/Button/ActionButton.stories.d.ts +15 -0
  42. package/dist/cjs/components/Button/ActionButton.stories.js +113 -0
  43. package/dist/cjs/components/Button/Button.d.ts +54 -0
  44. package/dist/cjs/components/Button/Button.js +240 -0
  45. package/dist/cjs/components/Button/Button.stories.d.ts +17 -0
  46. package/dist/cjs/components/Button/Button.stories.js +135 -0
  47. package/dist/cjs/components/Button/Button.test.d.ts +1 -0
  48. package/dist/cjs/components/Button/Button.test.js +46 -0
  49. package/dist/cjs/components/ImageAdapter/ImageAdapter.d.ts +23 -0
  50. package/dist/cjs/components/ImageAdapter/ImageAdapter.js +30 -0
  51. package/dist/cjs/components/Input/Input.d.ts +116 -0
  52. package/dist/cjs/components/Input/Input.js +237 -0
  53. package/dist/cjs/components/Input/Input.stories.d.ts +19 -0
  54. package/dist/cjs/components/Input/Input.stories.js +135 -0
  55. package/dist/cjs/components/Input/Input.test.d.ts +1 -0
  56. package/dist/cjs/components/Input/Input.test.js +32 -0
  57. package/dist/cjs/components/LinkAdapter/LinkAdapter.d.ts +23 -0
  58. package/dist/cjs/components/LinkAdapter/LinkAdapter.js +34 -0
  59. package/dist/cjs/components/ScrollSnap/ScrollSnap.d.ts +19 -0
  60. package/dist/cjs/components/ScrollSnap/ScrollSnap.js +59 -0
  61. package/dist/cjs/components/ScrollSnap/ScrollSnap.stories.d.ts +6 -0
  62. package/dist/cjs/components/ScrollSnap/ScrollSnap.stories.js +43 -0
  63. package/dist/cjs/components/ScrollSnap/useScrollSnap.d.ts +6 -0
  64. package/dist/cjs/components/ScrollSnap/useScrollSnap.js +36 -0
  65. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.d.ts +25 -0
  66. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.js +43 -0
  67. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.stories.d.ts +6 -0
  68. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.stories.js +44 -0
  69. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.test.d.ts +1 -0
  70. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.test.js +62 -0
  71. package/dist/cjs/components/TabButtons/TabButtonList.d.ts +25 -0
  72. package/dist/cjs/components/TabButtons/TabButtonList.js +97 -0
  73. package/dist/cjs/components/TabButtons/TabButtonList.stories.d.ts +24 -0
  74. package/dist/cjs/components/TabButtons/TabButtonList.stories.js +139 -0
  75. package/dist/cjs/components/TextField/TextField.d.ts +29 -0
  76. package/dist/cjs/components/TextField/TextField.js +33 -0
  77. package/dist/cjs/components/TextField/TextField.stories.d.ts +10 -0
  78. package/dist/cjs/components/TextField/TextField.stories.js +136 -0
  79. package/dist/cjs/components/TextField/TextField.test.d.ts +1 -0
  80. package/dist/cjs/components/TextField/TextField.test.js +77 -0
  81. package/dist/cjs/components/ThemeProvider/ThemeProvider.d.ts +21 -0
  82. package/dist/cjs/components/ThemeProvider/ThemeProvider.js +86 -0
  83. package/dist/cjs/components/ThemeProvider/ThemeProvider.stories.d.ts +63 -0
  84. package/dist/cjs/components/ThemeProvider/ThemeProvider.stories.js +102 -0
  85. package/dist/cjs/components/ThemeProvider/Typography.stories.d.ts +39 -0
  86. package/dist/cjs/components/ThemeProvider/Typography.stories.js +65 -0
  87. package/dist/cjs/components/ThemeProvider/breakpoints.d.ts +4 -0
  88. package/dist/cjs/components/ThemeProvider/breakpoints.js +19 -0
  89. package/dist/cjs/components/ThemeProvider/buttons.d.ts +7 -0
  90. package/dist/cjs/components/ThemeProvider/buttons.js +20 -0
  91. package/dist/cjs/components/ThemeProvider/chips.d.ts +3 -0
  92. package/dist/cjs/components/ThemeProvider/chips.js +154 -0
  93. package/dist/cjs/components/ThemeProvider/colors.d.ts +32 -0
  94. package/dist/cjs/components/ThemeProvider/colors.js +35 -0
  95. package/dist/cjs/components/ThemeProvider/typography.d.ts +18 -0
  96. package/dist/cjs/components/ThemeProvider/typography.js +173 -0
  97. package/dist/cjs/components/VisuallyHidden/VisuallyHidden.d.ts +24 -0
  98. package/dist/cjs/components/VisuallyHidden/VisuallyHidden.js +33 -0
  99. package/dist/cjs/components/VisuallyHidden/VisuallyHidden.stories.d.ts +6 -0
  100. package/dist/cjs/components/VisuallyHidden/VisuallyHidden.stories.js +13 -0
  101. package/dist/cjs/components/internal/FormHelpers/FormHelpers.d.ts +39 -0
  102. package/dist/cjs/components/internal/FormHelpers/FormHelpers.js +78 -0
  103. package/dist/cjs/components/internal/FormHelpers/FormHelpers.test.d.ts +1 -0
  104. package/dist/cjs/components/internal/FormHelpers/FormHelpers.test.js +93 -0
  105. package/dist/cjs/index.d.ts +16 -0
  106. package/dist/cjs/index.js +30 -0
  107. package/dist/cjs/jest-setup.d.ts +1 -0
  108. package/dist/cjs/jest-setup.js +18 -0
  109. package/dist/cjs/jsdom-extended.d.ts +6 -0
  110. package/dist/cjs/jsdom-extended.js +14 -0
  111. package/dist/cjs/story-utils/index.d.ts +6 -0
  112. package/dist/cjs/story-utils/index.js +17 -0
  113. package/dist/cjs/utils/composeRefs.d.ts +7 -0
  114. package/dist/cjs/utils/composeRefs.js +20 -0
  115. package/dist/cjs/utils/composeRefs.test.d.ts +1 -0
  116. package/dist/cjs/utils/composeRefs.test.js +19 -0
  117. package/dist/cjs/utils/useDevCheckStable.d.ts +8 -0
  118. package/dist/cjs/utils/useDevCheckStable.js +29 -0
  119. package/dist/cjs/utils/useInterval.d.ts +7 -0
  120. package/dist/cjs/utils/useInterval.js +25 -0
  121. package/dist/esm/ai.d.ts +3 -0
  122. package/dist/esm/ai.js +1 -0
  123. package/dist/esm/bundles/RemoteTutorDrawer/FlashcardsScreen.d.ts +9 -0
  124. package/dist/esm/bundles/RemoteTutorDrawer/FlashcardsScreen.js +83 -0
  125. package/dist/esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.d.ts +57 -0
  126. package/dist/esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.js +273 -0
  127. package/dist/esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.stories.d.ts +16 -0
  128. package/dist/esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.stories.js +280 -0
  129. package/dist/esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.test.d.ts +1 -0
  130. package/dist/esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.test.js +239 -0
  131. package/dist/esm/bundles/remoteTutorDrawer.d.ts +7 -0
  132. package/dist/esm/bundles/remoteTutorDrawer.js +37 -0
  133. package/dist/esm/components/AiChat/AiChat.d.ts +5 -0
  134. package/dist/esm/components/AiChat/AiChat.js +244 -0
  135. package/dist/esm/components/AiChat/AiChat.stories.d.ts +21 -0
  136. package/dist/esm/components/AiChat/AiChat.stories.js +240 -0
  137. package/dist/esm/components/AiChat/AiChat.test.d.ts +1 -0
  138. package/dist/esm/components/AiChat/AiChat.test.js +209 -0
  139. package/dist/esm/components/AiChat/ChatTitle.d.ts +8 -0
  140. package/dist/esm/components/AiChat/ChatTitle.js +40 -0
  141. package/dist/esm/components/AiChat/EntryScreen.d.ts +11 -0
  142. package/dist/esm/components/AiChat/EntryScreen.js +120 -0
  143. package/dist/esm/components/AiChat/TimLogo.d.ts +5 -0
  144. package/dist/esm/components/AiChat/TimLogo.js +13 -0
  145. package/dist/esm/components/AiChat/test-utils/api.d.ts +2 -0
  146. package/dist/esm/components/AiChat/test-utils/api.js +122 -0
  147. package/dist/esm/components/AiChat/types.d.ts +82 -0
  148. package/dist/esm/components/AiChat/types.js +2 -0
  149. package/dist/esm/components/AiChat/utils.d.ts +9 -0
  150. package/dist/esm/components/AiChat/utils.js +38 -0
  151. package/dist/esm/components/Alert/Alert.d.ts +15 -0
  152. package/dist/esm/components/Alert/Alert.js +59 -0
  153. package/dist/esm/components/Alert/Alert.stories.d.ts +8 -0
  154. package/dist/esm/components/Alert/Alert.stories.js +50 -0
  155. package/dist/esm/components/Button/ActionButton.d.ts +30 -0
  156. package/dist/esm/components/Button/ActionButton.js +68 -0
  157. package/dist/esm/components/Button/ActionButton.stories.d.ts +15 -0
  158. package/dist/esm/components/Button/ActionButton.stories.js +110 -0
  159. package/dist/esm/components/Button/Button.d.ts +54 -0
  160. package/dist/esm/components/Button/Button.js +232 -0
  161. package/dist/esm/components/Button/Button.stories.d.ts +17 -0
  162. package/dist/esm/components/Button/Button.stories.js +132 -0
  163. package/dist/esm/components/Button/Button.test.d.ts +1 -0
  164. package/dist/esm/components/Button/Button.test.js +44 -0
  165. package/dist/esm/components/ImageAdapter/ImageAdapter.d.ts +23 -0
  166. package/dist/esm/components/ImageAdapter/ImageAdapter.js +27 -0
  167. package/dist/esm/components/Input/Input.d.ts +116 -0
  168. package/dist/esm/components/Input/Input.js +232 -0
  169. package/dist/esm/components/Input/Input.stories.d.ts +19 -0
  170. package/dist/esm/components/Input/Input.stories.js +132 -0
  171. package/dist/esm/components/Input/Input.test.d.ts +1 -0
  172. package/dist/esm/components/Input/Input.test.js +30 -0
  173. package/dist/esm/components/LinkAdapter/LinkAdapter.d.ts +23 -0
  174. package/dist/esm/components/LinkAdapter/LinkAdapter.js +31 -0
  175. package/dist/esm/components/ScrollSnap/ScrollSnap.d.ts +19 -0
  176. package/dist/esm/components/ScrollSnap/ScrollSnap.js +56 -0
  177. package/dist/esm/components/ScrollSnap/ScrollSnap.stories.d.ts +6 -0
  178. package/dist/esm/components/ScrollSnap/ScrollSnap.stories.js +40 -0
  179. package/dist/esm/components/ScrollSnap/useScrollSnap.d.ts +6 -0
  180. package/dist/esm/components/ScrollSnap/useScrollSnap.js +33 -0
  181. package/dist/esm/components/SrAnnouncer/SrAnnouncer.d.ts +25 -0
  182. package/dist/esm/components/SrAnnouncer/SrAnnouncer.js +40 -0
  183. package/dist/esm/components/SrAnnouncer/SrAnnouncer.stories.d.ts +6 -0
  184. package/dist/esm/components/SrAnnouncer/SrAnnouncer.stories.js +41 -0
  185. package/dist/esm/components/SrAnnouncer/SrAnnouncer.test.d.ts +1 -0
  186. package/dist/esm/components/SrAnnouncer/SrAnnouncer.test.js +60 -0
  187. package/dist/esm/components/TabButtons/TabButtonList.d.ts +25 -0
  188. package/dist/esm/components/TabButtons/TabButtonList.js +92 -0
  189. package/dist/esm/components/TabButtons/TabButtonList.stories.d.ts +24 -0
  190. package/dist/esm/components/TabButtons/TabButtonList.stories.js +136 -0
  191. package/dist/esm/components/TextField/TextField.d.ts +29 -0
  192. package/dist/esm/components/TextField/TextField.js +30 -0
  193. package/dist/esm/components/TextField/TextField.stories.d.ts +10 -0
  194. package/dist/esm/components/TextField/TextField.stories.js +133 -0
  195. package/dist/esm/components/TextField/TextField.test.d.ts +1 -0
  196. package/dist/esm/components/TextField/TextField.test.js +75 -0
  197. package/dist/esm/components/ThemeProvider/ThemeProvider.d.ts +21 -0
  198. package/dist/esm/components/ThemeProvider/ThemeProvider.js +82 -0
  199. package/dist/esm/components/ThemeProvider/ThemeProvider.stories.d.ts +63 -0
  200. package/dist/esm/components/ThemeProvider/ThemeProvider.stories.js +99 -0
  201. package/dist/esm/components/ThemeProvider/Typography.stories.d.ts +39 -0
  202. package/dist/esm/components/ThemeProvider/Typography.stories.js +62 -0
  203. package/dist/esm/components/ThemeProvider/breakpoints.d.ts +4 -0
  204. package/dist/esm/components/ThemeProvider/breakpoints.js +15 -0
  205. package/dist/esm/components/ThemeProvider/buttons.d.ts +7 -0
  206. package/dist/esm/components/ThemeProvider/buttons.js +17 -0
  207. package/dist/esm/components/ThemeProvider/chips.d.ts +3 -0
  208. package/dist/esm/components/ThemeProvider/chips.js +151 -0
  209. package/dist/esm/components/ThemeProvider/colors.d.ts +32 -0
  210. package/dist/esm/components/ThemeProvider/colors.js +32 -0
  211. package/dist/esm/components/ThemeProvider/typography.d.ts +18 -0
  212. package/dist/esm/components/ThemeProvider/typography.js +167 -0
  213. package/dist/esm/components/VisuallyHidden/VisuallyHidden.d.ts +24 -0
  214. package/dist/esm/components/VisuallyHidden/VisuallyHidden.js +30 -0
  215. package/dist/esm/components/VisuallyHidden/VisuallyHidden.stories.d.ts +6 -0
  216. package/dist/esm/components/VisuallyHidden/VisuallyHidden.stories.js +10 -0
  217. package/dist/esm/components/internal/FormHelpers/FormHelpers.d.ts +39 -0
  218. package/dist/esm/components/internal/FormHelpers/FormHelpers.js +73 -0
  219. package/dist/esm/components/internal/FormHelpers/FormHelpers.test.d.ts +1 -0
  220. package/dist/esm/components/internal/FormHelpers/FormHelpers.test.js +91 -0
  221. package/dist/esm/index.d.ts +16 -0
  222. package/dist/esm/index.js +11 -0
  223. package/dist/esm/jest-setup.d.ts +1 -0
  224. package/dist/esm/jest-setup.js +16 -0
  225. package/dist/esm/jsdom-extended.d.ts +6 -0
  226. package/dist/esm/jsdom-extended.js +12 -0
  227. package/dist/esm/story-utils/index.d.ts +6 -0
  228. package/dist/esm/story-utils/index.js +13 -0
  229. package/dist/esm/utils/composeRefs.d.ts +7 -0
  230. package/dist/esm/utils/composeRefs.js +17 -0
  231. package/dist/esm/utils/composeRefs.test.d.ts +1 -0
  232. package/dist/esm/utils/composeRefs.test.js +17 -0
  233. package/dist/esm/utils/useDevCheckStable.d.ts +8 -0
  234. package/dist/esm/utils/useDevCheckStable.js +26 -0
  235. package/dist/esm/utils/useInterval.d.ts +7 -0
  236. package/dist/esm/utils/useInterval.js +22 -0
  237. package/dist/tsconfig.tsbuildinfo +1 -0
  238. package/dist/type-augmentation/TypescriptDocs.mdx +17 -0
  239. package/dist/type-augmentation/imports.d.ts +3 -0
  240. package/dist/type-augmentation/index.d.ts +3 -0
  241. package/dist/type-augmentation/theme.d.ts +105 -0
  242. package/dist/type-augmentation/typography.d.ts +54 -0
  243. package/package.json +155 -0
@@ -0,0 +1,239 @@
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
+ import { act, render, screen } from "@testing-library/react";
11
+ import user from "@testing-library/user-event";
12
+ import { RemoteTutorDrawer } from "./RemoteTutorDrawer";
13
+ import { ThemeProvider } from "../../components/ThemeProvider/ThemeProvider";
14
+ import * as React from "react";
15
+ import { http, HttpResponse } from "msw";
16
+ import { setupServer } from "msw/node";
17
+ const TEST_API_STREAMING = "http://localhost:4567/test";
18
+ const CONTENT_FILE_URL = "http://localhost:4567/api/v1/contentfiles/1";
19
+ const CONTENT_RESPONSE = {
20
+ count: 1,
21
+ next: null,
22
+ previous: null,
23
+ results: [
24
+ {
25
+ id: 1,
26
+ summary: "This is a test summary",
27
+ flashcards: [
28
+ {
29
+ question: "Test question 1?",
30
+ answer: "Test answer 1",
31
+ },
32
+ {
33
+ question: "Test question 2?",
34
+ answer: "Test answer 2",
35
+ },
36
+ {
37
+ question: "Test question 3?",
38
+ answer: "Test answer 3",
39
+ },
40
+ ],
41
+ },
42
+ ],
43
+ };
44
+ class MockResizeObserver {
45
+ constructor() {
46
+ this.observe = jest.fn();
47
+ this.unobserve = jest.fn();
48
+ this.disconnect = jest.fn();
49
+ }
50
+ }
51
+ global.ResizeObserver = MockResizeObserver;
52
+ describe("RemoteTutorDrawer", () => {
53
+ const server = setupServer(http.post(TEST_API_STREAMING, () => __awaiter(void 0, void 0, void 0, function* () {
54
+ return HttpResponse.text("AI Response");
55
+ })), http.get(CONTENT_FILE_URL, () => {
56
+ return HttpResponse.json(CONTENT_RESPONSE);
57
+ }));
58
+ beforeEach(() => {
59
+ jest.resetAllMocks();
60
+ });
61
+ afterEach(() => {
62
+ server.resetHandlers();
63
+ });
64
+ afterAll(() => server.close());
65
+ const setup = (message) => __awaiter(void 0, void 0, void 0, function* () {
66
+ server.listen();
67
+ render(React.createElement(RemoteTutorDrawer, { "data-testid": "remote-tutor-drawer", messageOrigin: "http://localhost:6006", target: "ai-chat" }), { wrapper: ThemeProvider });
68
+ yield screen.findByTestId("remote-tutor-drawer-waiting");
69
+ const event = new MessageEvent("message", {
70
+ data: message,
71
+ origin: "http://localhost:6006",
72
+ });
73
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
74
+ window.dispatchEvent(event);
75
+ yield new Promise((resolve) => setTimeout(resolve, 100));
76
+ }));
77
+ });
78
+ test("Problem drawer opens showing title", () => __awaiter(void 0, void 0, void 0, function* () {
79
+ yield setup({
80
+ type: "smoot-design::tutor-drawer-open",
81
+ payload: {
82
+ blockType: "problem",
83
+ target: "ai-chat",
84
+ title: "Drawer Title",
85
+ chat: {
86
+ apiUrl: TEST_API_STREAMING,
87
+ },
88
+ },
89
+ });
90
+ screen.getByRole("heading", { name: "Drawer Title" });
91
+ }));
92
+ test("Video drawer opens showing chat entry screen and tabs", () => __awaiter(void 0, void 0, void 0, function* () {
93
+ yield setup({
94
+ type: "smoot-design::tutor-drawer-open",
95
+ payload: {
96
+ blockType: "video",
97
+ target: "ai-chat",
98
+ chat: {
99
+ entryScreenTitle: "Entry screen title",
100
+ apiUrl: TEST_API_STREAMING,
101
+ conversationStarters: [
102
+ { content: "Prompt 1" },
103
+ { content: "Prompt 2" },
104
+ { content: "Prompt 3" },
105
+ ],
106
+ },
107
+ summary: {
108
+ apiUrl: CONTENT_FILE_URL,
109
+ },
110
+ },
111
+ });
112
+ screen.getByText("Entry screen title");
113
+ screen.getByRole("tab", { name: "Chat" });
114
+ screen.getByRole("tab", { name: "Flashcards" });
115
+ screen.getByRole("tab", { name: "Summary" });
116
+ screen.getByRole("button", { name: "Prompt 1" });
117
+ screen.getByRole("button", { name: "Prompt 2" });
118
+ screen.getByRole("button", { name: "Prompt 3" });
119
+ }));
120
+ test("Video drawer chat entry screen selects starters from flashcards", () => __awaiter(void 0, void 0, void 0, function* () {
121
+ yield setup({
122
+ type: "smoot-design::tutor-drawer-open",
123
+ payload: {
124
+ blockType: "video",
125
+ target: "ai-chat",
126
+ chat: {
127
+ entryScreenTitle: "Entry screen title",
128
+ apiUrl: TEST_API_STREAMING,
129
+ },
130
+ summary: {
131
+ apiUrl: CONTENT_FILE_URL,
132
+ },
133
+ },
134
+ });
135
+ screen.getByRole("button", { name: "Test question 1?" });
136
+ screen.getByRole("button", { name: "Test question 2?" });
137
+ screen.getByRole("button", { name: "Test question 3?" });
138
+ }));
139
+ test("Video drawer chat entry screen shows default starters where no flashcards are available", server.boundary(() => __awaiter(void 0, void 0, void 0, function* () {
140
+ const contentResponse = JSON.parse(JSON.stringify(CONTENT_RESPONSE));
141
+ contentResponse.results[0].flashcards = null;
142
+ server.use(http.get(CONTENT_FILE_URL, () => {
143
+ return HttpResponse.json(contentResponse);
144
+ }));
145
+ yield setup({
146
+ type: "smoot-design::tutor-drawer-open",
147
+ payload: {
148
+ blockType: "video",
149
+ target: "ai-chat",
150
+ chat: {
151
+ entryScreenTitle: "Entry screen title",
152
+ apiUrl: TEST_API_STREAMING,
153
+ },
154
+ summary: {
155
+ apiUrl: CONTENT_FILE_URL,
156
+ },
157
+ },
158
+ });
159
+ screen.getByRole("button", {
160
+ name: "What are the most important concepts introduced in the video?",
161
+ });
162
+ screen.getByRole("button", {
163
+ name: "What examples are used to illustrate concepts covered in the video?",
164
+ });
165
+ screen.getByRole("button", {
166
+ name: "What are the key terms introduced in this video?",
167
+ });
168
+ })));
169
+ test("Video drawer chat entry screen displays default title", () => __awaiter(void 0, void 0, void 0, function* () {
170
+ yield setup({
171
+ type: "smoot-design::tutor-drawer-open",
172
+ payload: {
173
+ blockType: "video",
174
+ target: "ai-chat",
175
+ chat: {
176
+ apiUrl: TEST_API_STREAMING,
177
+ },
178
+ summary: {
179
+ apiUrl: CONTENT_FILE_URL,
180
+ },
181
+ },
182
+ });
183
+ screen.getByText("What do you want to know about this video?");
184
+ }));
185
+ test("Flashcard shows content and can be click navigated", server.boundary(() => __awaiter(void 0, void 0, void 0, function* () {
186
+ yield setup({
187
+ type: "smoot-design::tutor-drawer-open",
188
+ payload: {
189
+ blockType: "video",
190
+ target: "ai-chat",
191
+ chat: {
192
+ apiUrl: TEST_API_STREAMING,
193
+ },
194
+ summary: {
195
+ apiUrl: CONTENT_FILE_URL,
196
+ },
197
+ },
198
+ });
199
+ yield user.click(screen.getByRole("tab", { name: "Flashcards" }));
200
+ yield user.click(screen.getByText("Q: Test question 1?"));
201
+ screen.getByText("Answer: Test answer 1");
202
+ yield user.click(screen.getByRole("button", { name: "Next card" }));
203
+ yield user.click(screen.getByText("Q: Test question 2?"));
204
+ screen.getByText("Answer: Test answer 2");
205
+ yield user.click(screen.getByRole("button", { name: "Previous card" }));
206
+ screen.getByText("Q: Test question 1?");
207
+ })));
208
+ test("Flashcard shows content and can be keyboard navigated and cycles", server.boundary(() => __awaiter(void 0, void 0, void 0, function* () {
209
+ yield setup({
210
+ type: "smoot-design::tutor-drawer-open",
211
+ payload: {
212
+ blockType: "video",
213
+ target: "ai-chat",
214
+ chat: {
215
+ apiUrl: TEST_API_STREAMING,
216
+ },
217
+ summary: {
218
+ apiUrl: CONTENT_FILE_URL,
219
+ },
220
+ },
221
+ });
222
+ yield user.click(screen.getByRole("tab", { name: "Flashcards" }));
223
+ screen.getByText("Q: Test question 1?");
224
+ yield user.keyboard("{enter}");
225
+ screen.getByText("Answer: Test answer 1");
226
+ yield user.keyboard("{arrowright}");
227
+ screen.getByText("Q: Test question 2?");
228
+ yield user.keyboard("{enter}");
229
+ screen.getByText("Answer: Test answer 2");
230
+ yield user.keyboard("{arrowleft}");
231
+ screen.getByText("Q: Test question 1?");
232
+ yield user.keyboard("{arrowleft}");
233
+ screen.getByText("Q: Test question 3?");
234
+ yield user.keyboard("{arrowright}");
235
+ yield user.keyboard("{arrowright}");
236
+ yield user.keyboard("{arrowright}");
237
+ screen.getByText("Q: Test question 3?");
238
+ })));
239
+ });
@@ -0,0 +1,7 @@
1
+ import type { RemoteTutorDrawerProps } from "./RemoteTutorDrawer/RemoteTutorDrawer";
2
+ /**
3
+ * Renders the RemoteTutorDrawer in an shadow DOM in order to isolate the drawer
4
+ * styles from external stylesheets.
5
+ */
6
+ declare const init: (opts: RemoteTutorDrawerProps) => void;
7
+ export { init };
@@ -0,0 +1,37 @@
1
+ import * as React from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { RemoteTutorDrawer } from "./RemoteTutorDrawer/RemoteTutorDrawer";
4
+ import { ThemeProvider, createTheme, } from "../components/ThemeProvider/ThemeProvider";
5
+ import { CacheProvider } from "@emotion/react";
6
+ import createCache from "@emotion/cache";
7
+ /**
8
+ * Renders the RemoteTutorDrawer in an shadow DOM in order to isolate the drawer
9
+ * styles from external stylesheets.
10
+ */
11
+ const init = (opts) => {
12
+ const container = document.createElement("div");
13
+ document.body.appendChild(container);
14
+ const shadowContainer = container.attachShadow({ mode: "open" });
15
+ const shadowRootEl = document.createElement("div");
16
+ shadowRootEl.id = "smoot-chat-drawer-root";
17
+ shadowContainer.append(shadowRootEl);
18
+ // See https://mui.com/material-ui/customization/shadow-dom/
19
+ // Ensure style tags are rendered in shadow root
20
+ const cache = createCache({
21
+ key: "css",
22
+ prepend: true,
23
+ container: shadowContainer,
24
+ });
25
+ const theme = createTheme({
26
+ components: {
27
+ // Ensure modals, etc, are rendered in shadow root
28
+ MuiPopover: { defaultProps: { container: shadowRootEl } },
29
+ MuiPopper: { defaultProps: { container: shadowRootEl } },
30
+ MuiModal: { defaultProps: { container: shadowRootEl } },
31
+ },
32
+ });
33
+ createRoot(shadowRootEl).render(React.createElement(CacheProvider, { value: cache },
34
+ React.createElement(ThemeProvider, { theme: theme },
35
+ React.createElement(RemoteTutorDrawer, Object.assign({}, opts)))));
36
+ };
37
+ export { init };
@@ -0,0 +1,5 @@
1
+ import type { FC } from "react";
2
+ import type { AiChatProps } from "./types";
3
+ declare const AiChat: FC<AiChatProps>;
4
+ export { AiChat };
5
+ export type { AiChatProps };
@@ -0,0 +1,244 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import * as React from "react";
13
+ import { useEffect, useRef, useState, useMemo } from "react";
14
+ import styled from "@emotion/styled";
15
+ import Typography from "@mui/material/Typography";
16
+ import classNames from "classnames";
17
+ import { RiSendPlaneFill, RiStopFill, RiMoreFill } from "@remixicon/react";
18
+ import { Input, AdornmentButton } from "../Input/Input";
19
+ import { EntryScreen } from "./EntryScreen";
20
+ import Markdown from "react-markdown";
21
+ import { ScrollSnap } from "../ScrollSnap/ScrollSnap";
22
+ import { SrAnnouncer } from "../SrAnnouncer/SrAnnouncer";
23
+ import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden";
24
+ import { Alert } from "../Alert/Alert";
25
+ import { ChatTitle } from "./ChatTitle";
26
+ import { useAiChat } from "./utils";
27
+ import { useScrollSnap } from "../ScrollSnap/useScrollSnap";
28
+ const classes = {
29
+ root: "MitAiChat--root",
30
+ title: "MitAiChat--title",
31
+ entryScreenContainer: "MitAiChat--entryScreenContainer",
32
+ conversationStarter: "MitAiChat--conversationStarter",
33
+ chatScreenContainer: "MitAiChat--chatScreenContainer",
34
+ messagesContainer: "MitAiChat--messagesContainer",
35
+ messageRow: "MitAiChat--messageRow",
36
+ messageRowUser: "MitAiChat--messageRowUser",
37
+ messageRowAssistant: "MitAiChat--messageRowAssistant",
38
+ message: "MitAiChat--message",
39
+ input: "MitAiChat--input",
40
+ bottomSection: "MitAiChat--bottomSection",
41
+ };
42
+ const Container = styled.div();
43
+ const ChatScreen = styled.div(({ externalScroll, theme }) => (Object.assign({ padding: "16px 32px 0", [theme.breakpoints.down("md")]: {
44
+ padding: "16px 16px 0",
45
+ }, boxSizing: "border-box", background: "white", position: "absolute", bottom: 0, top: 0, left: 0, right: 0, zIndex: 1 }, (externalScroll && {
46
+ padding: "0 32px",
47
+ [theme.breakpoints.down("md")]: {
48
+ padding: "0 16px",
49
+ },
50
+ }))));
51
+ const ChatContainer = styled.div(({ externalScroll }) => ({
52
+ width: "100%",
53
+ height: externalScroll ? "auto" : "100%",
54
+ minHeight: externalScroll ? "100%" : "auto",
55
+ display: "flex",
56
+ flexDirection: "column",
57
+ }));
58
+ const MessagesContainer = styled(ScrollSnap)(({ externalScroll }) => ({
59
+ display: "flex",
60
+ flexDirection: "column",
61
+ flex: 1,
62
+ padding: "14px 0",
63
+ overflow: externalScroll ? "visible" : "auto",
64
+ gap: "16px",
65
+ }));
66
+ const MessageRow = styled.div({
67
+ display: "flex",
68
+ width: "100%",
69
+ gap: "10px",
70
+ [`&.${classes.messageRowUser}`]: {
71
+ flexDirection: "row-reverse",
72
+ },
73
+ "> *": {
74
+ minWidth: 0,
75
+ },
76
+ position: "relative",
77
+ });
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": {
79
+ marginTop: 0,
80
+ }, "p:last-of-type": {
81
+ marginBottom: 0,
82
+ }, "ol, ul": {
83
+ paddingInlineStart: "16px",
84
+ marginLeft: "6px",
85
+ "ol, ul": {
86
+ marginLeft: 0,
87
+ },
88
+ li: {
89
+ margin: "16px 0",
90
+ },
91
+ }, a: {
92
+ color: theme.custom.colors.red,
93
+ fontWeight: "normal",
94
+ }, borderRadius: "12px", [`.${classes.messageRowAssistant} &`]: {
95
+ padding: "12px 16px 12px 0",
96
+ }, [`.${classes.messageRowUser} &`]: {
97
+ borderRadius: "8px 0px 8px 8px",
98
+ backgroundColor: theme.custom.colors.lightGray1,
99
+ } })));
100
+ const StarterContainer = styled.div({
101
+ alignSelf: "flex-end",
102
+ alignItems: "end",
103
+ display: "flex",
104
+ flexDirection: "column",
105
+ gap: "12px",
106
+ });
107
+ const Starter = styled.button(({ theme }) => (Object.assign(Object.assign({ border: `1px solid ${theme.custom.colors.lightGray2}`, backgroundColor: theme.custom.colors.white, padding: "8px 16px" }, theme.typography.body3), { cursor: "pointer", boxSizing: "border-box", "&:hover": {
108
+ color: theme.custom.colors.white,
109
+ backgroundColor: theme.custom.colors.silverGrayDark,
110
+ borderColor: "transparent",
111
+ }, borderRadius: "8px" })));
112
+ const StyledSendButton = styled(RiSendPlaneFill)(({ theme }) => ({
113
+ fill: theme.custom.colors.red,
114
+ }));
115
+ const StyledStopButton = styled(RiStopFill)(({ theme }) => ({
116
+ fill: theme.custom.colors.red,
117
+ }));
118
+ const BottomSection = styled.div(({ externalScroll, theme }) => (Object.assign(Object.assign({ padding: "12px 0 16px" }, (externalScroll && {
119
+ position: "sticky",
120
+ bottom: 0,
121
+ background: theme.custom.colors.white,
122
+ })), { "button:focus-visible": {
123
+ outlineOffset: "-1px",
124
+ borderRadius: "7px",
125
+ } })));
126
+ const Disclaimer = styled(Typography)(({ theme }) => ({
127
+ color: theme.custom.colors.silverGrayDark,
128
+ marginTop: "16px",
129
+ textAlign: "center",
130
+ }));
131
+ const AiChat = (_a) => {
132
+ 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
134
+ ;
135
+ const containerRef = useRef(null);
136
+ const messagesContainerRef = useRef(null);
137
+ const [showEntryScreen, setShowEntryScreen] = useState(entryScreenEnabled);
138
+ const chatScreenRef = useRef(null);
139
+ const [initialMessages, setInitialMessages] = useState();
140
+ const promptInputRef = useRef(null);
141
+ const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, stop, error, } = useAiChat(requestOpts, {
142
+ initialMessages,
143
+ id: chatId,
144
+ });
145
+ useScrollSnap({
146
+ scrollElement: scrollElement || messagesContainerRef.current,
147
+ contentElement: scrollElement ? messagesContainerRef.current : null,
148
+ threshold: 200,
149
+ });
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]);
156
+ useEffect(() => {
157
+ var _a, _b;
158
+ if (!showEntryScreen) {
159
+ (_b = (_a = promptInputRef.current) === null || _a === void 0 ? void 0 : _a.querySelector("input")) === null || _b === void 0 ? void 0 : _b.focus();
160
+ }
161
+ }, [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]);
172
+ const showStarters = messages.length === ((initialMessages === null || initialMessages === void 0 ? void 0 : initialMessages.length) || 0);
173
+ const waiting = !showStarters && !error && ((_b = messages[messages.length - 1]) === null || _b === void 0 ? void 0 : _b.role) === "user";
174
+ const stoppable = isLoading && ((_c = messages[messages.length - 1]) === null || _c === void 0 ? void 0 : _c.role) !== "user";
175
+ const scrollToBottom = () => {
176
+ const element = scrollElement || messagesContainerRef.current;
177
+ element === null || element === void 0 ? void 0 : element.scrollBy({
178
+ behavior: "instant",
179
+ top: element === null || element === void 0 ? void 0 : element.scrollHeight,
180
+ });
181
+ };
182
+ const lastMsg = messages[messages.length - 1];
183
+ 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) => {
194
+ if (prompt.trim() === "") {
195
+ return;
196
+ }
197
+ setShowEntryScreen(false);
198
+ append({ role: "user", content: prompt });
199
+ } })) : (React.createElement(ChatScreen, { className: classes.chatScreenContainer, "data-testid": "ai-chat-screen", externalScroll: externalScroll, ref: chatScreenRef },
200
+ React.createElement(ChatContainer, Object.assign({ className: classNames(className, classes.root), externalScroll: externalScroll }, others),
201
+ React.createElement(ChatTitle, { askTimTitle: askTimTitle, externalScroll: externalScroll, className: classNames(className, classes.title) }),
202
+ 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))))),
210
+ 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
+ scrollToBottom();
212
+ append({ role: "user", content: m.content });
213
+ } }, m.content))))) : null,
214
+ waiting ? (React.createElement(MessageRow, { className: classNames(classes.messageRow, classes.messageRowAssistant), key: "loading" },
215
+ React.createElement(Message, null,
216
+ React.createElement(RiMoreFill, null)))) : null,
217
+ error ? (React.createElement(Alert, { severity: "error", closable: true }, "An unexpected error has occurred.")) : null),
218
+ React.createElement(BottomSection, { externalScroll: externalScroll, className: classes.bottomSection },
219
+ React.createElement("form", { onSubmit: (e) => {
220
+ e.preventDefault();
221
+ if (isLoading && stoppable) {
222
+ stop();
223
+ }
224
+ else {
225
+ scrollToBottom();
226
+ handleSubmit(e);
227
+ }
228
+ } },
229
+ React.createElement(Input, { ref: promptInputRef, fullWidth: true, size: "chat", className: classes.input, placeholder: placeholder, name: "message", sx: { flex: 1 }, value: input, onChange: handleInputChange, inputProps: {
230
+ "aria-label": "Ask a question",
231
+ }, endAdornment: isLoading ? (React.createElement(AdornmentButton, { "aria-label": "Stop", onClick: stop, disabled: !stoppable },
232
+ React.createElement(StyledStopButton, null))) : (React.createElement(AdornmentButton, { "aria-label": "Send", type: "submit", onClick: (e) => {
233
+ if (input.trim() === "") {
234
+ e.preventDefault();
235
+ return;
236
+ }
237
+ scrollToBottom();
238
+ handleSubmit(e);
239
+ } },
240
+ React.createElement(StyledSendButton, null))) })),
241
+ React.createElement(Disclaimer, { variant: "body3" }, "AI-generated content may be incorrect.")),
242
+ 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
+ };
244
+ export { AiChat };
@@ -0,0 +1,21 @@
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 StreamingResponses: Story;
7
+ /**
8
+ * Here `AiChat` is used with a non-streaming JSON API. The JSON is converted
9
+ * to text via `parseContent`.
10
+ */
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
+ /**
17
+ * Where a scrollable container exists in the including component, it can be passed in
18
+ * for the AiChat component to use in place of its own height constrained message container
19
+ * overflow scroll. This is useful for cases such as use inside a modal or drawer.
20
+ */
21
+ export declare const ScrollContainer: Story;