@jupyter/chat 0.20.0-alpha.2 → 0.20.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/widgets/chat-selector-popup.d.ts +102 -0
- package/lib/widgets/chat-selector-popup.js +293 -0
- package/lib/widgets/chat-widget.js +1 -0
- package/lib/widgets/index.d.ts +1 -0
- package/lib/widgets/index.js +1 -0
- package/lib/widgets/multichat-panel.d.ts +100 -73
- package/lib/widgets/multichat-panel.js +356 -159
- package/package.json +1 -1
- package/src/widgets/chat-selector-popup.tsx +440 -0
- package/src/widgets/chat-widget.tsx +1 -0
- package/src/widgets/index.ts +1 -0
- package/src/widgets/multichat-panel.tsx +434 -207
- package/style/base.css +1 -0
- package/style/chat-selector.css +161 -0
- package/style/chat.css +34 -2
|
@@ -7,38 +7,90 @@
|
|
|
7
7
|
* Originally adapted from jupyterlab-chat's ChatPanel
|
|
8
8
|
*/
|
|
9
9
|
import { InputDialog } from '@jupyterlab/apputils';
|
|
10
|
-
import { addIcon, closeIcon,
|
|
10
|
+
import { addIcon, closeIcon, launchIcon, PanelWithToolbar, ReactWidget, Spinner, ToolbarButton } from '@jupyterlab/ui-components';
|
|
11
|
+
import { ArrayExt } from '@lumino/algorithm';
|
|
11
12
|
import { Debouncer } from '@lumino/polling';
|
|
12
13
|
import { Signal } from '@lumino/signaling';
|
|
13
|
-
import
|
|
14
|
+
import { Widget } from '@lumino/widgets';
|
|
15
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
14
16
|
import { ChatWidget } from './chat-widget';
|
|
17
|
+
import { ChatSelectorPopup } from './chat-selector-popup';
|
|
15
18
|
import { chatIcon, readIcon } from '../icons';
|
|
16
19
|
const SIDEPANEL_CLASS = 'jp-chat-sidepanel';
|
|
17
20
|
const ADD_BUTTON_CLASS = 'jp-chat-add';
|
|
18
21
|
const OPEN_SELECT_CLASS = 'jp-chat-open';
|
|
19
|
-
const
|
|
20
|
-
const TOOLBAR_CLASS = 'jp-chat-
|
|
22
|
+
const SIDEPANEL_WIDGET_CLASS = 'jp-chat-sidepanel-widget';
|
|
23
|
+
const TOOLBAR_CLASS = 'jp-chat-sidepanel-widget-toolbar';
|
|
21
24
|
/**
|
|
22
25
|
* Generic sidepanel widget including multiple chats and the add chat button.
|
|
23
26
|
*/
|
|
24
|
-
export class MultiChatPanel extends
|
|
27
|
+
export class MultiChatPanel extends PanelWithToolbar {
|
|
28
|
+
/**
|
|
29
|
+
* The constructor of the multichat panel.
|
|
30
|
+
*/
|
|
25
31
|
constructor(options) {
|
|
26
32
|
super(options);
|
|
33
|
+
/**
|
|
34
|
+
* Invoke the update of the list of available chats.
|
|
35
|
+
*/
|
|
36
|
+
this.updateChatList = () => {
|
|
37
|
+
this._updateChatListDebouncer.invoke();
|
|
38
|
+
};
|
|
27
39
|
/**
|
|
28
40
|
* Update the list of available chats.
|
|
29
41
|
*/
|
|
30
42
|
this._updateChatList = async () => {
|
|
31
|
-
var _a;
|
|
43
|
+
var _a, _b;
|
|
32
44
|
try {
|
|
33
45
|
const chatNames = await ((_a = this._getChatNames) === null || _a === void 0 ? void 0 : _a.call(this));
|
|
34
|
-
|
|
46
|
+
if (!ArrayExt.shallowEqual(Object.keys(chatNames !== null && chatNames !== void 0 ? chatNames : {}), Object.keys(this._chatNames))) {
|
|
47
|
+
this._chatNames = chatNames !== null && chatNames !== void 0 ? chatNames : {};
|
|
48
|
+
(_b = this._chatSelectorPopup) === null || _b === void 0 ? void 0 : _b.updateChats(Object.keys(this._chatNames));
|
|
49
|
+
}
|
|
35
50
|
}
|
|
36
51
|
catch (e) {
|
|
37
52
|
console.error('Error getting chat files', e);
|
|
38
53
|
}
|
|
39
54
|
};
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Update loaded model when the current widget updates its name.
|
|
57
|
+
*/
|
|
58
|
+
this._modelNameChanged = (_, change) => {
|
|
59
|
+
var _a, _b, _c;
|
|
60
|
+
const model = this.getLoadedModel(change.old);
|
|
61
|
+
if (model) {
|
|
62
|
+
this._loadedModels.set(change.new, model);
|
|
63
|
+
this._loadedModels.delete(change.old);
|
|
64
|
+
(_a = this._chatSelectorPopup) === null || _a === void 0 ? void 0 : _a.setLoadedModels(this.getLoadedModelNames());
|
|
65
|
+
if (((_b = this._currentWidget) === null || _b === void 0 ? void 0 : _b.model.name) === model.name) {
|
|
66
|
+
(_c = this._chatSelectorPopup) === null || _c === void 0 ? void 0 : _c.setCurrentChat(change.new);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Handle chat selection from the popup.
|
|
72
|
+
*/
|
|
73
|
+
this._onSelectChat = async (name) => {
|
|
74
|
+
var _a;
|
|
75
|
+
// Check if model is already loaded
|
|
76
|
+
let openChatArgs = {
|
|
77
|
+
model: this.getLoadedModel(name),
|
|
78
|
+
displayName: name
|
|
79
|
+
};
|
|
80
|
+
// If not, create the model.
|
|
81
|
+
if (!openChatArgs.model && this._createModel) {
|
|
82
|
+
const chatID = this._chatNames[name];
|
|
83
|
+
openChatArgs = await this._createModel(chatID);
|
|
84
|
+
}
|
|
85
|
+
if (openChatArgs.model) {
|
|
86
|
+
this.open(openChatArgs);
|
|
87
|
+
}
|
|
88
|
+
(_a = this._chatSelectorPopup) === null || _a === void 0 ? void 0 : _a.hide();
|
|
89
|
+
};
|
|
90
|
+
this._chatOpened = new Signal(this);
|
|
91
|
+
this._loadedModels = new Map();
|
|
92
|
+
this._chatNames = {};
|
|
93
|
+
this._visibilityChanged = new Signal(this);
|
|
42
94
|
this.id = 'jupyter-chat::multi-chat-panel';
|
|
43
95
|
this.title.icon = chatIcon;
|
|
44
96
|
this.title.caption = 'Jupyter Chat'; // TODO: i18n/
|
|
@@ -54,96 +106,173 @@ export class MultiChatPanel extends SidePanel {
|
|
|
54
106
|
const addChat = new ToolbarButton({
|
|
55
107
|
onClick: async () => {
|
|
56
108
|
const addChatArgs = await this._createModel();
|
|
57
|
-
this.
|
|
109
|
+
this.open(addChatArgs);
|
|
58
110
|
},
|
|
59
111
|
icon: addIcon,
|
|
60
|
-
|
|
61
|
-
tooltip: 'Add a new chat'
|
|
112
|
+
tooltip: 'Create a new chat'
|
|
62
113
|
});
|
|
63
114
|
addChat.addClass(ADD_BUTTON_CLASS);
|
|
64
115
|
this.toolbar.addItem('createChat', addChat);
|
|
65
116
|
}
|
|
66
117
|
if (this._getChatNames && this._createModel) {
|
|
67
|
-
// Chat
|
|
68
|
-
this._openChatWidget = ReactWidget.create(React.createElement(
|
|
118
|
+
// Chat selector with search input
|
|
119
|
+
this._openChatWidget = ReactWidget.create(React.createElement(ChatSearchInput, { selectChat: this._onSelectChat, getPopup: () => this._chatSelectorPopup, chatOpened: this._chatOpened }));
|
|
69
120
|
this._openChatWidget.addClass(OPEN_SELECT_CLASS);
|
|
70
121
|
this.toolbar.addItem('openChat', this._openChatWidget);
|
|
122
|
+
// Create the popup widget (attached to document body)
|
|
123
|
+
this._chatSelectorPopup = new ChatSelectorPopup({
|
|
124
|
+
chatNames: [],
|
|
125
|
+
onSelect: this._onSelectChat,
|
|
126
|
+
onClose: (name) => {
|
|
127
|
+
this.disposeLoadedModel(name);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
71
130
|
}
|
|
72
|
-
|
|
73
|
-
|
|
131
|
+
// Insert the toolbar as first child.
|
|
132
|
+
this.insertWidget(0, this.toolbar);
|
|
74
133
|
this._updateChatListDebouncer = new Debouncer(this._updateChatList, 200);
|
|
75
134
|
}
|
|
76
135
|
/**
|
|
77
|
-
* The
|
|
136
|
+
* The currently displayed chat widget.
|
|
137
|
+
*/
|
|
138
|
+
get current() {
|
|
139
|
+
return this._currentWidget;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* A signal emitting when a chat widget is opened in the panel.
|
|
78
143
|
*/
|
|
79
|
-
get
|
|
80
|
-
return this.
|
|
144
|
+
get chatOpened() {
|
|
145
|
+
return this._chatOpened;
|
|
81
146
|
}
|
|
82
147
|
/**
|
|
83
|
-
* A signal emitting when
|
|
148
|
+
* A signal emitting when the panel visibility changed.
|
|
84
149
|
*/
|
|
85
|
-
get
|
|
86
|
-
return this.
|
|
150
|
+
get visibilityChanged() {
|
|
151
|
+
return this._visibilityChanged;
|
|
87
152
|
}
|
|
88
153
|
/**
|
|
89
|
-
* Add a
|
|
154
|
+
* Add a chat to the panel by creating or showing its widget.
|
|
90
155
|
*
|
|
91
|
-
* @param
|
|
92
|
-
* @param displayName - the name of the chat.
|
|
156
|
+
* @param args - the chat args including model and display name.
|
|
93
157
|
*/
|
|
94
|
-
|
|
95
|
-
|
|
158
|
+
open(args) {
|
|
159
|
+
var _a, _b;
|
|
160
|
+
const { model } = args;
|
|
96
161
|
if (!model) {
|
|
97
162
|
return;
|
|
98
163
|
}
|
|
99
|
-
|
|
164
|
+
const displayName = (_a = args.displayName) !== null && _a !== void 0 ? _a : model.name;
|
|
165
|
+
// Add model to loaded models
|
|
166
|
+
if (!this._loadedModels.has(displayName)) {
|
|
167
|
+
this._loadedModels.set(displayName, model);
|
|
168
|
+
(_b = this._chatSelectorPopup) === null || _b === void 0 ? void 0 : _b.setLoadedModels(this.getLoadedModelNames());
|
|
169
|
+
}
|
|
170
|
+
// Open this chat (will create widget)
|
|
171
|
+
return this._open(displayName);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get a loaded model by name, or undefined if not loaded.
|
|
175
|
+
*/
|
|
176
|
+
getLoadedModel(name) {
|
|
177
|
+
return this._loadedModels.get(name);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get all loaded model names.
|
|
181
|
+
*/
|
|
182
|
+
getLoadedModelNames() {
|
|
183
|
+
return Array.from(this._loadedModels.keys());
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Dispose a model, removing it from loaded models.
|
|
187
|
+
*/
|
|
188
|
+
disposeLoadedModel(name) {
|
|
189
|
+
var _a, _b;
|
|
190
|
+
const model = this._loadedModels.get(name);
|
|
191
|
+
if (model) {
|
|
192
|
+
// If this is the currently displayed chat, remove it.
|
|
193
|
+
if (((_a = this._currentWidget) === null || _a === void 0 ? void 0 : _a.model) === model) {
|
|
194
|
+
this._currentWidget.nameChanged.disconnect(this._modelNameChanged);
|
|
195
|
+
this._currentWidget.dispose();
|
|
196
|
+
this._currentWidget = undefined;
|
|
197
|
+
// Clear current chat in selector
|
|
198
|
+
if (this._chatSelectorPopup) {
|
|
199
|
+
this._chatSelectorPopup.setCurrentChat(null);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
model.dispose();
|
|
203
|
+
this._loadedModels.delete(name);
|
|
204
|
+
(_b = this._chatSelectorPopup) === null || _b === void 0 ? void 0 : _b.setLoadedModels(this.getLoadedModelNames());
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Emit a signal when the panel visibility changed.
|
|
209
|
+
*/
|
|
210
|
+
onAfterShow(msg) {
|
|
211
|
+
this._visibilityChanged.emit(true);
|
|
212
|
+
}
|
|
213
|
+
onBeforeHide(msg) {
|
|
214
|
+
this._visibilityChanged.emit(false);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Open a specific chat by name, creating a new sidepanel widget.
|
|
218
|
+
*/
|
|
219
|
+
_open(name) {
|
|
220
|
+
const model = this._loadedModels.get(name);
|
|
221
|
+
if (!model) {
|
|
100
222
|
return;
|
|
101
223
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
224
|
+
// Dispose current chat widget if any
|
|
225
|
+
if (this._currentWidget) {
|
|
226
|
+
this._currentWidget.nameChanged.disconnect(this._modelNameChanged);
|
|
227
|
+
this._currentWidget.dispose();
|
|
105
228
|
}
|
|
106
229
|
// Create the toolbar registry.
|
|
107
230
|
let inputToolbarRegistry;
|
|
108
231
|
if (this._inputToolbarFactory) {
|
|
109
232
|
inputToolbarRegistry = this._inputToolbarFactory.create();
|
|
110
233
|
}
|
|
111
|
-
// Create a new widget
|
|
112
|
-
const
|
|
234
|
+
// Create a new widget for this model
|
|
235
|
+
const chatWidget = new ChatWidget({
|
|
113
236
|
model,
|
|
114
237
|
...this._chatOptions,
|
|
115
238
|
inputToolbarRegistry,
|
|
116
239
|
area: 'sidebar'
|
|
117
240
|
});
|
|
118
|
-
|
|
119
|
-
|
|
241
|
+
// Create a chat with toolbar
|
|
242
|
+
const widget = new SidePanelWidget({
|
|
243
|
+
widget: chatWidget,
|
|
244
|
+
displayName: name,
|
|
120
245
|
openInMain: this._openInMain,
|
|
121
246
|
renameChat: this._renameChat,
|
|
122
|
-
|
|
247
|
+
onClose: (name) => {
|
|
248
|
+
this.disposeLoadedModel(name);
|
|
249
|
+
}
|
|
123
250
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.
|
|
251
|
+
// Add to content panel
|
|
252
|
+
this.addWidget(widget);
|
|
253
|
+
this.update();
|
|
254
|
+
this._currentWidget = widget;
|
|
255
|
+
this._currentWidget.nameChanged.connect(this._modelNameChanged);
|
|
256
|
+
// Update selector to show current chat
|
|
257
|
+
if (this._chatSelectorPopup) {
|
|
258
|
+
this._chatSelectorPopup.setCurrentChat(name);
|
|
259
|
+
}
|
|
260
|
+
this._chatOpened.emit(chatWidget);
|
|
261
|
+
return chatWidget;
|
|
134
262
|
}
|
|
135
263
|
/**
|
|
136
|
-
* Open a chat if
|
|
264
|
+
* Open a chat if its model is already loaded.
|
|
137
265
|
*
|
|
138
266
|
* @param name - the name of the chat.
|
|
139
|
-
* @returns a boolean, whether the chat
|
|
267
|
+
* @returns a boolean, whether the chat model was already loaded or not.
|
|
140
268
|
*/
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
this.
|
|
269
|
+
openIfLoaded(name) {
|
|
270
|
+
const model = this._loadedModels.get(name);
|
|
271
|
+
if (model) {
|
|
272
|
+
this._open(name);
|
|
273
|
+
return true;
|
|
145
274
|
}
|
|
146
|
-
return
|
|
275
|
+
return false;
|
|
147
276
|
}
|
|
148
277
|
/**
|
|
149
278
|
* A message handler invoked on an `'after-attach'` message.
|
|
@@ -151,82 +280,65 @@ export class MultiChatPanel extends SidePanel {
|
|
|
151
280
|
onAfterAttach() {
|
|
152
281
|
var _a, _b;
|
|
153
282
|
(_b = (_a = this._openChatWidget) === null || _a === void 0 ? void 0 : _a.renderPromise) === null || _b === void 0 ? void 0 : _b.then(() => this.updateChatList());
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
*
|
|
158
|
-
* @param name - the chat name.
|
|
159
|
-
*/
|
|
160
|
-
_getChatIndex(name) {
|
|
161
|
-
return this.sections.findIndex(section => { var _a; return ((_a = section.model) === null || _a === void 0 ? void 0 : _a.name) === name; });
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Expand the chat from its index.
|
|
165
|
-
*/
|
|
166
|
-
_expandChat(index) {
|
|
167
|
-
if (!this.widgets[index].isVisible) {
|
|
168
|
-
this.content.expand(index);
|
|
283
|
+
// Attach the popup to the document body
|
|
284
|
+
if (this._chatSelectorPopup && !this._chatSelectorPopup.isAttached) {
|
|
285
|
+
Widget.attach(this._chatSelectorPopup, document.body);
|
|
169
286
|
}
|
|
170
287
|
}
|
|
171
288
|
/**
|
|
172
|
-
*
|
|
289
|
+
* A message handler invoked on an `'before-detach'` message.
|
|
173
290
|
*/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
if (this._createModel) {
|
|
180
|
-
const addChatArgs = await this._createModel(selection);
|
|
181
|
-
this.addChat(addChatArgs);
|
|
291
|
+
onBeforeDetach() {
|
|
292
|
+
// Detach the popup
|
|
293
|
+
if (this._chatSelectorPopup && this._chatSelectorPopup.isAttached) {
|
|
294
|
+
Widget.detach(this._chatSelectorPopup);
|
|
182
295
|
}
|
|
183
|
-
event.target.selectedIndex = 0;
|
|
184
296
|
}
|
|
185
297
|
/**
|
|
186
|
-
*
|
|
187
|
-
* sections are closed.
|
|
298
|
+
* Dispose of the resources held by the widget.
|
|
188
299
|
*/
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
300
|
+
dispose() {
|
|
301
|
+
// Dispose all loaded models
|
|
302
|
+
for (const model of this._loadedModels.values()) {
|
|
303
|
+
model.dispose();
|
|
192
304
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
305
|
+
this._loadedModels.clear();
|
|
306
|
+
if (this._chatSelectorPopup) {
|
|
307
|
+
this._chatSelectorPopup.dispose();
|
|
308
|
+
this._chatSelectorPopup = undefined;
|
|
197
309
|
}
|
|
310
|
+
super.dispose();
|
|
198
311
|
}
|
|
199
312
|
}
|
|
200
313
|
/**
|
|
201
|
-
*
|
|
314
|
+
* A widget containing the chat and its toolbar.
|
|
202
315
|
*/
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Constructor of the chat section.
|
|
206
|
-
*/
|
|
316
|
+
class SidePanelWidget extends PanelWithToolbar {
|
|
207
317
|
constructor(options) {
|
|
208
|
-
var _a, _b, _c, _d
|
|
209
|
-
super(
|
|
318
|
+
var _a, _b, _c, _d;
|
|
319
|
+
super();
|
|
210
320
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* TODO: fix it upstream in @jupyterlab/ui-components.
|
|
214
|
-
* Updating the title create a new Title widget, but does not attach again the
|
|
215
|
-
* toolbar. The toolbar is attached only when the title widget is attached the first
|
|
216
|
-
* time.
|
|
321
|
+
* Enable/disable unread icon.
|
|
217
322
|
*/
|
|
218
323
|
this._unreadChanged = (_, unread) => {
|
|
219
324
|
this._markAsRead.enabled = unread.length > 0;
|
|
220
325
|
};
|
|
221
|
-
this.
|
|
326
|
+
this._nameChanged = new Signal(this);
|
|
222
327
|
this._chatWidget = options.widget;
|
|
223
|
-
this.
|
|
224
|
-
this.
|
|
225
|
-
this.addClass(SECTION_CLASS);
|
|
328
|
+
this._displayName = (_a = options.displayName) !== null && _a !== void 0 ? _a : options.widget.model.name;
|
|
329
|
+
this.addClass(SIDEPANEL_WIDGET_CLASS);
|
|
226
330
|
this.toolbar.addClass(TOOLBAR_CLASS);
|
|
227
|
-
this._displayName =
|
|
228
|
-
(_b = (_a = options.displayName) !== null && _a !== void 0 ? _a : options.widget.model.name) !== null && _b !== void 0 ? _b : 'Chat';
|
|
229
331
|
this._updateTitle();
|
|
332
|
+
this.addWidget(this.toolbar);
|
|
333
|
+
// Add spinner while loading
|
|
334
|
+
const spinner = new Spinner();
|
|
335
|
+
this.addWidget(spinner);
|
|
336
|
+
this._chatWidget.model.ready.then(() => {
|
|
337
|
+
spinner.dispose();
|
|
338
|
+
});
|
|
339
|
+
// Add the chat widget
|
|
340
|
+
this.addWidget(this._chatWidget);
|
|
341
|
+
// Add toolbar buttons
|
|
230
342
|
this._markAsRead = new ToolbarButton({
|
|
231
343
|
icon: readIcon,
|
|
232
344
|
iconLabel: 'Mark chat as read',
|
|
@@ -244,24 +356,32 @@ export class ChatSection extends PanelWithToolbar {
|
|
|
244
356
|
iconLabel: 'Rename chat',
|
|
245
357
|
className: 'jp-mod-styled',
|
|
246
358
|
onClick: async () => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const result = await InputDialog.getText({
|
|
250
|
-
title: 'Rename Chat',
|
|
251
|
-
text: this.model.name,
|
|
252
|
-
placeholder: 'new-name'
|
|
253
|
-
});
|
|
254
|
-
if (!result.button.accept) {
|
|
255
|
-
return; // user cancelled
|
|
359
|
+
if (!options.renameChat) {
|
|
360
|
+
return;
|
|
256
361
|
}
|
|
257
|
-
|
|
258
|
-
if (
|
|
259
|
-
|
|
362
|
+
let newName;
|
|
363
|
+
if (options.renameChat === true) {
|
|
364
|
+
// If rename chat is true, let's provide a input to select new name.
|
|
365
|
+
const result = await InputDialog.getText({
|
|
366
|
+
title: 'Rename Chat',
|
|
367
|
+
text: this.model.name,
|
|
368
|
+
placeholder: 'new-name'
|
|
369
|
+
});
|
|
370
|
+
if (!result.button.accept && result.value) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
newName = result.value;
|
|
374
|
+
if (newName) {
|
|
260
375
|
this.model.name = newName;
|
|
261
|
-
this._displayName = newName;
|
|
262
|
-
this._updateTitle();
|
|
263
376
|
}
|
|
264
377
|
}
|
|
378
|
+
else {
|
|
379
|
+
// Rename chat is a function, let's call it.
|
|
380
|
+
newName = await options.renameChat(this.model.name);
|
|
381
|
+
}
|
|
382
|
+
if (newName) {
|
|
383
|
+
this.name = newName;
|
|
384
|
+
}
|
|
265
385
|
}
|
|
266
386
|
});
|
|
267
387
|
this.toolbar.addItem('rename', renameButton);
|
|
@@ -275,8 +395,7 @@ export class ChatSection extends PanelWithToolbar {
|
|
|
275
395
|
var _a;
|
|
276
396
|
const name = this.model.name;
|
|
277
397
|
if (await ((_a = options.openInMain) === null || _a === void 0 ? void 0 : _a.call(options, name))) {
|
|
278
|
-
this.
|
|
279
|
-
this.dispose();
|
|
398
|
+
options.onClose(this._displayName);
|
|
280
399
|
}
|
|
281
400
|
}
|
|
282
401
|
});
|
|
@@ -287,33 +406,16 @@ export class ChatSection extends PanelWithToolbar {
|
|
|
287
406
|
iconLabel: 'Close the chat',
|
|
288
407
|
className: 'jp-mod-styled',
|
|
289
408
|
onClick: () => {
|
|
290
|
-
this.
|
|
291
|
-
this.dispose();
|
|
409
|
+
options.onClose(this._displayName);
|
|
292
410
|
}
|
|
293
411
|
});
|
|
294
412
|
this.toolbar.addItem('close', closeButton);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Remove the spinner when the chat is ready.
|
|
300
|
-
*/
|
|
301
|
-
this.model.ready.then(() => {
|
|
302
|
-
this._spinner.dispose();
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* The display name.
|
|
307
|
-
*/
|
|
308
|
-
get displayName() {
|
|
309
|
-
return this._displayName;
|
|
310
|
-
}
|
|
311
|
-
set displayName(value) {
|
|
312
|
-
this._displayName = value;
|
|
313
|
-
this._updateTitle();
|
|
413
|
+
// Update mark as read button state
|
|
414
|
+
(_b = this.model.unreadChanged) === null || _b === void 0 ? void 0 : _b.connect(this._unreadChanged);
|
|
415
|
+
this._markAsRead.enabled = ((_d = (_c = this.model) === null || _c === void 0 ? void 0 : _c.unreadMessages.length) !== null && _d !== void 0 ? _d : 0) > 0;
|
|
314
416
|
}
|
|
315
417
|
/**
|
|
316
|
-
* The chat widget
|
|
418
|
+
* The chat widget embedded in the sidepanel widget.
|
|
317
419
|
*/
|
|
318
420
|
get widget() {
|
|
319
421
|
return this._chatWidget;
|
|
@@ -324,6 +426,27 @@ export class ChatSection extends PanelWithToolbar {
|
|
|
324
426
|
get model() {
|
|
325
427
|
return this._chatWidget.model;
|
|
326
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* The displayed name of the widget.
|
|
431
|
+
*/
|
|
432
|
+
get name() {
|
|
433
|
+
return this._displayName;
|
|
434
|
+
}
|
|
435
|
+
set name(value) {
|
|
436
|
+
const old = this._displayName;
|
|
437
|
+
if (old === value) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
this._displayName = value;
|
|
441
|
+
this._updateTitle();
|
|
442
|
+
this._nameChanged.emit({ old, new: value });
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* A signal emitting when the name has changed.
|
|
446
|
+
*/
|
|
447
|
+
get nameChanged() {
|
|
448
|
+
return this._nameChanged;
|
|
449
|
+
}
|
|
327
450
|
/**
|
|
328
451
|
* Dispose of the resources held by the widget.
|
|
329
452
|
*/
|
|
@@ -336,25 +459,99 @@ export class ChatSection extends PanelWithToolbar {
|
|
|
336
459
|
super.dispose();
|
|
337
460
|
}
|
|
338
461
|
/**
|
|
339
|
-
*
|
|
340
|
-
|
|
462
|
+
* Update the title based on the chat name.
|
|
463
|
+
*/
|
|
341
464
|
_updateTitle() {
|
|
342
|
-
this.title.label = this.
|
|
465
|
+
this.title.label = this.model.name;
|
|
343
466
|
this.title.caption = this._displayName;
|
|
467
|
+
const titleElement = document.createElement('span');
|
|
468
|
+
titleElement.classList.add('jp-chat-sidepanel-widget-title');
|
|
469
|
+
titleElement.title = this._displayName;
|
|
470
|
+
titleElement.textContent = this._displayName;
|
|
471
|
+
// Dispose of the previous widget.
|
|
472
|
+
if (this._titleWidget) {
|
|
473
|
+
this._titleWidget.dispose();
|
|
474
|
+
}
|
|
475
|
+
// Insert the new title widget in toolbar.
|
|
476
|
+
this._titleWidget = new Widget({ node: titleElement });
|
|
477
|
+
this.toolbar.insertItem(0, 'title', this._titleWidget);
|
|
344
478
|
}
|
|
345
479
|
}
|
|
346
480
|
/**
|
|
347
|
-
* A component
|
|
481
|
+
* A search input component for selecting a chat.
|
|
348
482
|
*/
|
|
349
|
-
function
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
483
|
+
function ChatSearchInput({ selectChat, getPopup, chatOpened }) {
|
|
484
|
+
const [query, setQuery] = useState('');
|
|
485
|
+
const inputRef = useRef(null);
|
|
486
|
+
useEffect(() => {
|
|
487
|
+
const resetQuery = () => {
|
|
488
|
+
setQuery('');
|
|
489
|
+
};
|
|
490
|
+
chatOpened.connect(resetQuery);
|
|
491
|
+
return () => {
|
|
492
|
+
chatOpened.disconnect(resetQuery);
|
|
493
|
+
};
|
|
494
|
+
}, [chatOpened]);
|
|
495
|
+
const handleInputChange = (event) => {
|
|
496
|
+
const value = event.target.value;
|
|
497
|
+
setQuery(value);
|
|
498
|
+
const popup = getPopup();
|
|
499
|
+
if (popup) {
|
|
500
|
+
popup.setQuery(value);
|
|
501
|
+
if (!popup.isVisible && value) {
|
|
502
|
+
popup.show();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
const handleInputFocus = () => {
|
|
507
|
+
const popup = getPopup();
|
|
508
|
+
if (popup && inputRef.current) {
|
|
509
|
+
// Set anchor element before showing
|
|
510
|
+
popup.anchor = inputRef.current;
|
|
511
|
+
popup.setQuery(query);
|
|
512
|
+
popup.show();
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const handleInputClick = () => {
|
|
516
|
+
var _a;
|
|
517
|
+
const popup = getPopup();
|
|
518
|
+
if (popup && inputRef.current && !popup.isVisible) {
|
|
519
|
+
popup.anchor = inputRef.current;
|
|
520
|
+
popup.setQuery(query);
|
|
521
|
+
popup.show();
|
|
522
|
+
}
|
|
523
|
+
// Force focus on input.
|
|
524
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
525
|
+
};
|
|
526
|
+
const handleKeyDown = (event) => {
|
|
527
|
+
const popup = getPopup();
|
|
528
|
+
if (!popup || !popup.isVisible) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
let value;
|
|
532
|
+
switch (event.key) {
|
|
533
|
+
case 'ArrowDown':
|
|
534
|
+
event.preventDefault();
|
|
535
|
+
popup.selectNext();
|
|
536
|
+
break;
|
|
537
|
+
case 'ArrowUp':
|
|
538
|
+
event.preventDefault();
|
|
539
|
+
popup.selectPrevious();
|
|
540
|
+
break;
|
|
541
|
+
case 'Enter':
|
|
542
|
+
event.preventDefault();
|
|
543
|
+
value = popup.getSelectedValue();
|
|
544
|
+
if (value) {
|
|
545
|
+
selectChat(value);
|
|
546
|
+
popup.hide();
|
|
547
|
+
}
|
|
548
|
+
break;
|
|
549
|
+
case 'Escape':
|
|
550
|
+
event.preventDefault();
|
|
551
|
+
popup.hide();
|
|
552
|
+
setQuery('');
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
return (React.createElement("input", { ref: inputRef, type: "text", placeholder: "Select a chat", value: query, onChange: handleInputChange, onFocus: handleInputFocus, onClick: handleInputClick, onKeyDown: handleKeyDown, className: "jp-chat-search-input" }));
|
|
360
557
|
}
|