@notebook-intelligence/notebook-intelligence 2.6.1 → 3.1.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.
- package/README.md +24 -8
- package/lib/api.d.ts +1 -1
- package/lib/api.js +2 -1
- package/lib/chat-sidebar.d.ts +2 -0
- package/lib/chat-sidebar.js +97 -23
- package/lib/components/checkbox.js +1 -1
- package/lib/components/settings-panel.js +9 -3
- package/lib/index.js +37 -3
- package/lib/tokens.d.ts +5 -1
- package/lib/tokens.js +4 -0
- package/package.json +1 -1
- package/src/api.ts +2 -0
- package/src/chat-sidebar.tsx +112 -22
- package/src/components/checkbox.tsx +1 -1
- package/src/components/settings-panel.tsx +45 -17
- package/src/index.ts +46 -4
- package/src/tokens.ts +5 -1
- package/style/base.css +22 -1
package/README.md
CHANGED
|
@@ -75,21 +75,37 @@ To let Notebook Intelligence remember your GitHub access token, go to Notebook I
|
|
|
75
75
|
|
|
76
76
|
If your stored access token fails to login (due to expiration or other reasons), you will be prompted to relogin on the UI.
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
## Built-in Tools
|
|
79
79
|
|
|
80
|
-
Notebook
|
|
80
|
+
- **Notebook Edit** (nbi-notebook-edit): Edit notebook using the JupyterLab notebook editor.
|
|
81
|
+
- **Notebook Execute** (nbi-notebook-execute): Run notebooks in JupyterLab UI.
|
|
82
|
+
- **Python File Edit** (nbi-python-file-edit): Edit Python files using the JupyterLab file editor.
|
|
83
|
+
- **File Edit** (nbi-file-edit): Edit files in the Jupyter root directory.
|
|
84
|
+
- **File Read** (nbi-file-read): Read files in the Jupyter root directory.
|
|
85
|
+
- **Command Execute** (nbi-command-execute): Execute shell commands using embedded terminal in Agent UI or JupyterLab terminal.
|
|
81
86
|
|
|
82
|
-
|
|
87
|
+
### Disabling Built-in tools
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
All built-in toolas are enabled by default in Agent Mode. However, you can disable them and make them controlled by an environment variable.
|
|
90
|
+
|
|
91
|
+
In order to disable any built-in tool use the `disabled_tools` config:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
c.NotebookIntelligence.disabled_tools = ["nbi-notebook-execute","nbi-python-file-edit"]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Valid built-in tool values are `nbi-notebook-edit`, `nbi-notebook-execute`, `nbi-python-file-edit`, `nbi-file-edit`, `nbi-file-read`, `nbi-command-execute`.
|
|
98
|
+
|
|
99
|
+
In order to disable a built-in tool by default but allow re-enabling using an environment variable use the `allow_enabling_tools_with_env` config:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
c.NotebookIntelligence.allow_enabling_tools_with_env = True
|
|
86
103
|
```
|
|
87
104
|
|
|
88
|
-
|
|
105
|
+
Then the environment variable `NBI_ENABLED_BUILTIN_TOOLS` can be used to re-enable specific built-in tools.
|
|
89
106
|
|
|
90
107
|
```bash
|
|
91
|
-
|
|
92
|
-
jupyter lab --NotebookIntelligence.notebook_execute_tool=env_enabled
|
|
108
|
+
export NBI_ENABLED_BUILTIN_TOOLS=nbi-notebook-execute,nbi-python-file-edit
|
|
93
109
|
```
|
|
94
110
|
|
|
95
111
|
### Configuration files
|
package/lib/api.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export declare class NBIAPI {
|
|
|
53
53
|
static updateOllamaModelList(): Promise<void>;
|
|
54
54
|
static getMCPConfigFile(): Promise<any>;
|
|
55
55
|
static setMCPConfigFile(config: any): Promise<any>;
|
|
56
|
-
static chatRequest(messageId: string, chatId: string, prompt: string, language: string, filename: string, additionalContext: IContextItem[], chatMode: string, toolSelections: IToolSelections, responseEmitter: IChatCompletionResponseEmitter): Promise<void>;
|
|
56
|
+
static chatRequest(messageId: string, chatId: string, prompt: string, language: string, currentDirectory: string, filename: string, additionalContext: IContextItem[], chatMode: string, toolSelections: IToolSelections, responseEmitter: IChatCompletionResponseEmitter): Promise<void>;
|
|
57
57
|
static reloadMCPServers(): Promise<any>;
|
|
58
58
|
static generateCode(chatId: string, prompt: string, prefix: string, suffix: string, existingCode: string, language: string, filename: string, responseEmitter: IChatCompletionResponseEmitter): Promise<void>;
|
|
59
59
|
static sendChatUserInput(messageId: string, data: any): Promise<void>;
|
package/lib/api.js
CHANGED
|
@@ -254,7 +254,7 @@ class NBIAPI {
|
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
}
|
|
257
|
-
static async chatRequest(messageId, chatId, prompt, language, filename, additionalContext, chatMode, toolSelections, responseEmitter) {
|
|
257
|
+
static async chatRequest(messageId, chatId, prompt, language, currentDirectory, filename, additionalContext, chatMode, toolSelections, responseEmitter) {
|
|
258
258
|
this._messageReceived.connect((_, msg) => {
|
|
259
259
|
msg = JSON.parse(msg);
|
|
260
260
|
if (msg.id === messageId) {
|
|
@@ -268,6 +268,7 @@ class NBIAPI {
|
|
|
268
268
|
chatId,
|
|
269
269
|
prompt,
|
|
270
270
|
language,
|
|
271
|
+
currentDirectory,
|
|
271
272
|
filename,
|
|
272
273
|
additionalContext,
|
|
273
274
|
chatMode,
|
package/lib/chat-sidebar.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface IRunChatCompletionRequest {
|
|
|
16
16
|
type: RunChatCompletionType;
|
|
17
17
|
content: string;
|
|
18
18
|
language?: string;
|
|
19
|
+
currentDirectory?: string;
|
|
19
20
|
filename?: string;
|
|
20
21
|
prefix?: string;
|
|
21
22
|
suffix?: string;
|
|
@@ -25,6 +26,7 @@ export interface IRunChatCompletionRequest {
|
|
|
25
26
|
toolSelections?: IToolSelections;
|
|
26
27
|
}
|
|
27
28
|
export interface IChatSidebarOptions {
|
|
29
|
+
getCurrentDirectory: () => string;
|
|
28
30
|
getActiveDocumentInfo: () => IActiveDocumentInfo;
|
|
29
31
|
getActiveSelectionContent: () => string;
|
|
30
32
|
getCurrentCellContents: () => ICellContents;
|
package/lib/chat-sidebar.js
CHANGED
|
@@ -30,7 +30,7 @@ export class ChatSidebar extends ReactWidget {
|
|
|
30
30
|
this.node.style.height = '100%';
|
|
31
31
|
}
|
|
32
32
|
render() {
|
|
33
|
-
return (React.createElement(SidebarComponent, { getActiveDocumentInfo: this._options.getActiveDocumentInfo, getActiveSelectionContent: this._options.getActiveSelectionContent, getCurrentCellContents: this._options.getCurrentCellContents, openFile: this._options.openFile, getApp: this._options.getApp, getTelemetryEmitter: this._options.getTelemetryEmitter }));
|
|
33
|
+
return (React.createElement(SidebarComponent, { getCurrentDirectory: this._options.getCurrentDirectory, getActiveDocumentInfo: this._options.getActiveDocumentInfo, getActiveSelectionContent: this._options.getActiveSelectionContent, getCurrentCellContents: this._options.getCurrentCellContents, openFile: this._options.openFile, getApp: this._options.getApp, getTelemetryEmitter: this._options.getTelemetryEmitter }));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
export class InlinePromptWidget extends ReactWidget {
|
|
@@ -154,6 +154,12 @@ function ChatResponse(props) {
|
|
|
154
154
|
// group messages by type
|
|
155
155
|
const groupedContents = [];
|
|
156
156
|
let lastItemType;
|
|
157
|
+
const responseDetailTags = [
|
|
158
|
+
'<think>',
|
|
159
|
+
'</think>',
|
|
160
|
+
'<terminal-output>',
|
|
161
|
+
'</terminal-output>'
|
|
162
|
+
];
|
|
157
163
|
const extractReasoningContent = (item) => {
|
|
158
164
|
let currentContent = item.content;
|
|
159
165
|
if (typeof currentContent !== 'string') {
|
|
@@ -162,17 +168,31 @@ function ChatResponse(props) {
|
|
|
162
168
|
let reasoningContent = '';
|
|
163
169
|
let reasoningStartTime = new Date();
|
|
164
170
|
const reasoningEndTime = new Date();
|
|
165
|
-
|
|
171
|
+
let startPos = -1;
|
|
172
|
+
let startTag = '';
|
|
173
|
+
for (const tag of responseDetailTags) {
|
|
174
|
+
startPos = currentContent.indexOf(tag);
|
|
175
|
+
if (startPos >= 0) {
|
|
176
|
+
startTag = tag;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
166
180
|
const hasStart = startPos >= 0;
|
|
167
181
|
reasoningStartTime = new Date(item.created);
|
|
168
182
|
if (hasStart) {
|
|
169
|
-
currentContent = currentContent.substring(startPos +
|
|
183
|
+
currentContent = currentContent.substring(startPos + startTag.length);
|
|
184
|
+
}
|
|
185
|
+
let endPos = -1;
|
|
186
|
+
for (const tag of responseDetailTags) {
|
|
187
|
+
endPos = currentContent.indexOf(tag);
|
|
188
|
+
if (endPos >= 0) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
170
191
|
}
|
|
171
|
-
const endPos = currentContent.indexOf('</think>');
|
|
172
192
|
const hasEnd = endPos >= 0;
|
|
173
193
|
if (hasEnd) {
|
|
174
194
|
reasoningContent += currentContent.substring(0, endPos);
|
|
175
|
-
currentContent = currentContent.substring(endPos +
|
|
195
|
+
currentContent = currentContent.substring(endPos + startTag.length);
|
|
176
196
|
}
|
|
177
197
|
else {
|
|
178
198
|
if (hasStart) {
|
|
@@ -181,6 +201,7 @@ function ChatResponse(props) {
|
|
|
181
201
|
}
|
|
182
202
|
}
|
|
183
203
|
item.content = currentContent;
|
|
204
|
+
item.reasoningTag = startTag;
|
|
184
205
|
item.reasoningContent = reasoningContent;
|
|
185
206
|
item.reasoningFinished = hasEnd;
|
|
186
207
|
item.reasoningTime =
|
|
@@ -225,6 +246,21 @@ function ChatResponse(props) {
|
|
|
225
246
|
parent.classList.add('expanded');
|
|
226
247
|
}
|
|
227
248
|
};
|
|
249
|
+
const getReasoningTitle = (item) => {
|
|
250
|
+
if (item.reasoningTag === '<think>') {
|
|
251
|
+
return item.reasoningFinished
|
|
252
|
+
? 'Thought'
|
|
253
|
+
: `Thinking (${Math.floor(item.reasoningTime)} s)`;
|
|
254
|
+
}
|
|
255
|
+
else if (item.reasoningTag === '<terminal-output>') {
|
|
256
|
+
return item.reasoningFinished
|
|
257
|
+
? 'Output'
|
|
258
|
+
: `Running (${Math.floor(item.reasoningTime)} s)`;
|
|
259
|
+
}
|
|
260
|
+
return item.reasoningFinished
|
|
261
|
+
? 'Output'
|
|
262
|
+
: `Output (${Math.floor(item.reasoningTime)} s)`;
|
|
263
|
+
};
|
|
228
264
|
return (React.createElement("div", { className: `chat-message chat-message-${msg.from}`, "data-render-count": renderCount },
|
|
229
265
|
React.createElement("div", { className: "chat-message-header" },
|
|
230
266
|
React.createElement("div", { className: "chat-message-from" },
|
|
@@ -242,18 +278,16 @@ function ChatResponse(props) {
|
|
|
242
278
|
case ResponseStreamDataType.Markdown:
|
|
243
279
|
case ResponseStreamDataType.MarkdownPart:
|
|
244
280
|
return (React.createElement(React.Fragment, null,
|
|
245
|
-
item.reasoningContent && (React.createElement("div", { className: "expandable-content" },
|
|
281
|
+
item.reasoningContent && (React.createElement("div", { className: "expandable-content expanded" },
|
|
246
282
|
React.createElement("div", { className: "expandable-content-title", onClick: (event) => onExpandCollapseClick(event) },
|
|
247
283
|
React.createElement(VscTriangleRight, { className: "collapsed-icon" }),
|
|
248
284
|
React.createElement(VscTriangleDown, { className: "expanded-icon" }),
|
|
249
285
|
' ',
|
|
250
|
-
item
|
|
251
|
-
? 'Thought'
|
|
252
|
-
: `Thinking (${Math.floor(item.reasoningTime)} s)`),
|
|
286
|
+
getReasoningTitle(item)),
|
|
253
287
|
React.createElement("div", { className: "expandable-content-text" },
|
|
254
288
|
React.createElement(MarkdownRenderer, { key: `key-${index}`, getApp: props.getApp, getActiveDocumentInfo: props.getActiveDocumentInfo }, item.reasoningContent)))),
|
|
255
289
|
React.createElement(MarkdownRenderer, { key: `key-${index}`, getApp: props.getApp, getActiveDocumentInfo: props.getActiveDocumentInfo }, item.content),
|
|
256
|
-
item.contentDetail ? (React.createElement("div", { className: "expandable-content" },
|
|
290
|
+
item.contentDetail ? (React.createElement("div", { className: "expandable-content expanded" },
|
|
257
291
|
React.createElement("div", { className: "expandable-content-title", onClick: (event) => onExpandCollapseClick(event) },
|
|
258
292
|
React.createElement(VscTriangleRight, { className: "collapsed-icon" }),
|
|
259
293
|
React.createElement(VscTriangleDown, { className: "expanded-icon" }),
|
|
@@ -304,12 +338,12 @@ const MemoizedChatResponse = memo(ChatResponse);
|
|
|
304
338
|
async function submitCompletionRequest(request, responseEmitter) {
|
|
305
339
|
switch (request.type) {
|
|
306
340
|
case RunChatCompletionType.Chat:
|
|
307
|
-
return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.filename || 'Untitled.ipynb', request.additionalContext || [], request.chatMode, request.toolSelections || {}, responseEmitter);
|
|
341
|
+
return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.currentDirectory || '', request.filename || 'Untitled.ipynb', request.additionalContext || [], request.chatMode, request.toolSelections || {}, responseEmitter);
|
|
308
342
|
case RunChatCompletionType.ExplainThis:
|
|
309
343
|
case RunChatCompletionType.FixThis:
|
|
310
344
|
case RunChatCompletionType.ExplainThisOutput:
|
|
311
345
|
case RunChatCompletionType.TroubleshootThisOutput: {
|
|
312
|
-
return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.filename || 'Untitled.ipynb', [], 'ask', {}, responseEmitter);
|
|
346
|
+
return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.currentDirectory || '', request.filename || 'Untitled.ipynb', [], 'ask', {}, responseEmitter);
|
|
313
347
|
}
|
|
314
348
|
case RunChatCompletionType.GenerateCode:
|
|
315
349
|
return NBIAPI.generateCode(request.chatId, request.content, request.prefix || '', request.suffix || '', request.existingCode || '', request.language || 'python', request.filename || 'Untitled.ipynb', responseEmitter);
|
|
@@ -341,7 +375,7 @@ function SidebarComponent(props) {
|
|
|
341
375
|
const [chatMode, setChatMode] = useState(NBIAPI.config.defaultChatMode);
|
|
342
376
|
const [toolSelectionTitle, setToolSelectionTitle] = useState('Tool selection');
|
|
343
377
|
const [selectedToolCount, setSelectedToolCount] = useState(0);
|
|
344
|
-
const [
|
|
378
|
+
const [unsafeToolSelected, setUnsafeToolSelected] = useState(false);
|
|
345
379
|
const [renderCount, setRenderCount] = useState(1);
|
|
346
380
|
const toolConfigRef = useRef({
|
|
347
381
|
builtinToolsets: [
|
|
@@ -355,7 +389,7 @@ function SidebarComponent(props) {
|
|
|
355
389
|
const [mcpServerEnabledState, setMCPServerEnabledState] = useState(new Map(mcpServerSettingsToEnabledState(toolConfigRef.current.mcpServers, mcpServerSettingsRef.current)));
|
|
356
390
|
const [showModeTools, setShowModeTools] = useState(false);
|
|
357
391
|
const toolSelectionsInitial = {
|
|
358
|
-
builtinToolsets: [
|
|
392
|
+
builtinToolsets: [],
|
|
359
393
|
mcpServers: {},
|
|
360
394
|
extensions: {}
|
|
361
395
|
};
|
|
@@ -368,13 +402,47 @@ function SidebarComponent(props) {
|
|
|
368
402
|
const [hasExtensionTools, setHasExtensionTools] = useState(false);
|
|
369
403
|
const [lastScrollTime, setLastScrollTime] = useState(0);
|
|
370
404
|
const [scrollPending, setScrollPending] = useState(false);
|
|
405
|
+
const cleanupRemovedToolsFromToolSelections = () => {
|
|
406
|
+
const newToolSelections = { ...toolSelections };
|
|
407
|
+
// if servers or tool is not in mcpServerEnabledState, remove it from newToolSelections
|
|
408
|
+
for (const serverId in newToolSelections.mcpServers) {
|
|
409
|
+
if (!mcpServerEnabledState.has(serverId)) {
|
|
410
|
+
delete newToolSelections.mcpServers[serverId];
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
for (const tool of newToolSelections.mcpServers[serverId]) {
|
|
414
|
+
if (!mcpServerEnabledState.get(serverId).has(tool)) {
|
|
415
|
+
newToolSelections.mcpServers[serverId].splice(newToolSelections.mcpServers[serverId].indexOf(tool), 1);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
for (const extensionId in newToolSelections.extensions) {
|
|
421
|
+
if (!mcpServerEnabledState.has(extensionId)) {
|
|
422
|
+
delete newToolSelections.extensions[extensionId];
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
for (const toolsetId in newToolSelections.extensions[extensionId]) {
|
|
426
|
+
for (const tool of newToolSelections.extensions[extensionId][toolsetId]) {
|
|
427
|
+
if (!mcpServerEnabledState.get(extensionId).has(tool)) {
|
|
428
|
+
newToolSelections.extensions[extensionId][toolsetId].splice(newToolSelections.extensions[extensionId][toolsetId].indexOf(tool), 1);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
setToolSelections(newToolSelections);
|
|
435
|
+
setRenderCount(renderCount => renderCount + 1);
|
|
436
|
+
};
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
cleanupRemovedToolsFromToolSelections();
|
|
439
|
+
}, [mcpServerEnabledState]);
|
|
371
440
|
useEffect(() => {
|
|
372
441
|
NBIAPI.configChanged.connect(() => {
|
|
373
442
|
toolConfigRef.current = NBIAPI.config.toolConfig;
|
|
374
443
|
mcpServerSettingsRef.current = NBIAPI.config.mcpServerSettings;
|
|
375
444
|
const newMcpServerEnabledState = mcpServerSettingsToEnabledState(toolConfigRef.current.mcpServers, mcpServerSettingsRef.current);
|
|
376
445
|
setMCPServerEnabledState(newMcpServerEnabledState);
|
|
377
|
-
setToolSelections(structuredClone(toolSelectionsInitial));
|
|
378
446
|
setRenderCount(renderCount => renderCount + 1);
|
|
379
447
|
});
|
|
380
448
|
}, []);
|
|
@@ -414,7 +482,13 @@ function SidebarComponent(props) {
|
|
|
414
482
|
typeCounts.push(`${extensionToolSelCount} ext`);
|
|
415
483
|
}
|
|
416
484
|
setSelectedToolCount(builtinToolSelCount + mcpServerToolSelCount + extensionToolSelCount);
|
|
417
|
-
|
|
485
|
+
setUnsafeToolSelected(toolSelections.builtinToolsets.some((toolsetName) => [
|
|
486
|
+
BuiltinToolsetType.NotebookEdit,
|
|
487
|
+
BuiltinToolsetType.NotebookExecute,
|
|
488
|
+
BuiltinToolsetType.PythonFileEdit,
|
|
489
|
+
BuiltinToolsetType.FileEdit,
|
|
490
|
+
BuiltinToolsetType.CommandExecute
|
|
491
|
+
].includes(toolsetName)));
|
|
418
492
|
setToolSelectionTitle(typeCounts.length === 0
|
|
419
493
|
? 'Tool selection'
|
|
420
494
|
: `Tool selection (${typeCounts.join(', ')})`);
|
|
@@ -615,7 +689,6 @@ function SidebarComponent(props) {
|
|
|
615
689
|
useEffect(() => {
|
|
616
690
|
var _a;
|
|
617
691
|
const prefixes = [];
|
|
618
|
-
prefixes.push('/clear');
|
|
619
692
|
if (chatMode === 'ask') {
|
|
620
693
|
const chatParticipants = NBIAPI.config.chatParticipants;
|
|
621
694
|
for (const participant of chatParticipants) {
|
|
@@ -631,6 +704,9 @@ function SidebarComponent(props) {
|
|
|
631
704
|
}
|
|
632
705
|
}
|
|
633
706
|
}
|
|
707
|
+
else {
|
|
708
|
+
prefixes.push('/clear');
|
|
709
|
+
}
|
|
634
710
|
const mcpServers = NBIAPI.config.toolConfig.mcpServers;
|
|
635
711
|
const mcpServerSettings = NBIAPI.config.mcpServerSettings;
|
|
636
712
|
for (const mcpServer of mcpServers) {
|
|
@@ -810,6 +886,7 @@ function SidebarComponent(props) {
|
|
|
810
886
|
type: RunChatCompletionType.Chat,
|
|
811
887
|
content: extractedPrompt,
|
|
812
888
|
language: activeDocInfo.language,
|
|
889
|
+
currentDirectory: props.getCurrentDirectory(),
|
|
813
890
|
filename: activeDocInfo.filePath,
|
|
814
891
|
additionalContext,
|
|
815
892
|
chatMode,
|
|
@@ -1217,16 +1294,13 @@ function SidebarComponent(props) {
|
|
|
1217
1294
|
if (event.target.value === 'ask') {
|
|
1218
1295
|
setToolSelections(toolSelectionsEmpty);
|
|
1219
1296
|
}
|
|
1220
|
-
else if (event.target.value === 'agent') {
|
|
1221
|
-
setToolSelections(structuredClone(toolSelectionsInitial));
|
|
1222
|
-
}
|
|
1223
1297
|
setShowModeTools(false);
|
|
1224
1298
|
setChatMode(event.target.value);
|
|
1225
1299
|
} },
|
|
1226
1300
|
React.createElement("option", { value: "ask" }, "Ask"),
|
|
1227
1301
|
React.createElement("option", { value: "agent" }, "Agent"))),
|
|
1228
|
-
chatMode !== 'ask' && (React.createElement("div", { className: `user-input-footer-button tools-button ${
|
|
1229
|
-
? `
|
|
1302
|
+
chatMode !== 'ask' && (React.createElement("div", { className: `user-input-footer-button tools-button ${unsafeToolSelected ? 'tools-button-warning' : selectedToolCount > 0 ? 'tools-button-active' : ''}`, onClick: () => handleChatToolsButtonClick(), title: unsafeToolSelected
|
|
1303
|
+
? `Tool selection can cause irreversible changes! Review each tool execution carefully.\n${toolSelectionTitle}`
|
|
1230
1304
|
: toolSelectionTitle },
|
|
1231
1305
|
React.createElement(VscTools, null),
|
|
1232
1306
|
selectedToolCount > 0 && React.createElement(React.Fragment, null, selectedToolCount)))),
|
|
@@ -1257,7 +1331,7 @@ function SidebarComponent(props) {
|
|
|
1257
1331
|
React.createElement("div", null, "Done"))),
|
|
1258
1332
|
React.createElement("div", { className: "mode-tools-popover-tool-list" },
|
|
1259
1333
|
React.createElement("div", { className: "mode-tools-group-header" }, "Built-in"),
|
|
1260
|
-
React.createElement("div", { className: "mode-tools-group mode-tools-group-built-in" }, toolConfigRef.current.builtinToolsets.map((toolset) => (React.createElement(CheckBoxItem, { key: toolset.id, label: toolset.name, checked: getBuiltinToolsetState(toolset.id), header: true, onClick: () => {
|
|
1334
|
+
React.createElement("div", { className: "mode-tools-group mode-tools-group-built-in" }, toolConfigRef.current.builtinToolsets.map((toolset) => (React.createElement(CheckBoxItem, { key: toolset.id, label: toolset.name, checked: getBuiltinToolsetState(toolset.id), tooltip: toolset.description, header: true, onClick: () => {
|
|
1261
1335
|
setBuiltinToolsetState(toolset.id, !getBuiltinToolsetState(toolset.id));
|
|
1262
1336
|
} })))),
|
|
1263
1337
|
renderCount > 0 &&
|
|
@@ -3,7 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import { MdOutlineCheckBoxOutlineBlank, MdCheckBox } from 'react-icons/md';
|
|
4
4
|
export function CheckBoxItem(props) {
|
|
5
5
|
const indent = props.indent || 0;
|
|
6
|
-
return (React.createElement("div", { className: `checkbox-item checkbox-item-indent-${indent} ${props.header ? 'checkbox-item-header' : ''}`, title: props.title, onClick: event => props.onClick(event) },
|
|
6
|
+
return (React.createElement("div", { className: `checkbox-item checkbox-item-indent-${indent} ${props.header ? 'checkbox-item-header' : ''}`, title: props.tooltip || props.title || '', onClick: event => props.onClick(event) },
|
|
7
7
|
React.createElement("div", { className: "checkbox-item-toggle" },
|
|
8
8
|
props.checked ? (React.createElement(MdCheckBox, { className: "checkbox-icon" })) : (React.createElement(MdOutlineCheckBoxOutlineBlank, { className: "checkbox-icon" })),
|
|
9
9
|
props.label),
|
|
@@ -364,9 +364,15 @@ function SettingsPanelComponentMCPServers(props) {
|
|
|
364
364
|
setMCPServerEnabled(server.id, !getMCPServerEnabled(server.id));
|
|
365
365
|
} }),
|
|
366
366
|
React.createElement("div", { className: `server-status-indicator ${server.status}`, title: server.status })),
|
|
367
|
-
getMCPServerEnabled(server.id) && (React.createElement("div", null,
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
getMCPServerEnabled(server.id) && (React.createElement("div", null,
|
|
368
|
+
server.tools.length > 0 && (React.createElement("div", { className: "mcp-server-tools" },
|
|
369
|
+
React.createElement("div", { className: "mcp-server-tools-header" }, "Tools"),
|
|
370
|
+
React.createElement("div", null, server.tools.map((tool) => (React.createElement(PillItem, { label: tool.name, title: tool.description, checked: getMCPServerToolEnabled(server.id, tool.name), onClick: () => {
|
|
371
|
+
setMCPServerToolEnabled(server.id, tool.name, !getMCPServerToolEnabled(server.id, tool.name));
|
|
372
|
+
} })))))),
|
|
373
|
+
server.prompts.length > 0 && (React.createElement("div", { className: "mcp-server-prompts" },
|
|
374
|
+
React.createElement("div", { className: "mcp-server-prompts-header" }, "Prompts"),
|
|
375
|
+
React.createElement("div", null, server.prompts.map((prompt) => (React.createElement(PillItem, { label: prompt.name, title: prompt.description, checked: true })))))))))))))),
|
|
370
376
|
React.createElement("div", { className: "model-config-section-row" },
|
|
371
377
|
React.createElement("div", { className: "model-config-section-column", style: { flexGrow: 'initial' } },
|
|
372
378
|
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", style: { width: 'max-content' }, onClick: props.onEditMCPConfigClicked },
|
package/lib/index.js
CHANGED
|
@@ -54,6 +54,7 @@ var CommandIDs;
|
|
|
54
54
|
CommandIDs.setCurrentFileContent = 'notebook-intelligence:set-current-file-content';
|
|
55
55
|
CommandIDs.openMCPConfigEditor = 'notebook-intelligence:open-mcp-config-editor';
|
|
56
56
|
CommandIDs.showFormInputDialog = 'notebook-intelligence:show-form-input-dialog';
|
|
57
|
+
CommandIDs.runCommandInTerminal = 'notebook-intelligence:run-command-in-terminal';
|
|
57
58
|
})(CommandIDs || (CommandIDs = {}));
|
|
58
59
|
const DOCUMENT_WATCH_INTERVAL = 1000;
|
|
59
60
|
const MAX_TOKENS = 4096;
|
|
@@ -77,7 +78,7 @@ const emptyNotebookContent = {
|
|
|
77
78
|
};
|
|
78
79
|
const BACKEND_TELEMETRY_LISTENER_NAME = 'backend-telemetry-listener';
|
|
79
80
|
class ActiveDocumentWatcher {
|
|
80
|
-
static initialize(app, languageRegistry) {
|
|
81
|
+
static initialize(app, languageRegistry, fileBrowser) {
|
|
81
82
|
var _a;
|
|
82
83
|
ActiveDocumentWatcher._languageRegistry = languageRegistry;
|
|
83
84
|
(_a = app.shell.currentChanged) === null || _a === void 0 ? void 0 : _a.connect((_sender, args) => {
|
|
@@ -86,6 +87,12 @@ class ActiveDocumentWatcher {
|
|
|
86
87
|
ActiveDocumentWatcher.activeDocumentInfo.activeWidget =
|
|
87
88
|
app.shell.currentWidget;
|
|
88
89
|
ActiveDocumentWatcher.handleWatchDocument();
|
|
90
|
+
if (fileBrowser) {
|
|
91
|
+
const onPathChanged = (model) => {
|
|
92
|
+
ActiveDocumentWatcher.currentDirectory = model.path;
|
|
93
|
+
};
|
|
94
|
+
fileBrowser.model.pathChanged.connect(onPathChanged);
|
|
95
|
+
}
|
|
89
96
|
}
|
|
90
97
|
static watchDocument(widget) {
|
|
91
98
|
if (ActiveDocumentWatcher.activeDocumentInfo.activeWidget === widget) {
|
|
@@ -209,6 +216,7 @@ class ActiveDocumentWatcher {
|
|
|
209
216
|
}));
|
|
210
217
|
}
|
|
211
218
|
}
|
|
219
|
+
ActiveDocumentWatcher.currentDirectory = '';
|
|
212
220
|
ActiveDocumentWatcher.activeDocumentInfo = {
|
|
213
221
|
language: 'python',
|
|
214
222
|
filename: 'nb-doesnt-exist.ipynb',
|
|
@@ -510,6 +518,9 @@ const plugin = {
|
|
|
510
518
|
});
|
|
511
519
|
panel.title.icon = sidebarIcon;
|
|
512
520
|
const sidebar = new ChatSidebar({
|
|
521
|
+
getCurrentDirectory: () => {
|
|
522
|
+
return ActiveDocumentWatcher.currentDirectory;
|
|
523
|
+
},
|
|
513
524
|
getActiveDocumentInfo: () => {
|
|
514
525
|
return ActiveDocumentWatcher.activeDocumentInfo;
|
|
515
526
|
},
|
|
@@ -530,7 +541,7 @@ const plugin = {
|
|
|
530
541
|
}
|
|
531
542
|
});
|
|
532
543
|
panel.addWidget(sidebar);
|
|
533
|
-
app.shell.add(panel, '
|
|
544
|
+
app.shell.add(panel, 'right', { rank: 1000 });
|
|
534
545
|
app.shell.activateById(panel.id);
|
|
535
546
|
const updateSidebarIcon = () => {
|
|
536
547
|
if (NBIAPI.getChatEnabled()) {
|
|
@@ -723,6 +734,29 @@ const plugin = {
|
|
|
723
734
|
}
|
|
724
735
|
}
|
|
725
736
|
});
|
|
737
|
+
app.commands.addCommand(CommandIDs.runCommandInTerminal, {
|
|
738
|
+
execute: async (args) => {
|
|
739
|
+
var _a;
|
|
740
|
+
const command = args.command;
|
|
741
|
+
const terminal = await app.commands.execute('terminal:create-new', {
|
|
742
|
+
cwd: args.cwd || ActiveDocumentWatcher.currentDirectory
|
|
743
|
+
});
|
|
744
|
+
const session = (_a = terminal === null || terminal === void 0 ? void 0 : terminal.content) === null || _a === void 0 ? void 0 : _a.session;
|
|
745
|
+
session.messageReceived.connect((sender, message) => {
|
|
746
|
+
console.log('Message received in Jupyter terminal:', message);
|
|
747
|
+
});
|
|
748
|
+
if (session) {
|
|
749
|
+
session.send({
|
|
750
|
+
type: 'stdin',
|
|
751
|
+
content: [command + '\n'] // Add newline to execute the command
|
|
752
|
+
});
|
|
753
|
+
return 'Command executed in Jupyter terminal';
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
return 'Failed to execute command in Jupyter terminal';
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
});
|
|
726
760
|
const isNewEmptyNotebook = (model) => {
|
|
727
761
|
return (model.cells.length === 1 &&
|
|
728
762
|
model.cells[0].cell_type === 'code' &&
|
|
@@ -1441,7 +1475,7 @@ const plugin = {
|
|
|
1441
1475
|
});
|
|
1442
1476
|
}
|
|
1443
1477
|
const jlabApp = app;
|
|
1444
|
-
ActiveDocumentWatcher.initialize(jlabApp, languageRegistry);
|
|
1478
|
+
ActiveDocumentWatcher.initialize(jlabApp, languageRegistry, defaultBrowser);
|
|
1445
1479
|
return extensionService;
|
|
1446
1480
|
}
|
|
1447
1481
|
};
|
package/lib/tokens.d.ts
CHANGED
|
@@ -86,7 +86,11 @@ export interface IToolSelections {
|
|
|
86
86
|
}
|
|
87
87
|
export declare enum BuiltinToolsetType {
|
|
88
88
|
NotebookEdit = "nbi-notebook-edit",
|
|
89
|
-
NotebookExecute = "nbi-notebook-execute"
|
|
89
|
+
NotebookExecute = "nbi-notebook-execute",
|
|
90
|
+
PythonFileEdit = "nbi-python-file-edit",
|
|
91
|
+
FileEdit = "nbi-file-edit",
|
|
92
|
+
FileRead = "nbi-file-read",
|
|
93
|
+
CommandExecute = "nbi-command-execute"
|
|
90
94
|
}
|
|
91
95
|
export declare const GITHUB_COPILOT_PROVIDER_ID = "github-copilot";
|
|
92
96
|
export declare enum TelemetryEventType {
|
package/lib/tokens.js
CHANGED
|
@@ -52,6 +52,10 @@ export var BuiltinToolsetType;
|
|
|
52
52
|
(function (BuiltinToolsetType) {
|
|
53
53
|
BuiltinToolsetType["NotebookEdit"] = "nbi-notebook-edit";
|
|
54
54
|
BuiltinToolsetType["NotebookExecute"] = "nbi-notebook-execute";
|
|
55
|
+
BuiltinToolsetType["PythonFileEdit"] = "nbi-python-file-edit";
|
|
56
|
+
BuiltinToolsetType["FileEdit"] = "nbi-file-edit";
|
|
57
|
+
BuiltinToolsetType["FileRead"] = "nbi-file-read";
|
|
58
|
+
BuiltinToolsetType["CommandExecute"] = "nbi-command-execute";
|
|
55
59
|
})(BuiltinToolsetType || (BuiltinToolsetType = {}));
|
|
56
60
|
export const GITHUB_COPILOT_PROVIDER_ID = 'github-copilot';
|
|
57
61
|
export var TelemetryEventType;
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -339,6 +339,7 @@ export class NBIAPI {
|
|
|
339
339
|
chatId: string,
|
|
340
340
|
prompt: string,
|
|
341
341
|
language: string,
|
|
342
|
+
currentDirectory: string,
|
|
342
343
|
filename: string,
|
|
343
344
|
additionalContext: IContextItem[],
|
|
344
345
|
chatMode: string,
|
|
@@ -359,6 +360,7 @@ export class NBIAPI {
|
|
|
359
360
|
chatId,
|
|
360
361
|
prompt,
|
|
361
362
|
language,
|
|
363
|
+
currentDirectory,
|
|
362
364
|
filename,
|
|
363
365
|
additionalContext,
|
|
364
366
|
chatMode,
|
package/src/chat-sidebar.tsx
CHANGED
|
@@ -70,6 +70,7 @@ export interface IRunChatCompletionRequest {
|
|
|
70
70
|
type: RunChatCompletionType;
|
|
71
71
|
content: string;
|
|
72
72
|
language?: string;
|
|
73
|
+
currentDirectory?: string;
|
|
73
74
|
filename?: string;
|
|
74
75
|
prefix?: string;
|
|
75
76
|
suffix?: string;
|
|
@@ -80,6 +81,7 @@ export interface IRunChatCompletionRequest {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
export interface IChatSidebarOptions {
|
|
84
|
+
getCurrentDirectory: () => string;
|
|
83
85
|
getActiveDocumentInfo: () => IActiveDocumentInfo;
|
|
84
86
|
getActiveSelectionContent: () => string;
|
|
85
87
|
getCurrentCellContents: () => ICellContents;
|
|
@@ -99,6 +101,7 @@ export class ChatSidebar extends ReactWidget {
|
|
|
99
101
|
render(): JSX.Element {
|
|
100
102
|
return (
|
|
101
103
|
<SidebarComponent
|
|
104
|
+
getCurrentDirectory={this._options.getCurrentDirectory}
|
|
102
105
|
getActiveDocumentInfo={this._options.getActiveDocumentInfo}
|
|
103
106
|
getActiveSelectionContent={this._options.getActiveSelectionContent}
|
|
104
107
|
getCurrentCellContents={this._options.getCurrentCellContents}
|
|
@@ -262,6 +265,7 @@ interface IChatMessageContent {
|
|
|
262
265
|
content: any;
|
|
263
266
|
contentDetail?: any;
|
|
264
267
|
created: Date;
|
|
268
|
+
reasoningTag?: string;
|
|
265
269
|
reasoningContent?: string;
|
|
266
270
|
reasoningFinished?: boolean;
|
|
267
271
|
reasoningTime?: number;
|
|
@@ -323,6 +327,12 @@ function ChatResponse(props: any) {
|
|
|
323
327
|
// group messages by type
|
|
324
328
|
const groupedContents: IChatMessageContent[] = [];
|
|
325
329
|
let lastItemType: ResponseStreamDataType | undefined;
|
|
330
|
+
const responseDetailTags = [
|
|
331
|
+
'<think>',
|
|
332
|
+
'</think>',
|
|
333
|
+
'<terminal-output>',
|
|
334
|
+
'</terminal-output>'
|
|
335
|
+
];
|
|
326
336
|
|
|
327
337
|
const extractReasoningContent = (item: IChatMessageContent) => {
|
|
328
338
|
let currentContent = item.content as string;
|
|
@@ -334,21 +344,35 @@ function ChatResponse(props: any) {
|
|
|
334
344
|
let reasoningStartTime = new Date();
|
|
335
345
|
const reasoningEndTime = new Date();
|
|
336
346
|
|
|
337
|
-
|
|
347
|
+
let startPos = -1;
|
|
348
|
+
let startTag = '';
|
|
349
|
+
for (const tag of responseDetailTags) {
|
|
350
|
+
startPos = currentContent.indexOf(tag);
|
|
351
|
+
if (startPos >= 0) {
|
|
352
|
+
startTag = tag;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
338
356
|
|
|
339
357
|
const hasStart = startPos >= 0;
|
|
340
358
|
reasoningStartTime = new Date(item.created);
|
|
341
359
|
|
|
342
360
|
if (hasStart) {
|
|
343
|
-
currentContent = currentContent.substring(startPos +
|
|
361
|
+
currentContent = currentContent.substring(startPos + startTag.length);
|
|
344
362
|
}
|
|
345
363
|
|
|
346
|
-
|
|
364
|
+
let endPos = -1;
|
|
365
|
+
for (const tag of responseDetailTags) {
|
|
366
|
+
endPos = currentContent.indexOf(tag);
|
|
367
|
+
if (endPos >= 0) {
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
347
371
|
const hasEnd = endPos >= 0;
|
|
348
372
|
|
|
349
373
|
if (hasEnd) {
|
|
350
374
|
reasoningContent += currentContent.substring(0, endPos);
|
|
351
|
-
currentContent = currentContent.substring(endPos +
|
|
375
|
+
currentContent = currentContent.substring(endPos + startTag.length);
|
|
352
376
|
} else {
|
|
353
377
|
if (hasStart) {
|
|
354
378
|
reasoningContent += currentContent;
|
|
@@ -357,6 +381,7 @@ function ChatResponse(props: any) {
|
|
|
357
381
|
}
|
|
358
382
|
|
|
359
383
|
item.content = currentContent;
|
|
384
|
+
item.reasoningTag = startTag;
|
|
360
385
|
item.reasoningContent = reasoningContent;
|
|
361
386
|
item.reasoningFinished = hasEnd;
|
|
362
387
|
item.reasoningTime =
|
|
@@ -409,6 +434,21 @@ function ChatResponse(props: any) {
|
|
|
409
434
|
}
|
|
410
435
|
};
|
|
411
436
|
|
|
437
|
+
const getReasoningTitle = (item: IChatMessageContent) => {
|
|
438
|
+
if (item.reasoningTag === '<think>') {
|
|
439
|
+
return item.reasoningFinished
|
|
440
|
+
? 'Thought'
|
|
441
|
+
: `Thinking (${Math.floor(item.reasoningTime)} s)`;
|
|
442
|
+
} else if (item.reasoningTag === '<terminal-output>') {
|
|
443
|
+
return item.reasoningFinished
|
|
444
|
+
? 'Output'
|
|
445
|
+
: `Running (${Math.floor(item.reasoningTime)} s)`;
|
|
446
|
+
}
|
|
447
|
+
return item.reasoningFinished
|
|
448
|
+
? 'Output'
|
|
449
|
+
: `Output (${Math.floor(item.reasoningTime)} s)`;
|
|
450
|
+
};
|
|
451
|
+
|
|
412
452
|
return (
|
|
413
453
|
<div
|
|
414
454
|
className={`chat-message chat-message-${msg.from}`}
|
|
@@ -445,16 +485,14 @@ function ChatResponse(props: any) {
|
|
|
445
485
|
return (
|
|
446
486
|
<>
|
|
447
487
|
{item.reasoningContent && (
|
|
448
|
-
<div className="expandable-content">
|
|
488
|
+
<div className="expandable-content expanded">
|
|
449
489
|
<div
|
|
450
490
|
className="expandable-content-title"
|
|
451
491
|
onClick={(event: any) => onExpandCollapseClick(event)}
|
|
452
492
|
>
|
|
453
493
|
<VscTriangleRight className="collapsed-icon"></VscTriangleRight>
|
|
454
494
|
<VscTriangleDown className="expanded-icon"></VscTriangleDown>{' '}
|
|
455
|
-
{item
|
|
456
|
-
? 'Thought'
|
|
457
|
-
: `Thinking (${Math.floor(item.reasoningTime)} s)`}
|
|
495
|
+
{getReasoningTitle(item)}
|
|
458
496
|
</div>
|
|
459
497
|
<div className="expandable-content-text">
|
|
460
498
|
<MarkdownRenderer
|
|
@@ -475,7 +513,7 @@ function ChatResponse(props: any) {
|
|
|
475
513
|
{item.content}
|
|
476
514
|
</MarkdownRenderer>
|
|
477
515
|
{item.contentDetail ? (
|
|
478
|
-
<div className="expandable-content">
|
|
516
|
+
<div className="expandable-content expanded">
|
|
479
517
|
<div
|
|
480
518
|
className="expandable-content-title"
|
|
481
519
|
onClick={(event: any) => onExpandCollapseClick(event)}
|
|
@@ -616,6 +654,7 @@ async function submitCompletionRequest(
|
|
|
616
654
|
request.chatId,
|
|
617
655
|
request.content,
|
|
618
656
|
request.language || 'python',
|
|
657
|
+
request.currentDirectory || '',
|
|
619
658
|
request.filename || 'Untitled.ipynb',
|
|
620
659
|
request.additionalContext || [],
|
|
621
660
|
request.chatMode,
|
|
@@ -631,6 +670,7 @@ async function submitCompletionRequest(
|
|
|
631
670
|
request.chatId,
|
|
632
671
|
request.content,
|
|
633
672
|
request.language || 'python',
|
|
673
|
+
request.currentDirectory || '',
|
|
634
674
|
request.filename || 'Untitled.ipynb',
|
|
635
675
|
[],
|
|
636
676
|
'ask',
|
|
@@ -685,8 +725,7 @@ function SidebarComponent(props: any) {
|
|
|
685
725
|
const [toolSelectionTitle, setToolSelectionTitle] =
|
|
686
726
|
useState('Tool selection');
|
|
687
727
|
const [selectedToolCount, setSelectedToolCount] = useState(0);
|
|
688
|
-
const [
|
|
689
|
-
useState(false);
|
|
728
|
+
const [unsafeToolSelected, setUnsafeToolSelected] = useState(false);
|
|
690
729
|
|
|
691
730
|
const [renderCount, setRenderCount] = useState(1);
|
|
692
731
|
const toolConfigRef = useRef({
|
|
@@ -709,7 +748,7 @@ function SidebarComponent(props: any) {
|
|
|
709
748
|
|
|
710
749
|
const [showModeTools, setShowModeTools] = useState(false);
|
|
711
750
|
const toolSelectionsInitial: any = {
|
|
712
|
-
builtinToolsets: [
|
|
751
|
+
builtinToolsets: [],
|
|
713
752
|
mcpServers: {},
|
|
714
753
|
extensions: {}
|
|
715
754
|
};
|
|
@@ -725,6 +764,51 @@ function SidebarComponent(props: any) {
|
|
|
725
764
|
const [lastScrollTime, setLastScrollTime] = useState(0);
|
|
726
765
|
const [scrollPending, setScrollPending] = useState(false);
|
|
727
766
|
|
|
767
|
+
const cleanupRemovedToolsFromToolSelections = () => {
|
|
768
|
+
const newToolSelections = { ...toolSelections };
|
|
769
|
+
// if servers or tool is not in mcpServerEnabledState, remove it from newToolSelections
|
|
770
|
+
for (const serverId in newToolSelections.mcpServers) {
|
|
771
|
+
if (!mcpServerEnabledState.has(serverId)) {
|
|
772
|
+
delete newToolSelections.mcpServers[serverId];
|
|
773
|
+
} else {
|
|
774
|
+
for (const tool of newToolSelections.mcpServers[serverId]) {
|
|
775
|
+
if (!mcpServerEnabledState.get(serverId).has(tool)) {
|
|
776
|
+
newToolSelections.mcpServers[serverId].splice(
|
|
777
|
+
newToolSelections.mcpServers[serverId].indexOf(tool),
|
|
778
|
+
1
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
for (const extensionId in newToolSelections.extensions) {
|
|
785
|
+
if (!mcpServerEnabledState.has(extensionId)) {
|
|
786
|
+
delete newToolSelections.extensions[extensionId];
|
|
787
|
+
} else {
|
|
788
|
+
for (const toolsetId in newToolSelections.extensions[extensionId]) {
|
|
789
|
+
for (const tool of newToolSelections.extensions[extensionId][
|
|
790
|
+
toolsetId
|
|
791
|
+
]) {
|
|
792
|
+
if (!mcpServerEnabledState.get(extensionId).has(tool)) {
|
|
793
|
+
newToolSelections.extensions[extensionId][toolsetId].splice(
|
|
794
|
+
newToolSelections.extensions[extensionId][toolsetId].indexOf(
|
|
795
|
+
tool
|
|
796
|
+
),
|
|
797
|
+
1
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
setToolSelections(newToolSelections);
|
|
805
|
+
setRenderCount(renderCount => renderCount + 1);
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
useEffect(() => {
|
|
809
|
+
cleanupRemovedToolsFromToolSelections();
|
|
810
|
+
}, [mcpServerEnabledState]);
|
|
811
|
+
|
|
728
812
|
useEffect(() => {
|
|
729
813
|
NBIAPI.configChanged.connect(() => {
|
|
730
814
|
toolConfigRef.current = NBIAPI.config.toolConfig;
|
|
@@ -734,7 +818,6 @@ function SidebarComponent(props: any) {
|
|
|
734
818
|
mcpServerSettingsRef.current
|
|
735
819
|
);
|
|
736
820
|
setMCPServerEnabledState(newMcpServerEnabledState);
|
|
737
|
-
setToolSelections(structuredClone(toolSelectionsInitial));
|
|
738
821
|
setRenderCount(renderCount => renderCount + 1);
|
|
739
822
|
});
|
|
740
823
|
}, []);
|
|
@@ -782,9 +865,15 @@ function SidebarComponent(props: any) {
|
|
|
782
865
|
setSelectedToolCount(
|
|
783
866
|
builtinToolSelCount + mcpServerToolSelCount + extensionToolSelCount
|
|
784
867
|
);
|
|
785
|
-
|
|
786
|
-
toolSelections.builtinToolsets.
|
|
787
|
-
|
|
868
|
+
setUnsafeToolSelected(
|
|
869
|
+
toolSelections.builtinToolsets.some((toolsetName: string) =>
|
|
870
|
+
[
|
|
871
|
+
BuiltinToolsetType.NotebookEdit,
|
|
872
|
+
BuiltinToolsetType.NotebookExecute,
|
|
873
|
+
BuiltinToolsetType.PythonFileEdit,
|
|
874
|
+
BuiltinToolsetType.FileEdit,
|
|
875
|
+
BuiltinToolsetType.CommandExecute
|
|
876
|
+
].includes(toolsetName as unknown as BuiltinToolsetType)
|
|
788
877
|
)
|
|
789
878
|
);
|
|
790
879
|
setToolSelectionTitle(
|
|
@@ -1079,7 +1168,6 @@ function SidebarComponent(props: any) {
|
|
|
1079
1168
|
|
|
1080
1169
|
useEffect(() => {
|
|
1081
1170
|
const prefixes: string[] = [];
|
|
1082
|
-
prefixes.push('/clear');
|
|
1083
1171
|
|
|
1084
1172
|
if (chatMode === 'ask') {
|
|
1085
1173
|
const chatParticipants = NBIAPI.config.chatParticipants;
|
|
@@ -1096,6 +1184,8 @@ function SidebarComponent(props: any) {
|
|
|
1096
1184
|
prefixes.push(`${commandPrefix}/${command}`);
|
|
1097
1185
|
}
|
|
1098
1186
|
}
|
|
1187
|
+
} else {
|
|
1188
|
+
prefixes.push('/clear');
|
|
1099
1189
|
}
|
|
1100
1190
|
|
|
1101
1191
|
const mcpServers = NBIAPI.config.toolConfig.mcpServers;
|
|
@@ -1311,6 +1401,7 @@ function SidebarComponent(props: any) {
|
|
|
1311
1401
|
type: RunChatCompletionType.Chat,
|
|
1312
1402
|
content: extractedPrompt,
|
|
1313
1403
|
language: activeDocInfo.language,
|
|
1404
|
+
currentDirectory: props.getCurrentDirectory(),
|
|
1314
1405
|
filename: activeDocInfo.filePath,
|
|
1315
1406
|
additionalContext,
|
|
1316
1407
|
chatMode,
|
|
@@ -1886,8 +1977,6 @@ function SidebarComponent(props: any) {
|
|
|
1886
1977
|
onChange={event => {
|
|
1887
1978
|
if (event.target.value === 'ask') {
|
|
1888
1979
|
setToolSelections(toolSelectionsEmpty);
|
|
1889
|
-
} else if (event.target.value === 'agent') {
|
|
1890
|
-
setToolSelections(structuredClone(toolSelectionsInitial));
|
|
1891
1980
|
}
|
|
1892
1981
|
setShowModeTools(false);
|
|
1893
1982
|
setChatMode(event.target.value);
|
|
@@ -1899,11 +1988,11 @@ function SidebarComponent(props: any) {
|
|
|
1899
1988
|
</div>
|
|
1900
1989
|
{chatMode !== 'ask' && (
|
|
1901
1990
|
<div
|
|
1902
|
-
className={`user-input-footer-button tools-button ${
|
|
1991
|
+
className={`user-input-footer-button tools-button ${unsafeToolSelected ? 'tools-button-warning' : selectedToolCount > 0 ? 'tools-button-active' : ''}`}
|
|
1903
1992
|
onClick={() => handleChatToolsButtonClick()}
|
|
1904
1993
|
title={
|
|
1905
|
-
|
|
1906
|
-
? `
|
|
1994
|
+
unsafeToolSelected
|
|
1995
|
+
? `Tool selection can cause irreversible changes! Review each tool execution carefully.\n${toolSelectionTitle}`
|
|
1907
1996
|
: toolSelectionTitle
|
|
1908
1997
|
}
|
|
1909
1998
|
>
|
|
@@ -1996,6 +2085,7 @@ function SidebarComponent(props: any) {
|
|
|
1996
2085
|
key={toolset.id}
|
|
1997
2086
|
label={toolset.name}
|
|
1998
2087
|
checked={getBuiltinToolsetState(toolset.id)}
|
|
2088
|
+
tooltip={toolset.description}
|
|
1999
2089
|
header={true}
|
|
2000
2090
|
onClick={() => {
|
|
2001
2091
|
setBuiltinToolsetState(
|
|
@@ -10,7 +10,7 @@ export function CheckBoxItem(props: any) {
|
|
|
10
10
|
return (
|
|
11
11
|
<div
|
|
12
12
|
className={`checkbox-item checkbox-item-indent-${indent} ${props.header ? 'checkbox-item-header' : ''}`}
|
|
13
|
-
title={props.title}
|
|
13
|
+
title={props.tooltip || props.title || ''}
|
|
14
14
|
onClick={event => props.onClick(event)}
|
|
15
15
|
>
|
|
16
16
|
<div className="checkbox-item-toggle">
|
|
@@ -724,23 +724,51 @@ function SettingsPanelComponentMCPServers(props: any) {
|
|
|
724
724
|
</div>
|
|
725
725
|
{getMCPServerEnabled(server.id) && (
|
|
726
726
|
<div>
|
|
727
|
-
{server.tools.
|
|
728
|
-
<
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
tool
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
727
|
+
{server.tools.length > 0 && (
|
|
728
|
+
<div className="mcp-server-tools">
|
|
729
|
+
<div className="mcp-server-tools-header">
|
|
730
|
+
Tools
|
|
731
|
+
</div>
|
|
732
|
+
<div>
|
|
733
|
+
{server.tools.map((tool: any) => (
|
|
734
|
+
<PillItem
|
|
735
|
+
label={tool.name}
|
|
736
|
+
title={tool.description}
|
|
737
|
+
checked={getMCPServerToolEnabled(
|
|
738
|
+
server.id,
|
|
739
|
+
tool.name
|
|
740
|
+
)}
|
|
741
|
+
onClick={() => {
|
|
742
|
+
setMCPServerToolEnabled(
|
|
743
|
+
server.id,
|
|
744
|
+
tool.name,
|
|
745
|
+
!getMCPServerToolEnabled(
|
|
746
|
+
server.id,
|
|
747
|
+
tool.name
|
|
748
|
+
)
|
|
749
|
+
);
|
|
750
|
+
}}
|
|
751
|
+
></PillItem>
|
|
752
|
+
))}
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
755
|
+
)}
|
|
756
|
+
{server.prompts.length > 0 && (
|
|
757
|
+
<div className="mcp-server-prompts">
|
|
758
|
+
<div className="mcp-server-prompts-header">
|
|
759
|
+
Prompts
|
|
760
|
+
</div>
|
|
761
|
+
<div>
|
|
762
|
+
{server.prompts.map((prompt: any) => (
|
|
763
|
+
<PillItem
|
|
764
|
+
label={prompt.name}
|
|
765
|
+
title={prompt.description}
|
|
766
|
+
checked={true}
|
|
767
|
+
></PillItem>
|
|
768
|
+
))}
|
|
769
|
+
</div>
|
|
770
|
+
</div>
|
|
771
|
+
)}
|
|
744
772
|
</div>
|
|
745
773
|
)}
|
|
746
774
|
</div>
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ import { NotebookPanel } from '@jupyterlab/notebook';
|
|
|
32
32
|
import { CodeEditor } from '@jupyterlab/codeeditor';
|
|
33
33
|
import { FileEditorWidget } from '@jupyterlab/fileeditor';
|
|
34
34
|
|
|
35
|
-
import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
|
|
35
|
+
import { FileBrowserModel, IDefaultFileBrowser } from '@jupyterlab/filebrowser';
|
|
36
36
|
|
|
37
37
|
import { ContentsManager, KernelSpecManager } from '@jupyterlab/services';
|
|
38
38
|
|
|
@@ -83,6 +83,7 @@ import { UUID } from '@lumino/coreutils';
|
|
|
83
83
|
|
|
84
84
|
import * as path from 'path';
|
|
85
85
|
import { SettingsPanel } from './components/settings-panel';
|
|
86
|
+
import { ITerminalConnection } from '@jupyterlab/services/lib/terminal/terminal';
|
|
86
87
|
|
|
87
88
|
namespace CommandIDs {
|
|
88
89
|
export const chatuserInput = 'notebook-intelligence:chat-user-input';
|
|
@@ -130,6 +131,8 @@ namespace CommandIDs {
|
|
|
130
131
|
'notebook-intelligence:open-mcp-config-editor';
|
|
131
132
|
export const showFormInputDialog =
|
|
132
133
|
'notebook-intelligence:show-form-input-dialog';
|
|
134
|
+
export const runCommandInTerminal =
|
|
135
|
+
'notebook-intelligence:run-command-in-terminal';
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
const DOCUMENT_WATCH_INTERVAL = 1000;
|
|
@@ -160,7 +163,8 @@ const BACKEND_TELEMETRY_LISTENER_NAME = 'backend-telemetry-listener';
|
|
|
160
163
|
class ActiveDocumentWatcher {
|
|
161
164
|
static initialize(
|
|
162
165
|
app: JupyterLab,
|
|
163
|
-
languageRegistry: IEditorLanguageRegistry
|
|
166
|
+
languageRegistry: IEditorLanguageRegistry,
|
|
167
|
+
fileBrowser: IDefaultFileBrowser
|
|
164
168
|
) {
|
|
165
169
|
ActiveDocumentWatcher._languageRegistry = languageRegistry;
|
|
166
170
|
|
|
@@ -171,6 +175,13 @@ class ActiveDocumentWatcher {
|
|
|
171
175
|
ActiveDocumentWatcher.activeDocumentInfo.activeWidget =
|
|
172
176
|
app.shell.currentWidget;
|
|
173
177
|
ActiveDocumentWatcher.handleWatchDocument();
|
|
178
|
+
|
|
179
|
+
if (fileBrowser) {
|
|
180
|
+
const onPathChanged = (model: FileBrowserModel) => {
|
|
181
|
+
ActiveDocumentWatcher.currentDirectory = model.path;
|
|
182
|
+
};
|
|
183
|
+
fileBrowser.model.pathChanged.connect(onPathChanged);
|
|
184
|
+
}
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
static watchDocument(widget: Widget) {
|
|
@@ -315,6 +326,8 @@ class ActiveDocumentWatcher {
|
|
|
315
326
|
);
|
|
316
327
|
}
|
|
317
328
|
|
|
329
|
+
static currentDirectory: string = '';
|
|
330
|
+
|
|
318
331
|
static activeDocumentInfo: IActiveDocumentInfo = {
|
|
319
332
|
language: 'python',
|
|
320
333
|
filename: 'nb-doesnt-exist.ipynb',
|
|
@@ -724,6 +737,9 @@ const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
|
|
|
724
737
|
});
|
|
725
738
|
panel.title.icon = sidebarIcon;
|
|
726
739
|
const sidebar = new ChatSidebar({
|
|
740
|
+
getCurrentDirectory: (): string => {
|
|
741
|
+
return ActiveDocumentWatcher.currentDirectory;
|
|
742
|
+
},
|
|
727
743
|
getActiveDocumentInfo: (): IActiveDocumentInfo => {
|
|
728
744
|
return ActiveDocumentWatcher.activeDocumentInfo;
|
|
729
745
|
},
|
|
@@ -744,7 +760,7 @@ const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
|
|
|
744
760
|
}
|
|
745
761
|
});
|
|
746
762
|
panel.addWidget(sidebar);
|
|
747
|
-
app.shell.add(panel, '
|
|
763
|
+
app.shell.add(panel, 'right', { rank: 1000 });
|
|
748
764
|
app.shell.activateById(panel.id);
|
|
749
765
|
|
|
750
766
|
const updateSidebarIcon = () => {
|
|
@@ -962,6 +978,32 @@ const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
|
|
|
962
978
|
}
|
|
963
979
|
});
|
|
964
980
|
|
|
981
|
+
app.commands.addCommand(CommandIDs.runCommandInTerminal, {
|
|
982
|
+
execute: async args => {
|
|
983
|
+
const command = args.command as string;
|
|
984
|
+
const terminal = await app.commands.execute('terminal:create-new', {
|
|
985
|
+
cwd: (args.cwd as string) || ActiveDocumentWatcher.currentDirectory
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
const session: ITerminalConnection = terminal?.content?.session;
|
|
989
|
+
|
|
990
|
+
session.messageReceived.connect((sender, message) => {
|
|
991
|
+
console.log('Message received in Jupyter terminal:', message);
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
if (session) {
|
|
995
|
+
session.send({
|
|
996
|
+
type: 'stdin',
|
|
997
|
+
content: [command + '\n'] // Add newline to execute the command
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
return 'Command executed in Jupyter terminal';
|
|
1001
|
+
} else {
|
|
1002
|
+
return 'Failed to execute command in Jupyter terminal';
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
|
|
965
1007
|
const isNewEmptyNotebook = (model: ISharedNotebook) => {
|
|
966
1008
|
return (
|
|
967
1009
|
model.cells.length === 1 &&
|
|
@@ -1848,7 +1890,7 @@ const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
|
|
|
1848
1890
|
}
|
|
1849
1891
|
|
|
1850
1892
|
const jlabApp = app as JupyterLab;
|
|
1851
|
-
ActiveDocumentWatcher.initialize(jlabApp, languageRegistry);
|
|
1893
|
+
ActiveDocumentWatcher.initialize(jlabApp, languageRegistry, defaultBrowser);
|
|
1852
1894
|
|
|
1853
1895
|
return extensionService;
|
|
1854
1896
|
}
|
package/src/tokens.ts
CHANGED
|
@@ -100,7 +100,11 @@ export interface IToolSelections {
|
|
|
100
100
|
|
|
101
101
|
export enum BuiltinToolsetType {
|
|
102
102
|
NotebookEdit = 'nbi-notebook-edit',
|
|
103
|
-
NotebookExecute = 'nbi-notebook-execute'
|
|
103
|
+
NotebookExecute = 'nbi-notebook-execute',
|
|
104
|
+
PythonFileEdit = 'nbi-python-file-edit',
|
|
105
|
+
FileEdit = 'nbi-file-edit',
|
|
106
|
+
FileRead = 'nbi-file-read',
|
|
107
|
+
CommandExecute = 'nbi-command-execute'
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
export const GITHUB_COPILOT_PROVIDER_ID = 'github-copilot';
|
package/style/base.css
CHANGED
|
@@ -733,8 +733,11 @@ body[data-jp-theme-light='false'] .inline-popover {
|
|
|
733
733
|
display: none;
|
|
734
734
|
margin: 5px;
|
|
735
735
|
padding: 0 5px;
|
|
736
|
-
border: 1px
|
|
736
|
+
border: 1px solid var(--jp-border-color2);
|
|
737
737
|
border-radius: 5px;
|
|
738
|
+
max-height: 200px;
|
|
739
|
+
overflow-y: auto;
|
|
740
|
+
box-shadow: inset 0 0 15px 15px rgb(23 23 23 / 50%);
|
|
738
741
|
}
|
|
739
742
|
|
|
740
743
|
.expandable-content .collapsed-icon {
|
|
@@ -816,6 +819,14 @@ svg.access-token-warning {
|
|
|
816
819
|
font-weight: bold;
|
|
817
820
|
}
|
|
818
821
|
|
|
822
|
+
.mode-tools-group-built-in {
|
|
823
|
+
flex-flow: row wrap;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
.mode-tools-group-built-in .checkbox-item {
|
|
827
|
+
width: 50%;
|
|
828
|
+
}
|
|
829
|
+
|
|
819
830
|
.mode-tools-group-built-in .checkbox-item-toggle {
|
|
820
831
|
font-weight: normal;
|
|
821
832
|
}
|
|
@@ -930,3 +941,13 @@ svg.access-token-warning {
|
|
|
930
941
|
.form-input-dialog-body-content-field-input {
|
|
931
942
|
width: 50%;
|
|
932
943
|
}
|
|
944
|
+
|
|
945
|
+
.mcp-server-prompts .pill-item {
|
|
946
|
+
cursor: default;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
.mcp-server-tools-header,
|
|
950
|
+
.mcp-server-prompts-header {
|
|
951
|
+
margin: 5px;
|
|
952
|
+
font-style: italic;
|
|
953
|
+
}
|