@notebook-intelligence/notebook-intelligence 2.4.2 → 2.6.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 +114 -95
- package/lib/api.d.ts +8 -1
- package/lib/api.js +59 -15
- package/lib/chat-sidebar.d.ts +7 -5
- package/lib/chat-sidebar.js +144 -331
- 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 +82 -22
- package/lib/tokens.d.ts +13 -1
- package/lib/tokens.js +13 -0
- package/package.json +1 -1
- package/src/api.ts +76 -16
- package/src/chat-sidebar.tsx +286 -700
- 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 +93 -25
- package/src/tokens.ts +14 -1
- package/style/base.css +103 -2
- package/style/icons/sparkles-warning.svg +5 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { ReactWidget } from '@jupyterlab/apputils';
|
|
5
|
+
import { VscWarning } from 'react-icons/vsc';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
import copySvgstr from '../../style/icons/copy.svg';
|
|
9
|
+
import { NBIAPI } from '../api';
|
|
10
|
+
import { CheckBoxItem } from './checkbox';
|
|
11
|
+
import { PillItem } from './pill';
|
|
12
|
+
import { mcpServerSettingsToEnabledState } from './mcp-util';
|
|
13
|
+
|
|
14
|
+
const OPENAI_COMPATIBLE_CHAT_MODEL_ID = 'openai-compatible-chat-model';
|
|
15
|
+
const LITELLM_COMPATIBLE_CHAT_MODEL_ID = 'litellm-compatible-chat-model';
|
|
16
|
+
const OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID =
|
|
17
|
+
'openai-compatible-inline-completion-model';
|
|
18
|
+
const LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID =
|
|
19
|
+
'litellm-compatible-inline-completion-model';
|
|
20
|
+
|
|
21
|
+
export class SettingsPanel extends ReactWidget {
|
|
22
|
+
constructor(options: {
|
|
23
|
+
onSave: () => void;
|
|
24
|
+
onEditMCPConfigClicked: () => void;
|
|
25
|
+
}) {
|
|
26
|
+
super();
|
|
27
|
+
|
|
28
|
+
this._onSave = options.onSave;
|
|
29
|
+
this._onEditMCPConfigClicked = options.onEditMCPConfigClicked;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
render(): JSX.Element {
|
|
33
|
+
return (
|
|
34
|
+
<SettingsPanelComponent
|
|
35
|
+
onSave={this._onSave}
|
|
36
|
+
onEditMCPConfigClicked={this._onEditMCPConfigClicked}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private _onSave: () => void;
|
|
42
|
+
private _onEditMCPConfigClicked: () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function SettingsPanelComponent(props: any) {
|
|
46
|
+
const [activeTab, setActiveTab] = useState('general');
|
|
47
|
+
|
|
48
|
+
const onTabSelected = (tab: string) => {
|
|
49
|
+
setActiveTab(tab);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="nbi-settings-panel">
|
|
54
|
+
<div className="nbi-settings-panel-tabs">
|
|
55
|
+
<SettingsPanelTabsComponent
|
|
56
|
+
onTabSelected={onTabSelected}
|
|
57
|
+
activeTab={activeTab}
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
<div className="nbi-settings-panel-tab-content">
|
|
61
|
+
{activeTab === 'general' && (
|
|
62
|
+
<SettingsPanelComponentGeneral
|
|
63
|
+
onSave={props.onSave}
|
|
64
|
+
onEditMCPConfigClicked={props.onEditMCPConfigClicked}
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
67
|
+
{activeTab === 'mcp-servers' && (
|
|
68
|
+
<SettingsPanelComponentMCPServers
|
|
69
|
+
onEditMCPConfigClicked={props.onEditMCPConfigClicked}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function SettingsPanelTabsComponent(props: any) {
|
|
78
|
+
const [activeTab, setActiveTab] = useState(props.activeTab);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div>
|
|
82
|
+
<div
|
|
83
|
+
className={`nbi-settings-panel-tab ${activeTab === 'general' ? 'active' : ''}`}
|
|
84
|
+
onClick={() => {
|
|
85
|
+
setActiveTab('general');
|
|
86
|
+
props.onTabSelected('general');
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
General
|
|
90
|
+
</div>
|
|
91
|
+
<div
|
|
92
|
+
className={`nbi-settings-panel-tab ${activeTab === 'mcp-servers' ? 'active' : ''}`}
|
|
93
|
+
onClick={() => {
|
|
94
|
+
setActiveTab('mcp-servers');
|
|
95
|
+
props.onTabSelected('mcp-servers');
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
MCP Servers
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function SettingsPanelComponentGeneral(props: any) {
|
|
105
|
+
const nbiConfig = NBIAPI.config;
|
|
106
|
+
const llmProviders = nbiConfig.llmProviders;
|
|
107
|
+
const [chatModels, setChatModels] = useState([]);
|
|
108
|
+
const [inlineCompletionModels, setInlineCompletionModels] = useState([]);
|
|
109
|
+
|
|
110
|
+
const handleSaveSettings = async () => {
|
|
111
|
+
const config: any = {
|
|
112
|
+
default_chat_mode: defaultChatMode,
|
|
113
|
+
chat_model: {
|
|
114
|
+
provider: chatModelProvider,
|
|
115
|
+
model: chatModel,
|
|
116
|
+
properties: chatModelProperties
|
|
117
|
+
},
|
|
118
|
+
inline_completion_model: {
|
|
119
|
+
provider: inlineCompletionModelProvider,
|
|
120
|
+
model: inlineCompletionModel,
|
|
121
|
+
properties: inlineCompletionModelProperties
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
chatModelProvider === 'github-copilot' ||
|
|
127
|
+
inlineCompletionModelProvider === 'github-copilot'
|
|
128
|
+
) {
|
|
129
|
+
config.store_github_access_token = storeGitHubAccessToken;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await NBIAPI.setConfig(config);
|
|
133
|
+
|
|
134
|
+
props.onSave();
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const handleRefreshOllamaModelListClick = async () => {
|
|
138
|
+
await NBIAPI.updateOllamaModelList();
|
|
139
|
+
updateModelOptionsForProvider(chatModelProvider, 'chat');
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const [chatModelProvider, setChatModelProvider] = useState(
|
|
143
|
+
nbiConfig.chatModel.provider || 'none'
|
|
144
|
+
);
|
|
145
|
+
const [inlineCompletionModelProvider, setInlineCompletionModelProvider] =
|
|
146
|
+
useState(nbiConfig.inlineCompletionModel.provider || 'none');
|
|
147
|
+
const [defaultChatMode, setDefaultChatMode] = useState<string>(
|
|
148
|
+
nbiConfig.defaultChatMode
|
|
149
|
+
);
|
|
150
|
+
const [chatModel, setChatModel] = useState<string>(nbiConfig.chatModel.model);
|
|
151
|
+
const [chatModelProperties, setChatModelProperties] = useState<any[]>([]);
|
|
152
|
+
const [inlineCompletionModelProperties, setInlineCompletionModelProperties] =
|
|
153
|
+
useState<any[]>([]);
|
|
154
|
+
const [inlineCompletionModel, setInlineCompletionModel] = useState(
|
|
155
|
+
nbiConfig.inlineCompletionModel.model
|
|
156
|
+
);
|
|
157
|
+
const [storeGitHubAccessToken, setStoreGitHubAccessToken] = useState(
|
|
158
|
+
nbiConfig.storeGitHubAccessToken
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const updateModelOptionsForProvider = (
|
|
162
|
+
providerId: string,
|
|
163
|
+
modelType: 'chat' | 'inline-completion'
|
|
164
|
+
) => {
|
|
165
|
+
if (modelType === 'chat') {
|
|
166
|
+
setChatModelProvider(providerId);
|
|
167
|
+
} else {
|
|
168
|
+
setInlineCompletionModelProvider(providerId);
|
|
169
|
+
}
|
|
170
|
+
const models =
|
|
171
|
+
modelType === 'chat'
|
|
172
|
+
? nbiConfig.chatModels
|
|
173
|
+
: nbiConfig.inlineCompletionModels;
|
|
174
|
+
const selectedModelId =
|
|
175
|
+
modelType === 'chat'
|
|
176
|
+
? nbiConfig.chatModel.model
|
|
177
|
+
: nbiConfig.inlineCompletionModel.model;
|
|
178
|
+
|
|
179
|
+
const providerModels = models.filter(
|
|
180
|
+
(model: any) => model.provider === providerId
|
|
181
|
+
);
|
|
182
|
+
if (modelType === 'chat') {
|
|
183
|
+
setChatModels(providerModels);
|
|
184
|
+
} else {
|
|
185
|
+
setInlineCompletionModels(providerModels);
|
|
186
|
+
}
|
|
187
|
+
let selectedModel = providerModels.find(
|
|
188
|
+
(model: any) => model.id === selectedModelId
|
|
189
|
+
);
|
|
190
|
+
if (!selectedModel) {
|
|
191
|
+
selectedModel = providerModels?.[0];
|
|
192
|
+
}
|
|
193
|
+
if (selectedModel) {
|
|
194
|
+
if (modelType === 'chat') {
|
|
195
|
+
setChatModel(selectedModel.id);
|
|
196
|
+
setChatModelProperties(selectedModel.properties);
|
|
197
|
+
} else {
|
|
198
|
+
setInlineCompletionModel(selectedModel.id);
|
|
199
|
+
setInlineCompletionModelProperties(selectedModel.properties);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
if (modelType === 'chat') {
|
|
203
|
+
setChatModelProperties([]);
|
|
204
|
+
} else {
|
|
205
|
+
setInlineCompletionModelProperties([]);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const onModelPropertyChange = (
|
|
211
|
+
modelType: 'chat' | 'inline-completion',
|
|
212
|
+
propertyId: string,
|
|
213
|
+
value: string
|
|
214
|
+
) => {
|
|
215
|
+
const modelProperties =
|
|
216
|
+
modelType === 'chat'
|
|
217
|
+
? chatModelProperties
|
|
218
|
+
: inlineCompletionModelProperties;
|
|
219
|
+
const updatedProperties = modelProperties.map((property: any) => {
|
|
220
|
+
if (property.id === propertyId) {
|
|
221
|
+
return { ...property, value };
|
|
222
|
+
}
|
|
223
|
+
return property;
|
|
224
|
+
});
|
|
225
|
+
if (modelType === 'chat') {
|
|
226
|
+
setChatModelProperties(updatedProperties);
|
|
227
|
+
} else {
|
|
228
|
+
setInlineCompletionModelProperties(updatedProperties);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
updateModelOptionsForProvider(chatModelProvider, 'chat');
|
|
234
|
+
updateModelOptionsForProvider(
|
|
235
|
+
inlineCompletionModelProvider,
|
|
236
|
+
'inline-completion'
|
|
237
|
+
);
|
|
238
|
+
}, []);
|
|
239
|
+
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
handleSaveSettings();
|
|
242
|
+
}, [
|
|
243
|
+
defaultChatMode,
|
|
244
|
+
chatModelProvider,
|
|
245
|
+
chatModel,
|
|
246
|
+
chatModelProperties,
|
|
247
|
+
inlineCompletionModelProvider,
|
|
248
|
+
inlineCompletionModel,
|
|
249
|
+
inlineCompletionModelProperties,
|
|
250
|
+
storeGitHubAccessToken
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div className="config-dialog">
|
|
255
|
+
<div className="config-dialog-body">
|
|
256
|
+
<div className="model-config-section">
|
|
257
|
+
<div className="model-config-section-header">Default chat mode</div>
|
|
258
|
+
<div className="model-config-section-body">
|
|
259
|
+
<div className="model-config-section-row">
|
|
260
|
+
<div className="model-config-section-column">
|
|
261
|
+
<div>
|
|
262
|
+
<select
|
|
263
|
+
className="jp-mod-styled"
|
|
264
|
+
value={defaultChatMode}
|
|
265
|
+
onChange={event => setDefaultChatMode(event.target.value)}
|
|
266
|
+
>
|
|
267
|
+
<option value="ask">Ask</option>
|
|
268
|
+
<option value="agent">Agent</option>
|
|
269
|
+
</select>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
<div className="model-config-section-column"> </div>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div className="model-config-section">
|
|
278
|
+
<div className="model-config-section-header">Chat model</div>
|
|
279
|
+
<div className="model-config-section-body">
|
|
280
|
+
<div className="model-config-section-row">
|
|
281
|
+
<div className="model-config-section-column">
|
|
282
|
+
<div>Provider</div>
|
|
283
|
+
<div>
|
|
284
|
+
<select
|
|
285
|
+
className="jp-mod-styled"
|
|
286
|
+
onChange={event =>
|
|
287
|
+
updateModelOptionsForProvider(event.target.value, 'chat')
|
|
288
|
+
}
|
|
289
|
+
>
|
|
290
|
+
{llmProviders.map((provider: any, index: number) => (
|
|
291
|
+
<option
|
|
292
|
+
key={index}
|
|
293
|
+
value={provider.id}
|
|
294
|
+
selected={provider.id === chatModelProvider}
|
|
295
|
+
>
|
|
296
|
+
{provider.name}
|
|
297
|
+
</option>
|
|
298
|
+
))}
|
|
299
|
+
<option
|
|
300
|
+
key={-1}
|
|
301
|
+
value="none"
|
|
302
|
+
selected={
|
|
303
|
+
chatModelProvider === 'none' ||
|
|
304
|
+
!llmProviders.find(
|
|
305
|
+
provider => provider.id === chatModelProvider
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
>
|
|
309
|
+
None
|
|
310
|
+
</option>
|
|
311
|
+
</select>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
{!['openai-compatible', 'litellm-compatible', 'none'].includes(
|
|
315
|
+
chatModelProvider
|
|
316
|
+
) &&
|
|
317
|
+
chatModels.length > 0 && (
|
|
318
|
+
<div className="model-config-section-column">
|
|
319
|
+
<div>Model</div>
|
|
320
|
+
{![
|
|
321
|
+
OPENAI_COMPATIBLE_CHAT_MODEL_ID,
|
|
322
|
+
LITELLM_COMPATIBLE_CHAT_MODEL_ID
|
|
323
|
+
].includes(chatModel) &&
|
|
324
|
+
chatModels.length > 0 && (
|
|
325
|
+
<div>
|
|
326
|
+
<select
|
|
327
|
+
className="jp-mod-styled"
|
|
328
|
+
onChange={event => setChatModel(event.target.value)}
|
|
329
|
+
>
|
|
330
|
+
{chatModels.map((model: any, index: number) => (
|
|
331
|
+
<option
|
|
332
|
+
key={index}
|
|
333
|
+
value={model.id}
|
|
334
|
+
selected={model.id === chatModel}
|
|
335
|
+
>
|
|
336
|
+
{model.name}
|
|
337
|
+
</option>
|
|
338
|
+
))}
|
|
339
|
+
</select>
|
|
340
|
+
</div>
|
|
341
|
+
)}
|
|
342
|
+
</div>
|
|
343
|
+
)}
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
<div className="model-config-section-row">
|
|
347
|
+
<div className="model-config-section-column">
|
|
348
|
+
{chatModelProvider === 'ollama' && chatModels.length === 0 && (
|
|
349
|
+
<div className="ollama-warning-message">
|
|
350
|
+
No Ollama models found! Make sure{' '}
|
|
351
|
+
<a href="https://ollama.com/" target="_blank">
|
|
352
|
+
Ollama
|
|
353
|
+
</a>{' '}
|
|
354
|
+
is running and models are downloaded to your computer.{' '}
|
|
355
|
+
<a
|
|
356
|
+
href="javascript:void(0)"
|
|
357
|
+
onClick={handleRefreshOllamaModelListClick}
|
|
358
|
+
>
|
|
359
|
+
Try again
|
|
360
|
+
</a>{' '}
|
|
361
|
+
once ready.
|
|
362
|
+
</div>
|
|
363
|
+
)}
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
|
|
367
|
+
<div className="model-config-section-row">
|
|
368
|
+
<div className="model-config-section-column">
|
|
369
|
+
{chatModelProperties.map((property: any, index: number) => (
|
|
370
|
+
<div className="form-field-row" key={index}>
|
|
371
|
+
<div className="form-field-description">
|
|
372
|
+
{property.name} {property.optional ? '(optional)' : ''}
|
|
373
|
+
</div>
|
|
374
|
+
<input
|
|
375
|
+
name="chat-model-id-input"
|
|
376
|
+
placeholder={property.description}
|
|
377
|
+
className="jp-mod-styled"
|
|
378
|
+
spellCheck={false}
|
|
379
|
+
value={property.value}
|
|
380
|
+
onChange={event =>
|
|
381
|
+
onModelPropertyChange(
|
|
382
|
+
'chat',
|
|
383
|
+
property.id,
|
|
384
|
+
event.target.value
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
/>
|
|
388
|
+
</div>
|
|
389
|
+
))}
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<div className="model-config-section">
|
|
396
|
+
<div className="model-config-section-header">Auto-complete model</div>
|
|
397
|
+
<div className="model-config-section-body">
|
|
398
|
+
<div className="model-config-section-row">
|
|
399
|
+
<div className="model-config-section-column">
|
|
400
|
+
<div>Provider</div>
|
|
401
|
+
<div>
|
|
402
|
+
<select
|
|
403
|
+
className="jp-mod-styled"
|
|
404
|
+
onChange={event =>
|
|
405
|
+
updateModelOptionsForProvider(
|
|
406
|
+
event.target.value,
|
|
407
|
+
'inline-completion'
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
>
|
|
411
|
+
{llmProviders.map((provider: any, index: number) => (
|
|
412
|
+
<option
|
|
413
|
+
key={index}
|
|
414
|
+
value={provider.id}
|
|
415
|
+
selected={provider.id === inlineCompletionModelProvider}
|
|
416
|
+
>
|
|
417
|
+
{provider.name}
|
|
418
|
+
</option>
|
|
419
|
+
))}
|
|
420
|
+
<option
|
|
421
|
+
key={-1}
|
|
422
|
+
value="none"
|
|
423
|
+
selected={
|
|
424
|
+
inlineCompletionModelProvider === 'none' ||
|
|
425
|
+
!llmProviders.find(
|
|
426
|
+
provider =>
|
|
427
|
+
provider.id === inlineCompletionModelProvider
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
>
|
|
431
|
+
None
|
|
432
|
+
</option>
|
|
433
|
+
</select>
|
|
434
|
+
</div>
|
|
435
|
+
</div>
|
|
436
|
+
{!['openai-compatible', 'litellm-compatible', 'none'].includes(
|
|
437
|
+
inlineCompletionModelProvider
|
|
438
|
+
) && (
|
|
439
|
+
<div className="model-config-section-column">
|
|
440
|
+
<div>Model</div>
|
|
441
|
+
{![
|
|
442
|
+
OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID,
|
|
443
|
+
LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID
|
|
444
|
+
].includes(inlineCompletionModel) && (
|
|
445
|
+
<div>
|
|
446
|
+
<select
|
|
447
|
+
className="jp-mod-styled"
|
|
448
|
+
onChange={event =>
|
|
449
|
+
setInlineCompletionModel(event.target.value)
|
|
450
|
+
}
|
|
451
|
+
>
|
|
452
|
+
{inlineCompletionModels.map(
|
|
453
|
+
(model: any, index: number) => (
|
|
454
|
+
<option
|
|
455
|
+
key={index}
|
|
456
|
+
value={model.id}
|
|
457
|
+
selected={model.id === inlineCompletionModel}
|
|
458
|
+
>
|
|
459
|
+
{model.name}
|
|
460
|
+
</option>
|
|
461
|
+
)
|
|
462
|
+
)}
|
|
463
|
+
</select>
|
|
464
|
+
</div>
|
|
465
|
+
)}
|
|
466
|
+
</div>
|
|
467
|
+
)}
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
<div className="model-config-section-row">
|
|
471
|
+
<div className="model-config-section-column">
|
|
472
|
+
{inlineCompletionModelProperties.map(
|
|
473
|
+
(property: any, index: number) => (
|
|
474
|
+
<div className="form-field-row" key={index}>
|
|
475
|
+
<div className="form-field-description">
|
|
476
|
+
{property.name} {property.optional ? '(optional)' : ''}
|
|
477
|
+
</div>
|
|
478
|
+
<input
|
|
479
|
+
name="inline-completion-model-id-input"
|
|
480
|
+
placeholder={property.description}
|
|
481
|
+
className="jp-mod-styled"
|
|
482
|
+
spellCheck={false}
|
|
483
|
+
value={property.value}
|
|
484
|
+
onChange={event =>
|
|
485
|
+
onModelPropertyChange(
|
|
486
|
+
'inline-completion',
|
|
487
|
+
property.id,
|
|
488
|
+
event.target.value
|
|
489
|
+
)
|
|
490
|
+
}
|
|
491
|
+
/>
|
|
492
|
+
</div>
|
|
493
|
+
)
|
|
494
|
+
)}
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
|
|
500
|
+
{(chatModelProvider === 'github-copilot' ||
|
|
501
|
+
inlineCompletionModelProvider === 'github-copilot') && (
|
|
502
|
+
<div className="model-config-section">
|
|
503
|
+
<div className="model-config-section-header access-token-config-header">
|
|
504
|
+
GitHub Copilot login{' '}
|
|
505
|
+
<a
|
|
506
|
+
href="https://github.com/notebook-intelligence/notebook-intelligence/blob/main/README.md#remembering-github-copilot-login"
|
|
507
|
+
target="_blank"
|
|
508
|
+
>
|
|
509
|
+
{' '}
|
|
510
|
+
<VscWarning
|
|
511
|
+
className="access-token-warning"
|
|
512
|
+
title="Click to learn more about security implications"
|
|
513
|
+
/>
|
|
514
|
+
</a>
|
|
515
|
+
</div>
|
|
516
|
+
<div className="model-config-section-body">
|
|
517
|
+
<div className="model-config-section-row">
|
|
518
|
+
<div className="model-config-section-column">
|
|
519
|
+
<label>
|
|
520
|
+
<input
|
|
521
|
+
type="checkbox"
|
|
522
|
+
checked={storeGitHubAccessToken}
|
|
523
|
+
onChange={event => {
|
|
524
|
+
setStoreGitHubAccessToken(event.target.checked);
|
|
525
|
+
}}
|
|
526
|
+
/>
|
|
527
|
+
Remember my GitHub Copilot access token
|
|
528
|
+
</label>
|
|
529
|
+
</div>
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
)}
|
|
534
|
+
|
|
535
|
+
<div className="model-config-section">
|
|
536
|
+
<div className="model-config-section-header">Config file path</div>
|
|
537
|
+
<div className="model-config-section-body">
|
|
538
|
+
<div className="model-config-section-row">
|
|
539
|
+
<div className="model-config-section-column">
|
|
540
|
+
<span
|
|
541
|
+
className="user-code-span"
|
|
542
|
+
onClick={() => {
|
|
543
|
+
navigator.clipboard.writeText(
|
|
544
|
+
path.join(NBIAPI.config.userConfigDir, 'config.json')
|
|
545
|
+
);
|
|
546
|
+
return true;
|
|
547
|
+
}}
|
|
548
|
+
>
|
|
549
|
+
{path.join(NBIAPI.config.userConfigDir, 'config.json')}{' '}
|
|
550
|
+
<span
|
|
551
|
+
className="copy-icon"
|
|
552
|
+
dangerouslySetInnerHTML={{ __html: copySvgstr }}
|
|
553
|
+
></span>
|
|
554
|
+
</span>
|
|
555
|
+
</div>
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function SettingsPanelComponentMCPServers(props: any) {
|
|
565
|
+
const nbiConfig = NBIAPI.config;
|
|
566
|
+
const mcpServersRef = useRef<any>(nbiConfig.toolConfig.mcpServers);
|
|
567
|
+
const mcpServerSettingsRef = useRef<any>(nbiConfig.mcpServerSettings);
|
|
568
|
+
const [renderCount, setRenderCount] = useState(1);
|
|
569
|
+
|
|
570
|
+
const [mcpServerEnabledState, setMCPServerEnabledState] = useState(
|
|
571
|
+
new Map<string, Set<string>>(
|
|
572
|
+
mcpServerSettingsToEnabledState(
|
|
573
|
+
mcpServersRef.current,
|
|
574
|
+
mcpServerSettingsRef.current
|
|
575
|
+
)
|
|
576
|
+
)
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
const mcpServerEnabledStateToMcpServerSettings = () => {
|
|
580
|
+
const mcpServerSettings: any = {};
|
|
581
|
+
for (const mcpServer of mcpServersRef.current) {
|
|
582
|
+
if (mcpServerEnabledState.has(mcpServer.id)) {
|
|
583
|
+
const disabledTools = [];
|
|
584
|
+
for (const tool of mcpServer.tools) {
|
|
585
|
+
if (!mcpServerEnabledState.get(mcpServer.id).has(tool.name)) {
|
|
586
|
+
disabledTools.push(tool.name);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
mcpServerSettings[mcpServer.id] = {
|
|
590
|
+
disabled: false,
|
|
591
|
+
disabled_tools: disabledTools
|
|
592
|
+
};
|
|
593
|
+
} else {
|
|
594
|
+
mcpServerSettings[mcpServer.id] = { disabled: true };
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return mcpServerSettings;
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
const syncSettingsToServerState = () => {
|
|
601
|
+
NBIAPI.setConfig({
|
|
602
|
+
mcp_server_settings: mcpServerSettingsRef.current
|
|
603
|
+
});
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const handleReloadMCPServersClick = async () => {
|
|
607
|
+
await NBIAPI.reloadMCPServers();
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
useEffect(() => {
|
|
611
|
+
syncSettingsToServerState();
|
|
612
|
+
}, [mcpServerSettingsRef.current]);
|
|
613
|
+
|
|
614
|
+
useEffect(() => {
|
|
615
|
+
mcpServerSettingsRef.current = mcpServerEnabledStateToMcpServerSettings();
|
|
616
|
+
setRenderCount(renderCount => renderCount + 1);
|
|
617
|
+
}, [mcpServerEnabledState]);
|
|
618
|
+
|
|
619
|
+
const setMCPServerEnabled = (serverId: string, enabled: boolean) => {
|
|
620
|
+
const currentState = new Map(mcpServerEnabledState);
|
|
621
|
+
if (enabled) {
|
|
622
|
+
if (!(serverId in currentState)) {
|
|
623
|
+
currentState.set(
|
|
624
|
+
serverId,
|
|
625
|
+
new Set<string>(
|
|
626
|
+
mcpServersRef.current
|
|
627
|
+
.find((server: any) => server.id === serverId)
|
|
628
|
+
?.tools.map((tool: any) => tool.name)
|
|
629
|
+
)
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
currentState.delete(serverId);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
setMCPServerEnabledState(currentState);
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const getMCPServerEnabled = (serverId: string) => {
|
|
640
|
+
return mcpServerEnabledState.has(serverId);
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const getMCPServerToolEnabled = (serverId: string, toolName: string) => {
|
|
644
|
+
return (
|
|
645
|
+
mcpServerEnabledState.has(serverId) &&
|
|
646
|
+
mcpServerEnabledState.get(serverId).has(toolName)
|
|
647
|
+
);
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
const setMCPServerToolEnabled = (
|
|
651
|
+
serverId: string,
|
|
652
|
+
toolName: string,
|
|
653
|
+
enabled: boolean
|
|
654
|
+
) => {
|
|
655
|
+
const currentState = new Map(mcpServerEnabledState);
|
|
656
|
+
const serverState = currentState.get(serverId);
|
|
657
|
+
if (enabled) {
|
|
658
|
+
serverState.add(toolName);
|
|
659
|
+
} else {
|
|
660
|
+
serverState.delete(toolName);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
setMCPServerEnabledState(currentState);
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
useEffect(() => {
|
|
667
|
+
NBIAPI.configChanged.connect(() => {
|
|
668
|
+
mcpServersRef.current = nbiConfig.toolConfig.mcpServers;
|
|
669
|
+
mcpServerSettingsRef.current = nbiConfig.mcpServerSettings;
|
|
670
|
+
setRenderCount(renderCount => renderCount + 1);
|
|
671
|
+
});
|
|
672
|
+
}, []);
|
|
673
|
+
|
|
674
|
+
return (
|
|
675
|
+
<div className="config-dialog">
|
|
676
|
+
<div className="config-dialog-body">
|
|
677
|
+
<div className="model-config-section">
|
|
678
|
+
<div
|
|
679
|
+
className="model-config-section-header"
|
|
680
|
+
style={{ display: 'flex' }}
|
|
681
|
+
>
|
|
682
|
+
<div style={{ flexGrow: 1 }}>MCP Servers</div>
|
|
683
|
+
<div>
|
|
684
|
+
<button
|
|
685
|
+
className="jp-toast-button jp-mod-small jp-Button"
|
|
686
|
+
onClick={handleReloadMCPServersClick}
|
|
687
|
+
>
|
|
688
|
+
<div className="jp-Dialog-buttonLabel">Reload</div>
|
|
689
|
+
</button>
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
<div className="model-config-section-body">
|
|
693
|
+
{mcpServersRef.current.length === 0 && renderCount > 0 && (
|
|
694
|
+
<div className="model-config-section-row">
|
|
695
|
+
<div className="model-config-section-column">
|
|
696
|
+
<div>
|
|
697
|
+
No MCP servers found. Add MCP servers in the configuration
|
|
698
|
+
file.
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
702
|
+
)}
|
|
703
|
+
{mcpServersRef.current.length > 0 && renderCount > 0 && (
|
|
704
|
+
<div className="model-config-section-row">
|
|
705
|
+
<div className="model-config-section-column">
|
|
706
|
+
{mcpServersRef.current.map((server: any) => (
|
|
707
|
+
<div key={server.id}>
|
|
708
|
+
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
709
|
+
<CheckBoxItem
|
|
710
|
+
header={true}
|
|
711
|
+
label={server.id}
|
|
712
|
+
checked={getMCPServerEnabled(server.id)}
|
|
713
|
+
onClick={() => {
|
|
714
|
+
setMCPServerEnabled(
|
|
715
|
+
server.id,
|
|
716
|
+
!getMCPServerEnabled(server.id)
|
|
717
|
+
);
|
|
718
|
+
}}
|
|
719
|
+
></CheckBoxItem>
|
|
720
|
+
<div
|
|
721
|
+
className={`server-status-indicator ${server.status}`}
|
|
722
|
+
title={server.status}
|
|
723
|
+
></div>
|
|
724
|
+
</div>
|
|
725
|
+
{getMCPServerEnabled(server.id) && (
|
|
726
|
+
<div>
|
|
727
|
+
{server.tools.map((tool: any) => (
|
|
728
|
+
<PillItem
|
|
729
|
+
label={tool.name}
|
|
730
|
+
title={tool.description}
|
|
731
|
+
checked={getMCPServerToolEnabled(
|
|
732
|
+
server.id,
|
|
733
|
+
tool.name
|
|
734
|
+
)}
|
|
735
|
+
onClick={() => {
|
|
736
|
+
setMCPServerToolEnabled(
|
|
737
|
+
server.id,
|
|
738
|
+
tool.name,
|
|
739
|
+
!getMCPServerToolEnabled(server.id, tool.name)
|
|
740
|
+
);
|
|
741
|
+
}}
|
|
742
|
+
></PillItem>
|
|
743
|
+
))}
|
|
744
|
+
</div>
|
|
745
|
+
)}
|
|
746
|
+
</div>
|
|
747
|
+
))}
|
|
748
|
+
</div>
|
|
749
|
+
</div>
|
|
750
|
+
)}
|
|
751
|
+
<div className="model-config-section-row">
|
|
752
|
+
<div
|
|
753
|
+
className="model-config-section-column"
|
|
754
|
+
style={{ flexGrow: 'initial' }}
|
|
755
|
+
>
|
|
756
|
+
<button
|
|
757
|
+
className="jp-Dialog-button jp-mod-accept jp-mod-styled"
|
|
758
|
+
style={{ width: 'max-content' }}
|
|
759
|
+
onClick={props.onEditMCPConfigClicked}
|
|
760
|
+
>
|
|
761
|
+
<div className="jp-Dialog-buttonLabel">Add / Edit</div>
|
|
762
|
+
</button>
|
|
763
|
+
</div>
|
|
764
|
+
</div>
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
</div>
|
|
769
|
+
);
|
|
770
|
+
}
|