@jupyter/chat 0.1.0 → 0.3.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 +151 -0
- package/lib/active-cell-manager.js +201 -0
- package/lib/components/chat-input.d.ts +14 -4
- package/lib/components/chat-input.js +118 -10
- package/lib/components/chat-messages.d.ts +45 -15
- package/lib/components/chat-messages.js +237 -55
- package/lib/components/chat.d.ts +21 -6
- package/lib/components/chat.js +15 -45
- package/lib/components/code-blocks/code-toolbar.d.ts +13 -0
- package/lib/components/code-blocks/code-toolbar.js +70 -0
- package/lib/components/{copy-button.d.ts → code-blocks/copy-button.d.ts} +1 -0
- package/lib/components/code-blocks/copy-button.js +43 -0
- package/lib/components/mui-extras/contrasting-tooltip.d.ts +6 -0
- package/lib/components/mui-extras/contrasting-tooltip.js +21 -0
- package/lib/components/mui-extras/tooltipped-icon-button.d.ts +35 -0
- package/lib/components/mui-extras/tooltipped-icon-button.js +36 -0
- package/lib/components/rendermime-markdown.d.ts +2 -0
- package/lib/components/rendermime-markdown.js +29 -15
- package/lib/components/scroll-container.js +1 -19
- package/lib/icons.d.ts +2 -0
- package/lib/icons.js +10 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/model.d.ts +98 -14
- package/lib/model.js +197 -6
- package/lib/registry.d.ts +78 -0
- package/lib/registry.js +83 -0
- package/lib/types.d.ts +60 -4
- package/lib/widgets/chat-sidebar.d.ts +3 -4
- package/lib/widgets/chat-sidebar.js +2 -2
- package/lib/widgets/chat-widget.d.ts +2 -8
- package/lib/widgets/chat-widget.js +6 -6
- package/package.json +204 -200
- package/src/active-cell-manager.ts +318 -0
- package/src/components/chat-input.tsx +196 -50
- package/src/components/chat-messages.tsx +357 -95
- package/src/components/chat.tsx +43 -69
- package/src/components/code-blocks/code-toolbar.tsx +143 -0
- package/src/components/code-blocks/copy-button.tsx +68 -0
- package/src/components/mui-extras/contrasting-tooltip.tsx +27 -0
- package/src/components/mui-extras/tooltipped-icon-button.tsx +84 -0
- package/src/components/rendermime-markdown.tsx +44 -20
- package/src/components/scroll-container.tsx +1 -25
- package/src/icons.ts +12 -0
- package/src/index.ts +2 -0
- package/src/model.ts +275 -21
- package/src/registry.ts +129 -0
- package/src/types.ts +62 -4
- package/src/widgets/chat-sidebar.tsx +3 -15
- package/src/widgets/chat-widget.tsx +8 -21
- package/style/chat.css +40 -0
- package/style/icons/read.svg +11 -0
- package/style/icons/replace-cell.svg +8 -0
- package/lib/components/copy-button.js +0 -35
- package/src/components/copy-button.tsx +0 -55
package/src/model.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
6
7
|
import { IDisposable } from '@lumino/disposable';
|
|
7
8
|
import { ISignal, Signal } from '@lumino/signaling';
|
|
8
9
|
|
|
@@ -13,21 +14,32 @@ import {
|
|
|
13
14
|
IConfig,
|
|
14
15
|
IUser
|
|
15
16
|
} from './types';
|
|
17
|
+
import { IActiveCellManager } from './active-cell-manager';
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* The chat model interface.
|
|
19
21
|
*/
|
|
20
22
|
export interface IChatModel extends IDisposable {
|
|
21
23
|
/**
|
|
22
|
-
* The chat model
|
|
24
|
+
* The chat model name.
|
|
23
25
|
*/
|
|
24
|
-
|
|
26
|
+
name: string;
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* The configuration for the chat panel.
|
|
28
30
|
*/
|
|
29
31
|
config: IConfig;
|
|
30
32
|
|
|
33
|
+
/**
|
|
34
|
+
* The indexes list of the unread messages.
|
|
35
|
+
*/
|
|
36
|
+
unreadMessages: number[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The indexes list of the messages currently in the viewport.
|
|
40
|
+
*/
|
|
41
|
+
messagesInViewport?: number[];
|
|
42
|
+
|
|
31
43
|
/**
|
|
32
44
|
* The user connected to the chat panel.
|
|
33
45
|
*/
|
|
@@ -39,10 +51,30 @@ export interface IChatModel extends IDisposable {
|
|
|
39
51
|
readonly messages: IChatMessage[];
|
|
40
52
|
|
|
41
53
|
/**
|
|
42
|
-
*
|
|
54
|
+
* Get the active cell manager.
|
|
55
|
+
*/
|
|
56
|
+
readonly activeCellManager: IActiveCellManager | null;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A signal emitting when the messages list is updated.
|
|
43
60
|
*/
|
|
44
61
|
readonly messagesUpdated: ISignal<IChatModel, void>;
|
|
45
62
|
|
|
63
|
+
/**
|
|
64
|
+
* A signal emitting when the messages list is updated.
|
|
65
|
+
*/
|
|
66
|
+
get configChanged(): ISignal<IChatModel, IConfig>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A signal emitting when unread messages change.
|
|
70
|
+
*/
|
|
71
|
+
readonly unreadChanged?: ISignal<IChatModel, number[]>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A signal emitting when the viewport change.
|
|
75
|
+
*/
|
|
76
|
+
readonly viewportChanged?: ISignal<IChatModel, number[]>;
|
|
77
|
+
|
|
46
78
|
/**
|
|
47
79
|
* Send a message, to be defined depending on the chosen technology.
|
|
48
80
|
* Default to no-op.
|
|
@@ -119,7 +151,14 @@ export class ChatModel implements IChatModel {
|
|
|
119
151
|
* Create a new chat model.
|
|
120
152
|
*/
|
|
121
153
|
constructor(options: ChatModel.IOptions = {}) {
|
|
122
|
-
|
|
154
|
+
const config = options.config ?? {};
|
|
155
|
+
|
|
156
|
+
// Stack consecutive messages from the same user by default.
|
|
157
|
+
this._config = { stackMessages: true, ...config };
|
|
158
|
+
|
|
159
|
+
this._commands = options.commands;
|
|
160
|
+
|
|
161
|
+
this._activeCellManager = options.activeCellManager ?? null;
|
|
123
162
|
}
|
|
124
163
|
|
|
125
164
|
/**
|
|
@@ -129,16 +168,52 @@ export class ChatModel implements IChatModel {
|
|
|
129
168
|
return this._messages;
|
|
130
169
|
}
|
|
131
170
|
|
|
171
|
+
get activeCellManager(): IActiveCellManager | null {
|
|
172
|
+
return this._activeCellManager;
|
|
173
|
+
}
|
|
132
174
|
/**
|
|
133
|
-
* The chat model
|
|
175
|
+
* The chat model id.
|
|
134
176
|
*/
|
|
135
|
-
get id(): string {
|
|
177
|
+
get id(): string | undefined {
|
|
136
178
|
return this._id;
|
|
137
179
|
}
|
|
138
|
-
set id(value: string) {
|
|
180
|
+
set id(value: string | undefined) {
|
|
139
181
|
this._id = value;
|
|
140
182
|
}
|
|
141
183
|
|
|
184
|
+
/**
|
|
185
|
+
* The chat model name.
|
|
186
|
+
*/
|
|
187
|
+
get name(): string {
|
|
188
|
+
return this._name;
|
|
189
|
+
}
|
|
190
|
+
set name(value: string) {
|
|
191
|
+
this._name = value;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Timestamp of the last read message in local storage.
|
|
196
|
+
*/
|
|
197
|
+
get lastRead(): number {
|
|
198
|
+
if (this._id === undefined) {
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
const storage = JSON.parse(
|
|
202
|
+
localStorage.getItem(`@jupyter/chat:${this._id}`) || '{}'
|
|
203
|
+
);
|
|
204
|
+
return storage.lastRead ?? 0;
|
|
205
|
+
}
|
|
206
|
+
set lastRead(value: number) {
|
|
207
|
+
if (this._id === undefined) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const storage = JSON.parse(
|
|
211
|
+
localStorage.getItem(`@jupyter/chat:${this._id}`) || '{}'
|
|
212
|
+
);
|
|
213
|
+
storage.lastRead = value;
|
|
214
|
+
localStorage.setItem(`@jupyter/chat:${this._id}`, JSON.stringify(storage));
|
|
215
|
+
}
|
|
216
|
+
|
|
142
217
|
/**
|
|
143
218
|
* The chat settings.
|
|
144
219
|
*/
|
|
@@ -146,16 +221,113 @@ export class ChatModel implements IChatModel {
|
|
|
146
221
|
return this._config;
|
|
147
222
|
}
|
|
148
223
|
set config(value: Partial<IConfig>) {
|
|
224
|
+
const stackMessagesChanged =
|
|
225
|
+
'stackMessages' in value &&
|
|
226
|
+
this._config.stackMessages !== value.stackMessages;
|
|
227
|
+
|
|
228
|
+
const unreadNotificationsChanged =
|
|
229
|
+
'unreadNotifications' in value &&
|
|
230
|
+
this._config.unreadNotifications !== value.unreadNotifications;
|
|
231
|
+
|
|
149
232
|
this._config = { ...this._config, ...value };
|
|
233
|
+
|
|
234
|
+
this._configChanged.emit(this._config);
|
|
235
|
+
|
|
236
|
+
// Update the stacked status of the messages and the view.
|
|
237
|
+
if (stackMessagesChanged) {
|
|
238
|
+
if (this._config.stackMessages) {
|
|
239
|
+
this._messages.slice(1).forEach((message, idx) => {
|
|
240
|
+
const previousUser = this._messages[idx].sender.username;
|
|
241
|
+
message.stacked = previousUser === message.sender.username;
|
|
242
|
+
});
|
|
243
|
+
} else {
|
|
244
|
+
this._messages.forEach(message => {
|
|
245
|
+
delete message.stacked;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
this._messagesUpdated.emit();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// remove existing notifications if they are not required anymore.
|
|
252
|
+
if (unreadNotificationsChanged && !this._config.unreadNotifications) {
|
|
253
|
+
this._notify(0);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The indexes list of the unread messages.
|
|
259
|
+
*/
|
|
260
|
+
get unreadMessages(): number[] {
|
|
261
|
+
return this._unreadMessages;
|
|
262
|
+
}
|
|
263
|
+
set unreadMessages(unread: number[]) {
|
|
264
|
+
const recentlyRead = this._unreadMessages.filter(
|
|
265
|
+
elem => !unread.includes(elem)
|
|
266
|
+
);
|
|
267
|
+
const unreadCountDiff = unread.length - this._unreadMessages.length;
|
|
268
|
+
|
|
269
|
+
this._unreadMessages = unread;
|
|
270
|
+
this._unreadChanged.emit(this._unreadMessages);
|
|
271
|
+
|
|
272
|
+
// Notify the change.
|
|
273
|
+
this._notify(unread.length, unreadCountDiff > 0);
|
|
274
|
+
|
|
275
|
+
// Save the last read to the local storage.
|
|
276
|
+
if (this._id !== undefined && recentlyRead.length) {
|
|
277
|
+
let lastReadChanged = false;
|
|
278
|
+
let lastRead = this.lastRead ?? this.messages[recentlyRead[0]].time;
|
|
279
|
+
recentlyRead.forEach(index => {
|
|
280
|
+
if (this.messages[index].time > lastRead) {
|
|
281
|
+
lastRead = this.messages[index].time;
|
|
282
|
+
lastReadChanged = true;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (lastReadChanged) {
|
|
287
|
+
this.lastRead = lastRead;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The indexes list of the messages currently in the viewport.
|
|
294
|
+
*/
|
|
295
|
+
get messagesInViewport(): number[] {
|
|
296
|
+
return this._messagesInViewport;
|
|
297
|
+
}
|
|
298
|
+
set messagesInViewport(values: number[]) {
|
|
299
|
+
this._messagesInViewport = values;
|
|
300
|
+
this._viewportChanged.emit(values);
|
|
150
301
|
}
|
|
151
302
|
|
|
152
303
|
/**
|
|
153
|
-
*
|
|
304
|
+
* A signal emitting when the messages list is updated.
|
|
154
305
|
*/
|
|
155
306
|
get messagesUpdated(): ISignal<IChatModel, void> {
|
|
156
307
|
return this._messagesUpdated;
|
|
157
308
|
}
|
|
158
309
|
|
|
310
|
+
/**
|
|
311
|
+
* A signal emitting when the messages list is updated.
|
|
312
|
+
*/
|
|
313
|
+
get configChanged(): ISignal<IChatModel, IConfig> {
|
|
314
|
+
return this._configChanged;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* A signal emitting when unread messages change.
|
|
319
|
+
*/
|
|
320
|
+
get unreadChanged(): ISignal<IChatModel, number[]> {
|
|
321
|
+
return this._unreadChanged;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* A signal emitting when the viewport change.
|
|
326
|
+
*/
|
|
327
|
+
get viewportChanged(): ISignal<IChatModel, number[]> {
|
|
328
|
+
return this._viewportChanged;
|
|
329
|
+
}
|
|
330
|
+
|
|
159
331
|
/**
|
|
160
332
|
* Send a message, to be defined depending on the chosen technology.
|
|
161
333
|
* Default to no-op.
|
|
@@ -165,17 +337,6 @@ export class ChatModel implements IChatModel {
|
|
|
165
337
|
*/
|
|
166
338
|
addMessage(message: INewMessage): Promise<boolean | void> | boolean | void {}
|
|
167
339
|
|
|
168
|
-
/**
|
|
169
|
-
* Optional, to update a message from the chat panel.
|
|
170
|
-
*
|
|
171
|
-
* @param id - the unique ID of the message.
|
|
172
|
-
* @param message - the message to update.
|
|
173
|
-
*/
|
|
174
|
-
updateMessage?(
|
|
175
|
-
id: string,
|
|
176
|
-
message: INewMessage
|
|
177
|
-
): Promise<boolean | void> | boolean | void;
|
|
178
|
-
|
|
179
340
|
/**
|
|
180
341
|
* Dispose the chat model.
|
|
181
342
|
*/
|
|
@@ -231,10 +392,35 @@ export class ChatModel implements IChatModel {
|
|
|
231
392
|
*/
|
|
232
393
|
messagesInserted(index: number, messages: IChatMessage[]): void {
|
|
233
394
|
const formattedMessages: IChatMessage[] = [];
|
|
234
|
-
|
|
395
|
+
const unreadIndexes: number[] = [];
|
|
396
|
+
|
|
397
|
+
const lastRead = this.lastRead ?? 0;
|
|
398
|
+
|
|
399
|
+
// Format the messages.
|
|
400
|
+
messages.forEach((message, idx) => {
|
|
235
401
|
formattedMessages.push(this.formatChatMessage(message));
|
|
402
|
+
if (message.time > lastRead) {
|
|
403
|
+
unreadIndexes.push(index + idx);
|
|
404
|
+
}
|
|
236
405
|
});
|
|
406
|
+
|
|
407
|
+
// Insert the messages.
|
|
237
408
|
this._messages.splice(index, 0, ...formattedMessages);
|
|
409
|
+
|
|
410
|
+
if (this._config.stackMessages) {
|
|
411
|
+
// Check if some messages should be stacked by comparing each message' sender
|
|
412
|
+
// with the previous one.
|
|
413
|
+
const lastIdx = index + formattedMessages.length - 1;
|
|
414
|
+
const start = index === 0 ? 1 : index;
|
|
415
|
+
const end = this._messages.length > lastIdx + 1 ? lastIdx + 1 : lastIdx;
|
|
416
|
+
for (let idx = start; idx <= end; idx++) {
|
|
417
|
+
const message = this._messages[idx];
|
|
418
|
+
const previousUser = this._messages[idx - 1].sender.username;
|
|
419
|
+
message.stacked = previousUser === message.sender.username;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this._addUnreadMessages(unreadIndexes);
|
|
238
424
|
this._messagesUpdated.emit();
|
|
239
425
|
}
|
|
240
426
|
|
|
@@ -249,11 +435,69 @@ export class ChatModel implements IChatModel {
|
|
|
249
435
|
this._messagesUpdated.emit();
|
|
250
436
|
}
|
|
251
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Add unread messages to the list.
|
|
440
|
+
* @param indexes - list of new indexes.
|
|
441
|
+
*/
|
|
442
|
+
private _addUnreadMessages(indexes: number[]) {
|
|
443
|
+
const unread = new Set(this._unreadMessages);
|
|
444
|
+
indexes.forEach(index => unread.add(index));
|
|
445
|
+
this.unreadMessages = Array.from(unread.values());
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Notifications on unread messages.
|
|
450
|
+
*
|
|
451
|
+
* @param unreadCount - number of unread messages.
|
|
452
|
+
* If the value is 0, existing notification will be deleted.
|
|
453
|
+
* @param canCreate - whether to create a notification if it does not exist.
|
|
454
|
+
* Usually it is used when there are new unread messages, and not when the
|
|
455
|
+
* unread messages count decrease.
|
|
456
|
+
*/
|
|
457
|
+
private _notify(unreadCount: number, canCreate: boolean = false) {
|
|
458
|
+
if (this._commands) {
|
|
459
|
+
if (unreadCount && this._config.unreadNotifications) {
|
|
460
|
+
// Update the notification if exist.
|
|
461
|
+
this._commands
|
|
462
|
+
.execute('apputils:update-notification', {
|
|
463
|
+
id: this._notificationId,
|
|
464
|
+
message: `${unreadCount} incoming message(s) ${this._name ? 'in ' + this._name : ''}`
|
|
465
|
+
})
|
|
466
|
+
.then(success => {
|
|
467
|
+
// Create a new notification only if messages are added.
|
|
468
|
+
if (!success && canCreate) {
|
|
469
|
+
this._commands!.execute('apputils:notify', {
|
|
470
|
+
type: 'info',
|
|
471
|
+
message: `${unreadCount} incoming message(s) in ${this._name}`
|
|
472
|
+
}).then(id => {
|
|
473
|
+
this._notificationId = id;
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
} else if (this._notificationId) {
|
|
478
|
+
// Delete notification if there is no more unread messages.
|
|
479
|
+
this._commands.execute('apputils:dismiss-notification', {
|
|
480
|
+
id: this._notificationId
|
|
481
|
+
});
|
|
482
|
+
this._notificationId = null;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
252
487
|
private _messages: IChatMessage[] = [];
|
|
253
|
-
private
|
|
488
|
+
private _unreadMessages: number[] = [];
|
|
489
|
+
private _messagesInViewport: number[] = [];
|
|
490
|
+
private _id: string | undefined;
|
|
491
|
+
private _name: string = '';
|
|
254
492
|
private _config: IConfig;
|
|
255
493
|
private _isDisposed = false;
|
|
494
|
+
private _commands?: CommandRegistry;
|
|
495
|
+
private _activeCellManager: IActiveCellManager | null;
|
|
496
|
+
private _notificationId: string | null = null;
|
|
256
497
|
private _messagesUpdated = new Signal<IChatModel, void>(this);
|
|
498
|
+
private _configChanged = new Signal<IChatModel, IConfig>(this);
|
|
499
|
+
private _unreadChanged = new Signal<IChatModel, number[]>(this);
|
|
500
|
+
private _viewportChanged = new Signal<IChatModel, number[]>(this);
|
|
257
501
|
}
|
|
258
502
|
|
|
259
503
|
/**
|
|
@@ -268,5 +512,15 @@ export namespace ChatModel {
|
|
|
268
512
|
* Initial config for the chat widget.
|
|
269
513
|
*/
|
|
270
514
|
config?: IConfig;
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Commands registry.
|
|
518
|
+
*/
|
|
519
|
+
commands?: CommandRegistry;
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Active cell manager
|
|
523
|
+
*/
|
|
524
|
+
activeCellManager?: IActiveCellManager | null;
|
|
271
525
|
}
|
|
272
526
|
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { Token } from '@lumino/coreutils';
|
|
6
|
+
import { IAutocompletionCommandsProps } from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The token for the autocomplete registry, which can be provided by an extension
|
|
10
|
+
* using @jupyter/chat package.
|
|
11
|
+
*/
|
|
12
|
+
export const IAutocompletionRegistry = new Token<IAutocompletionRegistry>(
|
|
13
|
+
'@jupyter/chat:IAutocompleteRegistry'
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The interface of a registry to provide autocompleters.
|
|
18
|
+
*/
|
|
19
|
+
export interface IAutocompletionRegistry {
|
|
20
|
+
/**
|
|
21
|
+
* The default autocompletion name.
|
|
22
|
+
*/
|
|
23
|
+
default: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Get the default autocompletion.
|
|
26
|
+
*/
|
|
27
|
+
getDefaultCompletion(): IAutocompletionCommandsProps | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Return a registered autocomplete props.
|
|
30
|
+
*
|
|
31
|
+
* @param name - the name of the registered autocomplete props.
|
|
32
|
+
*/
|
|
33
|
+
get(name: string): IAutocompletionCommandsProps | undefined;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Register autocomplete props.
|
|
37
|
+
*
|
|
38
|
+
* @param name - the name for the registration.
|
|
39
|
+
* @param autocompletion - the autocomplete props.
|
|
40
|
+
*/
|
|
41
|
+
add(name: string, autocompletion: IAutocompletionCommandsProps): boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Remove a registered autocomplete props.
|
|
45
|
+
*
|
|
46
|
+
* @param name - the name of the autocomplete props.
|
|
47
|
+
*/
|
|
48
|
+
remove(name: string): boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A registry to provide autocompleters.
|
|
53
|
+
*/
|
|
54
|
+
export class AutocompletionRegistry implements IAutocompletionRegistry {
|
|
55
|
+
/**
|
|
56
|
+
* Getter and setter for the default autocompletion name.
|
|
57
|
+
*/
|
|
58
|
+
get default(): string | null {
|
|
59
|
+
return this._default;
|
|
60
|
+
}
|
|
61
|
+
set default(name: string | null) {
|
|
62
|
+
if (name === null || this._autocompletions.has(name)) {
|
|
63
|
+
this._default = name;
|
|
64
|
+
} else {
|
|
65
|
+
console.warn(`There is no registered completer with the name '${name}'`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the default autocompletion.
|
|
71
|
+
*/
|
|
72
|
+
getDefaultCompletion(): IAutocompletionCommandsProps | undefined {
|
|
73
|
+
if (this._default === null) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return this._autocompletions.get(this._default);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Return a registered autocomplete props.
|
|
81
|
+
*
|
|
82
|
+
* @param name - the name of the registered autocomplete props.
|
|
83
|
+
*/
|
|
84
|
+
get(name: string): IAutocompletionCommandsProps | undefined {
|
|
85
|
+
return this._autocompletions.get(name);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Register autocomplete props.
|
|
90
|
+
*
|
|
91
|
+
* @param name - the name for the registration.
|
|
92
|
+
* @param autocompletion - the autocomplete props.
|
|
93
|
+
*/
|
|
94
|
+
add(
|
|
95
|
+
name: string,
|
|
96
|
+
autocompletion: IAutocompletionCommandsProps,
|
|
97
|
+
isDefault: boolean = false
|
|
98
|
+
): boolean {
|
|
99
|
+
if (!this._autocompletions.has(name)) {
|
|
100
|
+
this._autocompletions.set(name, autocompletion);
|
|
101
|
+
if (this._autocompletions.size === 1 || isDefault) {
|
|
102
|
+
this.default = name;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
} else {
|
|
106
|
+
console.warn(`A completer with the name '${name}' is already registered`);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Remove a registered autocomplete props.
|
|
113
|
+
*
|
|
114
|
+
* @param name - the name of the autocomplete props.
|
|
115
|
+
*/
|
|
116
|
+
remove(name: string): boolean {
|
|
117
|
+
return this._autocompletions.delete(name);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Remove all registered autocompletions.
|
|
122
|
+
*/
|
|
123
|
+
removeAll(): void {
|
|
124
|
+
this._autocompletions.clear();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private _default: string | null = null;
|
|
128
|
+
private _autocompletions = new Map<string, IAutocompletionCommandsProps>();
|
|
129
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -19,22 +19,41 @@ export interface IUser {
|
|
|
19
19
|
* The configuration interface.
|
|
20
20
|
*/
|
|
21
21
|
export interface IConfig {
|
|
22
|
+
/**
|
|
23
|
+
* Whether to send a message via Shift-Enter instead of Enter.
|
|
24
|
+
*/
|
|
22
25
|
sendWithShiftEnter?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Last read message (no use yet).
|
|
28
|
+
*/
|
|
23
29
|
lastRead?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to stack consecutive messages from same user.
|
|
32
|
+
*/
|
|
33
|
+
stackMessages?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether to enable or not the notifications on unread messages.
|
|
36
|
+
*/
|
|
37
|
+
unreadNotifications?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Whether to enable or not the code toolbar.
|
|
40
|
+
*/
|
|
41
|
+
enableCodeToolbar?: boolean;
|
|
24
42
|
}
|
|
25
43
|
|
|
26
44
|
/**
|
|
27
|
-
* The chat message
|
|
45
|
+
* The chat message description.
|
|
28
46
|
*/
|
|
29
|
-
export interface IChatMessage {
|
|
47
|
+
export interface IChatMessage<T = IUser> {
|
|
30
48
|
type: 'msg';
|
|
31
49
|
body: string;
|
|
32
50
|
id: string;
|
|
33
51
|
time: number;
|
|
34
|
-
sender:
|
|
52
|
+
sender: T;
|
|
35
53
|
raw_time?: boolean;
|
|
36
54
|
deleted?: boolean;
|
|
37
55
|
edited?: boolean;
|
|
56
|
+
stacked?: boolean;
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
/**
|
|
@@ -53,6 +72,45 @@ export interface INewMessage {
|
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
/**
|
|
56
|
-
* An empty interface to describe optional settings
|
|
75
|
+
* An empty interface to describe optional settings that could be fetched from server.
|
|
57
76
|
*/
|
|
58
77
|
export interface ISettings {}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The autocomplete command type.
|
|
81
|
+
*/
|
|
82
|
+
export type AutocompleteCommand = {
|
|
83
|
+
label: string;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The properties of the autocompletion.
|
|
88
|
+
*
|
|
89
|
+
* The autocompletion component will open if the 'opener' string is typed at the
|
|
90
|
+
* beginning of the input field.
|
|
91
|
+
*/
|
|
92
|
+
export interface IAutocompletionCommandsProps {
|
|
93
|
+
/**
|
|
94
|
+
* The string that open the completer.
|
|
95
|
+
*/
|
|
96
|
+
opener: string;
|
|
97
|
+
/**
|
|
98
|
+
* The list of available commands.
|
|
99
|
+
*/
|
|
100
|
+
commands?: AutocompleteCommand[] | (() => Promise<AutocompleteCommand[]>);
|
|
101
|
+
/**
|
|
102
|
+
* The props for the Autocomplete component.
|
|
103
|
+
*
|
|
104
|
+
* Must be compatible with https://mui.com/material-ui/api/autocomplete/#props.
|
|
105
|
+
*
|
|
106
|
+
* ## NOTES:
|
|
107
|
+
* - providing `options` will overwrite the commands argument.
|
|
108
|
+
* - providing `renderInput` will overwrite the input component.
|
|
109
|
+
* - providing `renderOptions` allows to customize the rendering of the component.
|
|
110
|
+
* - some arguments should not be provided and would be overwritten:
|
|
111
|
+
* - inputValue
|
|
112
|
+
* - onInputChange
|
|
113
|
+
* - onHighlightChange
|
|
114
|
+
*/
|
|
115
|
+
props?: any;
|
|
116
|
+
}
|
|
@@ -3,26 +3,14 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
6
|
+
import { ReactWidget } from '@jupyterlab/apputils';
|
|
8
7
|
import React from 'react';
|
|
9
8
|
|
|
10
9
|
import { Chat } from '../components/chat';
|
|
11
10
|
import { chatIcon } from '../icons';
|
|
12
|
-
import { IChatModel } from '../model';
|
|
13
11
|
|
|
14
|
-
export function buildChatSidebar(
|
|
15
|
-
|
|
16
|
-
themeManager: IThemeManager | null,
|
|
17
|
-
rmRegistry: IRenderMimeRegistry
|
|
18
|
-
): ReactWidget {
|
|
19
|
-
const ChatWidget = ReactWidget.create(
|
|
20
|
-
<Chat
|
|
21
|
-
model={chatModel}
|
|
22
|
-
themeManager={themeManager}
|
|
23
|
-
rmRegistry={rmRegistry}
|
|
24
|
-
/>
|
|
25
|
-
);
|
|
12
|
+
export function buildChatSidebar(options: Chat.IOptions): ReactWidget {
|
|
13
|
+
const ChatWidget = ReactWidget.create(<Chat {...options} />);
|
|
26
14
|
ChatWidget.id = 'jupyter-chat::side-panel';
|
|
27
15
|
ChatWidget.title.icon = chatIcon;
|
|
28
16
|
ChatWidget.title.caption = 'Jupyter Chat'; // TODO: i18n
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
6
|
+
import { ReactWidget } from '@jupyterlab/apputils';
|
|
8
7
|
import React from 'react';
|
|
9
8
|
|
|
10
9
|
import { Chat } from '../components/chat';
|
|
@@ -19,33 +18,21 @@ export class ChatWidget extends ReactWidget {
|
|
|
19
18
|
this.title.icon = chatIcon;
|
|
20
19
|
this.title.caption = 'Jupyter Chat'; // TODO: i18n
|
|
21
20
|
|
|
22
|
-
this.
|
|
23
|
-
this._themeManager = options?.themeManager || null;
|
|
24
|
-
this._rmRegistry = options.rmRegistry;
|
|
21
|
+
this._chatOptions = options;
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
/**
|
|
28
|
-
*
|
|
25
|
+
* Get the model of the widget.
|
|
29
26
|
*/
|
|
30
27
|
get model(): IChatModel {
|
|
31
|
-
return this.
|
|
28
|
+
return this._chatOptions.model;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
render() {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
themeManager={this._themeManager}
|
|
39
|
-
rmRegistry={this._rmRegistry}
|
|
40
|
-
/>
|
|
41
|
-
);
|
|
32
|
+
// The model need to be passed, otherwise it is undefined in the widget in
|
|
33
|
+
// the case of collaborative document.
|
|
34
|
+
return <Chat {...this._chatOptions} model={this._chatOptions.model} />;
|
|
42
35
|
}
|
|
43
36
|
|
|
44
|
-
private
|
|
45
|
-
private _themeManager: IThemeManager | null;
|
|
46
|
-
private _rmRegistry: IRenderMimeRegistry;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export namespace ChatWidget {
|
|
50
|
-
export interface IOptions extends Chat.IOptions {}
|
|
37
|
+
private _chatOptions: Chat.IOptions;
|
|
51
38
|
}
|