Skip to content

Commit 2b688d7

Browse files
dlqqqandrii-ibrichet
authored
Upgrade to Jupyter Collaboration 4 (#227)
* upgrade to Jupyter Collaboration 4 Co-authored-by: David L. Qiu <david@qiu.dev> * add EOL * deduplicate yarn via `jlpm dlx yarn-berry-deduplicate` * remove unnecessary token casting * Update packages/jupyterlab-chat/src/widget.tsx Co-authored-by: Nicolas Brichet <32258950+brichet@users.noreply.github.com> * bump jupyterlab packages to 4.4+, notebook package to 7.4, ts to 5.5.4, lumino packages to latest versions used in JupyterLab 4.4 * fix jest tests by adding IntersectionObserver mock --------- Co-authored-by: Andrii Ieroshenko <ieroshenkoa@gmail.com> Co-authored-by: Nicolas Brichet <32258950+brichet@users.noreply.github.com>
1 parent c9917b7 commit 2b688d7

File tree

9 files changed

+855
-1360
lines changed

9 files changed

+855
-1360
lines changed

packages/jupyter-chat/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module.exports = {
3232
'!src/**/.ipynb_checkpoints/*'
3333
],
3434
coverageReporters: ['lcov', 'text'],
35+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
3536
testRegex: 'src/.*/.*.spec.ts[x]?$',
3637
transformIgnorePatterns: [`/node_modules/(?!${esModules}).+`]
3738
};

packages/jupyter-chat/jest.setup.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) Jupyter Development Team.
3+
* Distributed under the terms of the Modified BSD License.
4+
*/
5+
6+
/*
7+
* Mock IntersectionObserver used by @jupyterlab/notebook for windowing.
8+
* We use a simple mock here instead of @jupyterlab/testutils/lib/jest-shim
9+
* to avoid extra dependencies. This lightweight mock is sufficient for our tests.
10+
*/
11+
global.IntersectionObserver = class IntersectionObserver {
12+
constructor() {}
13+
disconnect() {}
14+
observe() {}
15+
unobserve() {}
16+
};

packages/jupyterlab-chat-extension/package.json

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,25 +47,25 @@
4747
"watch:labextension": "jupyter labextension watch ."
4848
},
4949
"dependencies": {
50-
"@jupyter-notebook/application": "^7.2.0",
51-
"@jupyter/collaborative-drive": "^3.0.0",
52-
"@jupyter/ydoc": "^2.0.0 || ^3.0.0",
53-
"@jupyterlab/application": "^4.2.0",
54-
"@jupyterlab/apputils": "^4.3.0",
55-
"@jupyterlab/coreutils": "^6.2.0",
56-
"@jupyterlab/docregistry": "^4.2.0",
57-
"@jupyterlab/filebrowser": "^4.2.0",
58-
"@jupyterlab/launcher": "^4.2.0",
59-
"@jupyterlab/notebook": "^4.2.0",
60-
"@jupyterlab/rendermime": "^4.2.0",
61-
"@jupyterlab/services": "^7.2.0",
62-
"@jupyterlab/settingregistry": "^4.2.0",
63-
"@jupyterlab/translation": "^4.2.0",
64-
"@jupyterlab/ui-components": "^4.2.0",
65-
"@lumino/commands": "^2.0.0",
66-
"@lumino/coreutils": "^2.0.0",
67-
"@lumino/signaling": "^2.0.0",
68-
"@lumino/widgets": "^2.0.0",
50+
"@jupyter-notebook/application": "^7.4.0",
51+
"@jupyter/collaborative-drive": "^4.0.2",
52+
"@jupyter/ydoc": "^3.0.0",
53+
"@jupyterlab/application": "^4.4.0",
54+
"@jupyterlab/apputils": "^4.5.0",
55+
"@jupyterlab/coreutils": "^6.4.0",
56+
"@jupyterlab/docregistry": "^4.4.0",
57+
"@jupyterlab/filebrowser": "^4.4.0",
58+
"@jupyterlab/launcher": "^4.4.0",
59+
"@jupyterlab/notebook": "^4.4.0",
60+
"@jupyterlab/rendermime": "^4.4.0",
61+
"@jupyterlab/services": "^7.4.0",
62+
"@jupyterlab/settingregistry": "^4.4.0",
63+
"@jupyterlab/translation": "^4.4.0",
64+
"@jupyterlab/ui-components": "^4.4.0",
65+
"@lumino/commands": "^2.3.2",
66+
"@lumino/coreutils": "^2.2.1",
67+
"@lumino/signaling": "^2.1.4",
68+
"@lumino/widgets": "^2.7.0",
6969
"jupyterlab-chat": "^0.12.0",
7070
"react": "^18.2.0",
7171
"y-protocols": "^1.0.5",
@@ -82,7 +82,7 @@
8282
"rimraf": "^5.0.1",
8383
"source-map-loader": "^1.0.2",
8484
"style-loader": "^3.3.1",
85-
"typescript": "~5.0.2",
85+
"typescript": "~5.5.4",
8686
"yjs": "^13.5.0"
8787
},
8888
"sideEffects": [

packages/jupyterlab-chat-extension/src/index.ts

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ import {
2020
chatIcon,
2121
readIcon
2222
} from '@jupyter/chat';
23-
import {
24-
ICollaborativeDrive,
25-
SharedDocumentFactory
26-
} from '@jupyter/collaborative-drive';
23+
import { ICollaborativeContentProvider } from '@jupyter/collaborative-drive';
2724
import {
2825
ILayoutRestorer,
2926
JupyterFrontEnd,
@@ -114,7 +111,7 @@ const docFactories: JupyterFrontEndPlugin<IChatFactory> = {
114111
IActiveCellManagerToken,
115112
IAttachmentOpenerRegistry,
116113
IChatCommandRegistry,
117-
ICollaborativeDrive,
114+
ICollaborativeContentProvider,
118115
IDefaultFileBrowser,
119116
IInputToolbarRegistryFactory,
120117
ILayoutRestorer,
@@ -133,7 +130,7 @@ const docFactories: JupyterFrontEndPlugin<IChatFactory> = {
133130
activeCellManager: IActiveCellManager | null,
134131
attachmentOpenerRegistry: IAttachmentOpenerRegistry,
135132
chatCommandRegistry: IChatCommandRegistry,
136-
drive: ICollaborativeDrive | null,
133+
drive: ICollaborativeContentProvider | null,
137134
filebrowser: IDefaultFileBrowser | null,
138135
inputToolbarFactory: IInputToolbarRegistryFactory,
139136
restorer: ILayoutRestorer | null,
@@ -175,11 +172,11 @@ const docFactories: JupyterFrontEndPlugin<IChatFactory> = {
175172
previousDirectory &&
176173
previousDirectory !== currentDirectory
177174
) {
178-
drive
175+
app.serviceManager.contents
179176
.get(previousDirectory)
180177
.then(contentModel => {
181178
if (contentModel.content.length === 0) {
182-
drive.delete(previousDirectory).catch(e => {
179+
app.serviceManager.contents.delete(previousDirectory).catch(e => {
183180
// no-op, the directory might not be empty
184181
});
185182
}
@@ -194,18 +191,18 @@ const docFactories: JupyterFrontEndPlugin<IChatFactory> = {
194191
Promise.resolve(null);
195192

196193
if (drive && currentDirectory && previousDirectory !== currentDirectory) {
197-
directoryCreation = drive
194+
directoryCreation = app.serviceManager.contents
198195
.get(currentDirectory, { content: false })
199196
.catch(async () => {
200-
return drive
197+
return app.serviceManager.contents
201198
.newUntitled({
202199
type: 'directory'
203200
})
204201
.then(async contentModel => {
205-
return drive
202+
return app.serviceManager.contents
206203
.rename(contentModel.path, currentDirectory)
207204
.catch(e => {
208-
drive.delete(contentModel.path);
205+
app.serviceManager.contents.delete(contentModel.path);
209206
throw new Error(e);
210207
});
211208
})
@@ -270,9 +267,7 @@ const docFactories: JupyterFrontEndPlugin<IChatFactory> = {
270267
app.docRegistry.addFileType(chatFileType);
271268

272269
if (drive) {
273-
const chatFactory: SharedDocumentFactory = () => {
274-
return YChat.create();
275-
};
270+
const chatFactory = () => YChat.create();
276271
drive.sharedModelFactory.registerDocumentFactory('chat', chatFactory);
277272
}
278273

@@ -364,7 +359,7 @@ const chatCommands: JupyterFrontEndPlugin<void> = {
364359
id: pluginIds.chatCommands,
365360
description: 'The commands to create or open a chat.',
366361
autoStart: true,
367-
requires: [ICollaborativeDrive, IChatFactory],
362+
requires: [ICollaborativeContentProvider, IChatFactory],
368363
optional: [
369364
IActiveCellManagerToken,
370365
IChatPanel,
@@ -375,7 +370,7 @@ const chatCommands: JupyterFrontEndPlugin<void> = {
375370
],
376371
activate: (
377372
app: JupyterFrontEnd,
378-
drive: ICollaborativeDrive,
373+
drive: ICollaborativeContentProvider,
379374
factory: IChatFactory,
380375
activeCellManager: IActiveCellManager | null,
381376
chatPanel: ChatPanel | null,
@@ -434,23 +429,29 @@ const chatCommands: JupyterFrontEndPlugin<void> = {
434429

435430
let fileExist = true;
436431
if (filepath) {
437-
await drive.get(filepath, { content: false }).catch(() => {
438-
fileExist = false;
439-
});
432+
await app.serviceManager.contents
433+
.get(filepath, { content: false })
434+
.catch(() => {
435+
fileExist = false;
436+
});
440437
} else {
441438
fileExist = false;
442439
}
443440

444441
// Create a new file if it does not exists
445442
if (!fileExist) {
446443
// Create a new untitled chat.
447-
let model: Contents.IModel | null = await drive.newUntitled({
448-
type: 'file',
449-
ext: chatFileType.extensions[0]
450-
});
444+
let model: Contents.IModel | null =
445+
await app.serviceManager.contents.newUntitled({
446+
type: 'file',
447+
ext: chatFileType.extensions[0]
448+
});
451449
// Rename it if a name has been provided.
452450
if (filepath) {
453-
model = await drive.rename(model.path, filepath);
451+
model = await app.serviceManager.contents.rename(
452+
model.path,
453+
filepath
454+
);
454455
}
455456

456457
if (!model) {
@@ -470,7 +471,8 @@ const chatCommands: JupyterFrontEndPlugin<void> = {
470471
});
471472
} else {
472473
commands.execute('docmanager:open', {
473-
path: `RTC:${filepath}`,
474+
// TODO: support JCollab v3 by optionally prefixing 'RTC:'
475+
path: `${filepath}`,
474476
factory: FACTORY
475477
});
476478
}
@@ -553,9 +555,11 @@ const chatCommands: JupyterFrontEndPlugin<void> = {
553555
}
554556

555557
let fileExist = true;
556-
await drive.get(filepath, { content: false }).catch(() => {
557-
fileExist = false;
558-
});
558+
await app.serviceManager.contents
559+
.get(filepath, { content: false })
560+
.catch(() => {
561+
fileExist = false;
562+
});
559563

560564
if (!fileExist) {
561565
showErrorMessage(
@@ -589,7 +593,7 @@ const chatCommands: JupyterFrontEndPlugin<void> = {
589593
return;
590594
}
591595

592-
const model = await drive.get(filepath);
596+
const model = await app.serviceManager.contents.get(filepath);
593597

594598
// Create a share model from the chat file
595599
const sharedModel = drive.sharedModelFactory.createNew({
@@ -618,8 +622,9 @@ const chatCommands: JupyterFrontEndPlugin<void> = {
618622
factory.tracker.add(widget);
619623
} else {
620624
// The chat is opened in the main area
625+
// TODO: support JCollab v3 by optionally prefixing 'RTC:'
621626
commands.execute('docmanager:open', {
622-
path: `RTC:${filepath}`,
627+
path: `${filepath}`,
623628
factory: FACTORY
624629
});
625630
}
@@ -668,7 +673,7 @@ const chatPanel: JupyterFrontEndPlugin<ChatPanel> = {
668673
description: 'The chat panel widget.',
669674
autoStart: true,
670675
provides: IChatPanel,
671-
requires: [IChatFactory, ICollaborativeDrive, IRenderMimeRegistry],
676+
requires: [IChatFactory, ICollaborativeContentProvider, IRenderMimeRegistry],
672677
optional: [
673678
IAttachmentOpenerRegistry,
674679
IChatCommandRegistry,
@@ -681,7 +686,7 @@ const chatPanel: JupyterFrontEndPlugin<ChatPanel> = {
681686
activate: (
682687
app: JupyterFrontEnd,
683688
factory: IChatFactory,
684-
drive: ICollaborativeDrive,
689+
drive: ICollaborativeContentProvider,
685690
rmRegistry: IRenderMimeRegistry,
686691
attachmentOpenerRegistry: IAttachmentOpenerRegistry,
687692
chatCommandRegistry: IChatCommandRegistry,
@@ -700,7 +705,7 @@ const chatPanel: JupyterFrontEndPlugin<ChatPanel> = {
700705
*/
701706
const chatPanel = new ChatPanel({
702707
commands,
703-
drive,
708+
contentsManager: app.serviceManager.contents,
704709
rmRegistry,
705710
themeManager,
706711
defaultDirectory,

packages/jupyterlab-chat/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
},
4444
"dependencies": {
4545
"@jupyter/chat": "^0.12.0",
46-
"@jupyter/collaborative-drive": "^3.0.0",
46+
"@jupyter/collaborative-drive": "^4.0.2",
4747
"@jupyter/ydoc": "^2.0.0 || ^3.0.0",
4848
"@jupyterlab/application": "^4.2.0",
4949
"@jupyterlab/apputils": "^4.3.0",

packages/jupyterlab-chat/src/factory.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ export class ChatWidgetFactory extends ABCWidgetFactory<
111111
});
112112
}
113113

114+
/**
115+
* IMPORTANT: this property must be defined to use the `RtcContentProvider`
116+
* registered by `jupyter_collaboration`. Without this, the chat document
117+
* widgets will not connect.
118+
*/
119+
get contentProviderId(): string {
120+
return 'rtc';
121+
}
122+
// Must override both getter and setter from ABCFactory for type compatibility.
123+
set contentProviderId(_value: string | undefined) {}
114124
private _themeManager: IThemeManager | null;
115125
private _rmRegistry: IRenderMimeRegistry;
116126
private _chatCommandRegistry?: IChatCommandRegistry;

packages/jupyterlab-chat/src/widget.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
IMessageFooterRegistry,
1313
readIcon
1414
} from '@jupyter/chat';
15-
import { ICollaborativeDrive } from '@jupyter/collaborative-drive';
15+
import { Contents } from '@jupyterlab/services';
1616
import { IThemeManager } from '@jupyterlab/apputils';
1717
import { PathExt } from '@jupyterlab/coreutils';
1818
import { DocumentWidget } from '@jupyterlab/docregistry';
@@ -105,7 +105,7 @@ export class ChatPanel extends SidePanel {
105105
super(options);
106106
this.addClass(SIDEPANEL_CLASS);
107107
this._commands = options.commands;
108-
this._drive = options.drive;
108+
this._contentsManager = options.contentsManager;
109109
this._rmRegistry = options.rmRegistry;
110110
this._themeManager = options.themeManager;
111111
this._defaultDirectory = options.defaultDirectory;
@@ -205,9 +205,9 @@ export class ChatPanel extends SidePanel {
205205
*/
206206
updateChatList = async (): Promise<void> => {
207207
const extension = chatFileType.extensions[0];
208-
this._drive
208+
this._contentsManager
209209
.get(this._defaultDirectory)
210-
.then(contentModel => {
210+
.then((contentModel: Contents.IModel) => {
211211
const chatsNames: { [name: string]: string } = {};
212212
(contentModel.content as any[])
213213
.filter(f => f.type === 'file' && f.name.endsWith(extension))
@@ -300,7 +300,7 @@ export class ChatPanel extends SidePanel {
300300
);
301301
private _commands: CommandRegistry;
302302
private _defaultDirectory: string;
303-
private _drive: ICollaborativeDrive;
303+
private _contentsManager: Contents.IManager;
304304
private _openChat: ReactWidget;
305305
private _rmRegistry: IRenderMimeRegistry;
306306
private _themeManager: IThemeManager | null;
@@ -320,7 +320,7 @@ export namespace ChatPanel {
320320
*/
321321
export interface IOptions extends SidePanel.IOptions {
322322
commands: CommandRegistry;
323-
drive: ICollaborativeDrive;
323+
contentsManager: Contents.IManager;
324324
rmRegistry: IRenderMimeRegistry;
325325
themeManager: IThemeManager | null;
326326
defaultDirectory: string;

python/jupyterlab-chat/pyproject.toml

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,14 @@ classifiers = [
2727
"Programming Language :: Python :: 3.12",
2828
]
2929
dependencies = [
30-
"jupyterlab~=4.0",
31-
"jupyter_collaboration>=3,<4",
30+
"jupyter_collaboration>=4,<5",
3231
"jupyter_server>=2.0.1,<3",
33-
"jupyter_ydoc",
34-
"pycrdt"
35-
]
36-
keywords = [
37-
"jupyter",
38-
"jupyterlab",
39-
"jupyterlab-extension"
32+
"jupyter_ydoc>=3.0.0,<4.0.0",
33+
"pycrdt>=0.12.0,<0.13.0",
4034
]
35+
keywords = ["jupyter", "jupyterlab", "jupyterlab-extension"]
4136
authors = [
42-
{ name = "Jupyter Development Team", email = "jupyter@googlegroups.com" }
37+
{ name = "Jupyter Development Team", email = "jupyter@googlegroups.com" },
4338
]
4439
dynamic = ["version"]
4540

@@ -50,7 +45,7 @@ test = [
5045
"pytest",
5146
"pytest-asyncio",
5247
"pytest-cov",
53-
"pytest-jupyter[server]>=0.6.0"
48+
"pytest-jupyter[server]>=0.6.0",
5449
]
5550

5651
[project.urls]

0 commit comments

Comments
 (0)