@jupyter/chat 0.19.0-alpha.2 → 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/buttons/attach-button.js +1 -1
- package/lib/components/input/buttons/cancel-button.js +1 -1
- package/lib/components/input/buttons/save-edit-button.js +1 -1
- package/lib/components/input/buttons/send-button.js +3 -1
- package/lib/components/input/buttons/stop-button.js +1 -1
- package/lib/components/input/chat-input.js +3 -30
- 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/mui-extras/tooltipped-button.d.ts +5 -26
- package/lib/components/mui-extras/tooltipped-button.js +4 -33
- package/lib/components/mui-extras/tooltipped-icon-button.d.ts +12 -22
- package/lib/components/mui-extras/tooltipped-icon-button.js +10 -8
- 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/theme-provider.d.ts +19 -0
- package/lib/theme-provider.js +74 -3
- package/lib/types.d.ts +21 -3
- package/lib/widgets/chat-widget.d.ts +4 -0
- package/lib/widgets/chat-widget.js +52 -8
- package/lib/widgets/multichat-panel.js +1 -1
- package/package.json +3 -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/buttons/attach-button.tsx +1 -1
- package/src/components/input/buttons/cancel-button.tsx +1 -1
- package/src/components/input/buttons/save-edit-button.tsx +1 -1
- package/src/components/input/buttons/send-button.tsx +4 -1
- package/src/components/input/buttons/stop-button.tsx +1 -1
- package/src/components/input/chat-input.tsx +1 -34
- 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/mui-extras/tooltipped-button.tsx +9 -43
- package/src/components/mui-extras/tooltipped-icon-button.tsx +17 -38
- 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/theme-provider.ts +95 -3
- package/src/types.ts +23 -3
- package/src/widgets/chat-widget.tsx +61 -10
- package/src/widgets/multichat-panel.tsx +5 -1
- package/style/chat.css +10 -0
- package/style/input.css +4 -0
- 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
|
@@ -38,7 +38,7 @@ export function AttachButton(props) {
|
|
|
38
38
|
console.warn('Error selecting files to attach', e);
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
-
return (React.createElement(TooltippedIconButton, { onClick: onclick, tooltip: tooltip,
|
|
41
|
+
return (React.createElement(TooltippedIconButton, { onClick: onclick, tooltip: tooltip, buttonProps: {
|
|
42
42
|
title: tooltip,
|
|
43
43
|
className: ATTACH_BUTTON_CLASS
|
|
44
44
|
} },
|
|
@@ -14,7 +14,7 @@ export function CancelButton(props) {
|
|
|
14
14
|
return React.createElement(React.Fragment, null);
|
|
15
15
|
}
|
|
16
16
|
const tooltip = 'Cancel editing';
|
|
17
|
-
return (React.createElement(TooltippedIconButton, { onClick: props.model.cancel, tooltip: tooltip,
|
|
17
|
+
return (React.createElement(TooltippedIconButton, { onClick: props.model.cancel, tooltip: tooltip, buttonProps: {
|
|
18
18
|
title: tooltip,
|
|
19
19
|
className: CANCEL_BUTTON_CLASS
|
|
20
20
|
} },
|
|
@@ -36,7 +36,7 @@ export function SaveEditButton(props) {
|
|
|
36
36
|
await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
|
|
37
37
|
model.send(model.value);
|
|
38
38
|
}
|
|
39
|
-
return (React.createElement(TooltippedIconButton, { onClick: save, tooltip: tooltip, disabled: disabled,
|
|
39
|
+
return (React.createElement(TooltippedIconButton, { onClick: save, tooltip: tooltip, disabled: disabled, buttonProps: {
|
|
40
40
|
title: tooltip,
|
|
41
41
|
className: SAVE_EDIT_BUTTON_CLASS
|
|
42
42
|
}, "aria-label": tooltip },
|
|
@@ -33,6 +33,8 @@ export function SendButton(props) {
|
|
|
33
33
|
: 'Send message (ENTER)');
|
|
34
34
|
};
|
|
35
35
|
model.configChanged.connect(configChanged);
|
|
36
|
+
// Initialize the tooltip.
|
|
37
|
+
configChanged(model, model.config);
|
|
36
38
|
return () => {
|
|
37
39
|
var _a, _b;
|
|
38
40
|
model.valueChanged.disconnect(inputChanged);
|
|
@@ -51,7 +53,7 @@ export function SendButton(props) {
|
|
|
51
53
|
model.value = '';
|
|
52
54
|
model.focus();
|
|
53
55
|
}
|
|
54
|
-
return (React.createElement(TooltippedIconButton, { onClick: send, tooltip: tooltip, disabled: disabled,
|
|
56
|
+
return (React.createElement(TooltippedIconButton, { onClick: send, tooltip: tooltip, disabled: disabled, buttonProps: {
|
|
55
57
|
title: tooltip,
|
|
56
58
|
className: SEND_BUTTON_CLASS
|
|
57
59
|
}, "aria-label": tooltip },
|
|
@@ -38,7 +38,7 @@ export function StopButton(props) {
|
|
|
38
38
|
// This will need to be implemented based on how the chat model handles stopping AI responses
|
|
39
39
|
console.log('Stop button clicked');
|
|
40
40
|
}
|
|
41
|
-
return (React.createElement(TooltippedIconButton, { onClick: stop, tooltip: tooltip, disabled: disabled,
|
|
41
|
+
return (React.createElement(TooltippedIconButton, { onClick: stop, tooltip: tooltip, disabled: disabled, buttonProps: {
|
|
42
42
|
title: tooltip,
|
|
43
43
|
className: STOP_BUTTON_CLASS
|
|
44
44
|
}, "aria-label": tooltip },
|
|
@@ -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,8 +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 [isFocused, setIsFocused] = useState(false);
|
|
27
|
-
const [writers, setWriters] = useState([]);
|
|
28
25
|
/**
|
|
29
26
|
* Auto-focus the input when the component is first mounted.
|
|
30
27
|
*/
|
|
@@ -80,27 +77,6 @@ export function ChatInput(props) {
|
|
|
80
77
|
inputToolbarRegistry === null || inputToolbarRegistry === void 0 ? void 0 : inputToolbarRegistry.itemsChanged.disconnect(updateToolbar);
|
|
81
78
|
};
|
|
82
79
|
}, [inputToolbarRegistry]);
|
|
83
|
-
/**
|
|
84
|
-
* Handle the changes in the writers list.
|
|
85
|
-
*/
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
var _a;
|
|
88
|
-
if (!chatModel) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const updateWriters = (_, writers) => {
|
|
92
|
-
// Show all writers for now - AI generating responses will have messageID
|
|
93
|
-
setWriters(writers);
|
|
94
|
-
};
|
|
95
|
-
// Set initial writers state
|
|
96
|
-
const initialWriters = chatModel.writers;
|
|
97
|
-
setWriters(initialWriters);
|
|
98
|
-
(_a = chatModel.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(updateWriters);
|
|
99
|
-
return () => {
|
|
100
|
-
var _a;
|
|
101
|
-
(_a = chatModel === null || chatModel === void 0 ? void 0 : chatModel.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(updateWriters);
|
|
102
|
-
};
|
|
103
|
-
}, [chatModel]);
|
|
104
80
|
const inputExists = !!input.trim();
|
|
105
81
|
/**
|
|
106
82
|
* `handleKeyDown()`: callback invoked when the user presses any key in the
|
|
@@ -163,9 +139,7 @@ export function ChatInput(props) {
|
|
|
163
139
|
return (React.createElement(Box, { sx: props.sx, className: clsx(INPUT_BOX_CLASS), "data-input-id": model.id },
|
|
164
140
|
React.createElement(Box, { sx: {
|
|
165
141
|
border: '1px solid',
|
|
166
|
-
borderColor:
|
|
167
|
-
? 'var(--jp-brand-color1)'
|
|
168
|
-
: 'var(--jp-border-color1)',
|
|
142
|
+
borderColor: 'var(--jp-border-color1)',
|
|
169
143
|
borderRadius: 2,
|
|
170
144
|
transition: 'border-color 0.2s ease',
|
|
171
145
|
display: 'flex',
|
|
@@ -188,7 +162,7 @@ export function ChatInput(props) {
|
|
|
188
162
|
padding: 0
|
|
189
163
|
}
|
|
190
164
|
}
|
|
191
|
-
}, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "standard", className: INPUT_TEXTFIELD_CLASS, multiline: true, maxRows: 10, onKeyDown: handleKeyDown, placeholder: "Type a chat message, @ to mention...", inputRef: inputRef,
|
|
165
|
+
}, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "standard", className: INPUT_TEXTFIELD_CLASS, multiline: true, maxRows: 10, onKeyDown: handleKeyDown, placeholder: "Type a chat message, @ to mention...", inputRef: inputRef, onSelect: () => { var _a, _b; return (model.cursorIndex = (_b = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.selectionStart) !== null && _b !== void 0 ? _b : null); }, sx: {
|
|
192
166
|
padding: 1.5,
|
|
193
167
|
margin: 0,
|
|
194
168
|
boxSizing: 'border-box',
|
|
@@ -235,6 +209,5 @@ export function ChatInput(props) {
|
|
|
235
209
|
borderColor: 'var(--jp-border-color1)',
|
|
236
210
|
backgroundColor: 'var(--jp-layout-color0)',
|
|
237
211
|
transition: 'background-color 0.2s ease'
|
|
238
|
-
} }, toolbarElements.map((item, index) => (React.createElement(item.element, { key: index, model: model, chatCommandRegistry: chatCommandRegistry, chatModel: chatModel, edit: props.edit })))))
|
|
239
|
-
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 })))))));
|
|
240
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)));
|
|
@@ -1,35 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ButtonProps, SxProps, TooltipProps } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
export declare const TOOLTIPPED_WRAP_CLASS = "jp-chat-tooltipped-wrap";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
width: string;
|
|
8
|
-
height: string;
|
|
9
|
-
lineHeight: number;
|
|
10
|
-
'&:disabled': {
|
|
11
|
-
opacity: number;
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
export declare const INPUT_TOOLBAR_BUTTON_SX: {
|
|
15
|
-
backgroundColor: string;
|
|
16
|
-
color: string;
|
|
17
|
-
borderRadius: string;
|
|
18
|
-
boxShadow: string;
|
|
19
|
-
'&:hover': {
|
|
20
|
-
backgroundColor: string;
|
|
21
|
-
boxShadow: string;
|
|
22
|
-
};
|
|
23
|
-
'&:disabled': {
|
|
24
|
-
backgroundColor: string;
|
|
25
|
-
color: string;
|
|
26
|
-
opacity: number;
|
|
27
|
-
};
|
|
28
|
-
};
|
|
4
|
+
/**
|
|
5
|
+
* The props for the tooltipped button.
|
|
6
|
+
*/
|
|
29
7
|
export type TooltippedButtonProps = {
|
|
30
8
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
31
9
|
tooltip: string;
|
|
32
10
|
children: JSX.Element;
|
|
11
|
+
className?: string;
|
|
33
12
|
inputToolbar?: boolean;
|
|
34
13
|
disabled?: boolean;
|
|
35
14
|
placement?: TooltipProps['placement'];
|
|
@@ -2,38 +2,11 @@
|
|
|
2
2
|
* Copyright (c) Jupyter Development Team.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
import { classes } from '@jupyterlab/ui-components';
|
|
5
6
|
import { Button } from '@mui/material';
|
|
6
7
|
import React from 'react';
|
|
7
8
|
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
8
9
|
export const TOOLTIPPED_WRAP_CLASS = 'jp-chat-tooltipped-wrap';
|
|
9
|
-
export const DEFAULT_BUTTON_PROPS = {
|
|
10
|
-
size: 'small',
|
|
11
|
-
variant: 'contained'
|
|
12
|
-
};
|
|
13
|
-
export const DEFAULT_BUTTON_SX = {
|
|
14
|
-
minWidth: '24px',
|
|
15
|
-
width: '24px',
|
|
16
|
-
height: '24px',
|
|
17
|
-
lineHeight: 0,
|
|
18
|
-
'&:disabled': {
|
|
19
|
-
opacity: 0.5
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
export const INPUT_TOOLBAR_BUTTON_SX = {
|
|
23
|
-
backgroundColor: 'var(--jp-brand-color1)',
|
|
24
|
-
color: 'white',
|
|
25
|
-
borderRadius: '4px',
|
|
26
|
-
boxShadow: 'none',
|
|
27
|
-
'&:hover': {
|
|
28
|
-
backgroundColor: 'var(--jp-brand-color0)',
|
|
29
|
-
boxShadow: 'none'
|
|
30
|
-
},
|
|
31
|
-
'&:disabled': {
|
|
32
|
-
backgroundColor: 'var(--jp-border-color2)',
|
|
33
|
-
color: 'var(--jp-ui-font-color3)',
|
|
34
|
-
opacity: 0.5
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
10
|
/**
|
|
38
11
|
* A component that renders an MUI `Button` with a high-contrast tooltip
|
|
39
12
|
* provided by `ContrastingTooltip`. This component differs from the MUI
|
|
@@ -63,10 +36,8 @@ export function TooltippedButton(props) {
|
|
|
63
36
|
]
|
|
64
37
|
}
|
|
65
38
|
} },
|
|
66
|
-
React.createElement("span", {
|
|
67
|
-
React.createElement(Button, { ...
|
|
68
|
-
...DEFAULT_BUTTON_SX,
|
|
69
|
-
...(((_b = props.inputToolbar) !== null && _b !== void 0 ? _b : true) && INPUT_TOOLBAR_BUTTON_SX),
|
|
39
|
+
React.createElement("span", { className: classes(props.className, TOOLTIPPED_WRAP_CLASS) },
|
|
40
|
+
React.createElement(Button, { ...(((_b = props.inputToolbar) !== null && _b !== void 0 ? _b : true) && { variant: 'input-toolbar' }), ...props.buttonProps, onClick: props.onClick, disabled: props.disabled, "aria-label": (_c = props['aria-label']) !== null && _c !== void 0 ? _c : props.tooltip, sx: {
|
|
70
41
|
...props.sx
|
|
71
|
-
}
|
|
42
|
+
} }, props.children))));
|
|
72
43
|
}
|
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { IconButtonProps
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
inputToolbar?: boolean;
|
|
9
|
-
disabled?: boolean;
|
|
10
|
-
placement?: TooltipProps['placement'];
|
|
11
|
-
/**
|
|
12
|
-
* The font size of the icon. By default it will be set to 'small'.
|
|
13
|
-
*/
|
|
14
|
-
fontSize?: SvgIconOwnProps['fontSize'];
|
|
15
|
-
/**
|
|
16
|
-
* The offset of the tooltip popup.
|
|
17
|
-
*
|
|
18
|
-
* The expected syntax is defined by the Popper library:
|
|
19
|
-
* https://popper.js.org/docs/v2/modifiers/offset/
|
|
20
|
-
*/
|
|
21
|
-
offset?: [number, number];
|
|
22
|
-
'aria-label'?: string;
|
|
2
|
+
import { IconButtonProps } from '@mui/material';
|
|
3
|
+
import { TooltippedButtonProps } from './tooltipped-button';
|
|
4
|
+
/**
|
|
5
|
+
* The props for the tooltipped icon button.
|
|
6
|
+
*/
|
|
7
|
+
export type TooltippedIconButtonProps = TooltippedButtonProps & {
|
|
23
8
|
/**
|
|
24
9
|
* Props passed directly to the MUI `IconButton` component.
|
|
25
10
|
*/
|
|
26
|
-
|
|
11
|
+
buttonProps?: IconButtonProps;
|
|
27
12
|
};
|
|
28
13
|
/**
|
|
29
14
|
* A component that renders an MUI `IconButton` with a high-contrast tooltip
|
|
@@ -36,5 +21,10 @@ export type TooltippedIconButtonProps = {
|
|
|
36
21
|
* - Lowers the opacity of the IconButton when disabled.
|
|
37
22
|
* - Renders the IconButton with `line-height: 0` to avoid showing extra
|
|
38
23
|
* vertical space in SVG icons.
|
|
24
|
+
*
|
|
25
|
+
* NOTES:
|
|
26
|
+
* This kind of button doesn't allow regular variants ('outlined', 'contained', 'text').
|
|
27
|
+
* The only one allowed is 'input-toolbar'. If you want to use one of the regular, use
|
|
28
|
+
* the TooltippedButton instead.
|
|
39
29
|
*/
|
|
40
30
|
export declare function TooltippedIconButton(props: TooltippedIconButtonProps): JSX.Element;
|
|
@@ -6,7 +6,7 @@ import { classes } from '@jupyterlab/ui-components';
|
|
|
6
6
|
import { IconButton } from '@mui/material';
|
|
7
7
|
import React from 'react';
|
|
8
8
|
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
9
|
-
import {
|
|
9
|
+
import { TOOLTIPPED_WRAP_CLASS } from './tooltipped-button';
|
|
10
10
|
/**
|
|
11
11
|
* A component that renders an MUI `IconButton` with a high-contrast tooltip
|
|
12
12
|
* provided by `ContrastingTooltip`. This component differs from the MUI
|
|
@@ -18,12 +18,15 @@ import { DEFAULT_BUTTON_PROPS, DEFAULT_BUTTON_SX, INPUT_TOOLBAR_BUTTON_SX, TOOLT
|
|
|
18
18
|
* - Lowers the opacity of the IconButton when disabled.
|
|
19
19
|
* - Renders the IconButton with `line-height: 0` to avoid showing extra
|
|
20
20
|
* vertical space in SVG icons.
|
|
21
|
+
*
|
|
22
|
+
* NOTES:
|
|
23
|
+
* This kind of button doesn't allow regular variants ('outlined', 'contained', 'text').
|
|
24
|
+
* The only one allowed is 'input-toolbar'. If you want to use one of the regular, use
|
|
25
|
+
* the TooltippedButton instead.
|
|
21
26
|
*/
|
|
22
27
|
export function TooltippedIconButton(props) {
|
|
23
28
|
var _a, _b, _c;
|
|
24
|
-
|
|
25
|
-
props.children.props.fontSize = (_a = props.fontSize) !== null && _a !== void 0 ? _a : 'small';
|
|
26
|
-
return (React.createElement(ContrastingTooltip, { title: props.tooltip, placement: (_b = props.placement) !== null && _b !== void 0 ? _b : 'top', slotProps: {
|
|
29
|
+
return (React.createElement(ContrastingTooltip, { title: props.tooltip, placement: (_a = props.placement) !== null && _a !== void 0 ? _a : 'top', slotProps: {
|
|
27
30
|
popper: {
|
|
28
31
|
modifiers: [
|
|
29
32
|
{
|
|
@@ -36,8 +39,7 @@ export function TooltippedIconButton(props) {
|
|
|
36
39
|
}
|
|
37
40
|
} },
|
|
38
41
|
React.createElement("span", { className: classes(props.className, TOOLTIPPED_WRAP_CLASS) },
|
|
39
|
-
React.createElement(IconButton, { ...
|
|
40
|
-
...
|
|
41
|
-
|
|
42
|
-
}, "aria-label": props['aria-label'] }, props.children))));
|
|
42
|
+
React.createElement(IconButton, { ...(((_b = props.inputToolbar) !== null && _b !== void 0 ? _b : true) && { variant: 'input-toolbar' }), ...props.buttonProps, onClick: props.onClick, disabled: props.disabled, "aria-label": (_c = props['aria-label']) !== null && _c !== void 0 ? _c : props.tooltip, sx: {
|
|
43
|
+
...props.sx
|
|
44
|
+
} }, props.children))));
|
|
43
45
|
}
|
|
@@ -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: {
|