@jupyterlite/ai 0.6.1 → 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 +1 -3
- package/lib/default-providers/Anthropic/completer.js +4 -6
- package/lib/default-providers/ChromeAI/completer.d.ts +1 -3
- package/lib/default-providers/ChromeAI/completer.js +4 -6
- 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 +1 -3
- package/lib/default-providers/MistralAI/completer.js +5 -6
- 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 +1 -3
- package/lib/default-providers/OpenAI/completer.js +4 -6
- 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 +124 -54
- package/lib/settings/index.d.ts +0 -1
- package/lib/settings/index.js +0 -1
- package/lib/settings/panel.d.ts +44 -11
- package/lib/settings/panel.js +231 -130
- 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 +15 -12
- 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 +3 -8
- package/src/default-providers/ChromeAI/completer.ts +3 -8
- package/src/default-providers/ChromeAI/instructions.ts +21 -0
- package/src/default-providers/MistralAI/completer.ts +3 -8
- 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 +3 -8
- 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 +135 -46
- package/src/settings/index.ts +0 -1
- package/src/settings/panel.tsx +223 -79
- 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 -89
package/src/settings/panel.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { ArrayExt } from '@lumino/algorithm';
|
|
4
|
+
Button,
|
|
5
|
+
FormComponent,
|
|
6
|
+
IFormRenderer
|
|
7
|
+
} from '@jupyterlab/ui-components';
|
|
8
8
|
import { JSONExt } from '@lumino/coreutils';
|
|
9
9
|
import { IChangeEvent } from '@rjsf/core';
|
|
10
10
|
import type { FieldProps } from '@rjsf/utils';
|
|
@@ -13,13 +13,14 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
13
13
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
14
14
|
import React from 'react';
|
|
15
15
|
|
|
16
|
-
import { getSecretId,
|
|
16
|
+
import { getSecretId, SECRETS_REPLACEMENT } from '.';
|
|
17
17
|
import baseSettings from './base.json';
|
|
18
18
|
import { IAIProviderRegistry, IDict, PLUGIN_IDS } from '../tokens';
|
|
19
19
|
|
|
20
20
|
const MD_MIME_TYPE = 'text/markdown';
|
|
21
21
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
22
22
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
23
|
+
const ERROR_CLASS = 'jp-AISettingsError';
|
|
23
24
|
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
|
|
24
25
|
|
|
25
26
|
export const aiSettingsRenderer = (options: {
|
|
@@ -27,7 +28,6 @@ export const aiSettingsRenderer = (options: {
|
|
|
27
28
|
secretsToken?: symbol;
|
|
28
29
|
rmRegistry?: IRenderMimeRegistry;
|
|
29
30
|
secretsManager?: ISecretsManager;
|
|
30
|
-
settingConnector?: ISettingConnector;
|
|
31
31
|
}): IFormRenderer => {
|
|
32
32
|
const { secretsToken } = options;
|
|
33
33
|
delete options.secretsToken;
|
|
@@ -45,6 +45,8 @@ export const aiSettingsRenderer = (options: {
|
|
|
45
45
|
export interface ISettingsFormStates {
|
|
46
46
|
schema: JSONSchema7;
|
|
47
47
|
instruction: HTMLElement | null;
|
|
48
|
+
compatibilityError: string | null;
|
|
49
|
+
isModified?: boolean;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
const WrappedFormComponent = (props: any): JSX.Element => {
|
|
@@ -65,13 +67,12 @@ export class AiSettings extends React.Component<
|
|
|
65
67
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
66
68
|
this._rmRegistry = props.formContext.rmRegistry ?? null;
|
|
67
69
|
this._secretsManager = props.formContext.secretsManager ?? null;
|
|
68
|
-
this._settingConnector = props.formContext.settingConnector ?? null;
|
|
69
70
|
this._settings = props.formContext.settings;
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
const useSecretsManagerSetting =
|
|
72
73
|
(this._settings.get('UseSecretsManager').composite as boolean) ?? true;
|
|
73
|
-
this.
|
|
74
|
-
|
|
74
|
+
this._useSecretsManager =
|
|
75
|
+
useSecretsManagerSetting && this._secretsManager !== null;
|
|
75
76
|
|
|
76
77
|
// Initialize the providers schema.
|
|
77
78
|
const providerSchema = JSONExt.deepCopy(baseSettings) as any;
|
|
@@ -85,7 +86,7 @@ export class AiSettings extends React.Component<
|
|
|
85
86
|
this._providerSchema = providerSchema as JSONSchema7;
|
|
86
87
|
|
|
87
88
|
// Check if there is saved values in local storage, otherwise use the settings from
|
|
88
|
-
// the setting registry (
|
|
89
|
+
// the setting registry (leads to default if there are no user settings).
|
|
89
90
|
const storageSettings = localStorage.getItem(STORAGE_NAME);
|
|
90
91
|
if (storageSettings === null) {
|
|
91
92
|
const labSettings = this._settings.get('AIprovider').composite;
|
|
@@ -105,47 +106,43 @@ export class AiSettings extends React.Component<
|
|
|
105
106
|
|
|
106
107
|
// Initialize the settings from the saved ones.
|
|
107
108
|
this._provider = this.getCurrentProvider();
|
|
108
|
-
this._currentSettings = this.getSettings();
|
|
109
109
|
|
|
110
110
|
// Initialize the schema.
|
|
111
111
|
const schema = this._buildSchema();
|
|
112
|
-
this.state = { schema, instruction: null };
|
|
113
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
|
+
};
|
|
114
124
|
this._renderInstruction();
|
|
115
125
|
|
|
126
|
+
this._checkProviderCompatibility();
|
|
127
|
+
|
|
116
128
|
// Update the setting registry.
|
|
117
|
-
this.
|
|
118
|
-
.set('AIprovider', this._currentSettings)
|
|
119
|
-
.catch(console.error);
|
|
129
|
+
this.saveSettingsToRegistry();
|
|
120
130
|
|
|
121
|
-
this.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
const hideSecretFields =
|
|
128
|
-
(this._settings.get('HideSecretFields').composite as boolean) ?? true;
|
|
129
|
-
if (hideSecretFields !== this._hideSecretFields) {
|
|
130
|
-
this._hideSecretFields = hideSecretFields;
|
|
131
|
-
this._updateSchema();
|
|
132
|
-
}
|
|
133
|
-
});
|
|
131
|
+
this._secretsManager?.fieldVisibilityChanged.connect(
|
|
132
|
+
this._fieldVisibilityChanged
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
this._settings.changed.connect(this._settingsChanged);
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
async componentDidUpdate(): Promise<void> {
|
|
137
139
|
if (!this._secretsManager || !this._useSecretsManager) {
|
|
138
140
|
return;
|
|
139
141
|
}
|
|
140
|
-
// Attach the password inputs to the secrets manager only if they have changed.
|
|
141
|
-
const inputs = this._formRef.current?.getElementsByTagName('input') || [];
|
|
142
|
-
if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
142
|
|
|
143
|
+
// Attach the password inputs to the secrets manager.
|
|
146
144
|
await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
147
|
-
this.
|
|
148
|
-
this._unsavedFields = [];
|
|
145
|
+
const inputs = this._formRef.current?.getElementsByTagName('input') || [];
|
|
149
146
|
for (let i = 0; i < inputs.length; i++) {
|
|
150
147
|
if (inputs[i].type.toLowerCase() === 'password') {
|
|
151
148
|
const label = inputs[i].getAttribute('label');
|
|
@@ -158,16 +155,16 @@ export class AiSettings extends React.Component<
|
|
|
158
155
|
inputs[i],
|
|
159
156
|
(value: string) => this._onPasswordUpdated(label, value)
|
|
160
157
|
);
|
|
161
|
-
this._unsavedFields.push(label);
|
|
162
158
|
}
|
|
163
159
|
}
|
|
164
160
|
}
|
|
165
|
-
if (this._settingConnector instanceof SettingConnector) {
|
|
166
|
-
this._settingConnector.doNotSave = this._unsavedFields;
|
|
167
|
-
}
|
|
168
161
|
}
|
|
169
162
|
|
|
170
163
|
componentWillUnmount(): void {
|
|
164
|
+
this._settings.changed.disconnect(this._settingsChanged);
|
|
165
|
+
this._secretsManager?.fieldVisibilityChanged.disconnect(
|
|
166
|
+
this._fieldVisibilityChanged
|
|
167
|
+
);
|
|
171
168
|
if (!this._secretsManager || !this._useSecretsManager) {
|
|
172
169
|
return;
|
|
173
170
|
}
|
|
@@ -194,7 +191,7 @@ export class AiSettings extends React.Component<
|
|
|
194
191
|
/**
|
|
195
192
|
* Get settings from local storage for a given provider.
|
|
196
193
|
*/
|
|
197
|
-
|
|
194
|
+
getSettingsFromLocalStorage(): IDict<any> {
|
|
198
195
|
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
199
196
|
return settings[this._provider] ?? { provider: this._provider };
|
|
200
197
|
}
|
|
@@ -202,29 +199,72 @@ export class AiSettings extends React.Component<
|
|
|
202
199
|
/**
|
|
203
200
|
* Save settings in local storage for a given provider.
|
|
204
201
|
*/
|
|
205
|
-
|
|
206
|
-
const currentSettings = { ...
|
|
202
|
+
saveSettingsToLocalStorage() {
|
|
203
|
+
const currentSettings = { ...this._currentSettings };
|
|
207
204
|
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) ?? '{}');
|
|
208
|
-
|
|
205
|
+
// Do not save secrets in local storage if using the secrets manager.
|
|
206
|
+
if (this._useSecretsManager) {
|
|
207
|
+
this._secretFields.forEach(field => delete currentSettings[field]);
|
|
208
|
+
}
|
|
209
209
|
settings[this._provider] = currentSettings;
|
|
210
210
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
|
|
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
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Update the settings whether the secrets manager is used or not.
|
|
251
|
+
*
|
|
252
|
+
* @param value - whether to use the secrets manager or not.
|
|
253
|
+
*/
|
|
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.
|
|
214
261
|
this._useSecretsManager = value;
|
|
215
262
|
if (!value) {
|
|
216
263
|
// Detach all the password inputs attached to the secrets manager, and save the
|
|
217
264
|
// current settings to the local storage to save the password.
|
|
218
|
-
this._secretsManager
|
|
219
|
-
this._formInputs = [];
|
|
220
|
-
this._unsavedFields = [];
|
|
221
|
-
if (this._settingConnector instanceof SettingConnector) {
|
|
222
|
-
this._settingConnector.doNotSave = [];
|
|
223
|
-
}
|
|
224
|
-
this.saveSettings(this._currentSettings);
|
|
265
|
+
this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
225
266
|
} else {
|
|
226
|
-
// Remove all the keys stored locally
|
|
227
|
-
// secrets manager.
|
|
267
|
+
// Remove all the keys stored locally.
|
|
228
268
|
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
229
269
|
Object.keys(settings).forEach(provider => {
|
|
230
270
|
Object.keys(settings[provider])
|
|
@@ -234,11 +274,10 @@ export class AiSettings extends React.Component<
|
|
|
234
274
|
});
|
|
235
275
|
});
|
|
236
276
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
237
|
-
this.componentDidUpdate();
|
|
238
277
|
}
|
|
239
|
-
this.
|
|
240
|
-
|
|
241
|
-
|
|
278
|
+
this._updateSchema();
|
|
279
|
+
this.saveSettingsToLocalStorage();
|
|
280
|
+
this.saveSettingsToRegistry();
|
|
242
281
|
};
|
|
243
282
|
|
|
244
283
|
/**
|
|
@@ -251,17 +290,31 @@ export class AiSettings extends React.Component<
|
|
|
251
290
|
this._provider
|
|
252
291
|
);
|
|
253
292
|
|
|
293
|
+
this._secretFields = [];
|
|
294
|
+
this._defaultFormData = {};
|
|
254
295
|
if (settingsSchema) {
|
|
255
296
|
Object.entries(settingsSchema).forEach(([key, value]) => {
|
|
256
297
|
if (key.toLowerCase().includes('key')) {
|
|
257
|
-
|
|
298
|
+
this._secretFields.push(key);
|
|
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) {
|
|
258
306
|
return;
|
|
259
307
|
}
|
|
308
|
+
|
|
260
309
|
this._uiSchema[key] = { 'ui:widget': 'password' };
|
|
261
310
|
}
|
|
262
311
|
schema.properties[key] = value;
|
|
312
|
+
if (value.default !== undefined) {
|
|
313
|
+
this._defaultFormData[key] = value.default;
|
|
314
|
+
}
|
|
263
315
|
});
|
|
264
316
|
}
|
|
317
|
+
|
|
265
318
|
return schema as JSONSchema7;
|
|
266
319
|
}
|
|
267
320
|
|
|
@@ -293,7 +346,30 @@ export class AiSettings extends React.Component<
|
|
|
293
346
|
}
|
|
294
347
|
|
|
295
348
|
/**
|
|
296
|
-
*
|
|
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.
|
|
297
373
|
* Update the Jupyterlab settings accordingly.
|
|
298
374
|
*/
|
|
299
375
|
private _onProviderChanged = (e: IChangeEvent) => {
|
|
@@ -303,12 +379,18 @@ export class AiSettings extends React.Component<
|
|
|
303
379
|
}
|
|
304
380
|
this._provider = provider;
|
|
305
381
|
this.saveCurrentProvider();
|
|
306
|
-
this._currentSettings = this.getSettings();
|
|
307
382
|
this._updateSchema();
|
|
308
383
|
this._renderInstruction();
|
|
309
|
-
this.
|
|
310
|
-
|
|
311
|
-
|
|
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();
|
|
312
394
|
};
|
|
313
395
|
|
|
314
396
|
/**
|
|
@@ -317,22 +399,65 @@ export class AiSettings extends React.Component<
|
|
|
317
399
|
*/
|
|
318
400
|
private _onPasswordUpdated = (fieldName: string, value: string) => {
|
|
319
401
|
this._currentSettings[fieldName] = value;
|
|
320
|
-
this.
|
|
321
|
-
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
322
|
-
.catch(console.error);
|
|
402
|
+
this.saveSettingsToRegistry();
|
|
323
403
|
};
|
|
324
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
|
+
|
|
325
432
|
/**
|
|
326
433
|
* Triggered when the form value has changed, to update the current settings and save
|
|
327
434
|
* it in local storage.
|
|
328
435
|
* Update the Jupyterlab settings accordingly.
|
|
329
436
|
*/
|
|
330
|
-
private
|
|
331
|
-
|
|
332
|
-
this.
|
|
333
|
-
this.
|
|
334
|
-
|
|
335
|
-
|
|
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 });
|
|
336
461
|
};
|
|
337
462
|
|
|
338
463
|
render(): JSX.Element {
|
|
@@ -343,6 +468,12 @@ export class AiSettings extends React.Component<
|
|
|
343
468
|
schema={this._providerSchema}
|
|
344
469
|
onChange={this._onProviderChanged}
|
|
345
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
|
+
)}
|
|
346
477
|
{this.state.instruction !== null && (
|
|
347
478
|
<details>
|
|
348
479
|
<summary className={INSTRUCTION_CLASS}>Instructions</summary>
|
|
@@ -353,11 +484,26 @@ export class AiSettings extends React.Component<
|
|
|
353
484
|
/>
|
|
354
485
|
</details>
|
|
355
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>
|
|
356
497
|
<WrappedFormComponent
|
|
357
498
|
formData={this._currentSettings}
|
|
358
499
|
schema={this.state.schema}
|
|
359
|
-
onChange={this.
|
|
500
|
+
onChange={this._onFormChanged}
|
|
360
501
|
uiSchema={this._uiSchema}
|
|
502
|
+
idPrefix={`jp-SettingsEditor-${PLUGIN_IDS.providerRegistry}`}
|
|
503
|
+
formContext={{
|
|
504
|
+
...this.props.formContext,
|
|
505
|
+
defaultFormData: this._defaultFormData
|
|
506
|
+
}}
|
|
361
507
|
/>
|
|
362
508
|
</div>
|
|
363
509
|
);
|
|
@@ -367,16 +513,14 @@ export class AiSettings extends React.Component<
|
|
|
367
513
|
private _provider: string;
|
|
368
514
|
private _providerSchema: JSONSchema7;
|
|
369
515
|
private _useSecretsManager: boolean;
|
|
370
|
-
private _hideSecretFields: boolean;
|
|
371
516
|
private _rmRegistry: IRenderMimeRegistry | null;
|
|
372
517
|
private _secretsManager: ISecretsManager | null;
|
|
373
|
-
private _settingConnector: ISettingConnector | null;
|
|
374
518
|
private _currentSettings: IDict<any> = { provider: 'None' };
|
|
375
519
|
private _uiSchema: IDict<any> = {};
|
|
376
520
|
private _settings: ISettingRegistry.ISettings;
|
|
377
521
|
private _formRef = React.createRef<HTMLDivElement>();
|
|
378
|
-
private
|
|
379
|
-
private
|
|
522
|
+
private _secretFields: string[] = [];
|
|
523
|
+
private _defaultFormData: IDict<any> = {};
|
|
380
524
|
}
|
|
381
525
|
|
|
382
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
|
-
}
|