@jupyter/chat 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/lib/active-cell-manager.d.ts +3 -0
  2. package/lib/components/chat-input.d.ts +4 -0
  3. package/lib/components/chat-input.js +32 -15
  4. package/lib/components/chat-messages.d.ts +31 -1
  5. package/lib/components/chat-messages.js +57 -19
  6. package/lib/components/chat.js +1 -1
  7. package/lib/components/code-blocks/code-toolbar.js +51 -17
  8. package/lib/components/input/cancel-button.d.ts +12 -0
  9. package/lib/components/input/cancel-button.js +27 -0
  10. package/lib/components/input/send-button.d.ts +18 -0
  11. package/lib/components/input/send-button.js +143 -0
  12. package/lib/components/mui-extras/tooltipped-button.d.ts +41 -0
  13. package/lib/components/mui-extras/tooltipped-button.js +43 -0
  14. package/lib/components/mui-extras/tooltipped-icon-button.js +5 -1
  15. package/lib/components/rendermime-markdown.js +15 -6
  16. package/lib/icons.d.ts +1 -0
  17. package/lib/icons.js +5 -0
  18. package/lib/index.d.ts +2 -1
  19. package/lib/index.js +2 -1
  20. package/lib/model.d.ts +51 -8
  21. package/lib/model.js +44 -12
  22. package/lib/selection-watcher.d.ts +62 -0
  23. package/lib/selection-watcher.js +134 -0
  24. package/lib/types.d.ts +22 -0
  25. package/lib/utils.d.ts +11 -0
  26. package/lib/utils.js +37 -0
  27. package/package.json +2 -1
  28. package/src/active-cell-manager.ts +3 -0
  29. package/src/components/chat-input.tsx +48 -30
  30. package/src/components/chat-messages.tsx +112 -32
  31. package/src/components/chat.tsx +1 -1
  32. package/src/components/code-blocks/code-toolbar.tsx +56 -18
  33. package/src/components/input/cancel-button.tsx +47 -0
  34. package/src/components/input/send-button.tsx +210 -0
  35. package/src/components/mui-extras/tooltipped-button.tsx +92 -0
  36. package/src/components/mui-extras/tooltipped-icon-button.tsx +5 -1
  37. package/src/components/rendermime-markdown.tsx +16 -5
  38. package/src/icons.ts +6 -0
  39. package/src/index.ts +2 -1
  40. package/src/model.ts +77 -13
  41. package/src/selection-watcher.ts +221 -0
  42. package/src/types.ts +25 -0
  43. package/src/utils.ts +47 -0
  44. package/style/chat.css +13 -0
  45. package/style/icons/include-selection.svg +5 -0
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import React from 'react';
6
+ import { Button } from '@mui/material';
7
+ import { ContrastingTooltip } from './contrasting-tooltip';
8
+ /**
9
+ * A component that renders an MUI `Button` with a high-contrast tooltip
10
+ * provided by `ContrastingTooltip`. This component differs from the MUI
11
+ * defaults in the following ways:
12
+ *
13
+ * - Shows the tooltip on hover even if disabled.
14
+ * - Renders the tooltip above the button by default.
15
+ * - Renders the tooltip closer to the button by default.
16
+ * - Lowers the opacity of the Button when disabled.
17
+ * - Renders the Button with `line-height: 0` to avoid showing extra
18
+ * vertical space in SVG icons.
19
+ *
20
+ * NOTE TO DEVS: Please keep this component's features synchronized with
21
+ * features available to `TooltippedIconButton`.
22
+ */
23
+ export function TooltippedButton(props) {
24
+ var _a;
25
+ return (React.createElement(ContrastingTooltip, { title: props.tooltip, placement: (_a = props.placement) !== null && _a !== void 0 ? _a : 'top', slotProps: {
26
+ popper: {
27
+ modifiers: [
28
+ {
29
+ name: 'offset',
30
+ options: {
31
+ offset: [0, -8]
32
+ }
33
+ }
34
+ ]
35
+ }
36
+ } },
37
+ React.createElement("span", { style: { cursor: 'default' } },
38
+ React.createElement(Button, { ...props.buttonProps, onClick: props.onClick, disabled: props.disabled, sx: {
39
+ lineHeight: 0,
40
+ ...(props.disabled && { opacity: 0.5 }),
41
+ ...props.sx
42
+ }, "aria-label": props['aria-label'] }, props.children))));
43
+ }
@@ -32,5 +32,9 @@ export function TooltippedIconButton(props) {
32
32
  }
33
33
  } },
34
34
  React.createElement("span", { className: props.className },
35
- React.createElement(IconButton, { ...props.iconButtonProps, onClick: props.onClick, disabled: props.disabled, sx: { lineHeight: 0, ...(props.disabled && { opacity: 0.5 }) }, "aria-label": props['aria-label'] }, props.children))));
35
+ React.createElement(IconButton, { ...props.iconButtonProps, onClick: props.onClick, disabled: props.disabled, sx: {
36
+ marginLeft: '8px',
37
+ lineHeight: 0,
38
+ ...(props.disabled && { opacity: 0.5 })
39
+ }, "aria-label": props['aria-label'] }, props.children))));
36
40
  }
@@ -9,14 +9,19 @@ import { MessageToolbar } from './toolbar';
9
9
  const MD_MIME_TYPE = 'text/markdown';
10
10
  const RENDERMIME_MD_CLASS = 'jp-chat-rendermime-markdown';
11
11
  /**
12
- * Takes \( and returns \\(. Escapes LaTeX delimeters by adding extra backslashes where needed for proper rendering by @jupyterlab/rendermime.
12
+ * Escapes backslashes in LaTeX delimiters such that they appear in the DOM
13
+ * after the initial MarkDown render. For example, this function takes '\(` and
14
+ * returns `\\(`.
15
+ *
16
+ * Required for proper rendering of MarkDown + LaTeX markup in the chat by
17
+ * `ILatexTypesetter`.
13
18
  */
14
19
  function escapeLatexDelimiters(text) {
15
20
  return text
16
- .replace('\\(', '\\\\(')
17
- .replace('\\)', '\\\\)')
18
- .replace('\\[', '\\\\[')
19
- .replace('\\]', '\\\\]');
21
+ .replace('\\(/g', '\\\\(')
22
+ .replace('\\)/g', '\\\\)')
23
+ .replace('\\[/g', '\\\\[')
24
+ .replace('\\]/g', '\\\\]');
20
25
  }
21
26
  function RendermimeMarkdownBase(props) {
22
27
  const appendContent = props.appendContent || false;
@@ -25,17 +30,21 @@ function RendermimeMarkdownBase(props) {
25
30
  const [codeToolbarDefns, setCodeToolbarDefns] = useState([]);
26
31
  useEffect(() => {
27
32
  const renderContent = async () => {
28
- var _a;
33
+ var _a, _b;
34
+ // initialize mime model
29
35
  const mdStr = escapeLatexDelimiters(props.markdownStr);
30
36
  const model = props.rmRegistry.createModel({
31
37
  data: { [MD_MIME_TYPE]: mdStr }
32
38
  });
33
39
  const renderer = props.rmRegistry.createRenderer(MD_MIME_TYPE);
40
+ // step 1: render markdown
34
41
  await renderer.renderModel(model);
35
42
  (_a = props.rmRegistry.latexTypesetter) === null || _a === void 0 ? void 0 : _a.typeset(renderer.node);
36
43
  if (!renderer.node) {
37
44
  throw new Error('Rendermime was unable to render Markdown content within a chat message. Please report this upstream to Jupyter chat on GitHub.');
38
45
  }
46
+ // step 2: render LaTeX via MathJax.
47
+ (_b = props.rmRegistry.latexTypesetter) === null || _b === void 0 ? void 0 : _b.typeset(renderer.node);
39
48
  const newCodeToolbarDefns = [];
40
49
  // Attach CodeToolbar root element to each <pre> block
41
50
  const preBlocks = renderer.node.querySelectorAll('pre');
package/lib/icons.d.ts CHANGED
@@ -2,3 +2,4 @@ import { LabIcon } from '@jupyterlab/ui-components';
2
2
  export declare const chatIcon: LabIcon;
3
3
  export declare const readIcon: LabIcon;
4
4
  export declare const replaceCellIcon: LabIcon;
5
+ export declare const includeSelectionIcon: LabIcon;
package/lib/icons.js CHANGED
@@ -5,6 +5,7 @@
5
5
  // This file is based on iconimports.ts in @jupyterlab/ui-components, but is manually generated.
6
6
  import { LabIcon } from '@jupyterlab/ui-components';
7
7
  import chatSvgStr from '../style/icons/chat.svg';
8
+ import includeSelectionIconStr from '../style/icons/include-selection.svg';
8
9
  import readSvgStr from '../style/icons/read.svg';
9
10
  import replaceCellSvg from '../style/icons/replace-cell.svg';
10
11
  export const chatIcon = new LabIcon({
@@ -19,3 +20,7 @@ export const replaceCellIcon = new LabIcon({
19
20
  name: 'jupyter-ai::replace-cell',
20
21
  svgstr: replaceCellSvg
21
22
  });
23
+ export const includeSelectionIcon = new LabIcon({
24
+ name: 'jupyter-chat::include',
25
+ svgstr: includeSelectionIconStr
26
+ });
package/lib/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
+ export * from './active-cell-manager';
1
2
  export * from './icons';
2
3
  export * from './model';
3
4
  export * from './registry';
5
+ export * from './selection-watcher';
4
6
  export * from './types';
5
- export * from './active-cell-manager';
6
7
  export * from './widgets/chat-error';
7
8
  export * from './widgets/chat-sidebar';
8
9
  export * from './widgets/chat-widget';
package/lib/index.js CHANGED
@@ -2,11 +2,12 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ export * from './active-cell-manager';
5
6
  export * from './icons';
6
7
  export * from './model';
7
8
  export * from './registry';
9
+ export * from './selection-watcher';
8
10
  export * from './types';
9
- export * from './active-cell-manager';
10
11
  export * from './widgets/chat-error';
11
12
  export * from './widgets/chat-sidebar';
12
13
  export * from './widgets/chat-widget';
package/lib/model.d.ts CHANGED
@@ -3,6 +3,7 @@ import { IDisposable } from '@lumino/disposable';
3
3
  import { ISignal } from '@lumino/signaling';
4
4
  import { IChatHistory, INewMessage, IChatMessage, IConfig, IUser } from './types';
5
5
  import { IActiveCellManager } from './active-cell-manager';
6
+ import { ISelectionWatcher } from './selection-watcher';
6
7
  /**
7
8
  * The chat model interface.
8
9
  */
@@ -35,6 +36,10 @@ export interface IChatModel extends IDisposable {
35
36
  * Get the active cell manager.
36
37
  */
37
38
  readonly activeCellManager: IActiveCellManager | null;
39
+ /**
40
+ * Get the selection watcher.
41
+ */
42
+ readonly selectionWatcher: ISelectionWatcher | null;
38
43
  /**
39
44
  * A signal emitting when the messages list is updated.
40
45
  */
@@ -51,6 +56,10 @@ export interface IChatModel extends IDisposable {
51
56
  * A signal emitting when the viewport change.
52
57
  */
53
58
  readonly viewportChanged?: ISignal<IChatModel, number[]>;
59
+ /**
60
+ * A signal emitting when the writers change.
61
+ */
62
+ readonly writersChanged?: ISignal<IChatModel, IUser[]>;
54
63
  /**
55
64
  * A signal emitting when the focus is requested on the input.
56
65
  */
@@ -62,7 +71,7 @@ export interface IChatModel extends IDisposable {
62
71
  * @param message - the message to send.
63
72
  * @returns whether the message has been sent or not, or nothing if not needed.
64
73
  */
65
- addMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
74
+ sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
66
75
  /**
67
76
  * Optional, to update a message from the chat panel.
68
77
  *
@@ -108,10 +117,18 @@ export interface IChatModel extends IDisposable {
108
117
  * @param count - the number of messages to delete.
109
118
  */
110
119
  messagesDeleted(index: number, count: number): void;
120
+ /**
121
+ * Update the current writers list.
122
+ */
123
+ updateWriters(writers: IUser[]): void;
111
124
  /**
112
125
  * Function to request the focus on the input of the chat.
113
126
  */
114
127
  focusInput(): void;
128
+ /**
129
+ * Function called by the input on key pressed.
130
+ */
131
+ inputChanged?(input?: string): void;
115
132
  }
116
133
  /**
117
134
  * The default chat model implementation.
@@ -123,11 +140,6 @@ export declare class ChatModel implements IChatModel {
123
140
  * Create a new chat model.
124
141
  */
125
142
  constructor(options?: ChatModel.IOptions);
126
- /**
127
- * The chat messages list.
128
- */
129
- get messages(): IChatMessage[];
130
- get activeCellManager(): IActiveCellManager | null;
131
143
  /**
132
144
  * The chat model id.
133
145
  */
@@ -138,6 +150,18 @@ export declare class ChatModel implements IChatModel {
138
150
  */
139
151
  get name(): string;
140
152
  set name(value: string);
153
+ /**
154
+ * The chat messages list.
155
+ */
156
+ get messages(): IChatMessage[];
157
+ /**
158
+ * Get the active cell manager.
159
+ */
160
+ get activeCellManager(): IActiveCellManager | null;
161
+ /**
162
+ * Get the selection watcher.
163
+ */
164
+ get selectionWatcher(): ISelectionWatcher | null;
141
165
  /**
142
166
  * Timestamp of the last read message in local storage.
143
167
  */
@@ -174,6 +198,10 @@ export declare class ChatModel implements IChatModel {
174
198
  * A signal emitting when the viewport change.
175
199
  */
176
200
  get viewportChanged(): ISignal<IChatModel, number[]>;
201
+ /**
202
+ * A signal emitting when the writers change.
203
+ */
204
+ get writersChanged(): ISignal<IChatModel, IUser[]>;
177
205
  /**
178
206
  * A signal emitting when the focus is requested on the input.
179
207
  */
@@ -185,7 +213,7 @@ export declare class ChatModel implements IChatModel {
185
213
  * @param message - the message to send.
186
214
  * @returns whether the message has been sent or not.
187
215
  */
188
- addMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
216
+ sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
189
217
  /**
190
218
  * Dispose the chat model.
191
219
  */
@@ -219,10 +247,19 @@ export declare class ChatModel implements IChatModel {
219
247
  * @param count - the number of messages to delete.
220
248
  */
221
249
  messagesDeleted(index: number, count: number): void;
250
+ /**
251
+ * Update the current writers list.
252
+ * This implementation only propagate the list via a signal.
253
+ */
254
+ updateWriters(writers: IUser[]): void;
222
255
  /**
223
256
  * Function to request the focus on the input of the chat.
224
257
  */
225
258
  focusInput(): void;
259
+ /**
260
+ * Function called by the input on key pressed.
261
+ */
262
+ inputChanged?(input?: string): void;
226
263
  /**
227
264
  * Add unread messages to the list.
228
265
  * @param indexes - list of new indexes.
@@ -247,11 +284,13 @@ export declare class ChatModel implements IChatModel {
247
284
  private _isDisposed;
248
285
  private _commands?;
249
286
  private _activeCellManager;
287
+ private _selectionWatcher;
250
288
  private _notificationId;
251
289
  private _messagesUpdated;
252
290
  private _configChanged;
253
291
  private _unreadChanged;
254
292
  private _viewportChanged;
293
+ private _writersChanged;
255
294
  private _focusInputSignal;
256
295
  }
257
296
  /**
@@ -271,8 +310,12 @@ export declare namespace ChatModel {
271
310
  */
272
311
  commands?: CommandRegistry;
273
312
  /**
274
- * Active cell manager
313
+ * Active cell manager.
275
314
  */
276
315
  activeCellManager?: IActiveCellManager | null;
316
+ /**
317
+ * Selection watcher.
318
+ */
319
+ selectionWatcher?: ISelectionWatcher | null;
277
320
  }
278
321
  }
package/lib/model.js CHANGED
@@ -13,7 +13,7 @@ export class ChatModel {
13
13
  * Create a new chat model.
14
14
  */
15
15
  constructor(options = {}) {
16
- var _a, _b;
16
+ var _a, _b, _c;
17
17
  this._messages = [];
18
18
  this._unreadMessages = [];
19
19
  this._messagesInViewport = [];
@@ -24,21 +24,18 @@ export class ChatModel {
24
24
  this._configChanged = new Signal(this);
25
25
  this._unreadChanged = new Signal(this);
26
26
  this._viewportChanged = new Signal(this);
27
+ this._writersChanged = new Signal(this);
27
28
  this._focusInputSignal = new Signal(this);
28
29
  const config = (_a = options.config) !== null && _a !== void 0 ? _a : {};
29
30
  // Stack consecutive messages from the same user by default.
30
- this._config = { stackMessages: true, ...config };
31
+ this._config = {
32
+ stackMessages: true,
33
+ sendTypingNotification: true,
34
+ ...config
35
+ };
31
36
  this._commands = options.commands;
32
37
  this._activeCellManager = (_b = options.activeCellManager) !== null && _b !== void 0 ? _b : null;
33
- }
34
- /**
35
- * The chat messages list.
36
- */
37
- get messages() {
38
- return this._messages;
39
- }
40
- get activeCellManager() {
41
- return this._activeCellManager;
38
+ this._selectionWatcher = (_c = options.selectionWatcher) !== null && _c !== void 0 ? _c : null;
42
39
  }
43
40
  /**
44
41
  * The chat model id.
@@ -58,6 +55,24 @@ export class ChatModel {
58
55
  set name(value) {
59
56
  this._name = value;
60
57
  }
58
+ /**
59
+ * The chat messages list.
60
+ */
61
+ get messages() {
62
+ return this._messages;
63
+ }
64
+ /**
65
+ * Get the active cell manager.
66
+ */
67
+ get activeCellManager() {
68
+ return this._activeCellManager;
69
+ }
70
+ /**
71
+ * Get the selection watcher.
72
+ */
73
+ get selectionWatcher() {
74
+ return this._selectionWatcher;
75
+ }
61
76
  /**
62
77
  * Timestamp of the last read message in local storage.
63
78
  */
@@ -173,6 +188,12 @@ export class ChatModel {
173
188
  get viewportChanged() {
174
189
  return this._viewportChanged;
175
190
  }
191
+ /**
192
+ * A signal emitting when the writers change.
193
+ */
194
+ get writersChanged() {
195
+ return this._writersChanged;
196
+ }
176
197
  /**
177
198
  * A signal emitting when the focus is requested on the input.
178
199
  */
@@ -186,7 +207,7 @@ export class ChatModel {
186
207
  * @param message - the message to send.
187
208
  * @returns whether the message has been sent or not.
188
209
  */
189
- addMessage(message) { }
210
+ sendMessage(message) { }
190
211
  /**
191
212
  * Dispose the chat model.
192
213
  */
@@ -275,12 +296,23 @@ export class ChatModel {
275
296
  this._messages.splice(index, count);
276
297
  this._messagesUpdated.emit();
277
298
  }
299
+ /**
300
+ * Update the current writers list.
301
+ * This implementation only propagate the list via a signal.
302
+ */
303
+ updateWriters(writers) {
304
+ this._writersChanged.emit(writers);
305
+ }
278
306
  /**
279
307
  * Function to request the focus on the input of the chat.
280
308
  */
281
309
  focusInput() {
282
310
  this._focusInputSignal.emit();
283
311
  }
312
+ /**
313
+ * Function called by the input on key pressed.
314
+ */
315
+ inputChanged(input) { }
284
316
  /**
285
317
  * Add unread messages to the list.
286
318
  * @param indexes - list of new indexes.
@@ -0,0 +1,62 @@
1
+ import { JupyterFrontEnd } from '@jupyterlab/application';
2
+ import { CodeEditor } from '@jupyterlab/codeeditor';
3
+ import { Widget } from '@lumino/widgets';
4
+ import { ISignal, Signal } from '@lumino/signaling';
5
+ /**
6
+ * The selection watcher namespace.
7
+ */
8
+ export declare namespace SelectionWatcher {
9
+ /**
10
+ * The constructor options.
11
+ */
12
+ interface IOptions {
13
+ /**
14
+ * The current shell of the application.
15
+ */
16
+ shell: JupyterFrontEnd.IShell;
17
+ }
18
+ /**
19
+ * The selection type.
20
+ */
21
+ type Selection = CodeEditor.ITextSelection & {
22
+ /**
23
+ * The text within the selection as a string.
24
+ */
25
+ text: string;
26
+ /**
27
+ * Number of lines contained by the text selection.
28
+ */
29
+ numLines: number;
30
+ /**
31
+ * The ID of the document widget in which the selection was made.
32
+ */
33
+ widgetId: string;
34
+ /**
35
+ * The ID of the cell in which the selection was made, if the original widget
36
+ * was a notebook.
37
+ */
38
+ cellId?: string;
39
+ };
40
+ }
41
+ /**
42
+ * The selection watcher interface.
43
+ */
44
+ export interface ISelectionWatcher {
45
+ readonly selection: SelectionWatcher.Selection | null;
46
+ readonly selectionChanged: ISignal<ISelectionWatcher, SelectionWatcher.Selection | null>;
47
+ replaceSelection(selection: SelectionWatcher.Selection): void;
48
+ }
49
+ /**
50
+ * The selection watcher, read/write selected text in a DocumentWidget.
51
+ */
52
+ export declare class SelectionWatcher {
53
+ constructor(options: SelectionWatcher.IOptions);
54
+ get selection(): SelectionWatcher.Selection | null;
55
+ get selectionChanged(): ISignal<this, SelectionWatcher.Selection | null>;
56
+ replaceSelection(selection: SelectionWatcher.Selection): void;
57
+ protected _poll(): void;
58
+ protected _shell: JupyterFrontEnd.IShell;
59
+ protected _mainAreaDocumentWidget: Widget | null;
60
+ protected _selection: SelectionWatcher.Selection | null;
61
+ protected _selectionChanged: Signal<this, SelectionWatcher.Selection | null>;
62
+ }
@@ -0,0 +1,134 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { DocumentWidget } from '@jupyterlab/docregistry';
6
+ import { Notebook } from '@jupyterlab/notebook';
7
+ import { find } from '@lumino/algorithm';
8
+ import { Signal } from '@lumino/signaling';
9
+ import { getCellIndex, getEditor } from './utils';
10
+ /**
11
+ * The selection watcher, read/write selected text in a DocumentWidget.
12
+ */
13
+ export class SelectionWatcher {
14
+ constructor(options) {
15
+ var _a;
16
+ this._mainAreaDocumentWidget = null;
17
+ this._selection = null;
18
+ this._selectionChanged = new Signal(this);
19
+ this._shell = options.shell;
20
+ (_a = this._shell.currentChanged) === null || _a === void 0 ? void 0 : _a.connect((sender, args) => {
21
+ var _a;
22
+ // Do not change the main area widget if the new one has no editor, for example
23
+ // a chat panel. However, the selected text is only available if the main area
24
+ // widget is visible. (to avoid confusion in inclusion/replacement).
25
+ const widget = args.newValue;
26
+ // if there is no main area widget, set it to null.
27
+ if (widget === null) {
28
+ this._mainAreaDocumentWidget = null;
29
+ return;
30
+ }
31
+ const editor = getEditor(widget);
32
+ if (widget instanceof DocumentWidget &&
33
+ (editor || widget.content instanceof Notebook)) {
34
+ // if the new widget is a DocumentWidget and has an editor, set it.
35
+ // NOTE: special case for notebook which do not has an active cell at that stage,
36
+ // and so the editor can't be retrieved too.
37
+ this._mainAreaDocumentWidget = widget;
38
+ }
39
+ else if ((_a = this._mainAreaDocumentWidget) === null || _a === void 0 ? void 0 : _a.isDisposed) {
40
+ // if the previous document widget has been closed, set it to null.
41
+ this._mainAreaDocumentWidget = null;
42
+ }
43
+ });
44
+ setInterval(this._poll.bind(this), 200);
45
+ }
46
+ get selection() {
47
+ return this._selection;
48
+ }
49
+ get selectionChanged() {
50
+ return this._selectionChanged;
51
+ }
52
+ replaceSelection(selection) {
53
+ // unfortunately shell.currentWidget doesn't update synchronously after
54
+ // shell.activateById(), which is why we have to get a reference to the
55
+ // widget manually.
56
+ const widget = find(this._shell.widgets(), widget => widget.id === selection.widgetId);
57
+ // Do not allow replacement on non visible widget (to avoid confusion).
58
+ if (!(widget === null || widget === void 0 ? void 0 : widget.isVisible) || !(widget instanceof DocumentWidget)) {
59
+ return;
60
+ }
61
+ // activate the widget if not already active
62
+ this._shell.activateById(selection.widgetId);
63
+ // activate notebook cell if specified
64
+ if (widget.content instanceof Notebook && selection.cellId) {
65
+ const cellIndex = getCellIndex(widget.content, selection.cellId);
66
+ if (cellIndex !== -1) {
67
+ widget.content.activeCellIndex = cellIndex;
68
+ }
69
+ }
70
+ // get editor instance
71
+ const editor = getEditor(widget);
72
+ if (!editor) {
73
+ return;
74
+ }
75
+ editor.model.sharedModel.updateSource(editor.getOffsetAt(selection.start), editor.getOffsetAt(selection.end), selection.text);
76
+ const newPosition = editor.getPositionAt(editor.getOffsetAt(selection.start) + selection.text.length);
77
+ editor.setSelection({ start: newPosition, end: newPosition });
78
+ }
79
+ _poll() {
80
+ var _a;
81
+ let currSelection = null;
82
+ const prevSelection = this._selection;
83
+ // Do not return selected text if the main area widget is hidden.
84
+ if ((_a = this._mainAreaDocumentWidget) === null || _a === void 0 ? void 0 : _a.isVisible) {
85
+ currSelection = getTextSelection(this._mainAreaDocumentWidget);
86
+ }
87
+ if ((prevSelection === null || prevSelection === void 0 ? void 0 : prevSelection.text) !== (currSelection === null || currSelection === void 0 ? void 0 : currSelection.text)) {
88
+ this._selection = currSelection;
89
+ this._selectionChanged.emit(currSelection);
90
+ }
91
+ }
92
+ }
93
+ /**
94
+ * Gets a Selection object from a document widget. Returns `null` if unable.
95
+ */
96
+ function getTextSelection(widget) {
97
+ var _a;
98
+ const editor = getEditor(widget);
99
+ // widget type check is redundant but hints the type to TypeScript
100
+ if (!editor || !(widget instanceof DocumentWidget)) {
101
+ return null;
102
+ }
103
+ let cellId = undefined;
104
+ if (widget.content instanceof Notebook) {
105
+ cellId = (_a = widget.content.activeCell) === null || _a === void 0 ? void 0 : _a.model.id;
106
+ }
107
+ const selectionObj = editor.getSelection();
108
+ let { start, end } = selectionObj;
109
+ const startOffset = editor.getOffsetAt(start);
110
+ const endOffset = editor.getOffsetAt(end);
111
+ const text = editor.model.sharedModel
112
+ .getSource()
113
+ .substring(startOffset, endOffset);
114
+ // Do not return a Selection object if no text is selected
115
+ if (!text) {
116
+ return null;
117
+ }
118
+ // ensure start <= end
119
+ // required for editor.model.sharedModel.updateSource()
120
+ if (startOffset > endOffset) {
121
+ [start, end] = [end, start];
122
+ }
123
+ return {
124
+ ...selectionObj,
125
+ start,
126
+ end,
127
+ text,
128
+ numLines: text.split('\n').length,
129
+ widgetId: widget.id,
130
+ ...(cellId && {
131
+ cellId
132
+ })
133
+ };
134
+ }
package/lib/types.d.ts CHANGED
@@ -33,6 +33,10 @@ export interface IConfig {
33
33
  * Whether to enable or not the code toolbar.
34
34
  */
35
35
  enableCodeToolbar?: boolean;
36
+ /**
37
+ * Whether to send typing notification.
38
+ */
39
+ sendTypingNotification?: boolean;
36
40
  }
37
41
  /**
38
42
  * The chat message description.
@@ -72,6 +76,24 @@ export interface ISettings {
72
76
  export type AutocompleteCommand = {
73
77
  label: string;
74
78
  };
79
+ /**
80
+ * Representation of a selected text.
81
+ */
82
+ export type TextSelection = {
83
+ type: 'text';
84
+ source: string;
85
+ };
86
+ /**
87
+ * Representation of a selected cell.
88
+ */
89
+ export type CellSelection = {
90
+ type: 'cell';
91
+ source: string;
92
+ };
93
+ /**
94
+ * Selection object (text or cell).
95
+ */
96
+ export type Selection = TextSelection | CellSelection;
75
97
  /**
76
98
  * The properties of the autocompletion.
77
99
  *
package/lib/utils.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { CodeMirrorEditor } from '@jupyterlab/codemirror';
2
+ import { Notebook } from '@jupyterlab/notebook';
3
+ import { Widget } from '@lumino/widgets';
4
+ /**
5
+ * Gets the editor instance used by a document widget. Returns `null` if unable.
6
+ */
7
+ export declare function getEditor(widget: Widget | null): CodeMirrorEditor | null | undefined;
8
+ /**
9
+ * Gets the index of the cell associated with `cellId`.
10
+ */
11
+ export declare function getCellIndex(notebook: Notebook, cellId: string): number;