@jupyterlite/ai 0.8.0 → 0.9.0-a0

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.
Files changed (162) hide show
  1. package/lib/agent.d.ts +233 -0
  2. package/lib/agent.js +604 -0
  3. package/lib/chat-model.d.ts +195 -0
  4. package/lib/chat-model.js +590 -0
  5. package/lib/completion/completion-provider.d.ts +83 -0
  6. package/lib/completion/completion-provider.js +209 -0
  7. package/lib/completion/index.d.ts +1 -0
  8. package/lib/completion/index.js +1 -0
  9. package/lib/components/clear-button.d.ts +18 -0
  10. package/lib/components/clear-button.js +31 -0
  11. package/lib/components/index.d.ts +3 -0
  12. package/lib/components/index.js +3 -0
  13. package/lib/components/model-select.d.ts +19 -0
  14. package/lib/components/model-select.js +154 -0
  15. package/lib/components/stop-button.d.ts +3 -3
  16. package/lib/components/stop-button.js +8 -9
  17. package/lib/components/token-usage-display.d.ts +45 -0
  18. package/lib/components/token-usage-display.js +74 -0
  19. package/lib/components/tool-select.d.ts +27 -0
  20. package/lib/components/tool-select.js +130 -0
  21. package/lib/icons.d.ts +3 -1
  22. package/lib/icons.js +10 -13
  23. package/lib/index.d.ts +4 -5
  24. package/lib/index.js +322 -167
  25. package/lib/mcp/browser.d.ts +68 -0
  26. package/lib/mcp/browser.js +132 -0
  27. package/lib/models/settings-model.d.ts +69 -0
  28. package/lib/models/settings-model.js +295 -0
  29. package/lib/providers/built-in-providers.d.ts +9 -0
  30. package/lib/providers/built-in-providers.js +192 -0
  31. package/lib/providers/models.d.ts +37 -0
  32. package/lib/providers/models.js +28 -0
  33. package/lib/providers/provider-registry.d.ts +94 -0
  34. package/lib/providers/provider-registry.js +155 -0
  35. package/lib/tokens.d.ts +157 -86
  36. package/lib/tokens.js +16 -12
  37. package/lib/tools/commands.d.ts +11 -0
  38. package/lib/tools/commands.js +126 -0
  39. package/lib/tools/file.d.ts +27 -0
  40. package/lib/tools/file.js +262 -0
  41. package/lib/tools/notebook.d.ts +40 -0
  42. package/lib/tools/notebook.js +762 -0
  43. package/lib/tools/tool-registry.d.ts +35 -0
  44. package/lib/tools/tool-registry.js +55 -0
  45. package/lib/widgets/ai-settings.d.ts +39 -0
  46. package/lib/widgets/ai-settings.js +506 -0
  47. package/lib/widgets/chat-wrapper.d.ts +144 -0
  48. package/lib/widgets/chat-wrapper.js +390 -0
  49. package/lib/widgets/provider-config-dialog.d.ts +13 -0
  50. package/lib/widgets/provider-config-dialog.js +104 -0
  51. package/package.json +150 -41
  52. package/schema/settings-model.json +153 -0
  53. package/src/agent.ts +800 -0
  54. package/src/chat-model.ts +770 -0
  55. package/src/completion/completion-provider.ts +308 -0
  56. package/src/completion/index.ts +1 -0
  57. package/src/components/clear-button.tsx +56 -0
  58. package/src/components/index.ts +3 -0
  59. package/src/components/model-select.tsx +245 -0
  60. package/src/components/stop-button.tsx +11 -11
  61. package/src/components/token-usage-display.tsx +130 -0
  62. package/src/components/tool-select.tsx +218 -0
  63. package/src/icons.ts +12 -14
  64. package/src/index.ts +468 -238
  65. package/src/mcp/browser.ts +213 -0
  66. package/src/models/settings-model.ts +409 -0
  67. package/src/providers/built-in-providers.ts +216 -0
  68. package/src/providers/models.ts +79 -0
  69. package/src/providers/provider-registry.ts +189 -0
  70. package/src/tokens.ts +203 -90
  71. package/src/tools/commands.ts +151 -0
  72. package/src/tools/file.ts +307 -0
  73. package/src/tools/notebook.ts +964 -0
  74. package/src/tools/tool-registry.ts +63 -0
  75. package/src/types.d.ts +4 -0
  76. package/src/widgets/ai-settings.tsx +1100 -0
  77. package/src/widgets/chat-wrapper.tsx +543 -0
  78. package/src/widgets/provider-config-dialog.tsx +256 -0
  79. package/style/base.css +335 -14
  80. package/style/icons/jupyternaut-lite.svg +1 -1
  81. package/lib/base-completer.d.ts +0 -49
  82. package/lib/base-completer.js +0 -14
  83. package/lib/chat-handler.d.ts +0 -56
  84. package/lib/chat-handler.js +0 -201
  85. package/lib/completion-provider.d.ts +0 -34
  86. package/lib/completion-provider.js +0 -32
  87. package/lib/default-prompts.d.ts +0 -2
  88. package/lib/default-prompts.js +0 -31
  89. package/lib/default-providers/Anthropic/completer.d.ts +0 -12
  90. package/lib/default-providers/Anthropic/completer.js +0 -46
  91. package/lib/default-providers/Anthropic/settings-schema.json +0 -70
  92. package/lib/default-providers/ChromeAI/completer.d.ts +0 -12
  93. package/lib/default-providers/ChromeAI/completer.js +0 -56
  94. package/lib/default-providers/ChromeAI/instructions.d.ts +0 -6
  95. package/lib/default-providers/ChromeAI/instructions.js +0 -42
  96. package/lib/default-providers/ChromeAI/settings-schema.json +0 -18
  97. package/lib/default-providers/Gemini/completer.d.ts +0 -12
  98. package/lib/default-providers/Gemini/completer.js +0 -48
  99. package/lib/default-providers/Gemini/instructions.d.ts +0 -2
  100. package/lib/default-providers/Gemini/instructions.js +0 -9
  101. package/lib/default-providers/Gemini/settings-schema.json +0 -64
  102. package/lib/default-providers/MistralAI/completer.d.ts +0 -13
  103. package/lib/default-providers/MistralAI/completer.js +0 -52
  104. package/lib/default-providers/MistralAI/instructions.d.ts +0 -2
  105. package/lib/default-providers/MistralAI/instructions.js +0 -18
  106. package/lib/default-providers/MistralAI/settings-schema.json +0 -75
  107. package/lib/default-providers/Ollama/completer.d.ts +0 -12
  108. package/lib/default-providers/Ollama/completer.js +0 -43
  109. package/lib/default-providers/Ollama/instructions.d.ts +0 -2
  110. package/lib/default-providers/Ollama/instructions.js +0 -70
  111. package/lib/default-providers/Ollama/settings-schema.json +0 -143
  112. package/lib/default-providers/OpenAI/completer.d.ts +0 -12
  113. package/lib/default-providers/OpenAI/completer.js +0 -43
  114. package/lib/default-providers/OpenAI/settings-schema.json +0 -628
  115. package/lib/default-providers/WebLLM/completer.d.ts +0 -21
  116. package/lib/default-providers/WebLLM/completer.js +0 -127
  117. package/lib/default-providers/WebLLM/instructions.d.ts +0 -6
  118. package/lib/default-providers/WebLLM/instructions.js +0 -32
  119. package/lib/default-providers/WebLLM/settings-schema.json +0 -19
  120. package/lib/default-providers/index.d.ts +0 -2
  121. package/lib/default-providers/index.js +0 -179
  122. package/lib/provider.d.ts +0 -144
  123. package/lib/provider.js +0 -412
  124. package/lib/settings/base.json +0 -7
  125. package/lib/settings/index.d.ts +0 -3
  126. package/lib/settings/index.js +0 -3
  127. package/lib/settings/panel.d.ts +0 -226
  128. package/lib/settings/panel.js +0 -510
  129. package/lib/settings/textarea.d.ts +0 -2
  130. package/lib/settings/textarea.js +0 -18
  131. package/lib/settings/utils.d.ts +0 -2
  132. package/lib/settings/utils.js +0 -4
  133. package/lib/types/ai-model.d.ts +0 -24
  134. package/lib/types/ai-model.js +0 -5
  135. package/schema/chat.json +0 -28
  136. package/schema/provider-registry.json +0 -29
  137. package/schema/system-prompts.json +0 -22
  138. package/src/base-completer.ts +0 -75
  139. package/src/chat-handler.ts +0 -262
  140. package/src/completion-provider.ts +0 -64
  141. package/src/default-prompts.ts +0 -33
  142. package/src/default-providers/Anthropic/completer.ts +0 -59
  143. package/src/default-providers/ChromeAI/completer.ts +0 -73
  144. package/src/default-providers/ChromeAI/instructions.ts +0 -45
  145. package/src/default-providers/Gemini/completer.ts +0 -61
  146. package/src/default-providers/Gemini/instructions.ts +0 -9
  147. package/src/default-providers/MistralAI/completer.ts +0 -69
  148. package/src/default-providers/MistralAI/instructions.ts +0 -18
  149. package/src/default-providers/Ollama/completer.ts +0 -54
  150. package/src/default-providers/Ollama/instructions.ts +0 -70
  151. package/src/default-providers/OpenAI/completer.ts +0 -54
  152. package/src/default-providers/WebLLM/completer.ts +0 -151
  153. package/src/default-providers/WebLLM/instructions.ts +0 -33
  154. package/src/default-providers/index.ts +0 -211
  155. package/src/global.d.ts +0 -9
  156. package/src/provider.ts +0 -514
  157. package/src/settings/index.ts +0 -3
  158. package/src/settings/panel.tsx +0 -773
  159. package/src/settings/textarea.tsx +0 -33
  160. package/src/settings/utils.ts +0 -5
  161. package/src/types/ai-model.ts +0 -37
  162. package/src/types/service-worker.d.ts +0 -6
@@ -0,0 +1,1100 @@
1
+ import { IThemeManager } from '@jupyterlab/apputils';
2
+ import { ReactWidget } from '@jupyterlab/ui-components';
3
+ import Add from '@mui/icons-material/Add';
4
+ import Cable from '@mui/icons-material/Cable';
5
+ import CheckCircle from '@mui/icons-material/CheckCircle';
6
+ import CheckCircleOutline from '@mui/icons-material/CheckCircleOutline';
7
+ import Delete from '@mui/icons-material/Delete';
8
+ import Edit from '@mui/icons-material/Edit';
9
+ import Error from '@mui/icons-material/Error';
10
+ import ErrorOutline from '@mui/icons-material/ErrorOutline';
11
+ import MoreVert from '@mui/icons-material/MoreVert';
12
+ import Psychology from '@mui/icons-material/Psychology';
13
+ import Settings from '@mui/icons-material/Settings';
14
+ import {
15
+ Alert,
16
+ Box,
17
+ Button,
18
+ Card,
19
+ CardContent,
20
+ Dialog,
21
+ DialogActions,
22
+ DialogContent,
23
+ DialogTitle,
24
+ Divider,
25
+ FormControl,
26
+ FormControlLabel,
27
+ IconButton,
28
+ InputLabel,
29
+ List,
30
+ ListItem,
31
+ ListItemSecondaryAction,
32
+ ListItemText,
33
+ Menu,
34
+ MenuItem,
35
+ Select,
36
+ Slider,
37
+ Switch,
38
+ Tab,
39
+ Tabs,
40
+ TextField,
41
+ ThemeProvider,
42
+ Typography,
43
+ createTheme
44
+ } from '@mui/material';
45
+ import React, { useEffect, useState } from 'react';
46
+ import { AgentManager } from '../agent';
47
+ import {
48
+ AISettingsModel,
49
+ IAIConfig,
50
+ IMCPServerConfig,
51
+ IProviderConfig
52
+ } from '../models/settings-model';
53
+ import type { IChatProviderRegistry } from '../tokens';
54
+ import { ProviderConfigDialog } from './provider-config-dialog';
55
+
56
+ /**
57
+ * Create a theme that uses IThemeManager to detect theme
58
+ * @param themeManager - Optional theme manager to detect theme
59
+ * @returns A Material-UI theme configured for the current JupyterLab theme
60
+ */
61
+ const createJupyterLabTheme = (themeManager?: IThemeManager) => {
62
+ // Use IThemeManager if available, otherwise default to light theme
63
+ const isDark = themeManager?.theme
64
+ ? !themeManager.isLight(themeManager.theme)
65
+ : false;
66
+
67
+ return createTheme({
68
+ palette: {
69
+ mode: isDark ? 'dark' : 'light'
70
+ }
71
+ });
72
+ };
73
+
74
+ /**
75
+ * A JupyterLab widget for AI settings configuration
76
+ */
77
+ export class AISettingsWidget extends ReactWidget {
78
+ /**
79
+ * Construct a new AI settings widget
80
+ * @param options - The options for initializing the widget
81
+ */
82
+ constructor(options: AISettingsWidget.IOptions) {
83
+ super();
84
+ this._settingsModel = options.settingsModel;
85
+ this._agentManager = options.agentManager;
86
+ this._themeManager = options.themeManager;
87
+ this._chatProviderRegistry = options.chatProviderRegistry;
88
+ this.id = 'jupyterlite-ai-settings';
89
+ this.title.label = 'AI Settings';
90
+ this.title.caption = 'Configure AI providers and behavior';
91
+ this.title.closable = true;
92
+ }
93
+
94
+ /**
95
+ * Render the AI settings component
96
+ * @returns A React element containing the AI settings interface
97
+ */
98
+ protected render(): React.ReactElement {
99
+ return (
100
+ <AISettingsComponent
101
+ model={this._settingsModel}
102
+ agentManager={this._agentManager}
103
+ themeManager={this._themeManager}
104
+ chatProviderRegistry={this._chatProviderRegistry}
105
+ />
106
+ );
107
+ }
108
+
109
+ private _settingsModel: AISettingsModel;
110
+ private _agentManager?: AgentManager;
111
+ private _themeManager?: IThemeManager;
112
+ private _chatProviderRegistry: IChatProviderRegistry;
113
+ }
114
+
115
+ /**
116
+ * Props interface for the AISettingsComponent
117
+ */
118
+ interface IAISettingsComponentProps {
119
+ model: AISettingsModel;
120
+ agentManager?: AgentManager;
121
+ themeManager?: IThemeManager;
122
+ chatProviderRegistry: IChatProviderRegistry;
123
+ }
124
+
125
+ /**
126
+ * The main AI settings component that provides configuration UI
127
+ * @param props - Component props containing models and theme manager
128
+ * @returns A React component for AI settings configuration
129
+ */
130
+ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
131
+ model,
132
+ agentManager,
133
+ themeManager,
134
+ chatProviderRegistry
135
+ }) => {
136
+ if (!model) {
137
+ return <div>Settings model not available</div>;
138
+ }
139
+
140
+ const [config, setConfig] = useState(model.config || {});
141
+ const [theme, setTheme] = useState(() => createJupyterLabTheme(themeManager));
142
+ const [activeTab, setActiveTab] = useState(0);
143
+ const [dialogOpen, setDialogOpen] = useState(false);
144
+ const [editingProvider, setEditingProvider] = useState<
145
+ IProviderConfig | undefined
146
+ >();
147
+ const [menuAnchor, setMenuAnchor] = useState<null | HTMLElement>(null);
148
+ const [menuProviderId, setMenuProviderId] = useState<string>('');
149
+ const [mcpDialogOpen, setMcpDialogOpen] = useState(false);
150
+ const [editingMCPServer, setEditingMCPServer] = useState<
151
+ IMCPServerConfig | undefined
152
+ >();
153
+ const [mcpMenuAnchor, setMcpMenuAnchor] = useState<null | HTMLElement>(null);
154
+ const [mcpMenuServerId, setMcpMenuServerId] = useState<string>('');
155
+
156
+ /**
157
+ * Effect to listen for model state changes and update config
158
+ */
159
+ useEffect(() => {
160
+ if (!model || !model.stateChanged) {
161
+ return;
162
+ }
163
+
164
+ const onStateChanged = () => {
165
+ setConfig(model.config || {});
166
+ };
167
+
168
+ model.stateChanged.connect(onStateChanged);
169
+ return () => {
170
+ model.stateChanged.disconnect(onStateChanged);
171
+ };
172
+ }, [model]);
173
+
174
+ /**
175
+ * Effect to listen for theme changes and update the Material-UI theme
176
+ */
177
+ useEffect(() => {
178
+ if (!themeManager) {
179
+ return;
180
+ }
181
+
182
+ const updateTheme = () => {
183
+ setTheme(createJupyterLabTheme(themeManager));
184
+ };
185
+
186
+ themeManager.themeChanged.connect(updateTheme);
187
+ return () => {
188
+ themeManager.themeChanged.disconnect(updateTheme);
189
+ };
190
+ }, [themeManager]);
191
+
192
+ /**
193
+ * Effect to listen for MCP connection changes to re-render connection status
194
+ */
195
+ useEffect(() => {
196
+ if (!agentManager) {
197
+ return;
198
+ }
199
+
200
+ const onMCPConnectionChanged = () => {
201
+ // Force a re-render by updating the config state
202
+ setConfig(prevConfig => ({ ...prevConfig }));
203
+ };
204
+
205
+ agentManager.mcpConnectionChanged.connect(onMCPConnectionChanged);
206
+ return () => {
207
+ agentManager.mcpConnectionChanged.disconnect(onMCPConnectionChanged);
208
+ };
209
+ }, [agentManager]);
210
+
211
+ /**
212
+ * Handle adding a new AI provider
213
+ * @param providerConfig - The provider configuration to add
214
+ */
215
+ const handleAddProvider = async (
216
+ providerConfig: Omit<IProviderConfig, 'id'>
217
+ ) => {
218
+ await model.addProvider(providerConfig);
219
+ };
220
+
221
+ /**
222
+ * Handle editing an existing AI provider
223
+ * @param providerConfig - The updated provider configuration
224
+ */
225
+ const handleEditProvider = async (
226
+ providerConfig: Omit<IProviderConfig, 'id'>
227
+ ) => {
228
+ if (editingProvider) {
229
+ await model.updateProvider(editingProvider.id, providerConfig);
230
+ setEditingProvider(undefined);
231
+ }
232
+ };
233
+
234
+ /**
235
+ * Handle deleting an AI provider
236
+ * @param id - The ID of the provider to delete
237
+ */
238
+ const handleDeleteProvider = async (id: string) => {
239
+ await model.removeProvider(id);
240
+ setMenuAnchor(null);
241
+ };
242
+
243
+ /**
244
+ * Open the provider edit dialog
245
+ * @param provider - The provider to edit
246
+ */
247
+ const openEditDialog = (provider: IProviderConfig) => {
248
+ setEditingProvider(provider);
249
+ setDialogOpen(true);
250
+ setMenuAnchor(null);
251
+ };
252
+
253
+ /**
254
+ * Open the provider add dialog
255
+ */
256
+ const openAddDialog = () => {
257
+ setEditingProvider(undefined);
258
+ setDialogOpen(true);
259
+ };
260
+
261
+ /**
262
+ * Handle provider menu click
263
+ * @param event - The click event
264
+ * @param providerId - The ID of the provider
265
+ */
266
+ const handleMenuClick = (
267
+ event: React.MouseEvent<HTMLElement>,
268
+ providerId: string
269
+ ) => {
270
+ setMenuAnchor(event.currentTarget);
271
+ setMenuProviderId(providerId);
272
+ };
273
+
274
+ /**
275
+ * Handle provider menu close
276
+ */
277
+ const handleMenuClose = () => {
278
+ setMenuAnchor(null);
279
+ setMenuProviderId('');
280
+ };
281
+
282
+ /**
283
+ * Handle updating AI configuration
284
+ * @param updates - Partial configuration updates to apply
285
+ */
286
+ const handleConfigUpdate = async (updates: Partial<IAIConfig>) => {
287
+ await model.updateConfig(updates);
288
+ };
289
+
290
+ /**
291
+ * Handle adding a new MCP server
292
+ * @param serverConfig - The MCP server configuration to add
293
+ */
294
+ const handleAddMCPServer = async (
295
+ serverConfig: Omit<IMCPServerConfig, 'id'>
296
+ ) => {
297
+ await model.addMCPServer(serverConfig);
298
+ };
299
+
300
+ /**
301
+ * Handle editing an existing MCP server
302
+ * @param serverConfig - The updated MCP server configuration
303
+ */
304
+ const handleEditMCPServer = async (
305
+ serverConfig: Omit<IMCPServerConfig, 'id'>
306
+ ) => {
307
+ if (editingMCPServer) {
308
+ await model.updateMCPServer(editingMCPServer.id, serverConfig);
309
+ setEditingMCPServer(undefined);
310
+ }
311
+ };
312
+
313
+ /**
314
+ * Handle deleting an MCP server
315
+ * @param id - The ID of the MCP server to delete
316
+ */
317
+ const handleDeleteMCPServer = async (id: string) => {
318
+ await model.removeMCPServer(id);
319
+ setMcpMenuAnchor(null);
320
+ };
321
+
322
+ /**
323
+ * Open the MCP server edit dialog
324
+ * @param server - The MCP server to edit
325
+ */
326
+ const openEditMCPDialog = (server: IMCPServerConfig) => {
327
+ setEditingMCPServer(server);
328
+ setMcpDialogOpen(true);
329
+ setMcpMenuAnchor(null);
330
+ };
331
+
332
+ /**
333
+ * Open the MCP server add dialog
334
+ */
335
+ const openAddMCPDialog = () => {
336
+ setEditingMCPServer(undefined);
337
+ setMcpDialogOpen(true);
338
+ };
339
+
340
+ /**
341
+ * Handle MCP server menu click
342
+ * @param event - The click event
343
+ * @param serverId - The ID of the MCP server
344
+ */
345
+ const handleMCPMenuClick = (
346
+ event: React.MouseEvent<HTMLElement>,
347
+ serverId: string
348
+ ) => {
349
+ setMcpMenuAnchor(event.currentTarget);
350
+ setMcpMenuServerId(serverId);
351
+ };
352
+
353
+ /**
354
+ * Handle MCP server menu close
355
+ */
356
+ const handleMCPMenuClose = () => {
357
+ setMcpMenuAnchor(null);
358
+ setMcpMenuServerId('');
359
+ };
360
+
361
+ const isValidConfig = agentManager?.hasValidConfig() ?? false;
362
+
363
+ return (
364
+ <ThemeProvider theme={theme}>
365
+ <Box
366
+ sx={{
367
+ height: '100%',
368
+ maxHeight: '100vh',
369
+ overflow: 'auto',
370
+ p: 2,
371
+ pb: 4,
372
+ fontSize: '0.9rem'
373
+ }}
374
+ >
375
+ {/* Header */}
376
+ <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}>
377
+ <Settings color="primary" sx={{ fontSize: 24 }} />
378
+ <Typography variant="h5" component="h1" sx={{ fontWeight: 600 }}>
379
+ AI Settings
380
+ </Typography>
381
+ </Box>
382
+
383
+ {/* Status Alert */}
384
+ <Alert
385
+ severity={isValidConfig ? 'success' : 'warning'}
386
+ icon={isValidConfig ? <CheckCircle /> : <Error />}
387
+ sx={{ mb: 2 }}
388
+ >
389
+ {isValidConfig
390
+ ? 'Configuration is valid and ready to use'
391
+ : 'Please add and configure a provider to get started'}
392
+ </Alert>
393
+
394
+ {/* Tabs */}
395
+ <Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}>
396
+ <Tabs
397
+ value={activeTab}
398
+ onChange={(_, newValue) => setActiveTab(newValue)}
399
+ >
400
+ <Tab label="Providers" />
401
+ <Tab label="Model Parameters" />
402
+ <Tab label="Behavior" />
403
+ <Tab label="MCP Servers" />
404
+ </Tabs>
405
+ </Box>
406
+
407
+ {/* Tab Panels */}
408
+ {activeTab === 0 && (
409
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
410
+ {/* Active Provider Selection */}
411
+ {config.providers.length > 0 && (
412
+ <Card elevation={2}>
413
+ <CardContent>
414
+ <Typography variant="h6" component="h2" gutterBottom>
415
+ Active Providers
416
+ </Typography>
417
+
418
+ <Box
419
+ sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}
420
+ >
421
+ <FormControl fullWidth>
422
+ <InputLabel>Chat Provider</InputLabel>
423
+ <Select
424
+ value={config.activeProvider}
425
+ label="Chat Provider"
426
+ onChange={e => model.setActiveProvider(e.target.value)}
427
+ >
428
+ {config.providers.map(provider => (
429
+ <MenuItem key={provider.id} value={provider.id}>
430
+ {provider.name}
431
+ </MenuItem>
432
+ ))}
433
+ </Select>
434
+ </FormControl>
435
+
436
+ <FormControlLabel
437
+ control={
438
+ <Switch
439
+ checked={config.useSameProviderForChatAndCompleter}
440
+ onChange={e =>
441
+ handleConfigUpdate({
442
+ useSameProviderForChatAndCompleter:
443
+ e.target.checked
444
+ })
445
+ }
446
+ color="primary"
447
+ />
448
+ }
449
+ label="Use same provider for chat and completions"
450
+ />
451
+
452
+ {!config.useSameProviderForChatAndCompleter && (
453
+ <FormControl fullWidth>
454
+ <InputLabel>Completion Provider</InputLabel>
455
+ <Select
456
+ value={config.activeCompleterProvider || ''}
457
+ label="Completion Provider"
458
+ onChange={e =>
459
+ model.setActiveCompleterProvider(
460
+ e.target.value || undefined
461
+ )
462
+ }
463
+ >
464
+ <MenuItem value="">
465
+ <em>Use chat provider</em>
466
+ </MenuItem>
467
+ {config.providers.map(provider => (
468
+ <MenuItem key={provider.id} value={provider.id}>
469
+ {provider.name}
470
+ </MenuItem>
471
+ ))}
472
+ </Select>
473
+ </FormControl>
474
+ )}
475
+ </Box>
476
+ </CardContent>
477
+ </Card>
478
+ )}
479
+
480
+ {/* Providers Card */}
481
+ <Card elevation={2}>
482
+ <CardContent>
483
+ <Box
484
+ sx={{
485
+ display: 'flex',
486
+ alignItems: 'center',
487
+ justifyContent: 'space-between',
488
+ mb: 2
489
+ }}
490
+ >
491
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
492
+ <Typography variant="h6" component="h2">
493
+ Configured Providers
494
+ </Typography>
495
+ </Box>
496
+ <Button
497
+ variant="contained"
498
+ startIcon={<Add />}
499
+ onClick={openAddDialog}
500
+ size="small"
501
+ >
502
+ Add Provider
503
+ </Button>
504
+ </Box>
505
+
506
+ {config.providers.length === 0 ? (
507
+ <Alert severity="info">
508
+ No providers configured yet. Click "Add Provider" to get
509
+ started.
510
+ </Alert>
511
+ ) : (
512
+ <List>
513
+ {config.providers.map(provider => (
514
+ <ListItem key={provider.id} divider>
515
+ <ListItemText
516
+ primary={provider.name}
517
+ secondary={
518
+ <Typography variant="body2" color="text.secondary">
519
+ {provider.provider} • {provider.model}
520
+ </Typography>
521
+ }
522
+ />
523
+ <ListItemSecondaryAction>
524
+ <IconButton
525
+ onClick={e => handleMenuClick(e, provider.id)}
526
+ size="small"
527
+ >
528
+ <MoreVert />
529
+ </IconButton>
530
+ </ListItemSecondaryAction>
531
+ </ListItem>
532
+ ))}
533
+ </List>
534
+ )}
535
+ </CardContent>
536
+ </Card>
537
+ </Box>
538
+ )}
539
+
540
+ {activeTab === 1 && (
541
+ <Card elevation={2}>
542
+ <CardContent>
543
+ <Box
544
+ sx={{ display: 'flex', alignItems: 'center', mb: 1.5, gap: 1 }}
545
+ >
546
+ <Psychology color="primary" />
547
+ <Typography variant="h6" component="h2">
548
+ Model Parameters
549
+ </Typography>
550
+ </Box>
551
+
552
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
553
+ <Box>
554
+ <Typography variant="body1" gutterBottom>
555
+ Temperature: {config.temperature}
556
+ </Typography>
557
+ <Slider
558
+ value={config.temperature}
559
+ onChange={(_, value) =>
560
+ handleConfigUpdate({ temperature: value as number })
561
+ }
562
+ min={0}
563
+ max={2}
564
+ step={0.1}
565
+ valueLabelDisplay="auto"
566
+ />
567
+ <Typography variant="caption" color="text.secondary">
568
+ Controls response creativity: low value = deterministic and
569
+ focused, middle value = balanced, high value = creative and
570
+ varied
571
+ </Typography>
572
+ </Box>
573
+
574
+ <TextField
575
+ fullWidth
576
+ label="Max Tokens"
577
+ type="number"
578
+ value={config.maxTokens || ''}
579
+ onChange={e =>
580
+ handleConfigUpdate({
581
+ maxTokens: e.target.value
582
+ ? parseInt(e.target.value)
583
+ : undefined
584
+ })
585
+ }
586
+ inputProps={{ min: 1, max: 8192 }}
587
+ helperText="Maximum length of AI responses (leave empty for provider default)"
588
+ />
589
+
590
+ <TextField
591
+ fullWidth
592
+ label="Max Turns"
593
+ type="number"
594
+ value={config.maxTurns}
595
+ onChange={e =>
596
+ handleConfigUpdate({
597
+ maxTurns: parseInt(e.target.value)
598
+ })
599
+ }
600
+ inputProps={{ min: 1, max: 100 }}
601
+ helperText="Maximum number of tool execution turns (when using tools)"
602
+ />
603
+ </Box>
604
+ </CardContent>
605
+ </Card>
606
+ )}
607
+
608
+ {activeTab === 2 && (
609
+ <Card elevation={2}>
610
+ <CardContent>
611
+ <Typography variant="h6" component="h2" gutterBottom>
612
+ Behavior Settings
613
+ </Typography>
614
+
615
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
616
+ <FormControlLabel
617
+ control={
618
+ <Switch
619
+ checked={config.toolsEnabled}
620
+ onChange={e =>
621
+ handleConfigUpdate({
622
+ toolsEnabled: e.target.checked
623
+ })
624
+ }
625
+ color="primary"
626
+ />
627
+ }
628
+ label={
629
+ <Box>
630
+ <Typography variant="body1">Enable Tools</Typography>
631
+ <Typography variant="caption" color="text.secondary">
632
+ Allow the AI to use tools like notebook operations, code
633
+ execution, and file management
634
+ </Typography>
635
+ </Box>
636
+ }
637
+ />
638
+
639
+ <FormControlLabel
640
+ control={
641
+ <Switch
642
+ checked={config.sendWithShiftEnter}
643
+ onChange={e =>
644
+ handleConfigUpdate({
645
+ sendWithShiftEnter: e.target.checked
646
+ })
647
+ }
648
+ color="primary"
649
+ />
650
+ }
651
+ label={
652
+ <Box>
653
+ <Typography variant="body1">
654
+ Send with Shift+Enter
655
+ </Typography>
656
+ <Typography variant="caption" color="text.secondary">
657
+ Use Shift+Enter to send messages (Enter creates new
658
+ line)
659
+ </Typography>
660
+ </Box>
661
+ }
662
+ />
663
+
664
+ <FormControlLabel
665
+ control={
666
+ <Switch
667
+ checked={config.showTokenUsage}
668
+ onChange={e =>
669
+ handleConfigUpdate({
670
+ showTokenUsage: e.target.checked
671
+ })
672
+ }
673
+ color="primary"
674
+ />
675
+ }
676
+ label={
677
+ <Box>
678
+ <Typography variant="body1">Show Token Usage</Typography>
679
+ <Typography variant="caption" color="text.secondary">
680
+ Display token usage information in the chat toolbar
681
+ </Typography>
682
+ </Box>
683
+ }
684
+ />
685
+
686
+ <Divider sx={{ my: 1 }} />
687
+
688
+ <TextField
689
+ fullWidth
690
+ multiline
691
+ rows={3}
692
+ label="System Prompt"
693
+ value={config.systemPrompt}
694
+ onChange={e =>
695
+ handleConfigUpdate({ systemPrompt: e.target.value })
696
+ }
697
+ placeholder="Define the AI's behavior and personality..."
698
+ helperText="Instructions that define how the AI should behave and respond"
699
+ />
700
+
701
+ <Divider sx={{ my: 2 }} />
702
+
703
+ <Box>
704
+ <Typography variant="body1" gutterBottom>
705
+ Commands Requiring Approval
706
+ </Typography>
707
+ <Typography
708
+ variant="caption"
709
+ color="text.secondary"
710
+ gutterBottom
711
+ sx={{ display: 'block' }}
712
+ >
713
+ Commands that require user approval before AI can execute
714
+ them
715
+ </Typography>
716
+
717
+ <List sx={{ mb: 2, maxHeight: 200, overflow: 'auto' }}>
718
+ {config.commandsRequiringApproval.map((command, index) => (
719
+ <ListItem key={index} divider>
720
+ <ListItemText primary={command} />
721
+ <ListItemSecondaryAction>
722
+ <IconButton
723
+ onClick={() => {
724
+ const newCommands = [
725
+ ...config.commandsRequiringApproval
726
+ ];
727
+ newCommands.splice(index, 1);
728
+ handleConfigUpdate({
729
+ commandsRequiringApproval: newCommands
730
+ });
731
+ }}
732
+ size="small"
733
+ >
734
+ <Delete />
735
+ </IconButton>
736
+ </ListItemSecondaryAction>
737
+ </ListItem>
738
+ ))}
739
+ </List>
740
+
741
+ <TextField
742
+ fullWidth
743
+ label="Add New Command"
744
+ placeholder="e.g., notebook:run-cell"
745
+ onKeyDown={e => {
746
+ if (e.key === 'Enter') {
747
+ const value = (
748
+ e.target as HTMLInputElement
749
+ ).value.trim();
750
+ if (
751
+ value &&
752
+ !config.commandsRequiringApproval.includes(value)
753
+ ) {
754
+ const newCommands = [
755
+ ...config.commandsRequiringApproval,
756
+ value
757
+ ];
758
+ handleConfigUpdate({
759
+ commandsRequiringApproval: newCommands
760
+ });
761
+ (e.target as HTMLInputElement).value = '';
762
+ }
763
+ }
764
+ }}
765
+ helperText="Press Enter to add a command. Common commands: notebook:run-cell, console:execute, fileeditor:run-code"
766
+ />
767
+ </Box>
768
+ </Box>
769
+ </CardContent>
770
+ </Card>
771
+ )}
772
+
773
+ {activeTab === 3 && (
774
+ <Card elevation={2}>
775
+ <CardContent>
776
+ <Box
777
+ sx={{
778
+ display: 'flex',
779
+ alignItems: 'center',
780
+ justifyContent: 'space-between',
781
+ mb: 2
782
+ }}
783
+ >
784
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
785
+ <Cable color="primary" />
786
+ <Typography variant="h6" component="h2">
787
+ Remote MCP Servers
788
+ </Typography>
789
+ </Box>
790
+ <Button
791
+ variant="contained"
792
+ startIcon={<Add />}
793
+ onClick={openAddMCPDialog}
794
+ size="small"
795
+ >
796
+ Add Server
797
+ </Button>
798
+ </Box>
799
+
800
+ <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
801
+ Configure remote Model Context Protocol (MCP) servers to extend
802
+ the AI's capabilities with external tools and data sources.
803
+ </Typography>
804
+
805
+ {config.mcpServers.length === 0 ? (
806
+ <Alert severity="info">
807
+ No MCP servers configured yet. Click "Add Server" to connect
808
+ to remote MCP services.
809
+ </Alert>
810
+ ) : (
811
+ <List>
812
+ {config.mcpServers.map(server => (
813
+ <ListItem key={server.id} divider>
814
+ <ListItemText
815
+ primary={
816
+ <Box
817
+ sx={{
818
+ display: 'flex',
819
+ alignItems: 'center',
820
+ gap: 1
821
+ }}
822
+ >
823
+ <Typography variant="body1">
824
+ {server.name}
825
+ </Typography>
826
+ {server.enabled &&
827
+ agentManager?.isMCPServerConnected(
828
+ server.name
829
+ ) && (
830
+ <CheckCircleOutline
831
+ sx={{ color: 'success.main', fontSize: 16 }}
832
+ />
833
+ )}
834
+ {server.enabled &&
835
+ !agentManager?.isMCPServerConnected(
836
+ server.name
837
+ ) && (
838
+ <ErrorOutline
839
+ sx={{ color: 'error.main', fontSize: 16 }}
840
+ />
841
+ )}
842
+ <Switch
843
+ checked={server.enabled}
844
+ onChange={e =>
845
+ model.updateMCPServer(server.id, {
846
+ enabled: e.target.checked
847
+ })
848
+ }
849
+ size="small"
850
+ color="primary"
851
+ />
852
+ </Box>
853
+ }
854
+ secondary={
855
+ <Box>
856
+ <Typography variant="body2" color="text.secondary">
857
+ {server.url}
858
+ </Typography>
859
+ {server.enabled &&
860
+ server.connected !== undefined && (
861
+ <Typography
862
+ variant="caption"
863
+ color="text.secondary"
864
+ >
865
+ Status:{' '}
866
+ {server.connected
867
+ ? 'Connected'
868
+ : 'Connection failed'}
869
+ </Typography>
870
+ )}
871
+ </Box>
872
+ }
873
+ />
874
+ <ListItemSecondaryAction>
875
+ <IconButton
876
+ onClick={e => handleMCPMenuClick(e, server.id)}
877
+ size="small"
878
+ >
879
+ <MoreVert />
880
+ </IconButton>
881
+ </ListItemSecondaryAction>
882
+ </ListItem>
883
+ ))}
884
+ </List>
885
+ )}
886
+ </CardContent>
887
+ </Card>
888
+ )}
889
+
890
+ {/* Provider Configuration Dialog */}
891
+ <ProviderConfigDialog
892
+ open={dialogOpen}
893
+ onClose={() => setDialogOpen(false)}
894
+ onSave={editingProvider ? handleEditProvider : handleAddProvider}
895
+ initialConfig={editingProvider}
896
+ mode={editingProvider ? 'edit' : 'add'}
897
+ chatProviderRegistry={chatProviderRegistry}
898
+ />
899
+
900
+ {/* Provider Menu */}
901
+ <Menu
902
+ anchorEl={menuAnchor}
903
+ open={Boolean(menuAnchor)}
904
+ onClose={handleMenuClose}
905
+ >
906
+ <MenuItem
907
+ onClick={() => {
908
+ const provider = config.providers.find(
909
+ p => p.id === menuProviderId
910
+ );
911
+ if (provider) {
912
+ openEditDialog(provider);
913
+ }
914
+ }}
915
+ >
916
+ <Edit sx={{ mr: 1 }} />
917
+ Edit
918
+ </MenuItem>
919
+ <MenuItem
920
+ onClick={() => handleDeleteProvider(menuProviderId)}
921
+ sx={{ color: 'error.main' }}
922
+ >
923
+ <Delete sx={{ mr: 1 }} />
924
+ Delete
925
+ </MenuItem>
926
+ </Menu>
927
+
928
+ {/* MCP Server Configuration Dialog */}
929
+ <MCPServerDialog
930
+ open={mcpDialogOpen}
931
+ onClose={() => setMcpDialogOpen(false)}
932
+ onSave={editingMCPServer ? handleEditMCPServer : handleAddMCPServer}
933
+ initialConfig={editingMCPServer}
934
+ mode={editingMCPServer ? 'edit' : 'add'}
935
+ />
936
+
937
+ {/* MCP Server Menu */}
938
+ <Menu
939
+ anchorEl={mcpMenuAnchor}
940
+ open={Boolean(mcpMenuAnchor)}
941
+ onClose={handleMCPMenuClose}
942
+ >
943
+ <MenuItem
944
+ onClick={() => {
945
+ const server = config.mcpServers.find(
946
+ s => s.id === mcpMenuServerId
947
+ );
948
+ if (server) {
949
+ openEditMCPDialog(server);
950
+ }
951
+ }}
952
+ >
953
+ <Edit sx={{ mr: 1 }} />
954
+ Edit
955
+ </MenuItem>
956
+ <MenuItem
957
+ onClick={() => handleDeleteMCPServer(mcpMenuServerId)}
958
+ sx={{ color: 'error.main' }}
959
+ >
960
+ <Delete sx={{ mr: 1 }} />
961
+ Delete
962
+ </MenuItem>
963
+ </Menu>
964
+ </Box>
965
+ </ThemeProvider>
966
+ );
967
+ };
968
+
969
+ /**
970
+ * Props interface for the MCPServerDialog component
971
+ */
972
+ interface IMCPServerDialogProps {
973
+ open: boolean;
974
+ onClose: () => void;
975
+ onSave: (config: Omit<IMCPServerConfig, 'id'>) => void;
976
+ initialConfig?: IMCPServerConfig;
977
+ mode: 'add' | 'edit';
978
+ }
979
+
980
+ /**
981
+ * Dialog component for adding/editing MCP server configurations
982
+ * @param props - Component props for the MCP server dialog
983
+ * @returns A React component for MCP server configuration
984
+ */
985
+ const MCPServerDialog: React.FC<IMCPServerDialogProps> = ({
986
+ open,
987
+ onClose,
988
+ onSave,
989
+ initialConfig,
990
+ mode
991
+ }) => {
992
+ const [name, setName] = useState(initialConfig?.name || '');
993
+ const [url, setUrl] = useState(initialConfig?.url || '');
994
+ const [enabled, setEnabled] = useState(initialConfig?.enabled ?? true);
995
+
996
+ /**
997
+ * Effect to reset dialog state when opened with new config
998
+ */
999
+ useEffect(() => {
1000
+ if (open) {
1001
+ setName(initialConfig?.name || '');
1002
+ setUrl(initialConfig?.url || '');
1003
+ setEnabled(initialConfig?.enabled ?? true);
1004
+ }
1005
+ }, [open, initialConfig]);
1006
+
1007
+ /**
1008
+ * Handle saving the MCP server configuration
1009
+ */
1010
+ const handleSave = () => {
1011
+ if (!name.trim() || !url.trim()) {
1012
+ return;
1013
+ }
1014
+
1015
+ onSave({
1016
+ name: name.trim(),
1017
+ url: url.trim(),
1018
+ enabled,
1019
+ connected: false
1020
+ });
1021
+ onClose();
1022
+ };
1023
+
1024
+ /**
1025
+ * Check if a URL is valid
1026
+ * @param url - The URL to validate
1027
+ * @returns true if the URL is valid
1028
+ */
1029
+ const _isValidUrl = (url: string): boolean => {
1030
+ try {
1031
+ new URL(url);
1032
+ return true;
1033
+ } catch {
1034
+ return false;
1035
+ }
1036
+ };
1037
+
1038
+ const canSave = name.trim() && url.trim() && _isValidUrl(url.trim());
1039
+
1040
+ return (
1041
+ <Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
1042
+ <DialogTitle>
1043
+ {mode === 'add' ? 'Add MCP Server' : 'Edit MCP Server'}
1044
+ </DialogTitle>
1045
+ <DialogContent>
1046
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>
1047
+ <TextField
1048
+ autoFocus
1049
+ fullWidth
1050
+ label="Server Name"
1051
+ value={name}
1052
+ onChange={e => setName(e.target.value)}
1053
+ placeholder="My MCP Server"
1054
+ helperText="A friendly name to identify this MCP server"
1055
+ />
1056
+ <TextField
1057
+ fullWidth
1058
+ label="Server URL"
1059
+ value={url}
1060
+ onChange={e => setUrl(e.target.value)}
1061
+ placeholder="https://example.com/mcp"
1062
+ helperText="The HTTP/HTTPS URL of the MCP server"
1063
+ error={Boolean(url.trim() && !_isValidUrl(url.trim()))}
1064
+ />
1065
+ <FormControlLabel
1066
+ control={
1067
+ <Switch
1068
+ checked={enabled}
1069
+ onChange={e => setEnabled(e.target.checked)}
1070
+ color="primary"
1071
+ />
1072
+ }
1073
+ label="Enable this server"
1074
+ />
1075
+ </Box>
1076
+ </DialogContent>
1077
+ <DialogActions>
1078
+ <Button onClick={onClose}>Cancel</Button>
1079
+ <Button onClick={handleSave} variant="contained" disabled={!canSave}>
1080
+ {mode === 'add' ? 'Add' : 'Save'}
1081
+ </Button>
1082
+ </DialogActions>
1083
+ </Dialog>
1084
+ );
1085
+ };
1086
+
1087
+ /**
1088
+ * Namespace for AISettingsWidget types and interfaces
1089
+ */
1090
+ export namespace AISettingsWidget {
1091
+ /**
1092
+ * Options interface for constructing an AISettingsWidget
1093
+ */
1094
+ export interface IOptions {
1095
+ settingsModel: AISettingsModel;
1096
+ agentManager?: AgentManager;
1097
+ themeManager?: IThemeManager;
1098
+ chatProviderRegistry: IChatProviderRegistry;
1099
+ }
1100
+ }