1- import { ChatWidget , INotebookAttachment } from '@jupyter/chat' ;
1+ import {
2+ AttachmentOpenerRegistry ,
3+ ChatWidget ,
4+ IAttachment ,
5+ INotebookAttachment
6+ } from '@jupyter/chat' ;
27import {
38 JupyterFrontEnd ,
49 JupyterFrontEndPlugin
510} from '@jupyterlab/application' ;
611import { ICodeCellModel } from '@jupyterlab/cells' ;
7- import { INotebookTracker } from '@jupyterlab/notebook' ;
12+ import { INotebookTracker , NotebookPanel } from '@jupyterlab/notebook' ;
813import { IRenderMimeRegistry } from '@jupyterlab/rendermime' ;
914import { ISettingRegistry } from '@jupyterlab/settingregistry' ;
1015import { ITranslator , nullTranslator } from '@jupyterlab/translation' ;
1116import { infoIcon } from '@jupyterlab/ui-components' ;
1217
1318import { TutorChatModel } from './model' ;
19+ import { isContinuous } from './utils' ;
1420
1521const INFO_ICON_BASE_64 = btoa ( infoIcon . svgstr ) ;
1622
@@ -47,6 +53,53 @@ const plugin: JupyterFrontEndPlugin<void> = {
4753 const { commands } = app ;
4854 const trans = ( translator ?? nullTranslator ) . load ( 'jupyterlab' ) ;
4955
56+ // The attachment opener registry.
57+ const attachmentOpenerRegistry = new AttachmentOpenerRegistry ( ) ;
58+
59+ attachmentOpenerRegistry . set ( 'file' , ( attachment : IAttachment ) => {
60+ app . commands . execute ( 'docmanager:open' , { path : attachment . value } ) ;
61+ } ) ;
62+
63+ attachmentOpenerRegistry . set (
64+ 'notebook' ,
65+ async ( attachment : IAttachment ) => {
66+ // Reveal the notebook.
67+ const widget = await app . commands . execute ( 'docmanager:open' , {
68+ path : attachment . value
69+ } ) ;
70+
71+ // Check if cells are attached.
72+ if (
73+ widget &&
74+ attachment . type === 'notebook' &&
75+ attachment . cells ?. length
76+ ) {
77+ const panel = widget as NotebookPanel ;
78+ await panel . context . ready ;
79+
80+ // Get the attached cell indexes in order.
81+ const cellList = panel . context . model . cells ;
82+ const cellIds = attachment . cells . map ( cell => cell . id ) ;
83+ const range : number [ ] = [ ] ;
84+ for ( let i = 0 ; i < cellList . length ; i ++ ) {
85+ if ( cellIds . includes ( cellList . get ( i ) . id ) ) {
86+ range . push ( i ) ;
87+ }
88+ }
89+ range . sort ( ) ;
90+
91+ // Set the first cell as active.
92+ panel . content . activeCellIndex = range [ 0 ] ;
93+
94+ // If cells are contiguous, select all of them.
95+ if ( isContinuous ( range ) ) {
96+ panel . content . extendContiguousSelectionTo ( range [ range . length - 1 ] ) ;
97+ }
98+ }
99+ }
100+ ) ;
101+
102+ // Build the chat.
50103 const tutorModel = new TutorChatModel ( {
51104 id : 'jupyter-ai-tutor' ,
52105 translator : translator ?? undefined
@@ -57,7 +110,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
57110 translator : translator ?? undefined ,
58111 welcomeMessage : trans . __ (
59112 `## Select a code cell and click **Explain Code** <img src="data:image/svg+xml;base64,${ INFO_ICON_BASE_64 } " /> to get started.`
60- )
113+ ) ,
114+ attachmentOpenerRegistry
61115 } ) ;
62116 chatWidget . id = 'jupyter-ai-tutor-panel' ;
63117 chatWidget . title . label = trans . __ ( 'Tutor' ) ;
0 commit comments