@jupyterlite/ai 0.6.2 → 0.7.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 +1 -1
- package/lib/base-completer.d.ts +0 -5
- package/lib/chat-handler.d.ts +19 -5
- package/lib/chat-handler.js +47 -26
- package/lib/completion-provider.d.ts +2 -2
- package/lib/completion-provider.js +4 -3
- package/lib/components/stop-button.d.ts +0 -1
- package/lib/default-providers/Anthropic/completer.d.ts +0 -2
- package/lib/default-providers/Anthropic/completer.js +2 -4
- package/lib/default-providers/ChromeAI/completer.d.ts +0 -2
- package/lib/default-providers/ChromeAI/completer.js +2 -4
- package/lib/default-providers/ChromeAI/instructions.d.ts +4 -0
- package/lib/default-providers/ChromeAI/instructions.js +18 -0
- package/lib/default-providers/MistralAI/completer.d.ts +0 -2
- package/lib/default-providers/MistralAI/completer.js +3 -4
- package/lib/default-providers/Ollama/completer.d.ts +17 -0
- package/lib/default-providers/Ollama/completer.js +49 -0
- package/lib/default-providers/Ollama/instructions.d.ts +2 -0
- package/lib/default-providers/Ollama/instructions.js +70 -0
- package/lib/default-providers/Ollama/settings-schema.json +146 -0
- package/lib/default-providers/OpenAI/completer.d.ts +0 -2
- package/lib/default-providers/OpenAI/completer.js +2 -4
- package/lib/default-providers/WebLLM/completer.d.ts +27 -0
- package/lib/default-providers/WebLLM/completer.js +136 -0
- package/lib/default-providers/WebLLM/instructions.d.ts +6 -0
- package/lib/default-providers/WebLLM/instructions.js +32 -0
- package/lib/default-providers/WebLLM/settings-schema.json +21 -0
- package/lib/default-providers/index.js +119 -4
- package/lib/index.d.ts +2 -2
- package/lib/index.js +16 -26
- package/lib/provider.d.ts +11 -13
- package/lib/provider.js +120 -52
- package/lib/settings/index.d.ts +0 -1
- package/lib/settings/index.js +0 -1
- package/lib/settings/panel.d.ts +37 -8
- package/lib/settings/panel.js +225 -131
- package/lib/tokens.d.ts +21 -2
- package/lib/types/ai-model.d.ts +24 -0
- package/lib/types/ai-model.js +5 -0
- package/package.json +14 -11
- package/schema/provider-registry.json +0 -6
- package/src/base-completer.ts +0 -6
- package/src/chat-handler.ts +40 -7
- package/src/completion-provider.ts +2 -2
- package/src/default-providers/Anthropic/completer.ts +0 -5
- package/src/default-providers/ChromeAI/completer.ts +0 -5
- package/src/default-providers/ChromeAI/instructions.ts +21 -0
- package/src/default-providers/MistralAI/completer.ts +0 -5
- package/src/default-providers/Ollama/completer.ts +62 -0
- package/src/default-providers/Ollama/instructions.ts +70 -0
- package/src/default-providers/OpenAI/completer.ts +0 -5
- package/src/default-providers/WebLLM/completer.ts +162 -0
- package/src/default-providers/WebLLM/instructions.ts +33 -0
- package/src/default-providers/index.ts +151 -14
- package/src/index.ts +17 -29
- package/src/provider.ts +132 -45
- package/src/settings/index.ts +0 -1
- package/src/settings/panel.tsx +207 -73
- package/src/tokens.ts +23 -2
- package/src/types/ai-model.ts +37 -0
- package/src/types/service-worker.d.ts +6 -0
- package/style/base.css +5 -0
- package/lib/settings/settings-connector.d.ts +0 -31
- package/lib/settings/settings-connector.js +0 -61
- package/src/settings/settings-connector.ts +0 -88
package/src/settings/panel.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Button,
|
|
5
|
+
FormComponent,
|
|
6
|
+
IFormRenderer
|
|
7
|
+
} from '@jupyterlab/ui-components';
|
|
7
8
|
import { JSONExt } from '@lumino/coreutils';
|
|
8
9
|
import { IChangeEvent } from '@rjsf/core';
|
|
9
10
|
import type { FieldProps } from '@rjsf/utils';
|
|
@@ -12,13 +13,14 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
12
13
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
13
14
|
import React from 'react';
|
|
14
15
|
|
|
15
|
-
import { getSecretId,
|
|
16
|
+
import { getSecretId, SECRETS_REPLACEMENT } from '.';
|
|
16
17
|
import baseSettings from './base.json';
|
|
17
18
|
import { IAIProviderRegistry, IDict, PLUGIN_IDS } from '../tokens';
|
|
18
19
|
|
|
19
20
|
const MD_MIME_TYPE = 'text/markdown';
|
|
20
21
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
21
22
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
23
|
+
const ERROR_CLASS = 'jp-AISettingsError';
|
|
22
24
|
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
|
|
23
25
|
|
|
24
26
|
export const aiSettingsRenderer = (options: {
|
|
@@ -26,7 +28,6 @@ export const aiSettingsRenderer = (options: {
|
|
|
26
28
|
secretsToken?: symbol;
|
|
27
29
|
rmRegistry?: IRenderMimeRegistry;
|
|
28
30
|
secretsManager?: ISecretsManager;
|
|
29
|
-
settingConnector?: ISettingConnector;
|
|
30
31
|
}): IFormRenderer => {
|
|
31
32
|
const { secretsToken } = options;
|
|
32
33
|
delete options.secretsToken;
|
|
@@ -44,6 +45,8 @@ export const aiSettingsRenderer = (options: {
|
|
|
44
45
|
export interface ISettingsFormStates {
|
|
45
46
|
schema: JSONSchema7;
|
|
46
47
|
instruction: HTMLElement | null;
|
|
48
|
+
compatibilityError: string | null;
|
|
49
|
+
isModified?: boolean;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
const WrappedFormComponent = (props: any): JSX.Element => {
|
|
@@ -64,13 +67,12 @@ export class AiSettings extends React.Component<
|
|
|
64
67
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
65
68
|
this._rmRegistry = props.formContext.rmRegistry ?? null;
|
|
66
69
|
this._secretsManager = props.formContext.secretsManager ?? null;
|
|
67
|
-
this._settingConnector = props.formContext.settingConnector ?? null;
|
|
68
70
|
this._settings = props.formContext.settings;
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
const useSecretsManagerSetting =
|
|
71
73
|
(this._settings.get('UseSecretsManager').composite as boolean) ?? true;
|
|
72
|
-
this.
|
|
73
|
-
|
|
74
|
+
this._useSecretsManager =
|
|
75
|
+
useSecretsManagerSetting && this._secretsManager !== null;
|
|
74
76
|
|
|
75
77
|
// Initialize the providers schema.
|
|
76
78
|
const providerSchema = JSONExt.deepCopy(baseSettings) as any;
|
|
@@ -84,7 +86,7 @@ export class AiSettings extends React.Component<
|
|
|
84
86
|
this._providerSchema = providerSchema as JSONSchema7;
|
|
85
87
|
|
|
86
88
|
// Check if there is saved values in local storage, otherwise use the settings from
|
|
87
|
-
// the setting registry (
|
|
89
|
+
// the setting registry (leads to default if there are no user settings).
|
|
88
90
|
const storageSettings = localStorage.getItem(STORAGE_NAME);
|
|
89
91
|
if (storageSettings === null) {
|
|
90
92
|
const labSettings = this._settings.get('AIprovider').composite;
|
|
@@ -104,32 +106,33 @@ export class AiSettings extends React.Component<
|
|
|
104
106
|
|
|
105
107
|
// Initialize the settings from the saved ones.
|
|
106
108
|
this._provider = this.getCurrentProvider();
|
|
107
|
-
this._currentSettings = this.getSettings();
|
|
108
109
|
|
|
109
110
|
// Initialize the schema.
|
|
110
111
|
const schema = this._buildSchema();
|
|
111
|
-
this.state = { schema, instruction: null };
|
|
112
112
|
|
|
113
|
+
// Initialize the current settings.
|
|
114
|
+
const isModified = this._updatedFormData(
|
|
115
|
+
this.getSettingsFromLocalStorage()
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
this.state = {
|
|
119
|
+
schema,
|
|
120
|
+
instruction: null,
|
|
121
|
+
compatibilityError: null,
|
|
122
|
+
isModified: isModified
|
|
123
|
+
};
|
|
113
124
|
this._renderInstruction();
|
|
114
125
|
|
|
126
|
+
this._checkProviderCompatibility();
|
|
127
|
+
|
|
115
128
|
// Update the setting registry.
|
|
116
|
-
this.
|
|
117
|
-
.set('AIprovider', this._currentSettings)
|
|
118
|
-
.catch(console.error);
|
|
129
|
+
this.saveSettingsToRegistry();
|
|
119
130
|
|
|
120
|
-
this.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
const hideSecretFields =
|
|
127
|
-
(this._settings.get('HideSecretFields').composite as boolean) ?? true;
|
|
128
|
-
if (hideSecretFields !== this._hideSecretFields) {
|
|
129
|
-
this._hideSecretFields = hideSecretFields;
|
|
130
|
-
this._updateSchema();
|
|
131
|
-
}
|
|
132
|
-
});
|
|
131
|
+
this._secretsManager?.fieldVisibilityChanged.connect(
|
|
132
|
+
this._fieldVisibilityChanged
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
this._settings.changed.connect(this._settingsChanged);
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
async componentDidUpdate(): Promise<void> {
|
|
@@ -158,6 +161,10 @@ export class AiSettings extends React.Component<
|
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
componentWillUnmount(): void {
|
|
164
|
+
this._settings.changed.disconnect(this._settingsChanged);
|
|
165
|
+
this._secretsManager?.fieldVisibilityChanged.disconnect(
|
|
166
|
+
this._fieldVisibilityChanged
|
|
167
|
+
);
|
|
161
168
|
if (!this._secretsManager || !this._useSecretsManager) {
|
|
162
169
|
return;
|
|
163
170
|
}
|
|
@@ -184,7 +191,7 @@ export class AiSettings extends React.Component<
|
|
|
184
191
|
/**
|
|
185
192
|
* Get settings from local storage for a given provider.
|
|
186
193
|
*/
|
|
187
|
-
|
|
194
|
+
getSettingsFromLocalStorage(): IDict<any> {
|
|
188
195
|
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
189
196
|
return settings[this._provider] ?? { provider: this._provider };
|
|
190
197
|
}
|
|
@@ -192,32 +199,70 @@ export class AiSettings extends React.Component<
|
|
|
192
199
|
/**
|
|
193
200
|
* Save settings in local storage for a given provider.
|
|
194
201
|
*/
|
|
195
|
-
|
|
196
|
-
const currentSettings = { ...
|
|
202
|
+
saveSettingsToLocalStorage() {
|
|
203
|
+
const currentSettings = { ...this._currentSettings };
|
|
197
204
|
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) ?? '{}');
|
|
198
205
|
// Do not save secrets in local storage if using the secrets manager.
|
|
199
|
-
if (this.
|
|
206
|
+
if (this._useSecretsManager) {
|
|
200
207
|
this._secretFields.forEach(field => delete currentSettings[field]);
|
|
201
208
|
}
|
|
202
209
|
settings[this._provider] = currentSettings;
|
|
203
210
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
204
211
|
}
|
|
205
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Save the settings to the setting registry.
|
|
215
|
+
*/
|
|
216
|
+
saveSettingsToRegistry(): void {
|
|
217
|
+
const sanitizedSettings = { ...this._currentSettings };
|
|
218
|
+
if (this._useSecretsManager) {
|
|
219
|
+
this._secretFields.forEach(field => {
|
|
220
|
+
sanitizedSettings[field] = SECRETS_REPLACEMENT;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
this._settings
|
|
224
|
+
.set('AIprovider', { provider: this._provider, ...sanitizedSettings })
|
|
225
|
+
.catch(console.error);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Triggered when the settings has changed.
|
|
230
|
+
*/
|
|
231
|
+
private _settingsChanged = (settings: ISettingRegistry.ISettings) => {
|
|
232
|
+
this._updateUseSecretsManager(
|
|
233
|
+
(this._settings.get('UseSecretsManager').composite as boolean) ?? true
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Triggered when the secret fields visibility has changed.
|
|
239
|
+
*/
|
|
240
|
+
private _fieldVisibilityChanged = (
|
|
241
|
+
_: ISecretsManager,
|
|
242
|
+
value: boolean
|
|
243
|
+
): void => {
|
|
244
|
+
if (this._useSecretsManager) {
|
|
245
|
+
this._updateSchema();
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
206
249
|
/**
|
|
207
250
|
* Update the settings whether the secrets manager is used or not.
|
|
208
251
|
*
|
|
209
252
|
* @param value - whether to use the secrets manager or not.
|
|
210
253
|
*/
|
|
211
254
|
private _updateUseSecretsManager = (value: boolean) => {
|
|
255
|
+
// No-op if the value did not change or the secrets manager has not been provided.
|
|
256
|
+
if (value === this._useSecretsManager || this._secretsManager === null) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Update the secrets manager.
|
|
212
261
|
this._useSecretsManager = value;
|
|
213
262
|
if (!value) {
|
|
214
263
|
// Detach all the password inputs attached to the secrets manager, and save the
|
|
215
264
|
// current settings to the local storage to save the password.
|
|
216
|
-
this._secretsManager
|
|
217
|
-
if (this._settingConnector instanceof SettingConnector) {
|
|
218
|
-
this._settingConnector.doNotSave = [];
|
|
219
|
-
}
|
|
220
|
-
this.saveSettings(this._currentSettings);
|
|
265
|
+
this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
221
266
|
} else {
|
|
222
267
|
// Remove all the keys stored locally.
|
|
223
268
|
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
@@ -229,16 +274,10 @@ export class AiSettings extends React.Component<
|
|
|
229
274
|
});
|
|
230
275
|
});
|
|
231
276
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
232
|
-
// Update the fields not to save in settings.
|
|
233
|
-
if (this._settingConnector instanceof SettingConnector) {
|
|
234
|
-
this._settingConnector.doNotSave = this._secretFields;
|
|
235
|
-
}
|
|
236
|
-
// Attach the password inputs to the secrets manager.
|
|
237
|
-
this.componentDidUpdate();
|
|
238
277
|
}
|
|
239
|
-
this.
|
|
240
|
-
|
|
241
|
-
|
|
278
|
+
this._updateSchema();
|
|
279
|
+
this.saveSettingsToLocalStorage();
|
|
280
|
+
this.saveSettingsToRegistry();
|
|
242
281
|
};
|
|
243
282
|
|
|
244
283
|
/**
|
|
@@ -252,27 +291,30 @@ export class AiSettings extends React.Component<
|
|
|
252
291
|
);
|
|
253
292
|
|
|
254
293
|
this._secretFields = [];
|
|
294
|
+
this._defaultFormData = {};
|
|
255
295
|
if (settingsSchema) {
|
|
256
296
|
Object.entries(settingsSchema).forEach(([key, value]) => {
|
|
257
297
|
if (key.toLowerCase().includes('key')) {
|
|
258
298
|
this._secretFields.push(key);
|
|
259
|
-
|
|
299
|
+
|
|
300
|
+
// If the secrets manager is not used, do not show the secrets fields.
|
|
301
|
+
// If the secrets manager is used, check if the fields should be visible.
|
|
302
|
+
const showSecretFields =
|
|
303
|
+
!this._useSecretsManager ||
|
|
304
|
+
(this._secretsManager?.secretFieldsVisibility ?? true);
|
|
305
|
+
if (!showSecretFields) {
|
|
260
306
|
return;
|
|
261
307
|
}
|
|
308
|
+
|
|
262
309
|
this._uiSchema[key] = { 'ui:widget': 'password' };
|
|
263
310
|
}
|
|
264
311
|
schema.properties[key] = value;
|
|
312
|
+
if (value.default !== undefined) {
|
|
313
|
+
this._defaultFormData[key] = value.default;
|
|
314
|
+
}
|
|
265
315
|
});
|
|
266
316
|
}
|
|
267
317
|
|
|
268
|
-
// Do not save secrets in settings if using the secrets manager.
|
|
269
|
-
if (
|
|
270
|
-
this._secretsManager &&
|
|
271
|
-
this._useSecretsManager &&
|
|
272
|
-
this._settingConnector instanceof SettingConnector
|
|
273
|
-
) {
|
|
274
|
-
this._settingConnector.doNotSave = this._secretFields;
|
|
275
|
-
}
|
|
276
318
|
return schema as JSONSchema7;
|
|
277
319
|
}
|
|
278
320
|
|
|
@@ -304,7 +346,30 @@ export class AiSettings extends React.Component<
|
|
|
304
346
|
}
|
|
305
347
|
|
|
306
348
|
/**
|
|
307
|
-
*
|
|
349
|
+
* Check for compatibility of the provider with the current environment.
|
|
350
|
+
* If the provider is not compatible, display an error message.
|
|
351
|
+
*/
|
|
352
|
+
private async _checkProviderCompatibility(): Promise<void> {
|
|
353
|
+
const compatibilityCheck = this._providerRegistry.getCompatibilityCheck(
|
|
354
|
+
this._provider
|
|
355
|
+
);
|
|
356
|
+
if (!compatibilityCheck) {
|
|
357
|
+
this.setState({ compatibilityError: null });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const error = await compatibilityCheck();
|
|
361
|
+
if (!error) {
|
|
362
|
+
this.setState({ compatibilityError: null });
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const errorDiv = document.createElement('div');
|
|
366
|
+
errorDiv.className = ERROR_CLASS;
|
|
367
|
+
errorDiv.innerHTML = error;
|
|
368
|
+
this.setState({ compatibilityError: error });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Triggered when the provider has changed, to update the schema and values.
|
|
308
373
|
* Update the Jupyterlab settings accordingly.
|
|
309
374
|
*/
|
|
310
375
|
private _onProviderChanged = (e: IChangeEvent) => {
|
|
@@ -314,12 +379,18 @@ export class AiSettings extends React.Component<
|
|
|
314
379
|
}
|
|
315
380
|
this._provider = provider;
|
|
316
381
|
this.saveCurrentProvider();
|
|
317
|
-
this._currentSettings = this.getSettings();
|
|
318
382
|
this._updateSchema();
|
|
319
383
|
this._renderInstruction();
|
|
320
|
-
this.
|
|
321
|
-
|
|
322
|
-
|
|
384
|
+
this._checkProviderCompatibility();
|
|
385
|
+
|
|
386
|
+
// Initialize the current settings.
|
|
387
|
+
const isModified = this._updatedFormData(
|
|
388
|
+
this.getSettingsFromLocalStorage()
|
|
389
|
+
);
|
|
390
|
+
if (isModified !== this.state.isModified) {
|
|
391
|
+
this.setState({ isModified });
|
|
392
|
+
}
|
|
393
|
+
this.saveSettingsToRegistry();
|
|
323
394
|
};
|
|
324
395
|
|
|
325
396
|
/**
|
|
@@ -328,22 +399,65 @@ export class AiSettings extends React.Component<
|
|
|
328
399
|
*/
|
|
329
400
|
private _onPasswordUpdated = (fieldName: string, value: string) => {
|
|
330
401
|
this._currentSettings[fieldName] = value;
|
|
331
|
-
this.
|
|
332
|
-
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
333
|
-
.catch(console.error);
|
|
402
|
+
this.saveSettingsToRegistry();
|
|
334
403
|
};
|
|
335
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Update the current settings with the new values from the form.
|
|
407
|
+
*
|
|
408
|
+
* @param data - The form data to update.
|
|
409
|
+
* @returns - Boolean whether the form is not the default one.
|
|
410
|
+
*/
|
|
411
|
+
private _updatedFormData(data: IDict): boolean {
|
|
412
|
+
let isModified = false;
|
|
413
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
414
|
+
if (this._defaultFormData[key] !== undefined) {
|
|
415
|
+
if (value === undefined) {
|
|
416
|
+
const schemaProperty = this.state.schema.properties?.[
|
|
417
|
+
key
|
|
418
|
+
] as JSONSchema7;
|
|
419
|
+
if (schemaProperty.type === 'string') {
|
|
420
|
+
data[key] = '';
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (value !== this._defaultFormData[key]) {
|
|
424
|
+
isModified = true;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
this._currentSettings = JSONExt.deepCopy(data);
|
|
429
|
+
return isModified;
|
|
430
|
+
}
|
|
431
|
+
|
|
336
432
|
/**
|
|
337
433
|
* Triggered when the form value has changed, to update the current settings and save
|
|
338
434
|
* it in local storage.
|
|
339
435
|
* Update the Jupyterlab settings accordingly.
|
|
340
436
|
*/
|
|
341
|
-
private
|
|
342
|
-
|
|
343
|
-
this.
|
|
344
|
-
this.
|
|
345
|
-
|
|
346
|
-
|
|
437
|
+
private _onFormChanged = (e: IChangeEvent): void => {
|
|
438
|
+
const { formData } = e;
|
|
439
|
+
const isModified = this._updatedFormData(formData);
|
|
440
|
+
this.saveSettingsToLocalStorage();
|
|
441
|
+
this.saveSettingsToRegistry();
|
|
442
|
+
if (isModified !== this.state.isModified) {
|
|
443
|
+
this.setState({ isModified });
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Handler for the "Restore to defaults" button - clears all
|
|
449
|
+
* modified settings then calls `setFormData` to restore the
|
|
450
|
+
* values.
|
|
451
|
+
*/
|
|
452
|
+
private _reset = async (event: React.MouseEvent): Promise<void> => {
|
|
453
|
+
event.stopPropagation();
|
|
454
|
+
this._currentSettings = {
|
|
455
|
+
...this._currentSettings,
|
|
456
|
+
...this._defaultFormData
|
|
457
|
+
};
|
|
458
|
+
this.saveSettingsToLocalStorage();
|
|
459
|
+
this.saveSettingsToRegistry();
|
|
460
|
+
this.setState({ isModified: false });
|
|
347
461
|
};
|
|
348
462
|
|
|
349
463
|
render(): JSX.Element {
|
|
@@ -354,6 +468,12 @@ export class AiSettings extends React.Component<
|
|
|
354
468
|
schema={this._providerSchema}
|
|
355
469
|
onChange={this._onProviderChanged}
|
|
356
470
|
/>
|
|
471
|
+
{this.state.compatibilityError !== null && (
|
|
472
|
+
<div className={ERROR_CLASS}>
|
|
473
|
+
<i className={'fas fa-exclamation-triangle'}></i>
|
|
474
|
+
<span>{this.state.compatibilityError}</span>
|
|
475
|
+
</div>
|
|
476
|
+
)}
|
|
357
477
|
{this.state.instruction !== null && (
|
|
358
478
|
<details>
|
|
359
479
|
<summary className={INSTRUCTION_CLASS}>Instructions</summary>
|
|
@@ -364,11 +484,26 @@ export class AiSettings extends React.Component<
|
|
|
364
484
|
/>
|
|
365
485
|
</details>
|
|
366
486
|
)}
|
|
487
|
+
<div className="jp-SettingsHeader">
|
|
488
|
+
<h3 title={this._provider}>{this._provider}</h3>
|
|
489
|
+
<div className="jp-SettingsHeader-buttonbar">
|
|
490
|
+
{this.state.isModified && (
|
|
491
|
+
<Button className="jp-RestoreButton" onClick={this._reset}>
|
|
492
|
+
Restore to Defaults
|
|
493
|
+
</Button>
|
|
494
|
+
)}
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
367
497
|
<WrappedFormComponent
|
|
368
498
|
formData={this._currentSettings}
|
|
369
499
|
schema={this.state.schema}
|
|
370
|
-
onChange={this.
|
|
500
|
+
onChange={this._onFormChanged}
|
|
371
501
|
uiSchema={this._uiSchema}
|
|
502
|
+
idPrefix={`jp-SettingsEditor-${PLUGIN_IDS.providerRegistry}`}
|
|
503
|
+
formContext={{
|
|
504
|
+
...this.props.formContext,
|
|
505
|
+
defaultFormData: this._defaultFormData
|
|
506
|
+
}}
|
|
372
507
|
/>
|
|
373
508
|
</div>
|
|
374
509
|
);
|
|
@@ -378,15 +513,14 @@ export class AiSettings extends React.Component<
|
|
|
378
513
|
private _provider: string;
|
|
379
514
|
private _providerSchema: JSONSchema7;
|
|
380
515
|
private _useSecretsManager: boolean;
|
|
381
|
-
private _hideSecretFields: boolean;
|
|
382
516
|
private _rmRegistry: IRenderMimeRegistry | null;
|
|
383
517
|
private _secretsManager: ISecretsManager | null;
|
|
384
|
-
private _settingConnector: ISettingConnector | null;
|
|
385
518
|
private _currentSettings: IDict<any> = { provider: 'None' };
|
|
386
519
|
private _uiSchema: IDict<any> = {};
|
|
387
520
|
private _settings: ISettingRegistry.ISettings;
|
|
388
521
|
private _formRef = React.createRef<HTMLDivElement>();
|
|
389
522
|
private _secretFields: string[] = [];
|
|
523
|
+
private _defaultFormData: IDict<any> = {};
|
|
390
524
|
}
|
|
391
525
|
|
|
392
526
|
namespace Private {
|
package/src/tokens.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { ISignal } from '@lumino/signaling';
|
|
|
4
4
|
import { JSONSchema7 } from 'json-schema';
|
|
5
5
|
|
|
6
6
|
import { IBaseCompleter } from './base-completer';
|
|
7
|
+
import { AIChatModel, AICompleter } from './types/ai-model';
|
|
7
8
|
|
|
8
9
|
export const PLUGIN_IDS = {
|
|
9
10
|
chat: '@jupyterlite/ai:chat',
|
|
@@ -51,6 +52,20 @@ export interface IAIProvider {
|
|
|
51
52
|
* Default to `(error) => error.message`.
|
|
52
53
|
*/
|
|
53
54
|
errorMessage?: (error: any) => string;
|
|
55
|
+
/**
|
|
56
|
+
* Compatibility check function, to determine if the provider is compatible with the
|
|
57
|
+
* current environment.
|
|
58
|
+
*/
|
|
59
|
+
compatibilityCheck?: () => Promise<string | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Whether to expose or not the chat model.
|
|
62
|
+
*
|
|
63
|
+
* ### CAUTION
|
|
64
|
+
* This flag will expose the whole chat model API, which may contain private keys.
|
|
65
|
+
* Be sure to use it with a model that does not expose sensitive information in the
|
|
66
|
+
* API.
|
|
67
|
+
*/
|
|
68
|
+
exposeChatModel?: boolean;
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
/**
|
|
@@ -72,11 +87,11 @@ export interface IAIProviderRegistry {
|
|
|
72
87
|
/**
|
|
73
88
|
* Get the current completer of the completion provider.
|
|
74
89
|
*/
|
|
75
|
-
currentCompleter:
|
|
90
|
+
currentCompleter: AICompleter | null;
|
|
76
91
|
/**
|
|
77
92
|
* Get the current llm chat model.
|
|
78
93
|
*/
|
|
79
|
-
currentChatModel:
|
|
94
|
+
currentChatModel: AIChatModel | null;
|
|
80
95
|
/**
|
|
81
96
|
* Get the settings schema of a given provider.
|
|
82
97
|
*/
|
|
@@ -85,6 +100,12 @@ export interface IAIProviderRegistry {
|
|
|
85
100
|
* Get the instructions of a given provider.
|
|
86
101
|
*/
|
|
87
102
|
getInstructions(provider: string): string | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Get the compatibility check function of a given provider.
|
|
105
|
+
*/
|
|
106
|
+
getCompatibilityCheck(
|
|
107
|
+
provider: string
|
|
108
|
+
): (() => Promise<string | null>) | undefined;
|
|
88
109
|
/**
|
|
89
110
|
* Format an error message from the current provider.
|
|
90
111
|
*/
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
CompletionHandler,
|
|
8
|
+
IInlineCompletionContext
|
|
9
|
+
} from '@jupyterlab/completer';
|
|
10
|
+
import { IterableReadableStream } from '@langchain/core/utils/stream';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The reduced AI chat model interface.
|
|
14
|
+
*/
|
|
15
|
+
export type AIChatModel = {
|
|
16
|
+
/**
|
|
17
|
+
* The stream function of the chat model.
|
|
18
|
+
*/
|
|
19
|
+
stream: (input: any, options?: any) => Promise<IterableReadableStream<any>>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The reduced AI completer interface.
|
|
24
|
+
*/
|
|
25
|
+
export type AICompleter = {
|
|
26
|
+
/**
|
|
27
|
+
* The fetch function of the completer.
|
|
28
|
+
*/
|
|
29
|
+
fetch: (
|
|
30
|
+
request: CompletionHandler.IRequest,
|
|
31
|
+
context: IInlineCompletionContext
|
|
32
|
+
) => Promise<any>;
|
|
33
|
+
/**
|
|
34
|
+
* The optional request completion function of the completer.
|
|
35
|
+
*/
|
|
36
|
+
requestCompletion?: () => void;
|
|
37
|
+
};
|
package/style/base.css
CHANGED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { ISettingConnector, ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
2
|
-
import { DataConnector, IDataConnector } from '@jupyterlab/statedb';
|
|
3
|
-
/**
|
|
4
|
-
* A data connector for fetching settings.
|
|
5
|
-
*
|
|
6
|
-
* #### Notes
|
|
7
|
-
* This connector adds a query parameter to the base services setting manager.
|
|
8
|
-
*/
|
|
9
|
-
export declare class SettingConnector extends DataConnector<ISettingRegistry.IPlugin, string> implements ISettingConnector {
|
|
10
|
-
constructor(connector: IDataConnector<ISettingRegistry.IPlugin, string>);
|
|
11
|
-
set doNotSave(fields: string[]);
|
|
12
|
-
/**
|
|
13
|
-
* Fetch settings for a plugin.
|
|
14
|
-
* @param id - The plugin ID
|
|
15
|
-
*
|
|
16
|
-
* #### Notes
|
|
17
|
-
* The REST API requests are throttled at one request per plugin per 100ms.
|
|
18
|
-
*/
|
|
19
|
-
fetch(id: string): Promise<ISettingRegistry.IPlugin | undefined>;
|
|
20
|
-
list(query: 'ids'): Promise<{
|
|
21
|
-
ids: string[];
|
|
22
|
-
}>;
|
|
23
|
-
list(query: 'active' | 'all'): Promise<{
|
|
24
|
-
ids: string[];
|
|
25
|
-
values: ISettingRegistry.IPlugin[];
|
|
26
|
-
}>;
|
|
27
|
-
save(id: string, raw: string): Promise<void>;
|
|
28
|
-
private _connector;
|
|
29
|
-
private _doNotSave;
|
|
30
|
-
private _throttlers;
|
|
31
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { PageConfig } from '@jupyterlab/coreutils';
|
|
2
|
-
import { DataConnector } from '@jupyterlab/statedb';
|
|
3
|
-
import { Throttler } from '@lumino/polling';
|
|
4
|
-
import * as json5 from 'json5';
|
|
5
|
-
import { SECRETS_REPLACEMENT } from '.';
|
|
6
|
-
/**
|
|
7
|
-
* A data connector for fetching settings.
|
|
8
|
-
*
|
|
9
|
-
* #### Notes
|
|
10
|
-
* This connector adds a query parameter to the base services setting manager.
|
|
11
|
-
*/
|
|
12
|
-
export class SettingConnector extends DataConnector {
|
|
13
|
-
constructor(connector) {
|
|
14
|
-
super();
|
|
15
|
-
this._doNotSave = [];
|
|
16
|
-
this._throttlers = Object.create(null);
|
|
17
|
-
this._connector = connector;
|
|
18
|
-
}
|
|
19
|
-
set doNotSave(fields) {
|
|
20
|
-
this._doNotSave = [...fields];
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Fetch settings for a plugin.
|
|
24
|
-
* @param id - The plugin ID
|
|
25
|
-
*
|
|
26
|
-
* #### Notes
|
|
27
|
-
* The REST API requests are throttled at one request per plugin per 100ms.
|
|
28
|
-
*/
|
|
29
|
-
fetch(id) {
|
|
30
|
-
const throttlers = this._throttlers;
|
|
31
|
-
if (!(id in throttlers)) {
|
|
32
|
-
throttlers[id] = new Throttler(() => this._connector.fetch(id), 100);
|
|
33
|
-
}
|
|
34
|
-
return throttlers[id].invoke();
|
|
35
|
-
}
|
|
36
|
-
async list(query = 'all') {
|
|
37
|
-
const { isDisabled } = PageConfig.Extension;
|
|
38
|
-
const { ids, values } = await this._connector.list(query === 'ids' ? 'ids' : undefined);
|
|
39
|
-
if (query === 'all') {
|
|
40
|
-
return { ids, values };
|
|
41
|
-
}
|
|
42
|
-
if (query === 'ids') {
|
|
43
|
-
return { ids };
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
ids: ids.filter(id => !isDisabled(id)),
|
|
47
|
-
values: values.filter(({ id }) => !isDisabled(id))
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
async save(id, raw) {
|
|
51
|
-
const settings = json5.parse(raw);
|
|
52
|
-
// Replace secrets fields with the replacement string.
|
|
53
|
-
// Create the field if it does not exist in settings.
|
|
54
|
-
this._doNotSave.forEach(field => {
|
|
55
|
-
if (settings['AIprovider'] !== undefined) {
|
|
56
|
-
settings['AIprovider'][field] = SECRETS_REPLACEMENT;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
await this._connector.save(id, json5.stringify(settings, null, 2));
|
|
60
|
-
}
|
|
61
|
-
}
|