@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,151 @@
1
+ import { JupyterFrontEnd } from '@jupyterlab/application';
2
+ import { INotebookTracker } from '@jupyterlab/notebook';
3
+ import { IError as CellError } from '@jupyterlab/nbformat';
4
+ import { ISignal } from '@lumino/signaling';
5
+ type CellContent = {
6
+ type: string;
7
+ source: string;
8
+ };
9
+ type CellWithErrorContent = {
10
+ type: 'code';
11
+ source: string;
12
+ error: {
13
+ name: string;
14
+ value: string;
15
+ traceback: string[];
16
+ };
17
+ };
18
+ export interface IActiveCellManager {
19
+ /**
20
+ * Whether the notebook is available and an active cell exists.
21
+ */
22
+ readonly available: boolean;
23
+ /**
24
+ * The `CellError` output within the active cell, if any.
25
+ */
26
+ readonly activeCellError: CellError | null;
27
+ /**
28
+ * A signal emitting when the active cell changed.
29
+ */
30
+ readonly availabilityChanged: ISignal<this, boolean>;
31
+ /**
32
+ * A signal emitting when the error state of the active cell changed.
33
+ */
34
+ readonly activeCellErrorChanged: ISignal<this, CellError | null>;
35
+ /**
36
+ * Returns an `ActiveCellContent` object that describes the current active
37
+ * cell. If no active cell exists, this method returns `null`.
38
+ *
39
+ * When called with `withError = true`, this method returns `null` if the
40
+ * active cell does not have an error output. Otherwise it returns an
41
+ * `ActiveCellContentWithError` object that describes both the active cell and
42
+ * the error output.
43
+ */
44
+ getContent(withError: boolean): CellContent | CellWithErrorContent | null;
45
+ /**
46
+ * Inserts `content` in a new cell above the active cell.
47
+ */
48
+ insertAbove(content: string): void;
49
+ /**
50
+ * Inserts `content` in a new cell below the active cell.
51
+ */
52
+ insertBelow(content: string): void;
53
+ /**
54
+ * Replaces the contents of the active cell.
55
+ */
56
+ replace(content: string): Promise<void>;
57
+ }
58
+ /**
59
+ * The active cell manager namespace.
60
+ */
61
+ export declare namespace ActiveCellManager {
62
+ /**
63
+ * The constructor options.
64
+ */
65
+ interface IOptions {
66
+ /**
67
+ * The notebook tracker.
68
+ */
69
+ tracker: INotebookTracker;
70
+ /**
71
+ * The current shell of the application.
72
+ */
73
+ shell: JupyterFrontEnd.IShell;
74
+ }
75
+ }
76
+ /**
77
+ * A manager that maintains a reference to the current active notebook cell in
78
+ * the main panel (if any), and provides methods for inserting or appending
79
+ * content to the active cell.
80
+ *
81
+ * The current active cell should be obtained by listening to the
82
+ * `activeCellChanged` signal.
83
+ */
84
+ export declare class ActiveCellManager implements IActiveCellManager {
85
+ constructor(options: ActiveCellManager.IOptions);
86
+ /**
87
+ * Whether the notebook is available and an active cell exists.
88
+ */
89
+ get available(): boolean;
90
+ /**
91
+ * The `CellError` output within the active cell, if any.
92
+ */
93
+ get activeCellError(): CellError | null;
94
+ /**
95
+ * A signal emitting when the active cell changed.
96
+ */
97
+ get availabilityChanged(): ISignal<this, boolean>;
98
+ /**
99
+ * A signal emitting when the error state of the active cell changed.
100
+ */
101
+ get activeCellErrorChanged(): ISignal<this, CellError | null>;
102
+ /**
103
+ * Returns an `ActiveCellContent` object that describes the current active
104
+ * cell. If no active cell exists, this method returns `null`.
105
+ *
106
+ * When called with `withError = true`, this method returns `null` if the
107
+ * active cell does not have an error output. Otherwise it returns an
108
+ * `ActiveCellContentWithError` object that describes both the active cell and
109
+ * the error output.
110
+ */
111
+ getContent(withError: false): CellContent | null;
112
+ getContent(withError: true): CellWithErrorContent | null;
113
+ /**
114
+ * Inserts `content` in a new cell above the active cell.
115
+ */
116
+ insertAbove(content: string): void;
117
+ /**
118
+ * Inserts `content` in a new cell below the active cell.
119
+ */
120
+ insertBelow(content: string): void;
121
+ /**
122
+ * Replaces the contents of the active cell.
123
+ */
124
+ replace(content: string): Promise<void>;
125
+ private _onMainAreaChanged;
126
+ /**
127
+ * Handle the change of active notebook cell.
128
+ */
129
+ private _onActiveCellChanged;
130
+ /**
131
+ * Handle the change of the active cell state.
132
+ */
133
+ private _cellStateChange;
134
+ /**
135
+ * The notebook tracker.
136
+ */
137
+ private _notebookTracker;
138
+ /**
139
+ * Whether the current notebook panel is visible or not.
140
+ */
141
+ private _notebookVisible;
142
+ /**
143
+ * The active cell.
144
+ */
145
+ private _activeCell;
146
+ private _available;
147
+ private _activeCellError;
148
+ private _availabilityChanged;
149
+ private _activeCellErrorChanged;
150
+ }
151
+ export {};
@@ -0,0 +1,201 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { LabShell } from '@jupyterlab/application';
6
+ import { NotebookActions } from '@jupyterlab/notebook';
7
+ import { Signal } from '@lumino/signaling';
8
+ /**
9
+ * A manager that maintains a reference to the current active notebook cell in
10
+ * the main panel (if any), and provides methods for inserting or appending
11
+ * content to the active cell.
12
+ *
13
+ * The current active cell should be obtained by listening to the
14
+ * `activeCellChanged` signal.
15
+ */
16
+ export class ActiveCellManager {
17
+ constructor(options) {
18
+ var _a, _b;
19
+ this._onMainAreaChanged = () => {
20
+ var _a, _b;
21
+ const value = (_b = (_a = this._notebookTracker.currentWidget) === null || _a === void 0 ? void 0 : _a.isVisible) !== null && _b !== void 0 ? _b : false;
22
+ if (value !== this._notebookVisible) {
23
+ this._notebookVisible = value;
24
+ this._available = !!this._activeCell && this._notebookVisible;
25
+ this._availabilityChanged.emit(this._available);
26
+ }
27
+ };
28
+ /**
29
+ * Handle the change of active notebook cell.
30
+ */
31
+ this._onActiveCellChanged = (_, activeCell) => {
32
+ var _a;
33
+ if (this._activeCell !== activeCell) {
34
+ (_a = this._activeCell) === null || _a === void 0 ? void 0 : _a.model.stateChanged.disconnect(this._cellStateChange);
35
+ this._activeCell = activeCell;
36
+ activeCell === null || activeCell === void 0 ? void 0 : activeCell.ready.then(() => {
37
+ var _a, _b;
38
+ (_a = this._activeCell) === null || _a === void 0 ? void 0 : _a.model.stateChanged.connect(this._cellStateChange);
39
+ this._available = !!this._activeCell && this._notebookVisible;
40
+ this._availabilityChanged.emit(this._available);
41
+ (_b = this._activeCell) === null || _b === void 0 ? void 0 : _b.disposed.connect(() => {
42
+ this._activeCell = null;
43
+ });
44
+ });
45
+ }
46
+ };
47
+ /**
48
+ * Handle the change of the active cell state.
49
+ */
50
+ this._cellStateChange = (_, change) => {
51
+ var _a;
52
+ if (change.name === 'executionCount') {
53
+ const currSharedModel = (_a = this._activeCell) === null || _a === void 0 ? void 0 : _a.model.sharedModel;
54
+ const prevActiveCellError = this._activeCellError;
55
+ let currActiveCellError = null;
56
+ if (currSharedModel && 'outputs' in currSharedModel) {
57
+ currActiveCellError =
58
+ currSharedModel.outputs.find((output) => output.output_type === 'error') || null;
59
+ }
60
+ // for some reason, the `CellError` object is not referentially stable,
61
+ // meaning that this condition always evaluates to `true` and the
62
+ // `activeCellErrorChanged` signal is emitted every 200ms, even when the
63
+ // error output is unchanged. this is why we have to rely on
64
+ // `execution_count` to track changes to the error output.
65
+ if (prevActiveCellError !== currActiveCellError) {
66
+ this._activeCellError = currActiveCellError;
67
+ this._activeCellErrorChanged.emit(this._activeCellError);
68
+ }
69
+ }
70
+ };
71
+ /**
72
+ * Whether the current notebook panel is visible or not.
73
+ */
74
+ this._notebookVisible = false;
75
+ /**
76
+ * The active cell.
77
+ */
78
+ this._activeCell = null;
79
+ this._available = false;
80
+ this._activeCellError = null;
81
+ this._availabilityChanged = new Signal(this);
82
+ this._activeCellErrorChanged = new Signal(this);
83
+ this._notebookTracker = options.tracker;
84
+ this._notebookTracker.activeCellChanged.connect(this._onActiveCellChanged);
85
+ (_a = options.shell.currentChanged) === null || _a === void 0 ? void 0 : _a.connect(this._onMainAreaChanged);
86
+ if (options.shell instanceof LabShell) {
87
+ (_b = options.shell.layoutModified) === null || _b === void 0 ? void 0 : _b.connect(this._onMainAreaChanged);
88
+ }
89
+ this._onMainAreaChanged();
90
+ }
91
+ /**
92
+ * Whether the notebook is available and an active cell exists.
93
+ */
94
+ get available() {
95
+ return this._available;
96
+ }
97
+ /**
98
+ * The `CellError` output within the active cell, if any.
99
+ */
100
+ get activeCellError() {
101
+ return this._activeCellError;
102
+ }
103
+ /**
104
+ * A signal emitting when the active cell changed.
105
+ */
106
+ get availabilityChanged() {
107
+ return this._availabilityChanged;
108
+ }
109
+ /**
110
+ * A signal emitting when the error state of the active cell changed.
111
+ */
112
+ get activeCellErrorChanged() {
113
+ return this._activeCellErrorChanged;
114
+ }
115
+ getContent(withError = false) {
116
+ var _a;
117
+ const sharedModel = (_a = this._notebookTracker.activeCell) === null || _a === void 0 ? void 0 : _a.model.sharedModel;
118
+ if (!sharedModel) {
119
+ return null;
120
+ }
121
+ // case where withError = false
122
+ if (!withError) {
123
+ return {
124
+ type: sharedModel.cell_type,
125
+ source: sharedModel.getSource()
126
+ };
127
+ }
128
+ // case where withError = true
129
+ const error = this._activeCellError;
130
+ if (error) {
131
+ return {
132
+ type: 'code',
133
+ source: sharedModel.getSource(),
134
+ error: {
135
+ name: error.ename,
136
+ value: error.evalue,
137
+ traceback: error.traceback
138
+ }
139
+ };
140
+ }
141
+ return null;
142
+ }
143
+ /**
144
+ * Inserts `content` in a new cell above the active cell.
145
+ */
146
+ insertAbove(content) {
147
+ const notebookPanel = this._notebookTracker.currentWidget;
148
+ if (!notebookPanel || !notebookPanel.isVisible) {
149
+ return;
150
+ }
151
+ // create a new cell above the active cell and mark new cell as active
152
+ NotebookActions.insertAbove(notebookPanel.content);
153
+ // replace content of this new active cell
154
+ this.replace(content);
155
+ }
156
+ /**
157
+ * Inserts `content` in a new cell below the active cell.
158
+ */
159
+ insertBelow(content) {
160
+ const notebookPanel = this._notebookTracker.currentWidget;
161
+ if (!notebookPanel || !notebookPanel.isVisible) {
162
+ return;
163
+ }
164
+ // create a new cell below the active cell and mark new cell as active
165
+ NotebookActions.insertBelow(notebookPanel.content);
166
+ // replace content of this new active cell
167
+ this.replace(content);
168
+ }
169
+ /**
170
+ * Replaces the contents of the active cell.
171
+ */
172
+ async replace(content) {
173
+ var _a;
174
+ const notebookPanel = this._notebookTracker.currentWidget;
175
+ if (!notebookPanel || !notebookPanel.isVisible) {
176
+ return;
177
+ }
178
+ // get reference to active cell directly from Notebook API. this avoids the
179
+ // possibility of acting on an out-of-date reference.
180
+ const activeCell = this._notebookTracker.activeCell;
181
+ if (!activeCell) {
182
+ return;
183
+ }
184
+ // wait for editor to be ready
185
+ await activeCell.ready;
186
+ // replace the content of the active cell
187
+ /**
188
+ * NOTE: calling this method sometimes emits an error to the browser console:
189
+ *
190
+ * ```
191
+ * Error: Calls to EditorView.update are not allowed while an update is in progress
192
+ * ```
193
+ *
194
+ * However, there seems to be no impact on the behavior/stability of the
195
+ * JupyterLab application after this error is logged. Furthermore, this is
196
+ * the official API for setting the content of a cell in JupyterLab 4,
197
+ * meaning that this is likely unavoidable.
198
+ */
199
+ (_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.model.sharedModel.setSource(content);
200
+ }
201
+ }
@@ -1,5 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import { SxProps, Theme } from '@mui/material';
3
+ import { IChatModel } from '../model';
4
+ import { IAutocompletionRegistry } from '../registry';
3
5
  export declare function ChatInput(props: ChatInput.IProps): JSX.Element;
4
6
  /**
5
7
  * The chat input namespace.
@@ -9,6 +11,10 @@ export declare namespace ChatInput {
9
11
  * The properties of the react element.
10
12
  */
11
13
  interface IProps {
14
+ /**
15
+ * The chat model.
16
+ */
17
+ model: IChatModel;
12
18
  /**
13
19
  * The initial value of the input (default to '')
14
20
  */
@@ -21,13 +27,17 @@ export declare namespace ChatInput {
21
27
  * The function to be called to cancel editing.
22
28
  */
23
29
  onCancel?: () => unknown;
24
- /**
25
- * Whether using shift+enter to send the message.
26
- */
27
- sendWithShiftEnter: boolean;
28
30
  /**
29
31
  * Custom mui/material styles.
30
32
  */
31
33
  sx?: SxProps<Theme>;
34
+ /**
35
+ * Autocompletion properties.
36
+ */
37
+ autocompletionRegistry?: IAutocompletionRegistry;
38
+ /**
39
+ * Autocompletion name.
40
+ */
41
+ autocompletionName?: string;
32
42
  }
33
43
  }
@@ -2,19 +2,86 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- import React, { useState } from 'react';
6
- import { Box, TextField, IconButton, InputAdornment } from '@mui/material';
5
+ import React, { useEffect, useRef, useState } from 'react';
6
+ import { Autocomplete, Box, IconButton, InputAdornment, TextField } from '@mui/material';
7
7
  import { Send, Cancel } from '@mui/icons-material';
8
8
  import clsx from 'clsx';
9
9
  const INPUT_BOX_CLASS = 'jp-chat-input-container';
10
10
  const SEND_BUTTON_CLASS = 'jp-chat-send-button';
11
11
  const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
12
12
  export function ChatInput(props) {
13
+ var _a, _b;
14
+ const { autocompletionName, autocompletionRegistry, model } = props;
15
+ const autocompletion = useRef();
13
16
  const [input, setInput] = useState(props.value || '');
17
+ const [sendWithShiftEnter, setSendWithShiftEnter] = useState((_a = model.config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false);
18
+ useEffect(() => {
19
+ model.configChanged.connect((_, config) => {
20
+ var _a;
21
+ setSendWithShiftEnter((_a = config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false);
22
+ });
23
+ }, [model]);
24
+ // The autocomplete commands options.
25
+ const [commandOptions, setCommandOptions] = useState([]);
26
+ // whether any option is highlighted in the slash command autocomplete
27
+ const [highlighted, setHighlighted] = useState(false);
28
+ // controls whether the slash command autocomplete is open
29
+ const [open, setOpen] = useState(false);
30
+ /**
31
+ * Effect: fetch the list of available autocomplete commands.
32
+ */
33
+ useEffect(() => {
34
+ if (autocompletionRegistry === undefined) {
35
+ return;
36
+ }
37
+ autocompletion.current = autocompletionName
38
+ ? autocompletionRegistry.get(autocompletionName)
39
+ : autocompletionRegistry.getDefaultCompletion();
40
+ if (autocompletion.current === undefined) {
41
+ return;
42
+ }
43
+ if (Array.isArray(autocompletion.current.commands)) {
44
+ setCommandOptions(autocompletion.current.commands);
45
+ }
46
+ else if (typeof autocompletion.current.commands === 'function') {
47
+ autocompletion.current
48
+ .commands()
49
+ .then((commands) => {
50
+ setCommandOptions(commands);
51
+ });
52
+ }
53
+ }, []);
54
+ /**
55
+ * Effect: Open the autocomplete when the user types the 'opener' string into an
56
+ * empty chat input. Close the autocomplete and reset the last selected value when
57
+ * the user clears the chat input.
58
+ */
59
+ useEffect(() => {
60
+ var _a, _b;
61
+ if (!((_a = autocompletion.current) === null || _a === void 0 ? void 0 : _a.opener)) {
62
+ return;
63
+ }
64
+ if (input === ((_b = autocompletion.current) === null || _b === void 0 ? void 0 : _b.opener)) {
65
+ setOpen(true);
66
+ return;
67
+ }
68
+ if (input === '') {
69
+ setOpen(false);
70
+ return;
71
+ }
72
+ }, [input]);
14
73
  function handleKeyDown(event) {
74
+ if (event.key !== 'Enter') {
75
+ return;
76
+ }
77
+ // do not send the message if the user was selecting a suggested command from the
78
+ // Autocomplete component.
79
+ if (highlighted) {
80
+ return;
81
+ }
15
82
  if (event.key === 'Enter' &&
16
- ((props.sendWithShiftEnter && event.shiftKey) ||
17
- (!props.sendWithShiftEnter && !event.shiftKey))) {
83
+ ((sendWithShiftEnter && event.shiftKey) ||
84
+ (!sendWithShiftEnter && !event.shiftKey))) {
18
85
  onSend();
19
86
  event.stopPropagation();
20
87
  event.preventDefault();
@@ -35,7 +102,7 @@ export function ChatInput(props) {
35
102
  props.onCancel();
36
103
  }
37
104
  // Set the helper text based on whether Shift+Enter is used for sending.
38
- const helperText = props.sendWithShiftEnter ? (React.createElement("span", null,
105
+ const helperText = sendWithShiftEnter ? (React.createElement("span", null,
39
106
  "Press ",
40
107
  React.createElement("b", null, "Shift"),
41
108
  "+",
@@ -47,14 +114,55 @@ export function ChatInput(props) {
47
114
  React.createElement("b", null, "Enter"),
48
115
  " to add a new line"));
49
116
  return (React.createElement(Box, { sx: props.sx, className: clsx(INPUT_BOX_CLASS) },
50
- React.createElement(Box, { sx: { display: 'flex' } },
51
- React.createElement(TextField, { value: input, onChange: e => setInput(e.target.value), fullWidth: true, variant: "outlined", multiline: true, onKeyDown: handleKeyDown, placeholder: "Start chatting", InputProps: {
117
+ React.createElement(Autocomplete, { options: commandOptions, value: props.value, open: open, autoHighlight: true, freeSolo: true,
118
+ // ensure the autocomplete popup always renders on top
119
+ componentsProps: {
120
+ popper: {
121
+ placement: 'top'
122
+ },
123
+ paper: {
124
+ sx: {
125
+ border: '1px solid lightgray'
126
+ }
127
+ }
128
+ }, ListboxProps: {
129
+ sx: {
130
+ '& .MuiAutocomplete-option': {
131
+ padding: 2
132
+ }
133
+ }
134
+ }, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "outlined", multiline: true, onKeyDown: handleKeyDown, placeholder: "Start chatting", InputProps: {
135
+ ...params.InputProps,
52
136
  endAdornment: (React.createElement(InputAdornment, { position: "end" },
53
- props.onCancel && (React.createElement(IconButton, { size: "small", color: "primary", onClick: onCancel, disabled: !input.trim().length, title: 'Cancel edition', className: clsx(CANCEL_BUTTON_CLASS) },
137
+ props.onCancel && (React.createElement(IconButton, { size: "small", color: "primary", onClick: onCancel, title: 'Cancel edition', className: clsx(CANCEL_BUTTON_CLASS) },
54
138
  React.createElement(Cancel, null))),
55
- React.createElement(IconButton, { size: "small", color: "primary", onClick: onSend, disabled: !input.trim().length, title: `Send message ${props.sendWithShiftEnter ? '(SHIFT+ENTER)' : '(ENTER)'}`, className: clsx(SEND_BUTTON_CLASS) },
139
+ React.createElement(IconButton, { size: "small", color: "primary", onClick: onSend, disabled: props.onCancel
140
+ ? input === props.value
141
+ : !input.trim().length, title: `Send message ${sendWithShiftEnter ? '(SHIFT+ENTER)' : '(ENTER)'}`, className: clsx(SEND_BUTTON_CLASS) },
56
142
  React.createElement(Send, null))))
57
143
  }, FormHelperTextProps: {
58
144
  sx: { marginLeft: 'auto', marginRight: 0 }
59
- }, helperText: input.length > 2 ? helperText : ' ' }))));
145
+ }, helperText: input.length > 2 ? helperText : ' ' })), ...(_b = autocompletion.current) === null || _b === void 0 ? void 0 : _b.props, inputValue: input, onInputChange: (_, newValue) => {
146
+ setInput(newValue);
147
+ }, onHighlightChange:
148
+ /**
149
+ * On highlight change: set `highlighted` to whether an option is
150
+ * highlighted by the user.
151
+ *
152
+ * This isn't called when an option is selected for some reason, so we
153
+ * need to call `setHighlighted(false)` in `onClose()`.
154
+ */
155
+ (_, highlightedOption) => {
156
+ setHighlighted(!!highlightedOption);
157
+ }, onClose:
158
+ /**
159
+ * On close: set `highlighted` to `false` and close the popup by
160
+ * setting `open` to `false`.
161
+ */
162
+ () => {
163
+ setHighlighted(false);
164
+ setOpen(false);
165
+ },
166
+ // hide default extra right padding in the text field
167
+ disableClearable: true })));
60
168
  }
@@ -1,32 +1,62 @@
1
- /// <reference types="react" />
2
1
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
3
2
  import type { SxProps, Theme } from '@mui/material';
3
+ import React from 'react';
4
4
  import { IChatModel } from '../model';
5
- import { IChatMessage, IUser } from '../types';
5
+ import { IChatMessage } from '../types';
6
+ /**
7
+ * The base components props.
8
+ */
6
9
  type BaseMessageProps = {
7
10
  rmRegistry: IRenderMimeRegistry;
8
11
  model: IChatModel;
9
12
  };
10
- type ChatMessageProps = BaseMessageProps & {
13
+ /**
14
+ * The messages list component.
15
+ */
16
+ export declare function ChatMessages(props: BaseMessageProps): JSX.Element;
17
+ /**
18
+ * The message header props.
19
+ */
20
+ type ChatMessageHeaderProps = {
11
21
  message: IChatMessage;
12
- };
13
- type ChatMessagesProps = BaseMessageProps & {
14
- messages: IChatMessage[];
15
- };
16
- export type ChatMessageHeaderProps = IUser & {
17
- timestamp: number;
18
- rawTime?: boolean;
19
- deleted?: boolean;
20
- edited?: boolean;
21
22
  sx?: SxProps<Theme>;
22
23
  };
24
+ /**
25
+ * The message header component.
26
+ */
23
27
  export declare function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element;
24
28
  /**
25
- * The messages list UI.
29
+ * The message component props.
26
30
  */
27
- export declare function ChatMessages(props: ChatMessagesProps): JSX.Element;
31
+ type ChatMessageProps = BaseMessageProps & {
32
+ /**
33
+ * The message to display.
34
+ */
35
+ message: IChatMessage;
36
+ /**
37
+ * The index of the message in the list.
38
+ */
39
+ index: number;
40
+ /**
41
+ * The intersection observer for all the messages.
42
+ */
43
+ observer: IntersectionObserver | null;
44
+ };
28
45
  /**
29
- * the message UI.
46
+ * The message component body.
30
47
  */
31
48
  export declare function ChatMessage(props: ChatMessageProps): JSX.Element;
49
+ /**
50
+ * The navigation component props.
51
+ */
52
+ type NavigationProps = BaseMessageProps & {
53
+ /**
54
+ * The reference to the messages container.
55
+ */
56
+ refMsgBox: React.RefObject<HTMLDivElement>;
57
+ };
58
+ /**
59
+ * The navigation component, to navigate to unread messages.
60
+ */
61
+ export declare function Navigation(props: NavigationProps): JSX.Element;
32
62
  export {};