@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
@@ -1,446 +0,0 @@
1
- /*
2
- * Copyright (c) Jupyter Development Team.
3
- * Distributed under the terms of the Modified BSD License.
4
- */
5
- import { Button } from '@jupyter/react-components';
6
- import { LabIcon, caretDownEmptyIcon, classes } from '@jupyterlab/ui-components';
7
- import { PromiseDelegate } from '@lumino/coreutils';
8
- import { Avatar as MuiAvatar, Box, Typography } from '@mui/material';
9
- import clsx from 'clsx';
10
- import React, { useEffect, useState, useRef, forwardRef } from 'react';
11
- import { AttachmentPreviewList } from './attachments';
12
- import { ChatInput } from './chat-input';
13
- import { MessageFooter } from './messages/footer';
14
- import { MessageRenderer } from './messages/message-renderer';
15
- import { WelcomeMessage } from './messages/welcome';
16
- import { ScrollContainer } from './scroll-container';
17
- import { InputModel } from '../input-model';
18
- import { replaceSpanToMention } from '../utils';
19
- const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
20
- const MESSAGE_CLASS = 'jp-chat-message';
21
- const MESSAGE_STACKED_CLASS = 'jp-chat-message-stacked';
22
- const MESSAGE_HEADER_CLASS = 'jp-chat-message-header';
23
- const MESSAGE_TIME_CLASS = 'jp-chat-message-time';
24
- const WRITERS_CLASS = 'jp-chat-writers';
25
- const NAVIGATION_BUTTON_CLASS = 'jp-chat-navigation';
26
- const NAVIGATION_UNREAD_CLASS = 'jp-chat-navigation-unread';
27
- const NAVIGATION_TOP_CLASS = 'jp-chat-navigation-top';
28
- const NAVIGATION_BOTTOM_CLASS = 'jp-chat-navigation-bottom';
29
- /**
30
- * The messages list component.
31
- */
32
- export function ChatMessages(props) {
33
- const { model } = props;
34
- const [messages, setMessages] = useState(model.messages);
35
- const refMsgBox = useRef(null);
36
- const [currentWriters, setCurrentWriters] = useState([]);
37
- const [allRendered, setAllRendered] = useState(false);
38
- // The list of message DOM and their rendered promises.
39
- const listRef = useRef([]);
40
- const renderedPromise = useRef([]);
41
- /**
42
- * Effect: fetch history and config on initial render
43
- */
44
- useEffect(() => {
45
- async function fetchHistory() {
46
- if (!model.getHistory) {
47
- return;
48
- }
49
- model
50
- .getHistory()
51
- .then(history => setMessages(history.messages))
52
- .catch(e => console.error(e));
53
- }
54
- fetchHistory();
55
- setCurrentWriters([]);
56
- }, [model]);
57
- /**
58
- * Effect: listen to chat messages.
59
- */
60
- useEffect(() => {
61
- var _a;
62
- function handleChatEvents() {
63
- setMessages([...model.messages]);
64
- }
65
- function handleWritersChange(_, writers) {
66
- setCurrentWriters(writers.map(writer => writer.user));
67
- }
68
- model.messagesUpdated.connect(handleChatEvents);
69
- (_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(handleWritersChange);
70
- return function cleanup() {
71
- var _a;
72
- model.messagesUpdated.disconnect(handleChatEvents);
73
- (_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(handleChatEvents);
74
- };
75
- }, [model]);
76
- /**
77
- * Observe the messages to update the current viewport and the unread messages.
78
- */
79
- useEffect(() => {
80
- const observer = new IntersectionObserver(entries => {
81
- var _a;
82
- // Used on first rendering, to ensure all the message as been rendered once.
83
- if (!allRendered) {
84
- Promise.all(renderedPromise.current.map(p => p.promise)).then(() => {
85
- setAllRendered(true);
86
- });
87
- }
88
- const unread = [...model.unreadMessages];
89
- let unreadModified = false;
90
- const inViewport = [...((_a = model.messagesInViewport) !== null && _a !== void 0 ? _a : [])];
91
- entries.forEach(entry => {
92
- var _a;
93
- const index = parseInt((_a = entry.target.getAttribute('data-index')) !== null && _a !== void 0 ? _a : '');
94
- if (!isNaN(index)) {
95
- const viewportIdx = inViewport.indexOf(index);
96
- if (!entry.isIntersecting && viewportIdx !== -1) {
97
- inViewport.splice(viewportIdx, 1);
98
- }
99
- else if (entry.isIntersecting && viewportIdx === -1) {
100
- inViewport.push(index);
101
- }
102
- if (unread.length) {
103
- const unreadIdx = unread.indexOf(index);
104
- if (unreadIdx !== -1 && entry.isIntersecting) {
105
- unread.splice(unreadIdx, 1);
106
- unreadModified = true;
107
- }
108
- }
109
- }
110
- });
111
- props.model.messagesInViewport = inViewport;
112
- // Ensure that all messages are rendered before updating unread messages, otherwise
113
- // it can lead to wrong assumption , because more message are in the viewport
114
- // before they are rendered.
115
- if (allRendered && unreadModified) {
116
- model.unreadMessages = unread;
117
- }
118
- });
119
- /**
120
- * Observe the messages.
121
- */
122
- listRef.current.forEach(item => {
123
- if (item) {
124
- observer.observe(item);
125
- }
126
- });
127
- return () => {
128
- listRef.current.forEach(item => {
129
- if (item) {
130
- observer.unobserve(item);
131
- }
132
- });
133
- };
134
- }, [messages, allRendered]);
135
- return (React.createElement(React.Fragment, null,
136
- React.createElement(ScrollContainer, { sx: { flexGrow: 1 } },
137
- props.welcomeMessage && (React.createElement(WelcomeMessage, { rmRegistry: props.rmRegistry, content: props.welcomeMessage })),
138
- React.createElement(Box, { ref: refMsgBox, className: clsx(MESSAGES_BOX_CLASS) }, messages.map((message, i) => {
139
- renderedPromise.current[i] = new PromiseDelegate();
140
- return (
141
- // extra div needed to ensure each bubble is on a new line
142
- React.createElement(Box, { key: i, className: clsx(MESSAGE_CLASS, message.stacked ? MESSAGE_STACKED_CLASS : '') },
143
- React.createElement(ChatMessageHeader, { message: message }),
144
- React.createElement(ChatMessage, { ...props, message: message, index: i, renderedPromise: renderedPromise.current[i], ref: el => (listRef.current[i] = el) }),
145
- props.messageFooterRegistry && (React.createElement(MessageFooter, { registry: props.messageFooterRegistry, message: message, model: model }))));
146
- })),
147
- React.createElement(Writers, { writers: currentWriters })),
148
- React.createElement(Navigation, { ...props, refMsgBox: refMsgBox, allRendered: allRendered })));
149
- }
150
- /**
151
- * The message header component.
152
- */
153
- export function ChatMessageHeader(props) {
154
- var _a, _b;
155
- const [datetime, setDatetime] = useState({});
156
- const message = props.message;
157
- const sender = message.sender;
158
- /**
159
- * Effect: update cached datetime strings upon receiving a new message.
160
- */
161
- useEffect(() => {
162
- if (!datetime[message.time]) {
163
- const newDatetime = {};
164
- let datetime;
165
- const currentDate = new Date();
166
- const sameDay = (date) => date.getFullYear() === currentDate.getFullYear() &&
167
- date.getMonth() === currentDate.getMonth() &&
168
- date.getDate() === currentDate.getDate();
169
- const msgDate = new Date(message.time * 1000); // Convert message time to milliseconds
170
- // Display only the time if the day of the message is the current one.
171
- if (sameDay(msgDate)) {
172
- // Use the browser's default locale
173
- datetime = msgDate.toLocaleTimeString([], {
174
- hour: 'numeric',
175
- minute: '2-digit'
176
- });
177
- }
178
- else {
179
- // Use the browser's default locale
180
- datetime = msgDate.toLocaleString([], {
181
- day: 'numeric',
182
- month: 'numeric',
183
- year: 'numeric',
184
- hour: 'numeric',
185
- minute: '2-digit'
186
- });
187
- }
188
- newDatetime[message.time] = datetime;
189
- setDatetime(newDatetime);
190
- }
191
- });
192
- const avatar = message.stacked ? null : Avatar({ user: sender });
193
- const name = (_b = (_a = sender.display_name) !== null && _a !== void 0 ? _a : sender.name) !== null && _b !== void 0 ? _b : (sender.username || 'User undefined');
194
- return (React.createElement(Box, { className: MESSAGE_HEADER_CLASS, sx: {
195
- display: 'flex',
196
- alignItems: 'center',
197
- '& > :not(:last-child)': {
198
- marginRight: 3
199
- },
200
- marginBottom: message.stacked ? '0px' : '12px'
201
- } },
202
- avatar,
203
- React.createElement(Box, { sx: {
204
- display: 'flex',
205
- flexGrow: 1,
206
- flexWrap: 'wrap',
207
- justifyContent: 'space-between',
208
- alignItems: 'center'
209
- } },
210
- React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
211
- !message.stacked && (React.createElement(Typography, { sx: {
212
- fontWeight: 700,
213
- color: 'var(--jp-ui-font-color1)',
214
- paddingRight: '0.5em'
215
- } }, name)),
216
- (message.deleted || message.edited) && (React.createElement(Typography, { sx: {
217
- fontStyle: 'italic',
218
- fontSize: 'var(--jp-content-font-size0)'
219
- } }, message.deleted ? '(message deleted)' : '(edited)'))),
220
- React.createElement(Typography, { className: MESSAGE_TIME_CLASS, sx: {
221
- fontSize: '0.8em',
222
- color: 'var(--jp-ui-font-color2)',
223
- fontWeight: 300
224
- }, title: message.raw_time ? 'Unverified time' : '' }, `${datetime[message.time]}${message.raw_time ? '*' : ''}`))));
225
- }
226
- /**
227
- * The message component body.
228
- */
229
- export const ChatMessage = forwardRef((props, ref) => {
230
- const { message, model, rmRegistry } = props;
231
- const [edit, setEdit] = useState(false);
232
- const [deleted, setDeleted] = useState(false);
233
- const [canEdit, setCanEdit] = useState(false);
234
- const [canDelete, setCanDelete] = useState(false);
235
- // Look if the message can be deleted or edited.
236
- useEffect(() => {
237
- var _a;
238
- // Init canDelete and canEdit state.
239
- setDeleted((_a = message.deleted) !== null && _a !== void 0 ? _a : false);
240
- if (model.user !== undefined && !message.deleted) {
241
- if (model.user.username === message.sender.username) {
242
- setCanEdit(model.updateMessage !== undefined);
243
- setCanDelete(model.deleteMessage !== undefined);
244
- return;
245
- }
246
- if (message.sender.bot) {
247
- setCanDelete(model.deleteMessage !== undefined);
248
- }
249
- }
250
- else {
251
- setCanEdit(false);
252
- setCanDelete(false);
253
- }
254
- }, [model, message]);
255
- // Create an input model only if the message is edited.
256
- const startEdition = () => {
257
- var _a;
258
- if (!canEdit) {
259
- return;
260
- }
261
- let body = message.body;
262
- (_a = message.mentions) === null || _a === void 0 ? void 0 : _a.forEach(user => {
263
- body = replaceSpanToMention(body, user);
264
- });
265
- const inputModel = new InputModel({
266
- chatContext: model.createChatContext(),
267
- onSend: (input, model) => updateMessage(message.id, input, model),
268
- onCancel: () => cancelEdition(),
269
- value: body,
270
- activeCellManager: model.activeCellManager,
271
- selectionWatcher: model.selectionWatcher,
272
- documentManager: model.documentManager,
273
- config: {
274
- sendWithShiftEnter: model.config.sendWithShiftEnter
275
- },
276
- attachments: message.attachments,
277
- mentions: message.mentions
278
- });
279
- model.addEditionModel(message.id, inputModel);
280
- setEdit(true);
281
- };
282
- // Cancel the current edition of the message.
283
- const cancelEdition = () => {
284
- var _a;
285
- (_a = model.getEditionModel(message.id)) === null || _a === void 0 ? void 0 : _a.dispose();
286
- setEdit(false);
287
- };
288
- // Update the content of the message.
289
- const updateMessage = (id, input, inputModel) => {
290
- var _a;
291
- if (!canEdit || !inputModel) {
292
- return;
293
- }
294
- // Update the message
295
- const updatedMessage = { ...message };
296
- updatedMessage.body = input;
297
- updatedMessage.attachments = inputModel.attachments;
298
- updatedMessage.mentions = inputModel.mentions;
299
- model.updateMessage(id, updatedMessage);
300
- (_a = model.getEditionModel(message.id)) === null || _a === void 0 ? void 0 : _a.dispose();
301
- setEdit(false);
302
- };
303
- // Delete the message.
304
- const deleteMessage = (id) => {
305
- if (!canDelete) {
306
- return;
307
- }
308
- model.deleteMessage(id);
309
- };
310
- // Empty if the message has been deleted.
311
- return deleted ? (React.createElement("div", { ref: ref, "data-index": props.index })) : (React.createElement("div", { ref: ref, "data-index": props.index },
312
- 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 })),
313
- message.attachments && !edit && (
314
- // Display the attachments only if message is not edited, otherwise the
315
- // input component display them.
316
- React.createElement(AttachmentPreviewList, { attachments: message.attachments }))));
317
- });
318
- /**
319
- * The writers component, displaying the current writers.
320
- */
321
- export function Writers(props) {
322
- const { writers } = props;
323
- return writers.length > 0 ? (React.createElement(Box, { className: WRITERS_CLASS },
324
- writers.map((writer, index) => {
325
- var _a, _b;
326
- return (React.createElement("div", null,
327
- React.createElement(Avatar, { user: writer, small: true }),
328
- React.createElement("span", null, (_b = (_a = writer.display_name) !== null && _a !== void 0 ? _a : writer.name) !== null && _b !== void 0 ? _b : (writer.username || 'User undefined')),
329
- React.createElement("span", null, index < writers.length - 1
330
- ? index < writers.length - 2
331
- ? ', '
332
- : ' and '
333
- : '')));
334
- }),
335
- React.createElement("span", null, (writers.length > 1 ? ' are' : ' is') + ' writing'))) : null;
336
- }
337
- /**
338
- * The navigation component, to navigate to unread messages.
339
- */
340
- export function Navigation(props) {
341
- const { model } = props;
342
- const [lastInViewport, setLastInViewport] = useState(true);
343
- const [unreadBefore, setUnreadBefore] = useState(null);
344
- const [unreadAfter, setUnreadAfter] = useState(null);
345
- const gotoMessage = (msgIdx, alignToTop = true) => {
346
- var _a, _b;
347
- (_b = (_a = props.refMsgBox.current) === null || _a === void 0 ? void 0 : _a.children.item(msgIdx)) === null || _b === void 0 ? void 0 : _b.scrollIntoView(alignToTop);
348
- };
349
- // Listen for change in unread messages, and find the first unread message before or
350
- // after the current viewport, to display navigation buttons.
351
- useEffect(() => {
352
- var _a;
353
- // Do not attempt to display navigation until messages are rendered, it can lead to
354
- // wrong assumption, because more messages are in the viewport before they are
355
- // rendered.
356
- if (!props.allRendered) {
357
- return;
358
- }
359
- const unreadChanged = (model, unreadIndexes) => {
360
- const viewport = model.messagesInViewport;
361
- if (!viewport) {
362
- return;
363
- }
364
- // Initialize the next values with the current values if there still relevant.
365
- let before = unreadBefore !== null &&
366
- unreadIndexes.includes(unreadBefore) &&
367
- unreadBefore < Math.min(...viewport)
368
- ? unreadBefore
369
- : null;
370
- let after = unreadAfter !== null &&
371
- unreadIndexes.includes(unreadAfter) &&
372
- unreadAfter > Math.max(...viewport)
373
- ? unreadAfter
374
- : null;
375
- unreadIndexes.forEach(unread => {
376
- if (viewport === null || viewport === void 0 ? void 0 : viewport.includes(unread)) {
377
- return;
378
- }
379
- if (unread < (before !== null && before !== void 0 ? before : Math.min(...viewport))) {
380
- before = unread;
381
- }
382
- else if (unread > Math.max(...viewport) &&
383
- unread < (after !== null && after !== void 0 ? after : model.messages.length)) {
384
- after = unread;
385
- }
386
- });
387
- setUnreadBefore(before);
388
- setUnreadAfter(after);
389
- };
390
- (_a = model.unreadChanged) === null || _a === void 0 ? void 0 : _a.connect(unreadChanged);
391
- unreadChanged(model, model.unreadMessages);
392
- // Move to the last the message after all the messages have been first rendered.
393
- gotoMessage(model.messages.length - 1, false);
394
- return () => {
395
- var _a;
396
- (_a = model.unreadChanged) === null || _a === void 0 ? void 0 : _a.disconnect(unreadChanged);
397
- };
398
- }, [model, props.allRendered]);
399
- // Listen for change in the viewport, to add a navigation button if the last is not
400
- // in viewport.
401
- useEffect(() => {
402
- var _a, _b;
403
- const viewportChanged = (model, viewport) => {
404
- setLastInViewport(model.messages.length === 0 ||
405
- viewport.includes(model.messages.length - 1));
406
- };
407
- (_a = model.viewportChanged) === null || _a === void 0 ? void 0 : _a.connect(viewportChanged);
408
- viewportChanged(model, (_b = model.messagesInViewport) !== null && _b !== void 0 ? _b : []);
409
- return () => {
410
- var _a;
411
- (_a = model.viewportChanged) === null || _a === void 0 ? void 0 : _a.disconnect(viewportChanged);
412
- };
413
- }, [model]);
414
- return (React.createElement(React.Fragment, null,
415
- unreadBefore !== null && (React.createElement(Button, { className: `${NAVIGATION_BUTTON_CLASS} ${NAVIGATION_UNREAD_CLASS} ${NAVIGATION_TOP_CLASS}`, onClick: () => gotoMessage(unreadBefore), title: 'Go to unread messages' },
416
- React.createElement(LabIcon.resolveReact, { display: 'flex', icon: caretDownEmptyIcon, iconClass: classes('jp-Icon') }))),
417
- (unreadAfter !== null || !lastInViewport) && (React.createElement(Button, { className: `${NAVIGATION_BUTTON_CLASS} ${unreadAfter !== null ? NAVIGATION_UNREAD_CLASS : ''} ${NAVIGATION_BOTTOM_CLASS}`, onClick: unreadAfter === null
418
- ? () => gotoMessage(model.messages.length - 1, false)
419
- : () => gotoMessage(unreadAfter), title: unreadAfter !== null
420
- ? 'Go to unread messages'
421
- : 'Go to last message' },
422
- React.createElement(LabIcon.resolveReact, { display: 'flex', icon: caretDownEmptyIcon, iconClass: classes('jp-Icon') })))));
423
- }
424
- /**
425
- * The avatar component.
426
- */
427
- export function Avatar(props) {
428
- var _a, _b;
429
- const { user } = props;
430
- const sharedStyles = {
431
- height: `${props.small ? '16' : '24'}px`,
432
- width: `${props.small ? '16' : '24'}px`,
433
- bgcolor: user.color,
434
- fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`
435
- };
436
- const name = (_b = (_a = user.display_name) !== null && _a !== void 0 ? _a : user.name) !== null && _b !== void 0 ? _b : (user.username || 'User undefined');
437
- return user.avatar_url ? (React.createElement(MuiAvatar, { sx: {
438
- ...sharedStyles
439
- }, src: user.avatar_url, alt: name, title: name })) : user.initials ? (React.createElement(MuiAvatar, { sx: {
440
- ...sharedStyles
441
- }, alt: name, title: name },
442
- React.createElement(Typography, { sx: {
443
- fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`,
444
- color: 'var(--jp-ui-inverse-font-color1)'
445
- } }, user.initials))) : null;
446
- }
@@ -1,2 +0,0 @@
1
- export * from './registry';
2
- export * from './types';
@@ -1,26 +0,0 @@
1
- /// <reference types="react" />
2
- import { IChatModel } from '../model';
3
- import { IChatMessage } from '../types';
4
- /**
5
- * The props sent passed to each `MessageFooterSection` React component.
6
- */
7
- export type MessageFooterSectionProps = {
8
- model: IChatModel;
9
- message: IChatMessage;
10
- };
11
- /**
12
- * A message footer section which can be added to the footer registry.
13
- */
14
- export type MessageFooterSection = {
15
- component: React.FC<MessageFooterSectionProps>;
16
- position: 'left' | 'center' | 'right';
17
- };
18
- /**
19
- * The message footer returned by the registry, composed of 'left', 'center',
20
- * and 'right' sections.
21
- */
22
- export type MessageFooter = {
23
- left?: MessageFooterSection;
24
- center?: MessageFooterSection;
25
- right?: MessageFooterSection;
26
- };
@@ -1,5 +0,0 @@
1
- /*
2
- * Copyright (c) Jupyter Development Team.
3
- * Distributed under the terms of the Modified BSD License.
4
- */
5
- export {};
@@ -1,60 +0,0 @@
1
- /*
2
- * Copyright (c) Jupyter Development Team.
3
- * Distributed under the terms of the Modified BSD License.
4
- */
5
-
6
- import { Token } from '@lumino/coreutils';
7
- import { ChatCommand, IChatCommandProvider } from './types';
8
- import { IInputModel } from '../input-model';
9
-
10
- /**
11
- * Interface of a chat command registry, which tracks a list of chat command
12
- * providers. Providers provide a list of commands given a user's partial input,
13
- * and define how commands are handled when accepted in the chat commands menu.
14
- */
15
- export interface IChatCommandRegistry {
16
- addProvider(provider: IChatCommandProvider): void;
17
- getProviders(): IChatCommandProvider[];
18
-
19
- /**
20
- * Handles a chat command by calling `handleChatCommand()` on the provider
21
- * corresponding to this chat command.
22
- */
23
- handleChatCommand(command: ChatCommand, inputModel: IInputModel): void;
24
- }
25
-
26
- /**
27
- * Default chat command registry implementation.
28
- */
29
- export class ChatCommandRegistry implements IChatCommandRegistry {
30
- constructor() {
31
- this._providers = new Map<string, IChatCommandProvider>();
32
- }
33
-
34
- addProvider(provider: IChatCommandProvider): void {
35
- this._providers.set(provider.id, provider);
36
- }
37
-
38
- getProviders(): IChatCommandProvider[] {
39
- return Array.from(this._providers.values());
40
- }
41
-
42
- handleChatCommand(command: ChatCommand, inputModel: IInputModel) {
43
- const provider = this._providers.get(command.providerId);
44
- if (!provider) {
45
- console.error(
46
- 'Error in handling chat command: No command provider has an ID of ' +
47
- command.providerId
48
- );
49
- return;
50
- }
51
-
52
- provider.handleChatCommand(command, inputModel);
53
- }
54
-
55
- private _providers: Map<string, IChatCommandProvider>;
56
- }
57
-
58
- export const IChatCommandRegistry = new Token<IChatCommandRegistry>(
59
- '@jupyter/chat:IChatCommandRegistry'
60
- );
@@ -1,67 +0,0 @@
1
- /*
2
- * Copyright (c) Jupyter Development Team.
3
- * Distributed under the terms of the Modified BSD License.
4
- */
5
-
6
- import { LabIcon } from '@jupyterlab/ui-components';
7
- import { IInputModel } from '../input-model';
8
-
9
- export type ChatCommand = {
10
- /**
11
- * The name of the command. This defines what the user should type in the
12
- * input to have the command appear in the chat commands menu.
13
- */
14
- name: string;
15
-
16
- /**
17
- * ID of the provider the command originated from.
18
- */
19
- providerId: string;
20
-
21
- /**
22
- * If set, this will be rendered as the icon for the command in the chat
23
- * commands menu. Jupyter Chat will choose a default if this is unset.
24
- */
25
- icon?: LabIcon | JSX.Element | string | null;
26
-
27
- /**
28
- * If set, this will be rendered as the description for the command in the
29
- * chat commands menu. Jupyter Chat will choose a default if this is unset.
30
- */
31
- description?: string;
32
-
33
- /**
34
- * If set, Jupyter Chat will replace the current word with this string after
35
- * the command is run from the chat commands menu.
36
- *
37
- * If all commands from a provider have this property set, then
38
- * `handleChatCommands()` can just return on the first line.
39
- */
40
- replaceWith?: string;
41
- };
42
-
43
- /**
44
- * Interface of a command provider.
45
- */
46
- export interface IChatCommandProvider {
47
- /**
48
- * ID of this command provider.
49
- */
50
- id: string;
51
-
52
- /**
53
- * Async function which accepts the input model and returns a list of
54
- * valid chat commands that match the current word. The current word is
55
- * space-separated word at the user's cursor.
56
- */
57
- getChatCommands(inputModel: IInputModel): Promise<ChatCommand[]>;
58
-
59
- /**
60
- * Function called when a chat command is run by the user through the chat
61
- * commands menu.
62
- */
63
- handleChatCommand(
64
- command: ChatCommand,
65
- inputModel: IInputModel
66
- ): Promise<void>;
67
- }