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