@plmbr/notebook-intelligence 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +412 -0
- package/lib/api.d.ts +288 -0
- package/lib/api.js +927 -0
- package/lib/cell-output-bundle.d.ts +25 -0
- package/lib/cell-output-bundle.js +129 -0
- package/lib/cell-output-toolbar.d.ts +26 -0
- package/lib/cell-output-toolbar.js +188 -0
- package/lib/chat-progress-feedback.d.ts +3 -0
- package/lib/chat-progress-feedback.js +27 -0
- package/lib/chat-sidebar.d.ts +92 -0
- package/lib/chat-sidebar.js +3452 -0
- package/lib/command-ids.d.ts +39 -0
- package/lib/command-ids.js +44 -0
- package/lib/components/ask-user-question.d.ts +2 -0
- package/lib/components/ask-user-question.js +85 -0
- package/lib/components/checkbox.d.ts +2 -0
- package/lib/components/checkbox.js +30 -0
- package/lib/components/claude-mcp-panel.d.ts +2 -0
- package/lib/components/claude-mcp-panel.js +275 -0
- package/lib/components/claude-mcp-paste.d.ts +7 -0
- package/lib/components/claude-mcp-paste.js +104 -0
- package/lib/components/claude-session-picker.d.ts +8 -0
- package/lib/components/claude-session-picker.js +127 -0
- package/lib/components/form-dialog.d.ts +25 -0
- package/lib/components/form-dialog.js +35 -0
- package/lib/components/launcher-picker.d.ts +6 -0
- package/lib/components/launcher-picker.js +135 -0
- package/lib/components/mcp-util.d.ts +2 -0
- package/lib/components/mcp-util.js +37 -0
- package/lib/components/notebook-generation-popover.d.ts +7 -0
- package/lib/components/notebook-generation-popover.js +60 -0
- package/lib/components/pill.d.ts +2 -0
- package/lib/components/pill.js +5 -0
- package/lib/components/plugins-panel.d.ts +3 -0
- package/lib/components/plugins-panel.js +466 -0
- package/lib/components/settings-panel.d.ts +11 -0
- package/lib/components/settings-panel.js +742 -0
- package/lib/components/skills-panel.d.ts +2 -0
- package/lib/components/skills-panel.js +1264 -0
- package/lib/handler.d.ts +8 -0
- package/lib/handler.js +36 -0
- package/lib/icons.d.ts +45 -0
- package/lib/icons.js +54 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +2079 -0
- package/lib/markdown-renderer.d.ts +10 -0
- package/lib/markdown-renderer.js +64 -0
- package/lib/notebook-generation-toolbar.d.ts +16 -0
- package/lib/notebook-generation-toolbar.js +197 -0
- package/lib/notebook-generation.d.ts +8 -0
- package/lib/notebook-generation.js +12 -0
- package/lib/open-file-refresh-watcher-env.d.ts +4 -0
- package/lib/open-file-refresh-watcher-env.js +33 -0
- package/lib/open-file-refresh-watcher.d.ts +97 -0
- package/lib/open-file-refresh-watcher.js +190 -0
- package/lib/shell-utils.d.ts +6 -0
- package/lib/shell-utils.js +9 -0
- package/lib/task-target-notebook.d.ts +2 -0
- package/lib/task-target-notebook.js +28 -0
- package/lib/terminal-drag-format.d.ts +9 -0
- package/lib/terminal-drag-format.js +23 -0
- package/lib/terminal-drag.d.ts +12 -0
- package/lib/terminal-drag.js +268 -0
- package/lib/tokens.d.ts +149 -0
- package/lib/tokens.js +88 -0
- package/lib/tour/tour-anchors.d.ts +18 -0
- package/lib/tour/tour-anchors.js +18 -0
- package/lib/tour/tour-config.d.ts +66 -0
- package/lib/tour/tour-config.js +99 -0
- package/lib/tour/tour-defaults.json +58 -0
- package/lib/tour/tour-events.d.ts +19 -0
- package/lib/tour/tour-events.js +30 -0
- package/lib/tour/tour-overlay.d.ts +6 -0
- package/lib/tour/tour-overlay.js +350 -0
- package/lib/tour/tour-state.d.ts +20 -0
- package/lib/tour/tour-state.js +81 -0
- package/lib/tour/tour-steps.d.ts +33 -0
- package/lib/tour/tour-steps.js +216 -0
- package/lib/utils.d.ts +53 -0
- package/lib/utils.js +385 -0
- package/package.json +258 -0
- package/schema/plugin.json +42 -0
- package/src/api.ts +1424 -0
- package/src/cell-output-bundle.ts +176 -0
- package/src/cell-output-toolbar.ts +232 -0
- package/src/chat-progress-feedback.ts +35 -0
- package/src/chat-sidebar.tsx +5147 -0
- package/src/command-ids.ts +67 -0
- package/src/components/ask-user-question.tsx +151 -0
- package/src/components/checkbox.tsx +62 -0
- package/src/components/claude-mcp-panel.tsx +543 -0
- package/src/components/claude-mcp-paste.ts +132 -0
- package/src/components/claude-session-picker.tsx +214 -0
- package/src/components/form-dialog.tsx +75 -0
- package/src/components/launcher-picker.tsx +237 -0
- package/src/components/mcp-util.ts +53 -0
- package/src/components/notebook-generation-popover.tsx +127 -0
- package/src/components/pill.tsx +15 -0
- package/src/components/plugins-panel.tsx +774 -0
- package/src/components/settings-panel.tsx +1631 -0
- package/src/components/skills-panel.tsx +2084 -0
- package/src/handler.ts +51 -0
- package/src/icons.ts +71 -0
- package/src/index.ts +2583 -0
- package/src/markdown-renderer.tsx +153 -0
- package/src/notebook-generation-toolbar.tsx +281 -0
- package/src/notebook-generation.ts +23 -0
- package/src/open-file-refresh-watcher-env.ts +52 -0
- package/src/open-file-refresh-watcher.ts +260 -0
- package/src/shell-utils.ts +10 -0
- package/src/svg.d.ts +4 -0
- package/src/task-target-notebook.ts +37 -0
- package/src/terminal-drag-format.ts +29 -0
- package/src/terminal-drag.ts +382 -0
- package/src/tokens.ts +171 -0
- package/src/tour/tour-anchors.ts +21 -0
- package/src/tour/tour-config.ts +160 -0
- package/src/tour/tour-events.ts +34 -0
- package/src/tour/tour-overlay.tsx +474 -0
- package/src/tour/tour-state.ts +87 -0
- package/src/tour/tour-steps.ts +281 -0
- package/src/utils.ts +455 -0
- package/style/base.css +3238 -0
- package/style/icons/cell-toolbar-bug.svg +5 -0
- package/style/icons/cell-toolbar-chat.svg +5 -0
- package/style/icons/cell-toolbar-sparkle.svg +5 -0
- package/style/icons/claude.svg +1 -0
- package/style/icons/copilot-warning.svg +1 -0
- package/style/icons/copilot.svg +1 -0
- package/style/icons/copy.svg +1 -0
- package/style/icons/openai.svg +1 -0
- package/style/icons/opencode.svg +1 -0
- package/style/icons/sparkles-warning.svg +5 -0
- package/style/icons/sparkles.svg +1 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
|
@@ -0,0 +1,1631 @@
|
|
|
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 '../icons';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
import copySvgstr from '../../style/icons/copy.svg';
|
|
9
|
+
import claudeSvgStr from '../../style/icons/claude.svg';
|
|
10
|
+
import {
|
|
11
|
+
ClaudeModelType,
|
|
12
|
+
ClaudeToolType,
|
|
13
|
+
ICellOutputFeatureFlag,
|
|
14
|
+
IClaudeModelInfo,
|
|
15
|
+
NBIAPI
|
|
16
|
+
} from '../api';
|
|
17
|
+
import { CheckBoxItem } from './checkbox';
|
|
18
|
+
import { PillItem } from './pill';
|
|
19
|
+
import { mcpServerSettingsToEnabledState } from './mcp-util';
|
|
20
|
+
import { SettingsPanelComponentSkills } from './skills-panel';
|
|
21
|
+
import { SettingsPanelComponentClaudeMCP } from './claude-mcp-panel';
|
|
22
|
+
import { SettingsPanelComponentPlugins } from './plugins-panel';
|
|
23
|
+
import { writeTextToClipboard } from '../utils';
|
|
24
|
+
|
|
25
|
+
const lockedTip = (locked: boolean): string =>
|
|
26
|
+
locked ? 'Locked by your administrator' : '';
|
|
27
|
+
|
|
28
|
+
// Stable id helper so the tab and its panel agree on aria-controls /
|
|
29
|
+
// aria-labelledby without scattering string concatenation through the
|
|
30
|
+
// component.
|
|
31
|
+
const tabId = (prefix: string, id: string): string => `${prefix}-${id}`;
|
|
32
|
+
|
|
33
|
+
type TablistOrientation = 'vertical' | 'horizontal';
|
|
34
|
+
|
|
35
|
+
// WAI-ARIA tablist arrow-key navigation. Same shape for both the
|
|
36
|
+
// vertical (Up/Down) main tabs and the horizontal (Left/Right) Claude
|
|
37
|
+
// subtabs — the orientation flag picks which keys move the cursor.
|
|
38
|
+
// Returns an ``onKeyDown`` for the tablist container; callers decide
|
|
39
|
+
// what to do with each id (typically: select + focus).
|
|
40
|
+
function useTablistArrowKeys<T extends { id: string }>(
|
|
41
|
+
tabs: T[],
|
|
42
|
+
activeId: string,
|
|
43
|
+
onSelect: (id: string) => void,
|
|
44
|
+
orientation: TablistOrientation,
|
|
45
|
+
domIdFor: (id: string) => string
|
|
46
|
+
): (e: React.KeyboardEvent<HTMLDivElement>) => void {
|
|
47
|
+
return (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
48
|
+
const key = e.key;
|
|
49
|
+
const prevKey = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft';
|
|
50
|
+
const nextKey = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight';
|
|
51
|
+
if (key !== prevKey && key !== nextKey && key !== 'Home' && key !== 'End') {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
const idx = tabs.findIndex(t => t.id === activeId);
|
|
56
|
+
let next = idx;
|
|
57
|
+
if (key === nextKey) {
|
|
58
|
+
next = (idx + 1) % tabs.length;
|
|
59
|
+
} else if (key === prevKey) {
|
|
60
|
+
next = (idx - 1 + tabs.length) % tabs.length;
|
|
61
|
+
} else if (key === 'Home') {
|
|
62
|
+
next = 0;
|
|
63
|
+
} else if (key === 'End') {
|
|
64
|
+
next = tabs.length - 1;
|
|
65
|
+
}
|
|
66
|
+
onSelect(tabs[next].id);
|
|
67
|
+
document.getElementById(domIdFor(tabs[next].id))?.focus();
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// When a boolean policy is locked the panel shows the policy-resolved value;
|
|
72
|
+
// otherwise it shows the user's local toggle state.
|
|
73
|
+
const checkedValue = (
|
|
74
|
+
policy: ICellOutputFeatureFlag,
|
|
75
|
+
userValue: boolean
|
|
76
|
+
): boolean => (policy.locked ? policy.enabled : userValue);
|
|
77
|
+
|
|
78
|
+
function useNbiPolicies() {
|
|
79
|
+
const [featurePolicies, setFeaturePolicies] = useState(
|
|
80
|
+
NBIAPI.config.featurePolicies
|
|
81
|
+
);
|
|
82
|
+
const [settingLocks, setSettingLocks] = useState(NBIAPI.config.settingLocks);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const handler = () => {
|
|
85
|
+
setFeaturePolicies(NBIAPI.config.featurePolicies);
|
|
86
|
+
setSettingLocks(NBIAPI.config.settingLocks);
|
|
87
|
+
};
|
|
88
|
+
NBIAPI.configChanged.connect(handler);
|
|
89
|
+
return () => {
|
|
90
|
+
NBIAPI.configChanged.disconnect(handler);
|
|
91
|
+
};
|
|
92
|
+
}, []);
|
|
93
|
+
return { featurePolicies, settingLocks };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const OPENAI_COMPATIBLE_CHAT_MODEL_ID = 'openai-compatible-chat-model';
|
|
97
|
+
const LITELLM_COMPATIBLE_CHAT_MODEL_ID = 'litellm-compatible-chat-model';
|
|
98
|
+
const OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID =
|
|
99
|
+
'openai-compatible-inline-completion-model';
|
|
100
|
+
const LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID =
|
|
101
|
+
'litellm-compatible-inline-completion-model';
|
|
102
|
+
|
|
103
|
+
export class SettingsPanel extends ReactWidget {
|
|
104
|
+
constructor(options: {
|
|
105
|
+
onSave: () => void;
|
|
106
|
+
onEditMCPConfigClicked: () => void;
|
|
107
|
+
}) {
|
|
108
|
+
super();
|
|
109
|
+
|
|
110
|
+
this._onSave = options.onSave;
|
|
111
|
+
this._onEditMCPConfigClicked = options.onEditMCPConfigClicked;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
render(): JSX.Element {
|
|
115
|
+
return (
|
|
116
|
+
<SettingsPanelComponent
|
|
117
|
+
onSave={this._onSave}
|
|
118
|
+
onEditMCPConfigClicked={this._onEditMCPConfigClicked}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private _onSave: () => void;
|
|
124
|
+
private _onEditMCPConfigClicked: () => void;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Tab declaration. Adding a new tab is one entry here plus an icon
|
|
128
|
+
// (optional). The `visible` predicate runs against {featurePolicies,
|
|
129
|
+
// isInClaudeCodeMode, isClaudeCliAvailable} so policy / mode changes
|
|
130
|
+
// propagate through the registry without wiring extra props.
|
|
131
|
+
type TabSpec = {
|
|
132
|
+
id: string;
|
|
133
|
+
label: string;
|
|
134
|
+
icon?: () => JSX.Element;
|
|
135
|
+
visible: (ctx: TabVisibilityContext) => boolean;
|
|
136
|
+
render: (props: any) => JSX.Element;
|
|
137
|
+
};
|
|
138
|
+
type TabVisibilityContext = {
|
|
139
|
+
featurePolicies: import('../api').IFeaturePolicies;
|
|
140
|
+
isInClaudeCodeMode: boolean;
|
|
141
|
+
isClaudeCliAvailable: boolean;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const TABS: TabSpec[] = [
|
|
145
|
+
{
|
|
146
|
+
id: 'general',
|
|
147
|
+
label: 'General',
|
|
148
|
+
visible: () => true,
|
|
149
|
+
render: props => (
|
|
150
|
+
<SettingsPanelComponentGeneral
|
|
151
|
+
onSave={props.onSave}
|
|
152
|
+
onEditMCPConfigClicked={props.onEditMCPConfigClicked}
|
|
153
|
+
/>
|
|
154
|
+
)
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'claude',
|
|
158
|
+
label: 'Claude',
|
|
159
|
+
icon: () => (
|
|
160
|
+
<span
|
|
161
|
+
className="claude-icon"
|
|
162
|
+
dangerouslySetInnerHTML={{ __html: claudeSvgStr }}
|
|
163
|
+
></span>
|
|
164
|
+
),
|
|
165
|
+
visible: () => true,
|
|
166
|
+
render: props => (
|
|
167
|
+
<SettingsPanelComponentClaude
|
|
168
|
+
onEditMCPConfigClicked={props.onEditMCPConfigClicked}
|
|
169
|
+
/>
|
|
170
|
+
)
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'mcp-servers',
|
|
174
|
+
label: 'MCP Servers',
|
|
175
|
+
visible: ctx => !ctx.isInClaudeCodeMode,
|
|
176
|
+
render: props => (
|
|
177
|
+
<SettingsPanelComponentMCPServers
|
|
178
|
+
onEditMCPConfigClicked={props.onEditMCPConfigClicked}
|
|
179
|
+
/>
|
|
180
|
+
)
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'claude-mcp',
|
|
184
|
+
label: 'Claude MCP',
|
|
185
|
+
visible: ctx =>
|
|
186
|
+
ctx.featurePolicies.claude_mcp_management.enabled &&
|
|
187
|
+
ctx.isInClaudeCodeMode &&
|
|
188
|
+
ctx.isClaudeCliAvailable,
|
|
189
|
+
render: () => <SettingsPanelComponentClaudeMCP />
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: 'plugins',
|
|
193
|
+
label: 'Plugins',
|
|
194
|
+
visible: ctx =>
|
|
195
|
+
ctx.featurePolicies.claude_plugins_management.enabled &&
|
|
196
|
+
ctx.isInClaudeCodeMode &&
|
|
197
|
+
ctx.isClaudeCliAvailable,
|
|
198
|
+
render: () => <SettingsPanelComponentPlugins />
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: 'skills',
|
|
202
|
+
label: 'Skills',
|
|
203
|
+
visible: ctx => ctx.featurePolicies.skills_management.enabled,
|
|
204
|
+
render: () => <SettingsPanelComponentSkills />
|
|
205
|
+
}
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
function SettingsPanelComponent(props: any) {
|
|
209
|
+
const [activeTab, setActiveTab] = useState('general');
|
|
210
|
+
const { featurePolicies } = useNbiPolicies();
|
|
211
|
+
const [isInClaudeCodeMode, setIsInClaudeCodeMode] = useState(
|
|
212
|
+
NBIAPI.config.isInClaudeCodeMode
|
|
213
|
+
);
|
|
214
|
+
const [isClaudeCliAvailable, setIsClaudeCliAvailable] = useState(
|
|
215
|
+
NBIAPI.config.isClaudeCliAvailable
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
const handler = () => {
|
|
220
|
+
setIsInClaudeCodeMode(NBIAPI.config.isInClaudeCodeMode);
|
|
221
|
+
setIsClaudeCliAvailable(NBIAPI.config.isClaudeCliAvailable);
|
|
222
|
+
};
|
|
223
|
+
NBIAPI.configChanged.connect(handler);
|
|
224
|
+
return () => {
|
|
225
|
+
NBIAPI.configChanged.disconnect(handler);
|
|
226
|
+
};
|
|
227
|
+
}, []);
|
|
228
|
+
|
|
229
|
+
const ctx: TabVisibilityContext = {
|
|
230
|
+
featurePolicies,
|
|
231
|
+
isInClaudeCodeMode,
|
|
232
|
+
isClaudeCliAvailable
|
|
233
|
+
};
|
|
234
|
+
const visibleTabs = TABS.filter(t => t.visible(ctx));
|
|
235
|
+
const activeTabSpec = visibleTabs.find(t => t.id === activeTab);
|
|
236
|
+
|
|
237
|
+
// Bounce off a tab that just disappeared (admin policy flip, mode toggle).
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (!activeTabSpec) {
|
|
240
|
+
setActiveTab('general');
|
|
241
|
+
}
|
|
242
|
+
}, [activeTabSpec]);
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<div className="nbi-settings-panel">
|
|
246
|
+
<SettingsPanelTabsComponent
|
|
247
|
+
tabs={visibleTabs}
|
|
248
|
+
activeTab={activeTab}
|
|
249
|
+
onTabSelected={setActiveTab}
|
|
250
|
+
/>
|
|
251
|
+
<div
|
|
252
|
+
className="nbi-settings-panel-tab-content"
|
|
253
|
+
role="tabpanel"
|
|
254
|
+
id={tabId('nbi-settings-tabpanel', activeTab)}
|
|
255
|
+
aria-labelledby={tabId('nbi-settings-tab', activeTab)}
|
|
256
|
+
>
|
|
257
|
+
{activeTabSpec && activeTabSpec.render(props)}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function SettingsPanelTabsComponent(props: {
|
|
264
|
+
tabs: TabSpec[];
|
|
265
|
+
activeTab: string;
|
|
266
|
+
onTabSelected: (tab: string) => void;
|
|
267
|
+
}) {
|
|
268
|
+
const onKeyDown = useTablistArrowKeys(
|
|
269
|
+
props.tabs,
|
|
270
|
+
props.activeTab,
|
|
271
|
+
props.onTabSelected,
|
|
272
|
+
'vertical',
|
|
273
|
+
id => tabId('nbi-settings-tab', id)
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div
|
|
278
|
+
className="nbi-settings-panel-tabs"
|
|
279
|
+
role="tablist"
|
|
280
|
+
aria-orientation="vertical"
|
|
281
|
+
aria-label="Settings sections"
|
|
282
|
+
onKeyDown={onKeyDown}
|
|
283
|
+
>
|
|
284
|
+
{props.tabs.map(tab => {
|
|
285
|
+
const selected = tab.id === props.activeTab;
|
|
286
|
+
return (
|
|
287
|
+
<button
|
|
288
|
+
type="button"
|
|
289
|
+
key={tab.id}
|
|
290
|
+
id={tabId('nbi-settings-tab', tab.id)}
|
|
291
|
+
className={`nbi-settings-panel-tab ${selected ? 'active' : ''}`}
|
|
292
|
+
role="tab"
|
|
293
|
+
aria-selected={selected}
|
|
294
|
+
aria-controls={tabId('nbi-settings-tabpanel', tab.id)}
|
|
295
|
+
tabIndex={selected ? 0 : -1}
|
|
296
|
+
onClick={() => props.onTabSelected(tab.id)}
|
|
297
|
+
>
|
|
298
|
+
{tab.icon && tab.icon()}
|
|
299
|
+
{tab.label}
|
|
300
|
+
</button>
|
|
301
|
+
);
|
|
302
|
+
})}
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function SettingsPanelComponentGeneral(props: any) {
|
|
308
|
+
const nbiConfig = NBIAPI.config;
|
|
309
|
+
const llmProviders = nbiConfig.llmProviders;
|
|
310
|
+
const [chatModels, setChatModels] = useState([]);
|
|
311
|
+
const [inlineCompletionModels, setInlineCompletionModels] = useState([]);
|
|
312
|
+
const isInClaudeCodeMode = nbiConfig.isInClaudeCodeMode;
|
|
313
|
+
|
|
314
|
+
const handleSaveSettings = async () => {
|
|
315
|
+
const config: any = {
|
|
316
|
+
default_chat_mode: defaultChatMode,
|
|
317
|
+
chat_model: {
|
|
318
|
+
provider: chatModelProvider,
|
|
319
|
+
model: chatModel,
|
|
320
|
+
properties: chatModelProperties
|
|
321
|
+
},
|
|
322
|
+
inline_completion_model: {
|
|
323
|
+
provider: inlineCompletionModelProvider,
|
|
324
|
+
model: inlineCompletionModel,
|
|
325
|
+
properties: inlineCompletionModelProperties
|
|
326
|
+
},
|
|
327
|
+
inline_completion_debouncer_delay: inlineCompletionDebouncerDelay
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
if (
|
|
331
|
+
chatModelProvider === 'github-copilot' ||
|
|
332
|
+
inlineCompletionModelProvider === 'github-copilot'
|
|
333
|
+
) {
|
|
334
|
+
config.store_github_access_token = storeGitHubAccessToken;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
await NBIAPI.setConfig(config);
|
|
338
|
+
|
|
339
|
+
props.onSave();
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const handleRefreshOllamaModelListClick = async () => {
|
|
343
|
+
await NBIAPI.updateOllamaModelList();
|
|
344
|
+
updateModelOptionsForProvider(chatModelProvider, 'chat');
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const [chatModelProvider, setChatModelProvider] = useState(
|
|
348
|
+
nbiConfig.chatModel.provider || 'none'
|
|
349
|
+
);
|
|
350
|
+
const [inlineCompletionModelProvider, setInlineCompletionModelProvider] =
|
|
351
|
+
useState(nbiConfig.inlineCompletionModel.provider || 'none');
|
|
352
|
+
const [defaultChatMode, setDefaultChatMode] = useState<string>(
|
|
353
|
+
nbiConfig.defaultChatMode
|
|
354
|
+
);
|
|
355
|
+
const [chatModel, setChatModel] = useState<string>(nbiConfig.chatModel.model);
|
|
356
|
+
const [chatModelProperties, setChatModelProperties] = useState<any[]>([]);
|
|
357
|
+
const [inlineCompletionModelProperties, setInlineCompletionModelProperties] =
|
|
358
|
+
useState<any[]>([]);
|
|
359
|
+
const [inlineCompletionModel, setInlineCompletionModel] = useState(
|
|
360
|
+
nbiConfig.inlineCompletionModel.model
|
|
361
|
+
);
|
|
362
|
+
const [storeGitHubAccessToken, setStoreGitHubAccessToken] = useState(
|
|
363
|
+
nbiConfig.storeGitHubAccessToken
|
|
364
|
+
);
|
|
365
|
+
const [inlineCompletionDebouncerDelay, setInlineCompletionDebouncerDelay] =
|
|
366
|
+
useState(nbiConfig.inlineCompletionDebouncerDelay);
|
|
367
|
+
const { featurePolicies, settingLocks } = useNbiPolicies();
|
|
368
|
+
|
|
369
|
+
const toggleExplainError = () => {
|
|
370
|
+
NBIAPI.setConfig({
|
|
371
|
+
enable_explain_error: !featurePolicies.explain_error.enabled
|
|
372
|
+
});
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const toggleOutputFollowup = () => {
|
|
376
|
+
NBIAPI.setConfig({
|
|
377
|
+
enable_output_followup: !featurePolicies.output_followup.enabled
|
|
378
|
+
});
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const toggleOutputToolbar = () => {
|
|
382
|
+
NBIAPI.setConfig({
|
|
383
|
+
enable_output_toolbar: !featurePolicies.output_toolbar.enabled
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const toggleRefreshOpenFilesOnDiskChange = () => {
|
|
388
|
+
NBIAPI.setConfig({
|
|
389
|
+
refresh_open_files_on_disk_change:
|
|
390
|
+
!featurePolicies.refresh_open_files_on_disk_change.enabled
|
|
391
|
+
});
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const updateModelOptionsForProvider = (
|
|
395
|
+
providerId: string,
|
|
396
|
+
modelType: 'chat' | 'inline-completion'
|
|
397
|
+
) => {
|
|
398
|
+
if (modelType === 'chat') {
|
|
399
|
+
setChatModelProvider(providerId);
|
|
400
|
+
} else {
|
|
401
|
+
setInlineCompletionModelProvider(providerId);
|
|
402
|
+
}
|
|
403
|
+
const models =
|
|
404
|
+
modelType === 'chat'
|
|
405
|
+
? nbiConfig.chatModels
|
|
406
|
+
: nbiConfig.inlineCompletionModels;
|
|
407
|
+
const selectedModelId =
|
|
408
|
+
modelType === 'chat'
|
|
409
|
+
? nbiConfig.chatModel.model
|
|
410
|
+
: nbiConfig.inlineCompletionModel.model;
|
|
411
|
+
|
|
412
|
+
const providerModels = models.filter(
|
|
413
|
+
(model: any) => model.provider === providerId
|
|
414
|
+
);
|
|
415
|
+
if (modelType === 'chat') {
|
|
416
|
+
setChatModels(providerModels);
|
|
417
|
+
} else {
|
|
418
|
+
setInlineCompletionModels(providerModels);
|
|
419
|
+
}
|
|
420
|
+
let selectedModel = providerModels.find(
|
|
421
|
+
(model: any) => model.id === selectedModelId
|
|
422
|
+
);
|
|
423
|
+
if (!selectedModel) {
|
|
424
|
+
selectedModel = providerModels?.[0];
|
|
425
|
+
}
|
|
426
|
+
if (selectedModel) {
|
|
427
|
+
if (modelType === 'chat') {
|
|
428
|
+
setChatModel(selectedModel.id);
|
|
429
|
+
setChatModelProperties(selectedModel.properties);
|
|
430
|
+
} else {
|
|
431
|
+
setInlineCompletionModel(selectedModel.id);
|
|
432
|
+
setInlineCompletionModelProperties(selectedModel.properties);
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
if (modelType === 'chat') {
|
|
436
|
+
setChatModelProperties([]);
|
|
437
|
+
} else {
|
|
438
|
+
setInlineCompletionModelProperties([]);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const onModelPropertyChange = (
|
|
444
|
+
modelType: 'chat' | 'inline-completion',
|
|
445
|
+
propertyId: string,
|
|
446
|
+
value: string
|
|
447
|
+
) => {
|
|
448
|
+
const modelProperties =
|
|
449
|
+
modelType === 'chat'
|
|
450
|
+
? chatModelProperties
|
|
451
|
+
: inlineCompletionModelProperties;
|
|
452
|
+
const updatedProperties = modelProperties.map((property: any) => {
|
|
453
|
+
if (property.id === propertyId) {
|
|
454
|
+
return { ...property, value };
|
|
455
|
+
}
|
|
456
|
+
return property;
|
|
457
|
+
});
|
|
458
|
+
if (modelType === 'chat') {
|
|
459
|
+
setChatModelProperties(updatedProperties);
|
|
460
|
+
} else {
|
|
461
|
+
setInlineCompletionModelProperties(updatedProperties);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
useEffect(() => {
|
|
466
|
+
updateModelOptionsForProvider(chatModelProvider, 'chat');
|
|
467
|
+
updateModelOptionsForProvider(
|
|
468
|
+
inlineCompletionModelProvider,
|
|
469
|
+
'inline-completion'
|
|
470
|
+
);
|
|
471
|
+
}, []);
|
|
472
|
+
|
|
473
|
+
useEffect(() => {
|
|
474
|
+
handleSaveSettings();
|
|
475
|
+
}, [
|
|
476
|
+
defaultChatMode,
|
|
477
|
+
chatModelProvider,
|
|
478
|
+
chatModel,
|
|
479
|
+
chatModelProperties,
|
|
480
|
+
inlineCompletionModelProvider,
|
|
481
|
+
inlineCompletionModel,
|
|
482
|
+
inlineCompletionModelProperties,
|
|
483
|
+
storeGitHubAccessToken,
|
|
484
|
+
inlineCompletionDebouncerDelay
|
|
485
|
+
]);
|
|
486
|
+
|
|
487
|
+
return (
|
|
488
|
+
<div className="config-dialog">
|
|
489
|
+
<div className="config-dialog-body">
|
|
490
|
+
{!isInClaudeCodeMode && (
|
|
491
|
+
<div className="model-config-section">
|
|
492
|
+
<div className="model-config-section-header">Default chat mode</div>
|
|
493
|
+
<div className="model-config-section-body">
|
|
494
|
+
<div className="model-config-section-row">
|
|
495
|
+
<div className="model-config-section-column">
|
|
496
|
+
<div>
|
|
497
|
+
<select
|
|
498
|
+
className="jp-mod-styled"
|
|
499
|
+
value={defaultChatMode}
|
|
500
|
+
onChange={event => setDefaultChatMode(event.target.value)}
|
|
501
|
+
>
|
|
502
|
+
<option value="ask">Ask</option>
|
|
503
|
+
<option value="agent">Agent</option>
|
|
504
|
+
</select>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
<div className="model-config-section-column"> </div>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
)}
|
|
512
|
+
|
|
513
|
+
{!isInClaudeCodeMode && (
|
|
514
|
+
<div className="model-config-section">
|
|
515
|
+
<div className="model-config-section-header">Chat model</div>
|
|
516
|
+
<div className="model-config-section-body">
|
|
517
|
+
<div className="model-config-section-row">
|
|
518
|
+
<div className="model-config-section-column">
|
|
519
|
+
<div>Provider</div>
|
|
520
|
+
<div
|
|
521
|
+
title={lockedTip(settingLocks.chat_model_provider.locked)}
|
|
522
|
+
>
|
|
523
|
+
<select
|
|
524
|
+
className="jp-mod-styled"
|
|
525
|
+
disabled={settingLocks.chat_model_provider.locked}
|
|
526
|
+
onChange={event =>
|
|
527
|
+
updateModelOptionsForProvider(
|
|
528
|
+
event.target.value,
|
|
529
|
+
'chat'
|
|
530
|
+
)
|
|
531
|
+
}
|
|
532
|
+
>
|
|
533
|
+
{llmProviders.map((provider: any, index: number) => (
|
|
534
|
+
<option
|
|
535
|
+
key={index}
|
|
536
|
+
value={provider.id}
|
|
537
|
+
selected={provider.id === chatModelProvider}
|
|
538
|
+
>
|
|
539
|
+
{provider.name}
|
|
540
|
+
</option>
|
|
541
|
+
))}
|
|
542
|
+
<option
|
|
543
|
+
key={-1}
|
|
544
|
+
value="none"
|
|
545
|
+
selected={
|
|
546
|
+
chatModelProvider === 'none' ||
|
|
547
|
+
!llmProviders.find(
|
|
548
|
+
provider => provider.id === chatModelProvider
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
>
|
|
552
|
+
None
|
|
553
|
+
</option>
|
|
554
|
+
</select>
|
|
555
|
+
</div>
|
|
556
|
+
</div>
|
|
557
|
+
{!['openai-compatible', 'litellm-compatible', 'none'].includes(
|
|
558
|
+
chatModelProvider
|
|
559
|
+
) &&
|
|
560
|
+
chatModels.length > 0 && (
|
|
561
|
+
<div className="model-config-section-column">
|
|
562
|
+
<div>Model</div>
|
|
563
|
+
{![
|
|
564
|
+
OPENAI_COMPATIBLE_CHAT_MODEL_ID,
|
|
565
|
+
LITELLM_COMPATIBLE_CHAT_MODEL_ID
|
|
566
|
+
].includes(chatModel) &&
|
|
567
|
+
chatModels.length > 0 && (
|
|
568
|
+
<div
|
|
569
|
+
title={lockedTip(settingLocks.chat_model_id.locked)}
|
|
570
|
+
>
|
|
571
|
+
<select
|
|
572
|
+
className="jp-mod-styled"
|
|
573
|
+
disabled={settingLocks.chat_model_id.locked}
|
|
574
|
+
onChange={event =>
|
|
575
|
+
setChatModel(event.target.value)
|
|
576
|
+
}
|
|
577
|
+
>
|
|
578
|
+
{chatModels.map((model: any, index: number) => (
|
|
579
|
+
<option
|
|
580
|
+
key={index}
|
|
581
|
+
value={model.id}
|
|
582
|
+
selected={model.id === chatModel}
|
|
583
|
+
>
|
|
584
|
+
{model.name}
|
|
585
|
+
</option>
|
|
586
|
+
))}
|
|
587
|
+
</select>
|
|
588
|
+
</div>
|
|
589
|
+
)}
|
|
590
|
+
</div>
|
|
591
|
+
)}
|
|
592
|
+
</div>
|
|
593
|
+
|
|
594
|
+
<div className="model-config-section-row">
|
|
595
|
+
<div className="model-config-section-column">
|
|
596
|
+
{chatModelProvider === 'ollama' &&
|
|
597
|
+
chatModels.length === 0 && (
|
|
598
|
+
<div className="ollama-warning-message">
|
|
599
|
+
No Ollama models found! Make sure{' '}
|
|
600
|
+
<a href="https://ollama.com/" target="_blank">
|
|
601
|
+
Ollama
|
|
602
|
+
</a>{' '}
|
|
603
|
+
is running and models are downloaded to your computer.{' '}
|
|
604
|
+
<button
|
|
605
|
+
type="button"
|
|
606
|
+
className="link-button"
|
|
607
|
+
onClick={handleRefreshOllamaModelListClick}
|
|
608
|
+
>
|
|
609
|
+
Try again
|
|
610
|
+
</button>{' '}
|
|
611
|
+
once ready.
|
|
612
|
+
</div>
|
|
613
|
+
)}
|
|
614
|
+
</div>
|
|
615
|
+
</div>
|
|
616
|
+
|
|
617
|
+
<div className="model-config-section-row">
|
|
618
|
+
<div className="model-config-section-column">
|
|
619
|
+
{chatModelProperties.map((property: any, index: number) => (
|
|
620
|
+
<div className="form-field-row" key={index}>
|
|
621
|
+
<div className="form-field-description">
|
|
622
|
+
{property.name} {property.optional ? '(optional)' : ''}
|
|
623
|
+
</div>
|
|
624
|
+
<input
|
|
625
|
+
name="chat-model-id-input"
|
|
626
|
+
placeholder={property.description}
|
|
627
|
+
className="jp-mod-styled"
|
|
628
|
+
spellCheck={false}
|
|
629
|
+
value={property.value}
|
|
630
|
+
onChange={event =>
|
|
631
|
+
onModelPropertyChange(
|
|
632
|
+
'chat',
|
|
633
|
+
property.id,
|
|
634
|
+
event.target.value
|
|
635
|
+
)
|
|
636
|
+
}
|
|
637
|
+
/>
|
|
638
|
+
</div>
|
|
639
|
+
))}
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
</div>
|
|
643
|
+
</div>
|
|
644
|
+
)}
|
|
645
|
+
|
|
646
|
+
<div className="model-config-section">
|
|
647
|
+
<div className="model-config-section-header">Auto-complete model</div>
|
|
648
|
+
<div className="model-config-section-body">
|
|
649
|
+
<div className="model-config-section-row">
|
|
650
|
+
<div className="model-config-section-column">
|
|
651
|
+
<div>Provider</div>
|
|
652
|
+
<div
|
|
653
|
+
title={lockedTip(
|
|
654
|
+
settingLocks.inline_completion_model_provider.locked
|
|
655
|
+
)}
|
|
656
|
+
>
|
|
657
|
+
<select
|
|
658
|
+
className="jp-mod-styled"
|
|
659
|
+
disabled={
|
|
660
|
+
settingLocks.inline_completion_model_provider.locked
|
|
661
|
+
}
|
|
662
|
+
onChange={event =>
|
|
663
|
+
updateModelOptionsForProvider(
|
|
664
|
+
event.target.value,
|
|
665
|
+
'inline-completion'
|
|
666
|
+
)
|
|
667
|
+
}
|
|
668
|
+
>
|
|
669
|
+
{llmProviders.map((provider: any, index: number) => (
|
|
670
|
+
<option
|
|
671
|
+
key={index}
|
|
672
|
+
value={provider.id}
|
|
673
|
+
selected={provider.id === inlineCompletionModelProvider}
|
|
674
|
+
>
|
|
675
|
+
{provider.name}
|
|
676
|
+
</option>
|
|
677
|
+
))}
|
|
678
|
+
<option
|
|
679
|
+
key={-1}
|
|
680
|
+
value="none"
|
|
681
|
+
selected={
|
|
682
|
+
inlineCompletionModelProvider === 'none' ||
|
|
683
|
+
!llmProviders.find(
|
|
684
|
+
provider =>
|
|
685
|
+
provider.id === inlineCompletionModelProvider
|
|
686
|
+
)
|
|
687
|
+
}
|
|
688
|
+
>
|
|
689
|
+
None
|
|
690
|
+
</option>
|
|
691
|
+
</select>
|
|
692
|
+
</div>
|
|
693
|
+
</div>
|
|
694
|
+
{!['openai-compatible', 'litellm-compatible', 'none'].includes(
|
|
695
|
+
inlineCompletionModelProvider
|
|
696
|
+
) && (
|
|
697
|
+
<div className="model-config-section-column">
|
|
698
|
+
<div>Model</div>
|
|
699
|
+
{![
|
|
700
|
+
OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID,
|
|
701
|
+
LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID
|
|
702
|
+
].includes(inlineCompletionModel) && (
|
|
703
|
+
<div
|
|
704
|
+
title={lockedTip(
|
|
705
|
+
settingLocks.inline_completion_model_id.locked
|
|
706
|
+
)}
|
|
707
|
+
>
|
|
708
|
+
<select
|
|
709
|
+
className="jp-mod-styled"
|
|
710
|
+
disabled={
|
|
711
|
+
settingLocks.inline_completion_model_id.locked
|
|
712
|
+
}
|
|
713
|
+
onChange={event =>
|
|
714
|
+
setInlineCompletionModel(event.target.value)
|
|
715
|
+
}
|
|
716
|
+
>
|
|
717
|
+
{inlineCompletionModels.map(
|
|
718
|
+
(model: any, index: number) => (
|
|
719
|
+
<option
|
|
720
|
+
key={index}
|
|
721
|
+
value={model.id}
|
|
722
|
+
selected={model.id === inlineCompletionModel}
|
|
723
|
+
>
|
|
724
|
+
{model.name}
|
|
725
|
+
</option>
|
|
726
|
+
)
|
|
727
|
+
)}
|
|
728
|
+
</select>
|
|
729
|
+
</div>
|
|
730
|
+
)}
|
|
731
|
+
</div>
|
|
732
|
+
)}
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
<div className="model-config-section-row">
|
|
736
|
+
<div className="model-config-section-column">
|
|
737
|
+
{inlineCompletionModelProperties.map(
|
|
738
|
+
(property: any, index: number) => (
|
|
739
|
+
<div className="form-field-row" key={index}>
|
|
740
|
+
<div className="form-field-description">
|
|
741
|
+
{property.name} {property.optional ? '(optional)' : ''}
|
|
742
|
+
</div>
|
|
743
|
+
<input
|
|
744
|
+
name="inline-completion-model-id-input"
|
|
745
|
+
placeholder={property.description}
|
|
746
|
+
className="jp-mod-styled"
|
|
747
|
+
spellCheck={false}
|
|
748
|
+
value={property.value}
|
|
749
|
+
onChange={event =>
|
|
750
|
+
onModelPropertyChange(
|
|
751
|
+
'inline-completion',
|
|
752
|
+
property.id,
|
|
753
|
+
event.target.value
|
|
754
|
+
)
|
|
755
|
+
}
|
|
756
|
+
/>
|
|
757
|
+
</div>
|
|
758
|
+
)
|
|
759
|
+
)}
|
|
760
|
+
</div>
|
|
761
|
+
</div>
|
|
762
|
+
</div>
|
|
763
|
+
</div>
|
|
764
|
+
|
|
765
|
+
<div className="model-config-section-row" style={{ width: '50%' }}>
|
|
766
|
+
<div className="model-config-section-column">
|
|
767
|
+
<div className="form-field-row" style={{ paddingLeft: '10px' }}>
|
|
768
|
+
<div className="form-field-description">
|
|
769
|
+
Auto-complete debouncer delay (ms)
|
|
770
|
+
</div>
|
|
771
|
+
<input
|
|
772
|
+
name="inline-completion-debouncer-delay-input"
|
|
773
|
+
placeholder="Auto-complete debouncer delay (milliseconds)"
|
|
774
|
+
className="jp-mod-styled"
|
|
775
|
+
spellCheck={false}
|
|
776
|
+
value={inlineCompletionDebouncerDelay}
|
|
777
|
+
type="number"
|
|
778
|
+
onChange={event =>
|
|
779
|
+
setInlineCompletionDebouncerDelay(Number(event.target.value))
|
|
780
|
+
}
|
|
781
|
+
/>
|
|
782
|
+
</div>
|
|
783
|
+
</div>
|
|
784
|
+
</div>
|
|
785
|
+
|
|
786
|
+
{!isInClaudeCodeMode &&
|
|
787
|
+
(chatModelProvider === 'github-copilot' ||
|
|
788
|
+
inlineCompletionModelProvider === 'github-copilot') && (
|
|
789
|
+
<div className="model-config-section">
|
|
790
|
+
<div className="model-config-section-header access-token-config-header">
|
|
791
|
+
GitHub Copilot login{' '}
|
|
792
|
+
<a
|
|
793
|
+
href="https://github.com/plmbr/notebook-intelligence/blob/main/README.md#remembering-github-copilot-login"
|
|
794
|
+
target="_blank"
|
|
795
|
+
>
|
|
796
|
+
{' '}
|
|
797
|
+
<VscWarning
|
|
798
|
+
className="access-token-warning"
|
|
799
|
+
title="Click to learn more about security implications"
|
|
800
|
+
/>
|
|
801
|
+
</a>
|
|
802
|
+
</div>
|
|
803
|
+
<div className="model-config-section-body">
|
|
804
|
+
<div className="model-config-section-row">
|
|
805
|
+
<div className="model-config-section-column">
|
|
806
|
+
<label
|
|
807
|
+
title={lockedTip(
|
|
808
|
+
featurePolicies.store_github_access_token.locked
|
|
809
|
+
)}
|
|
810
|
+
>
|
|
811
|
+
<input
|
|
812
|
+
type="checkbox"
|
|
813
|
+
checked={checkedValue(
|
|
814
|
+
featurePolicies.store_github_access_token,
|
|
815
|
+
storeGitHubAccessToken
|
|
816
|
+
)}
|
|
817
|
+
disabled={
|
|
818
|
+
featurePolicies.store_github_access_token.locked
|
|
819
|
+
}
|
|
820
|
+
onChange={event => {
|
|
821
|
+
setStoreGitHubAccessToken(event.target.checked);
|
|
822
|
+
}}
|
|
823
|
+
/>
|
|
824
|
+
Remember my GitHub Copilot access token
|
|
825
|
+
</label>
|
|
826
|
+
</div>
|
|
827
|
+
</div>
|
|
828
|
+
</div>
|
|
829
|
+
</div>
|
|
830
|
+
)}
|
|
831
|
+
|
|
832
|
+
<div className="model-config-section">
|
|
833
|
+
<div className="model-config-section-header">
|
|
834
|
+
Cell output features
|
|
835
|
+
</div>
|
|
836
|
+
<div className="model-config-section-body">
|
|
837
|
+
<div className="model-config-section-row">
|
|
838
|
+
<div className="model-config-section-column">
|
|
839
|
+
<CheckBoxItem
|
|
840
|
+
label="Explain cell errors"
|
|
841
|
+
title="Show a 'Troubleshoot errors in output' context-menu item on failed cells"
|
|
842
|
+
checked={featurePolicies.explain_error.enabled}
|
|
843
|
+
disabled={featurePolicies.explain_error.locked}
|
|
844
|
+
tooltip={lockedTip(featurePolicies.explain_error.locked)}
|
|
845
|
+
onClick={toggleExplainError}
|
|
846
|
+
/>
|
|
847
|
+
</div>
|
|
848
|
+
</div>
|
|
849
|
+
<div className="model-config-section-row">
|
|
850
|
+
<div className="model-config-section-column">
|
|
851
|
+
<CheckBoxItem
|
|
852
|
+
label="Ask about cell outputs"
|
|
853
|
+
title="Right-click a cell output to attach it to the chat"
|
|
854
|
+
checked={featurePolicies.output_followup.enabled}
|
|
855
|
+
disabled={featurePolicies.output_followup.locked}
|
|
856
|
+
tooltip={lockedTip(featurePolicies.output_followup.locked)}
|
|
857
|
+
onClick={toggleOutputFollowup}
|
|
858
|
+
/>
|
|
859
|
+
</div>
|
|
860
|
+
</div>
|
|
861
|
+
<div className="model-config-section-row">
|
|
862
|
+
<div className="model-config-section-column">
|
|
863
|
+
<CheckBoxItem
|
|
864
|
+
label="Show output toolbar"
|
|
865
|
+
title="Show a hover toolbar over cell outputs with Explain / Ask / Troubleshoot buttons"
|
|
866
|
+
checked={featurePolicies.output_toolbar.enabled}
|
|
867
|
+
disabled={featurePolicies.output_toolbar.locked}
|
|
868
|
+
tooltip={lockedTip(featurePolicies.output_toolbar.locked)}
|
|
869
|
+
onClick={toggleOutputToolbar}
|
|
870
|
+
/>
|
|
871
|
+
</div>
|
|
872
|
+
</div>
|
|
873
|
+
</div>
|
|
874
|
+
</div>
|
|
875
|
+
|
|
876
|
+
<div className="model-config-section">
|
|
877
|
+
<div className="model-config-section-header">External changes</div>
|
|
878
|
+
<div className="model-config-section-body">
|
|
879
|
+
<div className="model-config-section-row">
|
|
880
|
+
<div className="model-config-section-column">
|
|
881
|
+
<CheckBoxItem
|
|
882
|
+
label="Refresh open files when changed on disk"
|
|
883
|
+
title="Automatically reload notebook and file editor tabs when an external process (terminal command, sync client, or AI agent) edits the file. Skipped when the tab has unsaved local edits."
|
|
884
|
+
checked={
|
|
885
|
+
featurePolicies.refresh_open_files_on_disk_change.enabled
|
|
886
|
+
}
|
|
887
|
+
disabled={
|
|
888
|
+
featurePolicies.refresh_open_files_on_disk_change.locked
|
|
889
|
+
}
|
|
890
|
+
tooltip={lockedTip(
|
|
891
|
+
featurePolicies.refresh_open_files_on_disk_change.locked
|
|
892
|
+
)}
|
|
893
|
+
onClick={toggleRefreshOpenFilesOnDiskChange}
|
|
894
|
+
/>
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
</div>
|
|
898
|
+
</div>
|
|
899
|
+
|
|
900
|
+
<div className="model-config-section">
|
|
901
|
+
<div className="model-config-section-header">Config file path</div>
|
|
902
|
+
<div className="model-config-section-body">
|
|
903
|
+
<div className="model-config-section-row">
|
|
904
|
+
<div className="model-config-section-column">
|
|
905
|
+
<span
|
|
906
|
+
className="user-code-span"
|
|
907
|
+
onClick={() => {
|
|
908
|
+
void writeTextToClipboard(
|
|
909
|
+
path.join(NBIAPI.config.userConfigDir, 'config.json')
|
|
910
|
+
);
|
|
911
|
+
return true;
|
|
912
|
+
}}
|
|
913
|
+
>
|
|
914
|
+
{path.join(NBIAPI.config.userConfigDir, 'config.json')}{' '}
|
|
915
|
+
<span
|
|
916
|
+
className="copy-icon"
|
|
917
|
+
dangerouslySetInnerHTML={{ __html: copySvgstr }}
|
|
918
|
+
></span>
|
|
919
|
+
</span>
|
|
920
|
+
</div>
|
|
921
|
+
</div>
|
|
922
|
+
</div>
|
|
923
|
+
</div>
|
|
924
|
+
</div>
|
|
925
|
+
</div>
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function SettingsPanelComponentMCPServers(props: any) {
|
|
930
|
+
const nbiConfig = NBIAPI.config;
|
|
931
|
+
const mcpServersRef = useRef<any>(nbiConfig.toolConfig.mcpServers);
|
|
932
|
+
const mcpServerSettingsRef = useRef<any>(nbiConfig.mcpServerSettings);
|
|
933
|
+
const [renderCount, setRenderCount] = useState(1);
|
|
934
|
+
|
|
935
|
+
const [mcpServerEnabledState, setMCPServerEnabledState] = useState(
|
|
936
|
+
new Map<string, Set<string>>(
|
|
937
|
+
mcpServerSettingsToEnabledState(
|
|
938
|
+
mcpServersRef.current,
|
|
939
|
+
mcpServerSettingsRef.current
|
|
940
|
+
)
|
|
941
|
+
)
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
const mcpServerEnabledStateToMcpServerSettings = () => {
|
|
945
|
+
const mcpServerSettings: any = {};
|
|
946
|
+
for (const mcpServer of mcpServersRef.current) {
|
|
947
|
+
if (mcpServerEnabledState.has(mcpServer.id)) {
|
|
948
|
+
const disabledTools = [];
|
|
949
|
+
for (const tool of mcpServer.tools) {
|
|
950
|
+
if (!mcpServerEnabledState.get(mcpServer.id).has(tool.name)) {
|
|
951
|
+
disabledTools.push(tool.name);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
mcpServerSettings[mcpServer.id] = {
|
|
955
|
+
disabled: false,
|
|
956
|
+
disabled_tools: disabledTools
|
|
957
|
+
};
|
|
958
|
+
} else {
|
|
959
|
+
mcpServerSettings[mcpServer.id] = { disabled: true };
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return mcpServerSettings;
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
const syncSettingsToServerState = () => {
|
|
966
|
+
NBIAPI.setConfig({
|
|
967
|
+
mcp_server_settings: mcpServerSettingsRef.current
|
|
968
|
+
});
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
const handleReloadMCPServersClick = async () => {
|
|
972
|
+
await NBIAPI.reloadMCPServers();
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
useEffect(() => {
|
|
976
|
+
syncSettingsToServerState();
|
|
977
|
+
}, [mcpServerSettingsRef.current]);
|
|
978
|
+
|
|
979
|
+
useEffect(() => {
|
|
980
|
+
mcpServerSettingsRef.current = mcpServerEnabledStateToMcpServerSettings();
|
|
981
|
+
setRenderCount(renderCount => renderCount + 1);
|
|
982
|
+
}, [mcpServerEnabledState]);
|
|
983
|
+
|
|
984
|
+
const setMCPServerEnabled = (serverId: string, enabled: boolean) => {
|
|
985
|
+
const currentState = new Map(mcpServerEnabledState);
|
|
986
|
+
if (enabled) {
|
|
987
|
+
if (!(serverId in currentState)) {
|
|
988
|
+
currentState.set(
|
|
989
|
+
serverId,
|
|
990
|
+
new Set<string>(
|
|
991
|
+
mcpServersRef.current
|
|
992
|
+
.find((server: any) => server.id === serverId)
|
|
993
|
+
?.tools.map((tool: any) => tool.name)
|
|
994
|
+
)
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
} else {
|
|
998
|
+
currentState.delete(serverId);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
setMCPServerEnabledState(currentState);
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
const getMCPServerEnabled = (serverId: string) => {
|
|
1005
|
+
return mcpServerEnabledState.has(serverId);
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
const getMCPServerToolEnabled = (serverId: string, toolName: string) => {
|
|
1009
|
+
return (
|
|
1010
|
+
mcpServerEnabledState.has(serverId) &&
|
|
1011
|
+
mcpServerEnabledState.get(serverId).has(toolName)
|
|
1012
|
+
);
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
const setMCPServerToolEnabled = (
|
|
1016
|
+
serverId: string,
|
|
1017
|
+
toolName: string,
|
|
1018
|
+
enabled: boolean
|
|
1019
|
+
) => {
|
|
1020
|
+
const currentState = new Map(mcpServerEnabledState);
|
|
1021
|
+
const serverState = currentState.get(serverId);
|
|
1022
|
+
if (enabled) {
|
|
1023
|
+
serverState.add(toolName);
|
|
1024
|
+
} else {
|
|
1025
|
+
serverState.delete(toolName);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
setMCPServerEnabledState(currentState);
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
useEffect(() => {
|
|
1032
|
+
const handler = () => {
|
|
1033
|
+
mcpServersRef.current = nbiConfig.toolConfig.mcpServers;
|
|
1034
|
+
mcpServerSettingsRef.current = nbiConfig.mcpServerSettings;
|
|
1035
|
+
setRenderCount(renderCount => renderCount + 1);
|
|
1036
|
+
};
|
|
1037
|
+
NBIAPI.configChanged.connect(handler);
|
|
1038
|
+
return () => {
|
|
1039
|
+
NBIAPI.configChanged.disconnect(handler);
|
|
1040
|
+
};
|
|
1041
|
+
}, []);
|
|
1042
|
+
|
|
1043
|
+
return (
|
|
1044
|
+
<div className="config-dialog">
|
|
1045
|
+
<div className="config-dialog-body">
|
|
1046
|
+
<div className="model-config-section">
|
|
1047
|
+
<div
|
|
1048
|
+
className="model-config-section-header"
|
|
1049
|
+
style={{ display: 'flex' }}
|
|
1050
|
+
>
|
|
1051
|
+
<div style={{ flexGrow: 1 }}>MCP Servers</div>
|
|
1052
|
+
<div>
|
|
1053
|
+
<button
|
|
1054
|
+
className="jp-toast-button jp-mod-small jp-Button"
|
|
1055
|
+
onClick={handleReloadMCPServersClick}
|
|
1056
|
+
>
|
|
1057
|
+
<div className="jp-Dialog-buttonLabel">Reload</div>
|
|
1058
|
+
</button>
|
|
1059
|
+
</div>
|
|
1060
|
+
</div>
|
|
1061
|
+
<div className="model-config-section-body">
|
|
1062
|
+
{mcpServersRef.current.length === 0 && renderCount > 0 && (
|
|
1063
|
+
<div className="model-config-section-row">
|
|
1064
|
+
<div className="model-config-section-column">
|
|
1065
|
+
<div>
|
|
1066
|
+
No MCP servers found. Add MCP servers in the configuration
|
|
1067
|
+
file.
|
|
1068
|
+
</div>
|
|
1069
|
+
</div>
|
|
1070
|
+
</div>
|
|
1071
|
+
)}
|
|
1072
|
+
{mcpServersRef.current.length > 0 && renderCount > 0 && (
|
|
1073
|
+
<div className="model-config-section-row">
|
|
1074
|
+
<div className="model-config-section-column">
|
|
1075
|
+
{mcpServersRef.current.map((server: any) => (
|
|
1076
|
+
<div key={server.id}>
|
|
1077
|
+
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
1078
|
+
<CheckBoxItem
|
|
1079
|
+
header={true}
|
|
1080
|
+
label={server.id}
|
|
1081
|
+
checked={getMCPServerEnabled(server.id)}
|
|
1082
|
+
onClick={() => {
|
|
1083
|
+
setMCPServerEnabled(
|
|
1084
|
+
server.id,
|
|
1085
|
+
!getMCPServerEnabled(server.id)
|
|
1086
|
+
);
|
|
1087
|
+
}}
|
|
1088
|
+
></CheckBoxItem>
|
|
1089
|
+
<div
|
|
1090
|
+
className={`server-status-indicator ${server.status}`}
|
|
1091
|
+
title={server.status}
|
|
1092
|
+
></div>
|
|
1093
|
+
</div>
|
|
1094
|
+
{getMCPServerEnabled(server.id) && (
|
|
1095
|
+
<div>
|
|
1096
|
+
{server.tools.length > 0 && (
|
|
1097
|
+
<div className="mcp-server-tools">
|
|
1098
|
+
<div className="mcp-server-tools-header">
|
|
1099
|
+
Tools
|
|
1100
|
+
</div>
|
|
1101
|
+
<div>
|
|
1102
|
+
{server.tools.map((tool: any) => (
|
|
1103
|
+
<PillItem
|
|
1104
|
+
label={tool.name}
|
|
1105
|
+
title={tool.description}
|
|
1106
|
+
checked={getMCPServerToolEnabled(
|
|
1107
|
+
server.id,
|
|
1108
|
+
tool.name
|
|
1109
|
+
)}
|
|
1110
|
+
onClick={() => {
|
|
1111
|
+
setMCPServerToolEnabled(
|
|
1112
|
+
server.id,
|
|
1113
|
+
tool.name,
|
|
1114
|
+
!getMCPServerToolEnabled(
|
|
1115
|
+
server.id,
|
|
1116
|
+
tool.name
|
|
1117
|
+
)
|
|
1118
|
+
);
|
|
1119
|
+
}}
|
|
1120
|
+
></PillItem>
|
|
1121
|
+
))}
|
|
1122
|
+
</div>
|
|
1123
|
+
</div>
|
|
1124
|
+
)}
|
|
1125
|
+
{server.prompts.length > 0 && (
|
|
1126
|
+
<div className="mcp-server-prompts">
|
|
1127
|
+
<div className="mcp-server-prompts-header">
|
|
1128
|
+
Prompts
|
|
1129
|
+
</div>
|
|
1130
|
+
<div>
|
|
1131
|
+
{server.prompts.map((prompt: any) => (
|
|
1132
|
+
<PillItem
|
|
1133
|
+
label={prompt.name}
|
|
1134
|
+
title={prompt.description}
|
|
1135
|
+
checked={true}
|
|
1136
|
+
></PillItem>
|
|
1137
|
+
))}
|
|
1138
|
+
</div>
|
|
1139
|
+
</div>
|
|
1140
|
+
)}
|
|
1141
|
+
</div>
|
|
1142
|
+
)}
|
|
1143
|
+
</div>
|
|
1144
|
+
))}
|
|
1145
|
+
</div>
|
|
1146
|
+
</div>
|
|
1147
|
+
)}
|
|
1148
|
+
<div className="model-config-section-row">
|
|
1149
|
+
<div
|
|
1150
|
+
className="model-config-section-column"
|
|
1151
|
+
style={{ flexGrow: 'initial' }}
|
|
1152
|
+
>
|
|
1153
|
+
<button
|
|
1154
|
+
className="jp-Dialog-button jp-mod-accept jp-mod-styled"
|
|
1155
|
+
style={{ width: 'max-content' }}
|
|
1156
|
+
onClick={props.onEditMCPConfigClicked}
|
|
1157
|
+
>
|
|
1158
|
+
<div className="jp-Dialog-buttonLabel">Add / Edit</div>
|
|
1159
|
+
</button>
|
|
1160
|
+
</div>
|
|
1161
|
+
</div>
|
|
1162
|
+
</div>
|
|
1163
|
+
</div>
|
|
1164
|
+
</div>
|
|
1165
|
+
</div>
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function SettingsPanelComponentClaude(props: any) {
|
|
1170
|
+
const nbiConfig = NBIAPI.config;
|
|
1171
|
+
const claudeSettingsRef = useRef<any>(nbiConfig.claudeSettings);
|
|
1172
|
+
const [_renderCount, setRenderCount] = useState(1);
|
|
1173
|
+
const [claudeEnabled, setClaudeEnabled] = useState(
|
|
1174
|
+
nbiConfig.isInClaudeCodeMode
|
|
1175
|
+
);
|
|
1176
|
+
const [chatModel, setChatModel] = useState(
|
|
1177
|
+
nbiConfig.claudeSettings.chat_model ?? ClaudeModelType.Default
|
|
1178
|
+
);
|
|
1179
|
+
const [inlineCompletionModel, setInlineCompletionModel] = useState(
|
|
1180
|
+
nbiConfig.claudeSettings.inline_completion_model ?? ClaudeModelType.Default
|
|
1181
|
+
);
|
|
1182
|
+
const [apiKey, setApiKey] = useState(nbiConfig.claudeSettings.api_key ?? '');
|
|
1183
|
+
const [baseUrl, setBaseUrl] = useState(
|
|
1184
|
+
nbiConfig.claudeSettings.base_url ?? ''
|
|
1185
|
+
);
|
|
1186
|
+
const [settingSources, setSettingSources] = useState(
|
|
1187
|
+
nbiConfig.claudeSettings.setting_sources ?? []
|
|
1188
|
+
);
|
|
1189
|
+
const [tools, setTools] = useState(
|
|
1190
|
+
nbiConfig.claudeSettings.tools ?? [
|
|
1191
|
+
ClaudeToolType.ClaudeCodeTools,
|
|
1192
|
+
ClaudeToolType.JupyterUITools
|
|
1193
|
+
]
|
|
1194
|
+
);
|
|
1195
|
+
const [continueConversation, setContinueConversation] = useState(
|
|
1196
|
+
nbiConfig.claudeSettings.continue_conversation ?? false
|
|
1197
|
+
);
|
|
1198
|
+
const [claudeModels, setClaudeModels] = useState<IClaudeModelInfo[]>(
|
|
1199
|
+
nbiConfig.claudeModels
|
|
1200
|
+
);
|
|
1201
|
+
const [loadingModels, setLoadingModels] = useState(false);
|
|
1202
|
+
const { featurePolicies, settingLocks } = useNbiPolicies();
|
|
1203
|
+
|
|
1204
|
+
useEffect(() => {
|
|
1205
|
+
const handler = () => {
|
|
1206
|
+
claudeSettingsRef.current = nbiConfig.claudeSettings;
|
|
1207
|
+
setClaudeModels(nbiConfig.claudeModels);
|
|
1208
|
+
setRenderCount(renderCount => renderCount + 1);
|
|
1209
|
+
};
|
|
1210
|
+
NBIAPI.configChanged.connect(handler);
|
|
1211
|
+
return () => {
|
|
1212
|
+
NBIAPI.configChanged.disconnect(handler);
|
|
1213
|
+
};
|
|
1214
|
+
}, []);
|
|
1215
|
+
|
|
1216
|
+
const refreshClaudeModels = async () => {
|
|
1217
|
+
setLoadingModels(true);
|
|
1218
|
+
try {
|
|
1219
|
+
await NBIAPI.updateClaudeModelList();
|
|
1220
|
+
const models = nbiConfig.claudeModels;
|
|
1221
|
+
console.log('claude_models after refresh:', models);
|
|
1222
|
+
setClaudeModels(models);
|
|
1223
|
+
} finally {
|
|
1224
|
+
setLoadingModels(false);
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
const syncSettingsToServerState = () => {
|
|
1229
|
+
NBIAPI.setConfig({
|
|
1230
|
+
claude_settings: {
|
|
1231
|
+
enabled: claudeEnabled,
|
|
1232
|
+
chat_model: chatModel,
|
|
1233
|
+
inline_completion_model: inlineCompletionModel,
|
|
1234
|
+
api_key: apiKey,
|
|
1235
|
+
base_url: baseUrl,
|
|
1236
|
+
setting_sources: settingSources,
|
|
1237
|
+
tools: tools,
|
|
1238
|
+
continue_conversation: continueConversation
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
useEffect(() => {
|
|
1244
|
+
syncSettingsToServerState();
|
|
1245
|
+
}, [
|
|
1246
|
+
claudeEnabled,
|
|
1247
|
+
chatModel,
|
|
1248
|
+
inlineCompletionModel,
|
|
1249
|
+
apiKey,
|
|
1250
|
+
baseUrl,
|
|
1251
|
+
settingSources,
|
|
1252
|
+
tools,
|
|
1253
|
+
continueConversation
|
|
1254
|
+
]);
|
|
1255
|
+
|
|
1256
|
+
return (
|
|
1257
|
+
<div className="config-dialog claude-mode-config-dialog">
|
|
1258
|
+
<div className="config-dialog-body">
|
|
1259
|
+
<div className="model-config-section">
|
|
1260
|
+
<div className="model-config-section-header">Enable Claude mode</div>
|
|
1261
|
+
<div className="model-config-section-body">
|
|
1262
|
+
<div className="model-config-section-row">
|
|
1263
|
+
<span>
|
|
1264
|
+
This requires a{' '}
|
|
1265
|
+
<a href="https://claude.ai" target="_blank">
|
|
1266
|
+
Claude
|
|
1267
|
+
</a>{' '}
|
|
1268
|
+
account and{' '}
|
|
1269
|
+
<a href="https://code.claude.com/" target="_blank">
|
|
1270
|
+
Claude Code
|
|
1271
|
+
</a>{' '}
|
|
1272
|
+
installed in your system.
|
|
1273
|
+
</span>
|
|
1274
|
+
</div>
|
|
1275
|
+
<div className="model-config-section-row">
|
|
1276
|
+
<div className="model-config-section-column">
|
|
1277
|
+
<div>
|
|
1278
|
+
<CheckBoxItem
|
|
1279
|
+
header={true}
|
|
1280
|
+
label="Enable Claude mode"
|
|
1281
|
+
checked={checkedValue(
|
|
1282
|
+
featurePolicies.claude_mode,
|
|
1283
|
+
claudeEnabled
|
|
1284
|
+
)}
|
|
1285
|
+
disabled={featurePolicies.claude_mode.locked}
|
|
1286
|
+
tooltip={lockedTip(featurePolicies.claude_mode.locked)}
|
|
1287
|
+
onClick={() => {
|
|
1288
|
+
setClaudeEnabled(!claudeEnabled);
|
|
1289
|
+
}}
|
|
1290
|
+
></CheckBoxItem>
|
|
1291
|
+
</div>
|
|
1292
|
+
</div>
|
|
1293
|
+
</div>
|
|
1294
|
+
</div>
|
|
1295
|
+
</div>
|
|
1296
|
+
|
|
1297
|
+
<div className="model-config-section">
|
|
1298
|
+
<div
|
|
1299
|
+
className="model-config-section-header"
|
|
1300
|
+
style={{ display: 'flex' }}
|
|
1301
|
+
>
|
|
1302
|
+
<div style={{ flexGrow: 1 }}>Models</div>
|
|
1303
|
+
<div>
|
|
1304
|
+
<button
|
|
1305
|
+
className="jp-toast-button jp-mod-small jp-Button"
|
|
1306
|
+
onClick={refreshClaudeModels}
|
|
1307
|
+
disabled={loadingModels}
|
|
1308
|
+
>
|
|
1309
|
+
<div className="jp-Dialog-buttonLabel">
|
|
1310
|
+
{loadingModels ? 'Loading...' : 'Refresh'}
|
|
1311
|
+
</div>
|
|
1312
|
+
</button>
|
|
1313
|
+
</div>
|
|
1314
|
+
</div>
|
|
1315
|
+
<div className="model-config-section-body">
|
|
1316
|
+
<div className="model-config-section-row">
|
|
1317
|
+
<div className="model-config-section-column">
|
|
1318
|
+
<div id="nbi-claude-chat-model-label">Chat model</div>
|
|
1319
|
+
<div title={lockedTip(settingLocks.claude_chat_model.locked)}>
|
|
1320
|
+
<select
|
|
1321
|
+
className="jp-mod-styled"
|
|
1322
|
+
aria-labelledby="nbi-claude-chat-model-label"
|
|
1323
|
+
aria-describedby={
|
|
1324
|
+
settingLocks.claude_chat_model.locked
|
|
1325
|
+
? 'nbi-claude-chat-model-lock-reason'
|
|
1326
|
+
: undefined
|
|
1327
|
+
}
|
|
1328
|
+
disabled={settingLocks.claude_chat_model.locked}
|
|
1329
|
+
value={chatModel}
|
|
1330
|
+
onChange={event => setChatModel(event.target.value)}
|
|
1331
|
+
>
|
|
1332
|
+
<option value={ClaudeModelType.Default}>
|
|
1333
|
+
Default (recommended)
|
|
1334
|
+
</option>
|
|
1335
|
+
{/* Placeholder for a persisted model id that hasn't
|
|
1336
|
+
landed in `claudeModels` yet (empty cache, no api
|
|
1337
|
+
key, mid-fetch). Rendered just after Default so
|
|
1338
|
+
the active value sits near the top of the list. */}
|
|
1339
|
+
{chatModel !== ClaudeModelType.Default &&
|
|
1340
|
+
!claudeModels.some(m => m.id === chatModel) && (
|
|
1341
|
+
<option key={chatModel} value={chatModel}>
|
|
1342
|
+
{chatModel}
|
|
1343
|
+
</option>
|
|
1344
|
+
)}
|
|
1345
|
+
{claudeModels.map(model => (
|
|
1346
|
+
<option key={model.id} value={model.id}>
|
|
1347
|
+
{model.name}
|
|
1348
|
+
</option>
|
|
1349
|
+
))}
|
|
1350
|
+
</select>
|
|
1351
|
+
{settingLocks.claude_chat_model.locked && (
|
|
1352
|
+
<span
|
|
1353
|
+
id="nbi-claude-chat-model-lock-reason"
|
|
1354
|
+
className="nbi-sr-only"
|
|
1355
|
+
>
|
|
1356
|
+
Locked by your administrator
|
|
1357
|
+
</span>
|
|
1358
|
+
)}
|
|
1359
|
+
</div>
|
|
1360
|
+
</div>
|
|
1361
|
+
<div className="model-config-section-column">
|
|
1362
|
+
<div id="nbi-claude-inline-model-label">
|
|
1363
|
+
Auto-complete model
|
|
1364
|
+
</div>
|
|
1365
|
+
<div
|
|
1366
|
+
title={lockedTip(
|
|
1367
|
+
settingLocks.claude_inline_completion_model.locked
|
|
1368
|
+
)}
|
|
1369
|
+
>
|
|
1370
|
+
<select
|
|
1371
|
+
className="jp-mod-styled"
|
|
1372
|
+
aria-labelledby="nbi-claude-inline-model-label"
|
|
1373
|
+
aria-describedby={
|
|
1374
|
+
settingLocks.claude_inline_completion_model.locked
|
|
1375
|
+
? 'nbi-claude-inline-model-lock-reason'
|
|
1376
|
+
: undefined
|
|
1377
|
+
}
|
|
1378
|
+
disabled={
|
|
1379
|
+
settingLocks.claude_inline_completion_model.locked
|
|
1380
|
+
}
|
|
1381
|
+
value={inlineCompletionModel}
|
|
1382
|
+
onChange={event =>
|
|
1383
|
+
setInlineCompletionModel(event.target.value)
|
|
1384
|
+
}
|
|
1385
|
+
>
|
|
1386
|
+
<option value={ClaudeModelType.None}>None</option>
|
|
1387
|
+
<option value={ClaudeModelType.Inherit}>
|
|
1388
|
+
Inherit from general settings
|
|
1389
|
+
</option>
|
|
1390
|
+
<option value={ClaudeModelType.Default}>
|
|
1391
|
+
Default (recommended)
|
|
1392
|
+
</option>
|
|
1393
|
+
{![
|
|
1394
|
+
ClaudeModelType.None,
|
|
1395
|
+
ClaudeModelType.Inherit,
|
|
1396
|
+
ClaudeModelType.Default
|
|
1397
|
+
].includes(inlineCompletionModel as ClaudeModelType) &&
|
|
1398
|
+
!claudeModels.some(
|
|
1399
|
+
m => m.id === inlineCompletionModel
|
|
1400
|
+
) && (
|
|
1401
|
+
<option
|
|
1402
|
+
key={inlineCompletionModel}
|
|
1403
|
+
value={inlineCompletionModel}
|
|
1404
|
+
>
|
|
1405
|
+
{inlineCompletionModel}
|
|
1406
|
+
</option>
|
|
1407
|
+
)}
|
|
1408
|
+
{claudeModels.map(model => (
|
|
1409
|
+
<option key={model.id} value={model.id}>
|
|
1410
|
+
{model.name}
|
|
1411
|
+
</option>
|
|
1412
|
+
))}
|
|
1413
|
+
</select>
|
|
1414
|
+
{settingLocks.claude_inline_completion_model.locked && (
|
|
1415
|
+
<span
|
|
1416
|
+
id="nbi-claude-inline-model-lock-reason"
|
|
1417
|
+
className="nbi-sr-only"
|
|
1418
|
+
>
|
|
1419
|
+
Locked by your administrator
|
|
1420
|
+
</span>
|
|
1421
|
+
)}
|
|
1422
|
+
</div>
|
|
1423
|
+
</div>
|
|
1424
|
+
</div>
|
|
1425
|
+
</div>
|
|
1426
|
+
</div>
|
|
1427
|
+
|
|
1428
|
+
<div className="model-config-section">
|
|
1429
|
+
<div className="model-config-section-header">
|
|
1430
|
+
Chat Agent setting sources
|
|
1431
|
+
</div>
|
|
1432
|
+
<div className="model-config-section-body">
|
|
1433
|
+
<div className="model-config-section-row">
|
|
1434
|
+
<div className="model-config-section-column">
|
|
1435
|
+
<div>
|
|
1436
|
+
<CheckBoxItem
|
|
1437
|
+
header={true}
|
|
1438
|
+
label="User"
|
|
1439
|
+
checked={checkedValue(
|
|
1440
|
+
featurePolicies.claude_setting_source_user,
|
|
1441
|
+
settingSources.includes('user')
|
|
1442
|
+
)}
|
|
1443
|
+
disabled={featurePolicies.claude_setting_source_user.locked}
|
|
1444
|
+
tooltip={lockedTip(
|
|
1445
|
+
featurePolicies.claude_setting_source_user.locked
|
|
1446
|
+
)}
|
|
1447
|
+
onClick={() => {
|
|
1448
|
+
setSettingSources(
|
|
1449
|
+
settingSources.includes('user')
|
|
1450
|
+
? settingSources.filter(
|
|
1451
|
+
(source: string) => source !== 'user'
|
|
1452
|
+
)
|
|
1453
|
+
: [...settingSources, 'user']
|
|
1454
|
+
);
|
|
1455
|
+
}}
|
|
1456
|
+
></CheckBoxItem>
|
|
1457
|
+
</div>
|
|
1458
|
+
</div>
|
|
1459
|
+
<div className="model-config-section-column">
|
|
1460
|
+
<div>
|
|
1461
|
+
<CheckBoxItem
|
|
1462
|
+
header={true}
|
|
1463
|
+
label="Project (Jupyter root directory)"
|
|
1464
|
+
checked={checkedValue(
|
|
1465
|
+
featurePolicies.claude_setting_source_project,
|
|
1466
|
+
settingSources.includes('project')
|
|
1467
|
+
)}
|
|
1468
|
+
disabled={
|
|
1469
|
+
featurePolicies.claude_setting_source_project.locked
|
|
1470
|
+
}
|
|
1471
|
+
tooltip={lockedTip(
|
|
1472
|
+
featurePolicies.claude_setting_source_project.locked
|
|
1473
|
+
)}
|
|
1474
|
+
onClick={() => {
|
|
1475
|
+
setSettingSources(
|
|
1476
|
+
settingSources.includes('project')
|
|
1477
|
+
? settingSources.filter(
|
|
1478
|
+
(source: string) => source !== 'project'
|
|
1479
|
+
)
|
|
1480
|
+
: [...settingSources, 'project']
|
|
1481
|
+
);
|
|
1482
|
+
}}
|
|
1483
|
+
></CheckBoxItem>
|
|
1484
|
+
</div>
|
|
1485
|
+
</div>
|
|
1486
|
+
</div>
|
|
1487
|
+
</div>
|
|
1488
|
+
</div>
|
|
1489
|
+
|
|
1490
|
+
<div className="model-config-section">
|
|
1491
|
+
<div className="model-config-section-header">Chat Agent tools</div>
|
|
1492
|
+
<div className="model-config-section-body">
|
|
1493
|
+
<div className="model-config-section-row">
|
|
1494
|
+
<div className="model-config-section-column">
|
|
1495
|
+
<div>
|
|
1496
|
+
<CheckBoxItem
|
|
1497
|
+
header={true}
|
|
1498
|
+
label="Claude Code tools"
|
|
1499
|
+
checked={checkedValue(
|
|
1500
|
+
featurePolicies.claude_code_tools,
|
|
1501
|
+
tools.includes(ClaudeToolType.ClaudeCodeTools)
|
|
1502
|
+
)}
|
|
1503
|
+
disabled={true}
|
|
1504
|
+
tooltip={lockedTip(
|
|
1505
|
+
featurePolicies.claude_code_tools.locked
|
|
1506
|
+
)}
|
|
1507
|
+
onClick={() => {
|
|
1508
|
+
setTools(
|
|
1509
|
+
tools.includes(ClaudeToolType.ClaudeCodeTools)
|
|
1510
|
+
? tools.filter(
|
|
1511
|
+
(tool: string) =>
|
|
1512
|
+
tool !== ClaudeToolType.ClaudeCodeTools
|
|
1513
|
+
)
|
|
1514
|
+
: [...tools, ClaudeToolType.ClaudeCodeTools]
|
|
1515
|
+
);
|
|
1516
|
+
}}
|
|
1517
|
+
></CheckBoxItem>
|
|
1518
|
+
</div>
|
|
1519
|
+
</div>
|
|
1520
|
+
<div className="model-config-section-column">
|
|
1521
|
+
<div>
|
|
1522
|
+
<CheckBoxItem
|
|
1523
|
+
header={true}
|
|
1524
|
+
label="Jupyter UI tools"
|
|
1525
|
+
checked={checkedValue(
|
|
1526
|
+
featurePolicies.claude_jupyter_ui_tools,
|
|
1527
|
+
tools.includes(ClaudeToolType.JupyterUITools)
|
|
1528
|
+
)}
|
|
1529
|
+
disabled={featurePolicies.claude_jupyter_ui_tools.locked}
|
|
1530
|
+
tooltip={lockedTip(
|
|
1531
|
+
featurePolicies.claude_jupyter_ui_tools.locked
|
|
1532
|
+
)}
|
|
1533
|
+
onClick={() => {
|
|
1534
|
+
setTools(
|
|
1535
|
+
tools.includes(ClaudeToolType.JupyterUITools)
|
|
1536
|
+
? tools.filter(
|
|
1537
|
+
(tool: string) =>
|
|
1538
|
+
tool !== ClaudeToolType.JupyterUITools
|
|
1539
|
+
)
|
|
1540
|
+
: [...tools, ClaudeToolType.JupyterUITools]
|
|
1541
|
+
);
|
|
1542
|
+
}}
|
|
1543
|
+
></CheckBoxItem>
|
|
1544
|
+
</div>
|
|
1545
|
+
</div>
|
|
1546
|
+
</div>
|
|
1547
|
+
</div>
|
|
1548
|
+
</div>
|
|
1549
|
+
|
|
1550
|
+
<div className="model-config-section">
|
|
1551
|
+
<div className="model-config-section-header">
|
|
1552
|
+
Conversation History
|
|
1553
|
+
</div>
|
|
1554
|
+
<div className="model-config-section-body">
|
|
1555
|
+
<div className="model-config-section-row">
|
|
1556
|
+
<div className="model-config-section-column">
|
|
1557
|
+
<div>
|
|
1558
|
+
<CheckBoxItem
|
|
1559
|
+
header={true}
|
|
1560
|
+
label="Remember conversation history"
|
|
1561
|
+
checked={checkedValue(
|
|
1562
|
+
featurePolicies.claude_continue_conversation,
|
|
1563
|
+
continueConversation
|
|
1564
|
+
)}
|
|
1565
|
+
disabled={
|
|
1566
|
+
featurePolicies.claude_continue_conversation.locked
|
|
1567
|
+
}
|
|
1568
|
+
tooltip={lockedTip(
|
|
1569
|
+
featurePolicies.claude_continue_conversation.locked
|
|
1570
|
+
)}
|
|
1571
|
+
onClick={() => {
|
|
1572
|
+
setContinueConversation(!continueConversation);
|
|
1573
|
+
}}
|
|
1574
|
+
></CheckBoxItem>
|
|
1575
|
+
</div>
|
|
1576
|
+
</div>
|
|
1577
|
+
</div>
|
|
1578
|
+
</div>
|
|
1579
|
+
</div>
|
|
1580
|
+
|
|
1581
|
+
<div className="model-config-section">
|
|
1582
|
+
<div className="model-config-section-header">Claude account</div>
|
|
1583
|
+
<div className="model-config-section-body">
|
|
1584
|
+
<div className="model-config-section-row">
|
|
1585
|
+
<div className="model-config-section-column">
|
|
1586
|
+
<div className="form-field-row">
|
|
1587
|
+
<div className="form-field-description">
|
|
1588
|
+
API Key (optional)
|
|
1589
|
+
</div>
|
|
1590
|
+
<input
|
|
1591
|
+
name="chat-model-id-input"
|
|
1592
|
+
placeholder={
|
|
1593
|
+
settingLocks.claude_api_key.locked
|
|
1594
|
+
? 'Locked by ANTHROPIC_API_KEY'
|
|
1595
|
+
: 'API Key'
|
|
1596
|
+
}
|
|
1597
|
+
className="jp-mod-styled"
|
|
1598
|
+
spellCheck={false}
|
|
1599
|
+
value={settingLocks.claude_api_key.locked ? '' : apiKey}
|
|
1600
|
+
disabled={settingLocks.claude_api_key.locked}
|
|
1601
|
+
title={lockedTip(settingLocks.claude_api_key.locked)}
|
|
1602
|
+
onChange={event => setApiKey(event.target.value)}
|
|
1603
|
+
/>
|
|
1604
|
+
</div>
|
|
1605
|
+
<div className="form-field-row">
|
|
1606
|
+
<div className="form-field-description">
|
|
1607
|
+
Base URL (optional)
|
|
1608
|
+
</div>
|
|
1609
|
+
<input
|
|
1610
|
+
name="chat-model-id-input"
|
|
1611
|
+
placeholder={
|
|
1612
|
+
settingLocks.claude_base_url.locked
|
|
1613
|
+
? 'Locked by ANTHROPIC_BASE_URL'
|
|
1614
|
+
: 'https://api.anthropic.com'
|
|
1615
|
+
}
|
|
1616
|
+
className="jp-mod-styled"
|
|
1617
|
+
spellCheck={false}
|
|
1618
|
+
value={baseUrl}
|
|
1619
|
+
disabled={settingLocks.claude_base_url.locked}
|
|
1620
|
+
title={lockedTip(settingLocks.claude_base_url.locked)}
|
|
1621
|
+
onChange={event => setBaseUrl(event.target.value)}
|
|
1622
|
+
/>
|
|
1623
|
+
</div>
|
|
1624
|
+
</div>
|
|
1625
|
+
</div>
|
|
1626
|
+
</div>
|
|
1627
|
+
</div>
|
|
1628
|
+
</div>
|
|
1629
|
+
</div>
|
|
1630
|
+
);
|
|
1631
|
+
}
|