@mitodl/smoot-design 3.1.0 → 3.3.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.
- package/dist/bundles/aiChat.es.js +9230 -9397
- package/dist/bundles/aiChat.umd.js +39 -75
- package/dist/cjs/ai.d.ts +1 -0
- package/dist/cjs/components/AiChat/AiChat.js +93 -99
- package/dist/cjs/components/AiChat/AiChat.stories.js +12 -3
- package/dist/cjs/components/AiChat/AiChat.test.js +1 -1
- package/dist/cjs/components/AiChat/types.d.ts +17 -7
- package/dist/cjs/components/ImageAdapter/ImageAdapter.d.ts +1 -1
- package/dist/cjs/components/ImageAdapter/ImageAdapter.js +1 -1
- package/dist/cjs/components/Input/Input.d.ts +2 -1
- package/dist/cjs/components/Input/Input.js +27 -4
- package/dist/cjs/components/Input/Input.stories.js +1 -0
- package/dist/cjs/components/ScrollSnap/ScrollSnap.js +1 -1
- package/dist/cjs/components/TextField/TextField.stories.js +1 -0
- package/dist/cjs/utils/composeRefs.test.js +2 -2
- package/dist/cjs/utils/useInterval.js +1 -1
- package/dist/esm/ai.d.ts +1 -0
- package/dist/esm/components/AiChat/AiChat.js +95 -101
- package/dist/esm/components/AiChat/AiChat.stories.js +12 -3
- package/dist/esm/components/AiChat/AiChat.test.js +1 -1
- package/dist/esm/components/AiChat/types.d.ts +17 -7
- package/dist/esm/components/ImageAdapter/ImageAdapter.d.ts +1 -1
- package/dist/esm/components/ImageAdapter/ImageAdapter.js +1 -1
- package/dist/esm/components/Input/Input.d.ts +2 -1
- package/dist/esm/components/Input/Input.js +27 -4
- package/dist/esm/components/Input/Input.stories.js +1 -0
- package/dist/esm/components/ScrollSnap/ScrollSnap.js +1 -1
- package/dist/esm/components/TextField/TextField.stories.js +1 -0
- package/dist/esm/utils/composeRefs.test.js +2 -2
- package/dist/esm/utils/useInterval.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type-augmentation/theme.d.ts +1 -0
- package/package.json +2 -2
- package/dist/static/images/mit_mascot_tim.png +0 -0
package/dist/cjs/ai.d.ts
CHANGED
|
@@ -14,7 +14,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.AiChat = void 0;
|
|
15
15
|
const React = require("react");
|
|
16
16
|
const styled_1 = require("@emotion/styled");
|
|
17
|
-
const Skeleton_1 = require("@mui/material/Skeleton");
|
|
18
17
|
const Input_1 = require("../Input/Input");
|
|
19
18
|
const ActionButton_1 = require("../Button/ActionButton");
|
|
20
19
|
const react_1 = require("@remixicon/react");
|
|
@@ -23,19 +22,17 @@ const react_markdown_1 = require("react-markdown");
|
|
|
23
22
|
const ScrollSnap_1 = require("../ScrollSnap/ScrollSnap");
|
|
24
23
|
const classnames_1 = require("classnames");
|
|
25
24
|
const SrAnnouncer_1 = require("../SrAnnouncer/SrAnnouncer");
|
|
26
|
-
const mit_mascot_tim_png_1 = require("../../../static/images/mit_mascot_tim.png");
|
|
27
25
|
const VisuallyHidden_1 = require("../VisuallyHidden/VisuallyHidden");
|
|
28
|
-
const ImageAdapter_1 = require("../ImageAdapter/ImageAdapter");
|
|
29
26
|
const Typography_1 = require("@mui/material/Typography");
|
|
30
27
|
const classes = {
|
|
31
28
|
root: "MitAiChat--root",
|
|
29
|
+
title: "MitAiChat--title",
|
|
32
30
|
conversationStarter: "MitAiChat--conversationStarter",
|
|
33
31
|
messagesContainer: "MitAiChat--messagesContainer",
|
|
34
32
|
messageRow: "MitAiChat--messageRow",
|
|
35
33
|
messageRowUser: "MitAiChat--messageRowUser",
|
|
36
34
|
messageRowAssistant: "MitAiChat--messageRowAssistant",
|
|
37
35
|
message: "MitAiChat--message",
|
|
38
|
-
avatar: "MitAiChat--avatar",
|
|
39
36
|
input: "MitAiChat--input",
|
|
40
37
|
};
|
|
41
38
|
const ChatContainer = styled_1.default.div({
|
|
@@ -44,19 +41,36 @@ const ChatContainer = styled_1.default.div({
|
|
|
44
41
|
display: "flex",
|
|
45
42
|
flexDirection: "column",
|
|
46
43
|
});
|
|
47
|
-
const
|
|
44
|
+
const CloseButton = (0, styled_1.default)(ActionButton_1.ActionButton)(({ theme }) => ({
|
|
45
|
+
"&&:hover": {
|
|
46
|
+
backgroundColor: theme.custom.colors.red,
|
|
47
|
+
color: theme.custom.colors.white,
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
const AskTimTitle = styled_1.default.div(({ theme }) => ({
|
|
51
|
+
display: "flex",
|
|
52
|
+
alignItems: "center",
|
|
53
|
+
gap: "8px",
|
|
54
|
+
color: theme.custom.colors.darkGray2,
|
|
55
|
+
img: {
|
|
56
|
+
width: "24px",
|
|
57
|
+
height: "24px",
|
|
58
|
+
},
|
|
59
|
+
svg: {
|
|
60
|
+
fill: theme.custom.colors.red,
|
|
61
|
+
width: "24px",
|
|
62
|
+
height: "24px",
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
const MessagesContainer = (0, styled_1.default)(ScrollSnap_1.ScrollSnap)({
|
|
48
66
|
display: "flex",
|
|
49
67
|
flexDirection: "column",
|
|
50
68
|
flex: 1,
|
|
51
|
-
|
|
52
|
-
paddingBottom: "
|
|
69
|
+
paddingTop: "14px",
|
|
70
|
+
paddingBottom: "24px",
|
|
53
71
|
overflow: "auto",
|
|
54
72
|
gap: "24px",
|
|
55
|
-
|
|
56
|
-
borderColor: theme.custom.colors.silverGrayLight,
|
|
57
|
-
borderStyle: "solid",
|
|
58
|
-
borderWidth: "0 1px",
|
|
59
|
-
}));
|
|
73
|
+
});
|
|
60
74
|
const MessageRow = styled_1.default.div({
|
|
61
75
|
display: "flex",
|
|
62
76
|
width: "100%",
|
|
@@ -69,45 +83,23 @@ const MessageRow = styled_1.default.div({
|
|
|
69
83
|
},
|
|
70
84
|
position: "relative",
|
|
71
85
|
});
|
|
72
|
-
const
|
|
73
|
-
flexShrink: 0,
|
|
74
|
-
borderRadius: "50%",
|
|
75
|
-
backgroundColor: theme.custom.colors.white,
|
|
76
|
-
display: "flex",
|
|
77
|
-
alignItems: "center",
|
|
78
|
-
justifyContent: "center",
|
|
79
|
-
img: {
|
|
80
|
-
width: "66%",
|
|
81
|
-
// This is the default, but NextJS adds a height attribute to images
|
|
82
|
-
// The attr is useful for aspect ratio, but we want the actual CSS size to
|
|
83
|
-
// be auto.
|
|
84
|
-
height: "auto",
|
|
85
|
-
},
|
|
86
|
-
width: "32px",
|
|
87
|
-
height: "32px",
|
|
88
|
-
position: "absolute",
|
|
89
|
-
top: "-16px",
|
|
90
|
-
[`.${classes.messageRowAssistant} &`]: {
|
|
91
|
-
left: "-10px",
|
|
92
|
-
},
|
|
93
|
-
[`.${classes.messageRowUser} &`]: {
|
|
94
|
-
right: "16px",
|
|
95
|
-
},
|
|
96
|
-
}));
|
|
97
|
-
const Message = styled_1.default.div(({ theme }) => (Object.assign(Object.assign({ border: `1px solid ${theme.custom.colors.silverGrayLight}`, backgroundColor: theme.custom.colors.white, padding: "12px" }, theme.typography.body2), { "p:first-of-type": {
|
|
86
|
+
const Message = styled_1.default.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": {
|
|
98
87
|
marginTop: 0,
|
|
99
88
|
}, "p:last-of-type": {
|
|
100
89
|
marginBottom: 0,
|
|
101
90
|
}, a: {
|
|
102
|
-
color: theme.custom.colors.mitRed,
|
|
103
|
-
textDecoration: "none",
|
|
104
|
-
}, "a:hover": {
|
|
105
91
|
color: theme.custom.colors.red,
|
|
106
|
-
|
|
92
|
+
fontWeight: "normal",
|
|
107
93
|
}, borderRadius: "12px", [`.${classes.messageRowAssistant} &`]: {
|
|
108
|
-
|
|
94
|
+
border: `1px solid ${theme.custom.colors.lightGray2}`,
|
|
95
|
+
borderRadius: "0px 8px 8px 8px",
|
|
96
|
+
svg: {
|
|
97
|
+
fill: theme.custom.colors.silverGrayDark,
|
|
98
|
+
display: "block",
|
|
99
|
+
},
|
|
109
100
|
}, [`.${classes.messageRowUser} &`]: {
|
|
110
|
-
borderRadius: "
|
|
101
|
+
borderRadius: "8px 0px 8px 8px",
|
|
102
|
+
backgroundColor: theme.custom.colors.lightGray1,
|
|
111
103
|
} })));
|
|
112
104
|
const StarterContainer = styled_1.default.div({
|
|
113
105
|
alignSelf: "flex-end",
|
|
@@ -116,74 +108,68 @@ const StarterContainer = styled_1.default.div({
|
|
|
116
108
|
flexDirection: "column",
|
|
117
109
|
gap: "12px",
|
|
118
110
|
});
|
|
119
|
-
const Starter = styled_1.default.button(({ theme }) => (Object.assign(Object.assign({ border: `1px solid ${theme.custom.colors.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
borderRadius: "
|
|
124
|
-
});
|
|
125
|
-
const ActionButtonStyled = (0, styled_1.default)(ActionButton_1.ActionButton)(({ theme }) => ({
|
|
126
|
-
backgroundColor: theme.custom.colors.red,
|
|
127
|
-
flexShrink: 0,
|
|
128
|
-
marginRight: "24px",
|
|
129
|
-
marginLeft: "12px",
|
|
130
|
-
"&:hover:not(:disabled)": {
|
|
131
|
-
backgroundColor: theme.custom.colors.mitRed,
|
|
132
|
-
},
|
|
133
|
-
}));
|
|
134
|
-
const DotsContainer = styled_1.default.span(({ theme }) => ({
|
|
135
|
-
display: "inline-flex",
|
|
136
|
-
gap: "4px",
|
|
137
|
-
".MuiSkeleton-root": {
|
|
138
|
-
backgroundColor: theme.custom.colors.silverGray,
|
|
139
|
-
},
|
|
140
|
-
}));
|
|
141
|
-
const Dots = () => {
|
|
142
|
-
return (React.createElement(DotsContainer, null,
|
|
143
|
-
React.createElement(Skeleton_1.default, { variant: "circular", width: "8px", height: "8px" }),
|
|
144
|
-
React.createElement(Skeleton_1.default, { variant: "circular", width: "8px", height: "8px" }),
|
|
145
|
-
React.createElement(Skeleton_1.default, { variant: "circular", width: "8px", height: "8px" })));
|
|
146
|
-
};
|
|
147
|
-
const CloseButton = (0, styled_1.default)(ActionButton_1.ActionButton)(({ theme }) => ({
|
|
148
|
-
color: "inherit",
|
|
149
|
-
backgroundColor: theme.custom.colors.red,
|
|
150
|
-
"&:hover:not(:disabled)": {
|
|
151
|
-
backgroundColor: theme.custom.colors.mitRed,
|
|
152
|
-
},
|
|
153
|
-
}));
|
|
111
|
+
const Starter = styled_1.default.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": {
|
|
112
|
+
color: theme.custom.colors.white,
|
|
113
|
+
backgroundColor: theme.custom.colors.silverGrayDark,
|
|
114
|
+
borderColor: "transparent",
|
|
115
|
+
}, borderRadius: "8px" })));
|
|
154
116
|
const RobotIcon = (0, styled_1.default)(react_1.RiRobot2Line)({
|
|
155
117
|
width: "40px",
|
|
156
118
|
height: "40px",
|
|
157
119
|
});
|
|
158
|
-
const
|
|
120
|
+
const StyledInput = (0, styled_1.default)(Input_1.Input)(({ theme }) => ({
|
|
121
|
+
backgroundColor: theme.custom.colors.lightGray1,
|
|
122
|
+
borderRadius: "8px",
|
|
123
|
+
border: `1px solid ${theme.custom.colors.lightGray2}`,
|
|
124
|
+
}));
|
|
125
|
+
const StyledSendButton = (0, styled_1.default)(react_1.RiSendPlaneFill)(({ theme }) => ({
|
|
126
|
+
fill: theme.custom.colors.red,
|
|
127
|
+
}));
|
|
128
|
+
const StyledStopButton = (0, styled_1.default)(react_1.RiStopFill)(({ theme }) => ({
|
|
129
|
+
fill: theme.custom.colors.red,
|
|
130
|
+
}));
|
|
131
|
+
const Disclaimer = (0, styled_1.default)(Typography_1.default)(({ theme }) => ({
|
|
132
|
+
color: theme.custom.colors.silverGrayDark,
|
|
133
|
+
marginTop: "16px",
|
|
134
|
+
textAlign: "center",
|
|
135
|
+
}));
|
|
136
|
+
const ChatTitle = (0, styled_1.default)(({ title, askTimTitle, onClose, className }) => {
|
|
159
137
|
return (React.createElement("div", { className: className },
|
|
160
|
-
React.createElement(
|
|
161
|
-
|
|
162
|
-
|
|
138
|
+
askTimTitle ? (React.createElement(AskTimTitle, null,
|
|
139
|
+
React.createElement(react_1.RiSparkling2Line, null),
|
|
140
|
+
React.createElement(Typography_1.default, { variant: "body1" },
|
|
141
|
+
"Ask",
|
|
142
|
+
React.createElement("strong", null, "TIM"),
|
|
143
|
+
"\u00A0",
|
|
144
|
+
askTimTitle))) : null,
|
|
145
|
+
title ? (React.createElement(React.Fragment, null,
|
|
146
|
+
React.createElement(RobotIcon, null),
|
|
147
|
+
React.createElement(Typography_1.default, { flex: 1, variant: "h5" }, title))) : null,
|
|
148
|
+
onClose ? (React.createElement(CloseButton, { variant: "tertiary", edge: "rounded", onClick: onClose, "aria-label": "Close chat" },
|
|
163
149
|
React.createElement(react_1.RiCloseLine, null))) : null));
|
|
164
150
|
})(({ theme }) => ({
|
|
165
|
-
backgroundColor: theme.custom.colors.red,
|
|
166
151
|
display: "flex",
|
|
167
152
|
alignItems: "center",
|
|
168
153
|
justifyContent: "space-between",
|
|
169
|
-
padding: "12px
|
|
154
|
+
padding: "12px 0",
|
|
170
155
|
gap: "16px",
|
|
171
156
|
color: theme.custom.colors.white,
|
|
172
157
|
borderRadius: "8px 8px 0 0",
|
|
173
158
|
}));
|
|
174
159
|
const AiChatInternal = function AiChat(_a) {
|
|
175
|
-
var _b;
|
|
176
|
-
var { chatId, className, conversationStarters, requestOpts, initialMessages: initMsgs, parseContent, srLoadingMessages, title, onClose, ImgComponent, placeholder = "
|
|
160
|
+
var _b, _c;
|
|
161
|
+
var { chatId, className, conversationStarters, requestOpts, initialMessages: initMsgs, parseContent, srLoadingMessages, title, askTimTitle, onClose, ImgComponent, placeholder = "", ref } = _a, others = __rest(_a, ["chatId", "className", "conversationStarters", "requestOpts", "initialMessages", "parseContent", "srLoadingMessages", "title", "askTimTitle", "onClose", "ImgComponent", "placeholder", "ref"]) // Could contain data attributes
|
|
177
162
|
;
|
|
178
163
|
const messagesRef = React.useRef(null);
|
|
179
164
|
const initialMessages = React.useMemo(() => {
|
|
180
165
|
const prefix = Math.random().toString().slice(2);
|
|
181
166
|
return initMsgs.map((m, i) => (Object.assign(Object.assign({}, m), { id: `initial-${prefix}-${i}` })));
|
|
182
167
|
}, [initMsgs]);
|
|
183
|
-
const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, } = (0, utils_1.useAiChat)(requestOpts, {
|
|
168
|
+
const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, stop, } = (0, utils_1.useAiChat)(requestOpts, {
|
|
184
169
|
initialMessages: initialMessages,
|
|
185
170
|
id: chatId,
|
|
186
171
|
});
|
|
172
|
+
React.useImperativeHandle(ref, () => ({ append }), [append]);
|
|
187
173
|
const messages = React.useMemo(() => {
|
|
188
174
|
const initial = initialMessages.map((m) => m.id);
|
|
189
175
|
return unparsed.map((m) => {
|
|
@@ -196,6 +182,7 @@ const AiChatInternal = function AiChat(_a) {
|
|
|
196
182
|
}, [parseContent, unparsed, initialMessages]);
|
|
197
183
|
const showStarters = messages.length === initialMessages.length;
|
|
198
184
|
const waiting = !showStarters && ((_b = messages[messages.length - 1]) === null || _b === void 0 ? void 0 : _b.role) === "user";
|
|
185
|
+
const stoppable = isLoading && ((_c = messages[messages.length - 1]) === null || _c === void 0 ? void 0 : _c.role) !== "user";
|
|
199
186
|
const scrollToBottom = () => {
|
|
200
187
|
var _a;
|
|
201
188
|
(_a = messagesRef.current) === null || _a === void 0 ? void 0 : _a.scrollBy({
|
|
@@ -205,14 +192,12 @@ const AiChatInternal = function AiChat(_a) {
|
|
|
205
192
|
};
|
|
206
193
|
const lastMsg = messages[messages.length - 1];
|
|
207
194
|
return (React.createElement(ChatContainer, Object.assign({ className: (0, classnames_1.default)(className, classes.root) }, others),
|
|
208
|
-
React.createElement(ChatTitle, { title: title, onClose: onClose }),
|
|
195
|
+
React.createElement(ChatTitle, { title: title, askTimTitle: askTimTitle, onClose: onClose, className: (0, classnames_1.default)(className, classes.title) }),
|
|
209
196
|
React.createElement(MessagesContainer, { className: classes.messagesContainer, ref: messagesRef },
|
|
210
197
|
messages.map((m) => (React.createElement(MessageRow, { key: m.id, "data-chat-role": m.role, className: (0, classnames_1.default)(classes.messageRow, {
|
|
211
198
|
[classes.messageRowUser]: m.role === "user",
|
|
212
199
|
[classes.messageRowAssistant]: m.role === "assistant",
|
|
213
200
|
}) },
|
|
214
|
-
m.role === "assistant" ? (React.createElement(Avatar, { className: classes.avatar },
|
|
215
|
-
React.createElement(ImageAdapter_1.ImageAdapter, { src: mit_mascot_tim_png_1.default, alt: "", Component: ImgComponent }))) : null,
|
|
216
201
|
React.createElement(Message, { className: classes.message },
|
|
217
202
|
React.createElement(VisuallyHidden_1.VisuallyHidden, null, m.role === "user" ? "You said: " : "Assistant said: "),
|
|
218
203
|
React.createElement(react_markdown_1.default, { skipHtml: true }, m.content))))),
|
|
@@ -221,16 +206,25 @@ const AiChatInternal = function AiChat(_a) {
|
|
|
221
206
|
append({ role: "user", content: m.content });
|
|
222
207
|
} }, m.content))))) : null,
|
|
223
208
|
waiting ? (React.createElement(MessageRow, { className: (0, classnames_1.default)(classes.messageRow, classes.messageRowAssistant), key: "loading" },
|
|
224
|
-
React.createElement(Avatar, { className: classes.avatar },
|
|
225
|
-
React.createElement(ImageAdapter_1.ImageAdapter, { src: mit_mascot_tim_png_1.default, alt: "", Component: ImgComponent })),
|
|
226
209
|
React.createElement(Message, null,
|
|
227
|
-
React.createElement(
|
|
210
|
+
React.createElement(react_1.RiMoreFill, null)))) : null),
|
|
228
211
|
React.createElement("form", { onSubmit: (e) => {
|
|
229
|
-
|
|
230
|
-
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
if (isLoading && stoppable) {
|
|
214
|
+
stop();
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
scrollToBottom();
|
|
218
|
+
handleSubmit(e);
|
|
219
|
+
}
|
|
231
220
|
} },
|
|
232
|
-
React.createElement(
|
|
233
|
-
React.createElement(
|
|
221
|
+
React.createElement(StyledInput, { fullWidth: true, size: "chat", className: classes.input, placeholder: placeholder, name: "message", sx: { flex: 1 }, value: input, onChange: handleInputChange, endAdornment: isLoading ? (React.createElement(Input_1.AdornmentButton, { "aria-label": "Stop", onClick: stop, disabled: !stoppable },
|
|
222
|
+
React.createElement(StyledStopButton, null))) : (React.createElement(Input_1.AdornmentButton, { "aria-label": "Send", type: "submit", disabled: !input, onClick: (e) => {
|
|
223
|
+
scrollToBottom();
|
|
224
|
+
handleSubmit(e);
|
|
225
|
+
} },
|
|
226
|
+
React.createElement(StyledSendButton, null))) })),
|
|
227
|
+
React.createElement(Disclaimer, { variant: "body3" }, "AI-generated content may be incorrect."),
|
|
234
228
|
React.createElement(SrAnnouncer_1.SrAnnouncer, { isLoading: isLoading, loadingMessages: srLoadingMessages, message: lastMsg.role === "assistant" ? lastMsg.content : "" })));
|
|
235
229
|
};
|
|
236
230
|
const AiChat = (props) => (
|
|
@@ -239,7 +233,7 @@ const AiChat = (props) => (
|
|
|
239
233
|
* hook calls. This can cause strange effects like loading API responses
|
|
240
234
|
* for previous chatId into new chatId.
|
|
241
235
|
*
|
|
242
|
-
* To avoid this, let's
|
|
236
|
+
* To avoid this, let's change the key, this will force React to make a new component
|
|
243
237
|
* not sharing any of the old state.
|
|
244
238
|
*/
|
|
245
239
|
React.createElement(AiChatInternal, Object.assign({ key: props.chatId }, props)));
|
|
@@ -13,6 +13,14 @@ const INITIAL_MESSAGES = [
|
|
|
13
13
|
content: "Hi! What are you interested in learning about?",
|
|
14
14
|
role: "assistant",
|
|
15
15
|
},
|
|
16
|
+
{
|
|
17
|
+
content: "I need to brush up on my Calculus",
|
|
18
|
+
role: "user",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
content: "Great! Do you want to start with the basics, like limits and derivatives, or jump into more advanced topics like integrals and series? Let me know how I can help!",
|
|
22
|
+
role: "assistant",
|
|
23
|
+
},
|
|
16
24
|
];
|
|
17
25
|
const STARTERS = [
|
|
18
26
|
{ content: "I'm interested in quantum computing" },
|
|
@@ -21,10 +29,10 @@ const STARTERS = [
|
|
|
21
29
|
];
|
|
22
30
|
const Container = styled_1.default.div({
|
|
23
31
|
width: "100%",
|
|
24
|
-
height: "
|
|
32
|
+
height: "800px",
|
|
25
33
|
});
|
|
26
34
|
const meta = {
|
|
27
|
-
title: "smoot-design/
|
|
35
|
+
title: "smoot-design/AI/AiChat",
|
|
28
36
|
component: AiChat_1.AiChat,
|
|
29
37
|
render: (args) => React.createElement(AiChat_1.AiChat, Object.assign({}, args)),
|
|
30
38
|
decorators: (Story) => {
|
|
@@ -35,7 +43,7 @@ const meta = {
|
|
|
35
43
|
initialMessages: INITIAL_MESSAGES,
|
|
36
44
|
requestOpts: { apiUrl: TEST_API_STREAMING },
|
|
37
45
|
conversationStarters: STARTERS,
|
|
38
|
-
|
|
46
|
+
askTimTitle: "to recommend a course",
|
|
39
47
|
onClose: (0, test_1.fn)(),
|
|
40
48
|
},
|
|
41
49
|
argTypes: {
|
|
@@ -71,6 +79,7 @@ exports.StreamingResponses = {};
|
|
|
71
79
|
*/
|
|
72
80
|
exports.JsonResponses = {
|
|
73
81
|
args: {
|
|
82
|
+
title: "Chat with AI",
|
|
74
83
|
requestOpts: { apiUrl: TEST_API_JSON },
|
|
75
84
|
parseContent: (content) => {
|
|
76
85
|
return JSON.parse(content).message;
|
|
@@ -60,7 +60,7 @@ describe("AiChat", () => {
|
|
|
60
60
|
{ content: en_1.faker.lorem.sentence() },
|
|
61
61
|
{ content: en_1.faker.lorem.sentence() },
|
|
62
62
|
];
|
|
63
|
-
const view = (0, react_1.render)(React.createElement(AiChat_1.AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: "http://localhost:4567/test" } }, props)), { wrapper: ThemeProvider_1.ThemeProvider });
|
|
63
|
+
const view = (0, react_1.render)(React.createElement(AiChat_1.AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: "http://localhost:4567/test" }, placeholder: "Type a message..." }, props)), { wrapper: ThemeProvider_1.ThemeProvider });
|
|
64
64
|
const rerender = (newProps) => {
|
|
65
65
|
view.rerender(React.createElement(AiChat_1.AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: "http://localhost:4567/test" } }, newProps)));
|
|
66
66
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
type Role = "assistant" | "user";
|
|
2
|
-
type
|
|
2
|
+
type AiChatMessage = {
|
|
3
3
|
id: string;
|
|
4
4
|
content: string;
|
|
5
5
|
role: Role;
|
|
@@ -12,12 +12,12 @@ type RequestOpts = {
|
|
|
12
12
|
*
|
|
13
13
|
* JSON.stringify is applied to the return value.
|
|
14
14
|
*/
|
|
15
|
-
transformBody?: (messages:
|
|
15
|
+
transformBody?: (messages: AiChatMessage[]) => unknown;
|
|
16
16
|
/**
|
|
17
17
|
* Extra options to pass to fetch.
|
|
18
18
|
*/
|
|
19
19
|
fetchOpts?: RequestInit;
|
|
20
|
-
onFinish?: (message:
|
|
20
|
+
onFinish?: (message: AiChatMessage) => void;
|
|
21
21
|
};
|
|
22
22
|
type AiChatProps = {
|
|
23
23
|
/**
|
|
@@ -30,15 +30,19 @@ type AiChatProps = {
|
|
|
30
30
|
*/
|
|
31
31
|
title?: string;
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* If provided, renders the "AskTIM" title motif followed by the text.
|
|
34
|
+
*/
|
|
35
|
+
askTimTitle?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Placeholder message for chat input
|
|
34
38
|
*/
|
|
35
39
|
placeholder?: string;
|
|
36
40
|
/**
|
|
37
|
-
*
|
|
41
|
+
* Sends an initial user prompt on first load
|
|
38
42
|
*/
|
|
39
43
|
onClose?: () => void;
|
|
40
44
|
className?: string;
|
|
41
|
-
initialMessages: Omit<
|
|
45
|
+
initialMessages: Omit<AiChatMessage, "id">[];
|
|
42
46
|
conversationStarters?: {
|
|
43
47
|
content: string;
|
|
44
48
|
}[];
|
|
@@ -61,5 +65,11 @@ type AiChatProps = {
|
|
|
61
65
|
* By default, the theme's ImageAdater is used.
|
|
62
66
|
*/
|
|
63
67
|
ImgComponent?: React.ElementType;
|
|
68
|
+
/**
|
|
69
|
+
* Provide a ref to the chat component to access the `append` method.
|
|
70
|
+
*/
|
|
71
|
+
ref?: React.Ref<{
|
|
72
|
+
append: (message: Omit<AiChatMessage, "id">) => void;
|
|
73
|
+
}>;
|
|
64
74
|
};
|
|
65
|
-
export type { RequestOpts, AiChatProps,
|
|
75
|
+
export type { RequestOpts, AiChatProps, AiChatMessage };
|
|
@@ -13,7 +13,7 @@ type ImageAdapterProps = React.ComponentProps<"img"> & {
|
|
|
13
13
|
Component?: React.ElementType;
|
|
14
14
|
} & ImageAdapterPropsOverrides;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Overridable Image component.
|
|
17
17
|
* - If `Component` is provided, renders as `Component`
|
|
18
18
|
* - else, if `theme.custom.ImageAdapter` is provided, renders as `theme.custom.ImageAdapter`
|
|
19
19
|
* - else, renders as `img` tag
|
|
@@ -15,7 +15,7 @@ exports.ImageAdapter = void 0;
|
|
|
15
15
|
const React = require("react");
|
|
16
16
|
const react_1 = require("@emotion/react");
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* Overridable Image component.
|
|
19
19
|
* - If `Component` is provided, renders as `Component`
|
|
20
20
|
* - else, if `theme.custom.ImageAdapter` is provided, renders as `theme.custom.ImageAdapter`
|
|
21
21
|
* - else, renders as `img` tag
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { InputBaseProps } from "@mui/material/InputBase";
|
|
3
3
|
import type { Theme } from "@mui/material/styles";
|
|
4
|
-
type Size = "small" | "medium" | "large" | "hero";
|
|
4
|
+
type Size = "small" | "medium" | "large" | "chat" | "hero";
|
|
5
5
|
type CustomInputProps = {
|
|
6
6
|
/**
|
|
7
7
|
* If true, the input will display one size smaller at mobile breakpoint.
|
|
@@ -34,6 +34,7 @@ declare const baseInputStyles: (theme: Theme) => {
|
|
|
34
34
|
borderWidth: string;
|
|
35
35
|
borderStyle: string;
|
|
36
36
|
borderRadius: string;
|
|
37
|
+
overflow: string;
|
|
37
38
|
"&.Mui-disabled": {
|
|
38
39
|
backgroundColor: string;
|
|
39
40
|
};
|
|
@@ -25,11 +25,12 @@ const responsiveSize = {
|
|
|
25
25
|
small: "small",
|
|
26
26
|
medium: "small",
|
|
27
27
|
large: "medium",
|
|
28
|
+
chat: "medium",
|
|
28
29
|
hero: "large",
|
|
29
30
|
};
|
|
30
31
|
const sizeStyles = ({ size, theme, multiline }) => (0, react_1.css)([
|
|
31
32
|
(size === "small" || size === "medium") && Object.assign({}, theme.typography.body2),
|
|
32
|
-
(size === "large" || size === "hero") && Object.assign({ ".remixicon": {
|
|
33
|
+
(size === "large" || size === "chat" || size === "hero") && Object.assign({ ".remixicon": {
|
|
33
34
|
width: "24px",
|
|
34
35
|
height: "24px",
|
|
35
36
|
} }, theme.typography.body1),
|
|
@@ -49,6 +50,10 @@ const sizeStyles = ({ size, theme, multiline }) => (0, react_1.css)([
|
|
|
49
50
|
!multiline && {
|
|
50
51
|
height: "48px",
|
|
51
52
|
},
|
|
53
|
+
size === "chat" &&
|
|
54
|
+
!multiline && {
|
|
55
|
+
height: "56px",
|
|
56
|
+
},
|
|
52
57
|
size === "hero" &&
|
|
53
58
|
!multiline && {
|
|
54
59
|
height: "72px",
|
|
@@ -79,6 +84,20 @@ const sizeStyles = ({ size, theme, multiline }) => (0, react_1.css)([
|
|
|
79
84
|
width: "48px",
|
|
80
85
|
},
|
|
81
86
|
},
|
|
87
|
+
size === "chat" && {
|
|
88
|
+
padding: "0 16px",
|
|
89
|
+
borderRadius: "8px",
|
|
90
|
+
"&:hover:not(.Mui-disabled):not(.Mui-focused)": {
|
|
91
|
+
borderColor: theme.custom.colors.silverGrayLight,
|
|
92
|
+
},
|
|
93
|
+
"&.Mui-focused": {
|
|
94
|
+
borderColor: theme.custom.colors.silverGrayLight,
|
|
95
|
+
outline: "none",
|
|
96
|
+
},
|
|
97
|
+
".Mit-AdornmentButton": {
|
|
98
|
+
padding: "0 16px",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
82
101
|
size === "hero" && {
|
|
83
102
|
padding: "0 24px",
|
|
84
103
|
".Mit-AdornmentButton": {
|
|
@@ -96,6 +115,7 @@ const baseInputStyles = (theme) => ({
|
|
|
96
115
|
borderWidth: "1px",
|
|
97
116
|
borderStyle: "solid",
|
|
98
117
|
borderRadius: "4px",
|
|
118
|
+
overflow: "hidden",
|
|
99
119
|
"&.Mui-disabled": {
|
|
100
120
|
backgroundColor: theme.custom.colors.lightGray1,
|
|
101
121
|
},
|
|
@@ -171,16 +191,19 @@ const Input = (0, styled_1.default)(InputBase_1.default, {
|
|
|
171
191
|
},
|
|
172
192
|
]);
|
|
173
193
|
exports.Input = Input;
|
|
174
|
-
const AdornmentButtonStyled = styled_1.default.button(({ theme }) => (Object.assign(Object.assign({}, theme.typography.button), {
|
|
194
|
+
const AdornmentButtonStyled = styled_1.default.button(({ theme, disabled }) => (Object.assign(Object.assign({}, theme.typography.button), {
|
|
175
195
|
// display
|
|
176
196
|
display: "flex", flexShrink: 0, justifyContent: "center", alignItems: "center",
|
|
177
197
|
// background and border
|
|
178
198
|
border: "none", background: "transparent", transition: `background ${theme.transitions.duration.short}ms`,
|
|
179
199
|
// cursor
|
|
180
|
-
cursor: "pointer", ":disabled": {
|
|
200
|
+
cursor: disabled ? "default" : "pointer", ":disabled": {
|
|
181
201
|
cursor: "default",
|
|
202
|
+
svg: {
|
|
203
|
+
fill: theme.custom.colors.silverGray,
|
|
204
|
+
},
|
|
182
205
|
}, ":hover": {
|
|
183
|
-
background: "rgba(0, 0, 0, 0.06)",
|
|
206
|
+
background: disabled ? "inherit" : "rgba(0, 0, 0, 0.06)",
|
|
184
207
|
}, color: theme.custom.colors.silverGray, ".MuiInputBase-root:hover &": {
|
|
185
208
|
color: "inherit",
|
|
186
209
|
}, ".MuiInputBase-root.Mui-focused &": {
|
|
@@ -24,7 +24,7 @@ const Scroller = styled_1.default.div({
|
|
|
24
24
|
* content is added, unless the user has scrolled up.
|
|
25
25
|
*/
|
|
26
26
|
const ScrollSnap = React.forwardRef(function ScrollSnap({ children, threshold = 2, className }, ref) {
|
|
27
|
-
const el = React.useRef();
|
|
27
|
+
const el = React.useRef(null);
|
|
28
28
|
// `content` a delayed version of children to allow measuring scroll position
|
|
29
29
|
// using the old children.
|
|
30
30
|
const [content, setContent] = React.useState(children);
|
|
@@ -5,8 +5,8 @@ const composeRefs_1 = require("./composeRefs");
|
|
|
5
5
|
const react_1 = require("@testing-library/react");
|
|
6
6
|
describe("composeRefs", () => {
|
|
7
7
|
test("Composing object + fn ref", () => {
|
|
8
|
-
const objRef1 =
|
|
9
|
-
const objRef2 =
|
|
8
|
+
const objRef1 = React.createRef();
|
|
9
|
+
const objRef2 = React.createRef();
|
|
10
10
|
const fnRef1 = jest.fn();
|
|
11
11
|
const fnRef2 = jest.fn();
|
|
12
12
|
(0, react_1.render)(React.createElement("div", { "data-testid": "my-div", ref: (0, composeRefs_1.composeRefs)(objRef1, objRef2, fnRef1, fnRef2) }));
|
|
@@ -8,7 +8,7 @@ const react_1 = require("react");
|
|
|
8
8
|
* Based on https://overreacted.io/making-setinterval-declarative-with-react-hooks/
|
|
9
9
|
*/
|
|
10
10
|
const useInterval = (callback, delay) => {
|
|
11
|
-
const savedCallback = (0, react_1.useRef)();
|
|
11
|
+
const savedCallback = (0, react_1.useRef)(null);
|
|
12
12
|
(0, react_1.useEffect)(() => {
|
|
13
13
|
savedCallback.current = callback;
|
|
14
14
|
}, [callback]);
|
package/dist/esm/ai.d.ts
CHANGED