@jupyter/chat 0.19.0-alpha.2 → 0.19.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/input/buttons/attach-button.js +1 -1
- package/lib/components/input/buttons/cancel-button.js +1 -1
- package/lib/components/input/buttons/save-edit-button.js +1 -1
- package/lib/components/input/buttons/send-button.js +3 -1
- package/lib/components/input/buttons/stop-button.js +1 -1
- package/lib/components/input/chat-input.js +2 -5
- package/lib/components/mui-extras/tooltipped-button.d.ts +5 -26
- package/lib/components/mui-extras/tooltipped-button.js +4 -33
- package/lib/components/mui-extras/tooltipped-icon-button.d.ts +12 -22
- package/lib/components/mui-extras/tooltipped-icon-button.js +10 -8
- package/lib/theme-provider.d.ts +19 -0
- package/lib/theme-provider.js +74 -3
- package/lib/widgets/chat-widget.d.ts +4 -0
- package/lib/widgets/chat-widget.js +38 -1
- package/lib/widgets/multichat-panel.js +1 -1
- package/package.json +3 -1
- package/src/components/input/buttons/attach-button.tsx +1 -1
- package/src/components/input/buttons/cancel-button.tsx +1 -1
- package/src/components/input/buttons/save-edit-button.tsx +1 -1
- package/src/components/input/buttons/send-button.tsx +4 -1
- package/src/components/input/buttons/stop-button.tsx +1 -1
- package/src/components/input/chat-input.tsx +1 -6
- package/src/components/mui-extras/tooltipped-button.tsx +9 -43
- package/src/components/mui-extras/tooltipped-icon-button.tsx +17 -38
- package/src/theme-provider.ts +95 -3
- package/src/widgets/chat-widget.tsx +44 -1
- package/src/widgets/multichat-panel.tsx +5 -1
- package/style/chat.css +10 -0
- package/style/input.css +4 -0
|
@@ -38,7 +38,7 @@ export function AttachButton(props) {
|
|
|
38
38
|
console.warn('Error selecting files to attach', e);
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
-
return (React.createElement(TooltippedIconButton, { onClick: onclick, tooltip: tooltip,
|
|
41
|
+
return (React.createElement(TooltippedIconButton, { onClick: onclick, tooltip: tooltip, buttonProps: {
|
|
42
42
|
title: tooltip,
|
|
43
43
|
className: ATTACH_BUTTON_CLASS
|
|
44
44
|
} },
|
|
@@ -14,7 +14,7 @@ export function CancelButton(props) {
|
|
|
14
14
|
return React.createElement(React.Fragment, null);
|
|
15
15
|
}
|
|
16
16
|
const tooltip = 'Cancel editing';
|
|
17
|
-
return (React.createElement(TooltippedIconButton, { onClick: props.model.cancel, tooltip: tooltip,
|
|
17
|
+
return (React.createElement(TooltippedIconButton, { onClick: props.model.cancel, tooltip: tooltip, buttonProps: {
|
|
18
18
|
title: tooltip,
|
|
19
19
|
className: CANCEL_BUTTON_CLASS
|
|
20
20
|
} },
|
|
@@ -36,7 +36,7 @@ export function SaveEditButton(props) {
|
|
|
36
36
|
await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
|
|
37
37
|
model.send(model.value);
|
|
38
38
|
}
|
|
39
|
-
return (React.createElement(TooltippedIconButton, { onClick: save, tooltip: tooltip, disabled: disabled,
|
|
39
|
+
return (React.createElement(TooltippedIconButton, { onClick: save, tooltip: tooltip, disabled: disabled, buttonProps: {
|
|
40
40
|
title: tooltip,
|
|
41
41
|
className: SAVE_EDIT_BUTTON_CLASS
|
|
42
42
|
}, "aria-label": tooltip },
|
|
@@ -33,6 +33,8 @@ export function SendButton(props) {
|
|
|
33
33
|
: 'Send message (ENTER)');
|
|
34
34
|
};
|
|
35
35
|
model.configChanged.connect(configChanged);
|
|
36
|
+
// Initialize the tooltip.
|
|
37
|
+
configChanged(model, model.config);
|
|
36
38
|
return () => {
|
|
37
39
|
var _a, _b;
|
|
38
40
|
model.valueChanged.disconnect(inputChanged);
|
|
@@ -51,7 +53,7 @@ export function SendButton(props) {
|
|
|
51
53
|
model.value = '';
|
|
52
54
|
model.focus();
|
|
53
55
|
}
|
|
54
|
-
return (React.createElement(TooltippedIconButton, { onClick: send, tooltip: tooltip, disabled: disabled,
|
|
56
|
+
return (React.createElement(TooltippedIconButton, { onClick: send, tooltip: tooltip, disabled: disabled, buttonProps: {
|
|
55
57
|
title: tooltip,
|
|
56
58
|
className: SEND_BUTTON_CLASS
|
|
57
59
|
}, "aria-label": tooltip },
|
|
@@ -38,7 +38,7 @@ export function StopButton(props) {
|
|
|
38
38
|
// This will need to be implemented based on how the chat model handles stopping AI responses
|
|
39
39
|
console.log('Stop button clicked');
|
|
40
40
|
}
|
|
41
|
-
return (React.createElement(TooltippedIconButton, { onClick: stop, tooltip: tooltip, disabled: disabled,
|
|
41
|
+
return (React.createElement(TooltippedIconButton, { onClick: stop, tooltip: tooltip, disabled: disabled, buttonProps: {
|
|
42
42
|
title: tooltip,
|
|
43
43
|
className: STOP_BUTTON_CLASS
|
|
44
44
|
}, "aria-label": tooltip },
|
|
@@ -23,7 +23,6 @@ export function ChatInput(props) {
|
|
|
23
23
|
const [sendWithShiftEnter, setSendWithShiftEnter] = useState((_a = model.config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false);
|
|
24
24
|
const [attachments, setAttachments] = useState(model.attachments);
|
|
25
25
|
const [toolbarElements, setToolbarElements] = useState([]);
|
|
26
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
27
26
|
const [writers, setWriters] = useState([]);
|
|
28
27
|
/**
|
|
29
28
|
* Auto-focus the input when the component is first mounted.
|
|
@@ -163,9 +162,7 @@ export function ChatInput(props) {
|
|
|
163
162
|
return (React.createElement(Box, { sx: props.sx, className: clsx(INPUT_BOX_CLASS), "data-input-id": model.id },
|
|
164
163
|
React.createElement(Box, { sx: {
|
|
165
164
|
border: '1px solid',
|
|
166
|
-
borderColor:
|
|
167
|
-
? 'var(--jp-brand-color1)'
|
|
168
|
-
: 'var(--jp-border-color1)',
|
|
165
|
+
borderColor: 'var(--jp-border-color1)',
|
|
169
166
|
borderRadius: 2,
|
|
170
167
|
transition: 'border-color 0.2s ease',
|
|
171
168
|
display: 'flex',
|
|
@@ -188,7 +185,7 @@ export function ChatInput(props) {
|
|
|
188
185
|
padding: 0
|
|
189
186
|
}
|
|
190
187
|
}
|
|
191
|
-
}, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "standard", className: INPUT_TEXTFIELD_CLASS, multiline: true, maxRows: 10, onKeyDown: handleKeyDown, placeholder: "Type a chat message, @ to mention...", inputRef: inputRef,
|
|
188
|
+
}, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "standard", className: INPUT_TEXTFIELD_CLASS, multiline: true, maxRows: 10, onKeyDown: handleKeyDown, placeholder: "Type a chat message, @ to mention...", inputRef: inputRef, onSelect: () => { var _a, _b; return (model.cursorIndex = (_b = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.selectionStart) !== null && _b !== void 0 ? _b : null); }, sx: {
|
|
192
189
|
padding: 1.5,
|
|
193
190
|
margin: 0,
|
|
194
191
|
boxSizing: 'border-box',
|
|
@@ -1,35 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ButtonProps, SxProps, TooltipProps } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
export declare const TOOLTIPPED_WRAP_CLASS = "jp-chat-tooltipped-wrap";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
width: string;
|
|
8
|
-
height: string;
|
|
9
|
-
lineHeight: number;
|
|
10
|
-
'&:disabled': {
|
|
11
|
-
opacity: number;
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
export declare const INPUT_TOOLBAR_BUTTON_SX: {
|
|
15
|
-
backgroundColor: string;
|
|
16
|
-
color: string;
|
|
17
|
-
borderRadius: string;
|
|
18
|
-
boxShadow: string;
|
|
19
|
-
'&:hover': {
|
|
20
|
-
backgroundColor: string;
|
|
21
|
-
boxShadow: string;
|
|
22
|
-
};
|
|
23
|
-
'&:disabled': {
|
|
24
|
-
backgroundColor: string;
|
|
25
|
-
color: string;
|
|
26
|
-
opacity: number;
|
|
27
|
-
};
|
|
28
|
-
};
|
|
4
|
+
/**
|
|
5
|
+
* The props for the tooltipped button.
|
|
6
|
+
*/
|
|
29
7
|
export type TooltippedButtonProps = {
|
|
30
8
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
31
9
|
tooltip: string;
|
|
32
10
|
children: JSX.Element;
|
|
11
|
+
className?: string;
|
|
33
12
|
inputToolbar?: boolean;
|
|
34
13
|
disabled?: boolean;
|
|
35
14
|
placement?: TooltipProps['placement'];
|
|
@@ -2,38 +2,11 @@
|
|
|
2
2
|
* Copyright (c) Jupyter Development Team.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
import { classes } from '@jupyterlab/ui-components';
|
|
5
6
|
import { Button } from '@mui/material';
|
|
6
7
|
import React from 'react';
|
|
7
8
|
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
8
9
|
export const TOOLTIPPED_WRAP_CLASS = 'jp-chat-tooltipped-wrap';
|
|
9
|
-
export const DEFAULT_BUTTON_PROPS = {
|
|
10
|
-
size: 'small',
|
|
11
|
-
variant: 'contained'
|
|
12
|
-
};
|
|
13
|
-
export const DEFAULT_BUTTON_SX = {
|
|
14
|
-
minWidth: '24px',
|
|
15
|
-
width: '24px',
|
|
16
|
-
height: '24px',
|
|
17
|
-
lineHeight: 0,
|
|
18
|
-
'&:disabled': {
|
|
19
|
-
opacity: 0.5
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
export const INPUT_TOOLBAR_BUTTON_SX = {
|
|
23
|
-
backgroundColor: 'var(--jp-brand-color1)',
|
|
24
|
-
color: 'white',
|
|
25
|
-
borderRadius: '4px',
|
|
26
|
-
boxShadow: 'none',
|
|
27
|
-
'&:hover': {
|
|
28
|
-
backgroundColor: 'var(--jp-brand-color0)',
|
|
29
|
-
boxShadow: 'none'
|
|
30
|
-
},
|
|
31
|
-
'&:disabled': {
|
|
32
|
-
backgroundColor: 'var(--jp-border-color2)',
|
|
33
|
-
color: 'var(--jp-ui-font-color3)',
|
|
34
|
-
opacity: 0.5
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
10
|
/**
|
|
38
11
|
* A component that renders an MUI `Button` with a high-contrast tooltip
|
|
39
12
|
* provided by `ContrastingTooltip`. This component differs from the MUI
|
|
@@ -63,10 +36,8 @@ export function TooltippedButton(props) {
|
|
|
63
36
|
]
|
|
64
37
|
}
|
|
65
38
|
} },
|
|
66
|
-
React.createElement("span", {
|
|
67
|
-
React.createElement(Button, { ...
|
|
68
|
-
...DEFAULT_BUTTON_SX,
|
|
69
|
-
...(((_b = props.inputToolbar) !== null && _b !== void 0 ? _b : true) && INPUT_TOOLBAR_BUTTON_SX),
|
|
39
|
+
React.createElement("span", { className: classes(props.className, TOOLTIPPED_WRAP_CLASS) },
|
|
40
|
+
React.createElement(Button, { ...(((_b = props.inputToolbar) !== null && _b !== void 0 ? _b : true) && { variant: 'input-toolbar' }), ...props.buttonProps, onClick: props.onClick, disabled: props.disabled, "aria-label": (_c = props['aria-label']) !== null && _c !== void 0 ? _c : props.tooltip, sx: {
|
|
70
41
|
...props.sx
|
|
71
|
-
}
|
|
42
|
+
} }, props.children))));
|
|
72
43
|
}
|
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { IconButtonProps
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
inputToolbar?: boolean;
|
|
9
|
-
disabled?: boolean;
|
|
10
|
-
placement?: TooltipProps['placement'];
|
|
11
|
-
/**
|
|
12
|
-
* The font size of the icon. By default it will be set to 'small'.
|
|
13
|
-
*/
|
|
14
|
-
fontSize?: SvgIconOwnProps['fontSize'];
|
|
15
|
-
/**
|
|
16
|
-
* The offset of the tooltip popup.
|
|
17
|
-
*
|
|
18
|
-
* The expected syntax is defined by the Popper library:
|
|
19
|
-
* https://popper.js.org/docs/v2/modifiers/offset/
|
|
20
|
-
*/
|
|
21
|
-
offset?: [number, number];
|
|
22
|
-
'aria-label'?: string;
|
|
2
|
+
import { IconButtonProps } from '@mui/material';
|
|
3
|
+
import { TooltippedButtonProps } from './tooltipped-button';
|
|
4
|
+
/**
|
|
5
|
+
* The props for the tooltipped icon button.
|
|
6
|
+
*/
|
|
7
|
+
export type TooltippedIconButtonProps = TooltippedButtonProps & {
|
|
23
8
|
/**
|
|
24
9
|
* Props passed directly to the MUI `IconButton` component.
|
|
25
10
|
*/
|
|
26
|
-
|
|
11
|
+
buttonProps?: IconButtonProps;
|
|
27
12
|
};
|
|
28
13
|
/**
|
|
29
14
|
* A component that renders an MUI `IconButton` with a high-contrast tooltip
|
|
@@ -36,5 +21,10 @@ export type TooltippedIconButtonProps = {
|
|
|
36
21
|
* - Lowers the opacity of the IconButton when disabled.
|
|
37
22
|
* - Renders the IconButton with `line-height: 0` to avoid showing extra
|
|
38
23
|
* vertical space in SVG icons.
|
|
24
|
+
*
|
|
25
|
+
* NOTES:
|
|
26
|
+
* This kind of button doesn't allow regular variants ('outlined', 'contained', 'text').
|
|
27
|
+
* The only one allowed is 'input-toolbar'. If you want to use one of the regular, use
|
|
28
|
+
* the TooltippedButton instead.
|
|
39
29
|
*/
|
|
40
30
|
export declare function TooltippedIconButton(props: TooltippedIconButtonProps): JSX.Element;
|
|
@@ -6,7 +6,7 @@ import { classes } from '@jupyterlab/ui-components';
|
|
|
6
6
|
import { IconButton } from '@mui/material';
|
|
7
7
|
import React from 'react';
|
|
8
8
|
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
9
|
-
import {
|
|
9
|
+
import { TOOLTIPPED_WRAP_CLASS } from './tooltipped-button';
|
|
10
10
|
/**
|
|
11
11
|
* A component that renders an MUI `IconButton` with a high-contrast tooltip
|
|
12
12
|
* provided by `ContrastingTooltip`. This component differs from the MUI
|
|
@@ -18,12 +18,15 @@ import { DEFAULT_BUTTON_PROPS, DEFAULT_BUTTON_SX, INPUT_TOOLBAR_BUTTON_SX, TOOLT
|
|
|
18
18
|
* - Lowers the opacity of the IconButton when disabled.
|
|
19
19
|
* - Renders the IconButton with `line-height: 0` to avoid showing extra
|
|
20
20
|
* vertical space in SVG icons.
|
|
21
|
+
*
|
|
22
|
+
* NOTES:
|
|
23
|
+
* This kind of button doesn't allow regular variants ('outlined', 'contained', 'text').
|
|
24
|
+
* The only one allowed is 'input-toolbar'. If you want to use one of the regular, use
|
|
25
|
+
* the TooltippedButton instead.
|
|
21
26
|
*/
|
|
22
27
|
export function TooltippedIconButton(props) {
|
|
23
28
|
var _a, _b, _c;
|
|
24
|
-
|
|
25
|
-
props.children.props.fontSize = (_a = props.fontSize) !== null && _a !== void 0 ? _a : 'small';
|
|
26
|
-
return (React.createElement(ContrastingTooltip, { title: props.tooltip, placement: (_b = props.placement) !== null && _b !== void 0 ? _b : 'top', slotProps: {
|
|
29
|
+
return (React.createElement(ContrastingTooltip, { title: props.tooltip, placement: (_a = props.placement) !== null && _a !== void 0 ? _a : 'top', slotProps: {
|
|
27
30
|
popper: {
|
|
28
31
|
modifiers: [
|
|
29
32
|
{
|
|
@@ -36,8 +39,7 @@ export function TooltippedIconButton(props) {
|
|
|
36
39
|
}
|
|
37
40
|
} },
|
|
38
41
|
React.createElement("span", { className: classes(props.className, TOOLTIPPED_WRAP_CLASS) },
|
|
39
|
-
React.createElement(IconButton, { ...
|
|
40
|
-
...
|
|
41
|
-
|
|
42
|
-
}, "aria-label": props['aria-label'] }, props.children))));
|
|
42
|
+
React.createElement(IconButton, { ...(((_b = props.inputToolbar) !== null && _b !== void 0 ? _b : true) && { variant: 'input-toolbar' }), ...props.buttonProps, onClick: props.onClick, disabled: props.disabled, "aria-label": (_c = props['aria-label']) !== null && _c !== void 0 ? _c : props.tooltip, sx: {
|
|
43
|
+
...props.sx
|
|
44
|
+
} }, props.children))));
|
|
43
45
|
}
|
package/lib/theme-provider.d.ts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
import '@mui/material/Button';
|
|
2
|
+
import '@mui/material/IconButton';
|
|
1
3
|
import { Theme } from '@mui/material/styles';
|
|
4
|
+
/**
|
|
5
|
+
* Allow a new variant type, for the input toolbar.
|
|
6
|
+
*/
|
|
7
|
+
declare module '@mui/material/Button' {
|
|
8
|
+
interface ButtonPropsVariantOverrides {
|
|
9
|
+
'input-toolbar': true;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Allow a new variant property in IconButton, to be able to set the same properties
|
|
14
|
+
* as the input toolbar buttons.
|
|
15
|
+
*/
|
|
16
|
+
declare module '@mui/material/IconButton' {
|
|
17
|
+
interface IconButtonOwnProps {
|
|
18
|
+
variant?: 'input-toolbar';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
2
21
|
export declare function pollUntilReady(): Promise<void>;
|
|
3
22
|
export declare function getJupyterLabTheme(): Promise<Theme>;
|
package/lib/theme-provider.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Copyright (c) Jupyter Development Team.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
import '@mui/material/Button';
|
|
6
|
+
import '@mui/material/IconButton';
|
|
5
7
|
import { createTheme } from '@mui/material/styles';
|
|
6
8
|
function getCSSVariable(name) {
|
|
7
9
|
return getComputedStyle(document.body).getPropertyValue(name).trim();
|
|
@@ -19,8 +21,41 @@ export async function getJupyterLabTheme() {
|
|
|
19
21
|
components: {
|
|
20
22
|
MuiButton: {
|
|
21
23
|
defaultProps: {
|
|
22
|
-
size: 'small'
|
|
23
|
-
|
|
24
|
+
size: 'small',
|
|
25
|
+
variant: 'contained'
|
|
26
|
+
},
|
|
27
|
+
styleOverrides: {
|
|
28
|
+
root: {
|
|
29
|
+
minWidth: '24px',
|
|
30
|
+
width: '24px',
|
|
31
|
+
height: '24px',
|
|
32
|
+
lineHeight: 0,
|
|
33
|
+
'&:disabled': {
|
|
34
|
+
opacity: 0.5
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
variants: [
|
|
39
|
+
{
|
|
40
|
+
// The default style for input toolbar button variant.
|
|
41
|
+
props: { variant: 'input-toolbar' },
|
|
42
|
+
style: {
|
|
43
|
+
backgroundColor: 'var(--jp-brand-color1)',
|
|
44
|
+
color: 'white',
|
|
45
|
+
borderRadius: '4px',
|
|
46
|
+
boxShadow: 'none',
|
|
47
|
+
'&:hover': {
|
|
48
|
+
backgroundColor: 'var(--jp-brand-color0)',
|
|
49
|
+
boxShadow: 'none'
|
|
50
|
+
},
|
|
51
|
+
'&:disabled': {
|
|
52
|
+
backgroundColor: 'var(--jp-border-color2)',
|
|
53
|
+
color: 'var(--jp-ui-font-color3)',
|
|
54
|
+
opacity: 0.5
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
24
59
|
},
|
|
25
60
|
MuiFilledInput: {
|
|
26
61
|
defaultProps: {
|
|
@@ -41,7 +76,43 @@ export async function getJupyterLabTheme() {
|
|
|
41
76
|
MuiIconButton: {
|
|
42
77
|
defaultProps: {
|
|
43
78
|
size: 'small'
|
|
44
|
-
}
|
|
79
|
+
},
|
|
80
|
+
styleOverrides: {
|
|
81
|
+
root: {
|
|
82
|
+
minWidth: '24px',
|
|
83
|
+
width: '24px',
|
|
84
|
+
height: '24px',
|
|
85
|
+
lineHeight: 0,
|
|
86
|
+
'&:disabled': {
|
|
87
|
+
opacity: 0.5
|
|
88
|
+
},
|
|
89
|
+
// Set the default size of the svg icon if not set by user.
|
|
90
|
+
'& .MuiSvgIcon-root:not([fontSize])': {
|
|
91
|
+
fontSize: 'medium'
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
variants: [
|
|
96
|
+
{
|
|
97
|
+
// The default style for input toolbar button variant.
|
|
98
|
+
props: { variant: 'input-toolbar' },
|
|
99
|
+
style: {
|
|
100
|
+
backgroundColor: 'var(--jp-brand-color1)',
|
|
101
|
+
color: 'white',
|
|
102
|
+
borderRadius: '4px',
|
|
103
|
+
boxShadow: 'none',
|
|
104
|
+
'&:hover': {
|
|
105
|
+
backgroundColor: 'var(--jp-brand-color0)',
|
|
106
|
+
boxShadow: 'none'
|
|
107
|
+
},
|
|
108
|
+
'&:disabled': {
|
|
109
|
+
backgroundColor: 'var(--jp-border-color2)',
|
|
110
|
+
color: 'var(--jp-ui-font-color3)',
|
|
111
|
+
opacity: 0.5
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
45
116
|
},
|
|
46
117
|
MuiInputBase: {
|
|
47
118
|
defaultProps: {
|
|
@@ -55,6 +55,10 @@ export declare class ChatWidget extends ReactWidget {
|
|
|
55
55
|
* Process dropped cells
|
|
56
56
|
*/
|
|
57
57
|
private _processCellDrop;
|
|
58
|
+
/**
|
|
59
|
+
* Process dropped tabBar files
|
|
60
|
+
*/
|
|
61
|
+
private _processTabDrop;
|
|
58
62
|
/**
|
|
59
63
|
* Find the notebook path for a cell by searching through active and open notebooks
|
|
60
64
|
*/
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { ReactWidget } from '@jupyterlab/apputils';
|
|
6
|
+
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
6
7
|
import { isCode, isMarkdown, isRaw } from '@jupyterlab/nbformat';
|
|
7
8
|
import React from 'react';
|
|
8
9
|
import { Drag } from '@lumino/dragdrop';
|
|
@@ -12,6 +13,8 @@ import { chatIcon } from '../icons';
|
|
|
12
13
|
const FILE_BROWSER_MIME = 'application/x-jupyter-icontentsrich';
|
|
13
14
|
// MIME type constant for Notebook cell drag events
|
|
14
15
|
const NOTEBOOK_CELL_MIME = 'application/vnd.jupyter.cells';
|
|
16
|
+
// MIME type constant for TabBar file drag events
|
|
17
|
+
const TABBAR_FILE_MIME = 'application/vnd.lumino.widget-factory';
|
|
15
18
|
// CSS class constants
|
|
16
19
|
const INPUT_CONTAINER_CLASS = 'jp-chat-input-container';
|
|
17
20
|
const DRAG_HOVER_CLASS = 'jp-chat-drag-hover';
|
|
@@ -146,7 +149,9 @@ export class ChatWidget extends ReactWidget {
|
|
|
146
149
|
*/
|
|
147
150
|
_canHandleDrop(event) {
|
|
148
151
|
const types = event.mimeData.types();
|
|
149
|
-
return (types.includes(NOTEBOOK_CELL_MIME) ||
|
|
152
|
+
return (types.includes(NOTEBOOK_CELL_MIME) ||
|
|
153
|
+
types.includes(FILE_BROWSER_MIME) ||
|
|
154
|
+
types.includes(TABBAR_FILE_MIME));
|
|
150
155
|
}
|
|
151
156
|
/**
|
|
152
157
|
* Handle drop events
|
|
@@ -166,6 +171,9 @@ export class ChatWidget extends ReactWidget {
|
|
|
166
171
|
else if (event.mimeData.hasData(FILE_BROWSER_MIME)) {
|
|
167
172
|
this._processFileDrop(event);
|
|
168
173
|
}
|
|
174
|
+
else if (event.mimeData.hasData(TABBAR_FILE_MIME)) {
|
|
175
|
+
this._processTabDrop(event);
|
|
176
|
+
}
|
|
169
177
|
}
|
|
170
178
|
catch (error) {
|
|
171
179
|
console.error('Error processing drop:', error);
|
|
@@ -268,6 +276,35 @@ export class ChatWidget extends ReactWidget {
|
|
|
268
276
|
console.error('Failed to process cell drop: ', error);
|
|
269
277
|
}
|
|
270
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Process dropped tabBar files
|
|
281
|
+
*/
|
|
282
|
+
_processTabDrop(event) {
|
|
283
|
+
var _a, _b;
|
|
284
|
+
const factory = event.mimeData.getData(TABBAR_FILE_MIME);
|
|
285
|
+
if (!factory) {
|
|
286
|
+
console.warn('No factory in drag event');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const widget = factory();
|
|
290
|
+
if (!widget || !(widget instanceof DocumentWidget)) {
|
|
291
|
+
console.warn('No file associated to the element');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const path = widget.context.path;
|
|
295
|
+
if (!path) {
|
|
296
|
+
console.warn('Widget has no path');
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const mimetype = ((_a = widget.context.model) === null || _a === void 0 ? void 0 : _a.mimeType) || 'application/octet-stream';
|
|
300
|
+
const attachment = {
|
|
301
|
+
type: 'file',
|
|
302
|
+
value: path,
|
|
303
|
+
mimetype
|
|
304
|
+
};
|
|
305
|
+
const inputModel = this._getInputFromEvent(event);
|
|
306
|
+
(_b = inputModel === null || inputModel === void 0 ? void 0 : inputModel.addAttachment) === null || _b === void 0 ? void 0 : _b.call(inputModel, attachment);
|
|
307
|
+
}
|
|
271
308
|
/**
|
|
272
309
|
* Find the notebook path for a cell by searching through active and open notebooks
|
|
273
310
|
*/
|
|
@@ -354,7 +354,7 @@ function ChatSelect({ chatNamesChanged, handleChange }) {
|
|
|
354
354
|
chatNamesChanged.connect((_, chatNames) => {
|
|
355
355
|
setChatNames(chatNames);
|
|
356
356
|
});
|
|
357
|
-
return (React.createElement(HTMLSelect, { onChange: handleChange, value: "-" },
|
|
357
|
+
return (React.createElement(HTMLSelect, { key: Object.keys(chatNames).join(), onChange: handleChange, value: "-" },
|
|
358
358
|
React.createElement("option", { value: "-", disabled: true, hidden: true }, "Open a chat"),
|
|
359
359
|
Object.keys(chatNames).map(name => (React.createElement("option", { value: chatNames[name] }, name)))));
|
|
360
360
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyter/chat",
|
|
3
|
-
"version": "0.19.0-alpha.
|
|
3
|
+
"version": "0.19.0-alpha.3",
|
|
4
4
|
"description": "A package that provides UI components that can be used to create a chat in a Jupyterlab extension.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@jupyterlab/codeeditor": "^4.2.0",
|
|
52
52
|
"@jupyterlab/codemirror": "^4.2.0",
|
|
53
53
|
"@jupyterlab/docmanager": "^4.2.0",
|
|
54
|
+
"@jupyterlab/docregistry": "^4.2.0",
|
|
54
55
|
"@jupyterlab/filebrowser": "^4.2.0",
|
|
55
56
|
"@jupyterlab/fileeditor": "^4.2.0",
|
|
56
57
|
"@jupyterlab/notebook": "^4.2.0",
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
"@lumino/disposable": "^2.0.0",
|
|
63
64
|
"@lumino/polling": "^2.0.0",
|
|
64
65
|
"@lumino/signaling": "^2.0.0",
|
|
66
|
+
"@lumino/widgets": "^2.0.0",
|
|
65
67
|
"@mui/icons-material": "^7.3.2",
|
|
66
68
|
"@mui/material": "^7.3.2",
|
|
67
69
|
"clsx": "^2.1.0",
|
|
@@ -48,6 +48,9 @@ export function SendButton(
|
|
|
48
48
|
};
|
|
49
49
|
model.configChanged.connect(configChanged);
|
|
50
50
|
|
|
51
|
+
// Initialize the tooltip.
|
|
52
|
+
configChanged(model, model.config);
|
|
53
|
+
|
|
51
54
|
return () => {
|
|
52
55
|
model.valueChanged.disconnect(inputChanged);
|
|
53
56
|
model.attachmentsChanged?.disconnect(inputChanged);
|
|
@@ -73,7 +76,7 @@ export function SendButton(
|
|
|
73
76
|
onClick={send}
|
|
74
77
|
tooltip={tooltip}
|
|
75
78
|
disabled={disabled}
|
|
76
|
-
|
|
79
|
+
buttonProps={{
|
|
77
80
|
title: tooltip,
|
|
78
81
|
className: SEND_BUTTON_CLASS
|
|
79
82
|
}}
|
|
@@ -46,7 +46,6 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
46
46
|
const [toolbarElements, setToolbarElements] = useState<
|
|
47
47
|
InputToolbarRegistry.IToolbarItem[]
|
|
48
48
|
>([]);
|
|
49
|
-
const [isFocused, setIsFocused] = useState<boolean>(false);
|
|
50
49
|
const [writers, setWriters] = useState<IChatModel.IWriter[]>([]);
|
|
51
50
|
|
|
52
51
|
/**
|
|
@@ -214,9 +213,7 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
214
213
|
<Box
|
|
215
214
|
sx={{
|
|
216
215
|
border: '1px solid',
|
|
217
|
-
borderColor:
|
|
218
|
-
? 'var(--jp-brand-color1)'
|
|
219
|
-
: 'var(--jp-border-color1)',
|
|
216
|
+
borderColor: 'var(--jp-border-color1)',
|
|
220
217
|
borderRadius: 2,
|
|
221
218
|
transition: 'border-color 0.2s ease',
|
|
222
219
|
display: 'flex',
|
|
@@ -262,8 +259,6 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
|
|
|
262
259
|
onKeyDown={handleKeyDown}
|
|
263
260
|
placeholder="Type a chat message, @ to mention..."
|
|
264
261
|
inputRef={inputRef}
|
|
265
|
-
onFocus={() => setIsFocused(true)}
|
|
266
|
-
onBlur={() => setIsFocused(false)}
|
|
267
262
|
onSelect={() =>
|
|
268
263
|
(model.cursorIndex = inputRef.current?.selectionStart ?? null)
|
|
269
264
|
}
|
|
@@ -3,54 +3,22 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
ButtonOwnProps,
|
|
9
|
-
ButtonProps,
|
|
10
|
-
SxProps,
|
|
11
|
-
TooltipProps
|
|
12
|
-
} from '@mui/material';
|
|
6
|
+
import { classes } from '@jupyterlab/ui-components';
|
|
7
|
+
import { Button, ButtonProps, SxProps, TooltipProps } from '@mui/material';
|
|
13
8
|
import React from 'react';
|
|
14
9
|
|
|
15
10
|
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
16
11
|
|
|
17
12
|
export const TOOLTIPPED_WRAP_CLASS = 'jp-chat-tooltipped-wrap';
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const DEFAULT_BUTTON_SX = {
|
|
25
|
-
minWidth: '24px',
|
|
26
|
-
width: '24px',
|
|
27
|
-
height: '24px',
|
|
28
|
-
lineHeight: 0,
|
|
29
|
-
'&:disabled': {
|
|
30
|
-
opacity: 0.5
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const INPUT_TOOLBAR_BUTTON_SX = {
|
|
35
|
-
backgroundColor: 'var(--jp-brand-color1)',
|
|
36
|
-
color: 'white',
|
|
37
|
-
borderRadius: '4px',
|
|
38
|
-
boxShadow: 'none',
|
|
39
|
-
'&:hover': {
|
|
40
|
-
backgroundColor: 'var(--jp-brand-color0)',
|
|
41
|
-
boxShadow: 'none'
|
|
42
|
-
},
|
|
43
|
-
'&:disabled': {
|
|
44
|
-
backgroundColor: 'var(--jp-border-color2)',
|
|
45
|
-
color: 'var(--jp-ui-font-color3)',
|
|
46
|
-
opacity: 0.5
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The props for the tooltipped button.
|
|
16
|
+
*/
|
|
50
17
|
export type TooltippedButtonProps = {
|
|
51
18
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
52
19
|
tooltip: string;
|
|
53
20
|
children: JSX.Element;
|
|
21
|
+
className?: string;
|
|
54
22
|
inputToolbar?: boolean;
|
|
55
23
|
disabled?: boolean;
|
|
56
24
|
placement?: TooltipProps['placement'];
|
|
@@ -112,18 +80,16 @@ export function TooltippedButton(props: TooltippedButtonProps): JSX.Element {
|
|
|
112
80
|
|
|
113
81
|
See: https://mui.com/material-ui/react-tooltip/#disabled-elements
|
|
114
82
|
*/}
|
|
115
|
-
<span
|
|
83
|
+
<span className={classes(props.className, TOOLTIPPED_WRAP_CLASS)}>
|
|
116
84
|
<Button
|
|
117
|
-
{...
|
|
85
|
+
{...((props.inputToolbar ?? true) && { variant: 'input-toolbar' })}
|
|
118
86
|
{...props.buttonProps}
|
|
119
87
|
onClick={props.onClick}
|
|
120
88
|
disabled={props.disabled}
|
|
89
|
+
aria-label={props['aria-label'] ?? props.tooltip}
|
|
121
90
|
sx={{
|
|
122
|
-
...DEFAULT_BUTTON_SX,
|
|
123
|
-
...((props.inputToolbar ?? true) && INPUT_TOOLBAR_BUTTON_SX),
|
|
124
91
|
...props.sx
|
|
125
92
|
}}
|
|
126
|
-
aria-label={props['aria-label'] ?? props.tooltip}
|
|
127
93
|
>
|
|
128
94
|
{props.children}
|
|
129
95
|
</Button>
|
|
@@ -4,46 +4,23 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { classes } from '@jupyterlab/ui-components';
|
|
7
|
-
import {
|
|
8
|
-
IconButton,
|
|
9
|
-
IconButtonProps,
|
|
10
|
-
SvgIconOwnProps,
|
|
11
|
-
TooltipProps
|
|
12
|
-
} from '@mui/material';
|
|
7
|
+
import { IconButton, IconButtonProps } from '@mui/material';
|
|
13
8
|
import React from 'react';
|
|
14
9
|
|
|
15
10
|
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
16
11
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
INPUT_TOOLBAR_BUTTON_SX,
|
|
20
|
-
TOOLTIPPED_WRAP_CLASS
|
|
12
|
+
TOOLTIPPED_WRAP_CLASS,
|
|
13
|
+
TooltippedButtonProps
|
|
21
14
|
} from './tooltipped-button';
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
className?: string;
|
|
28
|
-
inputToolbar?: boolean;
|
|
29
|
-
disabled?: boolean;
|
|
30
|
-
placement?: TooltipProps['placement'];
|
|
31
|
-
/**
|
|
32
|
-
* The font size of the icon. By default it will be set to 'small'.
|
|
33
|
-
*/
|
|
34
|
-
fontSize?: SvgIconOwnProps['fontSize'];
|
|
35
|
-
/**
|
|
36
|
-
* The offset of the tooltip popup.
|
|
37
|
-
*
|
|
38
|
-
* The expected syntax is defined by the Popper library:
|
|
39
|
-
* https://popper.js.org/docs/v2/modifiers/offset/
|
|
40
|
-
*/
|
|
41
|
-
offset?: [number, number];
|
|
42
|
-
'aria-label'?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The props for the tooltipped icon button.
|
|
18
|
+
*/
|
|
19
|
+
export type TooltippedIconButtonProps = TooltippedButtonProps & {
|
|
43
20
|
/**
|
|
44
21
|
* Props passed directly to the MUI `IconButton` component.
|
|
45
22
|
*/
|
|
46
|
-
|
|
23
|
+
buttonProps?: IconButtonProps;
|
|
47
24
|
};
|
|
48
25
|
|
|
49
26
|
/**
|
|
@@ -57,12 +34,15 @@ export type TooltippedIconButtonProps = {
|
|
|
57
34
|
* - Lowers the opacity of the IconButton when disabled.
|
|
58
35
|
* - Renders the IconButton with `line-height: 0` to avoid showing extra
|
|
59
36
|
* vertical space in SVG icons.
|
|
37
|
+
*
|
|
38
|
+
* NOTES:
|
|
39
|
+
* This kind of button doesn't allow regular variants ('outlined', 'contained', 'text').
|
|
40
|
+
* The only one allowed is 'input-toolbar'. If you want to use one of the regular, use
|
|
41
|
+
* the TooltippedButton instead.
|
|
60
42
|
*/
|
|
61
43
|
export function TooltippedIconButton(
|
|
62
44
|
props: TooltippedIconButtonProps
|
|
63
45
|
): JSX.Element {
|
|
64
|
-
// Override the default icon font size from 'medium' to 'small'
|
|
65
|
-
props.children.props.fontSize = props.fontSize ?? 'small';
|
|
66
46
|
return (
|
|
67
47
|
<ContrastingTooltip
|
|
68
48
|
title={props.tooltip}
|
|
@@ -89,15 +69,14 @@ export function TooltippedIconButton(
|
|
|
89
69
|
*/}
|
|
90
70
|
<span className={classes(props.className, TOOLTIPPED_WRAP_CLASS)}>
|
|
91
71
|
<IconButton
|
|
92
|
-
{...
|
|
93
|
-
{...props.
|
|
72
|
+
{...((props.inputToolbar ?? true) && { variant: 'input-toolbar' })}
|
|
73
|
+
{...props.buttonProps}
|
|
94
74
|
onClick={props.onClick}
|
|
95
75
|
disabled={props.disabled}
|
|
76
|
+
aria-label={props['aria-label'] ?? props.tooltip}
|
|
96
77
|
sx={{
|
|
97
|
-
...
|
|
98
|
-
...((props.inputToolbar ?? true) && INPUT_TOOLBAR_BUTTON_SX)
|
|
78
|
+
...props.sx
|
|
99
79
|
}}
|
|
100
|
-
aria-label={props['aria-label']}
|
|
101
80
|
>
|
|
102
81
|
{props.children}
|
|
103
82
|
</IconButton>
|
package/src/theme-provider.ts
CHANGED
|
@@ -3,8 +3,31 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import '@mui/material/Button';
|
|
7
|
+
import '@mui/material/IconButton';
|
|
6
8
|
import { Theme, createTheme } from '@mui/material/styles';
|
|
7
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Allow a new variant type, for the input toolbar.
|
|
12
|
+
*/
|
|
13
|
+
declare module '@mui/material/Button' {
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
15
|
+
interface ButtonPropsVariantOverrides {
|
|
16
|
+
'input-toolbar': true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Allow a new variant property in IconButton, to be able to set the same properties
|
|
22
|
+
* as the input toolbar buttons.
|
|
23
|
+
*/
|
|
24
|
+
declare module '@mui/material/IconButton' {
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
26
|
+
interface IconButtonOwnProps {
|
|
27
|
+
variant?: 'input-toolbar';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
8
31
|
function getCSSVariable(name: string): string {
|
|
9
32
|
return getComputedStyle(document.body).getPropertyValue(name).trim();
|
|
10
33
|
}
|
|
@@ -23,8 +46,41 @@ export async function getJupyterLabTheme(): Promise<Theme> {
|
|
|
23
46
|
components: {
|
|
24
47
|
MuiButton: {
|
|
25
48
|
defaultProps: {
|
|
26
|
-
size: 'small'
|
|
27
|
-
|
|
49
|
+
size: 'small',
|
|
50
|
+
variant: 'contained'
|
|
51
|
+
},
|
|
52
|
+
styleOverrides: {
|
|
53
|
+
root: {
|
|
54
|
+
minWidth: '24px',
|
|
55
|
+
width: '24px',
|
|
56
|
+
height: '24px',
|
|
57
|
+
lineHeight: 0,
|
|
58
|
+
'&:disabled': {
|
|
59
|
+
opacity: 0.5
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
variants: [
|
|
64
|
+
{
|
|
65
|
+
// The default style for input toolbar button variant.
|
|
66
|
+
props: { variant: 'input-toolbar' },
|
|
67
|
+
style: {
|
|
68
|
+
backgroundColor: 'var(--jp-brand-color1)',
|
|
69
|
+
color: 'white',
|
|
70
|
+
borderRadius: '4px',
|
|
71
|
+
boxShadow: 'none',
|
|
72
|
+
'&:hover': {
|
|
73
|
+
backgroundColor: 'var(--jp-brand-color0)',
|
|
74
|
+
boxShadow: 'none'
|
|
75
|
+
},
|
|
76
|
+
'&:disabled': {
|
|
77
|
+
backgroundColor: 'var(--jp-border-color2)',
|
|
78
|
+
color: 'var(--jp-ui-font-color3)',
|
|
79
|
+
opacity: 0.5
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
]
|
|
28
84
|
},
|
|
29
85
|
MuiFilledInput: {
|
|
30
86
|
defaultProps: {
|
|
@@ -45,7 +101,43 @@ export async function getJupyterLabTheme(): Promise<Theme> {
|
|
|
45
101
|
MuiIconButton: {
|
|
46
102
|
defaultProps: {
|
|
47
103
|
size: 'small'
|
|
48
|
-
}
|
|
104
|
+
},
|
|
105
|
+
styleOverrides: {
|
|
106
|
+
root: {
|
|
107
|
+
minWidth: '24px',
|
|
108
|
+
width: '24px',
|
|
109
|
+
height: '24px',
|
|
110
|
+
lineHeight: 0,
|
|
111
|
+
'&:disabled': {
|
|
112
|
+
opacity: 0.5
|
|
113
|
+
},
|
|
114
|
+
// Set the default size of the svg icon if not set by user.
|
|
115
|
+
'& .MuiSvgIcon-root:not([fontSize])': {
|
|
116
|
+
fontSize: 'medium'
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
variants: [
|
|
121
|
+
{
|
|
122
|
+
// The default style for input toolbar button variant.
|
|
123
|
+
props: { variant: 'input-toolbar' },
|
|
124
|
+
style: {
|
|
125
|
+
backgroundColor: 'var(--jp-brand-color1)',
|
|
126
|
+
color: 'white',
|
|
127
|
+
borderRadius: '4px',
|
|
128
|
+
boxShadow: 'none',
|
|
129
|
+
'&:hover': {
|
|
130
|
+
backgroundColor: 'var(--jp-brand-color0)',
|
|
131
|
+
boxShadow: 'none'
|
|
132
|
+
},
|
|
133
|
+
'&:disabled': {
|
|
134
|
+
backgroundColor: 'var(--jp-border-color2)',
|
|
135
|
+
color: 'var(--jp-ui-font-color3)',
|
|
136
|
+
opacity: 0.5
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
]
|
|
49
141
|
},
|
|
50
142
|
MuiInputBase: {
|
|
51
143
|
defaultProps: {
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
import { ReactWidget } from '@jupyterlab/apputils';
|
|
7
7
|
import { Cell } from '@jupyterlab/cells';
|
|
8
8
|
import { DirListing } from '@jupyterlab/filebrowser';
|
|
9
|
+
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
9
10
|
import { ICell, isCode, isMarkdown, isRaw } from '@jupyterlab/nbformat';
|
|
10
11
|
import React from 'react';
|
|
11
12
|
import { Message } from '@lumino/messaging';
|
|
12
13
|
import { Drag } from '@lumino/dragdrop';
|
|
14
|
+
import { Widget } from '@lumino/widgets';
|
|
13
15
|
|
|
14
16
|
import { Chat, IInputToolbarRegistry, MESSAGE_CLASS } from '../components';
|
|
15
17
|
import { chatIcon } from '../icons';
|
|
@@ -29,6 +31,9 @@ const FILE_BROWSER_MIME = 'application/x-jupyter-icontentsrich';
|
|
|
29
31
|
// MIME type constant for Notebook cell drag events
|
|
30
32
|
const NOTEBOOK_CELL_MIME = 'application/vnd.jupyter.cells';
|
|
31
33
|
|
|
34
|
+
// MIME type constant for TabBar file drag events
|
|
35
|
+
const TABBAR_FILE_MIME = 'application/vnd.lumino.widget-factory';
|
|
36
|
+
|
|
32
37
|
// CSS class constants
|
|
33
38
|
const INPUT_CONTAINER_CLASS = 'jp-chat-input-container';
|
|
34
39
|
const DRAG_HOVER_CLASS = 'jp-chat-drag-hover';
|
|
@@ -187,7 +192,9 @@ export class ChatWidget extends ReactWidget {
|
|
|
187
192
|
private _canHandleDrop(event: Drag.Event): boolean {
|
|
188
193
|
const types = event.mimeData.types();
|
|
189
194
|
return (
|
|
190
|
-
types.includes(NOTEBOOK_CELL_MIME) ||
|
|
195
|
+
types.includes(NOTEBOOK_CELL_MIME) ||
|
|
196
|
+
types.includes(FILE_BROWSER_MIME) ||
|
|
197
|
+
types.includes(TABBAR_FILE_MIME)
|
|
191
198
|
);
|
|
192
199
|
}
|
|
193
200
|
|
|
@@ -210,6 +217,8 @@ export class ChatWidget extends ReactWidget {
|
|
|
210
217
|
this._processCellDrop(event);
|
|
211
218
|
} else if (event.mimeData.hasData(FILE_BROWSER_MIME)) {
|
|
212
219
|
this._processFileDrop(event);
|
|
220
|
+
} else if (event.mimeData.hasData(TABBAR_FILE_MIME)) {
|
|
221
|
+
this._processTabDrop(event);
|
|
213
222
|
}
|
|
214
223
|
} catch (error) {
|
|
215
224
|
console.error('Error processing drop:', error);
|
|
@@ -329,6 +338,40 @@ export class ChatWidget extends ReactWidget {
|
|
|
329
338
|
}
|
|
330
339
|
}
|
|
331
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Process dropped tabBar files
|
|
343
|
+
*/
|
|
344
|
+
private _processTabDrop(event: Drag.Event) {
|
|
345
|
+
const factory = event.mimeData.getData(TABBAR_FILE_MIME) as () => Widget;
|
|
346
|
+
if (!factory) {
|
|
347
|
+
console.warn('No factory in drag event');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const widget = factory();
|
|
352
|
+
if (!widget || !(widget instanceof DocumentWidget)) {
|
|
353
|
+
console.warn('No file associated to the element');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const path = widget.context.path;
|
|
358
|
+
if (!path) {
|
|
359
|
+
console.warn('Widget has no path');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const mimetype =
|
|
364
|
+
widget.context.model?.mimeType || 'application/octet-stream';
|
|
365
|
+
const attachment: IFileAttachment = {
|
|
366
|
+
type: 'file',
|
|
367
|
+
value: path,
|
|
368
|
+
mimetype
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const inputModel = this._getInputFromEvent(event);
|
|
372
|
+
inputModel?.addAttachment?.(attachment);
|
|
373
|
+
}
|
|
374
|
+
|
|
332
375
|
/**
|
|
333
376
|
* Find the notebook path for a cell by searching through active and open notebooks
|
|
334
377
|
*/
|
|
@@ -546,7 +546,11 @@ function ChatSelect({
|
|
|
546
546
|
});
|
|
547
547
|
|
|
548
548
|
return (
|
|
549
|
-
<HTMLSelect
|
|
549
|
+
<HTMLSelect
|
|
550
|
+
key={Object.keys(chatNames).join()}
|
|
551
|
+
onChange={handleChange}
|
|
552
|
+
value="-"
|
|
553
|
+
>
|
|
550
554
|
<option value="-" disabled hidden>
|
|
551
555
|
Open a chat
|
|
552
556
|
</option>
|
package/style/chat.css
CHANGED
|
@@ -106,3 +106,13 @@
|
|
|
106
106
|
padding: 2px 0;
|
|
107
107
|
font-weight: bold;
|
|
108
108
|
}
|
|
109
|
+
|
|
110
|
+
.jp-chat-sidepanel .jp-chat-open {
|
|
111
|
+
flex: 1 1 0%;
|
|
112
|
+
min-width: 0;
|
|
113
|
+
max-width: fit-content;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.jp-AccordionPanel-title .jp-chat-section-toolbar {
|
|
117
|
+
flex: 0 0 auto;
|
|
118
|
+
}
|