Skip to content

Commit d88742e

Browse files
nakul-pygithub-actions[bot]brichet
authored
Adding multi chats panel in @jupyter/chat package (#262)
* adding multichats panel * Automatic application of license header * Adding comments * adding rename-chat button and ... * lint fix * Updating * ui tests * removing content manager from @jupyter/chat package * Clean some unused code, and change section title class name to avoid duplication * Remove the hook to listen for chat list, in favor to updating the list from the plugin * Remove closeChat and moveToMain commands, and use openInMain callback to move the chat in the main area * Remove rename modal in favor of jupyterlab modal, make the function optional in the panel, and improve error handling * Renaming * Revert wrong snapshot and test update, and fix commands test * Allow section to have a name different from the chat model name * Update chat list in throtter to avoid conflict * Fix the update debouncer * Better handling of chat file change, and increase timeout for first chat discovery * Make callbacks optional, as well as the buttons depending on these callbacks * Wait for the chat to be ready before creating the chat context of the main input, to ensure all the properties are available * Add a disposed signal in chat model, and add a default id/icon to the multichat-panel * Update the example * handle optional getChatNames --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Nicolas Brichet <[email protected]>
1 parent 98898f1 commit d88742e

File tree

20 files changed

+810
-597
lines changed

20 files changed

+810
-597
lines changed

docs/jupyter-chat-example/src/index.ts

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
*/
55

66
import {
7+
AbstractChatContext,
8+
AbstractChatModel,
79
ActiveCellManager,
810
AttachmentOpenerRegistry,
9-
buildChatSidebar,
10-
AbstractChatModel,
1111
IAttachment,
1212
IChatMessage,
13+
IChatContext,
14+
IConfig,
1315
INewMessage,
16+
MultiChatPanel,
1417
SelectionWatcher,
15-
IChatContext,
16-
AbstractChatContext
18+
IChatModel
1719
} from '@jupyter/chat';
1820
import {
1921
JupyterFrontEnd,
@@ -48,6 +50,11 @@ class ChatContext extends AbstractChatContext {
4850
}
4951

5052
class MyChatModel extends AbstractChatModel {
53+
constructor(options: IChatModel.IOptions) {
54+
super(options);
55+
this.setReady();
56+
}
57+
5158
sendMessage(
5259
newMessage: INewMessage
5360
): Promise<boolean | void> | boolean | void {
@@ -104,23 +111,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
104111
shell: app.shell
105112
});
106113

107-
const model = new MyChatModel({
108-
activeCellManager,
109-
selectionWatcher,
110-
documentManager: filebrowser?.model.manager
111-
});
112-
113-
// Update the settings when they change.
114-
function loadSetting(setting: ISettingRegistry.ISettings): void {
115-
model.config = {
116-
sendWithShiftEnter: setting.get('sendWithShiftEnter')
117-
.composite as boolean,
118-
stackMessages: setting.get('stackMessages').composite as boolean,
119-
unreadNotifications: setting.get('unreadNotifications')
120-
.composite as boolean,
121-
enableCodeToolbar: setting.get('enableCodeToolbar').composite as boolean
122-
};
123-
}
114+
let config: IConfig = {};
124115

125116
// Init the settings.
126117
if (settingRegistry) {
@@ -130,6 +121,21 @@ const plugin: JupyterFrontEndPlugin<void> = {
130121
settingRegistry.load('jupyter-chat-example:plugin')
131122
])
132123
.then(([, setting]) => {
124+
function loadSetting(setting: ISettingRegistry.ISettings) {
125+
config = {
126+
sendWithShiftEnter: setting.get('sendWithShiftEnter')
127+
.composite as boolean,
128+
stackMessages: setting.get('stackMessages').composite as boolean,
129+
unreadNotifications: setting.get('unreadNotifications')
130+
.composite as boolean,
131+
enableCodeToolbar: setting.get('enableCodeToolbar')
132+
.composite as boolean
133+
};
134+
135+
panel.sections.forEach(section => {
136+
section.model.config = config;
137+
});
138+
}
133139
// Read the settings
134140
loadSetting(setting);
135141

@@ -149,12 +155,26 @@ const plugin: JupyterFrontEndPlugin<void> = {
149155
app.commands.execute('docmanager:open', { path: attachment.value });
150156
});
151157

152-
const panel = buildChatSidebar({
153-
model,
158+
app.commands.addCommand('chat-example:openChat', {
159+
execute: async () => {
160+
const model = new MyChatModel({
161+
activeCellManager,
162+
selectionWatcher,
163+
documentManager: filebrowser?.model.manager,
164+
config
165+
});
166+
model.name = UUID.uuid4();
167+
panel.addChat(model);
168+
}
169+
});
170+
171+
const panel = new MultiChatPanel({
154172
rmRegistry,
155173
themeManager,
156174
attachmentOpenerRegistry,
157-
welcomeMessage
175+
welcomeMessage,
176+
createChat: () => app.commands.execute('chat-example:openChat'),
177+
renameChat: async (oldName: string, newName: string) => true
158178
});
159179
app.shell.add(panel, 'left');
160180
}

packages/jupyter-chat/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"@lumino/commands": "^2.0.0",
6161
"@lumino/coreutils": "^2.0.0",
6262
"@lumino/disposable": "^2.0.0",
63+
"@lumino/polling": "^2.0.0",
6364
"@lumino/signaling": "^2.0.0",
6465
"@mui/icons-material": "^5.11.0",
6566
"@mui/material": "^5.11.0",

packages/jupyter-chat/src/components/input/toolbar-registry.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
* Copyright (c) Jupyter Development Team.
33
* Distributed under the terms of the Modified BSD License.
44
*/
5+
import { Token } from '@lumino/coreutils';
6+
import { ISignal, Signal } from '@lumino/signaling';
57
import * as React from 'react';
68

79
import { AttachButton, CancelButton, SendButton } from './buttons';
810
import { IInputModel } from '../../input-model';
9-
import { ISignal, Signal } from '@lumino/signaling';
1011
import { IChatCommandRegistry } from '../../registers';
1112

1213
/**
@@ -166,3 +167,22 @@ export namespace InputToolbarRegistry {
166167
return registry;
167168
}
168169
}
170+
171+
/**
172+
* A factory interface for creating a new Input Toolbar Registry
173+
* for each Chat Panel.
174+
*/
175+
export interface IInputToolbarRegistryFactory {
176+
/**
177+
* Create a new input toolbar registry instance.
178+
*/
179+
create: () => IInputToolbarRegistry;
180+
}
181+
182+
/**
183+
* The token of the factory to create an input toolbar registry.
184+
*/
185+
export const IInputToolbarRegistryFactory =
186+
new Token<IInputToolbarRegistryFactory>(
187+
'@jupyter/chat:IInputToolbarRegistryFactory'
188+
);

packages/jupyter-chat/src/input-model.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import { IAttachment, INotebookAttachment, IUser } from './types';
1616
*/
1717
export interface IInputModel extends IDisposable {
1818
/**
19-
* The chat context (a readonly subset of the chat model).
19+
* The chat context (a subset of the chat model).
2020
*/
21-
readonly chatContext: IChatContext;
21+
chatContext?: IChatContext;
2222

2323
/**
2424
* Function to send a message.
@@ -175,10 +175,14 @@ export class InputModel implements IInputModel {
175175
/**
176176
* The chat context (a readonly subset of the chat model);
177177
*/
178-
get chatContext(): IChatContext {
178+
get chatContext(): IChatContext | undefined {
179179
return this._chatContext;
180180
}
181181

182+
set chatContext(value: IChatContext | undefined) {
183+
this._chatContext = value;
184+
}
185+
182186
/**
183187
* Function to send a message.
184188
*/
@@ -468,7 +472,7 @@ export class InputModel implements IInputModel {
468472
}
469473

470474
private _onSend: (input: string, model?: InputModel) => void;
471-
private _chatContext: IChatContext;
475+
private _chatContext?: IChatContext;
472476
private _value: string;
473477
private _cursorIndex: number | null = null;
474478
private _currentWord: string | null = null;
@@ -491,9 +495,9 @@ export class InputModel implements IInputModel {
491495
export namespace InputModel {
492496
export interface IOptions {
493497
/**
494-
* The chat context (a readonly subset of the chat model).
498+
* The chat context (a subset of the chat model).
495499
*/
496-
chatContext: IChatContext;
500+
chatContext?: IChatContext;
497501

498502
/**
499503
* The function that should send the message.

packages/jupyter-chat/src/model.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
IUser
2121
} from './types';
2222
import { replaceMentionToSpan } from './utils';
23+
import { PromiseDelegate } from '@lumino/coreutils';
2324

2425
/**
2526
* The chat model interface.
@@ -40,6 +41,11 @@ export interface IChatModel extends IDisposable {
4041
*/
4142
unreadMessages: number[];
4243

44+
/**
45+
* The promise resolving when the model is ready.
46+
*/
47+
readonly ready: Promise<void>;
48+
4349
/**
4450
* The indexes list of the messages currently in the viewport.
4551
*/
@@ -226,7 +232,6 @@ export abstract class AbstractChatModel implements IChatModel {
226232
};
227233

228234
this._inputModel = new InputModel({
229-
chatContext: this.createChatContext(),
230235
activeCellManager: options.activeCellManager,
231236
selectionWatcher: options.selectionWatcher,
232237
documentManager: options.documentManager,
@@ -241,6 +246,12 @@ export abstract class AbstractChatModel implements IChatModel {
241246
this._activeCellManager = options.activeCellManager ?? null;
242247
this._selectionWatcher = options.selectionWatcher ?? null;
243248
this._documentManager = options.documentManager ?? null;
249+
250+
this._readyDelegate = new PromiseDelegate<void>();
251+
252+
this.ready.then(() => {
253+
this._inputModel.chatContext = this.createChatContext();
254+
});
244255
}
245256

246257
/**
@@ -263,6 +274,10 @@ export abstract class AbstractChatModel implements IChatModel {
263274
this._name = value;
264275
}
265276

277+
get disposed(): ISignal<AbstractChatModel, void> {
278+
return this._disposed;
279+
}
280+
266281
/**
267282
* The chat messages list.
268283
*/
@@ -328,6 +343,20 @@ export abstract class AbstractChatModel implements IChatModel {
328343
localStorage.setItem(`@jupyter/chat:${this._id}`, JSON.stringify(storage));
329344
}
330345

346+
/**
347+
* Promise that resolves when the model is ready.
348+
*/
349+
get ready(): Promise<void> {
350+
return this._readyDelegate.promise;
351+
}
352+
353+
/**
354+
* Set the model as ready.
355+
*/
356+
protected setReady(): void {
357+
this._readyDelegate.resolve();
358+
}
359+
331360
/**
332361
* The chat settings.
333362
*/
@@ -484,6 +513,7 @@ export abstract class AbstractChatModel implements IChatModel {
484513
return;
485514
}
486515
this._isDisposed = true;
516+
this._disposed.emit();
487517
}
488518

489519
/**
@@ -677,7 +707,9 @@ export abstract class AbstractChatModel implements IChatModel {
677707
private _id: string | undefined;
678708
private _name: string = '';
679709
private _config: IConfig;
710+
private _readyDelegate = new PromiseDelegate<void>();
680711
private _inputModel: IInputModel;
712+
private _disposed = new Signal<this, void>(this);
681713
private _isDisposed = false;
682714
private _commands?: CommandRegistry;
683715
private _activeCellManager: IActiveCellManager | null;

packages/jupyter-chat/src/widgets/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
export * from './chat-error';
77
export * from './chat-sidebar';
88
export * from './chat-widget';
9+
export * from './multichat-panel';

0 commit comments

Comments
 (0)