@jupyter/chat 0.19.0-alpha.2 → 0.19.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 (65) hide show
  1. package/lib/__tests__/model.spec.js +2 -2
  2. package/lib/components/chat.js +29 -2
  3. package/lib/components/index.d.ts +1 -0
  4. package/lib/components/index.js +1 -0
  5. package/lib/components/input/buttons/attach-button.js +1 -1
  6. package/lib/components/input/buttons/cancel-button.js +1 -1
  7. package/lib/components/input/buttons/save-edit-button.js +1 -1
  8. package/lib/components/input/buttons/send-button.js +3 -1
  9. package/lib/components/input/buttons/stop-button.js +1 -1
  10. package/lib/components/input/chat-input.js +3 -30
  11. package/lib/components/input/index.d.ts +0 -1
  12. package/lib/components/input/index.js +0 -1
  13. package/lib/components/messages/footer.d.ts +2 -2
  14. package/lib/components/messages/header.d.ts +2 -2
  15. package/lib/components/messages/header.js +13 -6
  16. package/lib/components/messages/message.d.ts +2 -2
  17. package/lib/components/messages/message.js +16 -1
  18. package/lib/components/messages/messages.js +3 -2
  19. package/lib/components/messages/toolbar.js +6 -14
  20. package/lib/components/mui-extras/tooltipped-button.d.ts +5 -26
  21. package/lib/components/mui-extras/tooltipped-button.js +4 -33
  22. package/lib/components/mui-extras/tooltipped-icon-button.d.ts +12 -22
  23. package/lib/components/mui-extras/tooltipped-icon-button.js +10 -8
  24. package/lib/components/writing-indicator.d.ts +20 -0
  25. package/lib/components/{input/writing-indicator.js → writing-indicator.js} +3 -2
  26. package/lib/message.d.ts +41 -0
  27. package/lib/message.js +74 -0
  28. package/lib/model.d.ts +13 -13
  29. package/lib/model.js +9 -7
  30. package/lib/registers/footers.d.ts +2 -2
  31. package/lib/theme-provider.d.ts +19 -0
  32. package/lib/theme-provider.js +74 -3
  33. package/lib/types.d.ts +21 -3
  34. package/lib/widgets/chat-widget.d.ts +4 -0
  35. package/lib/widgets/chat-widget.js +52 -8
  36. package/lib/widgets/multichat-panel.js +1 -1
  37. package/package.json +3 -1
  38. package/src/__tests__/model.spec.ts +7 -7
  39. package/src/components/chat.tsx +35 -1
  40. package/src/components/index.ts +1 -0
  41. package/src/components/input/buttons/attach-button.tsx +1 -1
  42. package/src/components/input/buttons/cancel-button.tsx +1 -1
  43. package/src/components/input/buttons/save-edit-button.tsx +1 -1
  44. package/src/components/input/buttons/send-button.tsx +4 -1
  45. package/src/components/input/buttons/stop-button.tsx +1 -1
  46. package/src/components/input/chat-input.tsx +1 -34
  47. package/src/components/input/index.ts +0 -1
  48. package/src/components/messages/footer.tsx +2 -2
  49. package/src/components/messages/header.tsx +20 -8
  50. package/src/components/messages/message.tsx +23 -3
  51. package/src/components/messages/messages.tsx +9 -4
  52. package/src/components/messages/toolbar.tsx +14 -14
  53. package/src/components/mui-extras/tooltipped-button.tsx +9 -43
  54. package/src/components/mui-extras/tooltipped-icon-button.tsx +17 -38
  55. package/src/components/{input/writing-indicator.tsx → writing-indicator.tsx} +9 -4
  56. package/src/message.ts +83 -0
  57. package/src/model.ts +25 -22
  58. package/src/registers/footers.ts +2 -2
  59. package/src/theme-provider.ts +95 -3
  60. package/src/types.ts +23 -3
  61. package/src/widgets/chat-widget.tsx +61 -10
  62. package/src/widgets/multichat-panel.tsx +5 -1
  63. package/style/chat.css +10 -0
  64. package/style/input.css +4 -0
  65. package/lib/components/input/writing-indicator.d.ts +0 -15
@@ -52,7 +52,7 @@ describe('test chat model', () => {
52
52
  });
53
53
  model.messageAdded(msg);
54
54
  expect(messages).toHaveLength(1);
55
- expect(messages[0]).toBe(msg);
55
+ expect(messages[0].content).toBe(msg);
56
56
  });
57
57
  it('should format message', () => {
58
58
  model = new TestChat();
@@ -62,7 +62,7 @@ describe('test chat model', () => {
62
62
  });
63
63
  model.messageAdded({ ...msg });
64
64
  expect(messages).toHaveLength(1);
65
- expect(messages[0]).not.toBe(msg);
65
+ expect(messages[0].content).not.toBe(msg);
66
66
  expect(messages[0].body).toBe('formatted msg');
67
67
  });
68
68
  });
@@ -6,17 +6,40 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
6
6
  import SettingsIcon from '@mui/icons-material/Settings';
7
7
  import { IconButton } from '@mui/material';
8
8
  import { Box } from '@mui/system';
9
- import React, { useState } from 'react';
9
+ import React, { useEffect, useState } from 'react';
10
10
  import { ChatInput, InputToolbarRegistry } from './input';
11
11
  import { JlThemeProvider } from './jl-theme-provider';
12
12
  import { ChatMessages } from './messages';
13
+ import { WritingIndicator } from './writing-indicator';
13
14
  import { ChatReactContext } from '../context';
14
15
  export function ChatBody(props) {
15
16
  const { model } = props;
17
+ const [writers, setWriters] = useState([]);
16
18
  let { inputToolbarRegistry } = props;
17
19
  if (!inputToolbarRegistry) {
18
20
  inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
19
21
  }
22
+ /**
23
+ * Handle the changes in the writers list.
24
+ */
25
+ useEffect(() => {
26
+ var _a;
27
+ if (!model) {
28
+ return;
29
+ }
30
+ const updateWriters = (_, writers) => {
31
+ // Show all writers for now - AI generating responses will have messageID
32
+ setWriters(writers);
33
+ };
34
+ // Set initial writers state
35
+ const initialWriters = model.writers;
36
+ setWriters(initialWriters);
37
+ (_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(updateWriters);
38
+ return () => {
39
+ var _a;
40
+ (_a = model === null || model === void 0 ? void 0 : model.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(updateWriters);
41
+ };
42
+ }, [model]);
20
43
  // const horizontalPadding = props.area === 'main' ? 8 : 4;
21
44
  const horizontalPadding = 4;
22
45
  const contextValue = {
@@ -30,7 +53,11 @@ export function ChatBody(props) {
30
53
  paddingRight: horizontalPadding,
31
54
  paddingTop: 0,
32
55
  paddingBottom: 0
33
- }, model: model.input })));
56
+ }, model: model.input }),
57
+ React.createElement(WritingIndicator, { sx: {
58
+ paddingLeft: horizontalPadding,
59
+ paddingRight: horizontalPadding
60
+ }, writers: writers })));
34
61
  }
35
62
  export function Chat(props) {
36
63
  var _a;
@@ -6,3 +6,4 @@ export * from './jl-theme-provider';
6
6
  export * from './messages';
7
7
  export * from './mui-extras';
8
8
  export * from './scroll-container';
9
+ export * from './writing-indicator';
@@ -10,3 +10,4 @@ export * from './jl-theme-provider';
10
10
  export * from './messages';
11
11
  export * from './mui-extras';
12
12
  export * from './scroll-container';
13
+ export * from './writing-indicator';
@@ -38,7 +38,7 @@ export function AttachButton(props) {
38
38
  console.warn('Error selecting files to attach', e);
39
39
  }
40
40
  };
41
- return (React.createElement(TooltippedIconButton, { onClick: onclick, tooltip: tooltip, iconButtonProps: {
41
+ return (React.createElement(TooltippedIconButton, { onClick: onclick, tooltip: tooltip, buttonProps: {
42
42
  title: tooltip,
43
43
  className: ATTACH_BUTTON_CLASS
44
44
  } },
@@ -14,7 +14,7 @@ export function CancelButton(props) {
14
14
  return React.createElement(React.Fragment, null);
15
15
  }
16
16
  const tooltip = 'Cancel editing';
17
- return (React.createElement(TooltippedIconButton, { onClick: props.model.cancel, tooltip: tooltip, iconButtonProps: {
17
+ return (React.createElement(TooltippedIconButton, { onClick: props.model.cancel, tooltip: tooltip, buttonProps: {
18
18
  title: tooltip,
19
19
  className: CANCEL_BUTTON_CLASS
20
20
  } },
@@ -36,7 +36,7 @@ export function SaveEditButton(props) {
36
36
  await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
37
37
  model.send(model.value);
38
38
  }
39
- return (React.createElement(TooltippedIconButton, { onClick: save, tooltip: tooltip, disabled: disabled, iconButtonProps: {
39
+ return (React.createElement(TooltippedIconButton, { onClick: save, tooltip: tooltip, disabled: disabled, buttonProps: {
40
40
  title: tooltip,
41
41
  className: SAVE_EDIT_BUTTON_CLASS
42
42
  }, "aria-label": tooltip },
@@ -33,6 +33,8 @@ export function SendButton(props) {
33
33
  : 'Send message (ENTER)');
34
34
  };
35
35
  model.configChanged.connect(configChanged);
36
+ // Initialize the tooltip.
37
+ configChanged(model, model.config);
36
38
  return () => {
37
39
  var _a, _b;
38
40
  model.valueChanged.disconnect(inputChanged);
@@ -51,7 +53,7 @@ export function SendButton(props) {
51
53
  model.value = '';
52
54
  model.focus();
53
55
  }
54
- return (React.createElement(TooltippedIconButton, { onClick: send, tooltip: tooltip, disabled: disabled, iconButtonProps: {
56
+ return (React.createElement(TooltippedIconButton, { onClick: send, tooltip: tooltip, disabled: disabled, buttonProps: {
55
57
  title: tooltip,
56
58
  className: SEND_BUTTON_CLASS
57
59
  }, "aria-label": tooltip },
@@ -38,7 +38,7 @@ export function StopButton(props) {
38
38
  // This will need to be implemented based on how the chat model handles stopping AI responses
39
39
  console.log('Stop button clicked');
40
40
  }
41
- return (React.createElement(TooltippedIconButton, { onClick: stop, tooltip: tooltip, disabled: disabled, iconButtonProps: {
41
+ return (React.createElement(TooltippedIconButton, { onClick: stop, tooltip: tooltip, disabled: disabled, buttonProps: {
42
42
  title: tooltip,
43
43
  className: STOP_BUTTON_CLASS
44
44
  }, "aria-label": tooltip },
@@ -8,7 +8,6 @@ import React, { useEffect, useRef, useState } from 'react';
8
8
  import { useChatCommands } from './use-chat-commands';
9
9
  import { AttachmentPreviewList } from '../attachments';
10
10
  import { useChatContext } from '../../context';
11
- import { InputWritingIndicator } from './writing-indicator';
12
11
  const INPUT_BOX_CLASS = 'jp-chat-input-container';
13
12
  const INPUT_TEXTFIELD_CLASS = 'jp-chat-input-textfield';
14
13
  const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
@@ -23,8 +22,6 @@ export function ChatInput(props) {
23
22
  const [sendWithShiftEnter, setSendWithShiftEnter] = useState((_a = model.config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false);
24
23
  const [attachments, setAttachments] = useState(model.attachments);
25
24
  const [toolbarElements, setToolbarElements] = useState([]);
26
- const [isFocused, setIsFocused] = useState(false);
27
- const [writers, setWriters] = useState([]);
28
25
  /**
29
26
  * Auto-focus the input when the component is first mounted.
30
27
  */
@@ -80,27 +77,6 @@ export function ChatInput(props) {
80
77
  inputToolbarRegistry === null || inputToolbarRegistry === void 0 ? void 0 : inputToolbarRegistry.itemsChanged.disconnect(updateToolbar);
81
78
  };
82
79
  }, [inputToolbarRegistry]);
83
- /**
84
- * Handle the changes in the writers list.
85
- */
86
- useEffect(() => {
87
- var _a;
88
- if (!chatModel) {
89
- return;
90
- }
91
- const updateWriters = (_, writers) => {
92
- // Show all writers for now - AI generating responses will have messageID
93
- setWriters(writers);
94
- };
95
- // Set initial writers state
96
- const initialWriters = chatModel.writers;
97
- setWriters(initialWriters);
98
- (_a = chatModel.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(updateWriters);
99
- return () => {
100
- var _a;
101
- (_a = chatModel === null || chatModel === void 0 ? void 0 : chatModel.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(updateWriters);
102
- };
103
- }, [chatModel]);
104
80
  const inputExists = !!input.trim();
105
81
  /**
106
82
  * `handleKeyDown()`: callback invoked when the user presses any key in the
@@ -163,9 +139,7 @@ export function ChatInput(props) {
163
139
  return (React.createElement(Box, { sx: props.sx, className: clsx(INPUT_BOX_CLASS), "data-input-id": model.id },
164
140
  React.createElement(Box, { sx: {
165
141
  border: '1px solid',
166
- borderColor: isFocused
167
- ? 'var(--jp-brand-color1)'
168
- : 'var(--jp-border-color1)',
142
+ borderColor: 'var(--jp-border-color1)',
169
143
  borderRadius: 2,
170
144
  transition: 'border-color 0.2s ease',
171
145
  display: 'flex',
@@ -188,7 +162,7 @@ export function ChatInput(props) {
188
162
  padding: 0
189
163
  }
190
164
  }
191
- }, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "standard", className: INPUT_TEXTFIELD_CLASS, multiline: true, maxRows: 10, 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: {
165
+ }, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "standard", className: INPUT_TEXTFIELD_CLASS, multiline: true, maxRows: 10, onKeyDown: handleKeyDown, placeholder: "Type a chat message, @ to mention...", inputRef: inputRef, 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: {
192
166
  padding: 1.5,
193
167
  margin: 0,
194
168
  boxSizing: 'border-box',
@@ -235,6 +209,5 @@ export function ChatInput(props) {
235
209
  borderColor: 'var(--jp-border-color1)',
236
210
  backgroundColor: 'var(--jp-layout-color0)',
237
211
  transition: 'background-color 0.2s ease'
238
- } }, toolbarElements.map((item, index) => (React.createElement(item.element, { key: index, model: model, chatCommandRegistry: chatCommandRegistry, chatModel: chatModel, edit: props.edit }))))),
239
- React.createElement(InputWritingIndicator, { writers: writers })));
212
+ } }, toolbarElements.map((item, index) => (React.createElement(item.element, { key: index, model: model, chatCommandRegistry: chatCommandRegistry, chatModel: chatModel, edit: props.edit })))))));
240
213
  }
@@ -2,4 +2,3 @@ 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,4 +6,3 @@ 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';
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import { IChatMessage } from '../../types';
2
+ import { IMessageContent } from '../../types';
3
3
  /**
4
4
  * The chat footer component properties.
5
5
  */
@@ -7,7 +7,7 @@ export interface IMessageFootersProps {
7
7
  /**
8
8
  * The chat model.
9
9
  */
10
- message: IChatMessage;
10
+ message: IMessageContent;
11
11
  }
12
12
  /**
13
13
  * The chat footer component, which displays footer components on a row according to
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import { IChatMessage } from '../../types';
2
+ import { IMessage } from '../../types';
3
3
  /**
4
4
  * The message header props.
5
5
  */
@@ -7,7 +7,7 @@ type ChatMessageHeaderProps = {
7
7
  /**
8
8
  * The chat message.
9
9
  */
10
- message: IChatMessage;
10
+ message: IMessage;
11
11
  /**
12
12
  * Whether this message is from the current user.
13
13
  */
@@ -12,11 +12,7 @@ const MESSAGE_TIME_CLASS = 'jp-chat-message-time';
12
12
  */
13
13
  export function ChatMessageHeader(props) {
14
14
  var _a, _b;
15
- const message = props.message;
16
- // Don't render header for stacked messages not deleted or edited.
17
- if (message.stacked && !message.deleted && !message.edited) {
18
- return React.createElement(React.Fragment, null);
19
- }
15
+ const [message, setMessage] = useState(props.message.content);
20
16
  // Flag to display only the deleted or edited information (stacked message).
21
17
  const onlyState = message.stacked && (message.deleted || message.edited);
22
18
  const [datetime, setDatetime] = useState({});
@@ -55,9 +51,20 @@ export function ChatMessageHeader(props) {
55
51
  setDatetime(newDatetime);
56
52
  }
57
53
  });
54
+ // Listen for changes in the current message.
55
+ useEffect(() => {
56
+ function messageChanged() {
57
+ setMessage(props.message.content);
58
+ }
59
+ props.message.changed.connect(messageChanged);
60
+ return () => {
61
+ props.message.changed.disconnect(messageChanged);
62
+ };
63
+ }, [props.message]);
58
64
  const avatar = message.stacked ? null : Avatar({ user: sender });
59
65
  const name = (_b = (_a = sender.display_name) !== null && _a !== void 0 ? _a : sender.name) !== null && _b !== void 0 ? _b : (sender.username || 'User undefined');
60
- return (React.createElement(Box, { className: MESSAGE_HEADER_CLASS, sx: {
66
+ // Don't render header for stacked messages not deleted or edited.
67
+ return message.stacked && !message.deleted && !message.edited ? (React.createElement(React.Fragment, null)) : (React.createElement(Box, { className: MESSAGE_HEADER_CLASS, sx: {
61
68
  display: 'flex',
62
69
  alignItems: 'center',
63
70
  '& > :not(:last-child)': {
@@ -1,6 +1,6 @@
1
1
  import { PromiseDelegate } from '@lumino/coreutils';
2
2
  import React from 'react';
3
- import { IChatMessage } from '../../types';
3
+ import { IMessage } from '../../types';
4
4
  /**
5
5
  * The message component props.
6
6
  */
@@ -8,7 +8,7 @@ type ChatMessageProps = {
8
8
  /**
9
9
  * The message to display.
10
10
  */
11
- message: IChatMessage;
11
+ message: IMessage;
12
12
  /**
13
13
  * The index of the message in the list.
14
14
  */
@@ -13,8 +13,8 @@ import { replaceSpanToMention } from '../../utils';
13
13
  * The message component body.
14
14
  */
15
15
  export const ChatMessage = forwardRef((props, ref) => {
16
- const { message } = props;
17
16
  const { model } = useChatContext();
17
+ const [message, setMessage] = useState(props.message.content);
18
18
  const [edit, setEdit] = useState(false);
19
19
  const [deleted, setDeleted] = useState(false);
20
20
  const [canEdit, setCanEdit] = useState(false);
@@ -39,6 +39,21 @@ export const ChatMessage = forwardRef((props, ref) => {
39
39
  setCanDelete(false);
40
40
  }
41
41
  }, [model, message]);
42
+ // Listen for changes in the current message.
43
+ useEffect(() => {
44
+ function messageChanged() {
45
+ setMessage(props.message.content);
46
+ }
47
+ props.message.changed.connect(messageChanged);
48
+ // Initialize the message when the message is re-rendered.
49
+ // FIX ? This seems to be required for outofband change, to get the new value,
50
+ // even if when an outofband change occurs, all the messages are deleted and
51
+ // recreated.
52
+ setMessage(props.message.content);
53
+ return () => {
54
+ props.message.changed.disconnect(messageChanged);
55
+ };
56
+ }, [props.message]);
42
57
  // Create an input model only if the message is edited.
43
58
  const startEdition = () => {
44
59
  var _a;
@@ -13,6 +13,7 @@ import { Navigation } from './navigation';
13
13
  import { WelcomeMessage } from './welcome';
14
14
  import { ScrollContainer } from '../scroll-container';
15
15
  import { useChatContext } from '../../context';
16
+ import { Message } from '../../message';
16
17
  export const MESSAGE_CLASS = 'jp-chat-message';
17
18
  const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
18
19
  const MESSAGE_STACKED_CLASS = 'jp-chat-message-stacked';
@@ -39,7 +40,7 @@ export function ChatMessages() {
39
40
  }
40
41
  model
41
42
  .getHistory()
42
- .then(history => setMessages(history.messages))
43
+ .then(history => setMessages(history.messages.map(message => new Message({ ...message }))))
43
44
  .catch(e => console.error(e));
44
45
  }
45
46
  fetchHistory();
@@ -150,7 +151,7 @@ export function ChatMessages() {
150
151
  model.user.username === message.sender.username;
151
152
  return (
152
153
  // extra div needed to ensure each bubble is on a new line
153
- React.createElement(Box, { key: i, sx: {
154
+ React.createElement(Box, { key: message.id, sx: {
154
155
  ...(isCurrentUser && {
155
156
  marginLeft: area === 'main' ? '25%' : '10%',
156
157
  backgroundColor: 'var(--jp-layout-color2)',
@@ -2,8 +2,8 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- // import EditIcon from '@mui/icons-material/Edit';
6
5
  import DeleteIcon from '@mui/icons-material/Delete';
6
+ import EditIcon from '@mui/icons-material/Edit';
7
7
  import { Box } from '@mui/material';
8
8
  import React from 'react';
9
9
  import { TooltippedIconButton } from '../mui-extras';
@@ -13,19 +13,11 @@ const TOOLBAR_CLASS = 'jp-chat-toolbar';
13
13
  */
14
14
  export function MessageToolbar(props) {
15
15
  const buttons = [];
16
- // if (props.edit !== undefined) {
17
- // const editButton = (
18
- // <TooltippedIconButton
19
- // tooltip={'edit'}
20
- // onClick={props.edit}
21
- // aria-label={'Edit'}
22
- // inputToolbar={false}
23
- // >
24
- // <EditIcon />
25
- // </TooltippedIconButton>
26
- // );
27
- // buttons.push(editButton);
28
- // }
16
+ if (props.edit !== undefined) {
17
+ const editButton = (React.createElement(TooltippedIconButton, { tooltip: 'edit', onClick: props.edit, "aria-label": 'Edit', inputToolbar: false },
18
+ React.createElement(EditIcon, null)));
19
+ buttons.push(editButton);
20
+ }
29
21
  if (props.delete !== undefined) {
30
22
  const deleteButton = (React.createElement(TooltippedIconButton, { tooltip: 'Delete', onClick: props.delete, "aria-label": 'Delete', inputToolbar: false },
31
23
  React.createElement(DeleteIcon, null)));
@@ -1,35 +1,14 @@
1
- import { ButtonOwnProps, ButtonProps, SxProps, TooltipProps } from '@mui/material';
1
+ import { ButtonProps, SxProps, TooltipProps } from '@mui/material';
2
2
  import React from 'react';
3
3
  export declare const TOOLTIPPED_WRAP_CLASS = "jp-chat-tooltipped-wrap";
4
- export declare const DEFAULT_BUTTON_PROPS: Partial<ButtonOwnProps>;
5
- export declare const DEFAULT_BUTTON_SX: {
6
- minWidth: string;
7
- width: string;
8
- height: string;
9
- lineHeight: number;
10
- '&:disabled': {
11
- opacity: number;
12
- };
13
- };
14
- export declare const INPUT_TOOLBAR_BUTTON_SX: {
15
- backgroundColor: string;
16
- color: string;
17
- borderRadius: string;
18
- boxShadow: string;
19
- '&:hover': {
20
- backgroundColor: string;
21
- boxShadow: string;
22
- };
23
- '&:disabled': {
24
- backgroundColor: string;
25
- color: string;
26
- opacity: number;
27
- };
28
- };
4
+ /**
5
+ * The props for the tooltipped button.
6
+ */
29
7
  export type TooltippedButtonProps = {
30
8
  onClick: React.MouseEventHandler<HTMLButtonElement>;
31
9
  tooltip: string;
32
10
  children: JSX.Element;
11
+ className?: string;
33
12
  inputToolbar?: boolean;
34
13
  disabled?: boolean;
35
14
  placement?: TooltipProps['placement'];
@@ -2,38 +2,11 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ import { classes } from '@jupyterlab/ui-components';
5
6
  import { Button } from '@mui/material';
6
7
  import React from 'react';
7
8
  import { ContrastingTooltip } from './contrasting-tooltip';
8
9
  export const TOOLTIPPED_WRAP_CLASS = 'jp-chat-tooltipped-wrap';
9
- export const DEFAULT_BUTTON_PROPS = {
10
- size: 'small',
11
- variant: 'contained'
12
- };
13
- export const DEFAULT_BUTTON_SX = {
14
- minWidth: '24px',
15
- width: '24px',
16
- height: '24px',
17
- lineHeight: 0,
18
- '&:disabled': {
19
- opacity: 0.5
20
- }
21
- };
22
- export const INPUT_TOOLBAR_BUTTON_SX = {
23
- backgroundColor: 'var(--jp-brand-color1)',
24
- color: 'white',
25
- borderRadius: '4px',
26
- boxShadow: 'none',
27
- '&:hover': {
28
- backgroundColor: 'var(--jp-brand-color0)',
29
- boxShadow: 'none'
30
- },
31
- '&:disabled': {
32
- backgroundColor: 'var(--jp-border-color2)',
33
- color: 'var(--jp-ui-font-color3)',
34
- opacity: 0.5
35
- }
36
- };
37
10
  /**
38
11
  * A component that renders an MUI `Button` with a high-contrast tooltip
39
12
  * provided by `ContrastingTooltip`. This component differs from the MUI
@@ -63,10 +36,8 @@ export function TooltippedButton(props) {
63
36
  ]
64
37
  }
65
38
  } },
66
- React.createElement("span", { style: { cursor: 'default' }, className: TOOLTIPPED_WRAP_CLASS },
67
- React.createElement(Button, { ...DEFAULT_BUTTON_PROPS, ...props.buttonProps, onClick: props.onClick, disabled: props.disabled, sx: {
68
- ...DEFAULT_BUTTON_SX,
69
- ...(((_b = props.inputToolbar) !== null && _b !== void 0 ? _b : true) && INPUT_TOOLBAR_BUTTON_SX),
39
+ React.createElement("span", { className: classes(props.className, TOOLTIPPED_WRAP_CLASS) },
40
+ React.createElement(Button, { ...(((_b = props.inputToolbar) !== null && _b !== void 0 ? _b : true) && { variant: 'input-toolbar' }), ...props.buttonProps, onClick: props.onClick, disabled: props.disabled, "aria-label": (_c = props['aria-label']) !== null && _c !== void 0 ? _c : props.tooltip, sx: {
70
41
  ...props.sx
71
- }, "aria-label": (_c = props['aria-label']) !== null && _c !== void 0 ? _c : props.tooltip }, props.children))));
42
+ } }, props.children))));
72
43
  }
@@ -1,29 +1,14 @@
1
1
  /// <reference types="react" />
2
- import { IconButtonProps, SvgIconOwnProps, TooltipProps } from '@mui/material';
3
- export type TooltippedIconButtonProps = {
4
- onClick: () => unknown;
5
- tooltip: string;
6
- children: JSX.Element;
7
- className?: string;
8
- inputToolbar?: boolean;
9
- disabled?: boolean;
10
- placement?: TooltipProps['placement'];
11
- /**
12
- * The font size of the icon. By default it will be set to 'small'.
13
- */
14
- fontSize?: SvgIconOwnProps['fontSize'];
15
- /**
16
- * The offset of the tooltip popup.
17
- *
18
- * The expected syntax is defined by the Popper library:
19
- * https://popper.js.org/docs/v2/modifiers/offset/
20
- */
21
- offset?: [number, number];
22
- 'aria-label'?: string;
2
+ import { IconButtonProps } from '@mui/material';
3
+ import { TooltippedButtonProps } from './tooltipped-button';
4
+ /**
5
+ * The props for the tooltipped icon button.
6
+ */
7
+ export type TooltippedIconButtonProps = TooltippedButtonProps & {
23
8
  /**
24
9
  * Props passed directly to the MUI `IconButton` component.
25
10
  */
26
- iconButtonProps?: IconButtonProps;
11
+ buttonProps?: IconButtonProps;
27
12
  };
28
13
  /**
29
14
  * A component that renders an MUI `IconButton` with a high-contrast tooltip
@@ -36,5 +21,10 @@ export type TooltippedIconButtonProps = {
36
21
  * - Lowers the opacity of the IconButton when disabled.
37
22
  * - Renders the IconButton with `line-height: 0` to avoid showing extra
38
23
  * vertical space in SVG icons.
24
+ *
25
+ * NOTES:
26
+ * This kind of button doesn't allow regular variants ('outlined', 'contained', 'text').
27
+ * The only one allowed is 'input-toolbar'. If you want to use one of the regular, use
28
+ * the TooltippedButton instead.
39
29
  */
40
30
  export declare function TooltippedIconButton(props: TooltippedIconButtonProps): JSX.Element;
@@ -6,7 +6,7 @@ import { classes } from '@jupyterlab/ui-components';
6
6
  import { IconButton } from '@mui/material';
7
7
  import React from 'react';
8
8
  import { ContrastingTooltip } from './contrasting-tooltip';
9
- import { DEFAULT_BUTTON_PROPS, DEFAULT_BUTTON_SX, INPUT_TOOLBAR_BUTTON_SX, TOOLTIPPED_WRAP_CLASS } from './tooltipped-button';
9
+ import { TOOLTIPPED_WRAP_CLASS } from './tooltipped-button';
10
10
  /**
11
11
  * A component that renders an MUI `IconButton` with a high-contrast tooltip
12
12
  * provided by `ContrastingTooltip`. This component differs from the MUI
@@ -18,12 +18,15 @@ import { DEFAULT_BUTTON_PROPS, DEFAULT_BUTTON_SX, INPUT_TOOLBAR_BUTTON_SX, TOOLT
18
18
  * - Lowers the opacity of the IconButton when disabled.
19
19
  * - Renders the IconButton with `line-height: 0` to avoid showing extra
20
20
  * vertical space in SVG icons.
21
+ *
22
+ * NOTES:
23
+ * This kind of button doesn't allow regular variants ('outlined', 'contained', 'text').
24
+ * The only one allowed is 'input-toolbar'. If you want to use one of the regular, use
25
+ * the TooltippedButton instead.
21
26
  */
22
27
  export function TooltippedIconButton(props) {
23
28
  var _a, _b, _c;
24
- // Override the default icon font size from 'medium' to 'small'
25
- props.children.props.fontSize = (_a = props.fontSize) !== null && _a !== void 0 ? _a : 'small';
26
- return (React.createElement(ContrastingTooltip, { title: props.tooltip, placement: (_b = props.placement) !== null && _b !== void 0 ? _b : 'top', slotProps: {
29
+ return (React.createElement(ContrastingTooltip, { title: props.tooltip, placement: (_a = props.placement) !== null && _a !== void 0 ? _a : 'top', slotProps: {
27
30
  popper: {
28
31
  modifiers: [
29
32
  {
@@ -36,8 +39,7 @@ export function TooltippedIconButton(props) {
36
39
  }
37
40
  } },
38
41
  React.createElement("span", { className: classes(props.className, TOOLTIPPED_WRAP_CLASS) },
39
- React.createElement(IconButton, { ...DEFAULT_BUTTON_PROPS, ...props.iconButtonProps, onClick: props.onClick, disabled: props.disabled, sx: {
40
- ...DEFAULT_BUTTON_SX,
41
- ...(((_c = props.inputToolbar) !== null && _c !== void 0 ? _c : true) && INPUT_TOOLBAR_BUTTON_SX)
42
- }, "aria-label": props['aria-label'] }, props.children))));
42
+ React.createElement(IconButton, { ...(((_b = props.inputToolbar) !== null && _b !== void 0 ? _b : true) && { variant: 'input-toolbar' }), ...props.buttonProps, onClick: props.onClick, disabled: props.disabled, "aria-label": (_c = props['aria-label']) !== null && _c !== void 0 ? _c : props.tooltip, sx: {
43
+ ...props.sx
44
+ } }, props.children))));
43
45
  }
@@ -0,0 +1,20 @@
1
+ /// <reference types="react" />
2
+ import { SxProps, Theme } from '@mui/material';
3
+ import { IChatModel } from '../model';
4
+ /**
5
+ * The input writing indicator component props.
6
+ */
7
+ export interface IInputWritingIndicatorProps {
8
+ /**
9
+ * The list of users currently writing.
10
+ */
11
+ writers: IChatModel.IWriter[];
12
+ /**
13
+ * Custom mui/material styles.
14
+ */
15
+ sx?: SxProps<Theme>;
16
+ }
17
+ /**
18
+ * The writing indicator component, displaying typing status.
19
+ */
20
+ export declare function WritingIndicator(props: IInputWritingIndicatorProps): JSX.Element;
@@ -30,13 +30,14 @@ function formatWritersText(writers) {
30
30
  }
31
31
  }
32
32
  /**
33
- * The input writing indicator component, displaying typing status in the chat input area.
33
+ * The writing indicator component, displaying typing status.
34
34
  */
35
- export function InputWritingIndicator(props) {
35
+ export function WritingIndicator(props) {
36
36
  const { writers } = props;
37
37
  // Always render the container to reserve space, even if no writers
38
38
  const writersText = writers.length > 0 ? formatWritersText(writers) : '';
39
39
  return (React.createElement(Box, { className: WRITERS_ELEMENT_CLASSNAME, sx: {
40
+ ...props.sx,
40
41
  minHeight: '16px'
41
42
  } },
42
43
  React.createElement(Typography, { variant: "caption", sx: {