@jupyter/chat 0.13.0 → 0.15.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 (96) hide show
  1. package/lib/active-cell-manager.d.ts +2 -0
  2. package/lib/active-cell-manager.js +7 -2
  3. package/lib/components/avatar.d.ts +20 -0
  4. package/lib/components/avatar.js +29 -0
  5. package/lib/components/chat.d.ts +1 -3
  6. package/lib/components/chat.js +2 -3
  7. package/lib/components/index.d.ts +2 -3
  8. package/lib/components/index.js +2 -3
  9. package/lib/components/input/buttons/send-button.js +15 -5
  10. package/lib/components/{chat-input.d.ts → input/chat-input.d.ts} +3 -3
  11. package/lib/components/{chat-input.js → input/chat-input.js} +8 -5
  12. package/lib/components/input/index.d.ts +1 -0
  13. package/lib/components/input/index.js +1 -0
  14. package/lib/components/input/toolbar-registry.d.ts +6 -0
  15. package/lib/components/input/use-chat-commands.d.ts +1 -1
  16. package/lib/components/input/use-chat-commands.js +32 -13
  17. package/lib/components/messages/footer.d.ts +2 -2
  18. package/lib/components/messages/footer.js +1 -1
  19. package/lib/components/messages/header.d.ts +16 -0
  20. package/lib/components/messages/header.js +85 -0
  21. package/lib/components/messages/index.d.ts +9 -0
  22. package/lib/components/messages/index.js +13 -0
  23. package/lib/components/messages/message-renderer.js +1 -1
  24. package/lib/components/messages/message.d.ts +21 -0
  25. package/lib/components/messages/message.js +102 -0
  26. package/lib/components/messages/messages.d.ts +38 -0
  27. package/lib/components/messages/messages.js +139 -0
  28. package/lib/components/messages/navigation.d.ts +20 -0
  29. package/lib/components/messages/navigation.js +98 -0
  30. package/lib/components/messages/writers.d.ts +16 -0
  31. package/lib/components/messages/writers.js +39 -0
  32. package/lib/context.d.ts +1 -1
  33. package/lib/index.d.ts +2 -6
  34. package/lib/index.js +2 -6
  35. package/lib/input-model.js +30 -3
  36. package/lib/{registry.d.ts → registers/attachment-openers.d.ts} +1 -1
  37. package/lib/registers/chat-commands.d.ts +108 -0
  38. package/lib/{chat-commands/registry.js → registers/chat-commands.js} +8 -8
  39. package/lib/{footers/registry.d.ts → registers/footers.d.ts} +30 -5
  40. package/lib/registers/index.d.ts +3 -0
  41. package/lib/{footers → registers}/index.js +3 -2
  42. package/lib/selection-watcher.d.ts +11 -1
  43. package/lib/selection-watcher.js +10 -4
  44. package/lib/types.d.ts +7 -2
  45. package/lib/utils.js +8 -6
  46. package/lib/widgets/index.d.ts +3 -0
  47. package/lib/{chat-commands → widgets}/index.js +3 -2
  48. package/package.json +3 -1
  49. package/src/active-cell-manager.ts +10 -1
  50. package/src/components/avatar.tsx +68 -0
  51. package/src/components/chat.tsx +11 -6
  52. package/src/components/index.ts +2 -3
  53. package/src/components/input/buttons/send-button.tsx +17 -5
  54. package/src/components/{chat-input.tsx → input/chat-input.tsx} +13 -8
  55. package/src/components/input/index.ts +1 -0
  56. package/src/components/input/toolbar-registry.tsx +6 -0
  57. package/src/components/input/use-chat-commands.tsx +39 -16
  58. package/src/components/messages/footer.tsx +5 -2
  59. package/src/components/messages/header.tsx +133 -0
  60. package/src/components/messages/index.ts +14 -0
  61. package/src/components/messages/message-renderer.tsx +1 -1
  62. package/src/components/messages/message.tsx +156 -0
  63. package/src/components/messages/messages.tsx +218 -0
  64. package/src/components/messages/navigation.tsx +167 -0
  65. package/src/components/messages/welcome.tsx +1 -0
  66. package/src/components/messages/writers.tsx +81 -0
  67. package/src/context.ts +1 -1
  68. package/src/index.ts +2 -6
  69. package/src/input-model.ts +33 -4
  70. package/src/{registry.ts → registers/attachment-openers.ts} +2 -1
  71. package/src/registers/chat-commands.ts +142 -0
  72. package/src/{footers/registry.ts → registers/footers.ts} +35 -8
  73. package/src/{footers → registers}/index.ts +3 -2
  74. package/src/selection-watcher.ts +28 -5
  75. package/src/types.ts +7 -2
  76. package/src/utils.ts +8 -6
  77. package/src/{chat-commands → widgets}/index.ts +3 -2
  78. package/style/chat.css +82 -0
  79. package/lib/chat-commands/index.d.ts +0 -2
  80. package/lib/chat-commands/registry.d.ts +0 -28
  81. package/lib/chat-commands/types.d.ts +0 -52
  82. package/lib/chat-commands/types.js +0 -5
  83. package/lib/components/chat-messages.d.ts +0 -119
  84. package/lib/components/chat-messages.js +0 -446
  85. package/lib/footers/index.d.ts +0 -2
  86. package/lib/footers/types.d.ts +0 -26
  87. package/lib/footers/types.js +0 -5
  88. package/src/chat-commands/registry.ts +0 -60
  89. package/src/chat-commands/types.ts +0 -67
  90. package/src/components/chat-messages.tsx +0 -739
  91. package/src/footers/types.ts +0 -33
  92. package/lib/components/{toolbar.d.ts → messages/toolbar.d.ts} +0 -0
  93. package/lib/components/{toolbar.js → messages/toolbar.js} +0 -0
  94. package/lib/{registry.js → registers/attachment-openers.js} +0 -0
  95. package/lib/{footers/registry.js → registers/footers.js} +4 -4
  96. /package/src/components/{toolbar.tsx → messages/toolbar.tsx} +0 -0
@@ -5,10 +5,12 @@ import { ISignal } from '@lumino/signaling';
5
5
  type CellContent = {
6
6
  type: string;
7
7
  source: string;
8
+ language?: string;
8
9
  };
9
10
  type CellWithErrorContent = {
10
11
  type: 'code';
11
12
  source: string;
13
+ language?: string;
12
14
  error: {
13
15
  name: string;
14
16
  value: string;
@@ -110,8 +110,11 @@ export class ActiveCellManager {
110
110
  return this._activeCellErrorChanged;
111
111
  }
112
112
  getContent(withError = false) {
113
- var _a;
113
+ var _a, _b, _c;
114
114
  const sharedModel = (_a = this._notebookTracker.activeCell) === null || _a === void 0 ? void 0 : _a.model.sharedModel;
115
+ const language = (sharedModel === null || sharedModel === void 0 ? void 0 : sharedModel.cell_type) === 'code'
116
+ ? (_c = (_b = this._notebookTracker.currentWidget) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.defaultKernelLanguage
117
+ : undefined;
115
118
  if (!sharedModel) {
116
119
  return null;
117
120
  }
@@ -119,7 +122,8 @@ export class ActiveCellManager {
119
122
  if (!withError) {
120
123
  return {
121
124
  type: sharedModel.cell_type,
122
- source: sharedModel.getSource()
125
+ source: sharedModel.getSource(),
126
+ language
123
127
  };
124
128
  }
125
129
  // case where withError = true
@@ -128,6 +132,7 @@ export class ActiveCellManager {
128
132
  return {
129
133
  type: 'code',
130
134
  source: sharedModel.getSource(),
135
+ language,
131
136
  error: {
132
137
  name: error.ename,
133
138
  value: error.evalue,
@@ -0,0 +1,20 @@
1
+ /// <reference types="react" />
2
+ import { IUser } from '../types';
3
+ /**
4
+ * The avatar props.
5
+ */
6
+ type AvatarProps = {
7
+ /**
8
+ * The user to display an avatar.
9
+ */
10
+ user: IUser;
11
+ /**
12
+ * Whether the avatar should be small.
13
+ */
14
+ small?: boolean;
15
+ };
16
+ /**
17
+ * The avatar component.
18
+ */
19
+ export declare function Avatar(props: AvatarProps): JSX.Element | null;
20
+ export {};
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { Avatar as MuiAvatar, Typography } from '@mui/material';
6
+ import React from 'react';
7
+ /**
8
+ * The avatar component.
9
+ */
10
+ export function Avatar(props) {
11
+ var _a, _b;
12
+ const { user } = props;
13
+ const sharedStyles = {
14
+ height: `${props.small ? '16' : '24'}px`,
15
+ width: `${props.small ? '16' : '24'}px`,
16
+ bgcolor: user.color,
17
+ fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`
18
+ };
19
+ const name = (_b = (_a = user.display_name) !== null && _a !== void 0 ? _a : user.name) !== null && _b !== void 0 ? _b : (user.username || 'User undefined');
20
+ return user.avatar_url ? (React.createElement(MuiAvatar, { sx: {
21
+ ...sharedStyles
22
+ }, src: user.avatar_url, alt: name, title: name })) : user.initials ? (React.createElement(MuiAvatar, { sx: {
23
+ ...sharedStyles
24
+ }, alt: name, title: name },
25
+ React.createElement(Typography, { sx: {
26
+ fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`,
27
+ color: 'var(--jp-ui-inverse-font-color1)'
28
+ } }, user.initials))) : null;
29
+ }
@@ -1,11 +1,9 @@
1
1
  /// <reference types="react" />
2
2
  import { IThemeManager } from '@jupyterlab/apputils';
3
3
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
- import { IChatCommandRegistry } from '../chat-commands';
5
4
  import { IInputToolbarRegistry } from './input';
6
- import { IMessageFooterRegistry } from '../footers';
7
5
  import { IChatModel } from '../model';
8
- import { IAttachmentOpenerRegistry } from '../registry';
6
+ import { IAttachmentOpenerRegistry, IChatCommandRegistry, IMessageFooterRegistry } from '../registers';
9
7
  export declare function ChatBody(props: Chat.IChatBodyProps): JSX.Element;
10
8
  export declare function Chat(props: Chat.IOptions): JSX.Element;
11
9
  /**
@@ -7,10 +7,9 @@ import SettingsIcon from '@mui/icons-material/Settings';
7
7
  import { IconButton } from '@mui/material';
8
8
  import { Box } from '@mui/system';
9
9
  import React, { useState } from 'react';
10
+ import { ChatInput, InputToolbarRegistry } from './input';
10
11
  import { JlThemeProvider } from './jl-theme-provider';
11
- import { ChatMessages } from './chat-messages';
12
- import { ChatInput } from './chat-input';
13
- import { InputToolbarRegistry } from './input';
12
+ import { ChatMessages } from './messages';
14
13
  import { AttachmentOpenerContext } from '../context';
15
14
  export function ChatBody(props) {
16
15
  const { model } = props;
@@ -1,9 +1,8 @@
1
+ export * from './avatar';
1
2
  export * from './chat';
2
- export * from './chat-input';
3
- export * from './chat-messages';
4
3
  export * from './code-blocks';
5
4
  export * from './input';
6
5
  export * from './jl-theme-provider';
6
+ export * from './messages';
7
7
  export * from './mui-extras';
8
8
  export * from './scroll-container';
9
- export * from './toolbar';
@@ -2,12 +2,11 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ export * from './avatar';
5
6
  export * from './chat';
6
- export * from './chat-input';
7
- export * from './chat-messages';
8
7
  export * from './code-blocks';
9
8
  export * from './input';
10
9
  export * from './jl-theme-provider';
10
+ export * from './messages';
11
11
  export * from './mui-extras';
12
12
  export * from './scroll-container';
13
- export * from './toolbar';
@@ -15,7 +15,7 @@ const SEND_INCLUDE_LI_CLASS = 'jp-chat-send-include';
15
15
  * The send button, with optional 'include selection' menu.
16
16
  */
17
17
  export function SendButton(props) {
18
- const { model } = props;
18
+ const { model, chatCommandRegistry } = props;
19
19
  const { activeCellManager, selectionWatcher } = model;
20
20
  const hideIncludeSelection = !activeCellManager || !selectionWatcher;
21
21
  const [menuAnchorEl, setMenuAnchorEl] = useState(null);
@@ -77,21 +77,31 @@ export function SendButton(props) {
77
77
  activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.availabilityChanged.disconnect(toggleIncludeState);
78
78
  };
79
79
  }, [activeCellManager, selectionWatcher]);
80
- function sendWithSelection() {
80
+ async function send() {
81
+ await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
82
+ model.send(model.value);
83
+ }
84
+ async function sendWithSelection() {
81
85
  let source = '';
86
+ // Run all chat command providers
87
+ await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
88
+ let language;
82
89
  if (selectionWatcher === null || selectionWatcher === void 0 ? void 0 : selectionWatcher.selection) {
83
90
  // Append the selected text if exists.
84
91
  source = selectionWatcher.selection.text;
92
+ language = selectionWatcher.selection.language;
85
93
  }
86
94
  else if (activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.available) {
87
95
  // Append the active cell content if exists.
88
- source = activeCellManager.getContent(false).source;
96
+ const content = activeCellManager.getContent(false);
97
+ source = content.source;
98
+ language = content === null || content === void 0 ? void 0 : content.language;
89
99
  }
90
100
  let content = model.value;
91
101
  if (source) {
92
102
  content += `
93
103
 
94
- \`\`\`
104
+ \`\`\`${language !== null && language !== void 0 ? language : ''}
95
105
  ${source}
96
106
  \`\`\`
97
107
  `;
@@ -101,7 +111,7 @@ ${source}
101
111
  model.value = '';
102
112
  }
103
113
  return (React.createElement(React.Fragment, null,
104
- React.createElement(TooltippedButton, { onClick: () => model.send(model.value), disabled: disabled, tooltip: tooltip, buttonProps: {
114
+ React.createElement(TooltippedButton, { onClick: send, disabled: disabled, tooltip: tooltip, buttonProps: {
105
115
  size: 'small',
106
116
  title: tooltip,
107
117
  variant: 'contained',
@@ -1,8 +1,8 @@
1
1
  /// <reference types="react" />
2
2
  import { SxProps, Theme } from '@mui/material';
3
- import { IInputToolbarRegistry } from './input';
4
- import { IInputModel } from '../input-model';
5
- import { IChatCommandRegistry } from '../chat-commands';
3
+ import { IInputToolbarRegistry } from '.';
4
+ import { IInputModel } from '../../input-model';
5
+ import { IChatCommandRegistry } from '../../registers';
6
6
  export declare function ChatInput(props: ChatInput.IProps): JSX.Element;
7
7
  /**
8
8
  * The chat input namespace.
@@ -5,8 +5,8 @@
5
5
  import { Autocomplete, Box, InputAdornment, TextField } from '@mui/material';
6
6
  import clsx from 'clsx';
7
7
  import React, { useEffect, useRef, useState } from 'react';
8
- import { AttachmentPreviewList } from './attachments';
9
- import { useChatCommands } from './input';
8
+ import { AttachmentPreviewList } from '../attachments';
9
+ import { useChatCommands } from '.';
10
10
  const INPUT_BOX_CLASS = 'jp-chat-input-container';
11
11
  const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
12
12
  export function ChatInput(props) {
@@ -72,7 +72,8 @@ export function ChatInput(props) {
72
72
  * "Enter". This also handles many of the edge cases in the MUI Autocomplete
73
73
  * component.
74
74
  */
75
- function handleKeyDown(event) {
75
+ async function handleKeyDown(event) {
76
+ var _a;
76
77
  /**
77
78
  * IMPORTANT: This statement ensures that arrow keys can be used to navigate
78
79
  * the multiline input when the chat commands menu is closed.
@@ -115,7 +116,9 @@ export function ChatInput(props) {
115
116
  // Finally, send the message when all other conditions are met.
116
117
  if ((sendWithShiftEnter && event.shiftKey) ||
117
118
  (!sendWithShiftEnter && !event.shiftKey)) {
118
- model.send(input);
119
+ // Run all command providers
120
+ await ((_a = props.chatCommandRegistry) === null || _a === void 0 ? void 0 : _a.onSubmit(model));
121
+ model.send(model.value);
119
122
  event.stopPropagation();
120
123
  event.preventDefault();
121
124
  }
@@ -153,7 +156,7 @@ export function ChatInput(props) {
153
156
  }
154
157
  }, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "outlined", 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); }, InputProps: {
155
158
  ...params.InputProps,
156
- endAdornment: (React.createElement(InputAdornment, { position: "end", className: INPUT_TOOLBAR_CLASS }, toolbarElements.map(item => (React.createElement(item.element, { model: model })))))
159
+ endAdornment: (React.createElement(InputAdornment, { position: "end", className: INPUT_TOOLBAR_CLASS }, toolbarElements.map(item => (React.createElement(item.element, { model: model, chatCommandRegistry: props.chatCommandRegistry })))))
157
160
  }, FormHelperTextProps: {
158
161
  sx: { marginLeft: 'auto', marginRight: 0 }
159
162
  }, helperText: input.length > 2 ? helperText : ' ' })), inputValue: input, onInputChange: (_, newValue, reason) => {
@@ -1,3 +1,4 @@
1
1
  export * from './buttons';
2
+ export * from './chat-input';
2
3
  export * from './toolbar-registry';
3
4
  export * from './use-chat-commands';
@@ -3,5 +3,6 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  export * from './buttons';
6
+ export * from './chat-input';
6
7
  export * from './toolbar-registry';
7
8
  export * from './use-chat-commands';
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { IInputModel } from '../../input-model';
3
3
  import { ISignal } from '@lumino/signaling';
4
+ import { IChatCommandRegistry } from '../../registers';
4
5
  /**
5
6
  * The toolbar registry interface.
6
7
  */
@@ -90,6 +91,11 @@ export declare namespace InputToolbarRegistry {
90
91
  * The input model of the input component including the button.
91
92
  */
92
93
  model: IInputModel;
94
+ /**
95
+ * Chat command registry. Should be used by the "Send" button to run
96
+ * `onSubmit()` on all command providers before sending the message.
97
+ */
98
+ chatCommandRegistry?: IChatCommandRegistry;
93
99
  }
94
100
  /**
95
101
  * The default toolbar registry if none is provided.
@@ -1,5 +1,5 @@
1
1
  import type { AutocompleteProps as GenericAutocompleteProps } from '@mui/material';
2
- import { IChatCommandRegistry } from '../../chat-commands';
2
+ import { IChatCommandRegistry } from '../../registers';
3
3
  import { IInputModel } from '../../input-model';
4
4
  type AutocompleteProps = GenericAutocompleteProps<any, any, any, any>;
5
5
  type UseChatCommandsReturn = {
@@ -14,12 +14,17 @@ import React, { useEffect, useState } from 'react';
14
14
  export function useChatCommands(inputModel, chatCommandRegistry) {
15
15
  // whether an option is highlighted in the chat commands menu
16
16
  const [highlighted, setHighlighted] = useState(false);
17
- // whether the chat commands menu is open
17
+ // whether the chat commands menu is open.
18
+ // NOTE: every `setOpen(false)` call should be followed by a
19
+ // `setHighlighted(false)` call.
18
20
  const [open, setOpen] = useState(false);
19
21
  // current list of chat commands matched by the current word.
20
22
  // the current word is the space-separated word at the user's cursor.
21
23
  const [commands, setCommands] = useState([]);
22
24
  useEffect(() => {
25
+ /**
26
+ * Callback that runs whenever the current word changes.
27
+ */
23
28
  async function getCommands(_, currentWord) {
24
29
  const providers = chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.getProviders();
25
30
  if (!providers) {
@@ -31,20 +36,35 @@ export function useChatCommands(inputModel, chatCommandRegistry) {
31
36
  setHighlighted(false);
32
37
  return;
33
38
  }
34
- let newCommands = [];
39
+ let commandCompletions = [];
35
40
  for (const provider of providers) {
36
41
  // TODO: optimize performance when this method is truly async
37
42
  try {
38
- newCommands = newCommands.concat(await provider.getChatCommands(inputModel));
43
+ commandCompletions = commandCompletions.concat(await provider.listCommandCompletions(inputModel));
39
44
  }
40
45
  catch (e) {
41
46
  console.error(`Error when getting chat commands from command provider '${provider.id}': `, e);
42
47
  }
43
48
  }
44
- if (newCommands) {
49
+ // Immediately replace the current word if it exactly matches one command
50
+ // and 'replaceWith' is set.
51
+ if (commandCompletions.length === 1 &&
52
+ commandCompletions[0].name === inputModel.currentWord &&
53
+ commandCompletions[0].replaceWith !== undefined) {
54
+ const replacement = commandCompletions[0].replaceWith;
55
+ inputModel.replaceCurrentWord(replacement);
56
+ return;
57
+ }
58
+ // Otherwise, open/close the menu based on the presence of command
59
+ // completions and set the menu entries.
60
+ if (commandCompletions.length) {
45
61
  setOpen(true);
46
62
  }
47
- setCommands(newCommands);
63
+ else {
64
+ setOpen(false);
65
+ setHighlighted(false);
66
+ }
67
+ setCommands(commandCompletions);
48
68
  }
49
69
  inputModel.currentWordChanged.connect(getCommands);
50
70
  return () => {
@@ -53,7 +73,8 @@ export function useChatCommands(inputModel, chatCommandRegistry) {
53
73
  }, [inputModel]);
54
74
  /**
55
75
  * onChange(): the callback invoked when a command is selected from the chat
56
- * commands menu by the user.
76
+ * commands menu. When a command `cmd` is selected, this function replaces the
77
+ * current word with `cmd.replaceWith` if set, `cmd.name` otherwise.
57
78
  */
58
79
  const onChange = (e, command, reason) => {
59
80
  if (reason !== 'selectOption') {
@@ -68,19 +89,17 @@ export function useChatCommands(inputModel, chatCommandRegistry) {
68
89
  if (!currentWord) {
69
90
  return;
70
91
  }
71
- // if replaceWith is set, handle the command immediately
72
- if (command.replaceWith) {
73
- inputModel.replaceCurrentWord(command.replaceWith);
74
- return;
92
+ let replacement = command.replaceWith === undefined ? command.name : command.replaceWith;
93
+ if (command.spaceOnAccept) {
94
+ replacement += ' ';
75
95
  }
76
- // otherwise, defer handling to the command provider
77
- chatCommandRegistry.handleChatCommand(command, inputModel);
96
+ inputModel.replaceCurrentWord(replacement);
78
97
  };
79
98
  return {
80
99
  autocompleteProps: {
81
100
  open,
82
101
  options: commands,
83
- getOptionLabel: (command) => command.name,
102
+ getOptionLabel: (command) => typeof command === 'string' ? '' : command.name,
84
103
  renderOption: (defaultProps, command, __, ___) => {
85
104
  const { key, ...listItemProps } = defaultProps;
86
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)));
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import { IMessageFooterRegistry, MessageFooterSectionProps } from '../../footers';
2
+ import { IMessageFooterRegistry, MessageFooterSectionProps } from '../../registers';
3
3
  /**
4
4
  * The chat footer component properties.
5
5
  */
@@ -13,4 +13,4 @@ export interface IMessageFootersProps extends MessageFooterSectionProps {
13
13
  * The chat footer component, which displays footer components on a row according to
14
14
  * their respective positions.
15
15
  */
16
- export declare function MessageFooter(props: IMessageFootersProps): JSX.Element;
16
+ export declare function MessageFooterComponent(props: IMessageFootersProps): JSX.Element;
@@ -8,7 +8,7 @@ import React from 'react';
8
8
  * The chat footer component, which displays footer components on a row according to
9
9
  * their respective positions.
10
10
  */
11
- export function MessageFooter(props) {
11
+ export function MessageFooterComponent(props) {
12
12
  var _a, _b, _c;
13
13
  const { message, model, registry } = props;
14
14
  const footer = registry.getFooter();
@@ -0,0 +1,16 @@
1
+ /// <reference types="react" />
2
+ import { IChatMessage } from '../../types';
3
+ /**
4
+ * The message header props.
5
+ */
6
+ type ChatMessageHeaderProps = {
7
+ /**
8
+ * The chat message.
9
+ */
10
+ message: IChatMessage;
11
+ };
12
+ /**
13
+ * The message header component.
14
+ */
15
+ export declare function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element;
16
+ export {};
@@ -0,0 +1,85 @@
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, { useEffect, useState } from 'react';
7
+ import { Avatar } from '../avatar';
8
+ const MESSAGE_HEADER_CLASS = 'jp-chat-message-header';
9
+ const MESSAGE_TIME_CLASS = 'jp-chat-message-time';
10
+ /**
11
+ * The message header component.
12
+ */
13
+ export function ChatMessageHeader(props) {
14
+ var _a, _b;
15
+ const [datetime, setDatetime] = useState({});
16
+ const message = props.message;
17
+ const sender = message.sender;
18
+ /**
19
+ * Effect: update cached datetime strings upon receiving a new message.
20
+ */
21
+ useEffect(() => {
22
+ if (!datetime[message.time]) {
23
+ const newDatetime = {};
24
+ let datetime;
25
+ const currentDate = new Date();
26
+ const sameDay = (date) => date.getFullYear() === currentDate.getFullYear() &&
27
+ date.getMonth() === currentDate.getMonth() &&
28
+ date.getDate() === currentDate.getDate();
29
+ const msgDate = new Date(message.time * 1000); // Convert message time to milliseconds
30
+ // Display only the time if the day of the message is the current one.
31
+ if (sameDay(msgDate)) {
32
+ // Use the browser's default locale
33
+ datetime = msgDate.toLocaleTimeString([], {
34
+ hour: 'numeric',
35
+ minute: '2-digit'
36
+ });
37
+ }
38
+ else {
39
+ // Use the browser's default locale
40
+ datetime = msgDate.toLocaleString([], {
41
+ day: 'numeric',
42
+ month: 'numeric',
43
+ year: 'numeric',
44
+ hour: 'numeric',
45
+ minute: '2-digit'
46
+ });
47
+ }
48
+ newDatetime[message.time] = datetime;
49
+ setDatetime(newDatetime);
50
+ }
51
+ });
52
+ const avatar = message.stacked ? null : Avatar({ user: sender });
53
+ const name = (_b = (_a = sender.display_name) !== null && _a !== void 0 ? _a : sender.name) !== null && _b !== void 0 ? _b : (sender.username || 'User undefined');
54
+ return (React.createElement(Box, { className: MESSAGE_HEADER_CLASS, sx: {
55
+ display: 'flex',
56
+ alignItems: 'center',
57
+ '& > :not(:last-child)': {
58
+ marginRight: 3
59
+ },
60
+ marginBottom: message.stacked ? '0px' : '12px'
61
+ } },
62
+ avatar,
63
+ React.createElement(Box, { sx: {
64
+ display: 'flex',
65
+ flexGrow: 1,
66
+ flexWrap: 'wrap',
67
+ justifyContent: 'space-between',
68
+ alignItems: 'center'
69
+ } },
70
+ React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
71
+ !message.stacked && (React.createElement(Typography, { sx: {
72
+ fontWeight: 700,
73
+ color: 'var(--jp-ui-font-color1)',
74
+ paddingRight: '0.5em'
75
+ } }, name)),
76
+ (message.deleted || message.edited) && (React.createElement(Typography, { sx: {
77
+ fontStyle: 'italic',
78
+ fontSize: 'var(--jp-content-font-size0)'
79
+ } }, message.deleted ? '(message deleted)' : '(edited)'))),
80
+ React.createElement(Typography, { className: MESSAGE_TIME_CLASS, sx: {
81
+ fontSize: '0.8em',
82
+ color: 'var(--jp-ui-font-color2)',
83
+ fontWeight: 300
84
+ }, title: message.raw_time ? 'Unverified time' : '' }, `${datetime[message.time]}${message.raw_time ? '*' : ''}`))));
85
+ }
@@ -0,0 +1,9 @@
1
+ export * from './footer';
2
+ export * from './header';
3
+ export * from './message';
4
+ export * from './message-renderer';
5
+ export * from './messages';
6
+ export * from './navigation';
7
+ export * from './toolbar';
8
+ export * from './welcome';
9
+ export * from './writers';
@@ -0,0 +1,13 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export * from './footer';
6
+ export * from './header';
7
+ export * from './message';
8
+ export * from './message-renderer';
9
+ export * from './messages';
10
+ export * from './navigation';
11
+ export * from './toolbar';
12
+ export * from './welcome';
13
+ export * from './writers';
@@ -5,7 +5,7 @@
5
5
  import React, { useState, useEffect } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
7
  import { CodeToolbar } from '../code-blocks/code-toolbar';
8
- import { MessageToolbar } from '../toolbar';
8
+ import { MessageToolbar } from './toolbar';
9
9
  import { MarkdownRenderer, MD_RENDERED_CLASS } from '../../markdown-renderer';
10
10
  /**
11
11
  * The message renderer base component.
@@ -0,0 +1,21 @@
1
+ import { PromiseDelegate } from '@lumino/coreutils';
2
+ import React from 'react';
3
+ import { BaseMessageProps } from './messages';
4
+ import { IChatMessage } from '../../types';
5
+ /**
6
+ * The message component body.
7
+ */
8
+ export declare const ChatMessage: React.ForwardRefExoticComponent<BaseMessageProps & {
9
+ /**
10
+ * The message to display.
11
+ */
12
+ message: IChatMessage;
13
+ /**
14
+ * The index of the message in the list.
15
+ */
16
+ index: number;
17
+ /**
18
+ * The promise to resolve when the message is rendered.
19
+ */
20
+ renderedPromise: PromiseDelegate<void>;
21
+ } & React.RefAttributes<HTMLDivElement>>;