@mitodl/smoot-design 4.0.0 → 6.0.0

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 (46) hide show
  1. package/dist/bundles/remoteTutorDrawer.es.js +14071 -13718
  2. package/dist/bundles/remoteTutorDrawer.umd.js +44 -45
  3. package/dist/cjs/bundles/RemoteTutorDrawer/FlashcardsScreen.d.ts +9 -0
  4. package/dist/cjs/bundles/RemoteTutorDrawer/FlashcardsScreen.js +87 -0
  5. package/dist/cjs/bundles/RemoteTutorDrawer/RemoteTutorDrawer.d.ts +5 -3
  6. package/dist/cjs/bundles/RemoteTutorDrawer/RemoteTutorDrawer.js +103 -50
  7. package/dist/cjs/bundles/RemoteTutorDrawer/RemoteTutorDrawer.stories.js +141 -25
  8. package/dist/cjs/components/AiChat/AiChat.d.ts +2 -2
  9. package/dist/cjs/components/AiChat/AiChat.js +121 -122
  10. package/dist/cjs/components/AiChat/AiChat.stories.d.ts +6 -0
  11. package/dist/cjs/components/AiChat/AiChat.stories.js +139 -18
  12. package/dist/cjs/components/AiChat/AiChat.test.js +12 -2
  13. package/dist/cjs/components/AiChat/ChatTitle.d.ts +8 -0
  14. package/dist/cjs/components/AiChat/ChatTitle.js +43 -0
  15. package/dist/cjs/components/AiChat/EntryScreen.d.ts +11 -0
  16. package/dist/cjs/components/AiChat/EntryScreen.js +118 -0
  17. package/dist/cjs/components/AiChat/TimLogo.d.ts +5 -0
  18. package/dist/cjs/components/AiChat/TimLogo.js +15 -0
  19. package/dist/cjs/components/AiChat/types.d.ts +20 -18
  20. package/dist/cjs/components/AiChat/types.js +1 -1
  21. package/dist/cjs/components/Input/Input.js +1 -0
  22. package/dist/cjs/components/ScrollSnap/useScrollSnap.d.ts +6 -0
  23. package/dist/cjs/components/ScrollSnap/useScrollSnap.js +36 -0
  24. package/dist/esm/bundles/RemoteTutorDrawer/FlashcardsScreen.d.ts +9 -0
  25. package/dist/esm/bundles/RemoteTutorDrawer/FlashcardsScreen.js +83 -0
  26. package/dist/esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.d.ts +5 -3
  27. package/dist/esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.js +105 -52
  28. package/dist/esm/bundles/RemoteTutorDrawer/RemoteTutorDrawer.stories.js +141 -25
  29. package/dist/esm/components/AiChat/AiChat.d.ts +2 -2
  30. package/dist/esm/components/AiChat/AiChat.js +119 -120
  31. package/dist/esm/components/AiChat/AiChat.stories.d.ts +6 -0
  32. package/dist/esm/components/AiChat/AiChat.stories.js +138 -17
  33. package/dist/esm/components/AiChat/AiChat.test.js +12 -2
  34. package/dist/esm/components/AiChat/ChatTitle.d.ts +8 -0
  35. package/dist/esm/components/AiChat/ChatTitle.js +40 -0
  36. package/dist/esm/components/AiChat/EntryScreen.d.ts +11 -0
  37. package/dist/esm/components/AiChat/EntryScreen.js +115 -0
  38. package/dist/esm/components/AiChat/TimLogo.d.ts +5 -0
  39. package/dist/esm/components/AiChat/TimLogo.js +13 -0
  40. package/dist/esm/components/AiChat/types.d.ts +20 -18
  41. package/dist/esm/components/AiChat/types.js +1 -1
  42. package/dist/esm/components/Input/Input.js +1 -0
  43. package/dist/esm/components/ScrollSnap/useScrollSnap.d.ts +6 -0
  44. package/dist/esm/components/ScrollSnap/useScrollSnap.js +33 -0
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +2 -2
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntryScreen = void 0;
4
+ const React = require("react");
5
+ const react_1 = require("@remixicon/react");
6
+ const styled_1 = require("@emotion/styled");
7
+ const Typography_1 = require("@mui/material/Typography");
8
+ const Input_1 = require("../Input/Input");
9
+ const TimLogo_1 = require("./TimLogo");
10
+ const react_2 = require("react");
11
+ const Container = styled_1.default.div(({ theme }) => ({
12
+ display: "flex",
13
+ flexDirection: "column",
14
+ alignItems: "center",
15
+ gap: "16px",
16
+ padding: "114px 32px 24px",
17
+ [theme.breakpoints.down("md")]: {
18
+ padding: "114px 16px 24px",
19
+ width: "100%",
20
+ },
21
+ boxSizing: "border-box",
22
+ position: "absolute",
23
+ zIndex: 1,
24
+ background: "white",
25
+ bottom: 0,
26
+ top: 0,
27
+ left: 0,
28
+ right: 0,
29
+ }));
30
+ const TimLogoBox = styled_1.default.div(({ theme }) => ({
31
+ position: "relative",
32
+ padding: "16px",
33
+ border: `1px solid ${theme.custom.colors.lightGray2}`,
34
+ borderRadius: "8px",
35
+ "svg:first-child": {
36
+ fill: theme.custom.colors.red,
37
+ position: "absolute",
38
+ top: "-10px",
39
+ left: "-10px",
40
+ },
41
+ }));
42
+ const StyledTimLogo = (0, styled_1.default)(TimLogo_1.default)({
43
+ width: "40px",
44
+ height: "40px",
45
+ display: "block",
46
+ });
47
+ const Title = (0, styled_1.default)(Typography_1.default)({
48
+ textAlign: "center",
49
+ padding: "0 40px",
50
+ });
51
+ const StyledInput = (0, styled_1.default)(Input_1.Input)({
52
+ borderRadius: "8px",
53
+ margin: "8px 0 24px 0",
54
+ flexShrink: 0,
55
+ });
56
+ const SendIcon = (0, styled_1.default)(react_1.RiSendPlaneFill)(({ theme }) => ({
57
+ fill: theme.custom.colors.red,
58
+ "button:disabled &": {
59
+ fill: theme.custom.colors.silverGray,
60
+ },
61
+ }));
62
+ const Starters = styled_1.default.div(({ theme }) => ({
63
+ display: "flex",
64
+ gap: "16px",
65
+ width: "100%",
66
+ [theme.breakpoints.down("sm")]: {
67
+ flexDirection: "column",
68
+ },
69
+ }));
70
+ const Starter = styled_1.default.button(({ theme }) => ({
71
+ flex: 1,
72
+ display: "flex",
73
+ alignItems: "center",
74
+ borderRadius: "8px",
75
+ border: `1px solid ${theme.custom.colors.lightGray2}`,
76
+ padding: "12px 16px",
77
+ color: theme.custom.colors.darkGray2,
78
+ backgroundColor: "transparent",
79
+ textAlign: "left",
80
+ [theme.breakpoints.down("sm")]: {
81
+ textAlign: "center",
82
+ padding: "12px 36px",
83
+ },
84
+ ":hover": {
85
+ cursor: "pointer",
86
+ borderColor: "transparent",
87
+ color: theme.custom.colors.white,
88
+ backgroundColor: theme.custom.colors.darkGray1,
89
+ },
90
+ }));
91
+ const EntryScreen = ({ title, conversationStarters, className, onPromptSubmit, }) => {
92
+ const [prompt, setPrompt] = (0, react_2.useState)("");
93
+ const onPromptChange = (e) => {
94
+ setPrompt(e.target.value);
95
+ };
96
+ const onPromptKeyDown = (e) => {
97
+ if (e.key === "Enter" && prompt) {
98
+ onPromptSubmit(prompt);
99
+ }
100
+ else {
101
+ setPrompt(prompt);
102
+ }
103
+ };
104
+ return (React.createElement(Container, { className: className, "data-testid": "ai-chat-entry-screen" },
105
+ React.createElement(TimLogoBox, null,
106
+ React.createElement(react_1.RiSparkling2Line, null),
107
+ React.createElement(StyledTimLogo, null)),
108
+ title ? React.createElement(Title, { variant: "h4" }, title) : null,
109
+ React.createElement(StyledInput, { fullWidth: true, size: "chat", onChange: onPromptChange, onKeyDown: onPromptKeyDown, endAdornment: React.createElement(Input_1.AdornmentButton, { "aria-label": "Send", onClick: () => onPromptSubmit(prompt) },
110
+ React.createElement(SendIcon, null)), responsive: true }),
111
+ React.createElement(Starters, null, conversationStarters === null || conversationStarters === void 0 ? void 0 : conversationStarters.map(({ content }, index) => (React.createElement(Starter, { key: index, onClick: () => onPromptSubmit(content), tabIndex: index, onKeyDown: (e) => {
112
+ if (e.key === "Enter") {
113
+ onPromptSubmit(content);
114
+ }
115
+ } },
116
+ React.createElement(Typography_1.default, { variant: "body2" }, content)))))));
117
+ };
118
+ exports.EntryScreen = EntryScreen;
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ declare const TimLogo: ({ className }: {
3
+ className?: string;
4
+ }) => React.JSX.Element;
5
+ export default TimLogo;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const React = require("react");
4
+ const TimLogo = ({ className }) => (React.createElement("svg", { className: className, width: "40", height: "40", viewBox: "0 0 40 40", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
5
+ React.createElement("g", { clipPath: "url(#clip0_16894_46778)" },
6
+ React.createElement("path", { d: "M32.5546 5.17074C33.7099 6.67302 32.4042 7.80224 32.2498 7.94865L32.9819 8.97157C33.7621 8.23748 34.8612 6.35612 33.4291 4.49682C32.7933 3.67046 31.8246 3.32347 30.6251 3.49396C29.7868 3.6143 29.1209 3.95327 29.0928 3.96731L29.6102 4.74753C29.9974 4.57504 31.6059 3.93321 32.5546 5.16873V5.17074Z", fill: "#40464C" }),
7
+ React.createElement("path", { d: "M8.63481 5.04446L9.0881 4.22412C9.06002 4.21209 8.36805 3.92728 7.52163 3.87513C6.31219 3.80292 5.37351 4.22412 4.80589 5.10062C3.52825 7.07023 4.7738 8.85532 5.61219 9.52523L6.25803 8.44616C6.09356 8.31177 4.69959 7.29086 5.73053 5.70033C6.57895 4.3926 8.23166 4.90406 8.63079 5.04446H8.63481Z", fill: "#40464C" }),
8
+ React.createElement("path", { d: "M25.1577 7.25851C25.1778 7.25851 25.1978 7.25851 25.2179 7.25851C27.4382 7.29862 28.6296 8.96136 28.6296 8.96136L29.129 8.81896C29.109 8.75076 28.9144 8.12297 28.3468 7.48315C27.8133 6.88344 26.8425 6.16138 25.2399 6.1333C25.2119 6.1333 25.1858 6.1333 25.1577 6.1333C22.7168 6.1333 21.6056 8.07483 21.3589 9.11179L22.4721 9.37053C22.4941 9.28428 23.0216 7.2565 25.1577 7.2565V7.25851Z", fill: "#40464C" }),
9
+ React.createElement("path", { d: "M9.90619 9.34454C9.92625 9.2583 10.4357 7.23252 12.5898 7.23252C12.6099 7.23252 12.63 7.23252 12.65 7.23252C14.8703 7.27063 15.9213 8.97549 15.9213 8.97549L16.5612 8.79498C16.5411 8.72478 16.3465 8.09899 15.7789 7.46118C15.2454 6.86147 14.2746 6.13941 12.6721 6.11133C12.646 6.11133 12.6179 6.11133 12.5898 6.11133C10.1489 6.11133 9.03772 8.05286 8.79102 9.08982L9.90619 9.34655V9.34454Z", fill: "#40464C" }),
10
+ React.createElement("path", { d: "M19.6383 26.256L19.1973 26.269L19.363 31.8525L19.804 31.8394L19.6383 26.256Z", fill: "#40464C" }),
11
+ React.createElement("path", { d: "M14.1281 38.5777C14.786 38.9107 16.2321 39.4201 16.2321 39.4201L16.8318 39.5906L17.4616 39.725L17.8748 39.7912C17.8748 39.7912 19.0702 39.9336 19.704 39.9336C19.7562 39.9336 19.8103 39.9336 19.8625 39.9336C20.9255 39.9236 22.7748 39.6508 22.7748 39.6508L23.2782 39.5525L24.3031 39.2837L24.6461 39.1614L26.3068 38.5015L26.5977 38.3451L27.1994 37.984L27.4842 37.7754C27.4842 37.7754 28.6114 36.9411 29.0326 36.5359C29.4137 36.1689 29.6905 35.7797 29.8931 35.3987C30.3323 35.2482 31.6862 34.7689 33.1443 34.0187C33.732 33.7821 34.7429 33.0981 34.8091 33.054C38.0985 30.8718 39.7712 28.3907 39.9718 25.6429C40.3729 20.1592 36.6684 16.9922 35.6836 16.218C35.6415 14.8 35.469 13.0871 35.0137 11.318C35.6054 10.7163 36.173 9.95213 36.4518 9.02148C36.9472 7.36275 36.881 5.67595 36.2612 4.27195C35.5733 2.70949 34.2715 1.60234 32.4945 1.06681C30.6272 0.503205 28.6515 1.0989 27.2616 1.76079C25.1215 0.767959 22.6424 0.166244 19.9989 0.0238384C16.7235 -0.152665 13.8012 0.661656 11.3843 2.11179C9.95424 1.46394 7.56945 0.781999 5.46144 1.60635C3.73251 2.28428 2.52507 3.49373 1.96347 5.10432C1.46004 6.55245 1.52823 8.23925 2.15602 9.85385C2.56519 10.9028 3.50988 11.9338 4.33022 12.6378C4.0093 14.2404 3.89297 15.7366 3.8669 16.922C2.79584 17.8306 -1.06115 21.5793 0.276664 27.3718C0.936544 30.2279 2.92421 32.6528 6.02705 34.3858C6.36201 34.5723 6.709 34.7508 7.06 34.9173L8.38778 35.503C9.49494 35.9562 10.4898 36.2852 11.0012 36.4456C11.5528 36.931 12.1645 37.4004 12.8284 37.8316L14.1281 38.5797V38.5777ZM3.45572 9.54899C1.84714 6.20345 3.8328 3.63012 6.13536 2.72955C8.15111 1.9413 10.9029 3.49974 12.0201 4.09143L11.982 3.05648C14.2104 1.73271 16.9141 0.946468 19.9427 1.10893C22.151 1.22727 24.243 1.68457 26.0922 2.43672L26.2687 3.57195C27.3338 2.89201 29.8449 1.61437 31.9168 2.24016C34.2856 2.95419 36.199 5.26678 35.136 8.82292C34.765 10.0665 33.4472 11.2378 32.8696 11.6931L33.9827 12.1544C34.5945 15.2994 34.2796 18.1856 34.1372 19.1684C33.2426 18.8495 32.0612 18.5607 30.5209 18.3962C30.4547 16.6773 30.1117 12.9106 28.3126 11.2278C27.6106 10.5719 26.7621 10.2289 25.7894 10.2129C24.8186 10.1988 23.9862 10.5137 23.3304 11.1536C22.3716 12.0902 21.5332 13.5885 21.3948 15.8369C21.3447 16.6432 21.3587 17.3813 21.3868 17.9289C20.6186 17.7945 19.7943 17.7223 18.984 17.7203L18.5929 17.7143C17.951 17.7323 17.3714 17.7845 16.8479 17.8607C16.7717 16.1979 16.3926 12.9727 14.6516 11.3662C13.9577 10.7263 13.1213 10.3954 12.1605 10.3794C11.1978 10.3653 10.3815 10.6722 9.73362 11.294C7.87432 13.0831 7.72188 17.1005 7.73592 18.4684C6.76516 18.5767 5.99095 18.7432 5.3772 18.9297C5.27691 18.0191 5.08637 15.5501 5.56975 12.7561L6.26573 12.012C5.65399 11.6028 4.09755 10.8687 3.45974 9.54297L3.45572 9.54899ZM26.4673 13.0008C24.1888 13.1432 24.3011 17.0123 24.3914 18.2197C23.8017 18.2398 23.2842 18.2678 22.8691 18.2939C22.7708 16.4948 22.8951 13.1773 24.2189 11.8856C24.6261 11.4885 25.1155 11.296 25.7152 11.296C25.7312 11.296 25.7473 11.296 25.7613 11.296C26.4091 11.306 26.9567 11.5286 27.4341 11.9759C27.9636 12.4713 28.3968 13.2415 28.7177 14.2424C28.2464 13.4702 27.5364 12.9346 26.4653 13.0008H26.4673ZM27.7931 18.2718L26.9427 18.2257C26.4553 18.2057 25.9779 18.1956 25.5146 18.1896C25.4524 17.614 25.3601 15.9893 26.3309 15.9613C27.6607 15.9232 27.8051 17.4916 27.7931 18.2718ZM14.2364 18.2718L13.386 18.2257C12.8986 18.2057 12.4213 18.1956 11.958 18.1896C11.8958 17.614 11.8035 15.9893 12.7743 15.9613C14.1041 15.9232 14.2485 17.4916 14.2364 18.2718ZM19.2628 25.34C19.7903 25.4463 21.6436 25.9257 23.3143 26.7019C23.2662 27.2675 23.1579 28.1701 22.9112 29.0807C22.5722 30.3342 21.6596 32.2256 21.3347 32.8775C21.0318 32.8915 20.5224 32.9116 20.1252 32.9176C19.5355 32.9256 18.8777 32.9397 18.5347 32.9457C18.1596 32.4242 17.0886 30.8878 16.3946 29.4296C15.9192 28.4328 15.6204 27.5302 15.4599 26.9767C16.8439 26.1082 18.9358 25.4403 19.2628 25.338V25.34ZM10.7325 18.342C10.5881 18.342 10.4416 18.342 10.2892 18.342C9.94221 18.342 9.61528 18.3481 9.30038 18.3601C9.17001 16.7094 9.21614 13.3458 10.6081 12.006C11.0193 11.6109 11.5187 11.4203 12.1325 11.4324C12.7723 11.4424 13.3118 11.659 13.7832 12.0942C14.3528 12.6197 14.8101 13.4641 15.135 14.5713C14.6757 13.6868 13.9396 13.0389 12.7683 13.1111C10.4075 13.2556 10.6462 17.3913 10.7325 18.342ZM38.8686 25.4343C38.644 30.7454 31.6521 33.1182 30.3905 33.5093C30.4065 33.07 30.3524 32.7872 30.3524 32.7872C30.3303 32.8474 28.1561 38.7622 19.8525 38.8445C19.8023 38.8445 19.7562 38.8445 19.706 38.8445C13.3118 38.8445 7.3769 32.0411 7.3769 32.0411C7.68979 32.6027 8.24939 33.4712 9.04967 34.4319C7.42905 33.9686 2.62536 32.1855 1.45201 27.097C0.475229 22.8609 2.56719 19.9125 3.88495 18.5406C3.90902 19.0942 3.94311 19.4412 3.94712 19.5114L3.95114 19.5455C3.25716 19.9446 3.1308 20.2455 3.1308 20.2455C3.16088 20.2334 6.46229 19.387 10.2792 19.4251C11.5067 19.4372 12.4614 19.4833 13.1935 19.5415C13.0089 19.8102 12.9187 20.083 12.9167 20.3417C12.9127 21.4649 15.2393 21.8902 16.5089 22.3354C17.4015 22.6483 18.1536 22.9712 18.5848 23.1678C18.5909 23.6492 18.5989 24.287 18.6009 24.7944C17.7364 25.0251 15.7488 25.6288 14.4972 26.5795L14.439 26.6317C14.445 26.6618 14.5233 27.0108 14.6958 27.5603C13.8754 27.7308 12.9067 27.8471 11.7935 27.8451C7.76801 27.8411 5.18465 26.1844 5.18465 26.1844C5.26287 26.2967 7.17633 28.9282 11.7935 28.9322C12.1786 28.9322 12.5456 28.9182 12.8986 28.8941C13.1915 29.4918 13.713 30.7133 14.4811 31.7984C15.5362 33.2866 16.9562 34.6365 19.8585 34.6365C23.7857 34.6365 25.9779 32.4202 27.1292 29.1649C27.2977 29.1649 27.4641 29.1609 27.6326 29.1549C27.9174 29.1448 28.2022 29.1268 28.4891 29.1007C32.8595 28.6996 34.5925 26.9004 34.6647 26.8222C34.6647 26.8222 30.4406 27.8291 28.3908 28.0176C26.8945 28.156 25.4203 28.0417 24.0845 27.8191C24.2209 26.9145 24.243 26.2285 24.245 26.1423L24.251 26.1343C22.6444 25.3179 20.7209 24.9048 19.8043 24.7403C19.8003 24.1386 19.7903 23.3704 19.7843 22.893C20.1513 22.7005 20.6206 22.4738 21.1461 22.2612C23.0496 21.489 24.8427 21.2624 24.8748 20.0349C24.8828 19.746 24.7645 19.4813 24.5478 19.2406C26.1785 19.2226 28.2745 19.2567 30.2441 19.4592C34.1973 19.8644 36.7025 20.767 36.7185 20.775C36.7185 20.775 36.4598 20.3297 35.5211 19.7962V19.7822C35.5351 19.6859 35.6415 18.9297 35.6776 17.7704C36.9873 18.9979 39.0291 21.5492 38.8646 25.4302L38.8686 25.4343Z", fill: "#40464C" })),
12
+ React.createElement("defs", null,
13
+ React.createElement("clipPath", { id: "clip0_16894_46778" },
14
+ React.createElement("rect", { width: "40", height: "39.9338", fill: "white" })))));
15
+ exports.default = TimLogo;
@@ -1,3 +1,4 @@
1
+ import { RefAttributes } from "react";
1
2
  type Role = "assistant" | "user";
2
3
  type AiChatMessage = {
3
4
  id: string;
@@ -25,24 +26,31 @@ type AiChatProps = {
25
26
  * previously used value will restore the session state.
26
27
  */
27
28
  chatId?: string;
28
- /**
29
- * If provided, renders a title bar.
30
- */
31
- title?: string;
32
29
  /**
33
30
  * If provided, renders the "AskTIM" title motif followed by the text.
34
31
  */
35
32
  askTimTitle?: string;
36
33
  /**
37
- * Placeholder message for chat input
34
+ * Placeholder message for chat input.
38
35
  */
39
36
  placeholder?: string;
37
+ className?: string;
40
38
  /**
41
- * Sends an initial user prompt on first load
39
+ * Set to false to disable the entry screen and load the chat immediately.
40
+ * Defaults to true.
41
+ */
42
+ entryScreenEnabled?: boolean;
43
+ /**
44
+ * Title to display on the entry screen, also the initial assistant message if not overridden by `initialMessages`.
45
+ */
46
+ entryScreenTitle?: string;
47
+ /**
48
+ * Initial messages to display on the chat. If not provided, the entry screen title will be used as the initial message.
49
+ */
50
+ initialMessages?: Omit<AiChatMessage, "id">[];
51
+ /**
52
+ * Prompt suggestions for the user, clickable on the entry screen or in the chat if the entry screen is not enabled.
42
53
  */
43
- onClose?: () => void;
44
- className?: string;
45
- initialMessages: Omit<AiChatMessage, "id">[];
46
54
  conversationStarters?: {
47
55
  content: string;
48
56
  }[];
@@ -67,14 +75,8 @@ type AiChatProps = {
67
75
  ImgComponent?: React.ElementType;
68
76
  /**
69
77
  * Where the scroll container is provided by the component,
70
- * the AiChat will scroll to the bottom when a prompt is submitted.
78
+ * the AiChat will scroll to the bottom as chat messages are added.
71
79
  */
72
- scrollContainer?: HTMLElement | null;
73
- /**
74
- * Provide a ref to the chat component to access the `append` method.
75
- */
76
- ref?: React.Ref<{
77
- append: (message: Omit<AiChatMessage, "id">) => void;
78
- }>;
79
- };
80
+ scrollElement?: HTMLElement | null;
81
+ } & RefAttributes<HTMLDivElement>;
80
82
  export type { RequestOpts, AiChatProps, AiChatMessage };
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- // Some of these are based on (compatible, but simplfied / restricted) versions of ai/react types.
2
+ // Some of these are based on (compatible, but simplified / restricted) versions of ai/react types.
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -88,6 +88,7 @@ const sizeStyles = ({ size, theme, multiline }) => (0, react_1.css)([
88
88
  padding: "0 16px",
89
89
  borderRadius: "8px",
90
90
  borderColor: theme.custom.colors.silverGrayLight,
91
+ backgroundColor: theme.custom.colors.white,
91
92
  "&:hover:not(.Mui-disabled), &.Mui-focused, :hover:not(.Mui-disabled):not(.Mui-focused)": {
92
93
  boxShadow: "0px 8px 20px 0px rgba(120, 147, 172, 0.10)",
93
94
  borderColor: theme.custom.colors.silverGray,
@@ -0,0 +1,6 @@
1
+ declare const useScrollSnap: ({ scrollElement, contentElement, threshold, }: {
2
+ scrollElement: HTMLElement | null;
3
+ contentElement?: HTMLElement | null;
4
+ threshold?: number;
5
+ }) => void;
6
+ export { useScrollSnap };
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useScrollSnap = void 0;
4
+ const react_1 = require("react");
5
+ /**
6
+ * Returns the distance between visible content and the bottom of the element.
7
+ */
8
+ const distanceFromBottom = (el) => {
9
+ return el.scrollHeight - el.clientHeight - el.scrollTop;
10
+ };
11
+ /**
12
+ * Scrolls to the bottom of the element.
13
+ */
14
+ const scrollToBottom = (el) => {
15
+ el.scrollTop = el.scrollHeight;
16
+ };
17
+ const useScrollSnap = ({ scrollElement, contentElement, threshold = 2, }) => {
18
+ (0, react_1.useEffect)(() => {
19
+ const onGrow = () => {
20
+ if (!scrollElement)
21
+ return;
22
+ if (distanceFromBottom(scrollElement) < threshold) {
23
+ scrollToBottom(scrollElement);
24
+ }
25
+ };
26
+ onGrow();
27
+ const resizeObserver = new ResizeObserver(onGrow);
28
+ if (contentElement) {
29
+ resizeObserver.observe(contentElement);
30
+ }
31
+ return () => {
32
+ resizeObserver.disconnect();
33
+ };
34
+ }, [scrollElement, contentElement, threshold]);
35
+ };
36
+ exports.useScrollSnap = useScrollSnap;
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ export type Flashcard = {
3
+ question: string;
4
+ answer: string;
5
+ };
6
+ export declare const FlashcardsScreen: ({ flashcards, wasKeyboardFocus, }: {
7
+ flashcards?: Flashcard[];
8
+ wasKeyboardFocus: boolean;
9
+ }) => React.JSX.Element | null;
@@ -0,0 +1,83 @@
1
+ import { ActionButton } from "../../components/Button/ActionButton";
2
+ import Typography from "@mui/material/Typography";
3
+ import * as React from "react";
4
+ import { useState, useCallback, useEffect, useRef } from "react";
5
+ import styled from "@emotion/styled";
6
+ import { RiArrowRightLine, RiArrowLeftLine } from "@remixicon/react";
7
+ const Container = styled.div ``;
8
+ const FlashcardContainer = styled.div(({ theme }) => ({
9
+ display: "flex",
10
+ height: 300,
11
+ padding: 40,
12
+ flexDirection: "column",
13
+ justifyContent: "center",
14
+ alignItems: "center",
15
+ alignSelf: "stretch",
16
+ borderRadius: 8,
17
+ border: `1px solid ${theme.custom.colors.lightGray2}`,
18
+ marginTop: "8px",
19
+ cursor: "pointer",
20
+ textAlign: "center",
21
+ }));
22
+ const Navigation = styled.div({
23
+ display: "flex",
24
+ justifyContent: "space-between",
25
+ alignItems: "center",
26
+ width: "100%",
27
+ marginTop: "24px",
28
+ });
29
+ const Page = styled.div(({ theme }) => (Object.assign({ color: theme.custom.colors.silverGrayDark }, theme.typography.body2)));
30
+ const Flashcard = React.forwardRef(({ content, "aria-label": ariaLabel }, ref) => {
31
+ const [screen, setScreen] = useState(0);
32
+ useEffect(() => setScreen(0), [content]);
33
+ const handleKeyDown = (e) => {
34
+ if (e.key === "Enter" || e.key === " ") {
35
+ setScreen(screen === 0 ? 1 : 0);
36
+ }
37
+ };
38
+ return (React.createElement(FlashcardContainer, { ref: ref, onClick: () => setScreen(screen === 0 ? 1 : 0), onKeyDown: handleKeyDown, tabIndex: 0, "aria-label": ariaLabel },
39
+ React.createElement(Typography, { variant: "h5" }, screen === 0 ? `Q: ${content.question}` : `Answer: ${content.answer}`)));
40
+ });
41
+ Flashcard.displayName = "Flashcard";
42
+ export const FlashcardsScreen = ({ flashcards, wasKeyboardFocus, }) => {
43
+ const [cardIndex, setCardIndex] = useState(0);
44
+ const containerRef = useRef(null);
45
+ const flashcardRef = useRef(null);
46
+ const handleKeyDown = useCallback((e) => {
47
+ var _a;
48
+ if (!flashcards)
49
+ return;
50
+ if (!((_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement)) &&
51
+ wasKeyboardFocus) {
52
+ return;
53
+ }
54
+ if (e.key === "ArrowRight") {
55
+ setCardIndex((prevIndex) => (prevIndex + 1) % flashcards.length);
56
+ }
57
+ else if (e.key === "ArrowLeft") {
58
+ setCardIndex((prevIndex) => (prevIndex - 1 + flashcards.length) % flashcards.length);
59
+ }
60
+ }, [flashcards, wasKeyboardFocus]);
61
+ useEffect(() => {
62
+ var _a;
63
+ (_a = flashcardRef.current) === null || _a === void 0 ? void 0 : _a.focus();
64
+ }, [cardIndex]);
65
+ useEffect(() => {
66
+ window.addEventListener("keydown", handleKeyDown);
67
+ return () => window.removeEventListener("keydown", handleKeyDown);
68
+ }, [handleKeyDown]);
69
+ if (!flashcards) {
70
+ return null;
71
+ }
72
+ return (React.createElement(Container, { ref: containerRef },
73
+ React.createElement(Flashcard, { ref: flashcardRef, content: flashcards[cardIndex], "aria-label": `Flashcard ${cardIndex + 1} of ${flashcards.length}` }),
74
+ React.createElement(Navigation, null,
75
+ React.createElement(ActionButton, { onClick: () => setCardIndex(cardIndex - 1), disabled: cardIndex === 0, variant: "secondary", color: "secondary", size: "small" },
76
+ React.createElement(RiArrowLeftLine, { "aria-hidden": true })),
77
+ React.createElement(Page, null,
78
+ cardIndex + 1,
79
+ " / ",
80
+ flashcards.length),
81
+ React.createElement(ActionButton, { onClick: () => setCardIndex(cardIndex + 1), disabled: cardIndex === flashcards.length - 1, variant: "secondary", color: "secondary", size: "small" },
82
+ React.createElement(RiArrowRightLine, { "aria-hidden": true })))));
83
+ };
@@ -6,21 +6,23 @@ type RemoteTutorDrawerInitMessage = {
6
6
  payload: {
7
7
  blockType?: "problem" | "video";
8
8
  target?: string;
9
+ /**
10
+ * If the title begins "AskTIM", it is styled as the AskTIM logo.
11
+ */
12
+ title?: string;
9
13
  chat: {
10
14
  chatId?: AiChatProps["chatId"];
11
- askTimTitle?: AiChatProps["title"];
12
15
  conversationStarters?: AiChatProps["conversationStarters"];
13
16
  initialMessages: AiChatProps["initialMessages"];
14
17
  apiUrl: AiChatProps["requestOpts"]["apiUrl"];
15
18
  requestBody?: Record<string, unknown>;
16
19
  };
17
20
  summary?: {
18
- contentUrl: string;
21
+ apiUrl: string;
19
22
  };
20
23
  };
21
24
  };
22
25
  type RemoteTutorDrawerProps = {
23
- blockType?: "problem" | "video";
24
26
  className?: string;
25
27
  /**
26
28
  * The origin of the messages that will be received to open the chat.
@@ -8,11 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import * as React from "react";
11
- import { useEffect, useRef, useState } from "react";
11
+ import { useEffect, useState, useRef } from "react";
12
12
  import styled from "@emotion/styled";
13
13
  import Markdown from "react-markdown";
14
14
  import rehypeRaw from "rehype-raw";
15
- import { RiCloseLine } from "@remixicon/react";
15
+ import { RiCloseLine, RiSparkling2Line } from "@remixicon/react";
16
16
  import Drawer from "@mui/material/Drawer";
17
17
  import { TabButtonList, TabButton, } from "../../components/TabButtons/TabButtonList";
18
18
  import Typography from "@mui/material/Typography";
@@ -20,34 +20,72 @@ import TabContext from "@mui/lab/TabContext";
20
20
  import TabPanel from "@mui/lab/TabPanel";
21
21
  import { AiChat } from "../../components/AiChat/AiChat";
22
22
  import { ActionButton } from "../../components/Button/ActionButton";
23
+ import { FlashcardsScreen } from "./FlashcardsScreen";
24
+ const Header = styled.div(({ theme }) => ({
25
+ display: "flex",
26
+ alignItems: "center",
27
+ justifyContent: "space-between",
28
+ gap: "4px",
29
+ color: theme.custom.colors.white,
30
+ position: "sticky",
31
+ top: 0,
32
+ padding: "32px 0 16px 0",
33
+ zIndex: 2,
34
+ backgroundColor: theme.custom.colors.white,
35
+ borderRadius: 0,
36
+ }));
37
+ const Title = styled.div(({ theme }) => ({
38
+ display: "flex",
39
+ alignItems: "center",
40
+ gap: "8px",
41
+ color: theme.custom.colors.darkGray2,
42
+ img: {
43
+ width: "24px",
44
+ height: "24px",
45
+ },
46
+ svg: {
47
+ fill: theme.custom.colors.red,
48
+ width: "24px",
49
+ height: "24px",
50
+ flexShrink: 0,
51
+ },
52
+ overflow: "hidden",
53
+ p: {
54
+ textOverflow: "ellipsis",
55
+ overflow: "hidden",
56
+ whiteSpace: "nowrap",
57
+ },
58
+ }));
23
59
  const CloseButton = styled(ActionButton)(({ theme }) => ({
24
- position: "fixed",
25
- top: "24px",
26
- right: "40px",
27
60
  backgroundColor: theme.custom.colors.lightGray2,
28
61
  "&&:hover": {
29
62
  backgroundColor: theme.custom.colors.red,
30
63
  color: theme.custom.colors.white,
31
64
  },
32
- zIndex: 2,
65
+ zIndex: 3,
66
+ flexShrink: 0,
33
67
  }));
34
68
  const StyledTabButtonList = styled(TabButtonList)(({ theme }) => ({
35
- padding: "80px 0 16px",
69
+ padding: "0 0 16px",
36
70
  backgroundColor: theme.custom.colors.white,
37
71
  position: "sticky",
38
- top: 0,
39
- zIndex: 1,
72
+ top: "84px",
73
+ zIndex: 2,
40
74
  overflow: "visible",
41
75
  }));
42
76
  const StyledTabPanel = styled(TabPanel)({
43
77
  padding: "0",
44
78
  height: "calc(100% - 138px)",
79
+ position: "relative",
45
80
  });
46
- const StyledAiChat = styled(AiChat)({
47
- ".MitAiChat--title": {
48
- paddingTop: "8px",
81
+ const StyledAiChat = styled(AiChat)(({ hasTabs }) => ({
82
+ ".MitAiChat--chatScreenContainer": {
83
+ padding: hasTabs ? 0 : "0 25px 0 40px",
49
84
  },
50
- });
85
+ ".MitAiChat--messagesContainer": {
86
+ paddingTop: hasTabs ? 0 : "88px",
87
+ },
88
+ }));
51
89
  const StyledHTML = styled.div(({ theme }) => (Object.assign(Object.assign({ color: theme.custom.colors.darkGray2, backgroundColor: theme.custom.colors.white, padding: "12px 0 100px" }, theme.typography.body2), { "p:first-of-type": {
52
90
  marginTop: 0,
53
91
  }, "p:last-of-type": {
@@ -67,25 +105,8 @@ const identity = (x) => x;
67
105
  const DEFAULT_FETCH_OPTS = {
68
106
  credentials: "include",
69
107
  };
70
- const parseContent = (contentString) => {
71
- var _a;
72
- try {
73
- const parsed = JSON.parse(contentString);
74
- const content = (_a = parsed[0]) === null || _a === void 0 ? void 0 : _a.content;
75
- const unescaped = content
76
- .replace(/\\n/g, "\n")
77
- .replace(/\\"/g, '"')
78
- .replace(/\\'/g, "'");
79
- return unescaped;
80
- }
81
- catch (e) {
82
- console.warn("Could not parse content:", e);
83
- return contentString;
84
- }
85
- };
86
108
  const useContentFetch = (contentUrl) => {
87
- const [summary, setSummary] = useState(null);
88
- const [error, setError] = useState(null);
109
+ const [response, setResponse] = useState(null);
89
110
  const [loading, setLoading] = useState(false);
90
111
  useEffect(() => {
91
112
  if (!contentUrl)
@@ -95,11 +116,20 @@ const useContentFetch = (contentUrl) => {
95
116
  try {
96
117
  const response = yield fetch(contentUrl);
97
118
  const result = yield response.json();
98
- const parsedContent = parseContent(result.content);
99
- setSummary(parsedContent);
119
+ if (!result.results) {
120
+ throw new Error("Unexpected response");
121
+ }
122
+ const [contentFile] = result.results;
123
+ if (!contentFile) {
124
+ throw new Error("No result found");
125
+ }
126
+ setResponse({
127
+ summary: contentFile.summary,
128
+ flashcards: contentFile.flashcards,
129
+ });
100
130
  }
101
- catch (err) {
102
- setError(err instanceof Error ? err : new Error("Failed to fetch"));
131
+ catch (error) {
132
+ console.error("Error fetching content", error);
103
133
  }
104
134
  finally {
105
135
  setLoading(false);
@@ -107,24 +137,40 @@ const useContentFetch = (contentUrl) => {
107
137
  });
108
138
  fetchData();
109
139
  }, [contentUrl]);
110
- return { summary, error, loading };
140
+ return { response, loading };
111
141
  };
112
- const ChatComponent = ({ payload, transformBody, fetchOpts, }) => {
142
+ const ChatComponent = ({ payload, transformBody, fetchOpts, scrollElement, hasTabs, }) => {
113
143
  if (!payload)
114
144
  return null;
115
- return (React.createElement(StyledAiChat, { chatId: payload.chatId, askTimTitle: payload.askTimTitle, conversationStarters: payload.conversationStarters, initialMessages: payload.initialMessages, requestOpts: {
145
+ return (React.createElement(StyledAiChat, { chatId: payload.chatId, conversationStarters: payload.conversationStarters, initialMessages: payload.initialMessages, entryScreenEnabled: false, scrollElement: scrollElement, requestOpts: {
116
146
  transformBody: (messages) => (Object.assign(Object.assign({}, payload.requestBody), transformBody === null || transformBody === void 0 ? void 0 : transformBody(messages))),
117
147
  apiUrl: payload.apiUrl,
118
148
  fetchOpts: Object.assign(Object.assign({}, DEFAULT_FETCH_OPTS), fetchOpts),
119
- } }));
149
+ }, hasTabs: hasTabs }));
120
150
  };
121
151
  const RemoteTutorDrawer = ({ messageOrigin, transformBody = identity, className, fetchOpts, target, }) => {
122
- var _a;
152
+ var _a, _b, _c;
123
153
  const [open, setOpen] = useState(false);
124
154
  const [payload, setPayload] = useState(null);
125
155
  const [tab, setTab] = useState("chat");
126
- const paperRef = useRef(null);
127
- const { summary } = useContentFetch((_a = payload === null || payload === void 0 ? void 0 : payload.summary) === null || _a === void 0 ? void 0 : _a.contentUrl);
156
+ const { response } = useContentFetch((_a = payload === null || payload === void 0 ? void 0 : payload.summary) === null || _a === void 0 ? void 0 : _a.apiUrl);
157
+ const [_wasKeyboardFocus, setWasKeyboardFocus] = useState(false);
158
+ const mouseInteracted = useRef(false);
159
+ const handleMouseDown = () => {
160
+ mouseInteracted.current = true;
161
+ };
162
+ const handleFocus = () => {
163
+ if (!mouseInteracted.current) {
164
+ setWasKeyboardFocus(true);
165
+ }
166
+ mouseInteracted.current = false;
167
+ };
168
+ const [scrollElement, setScrollElement] = useState(null);
169
+ const paperRefCallback = (node) => {
170
+ if (node) {
171
+ setScrollElement(node);
172
+ }
173
+ };
128
174
  useEffect(() => {
129
175
  const cb = (event) => {
130
176
  if (event.origin !== messageOrigin) {
@@ -150,30 +196,37 @@ const RemoteTutorDrawer = ({ messageOrigin, transformBody = identity, className,
150
196
  const { blockType, chat } = payload;
151
197
  const hasTabs = blockType === "video";
152
198
  return (React.createElement(Drawer, { className: className, PaperProps: {
153
- ref: paperRef,
199
+ ref: paperRefCallback,
154
200
  sx: {
155
201
  width: "900px",
156
202
  maxWidth: "100%",
157
203
  boxSizing: "border-box",
158
204
  scrollbarGutter: "stable",
159
- padding: hasTabs ? "0 25px 24px 40px" : "24px 25px 24px 40px",
160
- ".MitAiChat--title": {
161
- paddingTop: "0px",
162
- },
205
+ padding: "0 25px 0 40px",
163
206
  },
164
207
  }, anchor: "right", open: open, onClose: () => setOpen(false) },
165
- React.createElement(CloseButton, { variant: "text", size: "medium", onClick: () => setOpen(false), "aria-label": "Close" },
166
- React.createElement(RiCloseLine, null)),
167
- blockType === "problem" ? (React.createElement(ChatComponent, { payload: chat, transformBody: transformBody, fetchOpts: fetchOpts })) : null,
208
+ React.createElement(Header, null,
209
+ React.createElement(Title, null,
210
+ React.createElement(RiSparkling2Line, null),
211
+ React.createElement(Typography, { variant: "body1" }, ((_b = payload.title) === null || _b === void 0 ? void 0 : _b.includes("AskTIM")) ? (React.createElement(React.Fragment, null,
212
+ "Ask",
213
+ React.createElement("strong", null, "TIM"),
214
+ payload.title.replace("AskTIM", ""))) : (payload.title))),
215
+ React.createElement(CloseButton, { variant: "text", size: "medium", onClick: () => setOpen(false), "aria-label": "Close" },
216
+ React.createElement(RiCloseLine, null))),
217
+ blockType === "problem" ? (React.createElement(ChatComponent, { payload: chat, transformBody: transformBody, fetchOpts: fetchOpts, scrollElement: scrollElement, hasTabs: hasTabs })) : null,
168
218
  blockType === "video" ? (React.createElement(TabContext, { value: tab },
169
219
  React.createElement(StyledTabButtonList, { styleVariant: "chat", onChange: (_event, val) => setTab(val) },
170
220
  React.createElement(TabButton, { value: "chat", label: "Chat" }),
221
+ React.createElement(TabButton, { value: "flashcards", label: "Flashcards", onMouseDown: handleMouseDown, onFocus: handleFocus }),
171
222
  React.createElement(TabButton, { value: "summary", label: "Summary" })),
172
223
  React.createElement(StyledTabPanel, { value: "chat" },
173
- React.createElement(ChatComponent, { payload: chat, transformBody: transformBody, fetchOpts: fetchOpts })),
224
+ React.createElement(ChatComponent, { payload: chat, transformBody: transformBody, fetchOpts: fetchOpts, scrollElement: scrollElement, hasTabs: hasTabs })),
225
+ React.createElement(StyledTabPanel, { value: "flashcards" },
226
+ React.createElement(FlashcardsScreen, { flashcards: response === null || response === void 0 ? void 0 : response.flashcards, wasKeyboardFocus: _wasKeyboardFocus })),
174
227
  React.createElement(StyledTabPanel, { value: "summary" },
175
228
  React.createElement(Typography, { variant: "h4", component: "h4" }),
176
229
  React.createElement(StyledHTML, null,
177
- React.createElement(Markdown, { rehypePlugins: [rehypeRaw] }, summary))))) : null));
230
+ React.createElement(Markdown, { rehypePlugins: [rehypeRaw] }, (_c = response === null || response === void 0 ? void 0 : response.summary) !== null && _c !== void 0 ? _c : ""))))) : null));
178
231
  };
179
232
  export { RemoteTutorDrawer };