@jupyter/chat 0.1.0 → 0.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.
Files changed (55) hide show
  1. package/lib/active-cell-manager.d.ts +151 -0
  2. package/lib/active-cell-manager.js +201 -0
  3. package/lib/components/chat-input.d.ts +14 -4
  4. package/lib/components/chat-input.js +118 -10
  5. package/lib/components/chat-messages.d.ts +45 -15
  6. package/lib/components/chat-messages.js +237 -55
  7. package/lib/components/chat.d.ts +21 -6
  8. package/lib/components/chat.js +15 -45
  9. package/lib/components/code-blocks/code-toolbar.d.ts +13 -0
  10. package/lib/components/code-blocks/code-toolbar.js +70 -0
  11. package/lib/components/{copy-button.d.ts → code-blocks/copy-button.d.ts} +1 -0
  12. package/lib/components/code-blocks/copy-button.js +43 -0
  13. package/lib/components/mui-extras/contrasting-tooltip.d.ts +6 -0
  14. package/lib/components/mui-extras/contrasting-tooltip.js +21 -0
  15. package/lib/components/mui-extras/tooltipped-icon-button.d.ts +35 -0
  16. package/lib/components/mui-extras/tooltipped-icon-button.js +36 -0
  17. package/lib/components/rendermime-markdown.d.ts +2 -0
  18. package/lib/components/rendermime-markdown.js +29 -15
  19. package/lib/components/scroll-container.js +1 -19
  20. package/lib/icons.d.ts +2 -0
  21. package/lib/icons.js +10 -0
  22. package/lib/index.d.ts +2 -0
  23. package/lib/index.js +2 -0
  24. package/lib/model.d.ts +98 -14
  25. package/lib/model.js +197 -6
  26. package/lib/registry.d.ts +78 -0
  27. package/lib/registry.js +83 -0
  28. package/lib/types.d.ts +60 -4
  29. package/lib/widgets/chat-sidebar.d.ts +3 -4
  30. package/lib/widgets/chat-sidebar.js +2 -2
  31. package/lib/widgets/chat-widget.d.ts +2 -8
  32. package/lib/widgets/chat-widget.js +6 -6
  33. package/package.json +204 -200
  34. package/src/active-cell-manager.ts +318 -0
  35. package/src/components/chat-input.tsx +196 -50
  36. package/src/components/chat-messages.tsx +357 -95
  37. package/src/components/chat.tsx +43 -69
  38. package/src/components/code-blocks/code-toolbar.tsx +143 -0
  39. package/src/components/code-blocks/copy-button.tsx +68 -0
  40. package/src/components/mui-extras/contrasting-tooltip.tsx +27 -0
  41. package/src/components/mui-extras/tooltipped-icon-button.tsx +84 -0
  42. package/src/components/rendermime-markdown.tsx +44 -20
  43. package/src/components/scroll-container.tsx +1 -25
  44. package/src/icons.ts +12 -0
  45. package/src/index.ts +2 -0
  46. package/src/model.ts +275 -21
  47. package/src/registry.ts +129 -0
  48. package/src/types.ts +62 -4
  49. package/src/widgets/chat-sidebar.tsx +3 -15
  50. package/src/widgets/chat-widget.tsx +8 -21
  51. package/style/chat.css +40 -0
  52. package/style/icons/read.svg +11 -0
  53. package/style/icons/replace-cell.svg +8 -0
  54. package/lib/components/copy-button.js +0 -35
  55. package/src/components/copy-button.tsx +0 -55
@@ -0,0 +1,318 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import { JupyterFrontEnd, LabShell } from '@jupyterlab/application';
7
+ import { Cell, ICellModel } from '@jupyterlab/cells';
8
+ import { IChangedArgs } from '@jupyterlab/coreutils';
9
+ import { INotebookTracker, NotebookActions } from '@jupyterlab/notebook';
10
+ import { IError as CellError } from '@jupyterlab/nbformat';
11
+ import { ISignal, Signal } from '@lumino/signaling';
12
+
13
+ type CellContent = {
14
+ type: string;
15
+ source: string;
16
+ };
17
+
18
+ type CellWithErrorContent = {
19
+ type: 'code';
20
+ source: string;
21
+ error: {
22
+ name: string;
23
+ value: string;
24
+ traceback: string[];
25
+ };
26
+ };
27
+
28
+ export interface IActiveCellManager {
29
+ /**
30
+ * Whether the notebook is available and an active cell exists.
31
+ */
32
+ readonly available: boolean;
33
+ /**
34
+ * The `CellError` output within the active cell, if any.
35
+ */
36
+ readonly activeCellError: CellError | null;
37
+ /**
38
+ * A signal emitting when the active cell changed.
39
+ */
40
+ readonly availabilityChanged: ISignal<this, boolean>;
41
+ /**
42
+ * A signal emitting when the error state of the active cell changed.
43
+ */
44
+ readonly activeCellErrorChanged: ISignal<this, CellError | null>;
45
+ /**
46
+ * Returns an `ActiveCellContent` object that describes the current active
47
+ * cell. If no active cell exists, this method returns `null`.
48
+ *
49
+ * When called with `withError = true`, this method returns `null` if the
50
+ * active cell does not have an error output. Otherwise it returns an
51
+ * `ActiveCellContentWithError` object that describes both the active cell and
52
+ * the error output.
53
+ */
54
+ getContent(withError: boolean): CellContent | CellWithErrorContent | null;
55
+ /**
56
+ * Inserts `content` in a new cell above the active cell.
57
+ */
58
+ insertAbove(content: string): void;
59
+ /**
60
+ * Inserts `content` in a new cell below the active cell.
61
+ */
62
+ insertBelow(content: string): void;
63
+ /**
64
+ * Replaces the contents of the active cell.
65
+ */
66
+ replace(content: string): Promise<void>;
67
+ }
68
+
69
+ /**
70
+ * The active cell manager namespace.
71
+ */
72
+ export namespace ActiveCellManager {
73
+ /**
74
+ * The constructor options.
75
+ */
76
+ export interface IOptions {
77
+ /**
78
+ * The notebook tracker.
79
+ */
80
+ tracker: INotebookTracker;
81
+ /**
82
+ * The current shell of the application.
83
+ */
84
+ shell: JupyterFrontEnd.IShell;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * A manager that maintains a reference to the current active notebook cell in
90
+ * the main panel (if any), and provides methods for inserting or appending
91
+ * content to the active cell.
92
+ *
93
+ * The current active cell should be obtained by listening to the
94
+ * `activeCellChanged` signal.
95
+ */
96
+ export class ActiveCellManager implements IActiveCellManager {
97
+ constructor(options: ActiveCellManager.IOptions) {
98
+ this._notebookTracker = options.tracker;
99
+ this._notebookTracker.activeCellChanged.connect(this._onActiveCellChanged);
100
+ options.shell.currentChanged?.connect(this._onMainAreaChanged);
101
+ if (options.shell instanceof LabShell) {
102
+ options.shell.layoutModified?.connect(this._onMainAreaChanged);
103
+ }
104
+ this._onMainAreaChanged();
105
+ }
106
+
107
+ /**
108
+ * Whether the notebook is available and an active cell exists.
109
+ */
110
+ get available(): boolean {
111
+ return this._available;
112
+ }
113
+
114
+ /**
115
+ * The `CellError` output within the active cell, if any.
116
+ */
117
+ get activeCellError(): CellError | null {
118
+ return this._activeCellError;
119
+ }
120
+
121
+ /**
122
+ * A signal emitting when the active cell changed.
123
+ */
124
+ get availabilityChanged(): ISignal<this, boolean> {
125
+ return this._availabilityChanged;
126
+ }
127
+
128
+ /**
129
+ * A signal emitting when the error state of the active cell changed.
130
+ */
131
+ get activeCellErrorChanged(): ISignal<this, CellError | null> {
132
+ return this._activeCellErrorChanged;
133
+ }
134
+
135
+ /**
136
+ * Returns an `ActiveCellContent` object that describes the current active
137
+ * cell. If no active cell exists, this method returns `null`.
138
+ *
139
+ * When called with `withError = true`, this method returns `null` if the
140
+ * active cell does not have an error output. Otherwise it returns an
141
+ * `ActiveCellContentWithError` object that describes both the active cell and
142
+ * the error output.
143
+ */
144
+ getContent(withError: false): CellContent | null;
145
+ getContent(withError: true): CellWithErrorContent | null;
146
+ getContent(withError = false): CellContent | CellWithErrorContent | null {
147
+ const sharedModel = this._notebookTracker.activeCell?.model.sharedModel;
148
+ if (!sharedModel) {
149
+ return null;
150
+ }
151
+
152
+ // case where withError = false
153
+ if (!withError) {
154
+ return {
155
+ type: sharedModel.cell_type,
156
+ source: sharedModel.getSource()
157
+ };
158
+ }
159
+
160
+ // case where withError = true
161
+ const error = this._activeCellError;
162
+ if (error) {
163
+ return {
164
+ type: 'code',
165
+ source: sharedModel.getSource(),
166
+ error: {
167
+ name: error.ename,
168
+ value: error.evalue,
169
+ traceback: error.traceback
170
+ }
171
+ };
172
+ }
173
+
174
+ return null;
175
+ }
176
+
177
+ /**
178
+ * Inserts `content` in a new cell above the active cell.
179
+ */
180
+ insertAbove(content: string): void {
181
+ const notebookPanel = this._notebookTracker.currentWidget;
182
+ if (!notebookPanel || !notebookPanel.isVisible) {
183
+ return;
184
+ }
185
+
186
+ // create a new cell above the active cell and mark new cell as active
187
+ NotebookActions.insertAbove(notebookPanel.content);
188
+ // replace content of this new active cell
189
+ this.replace(content);
190
+ }
191
+
192
+ /**
193
+ * Inserts `content` in a new cell below the active cell.
194
+ */
195
+ insertBelow(content: string): void {
196
+ const notebookPanel = this._notebookTracker.currentWidget;
197
+ if (!notebookPanel || !notebookPanel.isVisible) {
198
+ return;
199
+ }
200
+
201
+ // create a new cell below the active cell and mark new cell as active
202
+ NotebookActions.insertBelow(notebookPanel.content);
203
+ // replace content of this new active cell
204
+ this.replace(content);
205
+ }
206
+
207
+ /**
208
+ * Replaces the contents of the active cell.
209
+ */
210
+ async replace(content: string): Promise<void> {
211
+ const notebookPanel = this._notebookTracker.currentWidget;
212
+ if (!notebookPanel || !notebookPanel.isVisible) {
213
+ return;
214
+ }
215
+ // get reference to active cell directly from Notebook API. this avoids the
216
+ // possibility of acting on an out-of-date reference.
217
+ const activeCell = this._notebookTracker.activeCell;
218
+ if (!activeCell) {
219
+ return;
220
+ }
221
+
222
+ // wait for editor to be ready
223
+ await activeCell.ready;
224
+
225
+ // replace the content of the active cell
226
+ /**
227
+ * NOTE: calling this method sometimes emits an error to the browser console:
228
+ *
229
+ * ```
230
+ * Error: Calls to EditorView.update are not allowed while an update is in progress
231
+ * ```
232
+ *
233
+ * However, there seems to be no impact on the behavior/stability of the
234
+ * JupyterLab application after this error is logged. Furthermore, this is
235
+ * the official API for setting the content of a cell in JupyterLab 4,
236
+ * meaning that this is likely unavoidable.
237
+ */
238
+ activeCell.editor?.model.sharedModel.setSource(content);
239
+ }
240
+
241
+ private _onMainAreaChanged = () => {
242
+ const value = this._notebookTracker.currentWidget?.isVisible ?? false;
243
+ if (value !== this._notebookVisible) {
244
+ this._notebookVisible = value;
245
+ this._available = !!this._activeCell && this._notebookVisible;
246
+ this._availabilityChanged.emit(this._available);
247
+ }
248
+ };
249
+
250
+ /**
251
+ * Handle the change of active notebook cell.
252
+ */
253
+ private _onActiveCellChanged = (
254
+ _: INotebookTracker,
255
+ activeCell: Cell<ICellModel> | null
256
+ ): void => {
257
+ if (this._activeCell !== activeCell) {
258
+ this._activeCell?.model.stateChanged.disconnect(this._cellStateChange);
259
+ this._activeCell = activeCell;
260
+
261
+ activeCell?.ready.then(() => {
262
+ this._activeCell?.model.stateChanged.connect(this._cellStateChange);
263
+ this._available = !!this._activeCell && this._notebookVisible;
264
+ this._availabilityChanged.emit(this._available);
265
+ this._activeCell?.disposed.connect(() => {
266
+ this._activeCell = null;
267
+ });
268
+ });
269
+ }
270
+ };
271
+
272
+ /**
273
+ * Handle the change of the active cell state.
274
+ */
275
+ private _cellStateChange = (
276
+ _: ICellModel,
277
+ change: IChangedArgs<boolean, boolean, any>
278
+ ): void => {
279
+ if (change.name === 'executionCount') {
280
+ const currSharedModel = this._activeCell?.model.sharedModel;
281
+ const prevActiveCellError = this._activeCellError;
282
+ let currActiveCellError: CellError | null = null;
283
+ if (currSharedModel && 'outputs' in currSharedModel) {
284
+ currActiveCellError =
285
+ currSharedModel.outputs.find<CellError>(
286
+ (output): output is CellError => output.output_type === 'error'
287
+ ) || null;
288
+ }
289
+
290
+ // for some reason, the `CellError` object is not referentially stable,
291
+ // meaning that this condition always evaluates to `true` and the
292
+ // `activeCellErrorChanged` signal is emitted every 200ms, even when the
293
+ // error output is unchanged. this is why we have to rely on
294
+ // `execution_count` to track changes to the error output.
295
+ if (prevActiveCellError !== currActiveCellError) {
296
+ this._activeCellError = currActiveCellError;
297
+ this._activeCellErrorChanged.emit(this._activeCellError);
298
+ }
299
+ }
300
+ };
301
+
302
+ /**
303
+ * The notebook tracker.
304
+ */
305
+ private _notebookTracker: INotebookTracker;
306
+ /**
307
+ * Whether the current notebook panel is visible or not.
308
+ */
309
+ private _notebookVisible: boolean = false;
310
+ /**
311
+ * The active cell.
312
+ */
313
+ private _activeCell: Cell | null = null;
314
+ private _available: boolean = false;
315
+ private _activeCellError: CellError | null = null;
316
+ private _availabilityChanged = new Signal<this, boolean>(this);
317
+ private _activeCellErrorChanged = new Signal<this, CellError | null>(this);
318
+ }
@@ -3,31 +3,112 @@
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 { IChatModel } from '../model';
20
+ import { IAutocompletionRegistry } from '../registry';
21
+ import { AutocompleteCommand, IAutocompletionCommandsProps } from '../types';
18
22
 
19
23
  const INPUT_BOX_CLASS = 'jp-chat-input-container';
20
24
  const SEND_BUTTON_CLASS = 'jp-chat-send-button';
21
25
  const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
22
26
 
23
27
  export function ChatInput(props: ChatInput.IProps): JSX.Element {
24
- const [input, setInput] = useState(props.value || '');
28
+ const { autocompletionName, autocompletionRegistry, model } = props;
29
+ const autocompletion = useRef<IAutocompletionCommandsProps>();
30
+ const [input, setInput] = useState<string>(props.value || '');
31
+ const [sendWithShiftEnter, setSendWithShiftEnter] = useState<boolean>(
32
+ model.config.sendWithShiftEnter ?? false
33
+ );
34
+
35
+ useEffect(() => {
36
+ model.configChanged.connect((_, config) => {
37
+ setSendWithShiftEnter(config.sendWithShiftEnter ?? false);
38
+ });
39
+ }, [model]);
40
+
41
+ // The autocomplete commands options.
42
+ const [commandOptions, setCommandOptions] = useState<AutocompleteCommand[]>(
43
+ []
44
+ );
45
+ // whether any option is highlighted in the slash command autocomplete
46
+ const [highlighted, setHighlighted] = useState<boolean>(false);
47
+ // controls whether the slash command autocomplete is open
48
+ const [open, setOpen] = useState<boolean>(false);
49
+
50
+ /**
51
+ * Effect: fetch the list of available autocomplete commands.
52
+ */
53
+ useEffect(() => {
54
+ if (autocompletionRegistry === undefined) {
55
+ return;
56
+ }
57
+ autocompletion.current = autocompletionName
58
+ ? autocompletionRegistry.get(autocompletionName)
59
+ : autocompletionRegistry.getDefaultCompletion();
60
+
61
+ if (autocompletion.current === undefined) {
62
+ return;
63
+ }
64
+
65
+ if (Array.isArray(autocompletion.current.commands)) {
66
+ setCommandOptions(autocompletion.current.commands);
67
+ } else if (typeof autocompletion.current.commands === 'function') {
68
+ autocompletion.current
69
+ .commands()
70
+ .then((commands: AutocompleteCommand[]) => {
71
+ setCommandOptions(commands);
72
+ });
73
+ }
74
+ }, []);
75
+
76
+ /**
77
+ * Effect: Open the autocomplete when the user types the 'opener' string into an
78
+ * empty chat input. Close the autocomplete and reset the last selected value when
79
+ * the user clears the chat input.
80
+ */
81
+ useEffect(() => {
82
+ if (!autocompletion.current?.opener) {
83
+ return;
84
+ }
85
+
86
+ if (input === autocompletion.current?.opener) {
87
+ setOpen(true);
88
+ return;
89
+ }
90
+
91
+ if (input === '') {
92
+ setOpen(false);
93
+ return;
94
+ }
95
+ }, [input]);
25
96
 
26
97
  function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
98
+ if (event.key !== 'Enter') {
99
+ return;
100
+ }
101
+
102
+ // do not send the message if the user was selecting a suggested command from the
103
+ // Autocomplete component.
104
+ if (highlighted) {
105
+ return;
106
+ }
107
+
27
108
  if (
28
109
  event.key === 'Enter' &&
29
- ((props.sendWithShiftEnter && event.shiftKey) ||
30
- (!props.sendWithShiftEnter && !event.shiftKey))
110
+ ((sendWithShiftEnter && event.shiftKey) ||
111
+ (!sendWithShiftEnter && !event.shiftKey))
31
112
  ) {
32
113
  onSend();
33
114
  event.stopPropagation();
@@ -52,7 +133,7 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
52
133
  }
53
134
 
54
135
  // Set the helper text based on whether Shift+Enter is used for sending.
55
- const helperText = props.sendWithShiftEnter ? (
136
+ const helperText = sendWithShiftEnter ? (
56
137
  <span>
57
138
  Press <b>Shift</b>+<b>Enter</b> to send message
58
139
  </span>
@@ -64,49 +145,106 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
64
145
 
65
146
  return (
66
147
  <Box sx={props.sx} className={clsx(INPUT_BOX_CLASS)}>
67
- <Box sx={{ display: 'flex' }}>
68
- <TextField
69
- value={input}
70
- onChange={e => setInput(e.target.value)}
71
- fullWidth
72
- variant="outlined"
73
- multiline
74
- onKeyDown={handleKeyDown}
75
- placeholder="Start chatting"
76
- InputProps={{
77
- endAdornment: (
78
- <InputAdornment position="end">
79
- {props.onCancel && (
148
+ <Autocomplete
149
+ options={commandOptions}
150
+ value={props.value}
151
+ open={open}
152
+ autoHighlight
153
+ freeSolo
154
+ // ensure the autocomplete popup always renders on top
155
+ componentsProps={{
156
+ popper: {
157
+ placement: 'top'
158
+ },
159
+ paper: {
160
+ sx: {
161
+ border: '1px solid lightgray'
162
+ }
163
+ }
164
+ }}
165
+ ListboxProps={{
166
+ sx: {
167
+ '& .MuiAutocomplete-option': {
168
+ padding: 2
169
+ }
170
+ }
171
+ }}
172
+ renderInput={params => (
173
+ <TextField
174
+ {...params}
175
+ fullWidth
176
+ variant="outlined"
177
+ multiline
178
+ onKeyDown={handleKeyDown}
179
+ placeholder="Start chatting"
180
+ InputProps={{
181
+ ...params.InputProps,
182
+ endAdornment: (
183
+ <InputAdornment position="end">
184
+ {props.onCancel && (
185
+ <IconButton
186
+ size="small"
187
+ color="primary"
188
+ onClick={onCancel}
189
+ title={'Cancel edition'}
190
+ className={clsx(CANCEL_BUTTON_CLASS)}
191
+ >
192
+ <Cancel />
193
+ </IconButton>
194
+ )}
80
195
  <IconButton
81
196
  size="small"
82
197
  color="primary"
83
- onClick={onCancel}
84
- disabled={!input.trim().length}
85
- title={'Cancel edition'}
86
- className={clsx(CANCEL_BUTTON_CLASS)}
198
+ onClick={onSend}
199
+ disabled={
200
+ props.onCancel
201
+ ? input === props.value
202
+ : !input.trim().length
203
+ }
204
+ title={`Send message ${sendWithShiftEnter ? '(SHIFT+ENTER)' : '(ENTER)'}`}
205
+ className={clsx(SEND_BUTTON_CLASS)}
87
206
  >
88
- <Cancel />
207
+ <Send />
89
208
  </IconButton>
90
- )}
91
- <IconButton
92
- size="small"
93
- color="primary"
94
- onClick={onSend}
95
- disabled={!input.trim().length}
96
- title={`Send message ${props.sendWithShiftEnter ? '(SHIFT+ENTER)' : '(ENTER)'}`}
97
- className={clsx(SEND_BUTTON_CLASS)}
98
- >
99
- <Send />
100
- </IconButton>
101
- </InputAdornment>
102
- )
103
- }}
104
- FormHelperTextProps={{
105
- sx: { marginLeft: 'auto', marginRight: 0 }
106
- }}
107
- helperText={input.length > 2 ? helperText : ' '}
108
- />
109
- </Box>
209
+ </InputAdornment>
210
+ )
211
+ }}
212
+ FormHelperTextProps={{
213
+ sx: { marginLeft: 'auto', marginRight: 0 }
214
+ }}
215
+ helperText={input.length > 2 ? helperText : ' '}
216
+ />
217
+ )}
218
+ {...autocompletion.current?.props}
219
+ inputValue={input}
220
+ onInputChange={(_, newValue: string) => {
221
+ setInput(newValue);
222
+ }}
223
+ onHighlightChange={
224
+ /**
225
+ * On highlight change: set `highlighted` to whether an option is
226
+ * highlighted by the user.
227
+ *
228
+ * This isn't called when an option is selected for some reason, so we
229
+ * need to call `setHighlighted(false)` in `onClose()`.
230
+ */
231
+ (_, highlightedOption) => {
232
+ setHighlighted(!!highlightedOption);
233
+ }
234
+ }
235
+ onClose={
236
+ /**
237
+ * On close: set `highlighted` to `false` and close the popup by
238
+ * setting `open` to `false`.
239
+ */
240
+ () => {
241
+ setHighlighted(false);
242
+ setOpen(false);
243
+ }
244
+ }
245
+ // hide default extra right padding in the text field
246
+ disableClearable
247
+ />
110
248
  </Box>
111
249
  );
112
250
  }
@@ -119,6 +257,10 @@ export namespace ChatInput {
119
257
  * The properties of the react element.
120
258
  */
121
259
  export interface IProps {
260
+ /**
261
+ * The chat model.
262
+ */
263
+ model: IChatModel;
122
264
  /**
123
265
  * The initial value of the input (default to '')
124
266
  */
@@ -131,13 +273,17 @@ export namespace ChatInput {
131
273
  * The function to be called to cancel editing.
132
274
  */
133
275
  onCancel?: () => unknown;
134
- /**
135
- * Whether using shift+enter to send the message.
136
- */
137
- sendWithShiftEnter: boolean;
138
276
  /**
139
277
  * Custom mui/material styles.
140
278
  */
141
279
  sx?: SxProps<Theme>;
280
+ /**
281
+ * Autocompletion properties.
282
+ */
283
+ autocompletionRegistry?: IAutocompletionRegistry;
284
+ /**
285
+ * Autocompletion name.
286
+ */
287
+ autocompletionName?: string;
142
288
  }
143
289
  }