@notebook-intelligence/notebook-intelligence 2.4.2 → 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.
- package/README.md +17 -111
- package/lib/api.d.ts +2 -1
- package/lib/api.js +31 -15
- package/lib/chat-sidebar.d.ts +0 -9
- package/lib/chat-sidebar.js +58 -293
- package/lib/components/checkbox.d.ts +2 -0
- package/lib/components/checkbox.js +11 -0
- package/lib/components/mcp-util.d.ts +2 -0
- package/lib/components/mcp-util.js +37 -0
- package/lib/components/pill.d.ts +2 -0
- package/lib/components/pill.js +5 -0
- package/lib/components/settings-panel.d.ts +11 -0
- package/lib/components/settings-panel.js +374 -0
- package/lib/index.js +26 -21
- package/lib/tokens.d.ts +11 -1
- package/lib/tokens.js +11 -0
- package/package.json +1 -1
- package/src/api.ts +34 -16
- package/src/chat-sidebar.tsx +159 -671
- package/src/components/checkbox.tsx +29 -0
- package/src/components/mcp-util.ts +53 -0
- package/src/components/pill.tsx +15 -0
- package/src/components/settings-panel.tsx +770 -0
- package/src/index.ts +29 -24
- package/src/tokens.ts +12 -1
- package/style/base.css +77 -2
|
@@ -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,
|
|
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
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
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.
|
|
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
|
-
|
|
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,
|