@jupyternaut/persona 0.0.0 → 0.20.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/lib/chat-commands/mention.d.ts +9 -0
- package/lib/chat-commands/mention.js +30 -0
- package/lib/completion/completion-provider.d.ts +86 -0
- package/lib/completion/completion-provider.js +246 -0
- package/lib/completion/index.d.ts +2 -0
- package/lib/completion/index.js +1 -0
- package/lib/components/completion-status.d.ts +26 -0
- package/lib/components/completion-status.js +52 -0
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +1 -0
- package/lib/diff-manager.d.ts +25 -0
- package/lib/diff-manager.js +60 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +522 -0
- package/lib/models/settings-model.d.ts +36 -0
- package/lib/models/settings-model.js +356 -0
- package/lib/persona-registry.d.ts +15 -0
- package/lib/persona-registry.js +29 -0
- package/lib/persona.d.ts +66 -0
- package/lib/persona.js +414 -0
- package/lib/process-attachments.d.ts +5 -0
- package/lib/process-attachments.js +287 -0
- package/lib/tokens.d.ts +101 -0
- package/lib/tokens.js +20 -0
- package/lib/widgets/ai-settings.d.ts +54 -0
- package/lib/widgets/ai-settings.js +572 -0
- package/lib/widgets/provider-config-dialog.d.ts +16 -0
- package/lib/widgets/provider-config-dialog.js +384 -0
- package/package.json +111 -7
- package/schema/settings-model.json +287 -0
- package/src/chat-commands/mention.tsx +46 -0
- package/src/completion/completion-provider.ts +350 -0
- package/src/completion/index.ts +1 -0
- package/src/components/completion-status.tsx +93 -0
- package/src/components/index.ts +1 -0
- package/src/diff-manager.ts +81 -0
- package/src/index.ts +710 -0
- package/src/models/settings-model.ts +415 -0
- package/src/persona-registry.ts +46 -0
- package/src/persona.ts +610 -0
- package/src/process-attachments.ts +369 -0
- package/src/tokens.ts +121 -0
- package/src/widgets/ai-settings.tsx +1308 -0
- package/src/widgets/provider-config-dialog.tsx +997 -0
- package/style/base.css +14 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
- package/README.md +0 -3
- package/index.js +0 -1
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { getEffectiveContextWindow, SECRETS_REPLACEMENT } from '@jupyternaut/agent';
|
|
2
|
+
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
3
|
+
import { Debouncer } from '@lumino/polling';
|
|
4
|
+
import Add from '@mui/icons-material/Add';
|
|
5
|
+
import CheckCircle from '@mui/icons-material/CheckCircle';
|
|
6
|
+
import Delete from '@mui/icons-material/Delete';
|
|
7
|
+
import Edit from '@mui/icons-material/Edit';
|
|
8
|
+
import Error from '@mui/icons-material/Error';
|
|
9
|
+
import InfoOutlined from '@mui/icons-material/InfoOutlined';
|
|
10
|
+
import MoreVert from '@mui/icons-material/MoreVert';
|
|
11
|
+
import Settings from '@mui/icons-material/Settings';
|
|
12
|
+
import { Alert, Box, Button, Card, CardContent, Chip, Divider, FormControl, FormControlLabel, IconButton, InputLabel, List, ListItem, ListItemText, Menu, MenuItem, Select, Switch, Tab, Tabs, TextField, ThemeProvider, Tooltip, Typography, createTheme } from '@mui/material';
|
|
13
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
14
|
+
import { ProviderConfigDialog } from './provider-config-dialog';
|
|
15
|
+
/**
|
|
16
|
+
* Create a theme that uses IThemeManager to detect theme
|
|
17
|
+
* @param themeManager - Optional theme manager to detect theme
|
|
18
|
+
* @returns A Material-UI theme configured for the current JupyterLab theme
|
|
19
|
+
*/
|
|
20
|
+
const createJupyterLabTheme = (themeManager) => {
|
|
21
|
+
// Use IThemeManager if available, otherwise default to light theme
|
|
22
|
+
const isDark = themeManager?.theme
|
|
23
|
+
? !themeManager.isLight(themeManager.theme)
|
|
24
|
+
: false;
|
|
25
|
+
return createTheme({
|
|
26
|
+
palette: {
|
|
27
|
+
mode: isDark ? 'dark' : 'light'
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A JupyterLab widget for AI settings configuration
|
|
33
|
+
*/
|
|
34
|
+
export class AISettingsWidget extends ReactWidget {
|
|
35
|
+
/**
|
|
36
|
+
* Construct a new AI settings widget
|
|
37
|
+
* @param options - The options for initializing the widget
|
|
38
|
+
*/
|
|
39
|
+
constructor(options) {
|
|
40
|
+
super();
|
|
41
|
+
this._settingsModel = options.settingsModel;
|
|
42
|
+
this._agentManagerFactory = options.agentManagerFactory;
|
|
43
|
+
this._themeManager = options.themeManager;
|
|
44
|
+
this._providerRegistry = options.providerRegistry;
|
|
45
|
+
this._secretsAccess = options.secretsAccess;
|
|
46
|
+
this._trans = options.trans;
|
|
47
|
+
this._mcpServerRenderer = options.mcpServerRenderer;
|
|
48
|
+
this.id = 'jupyternaut-persona-settings';
|
|
49
|
+
this.title.label = this._trans.__('Jupyternaut Settings');
|
|
50
|
+
this.title.caption = this._trans.__('Configure AI providers and behavior');
|
|
51
|
+
this.title.closable = true;
|
|
52
|
+
// Disable the secrets manager if the token is empty.
|
|
53
|
+
if (!options.secretsAccess.isAvailable) {
|
|
54
|
+
this._settingsModel.updateConfig({ useSecretsManager: false });
|
|
55
|
+
this._secretsAccess = undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Render the AI settings component
|
|
60
|
+
* @returns A React element containing the AI settings interface
|
|
61
|
+
*/
|
|
62
|
+
render() {
|
|
63
|
+
return (React.createElement(AISettingsComponent, { model: this._settingsModel, agentManagerFactory: this._agentManagerFactory, themeManager: this._themeManager, providerRegistry: this._providerRegistry, secretsAccess: this._secretsAccess, trans: this._trans, McpServerRenderer: this._mcpServerRenderer }));
|
|
64
|
+
}
|
|
65
|
+
_settingsModel;
|
|
66
|
+
_agentManagerFactory;
|
|
67
|
+
_themeManager;
|
|
68
|
+
_providerRegistry;
|
|
69
|
+
_secretsAccess;
|
|
70
|
+
_trans;
|
|
71
|
+
_mcpServerRenderer;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The main AI settings component that provides configuration UI
|
|
75
|
+
* @param props - Component props containing models and theme manager
|
|
76
|
+
* @returns A React component for AI settings configuration
|
|
77
|
+
*/
|
|
78
|
+
const AISettingsComponent = ({ model, agentManagerFactory, themeManager, providerRegistry, secretsAccess, trans, McpServerRenderer }) => {
|
|
79
|
+
if (!model) {
|
|
80
|
+
return React.createElement("div", null, trans.__('Settings model not available'));
|
|
81
|
+
}
|
|
82
|
+
const [config, setConfig] = useState(model.config || {});
|
|
83
|
+
const [theme, setTheme] = useState(() => createJupyterLabTheme(themeManager));
|
|
84
|
+
const [activeTab, setActiveTab] = useState(0);
|
|
85
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
86
|
+
const [editingProvider, setEditingProvider] = useState();
|
|
87
|
+
const [menuAnchor, setMenuAnchor] = useState(null);
|
|
88
|
+
const [menuProviderId, setMenuProviderId] = useState('');
|
|
89
|
+
const [systemPromptValue, setSystemPromptValue] = useState(config.systemPrompt);
|
|
90
|
+
const systemPromptValueRef = React.useRef(config.systemPrompt);
|
|
91
|
+
const [completionPromptValue, setCompletionPromptValue] = useState(config.completionSystemPrompt);
|
|
92
|
+
const completionPromptValueRef = React.useRef(config.completionSystemPrompt);
|
|
93
|
+
/**
|
|
94
|
+
* Effect to listen for model state changes and update config
|
|
95
|
+
*/
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!model || !model.stateChanged) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const onStateChanged = () => {
|
|
101
|
+
setConfig(model.config || {});
|
|
102
|
+
};
|
|
103
|
+
model.stateChanged.connect(onStateChanged);
|
|
104
|
+
return () => {
|
|
105
|
+
model.stateChanged.disconnect(onStateChanged);
|
|
106
|
+
};
|
|
107
|
+
}, [model]);
|
|
108
|
+
/**
|
|
109
|
+
* Effect to listen for theme changes and update the Material-UI theme
|
|
110
|
+
*/
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!themeManager) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const updateTheme = () => {
|
|
116
|
+
setTheme(createJupyterLabTheme(themeManager));
|
|
117
|
+
};
|
|
118
|
+
themeManager.themeChanged.connect(updateTheme);
|
|
119
|
+
return () => {
|
|
120
|
+
themeManager.themeChanged.disconnect(updateTheme);
|
|
121
|
+
};
|
|
122
|
+
}, [themeManager]);
|
|
123
|
+
/**
|
|
124
|
+
* Effect to listen for MCP connection changes to re-render connection status
|
|
125
|
+
*/
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!agentManagerFactory) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const onMCPConnectionChanged = () => {
|
|
131
|
+
// Force a re-render by updating the config state
|
|
132
|
+
setConfig(prevConfig => ({ ...prevConfig }));
|
|
133
|
+
};
|
|
134
|
+
agentManagerFactory.mcpConnectionChanged.connect(onMCPConnectionChanged);
|
|
135
|
+
return () => {
|
|
136
|
+
agentManagerFactory.mcpConnectionChanged.disconnect(onMCPConnectionChanged);
|
|
137
|
+
};
|
|
138
|
+
}, [agentManagerFactory]);
|
|
139
|
+
// Sync local state when config changes externally
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
setSystemPromptValue(config.systemPrompt);
|
|
142
|
+
systemPromptValueRef.current = config.systemPrompt;
|
|
143
|
+
}, [config.systemPrompt]);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
setCompletionPromptValue(config.completionSystemPrompt);
|
|
146
|
+
completionPromptValueRef.current = config.completionSystemPrompt;
|
|
147
|
+
}, [config.completionSystemPrompt]);
|
|
148
|
+
const promptDebouncer = useMemo(() => new Debouncer(async () => {
|
|
149
|
+
await handleConfigUpdate({
|
|
150
|
+
systemPrompt: systemPromptValueRef.current,
|
|
151
|
+
completionSystemPrompt: completionPromptValueRef.current
|
|
152
|
+
});
|
|
153
|
+
}, 1000), []);
|
|
154
|
+
// Cleanup debouncer on unmount
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
return () => {
|
|
157
|
+
promptDebouncer.dispose();
|
|
158
|
+
};
|
|
159
|
+
}, [promptDebouncer]);
|
|
160
|
+
const handleSystemPromptChange = (value) => {
|
|
161
|
+
setSystemPromptValue(value);
|
|
162
|
+
systemPromptValueRef.current = value;
|
|
163
|
+
void promptDebouncer.invoke();
|
|
164
|
+
};
|
|
165
|
+
const handleCompletionPromptChange = (value) => {
|
|
166
|
+
setCompletionPromptValue(value);
|
|
167
|
+
completionPromptValueRef.current = value;
|
|
168
|
+
void promptDebouncer.invoke();
|
|
169
|
+
};
|
|
170
|
+
const getSecretFromManager = async (provider, fieldName) => {
|
|
171
|
+
return secretsAccess?.get(`${provider}:${fieldName}`);
|
|
172
|
+
};
|
|
173
|
+
const setSecretToManager = async (provider, fieldName, value) => {
|
|
174
|
+
await secretsAccess?.set(`${provider}:${fieldName}`, value);
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Attach a secrets field to the secrets manager.
|
|
178
|
+
* @param input - the DOm element to attach.
|
|
179
|
+
* @param provider - the name of the provider.
|
|
180
|
+
* @param fieldName - the name of the field.
|
|
181
|
+
*/
|
|
182
|
+
const handleSecretField = async (input, provider, fieldName) => {
|
|
183
|
+
if (!(model.config.useSecretsManager && secretsAccess?.isAvailable)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
await secretsAccess.attach(`${provider}:${fieldName}`, input);
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Handle adding a new AI provider
|
|
190
|
+
* @param providerConfig - The provider configuration to add
|
|
191
|
+
*/
|
|
192
|
+
const handleAddProvider = async (providerConfig) => {
|
|
193
|
+
if (model.config.useSecretsManager &&
|
|
194
|
+
secretsAccess?.isAvailable &&
|
|
195
|
+
providerConfig.apiKey) {
|
|
196
|
+
providerConfig.apiKey = SECRETS_REPLACEMENT;
|
|
197
|
+
}
|
|
198
|
+
await model.addProvider(providerConfig);
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Handle editing an existing AI provider
|
|
202
|
+
* @param providerConfig - The updated provider configuration
|
|
203
|
+
*/
|
|
204
|
+
const handleEditProvider = async (providerConfig) => {
|
|
205
|
+
if (editingProvider) {
|
|
206
|
+
if (model.config.useSecretsManager &&
|
|
207
|
+
secretsAccess?.isAvailable &&
|
|
208
|
+
providerConfig.apiKey) {
|
|
209
|
+
providerConfig.apiKey = SECRETS_REPLACEMENT;
|
|
210
|
+
}
|
|
211
|
+
await model.updateProvider(editingProvider.id, providerConfig);
|
|
212
|
+
setEditingProvider(undefined);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Handle deleting an AI provider
|
|
217
|
+
* @param id - The ID of the provider to delete
|
|
218
|
+
*/
|
|
219
|
+
const handleDeleteProvider = async (id) => {
|
|
220
|
+
await model.removeProvider(id);
|
|
221
|
+
setMenuAnchor(null);
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Open the provider edit dialog
|
|
225
|
+
* @param provider - The provider to edit
|
|
226
|
+
*/
|
|
227
|
+
const openEditDialog = async (provider) => {
|
|
228
|
+
// Retrieve the API key from the secrets manager if necessary.
|
|
229
|
+
if (model.config.useSecretsManager && secretsAccess?.isAvailable) {
|
|
230
|
+
provider.apiKey =
|
|
231
|
+
(await getSecretFromManager(provider.provider, 'apiKey')) ?? '';
|
|
232
|
+
}
|
|
233
|
+
setEditingProvider(provider);
|
|
234
|
+
setDialogOpen(true);
|
|
235
|
+
setMenuAnchor(null);
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Open the provider add dialog
|
|
239
|
+
*/
|
|
240
|
+
const openAddDialog = () => {
|
|
241
|
+
setEditingProvider(undefined);
|
|
242
|
+
setDialogOpen(true);
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Handle provider menu click
|
|
246
|
+
* @param event - The click event
|
|
247
|
+
* @param providerId - The ID of the provider
|
|
248
|
+
*/
|
|
249
|
+
const handleMenuClick = (event, providerId) => {
|
|
250
|
+
setMenuAnchor(event.currentTarget);
|
|
251
|
+
setMenuProviderId(providerId);
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* Handle provider menu close
|
|
255
|
+
*/
|
|
256
|
+
const handleMenuClose = () => {
|
|
257
|
+
setMenuAnchor(null);
|
|
258
|
+
setMenuProviderId('');
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Handle updating AI configuration
|
|
262
|
+
* @param updates - Partial configuration updates to apply
|
|
263
|
+
*/
|
|
264
|
+
const handleConfigUpdate = async (updates) => {
|
|
265
|
+
if (updates.useSecretsManager !== undefined) {
|
|
266
|
+
if (updates.useSecretsManager) {
|
|
267
|
+
for (const provider of model.config.providers) {
|
|
268
|
+
const settingsApiKey = provider.apiKey;
|
|
269
|
+
// If the secrets manager doesn't have the current API key, set the current
|
|
270
|
+
// one from settings.
|
|
271
|
+
// Update the settings value with SECRETS_REPLACEMENT if a key exist in the
|
|
272
|
+
// secrets manager (was already there or a value was set in settings).
|
|
273
|
+
if (!(await getSecretFromManager(provider.provider, 'apiKey'))) {
|
|
274
|
+
if (settingsApiKey !== undefined) {
|
|
275
|
+
setSecretToManager(provider.provider, 'apiKey', settingsApiKey !== SECRETS_REPLACEMENT ? settingsApiKey : '');
|
|
276
|
+
provider.apiKey = SECRETS_REPLACEMENT;
|
|
277
|
+
await model.updateProvider(provider.id, provider);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
provider.apiKey = SECRETS_REPLACEMENT;
|
|
282
|
+
await model.updateProvider(provider.id, provider);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
for (const provider of model.config.providers) {
|
|
288
|
+
const apiKey = await getSecretFromManager(provider.provider, 'apiKey');
|
|
289
|
+
provider.apiKey = apiKey;
|
|
290
|
+
await model.updateProvider(provider.id, provider);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
await model.updateConfig(updates);
|
|
295
|
+
};
|
|
296
|
+
return (React.createElement(ThemeProvider, { theme: theme },
|
|
297
|
+
React.createElement(Box, { sx: {
|
|
298
|
+
height: '100%',
|
|
299
|
+
maxHeight: '100vh',
|
|
300
|
+
overflow: 'auto',
|
|
301
|
+
p: 2,
|
|
302
|
+
pb: 4,
|
|
303
|
+
boxSizing: 'border-box',
|
|
304
|
+
fontSize: '0.9rem'
|
|
305
|
+
} },
|
|
306
|
+
React.createElement(Box, { sx: { mb: 2, display: 'flex', alignItems: 'center', gap: 2 } },
|
|
307
|
+
React.createElement(Settings, { color: "primary", sx: { fontSize: 24 } }),
|
|
308
|
+
React.createElement(Typography, { variant: "h5", component: "h1", sx: { fontWeight: 600 } }, trans.__('AI Settings'))),
|
|
309
|
+
React.createElement(Box, { sx: { borderBottom: 1, borderColor: 'divider', mb: 2 } },
|
|
310
|
+
React.createElement(Tabs, { value: activeTab, onChange: (_, newValue) => setActiveTab(newValue) },
|
|
311
|
+
React.createElement(Tab, { label: trans.__('Providers') }),
|
|
312
|
+
React.createElement(Tab, { label: trans.__('Behavior') }),
|
|
313
|
+
McpServerRenderer && React.createElement(Tab, { label: trans.__('MCP Servers') }))),
|
|
314
|
+
activeTab === 0 && (React.createElement(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 } },
|
|
315
|
+
config.providers.length > 0 && (React.createElement(Card, { elevation: 2 },
|
|
316
|
+
React.createElement(CardContent, null,
|
|
317
|
+
React.createElement(Typography, { variant: "h6", component: "h2", gutterBottom: true }, trans.__('Default Providers')),
|
|
318
|
+
React.createElement(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 } },
|
|
319
|
+
React.createElement(FormControl, { fullWidth: true },
|
|
320
|
+
React.createElement(InputLabel, null, trans.__('Chat Provider')),
|
|
321
|
+
React.createElement(Select, { value: config.defaultProvider, label: trans.__('Chat Provider'), onChange: e => model.setActiveProvider(e.target.value) }, config.providers.map(provider => (React.createElement(MenuItem, { key: provider.id, value: provider.id }, provider.name))))),
|
|
322
|
+
React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.useSameProviderForChatAndCompleter, onChange: e => handleConfigUpdate({
|
|
323
|
+
useSameProviderForChatAndCompleter: e.target.checked
|
|
324
|
+
}), color: "primary" }), label: trans.__('Use same provider for chat and completions') }),
|
|
325
|
+
!config.useSameProviderForChatAndCompleter && (React.createElement(FormControl, { fullWidth: true },
|
|
326
|
+
React.createElement(InputLabel, null, trans.__('Completion Provider')),
|
|
327
|
+
React.createElement(Select, { value: config.activeCompleterProvider || '', label: trans.__('Completion Provider'), className: "jp-ai-completion-provider-select", onChange: e => model.setActiveCompleterProvider(e.target.value || undefined) },
|
|
328
|
+
React.createElement(MenuItem, { value: "" },
|
|
329
|
+
React.createElement("em", null, trans.__('No completion'))),
|
|
330
|
+
config.providers.map(provider => (React.createElement(MenuItem, { key: provider.id, value: provider.id }, provider.name)))))))))),
|
|
331
|
+
React.createElement(Card, { elevation: 2 },
|
|
332
|
+
React.createElement(CardContent, null,
|
|
333
|
+
React.createElement(Box, { sx: {
|
|
334
|
+
display: 'flex',
|
|
335
|
+
alignItems: 'center',
|
|
336
|
+
justifyContent: 'space-between',
|
|
337
|
+
mb: 2
|
|
338
|
+
} },
|
|
339
|
+
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 } },
|
|
340
|
+
React.createElement(Typography, { variant: "h6", component: "h2" }, trans.__('Configured Providers'))),
|
|
341
|
+
React.createElement(Button, { variant: "contained", startIcon: React.createElement(Add, null), onClick: openAddDialog, size: "small" }, trans.__('Add Provider'))),
|
|
342
|
+
config.providers.length === 0 ? (React.createElement(Alert, { severity: "info" }, trans.__('No providers configured yet. Click "Add Provider" to get started.'))) : (React.createElement(List, null, config.providers.map(provider => {
|
|
343
|
+
const isActive = config.defaultProvider === provider.id;
|
|
344
|
+
const isActiveCompleter = config.useSameProviderForChatAndCompleter
|
|
345
|
+
? isActive
|
|
346
|
+
: config.activeCompleterProvider === provider.id;
|
|
347
|
+
const providerInfo = providerRegistry.getProviderInfo(provider.provider);
|
|
348
|
+
const providerToolCapabilities = providerInfo?.providerToolCapabilities;
|
|
349
|
+
const params = provider.parameters;
|
|
350
|
+
const effectiveContextWindow = getEffectiveContextWindow(provider, providerRegistry);
|
|
351
|
+
const webSearchEnabled = !!providerToolCapabilities?.webSearch &&
|
|
352
|
+
provider.customSettings?.webSearch?.enabled === true;
|
|
353
|
+
const webFetchEnabled = !!providerToolCapabilities?.webFetch &&
|
|
354
|
+
provider.customSettings?.webFetch?.enabled === true;
|
|
355
|
+
return (React.createElement(ListItem, { key: provider.id, sx: {
|
|
356
|
+
flexDirection: 'column',
|
|
357
|
+
alignItems: 'stretch',
|
|
358
|
+
py: 2
|
|
359
|
+
} },
|
|
360
|
+
React.createElement(Box, { sx: {
|
|
361
|
+
display: 'flex',
|
|
362
|
+
justifyContent: 'space-between',
|
|
363
|
+
alignItems: 'flex-start',
|
|
364
|
+
width: '100%',
|
|
365
|
+
mb: 1
|
|
366
|
+
} },
|
|
367
|
+
React.createElement(Box, { sx: { flex: 1 } },
|
|
368
|
+
React.createElement(Box, { sx: {
|
|
369
|
+
display: 'flex',
|
|
370
|
+
alignItems: 'center',
|
|
371
|
+
gap: 1,
|
|
372
|
+
mb: 0.5
|
|
373
|
+
} },
|
|
374
|
+
React.createElement(Typography, { variant: "subtitle1", fontWeight: "medium" }, provider.name),
|
|
375
|
+
isActive && (React.createElement(Chip, { label: trans.__('Chat'), size: "small", color: "primary", icon: React.createElement(CheckCircle, null) })),
|
|
376
|
+
isActiveCompleter && (React.createElement(Chip, { label: trans.__('Completion'), size: "small", color: "secondary", icon: React.createElement(CheckCircle, null) }))),
|
|
377
|
+
React.createElement(Typography, { variant: "body2", color: "text.secondary", gutterBottom: true },
|
|
378
|
+
provider.provider,
|
|
379
|
+
" \u2022 ",
|
|
380
|
+
provider.model,
|
|
381
|
+
provider.description &&
|
|
382
|
+
` • ${provider.description}`),
|
|
383
|
+
(params?.temperature !== undefined ||
|
|
384
|
+
params?.maxOutputTokens !== undefined ||
|
|
385
|
+
params?.maxTurns !== undefined ||
|
|
386
|
+
effectiveContextWindow !== undefined ||
|
|
387
|
+
webSearchEnabled ||
|
|
388
|
+
webFetchEnabled) && (React.createElement(Box, { sx: {
|
|
389
|
+
display: 'flex',
|
|
390
|
+
flexWrap: 'wrap',
|
|
391
|
+
gap: 1,
|
|
392
|
+
mt: 1
|
|
393
|
+
} },
|
|
394
|
+
params?.temperature !== undefined && (React.createElement(Chip, { label: trans.__('Temp: %1', params.temperature), size: "small", variant: "outlined" })),
|
|
395
|
+
params?.maxOutputTokens !== undefined && (React.createElement(Chip, { label: trans.__('Tokens: %1', params.maxOutputTokens), size: "small", variant: "outlined" })),
|
|
396
|
+
params?.maxTurns !== undefined && (React.createElement(Chip, { label: trans.__('Turns: %1', params.maxTurns), size: "small", variant: "outlined" })),
|
|
397
|
+
effectiveContextWindow !== undefined && (React.createElement(Chip, { label: trans.__('Context: %1', effectiveContextWindow), size: "small", variant: "outlined" })),
|
|
398
|
+
webSearchEnabled && (React.createElement(Chip, { label: trans.__('Web Search'), size: "small", variant: "outlined", color: "info" })),
|
|
399
|
+
webFetchEnabled && (React.createElement(Chip, { label: trans.__('Web Fetch'), size: "small", variant: "outlined", color: "info" }))))),
|
|
400
|
+
React.createElement(IconButton, { onClick: e => handleMenuClick(e, provider.id), size: "small" },
|
|
401
|
+
React.createElement(MoreVert, null)))));
|
|
402
|
+
}))))),
|
|
403
|
+
secretsAccess?.isAvailable && (React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.useSecretsManager, onChange: e => handleConfigUpdate({
|
|
404
|
+
useSecretsManager: e.target.checked
|
|
405
|
+
}), color: "primary", sx: { alignSelf: 'flex-start' } }), label: React.createElement("div", null,
|
|
406
|
+
React.createElement("span", null, trans.__('Use the secrets manager to manage API keys')),
|
|
407
|
+
!config.useSecretsManager && (React.createElement(Alert, { severity: "warning", icon: React.createElement(Error, null), sx: { mb: 2 } }, trans.__('The secrets are stored in plain text in settings')))) })))),
|
|
408
|
+
activeTab === 1 && (React.createElement(Card, { elevation: 2 },
|
|
409
|
+
React.createElement(CardContent, null,
|
|
410
|
+
React.createElement(Typography, { variant: "h6", component: "h2", gutterBottom: true }, trans.__('Behavior Settings')),
|
|
411
|
+
React.createElement(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 } },
|
|
412
|
+
React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.toolsEnabled, onChange: e => handleConfigUpdate({
|
|
413
|
+
toolsEnabled: e.target.checked
|
|
414
|
+
}), color: "primary" }), label: React.createElement(Box, null,
|
|
415
|
+
React.createElement(Typography, { variant: "body1" }, trans.__('Enable Tools')),
|
|
416
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, trans.__('Allow the AI to use tools like notebook operations, code execution, and file management'))) }),
|
|
417
|
+
React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.showCellDiff, onChange: e => handleConfigUpdate({
|
|
418
|
+
showCellDiff: e.target.checked
|
|
419
|
+
}), color: "primary" }), label: React.createElement(Box, null,
|
|
420
|
+
React.createElement(Typography, { variant: "body1" }, trans.__('Show Cell Diff')),
|
|
421
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, trans.__('Show diff view when AI modifies cell content'))) }),
|
|
422
|
+
config.showCellDiff && (React.createElement(FormControl, { sx: { ml: 4 } },
|
|
423
|
+
React.createElement(InputLabel, null, trans.__('Diff Display Mode')),
|
|
424
|
+
React.createElement(Select, { value: config.diffDisplayMode, label: trans.__('Diff Display Mode'), onChange: e => handleConfigUpdate({
|
|
425
|
+
diffDisplayMode: e.target.value
|
|
426
|
+
}) },
|
|
427
|
+
React.createElement(MenuItem, { value: "split" }, trans.__('Split View')),
|
|
428
|
+
React.createElement(MenuItem, { value: "unified" }, trans.__('Unified View'))))),
|
|
429
|
+
React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.showFileDiff, onChange: e => handleConfigUpdate({
|
|
430
|
+
showFileDiff: e.target.checked
|
|
431
|
+
}), color: "primary" }), label: React.createElement(Box, null,
|
|
432
|
+
React.createElement(Typography, { variant: "body1" }, trans.__('Show File Diff')),
|
|
433
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, trans.__('Show diff view when AI modifies file content'))) }),
|
|
434
|
+
React.createElement(Divider, { sx: { my: 1 } }),
|
|
435
|
+
React.createElement(TextField, { fullWidth: true, multiline: true, rows: 3, label: trans.__('System Prompt'), value: systemPromptValue, onChange: e => handleSystemPromptChange(e.target.value), placeholder: trans.__("Define the AI's behavior and personality..."), helperText: trans.__('Instructions that define how the AI should behave and respond') }),
|
|
436
|
+
React.createElement(TextField, { fullWidth: true, multiline: true, rows: 3, label: trans.__('Completion System Prompt'), value: completionPromptValue, onChange: e => handleCompletionPromptChange(e.target.value), placeholder: trans.__('Define how the AI should generate code completions...'), helperText: trans.__('Instructions that define how the AI should generate code completions') }),
|
|
437
|
+
React.createElement(Divider, { sx: { my: 2 } }),
|
|
438
|
+
React.createElement(Box, null,
|
|
439
|
+
React.createElement(Typography, { variant: "body1", gutterBottom: true, sx: {
|
|
440
|
+
display: 'inline-flex',
|
|
441
|
+
alignItems: 'center',
|
|
442
|
+
gap: 1
|
|
443
|
+
} },
|
|
444
|
+
trans.__('Skills Paths'),
|
|
445
|
+
React.createElement(Tooltip, { title: trans.__('Directories containing agent skills, relative to the server root. Skills are loaded from all paths; the first occurrence of a skill name takes priority.') },
|
|
446
|
+
React.createElement(InfoOutlined, { sx: { fontSize: 16 } }))),
|
|
447
|
+
React.createElement(List, { sx: { mb: 2, maxHeight: 200, overflow: 'auto' } }, (config.skillsPaths ?? []).map((skillPath, index) => (React.createElement(ListItem, { key: index, divider: true, secondaryAction: React.createElement(IconButton, { onClick: () => {
|
|
448
|
+
const newPaths = [...config.skillsPaths];
|
|
449
|
+
newPaths.splice(index, 1);
|
|
450
|
+
handleConfigUpdate({ skillsPaths: newPaths });
|
|
451
|
+
}, size: "small" },
|
|
452
|
+
React.createElement(Delete, null)) },
|
|
453
|
+
React.createElement(ListItemText, { primary: skillPath }))))),
|
|
454
|
+
React.createElement(TextField, { fullWidth: true, label: trans.__('Add Skills Path'), placeholder: trans.__('e.g., .claude/skills'), onKeyDown: e => {
|
|
455
|
+
if (e.key === 'Enter') {
|
|
456
|
+
const value = e.target.value.trim();
|
|
457
|
+
if (value &&
|
|
458
|
+
!(config.skillsPaths ?? []).includes(value)) {
|
|
459
|
+
const newPaths = [
|
|
460
|
+
...(config.skillsPaths ?? []),
|
|
461
|
+
value
|
|
462
|
+
];
|
|
463
|
+
handleConfigUpdate({ skillsPaths: newPaths });
|
|
464
|
+
e.target.value = '';
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}, helperText: trans.__('Press Enter to add a path. Defaults: .agents/skills, _agents/skills') })),
|
|
468
|
+
React.createElement(Divider, { sx: { my: 2 } }),
|
|
469
|
+
React.createElement(Box, null,
|
|
470
|
+
React.createElement(Typography, { variant: "body1", gutterBottom: true }, trans.__('Commands Requiring Approval')),
|
|
471
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary", gutterBottom: true, sx: { display: 'block' } }, trans.__('Commands that require user approval before AI can execute them')),
|
|
472
|
+
React.createElement(List, { sx: { mb: 2, maxHeight: 200, overflow: 'auto' } }, config.commandsRequiringApproval.map((command, index) => (React.createElement(ListItem, { key: index, divider: true, secondaryAction: React.createElement(IconButton, { onClick: () => {
|
|
473
|
+
const newCommands = [
|
|
474
|
+
...config.commandsRequiringApproval
|
|
475
|
+
];
|
|
476
|
+
newCommands.splice(index, 1);
|
|
477
|
+
handleConfigUpdate({
|
|
478
|
+
commandsRequiringApproval: newCommands
|
|
479
|
+
});
|
|
480
|
+
}, size: "small" },
|
|
481
|
+
React.createElement(Delete, null)) },
|
|
482
|
+
React.createElement(ListItemText, { primary: command }))))),
|
|
483
|
+
React.createElement(TextField, { fullWidth: true, label: trans.__('Add New Command'), placeholder: trans.__('e.g., notebook:run-cell'), onKeyDown: e => {
|
|
484
|
+
if (e.key === 'Enter') {
|
|
485
|
+
const value = e.target.value.trim();
|
|
486
|
+
if (value &&
|
|
487
|
+
!config.commandsRequiringApproval.includes(value)) {
|
|
488
|
+
const newCommands = [
|
|
489
|
+
...config.commandsRequiringApproval,
|
|
490
|
+
value
|
|
491
|
+
];
|
|
492
|
+
handleConfigUpdate({
|
|
493
|
+
commandsRequiringApproval: newCommands
|
|
494
|
+
});
|
|
495
|
+
e.target.value = '';
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}, helperText: trans.__('Press Enter to add a command. Common commands: notebook:run-cell, console:execute, fileeditor:run-code') })),
|
|
499
|
+
React.createElement(Divider, { sx: { my: 2 } }),
|
|
500
|
+
React.createElement(Box, null,
|
|
501
|
+
React.createElement(Typography, { variant: "body1", gutterBottom: true }, trans.__('Commands Auto-Rendering MIME Bundles')),
|
|
502
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary", gutterBottom: true, sx: { display: 'block' } }, trans.__('Only these execute_command command IDs can auto-render MIME bundle outputs in chat')),
|
|
503
|
+
React.createElement(List, { sx: { mb: 2, maxHeight: 200, overflow: 'auto' } }, (config.commandsAutoRenderMimeBundles ?? []).map((command, index) => (React.createElement(ListItem, { key: index, divider: true, secondaryAction: React.createElement(IconButton, { onClick: () => {
|
|
504
|
+
const newCommands = [
|
|
505
|
+
...(config.commandsAutoRenderMimeBundles ??
|
|
506
|
+
[])
|
|
507
|
+
];
|
|
508
|
+
newCommands.splice(index, 1);
|
|
509
|
+
handleConfigUpdate({
|
|
510
|
+
commandsAutoRenderMimeBundles: newCommands
|
|
511
|
+
});
|
|
512
|
+
}, size: "small" },
|
|
513
|
+
React.createElement(Delete, null)) },
|
|
514
|
+
React.createElement(ListItemText, { primary: command }))))),
|
|
515
|
+
React.createElement(TextField, { fullWidth: true, label: trans.__('Add Auto-Render Command'), placeholder: trans.__('e.g., jupyterlab-ai-commands:execute-in-kernel'), onKeyDown: e => {
|
|
516
|
+
if (e.key === 'Enter') {
|
|
517
|
+
const value = e.target.value.trim();
|
|
518
|
+
const existingCommands = config.commandsAutoRenderMimeBundles ?? [];
|
|
519
|
+
if (value && !existingCommands.includes(value)) {
|
|
520
|
+
const newCommands = [...existingCommands, value];
|
|
521
|
+
handleConfigUpdate({
|
|
522
|
+
commandsAutoRenderMimeBundles: newCommands
|
|
523
|
+
});
|
|
524
|
+
e.target.value = '';
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}, helperText: trans.__('Press Enter to add a command. Default: jupyterlab-ai-commands:execute-in-kernel') })),
|
|
528
|
+
React.createElement(Divider, { sx: { my: 2 } }),
|
|
529
|
+
React.createElement(Box, null,
|
|
530
|
+
React.createElement(Typography, { variant: "body1", gutterBottom: true }, trans.__('Trusted MIME Types for Auto-Render')),
|
|
531
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary", gutterBottom: true, sx: { display: 'block' } }, trans.__('When auto-rendering command outputs, these MIME types are marked trusted in chat')),
|
|
532
|
+
React.createElement(List, { sx: { mb: 2, maxHeight: 200, overflow: 'auto' } }, (config.trustedMimeTypesForAutoRender ?? []).map((mimeType, index) => (React.createElement(ListItem, { key: index, divider: true, secondaryAction: React.createElement(IconButton, { onClick: () => {
|
|
533
|
+
const newMimeTypes = [
|
|
534
|
+
...(config.trustedMimeTypesForAutoRender ??
|
|
535
|
+
[])
|
|
536
|
+
];
|
|
537
|
+
newMimeTypes.splice(index, 1);
|
|
538
|
+
handleConfigUpdate({
|
|
539
|
+
trustedMimeTypesForAutoRender: newMimeTypes
|
|
540
|
+
});
|
|
541
|
+
}, size: "small" },
|
|
542
|
+
React.createElement(Delete, null)) },
|
|
543
|
+
React.createElement(ListItemText, { primary: mimeType }))))),
|
|
544
|
+
React.createElement(TextField, { fullWidth: true, label: trans.__('Add Trusted MIME Type'), placeholder: trans.__('e.g., text/html'), onKeyDown: e => {
|
|
545
|
+
if (e.key === 'Enter') {
|
|
546
|
+
const value = e.target.value.trim();
|
|
547
|
+
const existingMimeTypes = config.trustedMimeTypesForAutoRender ?? [];
|
|
548
|
+
if (value && !existingMimeTypes.includes(value)) {
|
|
549
|
+
const newMimeTypes = [...existingMimeTypes, value];
|
|
550
|
+
handleConfigUpdate({
|
|
551
|
+
trustedMimeTypesForAutoRender: newMimeTypes
|
|
552
|
+
});
|
|
553
|
+
e.target.value = '';
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}, helperText: trans.__('Press Enter to add a MIME type. Default: text/html') })))))),
|
|
557
|
+
activeTab === 2 && (React.createElement(Card, { elevation: 2 },
|
|
558
|
+
React.createElement(CardContent, null, McpServerRenderer && React.createElement(McpServerRenderer, null)))),
|
|
559
|
+
React.createElement(ProviderConfigDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSave: editingProvider ? handleEditProvider : handleAddProvider, initialConfig: editingProvider, mode: editingProvider ? 'edit' : 'add', providerRegistry: providerRegistry, handleSecretField: handleSecretField, trans: trans }),
|
|
560
|
+
React.createElement(Menu, { anchorEl: menuAnchor, open: Boolean(menuAnchor), onClose: handleMenuClose },
|
|
561
|
+
React.createElement(MenuItem, { onClick: () => {
|
|
562
|
+
const provider = config.providers.find(p => p.id === menuProviderId);
|
|
563
|
+
if (provider) {
|
|
564
|
+
openEditDialog(provider);
|
|
565
|
+
}
|
|
566
|
+
} },
|
|
567
|
+
React.createElement(Edit, { sx: { mr: 1 } }),
|
|
568
|
+
trans.__('Edit')),
|
|
569
|
+
React.createElement(MenuItem, { onClick: () => handleDeleteProvider(menuProviderId), sx: { color: 'error.main' } },
|
|
570
|
+
React.createElement(Delete, { sx: { mr: 1 } }),
|
|
571
|
+
trans.__('Delete'))))));
|
|
572
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IProviderConfig, IProviderRegistry } from '@jupyternaut/agent';
|
|
2
|
+
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
interface IProviderConfigDialogProps {
|
|
5
|
+
open: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onSave: (config: Omit<IProviderConfig, 'id'>) => void;
|
|
8
|
+
initialConfig?: IProviderConfig;
|
|
9
|
+
mode: 'add' | 'edit';
|
|
10
|
+
providerRegistry: IProviderRegistry;
|
|
11
|
+
handleSecretField: (input: HTMLInputElement, provider: string, fieldName: string) => Promise<void>;
|
|
12
|
+
trans: TranslationBundle;
|
|
13
|
+
}
|
|
14
|
+
export declare const ProviderConfigDialog: React.FC<IProviderConfigDialogProps>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=provider-config-dialog.d.ts.map
|