@mitodl/smoot-design 3.1.0 → 3.2.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 +9244 -9419
- package/dist/bundles/aiChat.umd.js +39 -75
- package/dist/cjs/ai.d.ts +1 -0
- package/dist/cjs/components/AiChat/AiChat.js +83 -99
- package/dist/cjs/components/AiChat/AiChat.stories.js +11 -2
- 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 +85 -101
- package/dist/esm/components/AiChat/AiChat.stories.js +11 -2
- 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,30 @@ const ChatContainer = styled_1.default.div({
|
|
|
44
41
|
display: "flex",
|
|
45
42
|
flexDirection: "column",
|
|
46
43
|
});
|
|
47
|
-
const
|
|
44
|
+
const AskTimTitle = styled_1.default.div(({ theme }) => ({
|
|
45
|
+
display: "flex",
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
gap: "8px",
|
|
48
|
+
color: theme.custom.colors.darkGray2,
|
|
49
|
+
img: {
|
|
50
|
+
width: "24px",
|
|
51
|
+
height: "24px",
|
|
52
|
+
},
|
|
53
|
+
svg: {
|
|
54
|
+
fill: theme.custom.colors.red,
|
|
55
|
+
width: "24px",
|
|
56
|
+
height: "24px",
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
59
|
+
const MessagesContainer = (0, styled_1.default)(ScrollSnap_1.ScrollSnap)({
|
|
48
60
|
display: "flex",
|
|
49
61
|
flexDirection: "column",
|
|
50
62
|
flex: 1,
|
|
51
|
-
|
|
52
|
-
paddingBottom: "
|
|
63
|
+
paddingTop: "14px",
|
|
64
|
+
paddingBottom: "24px",
|
|
53
65
|
overflow: "auto",
|
|
54
66
|
gap: "24px",
|
|
55
|
-
|
|
56
|
-
borderColor: theme.custom.colors.silverGrayLight,
|
|
57
|
-
borderStyle: "solid",
|
|
58
|
-
borderWidth: "0 1px",
|
|
59
|
-
}));
|
|
67
|
+
});
|
|
60
68
|
const MessageRow = styled_1.default.div({
|
|
61
69
|
display: "flex",
|
|
62
70
|
width: "100%",
|
|
@@ -69,45 +77,25 @@ const MessageRow = styled_1.default.div({
|
|
|
69
77
|
},
|
|
70
78
|
position: "relative",
|
|
71
79
|
});
|
|
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": {
|
|
80
|
+
const Message = styled_1.default.div(({ theme }) => (Object.assign(Object.assign({ backgroundColor: theme.custom.colors.white, padding: "12px 16px" }, theme.typography.body2), { "p:first-of-type": {
|
|
98
81
|
marginTop: 0,
|
|
99
82
|
}, "p:last-of-type": {
|
|
100
83
|
marginBottom: 0,
|
|
101
84
|
}, a: {
|
|
102
|
-
color: theme.custom.colors.mitRed,
|
|
103
|
-
textDecoration: "none",
|
|
104
|
-
}, "a:hover": {
|
|
105
85
|
color: theme.custom.colors.red,
|
|
106
|
-
|
|
86
|
+
fontWeight: "normal",
|
|
107
87
|
}, borderRadius: "12px", [`.${classes.messageRowAssistant} &`]: {
|
|
108
|
-
|
|
88
|
+
border: `1px solid ${theme.custom.colors.lightGray2}`,
|
|
89
|
+
color: theme.custom.colors.darkGray2,
|
|
90
|
+
borderRadius: "0px 8px 8px 8px",
|
|
91
|
+
svg: {
|
|
92
|
+
fill: theme.custom.colors.silverGrayDark,
|
|
93
|
+
display: "block",
|
|
94
|
+
},
|
|
109
95
|
}, [`.${classes.messageRowUser} &`]: {
|
|
110
|
-
borderRadius: "
|
|
96
|
+
borderRadius: "8px 0px 8px 8px",
|
|
97
|
+
color: theme.custom.colors.white,
|
|
98
|
+
backgroundColor: theme.custom.colors.silverGrayDark,
|
|
111
99
|
} })));
|
|
112
100
|
const StarterContainer = styled_1.default.div({
|
|
113
101
|
alignSelf: "flex-end",
|
|
@@ -116,74 +104,63 @@ const StarterContainer = styled_1.default.div({
|
|
|
116
104
|
flexDirection: "column",
|
|
117
105
|
gap: "12px",
|
|
118
106
|
});
|
|
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
|
-
}));
|
|
107
|
+
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": {
|
|
108
|
+
color: theme.custom.colors.white,
|
|
109
|
+
backgroundColor: theme.custom.colors.silverGrayDark,
|
|
110
|
+
borderColor: "transparent",
|
|
111
|
+
}, borderRadius: "8px" })));
|
|
154
112
|
const RobotIcon = (0, styled_1.default)(react_1.RiRobot2Line)({
|
|
155
113
|
width: "40px",
|
|
156
114
|
height: "40px",
|
|
157
115
|
});
|
|
158
|
-
const
|
|
116
|
+
const StyledInput = (0, styled_1.default)(Input_1.Input)(({ theme }) => ({
|
|
117
|
+
backgroundColor: theme.custom.colors.lightGray1,
|
|
118
|
+
borderRadius: "8px",
|
|
119
|
+
border: `1px solid ${theme.custom.colors.lightGray2}`,
|
|
120
|
+
}));
|
|
121
|
+
const StyledSendButton = (0, styled_1.default)(react_1.RiSendPlaneFill)(({ theme }) => ({
|
|
122
|
+
fill: theme.custom.colors.red,
|
|
123
|
+
}));
|
|
124
|
+
const StyledStopButton = (0, styled_1.default)(react_1.RiStopFill)(({ theme }) => ({
|
|
125
|
+
fill: theme.custom.colors.red,
|
|
126
|
+
}));
|
|
127
|
+
const ChatTitle = (0, styled_1.default)(({ title, askTimTitle, onClose, className }) => {
|
|
159
128
|
return (React.createElement("div", { className: className },
|
|
160
|
-
React.createElement(
|
|
161
|
-
|
|
162
|
-
|
|
129
|
+
askTimTitle ? (React.createElement(AskTimTitle, null,
|
|
130
|
+
React.createElement(react_1.RiSparkling2Line, null),
|
|
131
|
+
React.createElement(Typography_1.default, { variant: "body1" },
|
|
132
|
+
"Ask",
|
|
133
|
+
React.createElement("strong", null, "TIM"),
|
|
134
|
+
"\u00A0",
|
|
135
|
+
askTimTitle))) : null,
|
|
136
|
+
title ? (React.createElement(React.Fragment, null,
|
|
137
|
+
React.createElement(RobotIcon, null),
|
|
138
|
+
React.createElement(Typography_1.default, { flex: 1, variant: "h5" }, title))) : null,
|
|
139
|
+
onClose ? (React.createElement(ActionButton_1.ActionButton, { variant: "text", edge: "none", onClick: onClose, "aria-label": "Close chat" },
|
|
163
140
|
React.createElement(react_1.RiCloseLine, null))) : null));
|
|
164
141
|
})(({ theme }) => ({
|
|
165
|
-
backgroundColor: theme.custom.colors.red,
|
|
166
142
|
display: "flex",
|
|
167
143
|
alignItems: "center",
|
|
168
144
|
justifyContent: "space-between",
|
|
169
|
-
padding: "12px
|
|
145
|
+
padding: "12px 0",
|
|
170
146
|
gap: "16px",
|
|
171
147
|
color: theme.custom.colors.white,
|
|
172
148
|
borderRadius: "8px 8px 0 0",
|
|
173
149
|
}));
|
|
174
150
|
const AiChatInternal = function AiChat(_a) {
|
|
175
|
-
var _b;
|
|
176
|
-
var { chatId, className, conversationStarters, requestOpts, initialMessages: initMsgs, parseContent, srLoadingMessages, title, onClose, ImgComponent, placeholder = "
|
|
151
|
+
var _b, _c;
|
|
152
|
+
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
153
|
;
|
|
178
154
|
const messagesRef = React.useRef(null);
|
|
179
155
|
const initialMessages = React.useMemo(() => {
|
|
180
156
|
const prefix = Math.random().toString().slice(2);
|
|
181
157
|
return initMsgs.map((m, i) => (Object.assign(Object.assign({}, m), { id: `initial-${prefix}-${i}` })));
|
|
182
158
|
}, [initMsgs]);
|
|
183
|
-
const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, } = (0, utils_1.useAiChat)(requestOpts, {
|
|
159
|
+
const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, stop, } = (0, utils_1.useAiChat)(requestOpts, {
|
|
184
160
|
initialMessages: initialMessages,
|
|
185
161
|
id: chatId,
|
|
186
162
|
});
|
|
163
|
+
React.useImperativeHandle(ref, () => ({ append }), [append]);
|
|
187
164
|
const messages = React.useMemo(() => {
|
|
188
165
|
const initial = initialMessages.map((m) => m.id);
|
|
189
166
|
return unparsed.map((m) => {
|
|
@@ -196,6 +173,7 @@ const AiChatInternal = function AiChat(_a) {
|
|
|
196
173
|
}, [parseContent, unparsed, initialMessages]);
|
|
197
174
|
const showStarters = messages.length === initialMessages.length;
|
|
198
175
|
const waiting = !showStarters && ((_b = messages[messages.length - 1]) === null || _b === void 0 ? void 0 : _b.role) === "user";
|
|
176
|
+
const stoppable = isLoading && ((_c = messages[messages.length - 1]) === null || _c === void 0 ? void 0 : _c.role) !== "user";
|
|
199
177
|
const scrollToBottom = () => {
|
|
200
178
|
var _a;
|
|
201
179
|
(_a = messagesRef.current) === null || _a === void 0 ? void 0 : _a.scrollBy({
|
|
@@ -205,14 +183,12 @@ const AiChatInternal = function AiChat(_a) {
|
|
|
205
183
|
};
|
|
206
184
|
const lastMsg = messages[messages.length - 1];
|
|
207
185
|
return (React.createElement(ChatContainer, Object.assign({ className: (0, classnames_1.default)(className, classes.root) }, others),
|
|
208
|
-
React.createElement(ChatTitle, { title: title, onClose: onClose }),
|
|
186
|
+
React.createElement(ChatTitle, { title: title, askTimTitle: askTimTitle, onClose: onClose, className: (0, classnames_1.default)(className, classes.title) }),
|
|
209
187
|
React.createElement(MessagesContainer, { className: classes.messagesContainer, ref: messagesRef },
|
|
210
188
|
messages.map((m) => (React.createElement(MessageRow, { key: m.id, "data-chat-role": m.role, className: (0, classnames_1.default)(classes.messageRow, {
|
|
211
189
|
[classes.messageRowUser]: m.role === "user",
|
|
212
190
|
[classes.messageRowAssistant]: m.role === "assistant",
|
|
213
191
|
}) },
|
|
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
192
|
React.createElement(Message, { className: classes.message },
|
|
217
193
|
React.createElement(VisuallyHidden_1.VisuallyHidden, null, m.role === "user" ? "You said: " : "Assistant said: "),
|
|
218
194
|
React.createElement(react_markdown_1.default, { skipHtml: true }, m.content))))),
|
|
@@ -221,16 +197,24 @@ const AiChatInternal = function AiChat(_a) {
|
|
|
221
197
|
append({ role: "user", content: m.content });
|
|
222
198
|
} }, m.content))))) : null,
|
|
223
199
|
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
200
|
React.createElement(Message, null,
|
|
227
|
-
React.createElement(
|
|
201
|
+
React.createElement(react_1.RiMoreFill, null)))) : null),
|
|
228
202
|
React.createElement("form", { onSubmit: (e) => {
|
|
229
|
-
|
|
230
|
-
|
|
203
|
+
e.preventDefault();
|
|
204
|
+
if (isLoading && stoppable) {
|
|
205
|
+
stop();
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
scrollToBottom();
|
|
209
|
+
handleSubmit(e);
|
|
210
|
+
}
|
|
231
211
|
} },
|
|
232
|
-
React.createElement(
|
|
233
|
-
React.createElement(
|
|
212
|
+
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 },
|
|
213
|
+
React.createElement(StyledStopButton, null))) : (React.createElement(Input_1.AdornmentButton, { "aria-label": "Send", type: "submit", disabled: !input, onClick: (e) => {
|
|
214
|
+
scrollToBottom();
|
|
215
|
+
handleSubmit(e);
|
|
216
|
+
} },
|
|
217
|
+
React.createElement(StyledSendButton, null))) })),
|
|
234
218
|
React.createElement(SrAnnouncer_1.SrAnnouncer, { isLoading: isLoading, loadingMessages: srLoadingMessages, message: lastMsg.role === "assistant" ? lastMsg.content : "" })));
|
|
235
219
|
};
|
|
236
220
|
const AiChat = (props) => (
|
|
@@ -239,7 +223,7 @@ const AiChat = (props) => (
|
|
|
239
223
|
* hook calls. This can cause strange effects like loading API responses
|
|
240
224
|
* for previous chatId into new chatId.
|
|
241
225
|
*
|
|
242
|
-
* To avoid this, let's
|
|
226
|
+
* To avoid this, let's change the key, this will force React to make a new component
|
|
243
227
|
* not sharing any of the old state.
|
|
244
228
|
*/
|
|
245
229
|
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,7 +29,7 @@ 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
35
|
title: "smoot-design/ai/AiChat",
|
|
@@ -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