@jupyter/chat 0.8.0 → 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.
Files changed (54) hide show
  1. package/lib/components/chat-input.d.ts +3 -11
  2. package/lib/components/chat-input.js +26 -40
  3. package/lib/components/chat-messages.d.ts +17 -4
  4. package/lib/components/chat-messages.js +9 -9
  5. package/lib/components/chat.d.ts +5 -5
  6. package/lib/components/chat.js +9 -8
  7. package/lib/components/code-blocks/copy-button.js +6 -3
  8. package/lib/components/input/buttons/attach-button.d.ts +6 -0
  9. package/lib/components/input/{attach-button.js → buttons/attach-button.js} +11 -8
  10. package/lib/components/input/buttons/cancel-button.d.ts +6 -0
  11. package/lib/components/input/{cancel-button.js → buttons/cancel-button.js} +5 -7
  12. package/lib/components/input/buttons/index.d.ts +3 -0
  13. package/lib/components/input/buttons/index.js +7 -0
  14. package/lib/components/input/buttons/send-button.d.ts +6 -0
  15. package/lib/components/input/{send-button.js → buttons/send-button.js} +52 -42
  16. package/lib/components/input/index.d.ts +3 -3
  17. package/lib/components/input/index.js +3 -3
  18. package/lib/components/input/toolbar-registry.d.ts +98 -0
  19. package/lib/components/input/toolbar-registry.js +85 -0
  20. package/lib/components/input/use-chat-commands.js +3 -2
  21. package/lib/components/mui-extras/tooltipped-button.d.ts +1 -1
  22. package/lib/components/mui-extras/tooltipped-button.js +3 -2
  23. package/lib/components/mui-extras/tooltipped-icon-button.js +4 -2
  24. package/lib/input-model.d.ts +41 -0
  25. package/lib/input-model.js +17 -1
  26. package/lib/model.d.ts +22 -0
  27. package/lib/model.js +18 -2
  28. package/lib/types.d.ts +0 -18
  29. package/lib/widgets/chat-widget.d.ts +5 -1
  30. package/lib/widgets/chat-widget.js +7 -1
  31. package/package.json +1 -1
  32. package/src/components/chat-input.tsx +40 -65
  33. package/src/components/chat-messages.tsx +31 -14
  34. package/src/components/chat.tsx +12 -21
  35. package/src/components/code-blocks/copy-button.tsx +9 -3
  36. package/src/components/input/{attach-button.tsx → buttons/attach-button.tsx} +15 -20
  37. package/src/components/input/{cancel-button.tsx → buttons/cancel-button.tsx} +9 -16
  38. package/src/components/input/buttons/index.ts +8 -0
  39. package/src/components/input/{send-button.tsx → buttons/send-button.tsx} +62 -61
  40. package/src/components/input/index.ts +3 -3
  41. package/src/components/input/toolbar-registry.tsx +162 -0
  42. package/src/components/input/use-chat-commands.tsx +8 -2
  43. package/src/components/mui-extras/tooltipped-button.tsx +4 -2
  44. package/src/components/mui-extras/tooltipped-icon-button.tsx +5 -2
  45. package/src/input-model.ts +58 -1
  46. package/src/model.ts +36 -2
  47. package/src/types.ts +0 -21
  48. package/src/widgets/chat-widget.tsx +8 -1
  49. package/style/base.css +1 -0
  50. package/style/chat.css +10 -0
  51. package/style/input.css +32 -0
  52. package/lib/components/input/attach-button.d.ts +0 -14
  53. package/lib/components/input/cancel-button.d.ts +0 -11
  54. 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 { AttachButton, CancelButton, SendButton } from './input';
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';
25
+ import { IAttachment } from '../types';
24
26
  import { IChatCommandRegistry } from '../chat-commands';
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 { documentManager, model } = props;
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
- // Display the include selection menu if it is not explicitly hidden, and if at least
43
- // one of the tool to check for text or cell selection is enabled.
44
- let hideIncludeSelection = props.hideIncludeSelection ?? false;
45
- if (model.activeCellManager === null && model.selectionWatcher === null) {
46
- hideIncludeSelection = true;
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
- onSend();
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
- {documentManager && model.addAttachment && (
226
- <AttachButton
227
- documentManager={documentManager}
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
  }}
@@ -277,25 +260,17 @@ export namespace ChatInput {
277
260
  */
278
261
  model: IInputModel;
279
262
  /**
280
- * The function to be called to send the message.
263
+ * The toolbar registry.
281
264
  */
282
- onSend: (input: string) => unknown;
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,6 +18,7 @@ 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';
@@ -41,10 +41,22 @@ const NAVIGATION_BOTTOM_CLASS = 'jp-chat-navigation-bottom';
41
41
  * The base components props.
42
42
  */
43
43
  type BaseMessageProps = {
44
+ /**
45
+ * The mime renderer registry.
46
+ */
44
47
  rmRegistry: IRenderMimeRegistry;
48
+ /**
49
+ * The chat model.
50
+ */
45
51
  model: IChatModel;
52
+ /**
53
+ * The chat commands registry.
54
+ */
46
55
  chatCommandRegistry?: IChatCommandRegistry;
47
- documentManager?: IDocumentManager;
56
+ /**
57
+ * The input toolbar registry.
58
+ */
59
+ inputToolbarRegistry: IInputToolbarRegistry;
48
60
  };
49
61
 
50
62
  /**
@@ -200,8 +212,10 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
200
212
  * The message header props.
201
213
  */
202
214
  type ChatMessageHeaderProps = {
215
+ /**
216
+ * The chat message.
217
+ */
203
218
  message: IChatMessage;
204
- sx?: SxProps<Theme>;
205
219
  };
206
220
 
207
221
  /**
@@ -262,8 +276,7 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
262
276
  '& > :not(:last-child)': {
263
277
  marginRight: 3
264
278
  },
265
- marginBottom: message.stacked ? '0px' : '12px',
266
- ...props.sx
279
+ marginBottom: message.stacked ? '0px' : '12px'
267
280
  }}
268
281
  >
269
282
  {avatar}
@@ -364,13 +377,15 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
364
377
  if (edit && canEdit) {
365
378
  setInputModel(
366
379
  new InputModel({
380
+ onSend: (input: string, model?: IInputModel) =>
381
+ updateMessage(message.id, input, model),
382
+ onCancel: () => cancelEdition(),
367
383
  value: message.body,
368
- activeCellManager: model.activeCellManager,
369
- selectionWatcher: model.selectionWatcher,
370
384
  config: {
371
385
  sendWithShiftEnter: model.config.sendWithShiftEnter
372
386
  },
373
- attachments: message.attachments
387
+ attachments: message.attachments,
388
+ documentManager: model.documentManager
374
389
  })
375
390
  );
376
391
  } else {
@@ -384,14 +399,18 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
384
399
  };
385
400
 
386
401
  // Update the content of the message.
387
- const updateMessage = (id: string, input: string): void => {
388
- if (!canEdit) {
402
+ const updateMessage = (
403
+ id: string,
404
+ input: string,
405
+ inputModel?: IInputModel
406
+ ): void => {
407
+ if (!canEdit || !inputModel) {
389
408
  return;
390
409
  }
391
410
  // Update the message
392
411
  const updatedMessage = { ...message };
393
412
  updatedMessage.body = input;
394
- updatedMessage.attachments = inputModel?.attachments;
413
+ updatedMessage.attachments = inputModel.attachments;
395
414
  model.updateMessage!(id, updatedMessage);
396
415
  setEdit(false);
397
416
  };
@@ -411,12 +430,10 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
411
430
  <div ref={ref} data-index={props.index}>
412
431
  {edit && canEdit && inputModel ? (
413
432
  <ChatInput
414
- onSend={(input: string) => updateMessage(message.id, input)}
415
433
  onCancel={() => cancelEdition()}
416
434
  model={inputModel}
417
- hideIncludeSelection={true}
418
435
  chatCommandRegistry={props.chatCommandRegistry}
419
- documentManager={props.documentManager}
436
+ toolbarRegistry={props.inputToolbarRegistry}
420
437
  />
421
438
  ) : (
422
439
  <MarkdownRenderer
@@ -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
- const onSend = async (input: string) => {
26
- // send message to backend
27
- model.sendMessage({ body: input });
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
- documentManager={props.documentManager}
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 [copyStatus, setCopyStatus] = useState<CopyStatus>(CopyStatus.None);
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 { TooltippedButton } from '../mui-extras/tooltipped-button';
12
- import { IAttachment } from '../../types';
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(props: AttachButtonProps): JSX.Element {
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: props.documentManager
35
+ manager: model.documentManager
35
36
  });
36
37
  if (files.value) {
37
38
  files.value.forEach(file => {
38
39
  if (file.type !== 'directory') {
39
- props.onAttach({ type: 'file', value: file.path });
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 { TooltippedButton } from '../mui-extras/tooltipped-button';
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(props: CancelButtonProps): JSX.Element {
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.onCancel}
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>
@@ -0,0 +1,8 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ export { AttachButton } from './attach-button';
7
+ export { CancelButton } from './cancel-button';
8
+ export { SendButton } from './send-button';