@jupyter/chat 0.1.0 → 0.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/lib/components/chat-input.d.ts +9 -0
- package/lib/components/chat-input.js +110 -9
- package/lib/components/chat-messages.d.ts +45 -15
- package/lib/components/chat-messages.js +237 -54
- package/lib/components/chat.d.ts +21 -6
- package/lib/components/chat.js +15 -44
- package/lib/components/scroll-container.js +1 -19
- package/lib/icons.d.ts +1 -0
- package/lib/icons.js +5 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/model.d.ts +78 -14
- package/lib/model.js +183 -5
- package/lib/registry.d.ts +78 -0
- package/lib/registry.js +83 -0
- package/lib/types.d.ts +56 -4
- package/lib/widgets/chat-sidebar.d.ts +3 -4
- package/lib/widgets/chat-sidebar.js +2 -2
- package/lib/widgets/chat-widget.d.ts +2 -8
- package/lib/widgets/chat-widget.js +6 -6
- package/package.json +202 -200
- package/src/components/chat-input.tsx +182 -45
- package/src/components/chat-messages.tsx +355 -94
- package/src/components/chat.tsx +42 -68
- package/src/components/scroll-container.tsx +1 -25
- package/src/icons.ts +6 -0
- package/src/index.ts +1 -0
- package/src/model.ts +242 -21
- package/src/registry.ts +129 -0
- package/src/types.ts +58 -4
- package/src/widgets/chat-sidebar.tsx +3 -15
- package/src/widgets/chat-widget.tsx +8 -21
- package/style/chat.css +40 -0
- package/style/icons/read.svg +11 -0
|
@@ -3,31 +3,103 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { useState } from 'react';
|
|
6
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
Autocomplete,
|
|
9
10
|
Box,
|
|
11
|
+
IconButton,
|
|
12
|
+
InputAdornment,
|
|
10
13
|
SxProps,
|
|
11
14
|
TextField,
|
|
12
|
-
Theme
|
|
13
|
-
IconButton,
|
|
14
|
-
InputAdornment
|
|
15
|
+
Theme
|
|
15
16
|
} from '@mui/material';
|
|
16
17
|
import { Send, Cancel } from '@mui/icons-material';
|
|
17
18
|
import clsx from 'clsx';
|
|
19
|
+
import { AutocompleteCommand, IAutocompletionCommandsProps } from '../types';
|
|
20
|
+
import { IAutocompletionRegistry } from '../registry';
|
|
18
21
|
|
|
19
22
|
const INPUT_BOX_CLASS = 'jp-chat-input-container';
|
|
20
23
|
const SEND_BUTTON_CLASS = 'jp-chat-send-button';
|
|
21
24
|
const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
|
|
22
25
|
|
|
23
26
|
export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
24
|
-
const
|
|
27
|
+
const { autocompletionName, autocompletionRegistry, sendWithShiftEnter } =
|
|
28
|
+
props;
|
|
29
|
+
const autocompletion = useRef<IAutocompletionCommandsProps>();
|
|
30
|
+
const [input, setInput] = useState<string>(props.value || '');
|
|
31
|
+
|
|
32
|
+
// The autocomplete commands options.
|
|
33
|
+
const [commandOptions, setCommandOptions] = useState<AutocompleteCommand[]>(
|
|
34
|
+
[]
|
|
35
|
+
);
|
|
36
|
+
// whether any option is highlighted in the slash command autocomplete
|
|
37
|
+
const [highlighted, setHighlighted] = useState<boolean>(false);
|
|
38
|
+
// controls whether the slash command autocomplete is open
|
|
39
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Effect: fetch the list of available autocomplete commands.
|
|
43
|
+
*/
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (autocompletionRegistry === undefined) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
autocompletion.current = autocompletionName
|
|
49
|
+
? autocompletionRegistry.get(autocompletionName)
|
|
50
|
+
: autocompletionRegistry.getDefaultCompletion();
|
|
51
|
+
|
|
52
|
+
if (autocompletion.current === undefined) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (Array.isArray(autocompletion.current.commands)) {
|
|
57
|
+
setCommandOptions(autocompletion.current.commands);
|
|
58
|
+
} else if (typeof autocompletion.current.commands === 'function') {
|
|
59
|
+
autocompletion.current
|
|
60
|
+
.commands()
|
|
61
|
+
.then((commands: AutocompleteCommand[]) => {
|
|
62
|
+
setCommandOptions(commands);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Effect: Open the autocomplete when the user types the 'opener' string into an
|
|
69
|
+
* empty chat input. Close the autocomplete and reset the last selected value when
|
|
70
|
+
* the user clears the chat input.
|
|
71
|
+
*/
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!autocompletion.current?.opener) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (input === autocompletion.current?.opener) {
|
|
78
|
+
setOpen(true);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (input === '') {
|
|
83
|
+
setOpen(false);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}, [input]);
|
|
25
87
|
|
|
26
88
|
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
|
|
89
|
+
if (event.key !== 'Enter') {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// do not send the message if the user was selecting a suggested command from the
|
|
94
|
+
// Autocomplete component.
|
|
95
|
+
if (highlighted) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
27
99
|
if (
|
|
28
100
|
event.key === 'Enter' &&
|
|
29
|
-
((
|
|
30
|
-
(!
|
|
101
|
+
((sendWithShiftEnter && event.shiftKey) ||
|
|
102
|
+
(!sendWithShiftEnter && !event.shiftKey))
|
|
31
103
|
) {
|
|
32
104
|
onSend();
|
|
33
105
|
event.stopPropagation();
|
|
@@ -64,49 +136,106 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
64
136
|
|
|
65
137
|
return (
|
|
66
138
|
<Box sx={props.sx} className={clsx(INPUT_BOX_CLASS)}>
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
139
|
+
<Autocomplete
|
|
140
|
+
options={commandOptions}
|
|
141
|
+
value={props.value}
|
|
142
|
+
open={open}
|
|
143
|
+
autoHighlight
|
|
144
|
+
freeSolo
|
|
145
|
+
// ensure the autocomplete popup always renders on top
|
|
146
|
+
componentsProps={{
|
|
147
|
+
popper: {
|
|
148
|
+
placement: 'top'
|
|
149
|
+
},
|
|
150
|
+
paper: {
|
|
151
|
+
sx: {
|
|
152
|
+
border: '1px solid lightgray'
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}}
|
|
156
|
+
ListboxProps={{
|
|
157
|
+
sx: {
|
|
158
|
+
'& .MuiAutocomplete-option': {
|
|
159
|
+
padding: 2
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}}
|
|
163
|
+
renderInput={params => (
|
|
164
|
+
<TextField
|
|
165
|
+
{...params}
|
|
166
|
+
fullWidth
|
|
167
|
+
variant="outlined"
|
|
168
|
+
multiline
|
|
169
|
+
onKeyDown={handleKeyDown}
|
|
170
|
+
placeholder="Start chatting"
|
|
171
|
+
InputProps={{
|
|
172
|
+
...params.InputProps,
|
|
173
|
+
endAdornment: (
|
|
174
|
+
<InputAdornment position="end">
|
|
175
|
+
{props.onCancel && (
|
|
176
|
+
<IconButton
|
|
177
|
+
size="small"
|
|
178
|
+
color="primary"
|
|
179
|
+
onClick={onCancel}
|
|
180
|
+
title={'Cancel edition'}
|
|
181
|
+
className={clsx(CANCEL_BUTTON_CLASS)}
|
|
182
|
+
>
|
|
183
|
+
<Cancel />
|
|
184
|
+
</IconButton>
|
|
185
|
+
)}
|
|
80
186
|
<IconButton
|
|
81
187
|
size="small"
|
|
82
188
|
color="primary"
|
|
83
|
-
onClick={
|
|
84
|
-
disabled={
|
|
85
|
-
|
|
86
|
-
|
|
189
|
+
onClick={onSend}
|
|
190
|
+
disabled={
|
|
191
|
+
props.onCancel
|
|
192
|
+
? input === props.value
|
|
193
|
+
: !input.trim().length
|
|
194
|
+
}
|
|
195
|
+
title={`Send message ${sendWithShiftEnter ? '(SHIFT+ENTER)' : '(ENTER)'}`}
|
|
196
|
+
className={clsx(SEND_BUTTON_CLASS)}
|
|
87
197
|
>
|
|
88
|
-
<
|
|
198
|
+
<Send />
|
|
89
199
|
</IconButton>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
200
|
+
</InputAdornment>
|
|
201
|
+
)
|
|
202
|
+
}}
|
|
203
|
+
FormHelperTextProps={{
|
|
204
|
+
sx: { marginLeft: 'auto', marginRight: 0 }
|
|
205
|
+
}}
|
|
206
|
+
helperText={input.length > 2 ? helperText : ' '}
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
209
|
+
{...autocompletion.current?.props}
|
|
210
|
+
inputValue={input}
|
|
211
|
+
onInputChange={(_, newValue: string) => {
|
|
212
|
+
setInput(newValue);
|
|
213
|
+
}}
|
|
214
|
+
onHighlightChange={
|
|
215
|
+
/**
|
|
216
|
+
* On highlight change: set `highlighted` to whether an option is
|
|
217
|
+
* highlighted by the user.
|
|
218
|
+
*
|
|
219
|
+
* This isn't called when an option is selected for some reason, so we
|
|
220
|
+
* need to call `setHighlighted(false)` in `onClose()`.
|
|
221
|
+
*/
|
|
222
|
+
(_, highlightedOption) => {
|
|
223
|
+
setHighlighted(!!highlightedOption);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
onClose={
|
|
227
|
+
/**
|
|
228
|
+
* On close: set `highlighted` to `false` and close the popup by
|
|
229
|
+
* setting `open` to `false`.
|
|
230
|
+
*/
|
|
231
|
+
() => {
|
|
232
|
+
setHighlighted(false);
|
|
233
|
+
setOpen(false);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// hide default extra right padding in the text field
|
|
237
|
+
disableClearable
|
|
238
|
+
/>
|
|
110
239
|
</Box>
|
|
111
240
|
);
|
|
112
241
|
}
|
|
@@ -139,5 +268,13 @@ export namespace ChatInput {
|
|
|
139
268
|
* Custom mui/material styles.
|
|
140
269
|
*/
|
|
141
270
|
sx?: SxProps<Theme>;
|
|
271
|
+
/**
|
|
272
|
+
* Autocompletion properties.
|
|
273
|
+
*/
|
|
274
|
+
autocompletionRegistry?: IAutocompletionRegistry;
|
|
275
|
+
/**
|
|
276
|
+
* Autocompletion name.
|
|
277
|
+
*/
|
|
278
|
+
autocompletionName?: string;
|
|
142
279
|
}
|
|
143
280
|
}
|