jupyterlab-chat 0.6.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.
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) Jupyter Development Team.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests
8
+ */
9
+ describe('jupyterlab-chat', () => {
10
+ it('should be tested', () => {
11
+ expect(1 + 1).toEqual(2);
12
+ });
13
+ });
@@ -0,0 +1,118 @@
1
+ import { IAutocompletionRegistry } from '@jupyter/chat';
2
+ import { IThemeManager } from '@jupyterlab/apputils';
3
+ import { ABCWidgetFactory, DocumentRegistry } from '@jupyterlab/docregistry';
4
+ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
5
+ import { Contents } from '@jupyterlab/services';
6
+ import { ISignal } from '@lumino/signaling';
7
+ import { LabChatModel } from './model';
8
+ import { LabChatPanel } from './widget';
9
+ import { YChat } from './ychat';
10
+ import { ILabChatConfig, IWidgetConfig } from './token';
11
+ /**
12
+ * The object provided by the chatDocument extension.
13
+ * It is used to set the current config (from settings) to newly created chat widget,
14
+ * and to propagate every changes to the existing chat widgets.
15
+ */
16
+ export declare class WidgetConfig implements IWidgetConfig {
17
+ /**
18
+ * The constructor of the WidgetConfig.
19
+ */
20
+ constructor(config: Partial<ILabChatConfig>);
21
+ /**
22
+ * Getter and setter for the config.
23
+ */
24
+ get config(): Partial<ILabChatConfig>;
25
+ set config(value: Partial<ILabChatConfig>);
26
+ /**
27
+ * Getter for the configChanged signal
28
+ */
29
+ get configChanged(): ISignal<WidgetConfig, Partial<ILabChatConfig>>;
30
+ private _config;
31
+ private _configChanged;
32
+ }
33
+ /**
34
+ * A widget factory to create new instances of LabChatWidget.
35
+ */
36
+ export declare class ChatWidgetFactory extends ABCWidgetFactory<LabChatPanel, LabChatModel> {
37
+ /**
38
+ * Constructor of ChatWidgetFactory.
39
+ *
40
+ * @param options Constructor options
41
+ */
42
+ constructor(options: ChatWidgetFactory.IOptions<LabChatPanel>);
43
+ /**
44
+ * Create a new widget given a context.
45
+ *
46
+ * @param context Contains the information of the file
47
+ * @returns The widget
48
+ */
49
+ protected createNewWidget(context: ChatWidgetFactory.IContext): LabChatPanel;
50
+ private _themeManager;
51
+ private _rmRegistry;
52
+ private _autocompletionRegistry?;
53
+ }
54
+ export declare namespace ChatWidgetFactory {
55
+ interface IContext extends DocumentRegistry.IContext<LabChatModel> {
56
+ themeManager: IThemeManager | null;
57
+ rmRegistry: IRenderMimeRegistry;
58
+ autocompletionRegistry?: IAutocompletionRegistry;
59
+ }
60
+ interface IOptions<T extends LabChatPanel> extends DocumentRegistry.IWidgetFactoryOptions<T> {
61
+ themeManager: IThemeManager | null;
62
+ rmRegistry: IRenderMimeRegistry;
63
+ autocompletionRegistry?: IAutocompletionRegistry;
64
+ }
65
+ }
66
+ export declare class LabChatModelFactory implements DocumentRegistry.IModelFactory<LabChatModel> {
67
+ constructor(options: LabChatModel.IOptions);
68
+ collaborative: boolean;
69
+ /**
70
+ * The name of the model.
71
+ *
72
+ * @returns The name
73
+ */
74
+ get name(): string;
75
+ /**
76
+ * The content type of the file.
77
+ *
78
+ * @returns The content type
79
+ */
80
+ get contentType(): Contents.ContentType;
81
+ /**
82
+ * The format of the file.
83
+ *
84
+ * @returns the file format
85
+ */
86
+ get fileFormat(): Contents.FileFormat;
87
+ /**
88
+ * Get whether the model factory has been disposed.
89
+ *
90
+ * @returns disposed status
91
+ */
92
+ get isDisposed(): boolean;
93
+ /**
94
+ * Dispose the model factory.
95
+ */
96
+ dispose(): void;
97
+ /**
98
+ * Get the preferred language given the path on the file.
99
+ *
100
+ * @param path path of the file represented by this document model
101
+ * @returns The preferred language
102
+ */
103
+ preferredLanguage(path: string): string;
104
+ /**
105
+ * Create a new instance of LabChatModel.
106
+ *
107
+ * @param languagePreference Language
108
+ * @param modelDB Model database
109
+ * @returns The model
110
+ */
111
+ createNew(options: DocumentRegistry.IModelOptions<YChat>): LabChatModel;
112
+ private _disposed;
113
+ private _user;
114
+ private _widgetConfig;
115
+ private _commands?;
116
+ private _activeCellManager;
117
+ private _selectionWatcher;
118
+ }
package/lib/factory.js ADDED
@@ -0,0 +1,146 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { ChatWidget } from '@jupyter/chat';
6
+ import { ABCWidgetFactory } from '@jupyterlab/docregistry';
7
+ import { Signal } from '@lumino/signaling';
8
+ import { LabChatModel } from './model';
9
+ import { LabChatPanel } from './widget';
10
+ /**
11
+ * The object provided by the chatDocument extension.
12
+ * It is used to set the current config (from settings) to newly created chat widget,
13
+ * and to propagate every changes to the existing chat widgets.
14
+ */
15
+ export class WidgetConfig {
16
+ /**
17
+ * The constructor of the WidgetConfig.
18
+ */
19
+ constructor(config) {
20
+ this._configChanged = new Signal(this);
21
+ this._config = config;
22
+ }
23
+ /**
24
+ * Getter and setter for the config.
25
+ */
26
+ get config() {
27
+ return this._config;
28
+ }
29
+ set config(value) {
30
+ this._config = { ...this._config, ...value };
31
+ this._configChanged.emit(value);
32
+ }
33
+ /**
34
+ * Getter for the configChanged signal
35
+ */
36
+ get configChanged() {
37
+ return this._configChanged;
38
+ }
39
+ }
40
+ /**
41
+ * A widget factory to create new instances of LabChatWidget.
42
+ */
43
+ export class ChatWidgetFactory extends ABCWidgetFactory {
44
+ /**
45
+ * Constructor of ChatWidgetFactory.
46
+ *
47
+ * @param options Constructor options
48
+ */
49
+ constructor(options) {
50
+ super(options);
51
+ this._themeManager = options.themeManager;
52
+ this._rmRegistry = options.rmRegistry;
53
+ this._autocompletionRegistry = options.autocompletionRegistry;
54
+ }
55
+ /**
56
+ * Create a new widget given a context.
57
+ *
58
+ * @param context Contains the information of the file
59
+ * @returns The widget
60
+ */
61
+ createNewWidget(context) {
62
+ context.rmRegistry = this._rmRegistry;
63
+ context.themeManager = this._themeManager;
64
+ context.autocompletionRegistry = this._autocompletionRegistry;
65
+ return new LabChatPanel({
66
+ context,
67
+ content: new ChatWidget(context)
68
+ });
69
+ }
70
+ }
71
+ export class LabChatModelFactory {
72
+ constructor(options) {
73
+ var _a, _b;
74
+ this.collaborative = true;
75
+ this._disposed = false;
76
+ this._user = options.user;
77
+ this._widgetConfig = options.widgetConfig;
78
+ this._commands = options.commands;
79
+ this._activeCellManager = (_a = options.activeCellManager) !== null && _a !== void 0 ? _a : null;
80
+ this._selectionWatcher = (_b = options.selectionWatcher) !== null && _b !== void 0 ? _b : null;
81
+ }
82
+ /**
83
+ * The name of the model.
84
+ *
85
+ * @returns The name
86
+ */
87
+ get name() {
88
+ return 'chat';
89
+ }
90
+ /**
91
+ * The content type of the file.
92
+ *
93
+ * @returns The content type
94
+ */
95
+ get contentType() {
96
+ return 'chat';
97
+ }
98
+ /**
99
+ * The format of the file.
100
+ *
101
+ * @returns the file format
102
+ */
103
+ get fileFormat() {
104
+ return 'text';
105
+ }
106
+ /**
107
+ * Get whether the model factory has been disposed.
108
+ *
109
+ * @returns disposed status
110
+ */
111
+ get isDisposed() {
112
+ return this._disposed;
113
+ }
114
+ /**
115
+ * Dispose the model factory.
116
+ */
117
+ dispose() {
118
+ this._disposed = true;
119
+ }
120
+ /**
121
+ * Get the preferred language given the path on the file.
122
+ *
123
+ * @param path path of the file represented by this document model
124
+ * @returns The preferred language
125
+ */
126
+ preferredLanguage(path) {
127
+ return '';
128
+ }
129
+ /**
130
+ * Create a new instance of LabChatModel.
131
+ *
132
+ * @param languagePreference Language
133
+ * @param modelDB Model database
134
+ * @returns The model
135
+ */
136
+ createNew(options) {
137
+ return new LabChatModel({
138
+ ...options,
139
+ user: this._user,
140
+ widgetConfig: this._widgetConfig,
141
+ commands: this._commands,
142
+ activeCellManager: this._activeCellManager,
143
+ selectionWatcher: this._selectionWatcher
144
+ });
145
+ }
146
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './factory';
2
+ export * from './model';
3
+ export * from './token';
4
+ export * from './widget';
5
+ export * from './ychat';
package/lib/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export * from './factory';
6
+ export * from './model';
7
+ export * from './token';
8
+ export * from './widget';
9
+ export * from './ychat';
package/lib/model.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { ChatModel, IChatMessage, INewMessage, IUser } from '@jupyter/chat';
2
+ import { IChangedArgs } from '@jupyterlab/coreutils';
3
+ import { DocumentRegistry } from '@jupyterlab/docregistry';
4
+ import { User } from '@jupyterlab/services';
5
+ import { PartialJSONObject } from '@lumino/coreutils';
6
+ import { ISignal } from '@lumino/signaling';
7
+ import { IWidgetConfig } from './token';
8
+ import { YChat } from './ychat';
9
+ /**
10
+ * Chat model namespace.
11
+ */
12
+ export declare namespace LabChatModel {
13
+ interface IOptions extends ChatModel.IOptions {
14
+ widgetConfig: IWidgetConfig;
15
+ user: User.IIdentity | null;
16
+ sharedModel?: YChat;
17
+ languagePreference?: string;
18
+ }
19
+ }
20
+ /**
21
+ * The chat model.
22
+ */
23
+ export declare class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
24
+ constructor(options: LabChatModel.IOptions);
25
+ readonly collaborative = true;
26
+ get user(): IUser;
27
+ get sharedModel(): YChat;
28
+ get contentChanged(): ISignal<this, void>;
29
+ get stateChanged(): ISignal<this, IChangedArgs<any, any, string>>;
30
+ get dirty(): boolean;
31
+ set dirty(value: boolean);
32
+ get readOnly(): boolean;
33
+ set readOnly(value: boolean);
34
+ get disposed(): ISignal<LabChatModel, void>;
35
+ dispose(): void;
36
+ toString(): string;
37
+ fromString(data: string): void;
38
+ toJSON(): PartialJSONObject;
39
+ fromJSON(data: PartialJSONObject): void;
40
+ sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
41
+ updateMessage(id: string, updatedMessage: IChatMessage): Promise<boolean | void> | boolean | void;
42
+ deleteMessage(id: string): Promise<boolean | void> | boolean | void;
43
+ /**
44
+ * Function called by the input on key pressed.
45
+ */
46
+ inputChanged(input?: string): void;
47
+ /**
48
+ * Triggered when an awareness state changes.
49
+ * Used to populate the writers list.
50
+ */
51
+ onAwarenessChange: () => void;
52
+ private _resetWritingStatus;
53
+ private _onchange;
54
+ readonly defaultKernelName: string;
55
+ readonly defaultKernelLanguage: string;
56
+ private _sharedModel;
57
+ private _dirty;
58
+ private _readOnly;
59
+ private _disposed;
60
+ private _contentChanged;
61
+ private _stateChanged;
62
+ private _timeoutWriting;
63
+ private _user;
64
+ }
package/lib/model.js ADDED
@@ -0,0 +1,216 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { ChatModel } from '@jupyter/chat';
6
+ import { UUID } from '@lumino/coreutils';
7
+ import { Signal } from '@lumino/signaling';
8
+ import { YChat } from './ychat';
9
+ const WRITING_DELAY = 1000;
10
+ /**
11
+ * The chat model.
12
+ */
13
+ export class LabChatModel extends ChatModel {
14
+ constructor(options) {
15
+ super(options);
16
+ this.collaborative = true;
17
+ /**
18
+ * Triggered when an awareness state changes.
19
+ * Used to populate the writers list.
20
+ */
21
+ this.onAwarenessChange = () => {
22
+ const writers = [];
23
+ const states = this.sharedModel.awareness.getStates();
24
+ for (const stateID of states.keys()) {
25
+ const state = states.get(stateID);
26
+ if (!state || !state.user || state.user.username === this.user.username) {
27
+ continue;
28
+ }
29
+ if (state.isWriting) {
30
+ writers.push(state.user);
31
+ }
32
+ }
33
+ this.updateWriters(writers);
34
+ };
35
+ this._onchange = (_, changes) => {
36
+ if (changes.messageChanges) {
37
+ const msgDelta = changes.messageChanges;
38
+ let index = 0;
39
+ msgDelta.forEach(delta => {
40
+ if (delta.retain) {
41
+ index += delta.retain;
42
+ }
43
+ else if (delta.insert) {
44
+ const messages = delta.insert.map(ymessage => {
45
+ const msg = {
46
+ ...ymessage,
47
+ sender: this.sharedModel.getUser(ymessage.sender) || {
48
+ username: 'User undefined'
49
+ }
50
+ };
51
+ return msg;
52
+ });
53
+ this.messagesInserted(index, messages);
54
+ index += messages.length;
55
+ }
56
+ else if (delta.delete) {
57
+ this.messagesDeleted(index, delta.delete);
58
+ }
59
+ });
60
+ }
61
+ if (changes.metadataChanges) {
62
+ changes.metadataChanges.forEach(change => {
63
+ // no need to search for update or add, if the new value contains ID, let's
64
+ // update the model ID.
65
+ if (change.key === 'id') {
66
+ this.id = change.newValue;
67
+ }
68
+ });
69
+ }
70
+ };
71
+ this.defaultKernelName = '';
72
+ this.defaultKernelLanguage = '';
73
+ this._dirty = false;
74
+ this._readOnly = false;
75
+ this._disposed = new Signal(this);
76
+ this._contentChanged = new Signal(this);
77
+ this._stateChanged = new Signal(this);
78
+ this._timeoutWriting = null;
79
+ this._user = options.user || { username: 'user undefined' };
80
+ const { widgetConfig, sharedModel } = options;
81
+ if (sharedModel) {
82
+ this._sharedModel = sharedModel;
83
+ }
84
+ else {
85
+ this._sharedModel = YChat.create();
86
+ }
87
+ this.id = this._sharedModel.id;
88
+ this.sharedModel.changed.connect(this._onchange, this);
89
+ this.config = widgetConfig.config;
90
+ widgetConfig.configChanged.connect((_, config) => {
91
+ this.config = config;
92
+ });
93
+ this.sharedModel.awareness.on('change', this.onAwarenessChange);
94
+ }
95
+ get user() {
96
+ return this._user;
97
+ }
98
+ get sharedModel() {
99
+ return this._sharedModel;
100
+ }
101
+ get contentChanged() {
102
+ return this._contentChanged;
103
+ }
104
+ get stateChanged() {
105
+ return this._stateChanged;
106
+ }
107
+ get dirty() {
108
+ return this._dirty;
109
+ }
110
+ set dirty(value) {
111
+ this._dirty = value;
112
+ }
113
+ get readOnly() {
114
+ return this._readOnly;
115
+ }
116
+ set readOnly(value) {
117
+ this._readOnly = value;
118
+ }
119
+ get disposed() {
120
+ return this._disposed;
121
+ }
122
+ dispose() {
123
+ if (this.isDisposed) {
124
+ return;
125
+ }
126
+ super.dispose();
127
+ this._sharedModel.dispose();
128
+ this._disposed.emit();
129
+ Signal.clearData(this);
130
+ }
131
+ toString() {
132
+ return JSON.stringify({}, null, 2);
133
+ }
134
+ fromString(data) {
135
+ /** */
136
+ }
137
+ toJSON() {
138
+ return JSON.parse(this.toString());
139
+ }
140
+ fromJSON(data) {
141
+ // nothing to do
142
+ }
143
+ sendMessage(message) {
144
+ this._resetWritingStatus();
145
+ if (this._timeoutWriting !== null) {
146
+ window.clearTimeout(this._timeoutWriting);
147
+ }
148
+ const msg = {
149
+ type: 'msg',
150
+ id: UUID.uuid4(),
151
+ body: message.body,
152
+ time: Date.now() / 1000,
153
+ sender: this._user.username,
154
+ raw_time: true
155
+ };
156
+ // Add the user if it does not exist or has changed
157
+ if (!(this.sharedModel.getUser(this._user.username) === this._user)) {
158
+ this.sharedModel.setUser(this._user);
159
+ }
160
+ this.sharedModel.addMessage(msg);
161
+ }
162
+ updateMessage(id, updatedMessage) {
163
+ const index = this.sharedModel.getMessageIndex(id);
164
+ let message = this.sharedModel.getMessage(index);
165
+ if (message) {
166
+ message.body = updatedMessage.body;
167
+ message.edited = true;
168
+ }
169
+ else {
170
+ const sender = updatedMessage.sender.username;
171
+ message = {
172
+ type: 'msg',
173
+ id: id || UUID.uuid4(),
174
+ body: updatedMessage.body,
175
+ time: updatedMessage.time || Date.now() / 1000,
176
+ sender: sender,
177
+ edited: true
178
+ };
179
+ }
180
+ this.sharedModel.updateMessage(index, message);
181
+ }
182
+ deleteMessage(id) {
183
+ const index = this.sharedModel.getMessageIndex(id);
184
+ const message = this.sharedModel.getMessage(index);
185
+ if (!message) {
186
+ console.error('The message to delete does not exist');
187
+ return;
188
+ }
189
+ message.body = '';
190
+ message.deleted = true;
191
+ this.sharedModel.updateMessage(index, message);
192
+ }
193
+ /**
194
+ * Function called by the input on key pressed.
195
+ */
196
+ inputChanged(input) {
197
+ if (!input || !this.config.sendTypingNotification) {
198
+ return;
199
+ }
200
+ const awareness = this.sharedModel.awareness;
201
+ if (this._timeoutWriting !== null) {
202
+ window.clearTimeout(this._timeoutWriting);
203
+ }
204
+ awareness.setLocalStateField('isWriting', true);
205
+ this._timeoutWriting = window.setTimeout(() => {
206
+ this._resetWritingStatus();
207
+ }, WRITING_DELAY);
208
+ }
209
+ _resetWritingStatus() {
210
+ const awareness = this.sharedModel.awareness;
211
+ const states = awareness.getLocalState();
212
+ states === null || states === void 0 ? true : delete states.isWriting;
213
+ awareness.setLocalState(states);
214
+ this._timeoutWriting = null;
215
+ }
216
+ }
package/lib/token.d.ts ADDED
@@ -0,0 +1,91 @@
1
+ import { IConfig, IActiveCellManager, ISelectionWatcher } from '@jupyter/chat';
2
+ import { IWidgetTracker } from '@jupyterlab/apputils';
3
+ import { DocumentRegistry } from '@jupyterlab/docregistry';
4
+ import { Token } from '@lumino/coreutils';
5
+ import { ISignal } from '@lumino/signaling';
6
+ import { ChatPanel, LabChatPanel } from './widget';
7
+ /**
8
+ * The file type for a chat document.
9
+ */
10
+ export declare const chatFileType: DocumentRegistry.IFileType;
11
+ /**
12
+ * The token for the chat widget factory.
13
+ */
14
+ export declare const IChatFactory: Token<IChatFactory>;
15
+ /**
16
+ * The chat configs.
17
+ */
18
+ export interface ILabChatConfig extends IConfig {
19
+ /**
20
+ * The default directory where to create and look for chat.
21
+ */
22
+ defaultDirectory?: string;
23
+ }
24
+ /**
25
+ * The interface for the chat factory objects.
26
+ */
27
+ export interface IChatFactory {
28
+ /**
29
+ * The chat widget config.
30
+ */
31
+ widgetConfig: IWidgetConfig;
32
+ /**
33
+ * The chat panel tracker.
34
+ */
35
+ tracker: IWidgetTracker<LabChatPanel>;
36
+ }
37
+ /**
38
+ * The interface for the chats config.
39
+ */
40
+ export interface IWidgetConfig {
41
+ /**
42
+ * The widget config
43
+ */
44
+ config: Partial<ILabChatConfig>;
45
+ /**
46
+ * A signal emitting when the configuration for the chats has changed.
47
+ */
48
+ configChanged: IConfigChanged;
49
+ }
50
+ /**
51
+ * A signal emitting when the configuration for the chats has changed.
52
+ */
53
+ export interface IConfigChanged extends ISignal<IWidgetConfig, Partial<ILabChatConfig>> {
54
+ }
55
+ /**
56
+ * Command ids.
57
+ */
58
+ export declare const CommandIDs: {
59
+ /**
60
+ * Create a chat file.
61
+ */
62
+ createChat: string;
63
+ /**
64
+ * Open a chat file.
65
+ */
66
+ openChat: string;
67
+ /**
68
+ * Move a main widget to the side panel.
69
+ */
70
+ moveToSide: string;
71
+ /**
72
+ * Mark as read.
73
+ */
74
+ markAsRead: string;
75
+ /**
76
+ * Focus the input of the current chat.
77
+ */
78
+ focusInput: string;
79
+ };
80
+ /**
81
+ * The chat panel token.
82
+ */
83
+ export declare const IChatPanel: Token<ChatPanel>;
84
+ /**
85
+ * The active cell manager plugin.
86
+ */
87
+ export declare const IActiveCellManagerToken: Token<IActiveCellManager>;
88
+ /**
89
+ * The selection watcher plugin.
90
+ */
91
+ export declare const ISelectionWatcherToken: Token<ISelectionWatcher>;