@jupyterlite/ai 0.9.0-a3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -89
- package/lib/agent.d.ts +10 -4
- package/lib/agent.js +30 -17
- package/lib/chat-model.d.ts +6 -0
- package/lib/chat-model.js +144 -17
- package/lib/completion/completion-provider.js +1 -13
- package/lib/components/completion-status.d.ts +20 -0
- package/lib/components/completion-status.js +51 -0
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/model-select.js +1 -2
- package/lib/diff-manager.d.ts +25 -0
- package/lib/diff-manager.js +60 -0
- package/lib/icons.d.ts +0 -1
- package/lib/icons.js +2 -6
- package/lib/index.d.ts +2 -2
- package/lib/index.js +54 -23
- package/lib/models/settings-model.d.ts +4 -0
- package/lib/models/settings-model.js +24 -2
- package/lib/providers/built-in-providers.d.ts +0 -4
- package/lib/providers/built-in-providers.js +17 -23
- package/lib/tokens.d.ts +74 -0
- package/lib/tokens.js +4 -0
- package/lib/tools/commands.js +36 -35
- package/lib/tools/file.d.ts +10 -1
- package/lib/tools/file.js +235 -146
- package/lib/tools/notebook.d.ts +2 -3
- package/lib/tools/notebook.js +11 -11
- package/lib/widgets/ai-settings.js +78 -13
- package/lib/widgets/provider-config-dialog.js +15 -8
- package/package.json +5 -3
- package/schema/settings-model.json +25 -0
- package/src/agent.ts +35 -20
- package/src/chat-model.ts +182 -19
- package/src/completion/completion-provider.ts +1 -14
- package/src/components/completion-status.tsx +79 -0
- package/src/components/index.ts +1 -0
- package/src/components/model-select.tsx +0 -3
- package/src/diff-manager.ts +81 -0
- package/src/icons.ts +2 -7
- package/src/index.ts +74 -24
- package/src/models/settings-model.ts +28 -2
- package/src/providers/built-in-providers.ts +17 -24
- package/src/tokens.ts +78 -0
- package/src/tools/commands.ts +45 -40
- package/src/tools/file.ts +295 -164
- package/src/tools/notebook.ts +13 -14
- package/src/widgets/ai-settings.tsx +184 -35
- package/src/widgets/provider-config-dialog.tsx +43 -16
- package/style/base.css +14 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
2
2
|
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
3
|
+
import { Debouncer } from '@lumino/polling';
|
|
3
4
|
import Add from '@mui/icons-material/Add';
|
|
4
5
|
import Cable from '@mui/icons-material/Cable';
|
|
5
6
|
import CheckCircle from '@mui/icons-material/CheckCircle';
|
|
@@ -42,7 +43,7 @@ import {
|
|
|
42
43
|
createTheme
|
|
43
44
|
} from '@mui/material';
|
|
44
45
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
45
|
-
import React, { useEffect, useState } from 'react';
|
|
46
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
46
47
|
import { AgentManagerFactory } from '../agent';
|
|
47
48
|
import {
|
|
48
49
|
AISettingsModel,
|
|
@@ -162,6 +163,14 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
162
163
|
>();
|
|
163
164
|
const [mcpMenuAnchor, setMcpMenuAnchor] = useState<null | HTMLElement>(null);
|
|
164
165
|
const [mcpMenuServerId, setMcpMenuServerId] = useState<string>('');
|
|
166
|
+
const [systemPromptValue, setSystemPromptValue] = useState(
|
|
167
|
+
config.systemPrompt
|
|
168
|
+
);
|
|
169
|
+
const systemPromptValueRef = React.useRef(config.systemPrompt);
|
|
170
|
+
const [completionPromptValue, setCompletionPromptValue] = useState(
|
|
171
|
+
config.completionSystemPrompt
|
|
172
|
+
);
|
|
173
|
+
const completionPromptValueRef = React.useRef(config.completionSystemPrompt);
|
|
165
174
|
|
|
166
175
|
/**
|
|
167
176
|
* Effect to listen for model state changes and update config
|
|
@@ -220,6 +229,47 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
220
229
|
};
|
|
221
230
|
}, [agentManagerFactory]);
|
|
222
231
|
|
|
232
|
+
// Sync local state when config changes externally
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
setSystemPromptValue(config.systemPrompt);
|
|
235
|
+
systemPromptValueRef.current = config.systemPrompt;
|
|
236
|
+
}, [config.systemPrompt]);
|
|
237
|
+
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
setCompletionPromptValue(config.completionSystemPrompt);
|
|
240
|
+
completionPromptValueRef.current = config.completionSystemPrompt;
|
|
241
|
+
}, [config.completionSystemPrompt]);
|
|
242
|
+
|
|
243
|
+
const promptDebouncer = useMemo(
|
|
244
|
+
() =>
|
|
245
|
+
new Debouncer(async () => {
|
|
246
|
+
await handleConfigUpdate({
|
|
247
|
+
systemPrompt: systemPromptValueRef.current,
|
|
248
|
+
completionSystemPrompt: completionPromptValueRef.current
|
|
249
|
+
});
|
|
250
|
+
}, 1000),
|
|
251
|
+
[]
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Cleanup debouncer on unmount
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
return () => {
|
|
257
|
+
promptDebouncer.dispose();
|
|
258
|
+
};
|
|
259
|
+
}, [promptDebouncer]);
|
|
260
|
+
|
|
261
|
+
const handleSystemPromptChange = (value: string) => {
|
|
262
|
+
setSystemPromptValue(value);
|
|
263
|
+
systemPromptValueRef.current = value;
|
|
264
|
+
void promptDebouncer.invoke();
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const handleCompletionPromptChange = (value: string) => {
|
|
268
|
+
setCompletionPromptValue(value);
|
|
269
|
+
completionPromptValueRef.current = value;
|
|
270
|
+
void promptDebouncer.invoke();
|
|
271
|
+
};
|
|
272
|
+
|
|
223
273
|
const getSecretFromManager = async (
|
|
224
274
|
provider: string,
|
|
225
275
|
fieldName: string
|
|
@@ -232,6 +282,23 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
232
282
|
return secret?.value;
|
|
233
283
|
};
|
|
234
284
|
|
|
285
|
+
const setSecretToManager = async (
|
|
286
|
+
provider: string,
|
|
287
|
+
fieldName: string,
|
|
288
|
+
value: string
|
|
289
|
+
): Promise<void> => {
|
|
290
|
+
await secretsManager?.set(
|
|
291
|
+
Private.getToken(),
|
|
292
|
+
SECRETS_NAMESPACE,
|
|
293
|
+
`${provider}:${fieldName}`,
|
|
294
|
+
{
|
|
295
|
+
namespace: SECRETS_NAMESPACE,
|
|
296
|
+
id: `${provider}:${fieldName}`,
|
|
297
|
+
value
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
};
|
|
301
|
+
|
|
235
302
|
/**
|
|
236
303
|
* Attach a secrets field to the secrets manager.
|
|
237
304
|
* @param input - the DOm element to attach.
|
|
@@ -308,9 +375,7 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
308
375
|
// Retrieve the API key from the secrets manager if necessary.
|
|
309
376
|
if (model.config.useSecretsManager && secretsManager) {
|
|
310
377
|
provider.apiKey =
|
|
311
|
-
(await getSecretFromManager(provider.provider, 'apiKey')) ??
|
|
312
|
-
provider.apiKey ??
|
|
313
|
-
'';
|
|
378
|
+
(await getSecretFromManager(provider.provider, 'apiKey')) ?? '';
|
|
314
379
|
}
|
|
315
380
|
setEditingProvider(provider);
|
|
316
381
|
setDialogOpen(true);
|
|
@@ -354,6 +419,15 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
354
419
|
if (updates.useSecretsManager !== undefined) {
|
|
355
420
|
if (updates.useSecretsManager) {
|
|
356
421
|
for (const provider of model.config.providers) {
|
|
422
|
+
// if the secrets manager doesn't have the current API key, copy the current
|
|
423
|
+
// one from settings.
|
|
424
|
+
if (!(await getSecretFromManager(provider.provider, 'apiKey'))) {
|
|
425
|
+
setSecretToManager(
|
|
426
|
+
provider.provider,
|
|
427
|
+
'apiKey',
|
|
428
|
+
provider.apiKey ?? ''
|
|
429
|
+
);
|
|
430
|
+
}
|
|
357
431
|
provider.apiKey = SECRETS_REPLACEMENT;
|
|
358
432
|
await model.updateProvider(provider.id, provider);
|
|
359
433
|
}
|
|
@@ -453,6 +527,7 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
453
527
|
overflow: 'auto',
|
|
454
528
|
p: 2,
|
|
455
529
|
pb: 4,
|
|
530
|
+
boxSizing: 'border-box',
|
|
456
531
|
fontSize: '0.9rem'
|
|
457
532
|
}}
|
|
458
533
|
>
|
|
@@ -478,32 +553,6 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
478
553
|
{/* Tab Panels */}
|
|
479
554
|
{activeTab === 0 && (
|
|
480
555
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
481
|
-
{secretsManager !== undefined && (
|
|
482
|
-
<FormControlLabel
|
|
483
|
-
control={
|
|
484
|
-
<Switch
|
|
485
|
-
checked={config.useSecretsManager}
|
|
486
|
-
onChange={e =>
|
|
487
|
-
handleConfigUpdate({
|
|
488
|
-
useSecretsManager: e.target.checked
|
|
489
|
-
})
|
|
490
|
-
}
|
|
491
|
-
color="primary"
|
|
492
|
-
sx={{ alignSelf: 'flex-start' }}
|
|
493
|
-
/>
|
|
494
|
-
}
|
|
495
|
-
label={
|
|
496
|
-
<div>
|
|
497
|
-
<span>Use the secrets manager to manage API keys</span>
|
|
498
|
-
{!config.useSecretsManager && (
|
|
499
|
-
<Alert severity="warning" icon={<Error />} sx={{ mb: 2 }}>
|
|
500
|
-
The secrets will be stored in plain text in settings
|
|
501
|
-
</Alert>
|
|
502
|
-
)}
|
|
503
|
-
</div>
|
|
504
|
-
}
|
|
505
|
-
/>
|
|
506
|
-
)}
|
|
507
556
|
{/* Default Provider Selection */}
|
|
508
557
|
{config.providers.length > 0 && (
|
|
509
558
|
<Card elevation={2}>
|
|
@@ -552,6 +601,7 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
552
601
|
<Select
|
|
553
602
|
value={config.activeCompleterProvider || ''}
|
|
554
603
|
label="Completion Provider"
|
|
604
|
+
className="jp-ai-completion-provider-select"
|
|
555
605
|
onChange={e =>
|
|
556
606
|
model.setActiveCompleterProvider(
|
|
557
607
|
e.target.value || undefined
|
|
@@ -559,7 +609,7 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
559
609
|
}
|
|
560
610
|
>
|
|
561
611
|
<MenuItem value="">
|
|
562
|
-
<em>
|
|
612
|
+
<em>No completion</em>
|
|
563
613
|
</MenuItem>
|
|
564
614
|
{config.providers.map(provider => (
|
|
565
615
|
<MenuItem key={provider.id} value={provider.id}>
|
|
@@ -726,6 +776,34 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
726
776
|
)}
|
|
727
777
|
</CardContent>
|
|
728
778
|
</Card>
|
|
779
|
+
|
|
780
|
+
{/* Secrets Manager Settings */}
|
|
781
|
+
{secretsManager !== undefined && (
|
|
782
|
+
<FormControlLabel
|
|
783
|
+
control={
|
|
784
|
+
<Switch
|
|
785
|
+
checked={config.useSecretsManager}
|
|
786
|
+
onChange={e =>
|
|
787
|
+
handleConfigUpdate({
|
|
788
|
+
useSecretsManager: e.target.checked
|
|
789
|
+
})
|
|
790
|
+
}
|
|
791
|
+
color="primary"
|
|
792
|
+
sx={{ alignSelf: 'flex-start' }}
|
|
793
|
+
/>
|
|
794
|
+
}
|
|
795
|
+
label={
|
|
796
|
+
<div>
|
|
797
|
+
<span>Use the secrets manager to manage API keys</span>
|
|
798
|
+
{!config.useSecretsManager && (
|
|
799
|
+
<Alert severity="warning" icon={<Error />} sx={{ mb: 2 }}>
|
|
800
|
+
The secrets are stored in plain text in settings
|
|
801
|
+
</Alert>
|
|
802
|
+
)}
|
|
803
|
+
</div>
|
|
804
|
+
}
|
|
805
|
+
/>
|
|
806
|
+
)}
|
|
729
807
|
</Box>
|
|
730
808
|
)}
|
|
731
809
|
|
|
@@ -807,6 +885,68 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
807
885
|
}
|
|
808
886
|
/>
|
|
809
887
|
|
|
888
|
+
<FormControlLabel
|
|
889
|
+
control={
|
|
890
|
+
<Switch
|
|
891
|
+
checked={config.showCellDiff}
|
|
892
|
+
onChange={e =>
|
|
893
|
+
handleConfigUpdate({
|
|
894
|
+
showCellDiff: e.target.checked
|
|
895
|
+
})
|
|
896
|
+
}
|
|
897
|
+
color="primary"
|
|
898
|
+
/>
|
|
899
|
+
}
|
|
900
|
+
label={
|
|
901
|
+
<Box>
|
|
902
|
+
<Typography variant="body1">Show Cell Diff</Typography>
|
|
903
|
+
<Typography variant="caption" color="text.secondary">
|
|
904
|
+
Show diff view when AI modifies cell content
|
|
905
|
+
</Typography>
|
|
906
|
+
</Box>
|
|
907
|
+
}
|
|
908
|
+
/>
|
|
909
|
+
|
|
910
|
+
{config.showCellDiff && (
|
|
911
|
+
<FormControl sx={{ ml: 4 }}>
|
|
912
|
+
<InputLabel>Diff Display Mode</InputLabel>
|
|
913
|
+
<Select
|
|
914
|
+
value={config.diffDisplayMode}
|
|
915
|
+
label="Diff Display Mode"
|
|
916
|
+
onChange={e =>
|
|
917
|
+
handleConfigUpdate({
|
|
918
|
+
diffDisplayMode: e.target.value as 'split' | 'unified'
|
|
919
|
+
})
|
|
920
|
+
}
|
|
921
|
+
>
|
|
922
|
+
<MenuItem value="split">Split View</MenuItem>
|
|
923
|
+
<MenuItem value="unified">Unified View</MenuItem>
|
|
924
|
+
</Select>
|
|
925
|
+
</FormControl>
|
|
926
|
+
)}
|
|
927
|
+
|
|
928
|
+
<FormControlLabel
|
|
929
|
+
control={
|
|
930
|
+
<Switch
|
|
931
|
+
checked={config.showFileDiff}
|
|
932
|
+
onChange={e =>
|
|
933
|
+
handleConfigUpdate({
|
|
934
|
+
showFileDiff: e.target.checked
|
|
935
|
+
})
|
|
936
|
+
}
|
|
937
|
+
color="primary"
|
|
938
|
+
/>
|
|
939
|
+
}
|
|
940
|
+
label={
|
|
941
|
+
<Box>
|
|
942
|
+
<Typography variant="body1">Show File Diff</Typography>
|
|
943
|
+
<Typography variant="caption" color="text.secondary">
|
|
944
|
+
Show diff view when AI modifies file content
|
|
945
|
+
</Typography>
|
|
946
|
+
</Box>
|
|
947
|
+
}
|
|
948
|
+
/>
|
|
949
|
+
|
|
810
950
|
<Divider sx={{ my: 1 }} />
|
|
811
951
|
|
|
812
952
|
<TextField
|
|
@@ -814,14 +954,23 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
|
|
|
814
954
|
multiline
|
|
815
955
|
rows={3}
|
|
816
956
|
label="System Prompt"
|
|
817
|
-
value={
|
|
818
|
-
onChange={e =>
|
|
819
|
-
handleConfigUpdate({ systemPrompt: e.target.value })
|
|
820
|
-
}
|
|
957
|
+
value={systemPromptValue}
|
|
958
|
+
onChange={e => handleSystemPromptChange(e.target.value)}
|
|
821
959
|
placeholder="Define the AI's behavior and personality..."
|
|
822
960
|
helperText="Instructions that define how the AI should behave and respond"
|
|
823
961
|
/>
|
|
824
962
|
|
|
963
|
+
<TextField
|
|
964
|
+
fullWidth
|
|
965
|
+
multiline
|
|
966
|
+
rows={3}
|
|
967
|
+
label="Completion System Prompt"
|
|
968
|
+
value={completionPromptValue}
|
|
969
|
+
onChange={e => handleCompletionPromptChange(e.target.value)}
|
|
970
|
+
placeholder="Define how the AI should generate code completions..."
|
|
971
|
+
helperText="Instructions that define how the AI should generate code completions"
|
|
972
|
+
/>
|
|
973
|
+
|
|
825
974
|
<Divider sx={{ my: 2 }} />
|
|
826
975
|
|
|
827
976
|
<Box>
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Accordion,
|
|
6
6
|
AccordionDetails,
|
|
7
7
|
AccordionSummary,
|
|
8
|
+
Autocomplete,
|
|
8
9
|
Box,
|
|
9
10
|
Button,
|
|
10
11
|
Chip,
|
|
@@ -83,9 +84,10 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
|
|
|
83
84
|
label: info.name,
|
|
84
85
|
models: info.defaultModels,
|
|
85
86
|
apiKeyRequirement: info.apiKeyRequirement,
|
|
86
|
-
allowCustomModel: id === '
|
|
87
|
+
allowCustomModel: id === 'generic', // Generic allows custom models
|
|
87
88
|
supportsBaseURL: info.supportsBaseURL,
|
|
88
|
-
description: info.description
|
|
89
|
+
description: info.description,
|
|
90
|
+
baseUrls: info.baseUrls
|
|
89
91
|
};
|
|
90
92
|
});
|
|
91
93
|
}, [providerRegistry]);
|
|
@@ -279,21 +281,46 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
|
|
|
279
281
|
)}
|
|
280
282
|
|
|
281
283
|
{selectedProvider?.supportsBaseURL && (
|
|
282
|
-
<
|
|
284
|
+
<Autocomplete
|
|
285
|
+
freeSolo
|
|
283
286
|
fullWidth
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
287
|
+
options={(selectedProvider.baseUrls ?? []).map(
|
|
288
|
+
option => option.url
|
|
289
|
+
)}
|
|
290
|
+
value={baseURL || ''}
|
|
291
|
+
onChange={(_, value) => {
|
|
292
|
+
if (value && typeof value === 'string') {
|
|
293
|
+
setBaseURL(value);
|
|
294
|
+
}
|
|
295
|
+
}}
|
|
296
|
+
inputValue={baseURL || ''}
|
|
297
|
+
renderOption={(props, option) => {
|
|
298
|
+
const urlOption = (selectedProvider.baseUrls ?? []).find(
|
|
299
|
+
u => u.url === option
|
|
300
|
+
);
|
|
301
|
+
return (
|
|
302
|
+
<Box component="li" {...props} key={option}>
|
|
303
|
+
<Box>
|
|
304
|
+
<Typography variant="body2">{option}</Typography>
|
|
305
|
+
{urlOption?.description && (
|
|
306
|
+
<Typography variant="caption" color="text.secondary">
|
|
307
|
+
{urlOption.description}
|
|
308
|
+
</Typography>
|
|
309
|
+
)}
|
|
310
|
+
</Box>
|
|
311
|
+
</Box>
|
|
312
|
+
);
|
|
313
|
+
}}
|
|
314
|
+
renderInput={params => (
|
|
315
|
+
<TextField
|
|
316
|
+
{...params}
|
|
317
|
+
fullWidth
|
|
318
|
+
label="Base URL"
|
|
319
|
+
placeholder="https://api.example.com/v1"
|
|
320
|
+
onChange={e => setBaseURL(e.target.value)}
|
|
321
|
+
/>
|
|
322
|
+
)}
|
|
323
|
+
clearOnBlur={false}
|
|
297
324
|
/>
|
|
298
325
|
)}
|
|
299
326
|
|
package/style/base.css
CHANGED
|
@@ -371,6 +371,11 @@
|
|
|
371
371
|
transform: rotate(180deg);
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
+
.jp-ai-settings-icon {
|
|
375
|
+
align-items: center;
|
|
376
|
+
display: flex;
|
|
377
|
+
}
|
|
378
|
+
|
|
374
379
|
.jp-chat-sidepanel .jp-chat-add span.jp-ToolbarButtonComponent-label {
|
|
375
380
|
display: none;
|
|
376
381
|
}
|
|
@@ -379,3 +384,12 @@
|
|
|
379
384
|
stroke: var(--jp-inverse-layout-color3);
|
|
380
385
|
stroke-width: 2;
|
|
381
386
|
}
|
|
387
|
+
|
|
388
|
+
/* Disabled color for the completion status */
|
|
389
|
+
.jp-ai-completion-status .jp-ai-completion-disabled circle {
|
|
390
|
+
fill: var(--jp-layout-color3);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.jp-ai-completion-status .jp-ai-completion-disabled path {
|
|
394
|
+
fill: var(--jp-layout-color2);
|
|
395
|
+
}
|