@jupyter/chat 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__tests__/chat-body-placeholder.spec.d.ts +1 -0
- package/lib/__tests__/chat-body-placeholder.spec.js +54 -0
- package/lib/__tests__/multichat-panel.spec.d.ts +1 -0
- package/lib/__tests__/multichat-panel.spec.js +116 -0
- package/lib/components/chat.d.ts +6 -0
- package/lib/components/input/buttons/send-button.d.ts +1 -1
- package/lib/components/input/buttons/send-button.js +108 -8
- package/lib/components/input/chat-input.js +4 -3
- package/lib/components/messages/chat-body-placeholder.d.ts +6 -0
- package/lib/components/messages/chat-body-placeholder.js +19 -0
- package/lib/components/messages/index.d.ts +1 -0
- package/lib/components/messages/index.js +1 -0
- package/lib/components/messages/message-renderer.js +1 -1
- package/lib/components/messages/messages.js +54 -2
- package/lib/components/scroll-container.d.ts +6 -13
- package/lib/components/scroll-container.js +11 -22
- package/lib/input-model.d.ts +4 -0
- package/lib/theme-provider.js +2 -2
- package/lib/tokens.d.ts +50 -1
- package/lib/tokens.js +12 -0
- package/lib/types.d.ts +4 -0
- package/lib/widgets/chat-widget.js +5 -1
- package/lib/widgets/index.d.ts +1 -0
- package/lib/widgets/index.js +1 -0
- package/lib/widgets/multichat-panel.d.ts +15 -0
- package/lib/widgets/multichat-panel.js +41 -9
- package/lib/widgets/placeholder.d.ts +48 -0
- package/lib/widgets/placeholder.js +48 -0
- package/package.json +3 -3
- package/src/__tests__/chat-body-placeholder.spec.ts +69 -0
- package/src/__tests__/multichat-panel.spec.ts +135 -0
- package/src/components/chat.tsx +6 -0
- package/src/components/input/buttons/send-button.tsx +158 -14
- package/src/components/input/chat-input.tsx +6 -5
- package/src/components/messages/chat-body-placeholder.tsx +24 -0
- package/src/components/messages/index.ts +1 -0
- package/src/components/messages/message-renderer.tsx +1 -1
- package/src/components/messages/messages.tsx +59 -2
- package/src/components/scroll-container.tsx +27 -34
- package/src/input-model.ts +4 -0
- package/src/theme-provider.ts +2 -2
- package/src/tokens.ts +61 -1
- package/src/types.ts +4 -0
- package/src/widgets/chat-widget.tsx +14 -1
- package/src/widgets/index.ts +1 -0
- package/src/widgets/multichat-panel.tsx +56 -10
- package/src/widgets/placeholder.tsx +122 -0
- package/style/base.css +1 -0
- package/style/placeholder.css +29 -0
|
@@ -26,8 +26,9 @@ import { ISignal, Signal } from '@lumino/signaling';
|
|
|
26
26
|
import { Widget } from '@lumino/widgets';
|
|
27
27
|
import React, { useEffect, useRef, useState } from 'react';
|
|
28
28
|
|
|
29
|
-
import { ChatWidget } from './chat-widget';
|
|
30
29
|
import { ChatSelectorPopup } from './chat-selector-popup';
|
|
30
|
+
import { ChatWidget } from './chat-widget';
|
|
31
|
+
import { defaultPlaceholder, Placeholder } from './placeholder';
|
|
31
32
|
import {
|
|
32
33
|
Chat,
|
|
33
34
|
IInputToolbarRegistry,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
import { TRANSLATION_DOMAIN } from '../context';
|
|
37
38
|
import { chatIcon, readIcon } from '../icons';
|
|
38
39
|
import { IChatModel } from '../model';
|
|
40
|
+
import { IChatPlaceholderFactory } from '../tokens';
|
|
39
41
|
|
|
40
42
|
const SIDEPANEL_CLASS = 'jp-chat-sidepanel';
|
|
41
43
|
const ADD_BUTTON_CLASS = 'jp-chat-add';
|
|
@@ -69,6 +71,7 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
69
71
|
this._createModel = options.createModel;
|
|
70
72
|
this._openInMain = options.openInMain;
|
|
71
73
|
this._renameChat = options.renameChat;
|
|
74
|
+
this._placeholderFactory = options.placeholderFactory;
|
|
72
75
|
|
|
73
76
|
if (this._createModel) {
|
|
74
77
|
// Add chat button calls the createChat callback
|
|
@@ -111,13 +114,18 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
111
114
|
// Insert the toolbar as first child.
|
|
112
115
|
this.insertWidget(0, this.toolbar);
|
|
113
116
|
this._updateChatListDebouncer = new Debouncer(this._updateChatList, 200);
|
|
117
|
+
|
|
118
|
+
// Add the placeholder by default.
|
|
119
|
+
this._addPlaceholder();
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
/**
|
|
117
123
|
* The currently displayed chat widget.
|
|
118
124
|
*/
|
|
119
125
|
get current(): SidePanelWidget | undefined {
|
|
120
|
-
return this._currentWidget
|
|
126
|
+
return this._currentWidget instanceof SidePanelWidget
|
|
127
|
+
? this._currentWidget
|
|
128
|
+
: undefined;
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
/**
|
|
@@ -178,15 +186,16 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
178
186
|
const model = this._loadedModels.get(name);
|
|
179
187
|
if (model) {
|
|
180
188
|
// If this is the currently displayed chat, remove it.
|
|
181
|
-
if (this.
|
|
182
|
-
this.
|
|
183
|
-
this.
|
|
189
|
+
if (this.current?.model === model) {
|
|
190
|
+
this.current.nameChanged.disconnect(this._modelNameChanged);
|
|
191
|
+
this.current.dispose();
|
|
184
192
|
this._currentWidget = undefined;
|
|
185
193
|
|
|
186
194
|
// Clear current chat in selector
|
|
187
195
|
if (this._chatSelectorPopup) {
|
|
188
196
|
this._chatSelectorPopup.setCurrentChat(null);
|
|
189
197
|
}
|
|
198
|
+
this._addPlaceholder();
|
|
190
199
|
}
|
|
191
200
|
|
|
192
201
|
model.dispose();
|
|
@@ -205,6 +214,29 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
205
214
|
this._visibilityChanged.emit(false);
|
|
206
215
|
}
|
|
207
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Add a placeholder in the panel.
|
|
219
|
+
*/
|
|
220
|
+
private _addPlaceholder(): void {
|
|
221
|
+
const props: Placeholder.IProps = {
|
|
222
|
+
chatNames: this._chatNames,
|
|
223
|
+
open: this._onSelectChat,
|
|
224
|
+
onCreate: this._createModel
|
|
225
|
+
? async () => {
|
|
226
|
+
const args = await this._createModel!();
|
|
227
|
+
this.open(args);
|
|
228
|
+
}
|
|
229
|
+
: undefined,
|
|
230
|
+
chatNamesChanged: this._chatNamesChanged,
|
|
231
|
+
trans: this._trans
|
|
232
|
+
};
|
|
233
|
+
const placeholder = this._placeholderFactory
|
|
234
|
+
? this._placeholderFactory.create(props)
|
|
235
|
+
: new defaultPlaceholder(props);
|
|
236
|
+
this.addWidget(placeholder);
|
|
237
|
+
this._currentWidget = placeholder;
|
|
238
|
+
}
|
|
239
|
+
|
|
208
240
|
/**
|
|
209
241
|
* Open a specific chat by name, creating a new sidepanel widget.
|
|
210
242
|
*/
|
|
@@ -214,9 +246,9 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
214
246
|
return;
|
|
215
247
|
}
|
|
216
248
|
|
|
217
|
-
// Dispose current chat widget
|
|
249
|
+
// Dispose of the current chat widget or placeholder.
|
|
218
250
|
if (this._currentWidget) {
|
|
219
|
-
this.
|
|
251
|
+
this.current?.nameChanged.disconnect(this._modelNameChanged);
|
|
220
252
|
this._currentWidget.dispose();
|
|
221
253
|
}
|
|
222
254
|
|
|
@@ -251,7 +283,7 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
251
283
|
this.update();
|
|
252
284
|
this._currentWidget = widget;
|
|
253
285
|
|
|
254
|
-
this.
|
|
286
|
+
this.current?.nameChanged.connect(this._modelNameChanged);
|
|
255
287
|
|
|
256
288
|
// Update selector to show current chat
|
|
257
289
|
if (this._chatSelectorPopup) {
|
|
@@ -283,6 +315,7 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
283
315
|
) {
|
|
284
316
|
this._chatNames = chatNames ?? {};
|
|
285
317
|
this._chatSelectorPopup?.updateChats(Object.keys(this._chatNames));
|
|
318
|
+
this._chatNamesChanged.emit(this._chatNames);
|
|
286
319
|
}
|
|
287
320
|
} catch (e) {
|
|
288
321
|
console.error('Error getting chat files', e);
|
|
@@ -355,7 +388,7 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
355
388
|
this._loadedModels.set(change.new, model);
|
|
356
389
|
this._loadedModels.delete(change.old);
|
|
357
390
|
this._chatSelectorPopup?.setLoadedModels(this.getLoadedModelNames());
|
|
358
|
-
if (this.
|
|
391
|
+
if (this.current?.model.name === model.name) {
|
|
359
392
|
this._chatSelectorPopup?.setCurrentChat(change.new);
|
|
360
393
|
}
|
|
361
394
|
}
|
|
@@ -382,6 +415,10 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
382
415
|
};
|
|
383
416
|
|
|
384
417
|
private _chatOpened = new Signal<MultiChatPanel, ChatWidget>(this);
|
|
418
|
+
private _chatNamesChanged = new Signal<
|
|
419
|
+
MultiChatPanel,
|
|
420
|
+
{ [name: string]: string }
|
|
421
|
+
>(this);
|
|
385
422
|
private _chatOptions: Omit<Chat.IOptions, 'model' | 'inputToolbarRegistry'>;
|
|
386
423
|
private _inputToolbarFactory?: IInputToolbarRegistryFactory;
|
|
387
424
|
private _updateChatListDebouncer: Debouncer;
|
|
@@ -392,10 +429,11 @@ export class MultiChatPanel extends PanelWithToolbar {
|
|
|
392
429
|
private _getChatNames?: () => Promise<{ [name: string]: string }>;
|
|
393
430
|
private _openInMain?: (name: string) => Promise<boolean>;
|
|
394
431
|
private _renameChat?: boolean | ((oldName: string) => Promise<string | null>);
|
|
432
|
+
private _placeholderFactory?: IChatPlaceholderFactory;
|
|
395
433
|
private _openChatWidget?: ReactWidget;
|
|
396
434
|
private _chatSelectorPopup?: ChatSelectorPopup;
|
|
397
435
|
private _loadedModels: Map<string, IChatModel> = new Map();
|
|
398
|
-
private _currentWidget?: SidePanelWidget;
|
|
436
|
+
private _currentWidget?: SidePanelWidget | Widget;
|
|
399
437
|
private _chatNames: { [name: string]: string } = {};
|
|
400
438
|
private _visibilityChanged = new Signal<MultiChatPanel, boolean>(this);
|
|
401
439
|
private _trans: TranslationBundle;
|
|
@@ -442,6 +480,14 @@ export namespace MultiChatPanel {
|
|
|
442
480
|
* @returns - a boolean, whether the chat has been renamed or not.
|
|
443
481
|
*/
|
|
444
482
|
renameChat?: boolean | ((oldName: string) => Promise<string | null>);
|
|
483
|
+
/**
|
|
484
|
+
* An optional factory to create a placeholder widget displayed when no chat
|
|
485
|
+
* is opened. Falls back to the default placeholder if not provided.
|
|
486
|
+
*
|
|
487
|
+
* @param props - the props passed to the placeholder.
|
|
488
|
+
* @returns a widget to display as placeholder.
|
|
489
|
+
*/
|
|
490
|
+
placeholderFactory?: IChatPlaceholderFactory;
|
|
445
491
|
}
|
|
446
492
|
/**
|
|
447
493
|
* The options for the add chat method.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IRenderMime } from '@jupyterlab/rendermime';
|
|
7
|
+
import {
|
|
8
|
+
addIcon,
|
|
9
|
+
ReactWidget,
|
|
10
|
+
ToolbarButtonComponent
|
|
11
|
+
} from '@jupyterlab/ui-components';
|
|
12
|
+
import { ISignal } from '@lumino/signaling';
|
|
13
|
+
import React, { useEffect, useState } from 'react';
|
|
14
|
+
|
|
15
|
+
import { useTranslator } from '../context';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The default placeholder widget.
|
|
19
|
+
*/
|
|
20
|
+
export class defaultPlaceholder extends ReactWidget {
|
|
21
|
+
constructor(options: Placeholder.IProps) {
|
|
22
|
+
super();
|
|
23
|
+
this._props = options;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
return <PlaceholderComponent {...this._props} />;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private _props: Placeholder.IProps;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The placeholder namespace.
|
|
35
|
+
*/
|
|
36
|
+
export namespace Placeholder {
|
|
37
|
+
/**
|
|
38
|
+
* The options of the placeholder widget.
|
|
39
|
+
*/
|
|
40
|
+
export interface IProps {
|
|
41
|
+
/**
|
|
42
|
+
* The initial chat names.
|
|
43
|
+
*/
|
|
44
|
+
chatNames: { [name: string]: string };
|
|
45
|
+
/**
|
|
46
|
+
* A callback to open an existing chat by name.
|
|
47
|
+
*
|
|
48
|
+
* @param name - the display name of the chat to open.
|
|
49
|
+
*/
|
|
50
|
+
open: (name: string) => Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* A callback to create and open a new chat.
|
|
53
|
+
*/
|
|
54
|
+
onCreate?: () => Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* An optional signal emitting when the chat list changes.
|
|
57
|
+
*/
|
|
58
|
+
chatNamesChanged?: ISignal<any, { [name: string]: string }>;
|
|
59
|
+
/**
|
|
60
|
+
* An optional translation bundle.
|
|
61
|
+
*/
|
|
62
|
+
trans?: IRenderMime.TranslationBundle;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The default placeholder component.
|
|
68
|
+
*/
|
|
69
|
+
const PlaceholderComponent = (props: Placeholder.IProps): JSX.Element => {
|
|
70
|
+
const trans = props.trans ?? useTranslator();
|
|
71
|
+
|
|
72
|
+
const [chatNames, setChatNames] = useState<{ [name: string]: string }>(
|
|
73
|
+
props.chatNames
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!props.chatNamesChanged) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const onChanged = (_: any, names: { [name: string]: string }) => {
|
|
81
|
+
setChatNames(names);
|
|
82
|
+
};
|
|
83
|
+
props.chatNamesChanged.connect(onChanged);
|
|
84
|
+
return () => {
|
|
85
|
+
props.chatNamesChanged!.disconnect(onChanged);
|
|
86
|
+
};
|
|
87
|
+
}, [props.chatNamesChanged]);
|
|
88
|
+
|
|
89
|
+
const names = Object.keys(chatNames).sort((a, b) => a.localeCompare(b));
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="jp-chat-placeholder">
|
|
93
|
+
<h3>{trans.__('No chat opened')}</h3>
|
|
94
|
+
{props.onCreate && (
|
|
95
|
+
<div className="jp-chat-placeholder-hint">
|
|
96
|
+
<ToolbarButtonComponent
|
|
97
|
+
label={trans.__('New chat')}
|
|
98
|
+
icon={addIcon}
|
|
99
|
+
onClick={props.onCreate}
|
|
100
|
+
tooltip={trans.__('Create a new chat')}
|
|
101
|
+
className="jp-chat-add"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
{names.length > 0 && (
|
|
106
|
+
<div className="jp-chat-placeholder-list">
|
|
107
|
+
<p>{trans.__('Open an existing chat:')}</p>
|
|
108
|
+
{names.map(name => (
|
|
109
|
+
<div key={name}>
|
|
110
|
+
<button
|
|
111
|
+
className="jp-chat-placeholder-chat-item"
|
|
112
|
+
onClick={() => props.open(name)}
|
|
113
|
+
>
|
|
114
|
+
{name}
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
))}
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
};
|
package/style/base.css
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.jp-chat-placeholder {
|
|
7
|
+
text-align: center;
|
|
8
|
+
color: var(--jp-content-font-color2);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.jp-chat-placeholder > h3 {
|
|
12
|
+
margin-bottom: var(--jp-content-heading-margin-bottom);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.jp-chat-placeholder-chat-item {
|
|
16
|
+
color: var(--jp-content-font-color1);
|
|
17
|
+
background: none;
|
|
18
|
+
border: none;
|
|
19
|
+
padding: 2px 0;
|
|
20
|
+
font: inherit;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
text-align: left;
|
|
23
|
+
text-decoration: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.jp-chat-placeholder-chat-item:hover {
|
|
27
|
+
text-decoration: underline;
|
|
28
|
+
font-weight: bold;
|
|
29
|
+
}
|