@notebook-intelligence/notebook-intelligence 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,1398 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+
3
+ import {
4
+ JupyterFrontEnd,
5
+ JupyterFrontEndPlugin,
6
+ JupyterLab
7
+ } from '@jupyterlab/application';
8
+
9
+ import { IDocumentManager } from '@jupyterlab/docmanager';
10
+ import { DocumentWidget } from '@jupyterlab/docregistry';
11
+
12
+ import { Dialog, ICommandPalette } from '@jupyterlab/apputils';
13
+ import { IMainMenu } from '@jupyterlab/mainmenu';
14
+
15
+ import { IEditorLanguageRegistry } from '@jupyterlab/codemirror';
16
+
17
+ import { CodeCell } from '@jupyterlab/cells';
18
+ import { ISharedNotebook } from '@jupyter/ydoc';
19
+
20
+ import { ISettingRegistry } from '@jupyterlab/settingregistry';
21
+
22
+ import {
23
+ CompletionHandler,
24
+ ICompletionProviderManager,
25
+ IInlineCompletionContext,
26
+ IInlineCompletionItem,
27
+ IInlineCompletionList,
28
+ IInlineCompletionProvider
29
+ } from '@jupyterlab/completer';
30
+
31
+ import { NotebookPanel } from '@jupyterlab/notebook';
32
+ import { CodeEditor } from '@jupyterlab/codeeditor';
33
+ import { FileEditorWidget } from '@jupyterlab/fileeditor';
34
+
35
+ import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
36
+
37
+ import { ContentsManager, KernelSpecManager } from '@jupyterlab/services';
38
+
39
+ import { LabIcon } from '@jupyterlab/ui-components';
40
+
41
+ import { Menu, Panel, Widget } from '@lumino/widgets';
42
+ import { CommandRegistry } from '@lumino/commands';
43
+ import { IStatusBar } from '@jupyterlab/statusbar';
44
+
45
+ import {
46
+ ChatSidebar,
47
+ ConfigurationDialogBody,
48
+ GitHubCopilotLoginDialogBody,
49
+ GitHubCopilotStatusBarItem,
50
+ InlinePromptWidget,
51
+ RunChatCompletionType
52
+ } from './chat-sidebar';
53
+ import { NBIAPI, GitHubCopilotLoginStatus } from './api';
54
+ import {
55
+ BackendMessageType,
56
+ GITHUB_COPILOT_PROVIDER_ID,
57
+ IActiveDocumentInfo,
58
+ ICellContents,
59
+ INotebookIntelligence,
60
+ ITelemetryEmitter,
61
+ ITelemetryEvent,
62
+ ITelemetryListener,
63
+ RequestDataType,
64
+ TelemetryEventType
65
+ } from './tokens';
66
+ import sparklesSvgstr from '../style/icons/sparkles.svg';
67
+ import copilotSvgstr from '../style/icons/copilot.svg';
68
+
69
+ import {
70
+ applyCodeToSelectionInEditor,
71
+ cellOutputAsText,
72
+ compareSelections,
73
+ extractLLMGeneratedCode,
74
+ getSelectionInEditor,
75
+ getTokenCount,
76
+ getWholeNotebookContent,
77
+ isSelectionEmpty,
78
+ markdownToComment,
79
+ waitForDuration
80
+ } from './utils';
81
+ import { UUID } from '@lumino/coreutils';
82
+
83
+ namespace CommandIDs {
84
+ export const chatuserInput = 'notebook-intelligence:chat-user-input';
85
+ export const insertAtCursor = 'notebook-intelligence:insert-at-cursor';
86
+ export const addCodeAsNewCell = 'notebook-intelligence:add-code-as-new-cell';
87
+ export const createNewFile = 'notebook-intelligence:create-new-file';
88
+ export const createNewNotebookFromPython =
89
+ 'notebook-intelligence:create-new-notebook-from-py';
90
+ export const addCodeCellToNotebook =
91
+ 'notebook-intelligence:add-code-cell-to-notebook';
92
+ export const addMarkdownCellToNotebook =
93
+ 'notebook-intelligence:add-markdown-cell-to-notebook';
94
+ export const editorGenerateCode =
95
+ 'notebook-intelligence:editor-generate-code';
96
+ export const editorExplainThisCode =
97
+ 'notebook-intelligence:editor-explain-this-code';
98
+ export const editorFixThisCode = 'notebook-intelligence:editor-fix-this-code';
99
+ export const editorExplainThisOutput =
100
+ 'notebook-intelligence:editor-explain-this-output';
101
+ export const editorTroubleshootThisOutput =
102
+ 'notebook-intelligence:editor-troubleshoot-this-output';
103
+ export const openGitHubCopilotLoginDialog =
104
+ 'notebook-intelligence:open-github-copilot-login-dialog';
105
+ export const openConfigurationDialog =
106
+ 'notebook-intelligence:open-configuration-dialog';
107
+ }
108
+
109
+ const DOCUMENT_WATCH_INTERVAL = 1000;
110
+ const MAX_TOKENS = 4096;
111
+ const githubCopilotIcon = new LabIcon({
112
+ name: 'notebook-intelligence:github-copilot-icon',
113
+ svgstr: copilotSvgstr
114
+ });
115
+
116
+ const sparkleIcon = new LabIcon({
117
+ name: 'notebook-intelligence:sparkles-icon',
118
+ svgstr: sparklesSvgstr
119
+ });
120
+
121
+ const emptyNotebookContent: any = {
122
+ cells: [],
123
+ metadata: {},
124
+ nbformat: 4,
125
+ nbformat_minor: 5
126
+ };
127
+
128
+ const BACKEND_TELEMETRY_LISTENER_NAME = 'backend-telemetry-listener';
129
+
130
+ class ActiveDocumentWatcher {
131
+ static initialize(
132
+ app: JupyterLab,
133
+ languageRegistry: IEditorLanguageRegistry
134
+ ) {
135
+ ActiveDocumentWatcher._languageRegistry = languageRegistry;
136
+
137
+ app.shell.currentChanged?.connect((_sender, args) => {
138
+ ActiveDocumentWatcher.watchDocument(args.newValue);
139
+ });
140
+
141
+ ActiveDocumentWatcher.activeDocumentInfo.activeWidget =
142
+ app.shell.currentWidget;
143
+ ActiveDocumentWatcher.handleWatchDocument();
144
+ }
145
+
146
+ static watchDocument(widget: Widget) {
147
+ if (ActiveDocumentWatcher.activeDocumentInfo.activeWidget === widget) {
148
+ return;
149
+ }
150
+ clearInterval(ActiveDocumentWatcher._watchTimer);
151
+ ActiveDocumentWatcher.activeDocumentInfo.activeWidget = widget;
152
+
153
+ ActiveDocumentWatcher._watchTimer = setInterval(() => {
154
+ ActiveDocumentWatcher.handleWatchDocument();
155
+ }, DOCUMENT_WATCH_INTERVAL);
156
+
157
+ ActiveDocumentWatcher.handleWatchDocument();
158
+ }
159
+
160
+ static handleWatchDocument() {
161
+ const activeDocumentInfo = ActiveDocumentWatcher.activeDocumentInfo;
162
+ const previousDocumentInfo = {
163
+ ...activeDocumentInfo,
164
+ ...{ activeWidget: null }
165
+ };
166
+
167
+ const activeWidget = activeDocumentInfo.activeWidget;
168
+ if (activeWidget instanceof NotebookPanel) {
169
+ const np = activeWidget as NotebookPanel;
170
+ activeDocumentInfo.filename = np.sessionContext.name;
171
+ activeDocumentInfo.filePath = np.sessionContext.path;
172
+ activeDocumentInfo.language =
173
+ (np.model?.sharedModel?.metadata?.kernelspec?.language as string) ||
174
+ 'python';
175
+ const { activeCellIndex, activeCell } = np.content;
176
+ activeDocumentInfo.activeCellIndex = activeCellIndex;
177
+ activeDocumentInfo.selection = activeCell?.editor?.getSelection();
178
+ } else if (activeWidget) {
179
+ const dw = activeWidget as DocumentWidget;
180
+ const contentsModel = dw.context?.contentsModel;
181
+ if (contentsModel?.format === 'text') {
182
+ const fileName = contentsModel.name;
183
+ const filePath = contentsModel.path;
184
+ const language =
185
+ ActiveDocumentWatcher._languageRegistry.findByMIME(
186
+ contentsModel.mimetype
187
+ ) || ActiveDocumentWatcher._languageRegistry.findByFileName(fileName);
188
+ activeDocumentInfo.language = language?.name || 'unknown';
189
+ activeDocumentInfo.filename = fileName;
190
+ activeDocumentInfo.filePath = filePath;
191
+ if (activeWidget instanceof FileEditorWidget) {
192
+ const fe = activeWidget as FileEditorWidget;
193
+ activeDocumentInfo.selection = fe.content.editor?.getSelection();
194
+ } else {
195
+ activeDocumentInfo.selection = undefined;
196
+ }
197
+ } else {
198
+ activeDocumentInfo.filename = '';
199
+ activeDocumentInfo.filePath = '';
200
+ activeDocumentInfo.language = '';
201
+ }
202
+ }
203
+
204
+ if (
205
+ ActiveDocumentWatcher.documentInfoChanged(
206
+ previousDocumentInfo,
207
+ activeDocumentInfo
208
+ )
209
+ ) {
210
+ ActiveDocumentWatcher.fireActiveDocumentChangedEvent();
211
+ }
212
+ }
213
+
214
+ private static documentInfoChanged(
215
+ lhs: IActiveDocumentInfo,
216
+ rhs: IActiveDocumentInfo
217
+ ): boolean {
218
+ if (!lhs || !rhs) {
219
+ return true;
220
+ }
221
+
222
+ return (
223
+ lhs.filename !== rhs.filename ||
224
+ lhs.filePath !== rhs.filePath ||
225
+ lhs.language !== rhs.language ||
226
+ lhs.activeCellIndex !== rhs.activeCellIndex ||
227
+ !compareSelections(lhs.selection, rhs.selection)
228
+ );
229
+ }
230
+
231
+ static getActiveSelectionContent(): string {
232
+ const activeDocumentInfo = ActiveDocumentWatcher.activeDocumentInfo;
233
+ const activeWidget = activeDocumentInfo.activeWidget;
234
+
235
+ if (activeWidget instanceof NotebookPanel) {
236
+ const np = activeWidget as NotebookPanel;
237
+ const editor = np.content.activeCell.editor;
238
+ if (isSelectionEmpty(editor.getSelection())) {
239
+ return getWholeNotebookContent(np);
240
+ } else {
241
+ return getSelectionInEditor(editor);
242
+ }
243
+ } else if (activeWidget instanceof FileEditorWidget) {
244
+ const fe = activeWidget as FileEditorWidget;
245
+ const editor = fe.content.editor;
246
+ if (isSelectionEmpty(editor.getSelection())) {
247
+ return editor.model.sharedModel.getSource();
248
+ } else {
249
+ return getSelectionInEditor(editor);
250
+ }
251
+ } else {
252
+ const dw = activeWidget as DocumentWidget;
253
+ return dw?.context?.model?.toString();
254
+ }
255
+ }
256
+
257
+ static getCurrentCellContents(): ICellContents {
258
+ const activeDocumentInfo = ActiveDocumentWatcher.activeDocumentInfo;
259
+ const activeWidget = activeDocumentInfo.activeWidget;
260
+
261
+ if (activeWidget instanceof NotebookPanel) {
262
+ const np = activeWidget as NotebookPanel;
263
+ const activeCell = np.content.activeCell;
264
+ const input = activeCell.model.sharedModel.source.trim();
265
+ let output = '';
266
+ if (activeCell instanceof CodeCell) {
267
+ output = cellOutputAsText(np.content.activeCell as CodeCell);
268
+ }
269
+
270
+ return { input, output };
271
+ }
272
+
273
+ return null;
274
+ }
275
+
276
+ static fireActiveDocumentChangedEvent() {
277
+ document.dispatchEvent(
278
+ new CustomEvent('copilotSidebar:activeDocumentChanged', {
279
+ detail: {
280
+ activeDocumentInfo: ActiveDocumentWatcher.activeDocumentInfo
281
+ }
282
+ })
283
+ );
284
+ }
285
+
286
+ static activeDocumentInfo: IActiveDocumentInfo = {
287
+ language: 'python',
288
+ filename: 'nb-doesnt-exist.ipynb',
289
+ filePath: 'nb-doesnt-exist.ipynb',
290
+ activeWidget: null,
291
+ activeCellIndex: -1,
292
+ selection: null
293
+ };
294
+ private static _watchTimer: any;
295
+ private static _languageRegistry: IEditorLanguageRegistry;
296
+ }
297
+
298
+ class NBIInlineCompletionProvider
299
+ implements IInlineCompletionProvider<IInlineCompletionItem>
300
+ {
301
+ constructor(telemetryEmitter: TelemetryEmitter) {
302
+ this._telemetryEmitter = telemetryEmitter;
303
+ }
304
+
305
+ get schema(): ISettingRegistry.IProperty {
306
+ return {
307
+ default: {
308
+ debouncerDelay: 200,
309
+ timeout: 15000
310
+ }
311
+ };
312
+ }
313
+
314
+ fetch(
315
+ request: CompletionHandler.IRequest,
316
+ context: IInlineCompletionContext
317
+ ): Promise<IInlineCompletionList<IInlineCompletionItem>> {
318
+ let preContent = '';
319
+ let postContent = '';
320
+ const preCursor = request.text.substring(0, request.offset);
321
+ const postCursor = request.text.substring(request.offset);
322
+ let language = ActiveDocumentWatcher.activeDocumentInfo.language;
323
+
324
+ let editorType = 'file-editor';
325
+
326
+ if (context.widget instanceof NotebookPanel) {
327
+ editorType = 'notebook';
328
+ const activeCell = context.widget.content.activeCell;
329
+ if (activeCell.model.sharedModel.cell_type === 'markdown') {
330
+ language = 'markdown';
331
+ }
332
+ let activeCellReached = false;
333
+
334
+ for (const cell of context.widget.content.widgets) {
335
+ const cellModel = cell.model.sharedModel;
336
+ if (cell === activeCell) {
337
+ activeCellReached = true;
338
+ } else if (!activeCellReached) {
339
+ if (cellModel.cell_type === 'code') {
340
+ preContent += cellModel.source + '\n';
341
+ } else if (cellModel.cell_type === 'markdown') {
342
+ preContent += markdownToComment(cellModel.source) + '\n';
343
+ }
344
+ } else {
345
+ if (cellModel.cell_type === 'code') {
346
+ postContent += cellModel.source + '\n';
347
+ } else if (cellModel.cell_type === 'markdown') {
348
+ postContent += markdownToComment(cellModel.source) + '\n';
349
+ }
350
+ }
351
+ }
352
+ }
353
+
354
+ const nbiConfig = NBIAPI.config;
355
+ const inlineCompletionsEnabled =
356
+ nbiConfig.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID
357
+ ? NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.LoggedIn
358
+ : nbiConfig.inlineCompletionModel.provider !== 'none';
359
+
360
+ this._telemetryEmitter.emitTelemetryEvent({
361
+ type: TelemetryEventType.InlineCompletionRequest,
362
+ data: {
363
+ inlineCompletionModel: {
364
+ provider: NBIAPI.config.inlineCompletionModel.provider,
365
+ model: NBIAPI.config.inlineCompletionModel.model
366
+ },
367
+ editorType
368
+ }
369
+ });
370
+
371
+ return new Promise((resolve, reject) => {
372
+ const items: IInlineCompletionItem[] = [];
373
+
374
+ if (!inlineCompletionsEnabled) {
375
+ resolve({ items });
376
+ return;
377
+ }
378
+
379
+ if (this._lastRequestInfo) {
380
+ NBIAPI.sendWebSocketMessage(
381
+ this._lastRequestInfo.messageId,
382
+ RequestDataType.CancelInlineCompletionRequest,
383
+ { chatId: this._lastRequestInfo.chatId }
384
+ );
385
+ }
386
+
387
+ const messageId = UUID.uuid4();
388
+ const chatId = UUID.uuid4();
389
+ this._lastRequestInfo = { chatId, messageId, requestTime: new Date() };
390
+
391
+ NBIAPI.inlineCompletionsRequest(
392
+ chatId,
393
+ messageId,
394
+ preContent + preCursor,
395
+ postCursor + postContent,
396
+ language,
397
+ ActiveDocumentWatcher.activeDocumentInfo.filename,
398
+ {
399
+ emit: (response: any) => {
400
+ if (
401
+ response.type === BackendMessageType.StreamMessage &&
402
+ response.id === this._lastRequestInfo.messageId
403
+ ) {
404
+ items.push({
405
+ insertText: response.data.completions
406
+ });
407
+
408
+ const timeElapsed =
409
+ (new Date().getTime() -
410
+ this._lastRequestInfo.requestTime.getTime()) /
411
+ 1000;
412
+ this._telemetryEmitter.emitTelemetryEvent({
413
+ type: TelemetryEventType.InlineCompletionResponse,
414
+ data: {
415
+ inlineCompletionModel: {
416
+ provider: NBIAPI.config.inlineCompletionModel.provider,
417
+ model: NBIAPI.config.inlineCompletionModel.model
418
+ },
419
+ timeElapsed
420
+ }
421
+ });
422
+
423
+ resolve({ items });
424
+ } else {
425
+ reject();
426
+ }
427
+ }
428
+ }
429
+ );
430
+ });
431
+ }
432
+
433
+ get name(): string {
434
+ return 'Notebook Intelligence';
435
+ }
436
+
437
+ get identifier(): string {
438
+ return '@notebook-intelligence/notebook-intelligence';
439
+ }
440
+
441
+ get icon(): LabIcon.ILabIcon {
442
+ return NBIAPI.config.usingGitHubCopilotModel
443
+ ? githubCopilotIcon
444
+ : sparkleIcon;
445
+ }
446
+
447
+ private _lastRequestInfo: {
448
+ chatId: string;
449
+ messageId: string;
450
+ requestTime: Date;
451
+ } = null;
452
+ private _telemetryEmitter: TelemetryEmitter;
453
+ }
454
+
455
+ class TelemetryEmitter implements ITelemetryEmitter {
456
+ registerTelemetryListener(listener: ITelemetryListener) {
457
+ const listenerName = listener.name;
458
+
459
+ if (listenerName !== BACKEND_TELEMETRY_LISTENER_NAME) {
460
+ console.warn(
461
+ `Notebook Intelligence telemetry listener '${listenerName}' registered. Make sure it is from a trusted source.`
462
+ );
463
+ }
464
+
465
+ let listenerAlreadyExists = false;
466
+ this._listeners.forEach(existingListener => {
467
+ if (existingListener.name === listenerName) {
468
+ listenerAlreadyExists = true;
469
+ }
470
+ });
471
+
472
+ if (listenerAlreadyExists) {
473
+ console.error(
474
+ `Notebook Intelligence telemetry listener '${listenerName}' already exists!`
475
+ );
476
+ return;
477
+ }
478
+
479
+ this._listeners.add(listener);
480
+ }
481
+
482
+ unregisterTelemetryListener(listener: ITelemetryListener) {
483
+ this._listeners.delete(listener);
484
+ }
485
+
486
+ emitTelemetryEvent(event: ITelemetryEvent) {
487
+ this._listeners.forEach(listener => {
488
+ listener.onTelemetryEvent(event);
489
+ });
490
+ }
491
+
492
+ private _listeners: Set<ITelemetryListener> = new Set<ITelemetryListener>();
493
+ }
494
+
495
+ /**
496
+ * Initialization data for the @notebook-intelligence/notebook-intelligence extension.
497
+ */
498
+ const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
499
+ id: '@notebook-intelligence/notebook-intelligence:plugin',
500
+ description: 'Notebook Intelligence',
501
+ autoStart: true,
502
+ requires: [
503
+ ICompletionProviderManager,
504
+ IDocumentManager,
505
+ IDefaultFileBrowser,
506
+ IEditorLanguageRegistry,
507
+ ICommandPalette,
508
+ IMainMenu
509
+ ],
510
+ optional: [ISettingRegistry, IStatusBar],
511
+ provides: INotebookIntelligence,
512
+ activate: async (
513
+ app: JupyterFrontEnd,
514
+ completionManager: ICompletionProviderManager,
515
+ docManager: IDocumentManager,
516
+ defaultBrowser: IDefaultFileBrowser,
517
+ languageRegistry: IEditorLanguageRegistry,
518
+ palette: ICommandPalette,
519
+ mainMenu: IMainMenu,
520
+ settingRegistry: ISettingRegistry | null,
521
+ statusBar: IStatusBar | null
522
+ ) => {
523
+ console.log(
524
+ 'JupyterLab extension @notebook-intelligence/notebook-intelligence is activated!'
525
+ );
526
+
527
+ const telemetryEmitter = new TelemetryEmitter();
528
+
529
+ telemetryEmitter.registerTelemetryListener({
530
+ name: BACKEND_TELEMETRY_LISTENER_NAME,
531
+ onTelemetryEvent: event => {
532
+ NBIAPI.emitTelemetryEvent(event);
533
+ }
534
+ });
535
+
536
+ const extensionService: INotebookIntelligence = {
537
+ registerTelemetryListener: (listener: ITelemetryListener) => {
538
+ telemetryEmitter.registerTelemetryListener(listener);
539
+ },
540
+ unregisterTelemetryListener: (listener: ITelemetryListener) => {
541
+ telemetryEmitter.unregisterTelemetryListener(listener);
542
+ }
543
+ };
544
+
545
+ await NBIAPI.initialize();
546
+
547
+ let openPopover: InlinePromptWidget | null = null;
548
+
549
+ completionManager.registerInlineProvider(
550
+ new NBIInlineCompletionProvider(telemetryEmitter)
551
+ );
552
+
553
+ if (settingRegistry) {
554
+ settingRegistry
555
+ .load(plugin.id)
556
+ .then(settings => {
557
+ //
558
+ })
559
+ .catch(reason => {
560
+ console.error(
561
+ 'Failed to load settings for @notebook-intelligence/notebook-intelligence.',
562
+ reason
563
+ );
564
+ });
565
+ }
566
+
567
+ const waitForFileToBeActive = async (
568
+ filePath: string
569
+ ): Promise<boolean> => {
570
+ const isNotebook = filePath.endsWith('.ipynb');
571
+
572
+ return new Promise<boolean>((resolve, reject) => {
573
+ const checkIfActive = () => {
574
+ const activeFilePath =
575
+ ActiveDocumentWatcher.activeDocumentInfo.filePath;
576
+ const filePathToCheck = filePath;
577
+ const currentWidget = app.shell.currentWidget;
578
+ if (
579
+ activeFilePath === filePathToCheck &&
580
+ ((isNotebook &&
581
+ currentWidget instanceof NotebookPanel &&
582
+ currentWidget.content.activeCell &&
583
+ currentWidget.content.activeCell.node.contains(
584
+ document.activeElement
585
+ )) ||
586
+ (!isNotebook &&
587
+ currentWidget instanceof FileEditorWidget &&
588
+ currentWidget.content.editor.hasFocus()))
589
+ ) {
590
+ resolve(true);
591
+ } else {
592
+ setTimeout(checkIfActive, 200);
593
+ }
594
+ };
595
+ checkIfActive();
596
+
597
+ waitForDuration(10000).then(() => {
598
+ resolve(false);
599
+ });
600
+ });
601
+ };
602
+
603
+ const panel = new Panel();
604
+ panel.id = 'notebook-intelligence-tab';
605
+ panel.title.caption = 'Copilot Chat';
606
+ const sidebarIcon = new LabIcon({
607
+ name: 'ui-components:palette',
608
+ svgstr: sparklesSvgstr
609
+ });
610
+ panel.title.icon = sidebarIcon;
611
+ const sidebar = new ChatSidebar({
612
+ getActiveDocumentInfo: (): IActiveDocumentInfo => {
613
+ return ActiveDocumentWatcher.activeDocumentInfo;
614
+ },
615
+ getActiveSelectionContent: (): string => {
616
+ return ActiveDocumentWatcher.getActiveSelectionContent();
617
+ },
618
+ getCurrentCellContents: (): ICellContents => {
619
+ return ActiveDocumentWatcher.getCurrentCellContents();
620
+ },
621
+ openFile: (path: string) => {
622
+ docManager.openOrReveal(path);
623
+ },
624
+ getApp(): JupyterFrontEnd {
625
+ return app;
626
+ },
627
+ getTelemetryEmitter(): ITelemetryEmitter {
628
+ return telemetryEmitter;
629
+ }
630
+ });
631
+ panel.addWidget(sidebar);
632
+ app.shell.add(panel, 'left', { rank: 1000 });
633
+ app.shell.activateById(panel.id);
634
+
635
+ app.commands.addCommand(CommandIDs.chatuserInput, {
636
+ execute: args => {
637
+ NBIAPI.sendChatUserInput(args.id as string, args.data);
638
+ }
639
+ });
640
+
641
+ app.commands.addCommand(CommandIDs.insertAtCursor, {
642
+ execute: args => {
643
+ const currentWidget = app.shell.currentWidget;
644
+ if (currentWidget instanceof NotebookPanel) {
645
+ const activeCell = currentWidget.content.activeCell;
646
+ if (activeCell) {
647
+ applyCodeToSelectionInEditor(
648
+ activeCell.editor,
649
+ args.code as string
650
+ );
651
+ return;
652
+ }
653
+ } else if (currentWidget instanceof FileEditorWidget) {
654
+ applyCodeToSelectionInEditor(
655
+ currentWidget.content.editor,
656
+ args.code as string
657
+ );
658
+ return;
659
+ }
660
+
661
+ app.commands.execute('apputils:notify', {
662
+ message:
663
+ 'Failed to insert at cursor. Open a notebook or file to insert the code.',
664
+ type: 'error',
665
+ options: { autoClose: true }
666
+ });
667
+ }
668
+ });
669
+
670
+ app.commands.addCommand(CommandIDs.addCodeAsNewCell, {
671
+ execute: args => {
672
+ const currentWidget = app.shell.currentWidget;
673
+ if (currentWidget instanceof NotebookPanel) {
674
+ let activeCellIndex = currentWidget.content.activeCellIndex;
675
+ activeCellIndex =
676
+ activeCellIndex === -1
677
+ ? currentWidget.content.widgets.length
678
+ : activeCellIndex + 1;
679
+
680
+ currentWidget.model?.sharedModel.insertCell(activeCellIndex, {
681
+ cell_type: 'code',
682
+ metadata: { trusted: true },
683
+ source: args.code as string
684
+ });
685
+ currentWidget.content.activeCellIndex = activeCellIndex;
686
+ } else {
687
+ app.commands.execute('apputils:notify', {
688
+ message: 'Open a notebook to insert the code as new cell',
689
+ type: 'error',
690
+ options: { autoClose: true }
691
+ });
692
+ }
693
+ }
694
+ });
695
+
696
+ app.commands.addCommand(CommandIDs.createNewFile, {
697
+ execute: async args => {
698
+ const contents = new ContentsManager();
699
+ const newPyFile = await contents.newUntitled({
700
+ ext: '.py',
701
+ path: defaultBrowser?.model.path
702
+ });
703
+ contents.save(newPyFile.path, {
704
+ content: extractLLMGeneratedCode(args.code as string),
705
+ format: 'text',
706
+ type: 'file'
707
+ });
708
+ docManager.openOrReveal(newPyFile.path);
709
+
710
+ await waitForFileToBeActive(newPyFile.path);
711
+
712
+ return newPyFile;
713
+ }
714
+ });
715
+
716
+ app.commands.addCommand(CommandIDs.createNewNotebookFromPython, {
717
+ execute: async args => {
718
+ let pythonKernelSpec = null;
719
+ const contents = new ContentsManager();
720
+ const kernels = new KernelSpecManager();
721
+ await kernels.ready;
722
+ const kernelspecs = kernels.specs?.kernelspecs;
723
+ if (kernelspecs) {
724
+ for (const key in kernelspecs) {
725
+ const kernelspec = kernelspecs[key];
726
+ if (kernelspec?.language === 'python') {
727
+ pythonKernelSpec = kernelspec;
728
+ break;
729
+ }
730
+ }
731
+ }
732
+
733
+ const newNBFile = await contents.newUntitled({
734
+ ext: '.ipynb',
735
+ path: defaultBrowser?.model.path
736
+ });
737
+ const nbFileContent = structuredClone(emptyNotebookContent);
738
+ if (pythonKernelSpec) {
739
+ nbFileContent.metadata = {
740
+ kernelspec: {
741
+ language: 'python',
742
+ name: pythonKernelSpec.name,
743
+ display_name: pythonKernelSpec.display_name
744
+ }
745
+ };
746
+ }
747
+
748
+ if (args.code) {
749
+ nbFileContent.cells.push({
750
+ cell_type: 'code',
751
+ metadata: { trusted: true },
752
+ source: [args.code as string],
753
+ outputs: []
754
+ });
755
+ }
756
+
757
+ contents.save(newNBFile.path, {
758
+ content: nbFileContent,
759
+ format: 'json',
760
+ type: 'notebook'
761
+ });
762
+ docManager.openOrReveal(newNBFile.path);
763
+
764
+ await waitForFileToBeActive(newNBFile.path);
765
+
766
+ return newNBFile;
767
+ }
768
+ });
769
+
770
+ const isNewEmptyNotebook = (model: ISharedNotebook) => {
771
+ return (
772
+ model.cells.length === 1 &&
773
+ model.cells[0].cell_type === 'code' &&
774
+ model.cells[0].source === ''
775
+ );
776
+ };
777
+
778
+ const githubLoginRequired = () => {
779
+ return (
780
+ NBIAPI.config.usingGitHubCopilotModel &&
781
+ NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.NotLoggedIn
782
+ );
783
+ };
784
+
785
+ const isChatEnabled = (): boolean => {
786
+ return NBIAPI.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
787
+ ? !githubLoginRequired()
788
+ : NBIAPI.config.chatModel.provider !== 'none';
789
+ };
790
+
791
+ const isActiveCellCodeCell = (): boolean => {
792
+ if (!(app.shell.currentWidget instanceof NotebookPanel)) {
793
+ return false;
794
+ }
795
+ const np = app.shell.currentWidget as NotebookPanel;
796
+ const activeCell = np.content.activeCell;
797
+ return activeCell instanceof CodeCell;
798
+ };
799
+
800
+ const isCurrentWidgetFileEditor = (): boolean => {
801
+ return app.shell.currentWidget instanceof FileEditorWidget;
802
+ };
803
+
804
+ const addCellToNotebook = (
805
+ filePath: string,
806
+ cellType: 'code' | 'markdown',
807
+ source: string
808
+ ): boolean => {
809
+ const currentWidget = app.shell.currentWidget;
810
+ const notebookOpen =
811
+ currentWidget instanceof NotebookPanel &&
812
+ currentWidget.sessionContext.path === filePath &&
813
+ currentWidget.model;
814
+ if (!notebookOpen) {
815
+ app.commands.execute('apputils:notify', {
816
+ message: `Failed to access the notebook: ${filePath}`,
817
+ type: 'error',
818
+ options: { autoClose: true }
819
+ });
820
+ return false;
821
+ }
822
+
823
+ const model = currentWidget.model.sharedModel;
824
+
825
+ const newCellIndex = isNewEmptyNotebook(model)
826
+ ? 0
827
+ : model.cells.length - 1;
828
+ model.insertCell(newCellIndex, {
829
+ cell_type: cellType,
830
+ metadata: { trusted: true },
831
+ source
832
+ });
833
+
834
+ return true;
835
+ };
836
+
837
+ app.commands.addCommand(CommandIDs.addCodeCellToNotebook, {
838
+ execute: args => {
839
+ return addCellToNotebook(
840
+ args.path as string,
841
+ 'code',
842
+ args.code as string
843
+ );
844
+ }
845
+ });
846
+
847
+ app.commands.addCommand(CommandIDs.addMarkdownCellToNotebook, {
848
+ execute: args => {
849
+ return addCellToNotebook(
850
+ args.path as string,
851
+ 'markdown',
852
+ args.markdown as string
853
+ );
854
+ }
855
+ });
856
+
857
+ app.commands.addCommand(CommandIDs.openGitHubCopilotLoginDialog, {
858
+ execute: args => {
859
+ let dialog: Dialog<unknown> | null = null;
860
+ const dialogBody = new GitHubCopilotLoginDialogBody({
861
+ onLoggedIn: () => dialog?.dispose()
862
+ });
863
+ dialog = new Dialog({
864
+ title: 'GitHub Copilot Status',
865
+ hasClose: true,
866
+ body: dialogBody,
867
+ buttons: []
868
+ });
869
+
870
+ dialog.launch();
871
+ }
872
+ });
873
+
874
+ app.commands.addCommand(CommandIDs.openConfigurationDialog, {
875
+ label: 'Notebook Intelligence Settings',
876
+ execute: args => {
877
+ let dialog: Dialog<unknown> | null = null;
878
+ const dialogBody = new ConfigurationDialogBody({
879
+ onSave: () => {
880
+ dialog?.dispose();
881
+ NBIAPI.fetchCapabilities();
882
+ }
883
+ });
884
+ dialog = new Dialog({
885
+ title: 'Notebook Intelligence Settings',
886
+ hasClose: true,
887
+ body: dialogBody,
888
+ buttons: []
889
+ });
890
+ dialog.node.classList.add('config-dialog-container');
891
+
892
+ dialog.launch();
893
+ }
894
+ });
895
+
896
+ palette.addItem({
897
+ command: CommandIDs.openConfigurationDialog,
898
+ category: 'Notebook Intelligence'
899
+ });
900
+
901
+ mainMenu.settingsMenu.addGroup([
902
+ {
903
+ command: CommandIDs.openConfigurationDialog
904
+ }
905
+ ]);
906
+
907
+ const getPrefixAndSuffixForActiveCell = (): {
908
+ prefix: string;
909
+ suffix: string;
910
+ } => {
911
+ let prefix = '';
912
+ let suffix = '';
913
+ const currentWidget = app.shell.currentWidget;
914
+ if (
915
+ !(
916
+ currentWidget instanceof NotebookPanel &&
917
+ currentWidget.content.activeCell
918
+ )
919
+ ) {
920
+ return { prefix, suffix };
921
+ }
922
+
923
+ const activeCellIndex = currentWidget.content.activeCellIndex;
924
+ const numCells = currentWidget.content.widgets.length;
925
+ const maxContext = 0.7 * MAX_TOKENS;
926
+
927
+ for (let d = 1; d < numCells; ++d) {
928
+ const above = activeCellIndex - d;
929
+ const below = activeCellIndex + d;
930
+ if (
931
+ (above < 0 && below >= numCells) ||
932
+ getTokenCount(`${prefix} ${suffix}`) >= maxContext
933
+ ) {
934
+ break;
935
+ }
936
+
937
+ if (above >= 0) {
938
+ const aboveCell = currentWidget.content.widgets[above];
939
+ const cellModel = aboveCell.model.sharedModel;
940
+
941
+ if (cellModel.cell_type === 'code') {
942
+ prefix = cellModel.source + '\n' + prefix;
943
+ } else if (cellModel.cell_type === 'markdown') {
944
+ prefix = markdownToComment(cellModel.source) + '\n' + prefix;
945
+ }
946
+ }
947
+
948
+ if (below < numCells) {
949
+ const belowCell = currentWidget.content.widgets[below];
950
+ const cellModel = belowCell.model.sharedModel;
951
+
952
+ if (cellModel.cell_type === 'code') {
953
+ suffix += cellModel.source + '\n';
954
+ } else if (cellModel.cell_type === 'markdown') {
955
+ suffix += markdownToComment(cellModel.source) + '\n';
956
+ }
957
+ }
958
+ }
959
+
960
+ return { prefix, suffix };
961
+ };
962
+
963
+ const getPrefixAndSuffixForFileEditor = (): {
964
+ prefix: string;
965
+ suffix: string;
966
+ } => {
967
+ let prefix = '';
968
+ let suffix = '';
969
+ const currentWidget = app.shell.currentWidget;
970
+ if (!(currentWidget instanceof FileEditorWidget)) {
971
+ return { prefix, suffix };
972
+ }
973
+
974
+ const fe = currentWidget as FileEditorWidget;
975
+
976
+ const cursor = fe.content.editor.getCursorPosition();
977
+ const offset = fe.content.editor.getOffsetAt(cursor);
978
+ const source = fe.content.editor.model.sharedModel.getSource();
979
+ prefix = source.substring(0, offset);
980
+ suffix = source.substring(offset);
981
+
982
+ return { prefix, suffix };
983
+ };
984
+
985
+ const generateCodeForCellOrFileEditor = () => {
986
+ const isCodeCell = isActiveCellCodeCell();
987
+ const currentWidget = app.shell.currentWidget;
988
+ let editor: CodeEditor.IEditor;
989
+ let codeInput: HTMLElement = null;
990
+ let scrollEl: HTMLElement = null;
991
+ if (isCodeCell) {
992
+ const np = currentWidget as NotebookPanel;
993
+ const activeCell = np.content.activeCell;
994
+ codeInput = activeCell.node.querySelector('.jp-InputArea-editor');
995
+ if (!codeInput) {
996
+ return;
997
+ }
998
+ scrollEl = np.node.querySelector('.jp-WindowedPanel-outer');
999
+ editor = activeCell.editor;
1000
+ } else {
1001
+ const fe = currentWidget as FileEditorWidget;
1002
+ codeInput = fe.node;
1003
+ scrollEl = fe.node.querySelector('.cm-scroller');
1004
+ editor = fe.content.editor;
1005
+ }
1006
+
1007
+ const getRectAtCursor = (): DOMRect => {
1008
+ const selection = editor.getSelection();
1009
+ const line = Math.min(selection.end.line + 1, editor.lineCount - 1);
1010
+ const coords = editor.getCoordinateForPosition({
1011
+ line,
1012
+ column: selection.end.column
1013
+ });
1014
+ const editorRect = codeInput.getBoundingClientRect();
1015
+ if (!coords) {
1016
+ return editorRect;
1017
+ }
1018
+ const yOffset = 30;
1019
+ const rect: DOMRect = new DOMRect(
1020
+ editorRect.left,
1021
+ coords.top - yOffset,
1022
+ editorRect.right - editorRect.left,
1023
+ coords.bottom - coords.top
1024
+ );
1025
+ return rect;
1026
+ };
1027
+
1028
+ const rect = getRectAtCursor();
1029
+
1030
+ const updatePopoverPosition = () => {
1031
+ if (openPopover !== null) {
1032
+ const rect = getRectAtCursor();
1033
+ openPopover.updatePosition(rect);
1034
+ }
1035
+ };
1036
+
1037
+ const inputResizeObserver = new ResizeObserver(updatePopoverPosition);
1038
+
1039
+ const addPositionListeners = () => {
1040
+ inputResizeObserver.observe(codeInput);
1041
+ if (scrollEl) {
1042
+ scrollEl.addEventListener('scroll', updatePopoverPosition);
1043
+ }
1044
+ };
1045
+
1046
+ const removePositionListeners = () => {
1047
+ inputResizeObserver.unobserve(codeInput);
1048
+ if (scrollEl) {
1049
+ scrollEl.removeEventListener('scroll', updatePopoverPosition);
1050
+ }
1051
+ };
1052
+
1053
+ const removePopover = () => {
1054
+ if (openPopover !== null) {
1055
+ removePositionListeners();
1056
+ openPopover = null;
1057
+ Widget.detach(inlinePrompt);
1058
+ }
1059
+
1060
+ if (isCodeCell) {
1061
+ codeInput?.classList.remove('generating');
1062
+ }
1063
+ };
1064
+
1065
+ let userPrompt = '';
1066
+ let existingCode = '';
1067
+ let generatedContent = '';
1068
+
1069
+ let prefix = '',
1070
+ suffix = '';
1071
+ if (isCodeCell) {
1072
+ const ps = getPrefixAndSuffixForActiveCell();
1073
+ prefix = ps.prefix;
1074
+ suffix = ps.suffix;
1075
+ } else {
1076
+ const ps = getPrefixAndSuffixForFileEditor();
1077
+ prefix = ps.prefix;
1078
+ suffix = ps.suffix;
1079
+ }
1080
+ const selection = editor.getSelection();
1081
+
1082
+ const startOffset = editor.getOffsetAt(selection.start);
1083
+ const endOffset = editor.getOffsetAt(selection.end);
1084
+ const source = editor.model.sharedModel.getSource();
1085
+
1086
+ if (isCodeCell) {
1087
+ prefix += '\n' + source.substring(0, startOffset);
1088
+ existingCode = source.substring(startOffset, endOffset);
1089
+ suffix = source.substring(endOffset) + '\n' + suffix;
1090
+ } else {
1091
+ existingCode = source.substring(startOffset, endOffset);
1092
+ }
1093
+
1094
+ const applyGeneratedCode = () => {
1095
+ generatedContent = extractLLMGeneratedCode(generatedContent);
1096
+ applyCodeToSelectionInEditor(editor, generatedContent);
1097
+ generatedContent = '';
1098
+ removePopover();
1099
+ };
1100
+
1101
+ removePopover();
1102
+
1103
+ const inlinePrompt = new InlinePromptWidget(rect, {
1104
+ prompt: userPrompt,
1105
+ existingCode,
1106
+ prefix: prefix,
1107
+ suffix: suffix,
1108
+ onRequestSubmitted: (prompt: string) => {
1109
+ userPrompt = prompt;
1110
+ generatedContent = '';
1111
+ if (existingCode !== '') {
1112
+ return;
1113
+ }
1114
+ removePopover();
1115
+ if (isCodeCell) {
1116
+ codeInput?.classList.add('generating');
1117
+ }
1118
+ },
1119
+ onRequestCancelled: () => {
1120
+ removePopover();
1121
+ editor.focus();
1122
+ },
1123
+ onContentStream: (content: string) => {
1124
+ if (existingCode !== '') {
1125
+ return;
1126
+ }
1127
+ generatedContent += content;
1128
+ },
1129
+ onContentStreamEnd: () => {
1130
+ if (existingCode !== '') {
1131
+ return;
1132
+ }
1133
+ applyGeneratedCode();
1134
+ editor.focus();
1135
+ },
1136
+ onUpdatedCodeChange: (content: string) => {
1137
+ generatedContent = content;
1138
+ },
1139
+ onUpdatedCodeAccepted: () => {
1140
+ applyGeneratedCode();
1141
+ editor.focus();
1142
+ },
1143
+ telemetryEmitter: telemetryEmitter
1144
+ });
1145
+ openPopover = inlinePrompt;
1146
+ addPositionListeners();
1147
+ Widget.attach(inlinePrompt, document.body);
1148
+
1149
+ telemetryEmitter.emitTelemetryEvent({
1150
+ type: TelemetryEventType.GenerateCodeRequest,
1151
+ data: {
1152
+ chatModel: {
1153
+ provider: NBIAPI.config.chatModel.provider,
1154
+ model: NBIAPI.config.chatModel.model
1155
+ },
1156
+ editorType: isCodeCell ? 'notebook' : 'file-editor'
1157
+ }
1158
+ });
1159
+ };
1160
+
1161
+ const generateCellCodeCommand: CommandRegistry.ICommandOptions = {
1162
+ execute: args => {
1163
+ generateCodeForCellOrFileEditor();
1164
+ },
1165
+ label: 'Generate code',
1166
+ isEnabled: () =>
1167
+ isChatEnabled() &&
1168
+ (isActiveCellCodeCell() || isCurrentWidgetFileEditor())
1169
+ };
1170
+ app.commands.addCommand(
1171
+ CommandIDs.editorGenerateCode,
1172
+ generateCellCodeCommand
1173
+ );
1174
+
1175
+ const copilotMenuCommands = new CommandRegistry();
1176
+ copilotMenuCommands.addCommand(
1177
+ CommandIDs.editorGenerateCode,
1178
+ generateCellCodeCommand
1179
+ );
1180
+ copilotMenuCommands.addCommand(CommandIDs.editorExplainThisCode, {
1181
+ execute: () => {
1182
+ const np = app.shell.currentWidget as NotebookPanel;
1183
+ const activeCell = np.content.activeCell;
1184
+ const content = activeCell?.model.sharedModel.source || '';
1185
+ document.dispatchEvent(
1186
+ new CustomEvent('copilotSidebar:runPrompt', {
1187
+ detail: {
1188
+ type: RunChatCompletionType.ExplainThis,
1189
+ content,
1190
+ language: ActiveDocumentWatcher.activeDocumentInfo.language,
1191
+ filename: ActiveDocumentWatcher.activeDocumentInfo.filename
1192
+ }
1193
+ })
1194
+ );
1195
+
1196
+ app.commands.execute('tabsmenu:activate-by-id', { id: panel.id });
1197
+
1198
+ telemetryEmitter.emitTelemetryEvent({
1199
+ type: TelemetryEventType.ExplainThisRequest,
1200
+ data: {
1201
+ chatModel: {
1202
+ provider: NBIAPI.config.chatModel.provider,
1203
+ model: NBIAPI.config.chatModel.model
1204
+ }
1205
+ }
1206
+ });
1207
+ },
1208
+ label: 'Explain code',
1209
+ isEnabled: () => isChatEnabled() && isActiveCellCodeCell()
1210
+ });
1211
+ copilotMenuCommands.addCommand(CommandIDs.editorFixThisCode, {
1212
+ execute: () => {
1213
+ const np = app.shell.currentWidget as NotebookPanel;
1214
+ const activeCell = np.content.activeCell;
1215
+ const content = activeCell?.model.sharedModel.source || '';
1216
+ document.dispatchEvent(
1217
+ new CustomEvent('copilotSidebar:runPrompt', {
1218
+ detail: {
1219
+ type: RunChatCompletionType.FixThis,
1220
+ content,
1221
+ language: ActiveDocumentWatcher.activeDocumentInfo.language,
1222
+ filename: ActiveDocumentWatcher.activeDocumentInfo.filename
1223
+ }
1224
+ })
1225
+ );
1226
+
1227
+ app.commands.execute('tabsmenu:activate-by-id', { id: panel.id });
1228
+
1229
+ telemetryEmitter.emitTelemetryEvent({
1230
+ type: TelemetryEventType.FixThisCodeRequest,
1231
+ data: {
1232
+ chatModel: {
1233
+ provider: NBIAPI.config.chatModel.provider,
1234
+ model: NBIAPI.config.chatModel.model
1235
+ }
1236
+ }
1237
+ });
1238
+ },
1239
+ label: 'Fix code',
1240
+ isEnabled: () => isChatEnabled() && isActiveCellCodeCell()
1241
+ });
1242
+ copilotMenuCommands.addCommand(CommandIDs.editorExplainThisOutput, {
1243
+ execute: () => {
1244
+ const np = app.shell.currentWidget as NotebookPanel;
1245
+ const activeCell = np.content.activeCell;
1246
+ if (!(activeCell instanceof CodeCell)) {
1247
+ return;
1248
+ }
1249
+ const content = cellOutputAsText(activeCell as CodeCell);
1250
+ document.dispatchEvent(
1251
+ new CustomEvent('copilotSidebar:runPrompt', {
1252
+ detail: {
1253
+ type: RunChatCompletionType.ExplainThisOutput,
1254
+ content,
1255
+ language: ActiveDocumentWatcher.activeDocumentInfo.language,
1256
+ filename: ActiveDocumentWatcher.activeDocumentInfo.filename
1257
+ }
1258
+ })
1259
+ );
1260
+
1261
+ app.commands.execute('tabsmenu:activate-by-id', { id: panel.id });
1262
+
1263
+ telemetryEmitter.emitTelemetryEvent({
1264
+ type: TelemetryEventType.ExplainThisOutputRequest,
1265
+ data: {
1266
+ chatModel: {
1267
+ provider: NBIAPI.config.chatModel.provider,
1268
+ model: NBIAPI.config.chatModel.model
1269
+ }
1270
+ }
1271
+ });
1272
+ },
1273
+ label: 'Explain output',
1274
+ isEnabled: () => {
1275
+ if (
1276
+ !(isChatEnabled() && app.shell.currentWidget instanceof NotebookPanel)
1277
+ ) {
1278
+ return false;
1279
+ }
1280
+ const np = app.shell.currentWidget as NotebookPanel;
1281
+ const activeCell = np.content.activeCell;
1282
+ if (!(activeCell instanceof CodeCell)) {
1283
+ return false;
1284
+ }
1285
+ const outputs = activeCell.outputArea.model.toJSON();
1286
+ return Array.isArray(outputs) && outputs.length > 0;
1287
+ }
1288
+ });
1289
+ copilotMenuCommands.addCommand(CommandIDs.editorTroubleshootThisOutput, {
1290
+ execute: () => {
1291
+ const np = app.shell.currentWidget as NotebookPanel;
1292
+ const activeCell = np.content.activeCell;
1293
+ if (!(activeCell instanceof CodeCell)) {
1294
+ return;
1295
+ }
1296
+ const content = cellOutputAsText(activeCell as CodeCell);
1297
+ document.dispatchEvent(
1298
+ new CustomEvent('copilotSidebar:runPrompt', {
1299
+ detail: {
1300
+ type: RunChatCompletionType.TroubleshootThisOutput,
1301
+ content,
1302
+ language: ActiveDocumentWatcher.activeDocumentInfo.language,
1303
+ filename: ActiveDocumentWatcher.activeDocumentInfo.filename
1304
+ }
1305
+ })
1306
+ );
1307
+
1308
+ app.commands.execute('tabsmenu:activate-by-id', { id: panel.id });
1309
+
1310
+ telemetryEmitter.emitTelemetryEvent({
1311
+ type: TelemetryEventType.TroubleshootThisOutputRequest,
1312
+ data: {
1313
+ chatModel: {
1314
+ provider: NBIAPI.config.chatModel.provider,
1315
+ model: NBIAPI.config.chatModel.model
1316
+ }
1317
+ }
1318
+ });
1319
+ },
1320
+ label: 'Troubleshoot errors in output',
1321
+ isEnabled: () => {
1322
+ if (
1323
+ !(isChatEnabled() && app.shell.currentWidget instanceof NotebookPanel)
1324
+ ) {
1325
+ return false;
1326
+ }
1327
+ const np = app.shell.currentWidget as NotebookPanel;
1328
+ const activeCell = np.content.activeCell;
1329
+ if (!(activeCell instanceof CodeCell)) {
1330
+ return false;
1331
+ }
1332
+ const outputs = activeCell.outputArea.model.toJSON();
1333
+ return (
1334
+ Array.isArray(outputs) &&
1335
+ outputs.length > 0 &&
1336
+ outputs.some(output => output.output_type === 'error')
1337
+ );
1338
+ }
1339
+ });
1340
+
1341
+ const copilotContextMenu = new Menu({ commands: copilotMenuCommands });
1342
+ copilotContextMenu.id = 'notebook-intelligence:editor-context-menu';
1343
+ copilotContextMenu.title.label = 'Copilot';
1344
+ copilotContextMenu.title.icon = sidebarIcon;
1345
+ copilotContextMenu.addItem({ command: CommandIDs.editorGenerateCode });
1346
+ copilotContextMenu.addItem({ command: CommandIDs.editorExplainThisCode });
1347
+ copilotContextMenu.addItem({ command: CommandIDs.editorFixThisCode });
1348
+ copilotContextMenu.addItem({ command: CommandIDs.editorExplainThisOutput });
1349
+ copilotContextMenu.addItem({
1350
+ command: CommandIDs.editorTroubleshootThisOutput
1351
+ });
1352
+
1353
+ app.contextMenu.addItem({
1354
+ type: 'submenu',
1355
+ submenu: copilotContextMenu,
1356
+ selector: '.jp-Editor',
1357
+ rank: 1
1358
+ });
1359
+
1360
+ app.contextMenu.addItem({
1361
+ type: 'submenu',
1362
+ submenu: copilotContextMenu,
1363
+ selector: '.jp-OutputArea-child',
1364
+ rank: 1
1365
+ });
1366
+
1367
+ if (statusBar) {
1368
+ const githubCopilotStatusBarItem = new GitHubCopilotStatusBarItem({
1369
+ getApp: () => app
1370
+ });
1371
+
1372
+ statusBar.registerStatusItem(
1373
+ 'notebook-intelligence:github-copilot-status',
1374
+ {
1375
+ item: githubCopilotStatusBarItem,
1376
+ align: 'right',
1377
+ rank: 100,
1378
+ isActive: () => NBIAPI.config.usingGitHubCopilotModel
1379
+ }
1380
+ );
1381
+
1382
+ NBIAPI.configChanged.connect(() => {
1383
+ if (NBIAPI.config.usingGitHubCopilotModel) {
1384
+ githubCopilotStatusBarItem.show();
1385
+ } else {
1386
+ githubCopilotStatusBarItem.hide();
1387
+ }
1388
+ });
1389
+ }
1390
+
1391
+ const jlabApp = app as JupyterLab;
1392
+ ActiveDocumentWatcher.initialize(jlabApp, languageRegistry);
1393
+
1394
+ return extensionService;
1395
+ }
1396
+ };
1397
+
1398
+ export default plugin;