jupyterlab-chat 0.24.0 → 0.24.1
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__/model.spec.d.ts +1 -0
- package/lib/__tests__/model.spec.js +151 -0
- package/lib/model.js +15 -6
- package/package.json +2 -2
- package/src/__tests__/model.spec.ts +177 -0
- package/src/model.ts +14 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { UUID } from '@lumino/coreutils';
|
|
6
|
+
import { Signal } from '@lumino/signaling';
|
|
7
|
+
import { LabChatModel } from '../model';
|
|
8
|
+
import { YChat } from '../ychat';
|
|
9
|
+
const flushPromises = () => new Promise(resolve => setTimeout(resolve, 0));
|
|
10
|
+
const TEST_USER = {
|
|
11
|
+
username: 'test-user',
|
|
12
|
+
name: 'Test User',
|
|
13
|
+
display_name: 'Test',
|
|
14
|
+
initials: 'TU',
|
|
15
|
+
color: '#aabbcc'
|
|
16
|
+
};
|
|
17
|
+
function makeWidgetConfig() {
|
|
18
|
+
const owner = {};
|
|
19
|
+
owner.config = {};
|
|
20
|
+
owner.configChanged = new Signal(owner);
|
|
21
|
+
return owner;
|
|
22
|
+
}
|
|
23
|
+
describe('LabChatModel', () => {
|
|
24
|
+
let sharedModel;
|
|
25
|
+
let model;
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
sharedModel = YChat.create();
|
|
28
|
+
model = new LabChatModel({
|
|
29
|
+
widgetConfig: makeWidgetConfig(),
|
|
30
|
+
user: TEST_USER,
|
|
31
|
+
sharedModel
|
|
32
|
+
});
|
|
33
|
+
// Resolve the ready promise so messagesInserted can proceed.
|
|
34
|
+
model.id = UUID.uuid4();
|
|
35
|
+
});
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
Signal.clearData(model);
|
|
38
|
+
sharedModel.dispose();
|
|
39
|
+
});
|
|
40
|
+
describe('message attribute updates from shared model', () => {
|
|
41
|
+
it('should reflect a body change', async () => {
|
|
42
|
+
const id = UUID.uuid4();
|
|
43
|
+
sharedModel.addMessage({
|
|
44
|
+
type: 'msg',
|
|
45
|
+
id,
|
|
46
|
+
body: 'original',
|
|
47
|
+
time: 1000,
|
|
48
|
+
sender: TEST_USER.username
|
|
49
|
+
});
|
|
50
|
+
await flushPromises();
|
|
51
|
+
sharedModel.updateMessage(0, {
|
|
52
|
+
type: 'msg',
|
|
53
|
+
id,
|
|
54
|
+
body: 'updated',
|
|
55
|
+
time: 1000,
|
|
56
|
+
sender: TEST_USER.username
|
|
57
|
+
});
|
|
58
|
+
await flushPromises();
|
|
59
|
+
expect(model.messages[0].body).toBe('updated');
|
|
60
|
+
});
|
|
61
|
+
it('should reflect a mime_model change', async () => {
|
|
62
|
+
const id = UUID.uuid4();
|
|
63
|
+
sharedModel.addMessage({
|
|
64
|
+
type: 'msg',
|
|
65
|
+
id,
|
|
66
|
+
body: '',
|
|
67
|
+
time: 1000,
|
|
68
|
+
sender: TEST_USER.username
|
|
69
|
+
});
|
|
70
|
+
await flushPromises();
|
|
71
|
+
const mimeModel = { data: { 'text/plain': 'streamed output' } };
|
|
72
|
+
sharedModel.updateMessage(0, {
|
|
73
|
+
type: 'msg',
|
|
74
|
+
id,
|
|
75
|
+
body: '',
|
|
76
|
+
time: 1000,
|
|
77
|
+
sender: TEST_USER.username,
|
|
78
|
+
mime_model: mimeModel
|
|
79
|
+
});
|
|
80
|
+
await flushPromises();
|
|
81
|
+
expect(model.messages[0].mime_model).toEqual(mimeModel);
|
|
82
|
+
});
|
|
83
|
+
it('should not set edited when only mime_model changes', async () => {
|
|
84
|
+
const id = UUID.uuid4();
|
|
85
|
+
sharedModel.addMessage({
|
|
86
|
+
type: 'msg',
|
|
87
|
+
id,
|
|
88
|
+
body: '',
|
|
89
|
+
time: 1000,
|
|
90
|
+
sender: TEST_USER.username
|
|
91
|
+
});
|
|
92
|
+
await flushPromises();
|
|
93
|
+
sharedModel.updateMessage(0, {
|
|
94
|
+
type: 'msg',
|
|
95
|
+
id,
|
|
96
|
+
body: '',
|
|
97
|
+
time: 1000,
|
|
98
|
+
sender: TEST_USER.username,
|
|
99
|
+
mime_model: { data: { 'text/plain': 'streamed output' } }
|
|
100
|
+
});
|
|
101
|
+
await flushPromises();
|
|
102
|
+
expect(model.messages[0].edited).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('rerender tracking', () => {
|
|
106
|
+
it('should replace renderedDelegate when mime_model changes', async () => {
|
|
107
|
+
const id = UUID.uuid4();
|
|
108
|
+
sharedModel.addMessage({
|
|
109
|
+
type: 'msg',
|
|
110
|
+
id,
|
|
111
|
+
body: '',
|
|
112
|
+
time: 1000,
|
|
113
|
+
sender: TEST_USER.username
|
|
114
|
+
});
|
|
115
|
+
await flushPromises();
|
|
116
|
+
const delegateBefore = model.messages[0].renderedDelegate;
|
|
117
|
+
sharedModel.updateMessage(0, {
|
|
118
|
+
type: 'msg',
|
|
119
|
+
id,
|
|
120
|
+
body: '',
|
|
121
|
+
time: 1000,
|
|
122
|
+
sender: TEST_USER.username,
|
|
123
|
+
mime_model: { data: { 'text/plain': 'streamed output' } }
|
|
124
|
+
});
|
|
125
|
+
await flushPromises();
|
|
126
|
+
expect(model.messages[0].renderedDelegate).not.toBe(delegateBefore);
|
|
127
|
+
});
|
|
128
|
+
it('should not replace renderedDelegate when only edited changes', async () => {
|
|
129
|
+
const id = UUID.uuid4();
|
|
130
|
+
sharedModel.addMessage({
|
|
131
|
+
type: 'msg',
|
|
132
|
+
id,
|
|
133
|
+
body: 'hello',
|
|
134
|
+
time: 1000,
|
|
135
|
+
sender: TEST_USER.username
|
|
136
|
+
});
|
|
137
|
+
await flushPromises();
|
|
138
|
+
const delegateBefore = model.messages[0].renderedDelegate;
|
|
139
|
+
sharedModel.updateMessage(0, {
|
|
140
|
+
type: 'msg',
|
|
141
|
+
id,
|
|
142
|
+
body: 'hello',
|
|
143
|
+
time: 1000,
|
|
144
|
+
sender: TEST_USER.username,
|
|
145
|
+
edited: true
|
|
146
|
+
});
|
|
147
|
+
await flushPromises();
|
|
148
|
+
expect(model.messages[0].renderedDelegate).toBe(delegateBefore);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
package/lib/model.js
CHANGED
|
@@ -196,7 +196,8 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
196
196
|
'raw_time',
|
|
197
197
|
'deleted',
|
|
198
198
|
'edited',
|
|
199
|
-
'metadata'
|
|
199
|
+
'metadata',
|
|
200
|
+
'mime_model'
|
|
200
201
|
].includes(key)) {
|
|
201
202
|
const update = {};
|
|
202
203
|
update[key] = value;
|
|
@@ -353,16 +354,20 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
353
354
|
});
|
|
354
355
|
}
|
|
355
356
|
sendMessage(message) {
|
|
356
|
-
var _a, _b, _c, _d;
|
|
357
|
-
|
|
357
|
+
var _a, _b, _c, _d, _e;
|
|
358
|
+
// Allow empty message for bot only, as it may be streamed later.
|
|
359
|
+
if (!message.body &&
|
|
360
|
+
!message.mime_model &&
|
|
361
|
+
!((_a = message.attachments) === null || _a === void 0 ? void 0 : _a.length) &&
|
|
362
|
+
!((_b = message.sender) === null || _b === void 0 ? void 0 : _b.bot)) {
|
|
358
363
|
return null;
|
|
359
364
|
}
|
|
360
365
|
this._resetWritingStatus();
|
|
361
366
|
if (this._timeoutWriting !== null) {
|
|
362
367
|
window.clearTimeout(this._timeoutWriting);
|
|
363
368
|
}
|
|
364
|
-
const body = (
|
|
365
|
-
const user = (
|
|
369
|
+
const body = (_c = message.body) !== null && _c !== void 0 ? _c : '';
|
|
370
|
+
const user = (_d = message.sender) !== null && _d !== void 0 ? _d : this._user;
|
|
366
371
|
const msg = {
|
|
367
372
|
type: 'msg',
|
|
368
373
|
id: UUID.uuid4(),
|
|
@@ -380,7 +385,7 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
380
385
|
this.sharedModel.setUser(user);
|
|
381
386
|
}
|
|
382
387
|
// Add the attachments to the message.
|
|
383
|
-
const attachmentIds = (
|
|
388
|
+
const attachmentIds = (_e = message.attachments) === null || _e === void 0 ? void 0 : _e.map(attachment => this.sharedModel.setAttachment(attachment));
|
|
384
389
|
if (attachmentIds === null || attachmentIds === void 0 ? void 0 : attachmentIds.length) {
|
|
385
390
|
msg.attachments = attachmentIds;
|
|
386
391
|
}
|
|
@@ -421,6 +426,10 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
421
426
|
edited: true
|
|
422
427
|
};
|
|
423
428
|
}
|
|
429
|
+
// Update the mime model.
|
|
430
|
+
if (updatedMessage.mime_model) {
|
|
431
|
+
message.mime_model = updatedMessage.mime_model;
|
|
432
|
+
}
|
|
424
433
|
// Update the attachments.
|
|
425
434
|
const attachmentIds = (_a = updatedMessage.attachments) === null || _a === void 0 ? void 0 : _a.map(attachment => this.sharedModel.setAttachment(attachment));
|
|
426
435
|
if (attachmentIds === null || attachmentIds === void 0 ? void 0 : attachmentIds.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab-chat",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "The library to build a chat based on shared document",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"watch:src": "tsc -w --sourceMap"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@jupyter/chat": "^0.24.
|
|
45
|
+
"@jupyter/chat": "^0.24.1",
|
|
46
46
|
"@jupyter/collaborative-drive": "^4.4.0 || ^5.0.0",
|
|
47
47
|
"@jupyter/ydoc": "^3.0.0 || ^4.0.0",
|
|
48
48
|
"@jupyterlab/application": "^4.5.0",
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { UUID } from '@lumino/coreutils';
|
|
7
|
+
import { Signal } from '@lumino/signaling';
|
|
8
|
+
|
|
9
|
+
import { LabChatModel } from '../model';
|
|
10
|
+
import { IWidgetConfig } from '../token';
|
|
11
|
+
import { YChat } from '../ychat';
|
|
12
|
+
|
|
13
|
+
const flushPromises = () => new Promise(resolve => setTimeout(resolve, 0));
|
|
14
|
+
|
|
15
|
+
const TEST_USER = {
|
|
16
|
+
username: 'test-user',
|
|
17
|
+
name: 'Test User',
|
|
18
|
+
display_name: 'Test',
|
|
19
|
+
initials: 'TU',
|
|
20
|
+
color: '#aabbcc'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function makeWidgetConfig(): IWidgetConfig {
|
|
24
|
+
const owner = {} as IWidgetConfig;
|
|
25
|
+
owner.config = {};
|
|
26
|
+
(owner as any).configChanged = new Signal<IWidgetConfig, any>(owner);
|
|
27
|
+
return owner;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('LabChatModel', () => {
|
|
31
|
+
let sharedModel: YChat;
|
|
32
|
+
let model: LabChatModel;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
sharedModel = YChat.create();
|
|
36
|
+
model = new LabChatModel({
|
|
37
|
+
widgetConfig: makeWidgetConfig(),
|
|
38
|
+
user: TEST_USER,
|
|
39
|
+
sharedModel
|
|
40
|
+
});
|
|
41
|
+
// Resolve the ready promise so messagesInserted can proceed.
|
|
42
|
+
model.id = UUID.uuid4();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
Signal.clearData(model);
|
|
47
|
+
sharedModel.dispose();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('message attribute updates from shared model', () => {
|
|
51
|
+
it('should reflect a body change', async () => {
|
|
52
|
+
const id = UUID.uuid4();
|
|
53
|
+
sharedModel.addMessage({
|
|
54
|
+
type: 'msg',
|
|
55
|
+
id,
|
|
56
|
+
body: 'original',
|
|
57
|
+
time: 1000,
|
|
58
|
+
sender: TEST_USER.username
|
|
59
|
+
});
|
|
60
|
+
await flushPromises();
|
|
61
|
+
|
|
62
|
+
sharedModel.updateMessage(0, {
|
|
63
|
+
type: 'msg',
|
|
64
|
+
id,
|
|
65
|
+
body: 'updated',
|
|
66
|
+
time: 1000,
|
|
67
|
+
sender: TEST_USER.username
|
|
68
|
+
});
|
|
69
|
+
await flushPromises();
|
|
70
|
+
|
|
71
|
+
expect(model.messages[0].body).toBe('updated');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should reflect a mime_model change', async () => {
|
|
75
|
+
const id = UUID.uuid4();
|
|
76
|
+
sharedModel.addMessage({
|
|
77
|
+
type: 'msg',
|
|
78
|
+
id,
|
|
79
|
+
body: '',
|
|
80
|
+
time: 1000,
|
|
81
|
+
sender: TEST_USER.username
|
|
82
|
+
});
|
|
83
|
+
await flushPromises();
|
|
84
|
+
|
|
85
|
+
const mimeModel = { data: { 'text/plain': 'streamed output' } };
|
|
86
|
+
sharedModel.updateMessage(0, {
|
|
87
|
+
type: 'msg',
|
|
88
|
+
id,
|
|
89
|
+
body: '',
|
|
90
|
+
time: 1000,
|
|
91
|
+
sender: TEST_USER.username,
|
|
92
|
+
mime_model: mimeModel
|
|
93
|
+
});
|
|
94
|
+
await flushPromises();
|
|
95
|
+
|
|
96
|
+
expect(model.messages[0].mime_model).toEqual(mimeModel);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should not set edited when only mime_model changes', async () => {
|
|
100
|
+
const id = UUID.uuid4();
|
|
101
|
+
sharedModel.addMessage({
|
|
102
|
+
type: 'msg',
|
|
103
|
+
id,
|
|
104
|
+
body: '',
|
|
105
|
+
time: 1000,
|
|
106
|
+
sender: TEST_USER.username
|
|
107
|
+
});
|
|
108
|
+
await flushPromises();
|
|
109
|
+
|
|
110
|
+
sharedModel.updateMessage(0, {
|
|
111
|
+
type: 'msg',
|
|
112
|
+
id,
|
|
113
|
+
body: '',
|
|
114
|
+
time: 1000,
|
|
115
|
+
sender: TEST_USER.username,
|
|
116
|
+
mime_model: { data: { 'text/plain': 'streamed output' } }
|
|
117
|
+
});
|
|
118
|
+
await flushPromises();
|
|
119
|
+
|
|
120
|
+
expect(model.messages[0].edited).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('rerender tracking', () => {
|
|
125
|
+
it('should replace renderedDelegate when mime_model changes', async () => {
|
|
126
|
+
const id = UUID.uuid4();
|
|
127
|
+
sharedModel.addMessage({
|
|
128
|
+
type: 'msg',
|
|
129
|
+
id,
|
|
130
|
+
body: '',
|
|
131
|
+
time: 1000,
|
|
132
|
+
sender: TEST_USER.username
|
|
133
|
+
});
|
|
134
|
+
await flushPromises();
|
|
135
|
+
|
|
136
|
+
const delegateBefore = model.messages[0].renderedDelegate;
|
|
137
|
+
|
|
138
|
+
sharedModel.updateMessage(0, {
|
|
139
|
+
type: 'msg',
|
|
140
|
+
id,
|
|
141
|
+
body: '',
|
|
142
|
+
time: 1000,
|
|
143
|
+
sender: TEST_USER.username,
|
|
144
|
+
mime_model: { data: { 'text/plain': 'streamed output' } }
|
|
145
|
+
});
|
|
146
|
+
await flushPromises();
|
|
147
|
+
|
|
148
|
+
expect(model.messages[0].renderedDelegate).not.toBe(delegateBefore);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should not replace renderedDelegate when only edited changes', async () => {
|
|
152
|
+
const id = UUID.uuid4();
|
|
153
|
+
sharedModel.addMessage({
|
|
154
|
+
type: 'msg',
|
|
155
|
+
id,
|
|
156
|
+
body: 'hello',
|
|
157
|
+
time: 1000,
|
|
158
|
+
sender: TEST_USER.username
|
|
159
|
+
});
|
|
160
|
+
await flushPromises();
|
|
161
|
+
|
|
162
|
+
const delegateBefore = model.messages[0].renderedDelegate;
|
|
163
|
+
|
|
164
|
+
sharedModel.updateMessage(0, {
|
|
165
|
+
type: 'msg',
|
|
166
|
+
id,
|
|
167
|
+
body: 'hello',
|
|
168
|
+
time: 1000,
|
|
169
|
+
sender: TEST_USER.username,
|
|
170
|
+
edited: true
|
|
171
|
+
});
|
|
172
|
+
await flushPromises();
|
|
173
|
+
|
|
174
|
+
expect(model.messages[0].renderedDelegate).toBe(delegateBefore);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
package/src/model.ts
CHANGED
|
@@ -234,7 +234,13 @@ export class LabChatModel
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
sendMessage(message: INewMessage): string | null {
|
|
237
|
-
|
|
237
|
+
// Allow empty message for bot only, as it may be streamed later.
|
|
238
|
+
if (
|
|
239
|
+
!message.body &&
|
|
240
|
+
!message.mime_model &&
|
|
241
|
+
!message.attachments?.length &&
|
|
242
|
+
!message.sender?.bot
|
|
243
|
+
) {
|
|
238
244
|
return null;
|
|
239
245
|
}
|
|
240
246
|
this._resetWritingStatus();
|
|
@@ -316,6 +322,11 @@ export class LabChatModel
|
|
|
316
322
|
};
|
|
317
323
|
}
|
|
318
324
|
|
|
325
|
+
// Update the mime model.
|
|
326
|
+
if (updatedMessage.mime_model) {
|
|
327
|
+
message.mime_model = updatedMessage.mime_model;
|
|
328
|
+
}
|
|
329
|
+
|
|
319
330
|
// Update the attachments.
|
|
320
331
|
const attachmentIds = updatedMessage.attachments?.map(attachment =>
|
|
321
332
|
this.sharedModel.setAttachment(attachment)
|
|
@@ -551,7 +562,8 @@ export class LabChatModel
|
|
|
551
562
|
'raw_time',
|
|
552
563
|
'deleted',
|
|
553
564
|
'edited',
|
|
554
|
-
'metadata'
|
|
565
|
+
'metadata',
|
|
566
|
+
'mime_model'
|
|
555
567
|
].includes(key)
|
|
556
568
|
) {
|
|
557
569
|
const update: Partial<IMessageContent> = {};
|