@jupyter/chat 0.8.1 → 0.9.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/components/chat-input.d.ts +3 -11
- package/lib/components/chat-input.js +26 -40
- package/lib/components/chat-messages.d.ts +17 -4
- package/lib/components/chat-messages.js +9 -9
- package/lib/components/chat.d.ts +5 -5
- package/lib/components/chat.js +9 -8
- package/lib/components/code-blocks/copy-button.js +6 -3
- package/lib/components/input/buttons/attach-button.d.ts +6 -0
- package/lib/components/input/{attach-button.js → buttons/attach-button.js} +11 -8
- package/lib/components/input/buttons/cancel-button.d.ts +6 -0
- package/lib/components/input/{cancel-button.js → buttons/cancel-button.js} +5 -7
- package/lib/components/input/buttons/index.d.ts +3 -0
- package/lib/components/input/buttons/index.js +7 -0
- package/lib/components/input/buttons/send-button.d.ts +6 -0
- package/lib/components/input/{send-button.js → buttons/send-button.js} +52 -42
- package/lib/components/input/index.d.ts +3 -3
- package/lib/components/input/index.js +3 -3
- package/lib/components/input/toolbar-registry.d.ts +98 -0
- package/lib/components/input/toolbar-registry.js +85 -0
- package/lib/components/input/use-chat-commands.js +3 -2
- package/lib/components/mui-extras/tooltipped-button.d.ts +1 -1
- package/lib/components/mui-extras/tooltipped-button.js +3 -2
- package/lib/components/mui-extras/tooltipped-icon-button.js +4 -2
- package/lib/input-model.d.ts +41 -0
- package/lib/input-model.js +17 -1
- package/lib/model.d.ts +22 -0
- package/lib/model.js +18 -2
- package/lib/types.d.ts +0 -18
- package/lib/widgets/chat-widget.d.ts +5 -1
- package/lib/widgets/chat-widget.js +7 -1
- package/package.json +1 -1
- package/src/components/chat-input.tsx +40 -65
- package/src/components/chat-messages.tsx +31 -14
- package/src/components/chat.tsx +12 -21
- package/src/components/code-blocks/copy-button.tsx +9 -3
- package/src/components/input/{attach-button.tsx → buttons/attach-button.tsx} +15 -20
- package/src/components/input/{cancel-button.tsx → buttons/cancel-button.tsx} +9 -16
- package/src/components/input/buttons/index.ts +8 -0
- package/src/components/input/{send-button.tsx → buttons/send-button.tsx} +62 -61
- package/src/components/input/index.ts +3 -3
- package/src/components/input/toolbar-registry.tsx +162 -0
- package/src/components/input/use-chat-commands.tsx +8 -2
- package/src/components/mui-extras/tooltipped-button.tsx +4 -2
- package/src/components/mui-extras/tooltipped-icon-button.tsx +5 -2
- package/src/input-model.ts +58 -1
- package/src/model.ts +36 -2
- package/src/types.ts +0 -21
- package/src/widgets/chat-widget.tsx +8 -1
- package/style/base.css +1 -0
- package/style/input.css +32 -0
- package/lib/components/input/attach-button.d.ts +0 -14
- package/lib/components/input/cancel-button.d.ts +0 -11
- package/lib/components/input/send-button.d.ts +0 -18
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
3
2
|
import { SxProps, Theme } from '@mui/material';
|
|
3
|
+
import { IInputToolbarRegistry } from './input';
|
|
4
4
|
import { IInputModel } from '../input-model';
|
|
5
5
|
import { IChatCommandRegistry } from '../chat-commands';
|
|
6
6
|
export declare function ChatInput(props: ChatInput.IProps): JSX.Element;
|
|
@@ -17,25 +17,17 @@ export declare namespace ChatInput {
|
|
|
17
17
|
*/
|
|
18
18
|
model: IInputModel;
|
|
19
19
|
/**
|
|
20
|
-
* The
|
|
20
|
+
* The toolbar registry.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
toolbarRegistry: IInputToolbarRegistry;
|
|
23
23
|
/**
|
|
24
24
|
* The function to be called to cancel editing.
|
|
25
25
|
*/
|
|
26
26
|
onCancel?: () => unknown;
|
|
27
|
-
/**
|
|
28
|
-
* Whether to allow or not including selection.
|
|
29
|
-
*/
|
|
30
|
-
hideIncludeSelection?: boolean;
|
|
31
27
|
/**
|
|
32
28
|
* Custom mui/material styles.
|
|
33
29
|
*/
|
|
34
30
|
sx?: SxProps<Theme>;
|
|
35
|
-
/**
|
|
36
|
-
* The document manager.
|
|
37
|
-
*/
|
|
38
|
-
documentManager?: IDocumentManager;
|
|
39
31
|
/**
|
|
40
32
|
* Chat command registry.
|
|
41
33
|
*/
|
|
@@ -6,23 +6,24 @@ import { Autocomplete, Box, InputAdornment, TextField } from '@mui/material';
|
|
|
6
6
|
import clsx from 'clsx';
|
|
7
7
|
import React, { useEffect, useRef, useState } from 'react';
|
|
8
8
|
import { AttachmentPreviewList } from './attachments';
|
|
9
|
-
import {
|
|
10
|
-
import { useChatCommands } from './input/use-chat-commands';
|
|
9
|
+
import { useChatCommands } from './input';
|
|
11
10
|
const INPUT_BOX_CLASS = 'jp-chat-input-container';
|
|
11
|
+
const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
|
|
12
12
|
export function ChatInput(props) {
|
|
13
|
-
var _a
|
|
14
|
-
const {
|
|
13
|
+
var _a;
|
|
14
|
+
const { model, toolbarRegistry } = props;
|
|
15
15
|
const [input, setInput] = useState(model.value);
|
|
16
16
|
const inputRef = useRef();
|
|
17
17
|
const chatCommands = useChatCommands(model, props.chatCommandRegistry);
|
|
18
18
|
const [sendWithShiftEnter, setSendWithShiftEnter] = useState((_a = model.config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false);
|
|
19
19
|
const [attachments, setAttachments] = useState(model.attachments);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
const [toolbarElements, setToolbarElements] = useState([]);
|
|
21
|
+
/**
|
|
22
|
+
* Handle the changes on the model that affect the input.
|
|
23
|
+
* - focus requested
|
|
24
|
+
* - config changed
|
|
25
|
+
* - attachments changed
|
|
26
|
+
*/
|
|
26
27
|
useEffect(() => {
|
|
27
28
|
var _a, _b;
|
|
28
29
|
const inputChanged = (_, value) => {
|
|
@@ -51,6 +52,19 @@ export function ChatInput(props) {
|
|
|
51
52
|
(_c = model.attachmentsChanged) === null || _c === void 0 ? void 0 : _c.disconnect(attachmentChanged);
|
|
52
53
|
};
|
|
53
54
|
}, [model]);
|
|
55
|
+
/**
|
|
56
|
+
* Handle the changes in the toolbar items.
|
|
57
|
+
*/
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const updateToolbar = () => {
|
|
60
|
+
setToolbarElements(toolbarRegistry.getItems());
|
|
61
|
+
};
|
|
62
|
+
toolbarRegistry.itemsChanged.connect(updateToolbar);
|
|
63
|
+
updateToolbar();
|
|
64
|
+
return () => {
|
|
65
|
+
toolbarRegistry.itemsChanged.disconnect(updateToolbar);
|
|
66
|
+
};
|
|
67
|
+
}, [toolbarRegistry]);
|
|
54
68
|
const inputExists = !!input.trim();
|
|
55
69
|
/**
|
|
56
70
|
* `handleKeyDown()`: callback invoked when the user presses any key in the
|
|
@@ -101,36 +115,11 @@ export function ChatInput(props) {
|
|
|
101
115
|
// Finally, send the message when all other conditions are met.
|
|
102
116
|
if ((sendWithShiftEnter && event.shiftKey) ||
|
|
103
117
|
(!sendWithShiftEnter && !event.shiftKey)) {
|
|
104
|
-
|
|
118
|
+
model.send(input);
|
|
105
119
|
event.stopPropagation();
|
|
106
120
|
event.preventDefault();
|
|
107
121
|
}
|
|
108
122
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Triggered when sending the message.
|
|
111
|
-
*
|
|
112
|
-
* Add code block if cell or text is selected.
|
|
113
|
-
*/
|
|
114
|
-
function onSend(selection) {
|
|
115
|
-
let content = input;
|
|
116
|
-
if (selection) {
|
|
117
|
-
content += `
|
|
118
|
-
|
|
119
|
-
\`\`\`
|
|
120
|
-
${selection.source}
|
|
121
|
-
\`\`\`
|
|
122
|
-
`;
|
|
123
|
-
}
|
|
124
|
-
props.onSend(content);
|
|
125
|
-
model.value = '';
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Triggered when cancelling edition.
|
|
129
|
-
*/
|
|
130
|
-
function onCancel() {
|
|
131
|
-
var _a;
|
|
132
|
-
(_a = props.onCancel) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
133
|
-
}
|
|
134
123
|
// Set the helper text based on whether Shift+Enter is used for sending.
|
|
135
124
|
const helperText = sendWithShiftEnter ? (React.createElement("span", null,
|
|
136
125
|
"Press ",
|
|
@@ -164,10 +153,7 @@ ${selection.source}
|
|
|
164
153
|
}
|
|
165
154
|
}, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "outlined", multiline: true, onKeyDown: handleKeyDown, placeholder: "Start chatting", inputRef: inputRef, sx: { marginTop: '1px' }, 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); }, InputProps: {
|
|
166
155
|
...params.InputProps,
|
|
167
|
-
endAdornment: (React.createElement(InputAdornment, { position: "end" },
|
|
168
|
-
documentManager && model.addAttachment && (React.createElement(AttachButton, { documentManager: documentManager, onAttach: model.addAttachment })),
|
|
169
|
-
props.onCancel && React.createElement(CancelButton, { onCancel: onCancel }),
|
|
170
|
-
React.createElement(SendButton, { model: model, sendWithShiftEnter: sendWithShiftEnter, inputExists: inputExists || attachments.length > 0, onSend: onSend, hideIncludeSelection: hideIncludeSelection, hasButtonOnLeft: !!props.onCancel })))
|
|
156
|
+
endAdornment: (React.createElement(InputAdornment, { position: "end", className: INPUT_TOOLBAR_CLASS }, toolbarElements.map(item => (React.createElement(item.element, { model: model })))))
|
|
171
157
|
}, FormHelperTextProps: {
|
|
172
158
|
sx: { marginLeft: 'auto', marginRight: 0 }
|
|
173
159
|
}, helperText: input.length > 2 ? helperText : ' ' })), inputValue: input, onInputChange: (_, newValue, reason) => {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
2
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
3
2
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
4
|
-
import type { SxProps, Theme } from '@mui/material';
|
|
5
3
|
import React from 'react';
|
|
4
|
+
import { IInputToolbarRegistry } from './input';
|
|
6
5
|
import { IChatCommandRegistry } from '../chat-commands';
|
|
7
6
|
import { IChatModel } from '../model';
|
|
8
7
|
import { IChatMessage, IUser } from '../types';
|
|
@@ -10,10 +9,22 @@ import { IChatMessage, IUser } from '../types';
|
|
|
10
9
|
* The base components props.
|
|
11
10
|
*/
|
|
12
11
|
type BaseMessageProps = {
|
|
12
|
+
/**
|
|
13
|
+
* The mime renderer registry.
|
|
14
|
+
*/
|
|
13
15
|
rmRegistry: IRenderMimeRegistry;
|
|
16
|
+
/**
|
|
17
|
+
* The chat model.
|
|
18
|
+
*/
|
|
14
19
|
model: IChatModel;
|
|
20
|
+
/**
|
|
21
|
+
* The chat commands registry.
|
|
22
|
+
*/
|
|
15
23
|
chatCommandRegistry?: IChatCommandRegistry;
|
|
16
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The input toolbar registry.
|
|
26
|
+
*/
|
|
27
|
+
inputToolbarRegistry: IInputToolbarRegistry;
|
|
17
28
|
};
|
|
18
29
|
/**
|
|
19
30
|
* The messages list component.
|
|
@@ -23,8 +34,10 @@ export declare function ChatMessages(props: BaseMessageProps): JSX.Element;
|
|
|
23
34
|
* The message header props.
|
|
24
35
|
*/
|
|
25
36
|
type ChatMessageHeaderProps = {
|
|
37
|
+
/**
|
|
38
|
+
* The chat message.
|
|
39
|
+
*/
|
|
26
40
|
message: IChatMessage;
|
|
27
|
-
sx?: SxProps<Theme>;
|
|
28
41
|
};
|
|
29
42
|
/**
|
|
30
43
|
* The message header component.
|
|
@@ -192,8 +192,7 @@ export function ChatMessageHeader(props) {
|
|
|
192
192
|
'& > :not(:last-child)': {
|
|
193
193
|
marginRight: 3
|
|
194
194
|
},
|
|
195
|
-
marginBottom: message.stacked ? '0px' : '12px'
|
|
196
|
-
...props.sx
|
|
195
|
+
marginBottom: message.stacked ? '0px' : '12px'
|
|
197
196
|
} },
|
|
198
197
|
avatar,
|
|
199
198
|
React.createElement(Box, { sx: {
|
|
@@ -248,13 +247,14 @@ export const ChatMessage = forwardRef((props, ref) => {
|
|
|
248
247
|
useEffect(() => {
|
|
249
248
|
if (edit && canEdit) {
|
|
250
249
|
setInputModel(new InputModel({
|
|
250
|
+
onSend: (input, model) => updateMessage(message.id, input, model),
|
|
251
|
+
onCancel: () => cancelEdition(),
|
|
251
252
|
value: message.body,
|
|
252
|
-
activeCellManager: model.activeCellManager,
|
|
253
|
-
selectionWatcher: model.selectionWatcher,
|
|
254
253
|
config: {
|
|
255
254
|
sendWithShiftEnter: model.config.sendWithShiftEnter
|
|
256
255
|
},
|
|
257
|
-
attachments: message.attachments
|
|
256
|
+
attachments: message.attachments,
|
|
257
|
+
documentManager: model.documentManager
|
|
258
258
|
}));
|
|
259
259
|
}
|
|
260
260
|
else {
|
|
@@ -266,14 +266,14 @@ export const ChatMessage = forwardRef((props, ref) => {
|
|
|
266
266
|
setEdit(false);
|
|
267
267
|
};
|
|
268
268
|
// Update the content of the message.
|
|
269
|
-
const updateMessage = (id, input) => {
|
|
270
|
-
if (!canEdit) {
|
|
269
|
+
const updateMessage = (id, input, inputModel) => {
|
|
270
|
+
if (!canEdit || !inputModel) {
|
|
271
271
|
return;
|
|
272
272
|
}
|
|
273
273
|
// Update the message
|
|
274
274
|
const updatedMessage = { ...message };
|
|
275
275
|
updatedMessage.body = input;
|
|
276
|
-
updatedMessage.attachments = inputModel
|
|
276
|
+
updatedMessage.attachments = inputModel.attachments;
|
|
277
277
|
model.updateMessage(id, updatedMessage);
|
|
278
278
|
setEdit(false);
|
|
279
279
|
};
|
|
@@ -286,7 +286,7 @@ export const ChatMessage = forwardRef((props, ref) => {
|
|
|
286
286
|
};
|
|
287
287
|
// Empty if the message has been deleted.
|
|
288
288
|
return deleted ? (React.createElement("div", { ref: ref, "data-index": props.index })) : (React.createElement("div", { ref: ref, "data-index": props.index },
|
|
289
|
-
edit && canEdit && inputModel ? (React.createElement(ChatInput, {
|
|
289
|
+
edit && canEdit && inputModel ? (React.createElement(ChatInput, { onCancel: () => cancelEdition(), model: inputModel, chatCommandRegistry: props.chatCommandRegistry, toolbarRegistry: props.inputToolbarRegistry })) : (React.createElement(MarkdownRenderer, { rmRegistry: rmRegistry, markdownStr: message.body, model: model, edit: canEdit ? () => setEdit(true) : undefined, delete: canDelete ? () => deleteMessage(message.id) : undefined, rendered: props.renderedPromise })),
|
|
290
290
|
message.attachments && !edit && (
|
|
291
291
|
// Display the attachments only if message is not edited, otherwise the
|
|
292
292
|
// input component display them.
|
package/lib/components/chat.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
3
|
-
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
4
3
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
5
4
|
import { IChatCommandRegistry } from '../chat-commands';
|
|
5
|
+
import { IInputToolbarRegistry } from './input';
|
|
6
6
|
import { IChatModel } from '../model';
|
|
7
7
|
import { IAttachmentOpenerRegistry } from '../registry';
|
|
8
8
|
export declare function ChatBody(props: Chat.IChatBodyProps): JSX.Element;
|
|
@@ -23,10 +23,6 @@ export declare namespace Chat {
|
|
|
23
23
|
* The rendermime registry.
|
|
24
24
|
*/
|
|
25
25
|
rmRegistry: IRenderMimeRegistry;
|
|
26
|
-
/**
|
|
27
|
-
* The document manager.
|
|
28
|
-
*/
|
|
29
|
-
documentManager?: IDocumentManager;
|
|
30
26
|
/**
|
|
31
27
|
* Chat command registry.
|
|
32
28
|
*/
|
|
@@ -35,6 +31,10 @@ export declare namespace Chat {
|
|
|
35
31
|
* Attachment opener registry.
|
|
36
32
|
*/
|
|
37
33
|
attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
34
|
+
/**
|
|
35
|
+
* The input toolbar registry
|
|
36
|
+
*/
|
|
37
|
+
inputToolbarRegistry?: IInputToolbarRegistry;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* The options to build the Chat UI.
|
package/lib/components/chat.js
CHANGED
|
@@ -10,22 +10,23 @@ import React, { useState } from 'react';
|
|
|
10
10
|
import { JlThemeProvider } from './jl-theme-provider';
|
|
11
11
|
import { ChatMessages } from './chat-messages';
|
|
12
12
|
import { ChatInput } from './chat-input';
|
|
13
|
+
import { InputToolbarRegistry } from './input';
|
|
13
14
|
import { AttachmentOpenerContext } from '../context';
|
|
14
15
|
export function ChatBody(props) {
|
|
15
16
|
const { model } = props;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
17
|
+
let { inputToolbarRegistry } = props;
|
|
18
|
+
if (!inputToolbarRegistry) {
|
|
19
|
+
inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
|
|
20
|
+
}
|
|
20
21
|
return (React.createElement(AttachmentOpenerContext.Provider, { value: props.attachmentOpenerRegistry },
|
|
21
|
-
React.createElement(ChatMessages, { rmRegistry: props.rmRegistry, model: model, chatCommandRegistry: props.chatCommandRegistry,
|
|
22
|
-
React.createElement(ChatInput, {
|
|
22
|
+
React.createElement(ChatMessages, { rmRegistry: props.rmRegistry, model: model, chatCommandRegistry: props.chatCommandRegistry, inputToolbarRegistry: inputToolbarRegistry }),
|
|
23
|
+
React.createElement(ChatInput, { sx: {
|
|
23
24
|
paddingLeft: 4,
|
|
24
25
|
paddingRight: 4,
|
|
25
26
|
paddingTop: 1,
|
|
26
27
|
paddingBottom: 0,
|
|
27
28
|
borderTop: '1px solid var(--jp-border-color1)'
|
|
28
|
-
}, model: model.input,
|
|
29
|
+
}, model: model.input, chatCommandRegistry: props.chatCommandRegistry, toolbarRegistry: inputToolbarRegistry })));
|
|
29
30
|
}
|
|
30
31
|
export function Chat(props) {
|
|
31
32
|
var _a;
|
|
@@ -53,7 +54,7 @@ export function Chat(props) {
|
|
|
53
54
|
React.createElement(ArrowBackIcon, null))) : (React.createElement(Box, null)),
|
|
54
55
|
view !== Chat.View.settings && props.settingsPanel ? (React.createElement(IconButton, { onClick: () => setView(Chat.View.settings) },
|
|
55
56
|
React.createElement(SettingsIcon, null))) : (React.createElement(Box, null))),
|
|
56
|
-
view === Chat.View.chat &&
|
|
57
|
+
view === Chat.View.chat && React.createElement(ChatBody, { ...props }),
|
|
57
58
|
view === Chat.View.settings && props.settingsPanel && (React.createElement(props.settingsPanel, null)))));
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
@@ -10,14 +10,17 @@ var CopyStatus;
|
|
|
10
10
|
CopyStatus[CopyStatus["None"] = 0] = "None";
|
|
11
11
|
CopyStatus[CopyStatus["Copying"] = 1] = "Copying";
|
|
12
12
|
CopyStatus[CopyStatus["Copied"] = 2] = "Copied";
|
|
13
|
+
CopyStatus[CopyStatus["Disabled"] = 3] = "Disabled";
|
|
13
14
|
})(CopyStatus || (CopyStatus = {}));
|
|
14
15
|
const COPYBTN_TEXT_BY_STATUS = {
|
|
15
16
|
[CopyStatus.None]: 'Copy to clipboard',
|
|
16
17
|
[CopyStatus.Copying]: 'Copying…',
|
|
17
|
-
[CopyStatus.Copied]: 'Copied!'
|
|
18
|
+
[CopyStatus.Copied]: 'Copied!',
|
|
19
|
+
[CopyStatus.Disabled]: 'Copy to clipboard disabled in insecure context'
|
|
18
20
|
};
|
|
19
21
|
export function CopyButton(props) {
|
|
20
|
-
const
|
|
22
|
+
const isCopyDisabled = navigator.clipboard === undefined;
|
|
23
|
+
const [copyStatus, setCopyStatus] = useState(isCopyDisabled ? CopyStatus.Disabled : CopyStatus.None);
|
|
21
24
|
const timeoutId = useRef(null);
|
|
22
25
|
const copy = useCallback(async () => {
|
|
23
26
|
// ignore if we are already copying
|
|
@@ -38,6 +41,6 @@ export function CopyButton(props) {
|
|
|
38
41
|
}
|
|
39
42
|
timeoutId.current = window.setTimeout(() => setCopyStatus(CopyStatus.None), 1000);
|
|
40
43
|
}, [copyStatus, props.value]);
|
|
41
|
-
return (React.createElement(TooltippedIconButton, { className: props.className, tooltip: COPYBTN_TEXT_BY_STATUS[copyStatus], placement: "top", onClick: copy, "aria-label": "Copy to clipboard" },
|
|
44
|
+
return (React.createElement(TooltippedIconButton, { disabled: isCopyDisabled, className: props.className, tooltip: COPYBTN_TEXT_BY_STATUS[copyStatus], placement: "top", onClick: copy, "aria-label": "Copy to clipboard" },
|
|
42
45
|
React.createElement(copyIcon.react, { height: "16px", width: "16px" })));
|
|
43
46
|
}
|
|
@@ -5,23 +5,31 @@
|
|
|
5
5
|
import { FileDialog } from '@jupyterlab/filebrowser';
|
|
6
6
|
import AttachFileIcon from '@mui/icons-material/AttachFile';
|
|
7
7
|
import React from 'react';
|
|
8
|
-
import { TooltippedButton } from '
|
|
8
|
+
import { TooltippedButton } from '../../mui-extras/tooltipped-button';
|
|
9
9
|
const ATTACH_BUTTON_CLASS = 'jp-chat-attach-button';
|
|
10
10
|
/**
|
|
11
11
|
* The attach button.
|
|
12
12
|
*/
|
|
13
13
|
export function AttachButton(props) {
|
|
14
|
+
const { model } = props;
|
|
14
15
|
const tooltip = 'Add attachment';
|
|
16
|
+
if (!model.documentManager || !model.addAttachment) {
|
|
17
|
+
return React.createElement(React.Fragment, null);
|
|
18
|
+
}
|
|
15
19
|
const onclick = async () => {
|
|
20
|
+
if (!model.documentManager || !model.addAttachment) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
16
23
|
try {
|
|
17
24
|
const files = await FileDialog.getOpenFiles({
|
|
18
25
|
title: 'Select files to attach',
|
|
19
|
-
manager:
|
|
26
|
+
manager: model.documentManager
|
|
20
27
|
});
|
|
21
28
|
if (files.value) {
|
|
22
29
|
files.value.forEach(file => {
|
|
30
|
+
var _a;
|
|
23
31
|
if (file.type !== 'directory') {
|
|
24
|
-
|
|
32
|
+
(_a = model.addAttachment) === null || _a === void 0 ? void 0 : _a.call(model, { type: 'file', value: file.path });
|
|
25
33
|
}
|
|
26
34
|
});
|
|
27
35
|
}
|
|
@@ -35,11 +43,6 @@ export function AttachButton(props) {
|
|
|
35
43
|
variant: 'contained',
|
|
36
44
|
title: tooltip,
|
|
37
45
|
className: ATTACH_BUTTON_CLASS
|
|
38
|
-
}, sx: {
|
|
39
|
-
minWidth: 'unset',
|
|
40
|
-
padding: '4px',
|
|
41
|
-
borderRadius: '2px 0px 0px 2px',
|
|
42
|
-
marginRight: '1px'
|
|
43
46
|
} },
|
|
44
47
|
React.createElement(AttachFileIcon, null)));
|
|
45
48
|
}
|
|
@@ -4,23 +4,21 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import CancelIcon from '@mui/icons-material/Cancel';
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import { TooltippedButton } from '
|
|
7
|
+
import { TooltippedButton } from '../../mui-extras/tooltipped-button';
|
|
8
8
|
const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
|
|
9
9
|
/**
|
|
10
10
|
* The cancel button.
|
|
11
11
|
*/
|
|
12
12
|
export function CancelButton(props) {
|
|
13
|
+
if (!props.model.cancel) {
|
|
14
|
+
return React.createElement(React.Fragment, null);
|
|
15
|
+
}
|
|
13
16
|
const tooltip = 'Cancel edition';
|
|
14
|
-
return (React.createElement(TooltippedButton, { onClick: props.
|
|
17
|
+
return (React.createElement(TooltippedButton, { onClick: props.model.cancel, tooltip: tooltip, buttonProps: {
|
|
15
18
|
size: 'small',
|
|
16
19
|
variant: 'contained',
|
|
17
20
|
title: tooltip,
|
|
18
21
|
className: CANCEL_BUTTON_CLASS
|
|
19
|
-
}, sx: {
|
|
20
|
-
minWidth: 'unset',
|
|
21
|
-
padding: '4px',
|
|
22
|
-
borderRadius: '2px 0px 0px 2px',
|
|
23
|
-
marginRight: '1px'
|
|
24
22
|
} },
|
|
25
23
|
React.createElement(CancelIcon, null)));
|
|
26
24
|
}
|
|
@@ -6,8 +6,8 @@ import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
|
|
|
6
6
|
import SendIcon from '@mui/icons-material/Send';
|
|
7
7
|
import { Box, Menu, MenuItem, Typography } from '@mui/material';
|
|
8
8
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
9
|
-
import { TooltippedButton } from '
|
|
10
|
-
import { includeSelectionIcon } from '
|
|
9
|
+
import { TooltippedButton } from '../../mui-extras/tooltipped-button';
|
|
10
|
+
import { includeSelectionIcon } from '../../../icons';
|
|
11
11
|
const SEND_BUTTON_CLASS = 'jp-chat-send-button';
|
|
12
12
|
const SEND_INCLUDE_OPENER_CLASS = 'jp-chat-send-include-opener';
|
|
13
13
|
const SEND_INCLUDE_LI_CLASS = 'jp-chat-send-include';
|
|
@@ -15,12 +15,13 @@ const SEND_INCLUDE_LI_CLASS = 'jp-chat-send-include';
|
|
|
15
15
|
* The send button, with optional 'include selection' menu.
|
|
16
16
|
*/
|
|
17
17
|
export function SendButton(props) {
|
|
18
|
-
|
|
19
|
-
const { activeCellManager, selectionWatcher } =
|
|
20
|
-
const hideIncludeSelection =
|
|
21
|
-
const hasButtonOnLeft = (_b = props.hasButtonOnLeft) !== null && _b !== void 0 ? _b : false;
|
|
18
|
+
const { model } = props;
|
|
19
|
+
const { activeCellManager, selectionWatcher } = model;
|
|
20
|
+
const hideIncludeSelection = !activeCellManager || !selectionWatcher;
|
|
22
21
|
const [menuAnchorEl, setMenuAnchorEl] = useState(null);
|
|
23
22
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
23
|
+
const [disabled, setDisabled] = useState(false);
|
|
24
|
+
const [tooltip, setTooltip] = useState('');
|
|
24
25
|
const openMenu = useCallback((el) => {
|
|
25
26
|
setMenuAnchorEl(el);
|
|
26
27
|
setMenuOpen(true);
|
|
@@ -28,9 +29,31 @@ export function SendButton(props) {
|
|
|
28
29
|
const closeMenu = useCallback(() => {
|
|
29
30
|
setMenuOpen(false);
|
|
30
31
|
}, []);
|
|
31
|
-
const disabled = !props.inputExists;
|
|
32
32
|
const [selectionTooltip, setSelectionTooltip] = useState('');
|
|
33
33
|
const [disableInclude, setDisableInclude] = useState(true);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
var _a;
|
|
36
|
+
const inputChanged = () => {
|
|
37
|
+
const inputExist = !!model.value.trim() || model.attachments.length;
|
|
38
|
+
setDisabled(!inputExist);
|
|
39
|
+
};
|
|
40
|
+
model.valueChanged.connect(inputChanged);
|
|
41
|
+
(_a = model.attachmentsChanged) === null || _a === void 0 ? void 0 : _a.connect(inputChanged);
|
|
42
|
+
inputChanged();
|
|
43
|
+
const configChanged = (_, config) => {
|
|
44
|
+
var _a;
|
|
45
|
+
setTooltip(((_a = config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false)
|
|
46
|
+
? 'Send message (SHIFT+ENTER)'
|
|
47
|
+
: 'Send message (ENTER)');
|
|
48
|
+
};
|
|
49
|
+
model.configChanged.connect(configChanged);
|
|
50
|
+
return () => {
|
|
51
|
+
var _a, _b;
|
|
52
|
+
model.valueChanged.disconnect(inputChanged);
|
|
53
|
+
(_a = model.attachmentsChanged) === null || _a === void 0 ? void 0 : _a.disconnect(inputChanged);
|
|
54
|
+
(_b = model.configChanged) === null || _b === void 0 ? void 0 : _b.disconnect(configChanged);
|
|
55
|
+
};
|
|
56
|
+
}, [model]);
|
|
34
57
|
useEffect(() => {
|
|
35
58
|
/**
|
|
36
59
|
* Enable or disable the include selection button, and adapt the tooltip.
|
|
@@ -53,44 +76,36 @@ export function SendButton(props) {
|
|
|
53
76
|
selectionWatcher === null || selectionWatcher === void 0 ? void 0 : selectionWatcher.selectionChanged.disconnect(toggleIncludeState);
|
|
54
77
|
activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.availabilityChanged.disconnect(toggleIncludeState);
|
|
55
78
|
};
|
|
56
|
-
}, [activeCellManager, selectionWatcher
|
|
57
|
-
const defaultTooltip = props.sendWithShiftEnter
|
|
58
|
-
? 'Send message (SHIFT+ENTER)'
|
|
59
|
-
: 'Send message (ENTER)';
|
|
60
|
-
const tooltip = defaultTooltip;
|
|
79
|
+
}, [activeCellManager, selectionWatcher]);
|
|
61
80
|
function sendWithSelection() {
|
|
62
|
-
|
|
81
|
+
let source = '';
|
|
63
82
|
if (selectionWatcher === null || selectionWatcher === void 0 ? void 0 : selectionWatcher.selection) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
// Append the selected text if exists.
|
|
84
|
+
source = selectionWatcher.selection.text;
|
|
85
|
+
}
|
|
86
|
+
else if (activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.available) {
|
|
87
|
+
// Append the active cell content if exists.
|
|
88
|
+
source = activeCellManager.getContent(false).source;
|
|
70
89
|
}
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return;
|
|
90
|
+
let content = model.value;
|
|
91
|
+
if (source) {
|
|
92
|
+
content += `
|
|
93
|
+
|
|
94
|
+
\`\`\`
|
|
95
|
+
${source}
|
|
96
|
+
\`\`\`
|
|
97
|
+
`;
|
|
80
98
|
}
|
|
99
|
+
model.send(content);
|
|
100
|
+
closeMenu();
|
|
101
|
+
model.value = '';
|
|
81
102
|
}
|
|
82
|
-
return (React.createElement(
|
|
83
|
-
React.createElement(TooltippedButton, { onClick: () =>
|
|
103
|
+
return (React.createElement(React.Fragment, null,
|
|
104
|
+
React.createElement(TooltippedButton, { onClick: () => model.send(model.value), disabled: disabled, tooltip: tooltip, buttonProps: {
|
|
84
105
|
size: 'small',
|
|
85
|
-
title:
|
|
106
|
+
title: tooltip,
|
|
86
107
|
variant: 'contained',
|
|
87
108
|
className: SEND_BUTTON_CLASS
|
|
88
|
-
}, sx: {
|
|
89
|
-
minWidth: 'unset',
|
|
90
|
-
borderTopLeftRadius: hasButtonOnLeft ? '0px' : '2px',
|
|
91
|
-
borderTopRightRadius: hideIncludeSelection ? '2px' : '0px',
|
|
92
|
-
borderBottomRightRadius: hideIncludeSelection ? '2px' : '0px',
|
|
93
|
-
borderBottomLeftRadius: hasButtonOnLeft ? '0px' : '2px'
|
|
94
109
|
} },
|
|
95
110
|
React.createElement(SendIcon, null)),
|
|
96
111
|
!hideIncludeSelection && (React.createElement(React.Fragment, null,
|
|
@@ -108,11 +123,6 @@ export function SendButton(props) {
|
|
|
108
123
|
e.stopPropagation();
|
|
109
124
|
},
|
|
110
125
|
className: SEND_INCLUDE_OPENER_CLASS
|
|
111
|
-
}, sx: {
|
|
112
|
-
minWidth: 'unset',
|
|
113
|
-
padding: '4px 0px',
|
|
114
|
-
borderRadius: '0px 2px 2px 0px',
|
|
115
|
-
marginLeft: '1px'
|
|
116
126
|
} },
|
|
117
127
|
React.createElement(KeyboardArrowDown, null)),
|
|
118
128
|
React.createElement(Menu, { open: menuOpen, onClose: closeMenu, anchorEl: menuAnchorEl, anchorOrigin: {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
1
|
+
export * from './buttons';
|
|
2
|
+
export * from './toolbar-registry';
|
|
3
|
+
export * from './use-chat-commands';
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Copyright (c) Jupyter Development Team.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './
|
|
5
|
+
export * from './buttons';
|
|
6
|
+
export * from './toolbar-registry';
|
|
7
|
+
export * from './use-chat-commands';
|