@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
package/lib/input-model.js
CHANGED
|
@@ -9,7 +9,14 @@ const WHITESPACE = new Set([' ', '\n', '\t']);
|
|
|
9
9
|
*/
|
|
10
10
|
export class InputModel {
|
|
11
11
|
constructor(options) {
|
|
12
|
-
var _a, _b;
|
|
12
|
+
var _a, _b, _c;
|
|
13
|
+
/**
|
|
14
|
+
* Function to send a message.
|
|
15
|
+
*/
|
|
16
|
+
this.send = (input) => {
|
|
17
|
+
this._onSend(input, this);
|
|
18
|
+
this.value = '';
|
|
19
|
+
};
|
|
13
20
|
/**
|
|
14
21
|
* Add attachment to send with next message.
|
|
15
22
|
*/
|
|
@@ -39,6 +46,12 @@ export class InputModel {
|
|
|
39
46
|
this._attachments = [];
|
|
40
47
|
this._attachmentsChanged.emit([]);
|
|
41
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Clear mentions list.
|
|
51
|
+
*/
|
|
52
|
+
this.clearMentions = () => {
|
|
53
|
+
this._mentions = [];
|
|
54
|
+
};
|
|
42
55
|
this._cursorIndex = null;
|
|
43
56
|
this._currentWord = null;
|
|
44
57
|
this._valueChanged = new Signal(this);
|
|
@@ -48,14 +61,25 @@ export class InputModel {
|
|
|
48
61
|
this._focusInputSignal = new Signal(this);
|
|
49
62
|
this._attachmentsChanged = new Signal(this);
|
|
50
63
|
this._isDisposed = false;
|
|
64
|
+
this._onSend = options.onSend;
|
|
65
|
+
this._chatContext = options.chatContext;
|
|
51
66
|
this._value = options.value || '';
|
|
52
67
|
this._attachments = options.attachments || [];
|
|
68
|
+
this._mentions = options.mentions || [];
|
|
53
69
|
this.cursorIndex = options.cursorIndex || this.value.length;
|
|
54
70
|
this._activeCellManager = (_a = options.activeCellManager) !== null && _a !== void 0 ? _a : null;
|
|
55
71
|
this._selectionWatcher = (_b = options.selectionWatcher) !== null && _b !== void 0 ? _b : null;
|
|
72
|
+
this._documentManager = (_c = options.documentManager) !== null && _c !== void 0 ? _c : null;
|
|
56
73
|
this._config = {
|
|
57
74
|
...options.config
|
|
58
75
|
};
|
|
76
|
+
this.cancel = options.onCancel;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The chat context (a readonly subset of the chat model);
|
|
80
|
+
*/
|
|
81
|
+
get chatContext() {
|
|
82
|
+
return this._chatContext;
|
|
59
83
|
}
|
|
60
84
|
/**
|
|
61
85
|
* The entire input value.
|
|
@@ -124,6 +148,12 @@ export class InputModel {
|
|
|
124
148
|
get selectionWatcher() {
|
|
125
149
|
return this._selectionWatcher;
|
|
126
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Get the document manager.
|
|
153
|
+
*/
|
|
154
|
+
get documentManager() {
|
|
155
|
+
return this._documentManager;
|
|
156
|
+
}
|
|
127
157
|
/**
|
|
128
158
|
* The input configuration.
|
|
129
159
|
*/
|
|
@@ -174,6 +204,30 @@ export class InputModel {
|
|
|
174
204
|
const [start, end] = Private.getCurrentWordBoundaries(this.value, this.cursorIndex);
|
|
175
205
|
this.value = this.value.slice(0, start) + newWord + this.value.slice(end);
|
|
176
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* The mentioned user list.
|
|
209
|
+
*/
|
|
210
|
+
get mentions() {
|
|
211
|
+
return this._mentions;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Add a user mention.
|
|
215
|
+
*/
|
|
216
|
+
addMention(user) {
|
|
217
|
+
const usernames = this._mentions.map(user => user.username);
|
|
218
|
+
if (!usernames.includes(user.username)) {
|
|
219
|
+
this._mentions.push(user);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Remove a user mention.
|
|
224
|
+
*/
|
|
225
|
+
removeMention(user) {
|
|
226
|
+
const index = this._mentions.indexOf(user);
|
|
227
|
+
if (index > -1) {
|
|
228
|
+
this._mentions.splice(index, 1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
177
231
|
/**
|
|
178
232
|
* Dispose the input model.
|
|
179
233
|
*/
|
package/lib/model.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
1
2
|
import { CommandRegistry } from '@lumino/commands';
|
|
2
3
|
import { IDisposable } from '@lumino/disposable';
|
|
3
4
|
import { ISignal } from '@lumino/signaling';
|
|
4
|
-
import { IChatHistory, INewMessage, IChatMessage, IConfig, IUser } from './types';
|
|
5
5
|
import { IActiveCellManager } from './active-cell-manager';
|
|
6
|
-
import { ISelectionWatcher } from './selection-watcher';
|
|
7
6
|
import { IInputModel } from './input-model';
|
|
7
|
+
import { ISelectionWatcher } from './selection-watcher';
|
|
8
|
+
import { IChatHistory, INewMessage, IChatMessage, IConfig, IUser } from './types';
|
|
8
9
|
/**
|
|
9
10
|
* The chat model interface.
|
|
10
11
|
*/
|
|
@@ -45,6 +46,10 @@ export interface IChatModel extends IDisposable {
|
|
|
45
46
|
* Get the selection watcher.
|
|
46
47
|
*/
|
|
47
48
|
readonly selectionWatcher: ISelectionWatcher | null;
|
|
49
|
+
/**
|
|
50
|
+
* Get the selection watcher.
|
|
51
|
+
*/
|
|
52
|
+
readonly documentManager: IDocumentManager | null;
|
|
48
53
|
/**
|
|
49
54
|
* A signal emitting when the messages list is updated.
|
|
50
55
|
*/
|
|
@@ -73,6 +78,10 @@ export interface IChatModel extends IDisposable {
|
|
|
73
78
|
* @returns whether the message has been sent or not, or nothing if not needed.
|
|
74
79
|
*/
|
|
75
80
|
sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
|
|
81
|
+
/**
|
|
82
|
+
* Clear the message list.
|
|
83
|
+
*/
|
|
84
|
+
clearMessages(): void;
|
|
76
85
|
/**
|
|
77
86
|
* Optional, to update a message from the chat panel.
|
|
78
87
|
*
|
|
@@ -122,17 +131,22 @@ export interface IChatModel extends IDisposable {
|
|
|
122
131
|
* Update the current writers list.
|
|
123
132
|
*/
|
|
124
133
|
updateWriters(writers: IUser[]): void;
|
|
134
|
+
/**
|
|
135
|
+
* Create the chat context that will be passed to the input model.
|
|
136
|
+
*/
|
|
137
|
+
createChatContext(): IChatContext;
|
|
125
138
|
}
|
|
126
139
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
140
|
+
* An abstract implementation of IChatModel.
|
|
141
|
+
*
|
|
142
|
+
* The class inheriting from it must implement at least:
|
|
143
|
+
* - sendMessage(message: INewMessage)
|
|
130
144
|
*/
|
|
131
|
-
export declare class
|
|
145
|
+
export declare abstract class AbstractChatModel implements IChatModel {
|
|
132
146
|
/**
|
|
133
147
|
* Create a new chat model.
|
|
134
148
|
*/
|
|
135
|
-
constructor(options?:
|
|
149
|
+
constructor(options?: IChatModel.IOptions);
|
|
136
150
|
/**
|
|
137
151
|
* The chat model id.
|
|
138
152
|
*/
|
|
@@ -159,6 +173,10 @@ export declare class ChatModel implements IChatModel {
|
|
|
159
173
|
* Get the selection watcher.
|
|
160
174
|
*/
|
|
161
175
|
get selectionWatcher(): ISelectionWatcher | null;
|
|
176
|
+
/**
|
|
177
|
+
* Get the document manager.
|
|
178
|
+
*/
|
|
179
|
+
get documentManager(): IDocumentManager | null;
|
|
162
180
|
/**
|
|
163
181
|
* Timestamp of the last read message in local storage.
|
|
164
182
|
*/
|
|
@@ -206,7 +224,11 @@ export declare class ChatModel implements IChatModel {
|
|
|
206
224
|
* @param message - the message to send.
|
|
207
225
|
* @returns whether the message has been sent or not.
|
|
208
226
|
*/
|
|
209
|
-
sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
|
|
227
|
+
abstract sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
|
|
228
|
+
/**
|
|
229
|
+
* Clear the message list.
|
|
230
|
+
*/
|
|
231
|
+
clearMessages(): void;
|
|
210
232
|
/**
|
|
211
233
|
* Dispose the chat model.
|
|
212
234
|
*/
|
|
@@ -245,6 +267,10 @@ export declare class ChatModel implements IChatModel {
|
|
|
245
267
|
* This implementation only propagate the list via a signal.
|
|
246
268
|
*/
|
|
247
269
|
updateWriters(writers: IUser[]): void;
|
|
270
|
+
/**
|
|
271
|
+
* Create the chat context that will be passed to the input model.
|
|
272
|
+
*/
|
|
273
|
+
abstract createChatContext(): IChatContext;
|
|
248
274
|
/**
|
|
249
275
|
* Add unread messages to the list.
|
|
250
276
|
* @param indexes - list of new indexes.
|
|
@@ -271,6 +297,7 @@ export declare class ChatModel implements IChatModel {
|
|
|
271
297
|
private _commands?;
|
|
272
298
|
private _activeCellManager;
|
|
273
299
|
private _selectionWatcher;
|
|
300
|
+
private _documentManager;
|
|
274
301
|
private _notificationId;
|
|
275
302
|
private _messagesUpdated;
|
|
276
303
|
private _configChanged;
|
|
@@ -281,7 +308,7 @@ export declare class ChatModel implements IChatModel {
|
|
|
281
308
|
/**
|
|
282
309
|
* The chat model namespace.
|
|
283
310
|
*/
|
|
284
|
-
export declare namespace
|
|
311
|
+
export declare namespace IChatModel {
|
|
285
312
|
/**
|
|
286
313
|
* The instantiation options for a ChatModel.
|
|
287
314
|
*/
|
|
@@ -306,5 +333,45 @@ export declare namespace ChatModel {
|
|
|
306
333
|
* Selection watcher.
|
|
307
334
|
*/
|
|
308
335
|
selectionWatcher?: ISelectionWatcher | null;
|
|
336
|
+
/**
|
|
337
|
+
* Document manager.
|
|
338
|
+
*/
|
|
339
|
+
documentManager?: IDocumentManager | null;
|
|
309
340
|
}
|
|
310
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Interface of the chat context, a 'subset' of the model with readonly attribute,
|
|
344
|
+
* which can be passed to the input model.
|
|
345
|
+
* This allows third party extensions to get some attribute of the model without
|
|
346
|
+
* exposing the method that can modify it.
|
|
347
|
+
*/
|
|
348
|
+
export interface IChatContext {
|
|
349
|
+
/**
|
|
350
|
+
* The name of the chat.
|
|
351
|
+
*/
|
|
352
|
+
readonly name: string;
|
|
353
|
+
/**
|
|
354
|
+
* A copy of the messages.
|
|
355
|
+
*/
|
|
356
|
+
readonly messages: IChatMessage[];
|
|
357
|
+
/**
|
|
358
|
+
* A list of all users who have connected to this chat.
|
|
359
|
+
*/
|
|
360
|
+
readonly users: IUser[];
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* An abstract base class implementing `IChatContext`. This can be extended into
|
|
364
|
+
* a complete implementation, as done in `jupyterlab-chat`.
|
|
365
|
+
*/
|
|
366
|
+
export declare abstract class AbstractChatContext implements IChatContext {
|
|
367
|
+
constructor(options: {
|
|
368
|
+
model: IChatModel;
|
|
369
|
+
});
|
|
370
|
+
get name(): string;
|
|
371
|
+
get messages(): IChatMessage[];
|
|
372
|
+
/**
|
|
373
|
+
* ABSTRACT: Should return a list of users who have connected to this chat.
|
|
374
|
+
*/
|
|
375
|
+
abstract get users(): IUser[];
|
|
376
|
+
protected _model: IChatModel;
|
|
377
|
+
}
|
package/lib/model.js
CHANGED
|
@@ -4,17 +4,19 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Signal } from '@lumino/signaling';
|
|
6
6
|
import { InputModel } from './input-model';
|
|
7
|
+
import { replaceMentionToSpan } from './utils';
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* An abstract implementation of IChatModel.
|
|
10
|
+
*
|
|
11
|
+
* The class inheriting from it must implement at least:
|
|
12
|
+
* - sendMessage(message: INewMessage)
|
|
11
13
|
*/
|
|
12
|
-
export class
|
|
14
|
+
export class AbstractChatModel {
|
|
13
15
|
/**
|
|
14
16
|
* Create a new chat model.
|
|
15
17
|
*/
|
|
16
18
|
constructor(options = {}) {
|
|
17
|
-
var _a, _b, _c;
|
|
19
|
+
var _a, _b, _c, _d;
|
|
18
20
|
this._messages = [];
|
|
19
21
|
this._unreadMessages = [];
|
|
20
22
|
this._messagesInViewport = [];
|
|
@@ -37,15 +39,19 @@ export class ChatModel {
|
|
|
37
39
|
...config
|
|
38
40
|
};
|
|
39
41
|
this._inputModel = new InputModel({
|
|
42
|
+
chatContext: this.createChatContext(),
|
|
40
43
|
activeCellManager: options.activeCellManager,
|
|
41
44
|
selectionWatcher: options.selectionWatcher,
|
|
45
|
+
documentManager: options.documentManager,
|
|
42
46
|
config: {
|
|
43
47
|
sendWithShiftEnter: config.sendWithShiftEnter
|
|
44
|
-
}
|
|
48
|
+
},
|
|
49
|
+
onSend: (input) => this.sendMessage({ body: input })
|
|
45
50
|
});
|
|
46
51
|
this._commands = options.commands;
|
|
47
52
|
this._activeCellManager = (_b = options.activeCellManager) !== null && _b !== void 0 ? _b : null;
|
|
48
53
|
this._selectionWatcher = (_c = options.selectionWatcher) !== null && _c !== void 0 ? _c : null;
|
|
54
|
+
this._documentManager = (_d = options.documentManager) !== null && _d !== void 0 ? _d : null;
|
|
49
55
|
}
|
|
50
56
|
/**
|
|
51
57
|
* The chat model id.
|
|
@@ -89,6 +95,12 @@ export class ChatModel {
|
|
|
89
95
|
get selectionWatcher() {
|
|
90
96
|
return this._selectionWatcher;
|
|
91
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Get the document manager.
|
|
100
|
+
*/
|
|
101
|
+
get documentManager() {
|
|
102
|
+
return this._documentManager;
|
|
103
|
+
}
|
|
92
104
|
/**
|
|
93
105
|
* Timestamp of the last read message in local storage.
|
|
94
106
|
*/
|
|
@@ -212,13 +224,12 @@ export class ChatModel {
|
|
|
212
224
|
return this._writersChanged;
|
|
213
225
|
}
|
|
214
226
|
/**
|
|
215
|
-
*
|
|
216
|
-
* Default to no-op.
|
|
217
|
-
*
|
|
218
|
-
* @param message - the message to send.
|
|
219
|
-
* @returns whether the message has been sent or not.
|
|
227
|
+
* Clear the message list.
|
|
220
228
|
*/
|
|
221
|
-
|
|
229
|
+
clearMessages() {
|
|
230
|
+
this._messages = [];
|
|
231
|
+
this._messagesUpdated.emit();
|
|
232
|
+
}
|
|
222
233
|
/**
|
|
223
234
|
* Dispose the chat model.
|
|
224
235
|
*/
|
|
@@ -239,6 +250,10 @@ export class ChatModel {
|
|
|
239
250
|
* Can be useful if some actions are required on the message.
|
|
240
251
|
*/
|
|
241
252
|
formatChatMessage(message) {
|
|
253
|
+
var _a;
|
|
254
|
+
(_a = message.mentions) === null || _a === void 0 ? void 0 : _a.forEach(user => {
|
|
255
|
+
message.body = replaceMentionToSpan(message.body, user);
|
|
256
|
+
});
|
|
242
257
|
return message;
|
|
243
258
|
}
|
|
244
259
|
/**
|
|
@@ -363,3 +378,18 @@ export class ChatModel {
|
|
|
363
378
|
}
|
|
364
379
|
}
|
|
365
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* An abstract base class implementing `IChatContext`. This can be extended into
|
|
383
|
+
* a complete implementation, as done in `jupyterlab-chat`.
|
|
384
|
+
*/
|
|
385
|
+
export class AbstractChatContext {
|
|
386
|
+
constructor(options) {
|
|
387
|
+
this._model = options.model;
|
|
388
|
+
}
|
|
389
|
+
get name() {
|
|
390
|
+
return this._model.name;
|
|
391
|
+
}
|
|
392
|
+
get messages() {
|
|
393
|
+
return [...this._model.messages];
|
|
394
|
+
}
|
|
395
|
+
}
|
package/lib/types.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export interface IUser {
|
|
|
8
8
|
initials?: string;
|
|
9
9
|
color?: string;
|
|
10
10
|
avatar_url?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The string to use to mention a user in the chat.
|
|
13
|
+
*/
|
|
14
|
+
mention_name?: string;
|
|
11
15
|
}
|
|
12
16
|
/**
|
|
13
17
|
* The configuration interface.
|
|
@@ -44,6 +48,7 @@ export interface IChatMessage<T = IUser, U = IAttachment> {
|
|
|
44
48
|
time: number;
|
|
45
49
|
sender: T;
|
|
46
50
|
attachments?: U[];
|
|
51
|
+
mentions?: T[];
|
|
47
52
|
raw_time?: boolean;
|
|
48
53
|
deleted?: boolean;
|
|
49
54
|
edited?: boolean;
|
|
@@ -84,21 +89,3 @@ export interface IAttachment {
|
|
|
84
89
|
*/
|
|
85
90
|
export interface ISettings {
|
|
86
91
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Representation of a selected text.
|
|
89
|
-
*/
|
|
90
|
-
export type TextSelection = {
|
|
91
|
-
type: 'text';
|
|
92
|
-
source: string;
|
|
93
|
-
};
|
|
94
|
-
/**
|
|
95
|
-
* Representation of a selected cell.
|
|
96
|
-
*/
|
|
97
|
-
export type CellSelection = {
|
|
98
|
-
type: 'cell';
|
|
99
|
-
source: string;
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
102
|
-
* Selection object (text or cell).
|
|
103
|
-
*/
|
|
104
|
-
export type Selection = TextSelection | CellSelection;
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CodeMirrorEditor } from '@jupyterlab/codemirror';
|
|
2
2
|
import { Notebook } from '@jupyterlab/notebook';
|
|
3
3
|
import { Widget } from '@lumino/widgets';
|
|
4
|
+
import { IUser } from './types';
|
|
4
5
|
/**
|
|
5
6
|
* Gets the editor instance used by a document widget. Returns `null` if unable.
|
|
6
7
|
*/
|
|
@@ -9,3 +10,17 @@ export declare function getEditor(widget: Widget | null): CodeMirrorEditor | nul
|
|
|
9
10
|
* Gets the index of the cell associated with `cellId`.
|
|
10
11
|
*/
|
|
11
12
|
export declare function getCellIndex(notebook: Notebook, cellId: string): number;
|
|
13
|
+
/**
|
|
14
|
+
* Replace a mention to user (@someone) to a span, for markdown renderer.
|
|
15
|
+
*
|
|
16
|
+
* @param content - the content to update.
|
|
17
|
+
* @param user - the user mentioned.
|
|
18
|
+
*/
|
|
19
|
+
export declare function replaceMentionToSpan(content: string, user: IUser): string;
|
|
20
|
+
/**
|
|
21
|
+
* Replace a span to a mentioned to user string (@someone).
|
|
22
|
+
*
|
|
23
|
+
* @param content - the content to update.
|
|
24
|
+
* @param user - the user mentioned.
|
|
25
|
+
*/
|
|
26
|
+
export declare function replaceSpanToMention(content: string, user: IUser): string;
|
package/lib/utils.js
CHANGED
|
@@ -6,6 +6,7 @@ import { CodeMirrorEditor } from '@jupyterlab/codemirror';
|
|
|
6
6
|
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
7
7
|
import { FileEditor } from '@jupyterlab/fileeditor';
|
|
8
8
|
import { Notebook } from '@jupyterlab/notebook';
|
|
9
|
+
const MENTION_CLASS = 'jp-chat-mention';
|
|
9
10
|
/**
|
|
10
11
|
* Gets the editor instance used by a document widget. Returns `null` if unable.
|
|
11
12
|
*/
|
|
@@ -35,3 +36,31 @@ export function getCellIndex(notebook, cellId) {
|
|
|
35
36
|
const idx = (_a = notebook.model) === null || _a === void 0 ? void 0 : _a.sharedModel.cells.findIndex(cell => cell.getId() === cellId);
|
|
36
37
|
return idx === undefined ? -1 : idx;
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Replace a mention to user (@someone) to a span, for markdown renderer.
|
|
41
|
+
*
|
|
42
|
+
* @param content - the content to update.
|
|
43
|
+
* @param user - the user mentioned.
|
|
44
|
+
*/
|
|
45
|
+
export function replaceMentionToSpan(content, user) {
|
|
46
|
+
if (!user.mention_name) {
|
|
47
|
+
return content;
|
|
48
|
+
}
|
|
49
|
+
const regex = new RegExp(user.mention_name, 'g');
|
|
50
|
+
const mention = `<span class="${MENTION_CLASS}">${user.mention_name}</span>`;
|
|
51
|
+
return content.replace(regex, mention);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Replace a span to a mentioned to user string (@someone).
|
|
55
|
+
*
|
|
56
|
+
* @param content - the content to update.
|
|
57
|
+
* @param user - the user mentioned.
|
|
58
|
+
*/
|
|
59
|
+
export function replaceSpanToMention(content, user) {
|
|
60
|
+
if (!user.mention_name) {
|
|
61
|
+
return content;
|
|
62
|
+
}
|
|
63
|
+
const span = `<span class="${MENTION_CLASS}">${user.mention_name}</span>`;
|
|
64
|
+
const regex = new RegExp(span, 'g');
|
|
65
|
+
return content.replace(regex, user.mention_name);
|
|
66
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactWidget } from '@jupyterlab/apputils';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { Chat } from '../components
|
|
3
|
+
import { Chat, IInputToolbarRegistry } from '../components';
|
|
4
4
|
import { IChatModel } from '../model';
|
|
5
5
|
export declare class ChatWidget extends ReactWidget {
|
|
6
6
|
constructor(options: Chat.IOptions);
|
|
@@ -8,6 +8,10 @@ export declare class ChatWidget extends ReactWidget {
|
|
|
8
8
|
* Get the model of the widget.
|
|
9
9
|
*/
|
|
10
10
|
get model(): IChatModel;
|
|
11
|
+
/**
|
|
12
|
+
* Get the input toolbar registry (if it has been provided when creating the widget).
|
|
13
|
+
*/
|
|
14
|
+
get inputToolbarRegistry(): IInputToolbarRegistry | undefined;
|
|
11
15
|
render(): React.JSX.Element;
|
|
12
16
|
private _chatOptions;
|
|
13
17
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { ReactWidget } from '@jupyterlab/apputils';
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import { Chat } from '../components
|
|
7
|
+
import { Chat } from '../components';
|
|
8
8
|
import { chatIcon } from '../icons';
|
|
9
9
|
export class ChatWidget extends ReactWidget {
|
|
10
10
|
constructor(options) {
|
|
@@ -21,6 +21,12 @@ export class ChatWidget extends ReactWidget {
|
|
|
21
21
|
get model() {
|
|
22
22
|
return this._chatOptions.model;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Get the input toolbar registry (if it has been provided when creating the widget).
|
|
26
|
+
*/
|
|
27
|
+
get inputToolbarRegistry() {
|
|
28
|
+
return this._chatOptions.inputToolbarRegistry;
|
|
29
|
+
}
|
|
24
30
|
render() {
|
|
25
31
|
// The model need to be passed, otherwise it is undefined in the widget in
|
|
26
32
|
// the case of collaborative document.
|
package/package.json
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
AbstractChatContext,
|
|
8
|
+
AbstractChatModel,
|
|
9
|
+
IChatModel,
|
|
10
|
+
IChatContext
|
|
11
|
+
} from '../model';
|
|
12
|
+
import { INewMessage } from '../types';
|
|
13
|
+
|
|
14
|
+
export class MockChatContext
|
|
15
|
+
extends AbstractChatContext
|
|
16
|
+
implements IChatContext
|
|
17
|
+
{
|
|
18
|
+
get users() {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class MockChatModel extends AbstractChatModel implements IChatModel {
|
|
24
|
+
sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void {
|
|
25
|
+
// No-op
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
createChatContext(): IChatContext {
|
|
29
|
+
return new MockChatContext({ model: this });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -7,29 +7,39 @@
|
|
|
7
7
|
* Example of [Jest](https://jestjs.io/docs/getting-started) unit tests
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import { IChatMessage } from '../types';
|
|
10
|
+
import { AbstractChatModel, IChatContext, IChatModel } from '../model';
|
|
11
|
+
import { IChatMessage, INewMessage } from '../types';
|
|
12
|
+
import { MockChatModel, MockChatContext } from './mocks';
|
|
12
13
|
|
|
13
14
|
describe('test chat model', () => {
|
|
14
15
|
describe('model instantiation', () => {
|
|
15
|
-
it('should create
|
|
16
|
-
const model = new
|
|
17
|
-
expect(model).toBeInstanceOf(
|
|
16
|
+
it('should create an AbstractChatModel', () => {
|
|
17
|
+
const model = new MockChatModel();
|
|
18
|
+
expect(model).toBeInstanceOf(AbstractChatModel);
|
|
18
19
|
});
|
|
19
20
|
|
|
20
|
-
it('should dispose
|
|
21
|
-
const model = new
|
|
21
|
+
it('should dispose an AbstractChatModel', () => {
|
|
22
|
+
const model = new MockChatModel();
|
|
22
23
|
model.dispose();
|
|
23
24
|
expect(model.isDisposed).toBeTruthy();
|
|
24
25
|
});
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
describe('incoming message', () => {
|
|
28
|
-
class TestChat extends
|
|
29
|
+
class TestChat extends AbstractChatModel implements IChatModel {
|
|
29
30
|
protected formatChatMessage(message: IChatMessage): IChatMessage {
|
|
30
31
|
message.body = 'formatted msg';
|
|
31
32
|
return message;
|
|
32
33
|
}
|
|
34
|
+
sendMessage(
|
|
35
|
+
message: INewMessage
|
|
36
|
+
): Promise<boolean | void> | boolean | void {
|
|
37
|
+
// No-op
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
createChatContext(): IChatContext {
|
|
41
|
+
return new MockChatContext({ model: this });
|
|
42
|
+
}
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
let model: IChatModel;
|
|
@@ -47,7 +57,7 @@ describe('test chat model', () => {
|
|
|
47
57
|
});
|
|
48
58
|
|
|
49
59
|
it('should signal incoming message', () => {
|
|
50
|
-
model = new
|
|
60
|
+
model = new MockChatModel();
|
|
51
61
|
model.messagesUpdated.connect((sender: IChatModel) => {
|
|
52
62
|
expect(sender).toBe(model);
|
|
53
63
|
messages = model.messages;
|
|
@@ -72,12 +82,12 @@ describe('test chat model', () => {
|
|
|
72
82
|
|
|
73
83
|
describe('model config', () => {
|
|
74
84
|
it('should have empty config', () => {
|
|
75
|
-
const model = new
|
|
85
|
+
const model = new MockChatModel();
|
|
76
86
|
expect(model.config.sendWithShiftEnter).toBeUndefined();
|
|
77
87
|
});
|
|
78
88
|
|
|
79
89
|
it('should allow config', () => {
|
|
80
|
-
const model = new
|
|
90
|
+
const model = new MockChatModel({ config: { sendWithShiftEnter: true } });
|
|
81
91
|
expect(model.config.sendWithShiftEnter).toBeTruthy();
|
|
82
92
|
});
|
|
83
93
|
});
|
|
@@ -11,25 +11,26 @@ import {
|
|
|
11
11
|
IRenderMimeRegistry,
|
|
12
12
|
RenderMimeRegistry
|
|
13
13
|
} from '@jupyterlab/rendermime';
|
|
14
|
-
import {
|
|
14
|
+
import { IChatModel } from '../model';
|
|
15
15
|
import { ChatWidget } from '../widgets/chat-widget';
|
|
16
|
+
import { MockChatModel } from './mocks';
|
|
16
17
|
|
|
17
18
|
describe('test chat widget', () => {
|
|
18
19
|
let model: IChatModel;
|
|
19
20
|
let rmRegistry: IRenderMimeRegistry;
|
|
20
21
|
|
|
21
22
|
beforeEach(() => {
|
|
22
|
-
model = new
|
|
23
|
+
model = new MockChatModel();
|
|
23
24
|
rmRegistry = new RenderMimeRegistry();
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
describe('model instantiation', () => {
|
|
27
|
-
it('should create
|
|
28
|
+
it('should create an AbstractChatModel', () => {
|
|
28
29
|
const widget = new ChatWidget({ model, rmRegistry });
|
|
29
30
|
expect(widget).toBeInstanceOf(ChatWidget);
|
|
30
31
|
});
|
|
31
32
|
|
|
32
|
-
it('should dispose
|
|
33
|
+
it('should dispose an AbstractChatModel', () => {
|
|
33
34
|
const widget = new ChatWidget({ model, rmRegistry });
|
|
34
35
|
widget.dispose();
|
|
35
36
|
expect(widget.isDisposed).toBeTruthy();
|
|
@@ -22,7 +22,7 @@ export type ChatCommand = {
|
|
|
22
22
|
* If set, this will be rendered as the icon for the command in the chat
|
|
23
23
|
* commands menu. Jupyter Chat will choose a default if this is unset.
|
|
24
24
|
*/
|
|
25
|
-
icon?: LabIcon | string;
|
|
25
|
+
icon?: LabIcon | JSX.Element | string | null;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* If set, this will be rendered as the description for the command in the
|