diff --git a/jupyter_collaboration/ychat.py b/jupyter_collaboration/ychat.py new file mode 100644 index 00000000..5328a768 --- /dev/null +++ b/jupyter_collaboration/ychat.py @@ -0,0 +1,67 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# TODO: remove this module in favor of the one in jupyter_ydoc when released. + +import json +from functools import partial +from typing import Any, Callable, List + +import jupyter_ydoc +from jupyter_ydoc.ybasedoc import YBaseDoc +from pycrdt import Array, Map + + +class YChat(YBaseDoc): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._ydoc["content"] = self._ycontent = Map() + self._ydoc["messages"] = self._ymessages = Array() + + @property + def version(self) -> str: + """ + Returns the version of the document. + :return: Document's version. + :rtype: str + """ + return "1.0.0" + + @property + def messages(self) -> List: + return self._ymessages.to_py() + + def get(self) -> str: + """ + Returns the messages of the document. + :return: Document's messages. + :rtype: Any + """ + + messages = self._ymessages.to_py() + data = dict(messages=messages) + return json.dumps(data, indent=2, sort_keys=True) + + def set(self, value: str) -> None: + """ + Sets the content of the document. + :param value: The content of the document. + :type value: str + """ + contents = json.loads(value) + if "messages" in contents.keys(): + with self._ydoc.transaction(): + for v in contents["messages"]: + self._ymessages.append(v) + + def observe(self, callback: Callable[[str, Any], None]) -> None: + self.unobserve() + self._subscriptions[self._ystate] = self._ystate.observe(partial(callback, "state")) + self._subscriptions[self._ymessages] = self._ymessages.observe( + partial(callback, "messages") + ) + self._subscriptions[self._ycontent] = self._ycontent.observe(partial(callback, "content")) + + +# Register the ydoc +jupyter_ydoc.ydocs["jupyter_collaboration.chat_ydoc:YChat"] = YChat diff --git a/packages/collaboration-extension/package.json b/packages/collaboration-extension/package.json index e8812ce1..2770b74b 100644 --- a/packages/collaboration-extension/package.json +++ b/packages/collaboration-extension/package.json @@ -55,7 +55,7 @@ "dependencies": { "@jupyter/collaboration": "^2.0.11", "@jupyter/docprovider": "^2.0.11", - "@jupyter/ydoc": "^1.1.0-a0", + "@jupyter/ydoc": "^1.1.1", "@jupyterlab/application": "^4.0.5", "@jupyterlab/apputils": "^4.0.5", "@jupyterlab/codemirror": "^4.0.5", @@ -65,6 +65,7 @@ "@jupyterlab/fileeditor": "^4.0.5", "@jupyterlab/logconsole": "^4.0.5", "@jupyterlab/notebook": "^4.0.5", + "@jupyterlab/rendermime": "^4.0.5", "@jupyterlab/services": "^7.0.5", "@jupyterlab/settingregistry": "^4.0.5", "@jupyterlab/statedb": "^4.0.5", @@ -72,13 +73,14 @@ "@jupyterlab/ui-components": "^4.0.5", "@lumino/commands": "^2.1.0", "@lumino/widgets": "^2.1.0", + "chat-jupyter": "0.1.0", "y-protocols": "^1.0.5", "y-websocket": "^1.3.15", "yjs": "^13.5.40" }, "devDependencies": { "@jupyterlab/builder": "^4.0.5", - "@types/react": "~18.0.26", + "@types/react": "^18.0.26", "npm-run-all": "^4.1.5", "rimraf": "^4.1.2", "typescript": "~5.0.4" diff --git a/packages/collaboration-extension/schema/chat.json b/packages/collaboration-extension/schema/chat.json new file mode 100644 index 00000000..125cfef9 --- /dev/null +++ b/packages/collaboration-extension/schema/chat.json @@ -0,0 +1,14 @@ +{ + "title": "Jupyter chat configuration", + "description": "Configuration for the chat panel", + "type": "object", + "properties": { + "sendWithShiftEnter": { + "description": "Whether to send a message via Shift-Enter instead of Enter.", + "type": "boolean", + "default": false, + "readOnly": false + } + }, + "additionalProperties": false +} diff --git a/packages/collaboration-extension/src/chat.ts b/packages/collaboration-extension/src/chat.ts new file mode 100644 index 00000000..dfbb2e3d --- /dev/null +++ b/packages/collaboration-extension/src/chat.ts @@ -0,0 +1,304 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. +/** + * @packageDocumentation + * @module collaboration-extension + */ + +import { chatIcon } from 'chat-jupyter'; +import { + IChatFileType, + IChatPanel, + IGlobalAwareness +} from '@jupyter/collaboration'; +import { + ChatPanel, + ChatWidgetFactory, + CollaborativeChatModelFactory, + CollaborativeChatModel, + CollaborativeChatWidget, + ICollaborativeDrive, + YChat +} from '@jupyter/docprovider'; +import { + ILayoutRestorer, + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; +import { + IThemeManager, + InputDialog, + WidgetTracker +} from '@jupyterlab/apputils'; +import { PathExt } from '@jupyterlab/coreutils'; +import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; +import { Contents } from '@jupyterlab/services'; +import { ISettingRegistry } from '@jupyterlab/settingregistry'; +import { addIcon } from '@jupyterlab/ui-components'; +import { Awareness } from 'y-protocols/awareness'; + +const pluginIds = { + chatDocument: '@jupyter/collaboration-extension:chat-document', + chat: '@jupyter/collaboration-extension:chat' +}; + +/** + * Extension registering the chat file type. + */ +export const chatDocument: JupyterFrontEndPlugin = { + id: pluginIds.chatDocument, + description: 'A document registration for collaborative chat', + autoStart: true, + requires: [IGlobalAwareness, IRenderMimeRegistry], + optional: [ICollaborativeDrive, IThemeManager], + provides: IChatFileType, + activate: ( + app: JupyterFrontEnd, + awareness: Awareness, + rmRegistry: IRenderMimeRegistry, + drive: ICollaborativeDrive | null, + themeManager: IThemeManager | null + ): IChatFileType => { + // Namespace for the tracker + const namespace = 'chat'; + + // Creating the tracker for the document + const tracker = new WidgetTracker({ namespace }); + + const chatFileType: IChatFileType = { + name: 'chat', + displayName: 'Chat', + mimeTypes: ['text/json', 'application/json'], + extensions: ['.chat'], + fileFormat: 'text', + contentType: 'chat' + }; + + app.docRegistry.addFileType(chatFileType); + + if (drive) { + const chatFactory = () => { + return YChat.create(); + }; + drive.sharedModelFactory.registerDocumentFactory('chat', chatFactory); + } + + // Creating and registering the model factory for our custom DocumentModel + const modelFactory = new CollaborativeChatModelFactory({ awareness }); + app.docRegistry.addModelFactory(modelFactory); + + // Creating the widget factory to register it so the document manager knows about + // our new DocumentWidget + const widgetFactory = new ChatWidgetFactory({ + name: 'chat-factory', + modelName: 'chat-model', + fileTypes: ['chat'], + defaultFor: ['chat'], + themeManager, + rmRegistry + }); + + // Add the widget to the tracker when it's created + widgetFactory.widgetCreated.connect((sender, widget) => { + // Notify the instance tracker if restore data needs to update. + widget.context.pathChanged.connect(() => { + tracker.save(widget); + }); + tracker.add(widget); + }); + + // Registering the widget factory + app.docRegistry.addWidgetFactory(widgetFactory); + + return chatFileType; + } +}; + +/** + * Extension providing a chat panel. + */ +export const chat: JupyterFrontEndPlugin = { + id: pluginIds.chat, + description: 'A chat extension for Jupyter', + autoStart: true, + provides: IChatPanel, + requires: [ + IChatFileType, + ICollaborativeDrive, + IGlobalAwareness, + IRenderMimeRegistry + ], + optional: [ILayoutRestorer, ISettingRegistry, IThemeManager], + activate: ( + app: JupyterFrontEnd, + chatFileType: IChatFileType, + drive: ICollaborativeDrive, + awareness: Awareness, + rmRegistry: IRenderMimeRegistry, + restorer: ILayoutRestorer | null, + settingsRegistry: ISettingRegistry, + 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'); + } + + /** + * Load the settings. + */ + let sendWithShiftEnter = false; + + function loadSetting(setting: ISettingRegistry.ISettings): void { + // Read the settings and convert to the correct type + sendWithShiftEnter = setting.get('sendWithShiftEnter') + .composite as boolean; + chatPanel.config = { sendWithShiftEnter }; + } + + // Wait for the application to be restored and + // for the settings to be loaded + Promise.all([app.restored, settingsRegistry.load(pluginIds.chat)]) + .then(([, setting]) => { + // Read the settings + loadSetting(setting); + + // Listen for the plugin setting changes + setting.changed.connect(loadSetting); + }) + .catch(reason => { + console.error( + `Something went wrong when reading the settings.\n${reason}` + ); + }); + + /** + * Command to create a new chat. + */ + commands.addCommand(ChatPanel.CommandIDs.createChat, { + label: 'New chat', + caption: 'Create a new chat', + icon: addIcon, + execute: async args => { + let filepath = (args.filepath as string) ?? ''; + if (!filepath) { + let name: string | null = (args.name as string) ?? null; + if (!name) { + name = ( + await InputDialog.getText({ + label: 'Name', + placeholder: 'untitled', + title: 'Name of the chat' + }) + ).value; + } + // no-op if the dialog has been cancelled + // fill the filepath if the dialog has been validated with content + // otherwise create a new untitled chat (empty filepath) + if (name === null) { + return; + } else if (name) { + filepath = `${name}${chatFileType.extensions[0]}`; + } + } + commands.execute(ChatPanel.CommandIDs.openOrCreateChat, { filepath }); + } + }); + + /** + * Command to open or create a chat. + * It requires the 'filepath' argument. + */ + commands.addCommand(ChatPanel.CommandIDs.openOrCreateChat, { + label: 'Open or create a chat', + execute: async args => { + if (args.filepath === undefined) { + console.error('Open or create a chat: filepath argument missing'); + } + const filepath = args.filepath as string; + let model: Contents.IModel | null = null; + if (filepath) { + model = await drive + .get(filepath) + .then(m => m) + .catch(() => null); + } + + if (!model) { + // Create a new untitled chat + model = await drive.newUntitled({ + type: 'file', + ext: chatFileType.extensions[0] + }); + // Rename it if a name has been provided + if (filepath) { + model = await drive.rename(model.path, filepath); + } + // Add an empty content in the file (empty JSON) + model = await drive.save(model.path, { + ...model, + format: chatFileType.fileFormat, + size: undefined, + content: '{}' + }); + // Open it again to ensure the file has been created. + // TODO: Fix it, the previous steps should already ensure it. + model = await drive + .get(model.path) + .then(m => m) + .catch(() => null); + } + + if (!model) { + console.error('The chat file has not been created'); + return; + } + + /** + * 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 }); + + /** + * Add a chat widget to the side panel. + */ + chatPanel.addChat( + chat, + PathExt.basename(model.name, chatFileType.extensions[0]) + ); + } + }); + + console.log('Collaborative chat initialized'); + + return chatPanel; + } +}; diff --git a/packages/collaboration-extension/src/index.ts b/packages/collaboration-extension/src/index.ts index 513befa1..86a3ee8c 100644 --- a/packages/collaboration-extension/src/index.ts +++ b/packages/collaboration-extension/src/index.ts @@ -7,6 +7,7 @@ import { JupyterFrontEndPlugin } from '@jupyterlab/application'; +import { chat, chatDocument } from './chat'; import { drive, yfile, @@ -27,6 +28,8 @@ import { sharedLink } from './sharedlink'; * Export the plugins as default. */ const plugins: JupyterFrontEndPlugin[] = [ + chat, + chatDocument, drive, yfile, ynotebook, diff --git a/packages/collaboration/src/tokens.ts b/packages/collaboration/src/tokens.ts index 5f717390..8dc28a54 100644 --- a/packages/collaboration/src/tokens.ts +++ b/packages/collaboration/src/tokens.ts @@ -3,8 +3,9 @@ import type { Menu } from '@lumino/widgets'; import { Token } from '@lumino/coreutils'; +import { ChatPanel } from '@jupyter/docprovider'; +import { DocumentRegistry } from '@jupyterlab/docregistry'; import type { User } from '@jupyterlab/services'; - import { IAwareness } from '@jupyter/ydoc'; /** @@ -24,6 +25,20 @@ export const IGlobalAwareness = new Token( '@jupyter/collaboration:IGlobalAwareness' ); +/** + * The chat panel token. + */ +export const IChatPanel = new Token( + '@jupyter/collaboration:IChatPanel' +); + +/** + * The token for the chat file type. + */ +export const IChatFileType = new Token( + '@jupyter/collaboration:IChatFileType' +); + /** * An interface describing the user menu. */ @@ -91,3 +106,8 @@ export interface ICollaboratorAwareness { */ current?: string; } + +/** + * Chat file type. + */ +export type IChatFileType = DocumentRegistry.IFileType; diff --git a/packages/docprovider/package.json b/packages/docprovider/package.json index fec4a135..058e08e5 100644 --- a/packages/docprovider/package.json +++ b/packages/docprovider/package.json @@ -41,12 +41,13 @@ "watch": "tsc -b --watch" }, "dependencies": { - "@jupyter/ydoc": "^1.1.0-a0", + "@jupyter/ydoc": "^1.1.1", "@jupyterlab/coreutils": "^6.0.5", "@jupyterlab/services": "^7.0.5", "@lumino/coreutils": "^2.1.0", "@lumino/disposable": "^2.1.0", "@lumino/signaling": "^2.1.0", + "chat-jupyter": "0.1.0", "y-protocols": "^1.0.5", "y-websocket": "^1.3.15", "yjs": "^13.5.40" diff --git a/packages/docprovider/src/chat/factory.ts b/packages/docprovider/src/chat/factory.ts new file mode 100644 index 00000000..a84a1285 --- /dev/null +++ b/packages/docprovider/src/chat/factory.ts @@ -0,0 +1,152 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +import { ChatWidget, IChatModel } from 'chat-jupyter'; +import { IThemeManager } from '@jupyterlab/apputils'; +import { ABCWidgetFactory, DocumentRegistry } from '@jupyterlab/docregistry'; +import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; +import { Contents } from '@jupyterlab/services'; +import { Awareness } from 'y-protocols/awareness'; + +import { CollaborativeChatModel } from './model'; +import { CollaborativeChatWidget } from './widget'; +import { YChat } from './ychat'; + +/** + * A widget factory to create new instances of CollaborativeChatWidget. + */ +export class ChatWidgetFactory extends ABCWidgetFactory< + CollaborativeChatWidget, + CollaborativeChatModel +> { + /** + * Constructor of ChatWidgetFactory. + * + * @param options Constructor options + */ + constructor(options: ChatWidgetFactory.IOptions) { + super(options); + this._themeManager = options.themeManager; + this._rmRegistry = options.rmRegistry; + } + + /** + * Create a new widget given a context. + * + * @param context Contains the information of the file + * @returns The widget + */ + protected createNewWidget( + context: ChatWidgetFactory.IContext + ): CollaborativeChatWidget { + context.chatModel = context.model; + context.rmRegistry = this._rmRegistry; + context.themeManager = this._themeManager; + return new CollaborativeChatWidget({ + context, + content: new ChatWidget(context) + }); + } + + private _themeManager: IThemeManager | null; + private _rmRegistry: IRenderMimeRegistry; +} + +export namespace ChatWidgetFactory { + export interface IContext + extends DocumentRegistry.IContext { + chatModel: IChatModel; + themeManager: IThemeManager | null; + rmRegistry: IRenderMimeRegistry; + } + + export interface IOptions extends DocumentRegistry.IWidgetFactoryOptions { + themeManager: IThemeManager | null; + rmRegistry: IRenderMimeRegistry; + } +} + +export class CollaborativeChatModelFactory + implements DocumentRegistry.IModelFactory +{ + constructor(options: CollaborativeChatModel.IOptions) { + this._awareness = options.awareness; + } + + collaborative = true; + /** + * The name of the model. + * + * @returns The name + */ + get name(): string { + return 'chat-model'; + } + + /** + * The content type of the file. + * + * @returns The content type + */ + get contentType(): Contents.ContentType { + return 'chat'; + } + + /** + * The format of the file. + * + * @returns the file format + */ + get fileFormat(): Contents.FileFormat { + return 'text'; + } + + /** + * Get whether the model factory has been disposed. + * + * @returns disposed status + */ + + get isDisposed(): boolean { + return this._disposed; + } + + /** + * Dispose the model factory. + */ + dispose(): void { + this._disposed = true; + } + + /** + * Get the preferred language given the path on the file. + * + * @param path path of the file represented by this document model + * @returns The preferred language + */ + preferredLanguage(path: string): string { + return ''; + } + + /** + * Create a new instance of CollaborativeChatModel. + * + * @param languagePreference Language + * @param modelDB Model database + * @returns The model + */ + + createNew( + options: DocumentRegistry.IModelOptions + ): CollaborativeChatModel { + return new CollaborativeChatModel({ + ...options, + awareness: this._awareness + }); + } + + private _disposed = false; + private _awareness: Awareness; +} diff --git a/packages/docprovider/src/chat/index.ts b/packages/docprovider/src/chat/index.ts new file mode 100644 index 00000000..52b5ade0 --- /dev/null +++ b/packages/docprovider/src/chat/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +export * from './factory'; +export * from './model'; +export * from './widget'; +export * from './ychat'; diff --git a/packages/docprovider/src/chat/model.ts b/packages/docprovider/src/chat/model.ts new file mode 100644 index 00000000..bb6a0f4b --- /dev/null +++ b/packages/docprovider/src/chat/model.ts @@ -0,0 +1,175 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +import { ChatModel, IChatMessage, INewMessage, IUser } from 'chat-jupyter'; +import { DocumentRegistry } from '@jupyterlab/docregistry'; +import { IChangedArgs } from '@jupyterlab/coreutils'; +import { PartialJSONObject, UUID } from '@lumino/coreutils'; +import { ISignal, Signal } from '@lumino/signaling'; +import { Awareness } from 'y-protocols/awareness'; +import { ChatChange, YChat } from './ychat'; + +/** + * Collaborative chat namespace. + */ +export namespace CollaborativeChatModel { + export interface IOptions extends ChatModel.IOptions { + awareness: Awareness; + sharedModel?: YChat; + languagePreference?: string; + } +} + +/** + * The collaborative chat model. + */ +export class CollaborativeChatModel + extends ChatModel + implements DocumentRegistry.IModel +{ + constructor(options: CollaborativeChatModel.IOptions) { + super(options); + + this._awareness = options.awareness; + const { sharedModel } = options; + + if (sharedModel) { + this._sharedModel = sharedModel; + } else { + this._sharedModel = YChat.create(); + } + + this.sharedModel.changed.connect(this._onchange, this); + + this._user = this._awareness.states.get(this._awareness.clientID)?.user; + } + + readonly collaborative = true; + + get sharedModel(): YChat { + return this._sharedModel; + } + + get contentChanged(): ISignal { + return this._contentChanged; + } + + get stateChanged(): ISignal> { + return this._stateChanged; + } + + get dirty(): boolean { + return this._dirty; + } + set dirty(value: boolean) { + this._dirty = value; + } + + get readOnly(): boolean { + return this._readOnly; + } + set readOnly(value: boolean) { + this._readOnly = value; + } + + get disposed(): ISignal { + return this._disposed; + } + + dispose(): void { + if (this.isDisposed) { + return; + } + super.dispose(); + this._sharedModel.dispose(); + this._disposed.emit(); + Signal.clearData(this); + } + + toString(): string { + return JSON.stringify({}, null, 2); + } + + fromString(data: string): void { + /** */ + } + + toJSON(): PartialJSONObject { + return JSON.parse(this.toString()); + } + + fromJSON(data: PartialJSONObject): void { + // nothing to do + } + + initialize(): void { + // + } + + /** + * A function called before transferring the message to the panel(s). + * Can be useful if some actions are required on the message. + * + * It is used in this case to retrieve the user avatar color, unknown on server side. + */ + protected formatChatMessage(message: IChatMessage): IChatMessage { + if (this._awareness) { + const sender = Array.from(this._awareness.states.values()).find( + awareness => awareness.user.username === message.sender.username + )?.user; + if (sender) { + message.sender.color = sender.color; + } + } + return message; + } + + addMessage(message: INewMessage): Promise | boolean | void { + const msg: IChatMessage = { + type: 'msg', + id: UUID.uuid4(), + body: message.body, + time: Date.now() / 1000, + sender: this._user + }; + + this.sharedModel.transact(() => void this.sharedModel.setMessage(msg)); + } + + private _onchange = (_: YChat, change: ChatChange) => { + if (change.messagesChange) { + const msgDelta = change.messagesChange; + // let retain: number; + const messages: IChatMessage[] = []; + msgDelta.forEach(data => { + if (data.retain) { + // retain = data.retain; + } else if (data.insert) { + messages.push(...data.insert); + } + }); + + if (messages) { + messages.forEach(message => { + this.onMessage(message); + }); + } + } + }; + + readonly defaultKernelName: string = ''; + readonly defaultKernelLanguage: string = ''; + + private _sharedModel: YChat; + + private _dirty = false; + private _readOnly = false; + private _disposed = new Signal(this); + private _contentChanged = new Signal(this); + private _stateChanged = new Signal>(this); + + private _awareness: Awareness; + private _user: IUser; +} diff --git a/packages/docprovider/src/chat/widget.tsx b/packages/docprovider/src/chat/widget.tsx new file mode 100644 index 00000000..a48f8a68 --- /dev/null +++ b/packages/docprovider/src/chat/widget.tsx @@ -0,0 +1,299 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +import { IChatModel, ChatWidget, IConfig } from 'chat-jupyter'; +import { IThemeManager } from '@jupyterlab/apputils'; +import { PathExt } from '@jupyterlab/coreutils'; +import { DocumentWidget } from '@jupyterlab/docregistry'; +import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; +import { + addIcon, + closeIcon, + CommandToolbarButton, + HTMLSelect, + PanelWithToolbar, + ReactWidget, + SidePanel, + ToolbarButton +} from '@jupyterlab/ui-components'; +import { CommandRegistry } from '@lumino/commands'; +import { AccordionPanel, Panel } from '@lumino/widgets'; +import React, { useEffect, useState } from 'react'; + +import { ICollaborativeDrive } from '../tokens'; +import { CollaborativeChatModel } from './model'; + +const SIDEPANEL_CLASS = 'jp-collab-chat-sidepanel'; +const ADD_BUTTON_CLASS = 'jp-collab-chat-add'; +const OPEN_SELECT_CLASS = 'jp-collab-chat-open'; +const SECTION_CLASS = 'jp-collab-chat-section'; +const TOOLBAR_CLASS = 'jp-collab-chat-toolbar'; + +/** + * DocumentWidget: widget that represents the view or editor for a file type. + */ +export class CollaborativeChatWidget extends DocumentWidget< + ChatWidget, + CollaborativeChatModel +> { + constructor( + options: DocumentWidget.IOptions + ) { + super(options); + } + + /** + * Dispose of the resources held by the widget. + */ + dispose(): void { + this.content.dispose(); + super.dispose(); + } +} + +/** + * Sidepanel widget including the chats and the add chat button. + */ +export class ChatPanel extends SidePanel { + /** + * The construcotr of the chat panel. + */ + constructor(options: ChatPanel.IOptions) { + super(options); + this.addClass(SIDEPANEL_CLASS); + this._commands = options.commands; + this._rmRegistry = options.rmRegistry; + this._themeManager = options.themeManager; + + const addChat = new CommandToolbarButton({ + commands: this._commands, + id: ChatPanel.CommandIDs.createChat, + icon: addIcon + }); + addChat.addClass(ADD_BUTTON_CLASS); + this.toolbar.addItem('createChat', addChat); + + const { drive } = options; + const openChat = ReactWidget.create( + + ); + openChat.addClass(OPEN_SELECT_CLASS); + this.toolbar.addItem('openChat', openChat); + + const content = this.content as AccordionPanel; + content.expansionToggled.connect(this._onExpansionToogled, this); + } + + /** + * Getter and setter of the config, propagated to all the chat widgets. + */ + get config(): IConfig { + return this._config; + } + set config(value: Partial) { + this._config = { ...this._config, ...value }; + this.widgets.forEach(w => { + (w as ChatSection).model.config = value; + }); + } + + /** + * Add a new widget to the chat panel. + * + * @param model - the model of the chat widget + * @param name - the name of the chat. + */ + addChat(model: IChatModel, name: string): void { + // Collapse all chats + const content = this.content as AccordionPanel; + for (let i = 0; i < this.widgets.length; i++) { + content.collapse(i); + } + + // Create a new widget. + const widget = new ChatWidget({ + model: model, + rmRegistry: this._rmRegistry, + themeManager: this._themeManager + }); + this.addWidget(new ChatSection({ widget, name })); + } + + /** + * Handle `change` events for the HTMLSelect component. + */ + private _chatSelected = ( + event: React.ChangeEvent + ): void => { + const value = event.target.value; + if (value === '-') { + return; + } + + const index = this.widgets.findIndex( + w => (w as ChatSection).name === value + ); + if (index === -1) { + this._commands.execute(ChatPanel.CommandIDs.openOrCreateChat, { + filepath: `${value}.chat` + }); + } else if (!this.widgets[index].isVisible) { + (this.content as AccordionPanel).expand(index); + } + event.target.selectedIndex = 0; + }; + + /** + * Triggered when a section is toogled. If the section is opened, all others + * sections are closed. + */ + private _onExpansionToogled(panel: AccordionPanel, index: number) { + if (!this.widgets[index].isVisible) { + return; + } + for (let i = 0; i < this.widgets.length; i++) { + if (i !== index) { + panel.collapse(i); + } + } + } + + private _commands: CommandRegistry; + private _config: IConfig = {}; + private _rmRegistry: IRenderMimeRegistry; + private _themeManager: IThemeManager | null; +} + +/** + * The chat panel namespace. + */ +export namespace ChatPanel { + /** + * Options of the constructor of the chat panel. + */ + export interface IOptions extends SidePanel.IOptions { + commands: CommandRegistry; + drive: ICollaborativeDrive; + rmRegistry: IRenderMimeRegistry; + themeManager: IThemeManager | null; + } + /** + * Command ids associated to the chat panel. + */ + export const CommandIDs = { + createChat: 'collaborativeChat:create', + openOrCreateChat: 'collaborativeChat:openOrCreate' + }; +} + +/** + * The chat section containing a chat widget. + */ +class ChatSection extends PanelWithToolbar { + /** + * Constructor of the chat section. + */ + constructor(options: ChatSection.IOptions) { + super(options); + this.addClass(SECTION_CLASS); + this._name = options.name; + this.title.label = this._name; + this.title.caption = this._name; + this.toolbar.addClass(TOOLBAR_CLASS); + + const closeButton = new ToolbarButton({ + icon: closeIcon, + className: 'jp-mod-styled', + onClick: () => { + this.model.dispose(); + this.dispose(); + } + }); + this.toolbar.addItem('collaborativeChat-close', closeButton); + + this.addWidget(options.widget); + options.widget.node.style.height = '100%'; + } + + /** + * The name of the chat. + */ + get name(): string { + return this._name; + } + + /** + * The model of the widget. + */ + get model(): IChatModel { + return (this.widgets[0] as ChatWidget).model; + } + + private _name: string; +} + +/** + * The chat section namespace. + */ +export namespace ChatSection { + /** + * Options to build a chat section. + */ + export interface IOptions extends Panel.IOptions { + widget: ChatWidget; + name: string; + } +} + +type ChatSelectProps = { + drive: ICollaborativeDrive; + handleChange: (event: React.ChangeEvent) => void; +}; + +/** + * A component to select a chat from the drive. + */ +function ChatSelect({ drive, handleChange }: ChatSelectProps): JSX.Element { + const [chatNames, setChatNames] = useState([]); + + /** + * Get chats list on initial render. + */ + useEffect(() => { + // Find chat files in drive (root level only) + // TODO: search in sub-directories ? + async function getChats() { + drive + .get('.') + .then(model => { + const chatsName = (model.content as any[]) + .filter(f => f.type === 'file' && f.name.endsWith('.chat')) + .map(f => PathExt.basename(f.name, '.chat')); + setChatNames(chatsName); + }) + .catch(e => console.error('Error getting the chat file in drive')); + } + + // Listen for changes in drive. + drive.fileChanged.connect((_, change) => { + getChats(); + }); + + // Initialize the chats list. + getChats(); + }, [drive]); + + return ( + + + {chatNames.map(name => ( + + ))} + + ); +} diff --git a/packages/docprovider/src/chat/ychat.ts b/packages/docprovider/src/chat/ychat.ts new file mode 100644 index 00000000..cdb276ec --- /dev/null +++ b/packages/docprovider/src/chat/ychat.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +// TODO: remove this module in favor of the one in jupyter_ydoc when released. + +import { Delta, DocumentChange, MapChange, YDocument } from '@jupyter/ydoc'; +import { JSONExt, JSONObject } from '@lumino/coreutils'; +import * as Y from 'yjs'; + +/** + * Definition of the shared Chat changes. + */ +export type ChatChange = DocumentChange & { + /** + * Messages list change + */ + messagesChange?: Delta; + /** + * Content change + */ + contentChange?: MapChange; +}; + +interface IDict { + [key: string]: T; +} + +/** + * The collaborative chat shared document. + */ +export class YChat extends YDocument { + /** + * Create a new collaborative chat model. + */ + constructor(options?: YDocument.IOptions) { + super(options); + this._content = this.ydoc.getMap('content'); + this._content.observe(this._contentObserver); + + this._messages = this.ydoc.getArray('messages'); + this._messages.observe(this._messagesObserver); + } + + /** + * Document version + */ + readonly version: string = '1.0.0'; + + /** + * Static method to create instances on the sharedModel + * + * @returns The sharedModel instance + */ + static create(options?: YDocument.IOptions): YChat { + return new YChat(options); + } + + get content(): JSONObject { + return JSONExt.deepCopy(this._content.toJSON()); + } + + get messages(): string[] { + return JSONExt.deepCopy(this._messages.toJSON()); + } + + setMessage(value: IDict): void { + this._messages.push([value]); + } + + private _contentObserver = (event: Y.YMapEvent): void => { + this._changed.emit(this.content); + }; + + private _messagesObserver = (event: Y.YArrayEvent): void => { + const changes: ChatChange = {}; + changes.messagesChange = event.delta; + this._changed.emit(changes); + }; + + private _content: Y.Map; + private _messages: Y.Array; +} diff --git a/packages/docprovider/src/index.ts b/packages/docprovider/src/index.ts index 62edebda..30892ff2 100644 --- a/packages/docprovider/src/index.ts +++ b/packages/docprovider/src/index.ts @@ -8,6 +8,8 @@ */ export * from './awareness'; +export * from './chat'; +export * from './requests'; export * from './ydrive'; export * from './yprovider'; export * from './tokens'; diff --git a/packages/docprovider/tsconfig.json b/packages/docprovider/tsconfig.json index 50493532..7ddd42de 100644 --- a/packages/docprovider/tsconfig.json +++ b/packages/docprovider/tsconfig.json @@ -4,6 +4,6 @@ "outDir": "lib", "rootDir": "src" }, - "include": ["src/*"], + "include": ["src/*", "src/**/*"], "exclude": ["src/__tests__/*"] } diff --git a/ui-tests/jupyter_server_test_config.py b/ui-tests/jupyter_server_test_config.py index 86fd4353..40174569 100644 --- a/ui-tests/jupyter_server_test_config.py +++ b/ui-tests/jupyter_server_test_config.py @@ -14,5 +14,7 @@ c: Any configure_jupyter_server(c) # noqa +c.FileContentsManager.delete_to_trash = False + # Uncomment to set server log level to debug level # c.ServerApp.log_level = "DEBUG" diff --git a/ui-tests/tests/chat.spec.ts b/ui-tests/tests/chat.spec.ts new file mode 100644 index 00000000..91bda7f4 --- /dev/null +++ b/ui-tests/tests/chat.spec.ts @@ -0,0 +1,145 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +import { expect, IJupyterLabPageFixture, test } from '@jupyterlab/galata'; +import type { User } from '@jupyterlab/services'; +import type { Locator } from '@playwright/test'; + +const openPanel = async (page: IJupyterLabPageFixture): Promise => { + const panel = page.locator('.jp-SidePanel.jp-collab-chat-sidepanel'); + + if (!(await panel?.isVisible())) { + const chatIcon = page.getByTitle('Jupyter Chat'); + await chatIcon.click(); + await expect(panel).toBeVisible(); + } + return panel.first(); +}; + +test.describe('Initialization', () => { + test('should contain the chat panel icon', async ({ page }) => { + const chatIcon = page.getByTitle('Jupyter Chat'); + expect(chatIcon).toHaveCount(1); + expect(await chatIcon.screenshot()).toMatchSnapshot( + 'chat_icon.png' + ); + }); + + test('chat panel should contain a toolbar', async ({ page }) => { + const panel = await openPanel(page); + const toolbar = panel.locator('.jp-SidePanel-toolbar'); + await expect(toolbar).toHaveCount(1); + + const items = toolbar.locator('.jp-Toolbar-item'); + await expect(items).toHaveCount(2); + await expect(items.first()).toHaveClass(/.jp-collab-chat-add/); + await expect(items.last()).toHaveClass(/.jp-collab-chat-open/); + }); + + test('chat panel should not contain a chat at init', async ({ page }) => { + const panel = await openPanel(page); + const content = panel.locator('.jp-SidePanel-content'); + await expect(content).toBeEmpty(); + }); +}) + +test.describe('Chat creation', () => { + let panel: Locator; + let dialog: Locator; + + test.beforeEach(async ({ page }) => { + panel = await openPanel(page); + const addButton = panel.locator( + '.jp-SidePanel-toolbar .jp-Toolbar-item.jp-collab-chat-add' + ); + await addButton.click(); + + dialog = page.locator('.jp-Dialog'); + await dialog.waitFor(); + }); + + test('should create a chat', async ({ page }) => { + await dialog.locator('input[type="text"]').type('chat-test'); + await dialog.getByRole('button').getByText('Ok').click(); + expect(await page.filebrowser.contents.fileExists('chat-test.chat')).toBeTruthy(); + + const chatTitle = panel.locator('.jp-SidePanel-content .jp-AccordionPanel-title'); + await expect(chatTitle).toHaveCount(1); + await expect(chatTitle.locator('.lm-AccordionPanel-titleLabel')).toHaveText( + 'chat-test' + ); + }); + + test('should create an untitled file if no name is provided', async ({ page }) => { + await dialog.getByRole('button').getByText('Ok').click(); + expect(await page.filebrowser.contents.fileExists('untitled.chat')).toBeTruthy(); + + const chatTitle = panel.locator('.jp-SidePanel-content .jp-AccordionPanel-title'); + await expect(chatTitle).toHaveCount(1); + await expect(chatTitle.locator('.lm-AccordionPanel-titleLabel')).toHaveText( + 'untitled' + ); + }); + + test('should not create a chat if dialog is cancelled', async ({ page }) => { + await dialog.getByRole('button').getByText('Cancel').click(); + + const content = panel.locator('.jp-SidePanel-content'); + await expect(content).toBeEmpty(); + }); +}) + +test.describe('Opening/closing chat', () => { + const name = 'my-chat'; + let panel: Locator; + let select: Locator; + + test.beforeEach(async ({ page }) => { + await page.filebrowser.contents.uploadContent('{}', 'text', `${name}.chat`); + }); + + test.afterEach(async ({ page }) => { + await page.filebrowser.contents.deleteFile( `${name}.chat`); + }); + + test('should list existing chat', async ({ page }) => { + // reload to update the chat list + // FIX: add listener on file creation + await page.reload(); + panel = await openPanel(page); + select = panel.locator( + '.jp-SidePanel-toolbar .jp-Toolbar-item.jp-collab-chat-open select' + ); + + for (let i=0; i< await select.locator('option').count(); i++) { + console.log(await select.locator('option').nth(i).textContent()); + } + await expect(select.locator('option')).toHaveCount(2); + await expect(select.locator('option').last()).toHaveText(name); + }); + + test('should open an existing chat and close it', async ({ page }) => { + // reload to update the chat list + // FIX: add listener on file creation + await page.reload(); + panel = await openPanel(page); + select = panel.locator( + '.jp-SidePanel-toolbar .jp-Toolbar-item.jp-collab-chat-open select' + ); + + await select.selectOption(name); + + const chatTitle = panel.locator('.jp-SidePanel-content .jp-AccordionPanel-title'); + await expect(chatTitle).toHaveCount(1); + await expect(chatTitle.locator('.lm-AccordionPanel-titleLabel')).toHaveText( + name + ); + + await page.pause(); + await chatTitle.getByRole('button').click(); + await page.pause(); + await expect(chatTitle).toHaveCount(0); + }); +}); diff --git a/ui-tests/tests/chat.spec.ts-snapshots/chat-icon-linux.png b/ui-tests/tests/chat.spec.ts-snapshots/chat-icon-linux.png new file mode 100644 index 00000000..76b5d4e0 Binary files /dev/null and b/ui-tests/tests/chat.spec.ts-snapshots/chat-icon-linux.png differ diff --git a/yarn.lock b/yarn.lock index f52db1a8..8e168b9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -190,6 +190,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.16.7": + version: 7.24.3 + resolution: "@babel/helper-module-imports@npm:7.24.3" + dependencies: + "@babel/types": ^7.24.0 + checksum: c23492189ba97a1ec7d37012336a5661174e8b88194836b6bbf90d13c3b72c1db4626263c654454986f924c6da8be7ba7f9447876d709cd00bd6ffde6ec00796 + languageName: node + linkType: hard + "@babel/helper-module-imports@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-module-imports@npm:7.18.6" @@ -293,6 +302,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.23.4": + version: 7.24.1 + resolution: "@babel/helper-string-parser@npm:7.24.1" + checksum: 8404e865b06013979a12406aab4c0e8d2e377199deec09dfe9f57b833b0c9ce7b6e8c1c553f2da8d0bcd240c5005bd7a269f4fef0d628aeb7d5fe035c436fb67 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": version: 7.19.1 resolution: "@babel/helper-validator-identifier@npm:7.19.1" @@ -300,6 +316,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-validator-identifier@npm:7.22.20" + checksum: 136412784d9428266bcdd4d91c32bcf9ff0e8d25534a9d94b044f77fe76bc50f941a90319b05aafd1ec04f7d127cd57a179a3716009ff7f3412ef835ada95bdc + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.18.6": version: 7.21.0 resolution: "@babel/helper-validator-option@npm:7.21.0" @@ -1258,6 +1281,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": + version: 7.24.1 + resolution: "@babel/runtime@npm:7.24.1" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 5c8f3b912ba949865f03b3cf8395c60e1f4ebd1033fbd835bdfe81b6cac8a87d85bc3c7aded5fcdf07be044c9ab8c818f467abe0deca50020c72496782639572 + languageName: node + linkType: hard + "@babel/runtime@npm:^7.8.4": version: 7.21.5 resolution: "@babel/runtime@npm:7.21.5" @@ -1307,6 +1339,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.24.0": + version: 7.24.0 + resolution: "@babel/types@npm:7.24.0" + dependencies: + "@babel/helper-string-parser": ^7.23.4 + "@babel/helper-validator-identifier": ^7.22.20 + to-fast-properties: ^2.0.0 + checksum: 4b574a37d490f621470ff36a5afaac6deca5546edcb9b5e316d39acbb20998e9c2be42f3fc0bf2b55906fc49ff2a5a6a097e8f5a726ee3f708a0b0ca93aed807 + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -1622,6 +1665,152 @@ __metadata: languageName: node linkType: hard +"@emotion/babel-plugin@npm:^11.11.0": + version: 11.11.0 + resolution: "@emotion/babel-plugin@npm:11.11.0" + dependencies: + "@babel/helper-module-imports": ^7.16.7 + "@babel/runtime": ^7.18.3 + "@emotion/hash": ^0.9.1 + "@emotion/memoize": ^0.8.1 + "@emotion/serialize": ^1.1.2 + babel-plugin-macros: ^3.1.0 + convert-source-map: ^1.5.0 + escape-string-regexp: ^4.0.0 + find-root: ^1.1.0 + source-map: ^0.5.7 + stylis: 4.2.0 + checksum: 6b363edccc10290f7a23242c06f88e451b5feb2ab94152b18bb8883033db5934fb0e421e2d67d09907c13837c21218a3ac28c51707778a54d6cd3706c0c2f3f9 + languageName: node + linkType: hard + +"@emotion/cache@npm:^11.11.0": + version: 11.11.0 + resolution: "@emotion/cache@npm:11.11.0" + dependencies: + "@emotion/memoize": ^0.8.1 + "@emotion/sheet": ^1.2.2 + "@emotion/utils": ^1.2.1 + "@emotion/weak-memoize": ^0.3.1 + stylis: 4.2.0 + checksum: 8eb1dc22beaa20c21a2e04c284d5a2630a018a9d51fb190e52de348c8d27f4e8ca4bbab003d68b4f6cd9cc1c569ca747a997797e0f76d6c734a660dc29decf08 + languageName: node + linkType: hard + +"@emotion/hash@npm:^0.9.1": + version: 0.9.1 + resolution: "@emotion/hash@npm:0.9.1" + checksum: 716e17e48bf9047bf9383982c071de49f2615310fb4e986738931776f5a823bc1f29c84501abe0d3df91a3803c80122d24e28b57351bca9e01356ebb33d89876 + languageName: node + linkType: hard + +"@emotion/is-prop-valid@npm:^1.2.1": + version: 1.2.2 + resolution: "@emotion/is-prop-valid@npm:1.2.2" + dependencies: + "@emotion/memoize": ^0.8.1 + checksum: 61f6b128ea62b9f76b47955057d5d86fcbe2a6989d2cd1e583daac592901a950475a37d049b9f7a7c6aa8758a33b408735db759fdedfd1f629df0f85ab60ea25 + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.8.1": + version: 0.8.1 + resolution: "@emotion/memoize@npm:0.8.1" + checksum: a19cc01a29fcc97514948eaab4dc34d8272e934466ed87c07f157887406bc318000c69ae6f813a9001c6a225364df04249842a50e692ef7a9873335fbcc141b0 + languageName: node + linkType: hard + +"@emotion/react@npm:^11.10.5": + version: 11.11.4 + resolution: "@emotion/react@npm:11.11.4" + dependencies: + "@babel/runtime": ^7.18.3 + "@emotion/babel-plugin": ^11.11.0 + "@emotion/cache": ^11.11.0 + "@emotion/serialize": ^1.1.3 + "@emotion/use-insertion-effect-with-fallbacks": ^1.0.1 + "@emotion/utils": ^1.2.1 + "@emotion/weak-memoize": ^0.3.1 + hoist-non-react-statics: ^3.3.1 + peerDependencies: + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 6abaa7a05c5e1db31bffca7ac79169f5456990022cbb3794e6903221536609a60420f2b4888dd3f84e9634a304e394130cb88dc32c243a1dedc263e50da329f8 + languageName: node + linkType: hard + +"@emotion/serialize@npm:^1.1.2, @emotion/serialize@npm:^1.1.3": + version: 1.1.3 + resolution: "@emotion/serialize@npm:1.1.3" + dependencies: + "@emotion/hash": ^0.9.1 + "@emotion/memoize": ^0.8.1 + "@emotion/unitless": ^0.8.1 + "@emotion/utils": ^1.2.1 + csstype: ^3.0.2 + checksum: 5a756ce7e2692322683978d8ed2e84eadd60bd6f629618a82c5018c84d98684b117e57fad0174f68ec2ec0ac089bb2e0bcc8ea8c2798eb904b6d3236aa046063 + languageName: node + linkType: hard + +"@emotion/sheet@npm:^1.2.2": + version: 1.2.2 + resolution: "@emotion/sheet@npm:1.2.2" + checksum: d973273c9c15f1c291ca2269728bf044bd3e92a67bca87943fa9ec6c3cd2b034f9a6bfe95ef1b5d983351d128c75b547b43ff196a00a3875f7e1d269793cecfe + languageName: node + linkType: hard + +"@emotion/styled@npm:^11.10.5": + version: 11.11.0 + resolution: "@emotion/styled@npm:11.11.0" + dependencies: + "@babel/runtime": ^7.18.3 + "@emotion/babel-plugin": ^11.11.0 + "@emotion/is-prop-valid": ^1.2.1 + "@emotion/serialize": ^1.1.2 + "@emotion/use-insertion-effect-with-fallbacks": ^1.0.1 + "@emotion/utils": ^1.2.1 + peerDependencies: + "@emotion/react": ^11.0.0-rc.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 904f641aad3892c65d7d6c0808b036dae1e6d6dad4861c1c7dc0baa59977047c6cad220691206eba7b4059f1a1c6e6c1ef4ebb8c829089e280fa0f2164a01e6b + languageName: node + linkType: hard + +"@emotion/unitless@npm:^0.8.1": + version: 0.8.1 + resolution: "@emotion/unitless@npm:0.8.1" + checksum: 385e21d184d27853bb350999471f00e1429fa4e83182f46cd2c164985999d9b46d558dc8b9cc89975cb337831ce50c31ac2f33b15502e85c299892e67e7b4a88 + languageName: node + linkType: hard + +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.0.1": + version: 1.0.1 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.0.1" + peerDependencies: + react: ">=16.8.0" + checksum: 700b6e5bbb37a9231f203bb3af11295eed01d73b2293abece0bc2a2237015e944d7b5114d4887ad9a79776504aa51ed2a8b0ddbc117c54495dd01a6b22f93786 + languageName: node + linkType: hard + +"@emotion/utils@npm:^1.2.1": + version: 1.2.1 + resolution: "@emotion/utils@npm:1.2.1" + checksum: e0b44be0705b56b079c55faff93952150be69e79b660ae70ddd5b6e09fc40eb1319654315a9f34bb479d7f4ec94be6068c061abbb9e18b9778ae180ad5d97c73 + languageName: node + linkType: hard + +"@emotion/weak-memoize@npm:^0.3.1": + version: 0.3.1 + resolution: "@emotion/weak-memoize@npm:0.3.1" + checksum: b2be47caa24a8122622ea18cd2d650dbb4f8ad37b636dc41ed420c2e082f7f1e564ecdea68122b546df7f305b159bf5ab9ffee872abd0f052e687428459af594 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -1664,6 +1853,44 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.0.0": + version: 1.6.0 + resolution: "@floating-ui/core@npm:1.6.0" + dependencies: + "@floating-ui/utils": ^0.2.1 + checksum: 2e25c53b0c124c5c9577972f8ae21d081f2f7895e6695836a53074463e8c65b47722744d6d2b5a993164936da006a268bcfe87fe68fd24dc235b1cb86bed3127 + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.6.1": + version: 1.6.3 + resolution: "@floating-ui/dom@npm:1.6.3" + dependencies: + "@floating-ui/core": ^1.0.0 + "@floating-ui/utils": ^0.2.0 + checksum: 81cbb18ece3afc37992f436e469e7fabab2e433248e46fff4302d12493a175b0c64310f8a971e6e1eda7218df28ace6b70237b0f3c22fe12a21bba05b5579555 + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.0.8": + version: 2.0.8 + resolution: "@floating-ui/react-dom@npm:2.0.8" + dependencies: + "@floating-ui/dom": ^1.6.1 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 5da7f13a69281e38859a3203a608fe9de1d850b332b355c10c0c2427c7b7209a0374c10f6295b6577c1a70237af8b678340bd4cc0a4b1c66436a94755d81e526 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.0, @floating-ui/utils@npm:^0.2.1": + version: 0.2.1 + resolution: "@floating-ui/utils@npm:0.2.1" + checksum: 9ed4380653c7c217cd6f66ae51f20fdce433730dbc77f95b5abfb5a808f5fdb029c6ae249b4e0490a816f2453aa6e586d9a873cd157fdba4690f65628efc6e06 + languageName: node + linkType: hard + "@fortawesome/fontawesome-free@npm:^5.12.0": version: 5.15.4 resolution: "@fortawesome/fontawesome-free@npm:5.15.4" @@ -2035,7 +2262,7 @@ __metadata: dependencies: "@jupyter/collaboration": ^2.0.11 "@jupyter/docprovider": ^2.0.11 - "@jupyter/ydoc": ^1.1.0-a0 + "@jupyter/ydoc": ^1.1.1 "@jupyterlab/application": ^4.0.5 "@jupyterlab/apputils": ^4.0.5 "@jupyterlab/builder": ^4.0.5 @@ -2046,6 +2273,7 @@ __metadata: "@jupyterlab/fileeditor": ^4.0.5 "@jupyterlab/logconsole": ^4.0.5 "@jupyterlab/notebook": ^4.0.5 + "@jupyterlab/rendermime": ^4.0.5 "@jupyterlab/services": ^7.0.5 "@jupyterlab/settingregistry": ^4.0.5 "@jupyterlab/statedb": ^4.0.5 @@ -2053,7 +2281,8 @@ __metadata: "@jupyterlab/ui-components": ^4.0.5 "@lumino/commands": ^2.1.0 "@lumino/widgets": ^2.1.0 - "@types/react": ~18.0.26 + "@types/react": ^18.0.26 + chat-jupyter: 0.1.0 npm-run-all: ^4.1.5 rimraf: ^4.1.2 typescript: ~5.0.4 @@ -2090,7 +2319,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyter/docprovider@workspace:packages/docprovider" dependencies: - "@jupyter/ydoc": ^1.1.0-a0 + "@jupyter/ydoc": ^1.1.1 "@jupyterlab/coreutils": ^6.0.5 "@jupyterlab/services": ^7.0.5 "@jupyterlab/testing": ^4.0.5 @@ -2098,6 +2327,7 @@ __metadata: "@lumino/disposable": ^2.1.0 "@lumino/signaling": ^2.1.0 "@types/jest": ^29.2.0 + chat-jupyter: 0.1.0 jest: ^29.5.0 rimraf: ^4.1.2 typescript: ~5.0.4 @@ -2144,9 +2374,9 @@ __metadata: languageName: node linkType: hard -"@jupyter/ydoc@npm:^1.1.0-a0": - version: 1.1.0-a0 - resolution: "@jupyter/ydoc@npm:1.1.0-a0" +"@jupyter/ydoc@npm:^1.1.1": + version: 1.1.1 + resolution: "@jupyter/ydoc@npm:1.1.1" dependencies: "@jupyterlab/nbformat": ^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0 "@lumino/coreutils": ^1.11.0 || ^2.0.0 @@ -2154,7 +2384,7 @@ __metadata: "@lumino/signaling": ^1.10.0 || ^2.0.0 y-protocols: ^1.0.5 yjs: ^13.5.40 - checksum: 0099bacb2884a460867658e7f5a944e4d6eca7c4d3d68a6b31102be77d8fc9b6ba162af55c20f81c70f7fef4e9d9329eee4da32063c0caa0c6e3f10a488f95b5 + checksum: a239b1dd57cfc9ba36c06ac5032a1b6388849ae01a1d0db0d45094f71fdadf4d473b4bf8becbef0cfcdc85cae505361fbec0822b02da5aa48e06b66f742dd7a0 languageName: node linkType: hard @@ -3216,6 +3446,180 @@ __metadata: languageName: node linkType: hard +"@mui/base@npm:5.0.0-beta.40": + version: 5.0.0-beta.40 + resolution: "@mui/base@npm:5.0.0-beta.40" + dependencies: + "@babel/runtime": ^7.23.9 + "@floating-ui/react-dom": ^2.0.8 + "@mui/types": ^7.2.14 + "@mui/utils": ^5.15.14 + "@popperjs/core": ^2.11.8 + clsx: ^2.1.0 + prop-types: ^15.8.1 + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 9c084ee67de372411a71af5eca9a5367db9f5bce57bb43973629c522760fe64fa2a43d2934dccd24d6dcbcd0ed399c5fc5c461226c86104f5767de1c9b8deba2 + languageName: node + linkType: hard + +"@mui/core-downloads-tracker@npm:^5.15.14": + version: 5.15.14 + resolution: "@mui/core-downloads-tracker@npm:5.15.14" + checksum: 0a1c63d906af594d0a7fb63d1d574482b3916351ea8908e8621c8bfa16ac38cf4edb5a334f0e28084f583ac928b587cab6e031f992195e0a590186faba13b9a5 + languageName: node + linkType: hard + +"@mui/icons-material@npm:^5.11.0": + version: 5.15.14 + resolution: "@mui/icons-material@npm:5.15.14" + dependencies: + "@babel/runtime": ^7.23.9 + peerDependencies: + "@mui/material": ^5.0.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 719036244167c7cb5456074d7d66df0c26d4b200c11620ba177a4e75186f6ecc4930a3b4bcb7300d92aa37df19ca8406fa63ad4347214cc167ddca986eb1a198 + languageName: node + linkType: hard + +"@mui/material@npm:^5.11.0": + version: 5.15.14 + resolution: "@mui/material@npm:5.15.14" + dependencies: + "@babel/runtime": ^7.23.9 + "@mui/base": 5.0.0-beta.40 + "@mui/core-downloads-tracker": ^5.15.14 + "@mui/system": ^5.15.14 + "@mui/types": ^7.2.14 + "@mui/utils": ^5.15.14 + "@types/react-transition-group": ^4.4.10 + clsx: ^2.1.0 + csstype: ^3.1.3 + prop-types: ^15.8.1 + react-is: ^18.2.0 + react-transition-group: ^4.4.5 + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 6105cea67d1a176bf4d360e4316760d761a92d9165a4aae76235c21927d6d72837d9272d9c8f14295393ce7c9bb8852cae7cb7cb4a2434118ce4e19165055625 + languageName: node + linkType: hard + +"@mui/private-theming@npm:^5.15.14": + version: 5.15.14 + resolution: "@mui/private-theming@npm:5.15.14" + dependencies: + "@babel/runtime": ^7.23.9 + "@mui/utils": ^5.15.14 + prop-types: ^15.8.1 + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 1b1ef54e8281c9b13fcc58f4c39682efc610946a68402283c19fcfbce8a7d7a231d61b536d6df9bf7a59a1426591bd403a453a59eb8efb9689437fb58554dc8c + languageName: node + linkType: hard + +"@mui/styled-engine@npm:^5.15.14": + version: 5.15.14 + resolution: "@mui/styled-engine@npm:5.15.14" + dependencies: + "@babel/runtime": ^7.23.9 + "@emotion/cache": ^11.11.0 + csstype: ^3.1.3 + prop-types: ^15.8.1 + peerDependencies: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 23b45c859a4f0d2b10933d06a6082c0ff093f7b6d8d32a2bfe3a6e515fe46d7a38ca9e7150d45c025a2e98d963bae9a5991d131cf4748b62670075ef0fa321ed + languageName: node + linkType: hard + +"@mui/system@npm:^5.15.14": + version: 5.15.14 + resolution: "@mui/system@npm:5.15.14" + dependencies: + "@babel/runtime": ^7.23.9 + "@mui/private-theming": ^5.15.14 + "@mui/styled-engine": ^5.15.14 + "@mui/types": ^7.2.14 + "@mui/utils": ^5.15.14 + clsx: ^2.1.0 + csstype: ^3.1.3 + prop-types: ^15.8.1 + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: bcc216274a99f5df9ef28551f0d4985e065ea55922307162d8877f828e136e50c8f3997241b3566668af3bd473a599a70f8d1becea81d5c052cc5defda5e0aac + languageName: node + linkType: hard + +"@mui/types@npm:^7.2.14": + version: 7.2.14 + resolution: "@mui/types@npm:7.2.14" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 615c9f9110933157f5d3c4fee69d6e70b98fc0d9ebc3b63079b6a1e23e6b389748687a25ab4ac15b56166fc228885da87c3929503b41fa322cfdee0f6d411206 + languageName: node + linkType: hard + +"@mui/utils@npm:^5.15.14": + version: 5.15.14 + resolution: "@mui/utils@npm:5.15.14" + dependencies: + "@babel/runtime": ^7.23.9 + "@types/prop-types": ^15.7.11 + prop-types: ^15.8.1 + react-is: ^18.2.0 + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 36543ba7e3b65fb3219ed27e8f1455aff15b47a74c9b642c63e60774e22baa6492a196079e72bcfa5a570421dab32160398f892110bd444428bcf8b266b11893 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -3770,6 +4174,13 @@ __metadata: languageName: node linkType: hard +"@popperjs/core@npm:^2.11.8": + version: 2.11.8 + resolution: "@popperjs/core@npm:2.11.8" + checksum: e5c69fdebf52a4012f6a1f14817ca8e9599cb1be73dd1387e1785e2ed5e5f0862ff817f420a87c7fc532add1f88a12e25aeb010ffcbdc98eace3d55ce2139cf0 + languageName: node + linkType: hard + "@rjsf/core@npm:^5.1.0": version: 5.6.2 resolution: "@rjsf/core@npm:5.6.2" @@ -4028,25 +4439,40 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.0.26, @types/react@npm:^18.0.27": - version: 18.2.6 - resolution: "@types/react@npm:18.2.6" +"@types/prop-types@npm:^15.7.11": + version: 15.7.12 + resolution: "@types/prop-types@npm:15.7.12" + checksum: ac16cc3d0a84431ffa5cfdf89579ad1e2269549f32ce0c769321fdd078f84db4fbe1b461ed5a1a496caf09e637c0e367d600c541435716a55b1d9713f5035dfe + languageName: node + linkType: hard + +"@types/react-transition-group@npm:^4.4.10": + version: 4.4.10 + resolution: "@types/react-transition-group@npm:4.4.10" + dependencies: + "@types/react": "*" + checksum: fe2ea11f70251e9f79f368e198c18fd469b1d4f1e1d44e4365845b44e15974b0ec925100036f449b023b0ca3480a82725c5f0a73040e282ad32ec7b0def9b57c + languageName: node + linkType: hard + +"@types/react@npm:*": + version: 18.2.73 + resolution: "@types/react@npm:18.2.73" dependencies: "@types/prop-types": "*" - "@types/scheduler": "*" csstype: ^3.0.2 - checksum: dea9d232d8df7ac357367a69dcb557711ab3d5501807ffa77cebeee73d49ee94d095f298e36853c63ed47cce097eee4c7eae2aaa8c02fac3f0171ec1b523a819 + checksum: 0921d3e3286f11365e796f01eff4fb64de315c68f569e0bbfdaa7680dc4b774c7e8dc416d72d77f7f16a0c2075048429386a55bbfd43ac507d1dddc8d44142e7 languageName: node linkType: hard -"@types/react@npm:~18.0.26": - version: 18.0.38 - resolution: "@types/react@npm:18.0.38" +"@types/react@npm:^18.0.26, @types/react@npm:^18.0.27": + version: 18.2.6 + resolution: "@types/react@npm:18.2.6" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 34481c79f4f7ea2aefbaa45281319dc183200230d932d968463eba1643bd3635073d0a17c5c613150a69e36ca18b811ecffafea6384fa3dff3b5203866339d69 + checksum: dea9d232d8df7ac357367a69dcb557711ab3d5501807ffa77cebeee73d49ee94d095f298e36853c63ed47cce097eee4c7eae2aaa8c02fac3f0171ec1b523a819 languageName: node linkType: hard @@ -4939,6 +5365,17 @@ __metadata: languageName: node linkType: hard +"babel-plugin-macros@npm:^3.1.0": + version: 3.1.0 + resolution: "babel-plugin-macros@npm:3.1.0" + dependencies: + "@babel/runtime": ^7.12.5 + cosmiconfig: ^7.0.0 + resolve: ^1.19.0 + checksum: 765de4abebd3e4688ebdfbff8571ddc8cd8061f839bb6c3e550b0344a4027b04c60491f843296ce3f3379fb356cc873d57a9ee6694262547eb822c14a25be9a6 + languageName: node + linkType: hard + "babel-plugin-polyfill-corejs2@npm:^0.3.3": version: 0.3.3 resolution: "babel-plugin-polyfill-corejs2@npm:0.3.3" @@ -5318,6 +5755,25 @@ __metadata: languageName: node linkType: hard +"chat-jupyter@npm:0.1.0": + version: 0.1.0 + resolution: "chat-jupyter@npm:0.1.0" + dependencies: + "@emotion/react": ^11.10.5 + "@emotion/styled": ^11.10.5 + "@jupyterlab/apputils": ^4.0.5 + "@jupyterlab/rendermime": ^4.0.5 + "@jupyterlab/ui-components": ^4.0.5 + "@lumino/disposable": ^2.1.2 + "@lumino/signaling": ^2.1.2 + "@mui/icons-material": ^5.11.0 + "@mui/material": ^5.11.0 + react: ^18.2.0 + react-dom: ^18.2.0 + checksum: fc32048309089b878427b709b119f2782883c407dd8d5cfbc1009d8e4ae2488820430535156aed07b9b166c122228524ddca21e88863ca1e7a1e5e9e4e9d70f3 + languageName: node + linkType: hard + "child_process@npm:~1.0.2": version: 1.0.2 resolution: "child_process@npm:1.0.2" @@ -5430,6 +5886,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.1.0": + version: 2.1.0 + resolution: "clsx@npm:2.1.0" + checksum: 43fefc29b6b49c9476fbce4f8b1cc75c27b67747738e598e6651dd40d63692135dc60b18fa1c5b78a2a9ba8ae6fd2055a068924b94e20b42039bd53b78b98e1d + languageName: node + linkType: hard + "cmd-shim@npm:5.0.0": version: 5.0.0 resolution: "cmd-shim@npm:5.0.0" @@ -5733,7 +6196,7 @@ __metadata: languageName: node linkType: hard -"convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": +"convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": version: 1.9.0 resolution: "convert-source-map@npm:1.9.0" checksum: dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8 @@ -5776,6 +6239,19 @@ __metadata: languageName: node linkType: hard +"cosmiconfig@npm:^7.0.0": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": ^4.0.0 + import-fresh: ^3.2.1 + parse-json: ^5.0.0 + path-type: ^4.0.0 + yaml: ^1.10.0 + checksum: c53bf7befc1591b2651a22414a5e786cd5f2eeaa87f3678a3d49d6069835a9d8d1aef223728e98aa8fec9a95bf831120d245096db12abe019fecb51f5696c96f + languageName: node + linkType: hard + "cosmiconfig@npm:^8.1.0": version: 8.1.3 resolution: "cosmiconfig@npm:8.1.3" @@ -5900,6 +6376,13 @@ __metadata: languageName: node linkType: hard +"csstype@npm:^3.1.3": + version: 3.1.3 + resolution: "csstype@npm:3.1.3" + checksum: 8db785cc92d259102725b3c694ec0c823f5619a84741b5c7991b8ad135dfaa66093038a1cc63e03361a6cd28d122be48f2106ae72334e067dd619a51f49eddf7 + languageName: node + linkType: hard + "dargs@npm:^7.0.0": version: 7.0.0 resolution: "dargs@npm:7.0.0" @@ -6121,6 +6604,16 @@ __metadata: languageName: node linkType: hard +"dom-helpers@npm:^5.0.1": + version: 5.2.1 + resolution: "dom-helpers@npm:5.2.1" + dependencies: + "@babel/runtime": ^7.8.7 + csstype: ^3.0.2 + checksum: 863ba9e086f7093df3376b43e74ce4422571d404fc9828bf2c56140963d5edf0e56160f9b2f3bb61b282c07f8fc8134f023c98fd684bddcb12daf7b0f14d951c + languageName: node + linkType: hard + "dom-serializer@npm:^1.0.1": version: 1.4.1 resolution: "dom-serializer@npm:1.4.1" @@ -6880,7 +7373,7 @@ __metadata: languageName: node linkType: hard -"find-root@npm:^1.0.0": +"find-root@npm:^1.0.0, find-root@npm:^1.1.0": version: 1.1.0 resolution: "find-root@npm:1.1.0" checksum: b2a59fe4b6c932eef36c45a048ae8f93c85640212ebe8363164814990ee20f154197505965f3f4f102efc33bfb1cbc26fd17c4a2fc739ebc51b886b137cbefaf @@ -7071,6 +7564,13 @@ __metadata: languageName: node linkType: hard +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 2b0ff4ce708d99715ad14a6d1f894e2a83242e4a52ccfcefaee5e40050562e5f6dafc1adbb4ce2d4ab47279a45dc736ab91ea5042d843c3c092820dfe032efb1 + languageName: node + linkType: hard + "function.prototype.name@npm:^1.1.5": version: 1.1.5 resolution: "function.prototype.name@npm:1.1.5" @@ -7525,6 +8025,24 @@ __metadata: languageName: node linkType: hard +"hasown@npm:^2.0.0": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: ^1.1.2 + checksum: e8516f776a15149ca6c6ed2ae3110c417a00b62260e222590e54aa367cbcd6ed99122020b37b7fbdf05748df57b265e70095d7bf35a47660587619b15ffb93db + languageName: node + linkType: hard + +"hoist-non-react-statics@npm:^3.3.1": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: ^16.7.0 + checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8 + languageName: node + linkType: hard + "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -7923,6 +8441,15 @@ __metadata: languageName: node linkType: hard +"is-core-module@npm:^2.13.0": + version: 2.13.1 + resolution: "is-core-module@npm:2.13.1" + dependencies: + hasown: ^2.0.0 + checksum: 256559ee8a9488af90e4bad16f5583c6d59e92f0742e9e8bb4331e758521ee86b810b93bae44f390766ffbc518a0488b18d9dab7da9a5ff997d499efc9403f7c + languageName: node + linkType: hard + "is-date-object@npm:^1.0.1": version: 1.0.5 resolution: "is-date-object@npm:1.0.5" @@ -11249,7 +11776,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.8.1": +"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -11358,7 +11885,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1": +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -11372,6 +11899,21 @@ __metadata: languageName: node linkType: hard +"react-transition-group@npm:^4.4.5": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" + dependencies: + "@babel/runtime": ^7.5.5 + dom-helpers: ^5.0.1 + loose-envify: ^1.4.0 + prop-types: ^15.6.2 + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 75602840106aa9c6545149d6d7ae1502fb7b7abadcce70a6954c4b64a438ff1cd16fc77a0a1e5197cdd72da398f39eb929ea06f9005c45b132ed34e056ebdeb1 + languageName: node + linkType: hard + "react@npm:^18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" @@ -11572,6 +12114,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38 + languageName: node + linkType: hard + "regenerator-transform@npm:^0.15.1": version: 0.15.1 resolution: "regenerator-transform@npm:0.15.1" @@ -11699,6 +12248,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.19.0": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: ^2.13.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: f8a26958aa572c9b064562750b52131a37c29d072478ea32e129063e2da7f83e31f7f11e7087a18225a8561cfe8d2f0df9dbea7c9d331a897571c0a2527dbb4c + languageName: node + linkType: hard + "resolve@npm:^2.0.0-next.4": version: 2.0.0-next.4 resolution: "resolve@npm:2.0.0-next.4" @@ -11725,6 +12287,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@^1.19.0#~builtin": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: ^2.13.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 5479b7d431cacd5185f8db64bfcb7286ae5e31eb299f4c4f404ad8aa6098b77599563ac4257cb2c37a42f59dfc06a1bec2bcf283bb448f319e37f0feb9a09847 + languageName: node + linkType: hard + "resolve@patch:resolve@^2.0.0-next.4#~builtin": version: 2.0.0-next.4 resolution: "resolve@patch:resolve@npm%3A2.0.0-next.4#~builtin::version=2.0.0-next.4&hash=c3c19d" @@ -12167,6 +12742,13 @@ __metadata: languageName: node linkType: hard +"source-map@npm:^0.5.7": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 5dc2043b93d2f194142c7f38f74a24670cd7a0063acdaf4bf01d2964b402257ae843c2a8fa822ad5b71013b5fcafa55af7421383da919752f22ff488bc553f4d + languageName: node + linkType: hard + "source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" @@ -12516,6 +13098,13 @@ __metadata: languageName: node linkType: hard +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 0eb6cc1b866dc17a6037d0a82ac7fa877eba6a757443e79e7c4f35bacedbf6421fadcab4363b39667b43355cbaaa570a3cde850f776498e5450f32ed2f9b7584 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0"