Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chats directory config #91

Merged
merged 9 commits into from
Oct 29, 2024
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": "",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the setting provide more information (for example via the description) on where files are saved by default? Since an empty string can be ambiguous?

"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
Loading