@jupyter/chat 0.8.1 → 0.10.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__/mocks.d.ts +9 -0
- package/lib/__tests__/mocks.js +18 -0
- package/lib/__tests__/model.spec.js +17 -10
- package/lib/__tests__/widgets.spec.js +4 -4
- package/lib/chat-commands/types.d.ts +2 -1
- package/lib/components/chat-input.d.ts +4 -12
- package/lib/components/chat-input.js +26 -40
- package/lib/components/chat-messages.d.ts +17 -4
- package/lib/components/chat-messages.js +28 -15
- 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 +6 -5
- 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/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/input-model.d.ts +93 -1
- package/lib/input-model.js +55 -1
- package/lib/model.d.ts +76 -9
- package/lib/model.js +42 -12
- package/lib/types.d.ts +5 -18
- package/lib/utils.d.ts +15 -0
- package/lib/utils.js +29 -0
- package/lib/widgets/chat-widget.d.ts +5 -1
- package/lib/widgets/chat-widget.js +7 -1
- package/package.json +1 -1
- package/src/__tests__/mocks.ts +31 -0
- package/src/__tests__/model.spec.ts +21 -11
- package/src/__tests__/widgets.spec.ts +5 -4
- package/src/chat-commands/types.ts +1 -1
- package/src/components/chat-input.tsx +41 -66
- package/src/components/chat-messages.tsx +44 -17
- 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 +14 -6
- package/src/components/mui-extras/tooltipped-button.tsx +4 -2
- package/src/components/mui-extras/tooltipped-icon-button.tsx +5 -2
- package/src/index.ts +1 -1
- package/src/input-model.ts +140 -2
- package/src/model.ts +110 -12
- package/src/types.ts +5 -21
- package/src/utils.ts +34 -0
- package/src/widgets/chat-widget.tsx +8 -1
- package/style/base.css +1 -0
- package/style/chat.css +6 -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
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
7
6
|
import {
|
|
8
7
|
Autocomplete,
|
|
9
8
|
AutocompleteInputChangeReason,
|
|
@@ -17,16 +16,20 @@ import clsx from 'clsx';
|
|
|
17
16
|
import React, { useEffect, useRef, useState } from 'react';
|
|
18
17
|
|
|
19
18
|
import { AttachmentPreviewList } from './attachments';
|
|
20
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
IInputToolbarRegistry,
|
|
21
|
+
InputToolbarRegistry,
|
|
22
|
+
useChatCommands
|
|
23
|
+
} from './input';
|
|
21
24
|
import { IInputModel, InputModel } from '../input-model';
|
|
22
|
-
import { IAttachment, Selection } from '../types';
|
|
23
|
-
import { useChatCommands } from './input/use-chat-commands';
|
|
24
25
|
import { IChatCommandRegistry } from '../chat-commands';
|
|
26
|
+
import { IAttachment } from '../types';
|
|
25
27
|
|
|
26
28
|
const INPUT_BOX_CLASS = 'jp-chat-input-container';
|
|
29
|
+
const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
|
|
27
30
|
|
|
28
31
|
export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
29
|
-
const {
|
|
32
|
+
const { model, toolbarRegistry } = props;
|
|
30
33
|
const [input, setInput] = useState<string>(model.value);
|
|
31
34
|
const inputRef = useRef<HTMLInputElement>();
|
|
32
35
|
|
|
@@ -38,14 +41,16 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
38
41
|
const [attachments, setAttachments] = useState<IAttachment[]>(
|
|
39
42
|
model.attachments
|
|
40
43
|
);
|
|
44
|
+
const [toolbarElements, setToolbarElements] = useState<
|
|
45
|
+
InputToolbarRegistry.IToolbarItem[]
|
|
46
|
+
>([]);
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Handle the changes on the model that affect the input.
|
|
50
|
+
* - focus requested
|
|
51
|
+
* - config changed
|
|
52
|
+
* - attachments changed
|
|
53
|
+
*/
|
|
49
54
|
useEffect(() => {
|
|
50
55
|
const inputChanged = (_: IInputModel, value: string) => {
|
|
51
56
|
setInput(value);
|
|
@@ -76,6 +81,22 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
76
81
|
};
|
|
77
82
|
}, [model]);
|
|
78
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Handle the changes in the toolbar items.
|
|
86
|
+
*/
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const updateToolbar = () => {
|
|
89
|
+
setToolbarElements(toolbarRegistry.getItems());
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
toolbarRegistry.itemsChanged.connect(updateToolbar);
|
|
93
|
+
updateToolbar();
|
|
94
|
+
|
|
95
|
+
return () => {
|
|
96
|
+
toolbarRegistry.itemsChanged.disconnect(updateToolbar);
|
|
97
|
+
};
|
|
98
|
+
}, [toolbarRegistry]);
|
|
99
|
+
|
|
79
100
|
const inputExists = !!input.trim();
|
|
80
101
|
|
|
81
102
|
/**
|
|
@@ -136,38 +157,12 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
136
157
|
(sendWithShiftEnter && event.shiftKey) ||
|
|
137
158
|
(!sendWithShiftEnter && !event.shiftKey)
|
|
138
159
|
) {
|
|
139
|
-
|
|
160
|
+
model.send(input);
|
|
140
161
|
event.stopPropagation();
|
|
141
162
|
event.preventDefault();
|
|
142
163
|
}
|
|
143
164
|
}
|
|
144
165
|
|
|
145
|
-
/**
|
|
146
|
-
* Triggered when sending the message.
|
|
147
|
-
*
|
|
148
|
-
* Add code block if cell or text is selected.
|
|
149
|
-
*/
|
|
150
|
-
function onSend(selection?: Selection) {
|
|
151
|
-
let content = input;
|
|
152
|
-
if (selection) {
|
|
153
|
-
content += `
|
|
154
|
-
|
|
155
|
-
\`\`\`
|
|
156
|
-
${selection.source}
|
|
157
|
-
\`\`\`
|
|
158
|
-
`;
|
|
159
|
-
}
|
|
160
|
-
props.onSend(content);
|
|
161
|
-
model.value = '';
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Triggered when cancelling edition.
|
|
166
|
-
*/
|
|
167
|
-
function onCancel() {
|
|
168
|
-
props.onCancel?.();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
166
|
// Set the helper text based on whether Shift+Enter is used for sending.
|
|
172
167
|
const helperText = sendWithShiftEnter ? (
|
|
173
168
|
<span>
|
|
@@ -221,22 +216,10 @@ ${selection.source}
|
|
|
221
216
|
InputProps={{
|
|
222
217
|
...params.InputProps,
|
|
223
218
|
endAdornment: (
|
|
224
|
-
<InputAdornment position="end">
|
|
225
|
-
{
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
onAttach={model.addAttachment}
|
|
229
|
-
/>
|
|
230
|
-
)}
|
|
231
|
-
{props.onCancel && <CancelButton onCancel={onCancel} />}
|
|
232
|
-
<SendButton
|
|
233
|
-
model={model}
|
|
234
|
-
sendWithShiftEnter={sendWithShiftEnter}
|
|
235
|
-
inputExists={inputExists || attachments.length > 0}
|
|
236
|
-
onSend={onSend}
|
|
237
|
-
hideIncludeSelection={hideIncludeSelection}
|
|
238
|
-
hasButtonOnLeft={!!props.onCancel}
|
|
239
|
-
/>
|
|
219
|
+
<InputAdornment position="end" className={INPUT_TOOLBAR_CLASS}>
|
|
220
|
+
{toolbarElements.map(item => (
|
|
221
|
+
<item.element model={model} />
|
|
222
|
+
))}
|
|
240
223
|
</InputAdornment>
|
|
241
224
|
)
|
|
242
225
|
}}
|
|
@@ -273,29 +256,21 @@ export namespace ChatInput {
|
|
|
273
256
|
*/
|
|
274
257
|
export interface IProps {
|
|
275
258
|
/**
|
|
276
|
-
* The
|
|
259
|
+
* The input model.
|
|
277
260
|
*/
|
|
278
261
|
model: IInputModel;
|
|
279
262
|
/**
|
|
280
|
-
* The
|
|
263
|
+
* The toolbar registry.
|
|
281
264
|
*/
|
|
282
|
-
|
|
265
|
+
toolbarRegistry: IInputToolbarRegistry;
|
|
283
266
|
/**
|
|
284
267
|
* The function to be called to cancel editing.
|
|
285
268
|
*/
|
|
286
269
|
onCancel?: () => unknown;
|
|
287
|
-
/**
|
|
288
|
-
* Whether to allow or not including selection.
|
|
289
|
-
*/
|
|
290
|
-
hideIncludeSelection?: boolean;
|
|
291
270
|
/**
|
|
292
271
|
* Custom mui/material styles.
|
|
293
272
|
*/
|
|
294
273
|
sx?: SxProps<Theme>;
|
|
295
|
-
/**
|
|
296
|
-
* The document manager.
|
|
297
|
-
*/
|
|
298
|
-
documentManager?: IDocumentManager;
|
|
299
274
|
/**
|
|
300
275
|
* Chat command registry.
|
|
301
276
|
*/
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Button } from '@jupyter/react-components';
|
|
7
|
-
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
8
7
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
9
8
|
import {
|
|
10
9
|
LabIcon,
|
|
@@ -19,12 +18,14 @@ import React, { useEffect, useState, useRef, forwardRef } from 'react';
|
|
|
19
18
|
|
|
20
19
|
import { AttachmentPreviewList } from './attachments';
|
|
21
20
|
import { ChatInput } from './chat-input';
|
|
21
|
+
import { IInputToolbarRegistry } from './input';
|
|
22
22
|
import { MarkdownRenderer } from './markdown-renderer';
|
|
23
23
|
import { ScrollContainer } from './scroll-container';
|
|
24
24
|
import { IChatCommandRegistry } from '../chat-commands';
|
|
25
25
|
import { IInputModel, InputModel } from '../input-model';
|
|
26
26
|
import { IChatModel } from '../model';
|
|
27
27
|
import { IChatMessage, IUser } from '../types';
|
|
28
|
+
import { replaceSpanToMention } from '../utils';
|
|
28
29
|
|
|
29
30
|
const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
|
|
30
31
|
const MESSAGE_CLASS = 'jp-chat-message';
|
|
@@ -41,10 +42,22 @@ const NAVIGATION_BOTTOM_CLASS = 'jp-chat-navigation-bottom';
|
|
|
41
42
|
* The base components props.
|
|
42
43
|
*/
|
|
43
44
|
type BaseMessageProps = {
|
|
45
|
+
/**
|
|
46
|
+
* The mime renderer registry.
|
|
47
|
+
*/
|
|
44
48
|
rmRegistry: IRenderMimeRegistry;
|
|
49
|
+
/**
|
|
50
|
+
* The chat model.
|
|
51
|
+
*/
|
|
45
52
|
model: IChatModel;
|
|
53
|
+
/**
|
|
54
|
+
* The chat commands registry.
|
|
55
|
+
*/
|
|
46
56
|
chatCommandRegistry?: IChatCommandRegistry;
|
|
47
|
-
|
|
57
|
+
/**
|
|
58
|
+
* The input toolbar registry.
|
|
59
|
+
*/
|
|
60
|
+
inputToolbarRegistry: IInputToolbarRegistry;
|
|
48
61
|
};
|
|
49
62
|
|
|
50
63
|
/**
|
|
@@ -200,8 +213,10 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
|
|
|
200
213
|
* The message header props.
|
|
201
214
|
*/
|
|
202
215
|
type ChatMessageHeaderProps = {
|
|
216
|
+
/**
|
|
217
|
+
* The chat message.
|
|
218
|
+
*/
|
|
203
219
|
message: IChatMessage;
|
|
204
|
-
sx?: SxProps<Theme>;
|
|
205
220
|
};
|
|
206
221
|
|
|
207
222
|
/**
|
|
@@ -262,8 +277,7 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
|
|
|
262
277
|
'& > :not(:last-child)': {
|
|
263
278
|
marginRight: 3
|
|
264
279
|
},
|
|
265
|
-
marginBottom: message.stacked ? '0px' : '12px'
|
|
266
|
-
...props.sx
|
|
280
|
+
marginBottom: message.stacked ? '0px' : '12px'
|
|
267
281
|
}}
|
|
268
282
|
>
|
|
269
283
|
{avatar}
|
|
@@ -362,17 +376,27 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
|
|
|
362
376
|
// Create an input model only if the message is edited.
|
|
363
377
|
useEffect(() => {
|
|
364
378
|
if (edit && canEdit) {
|
|
365
|
-
setInputModel(
|
|
366
|
-
|
|
367
|
-
|
|
379
|
+
setInputModel(() => {
|
|
380
|
+
let body = message.body;
|
|
381
|
+
message.mentions?.forEach(user => {
|
|
382
|
+
body = replaceSpanToMention(body, user);
|
|
383
|
+
});
|
|
384
|
+
return new InputModel({
|
|
385
|
+
chatContext: model.createChatContext(),
|
|
386
|
+
onSend: (input: string, model?: IInputModel) =>
|
|
387
|
+
updateMessage(message.id, input, model),
|
|
388
|
+
onCancel: () => cancelEdition(),
|
|
389
|
+
value: body,
|
|
368
390
|
activeCellManager: model.activeCellManager,
|
|
369
391
|
selectionWatcher: model.selectionWatcher,
|
|
392
|
+
documentManager: model.documentManager,
|
|
370
393
|
config: {
|
|
371
394
|
sendWithShiftEnter: model.config.sendWithShiftEnter
|
|
372
395
|
},
|
|
373
|
-
attachments: message.attachments
|
|
374
|
-
|
|
375
|
-
|
|
396
|
+
attachments: message.attachments,
|
|
397
|
+
mentions: message.mentions
|
|
398
|
+
});
|
|
399
|
+
});
|
|
376
400
|
} else {
|
|
377
401
|
setInputModel(null);
|
|
378
402
|
}
|
|
@@ -384,14 +408,19 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
|
|
|
384
408
|
};
|
|
385
409
|
|
|
386
410
|
// Update the content of the message.
|
|
387
|
-
const updateMessage = (
|
|
388
|
-
|
|
411
|
+
const updateMessage = (
|
|
412
|
+
id: string,
|
|
413
|
+
input: string,
|
|
414
|
+
inputModel?: IInputModel
|
|
415
|
+
): void => {
|
|
416
|
+
if (!canEdit || !inputModel) {
|
|
389
417
|
return;
|
|
390
418
|
}
|
|
391
419
|
// Update the message
|
|
392
420
|
const updatedMessage = { ...message };
|
|
393
421
|
updatedMessage.body = input;
|
|
394
|
-
updatedMessage.attachments = inputModel
|
|
422
|
+
updatedMessage.attachments = inputModel.attachments;
|
|
423
|
+
updatedMessage.mentions = inputModel.mentions;
|
|
395
424
|
model.updateMessage!(id, updatedMessage);
|
|
396
425
|
setEdit(false);
|
|
397
426
|
};
|
|
@@ -411,12 +440,10 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
|
|
|
411
440
|
<div ref={ref} data-index={props.index}>
|
|
412
441
|
{edit && canEdit && inputModel ? (
|
|
413
442
|
<ChatInput
|
|
414
|
-
onSend={(input: string) => updateMessage(message.id, input)}
|
|
415
443
|
onCancel={() => cancelEdition()}
|
|
416
444
|
model={inputModel}
|
|
417
|
-
hideIncludeSelection={true}
|
|
418
445
|
chatCommandRegistry={props.chatCommandRegistry}
|
|
419
|
-
|
|
446
|
+
toolbarRegistry={props.inputToolbarRegistry}
|
|
420
447
|
/>
|
|
421
448
|
) : (
|
|
422
449
|
<MarkdownRenderer
|
package/src/components/chat.tsx
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
7
|
-
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
8
7
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
9
8
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|
10
9
|
import SettingsIcon from '@mui/icons-material/Settings';
|
|
@@ -16,16 +15,17 @@ import { JlThemeProvider } from './jl-theme-provider';
|
|
|
16
15
|
import { IChatCommandRegistry } from '../chat-commands';
|
|
17
16
|
import { ChatMessages } from './chat-messages';
|
|
18
17
|
import { ChatInput } from './chat-input';
|
|
18
|
+
import { IInputToolbarRegistry, InputToolbarRegistry } from './input';
|
|
19
19
|
import { AttachmentOpenerContext } from '../context';
|
|
20
20
|
import { IChatModel } from '../model';
|
|
21
21
|
import { IAttachmentOpenerRegistry } from '../registry';
|
|
22
22
|
|
|
23
23
|
export function ChatBody(props: Chat.IChatBodyProps): JSX.Element {
|
|
24
24
|
const { model } = props;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
25
|
+
let { inputToolbarRegistry } = props;
|
|
26
|
+
if (!inputToolbarRegistry) {
|
|
27
|
+
inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
|
|
28
|
+
}
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
31
|
<AttachmentOpenerContext.Provider value={props.attachmentOpenerRegistry}>
|
|
@@ -33,10 +33,9 @@ export function ChatBody(props: Chat.IChatBodyProps): JSX.Element {
|
|
|
33
33
|
rmRegistry={props.rmRegistry}
|
|
34
34
|
model={model}
|
|
35
35
|
chatCommandRegistry={props.chatCommandRegistry}
|
|
36
|
-
|
|
36
|
+
inputToolbarRegistry={inputToolbarRegistry}
|
|
37
37
|
/>
|
|
38
38
|
<ChatInput
|
|
39
|
-
onSend={onSend}
|
|
40
39
|
sx={{
|
|
41
40
|
paddingLeft: 4,
|
|
42
41
|
paddingRight: 4,
|
|
@@ -45,8 +44,8 @@ export function ChatBody(props: Chat.IChatBodyProps): JSX.Element {
|
|
|
45
44
|
borderTop: '1px solid var(--jp-border-color1)'
|
|
46
45
|
}}
|
|
47
46
|
model={model.input}
|
|
48
|
-
documentManager={props.documentManager}
|
|
49
47
|
chatCommandRegistry={props.chatCommandRegistry}
|
|
48
|
+
toolbarRegistry={inputToolbarRegistry}
|
|
50
49
|
/>
|
|
51
50
|
</AttachmentOpenerContext.Provider>
|
|
52
51
|
);
|
|
@@ -89,15 +88,7 @@ export function Chat(props: Chat.IOptions): JSX.Element {
|
|
|
89
88
|
)}
|
|
90
89
|
</Box>
|
|
91
90
|
{/* body */}
|
|
92
|
-
{view === Chat.View.chat &&
|
|
93
|
-
<ChatBody
|
|
94
|
-
model={props.model}
|
|
95
|
-
rmRegistry={props.rmRegistry}
|
|
96
|
-
documentManager={props.documentManager}
|
|
97
|
-
chatCommandRegistry={props.chatCommandRegistry}
|
|
98
|
-
attachmentOpenerRegistry={props.attachmentOpenerRegistry}
|
|
99
|
-
/>
|
|
100
|
-
)}
|
|
91
|
+
{view === Chat.View.chat && <ChatBody {...props} />}
|
|
101
92
|
{view === Chat.View.settings && props.settingsPanel && (
|
|
102
93
|
<props.settingsPanel />
|
|
103
94
|
)}
|
|
@@ -122,10 +113,6 @@ export namespace Chat {
|
|
|
122
113
|
* The rendermime registry.
|
|
123
114
|
*/
|
|
124
115
|
rmRegistry: IRenderMimeRegistry;
|
|
125
|
-
/**
|
|
126
|
-
* The document manager.
|
|
127
|
-
*/
|
|
128
|
-
documentManager?: IDocumentManager;
|
|
129
116
|
/**
|
|
130
117
|
* Chat command registry.
|
|
131
118
|
*/
|
|
@@ -134,6 +121,10 @@ export namespace Chat {
|
|
|
134
121
|
* Attachment opener registry.
|
|
135
122
|
*/
|
|
136
123
|
attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
124
|
+
/**
|
|
125
|
+
* The input toolbar registry
|
|
126
|
+
*/
|
|
127
|
+
inputToolbarRegistry?: IInputToolbarRegistry;
|
|
137
128
|
}
|
|
138
129
|
|
|
139
130
|
/**
|
|
@@ -12,13 +12,15 @@ import { TooltippedIconButton } from '../mui-extras/tooltipped-icon-button';
|
|
|
12
12
|
enum CopyStatus {
|
|
13
13
|
None,
|
|
14
14
|
Copying,
|
|
15
|
-
Copied
|
|
15
|
+
Copied,
|
|
16
|
+
Disabled
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
const COPYBTN_TEXT_BY_STATUS: Record<CopyStatus, string> = {
|
|
19
20
|
[CopyStatus.None]: 'Copy to clipboard',
|
|
20
21
|
[CopyStatus.Copying]: 'Copying…',
|
|
21
|
-
[CopyStatus.Copied]: 'Copied!'
|
|
22
|
+
[CopyStatus.Copied]: 'Copied!',
|
|
23
|
+
[CopyStatus.Disabled]: 'Copy to clipboard disabled in insecure context'
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
type CopyButtonProps = {
|
|
@@ -27,7 +29,10 @@ type CopyButtonProps = {
|
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export function CopyButton(props: CopyButtonProps): JSX.Element {
|
|
30
|
-
const
|
|
32
|
+
const isCopyDisabled = navigator.clipboard === undefined;
|
|
33
|
+
const [copyStatus, setCopyStatus] = useState<CopyStatus>(
|
|
34
|
+
isCopyDisabled ? CopyStatus.Disabled : CopyStatus.None
|
|
35
|
+
);
|
|
31
36
|
const timeoutId = useRef<number | null>(null);
|
|
32
37
|
|
|
33
38
|
const copy = useCallback(async () => {
|
|
@@ -56,6 +61,7 @@ export function CopyButton(props: CopyButtonProps): JSX.Element {
|
|
|
56
61
|
|
|
57
62
|
return (
|
|
58
63
|
<TooltippedIconButton
|
|
64
|
+
disabled={isCopyDisabled}
|
|
59
65
|
className={props.className}
|
|
60
66
|
tooltip={COPYBTN_TEXT_BY_STATUS[copyStatus]}
|
|
61
67
|
placement="top"
|
|
@@ -3,40 +3,41 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
7
6
|
import { FileDialog } from '@jupyterlab/filebrowser';
|
|
8
7
|
import AttachFileIcon from '@mui/icons-material/AttachFile';
|
|
9
8
|
import React from 'react';
|
|
10
9
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { InputToolbarRegistry } from '../toolbar-registry';
|
|
11
|
+
import { TooltippedButton } from '../../mui-extras/tooltipped-button';
|
|
13
12
|
|
|
14
13
|
const ATTACH_BUTTON_CLASS = 'jp-chat-attach-button';
|
|
15
14
|
|
|
16
|
-
/**
|
|
17
|
-
* The attach button props.
|
|
18
|
-
*/
|
|
19
|
-
export type AttachButtonProps = {
|
|
20
|
-
documentManager: IDocumentManager;
|
|
21
|
-
onAttach: (attachment: IAttachment) => void;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
15
|
/**
|
|
25
16
|
* The attach button.
|
|
26
17
|
*/
|
|
27
|
-
export function AttachButton(
|
|
18
|
+
export function AttachButton(
|
|
19
|
+
props: InputToolbarRegistry.IToolbarItemProps
|
|
20
|
+
): JSX.Element {
|
|
21
|
+
const { model } = props;
|
|
28
22
|
const tooltip = 'Add attachment';
|
|
29
23
|
|
|
24
|
+
if (!model.documentManager || !model.addAttachment) {
|
|
25
|
+
return <></>;
|
|
26
|
+
}
|
|
27
|
+
|
|
30
28
|
const onclick = async () => {
|
|
29
|
+
if (!model.documentManager || !model.addAttachment) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
31
32
|
try {
|
|
32
33
|
const files = await FileDialog.getOpenFiles({
|
|
33
34
|
title: 'Select files to attach',
|
|
34
|
-
manager:
|
|
35
|
+
manager: model.documentManager
|
|
35
36
|
});
|
|
36
37
|
if (files.value) {
|
|
37
38
|
files.value.forEach(file => {
|
|
38
39
|
if (file.type !== 'directory') {
|
|
39
|
-
|
|
40
|
+
model.addAttachment?.({ type: 'file', value: file.path });
|
|
40
41
|
}
|
|
41
42
|
});
|
|
42
43
|
}
|
|
@@ -55,12 +56,6 @@ export function AttachButton(props: AttachButtonProps): JSX.Element {
|
|
|
55
56
|
title: tooltip,
|
|
56
57
|
className: ATTACH_BUTTON_CLASS
|
|
57
58
|
}}
|
|
58
|
-
sx={{
|
|
59
|
-
minWidth: 'unset',
|
|
60
|
-
padding: '4px',
|
|
61
|
-
borderRadius: '2px 0px 0px 2px',
|
|
62
|
-
marginRight: '1px'
|
|
63
|
-
}}
|
|
64
59
|
>
|
|
65
60
|
<AttachFileIcon />
|
|
66
61
|
</TooltippedButton>
|
|
@@ -6,25 +6,24 @@
|
|
|
6
6
|
import CancelIcon from '@mui/icons-material/Cancel';
|
|
7
7
|
import React from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { InputToolbarRegistry } from '../toolbar-registry';
|
|
10
|
+
import { TooltippedButton } from '../../mui-extras/tooltipped-button';
|
|
10
11
|
|
|
11
12
|
const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
-
* The cancel button props.
|
|
15
|
-
*/
|
|
16
|
-
export type CancelButtonProps = {
|
|
17
|
-
onCancel: () => void;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
14
|
/**
|
|
21
15
|
* The cancel button.
|
|
22
16
|
*/
|
|
23
|
-
export function CancelButton(
|
|
17
|
+
export function CancelButton(
|
|
18
|
+
props: InputToolbarRegistry.IToolbarItemProps
|
|
19
|
+
): JSX.Element {
|
|
20
|
+
if (!props.model.cancel) {
|
|
21
|
+
return <></>;
|
|
22
|
+
}
|
|
24
23
|
const tooltip = 'Cancel edition';
|
|
25
24
|
return (
|
|
26
25
|
<TooltippedButton
|
|
27
|
-
onClick={props.
|
|
26
|
+
onClick={props.model.cancel}
|
|
28
27
|
tooltip={tooltip}
|
|
29
28
|
buttonProps={{
|
|
30
29
|
size: 'small',
|
|
@@ -32,12 +31,6 @@ export function CancelButton(props: CancelButtonProps): JSX.Element {
|
|
|
32
31
|
title: tooltip,
|
|
33
32
|
className: CANCEL_BUTTON_CLASS
|
|
34
33
|
}}
|
|
35
|
-
sx={{
|
|
36
|
-
minWidth: 'unset',
|
|
37
|
-
padding: '4px',
|
|
38
|
-
borderRadius: '2px 0px 0px 2px',
|
|
39
|
-
marginRight: '1px'
|
|
40
|
-
}}
|
|
41
34
|
>
|
|
42
35
|
<CancelIcon />
|
|
43
36
|
</TooltippedButton>
|