Skip to content

Commit

Permalink
Chats directory config (#91)
Browse files Browse the repository at this point in the history
* A bit of cleaning on comments and descriptions

* Add a setting to change the default directory to save chat files

* update the section name and discovered chats in left panel

* Fix an error when trying to read the removed (or not yet created) directory content

* Fix chat list not updated if default directory is empty

* Add tests (and reorganize tests a bit)

* Automatic application of license header

* lint

* Add description on default directory

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
brichet and github-actions[bot] authored Oct 29, 2024
1 parent dd5c64f commit 528a770
Show file tree
Hide file tree
Showing 15 changed files with 789 additions and 474 deletions.
8 changes: 7 additions & 1 deletion packages/jupyter-chat/src/components/chat-messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ type AvatarProps = {
};

/**
* the avatar component.
* The avatar component.
*/
export function Avatar(props: AvatarProps): JSX.Element | null {
const { user } = props;
Expand All @@ -593,18 +593,24 @@ export function Avatar(props: AvatarProps): JSX.Element | null {
fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`
};

const name =
user.display_name ?? user.name ?? (user.username || 'User undefined');
return user.avatar_url ? (
<MuiAvatar
sx={{
...sharedStyles
}}
src={user.avatar_url}
alt={name}
title={name}
></MuiAvatar>
) : user.initials ? (
<MuiAvatar
sx={{
...sharedStyles
}}
alt={name}
title={name}
>
<Typography
sx={{
Expand Down
4 changes: 2 additions & 2 deletions packages/jupyter-chat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
* Distributed under the terms of the Modified BSD License.
*/

export * from './active-cell-manager';
export * from './icons';
export * from './model';
export * from './registry';
export * from './types';
export * from './active-cell-manager';
export * from './selection-watcher';
export * from './types';
export * from './widgets/chat-error';
export * from './widgets/chat-sidebar';
export * from './widgets/chat-widget';
21 changes: 13 additions & 8 deletions packages/jupyterlab-collaborative-chat/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
ChatWidget,
IActiveCellManager,
IAutocompletionRegistry,
IConfig,
ISelectionWatcher
} from '@jupyter/chat';
import { IThemeManager } from '@jupyterlab/apputils';
Expand All @@ -20,7 +19,7 @@ import { ISignal, Signal } from '@lumino/signaling';
import { CollaborativeChatModel } from './model';
import { CollaborativeChatPanel } from './widget';
import { YChat } from './ychat';
import { IWidgetConfig } from './token';
import { ICollaborativeChatConfig, IWidgetConfig } from './token';

/**
* The object provided by the chatDocument extension.
Expand All @@ -31,30 +30,36 @@ export class WidgetConfig implements IWidgetConfig {
/**
* The constructor of the WidgetConfig.
*/
constructor(config: Partial<IConfig>) {
constructor(config: Partial<ICollaborativeChatConfig>) {
this._config = config;
}

/**
* Getter and setter for the config.
*/
get config(): Partial<IConfig> {
get config(): Partial<ICollaborativeChatConfig> {
return this._config;
}
set config(value: Partial<IConfig>) {
set config(value: Partial<ICollaborativeChatConfig>) {
this._config = { ...this._config, ...value };
this._configChanged.emit(value);
}

/**
* Getter for the configChanged signal
*/
get configChanged(): ISignal<WidgetConfig, Partial<IConfig>> {
get configChanged(): ISignal<
WidgetConfig,
Partial<ICollaborativeChatConfig>
> {
return this._configChanged;
}

private _config: Partial<IConfig>;
private _configChanged = new Signal<WidgetConfig, Partial<IConfig>>(this);
private _config: Partial<ICollaborativeChatConfig>;
private _configChanged = new Signal<
WidgetConfig,
Partial<ICollaborativeChatConfig>
>(this);
}

/**
Expand Down
14 changes: 12 additions & 2 deletions packages/jupyterlab-collaborative-chat/src/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ export const IChatFactory = new Token<IChatFactory>(
'jupyter-collaborative-chat:IChatFactory'
);

/**
* The collaborative chat configs.
*/
export interface ICollaborativeChatConfig extends IConfig {
/**
* The default directory where to create and look for chat.
*/
defaultDirectory?: string;
}

/**
* The interface for the chat factory objects.
*/
Expand All @@ -56,7 +66,7 @@ export interface IWidgetConfig {
/**
* The widget config
*/
config: Partial<IConfig>;
config: Partial<ICollaborativeChatConfig>;

/**
* A signal emitting when the configuration for the chats has changed.
Expand All @@ -68,7 +78,7 @@ export interface IWidgetConfig {
* A signal emitting when the configuration for the chats has changed.
*/
export interface IConfigChanged
extends ISignal<IWidgetConfig, Partial<IConfig>> {}
extends ISignal<IWidgetConfig, Partial<ICollaborativeChatConfig>> {}

/**
* Command ids.
Expand Down
95 changes: 65 additions & 30 deletions packages/jupyterlab-collaborative-chat/src/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
ChatWidget,
IAutocompletionRegistry,
IChatModel,
IConfig,
readIcon
} from '@jupyter/chat';
import { ICollaborativeDrive } from '@jupyter/docprovider';
Expand Down Expand Up @@ -107,6 +106,7 @@ export class ChatPanel extends SidePanel {
this._drive = options.drive;
this._rmRegistry = options.rmRegistry;
this._themeManager = options.themeManager;
this._defaultDirectory = options.defaultDirectory;
this._autocompletionRegistry = options.autocompletionRegistry;

const addChat = new CommandToolbarButton({
Expand All @@ -133,15 +133,21 @@ export class ChatPanel extends SidePanel {
}

/**
* Getter and setter of the config, propagated to all the chat widgets.
* Getter and setter of the defaultDirectory.
*/
get config(): IConfig {
return this._config;
get defaultDirectory(): string {
return this._defaultDirectory;
}
set config(value: Partial<IConfig>) {
this._config = { ...this._config, ...value };
set defaultDirectory(value: string) {
if (value === this._defaultDirectory) {
return;
}
this._defaultDirectory = value;
// Update the list of discoverable chat (in default directory)
this.updateChatList();
// Update the sections names.
this.widgets.forEach(w => {
(w as ChatSection).model.config = value;
(w as ChatSection).defaultDirectory = value;
});
}

Expand All @@ -151,15 +157,15 @@ export class ChatPanel extends SidePanel {
* @param model - the model of the chat widget
* @param name - the name of the chat.
*/
addChat(model: IChatModel, name: string, path: string): void {
addChat(model: IChatModel, path: string): void {
// Collapse all chats
const content = this.content as AccordionPanel;
for (let i = 0; i < this.widgets.length; i++) {
content.collapse(i);
}

// Set the id of the model.
model.name = name;
// Set the name of the model.
model.name = path;

// Create a new widget.
const widget = new ChatWidget({
Expand All @@ -168,25 +174,31 @@ export class ChatPanel extends SidePanel {
themeManager: this._themeManager,
autocompletionRegistry: this._autocompletionRegistry
});

this.addWidget(
new ChatSection({ name, widget, commands: this._commands, path })
new ChatSection({
widget,
commands: this._commands,
path,
defaultDirectory: this._defaultDirectory
})
);
}

/**
* Update the list of available chats in the root directory of the drive.
* Update the list of available chats in the default directory.
*/
updateChatNames = async (): Promise<void> => {
updateChatList = async (): Promise<void> => {
const extension = chatFileType.extensions[0];
this._drive
.get('.')
.get(this._defaultDirectory)
.then(contentModel => {
const chatsNames: { [name: string]: string } = {};
(contentModel.content as any[])
.filter(f => f.type === 'file' && f.name.endsWith(extension))
.forEach(
f => (chatsNames[PathExt.basename(f.name, extension)] = f.name)
);
.forEach(f => {
chatsNames[PathExt.basename(f.name, extension)] = f.path;
});

this._chatNamesChanged.emit(chatsNames);
})
Expand All @@ -212,7 +224,7 @@ export class ChatPanel extends SidePanel {
*/
protected onAfterShow(msg: Message): void {
// Wait for the component to be rendered.
this._openChat.renderPromise?.then(() => this.updateChatNames());
this._openChat.renderPromise?.then(() => this.updateChatList());
}

/**
Expand Down Expand Up @@ -272,7 +284,7 @@ export class ChatPanel extends SidePanel {
this
);
private _commands: CommandRegistry;
private _config: IConfig = {};
private _defaultDirectory: string;
private _drive: ICollaborativeDrive;
private _openChat: ReactWidget;
private _rmRegistry: IRenderMimeRegistry;
Expand All @@ -292,6 +304,7 @@ export namespace ChatPanel {
drive: ICollaborativeDrive;
rmRegistry: IRenderMimeRegistry;
themeManager: IThemeManager | null;
defaultDirectory: string;
autocompletionRegistry?: IAutocompletionRegistry;
}
}
Expand All @@ -305,11 +318,13 @@ class ChatSection extends PanelWithToolbar {
*/
constructor(options: ChatSection.IOptions) {
super(options);

this.addWidget(options.widget);

this.addClass(SECTION_CLASS);
this._name = options.name;
this._defaultDirectory = options.defaultDirectory;
this._path = options.path;
this.title.label = this._name;
this.title.caption = this._path;
this._updateTitle();
this.toolbar.addClass(TOOLBAR_CLASS);

this._markAsRead = new ToolbarButton({
Expand All @@ -326,7 +341,7 @@ class ChatSection extends PanelWithToolbar {
onClick: () => {
this.model.dispose();
options.commands.execute(CommandIDs.openChat, {
filepath: `${this._name}${chatFileType.extensions[0]}`
filepath: this._path
});
this.dispose();
}
Expand All @@ -346,8 +361,6 @@ class ChatSection extends PanelWithToolbar {
this.toolbar.addItem('collaborativeChat-moveMain', moveToMain);
this.toolbar.addItem('collaborativeChat-close', closeButton);

this.addWidget(options.widget);

this.model.unreadChanged?.connect(this._unreadChanged);

this._markAsRead.enabled = this.model.unreadMessages.length > 0;
Expand All @@ -363,10 +376,11 @@ class ChatSection extends PanelWithToolbar {
}

/**
* The name of the chat.
* Set the default directory property.
*/
get name(): string {
return this._name;
set defaultDirectory(value: string) {
this._defaultDirectory = value;
this._updateTitle();
}

/**
Expand All @@ -384,6 +398,27 @@ class ChatSection extends PanelWithToolbar {
super.dispose();
}

/**
* Update the section's title, depending on the default directory and chat file name.
* If the chat file is in the default directory, the section's name is its relative
* path to that default directory. Otherwise, it is it absolute path.
*/
private _updateTitle(): void {
const inDefault = this._defaultDirectory
? !PathExt.relative(this._defaultDirectory, this._path).startsWith('..')
: true;

const pattern = new RegExp(`${chatFileType.extensions[0]}$`, 'g');
this.title.label = (
inDefault
? this._defaultDirectory
? PathExt.relative(this._defaultDirectory, this._path)
: this._path
: '/' + this._path
).replace(pattern, '');
this.title.caption = this._path;
}

/**
* Change the title when messages are unread.
*
Expand All @@ -397,7 +432,7 @@ class ChatSection extends PanelWithToolbar {
// this.title.label = `${unread.length ? '* ' : ''}${this._name}`;
};

private _name: string;
private _defaultDirectory: string;
private _markAsRead: ToolbarButton;
private _path: string;
}
Expand All @@ -411,7 +446,7 @@ export namespace ChatSection {
*/
export interface IOptions extends Panel.IOptions {
commands: CommandRegistry;
name: string;
defaultDirectory: string;
widget: ChatWidget;
path: string;
}
Expand Down
6 changes: 6 additions & 0 deletions python/jupyterlab-collaborative-chat/schema/factory.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
"default": true,
"readOnly": false
},
"defaultDirectory": {
"description": "Default directory where to create and look for chat, the jupyter root directory if empty.",
"type": "string",
"default": "",
"readOnly": false
},
"toolbar": {
"title": "File browser toolbar items",
"description": "Note: To disable a toolbar item,\ncopy it to User Preferences and add the\n\"disabled\" key. The following example will disable the uploader button:\n{\n \"toolbar\": [\n {\n \"name\": \"uploader\",\n \"disabled\": true\n }\n ]\n}\n\nToolbar description:",
Expand Down
Loading

0 comments on commit 528a770

Please sign in to comment.