@jupyter/chat 0.19.0-alpha.0 → 0.19.0-alpha.2
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.
- package/lib/active-cell-manager.js +4 -2
- package/lib/components/attachments.js +3 -3
- package/lib/components/chat.d.ts +3 -7
- package/lib/components/chat.js +9 -5
- package/lib/components/code-blocks/code-toolbar.js +8 -28
- package/lib/components/code-blocks/copy-button.js +4 -11
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/input/buttons/attach-button.js +3 -10
- package/lib/components/input/buttons/cancel-button.js +6 -10
- package/lib/components/input/buttons/save-edit-button.js +6 -13
- package/lib/components/input/buttons/send-button.js +6 -23
- package/lib/components/input/buttons/stop-button.js +6 -23
- package/lib/components/input/chat-input.d.ts +0 -20
- package/lib/components/input/chat-input.js +21 -18
- package/lib/components/messages/footer.d.ts +5 -5
- package/lib/components/messages/footer.js +7 -2
- package/lib/components/messages/header.js +10 -8
- package/lib/components/messages/message-renderer.d.ts +0 -10
- package/lib/components/messages/message-renderer.js +5 -3
- package/lib/components/messages/message.d.ts +8 -4
- package/lib/components/messages/message.js +4 -2
- package/lib/components/messages/messages.d.ts +1 -39
- package/lib/components/messages/messages.js +31 -13
- package/lib/components/messages/navigation.d.ts +1 -2
- package/lib/components/messages/navigation.js +2 -1
- package/lib/components/messages/toolbar.js +12 -26
- package/lib/components/messages/welcome.d.ts +10 -2
- package/lib/components/messages/welcome.js +2 -1
- package/lib/components/mui-extras/tooltipped-button.d.ts +28 -1
- package/lib/components/mui-extras/tooltipped-button.js +34 -6
- package/lib/components/mui-extras/tooltipped-icon-button.d.ts +6 -1
- package/lib/components/mui-extras/tooltipped-icon-button.js +8 -7
- package/lib/context.d.ts +3 -2
- package/lib/context.js +9 -2
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/tokens.d.ts +11 -0
- package/lib/tokens.js +9 -0
- package/lib/types.d.ts +4 -0
- package/lib/widgets/chat-widget.d.ts +5 -0
- package/lib/widgets/chat-widget.js +6 -0
- package/lib/widgets/multichat-panel.d.ts +1 -6
- package/lib/widgets/multichat-panel.js +3 -13
- package/package.json +1 -1
- package/src/active-cell-manager.ts +3 -1
- package/src/components/attachments.tsx +3 -3
- package/src/components/chat.tsx +13 -24
- package/src/components/code-blocks/code-toolbar.tsx +29 -55
- package/src/components/code-blocks/copy-button.tsx +13 -20
- package/src/components/index.ts +1 -0
- package/src/components/input/buttons/attach-button.tsx +5 -13
- package/src/components/input/buttons/cancel-button.tsx +11 -18
- package/src/components/input/buttons/save-edit-button.tsx +13 -22
- package/src/components/input/buttons/send-button.tsx +13 -34
- package/src/components/input/buttons/stop-button.tsx +13 -34
- package/src/components/input/chat-input.tsx +24 -38
- package/src/components/messages/footer.tsx +12 -10
- package/src/components/messages/header.tsx +23 -17
- package/src/components/messages/message-renderer.tsx +5 -13
- package/src/components/messages/message.tsx +4 -7
- package/src/components/messages/messages.tsx +73 -97
- package/src/components/messages/navigation.tsx +3 -3
- package/src/components/messages/toolbar.tsx +19 -33
- package/src/components/messages/welcome.tsx +13 -2
- package/src/components/mui-extras/tooltipped-button.tsx +44 -5
- package/src/components/mui-extras/tooltipped-icon-button.tsx +22 -6
- package/src/context.ts +15 -5
- package/src/index.ts +1 -0
- package/src/tokens.ts +24 -0
- package/src/types.ts +4 -0
- package/src/widgets/chat-widget.tsx +8 -0
- package/src/widgets/multichat-panel.tsx +7 -26
|
@@ -14,28 +14,28 @@ import {
|
|
|
14
14
|
import clsx from 'clsx';
|
|
15
15
|
import React, { useEffect, useRef, useState } from 'react';
|
|
16
16
|
|
|
17
|
+
import { InputToolbarRegistry } from './toolbar-registry';
|
|
18
|
+
import { useChatCommands } from './use-chat-commands';
|
|
17
19
|
import { AttachmentPreviewList } from '../attachments';
|
|
18
|
-
import {
|
|
19
|
-
IInputToolbarRegistry,
|
|
20
|
-
InputToolbarRegistry,
|
|
21
|
-
useChatCommands
|
|
22
|
-
} from '.';
|
|
20
|
+
import { useChatContext } from '../../context';
|
|
23
21
|
import { IInputModel, InputModel } from '../../input-model';
|
|
24
|
-
import { IChatCommandRegistry } from '../../registers';
|
|
25
|
-
import { IAttachment, ChatArea } from '../../types';
|
|
26
22
|
import { IChatModel } from '../../model';
|
|
27
23
|
import { InputWritingIndicator } from './writing-indicator';
|
|
24
|
+
import { IAttachment } from '../../types';
|
|
28
25
|
|
|
29
26
|
const INPUT_BOX_CLASS = 'jp-chat-input-container';
|
|
30
27
|
const INPUT_TEXTFIELD_CLASS = 'jp-chat-input-textfield';
|
|
31
28
|
const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
|
|
32
29
|
|
|
33
30
|
export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
34
|
-
const { model
|
|
31
|
+
const { model } = props;
|
|
32
|
+
const { area, chatCommandRegistry, inputToolbarRegistry } = useChatContext();
|
|
33
|
+
const chatModel = useChatContext().model;
|
|
34
|
+
|
|
35
35
|
const [input, setInput] = useState<string>(model.value);
|
|
36
36
|
const inputRef = useRef<HTMLInputElement>();
|
|
37
37
|
|
|
38
|
-
const chatCommands = useChatCommands(model,
|
|
38
|
+
const chatCommands = useChatCommands(model, chatCommandRegistry);
|
|
39
39
|
|
|
40
40
|
const [sendWithShiftEnter, setSendWithShiftEnter] = useState<boolean>(
|
|
41
41
|
model.config.sendWithShiftEnter ?? false
|
|
@@ -99,22 +99,22 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
99
99
|
*/
|
|
100
100
|
useEffect(() => {
|
|
101
101
|
const updateToolbar = () => {
|
|
102
|
-
setToolbarElements(
|
|
102
|
+
setToolbarElements(inputToolbarRegistry?.getItems() || []);
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
inputToolbarRegistry?.itemsChanged.connect(updateToolbar);
|
|
106
106
|
updateToolbar();
|
|
107
107
|
|
|
108
108
|
return () => {
|
|
109
|
-
|
|
109
|
+
inputToolbarRegistry?.itemsChanged.disconnect(updateToolbar);
|
|
110
110
|
};
|
|
111
|
-
}, [
|
|
111
|
+
}, [inputToolbarRegistry]);
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* Handle the changes in the writers list.
|
|
115
115
|
*/
|
|
116
116
|
useEffect(() => {
|
|
117
|
-
if (!
|
|
117
|
+
if (!chatModel) {
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -124,15 +124,15 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
124
124
|
};
|
|
125
125
|
|
|
126
126
|
// Set initial writers state
|
|
127
|
-
const initialWriters =
|
|
127
|
+
const initialWriters = chatModel.writers;
|
|
128
128
|
setWriters(initialWriters);
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
chatModel.writersChanged?.connect(updateWriters);
|
|
131
131
|
|
|
132
132
|
return () => {
|
|
133
|
-
|
|
133
|
+
chatModel?.writersChanged?.disconnect(updateWriters);
|
|
134
134
|
};
|
|
135
|
-
}, [
|
|
135
|
+
}, [chatModel]);
|
|
136
136
|
|
|
137
137
|
const inputExists = !!input.trim();
|
|
138
138
|
|
|
@@ -196,14 +196,14 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
196
196
|
(!sendWithShiftEnter && !event.shiftKey)
|
|
197
197
|
) {
|
|
198
198
|
// Run all command providers
|
|
199
|
-
await
|
|
199
|
+
await chatCommandRegistry?.onSubmit(model);
|
|
200
200
|
model.send(model.value);
|
|
201
201
|
event.stopPropagation();
|
|
202
202
|
event.preventDefault();
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
const horizontalPadding =
|
|
206
|
+
const horizontalPadding = area === 'sidebar' ? 1.5 : 2;
|
|
207
207
|
|
|
208
208
|
return (
|
|
209
209
|
<Box
|
|
@@ -258,6 +258,7 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
258
258
|
variant="standard"
|
|
259
259
|
className={INPUT_TEXTFIELD_CLASS}
|
|
260
260
|
multiline
|
|
261
|
+
maxRows={10}
|
|
261
262
|
onKeyDown={handleKeyDown}
|
|
262
263
|
placeholder="Type a chat message, @ to mention..."
|
|
263
264
|
inputRef={inputRef}
|
|
@@ -269,6 +270,7 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
269
270
|
sx={{
|
|
270
271
|
padding: 1.5,
|
|
271
272
|
margin: 0,
|
|
273
|
+
boxSizing: 'border-box',
|
|
272
274
|
backgroundColor: 'var(--jp-layout-color0)',
|
|
273
275
|
transition: 'background-color 0.2s ease',
|
|
274
276
|
'& .MuiInputBase-root': {
|
|
@@ -333,8 +335,8 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
333
335
|
<item.element
|
|
334
336
|
key={index}
|
|
335
337
|
model={model}
|
|
336
|
-
chatCommandRegistry={
|
|
337
|
-
chatModel={
|
|
338
|
+
chatCommandRegistry={chatCommandRegistry}
|
|
339
|
+
chatModel={chatModel}
|
|
338
340
|
edit={props.edit}
|
|
339
341
|
/>
|
|
340
342
|
))}
|
|
@@ -357,10 +359,6 @@ export namespace ChatInput {
|
|
|
357
359
|
* The input model.
|
|
358
360
|
*/
|
|
359
361
|
model: IInputModel;
|
|
360
|
-
/**
|
|
361
|
-
* The toolbar registry.
|
|
362
|
-
*/
|
|
363
|
-
toolbarRegistry: IInputToolbarRegistry;
|
|
364
362
|
/**
|
|
365
363
|
* The function to be called to cancel editing.
|
|
366
364
|
*/
|
|
@@ -369,18 +367,6 @@ export namespace ChatInput {
|
|
|
369
367
|
* Custom mui/material styles.
|
|
370
368
|
*/
|
|
371
369
|
sx?: SxProps<Theme>;
|
|
372
|
-
/**
|
|
373
|
-
* Chat command registry.
|
|
374
|
-
*/
|
|
375
|
-
chatCommandRegistry?: IChatCommandRegistry;
|
|
376
|
-
/**
|
|
377
|
-
* The area where the chat is displayed.
|
|
378
|
-
*/
|
|
379
|
-
area?: ChatArea;
|
|
380
|
-
/**
|
|
381
|
-
* The chat model.
|
|
382
|
-
*/
|
|
383
|
-
chatModel?: IChatModel;
|
|
384
370
|
/**
|
|
385
371
|
* Whether the input is in edit mode (editing an existing message).
|
|
386
372
|
* Defaults to false (new message mode).
|
|
@@ -6,19 +6,17 @@
|
|
|
6
6
|
import { Box } from '@mui/material';
|
|
7
7
|
import React from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
MessageFooterSectionProps
|
|
12
|
-
} from '../../registers';
|
|
9
|
+
import { useChatContext } from '../../context';
|
|
10
|
+
import { IChatMessage } from '../../types';
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* The chat footer component properties.
|
|
16
14
|
*/
|
|
17
|
-
export interface IMessageFootersProps
|
|
15
|
+
export interface IMessageFootersProps {
|
|
18
16
|
/**
|
|
19
|
-
* The chat
|
|
17
|
+
* The chat model.
|
|
20
18
|
*/
|
|
21
|
-
|
|
19
|
+
message: IChatMessage;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
/**
|
|
@@ -27,9 +25,13 @@ export interface IMessageFootersProps extends MessageFooterSectionProps {
|
|
|
27
25
|
*/
|
|
28
26
|
export function MessageFooterComponent(
|
|
29
27
|
props: IMessageFootersProps
|
|
30
|
-
): JSX.Element {
|
|
31
|
-
const { message
|
|
32
|
-
const
|
|
28
|
+
): JSX.Element | null {
|
|
29
|
+
const { message } = props;
|
|
30
|
+
const { model, messageFooterRegistry } = useChatContext();
|
|
31
|
+
if (!messageFooterRegistry) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const footer = messageFooterRegistry.getFooter();
|
|
33
35
|
|
|
34
36
|
return (
|
|
35
37
|
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
@@ -30,12 +30,16 @@ type ChatMessageHeaderProps = {
|
|
|
30
30
|
* The message header component.
|
|
31
31
|
*/
|
|
32
32
|
export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const message = props.message;
|
|
34
|
+
// Don't render header for stacked messages not deleted or edited.
|
|
35
|
+
if (message.stacked && !message.deleted && !message.edited) {
|
|
35
36
|
return <></>;
|
|
36
37
|
}
|
|
38
|
+
|
|
39
|
+
// Flag to display only the deleted or edited information (stacked message).
|
|
40
|
+
const onlyState = message.stacked && (message.deleted || message.edited);
|
|
41
|
+
|
|
37
42
|
const [datetime, setDatetime] = useState<Record<number, string>>({});
|
|
38
|
-
const message = props.message;
|
|
39
43
|
const sender = message.sender;
|
|
40
44
|
/**
|
|
41
45
|
* Effect: update cached datetime strings upon receiving a new message.
|
|
@@ -88,10 +92,10 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
|
|
|
88
92
|
'& > :not(:last-child)': {
|
|
89
93
|
marginRight: 3
|
|
90
94
|
},
|
|
91
|
-
marginBottom: message.stacked ? '0px' : '12px'
|
|
95
|
+
marginBottom: message.stacked || props.isCurrentUser ? '0px' : '12px'
|
|
92
96
|
}}
|
|
93
97
|
>
|
|
94
|
-
{avatar}
|
|
98
|
+
{!props.isCurrentUser && !onlyState && avatar}
|
|
95
99
|
<Box
|
|
96
100
|
sx={{
|
|
97
101
|
display: 'flex',
|
|
@@ -102,7 +106,7 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
|
|
|
102
106
|
}}
|
|
103
107
|
>
|
|
104
108
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
105
|
-
{!
|
|
109
|
+
{!onlyState && !props.isCurrentUser && (
|
|
106
110
|
<Typography
|
|
107
111
|
sx={{
|
|
108
112
|
fontWeight: 700,
|
|
@@ -124,17 +128,19 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
|
|
|
124
128
|
</Typography>
|
|
125
129
|
)}
|
|
126
130
|
</Box>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
{!onlyState && (
|
|
132
|
+
<Typography
|
|
133
|
+
className={MESSAGE_TIME_CLASS}
|
|
134
|
+
sx={{
|
|
135
|
+
fontSize: '0.8em',
|
|
136
|
+
color: 'var(--jp-ui-font-color2)',
|
|
137
|
+
fontWeight: 300
|
|
138
|
+
}}
|
|
139
|
+
title={message.raw_time ? 'Unverified time' : ''}
|
|
140
|
+
>
|
|
141
|
+
{`${datetime[message.time]}${message.raw_time ? '*' : ''}`}
|
|
142
|
+
</Typography>
|
|
143
|
+
)}
|
|
138
144
|
</Box>
|
|
139
145
|
</Box>
|
|
140
146
|
);
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
7
6
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
8
7
|
import React, { useState, useEffect } from 'react';
|
|
9
8
|
import { createPortal } from 'react-dom';
|
|
10
9
|
|
|
11
|
-
import { CodeToolbar, CodeToolbarProps } from '../code-blocks/code-toolbar';
|
|
12
10
|
import { MessageToolbar } from './toolbar';
|
|
11
|
+
import { CodeToolbar, CodeToolbarProps } from '../code-blocks/code-toolbar';
|
|
12
|
+
import { useChatContext } from '../../context';
|
|
13
13
|
import { MarkdownRenderer, MD_RENDERED_CLASS } from '../../markdown-renderer';
|
|
14
|
-
import { IChatModel } from '../../model';
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* The type of the props for the MessageRenderer component.
|
|
@@ -21,14 +20,6 @@ type MessageRendererProps = {
|
|
|
21
20
|
* The string to render.
|
|
22
21
|
*/
|
|
23
22
|
markdownStr: string;
|
|
24
|
-
/**
|
|
25
|
-
* The rendermime registry.
|
|
26
|
-
*/
|
|
27
|
-
rmRegistry: IRenderMimeRegistry;
|
|
28
|
-
/**
|
|
29
|
-
* The model of the chat.
|
|
30
|
-
*/
|
|
31
|
-
model: IChatModel;
|
|
32
23
|
/**
|
|
33
24
|
* The promise to resolve when the message is rendered.
|
|
34
25
|
*/
|
|
@@ -51,7 +42,8 @@ type MessageRendererProps = {
|
|
|
51
42
|
* The message renderer base component.
|
|
52
43
|
*/
|
|
53
44
|
function MessageRendererBase(props: MessageRendererProps): JSX.Element {
|
|
54
|
-
const { markdownStr
|
|
45
|
+
const { markdownStr } = props;
|
|
46
|
+
const { model, rmRegistry } = useChatContext();
|
|
55
47
|
const appendContent = props.appendContent || false;
|
|
56
48
|
const [renderedContent, setRenderedContent] = useState<HTMLElement | null>(
|
|
57
49
|
null
|
|
@@ -81,7 +73,7 @@ function MessageRendererBase(props: MessageRendererProps): JSX.Element {
|
|
|
81
73
|
);
|
|
82
74
|
newCodeToolbarDefns.push([
|
|
83
75
|
codeToolbarRoot,
|
|
84
|
-
{ model:
|
|
76
|
+
{ model: model, content: preBlock.textContent || '' }
|
|
85
77
|
]);
|
|
86
78
|
});
|
|
87
79
|
|
|
@@ -7,9 +7,9 @@ import { PromiseDelegate } from '@lumino/coreutils';
|
|
|
7
7
|
import React, { forwardRef, useEffect, useState } from 'react';
|
|
8
8
|
|
|
9
9
|
import { MessageRenderer } from './message-renderer';
|
|
10
|
-
import { BaseMessageProps } from './messages';
|
|
11
10
|
import { AttachmentPreviewList } from '../attachments';
|
|
12
11
|
import { ChatInput } from '../input';
|
|
12
|
+
import { useChatContext } from '../../context';
|
|
13
13
|
import { IInputModel, InputModel } from '../../input-model';
|
|
14
14
|
import { IChatMessage } from '../../types';
|
|
15
15
|
import { replaceSpanToMention } from '../../utils';
|
|
@@ -17,7 +17,7 @@ import { replaceSpanToMention } from '../../utils';
|
|
|
17
17
|
/**
|
|
18
18
|
* The message component props.
|
|
19
19
|
*/
|
|
20
|
-
type ChatMessageProps =
|
|
20
|
+
type ChatMessageProps = {
|
|
21
21
|
/**
|
|
22
22
|
* The message to display.
|
|
23
23
|
*/
|
|
@@ -37,7 +37,8 @@ type ChatMessageProps = BaseMessageProps & {
|
|
|
37
37
|
*/
|
|
38
38
|
export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
|
|
39
39
|
(props, ref): JSX.Element => {
|
|
40
|
-
const { message
|
|
40
|
+
const { message } = props;
|
|
41
|
+
const { model } = useChatContext();
|
|
41
42
|
const [edit, setEdit] = useState<boolean>(false);
|
|
42
43
|
const [deleted, setDeleted] = useState<boolean>(false);
|
|
43
44
|
const [canEdit, setCanEdit] = useState<boolean>(false);
|
|
@@ -132,15 +133,11 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
|
|
|
132
133
|
<ChatInput
|
|
133
134
|
onCancel={() => cancelEdition()}
|
|
134
135
|
model={model.getEditionModel(message.id)!}
|
|
135
|
-
chatCommandRegistry={props.chatCommandRegistry}
|
|
136
|
-
toolbarRegistry={props.inputToolbarRegistry}
|
|
137
136
|
edit={true}
|
|
138
137
|
/>
|
|
139
138
|
) : (
|
|
140
139
|
<MessageRenderer
|
|
141
|
-
rmRegistry={rmRegistry}
|
|
142
140
|
markdownStr={message.body}
|
|
143
|
-
model={model}
|
|
144
141
|
edit={canEdit ? startEdition : undefined}
|
|
145
142
|
delete={canDelete ? () => deleteMessage(message.id) : undefined}
|
|
146
143
|
rendered={props.renderedPromise}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
7
6
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
8
7
|
import { Box } from '@mui/material';
|
|
9
8
|
import clsx from 'clsx';
|
|
@@ -14,58 +13,28 @@ import { ChatMessageHeader } from './header';
|
|
|
14
13
|
import { ChatMessage } from './message';
|
|
15
14
|
import { Navigation } from './navigation';
|
|
16
15
|
import { WelcomeMessage } from './welcome';
|
|
17
|
-
import { IInputToolbarRegistry } from '../input';
|
|
18
16
|
import { ScrollContainer } from '../scroll-container';
|
|
19
|
-
import {
|
|
17
|
+
import { useChatContext } from '../../context';
|
|
18
|
+
import { IChatMessage, IConfig } from '../../types';
|
|
20
19
|
import { IChatModel } from '../../model';
|
|
21
|
-
import { ChatArea, IChatMessage } from '../../types';
|
|
22
20
|
|
|
23
21
|
export const MESSAGE_CLASS = 'jp-chat-message';
|
|
24
22
|
const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
|
|
25
23
|
const MESSAGE_STACKED_CLASS = 'jp-chat-message-stacked';
|
|
26
24
|
|
|
27
|
-
/**
|
|
28
|
-
* The base components props.
|
|
29
|
-
*/
|
|
30
|
-
export type BaseMessageProps = {
|
|
31
|
-
/**
|
|
32
|
-
* The mime renderer registry.
|
|
33
|
-
*/
|
|
34
|
-
rmRegistry: IRenderMimeRegistry;
|
|
35
|
-
/**
|
|
36
|
-
* The chat model.
|
|
37
|
-
*/
|
|
38
|
-
model: IChatModel;
|
|
39
|
-
/**
|
|
40
|
-
* The chat commands registry.
|
|
41
|
-
*/
|
|
42
|
-
chatCommandRegistry?: IChatCommandRegistry;
|
|
43
|
-
/**
|
|
44
|
-
* The input toolbar registry.
|
|
45
|
-
*/
|
|
46
|
-
inputToolbarRegistry: IInputToolbarRegistry;
|
|
47
|
-
/**
|
|
48
|
-
* The footer registry.
|
|
49
|
-
*/
|
|
50
|
-
messageFooterRegistry?: IMessageFooterRegistry;
|
|
51
|
-
/**
|
|
52
|
-
* The welcome message.
|
|
53
|
-
*/
|
|
54
|
-
welcomeMessage?: string;
|
|
55
|
-
/**
|
|
56
|
-
* The area where the chat is displayed.
|
|
57
|
-
*/
|
|
58
|
-
area?: ChatArea;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
25
|
/**
|
|
62
26
|
* The messages list component.
|
|
63
27
|
*/
|
|
64
|
-
export function ChatMessages(
|
|
65
|
-
const { model } =
|
|
28
|
+
export function ChatMessages(): JSX.Element {
|
|
29
|
+
const { area, messageFooterRegistry, model, welcomeMessage } =
|
|
30
|
+
useChatContext();
|
|
31
|
+
|
|
66
32
|
const [messages, setMessages] = useState<IChatMessage[]>(model.messages);
|
|
67
33
|
const refMsgBox = useRef<HTMLDivElement>(null);
|
|
68
34
|
const [allRendered, setAllRendered] = useState<boolean>(false);
|
|
35
|
+
const [showDeleted, setShowDeleted] = useState<boolean>(
|
|
36
|
+
model.config.showDeleted ?? false
|
|
37
|
+
);
|
|
69
38
|
|
|
70
39
|
// The list of message DOM and their rendered promises.
|
|
71
40
|
const listRef = useRef<(HTMLDivElement | null)[]>([]);
|
|
@@ -95,14 +64,29 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
|
|
|
95
64
|
function handleChatEvents() {
|
|
96
65
|
setMessages([...model.messages]);
|
|
97
66
|
}
|
|
98
|
-
|
|
99
67
|
model.messagesUpdated.connect(handleChatEvents);
|
|
100
68
|
|
|
101
|
-
return
|
|
69
|
+
return () => {
|
|
102
70
|
model.messagesUpdated.disconnect(handleChatEvents);
|
|
103
71
|
};
|
|
104
72
|
}, [model]);
|
|
105
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Effect: Listen to the config change.
|
|
76
|
+
*/
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
function handleConfigChange(_: IChatModel, config: IConfig) {
|
|
79
|
+
if (config.showDeleted !== showDeleted) {
|
|
80
|
+
setShowDeleted(config.showDeleted ?? false);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
model.configChanged.connect(handleConfigChange);
|
|
84
|
+
|
|
85
|
+
return () => {
|
|
86
|
+
model.configChanged.disconnect(handleConfigChange);
|
|
87
|
+
};
|
|
88
|
+
}, [model, showDeleted]);
|
|
89
|
+
|
|
106
90
|
/**
|
|
107
91
|
* Observe the messages to update the current viewport and the unread messages.
|
|
108
92
|
*/
|
|
@@ -137,7 +121,7 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
|
|
|
137
121
|
}
|
|
138
122
|
});
|
|
139
123
|
|
|
140
|
-
|
|
124
|
+
model.messagesInViewport = inViewport;
|
|
141
125
|
|
|
142
126
|
// Ensure that all messages are rendered before updating unread messages, otherwise
|
|
143
127
|
// it can lead to wrong assumption , because more message are in the viewport
|
|
@@ -165,16 +149,11 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
|
|
|
165
149
|
};
|
|
166
150
|
}, [messages, allRendered]);
|
|
167
151
|
|
|
168
|
-
const horizontalPadding =
|
|
152
|
+
const horizontalPadding = area === 'main' ? 8 : 4;
|
|
169
153
|
return (
|
|
170
154
|
<>
|
|
171
155
|
<ScrollContainer sx={{ flexGrow: 1 }}>
|
|
172
|
-
{
|
|
173
|
-
<WelcomeMessage
|
|
174
|
-
rmRegistry={props.rmRegistry}
|
|
175
|
-
content={props.welcomeMessage}
|
|
176
|
-
/>
|
|
177
|
-
)}
|
|
156
|
+
{welcomeMessage && <WelcomeMessage content={welcomeMessage} />}
|
|
178
157
|
<Box
|
|
179
158
|
sx={{
|
|
180
159
|
paddingLeft: horizontalPadding,
|
|
@@ -188,55 +167,52 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
|
|
|
188
167
|
ref={refMsgBox}
|
|
189
168
|
className={clsx(MESSAGES_BOX_CLASS)}
|
|
190
169
|
>
|
|
191
|
-
{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
</Box>
|
|
235
|
-
);
|
|
236
|
-
})}
|
|
170
|
+
{/* Filter the deleted message if user don't expect to see it. */}
|
|
171
|
+
{(showDeleted
|
|
172
|
+
? messages
|
|
173
|
+
: messages.filter(message => !message.deleted)
|
|
174
|
+
).map((message, i) => {
|
|
175
|
+
renderedPromise.current[i] = new PromiseDelegate();
|
|
176
|
+
const isCurrentUser =
|
|
177
|
+
model.user !== undefined &&
|
|
178
|
+
model.user.username === message.sender.username;
|
|
179
|
+
return (
|
|
180
|
+
// extra div needed to ensure each bubble is on a new line
|
|
181
|
+
<Box
|
|
182
|
+
key={i}
|
|
183
|
+
sx={{
|
|
184
|
+
...(isCurrentUser && {
|
|
185
|
+
marginLeft: area === 'main' ? '25%' : '10%',
|
|
186
|
+
backgroundColor: 'var(--jp-layout-color2)',
|
|
187
|
+
border: 'none',
|
|
188
|
+
borderRadius: 2,
|
|
189
|
+
padding: 2
|
|
190
|
+
})
|
|
191
|
+
}}
|
|
192
|
+
className={clsx(
|
|
193
|
+
MESSAGE_CLASS,
|
|
194
|
+
message.stacked ? MESSAGE_STACKED_CLASS : ''
|
|
195
|
+
)}
|
|
196
|
+
>
|
|
197
|
+
<ChatMessageHeader
|
|
198
|
+
message={message}
|
|
199
|
+
isCurrentUser={isCurrentUser}
|
|
200
|
+
/>
|
|
201
|
+
<ChatMessage
|
|
202
|
+
message={message}
|
|
203
|
+
index={i}
|
|
204
|
+
renderedPromise={renderedPromise.current[i]}
|
|
205
|
+
ref={el => (listRef.current[i] = el)}
|
|
206
|
+
/>
|
|
207
|
+
{messageFooterRegistry && (
|
|
208
|
+
<MessageFooterComponent message={message} />
|
|
209
|
+
)}
|
|
210
|
+
</Box>
|
|
211
|
+
);
|
|
212
|
+
})}
|
|
237
213
|
</Box>
|
|
238
214
|
</ScrollContainer>
|
|
239
|
-
<Navigation
|
|
215
|
+
<Navigation refMsgBox={refMsgBox} allRendered={allRendered} />
|
|
240
216
|
</>
|
|
241
217
|
);
|
|
242
218
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '@jupyterlab/ui-components';
|
|
12
12
|
import React, { useEffect, useState } from 'react';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { useChatContext } from '../../context';
|
|
15
15
|
import { IChatModel } from '../../model';
|
|
16
16
|
|
|
17
17
|
const NAVIGATION_BUTTON_CLASS = 'jp-chat-navigation';
|
|
@@ -22,7 +22,7 @@ const NAVIGATION_BOTTOM_CLASS = 'jp-chat-navigation-bottom';
|
|
|
22
22
|
/**
|
|
23
23
|
* The navigation component props.
|
|
24
24
|
*/
|
|
25
|
-
type NavigationProps =
|
|
25
|
+
type NavigationProps = {
|
|
26
26
|
/**
|
|
27
27
|
* The reference to the messages container.
|
|
28
28
|
*/
|
|
@@ -37,7 +37,7 @@ type NavigationProps = BaseMessageProps & {
|
|
|
37
37
|
* The navigation component, to navigate to unread messages.
|
|
38
38
|
*/
|
|
39
39
|
export function Navigation(props: NavigationProps): JSX.Element {
|
|
40
|
-
const { model } =
|
|
40
|
+
const { model } = useChatContext();
|
|
41
41
|
const [lastInViewport, setLastInViewport] = useState<boolean>(true);
|
|
42
42
|
const [unreadBefore, setUnreadBefore] = useState<number | null>(null);
|
|
43
43
|
const [unreadAfter, setUnreadAfter] = useState<number | null>(null);
|