@jupyter/chat 0.13.0 → 0.14.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 (90) 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} +7 -4
  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 +23 -12
  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/{registry.d.ts → registers/attachment-openers.d.ts} +1 -1
  36. package/lib/registers/chat-commands.d.ts +108 -0
  37. package/lib/{chat-commands/registry.js → registers/chat-commands.js} +8 -8
  38. package/lib/{footers/registry.d.ts → registers/footers.d.ts} +30 -5
  39. package/lib/registers/index.d.ts +3 -0
  40. package/lib/{footers → registers}/index.js +3 -2
  41. package/lib/selection-watcher.d.ts +11 -1
  42. package/lib/selection-watcher.js +10 -4
  43. package/lib/widgets/index.d.ts +3 -0
  44. package/lib/{chat-commands → widgets}/index.js +3 -2
  45. package/package.json +3 -1
  46. package/src/active-cell-manager.ts +10 -1
  47. package/src/components/avatar.tsx +68 -0
  48. package/src/components/chat.tsx +11 -6
  49. package/src/components/index.ts +2 -3
  50. package/src/components/input/buttons/send-button.tsx +17 -5
  51. package/src/components/{chat-input.tsx → input/chat-input.tsx} +12 -7
  52. package/src/components/input/index.ts +1 -0
  53. package/src/components/input/toolbar-registry.tsx +6 -0
  54. package/src/components/input/use-chat-commands.tsx +30 -15
  55. package/src/components/messages/footer.tsx +5 -2
  56. package/src/components/messages/header.tsx +133 -0
  57. package/src/components/messages/index.ts +14 -0
  58. package/src/components/messages/message-renderer.tsx +1 -1
  59. package/src/components/messages/message.tsx +156 -0
  60. package/src/components/messages/messages.tsx +218 -0
  61. package/src/components/messages/navigation.tsx +167 -0
  62. package/src/components/messages/welcome.tsx +1 -0
  63. package/src/components/messages/writers.tsx +81 -0
  64. package/src/context.ts +1 -1
  65. package/src/index.ts +2 -6
  66. package/src/{registry.ts → registers/attachment-openers.ts} +2 -1
  67. package/src/registers/chat-commands.ts +142 -0
  68. package/src/{footers/registry.ts → registers/footers.ts} +35 -8
  69. package/src/{footers → registers}/index.ts +3 -2
  70. package/src/selection-watcher.ts +28 -5
  71. package/src/{chat-commands → widgets}/index.ts +3 -2
  72. package/style/chat.css +82 -0
  73. package/lib/chat-commands/index.d.ts +0 -2
  74. package/lib/chat-commands/registry.d.ts +0 -28
  75. package/lib/chat-commands/types.d.ts +0 -52
  76. package/lib/chat-commands/types.js +0 -5
  77. package/lib/components/chat-messages.d.ts +0 -119
  78. package/lib/components/chat-messages.js +0 -446
  79. package/lib/footers/index.d.ts +0 -2
  80. package/lib/footers/types.d.ts +0 -26
  81. package/lib/footers/types.js +0 -5
  82. package/src/chat-commands/registry.ts +0 -60
  83. package/src/chat-commands/types.ts +0 -67
  84. package/src/components/chat-messages.tsx +0 -739
  85. package/src/footers/types.ts +0 -33
  86. package/lib/components/{toolbar.d.ts → messages/toolbar.d.ts} +0 -0
  87. package/lib/components/{toolbar.js → messages/toolbar.js} +0 -0
  88. package/lib/{registry.js → registers/attachment-openers.js} +0 -0
  89. package/lib/{footers/registry.js → registers/footers.js} +4 -4
  90. /package/src/components/{toolbar.tsx → messages/toolbar.tsx} +0 -0
@@ -0,0 +1,102 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import React, { forwardRef, useEffect, useState } from 'react';
6
+ import { MessageRenderer } from './message-renderer';
7
+ import { AttachmentPreviewList } from '../attachments';
8
+ import { ChatInput } from '../input';
9
+ import { InputModel } from '../../input-model';
10
+ import { replaceSpanToMention } from '../../utils';
11
+ /**
12
+ * The message component body.
13
+ */
14
+ export const ChatMessage = forwardRef((props, ref) => {
15
+ const { message, model, rmRegistry } = props;
16
+ const [edit, setEdit] = useState(false);
17
+ const [deleted, setDeleted] = useState(false);
18
+ const [canEdit, setCanEdit] = useState(false);
19
+ const [canDelete, setCanDelete] = useState(false);
20
+ // Look if the message can be deleted or edited.
21
+ useEffect(() => {
22
+ var _a;
23
+ // Init canDelete and canEdit state.
24
+ setDeleted((_a = message.deleted) !== null && _a !== void 0 ? _a : false);
25
+ if (model.user !== undefined && !message.deleted) {
26
+ if (model.user.username === message.sender.username) {
27
+ setCanEdit(model.updateMessage !== undefined);
28
+ setCanDelete(model.deleteMessage !== undefined);
29
+ return;
30
+ }
31
+ if (message.sender.bot) {
32
+ setCanDelete(model.deleteMessage !== undefined);
33
+ }
34
+ }
35
+ else {
36
+ setCanEdit(false);
37
+ setCanDelete(false);
38
+ }
39
+ }, [model, message]);
40
+ // Create an input model only if the message is edited.
41
+ const startEdition = () => {
42
+ var _a;
43
+ if (!canEdit) {
44
+ return;
45
+ }
46
+ let body = message.body;
47
+ (_a = message.mentions) === null || _a === void 0 ? void 0 : _a.forEach(user => {
48
+ body = replaceSpanToMention(body, user);
49
+ });
50
+ const inputModel = new InputModel({
51
+ chatContext: model.createChatContext(),
52
+ onSend: (input, model) => updateMessage(message.id, input, model),
53
+ onCancel: () => cancelEdition(),
54
+ value: body,
55
+ activeCellManager: model.activeCellManager,
56
+ selectionWatcher: model.selectionWatcher,
57
+ documentManager: model.documentManager,
58
+ config: {
59
+ sendWithShiftEnter: model.config.sendWithShiftEnter
60
+ },
61
+ attachments: message.attachments,
62
+ mentions: message.mentions
63
+ });
64
+ model.addEditionModel(message.id, inputModel);
65
+ setEdit(true);
66
+ };
67
+ // Cancel the current edition of the message.
68
+ const cancelEdition = () => {
69
+ var _a;
70
+ (_a = model.getEditionModel(message.id)) === null || _a === void 0 ? void 0 : _a.dispose();
71
+ setEdit(false);
72
+ };
73
+ // Update the content of the message.
74
+ const updateMessage = (id, input, inputModel) => {
75
+ var _a;
76
+ if (!canEdit || !inputModel) {
77
+ return;
78
+ }
79
+ // Update the message
80
+ const updatedMessage = { ...message };
81
+ updatedMessage.body = input;
82
+ updatedMessage.attachments = inputModel.attachments;
83
+ updatedMessage.mentions = inputModel.mentions;
84
+ model.updateMessage(id, updatedMessage);
85
+ (_a = model.getEditionModel(message.id)) === null || _a === void 0 ? void 0 : _a.dispose();
86
+ setEdit(false);
87
+ };
88
+ // Delete the message.
89
+ const deleteMessage = (id) => {
90
+ if (!canDelete) {
91
+ return;
92
+ }
93
+ model.deleteMessage(id);
94
+ };
95
+ // Empty if the message has been deleted.
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 })),
98
+ message.attachments && !edit && (
99
+ // Display the attachments only if message is not edited, otherwise the
100
+ // input component display them.
101
+ React.createElement(AttachmentPreviewList, { attachments: message.attachments }))));
102
+ });
@@ -0,0 +1,38 @@
1
+ /// <reference types="react" />
2
+ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
3
+ import { IInputToolbarRegistry } from '../input';
4
+ import { IChatCommandRegistry, IMessageFooterRegistry } from '../../registers';
5
+ import { IChatModel } from '../../model';
6
+ /**
7
+ * The base components props.
8
+ */
9
+ export type BaseMessageProps = {
10
+ /**
11
+ * The mime renderer registry.
12
+ */
13
+ rmRegistry: IRenderMimeRegistry;
14
+ /**
15
+ * The chat model.
16
+ */
17
+ model: IChatModel;
18
+ /**
19
+ * The chat commands registry.
20
+ */
21
+ chatCommandRegistry?: IChatCommandRegistry;
22
+ /**
23
+ * The input toolbar registry.
24
+ */
25
+ inputToolbarRegistry: IInputToolbarRegistry;
26
+ /**
27
+ * The footer registry.
28
+ */
29
+ messageFooterRegistry?: IMessageFooterRegistry;
30
+ /**
31
+ * The welcome message.
32
+ */
33
+ welcomeMessage?: string;
34
+ };
35
+ /**
36
+ * The messages list component.
37
+ */
38
+ export declare function ChatMessages(props: BaseMessageProps): JSX.Element;
@@ -0,0 +1,139 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { PromiseDelegate } from '@lumino/coreutils';
6
+ import { Box } from '@mui/material';
7
+ import clsx from 'clsx';
8
+ import React, { useEffect, useState, useRef } from 'react';
9
+ import { MessageFooterComponent } from './footer';
10
+ import { ChatMessageHeader } from './header';
11
+ import { ChatMessage } from './message';
12
+ import { Navigation } from './navigation';
13
+ import { WelcomeMessage } from './welcome';
14
+ import { WritingUsersList } from './writers';
15
+ import { ScrollContainer } from '../scroll-container';
16
+ const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
17
+ const MESSAGE_CLASS = 'jp-chat-message';
18
+ const MESSAGE_STACKED_CLASS = 'jp-chat-message-stacked';
19
+ /**
20
+ * The messages list component.
21
+ */
22
+ export function ChatMessages(props) {
23
+ const { model } = props;
24
+ const [messages, setMessages] = useState(model.messages);
25
+ const refMsgBox = useRef(null);
26
+ const [currentWriters, setCurrentWriters] = useState([]);
27
+ const [allRendered, setAllRendered] = useState(false);
28
+ // The list of message DOM and their rendered promises.
29
+ const listRef = useRef([]);
30
+ const renderedPromise = useRef([]);
31
+ /**
32
+ * Effect: fetch history and config on initial render
33
+ */
34
+ useEffect(() => {
35
+ async function fetchHistory() {
36
+ if (!model.getHistory) {
37
+ return;
38
+ }
39
+ model
40
+ .getHistory()
41
+ .then(history => setMessages(history.messages))
42
+ .catch(e => console.error(e));
43
+ }
44
+ fetchHistory();
45
+ setCurrentWriters([]);
46
+ }, [model]);
47
+ /**
48
+ * Effect: listen to chat messages.
49
+ */
50
+ useEffect(() => {
51
+ var _a;
52
+ function handleChatEvents() {
53
+ setMessages([...model.messages]);
54
+ }
55
+ function handleWritersChange(_, writers) {
56
+ setCurrentWriters(writers.map(writer => writer.user));
57
+ }
58
+ model.messagesUpdated.connect(handleChatEvents);
59
+ (_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(handleWritersChange);
60
+ return function cleanup() {
61
+ var _a;
62
+ model.messagesUpdated.disconnect(handleChatEvents);
63
+ (_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(handleChatEvents);
64
+ };
65
+ }, [model]);
66
+ /**
67
+ * Observe the messages to update the current viewport and the unread messages.
68
+ */
69
+ useEffect(() => {
70
+ const observer = new IntersectionObserver(entries => {
71
+ var _a;
72
+ // Used on first rendering, to ensure all the message as been rendered once.
73
+ if (!allRendered) {
74
+ Promise.all(renderedPromise.current.map(p => p.promise)).then(() => {
75
+ setAllRendered(true);
76
+ });
77
+ }
78
+ const unread = [...model.unreadMessages];
79
+ let unreadModified = false;
80
+ const inViewport = [...((_a = model.messagesInViewport) !== null && _a !== void 0 ? _a : [])];
81
+ entries.forEach(entry => {
82
+ var _a;
83
+ const index = parseInt((_a = entry.target.getAttribute('data-index')) !== null && _a !== void 0 ? _a : '');
84
+ if (!isNaN(index)) {
85
+ const viewportIdx = inViewport.indexOf(index);
86
+ if (!entry.isIntersecting && viewportIdx !== -1) {
87
+ inViewport.splice(viewportIdx, 1);
88
+ }
89
+ else if (entry.isIntersecting && viewportIdx === -1) {
90
+ inViewport.push(index);
91
+ }
92
+ if (unread.length) {
93
+ const unreadIdx = unread.indexOf(index);
94
+ if (unreadIdx !== -1 && entry.isIntersecting) {
95
+ unread.splice(unreadIdx, 1);
96
+ unreadModified = true;
97
+ }
98
+ }
99
+ }
100
+ });
101
+ props.model.messagesInViewport = inViewport;
102
+ // Ensure that all messages are rendered before updating unread messages, otherwise
103
+ // it can lead to wrong assumption , because more message are in the viewport
104
+ // before they are rendered.
105
+ if (allRendered && unreadModified) {
106
+ model.unreadMessages = unread;
107
+ }
108
+ });
109
+ /**
110
+ * Observe the messages.
111
+ */
112
+ listRef.current.forEach(item => {
113
+ if (item) {
114
+ observer.observe(item);
115
+ }
116
+ });
117
+ return () => {
118
+ listRef.current.forEach(item => {
119
+ if (item) {
120
+ observer.unobserve(item);
121
+ }
122
+ });
123
+ };
124
+ }, [messages, allRendered]);
125
+ return (React.createElement(React.Fragment, null,
126
+ React.createElement(ScrollContainer, { sx: { flexGrow: 1 } },
127
+ 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) => {
129
+ renderedPromise.current[i] = new PromiseDelegate();
130
+ return (
131
+ // 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 }),
134
+ React.createElement(ChatMessage, { ...props, message: message, index: i, renderedPromise: renderedPromise.current[i], ref: el => (listRef.current[i] = el) }),
135
+ props.messageFooterRegistry && (React.createElement(MessageFooterComponent, { registry: props.messageFooterRegistry, message: message, model: model }))));
136
+ }))),
137
+ React.createElement(WritingUsersList, { writers: currentWriters }),
138
+ React.createElement(Navigation, { ...props, refMsgBox: refMsgBox, allRendered: allRendered })));
139
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { BaseMessageProps } from './messages';
3
+ /**
4
+ * The navigation component props.
5
+ */
6
+ type NavigationProps = BaseMessageProps & {
7
+ /**
8
+ * The reference to the messages container.
9
+ */
10
+ refMsgBox: React.RefObject<HTMLDivElement>;
11
+ /**
12
+ * Whether all the messages has been rendered once on first display.
13
+ */
14
+ allRendered: boolean;
15
+ };
16
+ /**
17
+ * The navigation component, to navigate to unread messages.
18
+ */
19
+ export declare function Navigation(props: NavigationProps): JSX.Element;
20
+ export {};
@@ -0,0 +1,98 @@
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 React, { useEffect, useState } from 'react';
8
+ const NAVIGATION_BUTTON_CLASS = 'jp-chat-navigation';
9
+ const NAVIGATION_UNREAD_CLASS = 'jp-chat-navigation-unread';
10
+ const NAVIGATION_TOP_CLASS = 'jp-chat-navigation-top';
11
+ const NAVIGATION_BOTTOM_CLASS = 'jp-chat-navigation-bottom';
12
+ /**
13
+ * The navigation component, to navigate to unread messages.
14
+ */
15
+ export function Navigation(props) {
16
+ const { model } = props;
17
+ const [lastInViewport, setLastInViewport] = useState(true);
18
+ const [unreadBefore, setUnreadBefore] = useState(null);
19
+ const [unreadAfter, setUnreadAfter] = useState(null);
20
+ const gotoMessage = (msgIdx, alignToTop = true) => {
21
+ var _a, _b;
22
+ (_b = (_a = props.refMsgBox.current) === null || _a === void 0 ? void 0 : _a.children.item(msgIdx)) === null || _b === void 0 ? void 0 : _b.scrollIntoView(alignToTop);
23
+ };
24
+ // Listen for change in unread messages, and find the first unread message before or
25
+ // after the current viewport, to display navigation buttons.
26
+ useEffect(() => {
27
+ var _a;
28
+ // Do not attempt to display navigation until messages are rendered, it can lead to
29
+ // wrong assumption, because more messages are in the viewport before they are
30
+ // rendered.
31
+ if (!props.allRendered) {
32
+ return;
33
+ }
34
+ const unreadChanged = (model, unreadIndexes) => {
35
+ const viewport = model.messagesInViewport;
36
+ if (!viewport) {
37
+ return;
38
+ }
39
+ // Initialize the next values with the current values if there still relevant.
40
+ let before = unreadBefore !== null &&
41
+ unreadIndexes.includes(unreadBefore) &&
42
+ unreadBefore < Math.min(...viewport)
43
+ ? unreadBefore
44
+ : null;
45
+ let after = unreadAfter !== null &&
46
+ unreadIndexes.includes(unreadAfter) &&
47
+ unreadAfter > Math.max(...viewport)
48
+ ? unreadAfter
49
+ : null;
50
+ unreadIndexes.forEach(unread => {
51
+ if (viewport === null || viewport === void 0 ? void 0 : viewport.includes(unread)) {
52
+ return;
53
+ }
54
+ if (unread < (before !== null && before !== void 0 ? before : Math.min(...viewport))) {
55
+ before = unread;
56
+ }
57
+ else if (unread > Math.max(...viewport) &&
58
+ unread < (after !== null && after !== void 0 ? after : model.messages.length)) {
59
+ after = unread;
60
+ }
61
+ });
62
+ setUnreadBefore(before);
63
+ setUnreadAfter(after);
64
+ };
65
+ (_a = model.unreadChanged) === null || _a === void 0 ? void 0 : _a.connect(unreadChanged);
66
+ unreadChanged(model, model.unreadMessages);
67
+ // Move to the last the message after all the messages have been first rendered.
68
+ gotoMessage(model.messages.length - 1, false);
69
+ return () => {
70
+ var _a;
71
+ (_a = model.unreadChanged) === null || _a === void 0 ? void 0 : _a.disconnect(unreadChanged);
72
+ };
73
+ }, [model, props.allRendered]);
74
+ // Listen for change in the viewport, to add a navigation button if the last is not
75
+ // in viewport.
76
+ useEffect(() => {
77
+ var _a, _b;
78
+ const viewportChanged = (model, viewport) => {
79
+ setLastInViewport(model.messages.length === 0 ||
80
+ viewport.includes(model.messages.length - 1));
81
+ };
82
+ (_a = model.viewportChanged) === null || _a === void 0 ? void 0 : _a.connect(viewportChanged);
83
+ viewportChanged(model, (_b = model.messagesInViewport) !== null && _b !== void 0 ? _b : []);
84
+ return () => {
85
+ var _a;
86
+ (_a = model.viewportChanged) === null || _a === void 0 ? void 0 : _a.disconnect(viewportChanged);
87
+ };
88
+ }, [model]);
89
+ return (React.createElement(React.Fragment, null,
90
+ unreadBefore !== null && (React.createElement(Button, { className: `${NAVIGATION_BUTTON_CLASS} ${NAVIGATION_UNREAD_CLASS} ${NAVIGATION_TOP_CLASS}`, onClick: () => gotoMessage(unreadBefore), title: 'Go to unread messages' },
91
+ React.createElement(LabIcon.resolveReact, { display: 'flex', icon: caretDownEmptyIcon, iconClass: classes('jp-Icon') }))),
92
+ (unreadAfter !== null || !lastInViewport) && (React.createElement(Button, { className: `${NAVIGATION_BUTTON_CLASS} ${unreadAfter !== null ? NAVIGATION_UNREAD_CLASS : ''} ${NAVIGATION_BOTTOM_CLASS}`, onClick: unreadAfter === null
93
+ ? () => gotoMessage(model.messages.length - 1, false)
94
+ : () => gotoMessage(unreadAfter), title: unreadAfter !== null
95
+ ? 'Go to unread messages'
96
+ : 'Go to last message' },
97
+ React.createElement(LabIcon.resolveReact, { display: 'flex', icon: caretDownEmptyIcon, iconClass: classes('jp-Icon') })))));
98
+ }
@@ -0,0 +1,16 @@
1
+ /// <reference types="react" />
2
+ import { IUser } from '../../types';
3
+ /**
4
+ * The writers component props.
5
+ */
6
+ type writersProps = {
7
+ /**
8
+ * The list of users currently writing.
9
+ */
10
+ writers: IUser[];
11
+ };
12
+ /**
13
+ * The writers component, displaying the current writers.
14
+ */
15
+ export declare function WritingUsersList(props: writersProps): JSX.Element | null;
16
+ export {};
@@ -0,0 +1,39 @@
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, { useMemo } from 'react';
7
+ import { Avatar } from '../avatar';
8
+ const WRITERS_CLASS = 'jp-chat-writers';
9
+ /**
10
+ * Animated typing indicator component
11
+ */
12
+ const TypingIndicator = () => (React.createElement(Box, { className: "jp-chat-typing-indicator" },
13
+ React.createElement("span", { className: "jp-chat-typing-dot" }),
14
+ React.createElement("span", { className: "jp-chat-typing-dot" }),
15
+ React.createElement("span", { className: "jp-chat-typing-dot" })));
16
+ /**
17
+ * The writers component, displaying the current writers.
18
+ */
19
+ export function WritingUsersList(props) {
20
+ const { writers } = props;
21
+ // Don't render if no writers
22
+ if (writers.length === 0) {
23
+ return null;
24
+ }
25
+ const writersText = writers.length > 1 ? ' are writing' : ' is writing';
26
+ const writingUsers = useMemo(() => writers.map((writer, index) => {
27
+ var _a, _b;
28
+ return (React.createElement(Box, { key: writer.username || index, className: "jp-chat-writer-item" },
29
+ React.createElement(Avatar, { user: writer, small: true }),
30
+ React.createElement(Typography, { variant: "body2", className: "jp-chat-writer-name" }, (_b = (_a = writer.display_name) !== null && _a !== void 0 ? _a : writer.name) !== null && _b !== void 0 ? _b : (writer.username || 'User undefined')),
31
+ index < writers.length - 1 && (React.createElement(Typography, { variant: "body2", className: "jp-chat-writer-separator" }, index < writers.length - 2 ? ', ' : ' and '))));
32
+ }), [writers]);
33
+ return (React.createElement(Box, { className: `${WRITERS_CLASS}` },
34
+ React.createElement(Box, { className: "jp-chat-writers-content" },
35
+ writingUsers,
36
+ React.createElement(Box, { className: "jp-chat-writing-status" },
37
+ React.createElement(Typography, { variant: "body2", className: "jp-chat-writing-text" }, writersText),
38
+ React.createElement(TypingIndicator, null)))));
39
+ }
package/lib/context.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  /// <reference types="react" />
2
- import { IAttachmentOpenerRegistry } from './registry';
2
+ import { IAttachmentOpenerRegistry } from './registers';
3
3
  export declare const AttachmentOpenerContext: import("react").Context<IAttachmentOpenerRegistry | undefined>;
package/lib/index.d.ts CHANGED
@@ -1,14 +1,10 @@
1
1
  export * from './active-cell-manager';
2
- export * from './chat-commands';
3
2
  export * from './components';
4
- export * from './footers';
5
3
  export * from './icons';
6
4
  export * from './input-model';
7
5
  export * from './markdown-renderer';
8
6
  export * from './model';
9
- export * from './registry';
7
+ export * from './registers';
10
8
  export * from './selection-watcher';
11
9
  export * from './types';
12
- export * from './widgets/chat-error';
13
- export * from './widgets/chat-sidebar';
14
- export * from './widgets/chat-widget';
10
+ export * from './widgets';
package/lib/index.js CHANGED
@@ -3,16 +3,12 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  export * from './active-cell-manager';
6
- export * from './chat-commands';
7
6
  export * from './components';
8
- export * from './footers';
9
7
  export * from './icons';
10
8
  export * from './input-model';
11
9
  export * from './markdown-renderer';
12
10
  export * from './model';
13
- export * from './registry';
11
+ export * from './registers';
14
12
  export * from './selection-watcher';
15
13
  export * from './types';
16
- export * from './widgets/chat-error';
17
- export * from './widgets/chat-sidebar';
18
- export * from './widgets/chat-widget';
14
+ export * from './widgets';
@@ -1,5 +1,5 @@
1
1
  import { Token } from '@lumino/coreutils';
2
- import { IAttachment } from './types';
2
+ import { IAttachment } from '../types';
3
3
  /**
4
4
  * The token for the attachments opener registry, which can be provided by an extension
5
5
  * using @jupyter/chat package.
@@ -0,0 +1,108 @@
1
+ /// <reference types="react" />
2
+ import { LabIcon } from '@jupyterlab/ui-components';
3
+ import { Token } from '@lumino/coreutils';
4
+ import { IInputModel } from '../input-model';
5
+ /**
6
+ * The token for the chat command registry, which can be provided by an extension
7
+ * using @jupyter/chat package.
8
+ */
9
+ export declare const IChatCommandRegistry: Token<IChatCommandRegistry>;
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
+ /**
17
+ * Adds a chat command provider to the registry.
18
+ */
19
+ addProvider(provider: IChatCommandProvider): void;
20
+ /**
21
+ * Lists all chat command providers previously added via `addProvider()`.
22
+ */
23
+ getProviders(): IChatCommandProvider[];
24
+ /**
25
+ * Calls `onSubmit()` on each command provider in serial. Each command
26
+ * provider's `onSubmit()` method is responsible for checking the entire input
27
+ * for command calls and handling them accordingly.
28
+ *
29
+ * This method is called by the application after the user presses the "Send"
30
+ * button but before the message is sent to server.
31
+ */
32
+ onSubmit(inputModel: IInputModel): Promise<void>;
33
+ }
34
+ export type ChatCommand = {
35
+ /**
36
+ * The name of the command. This defines what the user should type in the
37
+ * input to have the command appear in the chat commands menu.
38
+ */
39
+ name: string;
40
+ /**
41
+ * ID of the provider the command originated from.
42
+ */
43
+ providerId: string;
44
+ /**
45
+ * If set, this will be rendered as the icon for the command in the chat
46
+ * commands menu. Jupyter Chat will choose a default if this is unset.
47
+ */
48
+ icon?: LabIcon | JSX.Element | string | null;
49
+ /**
50
+ * If set, this will be rendered as the description for the command in the
51
+ * chat commands menu. Jupyter Chat will choose a default if this is unset.
52
+ */
53
+ description?: string;
54
+ /**
55
+ * If set, Jupyter Chat will replace the current word with this string immediately when
56
+ * the command is accepted from the chat commands menu or the current word
57
+ * matches the command's `name` exactly.
58
+ *
59
+ * This is generally used by "shortcut command" providers, e.g. the emoji
60
+ * command provider.
61
+ */
62
+ replaceWith?: string;
63
+ /**
64
+ * Specifies whether the application should add a space ' ' after the command
65
+ * is accepted from the menu. This should be set to `true` if the command that
66
+ * replaces the current word needs to be handled on submit, and the command is
67
+ * valid on its own.
68
+ *
69
+ * Defaults to `false`.
70
+ */
71
+ spaceOnAccept?: boolean;
72
+ };
73
+ /**
74
+ * Interface of a command provider.
75
+ */
76
+ export interface IChatCommandProvider {
77
+ /**
78
+ * ID of this command provider.
79
+ */
80
+ id: string;
81
+ /**
82
+ * A method that should return the list of valid chat commands whose names
83
+ * complete the current word.
84
+ *
85
+ * The current word should be accessed from `inputModel.currentWord`.
86
+ */
87
+ listCommandCompletions(inputModel: IInputModel): Promise<ChatCommand[]>;
88
+ /**
89
+ * A method that should identify and handle *all* command calls within a
90
+ * message that the user intends to submit. This method is called after a user
91
+ * presses the "Send" button but before the message is sent to the server.
92
+ *
93
+ * The entire message should be read from `inputModel.value`. This method may
94
+ * modify the new message before submission by setting `inputModel.value` or
95
+ * by calling other methods available on `inputModel`.
96
+ */
97
+ onSubmit(inputModel: IInputModel): Promise<void>;
98
+ }
99
+ /**
100
+ * Default chat command registry implementation.
101
+ */
102
+ export declare class ChatCommandRegistry implements IChatCommandRegistry {
103
+ constructor();
104
+ addProvider(provider: IChatCommandProvider): void;
105
+ getProviders(): IChatCommandProvider[];
106
+ onSubmit(inputModel: IInputModel): Promise<void>;
107
+ private _providers;
108
+ }
@@ -3,6 +3,11 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import { Token } from '@lumino/coreutils';
6
+ /**
7
+ * The token for the chat command registry, which can be provided by an extension
8
+ * using @jupyter/chat package.
9
+ */
10
+ export const IChatCommandRegistry = new Token('@jupyter/chat:IChatCommandRegistry');
6
11
  /**
7
12
  * Default chat command registry implementation.
8
13
  */
@@ -16,14 +21,9 @@ export class ChatCommandRegistry {
16
21
  getProviders() {
17
22
  return Array.from(this._providers.values());
18
23
  }
19
- handleChatCommand(command, inputModel) {
20
- const provider = this._providers.get(command.providerId);
21
- if (!provider) {
22
- console.error('Error in handling chat command: No command provider has an ID of ' +
23
- command.providerId);
24
- return;
24
+ async onSubmit(inputModel) {
25
+ for (const provider of this._providers.values()) {
26
+ await provider.onSubmit(inputModel);
25
27
  }
26
- provider.handleChatCommand(command, inputModel);
27
28
  }
28
29
  }
29
- export const IChatCommandRegistry = new Token('@jupyter/chat:IChatCommandRegistry');