jupyterlab-chat 0.7.0 → 0.8.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/factory.d.ts +11 -4
- package/lib/factory.js +6 -2
- package/lib/model.d.ts +2 -2
- package/lib/model.js +45 -18
- package/lib/token.d.ts +4 -4
- package/lib/widget.d.ts +9 -4
- package/lib/widget.js +9 -6
- package/lib/ychat.d.ts +15 -2
- package/lib/ychat.js +61 -13
- package/package.json +3 -115
- package/src/factory.ts +18 -6
- package/src/model.ts +53 -6
- package/src/token.ts +5 -4
- package/src/widget.tsx +19 -10
- package/src/ychat.ts +83 -15
- package/style/base.css +10 -0
package/lib/factory.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IAttachmentOpenerRegistry, IChatCommandRegistry } from '@jupyter/chat';
|
|
2
2
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
3
|
+
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
3
4
|
import { ABCWidgetFactory, DocumentRegistry } from '@jupyterlab/docregistry';
|
|
4
5
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
5
6
|
import { Contents } from '@jupyterlab/services';
|
|
@@ -49,18 +50,24 @@ export declare class ChatWidgetFactory extends ABCWidgetFactory<LabChatPanel, La
|
|
|
49
50
|
protected createNewWidget(context: ChatWidgetFactory.IContext): LabChatPanel;
|
|
50
51
|
private _themeManager;
|
|
51
52
|
private _rmRegistry;
|
|
52
|
-
private
|
|
53
|
+
private _documentManager?;
|
|
54
|
+
private _chatCommandRegistry?;
|
|
55
|
+
private _attachmentOpenerRegistry?;
|
|
53
56
|
}
|
|
54
57
|
export declare namespace ChatWidgetFactory {
|
|
55
58
|
interface IContext extends DocumentRegistry.IContext<LabChatModel> {
|
|
56
59
|
themeManager: IThemeManager | null;
|
|
57
60
|
rmRegistry: IRenderMimeRegistry;
|
|
58
|
-
|
|
61
|
+
documentManager?: IDocumentManager;
|
|
62
|
+
chatCommandRegistry?: IChatCommandRegistry;
|
|
63
|
+
attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
59
64
|
}
|
|
60
65
|
interface IOptions<T extends LabChatPanel> extends DocumentRegistry.IWidgetFactoryOptions<T> {
|
|
61
66
|
themeManager: IThemeManager | null;
|
|
62
67
|
rmRegistry: IRenderMimeRegistry;
|
|
63
|
-
|
|
68
|
+
documentManager?: IDocumentManager;
|
|
69
|
+
chatCommandRegistry?: IChatCommandRegistry;
|
|
70
|
+
attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
export declare class LabChatModelFactory implements DocumentRegistry.IModelFactory<LabChatModel> {
|
package/lib/factory.js
CHANGED
|
@@ -50,7 +50,9 @@ export class ChatWidgetFactory extends ABCWidgetFactory {
|
|
|
50
50
|
super(options);
|
|
51
51
|
this._themeManager = options.themeManager;
|
|
52
52
|
this._rmRegistry = options.rmRegistry;
|
|
53
|
-
this.
|
|
53
|
+
this._documentManager = options.documentManager;
|
|
54
|
+
this._chatCommandRegistry = options.chatCommandRegistry;
|
|
55
|
+
this._attachmentOpenerRegistry = options.attachmentOpenerRegistry;
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
56
58
|
* Create a new widget given a context.
|
|
@@ -61,7 +63,9 @@ export class ChatWidgetFactory extends ABCWidgetFactory {
|
|
|
61
63
|
createNewWidget(context) {
|
|
62
64
|
context.rmRegistry = this._rmRegistry;
|
|
63
65
|
context.themeManager = this._themeManager;
|
|
64
|
-
context.
|
|
66
|
+
context.documentManager = this._documentManager;
|
|
67
|
+
context.chatCommandRegistry = this._chatCommandRegistry;
|
|
68
|
+
context.attachmentOpenerRegistry = this._attachmentOpenerRegistry;
|
|
65
69
|
return new LabChatPanel({
|
|
66
70
|
context,
|
|
67
71
|
content: new ChatWidget(context)
|
package/lib/model.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChatModel, IChatMessage, INewMessage, IUser } from '@jupyter/chat';
|
|
1
|
+
import { ChatModel, IChatMessage, IInputModel, INewMessage, IUser } from '@jupyter/chat';
|
|
2
2
|
import { IChangedArgs } from '@jupyterlab/coreutils';
|
|
3
3
|
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
4
4
|
import { User } from '@jupyterlab/services';
|
|
@@ -45,7 +45,7 @@ export declare class LabChatModel extends ChatModel implements DocumentRegistry.
|
|
|
45
45
|
/**
|
|
46
46
|
* Function called by the input on key pressed.
|
|
47
47
|
*/
|
|
48
|
-
|
|
48
|
+
onInputChanged: (_: IInputModel, value: string) => void;
|
|
49
49
|
/**
|
|
50
50
|
* Triggered when an awareness state changes.
|
|
51
51
|
* Used to populate the writers list.
|
package/lib/model.js
CHANGED
|
@@ -14,6 +14,22 @@ export class LabChatModel extends ChatModel {
|
|
|
14
14
|
constructor(options) {
|
|
15
15
|
super(options);
|
|
16
16
|
this.collaborative = true;
|
|
17
|
+
/**
|
|
18
|
+
* Function called by the input on key pressed.
|
|
19
|
+
*/
|
|
20
|
+
this.onInputChanged = (_, value) => {
|
|
21
|
+
if (!value || !this.config.sendTypingNotification) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const awareness = this.sharedModel.awareness;
|
|
25
|
+
if (this._timeoutWriting !== null) {
|
|
26
|
+
window.clearTimeout(this._timeoutWriting);
|
|
27
|
+
}
|
|
28
|
+
awareness.setLocalStateField('isWriting', true);
|
|
29
|
+
this._timeoutWriting = window.setTimeout(() => {
|
|
30
|
+
this._resetWritingStatus();
|
|
31
|
+
}, WRITING_DELAY);
|
|
32
|
+
};
|
|
17
33
|
/**
|
|
18
34
|
* Triggered when an awareness state changes.
|
|
19
35
|
* Used to populate the writers list.
|
|
@@ -42,12 +58,27 @@ export class LabChatModel extends ChatModel {
|
|
|
42
58
|
}
|
|
43
59
|
else if (delta.insert) {
|
|
44
60
|
const messages = delta.insert.map(ymessage => {
|
|
61
|
+
const { sender, attachments: attachmentIds, ...baseMessage } = ymessage;
|
|
62
|
+
// Build the base message with sender.
|
|
45
63
|
const msg = {
|
|
46
|
-
...
|
|
47
|
-
sender: this.sharedModel.getUser(
|
|
64
|
+
...baseMessage,
|
|
65
|
+
sender: this.sharedModel.getUser(sender) || {
|
|
48
66
|
username: 'User undefined'
|
|
49
67
|
}
|
|
50
68
|
};
|
|
69
|
+
// Add attachments.
|
|
70
|
+
if (attachmentIds) {
|
|
71
|
+
const attachments = [];
|
|
72
|
+
attachmentIds.forEach(attachmentId => {
|
|
73
|
+
const attachment = this.sharedModel.getAttachment(attachmentId);
|
|
74
|
+
if (attachment) {
|
|
75
|
+
attachments.push(attachment);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
if (attachments.length) {
|
|
79
|
+
msg.attachments = attachments;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
51
82
|
return msg;
|
|
52
83
|
});
|
|
53
84
|
this.messagesInserted(index, messages);
|
|
@@ -91,6 +122,7 @@ export class LabChatModel extends ChatModel {
|
|
|
91
122
|
this.config = config;
|
|
92
123
|
});
|
|
93
124
|
this.sharedModel.awareness.on('change', this.onAwarenessChange);
|
|
125
|
+
this.input.valueChanged.connect(this.onInputChanged);
|
|
94
126
|
}
|
|
95
127
|
get user() {
|
|
96
128
|
return this._user;
|
|
@@ -170,9 +202,16 @@ export class LabChatModel extends ChatModel {
|
|
|
170
202
|
if (!(this.sharedModel.getUser(this._user.username) === this._user)) {
|
|
171
203
|
this.sharedModel.setUser(this._user);
|
|
172
204
|
}
|
|
205
|
+
// Add the attachments to the message.
|
|
206
|
+
if (this.input.attachments.length) {
|
|
207
|
+
const attachmentIds = this.input.attachments.map(attachment => this.sharedModel.setAttachment(attachment));
|
|
208
|
+
msg.attachments = attachmentIds;
|
|
209
|
+
this.input.clearAttachments();
|
|
210
|
+
}
|
|
173
211
|
this.sharedModel.addMessage(msg);
|
|
174
212
|
}
|
|
175
213
|
updateMessage(id, updatedMessage) {
|
|
214
|
+
var _a;
|
|
176
215
|
const index = this.sharedModel.getMessageIndex(id);
|
|
177
216
|
let message = this.sharedModel.getMessage(index);
|
|
178
217
|
if (message) {
|
|
@@ -190,6 +229,10 @@ export class LabChatModel extends ChatModel {
|
|
|
190
229
|
edited: true
|
|
191
230
|
};
|
|
192
231
|
}
|
|
232
|
+
const attachmentIds = (_a = updatedMessage.attachments) === null || _a === void 0 ? void 0 : _a.map(attachment => this.sharedModel.setAttachment(attachment));
|
|
233
|
+
if (attachmentIds) {
|
|
234
|
+
message.attachments = attachmentIds;
|
|
235
|
+
}
|
|
193
236
|
this.sharedModel.updateMessage(index, message);
|
|
194
237
|
}
|
|
195
238
|
deleteMessage(id) {
|
|
@@ -203,22 +246,6 @@ export class LabChatModel extends ChatModel {
|
|
|
203
246
|
message.deleted = true;
|
|
204
247
|
this.sharedModel.updateMessage(index, message);
|
|
205
248
|
}
|
|
206
|
-
/**
|
|
207
|
-
* Function called by the input on key pressed.
|
|
208
|
-
*/
|
|
209
|
-
inputChanged(input) {
|
|
210
|
-
if (!input || !this.config.sendTypingNotification) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const awareness = this.sharedModel.awareness;
|
|
214
|
-
if (this._timeoutWriting !== null) {
|
|
215
|
-
window.clearTimeout(this._timeoutWriting);
|
|
216
|
-
}
|
|
217
|
-
awareness.setLocalStateField('isWriting', true);
|
|
218
|
-
this._timeoutWriting = window.setTimeout(() => {
|
|
219
|
-
this._resetWritingStatus();
|
|
220
|
-
}, WRITING_DELAY);
|
|
221
|
-
}
|
|
222
249
|
_resetWritingStatus() {
|
|
223
250
|
const awareness = this.sharedModel.awareness;
|
|
224
251
|
const states = awareness.getLocalState();
|
package/lib/token.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { IConfig, IActiveCellManager, ISelectionWatcher } from '@jupyter/chat';
|
|
2
|
-
import {
|
|
1
|
+
import { IConfig, IActiveCellManager, ISelectionWatcher, ChatWidget } from '@jupyter/chat';
|
|
2
|
+
import { WidgetTracker } from '@jupyterlab/apputils';
|
|
3
3
|
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
4
4
|
import { Token } from '@lumino/coreutils';
|
|
5
5
|
import { ISignal } from '@lumino/signaling';
|
|
@@ -32,7 +32,7 @@ export interface IChatFactory {
|
|
|
32
32
|
/**
|
|
33
33
|
* The chat panel tracker.
|
|
34
34
|
*/
|
|
35
|
-
tracker:
|
|
35
|
+
tracker: WidgetTracker<LabChatPanel | ChatWidget>;
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* The interface for the chats config.
|
|
@@ -50,7 +50,7 @@ export interface IWidgetConfig {
|
|
|
50
50
|
/**
|
|
51
51
|
* A signal emitting when the configuration for the chats has changed.
|
|
52
52
|
*/
|
|
53
|
-
export interface IConfigChanged extends ISignal<IWidgetConfig, Partial<ILabChatConfig>> {
|
|
53
|
+
export interface IConfigChanged/* eslint-disable-line @typescript-eslint/no-empty-object-type */ extends ISignal<IWidgetConfig, Partial<ILabChatConfig>> {
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* Command ids.
|
package/lib/widget.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChatWidget,
|
|
1
|
+
import { ChatWidget, IAttachmentOpenerRegistry, IChatCommandRegistry, IChatModel } from '@jupyter/chat';
|
|
2
2
|
import { ICollaborativeDrive } from '@jupyter/collaborative-drive';
|
|
3
3
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
4
4
|
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
@@ -8,6 +8,7 @@ import { CommandRegistry } from '@lumino/commands';
|
|
|
8
8
|
import { Message } from '@lumino/messaging';
|
|
9
9
|
import { Panel } from '@lumino/widgets';
|
|
10
10
|
import { LabChatModel } from './model';
|
|
11
|
+
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
11
12
|
/**
|
|
12
13
|
* DocumentWidget: widget that represents the view or editor for a file type.
|
|
13
14
|
*/
|
|
@@ -45,7 +46,7 @@ export declare class ChatPanel extends SidePanel {
|
|
|
45
46
|
* @param model - the model of the chat widget
|
|
46
47
|
* @param name - the name of the chat.
|
|
47
48
|
*/
|
|
48
|
-
addChat(model: IChatModel
|
|
49
|
+
addChat(model: IChatModel): ChatWidget;
|
|
49
50
|
/**
|
|
50
51
|
* Update the list of available chats in the default directory.
|
|
51
52
|
*/
|
|
@@ -87,7 +88,9 @@ export declare class ChatPanel extends SidePanel {
|
|
|
87
88
|
private _openChat;
|
|
88
89
|
private _rmRegistry;
|
|
89
90
|
private _themeManager;
|
|
90
|
-
private
|
|
91
|
+
private _documentManager?;
|
|
92
|
+
private _chatCommandRegistry?;
|
|
93
|
+
private _attachmentOpenerRegistry?;
|
|
91
94
|
}
|
|
92
95
|
/**
|
|
93
96
|
* The chat panel namespace.
|
|
@@ -102,7 +105,9 @@ export declare namespace ChatPanel {
|
|
|
102
105
|
rmRegistry: IRenderMimeRegistry;
|
|
103
106
|
themeManager: IThemeManager | null;
|
|
104
107
|
defaultDirectory: string;
|
|
105
|
-
|
|
108
|
+
documentManager?: IDocumentManager;
|
|
109
|
+
chatCommandRegistry?: IChatCommandRegistry;
|
|
110
|
+
attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
/**
|
package/lib/widget.js
CHANGED
|
@@ -105,7 +105,9 @@ export class ChatPanel extends SidePanel {
|
|
|
105
105
|
this._rmRegistry = options.rmRegistry;
|
|
106
106
|
this._themeManager = options.themeManager;
|
|
107
107
|
this._defaultDirectory = options.defaultDirectory;
|
|
108
|
-
this.
|
|
108
|
+
this._documentManager = options.documentManager;
|
|
109
|
+
this._chatCommandRegistry = options.chatCommandRegistry;
|
|
110
|
+
this._attachmentOpenerRegistry = options.attachmentOpenerRegistry;
|
|
109
111
|
const addChat = new CommandToolbarButton({
|
|
110
112
|
commands: this._commands,
|
|
111
113
|
id: CommandIDs.createChat,
|
|
@@ -144,27 +146,28 @@ export class ChatPanel extends SidePanel {
|
|
|
144
146
|
* @param model - the model of the chat widget
|
|
145
147
|
* @param name - the name of the chat.
|
|
146
148
|
*/
|
|
147
|
-
addChat(model
|
|
149
|
+
addChat(model) {
|
|
148
150
|
// Collapse all chats
|
|
149
151
|
const content = this.content;
|
|
150
152
|
for (let i = 0; i < this.widgets.length; i++) {
|
|
151
153
|
content.collapse(i);
|
|
152
154
|
}
|
|
153
|
-
// Set the name of the model.
|
|
154
|
-
model.name = path;
|
|
155
155
|
// Create a new widget.
|
|
156
156
|
const widget = new ChatWidget({
|
|
157
157
|
model: model,
|
|
158
158
|
rmRegistry: this._rmRegistry,
|
|
159
159
|
themeManager: this._themeManager,
|
|
160
|
-
|
|
160
|
+
documentManager: this._documentManager,
|
|
161
|
+
chatCommandRegistry: this._chatCommandRegistry,
|
|
162
|
+
attachmentOpenerRegistry: this._attachmentOpenerRegistry
|
|
161
163
|
});
|
|
162
164
|
this.addWidget(new ChatSection({
|
|
163
165
|
widget,
|
|
164
166
|
commands: this._commands,
|
|
165
|
-
path,
|
|
167
|
+
path: model.name,
|
|
166
168
|
defaultDirectory: this._defaultDirectory
|
|
167
169
|
}));
|
|
170
|
+
return widget;
|
|
168
171
|
}
|
|
169
172
|
/**
|
|
170
173
|
* Open a chat if it exists in the side panel.
|
package/lib/ychat.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { IChatMessage, IUser } from '@jupyter/chat';
|
|
1
|
+
import { IAttachment, IChatMessage, IUser } from '@jupyter/chat';
|
|
2
2
|
import { Delta, DocumentChange, IMapChange, YDocument } from '@jupyter/ydoc';
|
|
3
3
|
import { JSONObject, PartialJSONValue } from '@lumino/coreutils';
|
|
4
4
|
/**
|
|
5
5
|
* The type for a YMessage.
|
|
6
6
|
*/
|
|
7
|
-
export type IYmessage = IChatMessage<string>;
|
|
7
|
+
export type IYmessage = IChatMessage<string, string>;
|
|
8
8
|
/**
|
|
9
9
|
* The type for a YMessage.
|
|
10
10
|
*/
|
|
@@ -21,6 +21,10 @@ export interface IChatChanges extends DocumentChange {
|
|
|
21
21
|
* Changes in users.
|
|
22
22
|
*/
|
|
23
23
|
userChanges?: UserChange[];
|
|
24
|
+
/**
|
|
25
|
+
* Changes in attachments.
|
|
26
|
+
*/
|
|
27
|
+
attachmentChanges?: AttachmentChange[];
|
|
24
28
|
/**
|
|
25
29
|
* Changes in metadata.
|
|
26
30
|
*/
|
|
@@ -34,6 +38,10 @@ export type MessageChange = Delta<IYmessage[]>;
|
|
|
34
38
|
* The user change type.
|
|
35
39
|
*/
|
|
36
40
|
export type UserChange = IMapChange<IUser>;
|
|
41
|
+
/**
|
|
42
|
+
* The attachment change type.
|
|
43
|
+
*/
|
|
44
|
+
export type AttachmentChange = IMapChange<IAttachment>;
|
|
37
45
|
/**
|
|
38
46
|
* The metadata change type.
|
|
39
47
|
*/
|
|
@@ -59,6 +67,7 @@ export declare class YChat extends YDocument<IChatChanges> {
|
|
|
59
67
|
get id(): string;
|
|
60
68
|
get users(): JSONObject;
|
|
61
69
|
get messages(): string[];
|
|
70
|
+
get attachments(): JSONObject;
|
|
62
71
|
getSource(): JSONObject;
|
|
63
72
|
setSource(value: JSONObject): void;
|
|
64
73
|
getUser(username: string | undefined): IUser | undefined;
|
|
@@ -68,10 +77,14 @@ export declare class YChat extends YDocument<IChatChanges> {
|
|
|
68
77
|
updateMessage(index: number, value: IYmessage): void;
|
|
69
78
|
getMessageIndex(id: string): number;
|
|
70
79
|
deleteMessage(index: number): void;
|
|
80
|
+
getAttachment(id: string): IAttachment | undefined;
|
|
81
|
+
setAttachment(attachment: IAttachment): string;
|
|
71
82
|
private _usersObserver;
|
|
72
83
|
private _messagesObserver;
|
|
84
|
+
private _attachmentsObserver;
|
|
73
85
|
private _metadataObserver;
|
|
74
86
|
private _users;
|
|
75
87
|
private _messages;
|
|
88
|
+
private _attachments;
|
|
76
89
|
private _metadata;
|
|
77
90
|
}
|
package/lib/ychat.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { YDocument } from '@jupyter/ydoc';
|
|
6
|
-
import { JSONExt } from '@lumino/coreutils';
|
|
6
|
+
import { JSONExt, UUID } from '@lumino/coreutils';
|
|
7
7
|
/**
|
|
8
8
|
* The jupyterlab chat shared document.
|
|
9
9
|
*/
|
|
@@ -18,27 +18,27 @@ export class YChat extends YDocument {
|
|
|
18
18
|
*/
|
|
19
19
|
this.version = '1.0.0';
|
|
20
20
|
this._usersObserver = (event) => {
|
|
21
|
-
const
|
|
21
|
+
const userChanges = new Array();
|
|
22
22
|
event.keysChanged.forEach(key => {
|
|
23
23
|
const change = event.changes.keys.get(key);
|
|
24
24
|
if (change) {
|
|
25
25
|
switch (change.action) {
|
|
26
26
|
case 'add':
|
|
27
|
-
|
|
27
|
+
userChanges.push({
|
|
28
28
|
key,
|
|
29
29
|
newValue: this._users.get(key),
|
|
30
30
|
type: 'add'
|
|
31
31
|
});
|
|
32
32
|
break;
|
|
33
33
|
case 'delete':
|
|
34
|
-
|
|
34
|
+
userChanges.push({
|
|
35
35
|
key,
|
|
36
36
|
oldValue: change.oldValue,
|
|
37
37
|
type: 'remove'
|
|
38
38
|
});
|
|
39
39
|
break;
|
|
40
40
|
case 'update':
|
|
41
|
-
|
|
41
|
+
userChanges.push({
|
|
42
42
|
key: key,
|
|
43
43
|
oldValue: change.oldValue,
|
|
44
44
|
newValue: this._users.get(key),
|
|
@@ -48,7 +48,7 @@ export class YChat extends YDocument {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
|
-
this._changed.emit({
|
|
51
|
+
this._changed.emit({ userChanges });
|
|
52
52
|
};
|
|
53
53
|
this._messagesObserver = (event) => {
|
|
54
54
|
const messageChanges = event.delta;
|
|
@@ -56,26 +56,59 @@ export class YChat extends YDocument {
|
|
|
56
56
|
messageChanges: messageChanges
|
|
57
57
|
});
|
|
58
58
|
};
|
|
59
|
+
this._attachmentsObserver = (event) => {
|
|
60
|
+
const attachmentChanges = new Array();
|
|
61
|
+
event.keysChanged.forEach(key => {
|
|
62
|
+
const change = event.changes.keys.get(key);
|
|
63
|
+
if (change) {
|
|
64
|
+
switch (change.action) {
|
|
65
|
+
case 'add':
|
|
66
|
+
attachmentChanges.push({
|
|
67
|
+
key,
|
|
68
|
+
newValue: this._attachments.get(key),
|
|
69
|
+
type: 'add'
|
|
70
|
+
});
|
|
71
|
+
break;
|
|
72
|
+
case 'delete':
|
|
73
|
+
attachmentChanges.push({
|
|
74
|
+
key,
|
|
75
|
+
oldValue: change.oldValue,
|
|
76
|
+
type: 'remove'
|
|
77
|
+
});
|
|
78
|
+
break;
|
|
79
|
+
case 'update':
|
|
80
|
+
attachmentChanges.push({
|
|
81
|
+
key: key,
|
|
82
|
+
oldValue: change.oldValue,
|
|
83
|
+
newValue: this._attachments.get(key),
|
|
84
|
+
type: 'change'
|
|
85
|
+
});
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
this._changed.emit({ attachmentChanges });
|
|
91
|
+
};
|
|
59
92
|
this._metadataObserver = (event) => {
|
|
60
|
-
const
|
|
93
|
+
const metadataChanges = new Array();
|
|
61
94
|
event.changes.keys.forEach((change, key) => {
|
|
62
95
|
switch (change.action) {
|
|
63
96
|
case 'add':
|
|
64
|
-
|
|
97
|
+
metadataChanges.push({
|
|
65
98
|
key,
|
|
66
99
|
newValue: this._metadata.get(key),
|
|
67
100
|
type: 'add'
|
|
68
101
|
});
|
|
69
102
|
break;
|
|
70
103
|
case 'delete':
|
|
71
|
-
|
|
104
|
+
metadataChanges.push({
|
|
72
105
|
key,
|
|
73
106
|
oldValue: change.oldValue,
|
|
74
107
|
type: 'remove'
|
|
75
108
|
});
|
|
76
109
|
break;
|
|
77
110
|
case 'update':
|
|
78
|
-
|
|
111
|
+
metadataChanges.push({
|
|
79
112
|
key: key,
|
|
80
113
|
oldValue: change.oldValue,
|
|
81
114
|
newValue: this._metadata.get(key),
|
|
@@ -84,14 +117,14 @@ export class YChat extends YDocument {
|
|
|
84
117
|
break;
|
|
85
118
|
}
|
|
86
119
|
});
|
|
87
|
-
this._changed.emit({
|
|
88
|
-
metadataChanges: metadataChange
|
|
89
|
-
});
|
|
120
|
+
this._changed.emit({ metadataChanges });
|
|
90
121
|
};
|
|
91
122
|
this._users = this.ydoc.getMap('users');
|
|
92
123
|
this._users.observe(this._usersObserver);
|
|
93
124
|
this._messages = this.ydoc.getArray('messages');
|
|
94
125
|
this._messages.observe(this._messagesObserver);
|
|
126
|
+
this._attachments = this.ydoc.getMap('attachments');
|
|
127
|
+
this._attachments.observe(this._attachmentsObserver);
|
|
95
128
|
this._metadata = this.ydoc.getMap('metadata');
|
|
96
129
|
this._metadata.observe(this._metadataObserver);
|
|
97
130
|
}
|
|
@@ -112,6 +145,9 @@ export class YChat extends YDocument {
|
|
|
112
145
|
get messages() {
|
|
113
146
|
return JSONExt.deepCopy(this._messages.toJSON());
|
|
114
147
|
}
|
|
148
|
+
get attachments() {
|
|
149
|
+
return JSONExt.deepCopy(this._attachments.toJSON());
|
|
150
|
+
}
|
|
115
151
|
getSource() {
|
|
116
152
|
const users = this._users.toJSON();
|
|
117
153
|
const messages = this._messages.toJSON();
|
|
@@ -167,4 +203,16 @@ export class YChat extends YDocument {
|
|
|
167
203
|
this._messages.delete(index);
|
|
168
204
|
});
|
|
169
205
|
}
|
|
206
|
+
getAttachment(id) {
|
|
207
|
+
return this._attachments.get(id);
|
|
208
|
+
}
|
|
209
|
+
setAttachment(attachment) {
|
|
210
|
+
var _a;
|
|
211
|
+
// Search if the attachment already exist to update it, otherwise add it.
|
|
212
|
+
const id = ((_a = Array.from(this._attachments.entries()).find(([_, att]) => att.type === attachment.type && att.value === attachment.value)) === null || _a === void 0 ? void 0 : _a[0]) || UUID.uuid4();
|
|
213
|
+
this.transact(() => {
|
|
214
|
+
this._attachments.set(id, attachment);
|
|
215
|
+
});
|
|
216
|
+
return id;
|
|
217
|
+
}
|
|
170
218
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The library to build a chat based on shared document",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -37,26 +37,18 @@
|
|
|
37
37
|
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
|
|
38
38
|
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
|
|
39
39
|
"clean:all": "jlpm clean:lib && jlpm clean:lintcache",
|
|
40
|
-
"eslint": "jlpm eslint:check --fix",
|
|
41
|
-
"eslint:check": "eslint . --cache --ext .ts,.tsx",
|
|
42
40
|
"install:extension": "jlpm build",
|
|
43
|
-
"lint": "jlpm stylelint && jlpm prettier && jlpm eslint",
|
|
44
|
-
"lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check",
|
|
45
|
-
"prettier": "jlpm prettier:base --write --list-different",
|
|
46
|
-
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
|
|
47
|
-
"prettier:check": "jlpm prettier:base --check",
|
|
48
|
-
"stylelint": "jlpm stylelint:check --fix",
|
|
49
|
-
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
|
|
50
41
|
"test": "jest --coverage",
|
|
51
42
|
"watch:src": "tsc -w --sourceMap"
|
|
52
43
|
},
|
|
53
44
|
"dependencies": {
|
|
54
|
-
"@jupyter/chat": "^0.
|
|
45
|
+
"@jupyter/chat": "^0.8.0",
|
|
55
46
|
"@jupyter/collaborative-drive": "^3.0.0",
|
|
56
47
|
"@jupyter/ydoc": "^2.0.0 || ^3.0.0",
|
|
57
48
|
"@jupyterlab/application": "^4.2.0",
|
|
58
49
|
"@jupyterlab/apputils": "^4.3.0",
|
|
59
50
|
"@jupyterlab/coreutils": "^6.2.0",
|
|
51
|
+
"@jupyterlab/docmanager": "^4.2.0",
|
|
60
52
|
"@jupyterlab/docregistry": "^4.2.0",
|
|
61
53
|
"@jupyterlab/launcher": "^4.2.0",
|
|
62
54
|
"@jupyterlab/notebook": "^4.2.0",
|
|
@@ -79,24 +71,13 @@
|
|
|
79
71
|
"@types/json-schema": "^7.0.11",
|
|
80
72
|
"@types/react": "^18.2.0",
|
|
81
73
|
"@types/react-addons-linked-state-mixin": "^0.14.22",
|
|
82
|
-
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
|
83
|
-
"@typescript-eslint/parser": "^6.1.0",
|
|
84
74
|
"css-loader": "^6.7.1",
|
|
85
|
-
"eslint": "^8.36.0",
|
|
86
|
-
"eslint-config-prettier": "^8.8.0",
|
|
87
|
-
"eslint-plugin-prettier": "^5.0.0",
|
|
88
75
|
"jest": "^29.2.0",
|
|
89
76
|
"mkdirp": "^1.0.3",
|
|
90
77
|
"npm-run-all": "^4.1.5",
|
|
91
|
-
"prettier": "^3.0.0",
|
|
92
78
|
"rimraf": "^5.0.1",
|
|
93
79
|
"source-map-loader": "^1.0.2",
|
|
94
80
|
"style-loader": "^3.3.1",
|
|
95
|
-
"stylelint": "^15.10.1",
|
|
96
|
-
"stylelint-config-recommended": "^13.0.0",
|
|
97
|
-
"stylelint-config-standard": "^34.0.0",
|
|
98
|
-
"stylelint-csstree-validator": "^3.0.0",
|
|
99
|
-
"stylelint-prettier": "^4.0.0",
|
|
100
81
|
"typescript": "~5.0.2",
|
|
101
82
|
"yjs": "^13.5.0"
|
|
102
83
|
},
|
|
@@ -107,98 +88,5 @@
|
|
|
107
88
|
"styleModule": "style/index.js",
|
|
108
89
|
"publishConfig": {
|
|
109
90
|
"access": "public"
|
|
110
|
-
},
|
|
111
|
-
"eslintIgnore": [
|
|
112
|
-
"node_modules",
|
|
113
|
-
"dist",
|
|
114
|
-
"coverage",
|
|
115
|
-
"**/*.d.ts",
|
|
116
|
-
"tests",
|
|
117
|
-
"**/__tests__"
|
|
118
|
-
],
|
|
119
|
-
"eslintConfig": {
|
|
120
|
-
"extends": [
|
|
121
|
-
"eslint:recommended",
|
|
122
|
-
"plugin:@typescript-eslint/eslint-recommended",
|
|
123
|
-
"plugin:@typescript-eslint/recommended",
|
|
124
|
-
"plugin:prettier/recommended"
|
|
125
|
-
],
|
|
126
|
-
"parser": "@typescript-eslint/parser",
|
|
127
|
-
"parserOptions": {
|
|
128
|
-
"project": "tsconfig.json",
|
|
129
|
-
"sourceType": "module"
|
|
130
|
-
},
|
|
131
|
-
"plugins": [
|
|
132
|
-
"@typescript-eslint"
|
|
133
|
-
],
|
|
134
|
-
"rules": {
|
|
135
|
-
"@typescript-eslint/naming-convention": [
|
|
136
|
-
"error",
|
|
137
|
-
{
|
|
138
|
-
"selector": "interface",
|
|
139
|
-
"format": [
|
|
140
|
-
"PascalCase"
|
|
141
|
-
],
|
|
142
|
-
"custom": {
|
|
143
|
-
"regex": "^I[A-Z]",
|
|
144
|
-
"match": true
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
],
|
|
148
|
-
"@typescript-eslint/no-unused-vars": [
|
|
149
|
-
"warn",
|
|
150
|
-
{
|
|
151
|
-
"args": "none"
|
|
152
|
-
}
|
|
153
|
-
],
|
|
154
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
155
|
-
"@typescript-eslint/no-namespace": "off",
|
|
156
|
-
"@typescript-eslint/no-use-before-define": "off",
|
|
157
|
-
"@typescript-eslint/quotes": [
|
|
158
|
-
"error",
|
|
159
|
-
"single",
|
|
160
|
-
{
|
|
161
|
-
"avoidEscape": true,
|
|
162
|
-
"allowTemplateLiterals": false
|
|
163
|
-
}
|
|
164
|
-
],
|
|
165
|
-
"curly": [
|
|
166
|
-
"error",
|
|
167
|
-
"all"
|
|
168
|
-
],
|
|
169
|
-
"eqeqeq": "error",
|
|
170
|
-
"prefer-arrow-callback": "error"
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
"prettier": {
|
|
174
|
-
"singleQuote": true,
|
|
175
|
-
"trailingComma": "none",
|
|
176
|
-
"arrowParens": "avoid",
|
|
177
|
-
"endOfLine": "auto",
|
|
178
|
-
"overrides": [
|
|
179
|
-
{
|
|
180
|
-
"files": "package.json",
|
|
181
|
-
"options": {
|
|
182
|
-
"tabWidth": 2
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
]
|
|
186
|
-
},
|
|
187
|
-
"stylelint": {
|
|
188
|
-
"extends": [
|
|
189
|
-
"stylelint-config-recommended",
|
|
190
|
-
"stylelint-config-standard",
|
|
191
|
-
"stylelint-prettier/recommended"
|
|
192
|
-
],
|
|
193
|
-
"plugins": [
|
|
194
|
-
"stylelint-csstree-validator"
|
|
195
|
-
],
|
|
196
|
-
"rules": {
|
|
197
|
-
"csstree/validator": true,
|
|
198
|
-
"property-no-vendor-prefix": null,
|
|
199
|
-
"selector-class-pattern": "^([a-z][A-z\\d]*)(-[A-z\\d]+)*$",
|
|
200
|
-
"selector-no-vendor-prefix": null,
|
|
201
|
-
"value-no-vendor-prefix": null
|
|
202
|
-
}
|
|
203
91
|
}
|
|
204
92
|
}
|
package/src/factory.ts
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
import {
|
|
7
7
|
ChatWidget,
|
|
8
8
|
IActiveCellManager,
|
|
9
|
-
|
|
9
|
+
IAttachmentOpenerRegistry,
|
|
10
|
+
IChatCommandRegistry,
|
|
10
11
|
ISelectionWatcher
|
|
11
12
|
} from '@jupyter/chat';
|
|
12
13
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
14
|
+
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
13
15
|
import { ABCWidgetFactory, DocumentRegistry } from '@jupyterlab/docregistry';
|
|
14
16
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
15
17
|
import { Contents, User } from '@jupyterlab/services';
|
|
@@ -74,7 +76,9 @@ export class ChatWidgetFactory extends ABCWidgetFactory<
|
|
|
74
76
|
super(options);
|
|
75
77
|
this._themeManager = options.themeManager;
|
|
76
78
|
this._rmRegistry = options.rmRegistry;
|
|
77
|
-
this.
|
|
79
|
+
this._documentManager = options.documentManager;
|
|
80
|
+
this._chatCommandRegistry = options.chatCommandRegistry;
|
|
81
|
+
this._attachmentOpenerRegistry = options.attachmentOpenerRegistry;
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
/**
|
|
@@ -86,7 +90,9 @@ export class ChatWidgetFactory extends ABCWidgetFactory<
|
|
|
86
90
|
protected createNewWidget(context: ChatWidgetFactory.IContext): LabChatPanel {
|
|
87
91
|
context.rmRegistry = this._rmRegistry;
|
|
88
92
|
context.themeManager = this._themeManager;
|
|
89
|
-
context.
|
|
93
|
+
context.documentManager = this._documentManager;
|
|
94
|
+
context.chatCommandRegistry = this._chatCommandRegistry;
|
|
95
|
+
context.attachmentOpenerRegistry = this._attachmentOpenerRegistry;
|
|
90
96
|
return new LabChatPanel({
|
|
91
97
|
context,
|
|
92
98
|
content: new ChatWidget(context)
|
|
@@ -95,21 +101,27 @@ export class ChatWidgetFactory extends ABCWidgetFactory<
|
|
|
95
101
|
|
|
96
102
|
private _themeManager: IThemeManager | null;
|
|
97
103
|
private _rmRegistry: IRenderMimeRegistry;
|
|
98
|
-
private
|
|
104
|
+
private _documentManager?: IDocumentManager;
|
|
105
|
+
private _chatCommandRegistry?: IChatCommandRegistry;
|
|
106
|
+
private _attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
export namespace ChatWidgetFactory {
|
|
102
110
|
export interface IContext extends DocumentRegistry.IContext<LabChatModel> {
|
|
103
111
|
themeManager: IThemeManager | null;
|
|
104
112
|
rmRegistry: IRenderMimeRegistry;
|
|
105
|
-
|
|
113
|
+
documentManager?: IDocumentManager;
|
|
114
|
+
chatCommandRegistry?: IChatCommandRegistry;
|
|
115
|
+
attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
export interface IOptions<T extends LabChatPanel>
|
|
109
119
|
extends DocumentRegistry.IWidgetFactoryOptions<T> {
|
|
110
120
|
themeManager: IThemeManager | null;
|
|
111
121
|
rmRegistry: IRenderMimeRegistry;
|
|
112
|
-
|
|
122
|
+
documentManager?: IDocumentManager;
|
|
123
|
+
chatCommandRegistry?: IChatCommandRegistry;
|
|
124
|
+
attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
127
|
|
package/src/model.ts
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ChatModel,
|
|
8
|
+
IAttachment,
|
|
9
|
+
IChatMessage,
|
|
10
|
+
IInputModel,
|
|
11
|
+
INewMessage,
|
|
12
|
+
IUser
|
|
13
|
+
} from '@jupyter/chat';
|
|
7
14
|
import { IChangedArgs } from '@jupyterlab/coreutils';
|
|
8
15
|
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
9
16
|
import { User } from '@jupyterlab/services';
|
|
@@ -53,6 +60,8 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
|
|
|
53
60
|
});
|
|
54
61
|
|
|
55
62
|
this.sharedModel.awareness.on('change', this.onAwarenessChange);
|
|
63
|
+
|
|
64
|
+
this.input.valueChanged.connect(this.onInputChanged);
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
readonly collaborative = true;
|
|
@@ -137,6 +146,7 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
|
|
|
137
146
|
if (this._timeoutWriting !== null) {
|
|
138
147
|
window.clearTimeout(this._timeoutWriting);
|
|
139
148
|
}
|
|
149
|
+
|
|
140
150
|
const msg: IYmessage = {
|
|
141
151
|
type: 'msg',
|
|
142
152
|
id: UUID.uuid4(),
|
|
@@ -150,6 +160,16 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
|
|
|
150
160
|
if (!(this.sharedModel.getUser(this._user.username) === this._user)) {
|
|
151
161
|
this.sharedModel.setUser(this._user);
|
|
152
162
|
}
|
|
163
|
+
|
|
164
|
+
// Add the attachments to the message.
|
|
165
|
+
if (this.input.attachments.length) {
|
|
166
|
+
const attachmentIds = this.input.attachments.map(attachment =>
|
|
167
|
+
this.sharedModel.setAttachment(attachment)
|
|
168
|
+
);
|
|
169
|
+
msg.attachments = attachmentIds;
|
|
170
|
+
this.input.clearAttachments();
|
|
171
|
+
}
|
|
172
|
+
|
|
153
173
|
this.sharedModel.addMessage(msg);
|
|
154
174
|
}
|
|
155
175
|
|
|
@@ -174,6 +194,12 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
|
|
|
174
194
|
edited: true
|
|
175
195
|
};
|
|
176
196
|
}
|
|
197
|
+
const attachmentIds = updatedMessage.attachments?.map(attachment =>
|
|
198
|
+
this.sharedModel.setAttachment(attachment)
|
|
199
|
+
);
|
|
200
|
+
if (attachmentIds) {
|
|
201
|
+
message.attachments = attachmentIds;
|
|
202
|
+
}
|
|
177
203
|
this.sharedModel.updateMessage(index, message as IYmessage);
|
|
178
204
|
}
|
|
179
205
|
|
|
@@ -192,8 +218,8 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
|
|
|
192
218
|
/**
|
|
193
219
|
* Function called by the input on key pressed.
|
|
194
220
|
*/
|
|
195
|
-
|
|
196
|
-
if (!
|
|
221
|
+
onInputChanged = (_: IInputModel, value: string): void => {
|
|
222
|
+
if (!value || !this.config.sendTypingNotification) {
|
|
197
223
|
return;
|
|
198
224
|
}
|
|
199
225
|
const awareness = this.sharedModel.awareness;
|
|
@@ -204,7 +230,7 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
|
|
|
204
230
|
this._timeoutWriting = window.setTimeout(() => {
|
|
205
231
|
this._resetWritingStatus();
|
|
206
232
|
}, WRITING_DELAY);
|
|
207
|
-
}
|
|
233
|
+
};
|
|
208
234
|
|
|
209
235
|
/**
|
|
210
236
|
* Triggered when an awareness state changes.
|
|
@@ -242,13 +268,34 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
|
|
|
242
268
|
index += delta.retain;
|
|
243
269
|
} else if (delta.insert) {
|
|
244
270
|
const messages = delta.insert.map(ymessage => {
|
|
271
|
+
const {
|
|
272
|
+
sender,
|
|
273
|
+
attachments: attachmentIds,
|
|
274
|
+
...baseMessage
|
|
275
|
+
} = ymessage;
|
|
276
|
+
|
|
277
|
+
// Build the base message with sender.
|
|
245
278
|
const msg: IChatMessage = {
|
|
246
|
-
...
|
|
247
|
-
sender: this.sharedModel.getUser(
|
|
279
|
+
...baseMessage,
|
|
280
|
+
sender: this.sharedModel.getUser(sender) || {
|
|
248
281
|
username: 'User undefined'
|
|
249
282
|
}
|
|
250
283
|
};
|
|
251
284
|
|
|
285
|
+
// Add attachments.
|
|
286
|
+
if (attachmentIds) {
|
|
287
|
+
const attachments: IAttachment[] = [];
|
|
288
|
+
attachmentIds.forEach(attachmentId => {
|
|
289
|
+
const attachment = this.sharedModel.getAttachment(attachmentId);
|
|
290
|
+
if (attachment) {
|
|
291
|
+
attachments.push(attachment);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
if (attachments.length) {
|
|
295
|
+
msg.attachments = attachments;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
252
299
|
return msg;
|
|
253
300
|
});
|
|
254
301
|
this.messagesInserted(index, messages);
|
package/src/token.ts
CHANGED
|
@@ -7,9 +7,10 @@ import {
|
|
|
7
7
|
IConfig,
|
|
8
8
|
chatIcon,
|
|
9
9
|
IActiveCellManager,
|
|
10
|
-
ISelectionWatcher
|
|
10
|
+
ISelectionWatcher,
|
|
11
|
+
ChatWidget
|
|
11
12
|
} from '@jupyter/chat';
|
|
12
|
-
import {
|
|
13
|
+
import { WidgetTracker } from '@jupyterlab/apputils';
|
|
13
14
|
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
14
15
|
import { Token } from '@lumino/coreutils';
|
|
15
16
|
import { ISignal } from '@lumino/signaling';
|
|
@@ -56,7 +57,7 @@ export interface IChatFactory {
|
|
|
56
57
|
/**
|
|
57
58
|
* The chat panel tracker.
|
|
58
59
|
*/
|
|
59
|
-
tracker:
|
|
60
|
+
tracker: WidgetTracker<LabChatPanel | ChatWidget>;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
@@ -77,7 +78,7 @@ export interface IWidgetConfig {
|
|
|
77
78
|
/**
|
|
78
79
|
* A signal emitting when the configuration for the chats has changed.
|
|
79
80
|
*/
|
|
80
|
-
export interface IConfigChanged
|
|
81
|
+
export interface IConfigChanged /* eslint-disable-line @typescript-eslint/no-empty-object-type */
|
|
81
82
|
extends ISignal<IWidgetConfig, Partial<ILabChatConfig>> {}
|
|
82
83
|
|
|
83
84
|
/**
|
package/src/widget.tsx
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
ChatWidget,
|
|
8
|
-
|
|
8
|
+
IAttachmentOpenerRegistry,
|
|
9
|
+
IChatCommandRegistry,
|
|
9
10
|
IChatModel,
|
|
10
11
|
readIcon
|
|
11
12
|
} from '@jupyter/chat';
|
|
@@ -33,6 +34,7 @@ import React, { useState } from 'react';
|
|
|
33
34
|
|
|
34
35
|
import { LabChatModel } from './model';
|
|
35
36
|
import { CommandIDs, chatFileType } from './token';
|
|
37
|
+
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
36
38
|
|
|
37
39
|
const MAIN_PANEL_CLASS = 'jp-lab-chat-main-panel';
|
|
38
40
|
const TITLE_UNREAD_CLASS = 'jp-lab-chat-title-unread';
|
|
@@ -102,7 +104,9 @@ export class ChatPanel extends SidePanel {
|
|
|
102
104
|
this._rmRegistry = options.rmRegistry;
|
|
103
105
|
this._themeManager = options.themeManager;
|
|
104
106
|
this._defaultDirectory = options.defaultDirectory;
|
|
105
|
-
this.
|
|
107
|
+
this._documentManager = options.documentManager;
|
|
108
|
+
this._chatCommandRegistry = options.chatCommandRegistry;
|
|
109
|
+
this._attachmentOpenerRegistry = options.attachmentOpenerRegistry;
|
|
106
110
|
|
|
107
111
|
const addChat = new CommandToolbarButton({
|
|
108
112
|
commands: this._commands,
|
|
@@ -152,32 +156,33 @@ export class ChatPanel extends SidePanel {
|
|
|
152
156
|
* @param model - the model of the chat widget
|
|
153
157
|
* @param name - the name of the chat.
|
|
154
158
|
*/
|
|
155
|
-
addChat(model: IChatModel
|
|
159
|
+
addChat(model: IChatModel): ChatWidget {
|
|
156
160
|
// Collapse all chats
|
|
157
161
|
const content = this.content as AccordionPanel;
|
|
158
162
|
for (let i = 0; i < this.widgets.length; i++) {
|
|
159
163
|
content.collapse(i);
|
|
160
164
|
}
|
|
161
165
|
|
|
162
|
-
// Set the name of the model.
|
|
163
|
-
model.name = path;
|
|
164
|
-
|
|
165
166
|
// Create a new widget.
|
|
166
167
|
const widget = new ChatWidget({
|
|
167
168
|
model: model,
|
|
168
169
|
rmRegistry: this._rmRegistry,
|
|
169
170
|
themeManager: this._themeManager,
|
|
170
|
-
|
|
171
|
+
documentManager: this._documentManager,
|
|
172
|
+
chatCommandRegistry: this._chatCommandRegistry,
|
|
173
|
+
attachmentOpenerRegistry: this._attachmentOpenerRegistry
|
|
171
174
|
});
|
|
172
175
|
|
|
173
176
|
this.addWidget(
|
|
174
177
|
new ChatSection({
|
|
175
178
|
widget,
|
|
176
179
|
commands: this._commands,
|
|
177
|
-
path,
|
|
180
|
+
path: model.name,
|
|
178
181
|
defaultDirectory: this._defaultDirectory
|
|
179
182
|
})
|
|
180
183
|
);
|
|
184
|
+
|
|
185
|
+
return widget;
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
/**
|
|
@@ -284,7 +289,9 @@ export class ChatPanel extends SidePanel {
|
|
|
284
289
|
private _openChat: ReactWidget;
|
|
285
290
|
private _rmRegistry: IRenderMimeRegistry;
|
|
286
291
|
private _themeManager: IThemeManager | null;
|
|
287
|
-
private
|
|
292
|
+
private _documentManager?: IDocumentManager;
|
|
293
|
+
private _chatCommandRegistry?: IChatCommandRegistry;
|
|
294
|
+
private _attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
288
295
|
}
|
|
289
296
|
|
|
290
297
|
/**
|
|
@@ -300,7 +307,9 @@ export namespace ChatPanel {
|
|
|
300
307
|
rmRegistry: IRenderMimeRegistry;
|
|
301
308
|
themeManager: IThemeManager | null;
|
|
302
309
|
defaultDirectory: string;
|
|
303
|
-
|
|
310
|
+
documentManager?: IDocumentManager;
|
|
311
|
+
chatCommandRegistry?: IChatCommandRegistry;
|
|
312
|
+
attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
|
|
304
313
|
}
|
|
305
314
|
}
|
|
306
315
|
|
package/src/ychat.ts
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IChatMessage, IUser } from '@jupyter/chat';
|
|
6
|
+
import { IAttachment, IChatMessage, IUser } from '@jupyter/chat';
|
|
7
7
|
import { Delta, DocumentChange, IMapChange, YDocument } from '@jupyter/ydoc';
|
|
8
|
-
import { JSONExt, JSONObject, PartialJSONValue } from '@lumino/coreutils';
|
|
8
|
+
import { JSONExt, JSONObject, PartialJSONValue, UUID } from '@lumino/coreutils';
|
|
9
9
|
import * as Y from 'yjs';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The type for a YMessage.
|
|
13
13
|
*/
|
|
14
|
-
export type IYmessage = IChatMessage<string>;
|
|
14
|
+
export type IYmessage = IChatMessage<string, string>;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The type for a YMessage.
|
|
@@ -30,6 +30,10 @@ export interface IChatChanges extends DocumentChange {
|
|
|
30
30
|
* Changes in users.
|
|
31
31
|
*/
|
|
32
32
|
userChanges?: UserChange[];
|
|
33
|
+
/**
|
|
34
|
+
* Changes in attachments.
|
|
35
|
+
*/
|
|
36
|
+
attachmentChanges?: AttachmentChange[];
|
|
33
37
|
/**
|
|
34
38
|
* Changes in metadata.
|
|
35
39
|
*/
|
|
@@ -46,6 +50,11 @@ export type MessageChange = Delta<IYmessage[]>;
|
|
|
46
50
|
*/
|
|
47
51
|
export type UserChange = IMapChange<IUser>;
|
|
48
52
|
|
|
53
|
+
/**
|
|
54
|
+
* The attachment change type.
|
|
55
|
+
*/
|
|
56
|
+
export type AttachmentChange = IMapChange<IAttachment>;
|
|
57
|
+
|
|
49
58
|
/**
|
|
50
59
|
* The metadata change type.
|
|
51
60
|
*/
|
|
@@ -66,6 +75,9 @@ export class YChat extends YDocument<IChatChanges> {
|
|
|
66
75
|
this._messages = this.ydoc.getArray<IYmessage>('messages');
|
|
67
76
|
this._messages.observe(this._messagesObserver);
|
|
68
77
|
|
|
78
|
+
this._attachments = this.ydoc.getMap<IAttachment>('attachments');
|
|
79
|
+
this._attachments.observe(this._attachmentsObserver);
|
|
80
|
+
|
|
69
81
|
this._metadata = this.ydoc.getMap<IMetadata>('metadata');
|
|
70
82
|
this._metadata.observe(this._metadataObserver);
|
|
71
83
|
}
|
|
@@ -96,6 +108,10 @@ export class YChat extends YDocument<IChatChanges> {
|
|
|
96
108
|
return JSONExt.deepCopy(this._messages.toJSON());
|
|
97
109
|
}
|
|
98
110
|
|
|
111
|
+
get attachments(): JSONObject {
|
|
112
|
+
return JSONExt.deepCopy(this._attachments.toJSON());
|
|
113
|
+
}
|
|
114
|
+
|
|
99
115
|
getSource(): JSONObject {
|
|
100
116
|
const users = this._users.toJSON();
|
|
101
117
|
const messages = this._messages.toJSON();
|
|
@@ -168,28 +184,46 @@ export class YChat extends YDocument<IChatChanges> {
|
|
|
168
184
|
});
|
|
169
185
|
}
|
|
170
186
|
|
|
187
|
+
getAttachment(id: string): IAttachment | undefined {
|
|
188
|
+
return this._attachments.get(id);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
setAttachment(attachment: IAttachment): string {
|
|
192
|
+
// Search if the attachment already exist to update it, otherwise add it.
|
|
193
|
+
const id =
|
|
194
|
+
Array.from(this._attachments.entries()).find(
|
|
195
|
+
([_, att]) =>
|
|
196
|
+
att.type === attachment.type && att.value === attachment.value
|
|
197
|
+
)?.[0] || UUID.uuid4();
|
|
198
|
+
|
|
199
|
+
this.transact(() => {
|
|
200
|
+
this._attachments.set(id, attachment);
|
|
201
|
+
});
|
|
202
|
+
return id;
|
|
203
|
+
}
|
|
204
|
+
|
|
171
205
|
private _usersObserver = (event: Y.YMapEvent<IUser>): void => {
|
|
172
|
-
const
|
|
206
|
+
const userChanges = new Array<UserChange>();
|
|
173
207
|
event.keysChanged.forEach(key => {
|
|
174
208
|
const change = event.changes.keys.get(key);
|
|
175
209
|
if (change) {
|
|
176
210
|
switch (change.action) {
|
|
177
211
|
case 'add':
|
|
178
|
-
|
|
212
|
+
userChanges.push({
|
|
179
213
|
key,
|
|
180
214
|
newValue: this._users.get(key),
|
|
181
215
|
type: 'add'
|
|
182
216
|
});
|
|
183
217
|
break;
|
|
184
218
|
case 'delete':
|
|
185
|
-
|
|
219
|
+
userChanges.push({
|
|
186
220
|
key,
|
|
187
221
|
oldValue: change.oldValue,
|
|
188
222
|
type: 'remove'
|
|
189
223
|
});
|
|
190
224
|
break;
|
|
191
225
|
case 'update':
|
|
192
|
-
|
|
226
|
+
userChanges.push({
|
|
193
227
|
key: key,
|
|
194
228
|
oldValue: change.oldValue,
|
|
195
229
|
newValue: this._users.get(key),
|
|
@@ -200,7 +234,7 @@ export class YChat extends YDocument<IChatChanges> {
|
|
|
200
234
|
}
|
|
201
235
|
});
|
|
202
236
|
|
|
203
|
-
this._changed.emit({
|
|
237
|
+
this._changed.emit({ userChanges } as Partial<IChatChanges>);
|
|
204
238
|
};
|
|
205
239
|
|
|
206
240
|
private _messagesObserver = (event: Y.YArrayEvent<IYmessage>): void => {
|
|
@@ -210,26 +244,61 @@ export class YChat extends YDocument<IChatChanges> {
|
|
|
210
244
|
} as Partial<IChatChanges>);
|
|
211
245
|
};
|
|
212
246
|
|
|
247
|
+
private _attachmentsObserver = (event: Y.YMapEvent<IAttachment>): void => {
|
|
248
|
+
const attachmentChanges = new Array<AttachmentChange>();
|
|
249
|
+
event.keysChanged.forEach(key => {
|
|
250
|
+
const change = event.changes.keys.get(key);
|
|
251
|
+
if (change) {
|
|
252
|
+
switch (change.action) {
|
|
253
|
+
case 'add':
|
|
254
|
+
attachmentChanges.push({
|
|
255
|
+
key,
|
|
256
|
+
newValue: this._attachments.get(key),
|
|
257
|
+
type: 'add'
|
|
258
|
+
});
|
|
259
|
+
break;
|
|
260
|
+
case 'delete':
|
|
261
|
+
attachmentChanges.push({
|
|
262
|
+
key,
|
|
263
|
+
oldValue: change.oldValue,
|
|
264
|
+
type: 'remove'
|
|
265
|
+
});
|
|
266
|
+
break;
|
|
267
|
+
case 'update':
|
|
268
|
+
attachmentChanges.push({
|
|
269
|
+
key: key,
|
|
270
|
+
oldValue: change.oldValue,
|
|
271
|
+
newValue: this._attachments.get(key),
|
|
272
|
+
type: 'change'
|
|
273
|
+
});
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
this._changed.emit({ attachmentChanges } as Partial<IChatChanges>);
|
|
280
|
+
};
|
|
281
|
+
|
|
213
282
|
private _metadataObserver = (event: Y.YMapEvent<IMetadata>): void => {
|
|
214
|
-
const
|
|
283
|
+
const metadataChanges = new Array<MetadataChange>();
|
|
215
284
|
event.changes.keys.forEach((change, key) => {
|
|
216
285
|
switch (change.action) {
|
|
217
286
|
case 'add':
|
|
218
|
-
|
|
287
|
+
metadataChanges.push({
|
|
219
288
|
key,
|
|
220
289
|
newValue: this._metadata.get(key),
|
|
221
290
|
type: 'add'
|
|
222
291
|
});
|
|
223
292
|
break;
|
|
224
293
|
case 'delete':
|
|
225
|
-
|
|
294
|
+
metadataChanges.push({
|
|
226
295
|
key,
|
|
227
296
|
oldValue: change.oldValue,
|
|
228
297
|
type: 'remove'
|
|
229
298
|
});
|
|
230
299
|
break;
|
|
231
300
|
case 'update':
|
|
232
|
-
|
|
301
|
+
metadataChanges.push({
|
|
233
302
|
key: key,
|
|
234
303
|
oldValue: change.oldValue,
|
|
235
304
|
newValue: this._metadata.get(key),
|
|
@@ -239,12 +308,11 @@ export class YChat extends YDocument<IChatChanges> {
|
|
|
239
308
|
}
|
|
240
309
|
});
|
|
241
310
|
|
|
242
|
-
this._changed.emit({
|
|
243
|
-
metadataChanges: metadataChange
|
|
244
|
-
} as Partial<IChatChanges>);
|
|
311
|
+
this._changed.emit({ metadataChanges } as Partial<IChatChanges>);
|
|
245
312
|
};
|
|
246
313
|
|
|
247
314
|
private _users: Y.Map<IUser>;
|
|
248
315
|
private _messages: Y.Array<IYmessage>;
|
|
316
|
+
private _attachments: Y.Map<IAttachment>;
|
|
249
317
|
private _metadata: Y.Map<IMetadata>;
|
|
250
318
|
}
|
package/style/base.css
CHANGED