@jupyter/chat 0.19.0-alpha.3 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__tests__/model.spec.js +2 -2
- package/lib/components/chat.js +29 -2
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/input/chat-input.js +1 -25
- package/lib/components/input/index.d.ts +0 -1
- package/lib/components/input/index.js +0 -1
- package/lib/components/messages/footer.d.ts +2 -2
- package/lib/components/messages/header.d.ts +2 -2
- package/lib/components/messages/header.js +13 -6
- package/lib/components/messages/message.d.ts +2 -2
- package/lib/components/messages/message.js +16 -1
- package/lib/components/messages/messages.js +3 -2
- package/lib/components/messages/toolbar.js +6 -14
- package/lib/components/writing-indicator.d.ts +20 -0
- package/lib/components/{input/writing-indicator.js → writing-indicator.js} +3 -2
- package/lib/message.d.ts +41 -0
- package/lib/message.js +74 -0
- package/lib/model.d.ts +13 -13
- package/lib/model.js +9 -7
- package/lib/registers/footers.d.ts +2 -2
- package/lib/types.d.ts +21 -3
- package/lib/widgets/chat-widget.js +18 -11
- package/package.json +1 -1
- package/src/__tests__/model.spec.ts +7 -7
- package/src/components/chat.tsx +35 -1
- package/src/components/index.ts +1 -0
- package/src/components/input/chat-input.tsx +0 -28
- package/src/components/input/index.ts +0 -1
- package/src/components/messages/footer.tsx +2 -2
- package/src/components/messages/header.tsx +20 -8
- package/src/components/messages/message.tsx +23 -3
- package/src/components/messages/messages.tsx +9 -4
- package/src/components/messages/toolbar.tsx +14 -14
- package/src/components/{input/writing-indicator.tsx → writing-indicator.tsx} +9 -4
- package/src/message.ts +83 -0
- package/src/model.ts +25 -22
- package/src/registers/footers.ts +2 -2
- package/src/types.ts +23 -3
- package/src/widgets/chat-widget.tsx +22 -14
- package/lib/components/input/writing-indicator.d.ts +0 -15
|
@@ -52,7 +52,7 @@ describe('test chat model', () => {
|
|
|
52
52
|
});
|
|
53
53
|
model.messageAdded(msg);
|
|
54
54
|
expect(messages).toHaveLength(1);
|
|
55
|
-
expect(messages[0]).toBe(msg);
|
|
55
|
+
expect(messages[0].content).toBe(msg);
|
|
56
56
|
});
|
|
57
57
|
it('should format message', () => {
|
|
58
58
|
model = new TestChat();
|
|
@@ -62,7 +62,7 @@ describe('test chat model', () => {
|
|
|
62
62
|
});
|
|
63
63
|
model.messageAdded({ ...msg });
|
|
64
64
|
expect(messages).toHaveLength(1);
|
|
65
|
-
expect(messages[0]).not.toBe(msg);
|
|
65
|
+
expect(messages[0].content).not.toBe(msg);
|
|
66
66
|
expect(messages[0].body).toBe('formatted msg');
|
|
67
67
|
});
|
|
68
68
|
});
|
package/lib/components/chat.js
CHANGED
|
@@ -6,17 +6,40 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|
|
6
6
|
import SettingsIcon from '@mui/icons-material/Settings';
|
|
7
7
|
import { IconButton } from '@mui/material';
|
|
8
8
|
import { Box } from '@mui/system';
|
|
9
|
-
import React, { useState } from 'react';
|
|
9
|
+
import React, { useEffect, useState } from 'react';
|
|
10
10
|
import { ChatInput, InputToolbarRegistry } from './input';
|
|
11
11
|
import { JlThemeProvider } from './jl-theme-provider';
|
|
12
12
|
import { ChatMessages } from './messages';
|
|
13
|
+
import { WritingIndicator } from './writing-indicator';
|
|
13
14
|
import { ChatReactContext } from '../context';
|
|
14
15
|
export function ChatBody(props) {
|
|
15
16
|
const { model } = props;
|
|
17
|
+
const [writers, setWriters] = useState([]);
|
|
16
18
|
let { inputToolbarRegistry } = props;
|
|
17
19
|
if (!inputToolbarRegistry) {
|
|
18
20
|
inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
|
|
19
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Handle the changes in the writers list.
|
|
24
|
+
*/
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
var _a;
|
|
27
|
+
if (!model) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const updateWriters = (_, writers) => {
|
|
31
|
+
// Show all writers for now - AI generating responses will have messageID
|
|
32
|
+
setWriters(writers);
|
|
33
|
+
};
|
|
34
|
+
// Set initial writers state
|
|
35
|
+
const initialWriters = model.writers;
|
|
36
|
+
setWriters(initialWriters);
|
|
37
|
+
(_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(updateWriters);
|
|
38
|
+
return () => {
|
|
39
|
+
var _a;
|
|
40
|
+
(_a = model === null || model === void 0 ? void 0 : model.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(updateWriters);
|
|
41
|
+
};
|
|
42
|
+
}, [model]);
|
|
20
43
|
// const horizontalPadding = props.area === 'main' ? 8 : 4;
|
|
21
44
|
const horizontalPadding = 4;
|
|
22
45
|
const contextValue = {
|
|
@@ -30,7 +53,11 @@ export function ChatBody(props) {
|
|
|
30
53
|
paddingRight: horizontalPadding,
|
|
31
54
|
paddingTop: 0,
|
|
32
55
|
paddingBottom: 0
|
|
33
|
-
}, model: model.input })
|
|
56
|
+
}, model: model.input }),
|
|
57
|
+
React.createElement(WritingIndicator, { sx: {
|
|
58
|
+
paddingLeft: horizontalPadding,
|
|
59
|
+
paddingRight: horizontalPadding
|
|
60
|
+
}, writers: writers })));
|
|
34
61
|
}
|
|
35
62
|
export function Chat(props) {
|
|
36
63
|
var _a;
|
package/lib/components/index.js
CHANGED
|
@@ -8,7 +8,6 @@ import React, { useEffect, useRef, useState } from 'react';
|
|
|
8
8
|
import { useChatCommands } from './use-chat-commands';
|
|
9
9
|
import { AttachmentPreviewList } from '../attachments';
|
|
10
10
|
import { useChatContext } from '../../context';
|
|
11
|
-
import { InputWritingIndicator } from './writing-indicator';
|
|
12
11
|
const INPUT_BOX_CLASS = 'jp-chat-input-container';
|
|
13
12
|
const INPUT_TEXTFIELD_CLASS = 'jp-chat-input-textfield';
|
|
14
13
|
const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
|
|
@@ -23,7 +22,6 @@ export function ChatInput(props) {
|
|
|
23
22
|
const [sendWithShiftEnter, setSendWithShiftEnter] = useState((_a = model.config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false);
|
|
24
23
|
const [attachments, setAttachments] = useState(model.attachments);
|
|
25
24
|
const [toolbarElements, setToolbarElements] = useState([]);
|
|
26
|
-
const [writers, setWriters] = useState([]);
|
|
27
25
|
/**
|
|
28
26
|
* Auto-focus the input when the component is first mounted.
|
|
29
27
|
*/
|
|
@@ -79,27 +77,6 @@ export function ChatInput(props) {
|
|
|
79
77
|
inputToolbarRegistry === null || inputToolbarRegistry === void 0 ? void 0 : inputToolbarRegistry.itemsChanged.disconnect(updateToolbar);
|
|
80
78
|
};
|
|
81
79
|
}, [inputToolbarRegistry]);
|
|
82
|
-
/**
|
|
83
|
-
* Handle the changes in the writers list.
|
|
84
|
-
*/
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
var _a;
|
|
87
|
-
if (!chatModel) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
const updateWriters = (_, writers) => {
|
|
91
|
-
// Show all writers for now - AI generating responses will have messageID
|
|
92
|
-
setWriters(writers);
|
|
93
|
-
};
|
|
94
|
-
// Set initial writers state
|
|
95
|
-
const initialWriters = chatModel.writers;
|
|
96
|
-
setWriters(initialWriters);
|
|
97
|
-
(_a = chatModel.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(updateWriters);
|
|
98
|
-
return () => {
|
|
99
|
-
var _a;
|
|
100
|
-
(_a = chatModel === null || chatModel === void 0 ? void 0 : chatModel.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(updateWriters);
|
|
101
|
-
};
|
|
102
|
-
}, [chatModel]);
|
|
103
80
|
const inputExists = !!input.trim();
|
|
104
81
|
/**
|
|
105
82
|
* `handleKeyDown()`: callback invoked when the user presses any key in the
|
|
@@ -232,6 +209,5 @@ export function ChatInput(props) {
|
|
|
232
209
|
borderColor: 'var(--jp-border-color1)',
|
|
233
210
|
backgroundColor: 'var(--jp-layout-color0)',
|
|
234
211
|
transition: 'background-color 0.2s ease'
|
|
235
|
-
} }, toolbarElements.map((item, index) => (React.createElement(item.element, { key: index, model: model, chatCommandRegistry: chatCommandRegistry, chatModel: chatModel, edit: props.edit })))))
|
|
236
|
-
React.createElement(InputWritingIndicator, { writers: writers })));
|
|
212
|
+
} }, toolbarElements.map((item, index) => (React.createElement(item.element, { key: index, model: model, chatCommandRegistry: chatCommandRegistry, chatModel: chatModel, edit: props.edit })))))));
|
|
237
213
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import {
|
|
2
|
+
import { IMessageContent } from '../../types';
|
|
3
3
|
/**
|
|
4
4
|
* The chat footer component properties.
|
|
5
5
|
*/
|
|
@@ -7,7 +7,7 @@ export interface IMessageFootersProps {
|
|
|
7
7
|
/**
|
|
8
8
|
* The chat model.
|
|
9
9
|
*/
|
|
10
|
-
message:
|
|
10
|
+
message: IMessageContent;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* The chat footer component, which displays footer components on a row according to
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import {
|
|
2
|
+
import { IMessage } from '../../types';
|
|
3
3
|
/**
|
|
4
4
|
* The message header props.
|
|
5
5
|
*/
|
|
@@ -7,7 +7,7 @@ type ChatMessageHeaderProps = {
|
|
|
7
7
|
/**
|
|
8
8
|
* The chat message.
|
|
9
9
|
*/
|
|
10
|
-
message:
|
|
10
|
+
message: IMessage;
|
|
11
11
|
/**
|
|
12
12
|
* Whether this message is from the current user.
|
|
13
13
|
*/
|
|
@@ -12,11 +12,7 @@ const MESSAGE_TIME_CLASS = 'jp-chat-message-time';
|
|
|
12
12
|
*/
|
|
13
13
|
export function ChatMessageHeader(props) {
|
|
14
14
|
var _a, _b;
|
|
15
|
-
const message = props.message;
|
|
16
|
-
// Don't render header for stacked messages not deleted or edited.
|
|
17
|
-
if (message.stacked && !message.deleted && !message.edited) {
|
|
18
|
-
return React.createElement(React.Fragment, null);
|
|
19
|
-
}
|
|
15
|
+
const [message, setMessage] = useState(props.message.content);
|
|
20
16
|
// Flag to display only the deleted or edited information (stacked message).
|
|
21
17
|
const onlyState = message.stacked && (message.deleted || message.edited);
|
|
22
18
|
const [datetime, setDatetime] = useState({});
|
|
@@ -55,9 +51,20 @@ export function ChatMessageHeader(props) {
|
|
|
55
51
|
setDatetime(newDatetime);
|
|
56
52
|
}
|
|
57
53
|
});
|
|
54
|
+
// Listen for changes in the current message.
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
function messageChanged() {
|
|
57
|
+
setMessage(props.message.content);
|
|
58
|
+
}
|
|
59
|
+
props.message.changed.connect(messageChanged);
|
|
60
|
+
return () => {
|
|
61
|
+
props.message.changed.disconnect(messageChanged);
|
|
62
|
+
};
|
|
63
|
+
}, [props.message]);
|
|
58
64
|
const avatar = message.stacked ? null : Avatar({ user: sender });
|
|
59
65
|
const name = (_b = (_a = sender.display_name) !== null && _a !== void 0 ? _a : sender.name) !== null && _b !== void 0 ? _b : (sender.username || 'User undefined');
|
|
60
|
-
|
|
66
|
+
// Don't render header for stacked messages not deleted or edited.
|
|
67
|
+
return message.stacked && !message.deleted && !message.edited ? (React.createElement(React.Fragment, null)) : (React.createElement(Box, { className: MESSAGE_HEADER_CLASS, sx: {
|
|
61
68
|
display: 'flex',
|
|
62
69
|
alignItems: 'center',
|
|
63
70
|
'& > :not(:last-child)': {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { IMessage } from '../../types';
|
|
4
4
|
/**
|
|
5
5
|
* The message component props.
|
|
6
6
|
*/
|
|
@@ -8,7 +8,7 @@ type ChatMessageProps = {
|
|
|
8
8
|
/**
|
|
9
9
|
* The message to display.
|
|
10
10
|
*/
|
|
11
|
-
message:
|
|
11
|
+
message: IMessage;
|
|
12
12
|
/**
|
|
13
13
|
* The index of the message in the list.
|
|
14
14
|
*/
|
|
@@ -13,8 +13,8 @@ import { replaceSpanToMention } from '../../utils';
|
|
|
13
13
|
* The message component body.
|
|
14
14
|
*/
|
|
15
15
|
export const ChatMessage = forwardRef((props, ref) => {
|
|
16
|
-
const { message } = props;
|
|
17
16
|
const { model } = useChatContext();
|
|
17
|
+
const [message, setMessage] = useState(props.message.content);
|
|
18
18
|
const [edit, setEdit] = useState(false);
|
|
19
19
|
const [deleted, setDeleted] = useState(false);
|
|
20
20
|
const [canEdit, setCanEdit] = useState(false);
|
|
@@ -39,6 +39,21 @@ export const ChatMessage = forwardRef((props, ref) => {
|
|
|
39
39
|
setCanDelete(false);
|
|
40
40
|
}
|
|
41
41
|
}, [model, message]);
|
|
42
|
+
// Listen for changes in the current message.
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
function messageChanged() {
|
|
45
|
+
setMessage(props.message.content);
|
|
46
|
+
}
|
|
47
|
+
props.message.changed.connect(messageChanged);
|
|
48
|
+
// Initialize the message when the message is re-rendered.
|
|
49
|
+
// FIX ? This seems to be required for outofband change, to get the new value,
|
|
50
|
+
// even if when an outofband change occurs, all the messages are deleted and
|
|
51
|
+
// recreated.
|
|
52
|
+
setMessage(props.message.content);
|
|
53
|
+
return () => {
|
|
54
|
+
props.message.changed.disconnect(messageChanged);
|
|
55
|
+
};
|
|
56
|
+
}, [props.message]);
|
|
42
57
|
// Create an input model only if the message is edited.
|
|
43
58
|
const startEdition = () => {
|
|
44
59
|
var _a;
|
|
@@ -13,6 +13,7 @@ import { Navigation } from './navigation';
|
|
|
13
13
|
import { WelcomeMessage } from './welcome';
|
|
14
14
|
import { ScrollContainer } from '../scroll-container';
|
|
15
15
|
import { useChatContext } from '../../context';
|
|
16
|
+
import { Message } from '../../message';
|
|
16
17
|
export const MESSAGE_CLASS = 'jp-chat-message';
|
|
17
18
|
const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
|
|
18
19
|
const MESSAGE_STACKED_CLASS = 'jp-chat-message-stacked';
|
|
@@ -39,7 +40,7 @@ export function ChatMessages() {
|
|
|
39
40
|
}
|
|
40
41
|
model
|
|
41
42
|
.getHistory()
|
|
42
|
-
.then(history => setMessages(history.messages))
|
|
43
|
+
.then(history => setMessages(history.messages.map(message => new Message({ ...message }))))
|
|
43
44
|
.catch(e => console.error(e));
|
|
44
45
|
}
|
|
45
46
|
fetchHistory();
|
|
@@ -150,7 +151,7 @@ export function ChatMessages() {
|
|
|
150
151
|
model.user.username === message.sender.username;
|
|
151
152
|
return (
|
|
152
153
|
// extra div needed to ensure each bubble is on a new line
|
|
153
|
-
React.createElement(Box, { key:
|
|
154
|
+
React.createElement(Box, { key: message.id, sx: {
|
|
154
155
|
...(isCurrentUser && {
|
|
155
156
|
marginLeft: area === 'main' ? '25%' : '10%',
|
|
156
157
|
backgroundColor: 'var(--jp-layout-color2)',
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Copyright (c) Jupyter Development Team.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
-
// import EditIcon from '@mui/icons-material/Edit';
|
|
6
5
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
6
|
+
import EditIcon from '@mui/icons-material/Edit';
|
|
7
7
|
import { Box } from '@mui/material';
|
|
8
8
|
import React from 'react';
|
|
9
9
|
import { TooltippedIconButton } from '../mui-extras';
|
|
@@ -13,19 +13,11 @@ const TOOLBAR_CLASS = 'jp-chat-toolbar';
|
|
|
13
13
|
*/
|
|
14
14
|
export function MessageToolbar(props) {
|
|
15
15
|
const buttons = [];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// aria-label={'Edit'}
|
|
22
|
-
// inputToolbar={false}
|
|
23
|
-
// >
|
|
24
|
-
// <EditIcon />
|
|
25
|
-
// </TooltippedIconButton>
|
|
26
|
-
// );
|
|
27
|
-
// buttons.push(editButton);
|
|
28
|
-
// }
|
|
16
|
+
if (props.edit !== undefined) {
|
|
17
|
+
const editButton = (React.createElement(TooltippedIconButton, { tooltip: 'edit', onClick: props.edit, "aria-label": 'Edit', inputToolbar: false },
|
|
18
|
+
React.createElement(EditIcon, null)));
|
|
19
|
+
buttons.push(editButton);
|
|
20
|
+
}
|
|
29
21
|
if (props.delete !== undefined) {
|
|
30
22
|
const deleteButton = (React.createElement(TooltippedIconButton, { tooltip: 'Delete', onClick: props.delete, "aria-label": 'Delete', inputToolbar: false },
|
|
31
23
|
React.createElement(DeleteIcon, null)));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { SxProps, Theme } from '@mui/material';
|
|
3
|
+
import { IChatModel } from '../model';
|
|
4
|
+
/**
|
|
5
|
+
* The input writing indicator component props.
|
|
6
|
+
*/
|
|
7
|
+
export interface IInputWritingIndicatorProps {
|
|
8
|
+
/**
|
|
9
|
+
* The list of users currently writing.
|
|
10
|
+
*/
|
|
11
|
+
writers: IChatModel.IWriter[];
|
|
12
|
+
/**
|
|
13
|
+
* Custom mui/material styles.
|
|
14
|
+
*/
|
|
15
|
+
sx?: SxProps<Theme>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The writing indicator component, displaying typing status.
|
|
19
|
+
*/
|
|
20
|
+
export declare function WritingIndicator(props: IInputWritingIndicatorProps): JSX.Element;
|
|
@@ -30,13 +30,14 @@ function formatWritersText(writers) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
* The
|
|
33
|
+
* The writing indicator component, displaying typing status.
|
|
34
34
|
*/
|
|
35
|
-
export function
|
|
35
|
+
export function WritingIndicator(props) {
|
|
36
36
|
const { writers } = props;
|
|
37
37
|
// Always render the container to reserve space, even if no writers
|
|
38
38
|
const writersText = writers.length > 0 ? formatWritersText(writers) : '';
|
|
39
39
|
return (React.createElement(Box, { className: WRITERS_ELEMENT_CLASSNAME, sx: {
|
|
40
|
+
...props.sx,
|
|
40
41
|
minHeight: '16px'
|
|
41
42
|
} },
|
|
42
43
|
React.createElement(Typography, { variant: "caption", sx: {
|
package/lib/message.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ISignal } from '@lumino/signaling';
|
|
2
|
+
import { IAttachment, IMessageContent, IMessage, IUser } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* The message object.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Message implements IMessage {
|
|
7
|
+
/**
|
|
8
|
+
* The constructor of the message.
|
|
9
|
+
*
|
|
10
|
+
* @param content: the content of the message.
|
|
11
|
+
*/
|
|
12
|
+
constructor(content: IMessageContent);
|
|
13
|
+
/**
|
|
14
|
+
* The message content.
|
|
15
|
+
*/
|
|
16
|
+
get content(): IMessageContent;
|
|
17
|
+
/**
|
|
18
|
+
* Getters for each attribute individually.
|
|
19
|
+
*/
|
|
20
|
+
get type(): string;
|
|
21
|
+
get body(): string;
|
|
22
|
+
get id(): string;
|
|
23
|
+
get time(): number;
|
|
24
|
+
get sender(): IUser;
|
|
25
|
+
get attachments(): IAttachment[] | undefined;
|
|
26
|
+
get mentions(): IUser[] | undefined;
|
|
27
|
+
get raw_time(): boolean | undefined;
|
|
28
|
+
get deleted(): boolean | undefined;
|
|
29
|
+
get edited(): boolean | undefined;
|
|
30
|
+
get stacked(): boolean | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* A signal emitting when the message has been updated.
|
|
33
|
+
*/
|
|
34
|
+
get changed(): ISignal<IMessage, void>;
|
|
35
|
+
/**
|
|
36
|
+
* Update one or several fields of the message.
|
|
37
|
+
*/
|
|
38
|
+
update(updated: Partial<IMessageContent>): void;
|
|
39
|
+
private _content;
|
|
40
|
+
private _changed;
|
|
41
|
+
}
|
package/lib/message.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { Signal } from '@lumino/signaling';
|
|
6
|
+
/**
|
|
7
|
+
* The message object.
|
|
8
|
+
*/
|
|
9
|
+
export class Message {
|
|
10
|
+
/**
|
|
11
|
+
* The constructor of the message.
|
|
12
|
+
*
|
|
13
|
+
* @param content: the content of the message.
|
|
14
|
+
*/
|
|
15
|
+
constructor(content) {
|
|
16
|
+
this._changed = new Signal(this);
|
|
17
|
+
this._content = content;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The message content.
|
|
21
|
+
*/
|
|
22
|
+
get content() {
|
|
23
|
+
return this._content;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Getters for each attribute individually.
|
|
27
|
+
*/
|
|
28
|
+
get type() {
|
|
29
|
+
return this._content.type;
|
|
30
|
+
}
|
|
31
|
+
get body() {
|
|
32
|
+
return this._content.body;
|
|
33
|
+
}
|
|
34
|
+
get id() {
|
|
35
|
+
return this._content.id;
|
|
36
|
+
}
|
|
37
|
+
get time() {
|
|
38
|
+
return this._content.time;
|
|
39
|
+
}
|
|
40
|
+
get sender() {
|
|
41
|
+
return this._content.sender;
|
|
42
|
+
}
|
|
43
|
+
get attachments() {
|
|
44
|
+
return this._content.attachments;
|
|
45
|
+
}
|
|
46
|
+
get mentions() {
|
|
47
|
+
return this._content.mentions;
|
|
48
|
+
}
|
|
49
|
+
get raw_time() {
|
|
50
|
+
return this._content.raw_time;
|
|
51
|
+
}
|
|
52
|
+
get deleted() {
|
|
53
|
+
return this._content.deleted;
|
|
54
|
+
}
|
|
55
|
+
get edited() {
|
|
56
|
+
return this._content.edited;
|
|
57
|
+
}
|
|
58
|
+
get stacked() {
|
|
59
|
+
return this._content.stacked;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A signal emitting when the message has been updated.
|
|
63
|
+
*/
|
|
64
|
+
get changed() {
|
|
65
|
+
return this._changed;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Update one or several fields of the message.
|
|
69
|
+
*/
|
|
70
|
+
update(updated) {
|
|
71
|
+
this._content = { ...this._content, ...updated };
|
|
72
|
+
this._changed.emit();
|
|
73
|
+
}
|
|
74
|
+
}
|
package/lib/model.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { ISignal } from '@lumino/signaling';
|
|
|
5
5
|
import { IActiveCellManager } from './active-cell-manager';
|
|
6
6
|
import { IInputModel } from './input-model';
|
|
7
7
|
import { ISelectionWatcher } from './selection-watcher';
|
|
8
|
-
import { IChatHistory,
|
|
8
|
+
import { IChatHistory, IConfig, IMessage, IMessageContent, INewMessage, IUser } from './types';
|
|
9
9
|
/**
|
|
10
10
|
* The chat model interface.
|
|
11
11
|
*/
|
|
@@ -37,7 +37,7 @@ export interface IChatModel extends IDisposable {
|
|
|
37
37
|
/**
|
|
38
38
|
* The chat messages list.
|
|
39
39
|
*/
|
|
40
|
-
readonly messages:
|
|
40
|
+
readonly messages: IMessage[];
|
|
41
41
|
/**
|
|
42
42
|
* The input model.
|
|
43
43
|
*/
|
|
@@ -100,7 +100,7 @@ export interface IChatModel extends IDisposable {
|
|
|
100
100
|
* @param id - the unique ID of the message.
|
|
101
101
|
* @param message - the updated message.
|
|
102
102
|
*/
|
|
103
|
-
updateMessage?(id: string, message:
|
|
103
|
+
updateMessage?(id: string, message: IMessageContent): Promise<boolean | void> | boolean | void;
|
|
104
104
|
/**
|
|
105
105
|
* Optional, to delete a message from the chat.
|
|
106
106
|
*
|
|
@@ -124,14 +124,14 @@ export interface IChatModel extends IDisposable {
|
|
|
124
124
|
*
|
|
125
125
|
* @param message - the message with user information and body.
|
|
126
126
|
*/
|
|
127
|
-
messageAdded(message:
|
|
127
|
+
messageAdded(message: IMessageContent): void;
|
|
128
128
|
/**
|
|
129
129
|
* Function called when messages are inserted.
|
|
130
130
|
*
|
|
131
131
|
* @param index - the index of the first message of the list.
|
|
132
132
|
* @param messages - the messages list.
|
|
133
133
|
*/
|
|
134
|
-
messagesInserted(index: number, messages:
|
|
134
|
+
messagesInserted(index: number, messages: IMessageContent[]): void;
|
|
135
135
|
/**
|
|
136
136
|
* Function called when messages are deleted.
|
|
137
137
|
*
|
|
@@ -185,7 +185,7 @@ export declare abstract class AbstractChatModel implements IChatModel {
|
|
|
185
185
|
/**
|
|
186
186
|
* The chat messages list.
|
|
187
187
|
*/
|
|
188
|
-
get messages():
|
|
188
|
+
get messages(): IMessage[];
|
|
189
189
|
/**
|
|
190
190
|
* The input model.
|
|
191
191
|
*/
|
|
@@ -235,7 +235,7 @@ export declare abstract class AbstractChatModel implements IChatModel {
|
|
|
235
235
|
get messagesInViewport(): number[];
|
|
236
236
|
set messagesInViewport(values: number[]);
|
|
237
237
|
/**
|
|
238
|
-
* A signal emitting when the
|
|
238
|
+
* A signal emitting when the message list is updated.
|
|
239
239
|
*/
|
|
240
240
|
get messagesUpdated(): ISignal<IChatModel, void>;
|
|
241
241
|
/**
|
|
@@ -282,20 +282,20 @@ export declare abstract class AbstractChatModel implements IChatModel {
|
|
|
282
282
|
* A function called before transferring the message to the panel(s).
|
|
283
283
|
* Can be useful if some actions are required on the message.
|
|
284
284
|
*/
|
|
285
|
-
protected formatChatMessage(message:
|
|
285
|
+
protected formatChatMessage(message: IMessageContent): IMessageContent;
|
|
286
286
|
/**
|
|
287
287
|
* Function to call when a message is received.
|
|
288
288
|
*
|
|
289
289
|
* @param message - the message with user information and body.
|
|
290
290
|
*/
|
|
291
|
-
messageAdded(message:
|
|
291
|
+
messageAdded(message: IMessageContent): void;
|
|
292
292
|
/**
|
|
293
293
|
* Function called when messages are inserted.
|
|
294
294
|
*
|
|
295
295
|
* @param index - the index of the first message of the list.
|
|
296
296
|
* @param messages - the messages list.
|
|
297
297
|
*/
|
|
298
|
-
messagesInserted(index: number, messages:
|
|
298
|
+
messagesInserted(index: number, messages: IMessageContent[]): void;
|
|
299
299
|
/**
|
|
300
300
|
* Function called when messages are deleted.
|
|
301
301
|
*
|
|
@@ -435,9 +435,9 @@ export interface IChatContext {
|
|
|
435
435
|
*/
|
|
436
436
|
readonly name: string;
|
|
437
437
|
/**
|
|
438
|
-
* A copy of the messages.
|
|
438
|
+
* A copy of the messages content.
|
|
439
439
|
*/
|
|
440
|
-
readonly messages:
|
|
440
|
+
readonly messages: IMessageContent[];
|
|
441
441
|
/**
|
|
442
442
|
* A list of all users who have connected to this chat.
|
|
443
443
|
*/
|
|
@@ -456,7 +456,7 @@ export declare abstract class AbstractChatContext implements IChatContext {
|
|
|
456
456
|
model: IChatModel;
|
|
457
457
|
});
|
|
458
458
|
get name(): string;
|
|
459
|
-
get messages():
|
|
459
|
+
get messages(): IMessageContent[];
|
|
460
460
|
get user(): IUser | undefined;
|
|
461
461
|
/**
|
|
462
462
|
* ABSTRACT: Should return a list of users who have connected to this chat.
|
package/lib/model.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { ArrayExt } from '@lumino/algorithm';
|
|
6
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
6
7
|
import { Signal } from '@lumino/signaling';
|
|
7
8
|
import { InputModel } from './input-model';
|
|
9
|
+
import { Message } from './message';
|
|
8
10
|
import { replaceMentionToSpan } from './utils';
|
|
9
|
-
import { PromiseDelegate } from '@lumino/coreutils';
|
|
10
11
|
/**
|
|
11
12
|
* An abstract implementation of IChatModel.
|
|
12
13
|
*
|
|
@@ -170,12 +171,12 @@ export class AbstractChatModel {
|
|
|
170
171
|
if (this._config.stackMessages) {
|
|
171
172
|
this._messages.slice(1).forEach((message, idx) => {
|
|
172
173
|
const previousUser = this._messages[idx].sender.username;
|
|
173
|
-
message.stacked
|
|
174
|
+
message.update({ stacked: previousUser === message.sender.username });
|
|
174
175
|
});
|
|
175
176
|
}
|
|
176
177
|
else {
|
|
177
178
|
this._messages.forEach(message => {
|
|
178
|
-
|
|
179
|
+
message.update({ stacked: undefined });
|
|
179
180
|
});
|
|
180
181
|
}
|
|
181
182
|
this._messagesUpdated.emit();
|
|
@@ -225,7 +226,7 @@ export class AbstractChatModel {
|
|
|
225
226
|
this._viewportChanged.emit(values);
|
|
226
227
|
}
|
|
227
228
|
/**
|
|
228
|
-
* A signal emitting when the
|
|
229
|
+
* A signal emitting when the message list is updated.
|
|
229
230
|
*/
|
|
230
231
|
get messagesUpdated() {
|
|
231
232
|
return this._messagesUpdated;
|
|
@@ -328,7 +329,8 @@ export class AbstractChatModel {
|
|
|
328
329
|
const lastRead = (_a = this.lastRead) !== null && _a !== void 0 ? _a : 0;
|
|
329
330
|
// Format the messages.
|
|
330
331
|
messages.forEach((message, idx) => {
|
|
331
|
-
|
|
332
|
+
const formattedMessage = this.formatChatMessage(message);
|
|
333
|
+
formattedMessages.push(new Message(formattedMessage));
|
|
332
334
|
if (message.time > lastRead) {
|
|
333
335
|
unreadIndexes.push(index + idx);
|
|
334
336
|
}
|
|
@@ -344,7 +346,7 @@ export class AbstractChatModel {
|
|
|
344
346
|
for (let idx = start; idx <= end; idx++) {
|
|
345
347
|
const message = this._messages[idx];
|
|
346
348
|
const previousUser = this._messages[idx - 1].sender.username;
|
|
347
|
-
message.stacked
|
|
349
|
+
message.update({ stacked: previousUser === message.sender.username });
|
|
348
350
|
}
|
|
349
351
|
}
|
|
350
352
|
this._addUnreadMessages(unreadIndexes);
|
|
@@ -461,7 +463,7 @@ export class AbstractChatContext {
|
|
|
461
463
|
return this._model.name;
|
|
462
464
|
}
|
|
463
465
|
get messages() {
|
|
464
|
-
return
|
|
466
|
+
return this._model.messages.map(message => ({ ...message.content }));
|
|
465
467
|
}
|
|
466
468
|
get user() {
|
|
467
469
|
var _a;
|