@jupyter/chat 0.23.0-alpha.2 → 0.23.0-alpha.3
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__/input-model.spec.d.ts +9 -0
- package/lib/__tests__/input-model.spec.js +102 -0
- package/lib/__tests__/multichat-panel.spec.js +78 -27
- package/lib/components/messages/message.js +15 -8
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/input-model.d.ts +50 -2
- package/lib/input-model.js +57 -1
- package/lib/model.js +1 -1
- package/lib/tokens.d.ts +21 -5
- package/lib/types.d.ts +1 -4
- package/lib/utils.d.ts +11 -0
- package/lib/utils.js +42 -0
- package/lib/widgets/multichat-panel.d.ts +20 -20
- package/lib/widgets/multichat-panel.js +32 -48
- package/package.json +2 -1
- package/src/__tests__/input-model.spec.ts +129 -0
- package/src/__tests__/multichat-panel.spec.ts +93 -37
- package/src/components/messages/message.tsx +21 -11
- package/src/index.ts +1 -0
- package/src/input-model.ts +106 -4
- package/src/model.ts +1 -1
- package/src/tokens.ts +21 -5
- package/src/types.ts +6 -4
- package/src/utils.ts +59 -0
- package/src/widgets/multichat-panel.tsx +53 -66
package/src/input-model.ts
CHANGED
|
@@ -10,7 +10,13 @@ import { ISignal, Signal } from '@lumino/signaling';
|
|
|
10
10
|
import { IActiveCellManager } from './active-cell-manager';
|
|
11
11
|
import { ISelectionWatcher } from './selection-watcher';
|
|
12
12
|
import { IChatContext } from './model';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
IAttachment,
|
|
15
|
+
IMessageMetadata,
|
|
16
|
+
INewMessage,
|
|
17
|
+
INotebookAttachment,
|
|
18
|
+
IUser
|
|
19
|
+
} from './types';
|
|
14
20
|
|
|
15
21
|
/**
|
|
16
22
|
* The chat input interface.
|
|
@@ -152,6 +158,31 @@ export interface IInputModel extends IDisposable {
|
|
|
152
158
|
*/
|
|
153
159
|
clearMentions(): void;
|
|
154
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Get the metadata to attach to the next message to send.
|
|
163
|
+
*/
|
|
164
|
+
getMetadata(): IMessageMetadata;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Merge a patch into the metadata to attach to the next message to send.
|
|
168
|
+
*
|
|
169
|
+
* This is a *shallow* merge: each key in `patch` replaces the existing value
|
|
170
|
+
* at that key wholesale, rather than being merged recursively. To change a
|
|
171
|
+
* nested field, pass the whole new value for its top-level key. The patch is
|
|
172
|
+
* deep-copied, so mutating it afterwards won't affect the stored metadata.
|
|
173
|
+
*/
|
|
174
|
+
updateMetadata(patch: IMessageMetadata): void;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Clear the metadata.
|
|
178
|
+
*/
|
|
179
|
+
clearMetadata(): void;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* A signal emitting when the metadata has changed.
|
|
183
|
+
*/
|
|
184
|
+
readonly metadataChanged?: ISignal<IInputModel, IMessageMetadata>;
|
|
185
|
+
|
|
155
186
|
/**
|
|
156
187
|
* A signal emitting when disposing of the model.
|
|
157
188
|
*/
|
|
@@ -169,6 +200,7 @@ export class InputModel implements IInputModel {
|
|
|
169
200
|
this._value = options.value || '';
|
|
170
201
|
this._attachments = options.attachments || [];
|
|
171
202
|
this._mentions = options.mentions || [];
|
|
203
|
+
this._metadata = options.metadata || {};
|
|
172
204
|
this.cursorIndex = options.cursorIndex || this.value.length;
|
|
173
205
|
this._activeCellManager = options.activeCellManager ?? null;
|
|
174
206
|
this._selectionWatcher = options.selectionWatcher ?? null;
|
|
@@ -194,8 +226,32 @@ export class InputModel implements IInputModel {
|
|
|
194
226
|
* Function to send a message.
|
|
195
227
|
*/
|
|
196
228
|
send = (input: string): void => {
|
|
197
|
-
|
|
229
|
+
const message: INewMessage = {
|
|
230
|
+
body: input
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Add the attachments
|
|
234
|
+
if (this.attachments.length) {
|
|
235
|
+
message.attachments = [...this.attachments];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Add the mentions
|
|
239
|
+
if (this.mentions.length) {
|
|
240
|
+
message.mentions = [...this.mentions];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Add the metadata
|
|
244
|
+
if (Object.keys(this._metadata).length) {
|
|
245
|
+
message.metadata = { ...this._metadata };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Send the message
|
|
249
|
+
this._onSend(message);
|
|
250
|
+
|
|
251
|
+
// Clear the input
|
|
198
252
|
this.value = '';
|
|
253
|
+
this.clearAttachments();
|
|
254
|
+
this.clearMentions();
|
|
199
255
|
};
|
|
200
256
|
|
|
201
257
|
/**
|
|
@@ -460,6 +516,45 @@ export class InputModel implements IInputModel {
|
|
|
460
516
|
this._mentions = [];
|
|
461
517
|
};
|
|
462
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Get the metadata to attach to the next message to send.
|
|
521
|
+
*/
|
|
522
|
+
getMetadata(): IMessageMetadata {
|
|
523
|
+
return this._metadata;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Merge a patch into the metadata to attach to the next message to send.
|
|
528
|
+
*
|
|
529
|
+
* This is a *shallow* merge: each key in `patch` replaces the existing value
|
|
530
|
+
* at that key wholesale, rather than being merged recursively. To change a
|
|
531
|
+
* nested field, pass the whole new value for its top-level key. The patch is
|
|
532
|
+
* deep-copied, so mutating it afterwards won't affect the stored metadata.
|
|
533
|
+
*/
|
|
534
|
+
updateMetadata = (patch: IMessageMetadata): void => {
|
|
535
|
+
// Deep-copy the patch so a caller mutating a nested field afterwards can't
|
|
536
|
+
// reach into the stored metadata. `this._metadata` only ever holds values
|
|
537
|
+
// that were themselves deep-copied on the way in, so a shallow spread of it
|
|
538
|
+
// is safe.
|
|
539
|
+
this._metadata = { ...this._metadata, ...structuredClone(patch) };
|
|
540
|
+
this._metadataChanged.emit(this._metadata);
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Clear the metadata.
|
|
545
|
+
*/
|
|
546
|
+
clearMetadata = (): void => {
|
|
547
|
+
this._metadata = {};
|
|
548
|
+
this._metadataChanged.emit(this._metadata);
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* A signal emitting when the metadata has changed.
|
|
553
|
+
*/
|
|
554
|
+
get metadataChanged(): ISignal<IInputModel, IMessageMetadata> {
|
|
555
|
+
return this._metadataChanged;
|
|
556
|
+
}
|
|
557
|
+
|
|
463
558
|
/**
|
|
464
559
|
* Dispose the input model.
|
|
465
560
|
*/
|
|
@@ -486,13 +581,14 @@ export class InputModel implements IInputModel {
|
|
|
486
581
|
}
|
|
487
582
|
|
|
488
583
|
private _id: string;
|
|
489
|
-
private _onSend: (
|
|
584
|
+
private _onSend: (message: INewMessage) => void;
|
|
490
585
|
private _chatContext?: IChatContext;
|
|
491
586
|
private _value: string;
|
|
492
587
|
private _cursorIndex: number | null = null;
|
|
493
588
|
private _currentWord: string | null = null;
|
|
494
589
|
private _attachments: IAttachment[];
|
|
495
590
|
private _mentions: IUser[];
|
|
591
|
+
private _metadata: IMessageMetadata;
|
|
496
592
|
private _activeCellManager: IActiveCellManager | null;
|
|
497
593
|
private _selectionWatcher: ISelectionWatcher | null;
|
|
498
594
|
private _documentManager: IDocumentManager | null;
|
|
@@ -503,6 +599,7 @@ export class InputModel implements IInputModel {
|
|
|
503
599
|
private _configChanged = new Signal<IInputModel, InputModel.IConfig>(this);
|
|
504
600
|
private _focusInputSignal = new Signal<InputModel, void>(this);
|
|
505
601
|
private _attachmentsChanged = new Signal<InputModel, IAttachment[]>(this);
|
|
602
|
+
private _metadataChanged = new Signal<InputModel, IMessageMetadata>(this);
|
|
506
603
|
private _onDisposed = new Signal<InputModel, void>(this);
|
|
507
604
|
private _isDisposed = false;
|
|
508
605
|
}
|
|
@@ -519,7 +616,7 @@ export namespace InputModel {
|
|
|
519
616
|
* @param content - the content of the message.
|
|
520
617
|
* @param model - the model of the input sending the message.
|
|
521
618
|
*/
|
|
522
|
-
onSend: (
|
|
619
|
+
onSend: (message: INewMessage) => void;
|
|
523
620
|
|
|
524
621
|
/**
|
|
525
622
|
* Function that should cancel the message edition.
|
|
@@ -541,6 +638,11 @@ export namespace InputModel {
|
|
|
541
638
|
*/
|
|
542
639
|
mentions?: IUser[];
|
|
543
640
|
|
|
641
|
+
/**
|
|
642
|
+
* The initial metadata.
|
|
643
|
+
*/
|
|
644
|
+
metadata?: IMessageMetadata;
|
|
645
|
+
|
|
544
646
|
/**
|
|
545
647
|
* The current cursor index.
|
|
546
648
|
* This refers to the index of the character in front of the cursor.
|
package/src/model.ts
CHANGED
|
@@ -264,7 +264,7 @@ export abstract class AbstractChatModel implements IChatModel {
|
|
|
264
264
|
config: {
|
|
265
265
|
sendWithShiftEnter: config.sendWithShiftEnter
|
|
266
266
|
},
|
|
267
|
-
onSend:
|
|
267
|
+
onSend: this.sendMessage.bind(this)
|
|
268
268
|
});
|
|
269
269
|
|
|
270
270
|
this._commands = options.commands;
|
package/src/tokens.ts
CHANGED
|
@@ -3,24 +3,40 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IWidgetTracker
|
|
6
|
+
import { IWidgetTracker } from '@jupyterlab/apputils';
|
|
7
7
|
import { Token } from '@lumino/coreutils';
|
|
8
8
|
import { Widget } from '@lumino/widgets';
|
|
9
9
|
|
|
10
10
|
import { ChatWidget, Placeholder } from './widgets';
|
|
11
11
|
import { IChatModel } from './model';
|
|
12
|
+
import { ChatArea } from './types';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* The main area
|
|
15
|
+
* The interface for any widget displaying a chat (main area or side panel).
|
|
15
16
|
*/
|
|
16
|
-
export
|
|
17
|
+
export interface IChatPanel extends Widget {
|
|
18
|
+
/**
|
|
19
|
+
* The chat widget embedded in the panel.
|
|
20
|
+
*/
|
|
21
|
+
widget: ChatWidget;
|
|
22
|
+
/**
|
|
23
|
+
* The model of the chat widget.
|
|
24
|
+
*/
|
|
17
25
|
model: IChatModel;
|
|
18
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The area of the panel.
|
|
28
|
+
*/
|
|
29
|
+
area: ChatArea;
|
|
30
|
+
/**
|
|
31
|
+
* The chat panel toolbar.
|
|
32
|
+
*/
|
|
33
|
+
toolbar: Widget;
|
|
34
|
+
}
|
|
19
35
|
|
|
20
36
|
/**
|
|
21
37
|
* the chat tracker type.
|
|
22
38
|
*/
|
|
23
|
-
export type IChatTracker = IWidgetTracker<
|
|
39
|
+
export type IChatTracker = IWidgetTracker<IChatPanel>;
|
|
24
40
|
|
|
25
41
|
/**
|
|
26
42
|
* A chat tracker token.
|
package/src/types.ts
CHANGED
|
@@ -174,10 +174,12 @@ export interface IChatHistory {
|
|
|
174
174
|
/**
|
|
175
175
|
* The content of a new message.
|
|
176
176
|
*/
|
|
177
|
-
export
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
export type INewMessage<T = IUser, U = IAttachment> = Partial<
|
|
178
|
+
Pick<
|
|
179
|
+
IMessageContent<T, U>,
|
|
180
|
+
'body' | 'attachments' | 'mentions' | 'metadata' | 'mime_model' | 'sender'
|
|
181
|
+
>
|
|
182
|
+
>;
|
|
181
183
|
|
|
182
184
|
/**
|
|
183
185
|
* The attachment type. Jupyter Chat allows for two types of attachments
|
package/src/utils.ts
CHANGED
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { CommandToolbarButton, ToolbarRegistry } from '@jupyterlab/apputils';
|
|
6
7
|
import { CodeEditor } from '@jupyterlab/codeeditor';
|
|
7
8
|
import { CodeMirrorEditor } from '@jupyterlab/codemirror';
|
|
8
9
|
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
9
10
|
import { FileEditor } from '@jupyterlab/fileeditor';
|
|
10
11
|
import { Notebook } from '@jupyterlab/notebook';
|
|
12
|
+
import { IObservableList, ObservableList } from '@jupyterlab/observables';
|
|
13
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
11
14
|
import { Widget } from '@lumino/widgets';
|
|
12
15
|
|
|
16
|
+
import { IChatPanel } from './tokens';
|
|
13
17
|
import { IUser } from './types';
|
|
14
18
|
|
|
15
19
|
const MENTION_CLASS = 'jp-chat-mention';
|
|
@@ -81,3 +85,58 @@ export function replaceSpanToMention(content: string, user: IUser): string {
|
|
|
81
85
|
const regex = new RegExp(mentionEl, 'g');
|
|
82
86
|
return content.replace(regex, mention);
|
|
83
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Wraps a toolbar factory so that every CommandToolbarButton it creates
|
|
91
|
+
* automatically receives the panel's area as an arg.
|
|
92
|
+
* This lets commands branch on `args.area` without each plugin having to
|
|
93
|
+
* register a per-item toolbarRegistry.addFactory call.
|
|
94
|
+
*/
|
|
95
|
+
export function injectAreaArg(
|
|
96
|
+
baseFactory: (
|
|
97
|
+
panel: IChatPanel
|
|
98
|
+
) => IObservableList<ToolbarRegistry.IToolbarItem>,
|
|
99
|
+
commands: CommandRegistry
|
|
100
|
+
): (panel: IChatPanel) => IObservableList<ToolbarRegistry.IToolbarItem> {
|
|
101
|
+
return (panel: IChatPanel) => {
|
|
102
|
+
const base = baseFactory(panel);
|
|
103
|
+
|
|
104
|
+
const inject = (
|
|
105
|
+
item: ToolbarRegistry.IToolbarItem
|
|
106
|
+
): ToolbarRegistry.IToolbarItem => {
|
|
107
|
+
const { widget } = item;
|
|
108
|
+
if (!(widget instanceof CommandToolbarButton)) {
|
|
109
|
+
return item;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
name: item.name,
|
|
113
|
+
widget: new CommandToolbarButton({
|
|
114
|
+
commands,
|
|
115
|
+
id: widget.commandId,
|
|
116
|
+
args: { area: panel.area }
|
|
117
|
+
})
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const wrapped = new ObservableList<ToolbarRegistry.IToolbarItem>({
|
|
122
|
+
values: Array.from({ length: base.length }, (_, i) => inject(base.get(i)))
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
base.changed.connect((_, change) => {
|
|
126
|
+
if (change.type === 'add') {
|
|
127
|
+
wrapped.insertAll(change.newIndex, change.newValues.map(inject));
|
|
128
|
+
} else if (change.type === 'remove') {
|
|
129
|
+
wrapped.removeRange(
|
|
130
|
+
change.oldIndex,
|
|
131
|
+
change.oldIndex + change.oldValues.length
|
|
132
|
+
);
|
|
133
|
+
} else if (change.type === 'set') {
|
|
134
|
+
change.newValues.forEach((item, i) =>
|
|
135
|
+
wrapped.set(change.newIndex + i, inject(item))
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return wrapped;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* Originally adapted from jupyterlab-chat's ChatPanel
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { InputDialog } from '@jupyterlab/apputils';
|
|
11
|
+
import { InputDialog, ToolbarRegistry } from '@jupyterlab/apputils';
|
|
12
|
+
import { IObservableList } from '@jupyterlab/observables';
|
|
12
13
|
import { nullTranslator, TranslationBundle } from '@jupyterlab/translation';
|
|
13
14
|
import {
|
|
14
15
|
addIcon,
|
|
15
16
|
closeIcon,
|
|
16
|
-
launchIcon,
|
|
17
17
|
PanelWithToolbar,
|
|
18
18
|
ReactiveToolbar,
|
|
19
19
|
ReactWidget,
|
|
@@ -37,9 +37,10 @@ import {
|
|
|
37
37
|
IInputToolbarRegistryFactory
|
|
38
38
|
} from '../components';
|
|
39
39
|
import { TRANSLATION_DOMAIN } from '../context';
|
|
40
|
-
import { chatIcon
|
|
40
|
+
import { chatIcon } from '../icons';
|
|
41
41
|
import { IChatModel } from '../model';
|
|
42
|
-
import { IChatPlaceholderFactory } from '../tokens';
|
|
42
|
+
import { IChatPanel, IChatPlaceholderFactory } from '../tokens';
|
|
43
|
+
import { ChatArea } from '../types';
|
|
43
44
|
|
|
44
45
|
const SIDEPANEL_CLASS = 'jp-chat-sidepanel';
|
|
45
46
|
const ADD_BUTTON_CLASS = 'jp-chat-add';
|
|
@@ -86,10 +87,10 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
86
87
|
|
|
87
88
|
this._chatOptions = options;
|
|
88
89
|
this._inputToolbarFactory = options.inputToolbarFactory;
|
|
90
|
+
this._chatToolbarFactory = options.chatToolbarFactory;
|
|
89
91
|
|
|
90
92
|
this._getChatNames = options.getChatNames;
|
|
91
93
|
this._createModel = options.createModel;
|
|
92
|
-
this._openInMain = options.openInMain;
|
|
93
94
|
this._renameChat = options.renameChat;
|
|
94
95
|
this._placeholderFactory = options.placeholderFactory;
|
|
95
96
|
|
|
@@ -149,9 +150,9 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
/**
|
|
152
|
-
* A signal emitting when a chat
|
|
153
|
+
* A signal emitting when a chat panel is opened in the sidepanel.
|
|
153
154
|
*/
|
|
154
|
-
get chatOpened(): ISignal<MultiChatPanel,
|
|
155
|
+
get chatOpened(): ISignal<MultiChatPanel, IChatPanel> {
|
|
155
156
|
return this._chatOpened;
|
|
156
157
|
}
|
|
157
158
|
|
|
@@ -293,8 +294,8 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
293
294
|
const widget = new SidePanelWidget({
|
|
294
295
|
widget: chatWidget,
|
|
295
296
|
displayName: name,
|
|
296
|
-
openInMain: this._openInMain,
|
|
297
297
|
renameChat: this._renameChat,
|
|
298
|
+
toolbarFactory: this._chatToolbarFactory,
|
|
298
299
|
onClose: (name: string, disposeModel = true) => {
|
|
299
300
|
this.unsetLoadedModel(name, disposeModel);
|
|
300
301
|
},
|
|
@@ -313,7 +314,7 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
313
314
|
this._chatSelectorPopup.setCurrentChat(name);
|
|
314
315
|
}
|
|
315
316
|
|
|
316
|
-
this._chatOpened.emit(
|
|
317
|
+
this._chatOpened.emit(widget);
|
|
317
318
|
return chatWidget;
|
|
318
319
|
}
|
|
319
320
|
|
|
@@ -437,20 +438,22 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
437
438
|
this._chatSelectorPopup?.hide();
|
|
438
439
|
};
|
|
439
440
|
|
|
440
|
-
private _chatOpened = new Signal<MultiChatPanel,
|
|
441
|
+
private _chatOpened = new Signal<MultiChatPanel, IChatPanel>(this);
|
|
441
442
|
private _chatNamesChanged = new Signal<
|
|
442
443
|
MultiChatPanel,
|
|
443
444
|
{ [name: string]: string }
|
|
444
445
|
>(this);
|
|
445
446
|
private _chatOptions: Omit<Chat.IOptions, 'model' | 'inputToolbarRegistry'>;
|
|
446
447
|
private _inputToolbarFactory?: IInputToolbarRegistryFactory;
|
|
448
|
+
private _chatToolbarFactory?: (
|
|
449
|
+
panel: IChatPanel
|
|
450
|
+
) => IObservableList<ToolbarRegistry.IToolbarItem>;
|
|
447
451
|
private _updateChatListDebouncer: Debouncer;
|
|
448
452
|
|
|
449
453
|
private _createModel?: (
|
|
450
454
|
name?: string
|
|
451
455
|
) => Promise<MultiChatPanel.IOpenChatArgs>;
|
|
452
456
|
private _getChatNames?: () => Promise<{ [name: string]: string }>;
|
|
453
|
-
private _openInMain?: (name: string) => Promise<boolean>;
|
|
454
457
|
private _renameChat?: boolean | ((oldName: string) => Promise<string | null>);
|
|
455
458
|
private _placeholderFactory?: IChatPlaceholderFactory;
|
|
456
459
|
private _openChatWidget?: ReactWidget;
|
|
@@ -476,6 +479,12 @@ export namespace MultiChatPanel {
|
|
|
476
479
|
* The input toolbar factory;
|
|
477
480
|
*/
|
|
478
481
|
inputToolbarFactory?: IInputToolbarRegistryFactory;
|
|
482
|
+
/**
|
|
483
|
+
* An optional toolbar factory for each opened chat.
|
|
484
|
+
*/
|
|
485
|
+
chatToolbarFactory?: (
|
|
486
|
+
panel: IChatPanel
|
|
487
|
+
) => IObservableList<ToolbarRegistry.IToolbarItem>;
|
|
479
488
|
/**
|
|
480
489
|
* An optional callback to create a chat model.
|
|
481
490
|
*
|
|
@@ -489,12 +498,6 @@ export namespace MultiChatPanel {
|
|
|
489
498
|
* @returns an object mapping chat display names to identifiers.
|
|
490
499
|
*/
|
|
491
500
|
getChatNames?: () => Promise<{ [name: string]: string }>;
|
|
492
|
-
/**
|
|
493
|
-
* An optional callback to open the chat in the main area.
|
|
494
|
-
*
|
|
495
|
-
* @param name - the name of the chat to move.
|
|
496
|
-
*/
|
|
497
|
-
openInMain?: (name: string) => Promise<boolean>;
|
|
498
501
|
/**
|
|
499
502
|
* An optional callback to rename a chat.
|
|
500
503
|
*
|
|
@@ -531,7 +534,7 @@ export namespace MultiChatPanel {
|
|
|
531
534
|
/**
|
|
532
535
|
* A widget containing the chat and its toolbar.
|
|
533
536
|
*/
|
|
534
|
-
class SidePanelWidget extends ReactivePanelWithToolbar {
|
|
537
|
+
class SidePanelWidget extends ReactivePanelWithToolbar implements IChatPanel {
|
|
535
538
|
constructor(options: SidePanelWidget.IOptions) {
|
|
536
539
|
super();
|
|
537
540
|
this._chatWidget = options.widget;
|
|
@@ -557,19 +560,6 @@ class SidePanelWidget extends ReactivePanelWithToolbar {
|
|
|
557
560
|
// Add the chat widget
|
|
558
561
|
this.addWidget(this._chatWidget);
|
|
559
562
|
|
|
560
|
-
// Add toolbar buttons
|
|
561
|
-
this._markAsRead = new ToolbarButton({
|
|
562
|
-
icon: readIcon,
|
|
563
|
-
iconLabel: trans.__('Mark chat as read'),
|
|
564
|
-
className: 'jp-mod-styled',
|
|
565
|
-
onClick: () => {
|
|
566
|
-
if (this.model) {
|
|
567
|
-
this.model.unreadMessages = [];
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
});
|
|
571
|
-
this.toolbar.addItem('markRead', this._markAsRead);
|
|
572
|
-
|
|
573
563
|
if (options.renameChat) {
|
|
574
564
|
const renameButton = new ToolbarButton({
|
|
575
565
|
iconClass: 'jp-EditIcon',
|
|
@@ -606,21 +596,6 @@ class SidePanelWidget extends ReactivePanelWithToolbar {
|
|
|
606
596
|
this.toolbar.addItem('rename', renameButton);
|
|
607
597
|
}
|
|
608
598
|
|
|
609
|
-
if (options.openInMain) {
|
|
610
|
-
const moveToMain = new ToolbarButton({
|
|
611
|
-
icon: launchIcon,
|
|
612
|
-
iconLabel: trans.__('Move the chat to the main area'),
|
|
613
|
-
className: 'jp-mod-styled',
|
|
614
|
-
onClick: async () => {
|
|
615
|
-
const name = this.model.name;
|
|
616
|
-
if (await options.openInMain?.(name)) {
|
|
617
|
-
options.onClose(this._displayName, false);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
});
|
|
621
|
-
this.toolbar.addItem('moveMain', moveToMain);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
599
|
const closeButton = new ToolbarButton({
|
|
625
600
|
icon: closeIcon,
|
|
626
601
|
iconLabel: trans.__('Close the chat'),
|
|
@@ -631,9 +606,24 @@ class SidePanelWidget extends ReactivePanelWithToolbar {
|
|
|
631
606
|
});
|
|
632
607
|
this.toolbar.addItem('close', closeButton);
|
|
633
608
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
609
|
+
if (options.toolbarFactory) {
|
|
610
|
+
const items = options.toolbarFactory(this);
|
|
611
|
+
for (let i = 0; i < items.length; i++) {
|
|
612
|
+
const { name, widget } = items.get(i);
|
|
613
|
+
this.toolbar.insertBefore('close', name, widget);
|
|
614
|
+
}
|
|
615
|
+
items.changed.connect((_, change) => {
|
|
616
|
+
if (change.type === 'add') {
|
|
617
|
+
for (const { name, widget } of change.newValues) {
|
|
618
|
+
this.toolbar.insertBefore('close', name, widget);
|
|
619
|
+
}
|
|
620
|
+
} else if (change.type === 'remove') {
|
|
621
|
+
for (const { widget } of change.oldValues) {
|
|
622
|
+
widget.dispose();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
637
627
|
}
|
|
638
628
|
|
|
639
629
|
protected onAfterAttach(msg: Message): void {
|
|
@@ -646,6 +636,13 @@ class SidePanelWidget extends ReactivePanelWithToolbar {
|
|
|
646
636
|
this._updateReactiveToolbar();
|
|
647
637
|
}
|
|
648
638
|
|
|
639
|
+
/**
|
|
640
|
+
* The area of the widget.
|
|
641
|
+
*/
|
|
642
|
+
get area(): ChatArea {
|
|
643
|
+
return 'sidebar';
|
|
644
|
+
}
|
|
645
|
+
|
|
649
646
|
/**
|
|
650
647
|
* The chat widget embedded in the sidepanel widget.
|
|
651
648
|
*/
|
|
@@ -687,10 +684,6 @@ class SidePanelWidget extends ReactivePanelWithToolbar {
|
|
|
687
684
|
* Dispose of the resources held by the widget.
|
|
688
685
|
*/
|
|
689
686
|
dispose(): void {
|
|
690
|
-
const model = this.model;
|
|
691
|
-
if (model) {
|
|
692
|
-
model.unreadChanged?.disconnect(this._unreadChanged);
|
|
693
|
-
}
|
|
694
687
|
super.dispose();
|
|
695
688
|
}
|
|
696
689
|
|
|
@@ -716,13 +709,6 @@ class SidePanelWidget extends ReactivePanelWithToolbar {
|
|
|
716
709
|
this.toolbar.insertItem(0, 'title', this._titleWidget);
|
|
717
710
|
}
|
|
718
711
|
|
|
719
|
-
/**
|
|
720
|
-
* Enable/disable unread icon.
|
|
721
|
-
*/
|
|
722
|
-
private _unreadChanged = (_: IChatModel, unread: number[]) => {
|
|
723
|
-
this._markAsRead.enabled = unread.length > 0;
|
|
724
|
-
};
|
|
725
|
-
|
|
726
712
|
/**
|
|
727
713
|
* Trigger reactive toolbar overflow computation from rendered toolbar size.
|
|
728
714
|
*/
|
|
@@ -739,7 +725,6 @@ class SidePanelWidget extends ReactivePanelWithToolbar {
|
|
|
739
725
|
}
|
|
740
726
|
|
|
741
727
|
private _chatWidget: ChatWidget;
|
|
742
|
-
private _markAsRead: ToolbarButton;
|
|
743
728
|
private _displayName: string;
|
|
744
729
|
private _titleWidget: Widget | undefined;
|
|
745
730
|
private _nameChanged = new Signal<
|
|
@@ -768,14 +753,16 @@ namespace SidePanelWidget {
|
|
|
768
753
|
* The displayed name of the chat.
|
|
769
754
|
*/
|
|
770
755
|
displayName?: string;
|
|
771
|
-
/**
|
|
772
|
-
* The callback to open the chat in main area.
|
|
773
|
-
*/
|
|
774
|
-
openInMain?: (name: string) => Promise<boolean>;
|
|
775
756
|
/**
|
|
776
757
|
* The callback to rename the chat.
|
|
777
758
|
*/
|
|
778
759
|
renameChat?: boolean | ((oldName: string) => Promise<string | null>);
|
|
760
|
+
/**
|
|
761
|
+
* An optional toolbar factory.
|
|
762
|
+
*/
|
|
763
|
+
toolbarFactory?: (
|
|
764
|
+
panel: IChatPanel
|
|
765
|
+
) => IObservableList<ToolbarRegistry.IToolbarItem>;
|
|
779
766
|
/**
|
|
780
767
|
* The translation bundle.
|
|
781
768
|
*/
|
|
@@ -795,7 +782,7 @@ type ChatSearchInputProps = {
|
|
|
795
782
|
/**
|
|
796
783
|
* Signal emitting when a chat is opened.
|
|
797
784
|
*/
|
|
798
|
-
chatOpened: ISignal<MultiChatPanel,
|
|
785
|
+
chatOpened: ISignal<MultiChatPanel, IChatPanel>;
|
|
799
786
|
/**
|
|
800
787
|
* The translation bundle.
|
|
801
788
|
*/
|