Skip to content

Commit

Permalink
Collaborative chat sidepanel (#11)
Browse files Browse the repository at this point in the history
* Includes a panel to manage the chats

* Add integration tests

* lint

* Use the jupyter events sytem to update the chat list

* Fix the chat select initialization

* Fix factory name after rebase

* Add buttons to move the chat between sidepanel and main area

* Code cleaning
  • Loading branch information
brichet committed May 7, 2024
1 parent 80b8c6d commit 64d01f7
Show file tree
Hide file tree
Showing 14 changed files with 771 additions and 65 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ jobs:
run: |
set -eux
python -m pip install "jupyterlab>=4.0.0,<5" jupyterlab_${{ matrix.extension }}_chat*.whl
- name: Install dependencies
working-directory: packages/jupyterlab-${{ matrix.extension }}-chat/ui-tests
env:
Expand All @@ -125,6 +126,7 @@ jobs:
working-directory: packages/jupyterlab-${{ matrix.extension }}-chat/ui-tests
run: |
jlpm playwright test
- name: Upload Playwright Test report
if: always()
uses: actions/upload-artifact@v3
Expand Down
5 changes: 4 additions & 1 deletion packages/jupyterlab-collaborative-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,11 @@
"@jupyterlab/services": "^7.0.0",
"@jupyterlab/settingregistry": "^4.0.0",
"@jupyterlab/translation": "^4.0.0",
"@jupyterlab/ui-components": "^4.0.0",
"@lumino/coreutils": "^2.0.0",
"@lumino/signaling": "^2.0.0",
"@lumino/widgets": "^2.0.0",
"react": "^18.2.0",
"y-protocols": "^1.0.5",
"yjs": "^13.5.40"
},
Expand All @@ -79,7 +82,7 @@
"@jupyterlab/testutils": "^4.0.0",
"@types/jest": "^29.2.0",
"@types/json-schema": "^7.0.11",
"@types/react": "^18.0.26",
"@types/react": "^18.2.0",
"@types/react-addons-linked-state-mixin": "^0.14.22",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
Expand Down
15 changes: 15 additions & 0 deletions packages/jupyterlab-collaborative-chat/schema/chat-panel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "Jupyter collaborative chat",
"description": "Configuration for the chat commands",
"type": "object",
"jupyter.lab.toolbars": {
"Chat": [
{
"name": "moveToSide",
"command": "collaborative-chat:moveToSide"
}
]
},
"properties": {},
"additionalProperties": false
}
2 changes: 1 addition & 1 deletion packages/jupyterlab-collaborative-chat/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { IWidgetConfig } from './token';
*/
export class WidgetConfig implements IWidgetConfig {
/**
* The constructor of the ChatDocument.
* The constructor of the WidgetConfig.
*/
constructor(config: Partial<IConfig>) {
this.config = config;
Expand Down
162 changes: 146 additions & 16 deletions packages/jupyterlab-collaborative-chat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,33 @@ import {
createToolbarFactory,
showErrorMessage
} from '@jupyterlab/apputils';
import { ILauncher } from '@jupyterlab/launcher';
import { PathExt } from '@jupyterlab/coreutils';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { ILauncher } from '@jupyterlab/launcher';
import { IObservableList } from '@jupyterlab/observables';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { Contents } from '@jupyterlab/services';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
import { launchIcon } from '@jupyterlab/ui-components';
import { Awareness } from 'y-protocols/awareness';

import {
WidgetConfig,
ChatWidgetFactory,
CollaborativeChatModelFactory
} from './factory';
import { chatFileType, CommandIDs, IWidgetConfig } from './token';
import { CollaborativeChatWidget } from './widget';
import { CollaborativeChatModel } from './model';
import { chatFileType, CommandIDs, IChatPanel, IWidgetConfig } from './token';
import { ChatPanel, CollaborativeChatWidget } from './widget';
import { YChat } from './ychat';

const FACTORY = 'Chat';

const pluginIds = {
chatCommands: 'jupyterlab-collaborative-chat:commands',
docFactories: 'jupyterlab-collaborative-chat:factories'
docFactories: 'jupyterlab-collaborative-chat:factories',
chatPanel: 'jupyterlab-collaborative-chat:chat-panel'
};

/**
Expand Down Expand Up @@ -106,7 +110,6 @@ export const docFactories: JupyterFrontEndPlugin<IWidgetConfig> = {
pluginIds.docFactories,
translator
);
console.log('Create toolbarFactory', toolbarFactory);
}

// Wait for the application to be restored and
Expand Down Expand Up @@ -194,17 +197,20 @@ export const docFactories: JupyterFrontEndPlugin<IWidgetConfig> = {
};

/**
* Extension registering the chat file type.
* Extension providing the commands, menu and laucher.
*/
export const chatCommands: JupyterFrontEndPlugin<void> = {
const chatCommands: JupyterFrontEndPlugin<void> = {
id: pluginIds.chatCommands,
description: 'The commands to create or open a chat',
autoStart: true,
requires: [ICollaborativeDrive],
optional: [ICommandPalette, ILauncher],
requires: [ICollaborativeDrive, IGlobalAwareness, IWidgetConfig],
optional: [IChatPanel, ICommandPalette, ILauncher],
activate: (
app: JupyterFrontEnd,
drive: ICollaborativeDrive,
awareness: Awareness,
widgetConfig: IWidgetConfig,
chatPanel: ChatPanel | null,
commandPalette: ICommandPalette | null,
launcher: ILauncher | null
) => {
Expand All @@ -221,6 +227,7 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
caption: 'Create a chat',
icon: args => (args.isPalette ? undefined : chatIcon),
execute: async args => {
const inSidePanel: boolean = (args.inSidePanel as boolean) ?? false;
let name: string | null = (args.name as string) ?? null;
let filepath = '';
if (!name) {
Expand Down Expand Up @@ -276,11 +283,11 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
filepath = model.path;
}

return commands.execute(CommandIDs.openChat, { filepath });
return commands.execute(CommandIDs.openChat, { filepath, inSidePanel });
}
});

/**
/*
* Command to open a chat.
*
* args:
Expand All @@ -289,6 +296,7 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
commands.addCommand(CommandIDs.openChat, {
label: 'Open a chat',
execute: async args => {
const inSidePanel: boolean = (args.inSidePanel as boolean) ?? false;
let filepath: string | null = (args.filepath as string) ?? null;
if (filepath === null) {
filepath = (
Expand Down Expand Up @@ -317,10 +325,44 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
return;
}

commands.execute('docmanager:open', {
path: `RTC:${filepath}`,
factory: FACTORY
});
if (inSidePanel && chatPanel) {
// The chat is opened in the chat panel.
app.shell.activateById(chatPanel.id);
const model = await drive.get(filepath);

/**
* Create a share model from the chat file
*/
const sharedModel = drive.sharedModelFactory.createNew({
path: model.path,
format: model.format,
contentType: chatFileType.contentType,
collaborative: true
}) as YChat;

/**
* Initialize the chat model with the share model
*/
const chat = new CollaborativeChatModel({
awareness,
sharedModel,
widgetConfig
});

/**
* Add a chat widget to the side panel.
*/
chatPanel.addChat(
chat,
PathExt.basename(model.name, chatFileType.extensions[0])
);
} else {
// The chat is opened in the main area
commands.execute('docmanager:open', {
path: `RTC:${filepath}`,
factory: FACTORY
});
}
}
});

Expand Down Expand Up @@ -348,4 +390,92 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
}
};

export default [chatCommands, docFactories];
/*
* Extension providing a chat panel.
*/
const chatPanel: JupyterFrontEndPlugin<ChatPanel> = {
id: pluginIds.chatPanel,
description: 'A chat extension for Jupyter',
autoStart: true,
provides: IChatPanel,
requires: [ICollaborativeDrive, IRenderMimeRegistry],
optional: [ILayoutRestorer, IThemeManager],
activate: (
app: JupyterFrontEnd,
drive: ICollaborativeDrive,
rmRegistry: IRenderMimeRegistry,
restorer: ILayoutRestorer | null,
themeManager: IThemeManager | null
): ChatPanel => {
const { commands } = app;

/**
* Add Chat widget to left sidebar
*/
const chatPanel = new ChatPanel({
commands,
drive,
rmRegistry,
themeManager
});
chatPanel.id = 'JupyterCollaborationChat:sidepanel';
chatPanel.title.icon = chatIcon;
chatPanel.title.caption = 'Jupyter Chat'; // TODO: i18n/

app.shell.add(chatPanel, 'left', {
rank: 2000
});

if (restorer) {
restorer.add(chatPanel, 'jupyter-chat');
}

// Use events system to watch changes on files.
const schemaID =
'https://events.jupyter.org/jupyter_server/contents_service/v1';
const actions = ['create', 'delete', 'rename'];
app.serviceManager.events.stream.connect((_, emission) => {
if (emission.schema_id === schemaID) {
const action = emission.action as string;
if (actions.includes(action)) {
chatPanel.updateChatNames();
}
}
});

/*
* Command to move a chat from the main area to the side panel.
*
*/
commands.addCommand(CommandIDs.moveToSide, {
label: 'Move the chat to the side panel',
caption: 'Move the chat to the side panel',
icon: launchIcon,
execute: async () => {
const widget = app.shell.currentWidget;
// Ensure widget is a CollaborativeChatWidget and is in main area
if (
!widget ||
!(widget instanceof CollaborativeChatWidget) ||
!Array.from(app.shell.widgets('main')).includes(widget)
) {
console.error(
`The command '${CommandIDs.moveToSide}' should be executed from the toolbar button only`
);
return;
}
// Remove potential drive prefix
const filepath = widget.context.path.split(':').pop();
commands.execute(CommandIDs.openChat, {
filepath,
inSidePanel: true
});
widget.dispose();
}
});

return chatPanel;
}
};

export default [chatCommands, docFactories, chatPanel];
15 changes: 14 additions & 1 deletion packages/jupyterlab-collaborative-chat/src/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const chatFileType: DocumentRegistry.IFileType = {
icon: chatIcon
};

import { ChatPanel } from './widget';

/**
* The token for the chat widget config
*/
Expand Down Expand Up @@ -57,5 +59,16 @@ export const CommandIDs = {
/**
* Open a chat file.
*/
openChat: 'collaborative-chat:open'
openChat: 'collaborative-chat:open',
/**
* Move a main widget to the side panel
*/
moveToSide: 'collaborative-chat:moveToSide'
};

/**
* The chat panel token.
*/
export const IChatPanel = new Token<ChatPanel>(
'@jupyter/collaboration:IChatPanel'
);
41 changes: 0 additions & 41 deletions packages/jupyterlab-collaborative-chat/src/widget.ts

This file was deleted.

Loading

0 comments on commit 64d01f7

Please sign in to comment.