Skip to content

Commit

Permalink
Integrates chat backend
Browse files Browse the repository at this point in the history
  • Loading branch information
hbcarlos committed Jul 19, 2023
1 parent 1bd18b5 commit cc62232
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 47 deletions.
5 changes: 2 additions & 3 deletions jupyter_collaboration/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ async def get(self, *args, **kwargs):
"""
Overrides default behavior to check whether the client is authenticated or not.
"""
if self.get_current_user() is None:
if self.current_user is None:
self.log.warning("Couldn't authenticate WebSocket connection")
raise web.HTTPError(403)
return await super().get(*args, **kwargs)
Expand Down Expand Up @@ -216,7 +216,6 @@ def on_message(self, message):
On message receive.
"""
message_type = message[0]
print("message type:", message_type)

if message_type == YMessageType.AWARENESS:
# awareness
Expand Down Expand Up @@ -245,7 +244,7 @@ def on_message(self, message):

if message_type == MessageType.CHAT:
msg = message[2:].decode("utf-8")
user = self.get_current_user()
user = self.current_user
data = json.dumps({"username": user.username, "msg": msg}).encode("utf8")
for client in self.room.clients:
if client != self:
Expand Down
1 change: 1 addition & 0 deletions packages/chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"watch": "tsc -b --watch"
},
"dependencies": {
"@jupyter/docprovider": "^1.0.1",
"@jupyterlab/services": "^7.0.0",
"@jupyterlab/translation": "^4.0.0",
"@jupyterlab/ui-components": "^4.0.0",
Expand Down
67 changes: 56 additions & 11 deletions packages/chat/src/chatpanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import { User } from '@jupyterlab/services';
import { ITranslator } from '@jupyterlab/translation';
import { LabIcon, SidePanel, caretRightIcon } from '@jupyterlab/ui-components';

import { Panel, Widget } from '@lumino/widgets';

import { IAwarenessProvider, IChatMessage } from '@jupyter/docprovider';

import chatSvgstr from '../style/icons/chat.svg';

/**
Expand All @@ -26,30 +29,59 @@ export class ChatPanel extends SidePanel {
constructor(options: ChatPanel.IOptions) {
super({ content: new Panel(), translator: options.translator });
this._user = options.currentUser;
this._provider = options.provider;
this.addClass('jp-ChatPanel');

this._messages.addClass('jp-ChatPanel-messages');
this.addWidget(this._messages);
this.addWidget(new Widget({ node: this._prompt }));

this._provider.chatMessage.connect(this.onMessageReceived, this);
}

dispose(): void {
if (this.isDisposed) {
return;
}
this._provider.chatMessage.disconnect(this.onMessageReceived, this);
super.dispose();
}

/**
* Add a new message in the list.
* @param messageContent - Content and metadata of the message.
*/
onMessageReceived(messageContent: ChatPanel.IMessage): void {
onMessageReceived(sender: IAwarenessProvider, msg: IChatMessage): void {
const state = sender.awareness.getStates();
let user: User.IIdentity | undefined = undefined;
state.forEach((value: any) => {
const u: User.IIdentity = value.user;
if (u.username === msg.username) {
user = u;
}
});

const message: ChatPanel.IMessage = {
user: user ?? {
name: msg.username,
username: msg.username,
display_name: msg.username,
initials: '',
color: ''
},
date: new Date(),
content: msg.msg
};

let index = this._messages.widgets.length;
for (const msg of this._messages.widgets.slice(1).reverse()) {
if (messageContent.date > (msg as ChatMessage).date) {
if (message.date > (msg as ChatMessage).date) {
break;
}
index -= 1;
}

this._messages.insertWidget(
index,
new ChatMessage(messageContent, this._user)
);
this._messages.insertWidget(index, new ChatMessage(message, this._user));
}

/**
Expand All @@ -60,7 +92,18 @@ export class ChatPanel extends SidePanel {
if (!message) {
return;
}
console.log(this._user, message);

const msg: ChatPanel.IMessage = {
user: this._user.identity!,
date: new Date(),
content: message
};
this._provider.sendMessage(message);

this._messages.insertWidget(
this._messages.widgets.length,
new ChatMessage(msg, this._user)
);
};

/**
Expand Down Expand Up @@ -112,6 +155,7 @@ export class ChatPanel extends SidePanel {

private _user: User.IManager;
private _messages = new Panel();
private _provider: IAwarenessProvider;
}

/**
Expand Down Expand Up @@ -147,10 +191,10 @@ class ChatMessage extends Widget {
const header = document.createElement('div');
const user = document.createElement('div');
user.innerText =
currentUser.identity?.username === this._message.user.identity?.username
currentUser.identity?.username === this._message.user.username
? 'You'
: this._message.user.identity?.display_name || '???';
user.style.color = this._message.user.identity?.color || 'inherit';
: this._message.user.display_name || '???';
user.style.color = this._message.user.color || 'inherit';
header.append(user);

const date = document.createElement('div');
Expand Down Expand Up @@ -182,6 +226,7 @@ export namespace ChatPanel {
* Options to use when building the chat panel.
*/
export interface IOptions {
provider: IAwarenessProvider;
currentUser: User.IManager;
translator?: ITranslator;
}
Expand All @@ -190,7 +235,7 @@ export namespace ChatPanel {
* The message content.
*/
export interface IMessage {
user: User.IManager;
user: User.IIdentity;
date: Date;
content: string;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/collaboration-extension/src/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,27 @@ import { DOMUtils } from '@jupyterlab/apputils';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';

import { chatIcon, ChatPanel } from '@jupyter/chat';
import { IAwarenessProvider } from '@jupyter/docprovider';

/**
* The default collaborative chat panel.
*/
export const chat: JupyterFrontEndPlugin<void> = {
id: '@jupyter/collaboration-extension:chat',
description: 'The default chat panel',
requires: [IAwarenessProvider],
optional: [ITranslator, ILayoutRestorer],
autoStart: true,
activate: (
app: JupyterFrontEnd,
provider: IAwarenessProvider,
translator: ITranslator,
restorer: ILayoutRestorer
): void => {
const { user } = app.serviceManager;
const trans = (translator ?? nullTranslator).load('jupyter_collaboration');

const panel = new ChatPanel({ translator, currentUser: user });
const panel = new ChatPanel({ provider, translator, currentUser: user });
panel.id = DOMUtils.createDomID();
panel.title.caption = trans.__('Collaboration');
panel.title.icon = chatIcon;
Expand Down
85 changes: 55 additions & 30 deletions packages/collaboration-extension/src/collaboration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import {
UserInfoPanel,
UserMenu
} from '@jupyter/collaboration';
import { IAwareness, WebSocketAwarenessProvider } from '@jupyter/docprovider';
import {
IAwareness,
IAwarenessProvider,
WebSocketAwarenessProvider
} from '@jupyter/docprovider';
import { SidePanel, usersIcon } from '@jupyterlab/ui-components';
import { Menu, MenuBar } from '@lumino/widgets';
import { URLExt } from '@jupyterlab/coreutils';
Expand All @@ -34,6 +38,47 @@ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
import * as Y from 'yjs';
import { Awareness } from 'y-protocols/awareness';

/**
* Jupyter plugin providing the awareness provider.
*/
export const awarenessProviderPlugin: JupyterFrontEndPlugin<IAwarenessProvider> =
{
id: '@jupyter/collaboration-extension:awarenessProvider',
description: 'A global provider for awareness.',
autoStart: true,
requires: [IStateDB],
provides: IAwarenessProvider,
activate: (app: JupyterFrontEnd, state: StateDB): IAwarenessProvider => {
const { user } = app.serviceManager;

const ydoc = new Y.Doc();
const awareness = new Awareness(ydoc);

const server = ServerConnection.makeSettings();
const url = URLExt.join(server.wsUrl, 'api/collaboration/room');

const provider = new WebSocketAwarenessProvider({
url: url,
roomID: 'JupyterLab:globalAwareness',
awareness: awareness,
user: user
});

state.changed.connect(async () => {
const data: any = await state.toJSON();
const current = data['layout-restorer:data']?.main?.current || '';

if (current.startsWith('editor') || current.startsWith('notebook')) {
awareness.setLocalStateField('current', current);
} else {
awareness.setLocalStateField('current', null);
}
});

return provider;
}
};

/**
* Jupyter plugin providing the IUserMenu.
*/
Expand Down Expand Up @@ -81,40 +126,20 @@ export const menuBarPlugin: JupyterFrontEndPlugin<void> = {

/**
* Jupyter plugin creating a global awareness for RTC.
*
* @deprecated Will be removed in v2.0.
* Note: Moved to IAwarenessProvider token.
*/
export const rtcGlobalAwarenessPlugin: JupyterFrontEndPlugin<IAwareness> = {
id: '@jupyter/collaboration-extension:rtcGlobalAwareness',
description: 'Add global awareness to share working document of users.',
requires: [IStateDB],
requires: [IAwarenessProvider],
provides: IGlobalAwareness,
activate: (app: JupyterFrontEnd, state: StateDB): IAwareness => {
const { user } = app.serviceManager;

const ydoc = new Y.Doc();
const awareness = new Awareness(ydoc);

const server = ServerConnection.makeSettings();
const url = URLExt.join(server.wsUrl, 'api/collaboration/room');

new WebSocketAwarenessProvider({
url: url,
roomID: 'JupyterLab:globalAwareness',
awareness: awareness,
user: user
});

state.changed.connect(async () => {
const data: any = await state.toJSON();
const current = data['layout-restorer:data']?.main?.current || '';

if (current.startsWith('editor') || current.startsWith('notebook')) {
awareness.setLocalStateField('current', current);
} else {
awareness.setLocalStateField('current', null);
}
});

return awareness;
activate: (
app: JupyterFrontEnd,
provider: IAwarenessProvider
): IAwareness => {
return provider.awareness;
}
};

Expand Down
2 changes: 2 additions & 0 deletions packages/collaboration-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
userMenuPlugin,
menuBarPlugin,
rtcGlobalAwarenessPlugin,
awarenessProviderPlugin,
rtcPanelPlugin,
userEditorCursors
} from './collaboration';
Expand All @@ -37,6 +38,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
userMenuPlugin,
menuBarPlugin,
rtcGlobalAwarenessPlugin,
awarenessProviderPlugin,
rtcPanelPlugin,
sharedLink,
userEditorCursors
Expand Down
2 changes: 0 additions & 2 deletions packages/docprovider/src/awareness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export class WebSocketAwarenessProvider
) => {
const content = decoding.readVarString(decoder);
const data = JSON.parse(content) as IChatMessage;
console.debug('Chat:', data);
this._chatMessage.emit(data);
};
}
Expand Down Expand Up @@ -93,7 +92,6 @@ export class WebSocketAwarenessProvider
* @param msg message
*/
sendMessage(msg: string): void {
console.debug('Send message:', msg);
const encoder = encoding.createEncoder();
encoding.writeVarUint(encoder, MessageType.CHAT);
encoding.writeVarString(encoder, msg);
Expand Down
12 changes: 12 additions & 0 deletions packages/docprovider/src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ export const ICollaborativeDrive = new Token<ICollaborativeDrive>(
'@jupyter/collaboration-extension:ICollaborativeDrive'
);

/**
* The awareness provider.
*/
export const IAwarenessProvider = new Token<IAwarenessProvider>(
'@jupyter/docprovider:IAwarenessProvider'
);

/**
* A document factory for registering shared models
*/
Expand Down Expand Up @@ -63,6 +70,11 @@ export type IAwareness = Awareness;
* A provider interface for global awareness features.
*/
export interface IAwarenessProvider {
/**
* The awareness object.
*/
readonly awareness: Awareness;

/**
* A signal to subscribe for incoming messages.
*/
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2019,6 +2019,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@jupyter/chat@workspace:packages/chat"
dependencies:
"@jupyter/docprovider": ^1.0.1
"@jupyterlab/services": ^7.0.0
"@jupyterlab/translation": ^4.0.0
"@jupyterlab/ui-components": ^4.0.0
Expand Down

0 comments on commit cc62232

Please sign in to comment.