@plmbr/notebook-intelligence 5.0.0

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.
Files changed (137) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +412 -0
  3. package/lib/api.d.ts +288 -0
  4. package/lib/api.js +927 -0
  5. package/lib/cell-output-bundle.d.ts +25 -0
  6. package/lib/cell-output-bundle.js +129 -0
  7. package/lib/cell-output-toolbar.d.ts +26 -0
  8. package/lib/cell-output-toolbar.js +188 -0
  9. package/lib/chat-progress-feedback.d.ts +3 -0
  10. package/lib/chat-progress-feedback.js +27 -0
  11. package/lib/chat-sidebar.d.ts +92 -0
  12. package/lib/chat-sidebar.js +3452 -0
  13. package/lib/command-ids.d.ts +39 -0
  14. package/lib/command-ids.js +44 -0
  15. package/lib/components/ask-user-question.d.ts +2 -0
  16. package/lib/components/ask-user-question.js +85 -0
  17. package/lib/components/checkbox.d.ts +2 -0
  18. package/lib/components/checkbox.js +30 -0
  19. package/lib/components/claude-mcp-panel.d.ts +2 -0
  20. package/lib/components/claude-mcp-panel.js +275 -0
  21. package/lib/components/claude-mcp-paste.d.ts +7 -0
  22. package/lib/components/claude-mcp-paste.js +104 -0
  23. package/lib/components/claude-session-picker.d.ts +8 -0
  24. package/lib/components/claude-session-picker.js +127 -0
  25. package/lib/components/form-dialog.d.ts +25 -0
  26. package/lib/components/form-dialog.js +35 -0
  27. package/lib/components/launcher-picker.d.ts +6 -0
  28. package/lib/components/launcher-picker.js +135 -0
  29. package/lib/components/mcp-util.d.ts +2 -0
  30. package/lib/components/mcp-util.js +37 -0
  31. package/lib/components/notebook-generation-popover.d.ts +7 -0
  32. package/lib/components/notebook-generation-popover.js +60 -0
  33. package/lib/components/pill.d.ts +2 -0
  34. package/lib/components/pill.js +5 -0
  35. package/lib/components/plugins-panel.d.ts +3 -0
  36. package/lib/components/plugins-panel.js +466 -0
  37. package/lib/components/settings-panel.d.ts +11 -0
  38. package/lib/components/settings-panel.js +742 -0
  39. package/lib/components/skills-panel.d.ts +2 -0
  40. package/lib/components/skills-panel.js +1264 -0
  41. package/lib/handler.d.ts +8 -0
  42. package/lib/handler.js +36 -0
  43. package/lib/icons.d.ts +45 -0
  44. package/lib/icons.js +54 -0
  45. package/lib/index.d.ts +8 -0
  46. package/lib/index.js +2079 -0
  47. package/lib/markdown-renderer.d.ts +10 -0
  48. package/lib/markdown-renderer.js +64 -0
  49. package/lib/notebook-generation-toolbar.d.ts +16 -0
  50. package/lib/notebook-generation-toolbar.js +197 -0
  51. package/lib/notebook-generation.d.ts +8 -0
  52. package/lib/notebook-generation.js +12 -0
  53. package/lib/open-file-refresh-watcher-env.d.ts +4 -0
  54. package/lib/open-file-refresh-watcher-env.js +33 -0
  55. package/lib/open-file-refresh-watcher.d.ts +97 -0
  56. package/lib/open-file-refresh-watcher.js +190 -0
  57. package/lib/shell-utils.d.ts +6 -0
  58. package/lib/shell-utils.js +9 -0
  59. package/lib/task-target-notebook.d.ts +2 -0
  60. package/lib/task-target-notebook.js +28 -0
  61. package/lib/terminal-drag-format.d.ts +9 -0
  62. package/lib/terminal-drag-format.js +23 -0
  63. package/lib/terminal-drag.d.ts +12 -0
  64. package/lib/terminal-drag.js +268 -0
  65. package/lib/tokens.d.ts +149 -0
  66. package/lib/tokens.js +88 -0
  67. package/lib/tour/tour-anchors.d.ts +18 -0
  68. package/lib/tour/tour-anchors.js +18 -0
  69. package/lib/tour/tour-config.d.ts +66 -0
  70. package/lib/tour/tour-config.js +99 -0
  71. package/lib/tour/tour-defaults.json +58 -0
  72. package/lib/tour/tour-events.d.ts +19 -0
  73. package/lib/tour/tour-events.js +30 -0
  74. package/lib/tour/tour-overlay.d.ts +6 -0
  75. package/lib/tour/tour-overlay.js +350 -0
  76. package/lib/tour/tour-state.d.ts +20 -0
  77. package/lib/tour/tour-state.js +81 -0
  78. package/lib/tour/tour-steps.d.ts +33 -0
  79. package/lib/tour/tour-steps.js +216 -0
  80. package/lib/utils.d.ts +53 -0
  81. package/lib/utils.js +385 -0
  82. package/package.json +258 -0
  83. package/schema/plugin.json +42 -0
  84. package/src/api.ts +1424 -0
  85. package/src/cell-output-bundle.ts +176 -0
  86. package/src/cell-output-toolbar.ts +232 -0
  87. package/src/chat-progress-feedback.ts +35 -0
  88. package/src/chat-sidebar.tsx +5147 -0
  89. package/src/command-ids.ts +67 -0
  90. package/src/components/ask-user-question.tsx +151 -0
  91. package/src/components/checkbox.tsx +62 -0
  92. package/src/components/claude-mcp-panel.tsx +543 -0
  93. package/src/components/claude-mcp-paste.ts +132 -0
  94. package/src/components/claude-session-picker.tsx +214 -0
  95. package/src/components/form-dialog.tsx +75 -0
  96. package/src/components/launcher-picker.tsx +237 -0
  97. package/src/components/mcp-util.ts +53 -0
  98. package/src/components/notebook-generation-popover.tsx +127 -0
  99. package/src/components/pill.tsx +15 -0
  100. package/src/components/plugins-panel.tsx +774 -0
  101. package/src/components/settings-panel.tsx +1631 -0
  102. package/src/components/skills-panel.tsx +2084 -0
  103. package/src/handler.ts +51 -0
  104. package/src/icons.ts +71 -0
  105. package/src/index.ts +2583 -0
  106. package/src/markdown-renderer.tsx +153 -0
  107. package/src/notebook-generation-toolbar.tsx +281 -0
  108. package/src/notebook-generation.ts +23 -0
  109. package/src/open-file-refresh-watcher-env.ts +52 -0
  110. package/src/open-file-refresh-watcher.ts +260 -0
  111. package/src/shell-utils.ts +10 -0
  112. package/src/svg.d.ts +4 -0
  113. package/src/task-target-notebook.ts +37 -0
  114. package/src/terminal-drag-format.ts +29 -0
  115. package/src/terminal-drag.ts +382 -0
  116. package/src/tokens.ts +171 -0
  117. package/src/tour/tour-anchors.ts +21 -0
  118. package/src/tour/tour-config.ts +160 -0
  119. package/src/tour/tour-events.ts +34 -0
  120. package/src/tour/tour-overlay.tsx +474 -0
  121. package/src/tour/tour-state.ts +87 -0
  122. package/src/tour/tour-steps.ts +281 -0
  123. package/src/utils.ts +455 -0
  124. package/style/base.css +3238 -0
  125. package/style/icons/cell-toolbar-bug.svg +5 -0
  126. package/style/icons/cell-toolbar-chat.svg +5 -0
  127. package/style/icons/cell-toolbar-sparkle.svg +5 -0
  128. package/style/icons/claude.svg +1 -0
  129. package/style/icons/copilot-warning.svg +1 -0
  130. package/style/icons/copilot.svg +1 -0
  131. package/style/icons/copy.svg +1 -0
  132. package/style/icons/openai.svg +1 -0
  133. package/style/icons/opencode.svg +1 -0
  134. package/style/icons/sparkles-warning.svg +5 -0
  135. package/style/icons/sparkles.svg +1 -0
  136. package/style/index.css +1 -0
  137. package/style/index.js +1 -0
@@ -0,0 +1,153 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+
3
+ import React from 'react';
4
+ import Markdown from 'react-markdown';
5
+ import remarkGfm from 'remark-gfm';
6
+ import { Prism as SyntaxHighlighterBase } from 'react-syntax-highlighter';
7
+ const SyntaxHighlighter =
8
+ SyntaxHighlighterBase as unknown as React.ComponentType<any>;
9
+ import {
10
+ oneLight,
11
+ oneDark
12
+ } from 'react-syntax-highlighter/dist/cjs/styles/prism';
13
+ import { VscNewFile, VscInsert, VscCopy, VscNotebook, VscAdd } from './icons';
14
+ import { JupyterFrontEnd } from '@jupyterlab/application';
15
+ import { isDarkTheme, writeTextToClipboard } from './utils';
16
+ import { IActiveDocumentInfo } from './tokens';
17
+
18
+ type MarkdownRendererProps = {
19
+ children: string;
20
+ getApp: () => JupyterFrontEnd;
21
+ getActiveDocumentInfo(): IActiveDocumentInfo;
22
+ };
23
+
24
+ export function MarkdownRenderer({
25
+ children: markdown,
26
+ getApp,
27
+ getActiveDocumentInfo
28
+ }: MarkdownRendererProps) {
29
+ const app = getApp();
30
+ const activeDocumentInfo = getActiveDocumentInfo();
31
+ const isNotebook = activeDocumentInfo.filename.endsWith('.ipynb');
32
+
33
+ return (
34
+ <Markdown
35
+ remarkPlugins={[remarkGfm]}
36
+ components={{
37
+ code({ node, inline, className, children, getApp, ...props }: any) {
38
+ const match = /language-(\w+)/.exec(className || '');
39
+ const codeString = String(children).replace(/\n$/, '');
40
+ const language = match ? match[1] : 'text';
41
+
42
+ const handleCopyClick = () => {
43
+ void writeTextToClipboard(codeString);
44
+ };
45
+
46
+ const handleInsertAtCursorClick = () => {
47
+ app.commands.execute('notebook-intelligence:insert-at-cursor', {
48
+ language,
49
+ code: codeString
50
+ });
51
+ };
52
+
53
+ const handleAddCodeAsNewCell = () => {
54
+ app.commands.execute('notebook-intelligence:add-code-as-new-cell', {
55
+ language,
56
+ code: codeString
57
+ });
58
+ };
59
+
60
+ const handleCreateNewFileClick = () => {
61
+ app.commands.execute('notebook-intelligence:create-new-file', {
62
+ language,
63
+ code: codeString
64
+ });
65
+ };
66
+
67
+ const handleCreateNewNotebookClick = () => {
68
+ app.commands.execute(
69
+ 'notebook-intelligence:create-new-notebook-from-py',
70
+ { language, code: codeString }
71
+ );
72
+ };
73
+
74
+ if (inline || !match) {
75
+ return (
76
+ <code className={className} {...props}>
77
+ {children}
78
+ </code>
79
+ );
80
+ }
81
+ return (
82
+ <div>
83
+ <div className="code-block-header">
84
+ <div className="code-block-header-language">
85
+ <span>{language}</span>
86
+ </div>
87
+ <button
88
+ type="button"
89
+ className="code-block-header-button"
90
+ onClick={() => handleCopyClick()}
91
+ aria-label="Copy code to clipboard"
92
+ >
93
+ <VscCopy size={16} aria-hidden="true" />
94
+ <span>Copy</span>
95
+ </button>
96
+ <button
97
+ type="button"
98
+ className="code-block-header-button"
99
+ onClick={() => handleInsertAtCursorClick()}
100
+ aria-label="Insert code at cursor"
101
+ title="Insert at cursor"
102
+ >
103
+ <VscInsert size={16} aria-hidden="true" />
104
+ </button>
105
+ {isNotebook && (
106
+ <button
107
+ type="button"
108
+ className="code-block-header-button"
109
+ onClick={() => handleAddCodeAsNewCell()}
110
+ aria-label="Add code as new cell"
111
+ title="Add as new cell"
112
+ >
113
+ <VscAdd size={16} aria-hidden="true" />
114
+ </button>
115
+ )}
116
+ <button
117
+ type="button"
118
+ className="code-block-header-button"
119
+ onClick={() => handleCreateNewFileClick()}
120
+ aria-label="Create new file from code"
121
+ title="New file"
122
+ >
123
+ <VscNewFile size={16} aria-hidden="true" />
124
+ </button>
125
+ {language === 'python' && (
126
+ <button
127
+ type="button"
128
+ className="code-block-header-button"
129
+ onClick={() => handleCreateNewNotebookClick()}
130
+ aria-label="Create new notebook from code"
131
+ title="New notebook"
132
+ >
133
+ <VscNotebook size={16} aria-hidden="true" />
134
+ </button>
135
+ )}
136
+ </div>
137
+ <SyntaxHighlighter
138
+ style={isDarkTheme() ? oneDark : oneLight}
139
+ PreTag="div"
140
+ language={language}
141
+ {...props}
142
+ >
143
+ {codeString}
144
+ </SyntaxHighlighter>
145
+ </div>
146
+ );
147
+ }
148
+ }}
149
+ >
150
+ {markdown}
151
+ </Markdown>
152
+ );
153
+ }
@@ -0,0 +1,281 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+
3
+ import { JupyterFrontEnd } from '@jupyterlab/application';
4
+ import { ReactWidget } from '@jupyterlab/apputils';
5
+ import { DocumentRegistry } from '@jupyterlab/docregistry';
6
+ import { INotebookModel, NotebookPanel } from '@jupyterlab/notebook';
7
+ import { LabIcon, ToolbarButton } from '@jupyterlab/ui-components';
8
+ import { IDisposable, DisposableDelegate } from '@lumino/disposable';
9
+ import { Widget } from '@lumino/widgets';
10
+ import { UUID } from '@lumino/coreutils';
11
+ import React from 'react';
12
+
13
+ import {
14
+ IRunChatCompletionRequest,
15
+ RunChatCompletionType
16
+ } from './chat-sidebar';
17
+ import {
18
+ buildNotebookGenerationPrompt,
19
+ INotebookGenerationProgressDetail,
20
+ NOTEBOOK_GENERATION_PROGRESS_EVENT
21
+ } from './notebook-generation';
22
+ import { NotebookGenerationPopover } from './components/notebook-generation-popover';
23
+
24
+ const TOOLBAR_BUTTON_NAME = 'nbi-generate-notebook';
25
+ const TOOLBAR_STATUS_NAME = 'nbi-generate-notebook-status';
26
+
27
+ interface INotebookGenerationToolbarOptions {
28
+ app: JupyterFrontEnd;
29
+ icon: LabIcon;
30
+ chatSidebarId: string;
31
+ }
32
+
33
+ interface INotebookGenerationPopoverWidgetOptions {
34
+ initialShowInChat: boolean;
35
+ onSubmit: (prompt: string, showInChat: boolean) => void;
36
+ onClose: () => void;
37
+ }
38
+
39
+ class NotebookGenerationPopoverWidget extends ReactWidget {
40
+ constructor(options: INotebookGenerationPopoverWidgetOptions) {
41
+ super();
42
+ this.addClass('nbi-notebook-generation-popover-host');
43
+ this._options = options;
44
+ }
45
+
46
+ protected onAfterAttach(): void {
47
+ document.addEventListener('mousedown', this._onDocumentMouseDown, true);
48
+ }
49
+
50
+ protected onBeforeDetach(): void {
51
+ document.removeEventListener('mousedown', this._onDocumentMouseDown, true);
52
+ }
53
+
54
+ private _onDocumentMouseDown = (event: MouseEvent): void => {
55
+ const target = event.target as Node | null;
56
+ if (target && this.node.contains(target)) {
57
+ return;
58
+ }
59
+ this._options.onClose();
60
+ };
61
+
62
+ positionAt(rect: DOMRect): void {
63
+ const popoverWidth = 360;
64
+ const margin = 8;
65
+ let left = rect.left;
66
+ if (left + popoverWidth + margin > window.innerWidth) {
67
+ left = Math.max(margin, window.innerWidth - popoverWidth - margin);
68
+ }
69
+ const top = rect.bottom + 4;
70
+ this.node.style.position = 'fixed';
71
+ this.node.style.left = `${left}px`;
72
+ this.node.style.top = `${top}px`;
73
+ this.node.style.width = `${popoverWidth}px`;
74
+ this.node.style.zIndex = '10000';
75
+ }
76
+
77
+ render(): JSX.Element {
78
+ return (
79
+ <NotebookGenerationPopover
80
+ initialShowInChat={this._options.initialShowInChat}
81
+ onSubmit={this._options.onSubmit}
82
+ onClose={this._options.onClose}
83
+ />
84
+ );
85
+ }
86
+
87
+ private _options: INotebookGenerationPopoverWidgetOptions;
88
+ }
89
+
90
+ class NotebookGenerationToolbarController {
91
+ constructor(
92
+ options: INotebookGenerationToolbarOptions,
93
+ panel: NotebookPanel
94
+ ) {
95
+ this._app = options.app;
96
+ this._chatSidebarId = options.chatSidebarId;
97
+ this._panel = panel;
98
+ }
99
+
100
+ openPopover(button: ToolbarButton): void {
101
+ if (this._popover) {
102
+ this.closePopover();
103
+ return;
104
+ }
105
+ const buttonRect = button.node.getBoundingClientRect();
106
+ this._popover = new NotebookGenerationPopoverWidget({
107
+ initialShowInChat: NotebookGenerationToolbarController._showInChat,
108
+ onSubmit: (prompt, showInChat) => {
109
+ NotebookGenerationToolbarController._showInChat = showInChat;
110
+ this._submitPrompt(prompt, showInChat);
111
+ this.closePopover();
112
+ },
113
+ onClose: () => this.closePopover()
114
+ });
115
+ Widget.attach(this._popover, document.body);
116
+ // ReactWidget renders on update-request; Widget.attach doesn't queue one.
117
+ this._popover.update();
118
+ this._popover.positionAt(buttonRect);
119
+ }
120
+
121
+ closePopover(): void {
122
+ if (!this._popover) {
123
+ return;
124
+ }
125
+ this._popover.dispose();
126
+ this._popover = null;
127
+ }
128
+
129
+ dispose(): void {
130
+ this.closePopover();
131
+ if (this._activeProgressRequestId) {
132
+ document.removeEventListener(
133
+ NOTEBOOK_GENERATION_PROGRESS_EVENT,
134
+ this._onProgress
135
+ );
136
+ this._activeProgressRequestId = null;
137
+ }
138
+ if (this._statusHideTimer !== null) {
139
+ clearTimeout(this._statusHideTimer);
140
+ this._statusHideTimer = null;
141
+ }
142
+ this._setStatus(null);
143
+ }
144
+
145
+ private _submitPrompt(rawPrompt: string, showInChat: boolean): void {
146
+ const prefixedPrompt = buildNotebookGenerationPrompt(rawPrompt);
147
+ const externalRequestId = UUID.uuid4();
148
+ // chatMode and toolSelections are forced by the chat-sidebar handler
149
+ // (NotebookGeneration always needs agent mode + the notebook-edit
150
+ // toolset). Leaving them off the request keeps the toolbar
151
+ // independent of the sidebar's current configuration.
152
+ const request: Partial<IRunChatCompletionRequest> = {
153
+ type: RunChatCompletionType.NotebookGeneration,
154
+ content: prefixedPrompt,
155
+ externalRequestId,
156
+ hideInChat: !showInChat
157
+ };
158
+
159
+ if (!showInChat) {
160
+ this._setStatus('Generating notebook…');
161
+ this._activeProgressRequestId = externalRequestId;
162
+ document.addEventListener(
163
+ NOTEBOOK_GENERATION_PROGRESS_EVENT,
164
+ this._onProgress
165
+ );
166
+ }
167
+
168
+ document.dispatchEvent(
169
+ new CustomEvent('copilotSidebar:runPrompt', { detail: request })
170
+ );
171
+
172
+ if (showInChat) {
173
+ this._app.commands.execute('tabsmenu:activate-by-id', {
174
+ id: this._chatSidebarId
175
+ });
176
+ }
177
+ }
178
+
179
+ private _onProgress = (event: Event): void => {
180
+ const detail = (event as CustomEvent<INotebookGenerationProgressDetail>)
181
+ .detail;
182
+ if (!detail || detail.requestId !== this._activeProgressRequestId) {
183
+ return;
184
+ }
185
+ if (!detail.inProgress) {
186
+ document.removeEventListener(
187
+ NOTEBOOK_GENERATION_PROGRESS_EVENT,
188
+ this._onProgress
189
+ );
190
+ this._activeProgressRequestId = null;
191
+ if (detail.error) {
192
+ this._setStatus(`Generation failed: ${detail.error}`);
193
+ this._scheduleStatusHide(4000);
194
+ } else {
195
+ this._setStatus('Notebook generation complete');
196
+ this._scheduleStatusHide(2500);
197
+ }
198
+ }
199
+ };
200
+
201
+ private _scheduleStatusHide(delayMs: number): void {
202
+ if (this._statusHideTimer !== null) {
203
+ clearTimeout(this._statusHideTimer);
204
+ }
205
+ this._statusHideTimer = setTimeout(() => {
206
+ this._statusHideTimer = null;
207
+ this._setStatus(null);
208
+ }, delayMs);
209
+ }
210
+
211
+ private _setStatus(message: string | null): void {
212
+ if (this._panel.isDisposed) {
213
+ return;
214
+ }
215
+ if (!message) {
216
+ if (this._statusWidget) {
217
+ this._statusWidget.dispose();
218
+ this._statusWidget = null;
219
+ }
220
+ return;
221
+ }
222
+ if (!this._statusWidget) {
223
+ const widget = new Widget();
224
+ widget.addClass('nbi-notebook-generation-status');
225
+ this._panel.toolbar.insertAfter(
226
+ TOOLBAR_BUTTON_NAME,
227
+ TOOLBAR_STATUS_NAME,
228
+ widget
229
+ );
230
+ this._statusWidget = widget;
231
+ }
232
+ this._statusWidget.node.textContent = message;
233
+ }
234
+
235
+ // Defaults ON; remembers the user's last choice for the rest of the tab.
236
+ private static _showInChat = true;
237
+
238
+ private _app: JupyterFrontEnd;
239
+ private _chatSidebarId: string;
240
+ private _panel: NotebookPanel;
241
+ private _popover: NotebookGenerationPopoverWidget | null = null;
242
+ private _activeProgressRequestId: string | null = null;
243
+ private _statusWidget: Widget | null = null;
244
+ private _statusHideTimer: ReturnType<typeof setTimeout> | null = null;
245
+ }
246
+
247
+ export class NotebookGenerationToolbarExtension
248
+ implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel>
249
+ {
250
+ constructor(options: INotebookGenerationToolbarOptions) {
251
+ this._options = options;
252
+ }
253
+
254
+ createNew(
255
+ panel: NotebookPanel,
256
+ _context: DocumentRegistry.IContext<INotebookModel>
257
+ ): IDisposable {
258
+ const controller = new NotebookGenerationToolbarController(
259
+ this._options,
260
+ panel
261
+ );
262
+ const button: ToolbarButton = new ToolbarButton({
263
+ icon: this._options.icon,
264
+ onClick: () => controller.openPopover(button),
265
+ // Notebook generation works in any chat mode — the chat-sidebar
266
+ // handler forces agent mode and the notebook-edit toolset when
267
+ // routing this request (issue #229). The button used to be gated
268
+ // on isInClaudeCodeMode here, but that gate undermined the
269
+ // sidebar-side fix.
270
+ tooltip: 'Update active notebook with AI'
271
+ });
272
+ button.addClass('nbi-notebook-generation-toolbar-button');
273
+ panel.toolbar.insertAfter('cellType', TOOLBAR_BUTTON_NAME, button);
274
+ return new DisposableDelegate(() => {
275
+ controller.dispose();
276
+ button.dispose();
277
+ });
278
+ }
279
+
280
+ private _options: INotebookGenerationToolbarOptions;
281
+ }
@@ -0,0 +1,23 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+ //
3
+ // Pure helpers and event constants used by the notebook-generation toolbar
4
+ // button. Keeping these in their own module (free of JupyterLab/React
5
+ // dependencies) makes them easy to unit-test under jsdom without bringing
6
+ // in the chat sidebar's heavyweight ESM imports.
7
+
8
+ export const NOTEBOOK_GENERATION_PROMPT_PREFIX =
9
+ "Update active notebook based on the user request. Don't create a new notebook, always update the active one. User request: ";
10
+
11
+ export const NOTEBOOK_GENERATION_PROGRESS_EVENT =
12
+ 'copilotSidebar:notebookGenerationProgress';
13
+
14
+ export interface INotebookGenerationProgressDetail {
15
+ requestId: string;
16
+ inProgress: boolean;
17
+ error?: string;
18
+ }
19
+
20
+ export function buildNotebookGenerationPrompt(rawPrompt: string): string {
21
+ const trimmed = (rawPrompt || '').trim();
22
+ return `${NOTEBOOK_GENERATION_PROMPT_PREFIX}${trimmed}`;
23
+ }
@@ -0,0 +1,52 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+
3
+ // Live binding for the open-file refresh watcher. Lives in its own
4
+ // file so unit tests can exercise the pure logic in
5
+ // `open-file-refresh-watcher.ts` without transitively importing
6
+ // `@jupyterlab/docregistry`, which ships ESM that ts-jest's default
7
+ // transform can't parse.
8
+
9
+ import { JupyterFrontEnd } from '@jupyterlab/application';
10
+ import { DocumentWidget } from '@jupyterlab/docregistry';
11
+ import { Contents } from '@jupyterlab/services';
12
+
13
+ import {
14
+ IRefreshWatcherEnv,
15
+ WATCHED_SHELL_AREAS
16
+ } from './open-file-refresh-watcher';
17
+
18
+ export function buildRefreshWatcherEnv(
19
+ app: JupyterFrontEnd,
20
+ contents: Contents.IManager
21
+ ): IRefreshWatcherEnv {
22
+ return {
23
+ iterDocumentWidgets: function* () {
24
+ for (const area of WATCHED_SHELL_AREAS) {
25
+ // Defensive try/catch: LabShell.widgets() throws for areas it
26
+ // doesn't implement. We've audited the current set against
27
+ // the runtime, but a future JL bump could rename or remove
28
+ // an area; surfacing it as a console warning rather than an
29
+ // unhandled rejection keeps the watcher running for the
30
+ // areas that DO work.
31
+ let widgets: Iterable<unknown>;
32
+ try {
33
+ widgets = app.shell.widgets(area);
34
+ } catch (err) {
35
+ console.warn(
36
+ `[NBI] open-file-refresh-watcher: skipping shell area "${area}":`,
37
+ err
38
+ );
39
+ continue;
40
+ }
41
+ for (const widget of widgets) {
42
+ if (widget instanceof DocumentWidget) {
43
+ yield widget;
44
+ }
45
+ }
46
+ }
47
+ },
48
+ fetchDiskModel: path => contents.get(path, { content: false }),
49
+ setInterval: (handler, ms) => window.setInterval(handler, ms),
50
+ clearInterval: handle => window.clearInterval(handle as number)
51
+ };
52
+ }