@jupyter/chat 0.18.2 → 0.19.0-alpha.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 (74) hide show
  1. package/lib/components/attachments.js +47 -21
  2. package/lib/components/chat.d.ts +5 -0
  3. package/lib/components/chat.js +7 -6
  4. package/lib/components/code-blocks/code-toolbar.js +29 -10
  5. package/lib/components/code-blocks/copy-button.js +11 -3
  6. package/lib/components/index.d.ts +0 -1
  7. package/lib/components/index.js +0 -1
  8. package/lib/components/input/buttons/attach-button.js +7 -2
  9. package/lib/components/input/buttons/cancel-button.js +12 -10
  10. package/lib/components/input/buttons/index.d.ts +2 -0
  11. package/lib/components/input/buttons/index.js +2 -0
  12. package/lib/components/input/buttons/save-edit-button.d.ts +6 -0
  13. package/lib/components/input/buttons/save-edit-button.js +51 -0
  14. package/lib/components/input/buttons/send-button.d.ts +1 -1
  15. package/lib/components/input/buttons/send-button.js +35 -122
  16. package/lib/components/input/buttons/stop-button.d.ts +6 -0
  17. package/lib/components/input/buttons/stop-button.js +63 -0
  18. package/lib/components/input/chat-input.d.ts +15 -0
  19. package/lib/components/input/chat-input.js +109 -46
  20. package/lib/components/input/index.d.ts +1 -0
  21. package/lib/components/input/index.js +1 -0
  22. package/lib/components/input/toolbar-registry.d.ts +10 -0
  23. package/lib/components/input/toolbar-registry.js +10 -1
  24. package/lib/components/input/use-chat-commands.js +9 -4
  25. package/lib/components/input/writing-indicator.d.ts +15 -0
  26. package/lib/components/input/writing-indicator.js +50 -0
  27. package/lib/components/messages/header.d.ts +4 -0
  28. package/lib/components/messages/header.js +4 -0
  29. package/lib/components/messages/index.d.ts +0 -1
  30. package/lib/components/messages/index.js +0 -1
  31. package/lib/components/messages/message.js +1 -1
  32. package/lib/components/messages/messages.d.ts +5 -0
  33. package/lib/components/messages/messages.js +24 -14
  34. package/lib/components/messages/toolbar.js +37 -15
  35. package/lib/input-model.d.ts +14 -0
  36. package/lib/input-model.js +12 -4
  37. package/lib/model.d.ts +8 -0
  38. package/lib/model.js +6 -0
  39. package/lib/types.d.ts +4 -0
  40. package/lib/widgets/chat-widget.d.ts +4 -0
  41. package/lib/widgets/chat-widget.js +36 -11
  42. package/lib/widgets/multichat-panel.js +2 -1
  43. package/package.json +1 -1
  44. package/src/components/attachments.tsx +70 -33
  45. package/src/components/chat.tsx +13 -4
  46. package/src/components/code-blocks/code-toolbar.tsx +56 -28
  47. package/src/components/code-blocks/copy-button.tsx +21 -12
  48. package/src/components/index.ts +0 -1
  49. package/src/components/input/buttons/attach-button.tsx +8 -2
  50. package/src/components/input/buttons/cancel-button.tsx +20 -15
  51. package/src/components/input/buttons/index.ts +2 -0
  52. package/src/components/input/buttons/save-edit-button.tsx +75 -0
  53. package/src/components/input/buttons/send-button.tsx +50 -167
  54. package/src/components/input/buttons/stop-button.tsx +88 -0
  55. package/src/components/input/chat-input.tsx +188 -83
  56. package/src/components/input/index.ts +1 -0
  57. package/src/components/input/toolbar-registry.tsx +25 -1
  58. package/src/components/input/use-chat-commands.tsx +25 -5
  59. package/src/components/input/writing-indicator.tsx +83 -0
  60. package/src/components/messages/header.tsx +8 -0
  61. package/src/components/messages/index.ts +0 -1
  62. package/src/components/messages/message.tsx +1 -0
  63. package/src/components/messages/messages.tsx +63 -39
  64. package/src/components/messages/toolbar.tsx +51 -21
  65. package/src/input-model.ts +21 -0
  66. package/src/model.ts +12 -0
  67. package/src/types.ts +5 -0
  68. package/src/widgets/chat-widget.tsx +43 -12
  69. package/src/widgets/multichat-panel.tsx +2 -1
  70. package/style/chat.css +13 -141
  71. package/style/input.css +0 -58
  72. package/lib/components/messages/writers.d.ts +0 -16
  73. package/lib/components/messages/writers.js +0 -39
  74. package/src/components/messages/writers.tsx +0 -81
@@ -3,6 +3,8 @@ import { SxProps, Theme } from '@mui/material';
3
3
  import { IInputToolbarRegistry } from '.';
4
4
  import { IInputModel } from '../../input-model';
5
5
  import { IChatCommandRegistry } from '../../registers';
6
+ import { ChatArea } from '../../types';
7
+ import { IChatModel } from '../../model';
6
8
  export declare function ChatInput(props: ChatInput.IProps): JSX.Element;
7
9
  /**
8
10
  * The chat input namespace.
@@ -32,5 +34,18 @@ export declare namespace ChatInput {
32
34
  * Chat command registry.
33
35
  */
34
36
  chatCommandRegistry?: IChatCommandRegistry;
37
+ /**
38
+ * The area where the chat is displayed.
39
+ */
40
+ area?: ChatArea;
41
+ /**
42
+ * The chat model.
43
+ */
44
+ chatModel?: IChatModel;
45
+ /**
46
+ * Whether the input is in edit mode (editing an existing message).
47
+ * Defaults to false (new message mode).
48
+ */
49
+ edit?: boolean;
35
50
  }
36
51
  }
@@ -2,14 +2,14 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- import { Autocomplete, Box, TextField, Toolbar } from '@mui/material';
5
+ import { Autocomplete, Box, TextField } from '@mui/material';
6
6
  import clsx from 'clsx';
7
7
  import React, { useEffect, useRef, useState } from 'react';
8
8
  import { AttachmentPreviewList } from '../attachments';
9
9
  import { useChatCommands } from '.';
10
+ import { InputWritingIndicator } from './writing-indicator';
10
11
  const INPUT_BOX_CLASS = 'jp-chat-input-container';
11
12
  const INPUT_TEXTFIELD_CLASS = 'jp-chat-input-textfield';
12
- const INPUT_COMPONENT_CLASS = 'jp-chat-input-component';
13
13
  const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
14
14
  export function ChatInput(props) {
15
15
  var _a;
@@ -20,6 +20,16 @@ export function ChatInput(props) {
20
20
  const [sendWithShiftEnter, setSendWithShiftEnter] = useState((_a = model.config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false);
21
21
  const [attachments, setAttachments] = useState(model.attachments);
22
22
  const [toolbarElements, setToolbarElements] = useState([]);
23
+ const [isFocused, setIsFocused] = useState(false);
24
+ const [writers, setWriters] = useState([]);
25
+ /**
26
+ * Auto-focus the input when the component is first mounted.
27
+ */
28
+ useEffect(() => {
29
+ if (inputRef.current) {
30
+ inputRef.current.focus();
31
+ }
32
+ }, []);
23
33
  /**
24
34
  * Handle the changes on the model that affect the input.
25
35
  * - focus requested
@@ -67,6 +77,27 @@ export function ChatInput(props) {
67
77
  toolbarRegistry.itemsChanged.disconnect(updateToolbar);
68
78
  };
69
79
  }, [toolbarRegistry]);
80
+ /**
81
+ * Handle the changes in the writers list.
82
+ */
83
+ useEffect(() => {
84
+ var _a;
85
+ if (!props.chatModel) {
86
+ return;
87
+ }
88
+ const updateWriters = (_, writers) => {
89
+ // Show all writers for now - AI generating responses will have messageID
90
+ setWriters(writers);
91
+ };
92
+ // Set initial writers state
93
+ const initialWriters = props.chatModel.writers;
94
+ setWriters(initialWriters);
95
+ (_a = props.chatModel.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(updateWriters);
96
+ return () => {
97
+ var _a, _b;
98
+ (_b = (_a = props.chatModel) === null || _a === void 0 ? void 0 : _a.writersChanged) === null || _b === void 0 ? void 0 : _b.disconnect(updateWriters);
99
+ };
100
+ }, [props.chatModel]);
70
101
  const inputExists = !!input.trim();
71
102
  /**
72
103
  * `handleKeyDown()`: callback invoked when the user presses any key in the
@@ -91,13 +122,14 @@ export function ChatInput(props) {
91
122
  }
92
123
  /**
93
124
  * IMPORTANT: This statement ensures that when the chat commands menu is
94
- * open with a highlighted command, the "Enter" key should run that command
125
+ * open, the "Enter" key should select the command (handled by Autocomplete)
95
126
  * instead of sending the message.
96
127
  *
97
128
  * This is done by returning early and letting the event propagate to the
98
- * `Autocomplete` component.
129
+ * `Autocomplete` component, which will select the auto-highlighted option
130
+ * thanks to autoSelect: true.
99
131
  */
100
- if (chatCommands.menu.highlighted) {
132
+ if (chatCommands.menu.open) {
101
133
  return;
102
134
  }
103
135
  // remainder of this function only handles the "Enter" key pressed while the
@@ -125,50 +157,81 @@ export function ChatInput(props) {
125
157
  event.preventDefault();
126
158
  }
127
159
  }
128
- // Set the helper text based on whether Shift+Enter is used for sending.
129
- const helperText = sendWithShiftEnter ? (React.createElement("span", null,
130
- "Press ",
131
- React.createElement("b", null, "Shift"),
132
- "+",
133
- React.createElement("b", null, "Enter"),
134
- " to send message")) : (React.createElement("span", null,
135
- "Press ",
136
- React.createElement("b", null, "Shift"),
137
- "+",
138
- React.createElement("b", null, "Enter"),
139
- " to add a new line"));
140
- return (React.createElement(Box, { sx: props.sx, className: clsx(INPUT_BOX_CLASS) },
141
- React.createElement(AttachmentPreviewList, { attachments: attachments, onRemove: model.removeAttachment }),
142
- React.createElement(Autocomplete, { ...chatCommands.autocompleteProps,
143
- // ensure the autocomplete popup always renders on top
144
- slotProps: {
145
- popper: {
146
- placement: 'top'
147
- },
148
- paper: {
149
- sx: {
150
- border: '1px solid lightgray'
151
- }
152
- },
153
- listbox: {
154
- sx: {
155
- '& .MuiAutocomplete-option': {
156
- padding: 2
160
+ const horizontalPadding = props.area === 'sidebar' ? 1.5 : 2;
161
+ return (React.createElement(Box, { sx: props.sx, className: clsx(INPUT_BOX_CLASS), "data-input-id": model.id },
162
+ React.createElement(Box, { sx: {
163
+ border: '1px solid',
164
+ borderColor: isFocused
165
+ ? 'var(--jp-brand-color1)'
166
+ : 'var(--jp-border-color1)',
167
+ borderRadius: 2,
168
+ transition: 'border-color 0.2s ease',
169
+ display: 'flex',
170
+ flexDirection: 'column',
171
+ overflow: 'hidden'
172
+ } },
173
+ attachments.length > 0 && (React.createElement(Box, { sx: {
174
+ px: horizontalPadding,
175
+ pt: 1,
176
+ pb: 1
177
+ } },
178
+ React.createElement(AttachmentPreviewList, { attachments: attachments, onRemove: model.removeAttachment }))),
179
+ React.createElement(Autocomplete, { ...chatCommands.autocompleteProps, slotProps: {
180
+ ...(chatCommands.autocompleteProps.slotProps || {}),
181
+ popper: {
182
+ placement: 'top-start'
183
+ },
184
+ listbox: {
185
+ sx: {
186
+ padding: 0
157
187
  }
158
188
  }
159
- }
160
- }, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "filled", className: INPUT_TEXTFIELD_CLASS, multiline: true, onKeyDown: handleKeyDown, placeholder: "Start chatting", inputRef: inputRef, sx: { marginTop: '1px' }, onSelect: () => { var _a, _b; return (model.cursorIndex = (_b = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.selectionStart) !== null && _b !== void 0 ? _b : null); }, slotProps: {
161
- input: {
189
+ }, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "standard", className: INPUT_TEXTFIELD_CLASS, multiline: true, onKeyDown: handleKeyDown, placeholder: "Type a chat message, @ to mention...", inputRef: inputRef, onFocus: () => setIsFocused(true), onBlur: () => setIsFocused(false), onSelect: () => { var _a, _b; return (model.cursorIndex = (_b = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.selectionStart) !== null && _b !== void 0 ? _b : null); }, sx: {
190
+ padding: 1.5,
191
+ margin: 0,
192
+ backgroundColor: 'var(--jp-layout-color0)',
193
+ transition: 'background-color 0.2s ease',
194
+ '& .MuiInputBase-root': {
195
+ padding: 0,
196
+ margin: 0,
197
+ '&:before': {
198
+ display: 'none'
199
+ },
200
+ '&:after': {
201
+ display: 'none'
202
+ }
203
+ },
204
+ '& .MuiInputBase-input': {
205
+ overflowWrap: 'break-word',
206
+ wordBreak: 'break-word'
207
+ }
208
+ }, InputProps: {
162
209
  ...params.InputProps,
163
- className: INPUT_COMPONENT_CLASS
210
+ disableUnderline: true
211
+ }, FormHelperTextProps: {
212
+ sx: { display: 'none' }
213
+ } })), inputValue: input, onInputChange: (_, newValue, reason) => {
214
+ // Skip value updates when an autocomplete option is selected.
215
+ // The 'onChange' callback handles the replacement via replaceCurrentWord.
216
+ // 'selectOption' - user selected an option (newValue is just the option label)
217
+ // 'reset' - autocomplete is resetting after selection
218
+ // 'blur' - when user blurs the input (newValue is set to empty string)
219
+ if (reason === 'selectOption' ||
220
+ reason === 'reset' ||
221
+ reason === 'blur') {
222
+ return;
164
223
  }
165
- }, label: input.length > 2 ? helperText : ' ' })), inputValue: input, onInputChange: (_, newValue, reason) => {
166
- // Do not update the value if the reason is 'reset', which should occur only
167
- // if an autocompletion command has been selected. In this case, the value is
168
- // set in the 'onChange()' callback of the autocompletion (to avoid conflicts).
169
- if (reason !== 'reset') {
170
224
  model.value = newValue;
171
- }
172
- } }),
173
- React.createElement(Toolbar, { className: INPUT_TOOLBAR_CLASS }, toolbarElements.map(item => (React.createElement(item.element, { model: model, chatCommandRegistry: props.chatCommandRegistry }))))));
225
+ } }),
226
+ React.createElement(Box, { className: INPUT_TOOLBAR_CLASS, sx: {
227
+ display: 'flex',
228
+ justifyContent: 'flex-end',
229
+ gap: 2,
230
+ padding: 1.5,
231
+ borderTop: '1px solid',
232
+ borderColor: 'var(--jp-border-color1)',
233
+ backgroundColor: 'var(--jp-layout-color0)',
234
+ transition: 'background-color 0.2s ease'
235
+ } }, toolbarElements.map((item, index) => (React.createElement(item.element, { key: index, model: model, chatCommandRegistry: props.chatCommandRegistry, chatModel: props.chatModel, edit: props.edit }))))),
236
+ React.createElement(InputWritingIndicator, { writers: writers })));
174
237
  }
@@ -2,3 +2,4 @@ export * from './buttons';
2
2
  export * from './chat-input';
3
3
  export * from './toolbar-registry';
4
4
  export * from './use-chat-commands';
5
+ export * from './writing-indicator';
@@ -6,3 +6,4 @@ export * from './buttons';
6
6
  export * from './chat-input';
7
7
  export * from './toolbar-registry';
8
8
  export * from './use-chat-commands';
9
+ export * from './writing-indicator';
@@ -3,6 +3,7 @@ import { ISignal } from '@lumino/signaling';
3
3
  import * as React from 'react';
4
4
  import { IInputModel } from '../../input-model';
5
5
  import { IChatCommandRegistry } from '../../registers';
6
+ import { IChatModel } from '../../model';
6
7
  /**
7
8
  * The toolbar registry interface.
8
9
  */
@@ -97,6 +98,15 @@ export declare namespace InputToolbarRegistry {
97
98
  * `onSubmit()` on all command providers before sending the message.
98
99
  */
99
100
  chatCommandRegistry?: IChatCommandRegistry;
101
+ /**
102
+ * The chat model. Provides access to messages, writers, and other chat state.
103
+ */
104
+ chatModel?: IChatModel;
105
+ /**
106
+ * Whether the input is in edit mode (editing an existing message).
107
+ * Defaults to false (new message mode).
108
+ */
109
+ edit?: boolean;
100
110
  }
101
111
  /**
102
112
  * The default toolbar registry if none is provided.
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { Token } from '@lumino/coreutils';
6
6
  import { Signal } from '@lumino/signaling';
7
- import { AttachButton, CancelButton, SendButton } from './buttons';
7
+ import { AttachButton, CancelButton, SaveEditButton, SendButton } from './buttons';
8
8
  /**
9
9
  * The toolbar registry implementation.
10
10
  */
@@ -72,10 +72,19 @@ export class InputToolbarRegistry {
72
72
  */
73
73
  function defaultToolbarRegistry() {
74
74
  const registry = new InputToolbarRegistry();
75
+ // TODO: Re-enable stop button once logic is fully implemented
76
+ // registry.addItem('stop', {
77
+ // element: StopButton,
78
+ // position: 90
79
+ // });
75
80
  registry.addItem('send', {
76
81
  element: SendButton,
77
82
  position: 100
78
83
  });
84
+ registry.addItem('saveEdit', {
85
+ element: SaveEditButton,
86
+ position: 95
87
+ });
79
88
  registry.addItem('attach', {
80
89
  element: AttachButton,
81
90
  position: 20
@@ -3,7 +3,7 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import { LabIcon } from '@jupyterlab/ui-components';
6
- import { Box } from '@mui/material';
6
+ import { Box, Typography } from '@mui/material';
7
7
  import React, { useEffect, useState } from 'react';
8
8
  /**
9
9
  * A hook which automatically returns the list of command options given the
@@ -103,18 +103,23 @@ export function useChatCommands(inputModel, chatCommandRegistry) {
103
103
  renderOption: (defaultProps, command, __, ___) => {
104
104
  const { key, ...listItemProps } = defaultProps;
105
105
  const commandIcon = React.isValidElement(command.icon) ? (command.icon) : (React.createElement("span", null, command.icon instanceof LabIcon ? (React.createElement(command.icon.react, null)) : (command.icon)));
106
- return (React.createElement(Box, { key: key, component: "li", ...listItemProps },
106
+ return (React.createElement(Box, { key: key, component: "li", ...listItemProps, sx: {
107
+ ...(listItemProps.sx || {}),
108
+ padding: '4px 8px !important',
109
+ gap: 2
110
+ } },
107
111
  commandIcon,
108
- React.createElement("p", { className: "jp-chat-command-name" }, command.name),
112
+ React.createElement(Typography, { variant: "body2", component: "span", className: "jp-chat-command-name" }, command.name),
109
113
  command.description && (React.createElement(React.Fragment, null,
110
114
  React.createElement("span", null, " - "),
111
- React.createElement("p", { className: "jp-chat-command-description" }, command.description)))));
115
+ React.createElement(Typography, { variant: "caption", component: "span", color: "text.secondary" }, command.description)))));
112
116
  },
113
117
  // always show all options, since command providers should exclusively
114
118
  // define what commands are added to the menu.
115
119
  filterOptions: (commands) => commands,
116
120
  value: null,
117
121
  autoHighlight: true,
122
+ autoSelect: true,
118
123
  freeSolo: true,
119
124
  disableClearable: true,
120
125
  onChange,
@@ -0,0 +1,15 @@
1
+ /// <reference types="react" />
2
+ import { IChatModel } from '../../model';
3
+ /**
4
+ * The input writing indicator component props.
5
+ */
6
+ export interface IInputWritingIndicatorProps {
7
+ /**
8
+ * The list of users currently writing.
9
+ */
10
+ writers: IChatModel.IWriter[];
11
+ }
12
+ /**
13
+ * The input writing indicator component, displaying typing status in the chat input area.
14
+ */
15
+ export declare function InputWritingIndicator(props: IInputWritingIndicatorProps): JSX.Element;
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { Box, Typography } from '@mui/material';
6
+ import React from 'react';
7
+ /**
8
+ * Classname on the root element. Used in E2E tests.
9
+ */
10
+ const WRITERS_ELEMENT_CLASSNAME = 'jp-chat-writers';
11
+ /**
12
+ * Format the writers list into a readable string.
13
+ * Examples: "Alice is typing...", "Alice and Bob are typing...", "Alice, Bob, and Carol are typing..."
14
+ */
15
+ function formatWritersText(writers) {
16
+ if (writers.length === 0) {
17
+ return '';
18
+ }
19
+ const names = writers.map(w => { var _a, _b, _c; return (_c = (_b = (_a = w.user.display_name) !== null && _a !== void 0 ? _a : w.user.name) !== null && _b !== void 0 ? _b : w.user.username) !== null && _c !== void 0 ? _c : 'Unknown'; });
20
+ if (names.length === 1) {
21
+ return `${names[0]} is typing...`;
22
+ }
23
+ else if (names.length === 2) {
24
+ return `${names[0]} and ${names[1]} are typing...`;
25
+ }
26
+ else {
27
+ const allButLast = names.slice(0, -1).join(', ');
28
+ const last = names[names.length - 1];
29
+ return `${allButLast}, and ${last} are typing...`;
30
+ }
31
+ }
32
+ /**
33
+ * The input writing indicator component, displaying typing status in the chat input area.
34
+ */
35
+ export function InputWritingIndicator(props) {
36
+ const { writers } = props;
37
+ // Always render the container to reserve space, even if no writers
38
+ const writersText = writers.length > 0 ? formatWritersText(writers) : '';
39
+ return (React.createElement(Box, { className: WRITERS_ELEMENT_CLASSNAME, sx: {
40
+ minHeight: '16px'
41
+ } },
42
+ React.createElement(Typography, { variant: "caption", sx: {
43
+ color: 'var(--jp-ui-font-color2)',
44
+ display: 'block',
45
+ fontSize: '10px',
46
+ fontFamily: 'var(--jp-ui-font-family)',
47
+ lineHeight: '16px',
48
+ visibility: writers.length > 0 ? 'visible' : 'hidden'
49
+ } }, writersText || '\u00A0')));
50
+ }
@@ -8,6 +8,10 @@ type ChatMessageHeaderProps = {
8
8
  * The chat message.
9
9
  */
10
10
  message: IChatMessage;
11
+ /**
12
+ * Whether this message is from the current user.
13
+ */
14
+ isCurrentUser?: boolean;
11
15
  };
12
16
  /**
13
17
  * The message header component.
@@ -12,6 +12,10 @@ const MESSAGE_TIME_CLASS = 'jp-chat-message-time';
12
12
  */
13
13
  export function ChatMessageHeader(props) {
14
14
  var _a, _b;
15
+ // Don't render header for stacked messages or current user messages
16
+ if (props.message.stacked || props.isCurrentUser) {
17
+ return React.createElement(React.Fragment, null);
18
+ }
15
19
  const [datetime, setDatetime] = useState({});
16
20
  const message = props.message;
17
21
  const sender = message.sender;
@@ -6,4 +6,3 @@ export * from './messages';
6
6
  export * from './navigation';
7
7
  export * from './toolbar';
8
8
  export * from './welcome';
9
- export * from './writers';
@@ -10,4 +10,3 @@ export * from './messages';
10
10
  export * from './navigation';
11
11
  export * from './toolbar';
12
12
  export * from './welcome';
13
- export * from './writers';
@@ -94,7 +94,7 @@ export const ChatMessage = forwardRef((props, ref) => {
94
94
  };
95
95
  // Empty if the message has been deleted.
96
96
  return deleted ? (React.createElement("div", { ref: ref, "data-index": props.index })) : (React.createElement("div", { ref: ref, "data-index": props.index },
97
- edit && canEdit && model.getEditionModel(message.id) ? (React.createElement(ChatInput, { onCancel: () => cancelEdition(), model: model.getEditionModel(message.id), chatCommandRegistry: props.chatCommandRegistry, toolbarRegistry: props.inputToolbarRegistry })) : (React.createElement(MessageRenderer, { rmRegistry: rmRegistry, markdownStr: message.body, model: model, edit: canEdit ? startEdition : undefined, delete: canDelete ? () => deleteMessage(message.id) : undefined, rendered: props.renderedPromise })),
97
+ edit && canEdit && model.getEditionModel(message.id) ? (React.createElement(ChatInput, { onCancel: () => cancelEdition(), model: model.getEditionModel(message.id), chatCommandRegistry: props.chatCommandRegistry, toolbarRegistry: props.inputToolbarRegistry, edit: true })) : (React.createElement(MessageRenderer, { rmRegistry: rmRegistry, markdownStr: message.body, model: model, edit: canEdit ? startEdition : undefined, delete: canDelete ? () => deleteMessage(message.id) : undefined, rendered: props.renderedPromise })),
98
98
  message.attachments && !edit && (
99
99
  // Display the attachments only if message is not edited, otherwise the
100
100
  // input component display them.
@@ -3,6 +3,7 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
3
3
  import { IInputToolbarRegistry } from '../input';
4
4
  import { IChatCommandRegistry, IMessageFooterRegistry } from '../../registers';
5
5
  import { IChatModel } from '../../model';
6
+ import { ChatArea } from '../../types';
6
7
  export declare const MESSAGE_CLASS = "jp-chat-message";
7
8
  /**
8
9
  * The base components props.
@@ -32,6 +33,10 @@ export type BaseMessageProps = {
32
33
  * The welcome message.
33
34
  */
34
35
  welcomeMessage?: string;
36
+ /**
37
+ * The area where the chat is displayed.
38
+ */
39
+ area?: ChatArea;
35
40
  };
36
41
  /**
37
42
  * The messages list component.
@@ -11,7 +11,6 @@ import { ChatMessageHeader } from './header';
11
11
  import { ChatMessage } from './message';
12
12
  import { Navigation } from './navigation';
13
13
  import { WelcomeMessage } from './welcome';
14
- import { WritingUsersList } from './writers';
15
14
  import { ScrollContainer } from '../scroll-container';
16
15
  export const MESSAGE_CLASS = 'jp-chat-message';
17
16
  const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
@@ -23,7 +22,6 @@ export function ChatMessages(props) {
23
22
  const { model } = props;
24
23
  const [messages, setMessages] = useState(model.messages);
25
24
  const refMsgBox = useRef(null);
26
- const [currentWriters, setCurrentWriters] = useState([]);
27
25
  const [allRendered, setAllRendered] = useState(false);
28
26
  // The list of message DOM and their rendered promises.
29
27
  const listRef = useRef([]);
@@ -42,25 +40,17 @@ export function ChatMessages(props) {
42
40
  .catch(e => console.error(e));
43
41
  }
44
42
  fetchHistory();
45
- setCurrentWriters([]);
46
43
  }, [model]);
47
44
  /**
48
45
  * Effect: listen to chat messages.
49
46
  */
50
47
  useEffect(() => {
51
- var _a;
52
48
  function handleChatEvents() {
53
49
  setMessages([...model.messages]);
54
50
  }
55
- function handleWritersChange(_, writers) {
56
- setCurrentWriters(writers.map(writer => writer.user));
57
- }
58
51
  model.messagesUpdated.connect(handleChatEvents);
59
- (_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(handleWritersChange);
60
52
  return function cleanup() {
61
- var _a;
62
53
  model.messagesUpdated.disconnect(handleChatEvents);
63
- (_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(handleChatEvents);
64
54
  };
65
55
  }, [model]);
66
56
  /**
@@ -122,18 +112,38 @@ export function ChatMessages(props) {
122
112
  });
123
113
  };
124
114
  }, [messages, allRendered]);
115
+ const horizontalPadding = props.area === 'main' ? 8 : 4;
125
116
  return (React.createElement(React.Fragment, null,
126
117
  React.createElement(ScrollContainer, { sx: { flexGrow: 1 } },
127
118
  props.welcomeMessage && (React.createElement(WelcomeMessage, { rmRegistry: props.rmRegistry, content: props.welcomeMessage })),
128
- React.createElement(Box, { ref: refMsgBox, className: clsx(MESSAGES_BOX_CLASS) }, messages.map((message, i) => {
119
+ React.createElement(Box, { sx: {
120
+ paddingLeft: horizontalPadding,
121
+ paddingRight: horizontalPadding,
122
+ paddingTop: 4,
123
+ paddingBottom: 16,
124
+ display: 'flex',
125
+ flexDirection: 'column',
126
+ gap: 4
127
+ }, ref: refMsgBox, className: clsx(MESSAGES_BOX_CLASS) }, messages
128
+ .filter(message => !message.deleted)
129
+ .map((message, i) => {
129
130
  renderedPromise.current[i] = new PromiseDelegate();
131
+ const isCurrentUser = model.user !== undefined &&
132
+ model.user.username === message.sender.username;
130
133
  return (
131
134
  // extra div needed to ensure each bubble is on a new line
132
- React.createElement(Box, { key: i, className: clsx(MESSAGE_CLASS, message.stacked ? MESSAGE_STACKED_CLASS : '') },
133
- React.createElement(ChatMessageHeader, { message: message }),
135
+ React.createElement(Box, { key: i, sx: {
136
+ ...(isCurrentUser && {
137
+ marginLeft: props.area === 'main' ? '25%' : '10%',
138
+ backgroundColor: 'var(--jp-layout-color2)',
139
+ border: 'none',
140
+ borderRadius: 2,
141
+ padding: 2
142
+ })
143
+ }, className: clsx(MESSAGE_CLASS, message.stacked ? MESSAGE_STACKED_CLASS : '') },
144
+ React.createElement(ChatMessageHeader, { message: message, isCurrentUser: isCurrentUser }),
134
145
  React.createElement(ChatMessage, { ...props, message: message, index: i, renderedPromise: renderedPromise.current[i], ref: el => (listRef.current[i] = el) }),
135
146
  props.messageFooterRegistry && (React.createElement(MessageFooterComponent, { registry: props.messageFooterRegistry, message: message, model: model }))));
136
147
  }))),
137
- React.createElement(WritingUsersList, { writers: currentWriters }),
138
148
  React.createElement(Navigation, { ...props, refMsgBox: refMsgBox, allRendered: allRendered })));
139
149
  }
@@ -2,7 +2,9 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- import { ToolbarButtonComponent, deleteIcon, editIcon } from '@jupyterlab/ui-components';
5
+ // import EditIcon from '@mui/icons-material/Edit';
6
+ import DeleteIcon from '@mui/icons-material/Delete';
7
+ import { Box, IconButton, Tooltip } from '@mui/material';
6
8
  import React from 'react';
7
9
  const TOOLBAR_CLASS = 'jp-chat-toolbar';
8
10
  /**
@@ -10,21 +12,41 @@ const TOOLBAR_CLASS = 'jp-chat-toolbar';
10
12
  */
11
13
  export function MessageToolbar(props) {
12
14
  const buttons = [];
13
- if (props.edit !== undefined) {
14
- const editButton = ToolbarButtonComponent({
15
- icon: editIcon,
16
- onClick: props.edit,
17
- tooltip: 'Edit'
18
- });
19
- buttons.push(editButton);
20
- }
15
+ // if (props.edit !== undefined) {
16
+ // const editButton = (
17
+ // <Tooltip key="edit" title="Edit" placement="top" arrow>
18
+ // <span>
19
+ // <IconButton
20
+ // onClick={props.edit}
21
+ // aria-label="Edit"
22
+ // sx={{
23
+ // width: '24px',
24
+ // height: '24px',
25
+ // padding: 0,
26
+ // lineHeight: 0
27
+ // }}
28
+ // >
29
+ // <EditIcon sx={{ fontSize: '16px' }} />
30
+ // </IconButton>
31
+ // </span>
32
+ // </Tooltip>
33
+ // );
34
+ // buttons.push(editButton);
35
+ // }
21
36
  if (props.delete !== undefined) {
22
- const deleteButton = ToolbarButtonComponent({
23
- icon: deleteIcon,
24
- onClick: props.delete,
25
- tooltip: 'Delete'
26
- });
37
+ const deleteButton = (React.createElement(Tooltip, { key: "delete", title: "Delete", placement: "top", arrow: true },
38
+ React.createElement("span", null,
39
+ React.createElement(IconButton, { onClick: props.delete, "aria-label": "Delete", sx: {
40
+ width: '24px',
41
+ height: '24px',
42
+ padding: 0,
43
+ lineHeight: 0
44
+ } },
45
+ React.createElement(DeleteIcon, { sx: { fontSize: '16px' } })))));
27
46
  buttons.push(deleteButton);
28
47
  }
29
- return (React.createElement("div", { className: TOOLBAR_CLASS }, buttons.map(toolbarButton => toolbarButton)));
48
+ return (React.createElement(Box, { className: TOOLBAR_CLASS, sx: {
49
+ display: 'flex',
50
+ gap: 2
51
+ } }, buttons));
30
52
  }