@jupyter/chat 0.18.2 → 0.19.0-alpha.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 (74) hide show
  1. package/lib/components/attachments.js +47 -21
  2. package/lib/components/chat.d.ts +5 -0
  3. package/lib/components/chat.js +7 -6
  4. package/lib/components/code-blocks/code-toolbar.js +29 -10
  5. package/lib/components/code-blocks/copy-button.js +11 -3
  6. package/lib/components/index.d.ts +0 -1
  7. package/lib/components/index.js +0 -1
  8. package/lib/components/input/buttons/attach-button.js +7 -2
  9. package/lib/components/input/buttons/cancel-button.js +12 -10
  10. package/lib/components/input/buttons/index.d.ts +2 -0
  11. package/lib/components/input/buttons/index.js +2 -0
  12. package/lib/components/input/buttons/save-edit-button.d.ts +6 -0
  13. package/lib/components/input/buttons/save-edit-button.js +51 -0
  14. package/lib/components/input/buttons/send-button.d.ts +1 -1
  15. package/lib/components/input/buttons/send-button.js +35 -122
  16. package/lib/components/input/buttons/stop-button.d.ts +6 -0
  17. package/lib/components/input/buttons/stop-button.js +63 -0
  18. package/lib/components/input/chat-input.d.ts +15 -0
  19. package/lib/components/input/chat-input.js +109 -46
  20. package/lib/components/input/index.d.ts +1 -0
  21. package/lib/components/input/index.js +1 -0
  22. package/lib/components/input/toolbar-registry.d.ts +10 -0
  23. package/lib/components/input/toolbar-registry.js +10 -1
  24. package/lib/components/input/use-chat-commands.js +9 -4
  25. package/lib/components/input/writing-indicator.d.ts +15 -0
  26. package/lib/components/input/writing-indicator.js +50 -0
  27. package/lib/components/messages/header.d.ts +4 -0
  28. package/lib/components/messages/header.js +4 -0
  29. package/lib/components/messages/index.d.ts +0 -1
  30. package/lib/components/messages/index.js +0 -1
  31. package/lib/components/messages/message.js +1 -1
  32. package/lib/components/messages/messages.d.ts +5 -0
  33. package/lib/components/messages/messages.js +24 -14
  34. package/lib/components/messages/toolbar.js +37 -15
  35. package/lib/input-model.d.ts +14 -0
  36. package/lib/input-model.js +12 -4
  37. package/lib/model.d.ts +8 -0
  38. package/lib/model.js +6 -0
  39. package/lib/types.d.ts +4 -0
  40. package/lib/widgets/chat-widget.d.ts +4 -0
  41. package/lib/widgets/chat-widget.js +36 -11
  42. package/lib/widgets/multichat-panel.js +2 -1
  43. package/package.json +1 -1
  44. package/src/components/attachments.tsx +70 -33
  45. package/src/components/chat.tsx +13 -4
  46. package/src/components/code-blocks/code-toolbar.tsx +56 -28
  47. package/src/components/code-blocks/copy-button.tsx +21 -12
  48. package/src/components/index.ts +0 -1
  49. package/src/components/input/buttons/attach-button.tsx +8 -2
  50. package/src/components/input/buttons/cancel-button.tsx +20 -15
  51. package/src/components/input/buttons/index.ts +2 -0
  52. package/src/components/input/buttons/save-edit-button.tsx +75 -0
  53. package/src/components/input/buttons/send-button.tsx +50 -167
  54. package/src/components/input/buttons/stop-button.tsx +88 -0
  55. package/src/components/input/chat-input.tsx +188 -83
  56. package/src/components/input/index.ts +1 -0
  57. package/src/components/input/toolbar-registry.tsx +25 -1
  58. package/src/components/input/use-chat-commands.tsx +25 -5
  59. package/src/components/input/writing-indicator.tsx +83 -0
  60. package/src/components/messages/header.tsx +8 -0
  61. package/src/components/messages/index.ts +0 -1
  62. package/src/components/messages/message.tsx +1 -0
  63. package/src/components/messages/messages.tsx +63 -39
  64. package/src/components/messages/toolbar.tsx +51 -21
  65. package/src/input-model.ts +21 -0
  66. package/src/model.ts +12 -0
  67. package/src/types.ts +5 -0
  68. package/src/widgets/chat-widget.tsx +43 -12
  69. package/src/widgets/multichat-panel.tsx +2 -1
  70. package/style/chat.css +13 -141
  71. package/style/input.css +0 -58
  72. package/lib/components/messages/writers.d.ts +0 -16
  73. package/lib/components/messages/writers.js +0 -39
  74. package/src/components/messages/writers.tsx +0 -81
@@ -3,13 +3,11 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import CloseIcon from '@mui/icons-material/Close';
6
- import { Box } from '@mui/material';
6
+ import { Box, Button, Tooltip } from '@mui/material';
7
7
  import React, { useContext } from 'react';
8
8
  import { PathExt } from '@jupyterlab/coreutils';
9
9
  import { UUID } from '@lumino/coreutils';
10
- import { TooltippedButton } from './mui-extras/tooltipped-button';
11
10
  import { AttachmentOpenerContext } from '../context';
12
- const ATTACHMENTS_CLASS = 'jp-chat-attachments';
13
11
  const ATTACHMENT_CLASS = 'jp-chat-attachment';
14
12
  const ATTACHMENT_CLICKABLE_CLASS = 'jp-chat-attachment-clickable';
15
13
  const REMOVE_BUTTON_CLASS = 'jp-chat-attachment-remove';
@@ -41,7 +39,13 @@ function getAttachmentDisplayName(attachment) {
41
39
  * The Attachments component.
42
40
  */
43
41
  export function AttachmentPreviewList(props) {
44
- return (React.createElement(Box, { className: ATTACHMENTS_CLASS }, props.attachments.map(attachment => (React.createElement(AttachmentPreview, { key: `${PathExt.basename(attachment.value)}-${UUID.uuid4()}`, ...props, attachment: attachment })))));
42
+ return (React.createElement(Box, { sx: {
43
+ display: 'flex',
44
+ flexWrap: 'wrap',
45
+ gap: 1,
46
+ rowGap: 1,
47
+ columnGap: 2
48
+ } }, props.attachments.map(attachment => (React.createElement(AttachmentPreview, { key: `${PathExt.basename(attachment.value)}-${UUID.uuid4()}`, ...props, attachment: attachment })))));
45
49
  }
46
50
  /**
47
51
  * The Attachment component.
@@ -49,21 +53,43 @@ export function AttachmentPreviewList(props) {
49
53
  export function AttachmentPreview(props) {
50
54
  const remove_tooltip = 'Remove attachment';
51
55
  const attachmentOpenerRegistry = useContext(AttachmentOpenerContext);
52
- return (React.createElement(Box, { className: ATTACHMENT_CLASS },
53
- React.createElement("span", { className: (attachmentOpenerRegistry === null || attachmentOpenerRegistry === void 0 ? void 0 : attachmentOpenerRegistry.get(props.attachment.type))
54
- ? ATTACHMENT_CLICKABLE_CLASS
55
- : '', onClick: () => {
56
- var _a;
57
- return (_a = attachmentOpenerRegistry === null || attachmentOpenerRegistry === void 0 ? void 0 : attachmentOpenerRegistry.get(props.attachment.type)) === null || _a === void 0 ? void 0 : _a(props.attachment);
58
- } }, getAttachmentDisplayName(props.attachment)),
59
- props.onRemove && (React.createElement(TooltippedButton, { onClick: () => props.onRemove(props.attachment), tooltip: remove_tooltip, buttonProps: {
60
- size: 'small',
61
- title: remove_tooltip,
62
- className: REMOVE_BUTTON_CLASS
63
- }, sx: {
64
- minWidth: 'unset',
65
- padding: '0',
66
- color: 'inherit'
67
- } },
68
- React.createElement(CloseIcon, null)))));
56
+ const isClickable = !!(attachmentOpenerRegistry === null || attachmentOpenerRegistry === void 0 ? void 0 : attachmentOpenerRegistry.get(props.attachment.type));
57
+ return (React.createElement(Box, { className: ATTACHMENT_CLASS, sx: {
58
+ border: '1px solid var(--jp-border-color1)',
59
+ borderRadius: '2px',
60
+ px: 1,
61
+ py: 0.5,
62
+ backgroundColor: 'var(--jp-layout-color2)',
63
+ display: 'flex',
64
+ alignItems: 'center',
65
+ gap: 0.5,
66
+ fontSize: '0.8125rem'
67
+ } },
68
+ React.createElement(Tooltip, { title: props.attachment.value, placement: "top", arrow: true },
69
+ React.createElement(Box, { className: (attachmentOpenerRegistry === null || attachmentOpenerRegistry === void 0 ? void 0 : attachmentOpenerRegistry.get(props.attachment.type))
70
+ ? ATTACHMENT_CLICKABLE_CLASS
71
+ : '', component: "span", onClick: () => {
72
+ var _a;
73
+ return (_a = attachmentOpenerRegistry === null || attachmentOpenerRegistry === void 0 ? void 0 : attachmentOpenerRegistry.get(props.attachment.type)) === null || _a === void 0 ? void 0 : _a(props.attachment);
74
+ }, sx: {
75
+ cursor: isClickable ? 'pointer' : 'default',
76
+ '&:hover': isClickable
77
+ ? {
78
+ textDecoration: 'underline'
79
+ }
80
+ : {}
81
+ } }, getAttachmentDisplayName(props.attachment))),
82
+ props.onRemove && (React.createElement(Tooltip, { title: remove_tooltip, placement: "top", arrow: true },
83
+ React.createElement("span", null,
84
+ React.createElement(Button, { onClick: () => props.onRemove(props.attachment), size: "small", className: REMOVE_BUTTON_CLASS, "aria-label": remove_tooltip, sx: {
85
+ minWidth: 'unset',
86
+ padding: 0,
87
+ lineHeight: 0,
88
+ color: 'var(--jp-ui-font-color2)',
89
+ '&:hover': {
90
+ color: 'var(--jp-ui-font-color0)',
91
+ backgroundColor: 'transparent'
92
+ }
93
+ } },
94
+ React.createElement(CloseIcon, { fontSize: "small" })))))));
69
95
  }
@@ -4,6 +4,7 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
4
  import { IInputToolbarRegistry } from './input';
5
5
  import { IChatModel } from '../model';
6
6
  import { IAttachmentOpenerRegistry, IChatCommandRegistry, IMessageFooterRegistry } from '../registers';
7
+ import { ChatArea } from '../types';
7
8
  export declare function ChatBody(props: Chat.IChatBodyProps): JSX.Element;
8
9
  export declare function Chat(props: Chat.IOptions): JSX.Element;
9
10
  /**
@@ -42,6 +43,10 @@ export declare namespace Chat {
42
43
  * The welcome message.
43
44
  */
44
45
  welcomeMessage?: string;
46
+ /**
47
+ * The area where the chat is displayed.
48
+ */
49
+ area?: ChatArea;
45
50
  }
46
51
  /**
47
52
  * The options to build the Chat UI.
@@ -17,15 +17,16 @@ export function ChatBody(props) {
17
17
  if (!inputToolbarRegistry) {
18
18
  inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
19
19
  }
20
+ // const horizontalPadding = props.area === 'main' ? 8 : 4;
21
+ const horizontalPadding = 4;
20
22
  return (React.createElement(AttachmentOpenerContext.Provider, { value: props.attachmentOpenerRegistry },
21
- React.createElement(ChatMessages, { rmRegistry: props.rmRegistry, model: model, chatCommandRegistry: props.chatCommandRegistry, inputToolbarRegistry: inputToolbarRegistry, messageFooterRegistry: props.messageFooterRegistry, welcomeMessage: props.welcomeMessage }),
23
+ React.createElement(ChatMessages, { rmRegistry: props.rmRegistry, model: model, chatCommandRegistry: props.chatCommandRegistry, inputToolbarRegistry: inputToolbarRegistry, messageFooterRegistry: props.messageFooterRegistry, welcomeMessage: props.welcomeMessage, area: props.area }),
22
24
  React.createElement(ChatInput, { sx: {
23
- paddingLeft: 4,
24
- paddingRight: 4,
25
+ paddingLeft: horizontalPadding,
26
+ paddingRight: horizontalPadding,
25
27
  paddingTop: 0,
26
- paddingBottom: 0,
27
- borderTop: '1px solid var(--jp-border-color1)'
28
- }, model: model.input, chatCommandRegistry: props.chatCommandRegistry, toolbarRegistry: inputToolbarRegistry })));
28
+ paddingBottom: 0
29
+ }, model: model.input, chatCommandRegistry: props.chatCommandRegistry, toolbarRegistry: inputToolbarRegistry, area: props.area, chatModel: model })));
29
30
  }
30
31
  export function Chat(props) {
31
32
  var _a;
@@ -3,10 +3,9 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import { addAboveIcon, addBelowIcon } from '@jupyterlab/ui-components';
6
- import { Box } from '@mui/material';
6
+ import { Box, IconButton, Tooltip } from '@mui/material';
7
7
  import React, { useEffect, useState } from 'react';
8
8
  import { CopyButton } from './copy-button';
9
- import { TooltippedIconButton } from '../mui-extras/tooltipped-icon-button';
10
9
  import { replaceCellIcon } from '../../icons';
11
10
  const CODE_TOOLBAR_CLASS = 'jp-chat-code-toolbar';
12
11
  const CODE_TOOLBAR_ITEM_CLASS = 'jp-chat-code-toolbar-item';
@@ -53,8 +52,7 @@ export function CodeToolbar(props) {
53
52
  alignItems: 'center',
54
53
  padding: '2px 2px',
55
54
  marginBottom: '1em',
56
- border: '1px solid var(--jp-cell-editor-border-color)',
57
- borderTop: 'none'
55
+ border: 'none'
58
56
  }, className: CODE_TOOLBAR_CLASS },
59
57
  React.createElement(InsertAboveButton, { ...toolbarBtnProps, className: CODE_TOOLBAR_ITEM_CLASS }),
60
58
  React.createElement(InsertBelowButton, { ...toolbarBtnProps, className: CODE_TOOLBAR_ITEM_CLASS }),
@@ -65,15 +63,29 @@ function InsertAboveButton(props) {
65
63
  const tooltip = props.activeCellAvailable
66
64
  ? 'Insert above active cell'
67
65
  : 'Insert above active cell (no active cell)';
68
- return (React.createElement(TooltippedIconButton, { className: props.className, tooltip: tooltip, onClick: () => { var _a; return (_a = props.activeCellManager) === null || _a === void 0 ? void 0 : _a.insertAbove(props.content); }, disabled: !props.activeCellAvailable },
69
- React.createElement(addAboveIcon.react, { height: "16px", width: "16px" })));
66
+ return (React.createElement(Tooltip, { title: tooltip, placement: "top", arrow: true },
67
+ React.createElement("span", null,
68
+ React.createElement(IconButton, { className: props.className, onClick: () => { var _a; return (_a = props.activeCellManager) === null || _a === void 0 ? void 0 : _a.insertAbove(props.content); }, disabled: !props.activeCellAvailable, "aria-label": tooltip, sx: {
69
+ lineHeight: 0,
70
+ '&.Mui-disabled': {
71
+ opacity: 0.5
72
+ }
73
+ } },
74
+ React.createElement(addAboveIcon.react, { height: "16px", width: "16px" })))));
70
75
  }
71
76
  function InsertBelowButton(props) {
72
77
  const tooltip = props.activeCellAvailable
73
78
  ? 'Insert below active cell'
74
79
  : 'Insert below active cell (no active cell)';
75
- return (React.createElement(TooltippedIconButton, { className: props.className, tooltip: tooltip, disabled: !props.activeCellAvailable, onClick: () => { var _a; return (_a = props.activeCellManager) === null || _a === void 0 ? void 0 : _a.insertBelow(props.content); } },
76
- React.createElement(addBelowIcon.react, { height: "16px", width: "16px" })));
80
+ return (React.createElement(Tooltip, { title: tooltip, placement: "top", arrow: true },
81
+ React.createElement("span", null,
82
+ React.createElement(IconButton, { className: props.className, disabled: !props.activeCellAvailable, onClick: () => { var _a; return (_a = props.activeCellManager) === null || _a === void 0 ? void 0 : _a.insertBelow(props.content); }, "aria-label": tooltip, sx: {
83
+ lineHeight: 0,
84
+ '&.Mui-disabled': {
85
+ opacity: 0.5
86
+ }
87
+ } },
88
+ React.createElement(addBelowIcon.react, { height: "16px", width: "16px" })))));
77
89
  }
78
90
  function ReplaceButton(props) {
79
91
  var _a, _b;
@@ -99,6 +111,13 @@ function ReplaceButton(props) {
99
111
  (_c = props.activeCellManager) === null || _c === void 0 ? void 0 : _c.replace(props.content);
100
112
  }
101
113
  };
102
- return (React.createElement(TooltippedIconButton, { className: props.className, tooltip: tooltip, disabled: disabled, onClick: replace },
103
- React.createElement(replaceCellIcon.react, { height: "16px", width: "16px" })));
114
+ return (React.createElement(Tooltip, { title: tooltip, placement: "top", arrow: true },
115
+ React.createElement("span", null,
116
+ React.createElement(IconButton, { className: props.className, disabled: disabled, onClick: replace, "aria-label": tooltip, sx: {
117
+ lineHeight: 0,
118
+ '&.Mui-disabled': {
119
+ opacity: 0.5
120
+ }
121
+ } },
122
+ React.createElement(replaceCellIcon.react, { height: "16px", width: "16px" })))));
104
123
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import React, { useState, useCallback, useRef } from 'react';
6
6
  import { copyIcon } from '@jupyterlab/ui-components';
7
- import { TooltippedIconButton } from '../mui-extras/tooltipped-icon-button';
7
+ import { IconButton, Tooltip } from '@mui/material';
8
8
  var CopyStatus;
9
9
  (function (CopyStatus) {
10
10
  CopyStatus[CopyStatus["None"] = 0] = "None";
@@ -41,6 +41,14 @@ export function CopyButton(props) {
41
41
  }
42
42
  timeoutId.current = window.setTimeout(() => setCopyStatus(CopyStatus.None), 1000);
43
43
  }, [copyStatus, props.value]);
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" },
45
- React.createElement(copyIcon.react, { height: "16px", width: "16px" })));
44
+ const tooltip = COPYBTN_TEXT_BY_STATUS[copyStatus];
45
+ return (React.createElement(Tooltip, { title: tooltip, placement: "top", arrow: true },
46
+ React.createElement("span", null,
47
+ React.createElement(IconButton, { disabled: isCopyDisabled, className: props.className, onClick: copy, "aria-label": "Copy to clipboard", sx: {
48
+ lineHeight: 0,
49
+ '&.Mui-disabled': {
50
+ opacity: 0.5
51
+ }
52
+ } },
53
+ React.createElement(copyIcon.react, { height: "16px", width: "16px" })))));
46
54
  }
@@ -4,5 +4,4 @@ export * from './code-blocks';
4
4
  export * from './input';
5
5
  export * from './jl-theme-provider';
6
6
  export * from './messages';
7
- export * from './mui-extras';
8
7
  export * from './scroll-container';
@@ -8,5 +8,4 @@ export * from './code-blocks';
8
8
  export * from './input';
9
9
  export * from './jl-theme-provider';
10
10
  export * from './messages';
11
- export * from './mui-extras';
12
11
  export * from './scroll-container';
@@ -40,9 +40,14 @@ export function AttachButton(props) {
40
40
  };
41
41
  return (React.createElement(TooltippedButton, { onClick: onclick, tooltip: tooltip, buttonProps: {
42
42
  size: 'small',
43
- variant: 'contained',
43
+ variant: 'text',
44
44
  title: tooltip,
45
45
  className: ATTACH_BUTTON_CLASS
46
+ }, sx: {
47
+ width: '24px',
48
+ height: '24px',
49
+ minWidth: '24px',
50
+ color: 'gray'
46
51
  } },
47
- React.createElement(AttachFileIcon, null)));
52
+ React.createElement(AttachFileIcon, { sx: { fontSize: '16px ' } })));
48
53
  }
@@ -2,9 +2,9 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- import CancelIcon from '@mui/icons-material/Cancel';
5
+ import CloseIcon from '@mui/icons-material/Close';
6
+ import { IconButton, Tooltip } from '@mui/material';
6
7
  import React from 'react';
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.
@@ -13,12 +13,14 @@ export function CancelButton(props) {
13
13
  if (!props.model.cancel) {
14
14
  return React.createElement(React.Fragment, null);
15
15
  }
16
- const tooltip = 'Cancel edition';
17
- return (React.createElement(TooltippedButton, { onClick: props.model.cancel, tooltip: tooltip, buttonProps: {
18
- size: 'small',
19
- variant: 'contained',
20
- title: tooltip,
21
- className: CANCEL_BUTTON_CLASS
22
- } },
23
- React.createElement(CancelIcon, null)));
16
+ const tooltip = 'Cancel editing';
17
+ return (React.createElement(Tooltip, { title: tooltip, placement: "top", arrow: true },
18
+ React.createElement("span", null,
19
+ React.createElement(IconButton, { onClick: props.model.cancel, className: CANCEL_BUTTON_CLASS, "aria-label": tooltip, sx: {
20
+ width: '24px',
21
+ height: '24px',
22
+ padding: 0,
23
+ lineHeight: 0
24
+ } },
25
+ React.createElement(CloseIcon, { sx: { fontSize: '16px' } })))));
24
26
  }
@@ -1,3 +1,5 @@
1
1
  export { AttachButton } from './attach-button';
2
2
  export { CancelButton } from './cancel-button';
3
+ export { SaveEditButton } from './save-edit-button';
3
4
  export { SendButton } from './send-button';
5
+ export { StopButton } from './stop-button';
@@ -4,4 +4,6 @@
4
4
  */
5
5
  export { AttachButton } from './attach-button';
6
6
  export { CancelButton } from './cancel-button';
7
+ export { SaveEditButton } from './save-edit-button';
7
8
  export { SendButton } from './send-button';
9
+ export { StopButton } from './stop-button';
@@ -0,0 +1,6 @@
1
+ /// <reference types="react" />
2
+ import { InputToolbarRegistry } from '../toolbar-registry';
3
+ /**
4
+ * The save edit button.
5
+ */
6
+ export declare function SaveEditButton(props: InputToolbarRegistry.IToolbarItemProps): JSX.Element;
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import CheckIcon from '@mui/icons-material/Check';
6
+ import { IconButton, Tooltip } from '@mui/material';
7
+ import React, { useEffect, useState } from 'react';
8
+ const SAVE_EDIT_BUTTON_CLASS = 'jp-chat-save-edit-button';
9
+ /**
10
+ * The save edit button.
11
+ */
12
+ export function SaveEditButton(props) {
13
+ const { model, chatCommandRegistry, edit } = props;
14
+ // Don't show this button when not in edit mode
15
+ if (!edit) {
16
+ return React.createElement(React.Fragment, null);
17
+ }
18
+ const [disabled, setDisabled] = useState(false);
19
+ const tooltip = 'Save edits';
20
+ useEffect(() => {
21
+ var _a;
22
+ const inputChanged = () => {
23
+ const inputExist = !!model.value.trim() || model.attachments.length;
24
+ setDisabled(!inputExist);
25
+ };
26
+ model.valueChanged.connect(inputChanged);
27
+ (_a = model.attachmentsChanged) === null || _a === void 0 ? void 0 : _a.connect(inputChanged);
28
+ inputChanged();
29
+ return () => {
30
+ var _a;
31
+ model.valueChanged.disconnect(inputChanged);
32
+ (_a = model.attachmentsChanged) === null || _a === void 0 ? void 0 : _a.disconnect(inputChanged);
33
+ };
34
+ }, [model]);
35
+ async function save() {
36
+ await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
37
+ model.send(model.value);
38
+ }
39
+ return (React.createElement(Tooltip, { title: tooltip, placement: "top", arrow: true },
40
+ React.createElement("span", null,
41
+ React.createElement(IconButton, { onClick: save, disabled: disabled, className: SAVE_EDIT_BUTTON_CLASS, "aria-label": tooltip, sx: {
42
+ width: '24px',
43
+ height: '24px',
44
+ padding: 0,
45
+ lineHeight: 0,
46
+ '&.Mui-disabled': {
47
+ opacity: 0.5
48
+ }
49
+ } },
50
+ React.createElement(CheckIcon, { sx: { fontSize: '16px' } })))));
51
+ }
@@ -1,6 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import { InputToolbarRegistry } from '../toolbar-registry';
3
3
  /**
4
- * The send button, with optional 'include selection' menu.
4
+ * The send button.
5
5
  */
6
6
  export declare function SendButton(props: InputToolbarRegistry.IToolbarItemProps): JSX.Element;
@@ -2,35 +2,21 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
6
- import SendIcon from '@mui/icons-material/Send';
7
- import { Box, Menu, MenuItem, Typography } from '@mui/material';
8
- import React, { useCallback, useEffect, useState } from 'react';
9
- import { TooltippedButton } from '../../mui-extras/tooltipped-button';
10
- import { includeSelectionIcon } from '../../../icons';
5
+ import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
6
+ import { Button, Tooltip } from '@mui/material';
7
+ import React, { useEffect, useState } from 'react';
11
8
  const SEND_BUTTON_CLASS = 'jp-chat-send-button';
12
- const SEND_INCLUDE_OPENER_CLASS = 'jp-chat-send-include-opener';
13
- const SEND_INCLUDE_LI_CLASS = 'jp-chat-send-include';
14
9
  /**
15
- * The send button, with optional 'include selection' menu.
10
+ * The send button.
16
11
  */
17
12
  export function SendButton(props) {
18
- const { model, chatCommandRegistry } = props;
19
- const { activeCellManager, selectionWatcher } = model;
20
- const hideIncludeSelection = !activeCellManager || !selectionWatcher;
21
- const [menuAnchorEl, setMenuAnchorEl] = useState(null);
22
- const [menuOpen, setMenuOpen] = useState(false);
13
+ const { model, chatModel, chatCommandRegistry, edit } = props;
14
+ // Don't show this button when in edit mode
15
+ if (edit) {
16
+ return React.createElement(React.Fragment, null);
17
+ }
23
18
  const [disabled, setDisabled] = useState(false);
24
19
  const [tooltip, setTooltip] = useState('');
25
- const openMenu = useCallback((el) => {
26
- setMenuAnchorEl(el);
27
- setMenuOpen(true);
28
- }, []);
29
- const closeMenu = useCallback(() => {
30
- setMenuOpen(false);
31
- }, []);
32
- const [selectionTooltip, setSelectionTooltip] = useState('');
33
- const [disableInclude, setDisableInclude] = useState(true);
34
20
  useEffect(() => {
35
21
  var _a;
36
22
  const inputChanged = () => {
@@ -54,110 +40,37 @@ export function SendButton(props) {
54
40
  (_b = model.configChanged) === null || _b === void 0 ? void 0 : _b.disconnect(configChanged);
55
41
  };
56
42
  }, [model]);
57
- useEffect(() => {
58
- /**
59
- * Enable or disable the include selection button, and adapt the tooltip.
60
- */
61
- const toggleIncludeState = () => {
62
- setDisableInclude(!((selectionWatcher === null || selectionWatcher === void 0 ? void 0 : selectionWatcher.selection) || (activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.available)));
63
- const tooltip = (selectionWatcher === null || selectionWatcher === void 0 ? void 0 : selectionWatcher.selection)
64
- ? `${selectionWatcher.selection.numLines} line(s) selected`
65
- : (activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.available)
66
- ? 'Code from 1 active cell'
67
- : 'No selection or active cell';
68
- setSelectionTooltip(tooltip);
69
- };
70
- if (!hideIncludeSelection) {
71
- selectionWatcher === null || selectionWatcher === void 0 ? void 0 : selectionWatcher.selectionChanged.connect(toggleIncludeState);
72
- activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.availabilityChanged.connect(toggleIncludeState);
73
- toggleIncludeState();
74
- }
75
- return () => {
76
- selectionWatcher === null || selectionWatcher === void 0 ? void 0 : selectionWatcher.selectionChanged.disconnect(toggleIncludeState);
77
- activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.availabilityChanged.disconnect(toggleIncludeState);
78
- };
79
- }, [activeCellManager, selectionWatcher]);
80
43
  async function send() {
44
+ // Run all command providers
81
45
  await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
82
- model.send(model.value);
83
- }
84
- async function sendWithSelection() {
85
- let source = '';
86
- // Run all chat command providers
87
- await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
88
- let language;
89
- if (selectionWatcher === null || selectionWatcher === void 0 ? void 0 : selectionWatcher.selection) {
90
- // Append the selected text if exists.
91
- source = selectionWatcher.selection.text;
92
- language = selectionWatcher.selection.language;
93
- }
94
- else if (activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.available) {
95
- // Append the active cell content if exists.
96
- const content = activeCellManager.getContent(false);
97
- source = content.source;
98
- language = content === null || content === void 0 ? void 0 : content.language;
99
- }
100
- let content = model.value;
101
- if (source) {
102
- content += `
103
-
104
- \`\`\`${language !== null && language !== void 0 ? language : ''}
105
- ${source}
106
- \`\`\`
107
- `;
108
- }
109
- model.send(content);
110
- closeMenu();
46
+ // send message through chat model
47
+ await (chatModel === null || chatModel === void 0 ? void 0 : chatModel.sendMessage({
48
+ body: model.value
49
+ }));
50
+ // clear input model value & re-focus
111
51
  model.value = '';
52
+ model.focus();
112
53
  }
113
- return (React.createElement(React.Fragment, null,
114
- React.createElement(TooltippedButton, { onClick: send, disabled: disabled, tooltip: tooltip, buttonProps: {
115
- size: 'small',
116
- title: tooltip,
117
- variant: 'contained',
118
- className: SEND_BUTTON_CLASS
119
- } },
120
- React.createElement(SendIcon, null)),
121
- !hideIncludeSelection && (React.createElement(React.Fragment, null,
122
- React.createElement(TooltippedButton, { onClick: e => {
123
- openMenu(e.currentTarget);
124
- }, disabled: disabled, tooltip: "", buttonProps: {
125
- variant: 'contained',
126
- onKeyDown: e => {
127
- if (e.key !== 'Enter' && e.key !== ' ') {
128
- return;
129
- }
130
- openMenu(e.currentTarget);
131
- // stopping propagation of this event prevents the prompt from being
132
- // sent when the dropdown button is selected and clicked via 'Enter'.
133
- e.stopPropagation();
134
- },
135
- className: SEND_INCLUDE_OPENER_CLASS
136
- } },
137
- React.createElement(KeyboardArrowDown, null)),
138
- React.createElement(Menu, { open: menuOpen, onClose: closeMenu, anchorEl: menuAnchorEl, anchorOrigin: {
139
- vertical: 'top',
140
- horizontal: 'right'
141
- }, transformOrigin: {
142
- vertical: 'bottom',
143
- horizontal: 'right'
144
- }, sx: {
145
- '& .MuiMenuItem-root': {
146
- display: 'flex',
147
- alignItems: 'center',
148
- gap: '8px'
54
+ return (React.createElement(Tooltip, { title: tooltip, placement: "top", arrow: true },
55
+ React.createElement("span", null,
56
+ React.createElement(Button, { onClick: send, disabled: disabled, size: "small", variant: "contained", className: SEND_BUTTON_CLASS, "aria-label": tooltip, sx: {
57
+ backgroundColor: 'var(--jp-brand-color1)',
58
+ color: 'white',
59
+ minWidth: '24px',
60
+ width: '24px',
61
+ height: '24px',
62
+ borderRadius: '4px',
63
+ boxShadow: 'none',
64
+ lineHeight: 0,
65
+ '&:hover': {
66
+ backgroundColor: 'var(--jp-brand-color0)',
67
+ boxShadow: 'none'
149
68
  },
150
- '& svg': {
151
- lineHeight: 0
69
+ '&.Mui-disabled': {
70
+ backgroundColor: 'var(--jp-border-color2)',
71
+ color: 'var(--jp-ui-font-color3)',
72
+ opacity: 0.5
152
73
  }
153
74
  } },
154
- React.createElement(MenuItem, { onClick: e => {
155
- sendWithSelection();
156
- // prevent sending second message with no selection
157
- e.stopPropagation();
158
- }, disabled: disableInclude, className: SEND_INCLUDE_LI_CLASS },
159
- React.createElement(includeSelectionIcon.react, null),
160
- React.createElement(Box, null,
161
- React.createElement(Typography, { display: "block" }, "Send message with selection"),
162
- React.createElement(Typography, { display: "block", sx: { opacity: 0.618 } }, selectionTooltip))))))));
75
+ React.createElement(ArrowUpwardIcon, { sx: { fontSize: '16px' } })))));
163
76
  }
@@ -0,0 +1,6 @@
1
+ /// <reference types="react" />
2
+ import { InputToolbarRegistry } from '../toolbar-registry';
3
+ /**
4
+ * The stop button.
5
+ */
6
+ export declare function StopButton(props: InputToolbarRegistry.IToolbarItemProps): JSX.Element;
@@ -0,0 +1,63 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import StopIcon from '@mui/icons-material/Stop';
6
+ import { Button, Tooltip } from '@mui/material';
7
+ import React, { useEffect, useState } from 'react';
8
+ const STOP_BUTTON_CLASS = 'jp-chat-stop-button';
9
+ /**
10
+ * The stop button.
11
+ */
12
+ export function StopButton(props) {
13
+ const { chatModel } = props;
14
+ const [disabled, setDisabled] = useState(true);
15
+ const tooltip = 'Stop generating';
16
+ useEffect(() => {
17
+ var _a;
18
+ if (!chatModel) {
19
+ setDisabled(true);
20
+ return;
21
+ }
22
+ const checkWriters = () => {
23
+ // Check if there's at least one AI agent writer (bot)
24
+ const hasAIWriter = chatModel.writers.some(writer => writer.user.bot);
25
+ setDisabled(!hasAIWriter);
26
+ };
27
+ // Check initially
28
+ checkWriters();
29
+ // Listen to writers changes
30
+ (_a = chatModel.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(checkWriters);
31
+ return () => {
32
+ var _a;
33
+ (_a = chatModel.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(checkWriters);
34
+ };
35
+ }, [chatModel]);
36
+ function stop() {
37
+ // TODO: Implement stop functionality
38
+ // This will need to be implemented based on how the chat model handles stopping AI responses
39
+ console.log('Stop button clicked');
40
+ }
41
+ return (React.createElement(Tooltip, { title: tooltip, placement: "top", arrow: true },
42
+ React.createElement("span", null,
43
+ React.createElement(Button, { onClick: stop, disabled: disabled, size: "small", variant: "contained", className: STOP_BUTTON_CLASS, "aria-label": tooltip, sx: {
44
+ backgroundColor: 'var(--jp-error-color1)',
45
+ color: 'white',
46
+ minWidth: '24px',
47
+ width: '24px',
48
+ height: '24px',
49
+ borderRadius: '4px',
50
+ boxShadow: 'none',
51
+ lineHeight: 0,
52
+ '&:hover': {
53
+ backgroundColor: 'var(--jp-error-color0)',
54
+ boxShadow: 'none'
55
+ },
56
+ '&.Mui-disabled': {
57
+ backgroundColor: 'var(--jp-border-color2)',
58
+ color: 'var(--jp-ui-font-color3)',
59
+ opacity: 0.5
60
+ }
61
+ } },
62
+ React.createElement(StopIcon, { sx: { fontSize: '16px' } })))));
63
+ }