jupyterlab-chat 0.22.0 → 0.23.0-alpha.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__/jupyterlab_collaborative_chat.spec.d.ts +1 -0
- package/lib/__tests__/jupyterlab_collaborative_chat.spec.js +35 -7
- package/lib/autosave.d.ts +8 -0
- package/lib/autosave.js +14 -0
- package/lib/model.d.ts +1 -0
- package/lib/model.js +15 -1
- package/lib/ychat.d.ts +1 -0
- package/lib/ychat.js +7 -0
- package/package.json +22 -22
- package/src/__tests__/jupyterlab_collaborative_chat.spec.ts +40 -6
- package/src/autosave.ts +21 -0
- package/src/model.ts +21 -1
- package/src/ychat.ts +8 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,13 +1,41 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/*
|
|
3
2
|
* Copyright (c) Jupyter Development Team.
|
|
4
3
|
* Distributed under the terms of the Modified BSD License.
|
|
5
4
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
import { enforceAutosaveEnabled } from '../autosave';
|
|
6
|
+
describe('enforceAutosaveEnabled', () => {
|
|
7
|
+
it('should set autosave to true when it is missing', () => {
|
|
8
|
+
const state = {};
|
|
9
|
+
const awareness = {
|
|
10
|
+
getLocalState: () => state,
|
|
11
|
+
setLocalStateField: (field, value) => {
|
|
12
|
+
state[field] = value;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
enforceAutosaveEnabled(awareness);
|
|
16
|
+
expect(state.autosave).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
it('should set autosave to true when it is false', () => {
|
|
19
|
+
const state = { autosave: false };
|
|
20
|
+
const awareness = {
|
|
21
|
+
getLocalState: () => state,
|
|
22
|
+
setLocalStateField: (field, value) => {
|
|
23
|
+
state[field] = value;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
enforceAutosaveEnabled(awareness);
|
|
27
|
+
expect(state.autosave).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
it('should keep autosave true when already enabled', () => {
|
|
30
|
+
const state = { autosave: true };
|
|
31
|
+
let called = false;
|
|
32
|
+
const awareness = {
|
|
33
|
+
getLocalState: () => state,
|
|
34
|
+
setLocalStateField: () => {
|
|
35
|
+
called = true;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
enforceAutosaveEnabled(awareness);
|
|
39
|
+
expect(called).toBe(false);
|
|
12
40
|
});
|
|
13
41
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IAwareness } from '@jupyter/ydoc';
|
|
2
|
+
type AutosaveAwareness = Pick<IAwareness, 'getLocalState' | 'setLocalStateField'>;
|
|
3
|
+
/**
|
|
4
|
+
* Chat files should always advertise autosave=true so server-side RTC saving
|
|
5
|
+
* remains enabled even when global autosave is disabled.
|
|
6
|
+
*/
|
|
7
|
+
export declare function enforceAutosaveEnabled(awareness: AutosaveAwareness): void;
|
|
8
|
+
export {};
|
package/lib/autosave.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Chat files should always advertise autosave=true so server-side RTC saving
|
|
7
|
+
* remains enabled even when global autosave is disabled.
|
|
8
|
+
*/
|
|
9
|
+
export function enforceAutosaveEnabled(awareness) {
|
|
10
|
+
var _a;
|
|
11
|
+
if (((_a = awareness.getLocalState()) === null || _a === void 0 ? void 0 : _a.autosave) !== true) {
|
|
12
|
+
awareness.setLocalStateField('autosave', true);
|
|
13
|
+
}
|
|
14
|
+
}
|
package/lib/model.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ export declare class LabChatModel extends AbstractChatModel implements DocumentR
|
|
|
91
91
|
onAwarenessChange: () => void;
|
|
92
92
|
private _buildMentionList;
|
|
93
93
|
private _resetWritingStatus;
|
|
94
|
+
private _enforceAutosaveEnabled;
|
|
94
95
|
private _onchange;
|
|
95
96
|
readonly defaultKernelName: string;
|
|
96
97
|
readonly defaultKernelLanguage: string;
|
package/lib/model.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { AbstractChatModel, AbstractChatContext } from '@jupyter/chat';
|
|
6
6
|
import { UUID } from '@lumino/coreutils';
|
|
7
7
|
import { Signal } from '@lumino/signaling';
|
|
8
|
+
import { enforceAutosaveEnabled } from './autosave';
|
|
8
9
|
import { YChat } from './ychat';
|
|
9
10
|
const WRITING_DELAY = 1000;
|
|
10
11
|
/**
|
|
@@ -103,6 +104,9 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
103
104
|
}
|
|
104
105
|
this.updateWriters(writers);
|
|
105
106
|
};
|
|
107
|
+
this._enforceAutosaveEnabled = () => {
|
|
108
|
+
enforceAutosaveEnabled(this.sharedModel.awareness);
|
|
109
|
+
};
|
|
106
110
|
this._onchange = async (_, changes) => {
|
|
107
111
|
if (changes.messageListChanges) {
|
|
108
112
|
const msgDelta = changes.messageListChanges;
|
|
@@ -221,6 +225,12 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
221
225
|
}
|
|
222
226
|
});
|
|
223
227
|
}
|
|
228
|
+
// Create a chat ID if not created when the document is not dirty.
|
|
229
|
+
if (changes.stateChange && !this._sharedModel.id) {
|
|
230
|
+
if (changes.stateChange.some(change => change.name === 'dirty' && !change.newValue)) {
|
|
231
|
+
this._sharedModel.id = UUID.uuid4();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
224
234
|
};
|
|
225
235
|
this.defaultKernelName = '';
|
|
226
236
|
this.defaultKernelLanguage = '';
|
|
@@ -238,12 +248,14 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
238
248
|
else {
|
|
239
249
|
this._sharedModel = YChat.create();
|
|
240
250
|
}
|
|
241
|
-
this.
|
|
251
|
+
this._sharedModel.changed.connect(this._onchange, this);
|
|
242
252
|
this.config = widgetConfig.config;
|
|
243
253
|
widgetConfig.configChanged.connect((_, config) => {
|
|
244
254
|
this.config = config;
|
|
245
255
|
});
|
|
246
256
|
this.sharedModel.awareness.on('change', this.onAwarenessChange);
|
|
257
|
+
this.sharedModel.awareness.on('change', this._enforceAutosaveEnabled);
|
|
258
|
+
this._enforceAutosaveEnabled();
|
|
247
259
|
this.input.valueChanged.connect((_, value) => this.onInputChanged(value));
|
|
248
260
|
this.messageEditionAdded.connect(this.onMessageEditionAdded);
|
|
249
261
|
}
|
|
@@ -282,6 +294,8 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
282
294
|
return;
|
|
283
295
|
}
|
|
284
296
|
super.dispose();
|
|
297
|
+
this.sharedModel.awareness.off('change', this.onAwarenessChange);
|
|
298
|
+
this.sharedModel.awareness.off('change', this._enforceAutosaveEnabled);
|
|
285
299
|
this._sharedModel.dispose();
|
|
286
300
|
Signal.clearData(this);
|
|
287
301
|
}
|
package/lib/ychat.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export declare class YChat extends YDocument<IChatChanges> {
|
|
|
76
76
|
*/
|
|
77
77
|
static create(options?: YDocument.IOptions): YChat;
|
|
78
78
|
get id(): string;
|
|
79
|
+
set id(value: string);
|
|
79
80
|
get users(): Record<string, IUser>;
|
|
80
81
|
get messages(): string[];
|
|
81
82
|
get attachments(): JSONObject;
|
package/lib/ychat.js
CHANGED
|
@@ -185,6 +185,13 @@ export class YChat extends YDocument {
|
|
|
185
185
|
get id() {
|
|
186
186
|
return this._metadata.get('id') || '';
|
|
187
187
|
}
|
|
188
|
+
set id(value) {
|
|
189
|
+
this.transact(() => {
|
|
190
|
+
if (!this._metadata.get('id')) {
|
|
191
|
+
this._metadata.set('id', value);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
188
195
|
get users() {
|
|
189
196
|
const userDict = JSONExt.deepCopy(this._users.toJSON());
|
|
190
197
|
return userDict;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0-alpha.0",
|
|
4
4
|
"description": "The library to build a chat based on shared document",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -42,31 +42,31 @@
|
|
|
42
42
|
"watch:src": "tsc -w --sourceMap"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@jupyter/chat": "^0.
|
|
46
|
-
"@jupyter/collaborative-drive": "^4.0
|
|
47
|
-
"@jupyter/ydoc": "^
|
|
48
|
-
"@jupyterlab/application": "^4.
|
|
49
|
-
"@jupyterlab/apputils": "^4.
|
|
50
|
-
"@jupyterlab/coreutils": "^6.
|
|
51
|
-
"@jupyterlab/docmanager": "^4.
|
|
52
|
-
"@jupyterlab/docregistry": "^4.
|
|
53
|
-
"@jupyterlab/launcher": "^4.
|
|
54
|
-
"@jupyterlab/notebook": "^4.
|
|
55
|
-
"@jupyterlab/rendermime": "^4.
|
|
56
|
-
"@jupyterlab/services": "^7.
|
|
57
|
-
"@jupyterlab/settingregistry": "^4.
|
|
58
|
-
"@jupyterlab/translation": "^4.
|
|
59
|
-
"@jupyterlab/ui-components": "^4.
|
|
60
|
-
"@lumino/commands": "^2.
|
|
61
|
-
"@lumino/coreutils": "^2.
|
|
62
|
-
"@lumino/signaling": "^2.
|
|
63
|
-
"@lumino/widgets": "^2.
|
|
45
|
+
"@jupyter/chat": "^0.23.0-alpha.0",
|
|
46
|
+
"@jupyter/collaborative-drive": "^4.4.0",
|
|
47
|
+
"@jupyter/ydoc": "^3.0.0 || ^4.0.0",
|
|
48
|
+
"@jupyterlab/application": "^4.6.0",
|
|
49
|
+
"@jupyterlab/apputils": "^4.7.0",
|
|
50
|
+
"@jupyterlab/coreutils": "^6.6.0",
|
|
51
|
+
"@jupyterlab/docmanager": "^4.6.0",
|
|
52
|
+
"@jupyterlab/docregistry": "^4.6.0",
|
|
53
|
+
"@jupyterlab/launcher": "^4.6.0",
|
|
54
|
+
"@jupyterlab/notebook": "^4.6.0",
|
|
55
|
+
"@jupyterlab/rendermime": "^4.6.0",
|
|
56
|
+
"@jupyterlab/services": "^7.6.0",
|
|
57
|
+
"@jupyterlab/settingregistry": "^4.6.0",
|
|
58
|
+
"@jupyterlab/translation": "^4.6.0",
|
|
59
|
+
"@jupyterlab/ui-components": "^4.6.0",
|
|
60
|
+
"@lumino/commands": "^2.3.3",
|
|
61
|
+
"@lumino/coreutils": "^2.2.2",
|
|
62
|
+
"@lumino/signaling": "^2.1.5",
|
|
63
|
+
"@lumino/widgets": "^2.8.0",
|
|
64
64
|
"react": "^18.2.0",
|
|
65
65
|
"y-protocols": "^1.0.5",
|
|
66
66
|
"yjs": "^13.5.40"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@jupyterlab/testing": "^4.
|
|
69
|
+
"@jupyterlab/testing": "^4.6.0",
|
|
70
70
|
"@types/jest": "^29.2.0",
|
|
71
71
|
"@types/json-schema": "^7.0.11",
|
|
72
72
|
"@types/react": "^18.2.0",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"rimraf": "^5.0.1",
|
|
79
79
|
"source-map-loader": "^1.0.2",
|
|
80
80
|
"style-loader": "^3.3.1",
|
|
81
|
-
"typescript": "~5.
|
|
81
|
+
"typescript": "~5.9.3",
|
|
82
82
|
"yjs": "^13.5.0"
|
|
83
83
|
},
|
|
84
84
|
"sideEffects": [
|
|
@@ -3,12 +3,46 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { enforceAutosaveEnabled } from '../autosave';
|
|
7
|
+
|
|
8
|
+
describe('enforceAutosaveEnabled', () => {
|
|
9
|
+
it('should set autosave to true when it is missing', () => {
|
|
10
|
+
const state: Record<string, unknown> = {};
|
|
11
|
+
const awareness = {
|
|
12
|
+
getLocalState: () => state,
|
|
13
|
+
setLocalStateField: (field: string, value: unknown) => {
|
|
14
|
+
state[field] = value;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
enforceAutosaveEnabled(awareness);
|
|
19
|
+
expect(state.autosave).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should set autosave to true when it is false', () => {
|
|
23
|
+
const state: Record<string, unknown> = { autosave: false };
|
|
24
|
+
const awareness = {
|
|
25
|
+
getLocalState: () => state,
|
|
26
|
+
setLocalStateField: (field: string, value: unknown) => {
|
|
27
|
+
state[field] = value;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
enforceAutosaveEnabled(awareness);
|
|
32
|
+
expect(state.autosave).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should keep autosave true when already enabled', () => {
|
|
36
|
+
const state: Record<string, unknown> = { autosave: true };
|
|
37
|
+
let called = false;
|
|
38
|
+
const awareness = {
|
|
39
|
+
getLocalState: () => state,
|
|
40
|
+
setLocalStateField: () => {
|
|
41
|
+
called = true;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
9
44
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
expect(1 + 1).toEqual(2);
|
|
45
|
+
enforceAutosaveEnabled(awareness);
|
|
46
|
+
expect(called).toBe(false);
|
|
13
47
|
});
|
|
14
48
|
});
|
package/src/autosave.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IAwareness } from '@jupyter/ydoc';
|
|
7
|
+
|
|
8
|
+
type AutosaveAwareness = Pick<
|
|
9
|
+
IAwareness,
|
|
10
|
+
'getLocalState' | 'setLocalStateField'
|
|
11
|
+
>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Chat files should always advertise autosave=true so server-side RTC saving
|
|
15
|
+
* remains enabled even when global autosave is disabled.
|
|
16
|
+
*/
|
|
17
|
+
export function enforceAutosaveEnabled(awareness: AutosaveAwareness): void {
|
|
18
|
+
if (awareness.getLocalState()?.autosave !== true) {
|
|
19
|
+
awareness.setLocalStateField('autosave', true);
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/model.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { User } from '@jupyterlab/services';
|
|
|
20
20
|
import { PartialJSONObject, UUID } from '@lumino/coreutils';
|
|
21
21
|
import { ISignal, Signal } from '@lumino/signaling';
|
|
22
22
|
|
|
23
|
+
import { enforceAutosaveEnabled } from './autosave';
|
|
23
24
|
import { IWidgetConfig } from './token';
|
|
24
25
|
import { IChatChanges, IYmessage, YChat } from './ychat';
|
|
25
26
|
|
|
@@ -105,7 +106,7 @@ export class LabChatModel
|
|
|
105
106
|
this._sharedModel = YChat.create();
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
this.
|
|
109
|
+
this._sharedModel.changed.connect(this._onchange, this);
|
|
109
110
|
|
|
110
111
|
this.config = widgetConfig.config;
|
|
111
112
|
|
|
@@ -114,6 +115,8 @@ export class LabChatModel
|
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
this.sharedModel.awareness.on('change', this.onAwarenessChange);
|
|
118
|
+
this.sharedModel.awareness.on('change', this._enforceAutosaveEnabled);
|
|
119
|
+
this._enforceAutosaveEnabled();
|
|
117
120
|
|
|
118
121
|
this.input.valueChanged.connect((_, value) => this.onInputChanged(value));
|
|
119
122
|
this.messageEditionAdded.connect(this.onMessageEditionAdded);
|
|
@@ -163,6 +166,8 @@ export class LabChatModel
|
|
|
163
166
|
return;
|
|
164
167
|
}
|
|
165
168
|
super.dispose();
|
|
169
|
+
this.sharedModel.awareness.off('change', this.onAwarenessChange);
|
|
170
|
+
this.sharedModel.awareness.off('change', this._enforceAutosaveEnabled);
|
|
166
171
|
this._sharedModel.dispose();
|
|
167
172
|
Signal.clearData(this);
|
|
168
173
|
}
|
|
@@ -397,6 +402,10 @@ export class LabChatModel
|
|
|
397
402
|
this._timeoutWriting = null;
|
|
398
403
|
}
|
|
399
404
|
|
|
405
|
+
private _enforceAutosaveEnabled = () => {
|
|
406
|
+
enforceAutosaveEnabled(this.sharedModel.awareness);
|
|
407
|
+
};
|
|
408
|
+
|
|
400
409
|
private _onchange = async (_: YChat, changes: IChatChanges) => {
|
|
401
410
|
if (changes.messageListChanges) {
|
|
402
411
|
const msgDelta = changes.messageListChanges;
|
|
@@ -530,6 +539,17 @@ export class LabChatModel
|
|
|
530
539
|
}
|
|
531
540
|
});
|
|
532
541
|
}
|
|
542
|
+
|
|
543
|
+
// Create a chat ID if not created when the document is not dirty.
|
|
544
|
+
if (changes.stateChange && !this._sharedModel.id) {
|
|
545
|
+
if (
|
|
546
|
+
changes.stateChange.some(
|
|
547
|
+
change => change.name === 'dirty' && !change.newValue
|
|
548
|
+
)
|
|
549
|
+
) {
|
|
550
|
+
this._sharedModel.id = UUID.uuid4();
|
|
551
|
+
}
|
|
552
|
+
}
|
|
533
553
|
};
|
|
534
554
|
|
|
535
555
|
readonly defaultKernelName: string = '';
|
package/src/ychat.ts
CHANGED
|
@@ -109,6 +109,14 @@ export class YChat extends YDocument<IChatChanges> {
|
|
|
109
109
|
return (this._metadata.get('id') as string) || '';
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
set id(value: string) {
|
|
113
|
+
this.transact(() => {
|
|
114
|
+
if (!this._metadata.get('id')) {
|
|
115
|
+
this._metadata.set('id', value);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
112
120
|
get users(): Record<string, IUser> {
|
|
113
121
|
const userDict = JSONExt.deepCopy(this._users.toJSON()) as Record<
|
|
114
122
|
string,
|