From 9d6de18a5291818af78b505d28df5fe19f06fed5 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Tue, 23 Sep 2025 23:08:38 +0200 Subject: [PATCH 1/7] Use a react context in the chat component and its children --- .../src/components/attachments.tsx | 6 +-- packages/jupyter-chat/src/components/chat.tsx | 30 +++++------ .../src/components/input/chat-input.tsx | 54 ++++++++----------- .../src/components/messages/footer.tsx | 22 ++++---- .../components/messages/message-renderer.tsx | 18 ++----- .../src/components/messages/message.tsx | 11 ++-- .../src/components/messages/messages.tsx | 53 ++++-------------- .../src/components/messages/navigation.tsx | 6 +-- .../src/components/messages/welcome.tsx | 15 +++++- packages/jupyter-chat/src/context.ts | 20 +++++-- 10 files changed, 97 insertions(+), 138 deletions(-) diff --git a/packages/jupyter-chat/src/components/attachments.tsx b/packages/jupyter-chat/src/components/attachments.tsx index 817ee943..2c3b51c1 100644 --- a/packages/jupyter-chat/src/components/attachments.tsx +++ b/packages/jupyter-chat/src/components/attachments.tsx @@ -5,12 +5,12 @@ import CloseIcon from '@mui/icons-material/Close'; import { Box, Button, Tooltip } from '@mui/material'; -import React, { useContext } from 'react'; +import React from 'react'; import { PathExt } from '@jupyterlab/coreutils'; import { UUID } from '@lumino/coreutils'; +import { useChatContext } from '../context'; import { IAttachment } from '../types'; -import { AttachmentOpenerContext } from '../context'; const ATTACHMENT_CLASS = 'jp-chat-attachment'; const ATTACHMENT_CLICKABLE_CLASS = 'jp-chat-attachment-clickable'; @@ -87,7 +87,7 @@ export type AttachmentProps = AttachmentsProps & { */ export function AttachmentPreview(props: AttachmentProps): JSX.Element { const remove_tooltip = 'Remove attachment'; - const attachmentOpenerRegistry = useContext(AttachmentOpenerContext); + const { attachmentOpenerRegistry } = useChatContext(); const isClickable = !!attachmentOpenerRegistry?.get(props.attachment.type); return ( diff --git a/packages/jupyter-chat/src/components/chat.tsx b/packages/jupyter-chat/src/components/chat.tsx index cda29956..bbb3e5f8 100644 --- a/packages/jupyter-chat/src/components/chat.tsx +++ b/packages/jupyter-chat/src/components/chat.tsx @@ -18,7 +18,7 @@ import { } from './input'; import { JlThemeProvider } from './jl-theme-provider'; import { ChatMessages } from './messages'; -import { AttachmentOpenerContext } from '../context'; +import { ChatReactContext } from '../context'; import { IChatModel } from '../model'; import { IAttachmentOpenerRegistry, @@ -27,7 +27,7 @@ import { } from '../registers'; import { ChatArea } from '../types'; -export function ChatBody(props: Chat.IChatBodyProps): JSX.Element { +export function ChatBody(props: Chat.IChatProps): JSX.Element { const { model } = props; let { inputToolbarRegistry } = props; if (!inputToolbarRegistry) { @@ -36,17 +36,14 @@ export function ChatBody(props: Chat.IChatBodyProps): JSX.Element { // const horizontalPadding = props.area === 'main' ? 8 : 4; const horizontalPadding = 4; + const contextValue: Chat.IChatProps = { + ...props, + inputToolbarRegistry + }; + return ( - - + + - + ); } @@ -117,7 +111,7 @@ export namespace Chat { /** * The props for the chat body component. */ - export interface IChatBodyProps { + export interface IChatProps { /** * The chat model. */ @@ -155,7 +149,7 @@ export namespace Chat { /** * The options to build the Chat UI. */ - export interface IOptions extends IChatBodyProps { + export interface IOptions extends IChatProps { /** * The theme manager. */ diff --git a/packages/jupyter-chat/src/components/input/chat-input.tsx b/packages/jupyter-chat/src/components/input/chat-input.tsx index cc4a6de1..51511aaf 100644 --- a/packages/jupyter-chat/src/components/input/chat-input.tsx +++ b/packages/jupyter-chat/src/components/input/chat-input.tsx @@ -14,28 +14,28 @@ import { import clsx from 'clsx'; import React, { useEffect, useRef, useState } from 'react'; +import { InputToolbarRegistry } from './toolbar-registry'; +import { useChatCommands } from './use-chat-commands'; import { AttachmentPreviewList } from '../attachments'; -import { - IInputToolbarRegistry, - InputToolbarRegistry, - useChatCommands -} from '.'; +import { useChatContext } from '../../context'; import { IInputModel, InputModel } from '../../input-model'; -import { IChatCommandRegistry } from '../../registers'; -import { IAttachment, ChatArea } from '../../types'; import { IChatModel } from '../../model'; import { InputWritingIndicator } from './writing-indicator'; +import { ChatArea, IAttachment } from '../../types'; const INPUT_BOX_CLASS = 'jp-chat-input-container'; const INPUT_TEXTFIELD_CLASS = 'jp-chat-input-textfield'; const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar'; export function ChatInput(props: ChatInput.IProps): JSX.Element { - const { model, toolbarRegistry } = props; + const { model } = props; + const { chatCommandRegistry, inputToolbarRegistry } = useChatContext(); + const chatModel = useChatContext().model; + const [input, setInput] = useState(model.value); const inputRef = useRef(); - const chatCommands = useChatCommands(model, props.chatCommandRegistry); + const chatCommands = useChatCommands(model, chatCommandRegistry); const [sendWithShiftEnter, setSendWithShiftEnter] = useState( model.config.sendWithShiftEnter ?? false @@ -99,22 +99,22 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element { */ useEffect(() => { const updateToolbar = () => { - setToolbarElements(toolbarRegistry.getItems()); + setToolbarElements(inputToolbarRegistry?.getItems() || []); }; - toolbarRegistry.itemsChanged.connect(updateToolbar); + inputToolbarRegistry?.itemsChanged.connect(updateToolbar); updateToolbar(); return () => { - toolbarRegistry.itemsChanged.disconnect(updateToolbar); + inputToolbarRegistry?.itemsChanged.disconnect(updateToolbar); }; - }, [toolbarRegistry]); + }, [inputToolbarRegistry]); /** * Handle the changes in the writers list. */ useEffect(() => { - if (!props.chatModel) { + if (!chatModel) { return; } @@ -124,15 +124,15 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element { }; // Set initial writers state - const initialWriters = props.chatModel.writers; + const initialWriters = chatModel.writers; setWriters(initialWriters); - props.chatModel.writersChanged?.connect(updateWriters); + chatModel.writersChanged?.connect(updateWriters); return () => { - props.chatModel?.writersChanged?.disconnect(updateWriters); + chatModel?.writersChanged?.disconnect(updateWriters); }; - }, [props.chatModel]); + }, [chatModel]); const inputExists = !!input.trim(); @@ -196,7 +196,7 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element { (!sendWithShiftEnter && !event.shiftKey) ) { // Run all command providers - await props.chatCommandRegistry?.onSubmit(model); + await chatCommandRegistry?.onSubmit(model); model.send(model.value); event.stopPropagation(); event.preventDefault(); @@ -333,8 +333,8 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element { ))} @@ -357,10 +357,6 @@ export namespace ChatInput { * The input model. */ model: IInputModel; - /** - * The toolbar registry. - */ - toolbarRegistry: IInputToolbarRegistry; /** * The function to be called to cancel editing. */ @@ -369,18 +365,10 @@ export namespace ChatInput { * Custom mui/material styles. */ sx?: SxProps; - /** - * Chat command registry. - */ - chatCommandRegistry?: IChatCommandRegistry; /** * The area where the chat is displayed. */ area?: ChatArea; - /** - * The chat model. - */ - chatModel?: IChatModel; /** * Whether the input is in edit mode (editing an existing message). * Defaults to false (new message mode). diff --git a/packages/jupyter-chat/src/components/messages/footer.tsx b/packages/jupyter-chat/src/components/messages/footer.tsx index 036ccbbc..0f7d6cf9 100644 --- a/packages/jupyter-chat/src/components/messages/footer.tsx +++ b/packages/jupyter-chat/src/components/messages/footer.tsx @@ -6,19 +6,17 @@ import { Box } from '@mui/material'; import React from 'react'; -import { - IMessageFooterRegistry, - MessageFooterSectionProps -} from '../../registers'; +import { useChatContext } from '../../context'; +import { IChatMessage } from '../../types'; /** * The chat footer component properties. */ -export interface IMessageFootersProps extends MessageFooterSectionProps { +export interface IMessageFootersProps { /** - * The chat footer registry. + * The chat model. */ - registry: IMessageFooterRegistry; + message: IChatMessage; } /** @@ -27,9 +25,13 @@ export interface IMessageFootersProps extends MessageFooterSectionProps { */ export function MessageFooterComponent( props: IMessageFootersProps -): JSX.Element { - const { message, model, registry } = props; - const footer = registry.getFooter(); +): JSX.Element | null { + const { message } = props; + const { model, messageFooterRegistry } = useChatContext(); + if (!messageFooterRegistry) { + return null; + } + const footer = messageFooterRegistry.getFooter(); return ( diff --git a/packages/jupyter-chat/src/components/messages/message-renderer.tsx b/packages/jupyter-chat/src/components/messages/message-renderer.tsx index c028001b..3514b636 100644 --- a/packages/jupyter-chat/src/components/messages/message-renderer.tsx +++ b/packages/jupyter-chat/src/components/messages/message-renderer.tsx @@ -3,15 +3,14 @@ * Distributed under the terms of the Modified BSD License. */ -import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { PromiseDelegate } from '@lumino/coreutils'; import React, { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; -import { CodeToolbar, CodeToolbarProps } from '../code-blocks/code-toolbar'; import { MessageToolbar } from './toolbar'; +import { CodeToolbar, CodeToolbarProps } from '../code-blocks/code-toolbar'; +import { useChatContext } from '../../context'; import { MarkdownRenderer, MD_RENDERED_CLASS } from '../../markdown-renderer'; -import { IChatModel } from '../../model'; /** * The type of the props for the MessageRenderer component. @@ -21,14 +20,6 @@ type MessageRendererProps = { * The string to render. */ markdownStr: string; - /** - * The rendermime registry. - */ - rmRegistry: IRenderMimeRegistry; - /** - * The model of the chat. - */ - model: IChatModel; /** * The promise to resolve when the message is rendered. */ @@ -51,7 +42,8 @@ type MessageRendererProps = { * The message renderer base component. */ function MessageRendererBase(props: MessageRendererProps): JSX.Element { - const { markdownStr, rmRegistry } = props; + const { markdownStr } = props; + const { model, rmRegistry } = useChatContext(); const appendContent = props.appendContent || false; const [renderedContent, setRenderedContent] = useState( null @@ -81,7 +73,7 @@ function MessageRendererBase(props: MessageRendererProps): JSX.Element { ); newCodeToolbarDefns.push([ codeToolbarRoot, - { model: props.model, content: preBlock.textContent || '' } + { model: model, content: preBlock.textContent || '' } ]); }); diff --git a/packages/jupyter-chat/src/components/messages/message.tsx b/packages/jupyter-chat/src/components/messages/message.tsx index ad4c3470..5be3d20b 100644 --- a/packages/jupyter-chat/src/components/messages/message.tsx +++ b/packages/jupyter-chat/src/components/messages/message.tsx @@ -7,9 +7,9 @@ import { PromiseDelegate } from '@lumino/coreutils'; import React, { forwardRef, useEffect, useState } from 'react'; import { MessageRenderer } from './message-renderer'; -import { BaseMessageProps } from './messages'; import { AttachmentPreviewList } from '../attachments'; import { ChatInput } from '../input'; +import { useChatContext } from '../../context'; import { IInputModel, InputModel } from '../../input-model'; import { IChatMessage } from '../../types'; import { replaceSpanToMention } from '../../utils'; @@ -17,7 +17,7 @@ import { replaceSpanToMention } from '../../utils'; /** * The message component props. */ -type ChatMessageProps = BaseMessageProps & { +type ChatMessageProps = { /** * The message to display. */ @@ -37,7 +37,8 @@ type ChatMessageProps = BaseMessageProps & { */ export const ChatMessage = forwardRef( (props, ref): JSX.Element => { - const { message, model, rmRegistry } = props; + const { message } = props; + const { model } = useChatContext(); const [edit, setEdit] = useState(false); const [deleted, setDeleted] = useState(false); const [canEdit, setCanEdit] = useState(false); @@ -132,15 +133,11 @@ export const ChatMessage = forwardRef( cancelEdition()} model={model.getEditionModel(message.id)!} - chatCommandRegistry={props.chatCommandRegistry} - toolbarRegistry={props.inputToolbarRegistry} edit={true} /> ) : ( deleteMessage(message.id) : undefined} rendered={props.renderedPromise} diff --git a/packages/jupyter-chat/src/components/messages/messages.tsx b/packages/jupyter-chat/src/components/messages/messages.tsx index ef182f4e..0662bcce 100644 --- a/packages/jupyter-chat/src/components/messages/messages.tsx +++ b/packages/jupyter-chat/src/components/messages/messages.tsx @@ -3,7 +3,6 @@ * Distributed under the terms of the Modified BSD License. */ -import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { PromiseDelegate } from '@lumino/coreutils'; import { Box } from '@mui/material'; import clsx from 'clsx'; @@ -14,10 +13,8 @@ import { ChatMessageHeader } from './header'; import { ChatMessage } from './message'; import { Navigation } from './navigation'; import { WelcomeMessage } from './welcome'; -import { IInputToolbarRegistry } from '../input'; import { ScrollContainer } from '../scroll-container'; -import { IChatCommandRegistry, IMessageFooterRegistry } from '../../registers'; -import { IChatModel } from '../../model'; +import { useChatContext } from '../../context'; import { ChatArea, IChatMessage } from '../../types'; export const MESSAGE_CLASS = 'jp-chat-message'; @@ -27,31 +24,7 @@ const MESSAGE_STACKED_CLASS = 'jp-chat-message-stacked'; /** * The base components props. */ -export type BaseMessageProps = { - /** - * The mime renderer registry. - */ - rmRegistry: IRenderMimeRegistry; - /** - * The chat model. - */ - model: IChatModel; - /** - * The chat commands registry. - */ - chatCommandRegistry?: IChatCommandRegistry; - /** - * The input toolbar registry. - */ - inputToolbarRegistry: IInputToolbarRegistry; - /** - * The footer registry. - */ - messageFooterRegistry?: IMessageFooterRegistry; - /** - * The welcome message. - */ - welcomeMessage?: string; +export type IMessagesProps = { /** * The area where the chat is displayed. */ @@ -61,8 +34,9 @@ export type BaseMessageProps = { /** * The messages list component. */ -export function ChatMessages(props: BaseMessageProps): JSX.Element { - const { model } = props; +export function ChatMessages(props: IMessagesProps): JSX.Element { + const { messageFooterRegistry, model, welcomeMessage } = useChatContext(); + const [messages, setMessages] = useState(model.messages); const refMsgBox = useRef(null); const [allRendered, setAllRendered] = useState(false); @@ -137,7 +111,7 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element { } }); - props.model.messagesInViewport = inViewport; + model.messagesInViewport = inViewport; // Ensure that all messages are rendered before updating unread messages, otherwise // it can lead to wrong assumption , because more message are in the viewport @@ -169,12 +143,7 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element { return ( <> - {props.welcomeMessage && ( - - )} + {welcomeMessage && } (listRef.current[i] = el)} /> - {props.messageFooterRegistry && ( - + {messageFooterRegistry && ( + )} ); diff --git a/packages/jupyter-chat/src/components/messages/navigation.tsx b/packages/jupyter-chat/src/components/messages/navigation.tsx index b31bae07..cf502432 100644 --- a/packages/jupyter-chat/src/components/messages/navigation.tsx +++ b/packages/jupyter-chat/src/components/messages/navigation.tsx @@ -11,7 +11,7 @@ import { } from '@jupyterlab/ui-components'; import React, { useEffect, useState } from 'react'; -import { BaseMessageProps } from './messages'; +import { useChatContext } from '../../context'; import { IChatModel } from '../../model'; const NAVIGATION_BUTTON_CLASS = 'jp-chat-navigation'; @@ -22,7 +22,7 @@ const NAVIGATION_BOTTOM_CLASS = 'jp-chat-navigation-bottom'; /** * The navigation component props. */ -type NavigationProps = BaseMessageProps & { +type NavigationProps = { /** * The reference to the messages container. */ @@ -37,7 +37,7 @@ type NavigationProps = BaseMessageProps & { * The navigation component, to navigate to unread messages. */ export function Navigation(props: NavigationProps): JSX.Element { - const { model } = props; + const { model } = useChatContext(); const [lastInViewport, setLastInViewport] = useState(true); const [unreadBefore, setUnreadBefore] = useState(null); const [unreadAfter, setUnreadAfter] = useState(null); diff --git a/packages/jupyter-chat/src/components/messages/welcome.tsx b/packages/jupyter-chat/src/components/messages/welcome.tsx index 7ad82bfe..e3f7cdfc 100644 --- a/packages/jupyter-chat/src/components/messages/welcome.tsx +++ b/packages/jupyter-chat/src/components/messages/welcome.tsx @@ -6,17 +6,28 @@ import { classes } from '@jupyterlab/ui-components'; import React, { useEffect, useRef } from 'react'; +import { useChatContext } from '../../context'; import { MarkdownRenderer, MD_RENDERED_CLASS } from '../../markdown-renderer'; const WELCOME_MESSAGE_CLASS = 'jp-chat-welcome-message'; +/** + * The component props. + */ +export interface IWelcomeMessageProps { + /** + * The content of the welcome message (markdown). + */ + content: string; +} + /** * The welcome message component. * This message is displayed on top of the chat messages, and is rendered using a * markdown renderer. */ -export function WelcomeMessage(props: MarkdownRenderer.IOptions): JSX.Element { - const { rmRegistry } = props; +export function WelcomeMessage(props: IWelcomeMessageProps): JSX.Element { + const { rmRegistry } = useChatContext(); const content = props.content + '\n----\n'; // ref that tracks the content container to store the rendermime node in diff --git a/packages/jupyter-chat/src/context.ts b/packages/jupyter-chat/src/context.ts index 75f4166f..94009a46 100644 --- a/packages/jupyter-chat/src/context.ts +++ b/packages/jupyter-chat/src/context.ts @@ -2,9 +2,19 @@ * Copyright (c) Jupyter Development Team. * Distributed under the terms of the Modified BSD License. */ -import { createContext } from 'react'; -import { IAttachmentOpenerRegistry } from './registers'; -export const AttachmentOpenerContext = createContext< - IAttachmentOpenerRegistry | undefined ->(undefined); +import { createContext, useContext } from 'react'; + +import { Chat } from './components'; + +export const ChatReactContext = createContext( + undefined +); + +export function useChatContext(): Chat.IChatProps { + const context = useContext(ChatReactContext); + if (!context) { + throw new Error('The chat context is missing in the chat'); + } + return context; +} From f5bbd996b1645e6a7d3b2566e6696246920739a2 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Tue, 23 Sep 2025 23:28:37 +0200 Subject: [PATCH 2/7] Use the chat.IOptions in multichat panel to clarify. --- packages/jupyter-chat/src/components/chat.tsx | 6 +--- .../src/widgets/multichat-panel.tsx | 29 +++---------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/packages/jupyter-chat/src/components/chat.tsx b/packages/jupyter-chat/src/components/chat.tsx index bbb3e5f8..16c657c7 100644 --- a/packages/jupyter-chat/src/components/chat.tsx +++ b/packages/jupyter-chat/src/components/chat.tsx @@ -59,7 +59,7 @@ export function ChatBody(props: Chat.IChatProps): JSX.Element { } export function Chat(props: Chat.IOptions): JSX.Element { - const [view, setView] = useState(props.chatView || Chat.View.chat); + const [view, setView] = useState(Chat.View.chat); return ( (this); - private _rmRegistry: IRenderMimeRegistry; - private _themeManager?: IThemeManager | null; - private _chatCommandRegistry?: IChatCommandRegistry; - private _attachmentOpenerRegistry?: IAttachmentOpenerRegistry; + private _chatOptions: Omit; private _inputToolbarFactory?: IInputToolbarRegistryFactory; - private _messageFooterRegistry?: IMessageFooterRegistry; - private _welcomeMessage?: string; private _updateChatListDebouncer: Debouncer; private _createModel?: ( From 2aab052af7fc1ca6f628fed987e9b73b5c78710a Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Tue, 23 Sep 2025 23:49:40 +0200 Subject: [PATCH 3/7] Use the Chat.IOptions in widget fatory --- packages/jupyterlab-chat/src/factory.ts | 55 +++++-------------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/packages/jupyterlab-chat/src/factory.ts b/packages/jupyterlab-chat/src/factory.ts index 22d250f9..31416cde 100644 --- a/packages/jupyterlab-chat/src/factory.ts +++ b/packages/jupyterlab-chat/src/factory.ts @@ -4,20 +4,14 @@ */ import { - ChatArea, + Chat, ChatWidget, IActiveCellManager, - IAttachmentOpenerRegistry, - IChatCommandRegistry, - IInputToolbarRegistry, - IMessageFooterRegistry, ISelectionWatcher, IInputToolbarRegistryFactory } from '@jupyter/chat'; -import { IThemeManager } from '@jupyterlab/apputils'; import { IDocumentManager } from '@jupyterlab/docmanager'; import { ABCWidgetFactory, DocumentRegistry } from '@jupyterlab/docregistry'; -import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { Contents, User } from '@jupyterlab/services'; import { CommandRegistry } from '@lumino/commands'; import { ISignal, Signal } from '@lumino/signaling'; @@ -78,13 +72,8 @@ export class ChatWidgetFactory extends ABCWidgetFactory< */ constructor(options: ChatWidgetFactory.IOptions) { super(options); - this._themeManager = options.themeManager; - this._rmRegistry = options.rmRegistry; - this._chatCommandRegistry = options.chatCommandRegistry; - this._attachmentOpenerRegistry = options.attachmentOpenerRegistry; + this._chatOptions = options; this._inputToolbarFactory = options.inputToolbarFactory; - this._messageFooterRegistry = options.messageFooterRegistry; - this._welcomeMessage = options.welcomeMessage; } /** @@ -94,13 +83,7 @@ export class ChatWidgetFactory extends ABCWidgetFactory< * @returns The widget */ protected createNewWidget(context: ChatWidgetFactory.IContext): LabChatPanel { - context.rmRegistry = this._rmRegistry; - context.themeManager = this._themeManager; - context.chatCommandRegistry = this._chatCommandRegistry; - context.attachmentOpenerRegistry = this._attachmentOpenerRegistry; - context.messageFooterRegistry = this._messageFooterRegistry; - context.welcomeMessage = this._welcomeMessage; - context.area = 'main'; + context = { ...context, area: 'main', ...this._chatOptions }; if (this._inputToolbarFactory) { context.inputToolbarRegistry = this._inputToolbarFactory.create(); } @@ -120,37 +103,19 @@ export class ChatWidgetFactory extends ABCWidgetFactory< } // Must override both getter and setter from ABCFactory for type compatibility. set contentProviderId(_value: string | undefined) {} - private _themeManager: IThemeManager | null; - private _rmRegistry: IRenderMimeRegistry; - private _chatCommandRegistry?: IChatCommandRegistry; - private _attachmentOpenerRegistry?: IAttachmentOpenerRegistry; + private _chatOptions: Omit; private _inputToolbarFactory?: IInputToolbarRegistryFactory; - private _messageFooterRegistry?: IMessageFooterRegistry; - private _welcomeMessage?: string; } export namespace ChatWidgetFactory { - export interface IContext extends DocumentRegistry.IContext { - themeManager: IThemeManager | null; - rmRegistry: IRenderMimeRegistry; - documentManager?: IDocumentManager; - chatCommandRegistry?: IChatCommandRegistry; - attachmentOpenerRegistry?: IAttachmentOpenerRegistry; - inputToolbarRegistry?: IInputToolbarRegistry; - messageFooterRegistry?: IMessageFooterRegistry; - welcomeMessage?: string; - area?: ChatArea; - } + export interface IContext + extends DocumentRegistry.IContext, + Omit {} export interface IOptions - extends DocumentRegistry.IWidgetFactoryOptions { - themeManager: IThemeManager | null; - rmRegistry: IRenderMimeRegistry; - chatCommandRegistry?: IChatCommandRegistry; - attachmentOpenerRegistry?: IAttachmentOpenerRegistry; - inputToolbarFactory?: IInputToolbarRegistryFactory; - messageFooterRegistry?: IMessageFooterRegistry; - welcomeMessage?: string; + extends DocumentRegistry.IWidgetFactoryOptions, + Omit { + inputToolbarFactory: IInputToolbarRegistryFactory; } } From acb0fbc5dd2538f06ffc059fd2d1237df426f440 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Wed, 24 Sep 2025 00:07:56 +0200 Subject: [PATCH 4/7] Use ChatModel.IOptions in model factory --- packages/jupyterlab-chat/src/factory.ts | 33 ++++--------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/packages/jupyterlab-chat/src/factory.ts b/packages/jupyterlab-chat/src/factory.ts index 31416cde..f61e23b6 100644 --- a/packages/jupyterlab-chat/src/factory.ts +++ b/packages/jupyterlab-chat/src/factory.ts @@ -3,17 +3,9 @@ * Distributed under the terms of the Modified BSD License. */ -import { - Chat, - ChatWidget, - IActiveCellManager, - ISelectionWatcher, - IInputToolbarRegistryFactory -} from '@jupyter/chat'; -import { IDocumentManager } from '@jupyterlab/docmanager'; +import { Chat, ChatWidget, IInputToolbarRegistryFactory } from '@jupyter/chat'; import { ABCWidgetFactory, DocumentRegistry } from '@jupyterlab/docregistry'; -import { Contents, User } from '@jupyterlab/services'; -import { CommandRegistry } from '@lumino/commands'; +import { Contents } from '@jupyterlab/services'; import { ISignal, Signal } from '@lumino/signaling'; import { LabChatModel } from './model'; @@ -123,12 +115,7 @@ export class LabChatModelFactory implements DocumentRegistry.IModelFactory { constructor(options: LabChatModel.IOptions) { - this._user = options.user; - this._widgetConfig = options.widgetConfig; - this._commands = options.commands; - this._activeCellManager = options.activeCellManager ?? null; - this._selectionWatcher = options.selectionWatcher ?? null; - this._documentManager = options.documentManager ?? null; + this._modelOptions = options; } collaborative = true; @@ -197,20 +184,10 @@ export class LabChatModelFactory createNew(options: DocumentRegistry.IModelOptions): LabChatModel { return new LabChatModel({ ...options, - user: this._user, - widgetConfig: this._widgetConfig, - commands: this._commands, - activeCellManager: this._activeCellManager, - selectionWatcher: this._selectionWatcher, - documentManager: this._documentManager + ...this._modelOptions }); } private _disposed = false; - private _user: User.IIdentity | null; - private _widgetConfig: IWidgetConfig; - private _commands?: CommandRegistry; - private _activeCellManager: IActiveCellManager | null; - private _selectionWatcher: ISelectionWatcher | null; - private _documentManager: IDocumentManager | null; + private _modelOptions: Omit; } From c4e1c7f308d192edc59da9cb27d1651286778dd6 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Wed, 24 Sep 2025 00:10:33 +0200 Subject: [PATCH 5/7] update gitignore to ignore vscode project files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 5f5d89b4..e564b443 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,9 @@ celerybeat-schedule .spyderproject .spyproject +# vscode project settings +.vscode + # Rope project settings .ropeproject From cd43148754734ff3f0c48acbea4d33ef5e89e54c Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Wed, 24 Sep 2025 21:55:53 +0200 Subject: [PATCH 6/7] Fix the chat widget factory context --- packages/jupyterlab-chat/src/factory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/jupyterlab-chat/src/factory.ts b/packages/jupyterlab-chat/src/factory.ts index f61e23b6..9ade93f4 100644 --- a/packages/jupyterlab-chat/src/factory.ts +++ b/packages/jupyterlab-chat/src/factory.ts @@ -75,7 +75,8 @@ export class ChatWidgetFactory extends ABCWidgetFactory< * @returns The widget */ protected createNewWidget(context: ChatWidgetFactory.IContext): LabChatPanel { - context = { ...context, area: 'main', ...this._chatOptions }; + Object.assign(context, this._chatOptions); + context.area = 'main'; if (this._inputToolbarFactory) { context.inputToolbarRegistry = this._inputToolbarFactory.create(); } @@ -93,6 +94,7 @@ export class ChatWidgetFactory extends ABCWidgetFactory< get contentProviderId(): string { return 'rtc'; } + // Must override both getter and setter from ABCFactory for type compatibility. set contentProviderId(_value: string | undefined) {} private _chatOptions: Omit; @@ -176,8 +178,6 @@ export class LabChatModelFactory /** * Create a new instance of LabChatModel. * - * @param languagePreference Language - * @param modelDB Model database * @returns The model */ From 133cfdd612ddbd03f8a06d41bdd9dffef18e6a81 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 3 Nov 2025 13:49:34 +0100 Subject: [PATCH 7/7] Use area from context --- packages/jupyter-chat/src/components/chat.tsx | 3 +-- .../src/components/input/chat-input.tsx | 10 +++----- .../src/components/messages/messages.tsx | 24 ++++++------------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/packages/jupyter-chat/src/components/chat.tsx b/packages/jupyter-chat/src/components/chat.tsx index 16c657c7..f95cf656 100644 --- a/packages/jupyter-chat/src/components/chat.tsx +++ b/packages/jupyter-chat/src/components/chat.tsx @@ -43,7 +43,7 @@ export function ChatBody(props: Chat.IChatProps): JSX.Element { return ( - + ); diff --git a/packages/jupyter-chat/src/components/input/chat-input.tsx b/packages/jupyter-chat/src/components/input/chat-input.tsx index 51511aaf..fa68ba92 100644 --- a/packages/jupyter-chat/src/components/input/chat-input.tsx +++ b/packages/jupyter-chat/src/components/input/chat-input.tsx @@ -21,7 +21,7 @@ import { useChatContext } from '../../context'; import { IInputModel, InputModel } from '../../input-model'; import { IChatModel } from '../../model'; import { InputWritingIndicator } from './writing-indicator'; -import { ChatArea, IAttachment } from '../../types'; +import { IAttachment } from '../../types'; const INPUT_BOX_CLASS = 'jp-chat-input-container'; const INPUT_TEXTFIELD_CLASS = 'jp-chat-input-textfield'; @@ -29,7 +29,7 @@ const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar'; export function ChatInput(props: ChatInput.IProps): JSX.Element { const { model } = props; - const { chatCommandRegistry, inputToolbarRegistry } = useChatContext(); + const { area, chatCommandRegistry, inputToolbarRegistry } = useChatContext(); const chatModel = useChatContext().model; const [input, setInput] = useState(model.value); @@ -203,7 +203,7 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element { } } - const horizontalPadding = props.area === 'sidebar' ? 1.5 : 2; + const horizontalPadding = area === 'sidebar' ? 1.5 : 2; return ( ; - /** - * The area where the chat is displayed. - */ - area?: ChatArea; /** * Whether the input is in edit mode (editing an existing message). * Defaults to false (new message mode). diff --git a/packages/jupyter-chat/src/components/messages/messages.tsx b/packages/jupyter-chat/src/components/messages/messages.tsx index 0662bcce..6da6639f 100644 --- a/packages/jupyter-chat/src/components/messages/messages.tsx +++ b/packages/jupyter-chat/src/components/messages/messages.tsx @@ -15,27 +15,18 @@ import { Navigation } from './navigation'; import { WelcomeMessage } from './welcome'; import { ScrollContainer } from '../scroll-container'; import { useChatContext } from '../../context'; -import { ChatArea, IChatMessage } from '../../types'; +import { IChatMessage } from '../../types'; export const MESSAGE_CLASS = 'jp-chat-message'; const MESSAGES_BOX_CLASS = 'jp-chat-messages-container'; const MESSAGE_STACKED_CLASS = 'jp-chat-message-stacked'; -/** - * The base components props. - */ -export type IMessagesProps = { - /** - * The area where the chat is displayed. - */ - area?: ChatArea; -}; - /** * The messages list component. */ -export function ChatMessages(props: IMessagesProps): JSX.Element { - const { messageFooterRegistry, model, welcomeMessage } = useChatContext(); +export function ChatMessages(): JSX.Element { + const { area, messageFooterRegistry, model, welcomeMessage } = + useChatContext(); const [messages, setMessages] = useState(model.messages); const refMsgBox = useRef(null); @@ -139,7 +130,7 @@ export function ChatMessages(props: IMessagesProps): JSX.Element { }; }, [messages, allRendered]); - const horizontalPadding = props.area === 'main' ? 8 : 4; + const horizontalPadding = area === 'main' ? 8 : 4; return ( <> @@ -170,7 +161,7 @@ export function ChatMessages(props: IMessagesProps): JSX.Element { key={i} sx={{ ...(isCurrentUser && { - marginLeft: props.area === 'main' ? '25%' : '10%', + marginLeft: area === 'main' ? '25%' : '10%', backgroundColor: 'var(--jp-layout-color2)', border: 'none', borderRadius: 2, @@ -187,7 +178,6 @@ export function ChatMessages(props: IMessagesProps): JSX.Element { isCurrentUser={isCurrentUser} /> - + ); }