@notebook-intelligence/notebook-intelligence 2.4.1 → 2.5.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.
@@ -0,0 +1,374 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ import { ReactWidget } from '@jupyterlab/apputils';
4
+ import { VscWarning } from 'react-icons/vsc';
5
+ import * as path from 'path';
6
+ import copySvgstr from '../../style/icons/copy.svg';
7
+ import { NBIAPI } from '../api';
8
+ import { CheckBoxItem } from './checkbox';
9
+ import { PillItem } from './pill';
10
+ import { mcpServerSettingsToEnabledState } from './mcp-util';
11
+ const OPENAI_COMPATIBLE_CHAT_MODEL_ID = 'openai-compatible-chat-model';
12
+ const LITELLM_COMPATIBLE_CHAT_MODEL_ID = 'litellm-compatible-chat-model';
13
+ const OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID = 'openai-compatible-inline-completion-model';
14
+ const LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID = 'litellm-compatible-inline-completion-model';
15
+ export class SettingsPanel extends ReactWidget {
16
+ constructor(options) {
17
+ super();
18
+ this._onSave = options.onSave;
19
+ this._onEditMCPConfigClicked = options.onEditMCPConfigClicked;
20
+ }
21
+ render() {
22
+ return (React.createElement(SettingsPanelComponent, { onSave: this._onSave, onEditMCPConfigClicked: this._onEditMCPConfigClicked }));
23
+ }
24
+ }
25
+ function SettingsPanelComponent(props) {
26
+ const [activeTab, setActiveTab] = useState('general');
27
+ const onTabSelected = (tab) => {
28
+ setActiveTab(tab);
29
+ };
30
+ return (React.createElement("div", { className: "nbi-settings-panel" },
31
+ React.createElement("div", { className: "nbi-settings-panel-tabs" },
32
+ React.createElement(SettingsPanelTabsComponent, { onTabSelected: onTabSelected, activeTab: activeTab })),
33
+ React.createElement("div", { className: "nbi-settings-panel-tab-content" },
34
+ activeTab === 'general' && (React.createElement(SettingsPanelComponentGeneral, { onSave: props.onSave, onEditMCPConfigClicked: props.onEditMCPConfigClicked })),
35
+ activeTab === 'mcp-servers' && (React.createElement(SettingsPanelComponentMCPServers, { onEditMCPConfigClicked: props.onEditMCPConfigClicked })))));
36
+ }
37
+ function SettingsPanelTabsComponent(props) {
38
+ const [activeTab, setActiveTab] = useState(props.activeTab);
39
+ return (React.createElement("div", null,
40
+ React.createElement("div", { className: `nbi-settings-panel-tab ${activeTab === 'general' ? 'active' : ''}`, onClick: () => {
41
+ setActiveTab('general');
42
+ props.onTabSelected('general');
43
+ } }, "General"),
44
+ React.createElement("div", { className: `nbi-settings-panel-tab ${activeTab === 'mcp-servers' ? 'active' : ''}`, onClick: () => {
45
+ setActiveTab('mcp-servers');
46
+ props.onTabSelected('mcp-servers');
47
+ } }, "MCP Servers")));
48
+ }
49
+ function SettingsPanelComponentGeneral(props) {
50
+ const nbiConfig = NBIAPI.config;
51
+ const llmProviders = nbiConfig.llmProviders;
52
+ const [chatModels, setChatModels] = useState([]);
53
+ const [inlineCompletionModels, setInlineCompletionModels] = useState([]);
54
+ const handleSaveSettings = async () => {
55
+ const config = {
56
+ default_chat_mode: defaultChatMode,
57
+ chat_model: {
58
+ provider: chatModelProvider,
59
+ model: chatModel,
60
+ properties: chatModelProperties
61
+ },
62
+ inline_completion_model: {
63
+ provider: inlineCompletionModelProvider,
64
+ model: inlineCompletionModel,
65
+ properties: inlineCompletionModelProperties
66
+ }
67
+ };
68
+ if (chatModelProvider === 'github-copilot' ||
69
+ inlineCompletionModelProvider === 'github-copilot') {
70
+ config.store_github_access_token = storeGitHubAccessToken;
71
+ }
72
+ await NBIAPI.setConfig(config);
73
+ props.onSave();
74
+ };
75
+ const handleRefreshOllamaModelListClick = async () => {
76
+ await NBIAPI.updateOllamaModelList();
77
+ updateModelOptionsForProvider(chatModelProvider, 'chat');
78
+ };
79
+ const [chatModelProvider, setChatModelProvider] = useState(nbiConfig.chatModel.provider || 'none');
80
+ const [inlineCompletionModelProvider, setInlineCompletionModelProvider] = useState(nbiConfig.inlineCompletionModel.provider || 'none');
81
+ const [defaultChatMode, setDefaultChatMode] = useState(nbiConfig.defaultChatMode);
82
+ const [chatModel, setChatModel] = useState(nbiConfig.chatModel.model);
83
+ const [chatModelProperties, setChatModelProperties] = useState([]);
84
+ const [inlineCompletionModelProperties, setInlineCompletionModelProperties] = useState([]);
85
+ const [inlineCompletionModel, setInlineCompletionModel] = useState(nbiConfig.inlineCompletionModel.model);
86
+ const [storeGitHubAccessToken, setStoreGitHubAccessToken] = useState(nbiConfig.storeGitHubAccessToken);
87
+ const updateModelOptionsForProvider = (providerId, modelType) => {
88
+ if (modelType === 'chat') {
89
+ setChatModelProvider(providerId);
90
+ }
91
+ else {
92
+ setInlineCompletionModelProvider(providerId);
93
+ }
94
+ const models = modelType === 'chat'
95
+ ? nbiConfig.chatModels
96
+ : nbiConfig.inlineCompletionModels;
97
+ const selectedModelId = modelType === 'chat'
98
+ ? nbiConfig.chatModel.model
99
+ : nbiConfig.inlineCompletionModel.model;
100
+ const providerModels = models.filter((model) => model.provider === providerId);
101
+ if (modelType === 'chat') {
102
+ setChatModels(providerModels);
103
+ }
104
+ else {
105
+ setInlineCompletionModels(providerModels);
106
+ }
107
+ let selectedModel = providerModels.find((model) => model.id === selectedModelId);
108
+ if (!selectedModel) {
109
+ selectedModel = providerModels === null || providerModels === void 0 ? void 0 : providerModels[0];
110
+ }
111
+ if (selectedModel) {
112
+ if (modelType === 'chat') {
113
+ setChatModel(selectedModel.id);
114
+ setChatModelProperties(selectedModel.properties);
115
+ }
116
+ else {
117
+ setInlineCompletionModel(selectedModel.id);
118
+ setInlineCompletionModelProperties(selectedModel.properties);
119
+ }
120
+ }
121
+ else {
122
+ if (modelType === 'chat') {
123
+ setChatModelProperties([]);
124
+ }
125
+ else {
126
+ setInlineCompletionModelProperties([]);
127
+ }
128
+ }
129
+ };
130
+ const onModelPropertyChange = (modelType, propertyId, value) => {
131
+ const modelProperties = modelType === 'chat'
132
+ ? chatModelProperties
133
+ : inlineCompletionModelProperties;
134
+ const updatedProperties = modelProperties.map((property) => {
135
+ if (property.id === propertyId) {
136
+ return { ...property, value };
137
+ }
138
+ return property;
139
+ });
140
+ if (modelType === 'chat') {
141
+ setChatModelProperties(updatedProperties);
142
+ }
143
+ else {
144
+ setInlineCompletionModelProperties(updatedProperties);
145
+ }
146
+ };
147
+ useEffect(() => {
148
+ updateModelOptionsForProvider(chatModelProvider, 'chat');
149
+ updateModelOptionsForProvider(inlineCompletionModelProvider, 'inline-completion');
150
+ }, []);
151
+ useEffect(() => {
152
+ handleSaveSettings();
153
+ }, [
154
+ defaultChatMode,
155
+ chatModelProvider,
156
+ chatModel,
157
+ chatModelProperties,
158
+ inlineCompletionModelProvider,
159
+ inlineCompletionModel,
160
+ inlineCompletionModelProperties,
161
+ storeGitHubAccessToken
162
+ ]);
163
+ return (React.createElement("div", { className: "config-dialog" },
164
+ React.createElement("div", { className: "config-dialog-body" },
165
+ React.createElement("div", { className: "model-config-section" },
166
+ React.createElement("div", { className: "model-config-section-header" }, "Default chat mode"),
167
+ React.createElement("div", { className: "model-config-section-body" },
168
+ React.createElement("div", { className: "model-config-section-row" },
169
+ React.createElement("div", { className: "model-config-section-column" },
170
+ React.createElement("div", null,
171
+ React.createElement("select", { className: "jp-mod-styled", value: defaultChatMode, onChange: event => setDefaultChatMode(event.target.value) },
172
+ React.createElement("option", { value: "ask" }, "Ask"),
173
+ React.createElement("option", { value: "agent" }, "Agent")))),
174
+ React.createElement("div", { className: "model-config-section-column" }, " ")))),
175
+ React.createElement("div", { className: "model-config-section" },
176
+ React.createElement("div", { className: "model-config-section-header" }, "Chat model"),
177
+ React.createElement("div", { className: "model-config-section-body" },
178
+ React.createElement("div", { className: "model-config-section-row" },
179
+ React.createElement("div", { className: "model-config-section-column" },
180
+ React.createElement("div", null, "Provider"),
181
+ React.createElement("div", null,
182
+ React.createElement("select", { className: "jp-mod-styled", onChange: event => updateModelOptionsForProvider(event.target.value, 'chat') },
183
+ llmProviders.map((provider, index) => (React.createElement("option", { key: index, value: provider.id, selected: provider.id === chatModelProvider }, provider.name))),
184
+ React.createElement("option", { key: -1, value: "none", selected: chatModelProvider === 'none' ||
185
+ !llmProviders.find(provider => provider.id === chatModelProvider) }, "None")))),
186
+ !['openai-compatible', 'litellm-compatible', 'none'].includes(chatModelProvider) &&
187
+ chatModels.length > 0 && (React.createElement("div", { className: "model-config-section-column" },
188
+ React.createElement("div", null, "Model"),
189
+ ![
190
+ OPENAI_COMPATIBLE_CHAT_MODEL_ID,
191
+ LITELLM_COMPATIBLE_CHAT_MODEL_ID
192
+ ].includes(chatModel) &&
193
+ chatModels.length > 0 && (React.createElement("div", null,
194
+ React.createElement("select", { className: "jp-mod-styled", onChange: event => setChatModel(event.target.value) }, chatModels.map((model, index) => (React.createElement("option", { key: index, value: model.id, selected: model.id === chatModel }, model.name))))))))),
195
+ React.createElement("div", { className: "model-config-section-row" },
196
+ React.createElement("div", { className: "model-config-section-column" }, chatModelProvider === 'ollama' && chatModels.length === 0 && (React.createElement("div", { className: "ollama-warning-message" },
197
+ "No Ollama models found! Make sure",
198
+ ' ',
199
+ React.createElement("a", { href: "https://ollama.com/", target: "_blank" }, "Ollama"),
200
+ ' ',
201
+ "is running and models are downloaded to your computer.",
202
+ ' ',
203
+ React.createElement("a", { href: "javascript:void(0)", onClick: handleRefreshOllamaModelListClick }, "Try again"),
204
+ ' ',
205
+ "once ready.")))),
206
+ React.createElement("div", { className: "model-config-section-row" },
207
+ React.createElement("div", { className: "model-config-section-column" }, chatModelProperties.map((property, index) => (React.createElement("div", { className: "form-field-row", key: index },
208
+ React.createElement("div", { className: "form-field-description" },
209
+ property.name,
210
+ " ",
211
+ property.optional ? '(optional)' : ''),
212
+ React.createElement("input", { name: "chat-model-id-input", placeholder: property.description, className: "jp-mod-styled", spellCheck: false, value: property.value, onChange: event => onModelPropertyChange('chat', property.id, event.target.value) })))))))),
213
+ React.createElement("div", { className: "model-config-section" },
214
+ React.createElement("div", { className: "model-config-section-header" }, "Auto-complete model"),
215
+ React.createElement("div", { className: "model-config-section-body" },
216
+ React.createElement("div", { className: "model-config-section-row" },
217
+ React.createElement("div", { className: "model-config-section-column" },
218
+ React.createElement("div", null, "Provider"),
219
+ React.createElement("div", null,
220
+ React.createElement("select", { className: "jp-mod-styled", onChange: event => updateModelOptionsForProvider(event.target.value, 'inline-completion') },
221
+ llmProviders.map((provider, index) => (React.createElement("option", { key: index, value: provider.id, selected: provider.id === inlineCompletionModelProvider }, provider.name))),
222
+ React.createElement("option", { key: -1, value: "none", selected: inlineCompletionModelProvider === 'none' ||
223
+ !llmProviders.find(provider => provider.id === inlineCompletionModelProvider) }, "None")))),
224
+ !['openai-compatible', 'litellm-compatible', 'none'].includes(inlineCompletionModelProvider) && (React.createElement("div", { className: "model-config-section-column" },
225
+ React.createElement("div", null, "Model"),
226
+ ![
227
+ OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID,
228
+ LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID
229
+ ].includes(inlineCompletionModel) && (React.createElement("div", null,
230
+ React.createElement("select", { className: "jp-mod-styled", onChange: event => setInlineCompletionModel(event.target.value) }, inlineCompletionModels.map((model, index) => (React.createElement("option", { key: index, value: model.id, selected: model.id === inlineCompletionModel }, model.name))))))))),
231
+ React.createElement("div", { className: "model-config-section-row" },
232
+ React.createElement("div", { className: "model-config-section-column" }, inlineCompletionModelProperties.map((property, index) => (React.createElement("div", { className: "form-field-row", key: index },
233
+ React.createElement("div", { className: "form-field-description" },
234
+ property.name,
235
+ " ",
236
+ property.optional ? '(optional)' : ''),
237
+ React.createElement("input", { name: "inline-completion-model-id-input", placeholder: property.description, className: "jp-mod-styled", spellCheck: false, value: property.value, onChange: event => onModelPropertyChange('inline-completion', property.id, event.target.value) })))))))),
238
+ (chatModelProvider === 'github-copilot' ||
239
+ inlineCompletionModelProvider === 'github-copilot') && (React.createElement("div", { className: "model-config-section" },
240
+ React.createElement("div", { className: "model-config-section-header access-token-config-header" },
241
+ "GitHub Copilot login",
242
+ ' ',
243
+ React.createElement("a", { href: "https://github.com/notebook-intelligence/notebook-intelligence/blob/main/README.md#remembering-github-copilot-login", target: "_blank" },
244
+ ' ',
245
+ React.createElement(VscWarning, { className: "access-token-warning", title: "Click to learn more about security implications" }))),
246
+ React.createElement("div", { className: "model-config-section-body" },
247
+ React.createElement("div", { className: "model-config-section-row" },
248
+ React.createElement("div", { className: "model-config-section-column" },
249
+ React.createElement("label", null,
250
+ React.createElement("input", { type: "checkbox", checked: storeGitHubAccessToken, onChange: event => {
251
+ setStoreGitHubAccessToken(event.target.checked);
252
+ } }),
253
+ "Remember my GitHub Copilot access token")))))),
254
+ React.createElement("div", { className: "model-config-section" },
255
+ React.createElement("div", { className: "model-config-section-header" }, "Config file path"),
256
+ React.createElement("div", { className: "model-config-section-body" },
257
+ React.createElement("div", { className: "model-config-section-row" },
258
+ React.createElement("div", { className: "model-config-section-column" },
259
+ React.createElement("span", { className: "user-code-span", onClick: () => {
260
+ navigator.clipboard.writeText(path.join(NBIAPI.config.userConfigDir, 'config.json'));
261
+ return true;
262
+ } },
263
+ path.join(NBIAPI.config.userConfigDir, 'config.json'),
264
+ ' ',
265
+ React.createElement("span", { className: "copy-icon", dangerouslySetInnerHTML: { __html: copySvgstr } })))))))));
266
+ }
267
+ function SettingsPanelComponentMCPServers(props) {
268
+ const nbiConfig = NBIAPI.config;
269
+ const mcpServersRef = useRef(nbiConfig.toolConfig.mcpServers);
270
+ const mcpServerSettingsRef = useRef(nbiConfig.mcpServerSettings);
271
+ const [renderCount, setRenderCount] = useState(1);
272
+ const [mcpServerEnabledState, setMCPServerEnabledState] = useState(new Map(mcpServerSettingsToEnabledState(mcpServersRef.current, mcpServerSettingsRef.current)));
273
+ const mcpServerEnabledStateToMcpServerSettings = () => {
274
+ const mcpServerSettings = {};
275
+ for (const mcpServer of mcpServersRef.current) {
276
+ if (mcpServerEnabledState.has(mcpServer.id)) {
277
+ const disabledTools = [];
278
+ for (const tool of mcpServer.tools) {
279
+ if (!mcpServerEnabledState.get(mcpServer.id).has(tool.name)) {
280
+ disabledTools.push(tool.name);
281
+ }
282
+ }
283
+ mcpServerSettings[mcpServer.id] = {
284
+ disabled: false,
285
+ disabled_tools: disabledTools
286
+ };
287
+ }
288
+ else {
289
+ mcpServerSettings[mcpServer.id] = { disabled: true };
290
+ }
291
+ }
292
+ return mcpServerSettings;
293
+ };
294
+ const syncSettingsToServerState = () => {
295
+ NBIAPI.setConfig({
296
+ mcp_server_settings: mcpServerSettingsRef.current
297
+ });
298
+ };
299
+ const handleReloadMCPServersClick = async () => {
300
+ await NBIAPI.reloadMCPServers();
301
+ };
302
+ useEffect(() => {
303
+ syncSettingsToServerState();
304
+ }, [mcpServerSettingsRef.current]);
305
+ useEffect(() => {
306
+ mcpServerSettingsRef.current = mcpServerEnabledStateToMcpServerSettings();
307
+ setRenderCount(renderCount => renderCount + 1);
308
+ }, [mcpServerEnabledState]);
309
+ const setMCPServerEnabled = (serverId, enabled) => {
310
+ var _a;
311
+ const currentState = new Map(mcpServerEnabledState);
312
+ if (enabled) {
313
+ if (!(serverId in currentState)) {
314
+ currentState.set(serverId, new Set((_a = mcpServersRef.current
315
+ .find((server) => server.id === serverId)) === null || _a === void 0 ? void 0 : _a.tools.map((tool) => tool.name)));
316
+ }
317
+ }
318
+ else {
319
+ currentState.delete(serverId);
320
+ }
321
+ setMCPServerEnabledState(currentState);
322
+ };
323
+ const getMCPServerEnabled = (serverId) => {
324
+ return mcpServerEnabledState.has(serverId);
325
+ };
326
+ const getMCPServerToolEnabled = (serverId, toolName) => {
327
+ return (mcpServerEnabledState.has(serverId) &&
328
+ mcpServerEnabledState.get(serverId).has(toolName));
329
+ };
330
+ const setMCPServerToolEnabled = (serverId, toolName, enabled) => {
331
+ const currentState = new Map(mcpServerEnabledState);
332
+ const serverState = currentState.get(serverId);
333
+ if (enabled) {
334
+ serverState.add(toolName);
335
+ }
336
+ else {
337
+ serverState.delete(toolName);
338
+ }
339
+ setMCPServerEnabledState(currentState);
340
+ };
341
+ useEffect(() => {
342
+ NBIAPI.configChanged.connect(() => {
343
+ mcpServersRef.current = nbiConfig.toolConfig.mcpServers;
344
+ mcpServerSettingsRef.current = nbiConfig.mcpServerSettings;
345
+ setRenderCount(renderCount => renderCount + 1);
346
+ });
347
+ }, []);
348
+ return (React.createElement("div", { className: "config-dialog" },
349
+ React.createElement("div", { className: "config-dialog-body" },
350
+ React.createElement("div", { className: "model-config-section" },
351
+ React.createElement("div", { className: "model-config-section-header", style: { display: 'flex' } },
352
+ React.createElement("div", { style: { flexGrow: 1 } }, "MCP Servers"),
353
+ React.createElement("div", null,
354
+ React.createElement("button", { className: "jp-toast-button jp-mod-small jp-Button", onClick: handleReloadMCPServersClick },
355
+ React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Reload")))),
356
+ React.createElement("div", { className: "model-config-section-body" },
357
+ mcpServersRef.current.length === 0 && renderCount > 0 && (React.createElement("div", { className: "model-config-section-row" },
358
+ React.createElement("div", { className: "model-config-section-column" },
359
+ React.createElement("div", null, "No MCP servers found. Add MCP servers in the configuration file.")))),
360
+ mcpServersRef.current.length > 0 && renderCount > 0 && (React.createElement("div", { className: "model-config-section-row" },
361
+ React.createElement("div", { className: "model-config-section-column" }, mcpServersRef.current.map((server) => (React.createElement("div", { key: server.id },
362
+ React.createElement("div", { style: { display: 'flex', alignItems: 'center' } },
363
+ React.createElement(CheckBoxItem, { header: true, label: server.id, checked: getMCPServerEnabled(server.id), onClick: () => {
364
+ setMCPServerEnabled(server.id, !getMCPServerEnabled(server.id));
365
+ } }),
366
+ React.createElement("div", { className: `server-status-indicator ${server.status}`, title: server.status })),
367
+ getMCPServerEnabled(server.id) && (React.createElement("div", null, server.tools.map((tool) => (React.createElement(PillItem, { label: tool.name, title: tool.description, checked: getMCPServerToolEnabled(server.id, tool.name), onClick: () => {
368
+ setMCPServerToolEnabled(server.id, tool.name, !getMCPServerToolEnabled(server.id, tool.name));
369
+ } }))))))))))),
370
+ React.createElement("div", { className: "model-config-section-row" },
371
+ React.createElement("div", { className: "model-config-section-column", style: { flexGrow: 'initial' } },
372
+ React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", style: { width: 'max-content' }, onClick: props.onEditMCPConfigClicked },
373
+ React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Add / Edit")))))))));
374
+ }
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
2
  import { IDocumentManager } from '@jupyterlab/docmanager';
3
- import { Dialog, ICommandPalette } from '@jupyterlab/apputils';
3
+ import { Dialog, ICommandPalette, MainAreaWidget } from '@jupyterlab/apputils';
4
4
  import { IMainMenu } from '@jupyterlab/mainmenu';
5
5
  import { IEditorLanguageRegistry } from '@jupyterlab/codemirror';
6
6
  import { CodeCell } from '@jupyterlab/cells';
@@ -14,7 +14,7 @@ import { LabIcon } from '@jupyterlab/ui-components';
14
14
  import { Menu, Panel, Widget } from '@lumino/widgets';
15
15
  import { CommandRegistry } from '@lumino/commands';
16
16
  import { IStatusBar } from '@jupyterlab/statusbar';
17
- import { ChatSidebar, ConfigurationDialogBody, GitHubCopilotLoginDialogBody, GitHubCopilotStatusBarItem, InlinePromptWidget, RunChatCompletionType } from './chat-sidebar';
17
+ import { ChatSidebar, GitHubCopilotLoginDialogBody, GitHubCopilotStatusBarItem, InlinePromptWidget, RunChatCompletionType } from './chat-sidebar';
18
18
  import { NBIAPI, GitHubCopilotLoginStatus } from './api';
19
19
  import { BackendMessageType, GITHUB_COPILOT_PROVIDER_ID, INotebookIntelligence, RequestDataType, TelemetryEventType } from './tokens';
20
20
  import sparklesSvgstr from '../style/icons/sparkles.svg';
@@ -22,6 +22,7 @@ import copilotSvgstr from '../style/icons/copilot.svg';
22
22
  import { applyCodeToSelectionInEditor, cellOutputAsText, compareSelections, extractLLMGeneratedCode, getSelectionInEditor, getTokenCount, getWholeNotebookContent, isSelectionEmpty, markdownToComment, waitForDuration } from './utils';
23
23
  import { UUID } from '@lumino/coreutils';
24
24
  import * as path from 'path';
25
+ import { SettingsPanel } from './components/settings-panel';
25
26
  var CommandIDs;
26
27
  (function (CommandIDs) {
27
28
  CommandIDs.chatuserInput = 'notebook-intelligence:chat-user-input';
@@ -922,28 +923,32 @@ const plugin = {
922
923
  dialog.launch();
923
924
  }
924
925
  });
926
+ const createNewSettingsWidget = () => {
927
+ const settingsPanel = new SettingsPanel({
928
+ onSave: () => {
929
+ NBIAPI.fetchCapabilities();
930
+ },
931
+ onEditMCPConfigClicked: () => {
932
+ app.commands.execute('notebook-intelligence:open-mcp-config-editor');
933
+ }
934
+ });
935
+ const widget = new MainAreaWidget({ content: settingsPanel });
936
+ widget.id = 'nbi-settings';
937
+ widget.title.label = 'NBI Settings';
938
+ widget.title.closable = true;
939
+ return widget;
940
+ };
941
+ let settingsWidget = createNewSettingsWidget();
925
942
  app.commands.addCommand(CommandIDs.openConfigurationDialog, {
926
943
  label: 'Notebook Intelligence Settings',
927
944
  execute: args => {
928
- let dialog = null;
929
- const dialogBody = new ConfigurationDialogBody({
930
- onSave: () => {
931
- dialog === null || dialog === void 0 ? void 0 : dialog.dispose();
932
- NBIAPI.fetchCapabilities();
933
- },
934
- onEditMCPConfigClicked: () => {
935
- dialog === null || dialog === void 0 ? void 0 : dialog.dispose();
936
- app.commands.execute('notebook-intelligence:open-mcp-config-editor');
937
- }
938
- });
939
- dialog = new Dialog({
940
- title: 'Notebook Intelligence Settings',
941
- hasClose: true,
942
- body: dialogBody,
943
- buttons: []
944
- });
945
- dialog.node.classList.add('config-dialog-container');
946
- dialog.launch();
945
+ if (settingsWidget.isDisposed) {
946
+ settingsWidget = createNewSettingsWidget();
947
+ }
948
+ if (!settingsWidget.isAttached) {
949
+ app.shell.add(settingsWidget, 'main');
950
+ }
951
+ app.shell.activateById(settingsWidget.id);
947
952
  }
948
953
  });
949
954
  app.commands.addCommand(CommandIDs.openMCPConfigEditor, {
package/lib/tokens.d.ts CHANGED
@@ -26,7 +26,8 @@ export declare enum BackendMessageType {
26
26
  StreamMessage = "stream-message",
27
27
  StreamEnd = "stream-end",
28
28
  RunUICommand = "run-ui-command",
29
- GitHubCopilotLoginStatusChange = "github-copilot-login-status-change"
29
+ GitHubCopilotLoginStatusChange = "github-copilot-login-status-change",
30
+ MCPServerStatusChange = "mcp-server-status-change"
30
31
  }
31
32
  export declare enum ResponseStreamDataType {
32
33
  LLMRaw = "llm-raw",
@@ -43,6 +44,15 @@ export declare enum ContextType {
43
44
  Custom = "custom",
44
45
  CurrentFile = "current-file"
45
46
  }
47
+ export declare enum MCPServerStatus {
48
+ NotConnected = "not-connected",
49
+ Connecting = "connecting",
50
+ Disconnecting = "disconnecting",
51
+ FailedToConnect = "failed-to-connect",
52
+ Connected = "connected",
53
+ UpdatingToolList = "updating-tool-list",
54
+ UpdatedToolList = "updated-tool-list"
55
+ }
46
56
  export interface IContextItem {
47
57
  type: ContextType;
48
58
  content: string;
package/lib/tokens.js CHANGED
@@ -17,6 +17,7 @@ export var BackendMessageType;
17
17
  BackendMessageType["StreamEnd"] = "stream-end";
18
18
  BackendMessageType["RunUICommand"] = "run-ui-command";
19
19
  BackendMessageType["GitHubCopilotLoginStatusChange"] = "github-copilot-login-status-change";
20
+ BackendMessageType["MCPServerStatusChange"] = "mcp-server-status-change";
20
21
  })(BackendMessageType || (BackendMessageType = {}));
21
22
  export var ResponseStreamDataType;
22
23
  (function (ResponseStreamDataType) {
@@ -35,6 +36,16 @@ export var ContextType;
35
36
  ContextType["Custom"] = "custom";
36
37
  ContextType["CurrentFile"] = "current-file";
37
38
  })(ContextType || (ContextType = {}));
39
+ export var MCPServerStatus;
40
+ (function (MCPServerStatus) {
41
+ MCPServerStatus["NotConnected"] = "not-connected";
42
+ MCPServerStatus["Connecting"] = "connecting";
43
+ MCPServerStatus["Disconnecting"] = "disconnecting";
44
+ MCPServerStatus["FailedToConnect"] = "failed-to-connect";
45
+ MCPServerStatus["Connected"] = "connected";
46
+ MCPServerStatus["UpdatingToolList"] = "updating-tool-list";
47
+ MCPServerStatus["UpdatedToolList"] = "updated-tool-list";
48
+ })(MCPServerStatus || (MCPServerStatus = {}));
38
49
  export var BuiltinToolsetType;
39
50
  (function (BuiltinToolsetType) {
40
51
  BuiltinToolsetType["NotebookEdit"] = "nbi-notebook-edit";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notebook-intelligence/notebook-intelligence",
3
- "version": "2.4.1",
3
+ "version": "2.5.0",
4
4
  "description": "AI coding assistant for JupyterLab",
5
5
  "keywords": [
6
6
  "AI",
package/src/api.ts CHANGED
@@ -76,6 +76,10 @@ export class NBIConfig {
76
76
  return this.capabilities.tool_config;
77
77
  }
78
78
 
79
+ get mcpServerSettings(): any {
80
+ return this.capabilities.mcp_server_settings;
81
+ }
82
+
79
83
  capabilities: any = {};
80
84
  chatParticipants: IChatParticipant[] = [];
81
85
 
@@ -102,7 +106,11 @@ export class NBIAPI {
102
106
 
103
107
  this._messageReceived.connect((_, msg) => {
104
108
  msg = JSON.parse(msg);
105
- if (msg.type === BackendMessageType.GitHubCopilotLoginStatusChange) {
109
+ if (msg.type === BackendMessageType.MCPServerStatusChange) {
110
+ this.fetchCapabilities();
111
+ } else if (
112
+ msg.type === BackendMessageType.GitHubCopilotLoginStatusChange
113
+ ) {
106
114
  this.updateGitHubLoginStatus().then(() => {
107
115
  this.githubLoginStatusChanged.emit();
108
116
  });
@@ -201,11 +209,21 @@ export class NBIAPI {
201
209
  return new Promise<void>((resolve, reject) => {
202
210
  requestAPI<any>('capabilities', { method: 'GET' })
203
211
  .then(data => {
212
+ const oldConfig = {
213
+ capabilities: structuredClone(this.config.capabilities),
214
+ chatParticipants: structuredClone(this.config.chatParticipants)
215
+ };
204
216
  this.config.capabilities = structuredClone(data);
205
217
  this.config.chatParticipants = structuredClone(
206
218
  data.chat_participants
207
219
  );
208
- this.configChanged.emit();
220
+ const newConfig = {
221
+ capabilities: structuredClone(this.config.capabilities),
222
+ chatParticipants: structuredClone(this.config.chatParticipants)
223
+ };
224
+ if (JSON.stringify(newConfig) !== JSON.stringify(oldConfig)) {
225
+ this.configChanged.emit();
226
+ }
209
227
  resolve();
210
228
  })
211
229
  .catch(reason => {
@@ -245,20 +263,6 @@ export class NBIAPI {
245
263
  });
246
264
  }
247
265
 
248
- static async reloadMCPServerList(): Promise<any> {
249
- return new Promise<any>((resolve, reject) => {
250
- requestAPI<any>('reload-mcp-servers', { method: 'POST' })
251
- .then(async data => {
252
- await NBIAPI.fetchCapabilities();
253
- resolve(data);
254
- })
255
- .catch(reason => {
256
- console.error(`Failed to reload MCP server list.\n${reason}`);
257
- reject(reason);
258
- });
259
- });
260
- }
261
-
262
266
  static async getMCPConfigFile(): Promise<any> {
263
267
  return new Promise<any>((resolve, reject) => {
264
268
  requestAPI<any>('mcp-config-file', { method: 'GET' })
@@ -322,6 +326,20 @@ export class NBIAPI {
322
326
  );
323
327
  }
324
328
 
329
+ static async reloadMCPServers(): Promise<any> {
330
+ return new Promise<any>((resolve, reject) => {
331
+ requestAPI<any>('reload-mcp-servers', { method: 'POST' })
332
+ .then(async data => {
333
+ await NBIAPI.fetchCapabilities();
334
+ resolve(data);
335
+ })
336
+ .catch(reason => {
337
+ console.error(`Failed to reload MCP servers.\n${reason}`);
338
+ reject(reason);
339
+ });
340
+ });
341
+ }
342
+
325
343
  static async generateCode(
326
344
  chatId: string,
327
345
  prompt: string,