@jupyter/chat 0.3.1 → 0.5.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/active-cell-manager.d.ts +3 -0
- package/lib/components/chat-input.d.ts +4 -0
- package/lib/components/chat-input.js +50 -18
- package/lib/components/chat-messages.d.ts +31 -1
- package/lib/components/chat-messages.js +55 -19
- package/lib/components/chat.js +1 -1
- package/lib/components/code-blocks/code-toolbar.js +50 -16
- package/lib/components/input/cancel-button.d.ts +12 -0
- package/lib/components/input/cancel-button.js +27 -0
- package/lib/components/input/send-button.d.ts +18 -0
- package/lib/components/input/send-button.js +143 -0
- package/lib/components/mui-extras/tooltipped-button.d.ts +41 -0
- package/lib/components/mui-extras/tooltipped-button.js +43 -0
- package/lib/icons.d.ts +1 -0
- package/lib/icons.js +5 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/model.d.ts +68 -8
- package/lib/model.js +57 -12
- package/lib/selection-watcher.d.ts +62 -0
- package/lib/selection-watcher.js +134 -0
- package/lib/types.d.ts +22 -0
- package/lib/utils.d.ts +11 -0
- package/lib/utils.js +37 -0
- package/package.json +3 -15
- package/src/active-cell-manager.ts +3 -0
- package/src/components/chat-input.tsx +71 -32
- package/src/components/chat-messages.tsx +106 -32
- package/src/components/chat.tsx +1 -1
- package/src/components/code-blocks/code-toolbar.tsx +55 -17
- package/src/components/input/cancel-button.tsx +47 -0
- package/src/components/input/send-button.tsx +210 -0
- package/src/components/mui-extras/tooltipped-button.tsx +92 -0
- package/src/icons.ts +6 -0
- package/src/index.ts +1 -0
- package/src/model.ts +102 -13
- package/src/selection-watcher.ts +221 -0
- package/src/types.ts +25 -0
- package/src/utils.ts +47 -0
- package/style/chat.css +13 -0
- package/style/icons/include-selection.svg +5 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
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';
|
|
10
|
+
|
|
11
|
+
import { IChatModel } from '../../model';
|
|
12
|
+
import { TooltippedButton } from '../mui-extras/tooltipped-button';
|
|
13
|
+
import { includeSelectionIcon } from '../../icons';
|
|
14
|
+
import { Selection } from '../../types';
|
|
15
|
+
|
|
16
|
+
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
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The send button props.
|
|
22
|
+
*/
|
|
23
|
+
export type SendButtonProps = {
|
|
24
|
+
model: IChatModel;
|
|
25
|
+
sendWithShiftEnter: boolean;
|
|
26
|
+
inputExists: boolean;
|
|
27
|
+
onSend: (selection?: Selection) => unknown;
|
|
28
|
+
hideIncludeSelection?: boolean;
|
|
29
|
+
hasButtonOnLeft?: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The send button, with optional 'include selection' menu.
|
|
34
|
+
*/
|
|
35
|
+
export function SendButton(props: SendButtonProps): JSX.Element {
|
|
36
|
+
const { activeCellManager, selectionWatcher } = props.model;
|
|
37
|
+
const hideIncludeSelection = props.hideIncludeSelection ?? false;
|
|
38
|
+
const hasButtonOnLeft = props.hasButtonOnLeft ?? false;
|
|
39
|
+
const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLElement | null>(null);
|
|
40
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
41
|
+
|
|
42
|
+
const openMenu = useCallback((el: HTMLElement | null) => {
|
|
43
|
+
setMenuAnchorEl(el);
|
|
44
|
+
setMenuOpen(true);
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
const closeMenu = useCallback(() => {
|
|
48
|
+
setMenuOpen(false);
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const disabled = !props.inputExists;
|
|
52
|
+
|
|
53
|
+
const [selectionTooltip, setSelectionTooltip] = useState<string>('');
|
|
54
|
+
const [disableInclude, setDisableInclude] = useState<boolean>(true);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
/**
|
|
58
|
+
* Enable or disable the include selection button, and adapt the tooltip.
|
|
59
|
+
*/
|
|
60
|
+
const toggleIncludeState = () => {
|
|
61
|
+
setDisableInclude(
|
|
62
|
+
!(selectionWatcher?.selection || activeCellManager?.available)
|
|
63
|
+
);
|
|
64
|
+
const tooltip = selectionWatcher?.selection
|
|
65
|
+
? `${selectionWatcher.selection.numLines} line(s) selected`
|
|
66
|
+
: activeCellManager?.available
|
|
67
|
+
? 'Code from 1 active cell'
|
|
68
|
+
: 'No selection or active cell';
|
|
69
|
+
setSelectionTooltip(tooltip);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (!hideIncludeSelection) {
|
|
73
|
+
selectionWatcher?.selectionChanged.connect(toggleIncludeState);
|
|
74
|
+
activeCellManager?.availabilityChanged.connect(toggleIncludeState);
|
|
75
|
+
toggleIncludeState();
|
|
76
|
+
}
|
|
77
|
+
return () => {
|
|
78
|
+
selectionWatcher?.selectionChanged.disconnect(toggleIncludeState);
|
|
79
|
+
activeCellManager?.availabilityChanged.disconnect(toggleIncludeState);
|
|
80
|
+
};
|
|
81
|
+
}, [activeCellManager, selectionWatcher, hideIncludeSelection]);
|
|
82
|
+
|
|
83
|
+
const defaultTooltip = props.sendWithShiftEnter
|
|
84
|
+
? 'Send message (SHIFT+ENTER)'
|
|
85
|
+
: 'Send message (ENTER)';
|
|
86
|
+
const tooltip = defaultTooltip;
|
|
87
|
+
|
|
88
|
+
function sendWithSelection() {
|
|
89
|
+
// Append the selected text if exists.
|
|
90
|
+
if (selectionWatcher?.selection) {
|
|
91
|
+
props.onSend({
|
|
92
|
+
type: 'text',
|
|
93
|
+
source: selectionWatcher.selection.text
|
|
94
|
+
});
|
|
95
|
+
closeMenu();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Append the active cell content if exists.
|
|
100
|
+
if (activeCellManager?.available) {
|
|
101
|
+
props.onSend({
|
|
102
|
+
type: 'cell',
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
104
|
+
source: activeCellManager.getContent(false)!.source
|
|
105
|
+
});
|
|
106
|
+
closeMenu();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Box sx={{ display: 'flex', flexWrap: 'nowrap' }}>
|
|
113
|
+
<TooltippedButton
|
|
114
|
+
onClick={() => props.onSend()}
|
|
115
|
+
disabled={disabled}
|
|
116
|
+
tooltip={tooltip}
|
|
117
|
+
buttonProps={{
|
|
118
|
+
size: 'small',
|
|
119
|
+
title: defaultTooltip,
|
|
120
|
+
variant: 'contained',
|
|
121
|
+
className: SEND_BUTTON_CLASS
|
|
122
|
+
}}
|
|
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
|
+
>
|
|
131
|
+
<SendIcon />
|
|
132
|
+
</TooltippedButton>
|
|
133
|
+
{!hideIncludeSelection && (
|
|
134
|
+
<>
|
|
135
|
+
<TooltippedButton
|
|
136
|
+
onClick={e => {
|
|
137
|
+
openMenu(e.currentTarget);
|
|
138
|
+
}}
|
|
139
|
+
disabled={disabled}
|
|
140
|
+
tooltip=""
|
|
141
|
+
buttonProps={{
|
|
142
|
+
variant: 'contained',
|
|
143
|
+
onKeyDown: e => {
|
|
144
|
+
if (e.key !== 'Enter' && e.key !== ' ') {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
openMenu(e.currentTarget);
|
|
148
|
+
// stopping propagation of this event prevents the prompt from being
|
|
149
|
+
// sent when the dropdown button is selected and clicked via 'Enter'.
|
|
150
|
+
e.stopPropagation();
|
|
151
|
+
},
|
|
152
|
+
className: SEND_INCLUDE_OPENER_CLASS
|
|
153
|
+
}}
|
|
154
|
+
sx={{
|
|
155
|
+
minWidth: 'unset',
|
|
156
|
+
padding: '4px 0px',
|
|
157
|
+
borderRadius: '0px 2px 2px 0px',
|
|
158
|
+
marginLeft: '1px'
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<KeyboardArrowDown />
|
|
162
|
+
</TooltippedButton>
|
|
163
|
+
<Menu
|
|
164
|
+
open={menuOpen}
|
|
165
|
+
onClose={closeMenu}
|
|
166
|
+
anchorEl={menuAnchorEl}
|
|
167
|
+
anchorOrigin={{
|
|
168
|
+
vertical: 'top',
|
|
169
|
+
horizontal: 'right'
|
|
170
|
+
}}
|
|
171
|
+
transformOrigin={{
|
|
172
|
+
vertical: 'bottom',
|
|
173
|
+
horizontal: 'right'
|
|
174
|
+
}}
|
|
175
|
+
sx={{
|
|
176
|
+
'& .MuiMenuItem-root': {
|
|
177
|
+
display: 'flex',
|
|
178
|
+
alignItems: 'center',
|
|
179
|
+
gap: '8px'
|
|
180
|
+
},
|
|
181
|
+
'& svg': {
|
|
182
|
+
lineHeight: 0
|
|
183
|
+
}
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
<MenuItem
|
|
187
|
+
onClick={e => {
|
|
188
|
+
sendWithSelection();
|
|
189
|
+
// prevent sending second message with no selection
|
|
190
|
+
e.stopPropagation();
|
|
191
|
+
}}
|
|
192
|
+
disabled={disableInclude}
|
|
193
|
+
className={SEND_INCLUDE_LI_CLASS}
|
|
194
|
+
>
|
|
195
|
+
<includeSelectionIcon.react />
|
|
196
|
+
<Box>
|
|
197
|
+
<Typography display="block">
|
|
198
|
+
Send message with selection
|
|
199
|
+
</Typography>
|
|
200
|
+
<Typography display="block" sx={{ opacity: 0.618 }}>
|
|
201
|
+
{selectionTooltip}
|
|
202
|
+
</Typography>
|
|
203
|
+
</Box>
|
|
204
|
+
</MenuItem>
|
|
205
|
+
</Menu>
|
|
206
|
+
</>
|
|
207
|
+
)}
|
|
208
|
+
</Box>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { Button, ButtonProps, SxProps, TooltipProps } from '@mui/material';
|
|
8
|
+
|
|
9
|
+
import { ContrastingTooltip } from './contrasting-tooltip';
|
|
10
|
+
|
|
11
|
+
export type TooltippedButtonProps = {
|
|
12
|
+
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
13
|
+
tooltip: string;
|
|
14
|
+
children: JSX.Element;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
placement?: TooltipProps['placement'];
|
|
17
|
+
/**
|
|
18
|
+
* The offset of the tooltip popup.
|
|
19
|
+
*
|
|
20
|
+
* The expected syntax is defined by the Popper library:
|
|
21
|
+
* https://popper.js.org/docs/v2/modifiers/offset/
|
|
22
|
+
*/
|
|
23
|
+
offset?: [number, number];
|
|
24
|
+
'aria-label'?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Props passed directly to the MUI `Button` component.
|
|
27
|
+
*/
|
|
28
|
+
buttonProps?: ButtonProps;
|
|
29
|
+
/**
|
|
30
|
+
* Styles applied to the MUI `Button` component.
|
|
31
|
+
*/
|
|
32
|
+
sx?: SxProps;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A component that renders an MUI `Button` with a high-contrast tooltip
|
|
37
|
+
* provided by `ContrastingTooltip`. This component differs from the MUI
|
|
38
|
+
* defaults in the following ways:
|
|
39
|
+
*
|
|
40
|
+
* - Shows the tooltip on hover even if disabled.
|
|
41
|
+
* - Renders the tooltip above the button by default.
|
|
42
|
+
* - Renders the tooltip closer to the button by default.
|
|
43
|
+
* - Lowers the opacity of the Button when disabled.
|
|
44
|
+
* - Renders the Button with `line-height: 0` to avoid showing extra
|
|
45
|
+
* vertical space in SVG icons.
|
|
46
|
+
*
|
|
47
|
+
* NOTE TO DEVS: Please keep this component's features synchronized with
|
|
48
|
+
* features available to `TooltippedIconButton`.
|
|
49
|
+
*/
|
|
50
|
+
export function TooltippedButton(props: TooltippedButtonProps): JSX.Element {
|
|
51
|
+
return (
|
|
52
|
+
<ContrastingTooltip
|
|
53
|
+
title={props.tooltip}
|
|
54
|
+
placement={props.placement ?? 'top'}
|
|
55
|
+
slotProps={{
|
|
56
|
+
popper: {
|
|
57
|
+
modifiers: [
|
|
58
|
+
{
|
|
59
|
+
name: 'offset',
|
|
60
|
+
options: {
|
|
61
|
+
offset: [0, -8]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
{/*
|
|
69
|
+
By default, tooltips never appear when the Button is disabled. The
|
|
70
|
+
official way to support this feature in MUI is to wrap the child Button
|
|
71
|
+
element in a `span` element.
|
|
72
|
+
|
|
73
|
+
See: https://mui.com/material-ui/react-tooltip/#disabled-elements
|
|
74
|
+
*/}
|
|
75
|
+
<span style={{ cursor: 'default' }}>
|
|
76
|
+
<Button
|
|
77
|
+
{...props.buttonProps}
|
|
78
|
+
onClick={props.onClick}
|
|
79
|
+
disabled={props.disabled}
|
|
80
|
+
sx={{
|
|
81
|
+
lineHeight: 0,
|
|
82
|
+
...(props.disabled && { opacity: 0.5 }),
|
|
83
|
+
...props.sx
|
|
84
|
+
}}
|
|
85
|
+
aria-label={props['aria-label']}
|
|
86
|
+
>
|
|
87
|
+
{props.children}
|
|
88
|
+
</Button>
|
|
89
|
+
</span>
|
|
90
|
+
</ContrastingTooltip>
|
|
91
|
+
);
|
|
92
|
+
}
|
package/src/icons.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
9
9
|
|
|
10
10
|
import chatSvgStr from '../style/icons/chat.svg';
|
|
11
|
+
import includeSelectionIconStr from '../style/icons/include-selection.svg';
|
|
11
12
|
import readSvgStr from '../style/icons/read.svg';
|
|
12
13
|
import replaceCellSvg from '../style/icons/replace-cell.svg';
|
|
13
14
|
|
|
@@ -25,3 +26,8 @@ export const replaceCellIcon = new LabIcon({
|
|
|
25
26
|
name: 'jupyter-ai::replace-cell',
|
|
26
27
|
svgstr: replaceCellSvg
|
|
27
28
|
});
|
|
29
|
+
|
|
30
|
+
export const includeSelectionIcon = new LabIcon({
|
|
31
|
+
name: 'jupyter-chat::include',
|
|
32
|
+
svgstr: includeSelectionIconStr
|
|
33
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './model';
|
|
|
8
8
|
export * from './registry';
|
|
9
9
|
export * from './types';
|
|
10
10
|
export * from './active-cell-manager';
|
|
11
|
+
export * from './selection-watcher';
|
|
11
12
|
export * from './widgets/chat-error';
|
|
12
13
|
export * from './widgets/chat-sidebar';
|
|
13
14
|
export * from './widgets/chat-widget';
|
package/src/model.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
IUser
|
|
16
16
|
} from './types';
|
|
17
17
|
import { IActiveCellManager } from './active-cell-manager';
|
|
18
|
+
import { ISelectionWatcher } from './selection-watcher';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* The chat model interface.
|
|
@@ -55,6 +56,11 @@ export interface IChatModel extends IDisposable {
|
|
|
55
56
|
*/
|
|
56
57
|
readonly activeCellManager: IActiveCellManager | null;
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Get the selection watcher.
|
|
61
|
+
*/
|
|
62
|
+
readonly selectionWatcher: ISelectionWatcher | null;
|
|
63
|
+
|
|
58
64
|
/**
|
|
59
65
|
* A signal emitting when the messages list is updated.
|
|
60
66
|
*/
|
|
@@ -75,6 +81,16 @@ export interface IChatModel extends IDisposable {
|
|
|
75
81
|
*/
|
|
76
82
|
readonly viewportChanged?: ISignal<IChatModel, number[]>;
|
|
77
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A signal emitting when the writers change.
|
|
86
|
+
*/
|
|
87
|
+
readonly writersChanged?: ISignal<IChatModel, IUser[]>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A signal emitting when the focus is requested on the input.
|
|
91
|
+
*/
|
|
92
|
+
readonly focusInputSignal?: ISignal<IChatModel, void>;
|
|
93
|
+
|
|
78
94
|
/**
|
|
79
95
|
* Send a message, to be defined depending on the chosen technology.
|
|
80
96
|
* Default to no-op.
|
|
@@ -82,7 +98,7 @@ export interface IChatModel extends IDisposable {
|
|
|
82
98
|
* @param message - the message to send.
|
|
83
99
|
* @returns whether the message has been sent or not, or nothing if not needed.
|
|
84
100
|
*/
|
|
85
|
-
|
|
101
|
+
sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
|
|
86
102
|
|
|
87
103
|
/**
|
|
88
104
|
* Optional, to update a message from the chat panel.
|
|
@@ -139,6 +155,21 @@ export interface IChatModel extends IDisposable {
|
|
|
139
155
|
* @param count - the number of messages to delete.
|
|
140
156
|
*/
|
|
141
157
|
messagesDeleted(index: number, count: number): void;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Update the current writers list.
|
|
161
|
+
*/
|
|
162
|
+
updateWriters(writers: IUser[]): void;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Function to request the focus on the input of the chat.
|
|
166
|
+
*/
|
|
167
|
+
focusInput(): void;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Function called by the input on key pressed.
|
|
171
|
+
*/
|
|
172
|
+
inputChanged?(input?: string): void;
|
|
142
173
|
}
|
|
143
174
|
|
|
144
175
|
/**
|
|
@@ -154,23 +185,19 @@ export class ChatModel implements IChatModel {
|
|
|
154
185
|
const config = options.config ?? {};
|
|
155
186
|
|
|
156
187
|
// Stack consecutive messages from the same user by default.
|
|
157
|
-
this._config = {
|
|
188
|
+
this._config = {
|
|
189
|
+
stackMessages: true,
|
|
190
|
+
sendTypingNotification: true,
|
|
191
|
+
...config
|
|
192
|
+
};
|
|
158
193
|
|
|
159
194
|
this._commands = options.commands;
|
|
160
195
|
|
|
161
196
|
this._activeCellManager = options.activeCellManager ?? null;
|
|
162
|
-
}
|
|
163
197
|
|
|
164
|
-
|
|
165
|
-
* The chat messages list.
|
|
166
|
-
*/
|
|
167
|
-
get messages(): IChatMessage[] {
|
|
168
|
-
return this._messages;
|
|
198
|
+
this._selectionWatcher = options.selectionWatcher ?? null;
|
|
169
199
|
}
|
|
170
200
|
|
|
171
|
-
get activeCellManager(): IActiveCellManager | null {
|
|
172
|
-
return this._activeCellManager;
|
|
173
|
-
}
|
|
174
201
|
/**
|
|
175
202
|
* The chat model id.
|
|
176
203
|
*/
|
|
@@ -191,6 +218,26 @@ export class ChatModel implements IChatModel {
|
|
|
191
218
|
this._name = value;
|
|
192
219
|
}
|
|
193
220
|
|
|
221
|
+
/**
|
|
222
|
+
* The chat messages list.
|
|
223
|
+
*/
|
|
224
|
+
get messages(): IChatMessage[] {
|
|
225
|
+
return this._messages;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get the active cell manager.
|
|
229
|
+
*/
|
|
230
|
+
get activeCellManager(): IActiveCellManager | null {
|
|
231
|
+
return this._activeCellManager;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get the selection watcher.
|
|
236
|
+
*/
|
|
237
|
+
get selectionWatcher(): ISelectionWatcher | null {
|
|
238
|
+
return this._selectionWatcher;
|
|
239
|
+
}
|
|
240
|
+
|
|
194
241
|
/**
|
|
195
242
|
* Timestamp of the last read message in local storage.
|
|
196
243
|
*/
|
|
@@ -328,6 +375,20 @@ export class ChatModel implements IChatModel {
|
|
|
328
375
|
return this._viewportChanged;
|
|
329
376
|
}
|
|
330
377
|
|
|
378
|
+
/**
|
|
379
|
+
* A signal emitting when the writers change.
|
|
380
|
+
*/
|
|
381
|
+
get writersChanged(): ISignal<IChatModel, IUser[]> {
|
|
382
|
+
return this._writersChanged;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* A signal emitting when the focus is requested on the input.
|
|
387
|
+
*/
|
|
388
|
+
get focusInputSignal(): ISignal<IChatModel, void> {
|
|
389
|
+
return this._focusInputSignal;
|
|
390
|
+
}
|
|
391
|
+
|
|
331
392
|
/**
|
|
332
393
|
* Send a message, to be defined depending on the chosen technology.
|
|
333
394
|
* Default to no-op.
|
|
@@ -335,7 +396,7 @@ export class ChatModel implements IChatModel {
|
|
|
335
396
|
* @param message - the message to send.
|
|
336
397
|
* @returns whether the message has been sent or not.
|
|
337
398
|
*/
|
|
338
|
-
|
|
399
|
+
sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void {}
|
|
339
400
|
|
|
340
401
|
/**
|
|
341
402
|
* Dispose the chat model.
|
|
@@ -435,6 +496,26 @@ export class ChatModel implements IChatModel {
|
|
|
435
496
|
this._messagesUpdated.emit();
|
|
436
497
|
}
|
|
437
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Update the current writers list.
|
|
501
|
+
* This implementation only propagate the list via a signal.
|
|
502
|
+
*/
|
|
503
|
+
updateWriters(writers: IUser[]): void {
|
|
504
|
+
this._writersChanged.emit(writers);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Function to request the focus on the input of the chat.
|
|
509
|
+
*/
|
|
510
|
+
focusInput(): void {
|
|
511
|
+
this._focusInputSignal.emit();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Function called by the input on key pressed.
|
|
516
|
+
*/
|
|
517
|
+
inputChanged?(input?: string): void {}
|
|
518
|
+
|
|
438
519
|
/**
|
|
439
520
|
* Add unread messages to the list.
|
|
440
521
|
* @param indexes - list of new indexes.
|
|
@@ -493,11 +574,14 @@ export class ChatModel implements IChatModel {
|
|
|
493
574
|
private _isDisposed = false;
|
|
494
575
|
private _commands?: CommandRegistry;
|
|
495
576
|
private _activeCellManager: IActiveCellManager | null;
|
|
577
|
+
private _selectionWatcher: ISelectionWatcher | null;
|
|
496
578
|
private _notificationId: string | null = null;
|
|
497
579
|
private _messagesUpdated = new Signal<IChatModel, void>(this);
|
|
498
580
|
private _configChanged = new Signal<IChatModel, IConfig>(this);
|
|
499
581
|
private _unreadChanged = new Signal<IChatModel, number[]>(this);
|
|
500
582
|
private _viewportChanged = new Signal<IChatModel, number[]>(this);
|
|
583
|
+
private _writersChanged = new Signal<IChatModel, IUser[]>(this);
|
|
584
|
+
private _focusInputSignal = new Signal<ChatModel, void>(this);
|
|
501
585
|
}
|
|
502
586
|
|
|
503
587
|
/**
|
|
@@ -519,8 +603,13 @@ export namespace ChatModel {
|
|
|
519
603
|
commands?: CommandRegistry;
|
|
520
604
|
|
|
521
605
|
/**
|
|
522
|
-
* Active cell manager
|
|
606
|
+
* Active cell manager.
|
|
523
607
|
*/
|
|
524
608
|
activeCellManager?: IActiveCellManager | null;
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Selection watcher.
|
|
612
|
+
*/
|
|
613
|
+
selectionWatcher?: ISelectionWatcher | null;
|
|
525
614
|
}
|
|
526
615
|
}
|