From e11948a199a02e2ef6d763da4eda1a3ef2ea2878 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Nov 2025 16:12:08 +0100 Subject: [PATCH 1/5] Add a widget tracker token in @jupyter/chat to have a unique tracker that can be used by external extensions --- packages/jupyter-chat/src/index.ts | 1 + packages/jupyter-chat/src/tokens.ts | 17 ++++++++++ .../jupyter-chat/src/widgets/chat-widget.tsx | 8 +++++ .../jupyterlab-chat-extension/src/index.ts | 31 +++++++++++++------ packages/jupyterlab-chat/src/token.ts | 3 +- 5 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 packages/jupyter-chat/src/tokens.ts diff --git a/packages/jupyter-chat/src/index.ts b/packages/jupyter-chat/src/index.ts index ad7b8f26..cbd0a002 100644 --- a/packages/jupyter-chat/src/index.ts +++ b/packages/jupyter-chat/src/index.ts @@ -11,5 +11,6 @@ export * from './markdown-renderer'; export * from './model'; export * from './registers'; export * from './selection-watcher'; +export * from './tokens'; export * from './types'; export * from './widgets'; diff --git a/packages/jupyter-chat/src/tokens.ts b/packages/jupyter-chat/src/tokens.ts new file mode 100644 index 00000000..465f98fe --- /dev/null +++ b/packages/jupyter-chat/src/tokens.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +import { WidgetTracker } from '@jupyterlab/apputils'; +import { Token } from '@lumino/coreutils'; + +import { ChatWidget } from './widgets'; + +/** + * A chat tracker token. + */ +export const IChatTracker = new Token>( + '@jupyter/chat:IChatTracker', + 'The chat widget tracker' +); diff --git a/packages/jupyter-chat/src/widgets/chat-widget.tsx b/packages/jupyter-chat/src/widgets/chat-widget.tsx index 0ea49239..300527f4 100644 --- a/packages/jupyter-chat/src/widgets/chat-widget.tsx +++ b/packages/jupyter-chat/src/widgets/chat-widget.tsx @@ -15,6 +15,7 @@ import { Chat, IInputToolbarRegistry, MESSAGE_CLASS } from '../components'; import { chatIcon } from '../icons'; import { IChatModel } from '../model'; import { + ChatArea, IFileAttachment, INotebookAttachment, INotebookAttachmentCell @@ -71,6 +72,13 @@ export class ChatWidget extends ReactWidget { return this._chatOptions.model; } + /** + * The area where the chat is opened. + */ + get area(): ChatArea | undefined { + return this._chatOptions.area; + } + /** * Get the input toolbar registry (if it has been provided when creating the widget). */ diff --git a/packages/jupyterlab-chat-extension/src/index.ts b/packages/jupyterlab-chat-extension/src/index.ts index ae104604..36b817c3 100644 --- a/packages/jupyterlab-chat-extension/src/index.ts +++ b/packages/jupyterlab-chat-extension/src/index.ts @@ -12,6 +12,7 @@ import { IAttachment, IAttachmentOpenerRegistry, IChatCommandRegistry, + IChatTracker, IMessageFooterRegistry, ISelectionWatcher, InputToolbarRegistry, @@ -76,6 +77,7 @@ const pluginIds = { attachmentOpenerRegistry: 'jupyterlab-chat-extension:attachmentOpener', chatCommands: 'jupyterlab-chat-extension:commands', chatPanel: 'jupyterlab-chat-extension:chat-panel', + chatTracker: 'jupyterlab-chat-extension:tracker', docFactories: 'jupyterlab-chat-extension:factory', inputToolbarFactory: 'jupyterlab-chat-extension:inputToolbarFactory', selectionWatcher: 'jupyterlab-chat-extension:selectionWatcher' @@ -163,7 +165,7 @@ const docFactories: JupyterFrontEndPlugin = { id: pluginIds.docFactories, description: 'Document factories for chat.', autoStart: true, - requires: [IRenderMimeRegistry], + requires: [IChatTracker, IRenderMimeRegistry], optional: [ IActiveCellManagerToken, IAttachmentOpenerRegistry, @@ -183,6 +185,7 @@ const docFactories: JupyterFrontEndPlugin = { provides: IChatFactory, activate: ( app: JupyterFrontEnd, + tracker: WidgetTracker, rmRegistry: IRenderMimeRegistry, activeCellManager: IActiveCellManager | null, attachmentOpenerRegistry: IAttachmentOpenerRegistry, @@ -316,11 +319,6 @@ const docFactories: JupyterFrontEndPlugin = { }); } - // Namespace for the tracker - const namespace = 'chat'; - - // Creating the tracker for the document - const tracker = new WidgetTracker({ namespace }); app.docRegistry.addFileType(chatFileType); if (drive) { @@ -369,9 +367,9 @@ const docFactories: JupyterFrontEndPlugin = { widgetFactory.widgetCreated.connect((sender, widget) => { // Notify the instance tracker if restore data needs to update. widget.context.pathChanged.connect(() => { - tracker.save(widget); + tracker.save(widget.content); }); - tracker.add(widget); + tracker.add(widget.content); // Update the 'markAsRead' command status when the unread changed. widget.model.unreadChanged.connect(() => @@ -398,7 +396,7 @@ const docFactories: JupyterFrontEndPlugin = { command: CommandIDs.openChat, args: widget => ({ filepath: widget.model.name ?? '', - inSidePanel: widget instanceof ChatWidget, + inSidePanel: widget.area === 'sidebar', startup: true }), name: widget => widget.model.name, @@ -410,6 +408,20 @@ const docFactories: JupyterFrontEndPlugin = { } }; +const chatTracker: JupyterFrontEndPlugin> = { + id: pluginIds.chatTracker, + description: 'The commands to create or open a chat.', + autoStart: true, + provides: IChatTracker, + activate: (app: JupyterFrontEnd): WidgetTracker => { + // Namespace for the tracker + const namespace = 'chat'; + + // Creating the tracker for the document + return new WidgetTracker({ namespace }); + } +}; + /** * Extension providing the commands, menu and laucher. */ @@ -1020,6 +1032,7 @@ export default [ chatCommands, chatCommandRegistryPlugin, chatPanel, + chatTracker, docFactories, footerRegistry, inputToolbarFactory, diff --git a/packages/jupyterlab-chat/src/token.ts b/packages/jupyterlab-chat/src/token.ts index ee312da3..c6ebdae2 100644 --- a/packages/jupyterlab-chat/src/token.ts +++ b/packages/jupyterlab-chat/src/token.ts @@ -14,7 +14,6 @@ import { WidgetTracker } from '@jupyterlab/apputils'; import { DocumentRegistry } from '@jupyterlab/docregistry'; import { Token } from '@lumino/coreutils'; import { ISignal } from '@lumino/signaling'; -import { LabChatPanel } from './widget'; import { MultiChatPanel as ChatPanel } from '@jupyter/chat'; /** @@ -58,7 +57,7 @@ export interface IChatFactory { /** * The chat panel tracker. */ - tracker: WidgetTracker; + tracker: WidgetTracker; } /** From 99c3182af1972f32441d987e87bfda41fc262e3d Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Nov 2025 16:21:07 +0100 Subject: [PATCH 2/5] Add a token/plugin for the chat config --- packages/jupyter-chat/src/tokens.ts | 9 +- .../jupyterlab-chat-extension/src/index.ts | 220 ++++++++++-------- packages/jupyterlab-chat/src/token.ts | 34 ++- 3 files changed, 148 insertions(+), 115 deletions(-) diff --git a/packages/jupyter-chat/src/tokens.ts b/packages/jupyter-chat/src/tokens.ts index 465f98fe..6a9c3763 100644 --- a/packages/jupyter-chat/src/tokens.ts +++ b/packages/jupyter-chat/src/tokens.ts @@ -3,15 +3,20 @@ * Distributed under the terms of the Modified BSD License. */ -import { WidgetTracker } from '@jupyterlab/apputils'; +import { IWidgetTracker } from '@jupyterlab/apputils'; import { Token } from '@lumino/coreutils'; import { ChatWidget } from './widgets'; +/** + * the chat tracker type. + */ +export type IChatTracker = IWidgetTracker; + /** * A chat tracker token. */ -export const IChatTracker = new Token>( +export const IChatTracker = new Token( '@jupyter/chat:IChatTracker', 'The chat widget tracker' ); diff --git a/packages/jupyterlab-chat-extension/src/index.ts b/packages/jupyterlab-chat-extension/src/index.ts index 36b817c3..e4e1fd77 100644 --- a/packages/jupyterlab-chat-extension/src/index.ts +++ b/packages/jupyterlab-chat-extension/src/index.ts @@ -33,6 +33,7 @@ import { ICommandPalette, IThemeManager, IToolbarWidgetRegistry, + IWidgetTracker, InputDialog, WidgetTracker, createToolbarFactory, @@ -59,6 +60,7 @@ import { IChatPanel, ISelectionWatcherToken, IWelcomeMessage, + IWidgetConfig, LabChatModelFactory, LabChatPanel, WidgetConfig, @@ -80,7 +82,8 @@ const pluginIds = { chatTracker: 'jupyterlab-chat-extension:tracker', docFactories: 'jupyterlab-chat-extension:factory', inputToolbarFactory: 'jupyterlab-chat-extension:inputToolbarFactory', - selectionWatcher: 'jupyterlab-chat-extension:selectionWatcher' + selectionWatcher: 'jupyterlab-chat-extension:selectionWatcher', + widgetConfig: 'jupyterlab-chat-extension:widget-config' }; /** @@ -159,60 +162,19 @@ const attachmentOpeners: JupyterFrontEndPlugin = { }; /** - * Extension registering the chat file type. + * Extension providing the chat widget config. */ -const docFactories: JupyterFrontEndPlugin = { - id: pluginIds.docFactories, - description: 'Document factories for chat.', +const chatConfig: JupyterFrontEndPlugin = { + id: pluginIds.widgetConfig, + description: 'Chat widget configuration.', autoStart: true, - requires: [IChatTracker, IRenderMimeRegistry], - optional: [ - IActiveCellManagerToken, - IAttachmentOpenerRegistry, - IChatCommandRegistry, - ICollaborativeContentProvider, - IDefaultFileBrowser, - IInputToolbarRegistryFactory, - ILayoutRestorer, - IMessageFooterRegistry, - ISelectionWatcherToken, - ISettingRegistry, - IThemeManager, - IToolbarWidgetRegistry, - ITranslator, - IWelcomeMessage - ], - provides: IChatFactory, + optional: [ICollaborativeContentProvider, ISettingRegistry], + provides: IWidgetConfig, activate: ( app: JupyterFrontEnd, - tracker: WidgetTracker, - rmRegistry: IRenderMimeRegistry, - activeCellManager: IActiveCellManager | null, - attachmentOpenerRegistry: IAttachmentOpenerRegistry, - chatCommandRegistry: IChatCommandRegistry, drive: ICollaborativeContentProvider | null, - filebrowser: IDefaultFileBrowser | null, - inputToolbarFactory: IInputToolbarRegistryFactory, - restorer: ILayoutRestorer | null, - messageFooterRegistry: IMessageFooterRegistry, - selectionWatcher: ISelectionWatcher | null, - settingRegistry: ISettingRegistry | null, - themeManager: IThemeManager | null, - toolbarRegistry: IToolbarWidgetRegistry | null, - translator_: ITranslator | null, - welcomeMessage: string - ): IChatFactory => { - const translator = translator_ ?? nullTranslator; - - // Declare the toolbar factory. - let toolbarFactory: - | (( - widget: LabChatPanel - ) => - | DocumentRegistry.IToolbarItem[] - | IObservableList) - | undefined; - + settingRegistry: ISettingRegistry | null + ): IWidgetConfig => { /** * The chat config object. */ @@ -291,17 +253,6 @@ const docFactories: JupyterFrontEndPlugin = { } if (settingRegistry) { - // Create the main area widget toolbar factory. - if (toolbarRegistry) { - toolbarFactory = createToolbarFactory( - toolbarRegistry, - settingRegistry, - FACTORY, - pluginIds.docFactories, - translator - ); - } - // Wait for the application to be restored and // for the settings to be loaded Promise.all([app.restored, settingRegistry.load(pluginIds.docFactories)]) @@ -318,6 +269,75 @@ const docFactories: JupyterFrontEndPlugin = { ); }); } + return widgetConfig; + } +}; + +/** + * Extension registering the chat file type. + */ +const docFactories: JupyterFrontEndPlugin = { + id: pluginIds.docFactories, + description: 'Document factories for chat.', + autoStart: true, + requires: [IRenderMimeRegistry, IWidgetConfig], + optional: [ + IActiveCellManagerToken, + IAttachmentOpenerRegistry, + IChatCommandRegistry, + ICollaborativeContentProvider, + IDefaultFileBrowser, + IInputToolbarRegistryFactory, + IMessageFooterRegistry, + ISelectionWatcherToken, + ISettingRegistry, + IThemeManager, + IToolbarWidgetRegistry, + ITranslator, + IWelcomeMessage + ], + provides: IChatFactory, + activate: ( + app: JupyterFrontEnd, + rmRegistry: IRenderMimeRegistry, + widgetConfig: IWidgetConfig, + activeCellManager: IActiveCellManager | null, + attachmentOpenerRegistry: IAttachmentOpenerRegistry, + chatCommandRegistry: IChatCommandRegistry, + drive: ICollaborativeContentProvider | null, + filebrowser: IDefaultFileBrowser | null, + inputToolbarFactory: IInputToolbarRegistryFactory, + messageFooterRegistry: IMessageFooterRegistry, + selectionWatcher: ISelectionWatcher | null, + settingRegistry: ISettingRegistry | null, + themeManager: IThemeManager | null, + toolbarRegistry: IToolbarWidgetRegistry | null, + translator_: ITranslator | null, + welcomeMessage: string + ): ChatWidgetFactory => { + const translator = translator_ ?? nullTranslator; + + // Declare the toolbar factory. + let toolbarFactory: + | (( + widget: LabChatPanel + ) => + | DocumentRegistry.IToolbarItem[] + | IObservableList) + | undefined; + + if (settingRegistry) { + // Create the main area widget toolbar factory. + if (toolbarRegistry) { + toolbarFactory = createToolbarFactory( + toolbarRegistry, + settingRegistry, + FACTORY, + pluginIds.docFactories, + translator + ); + } + } app.docRegistry.addFileType(chatFileType); @@ -363,8 +383,37 @@ const docFactories: JupyterFrontEndPlugin = { welcomeMessage }); + // Registering the widget factory + app.docRegistry.addWidgetFactory(widgetFactory); + + return widgetFactory; + } +}; + +/** + * Extension providing the chat widget tracker. + */ +const chatTracker: JupyterFrontEndPlugin = { + id: pluginIds.chatTracker, + description: 'The chat widget tracker', + autoStart: true, + provides: IChatTracker, + requires: [IChatFactory], + optional: [IChatPanel, ILayoutRestorer], + activate: ( + app: JupyterFrontEnd, + factory: ChatWidgetFactory, + chatPanel: MultiChatPanel | null, + restorer: ILayoutRestorer | null + ): IChatTracker => { + // Namespace for the tracker + const namespace = 'chat'; + + // Creating the tracker for the chat widgets. + const tracker = new WidgetTracker({ namespace }); + // Add the widget to the tracker when it's created - widgetFactory.widgetCreated.connect((sender, widget) => { + factory.widgetCreated.connect((sender, widget) => { // Notify the instance tracker if restore data needs to update. widget.context.pathChanged.connect(() => { tracker.save(widget.content); @@ -377,8 +426,10 @@ const docFactories: JupyterFrontEndPlugin = { ); }); - // Registering the widget factory - app.docRegistry.addWidgetFactory(widgetFactory); + // Add the new opened chat in the tracker. + chatPanel?.sectionAdded.connect((_, section) => { + tracker.add(section.widget); + }); // Handle state restoration. if (restorer) { @@ -404,21 +455,7 @@ const docFactories: JupyterFrontEndPlugin = { }); } - return { widgetConfig, tracker }; - } -}; - -const chatTracker: JupyterFrontEndPlugin> = { - id: pluginIds.chatTracker, - description: 'The commands to create or open a chat.', - autoStart: true, - provides: IChatTracker, - activate: (app: JupyterFrontEnd): WidgetTracker => { - // Namespace for the tracker - const namespace = 'chat'; - - // Creating the tracker for the document - return new WidgetTracker({ namespace }); + return tracker; } }; @@ -429,19 +466,20 @@ const chatCommands: JupyterFrontEndPlugin = { id: pluginIds.chatCommands, description: 'The commands to create or open a chat.', autoStart: true, - requires: [ICollaborativeContentProvider, IChatFactory], + requires: [ICollaborativeContentProvider, IWidgetConfig, IChatTracker], optional: [IChatPanel, ICommandPalette, IDefaultFileBrowser, ILauncher], activate: ( app: JupyterFrontEnd, drive: ICollaborativeContentProvider, - factory: IChatFactory, + widgetConfig: IWidgetConfig, + tracker: IWidgetTracker, chatPanel: MultiChatPanel | null, commandPalette: ICommandPalette | null, filebrowser: IDefaultFileBrowser | null, launcher: ILauncher | null ) => { const { commands } = app; - const { tracker, widgetConfig } = factory; + /** * Command to create a new chat. * @@ -793,7 +831,7 @@ const chatPanel: JupyterFrontEndPlugin = { description: 'The chat panel widget.', autoStart: true, provides: IChatPanel, - requires: [IChatFactory, ICollaborativeContentProvider, IRenderMimeRegistry], + requires: [IWidgetConfig, ICollaborativeContentProvider, IRenderMimeRegistry], optional: [ IAttachmentOpenerRegistry, IChatCommandRegistry, @@ -805,7 +843,7 @@ const chatPanel: JupyterFrontEndPlugin = { ], activate: ( app: JupyterFrontEnd, - factory: IChatFactory, + widgetConfig: IWidgetConfig, drive: ICollaborativeContentProvider, rmRegistry: IRenderMimeRegistry, attachmentOpenerRegistry: IAttachmentOpenerRegistry, @@ -823,7 +861,7 @@ const chatPanel: JupyterFrontEndPlugin = { // Get the chat in default directory const getChatNames = async () => { const dirContents = await serviceManager.contents.get( - factory.widgetConfig.config.defaultDirectory ?? '' + widgetConfig.config.defaultDirectory ?? '' ); const names: { [name: string]: string } = {}; for (const file of dirContents.content) { @@ -844,7 +882,7 @@ const chatPanel: JupyterFrontEndPlugin = { app, drive, path, - factory.widgetConfig.config.defaultDirectory + widgetConfig.config.defaultDirectory ); }, openInMain: path => { @@ -867,7 +905,7 @@ const chatPanel: JupyterFrontEndPlugin = { chatPanel.id = 'JupyterlabChat:sidepanel'; // Update available chats and section title when default directory changed. - factory.widgetConfig.configChanged.connect((_, config) => { + widgetConfig.configChanged.connect((_, config) => { if (config.defaultDirectory !== undefined) { chatPanel.updateChatList(); chatPanel.sections.forEach(section => { @@ -901,7 +939,7 @@ const chatPanel: JupyterFrontEndPlugin = { if (currentSection) { currentSection.displayName = getDisplayName( change.newValue.path, - factory.widgetConfig.config.defaultDirectory + widgetConfig.config.defaultDirectory ); } } @@ -915,11 +953,6 @@ const chatPanel: JupyterFrontEndPlugin = { restorer.add(chatPanel, 'jupyter-chat'); } - // Add the new opened chat in the tracker. - chatPanel.sectionAdded.connect((_, section) => { - factory.tracker.add(section.widget); - }); - /* * Command to move a chat from the main area to the side panel. */ @@ -1029,6 +1062,7 @@ const footerRegistry: JupyterFrontEndPlugin = { export default [ activeCellManager, attachmentOpeners, + chatConfig, chatCommands, chatCommandRegistryPlugin, chatPanel, diff --git a/packages/jupyterlab-chat/src/token.ts b/packages/jupyterlab-chat/src/token.ts index c6ebdae2..89a70a95 100644 --- a/packages/jupyterlab-chat/src/token.ts +++ b/packages/jupyterlab-chat/src/token.ts @@ -7,14 +7,13 @@ import { IConfig, chatIcon, IActiveCellManager, - ISelectionWatcher, - ChatWidget + ISelectionWatcher } from '@jupyter/chat'; -import { WidgetTracker } from '@jupyterlab/apputils'; import { DocumentRegistry } from '@jupyterlab/docregistry'; import { Token } from '@lumino/coreutils'; import { ISignal } from '@lumino/signaling'; -import { MultiChatPanel as ChatPanel } from '@jupyter/chat'; +import { MultiChatPanel } from '@jupyter/chat'; +import { ChatWidgetFactory } from './factory'; /** * The file type for a chat document. @@ -32,7 +31,7 @@ export const chatFileType: DocumentRegistry.IFileType = { /** * The token for the chat widget factory. */ -export const IChatFactory = new Token( +export const IChatFactory = new Token( 'jupyterlab-chat:IChatFactory' ); @@ -46,20 +45,6 @@ export interface ILabChatConfig extends IConfig { defaultDirectory?: string; } -/** - * The interface for the chat factory objects. - */ -export interface IChatFactory { - /** - * The chat widget config. - */ - widgetConfig: IWidgetConfig; - /** - * The chat panel tracker. - */ - tracker: WidgetTracker; -} - /** * The interface for the chats config. */ @@ -75,6 +60,13 @@ export interface IWidgetConfig { configChanged: IConfigChanged; } +/** + * The token providing the chat widget config. + */ +export const IWidgetConfig = new Token( + 'jupyterlab-chat:IWidgetConfig' +); + /** * A signal emitting when the configuration for the chats has changed. */ @@ -118,7 +110,9 @@ export const CommandIDs = { /** * The chat panel token. */ -export const IChatPanel = new Token('jupyterlab-chat:IChatPanel'); +export const IChatPanel = new Token( + 'jupyterlab-chat:IChatPanel' +); /** * The active cell manager plugin. From 2ffcb6091d4ebf864d199e16ed0cda596b82884b Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Nov 2025 17:39:43 +0100 Subject: [PATCH 3/5] Fix restoration for main area widget --- packages/jupyter-chat/src/tokens.ts | 6 ++++-- packages/jupyterlab-chat-extension/src/index.ts | 13 +++++++++---- packages/jupyterlab-chat/src/widget.tsx | 9 ++++++++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/jupyter-chat/src/tokens.ts b/packages/jupyter-chat/src/tokens.ts index 6a9c3763..5e2d4418 100644 --- a/packages/jupyter-chat/src/tokens.ts +++ b/packages/jupyter-chat/src/tokens.ts @@ -3,7 +3,7 @@ * Distributed under the terms of the Modified BSD License. */ -import { IWidgetTracker } from '@jupyterlab/apputils'; +import { IWidgetTracker, MainAreaWidget } from '@jupyterlab/apputils'; import { Token } from '@lumino/coreutils'; import { ChatWidget } from './widgets'; @@ -11,7 +11,9 @@ import { ChatWidget } from './widgets'; /** * the chat tracker type. */ -export type IChatTracker = IWidgetTracker; +export type IChatTracker = IWidgetTracker< + ChatWidget | MainAreaWidget +>; /** * A chat tracker token. diff --git a/packages/jupyterlab-chat-extension/src/index.ts b/packages/jupyterlab-chat-extension/src/index.ts index e4e1fd77..bcc796bb 100644 --- a/packages/jupyterlab-chat-extension/src/index.ts +++ b/packages/jupyterlab-chat-extension/src/index.ts @@ -410,15 +410,17 @@ const chatTracker: JupyterFrontEndPlugin = { const namespace = 'chat'; // Creating the tracker for the chat widgets. - const tracker = new WidgetTracker({ namespace }); + const tracker = new WidgetTracker({ + namespace + }); // Add the widget to the tracker when it's created factory.widgetCreated.connect((sender, widget) => { // Notify the instance tracker if restore data needs to update. widget.context.pathChanged.connect(() => { - tracker.save(widget.content); + tracker.save(widget); }); - tracker.add(widget.content); + tracker.add(widget); // Update the 'markAsRead' command status when the unread changed. widget.model.unreadChanged.connect(() => @@ -450,7 +452,10 @@ const chatTracker: JupyterFrontEndPlugin = { inSidePanel: widget.area === 'sidebar', startup: true }), - name: widget => widget.model.name, + name: widget => { + const area = widget.area ?? 'main'; + return `${area}:${widget.model.name}`; + }, when: openCommandReady.promise }); } diff --git a/packages/jupyterlab-chat/src/widget.tsx b/packages/jupyterlab-chat/src/widget.tsx index fbb1d17a..6fe41194 100644 --- a/packages/jupyterlab-chat/src/widget.tsx +++ b/packages/jupyterlab-chat/src/widget.tsx @@ -3,7 +3,7 @@ * Distributed under the terms of the Modified BSD License. */ -import { ChatWidget, IChatModel } from '@jupyter/chat'; +import { ChatArea, ChatWidget, IChatModel } from '@jupyter/chat'; import { DocumentWidget } from '@jupyterlab/docregistry'; import { LabChatModel } from './model'; @@ -39,6 +39,13 @@ export class LabChatPanel extends DocumentWidget { return this.context.model; } + /** + * The area of the widget. + */ + get area(): ChatArea | undefined { + return this.content.area; + } + /** * Add class to tab when messages are unread. */ From 7675fc98086b2a23b029c2364805d062b9382008 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Nov 2025 18:23:54 +0100 Subject: [PATCH 4/5] Fix ui test --- ui-tests/tests/input-toolbar.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui-tests/tests/input-toolbar.spec.ts b/ui-tests/tests/input-toolbar.spec.ts index ae5cba8c..0db97a16 100644 --- a/ui-tests/tests/input-toolbar.spec.ts +++ b/ui-tests/tests/input-toolbar.spec.ts @@ -25,9 +25,7 @@ test.describe('#inputToolbar', () => { // Modify the input toolbar when a chat is opened. await page.evaluate(async () => { - const tracker = ( - await window.getPlugin('jupyterlab-chat-extension:factory') - ).tracker; + const tracker = await window.getPlugin('jupyterlab-chat-extension:tracker'); const updateToolbar = registry => { registry.hide('attach'); From 03403096f3cb7d999755210f8367d7f2a53f12f4 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Nov 2025 18:52:31 +0100 Subject: [PATCH 5/5] lint --- ui-tests/tests/input-toolbar.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui-tests/tests/input-toolbar.spec.ts b/ui-tests/tests/input-toolbar.spec.ts index 0db97a16..2cc76e44 100644 --- a/ui-tests/tests/input-toolbar.spec.ts +++ b/ui-tests/tests/input-toolbar.spec.ts @@ -25,7 +25,9 @@ test.describe('#inputToolbar', () => { // Modify the input toolbar when a chat is opened. await page.evaluate(async () => { - const tracker = await window.getPlugin('jupyterlab-chat-extension:tracker'); + const tracker = await window.getPlugin( + 'jupyterlab-chat-extension:tracker' + ); const updateToolbar = registry => { registry.hide('attach');