@jupyter/chat 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__tests__/mocks.d.ts +9 -0
- package/lib/__tests__/mocks.js +18 -0
- package/lib/__tests__/model.spec.js +17 -10
- package/lib/__tests__/widgets.spec.js +4 -4
- package/lib/chat-commands/types.d.ts +2 -1
- package/lib/components/chat-input.d.ts +4 -12
- package/lib/components/chat-input.js +26 -40
- package/lib/components/chat-messages.d.ts +17 -4
- package/lib/components/chat-messages.js +28 -15
- package/lib/components/chat.d.ts +5 -5
- package/lib/components/chat.js +9 -8
- package/lib/components/code-blocks/copy-button.js +6 -3
- package/lib/components/input/buttons/attach-button.d.ts +6 -0
- package/lib/components/input/{attach-button.js → buttons/attach-button.js} +11 -8
- package/lib/components/input/buttons/cancel-button.d.ts +6 -0
- package/lib/components/input/{cancel-button.js → buttons/cancel-button.js} +5 -7
- package/lib/components/input/buttons/index.d.ts +3 -0
- package/lib/components/input/buttons/index.js +7 -0
- package/lib/components/input/buttons/send-button.d.ts +6 -0
- package/lib/components/input/{send-button.js → buttons/send-button.js} +52 -42
- package/lib/components/input/index.d.ts +3 -3
- package/lib/components/input/index.js +3 -3
- package/lib/components/input/toolbar-registry.d.ts +98 -0
- package/lib/components/input/toolbar-registry.js +85 -0
- package/lib/components/input/use-chat-commands.js +6 -5
- package/lib/components/mui-extras/tooltipped-button.d.ts +1 -1
- package/lib/components/mui-extras/tooltipped-button.js +3 -2
- package/lib/components/mui-extras/tooltipped-icon-button.js +4 -2
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/input-model.d.ts +93 -1
- package/lib/input-model.js +55 -1
- package/lib/model.d.ts +76 -9
- package/lib/model.js +42 -12
- package/lib/types.d.ts +5 -18
- package/lib/utils.d.ts +15 -0
- package/lib/utils.js +29 -0
- package/lib/widgets/chat-widget.d.ts +5 -1
- package/lib/widgets/chat-widget.js +7 -1
- package/package.json +1 -1
- package/src/__tests__/mocks.ts +31 -0
- package/src/__tests__/model.spec.ts +21 -11
- package/src/__tests__/widgets.spec.ts +5 -4
- package/src/chat-commands/types.ts +1 -1
- package/src/components/chat-input.tsx +41 -66
- package/src/components/chat-messages.tsx +44 -17
- package/src/components/chat.tsx +12 -21
- package/src/components/code-blocks/copy-button.tsx +9 -3
- package/src/components/input/{attach-button.tsx → buttons/attach-button.tsx} +15 -20
- package/src/components/input/{cancel-button.tsx → buttons/cancel-button.tsx} +9 -16
- package/src/components/input/buttons/index.ts +8 -0
- package/src/components/input/{send-button.tsx → buttons/send-button.tsx} +62 -61
- package/src/components/input/index.ts +3 -3
- package/src/components/input/toolbar-registry.tsx +162 -0
- package/src/components/input/use-chat-commands.tsx +14 -6
- package/src/components/mui-extras/tooltipped-button.tsx +4 -2
- package/src/components/mui-extras/tooltipped-icon-button.tsx +5 -2
- package/src/index.ts +1 -1
- package/src/input-model.ts +140 -2
- package/src/model.ts +110 -12
- package/src/types.ts +5 -21
- package/src/utils.ts +34 -0
- package/src/widgets/chat-widget.tsx +8 -1
- package/style/base.css +1 -0
- package/style/chat.css +6 -0
- package/style/input.css +32 -0
- package/lib/components/input/attach-button.d.ts +0 -14
- package/lib/components/input/cancel-button.d.ts +0 -11
- package/lib/components/input/send-button.d.ts +0 -18
|
@@ -8,36 +8,29 @@ import SendIcon from '@mui/icons-material/Send';
|
|
|
8
8
|
import { Box, Menu, MenuItem, Typography } from '@mui/material';
|
|
9
9
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
11
|
+
import { InputToolbarRegistry } from '../toolbar-registry';
|
|
12
|
+
import { TooltippedButton } from '../../mui-extras/tooltipped-button';
|
|
13
|
+
import { includeSelectionIcon } from '../../../icons';
|
|
14
|
+
import { IInputModel, InputModel } from '../../../input-model';
|
|
15
15
|
|
|
16
16
|
const SEND_BUTTON_CLASS = 'jp-chat-send-button';
|
|
17
17
|
const SEND_INCLUDE_OPENER_CLASS = 'jp-chat-send-include-opener';
|
|
18
18
|
const SEND_INCLUDE_LI_CLASS = 'jp-chat-send-include';
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* The send button props.
|
|
22
|
-
*/
|
|
23
|
-
export type SendButtonProps = {
|
|
24
|
-
model: IInputModel;
|
|
25
|
-
sendWithShiftEnter: boolean;
|
|
26
|
-
inputExists: boolean;
|
|
27
|
-
onSend: (selection?: Selection) => unknown;
|
|
28
|
-
hideIncludeSelection?: boolean;
|
|
29
|
-
hasButtonOnLeft?: boolean;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
20
|
/**
|
|
33
21
|
* The send button, with optional 'include selection' menu.
|
|
34
22
|
*/
|
|
35
|
-
export function SendButton(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
23
|
+
export function SendButton(
|
|
24
|
+
props: InputToolbarRegistry.IToolbarItemProps
|
|
25
|
+
): JSX.Element {
|
|
26
|
+
const { model } = props;
|
|
27
|
+
const { activeCellManager, selectionWatcher } = model;
|
|
28
|
+
const hideIncludeSelection = !activeCellManager || !selectionWatcher;
|
|
29
|
+
|
|
39
30
|
const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLElement | null>(null);
|
|
40
31
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
32
|
+
const [disabled, setDisabled] = useState(false);
|
|
33
|
+
const [tooltip, setTooltip] = useState<string>('');
|
|
41
34
|
|
|
42
35
|
const openMenu = useCallback((el: HTMLElement | null) => {
|
|
43
36
|
setMenuAnchorEl(el);
|
|
@@ -48,11 +41,36 @@ export function SendButton(props: SendButtonProps): JSX.Element {
|
|
|
48
41
|
setMenuOpen(false);
|
|
49
42
|
}, []);
|
|
50
43
|
|
|
51
|
-
const disabled = !props.inputExists;
|
|
52
|
-
|
|
53
44
|
const [selectionTooltip, setSelectionTooltip] = useState<string>('');
|
|
54
45
|
const [disableInclude, setDisableInclude] = useState<boolean>(true);
|
|
55
46
|
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const inputChanged = () => {
|
|
49
|
+
const inputExist = !!model.value.trim() || model.attachments.length;
|
|
50
|
+
setDisabled(!inputExist);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
model.valueChanged.connect(inputChanged);
|
|
54
|
+
model.attachmentsChanged?.connect(inputChanged);
|
|
55
|
+
|
|
56
|
+
inputChanged();
|
|
57
|
+
|
|
58
|
+
const configChanged = (_: IInputModel, config: InputModel.IConfig) => {
|
|
59
|
+
setTooltip(
|
|
60
|
+
(config.sendWithShiftEnter ?? false)
|
|
61
|
+
? 'Send message (SHIFT+ENTER)'
|
|
62
|
+
: 'Send message (ENTER)'
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
model.configChanged.connect(configChanged);
|
|
66
|
+
|
|
67
|
+
return () => {
|
|
68
|
+
model.valueChanged.disconnect(inputChanged);
|
|
69
|
+
model.attachmentsChanged?.disconnect(inputChanged);
|
|
70
|
+
model.configChanged?.disconnect(configChanged);
|
|
71
|
+
};
|
|
72
|
+
}, [model]);
|
|
73
|
+
|
|
56
74
|
useEffect(() => {
|
|
57
75
|
/**
|
|
58
76
|
* Enable or disable the include selection button, and adapt the tooltip.
|
|
@@ -78,55 +96,44 @@ export function SendButton(props: SendButtonProps): JSX.Element {
|
|
|
78
96
|
selectionWatcher?.selectionChanged.disconnect(toggleIncludeState);
|
|
79
97
|
activeCellManager?.availabilityChanged.disconnect(toggleIncludeState);
|
|
80
98
|
};
|
|
81
|
-
}, [activeCellManager, selectionWatcher
|
|
82
|
-
|
|
83
|
-
const defaultTooltip = props.sendWithShiftEnter
|
|
84
|
-
? 'Send message (SHIFT+ENTER)'
|
|
85
|
-
: 'Send message (ENTER)';
|
|
86
|
-
const tooltip = defaultTooltip;
|
|
99
|
+
}, [activeCellManager, selectionWatcher]);
|
|
87
100
|
|
|
88
101
|
function sendWithSelection() {
|
|
89
|
-
|
|
102
|
+
let source = '';
|
|
103
|
+
|
|
90
104
|
if (selectionWatcher?.selection) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return;
|
|
105
|
+
// Append the selected text if exists.
|
|
106
|
+
source = selectionWatcher.selection.text;
|
|
107
|
+
} else if (activeCellManager?.available) {
|
|
108
|
+
// Append the active cell content if exists.
|
|
109
|
+
source = activeCellManager.getContent(false)!.source;
|
|
97
110
|
}
|
|
111
|
+
let content = model.value;
|
|
112
|
+
if (source) {
|
|
113
|
+
content += `
|
|
98
114
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
104
|
-
source: activeCellManager.getContent(false)!.source
|
|
105
|
-
});
|
|
106
|
-
closeMenu();
|
|
107
|
-
return;
|
|
115
|
+
\`\`\`
|
|
116
|
+
${source}
|
|
117
|
+
\`\`\`
|
|
118
|
+
`;
|
|
108
119
|
}
|
|
120
|
+
model.send(content);
|
|
121
|
+
closeMenu();
|
|
122
|
+
model.value = '';
|
|
109
123
|
}
|
|
110
124
|
|
|
111
125
|
return (
|
|
112
|
-
|
|
126
|
+
<>
|
|
113
127
|
<TooltippedButton
|
|
114
|
-
onClick={() =>
|
|
128
|
+
onClick={() => model.send(model.value)}
|
|
115
129
|
disabled={disabled}
|
|
116
130
|
tooltip={tooltip}
|
|
117
131
|
buttonProps={{
|
|
118
132
|
size: 'small',
|
|
119
|
-
title:
|
|
133
|
+
title: tooltip,
|
|
120
134
|
variant: 'contained',
|
|
121
135
|
className: SEND_BUTTON_CLASS
|
|
122
136
|
}}
|
|
123
|
-
sx={{
|
|
124
|
-
minWidth: 'unset',
|
|
125
|
-
borderTopLeftRadius: hasButtonOnLeft ? '0px' : '2px',
|
|
126
|
-
borderTopRightRadius: hideIncludeSelection ? '2px' : '0px',
|
|
127
|
-
borderBottomRightRadius: hideIncludeSelection ? '2px' : '0px',
|
|
128
|
-
borderBottomLeftRadius: hasButtonOnLeft ? '0px' : '2px'
|
|
129
|
-
}}
|
|
130
137
|
>
|
|
131
138
|
<SendIcon />
|
|
132
139
|
</TooltippedButton>
|
|
@@ -151,12 +158,6 @@ export function SendButton(props: SendButtonProps): JSX.Element {
|
|
|
151
158
|
},
|
|
152
159
|
className: SEND_INCLUDE_OPENER_CLASS
|
|
153
160
|
}}
|
|
154
|
-
sx={{
|
|
155
|
-
minWidth: 'unset',
|
|
156
|
-
padding: '4px 0px',
|
|
157
|
-
borderRadius: '0px 2px 2px 0px',
|
|
158
|
-
marginLeft: '1px'
|
|
159
|
-
}}
|
|
160
161
|
>
|
|
161
162
|
<KeyboardArrowDown />
|
|
162
163
|
</TooltippedButton>
|
|
@@ -205,6 +206,6 @@ export function SendButton(props: SendButtonProps): JSX.Element {
|
|
|
205
206
|
</Menu>
|
|
206
207
|
</>
|
|
207
208
|
)}
|
|
208
|
-
|
|
209
|
+
</>
|
|
209
210
|
);
|
|
210
211
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './
|
|
6
|
+
export * from './buttons';
|
|
7
|
+
export * from './toolbar-registry';
|
|
8
|
+
export * from './use-chat-commands';
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
|
|
7
|
+
import { AttachButton, CancelButton, SendButton } from './buttons';
|
|
8
|
+
import { IInputModel } from '../../input-model';
|
|
9
|
+
import { ISignal, Signal } from '@lumino/signaling';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The toolbar registry interface.
|
|
13
|
+
*/
|
|
14
|
+
export interface IInputToolbarRegistry {
|
|
15
|
+
/**
|
|
16
|
+
* A signal emitting when the items has changed.
|
|
17
|
+
*/
|
|
18
|
+
readonly itemsChanged: ISignal<IInputToolbarRegistry, void>;
|
|
19
|
+
/**
|
|
20
|
+
* Get a toolbar item.
|
|
21
|
+
*/
|
|
22
|
+
get(name: string): InputToolbarRegistry.IToolbarItem | undefined;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the list of the visible toolbar items in order.
|
|
26
|
+
*/
|
|
27
|
+
getItems(): InputToolbarRegistry.IToolbarItem[];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Add a toolbar item.
|
|
31
|
+
*/
|
|
32
|
+
addItem(name: string, item: InputToolbarRegistry.IToolbarItem): void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hide an element.
|
|
36
|
+
*/
|
|
37
|
+
hide(name: string): void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Show an element.
|
|
41
|
+
*/
|
|
42
|
+
show(name: string): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The toolbar registry implementation.
|
|
47
|
+
*/
|
|
48
|
+
export class InputToolbarRegistry implements IInputToolbarRegistry {
|
|
49
|
+
/**
|
|
50
|
+
* A signal emitting when the items has changed.
|
|
51
|
+
*/
|
|
52
|
+
get itemsChanged(): ISignal<IInputToolbarRegistry, void> {
|
|
53
|
+
return this._itemsChanged;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get a toolbar item.
|
|
58
|
+
*/
|
|
59
|
+
get(name: string): InputToolbarRegistry.IToolbarItem | undefined {
|
|
60
|
+
return this._items.get(name);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the list of the visible toolbar items in order.
|
|
65
|
+
*/
|
|
66
|
+
getItems(): InputToolbarRegistry.IToolbarItem[] {
|
|
67
|
+
return Array.from(this._items.values())
|
|
68
|
+
.filter(item => !item.hidden)
|
|
69
|
+
.sort((a, b) => a.position - b.position);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Add a toolbar item.
|
|
74
|
+
*/
|
|
75
|
+
addItem(name: string, item: InputToolbarRegistry.IToolbarItem): void {
|
|
76
|
+
if (!this._items.has(name)) {
|
|
77
|
+
this._items.set(name, item);
|
|
78
|
+
this._itemsChanged.emit();
|
|
79
|
+
} else {
|
|
80
|
+
console.warn(`A chat input toolbar item '${name}' is already registered`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Hide an element.
|
|
86
|
+
*/
|
|
87
|
+
hide(name: string): void {
|
|
88
|
+
const item = this._items.get(name);
|
|
89
|
+
if (item) {
|
|
90
|
+
item.hidden = true;
|
|
91
|
+
this._itemsChanged.emit();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Show an element.
|
|
97
|
+
*/
|
|
98
|
+
show(name: string): void {
|
|
99
|
+
const item = this._items.get(name);
|
|
100
|
+
if (item) {
|
|
101
|
+
item.hidden = false;
|
|
102
|
+
this._itemsChanged.emit();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private _items = new Map<string, InputToolbarRegistry.IToolbarItem>();
|
|
107
|
+
private _itemsChanged = new Signal<this, void>(this);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export namespace InputToolbarRegistry {
|
|
111
|
+
/**
|
|
112
|
+
* The toolbar item interface.
|
|
113
|
+
*/
|
|
114
|
+
export interface IToolbarItem {
|
|
115
|
+
/**
|
|
116
|
+
* The react functional component with the button.
|
|
117
|
+
*
|
|
118
|
+
* NOTE:
|
|
119
|
+
* This component must be a TooltippedButton for a good integration in the toolbar.
|
|
120
|
+
*/
|
|
121
|
+
element: React.FunctionComponent<IToolbarItemProps>;
|
|
122
|
+
/**
|
|
123
|
+
* The position of the button in the toolbar.
|
|
124
|
+
*/
|
|
125
|
+
position: number;
|
|
126
|
+
/**
|
|
127
|
+
* Whether the button is hidden or not.
|
|
128
|
+
*/
|
|
129
|
+
hidden?: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The toolbar item properties, send to the button.
|
|
134
|
+
*/
|
|
135
|
+
export interface IToolbarItemProps {
|
|
136
|
+
/**
|
|
137
|
+
* The input model of the input component including the button.
|
|
138
|
+
*/
|
|
139
|
+
model: IInputModel;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The default toolbar registry if none is provided.
|
|
144
|
+
*/
|
|
145
|
+
export function defaultToolbarRegistry(): InputToolbarRegistry {
|
|
146
|
+
const registry = new InputToolbarRegistry();
|
|
147
|
+
|
|
148
|
+
registry.addItem('send', {
|
|
149
|
+
element: SendButton,
|
|
150
|
+
position: 100
|
|
151
|
+
});
|
|
152
|
+
registry.addItem('attach', {
|
|
153
|
+
element: AttachButton,
|
|
154
|
+
position: 20
|
|
155
|
+
});
|
|
156
|
+
registry.addItem('cancel', {
|
|
157
|
+
element: CancelButton,
|
|
158
|
+
position: 10
|
|
159
|
+
});
|
|
160
|
+
return registry;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import { useEffect, useState } from 'react';
|
|
6
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
8
7
|
import type {
|
|
9
8
|
AutocompleteChangeReason,
|
|
10
9
|
AutocompleteProps as GenericAutocompleteProps
|
|
11
10
|
} from '@mui/material';
|
|
12
11
|
import { Box } from '@mui/material';
|
|
12
|
+
import React, { useEffect, useState } from 'react';
|
|
13
13
|
|
|
14
14
|
import { ChatCommand, IChatCommandRegistry } from '../../chat-commands';
|
|
15
15
|
import { IInputModel } from '../../input-model';
|
|
@@ -131,9 +131,11 @@ export function useChatCommands(
|
|
|
131
131
|
___: unknown
|
|
132
132
|
) => {
|
|
133
133
|
const { key, ...listItemProps } = defaultProps;
|
|
134
|
-
const commandIcon: JSX.Element = (
|
|
134
|
+
const commandIcon: JSX.Element = React.isValidElement(command.icon) ? (
|
|
135
|
+
command.icon
|
|
136
|
+
) : (
|
|
135
137
|
<span>
|
|
136
|
-
{
|
|
138
|
+
{command.icon instanceof LabIcon ? (
|
|
137
139
|
<command.icon.react />
|
|
138
140
|
) : (
|
|
139
141
|
command.icon
|
|
@@ -144,8 +146,14 @@ export function useChatCommands(
|
|
|
144
146
|
<Box key={key} component="li" {...listItemProps}>
|
|
145
147
|
{commandIcon}
|
|
146
148
|
<p className="jp-chat-command-name">{command.name}</p>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
{command.description && (
|
|
150
|
+
<>
|
|
151
|
+
<span> - </span>
|
|
152
|
+
<p className="jp-chat-command-description">
|
|
153
|
+
{command.description}
|
|
154
|
+
</p>
|
|
155
|
+
</>
|
|
156
|
+
)}
|
|
149
157
|
</Box>
|
|
150
158
|
);
|
|
151
159
|
},
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from 'react';
|
|
7
6
|
import { Button, ButtonProps, SxProps, TooltipProps } from '@mui/material';
|
|
7
|
+
import React from 'react';
|
|
8
8
|
|
|
9
9
|
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
10
10
|
|
|
11
|
+
const TOOLTIPPED_WRAP_CLASS = 'jp-chat-tooltipped-wrap';
|
|
12
|
+
|
|
11
13
|
export type TooltippedButtonProps = {
|
|
12
14
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
13
15
|
tooltip: string;
|
|
@@ -72,7 +74,7 @@ export function TooltippedButton(props: TooltippedButtonProps): JSX.Element {
|
|
|
72
74
|
|
|
73
75
|
See: https://mui.com/material-ui/react-tooltip/#disabled-elements
|
|
74
76
|
*/}
|
|
75
|
-
<span style={{ cursor: 'default' }}>
|
|
77
|
+
<span style={{ cursor: 'default' }} className={TOOLTIPPED_WRAP_CLASS}>
|
|
76
78
|
<Button
|
|
77
79
|
{...props.buttonProps}
|
|
78
80
|
onClick={props.onClick}
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { classes } from '@jupyterlab/ui-components';
|
|
7
7
|
import { IconButton, IconButtonProps, TooltipProps } from '@mui/material';
|
|
8
|
+
import React from 'react';
|
|
8
9
|
|
|
9
10
|
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
10
11
|
|
|
12
|
+
const TOOLTIPPED_WRAP_CLASS = 'jp-chat-tooltipped-wrap';
|
|
13
|
+
|
|
11
14
|
export type TooltippedIconButtonProps = {
|
|
12
15
|
onClick: () => unknown;
|
|
13
16
|
tooltip: string;
|
|
@@ -68,7 +71,7 @@ export function TooltippedIconButton(
|
|
|
68
71
|
|
|
69
72
|
See: https://mui.com/material-ui/react-tooltip/#disabled-elements
|
|
70
73
|
*/}
|
|
71
|
-
<span className={props.className}>
|
|
74
|
+
<span className={classes(props.className, TOOLTIPPED_WRAP_CLASS)}>
|
|
72
75
|
<IconButton
|
|
73
76
|
{...props.iconButtonProps}
|
|
74
77
|
onClick={props.onClick}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export * from './active-cell-manager';
|
|
7
|
+
export * from './chat-commands';
|
|
7
8
|
export * from './components';
|
|
8
9
|
export * from './icons';
|
|
9
10
|
export * from './input-model';
|
|
@@ -14,4 +15,3 @@ export * from './types';
|
|
|
14
15
|
export * from './widgets/chat-error';
|
|
15
16
|
export * from './widgets/chat-sidebar';
|
|
16
17
|
export * from './widgets/chat-widget';
|
|
17
|
-
export * from './chat-commands';
|
package/src/input-model.ts
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
6
7
|
import { IDisposable } from '@lumino/disposable';
|
|
7
8
|
import { ISignal, Signal } from '@lumino/signaling';
|
|
8
9
|
import { IActiveCellManager } from './active-cell-manager';
|
|
9
10
|
import { ISelectionWatcher } from './selection-watcher';
|
|
10
|
-
import {
|
|
11
|
+
import { IChatContext } from './model';
|
|
12
|
+
import { IAttachment, IUser } from './types';
|
|
11
13
|
|
|
12
14
|
const WHITESPACE = new Set([' ', '\n', '\t']);
|
|
13
15
|
|
|
@@ -15,6 +17,21 @@ const WHITESPACE = new Set([' ', '\n', '\t']);
|
|
|
15
17
|
* The chat input interface.
|
|
16
18
|
*/
|
|
17
19
|
export interface IInputModel extends IDisposable {
|
|
20
|
+
/**
|
|
21
|
+
* The chat context (a readonly subset of the chat model).
|
|
22
|
+
*/
|
|
23
|
+
readonly chatContext: IChatContext;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Function to send a message.
|
|
27
|
+
*/
|
|
28
|
+
send: (content: string) => void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Optional function to cancel edition.
|
|
32
|
+
*/
|
|
33
|
+
cancel: (() => void) | undefined;
|
|
34
|
+
|
|
18
35
|
/**
|
|
19
36
|
* The entire input value.
|
|
20
37
|
*/
|
|
@@ -56,6 +73,11 @@ export interface IInputModel extends IDisposable {
|
|
|
56
73
|
*/
|
|
57
74
|
readonly selectionWatcher: ISelectionWatcher | null;
|
|
58
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Get the document manager.
|
|
78
|
+
*/
|
|
79
|
+
readonly documentManager: IDocumentManager | null;
|
|
80
|
+
|
|
59
81
|
/**
|
|
60
82
|
* The input configuration.
|
|
61
83
|
*/
|
|
@@ -105,6 +127,26 @@ export interface IInputModel extends IDisposable {
|
|
|
105
127
|
* Replace the current word in the input with a new one.
|
|
106
128
|
*/
|
|
107
129
|
replaceCurrentWord(newWord: string): void;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The mentioned user list.
|
|
133
|
+
*/
|
|
134
|
+
readonly mentions: IUser[];
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Add user mention.
|
|
138
|
+
*/
|
|
139
|
+
addMention?(user: IUser): void;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Remove a user mention.
|
|
143
|
+
*/
|
|
144
|
+
removeMention(user: IUser): void;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clear mentions list.
|
|
148
|
+
*/
|
|
149
|
+
clearMentions(): void;
|
|
108
150
|
}
|
|
109
151
|
|
|
110
152
|
/**
|
|
@@ -112,17 +154,41 @@ export interface IInputModel extends IDisposable {
|
|
|
112
154
|
*/
|
|
113
155
|
export class InputModel implements IInputModel {
|
|
114
156
|
constructor(options: InputModel.IOptions) {
|
|
157
|
+
this._onSend = options.onSend;
|
|
158
|
+
this._chatContext = options.chatContext;
|
|
115
159
|
this._value = options.value || '';
|
|
116
160
|
this._attachments = options.attachments || [];
|
|
161
|
+
this._mentions = options.mentions || [];
|
|
117
162
|
this.cursorIndex = options.cursorIndex || this.value.length;
|
|
118
163
|
this._activeCellManager = options.activeCellManager ?? null;
|
|
119
164
|
this._selectionWatcher = options.selectionWatcher ?? null;
|
|
120
|
-
|
|
165
|
+
this._documentManager = options.documentManager ?? null;
|
|
121
166
|
this._config = {
|
|
122
167
|
...options.config
|
|
123
168
|
};
|
|
169
|
+
this.cancel = options.onCancel;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The chat context (a readonly subset of the chat model);
|
|
174
|
+
*/
|
|
175
|
+
get chatContext(): IChatContext {
|
|
176
|
+
return this._chatContext;
|
|
124
177
|
}
|
|
125
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Function to send a message.
|
|
181
|
+
*/
|
|
182
|
+
send = (input: string): void => {
|
|
183
|
+
this._onSend(input, this);
|
|
184
|
+
this.value = '';
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Optional function to cancel edition.
|
|
189
|
+
*/
|
|
190
|
+
cancel: (() => void) | undefined;
|
|
191
|
+
|
|
126
192
|
/**
|
|
127
193
|
* The entire input value.
|
|
128
194
|
*/
|
|
@@ -199,6 +265,13 @@ export class InputModel implements IInputModel {
|
|
|
199
265
|
return this._selectionWatcher;
|
|
200
266
|
}
|
|
201
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Get the document manager.
|
|
270
|
+
*/
|
|
271
|
+
get documentManager(): IDocumentManager | null {
|
|
272
|
+
return this._documentManager;
|
|
273
|
+
}
|
|
274
|
+
|
|
202
275
|
/**
|
|
203
276
|
* The input configuration.
|
|
204
277
|
*/
|
|
@@ -297,6 +370,40 @@ export class InputModel implements IInputModel {
|
|
|
297
370
|
this.value = this.value.slice(0, start) + newWord + this.value.slice(end);
|
|
298
371
|
}
|
|
299
372
|
|
|
373
|
+
/**
|
|
374
|
+
* The mentioned user list.
|
|
375
|
+
*/
|
|
376
|
+
get mentions(): IUser[] {
|
|
377
|
+
return this._mentions;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Add a user mention.
|
|
382
|
+
*/
|
|
383
|
+
addMention(user: IUser): void {
|
|
384
|
+
const usernames = this._mentions.map(user => user.username);
|
|
385
|
+
if (!usernames.includes(user.username)) {
|
|
386
|
+
this._mentions.push(user);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Remove a user mention.
|
|
392
|
+
*/
|
|
393
|
+
removeMention(user: IUser): void {
|
|
394
|
+
const index = this._mentions.indexOf(user);
|
|
395
|
+
if (index > -1) {
|
|
396
|
+
this._mentions.splice(index, 1);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Clear mentions list.
|
|
402
|
+
*/
|
|
403
|
+
clearMentions = (): void => {
|
|
404
|
+
this._mentions = [];
|
|
405
|
+
};
|
|
406
|
+
|
|
300
407
|
/**
|
|
301
408
|
* Dispose the input model.
|
|
302
409
|
*/
|
|
@@ -314,12 +421,16 @@ export class InputModel implements IInputModel {
|
|
|
314
421
|
return this._isDisposed;
|
|
315
422
|
}
|
|
316
423
|
|
|
424
|
+
private _onSend: (input: string, model?: InputModel) => void;
|
|
425
|
+
private _chatContext: IChatContext;
|
|
317
426
|
private _value: string;
|
|
318
427
|
private _cursorIndex: number | null = null;
|
|
319
428
|
private _currentWord: string | null = null;
|
|
320
429
|
private _attachments: IAttachment[];
|
|
430
|
+
private _mentions: IUser[];
|
|
321
431
|
private _activeCellManager: IActiveCellManager | null;
|
|
322
432
|
private _selectionWatcher: ISelectionWatcher | null;
|
|
433
|
+
private _documentManager: IDocumentManager | null;
|
|
323
434
|
private _config: InputModel.IConfig;
|
|
324
435
|
private _valueChanged = new Signal<IInputModel, string>(this);
|
|
325
436
|
private _cursorIndexChanged = new Signal<IInputModel, number | null>(this);
|
|
@@ -332,6 +443,23 @@ export class InputModel implements IInputModel {
|
|
|
332
443
|
|
|
333
444
|
export namespace InputModel {
|
|
334
445
|
export interface IOptions {
|
|
446
|
+
/**
|
|
447
|
+
* The chat context (a readonly subset of the chat model).
|
|
448
|
+
*/
|
|
449
|
+
chatContext: IChatContext;
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* The function that should send the message.
|
|
453
|
+
* @param content - the content of the message.
|
|
454
|
+
* @param model - the model of the input sending the message.
|
|
455
|
+
*/
|
|
456
|
+
onSend: (content: string, model?: InputModel) => void;
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Function that should cancel the message edition.
|
|
460
|
+
*/
|
|
461
|
+
onCancel?: () => void;
|
|
462
|
+
|
|
335
463
|
/**
|
|
336
464
|
* The initial value of the input.
|
|
337
465
|
*/
|
|
@@ -342,6 +470,11 @@ export namespace InputModel {
|
|
|
342
470
|
*/
|
|
343
471
|
attachments?: IAttachment[];
|
|
344
472
|
|
|
473
|
+
/**
|
|
474
|
+
* The initial mentions.
|
|
475
|
+
*/
|
|
476
|
+
mentions?: IUser[];
|
|
477
|
+
|
|
345
478
|
/**
|
|
346
479
|
* The current cursor index.
|
|
347
480
|
* This refers to the index of the character in front of the cursor.
|
|
@@ -362,6 +495,11 @@ export namespace InputModel {
|
|
|
362
495
|
* Selection watcher.
|
|
363
496
|
*/
|
|
364
497
|
selectionWatcher?: ISelectionWatcher | null;
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Document manager.
|
|
501
|
+
*/
|
|
502
|
+
documentManager?: IDocumentManager | null;
|
|
365
503
|
}
|
|
366
504
|
|
|
367
505
|
export interface IConfig {
|