@jupyter/chat 0.18.2 → 0.19.0-alpha.1

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 (73) hide show
  1. package/lib/active-cell-manager.js +4 -2
  2. package/lib/components/attachments.js +47 -21
  3. package/lib/components/chat.d.ts +5 -0
  4. package/lib/components/chat.js +7 -6
  5. package/lib/components/code-blocks/code-toolbar.js +29 -10
  6. package/lib/components/code-blocks/copy-button.js +11 -3
  7. package/lib/components/input/buttons/attach-button.js +7 -2
  8. package/lib/components/input/buttons/cancel-button.js +12 -10
  9. package/lib/components/input/buttons/index.d.ts +2 -0
  10. package/lib/components/input/buttons/index.js +2 -0
  11. package/lib/components/input/buttons/save-edit-button.d.ts +6 -0
  12. package/lib/components/input/buttons/save-edit-button.js +51 -0
  13. package/lib/components/input/buttons/send-button.d.ts +1 -1
  14. package/lib/components/input/buttons/send-button.js +35 -122
  15. package/lib/components/input/buttons/stop-button.d.ts +6 -0
  16. package/lib/components/input/buttons/stop-button.js +63 -0
  17. package/lib/components/input/chat-input.d.ts +15 -0
  18. package/lib/components/input/chat-input.js +109 -46
  19. package/lib/components/input/index.d.ts +1 -0
  20. package/lib/components/input/index.js +1 -0
  21. package/lib/components/input/toolbar-registry.d.ts +10 -0
  22. package/lib/components/input/toolbar-registry.js +10 -1
  23. package/lib/components/input/use-chat-commands.js +9 -4
  24. package/lib/components/input/writing-indicator.d.ts +15 -0
  25. package/lib/components/input/writing-indicator.js +50 -0
  26. package/lib/components/messages/header.d.ts +4 -0
  27. package/lib/components/messages/header.js +4 -0
  28. package/lib/components/messages/index.d.ts +0 -1
  29. package/lib/components/messages/index.js +0 -1
  30. package/lib/components/messages/message.js +1 -1
  31. package/lib/components/messages/messages.d.ts +5 -0
  32. package/lib/components/messages/messages.js +24 -14
  33. package/lib/components/messages/toolbar.js +37 -15
  34. package/lib/input-model.d.ts +14 -0
  35. package/lib/input-model.js +12 -4
  36. package/lib/model.d.ts +8 -0
  37. package/lib/model.js +6 -0
  38. package/lib/types.d.ts +4 -0
  39. package/lib/widgets/chat-widget.d.ts +4 -0
  40. package/lib/widgets/chat-widget.js +36 -11
  41. package/lib/widgets/multichat-panel.js +2 -1
  42. package/package.json +1 -1
  43. package/src/active-cell-manager.ts +3 -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/input/buttons/attach-button.tsx +8 -2
  49. package/src/components/input/buttons/cancel-button.tsx +20 -15
  50. package/src/components/input/buttons/index.ts +2 -0
  51. package/src/components/input/buttons/save-edit-button.tsx +75 -0
  52. package/src/components/input/buttons/send-button.tsx +50 -167
  53. package/src/components/input/buttons/stop-button.tsx +88 -0
  54. package/src/components/input/chat-input.tsx +188 -83
  55. package/src/components/input/index.ts +1 -0
  56. package/src/components/input/toolbar-registry.tsx +25 -1
  57. package/src/components/input/use-chat-commands.tsx +25 -5
  58. package/src/components/input/writing-indicator.tsx +83 -0
  59. package/src/components/messages/header.tsx +8 -0
  60. package/src/components/messages/index.ts +0 -1
  61. package/src/components/messages/message.tsx +1 -0
  62. package/src/components/messages/messages.tsx +63 -39
  63. package/src/components/messages/toolbar.tsx +51 -21
  64. package/src/input-model.ts +21 -0
  65. package/src/model.ts +12 -0
  66. package/src/types.ts +5 -0
  67. package/src/widgets/chat-widget.tsx +43 -12
  68. package/src/widgets/multichat-panel.tsx +2 -1
  69. package/style/chat.css +13 -141
  70. package/style/input.css +0 -58
  71. package/lib/components/messages/writers.d.ts +0 -16
  72. package/lib/components/messages/writers.js +0 -39
  73. package/src/components/messages/writers.tsx +0 -81
@@ -3,11 +3,11 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
 
6
- import CancelIcon from '@mui/icons-material/Cancel';
6
+ import CloseIcon from '@mui/icons-material/Close';
7
+ import { IconButton, Tooltip } from '@mui/material';
7
8
  import React from 'react';
8
9
 
9
10
  import { InputToolbarRegistry } from '../toolbar-registry';
10
- import { TooltippedButton } from '../../mui-extras/tooltipped-button';
11
11
 
12
12
  const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
13
13
 
@@ -20,19 +20,24 @@ export function CancelButton(
20
20
  if (!props.model.cancel) {
21
21
  return <></>;
22
22
  }
23
- const tooltip = 'Cancel edition';
23
+ const tooltip = 'Cancel editing';
24
24
  return (
25
- <TooltippedButton
26
- onClick={props.model.cancel}
27
- tooltip={tooltip}
28
- buttonProps={{
29
- size: 'small',
30
- variant: 'contained',
31
- title: tooltip,
32
- className: CANCEL_BUTTON_CLASS
33
- }}
34
- >
35
- <CancelIcon />
36
- </TooltippedButton>
25
+ <Tooltip title={tooltip} placement="top" arrow>
26
+ <span>
27
+ <IconButton
28
+ onClick={props.model.cancel}
29
+ className={CANCEL_BUTTON_CLASS}
30
+ aria-label={tooltip}
31
+ sx={{
32
+ width: '24px',
33
+ height: '24px',
34
+ padding: 0,
35
+ lineHeight: 0
36
+ }}
37
+ >
38
+ <CloseIcon sx={{ fontSize: '16px' }} />
39
+ </IconButton>
40
+ </span>
41
+ </Tooltip>
37
42
  );
38
43
  }
@@ -5,4 +5,6 @@
5
5
 
6
6
  export { AttachButton } from './attach-button';
7
7
  export { CancelButton } from './cancel-button';
8
+ export { SaveEditButton } from './save-edit-button';
8
9
  export { SendButton } from './send-button';
10
+ export { StopButton } from './stop-button';
@@ -0,0 +1,75 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import CheckIcon from '@mui/icons-material/Check';
7
+ import { IconButton, Tooltip } from '@mui/material';
8
+ import React, { useEffect, useState } from 'react';
9
+
10
+ import { InputToolbarRegistry } from '../toolbar-registry';
11
+
12
+ const SAVE_EDIT_BUTTON_CLASS = 'jp-chat-save-edit-button';
13
+
14
+ /**
15
+ * The save edit button.
16
+ */
17
+ export function SaveEditButton(
18
+ props: InputToolbarRegistry.IToolbarItemProps
19
+ ): JSX.Element {
20
+ const { model, chatCommandRegistry, edit } = props;
21
+
22
+ // Don't show this button when not in edit mode
23
+ if (!edit) {
24
+ return <></>;
25
+ }
26
+
27
+ const [disabled, setDisabled] = useState(false);
28
+ const tooltip = 'Save edits';
29
+
30
+ useEffect(() => {
31
+ const inputChanged = () => {
32
+ const inputExist = !!model.value.trim() || model.attachments.length;
33
+ setDisabled(!inputExist);
34
+ };
35
+
36
+ model.valueChanged.connect(inputChanged);
37
+ model.attachmentsChanged?.connect(inputChanged);
38
+
39
+ inputChanged();
40
+
41
+ return () => {
42
+ model.valueChanged.disconnect(inputChanged);
43
+ model.attachmentsChanged?.disconnect(inputChanged);
44
+ };
45
+ }, [model]);
46
+
47
+ async function save() {
48
+ await chatCommandRegistry?.onSubmit(model);
49
+ model.send(model.value);
50
+ }
51
+
52
+ return (
53
+ <Tooltip title={tooltip} placement="top" arrow>
54
+ <span>
55
+ <IconButton
56
+ onClick={save}
57
+ disabled={disabled}
58
+ className={SAVE_EDIT_BUTTON_CLASS}
59
+ aria-label={tooltip}
60
+ sx={{
61
+ width: '24px',
62
+ height: '24px',
63
+ padding: 0,
64
+ lineHeight: 0,
65
+ '&.Mui-disabled': {
66
+ opacity: 0.5
67
+ }
68
+ }}
69
+ >
70
+ <CheckIcon sx={{ fontSize: '16px' }} />
71
+ </IconButton>
72
+ </span>
73
+ </Tooltip>
74
+ );
75
+ }
@@ -3,47 +3,31 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
 
6
- import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
7
- import SendIcon from '@mui/icons-material/Send';
8
- import { Box, Menu, MenuItem, Typography } from '@mui/material';
9
- import React, { useCallback, useEffect, useState } from 'react';
6
+ import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
7
+ import { Button, Tooltip } from '@mui/material';
8
+ import React, { useEffect, useState } from 'react';
10
9
 
11
10
  import { InputToolbarRegistry } from '../toolbar-registry';
12
- import { TooltippedButton } from '../../mui-extras/tooltipped-button';
13
- import { includeSelectionIcon } from '../../../icons';
14
11
  import { IInputModel, InputModel } from '../../../input-model';
15
12
 
16
13
  const SEND_BUTTON_CLASS = 'jp-chat-send-button';
17
- const SEND_INCLUDE_OPENER_CLASS = 'jp-chat-send-include-opener';
18
- const SEND_INCLUDE_LI_CLASS = 'jp-chat-send-include';
19
14
 
20
15
  /**
21
- * The send button, with optional 'include selection' menu.
16
+ * The send button.
22
17
  */
23
18
  export function SendButton(
24
19
  props: InputToolbarRegistry.IToolbarItemProps
25
20
  ): JSX.Element {
26
- const { model, chatCommandRegistry } = props;
27
- const { activeCellManager, selectionWatcher } = model;
28
- const hideIncludeSelection = !activeCellManager || !selectionWatcher;
21
+ const { model, chatModel, chatCommandRegistry, edit } = props;
22
+
23
+ // Don't show this button when in edit mode
24
+ if (edit) {
25
+ return <></>;
26
+ }
29
27
 
30
- const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLElement | null>(null);
31
- const [menuOpen, setMenuOpen] = useState(false);
32
28
  const [disabled, setDisabled] = useState(false);
33
29
  const [tooltip, setTooltip] = useState<string>('');
34
30
 
35
- const openMenu = useCallback((el: HTMLElement | null) => {
36
- setMenuAnchorEl(el);
37
- setMenuOpen(true);
38
- }, []);
39
-
40
- const closeMenu = useCallback(() => {
41
- setMenuOpen(false);
42
- }, []);
43
-
44
- const [selectionTooltip, setSelectionTooltip] = useState<string>('');
45
- const [disableInclude, setDisableInclude] = useState<boolean>(true);
46
-
47
31
  useEffect(() => {
48
32
  const inputChanged = () => {
49
33
  const inputExist = !!model.value.trim() || model.attachments.length;
@@ -71,153 +55,52 @@ export function SendButton(
71
55
  };
72
56
  }, [model]);
73
57
 
74
- useEffect(() => {
75
- /**
76
- * Enable or disable the include selection button, and adapt the tooltip.
77
- */
78
- const toggleIncludeState = () => {
79
- setDisableInclude(
80
- !(selectionWatcher?.selection || activeCellManager?.available)
81
- );
82
- const tooltip = selectionWatcher?.selection
83
- ? `${selectionWatcher.selection.numLines} line(s) selected`
84
- : activeCellManager?.available
85
- ? 'Code from 1 active cell'
86
- : 'No selection or active cell';
87
- setSelectionTooltip(tooltip);
88
- };
89
-
90
- if (!hideIncludeSelection) {
91
- selectionWatcher?.selectionChanged.connect(toggleIncludeState);
92
- activeCellManager?.availabilityChanged.connect(toggleIncludeState);
93
- toggleIncludeState();
94
- }
95
- return () => {
96
- selectionWatcher?.selectionChanged.disconnect(toggleIncludeState);
97
- activeCellManager?.availabilityChanged.disconnect(toggleIncludeState);
98
- };
99
- }, [activeCellManager, selectionWatcher]);
100
-
101
58
  async function send() {
59
+ // Run all command providers
102
60
  await chatCommandRegistry?.onSubmit(model);
103
- model.send(model.value);
104
- }
105
-
106
- async function sendWithSelection() {
107
- let source = '';
108
-
109
- // Run all chat command providers
110
- await chatCommandRegistry?.onSubmit(model);
111
-
112
- let language: string | undefined;
113
- if (selectionWatcher?.selection) {
114
- // Append the selected text if exists.
115
- source = selectionWatcher.selection.text;
116
- language = selectionWatcher.selection.language;
117
- } else if (activeCellManager?.available) {
118
- // Append the active cell content if exists.
119
- const content = activeCellManager.getContent(false);
120
- source = content!.source;
121
- language = content?.language;
122
- }
123
- let content = model.value;
124
- if (source) {
125
- content += `
126
61
 
127
- \`\`\`${language ?? ''}
128
- ${source}
129
- \`\`\`
130
- `;
131
- }
132
- model.send(content);
133
- closeMenu();
62
+ // send message through chat model
63
+ await chatModel?.sendMessage({
64
+ body: model.value
65
+ });
66
+ // clear input model value & re-focus
134
67
  model.value = '';
68
+ model.focus();
135
69
  }
136
70
 
137
71
  return (
138
- <>
139
- <TooltippedButton
140
- onClick={send}
141
- disabled={disabled}
142
- tooltip={tooltip}
143
- buttonProps={{
144
- size: 'small',
145
- title: tooltip,
146
- variant: 'contained',
147
- className: SEND_BUTTON_CLASS
148
- }}
149
- >
150
- <SendIcon />
151
- </TooltippedButton>
152
- {!hideIncludeSelection && (
153
- <>
154
- <TooltippedButton
155
- onClick={e => {
156
- openMenu(e.currentTarget);
157
- }}
158
- disabled={disabled}
159
- tooltip=""
160
- buttonProps={{
161
- variant: 'contained',
162
- onKeyDown: e => {
163
- if (e.key !== 'Enter' && e.key !== ' ') {
164
- return;
165
- }
166
- openMenu(e.currentTarget);
167
- // stopping propagation of this event prevents the prompt from being
168
- // sent when the dropdown button is selected and clicked via 'Enter'.
169
- e.stopPropagation();
170
- },
171
- className: SEND_INCLUDE_OPENER_CLASS
172
- }}
173
- >
174
- <KeyboardArrowDown />
175
- </TooltippedButton>
176
- <Menu
177
- open={menuOpen}
178
- onClose={closeMenu}
179
- anchorEl={menuAnchorEl}
180
- anchorOrigin={{
181
- vertical: 'top',
182
- horizontal: 'right'
183
- }}
184
- transformOrigin={{
185
- vertical: 'bottom',
186
- horizontal: 'right'
187
- }}
188
- sx={{
189
- '& .MuiMenuItem-root': {
190
- display: 'flex',
191
- alignItems: 'center',
192
- gap: '8px'
193
- },
194
- '& svg': {
195
- lineHeight: 0
196
- }
197
- }}
198
- >
199
- <MenuItem
200
- onClick={e => {
201
- sendWithSelection();
202
- // prevent sending second message with no selection
203
- e.stopPropagation();
204
- }}
205
- disabled={disableInclude}
206
- className={SEND_INCLUDE_LI_CLASS}
207
- >
208
- <includeSelectionIcon.react />
209
- <Box>
210
- <Typography display="block">
211
- Send message with selection
212
- </Typography>
213
- <Typography display="block" sx={{ opacity: 0.618 }}>
214
- {selectionTooltip}
215
- </Typography>
216
- </Box>
217
- </MenuItem>
218
- </Menu>
219
- </>
220
- )}
221
- </>
72
+ <Tooltip title={tooltip} placement="top" arrow>
73
+ <span>
74
+ <Button
75
+ onClick={send}
76
+ disabled={disabled}
77
+ size="small"
78
+ variant="contained"
79
+ className={SEND_BUTTON_CLASS}
80
+ aria-label={tooltip}
81
+ sx={{
82
+ backgroundColor: 'var(--jp-brand-color1)',
83
+ color: 'white',
84
+ minWidth: '24px',
85
+ width: '24px',
86
+ height: '24px',
87
+ borderRadius: '4px',
88
+ boxShadow: 'none',
89
+ lineHeight: 0,
90
+ '&:hover': {
91
+ backgroundColor: 'var(--jp-brand-color0)',
92
+ boxShadow: 'none'
93
+ },
94
+ '&.Mui-disabled': {
95
+ backgroundColor: 'var(--jp-border-color2)',
96
+ color: 'var(--jp-ui-font-color3)',
97
+ opacity: 0.5
98
+ }
99
+ }}
100
+ >
101
+ <ArrowUpwardIcon sx={{ fontSize: '16px' }} />
102
+ </Button>
103
+ </span>
104
+ </Tooltip>
222
105
  );
223
106
  }
@@ -0,0 +1,88 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import StopIcon from '@mui/icons-material/Stop';
7
+ import { Button, Tooltip } from '@mui/material';
8
+ import React, { useEffect, useState } from 'react';
9
+
10
+ import { InputToolbarRegistry } from '../toolbar-registry';
11
+
12
+ const STOP_BUTTON_CLASS = 'jp-chat-stop-button';
13
+
14
+ /**
15
+ * The stop button.
16
+ */
17
+ export function StopButton(
18
+ props: InputToolbarRegistry.IToolbarItemProps
19
+ ): JSX.Element {
20
+ const { chatModel } = props;
21
+ const [disabled, setDisabled] = useState(true);
22
+ const tooltip = 'Stop generating';
23
+
24
+ useEffect(() => {
25
+ if (!chatModel) {
26
+ setDisabled(true);
27
+ return;
28
+ }
29
+
30
+ const checkWriters = () => {
31
+ // Check if there's at least one AI agent writer (bot)
32
+ const hasAIWriter = chatModel.writers.some(writer => writer.user.bot);
33
+ setDisabled(!hasAIWriter);
34
+ };
35
+
36
+ // Check initially
37
+ checkWriters();
38
+
39
+ // Listen to writers changes
40
+ chatModel.writersChanged?.connect(checkWriters);
41
+
42
+ return () => {
43
+ chatModel.writersChanged?.disconnect(checkWriters);
44
+ };
45
+ }, [chatModel]);
46
+
47
+ function stop() {
48
+ // TODO: Implement stop functionality
49
+ // This will need to be implemented based on how the chat model handles stopping AI responses
50
+ console.log('Stop button clicked');
51
+ }
52
+
53
+ return (
54
+ <Tooltip title={tooltip} placement="top" arrow>
55
+ <span>
56
+ <Button
57
+ onClick={stop}
58
+ disabled={disabled}
59
+ size="small"
60
+ variant="contained"
61
+ className={STOP_BUTTON_CLASS}
62
+ aria-label={tooltip}
63
+ sx={{
64
+ backgroundColor: 'var(--jp-error-color1)',
65
+ color: 'white',
66
+ minWidth: '24px',
67
+ width: '24px',
68
+ height: '24px',
69
+ borderRadius: '4px',
70
+ boxShadow: 'none',
71
+ lineHeight: 0,
72
+ '&:hover': {
73
+ backgroundColor: 'var(--jp-error-color0)',
74
+ boxShadow: 'none'
75
+ },
76
+ '&.Mui-disabled': {
77
+ backgroundColor: 'var(--jp-border-color2)',
78
+ color: 'var(--jp-ui-font-color3)',
79
+ opacity: 0.5
80
+ }
81
+ }}
82
+ >
83
+ <StopIcon sx={{ fontSize: '16px' }} />
84
+ </Button>
85
+ </span>
86
+ </Tooltip>
87
+ );
88
+ }