@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/lib/settings/panel.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { FormComponent } from '@jupyterlab/ui-components';
|
|
1
|
+
import { Button, FormComponent } from '@jupyterlab/ui-components';
|
|
2
2
|
import { JSONExt } from '@lumino/coreutils';
|
|
3
3
|
import validator from '@rjsf/validator-ajv8';
|
|
4
4
|
import React from 'react';
|
|
5
|
-
import { getSecretId,
|
|
5
|
+
import { getSecretId, SECRETS_REPLACEMENT } from '.';
|
|
6
6
|
import baseSettings from './base.json';
|
|
7
7
|
import { PLUGIN_IDS } from '../tokens';
|
|
8
8
|
const MD_MIME_TYPE = 'text/markdown';
|
|
9
9
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
10
10
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
11
|
+
const ERROR_CLASS = 'jp-AISettingsError';
|
|
11
12
|
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
|
|
12
13
|
export const aiSettingsRenderer = (options) => {
|
|
13
14
|
const { secretsToken } = options;
|
|
@@ -27,103 +28,17 @@ const WrappedFormComponent = (props) => {
|
|
|
27
28
|
};
|
|
28
29
|
export class AiSettings extends React.Component {
|
|
29
30
|
constructor(props) {
|
|
30
|
-
var _a, _b, _c, _d, _e, _f;
|
|
31
31
|
super(props);
|
|
32
|
-
/**
|
|
33
|
-
* Update the settings whether the secrets manager is used or not.
|
|
34
|
-
*
|
|
35
|
-
* @param value - whether to use the secrets manager or not.
|
|
36
|
-
*/
|
|
37
|
-
this._updateUseSecretsManager = (value) => {
|
|
38
|
-
var _a;
|
|
39
|
-
this._useSecretsManager = value;
|
|
40
|
-
if (!value) {
|
|
41
|
-
// Detach all the password inputs attached to the secrets manager, and save the
|
|
42
|
-
// current settings to the local storage to save the password.
|
|
43
|
-
(_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
44
|
-
if (this._settingConnector instanceof SettingConnector) {
|
|
45
|
-
this._settingConnector.doNotSave = [];
|
|
46
|
-
}
|
|
47
|
-
this.saveSettings(this._currentSettings);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
// Remove all the keys stored locally.
|
|
51
|
-
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
52
|
-
Object.keys(settings).forEach(provider => {
|
|
53
|
-
Object.keys(settings[provider])
|
|
54
|
-
.filter(key => key.toLowerCase().includes('key'))
|
|
55
|
-
.forEach(key => {
|
|
56
|
-
delete settings[provider][key];
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
60
|
-
// Update the fields not to save in settings.
|
|
61
|
-
if (this._settingConnector instanceof SettingConnector) {
|
|
62
|
-
this._settingConnector.doNotSave = this._secretFields;
|
|
63
|
-
}
|
|
64
|
-
// Attach the password inputs to the secrets manager.
|
|
65
|
-
this.componentDidUpdate();
|
|
66
|
-
}
|
|
67
|
-
this._settings
|
|
68
|
-
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
69
|
-
.catch(console.error);
|
|
70
|
-
};
|
|
71
|
-
/**
|
|
72
|
-
* Triggered when the provider hes changed, to update the schema and values.
|
|
73
|
-
* Update the Jupyterlab settings accordingly.
|
|
74
|
-
*/
|
|
75
|
-
this._onProviderChanged = (e) => {
|
|
76
|
-
const provider = e.formData.provider;
|
|
77
|
-
if (provider === this._currentSettings.provider) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
this._provider = provider;
|
|
81
|
-
this.saveCurrentProvider();
|
|
82
|
-
this._currentSettings = this.getSettings();
|
|
83
|
-
this._updateSchema();
|
|
84
|
-
this._renderInstruction();
|
|
85
|
-
this._settings
|
|
86
|
-
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
87
|
-
.catch(console.error);
|
|
88
|
-
};
|
|
89
|
-
/**
|
|
90
|
-
* Callback function called when the password input has been programmatically updated
|
|
91
|
-
* with the secret manager.
|
|
92
|
-
*/
|
|
93
|
-
this._onPasswordUpdated = (fieldName, value) => {
|
|
94
|
-
this._currentSettings[fieldName] = value;
|
|
95
|
-
this._settings
|
|
96
|
-
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
97
|
-
.catch(console.error);
|
|
98
|
-
};
|
|
99
|
-
/**
|
|
100
|
-
* Triggered when the form value has changed, to update the current settings and save
|
|
101
|
-
* it in local storage.
|
|
102
|
-
* Update the Jupyterlab settings accordingly.
|
|
103
|
-
*/
|
|
104
|
-
this._onFormChange = (e) => {
|
|
105
|
-
this._currentSettings = JSONExt.deepCopy(e.formData);
|
|
106
|
-
this.saveSettings(this._currentSettings);
|
|
107
|
-
this._settings
|
|
108
|
-
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
109
|
-
.catch(console.error);
|
|
110
|
-
};
|
|
111
|
-
this._currentSettings = { provider: 'None' };
|
|
112
|
-
this._uiSchema = {};
|
|
113
|
-
this._formRef = React.createRef();
|
|
114
|
-
this._secretFields = [];
|
|
115
32
|
if (!props.formContext.providerRegistry) {
|
|
116
33
|
throw new Error('The provider registry is needed to enable the jupyterlite-ai settings panel');
|
|
117
34
|
}
|
|
118
35
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
119
|
-
this._rmRegistry =
|
|
120
|
-
this._secretsManager =
|
|
121
|
-
this._settingConnector = (_c = props.formContext.settingConnector) !== null && _c !== void 0 ? _c : null;
|
|
36
|
+
this._rmRegistry = props.formContext.rmRegistry ?? null;
|
|
37
|
+
this._secretsManager = props.formContext.secretsManager ?? null;
|
|
122
38
|
this._settings = props.formContext.settings;
|
|
39
|
+
const useSecretsManagerSetting = this._settings.get('UseSecretsManager').composite ?? true;
|
|
123
40
|
this._useSecretsManager =
|
|
124
|
-
|
|
125
|
-
this._hideSecretFields =
|
|
126
|
-
(_e = this._settings.get('HideSecretFields').composite) !== null && _e !== void 0 ? _e : true;
|
|
41
|
+
useSecretsManagerSetting && this._secretsManager !== null;
|
|
127
42
|
// Initialize the providers schema.
|
|
128
43
|
const providerSchema = JSONExt.deepCopy(baseSettings);
|
|
129
44
|
providerSchema.properties.provider = {
|
|
@@ -135,13 +50,13 @@ export class AiSettings extends React.Component {
|
|
|
135
50
|
};
|
|
136
51
|
this._providerSchema = providerSchema;
|
|
137
52
|
// Check if there is saved values in local storage, otherwise use the settings from
|
|
138
|
-
// the setting registry (
|
|
53
|
+
// the setting registry (leads to default if there are no user settings).
|
|
139
54
|
const storageSettings = localStorage.getItem(STORAGE_NAME);
|
|
140
55
|
if (storageSettings === null) {
|
|
141
56
|
const labSettings = this._settings.get('AIprovider').composite;
|
|
142
57
|
if (labSettings && Object.keys(labSettings).includes('provider')) {
|
|
143
58
|
// Get the provider name.
|
|
144
|
-
const provider =
|
|
59
|
+
const provider = Object.entries(labSettings).find(v => v[0] === 'provider')?.[1];
|
|
145
60
|
// Save the settings.
|
|
146
61
|
const settings = {
|
|
147
62
|
_current: provider
|
|
@@ -152,36 +67,30 @@ export class AiSettings extends React.Component {
|
|
|
152
67
|
}
|
|
153
68
|
// Initialize the settings from the saved ones.
|
|
154
69
|
this._provider = this.getCurrentProvider();
|
|
155
|
-
this._currentSettings = this.getSettings();
|
|
156
70
|
// Initialize the schema.
|
|
157
71
|
const schema = this._buildSchema();
|
|
158
|
-
|
|
72
|
+
// Initialize the current settings.
|
|
73
|
+
const isModified = this._updatedFormData(this.getSettingsFromLocalStorage());
|
|
74
|
+
this.state = {
|
|
75
|
+
schema,
|
|
76
|
+
instruction: null,
|
|
77
|
+
compatibilityError: null,
|
|
78
|
+
isModified: isModified
|
|
79
|
+
};
|
|
159
80
|
this._renderInstruction();
|
|
81
|
+
this._checkProviderCompatibility();
|
|
160
82
|
// Update the setting registry.
|
|
161
|
-
this.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
this._settings.changed.connect(() => {
|
|
165
|
-
var _a, _b;
|
|
166
|
-
const useSecretsManager = (_a = this._settings.get('UseSecretsManager').composite) !== null && _a !== void 0 ? _a : true;
|
|
167
|
-
if (useSecretsManager !== this._useSecretsManager) {
|
|
168
|
-
this._updateUseSecretsManager(useSecretsManager);
|
|
169
|
-
}
|
|
170
|
-
const hideSecretFields = (_b = this._settings.get('HideSecretFields').composite) !== null && _b !== void 0 ? _b : true;
|
|
171
|
-
if (hideSecretFields !== this._hideSecretFields) {
|
|
172
|
-
this._hideSecretFields = hideSecretFields;
|
|
173
|
-
this._updateSchema();
|
|
174
|
-
}
|
|
175
|
-
});
|
|
83
|
+
this.saveSettingsToRegistry();
|
|
84
|
+
this._secretsManager?.fieldVisibilityChanged.connect(this._fieldVisibilityChanged);
|
|
85
|
+
this._settings.changed.connect(this._settingsChanged);
|
|
176
86
|
}
|
|
177
87
|
async componentDidUpdate() {
|
|
178
|
-
var _a;
|
|
179
88
|
if (!this._secretsManager || !this._useSecretsManager) {
|
|
180
89
|
return;
|
|
181
90
|
}
|
|
182
91
|
// Attach the password inputs to the secrets manager.
|
|
183
92
|
await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
184
|
-
const inputs =
|
|
93
|
+
const inputs = this._formRef.current?.getElementsByTagName('input') || [];
|
|
185
94
|
for (let i = 0; i < inputs.length; i++) {
|
|
186
95
|
if (inputs[i].type.toLowerCase() === 'password') {
|
|
187
96
|
const label = inputs[i].getAttribute('label');
|
|
@@ -193,6 +102,8 @@ export class AiSettings extends React.Component {
|
|
|
193
102
|
}
|
|
194
103
|
}
|
|
195
104
|
componentWillUnmount() {
|
|
105
|
+
this._settings.changed.disconnect(this._settingsChanged);
|
|
106
|
+
this._secretsManager?.fieldVisibilityChanged.disconnect(this._fieldVisibilityChanged);
|
|
196
107
|
if (!this._secretsManager || !this._useSecretsManager) {
|
|
197
108
|
return;
|
|
198
109
|
}
|
|
@@ -202,9 +113,8 @@ export class AiSettings extends React.Component {
|
|
|
202
113
|
* Get the current provider from the local storage.
|
|
203
114
|
*/
|
|
204
115
|
getCurrentProvider() {
|
|
205
|
-
var _a;
|
|
206
116
|
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
207
|
-
return
|
|
117
|
+
return settings['_current'] ?? 'None';
|
|
208
118
|
}
|
|
209
119
|
/**
|
|
210
120
|
* Save the current provider to the local storage.
|
|
@@ -217,25 +127,84 @@ export class AiSettings extends React.Component {
|
|
|
217
127
|
/**
|
|
218
128
|
* Get settings from local storage for a given provider.
|
|
219
129
|
*/
|
|
220
|
-
|
|
221
|
-
var _a;
|
|
130
|
+
getSettingsFromLocalStorage() {
|
|
222
131
|
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
223
|
-
return
|
|
132
|
+
return settings[this._provider] ?? { provider: this._provider };
|
|
224
133
|
}
|
|
225
134
|
/**
|
|
226
135
|
* Save settings in local storage for a given provider.
|
|
227
136
|
*/
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
const settings = JSON.parse((_a = localStorage.getItem(STORAGE_NAME)) !== null && _a !== void 0 ? _a : '{}');
|
|
137
|
+
saveSettingsToLocalStorage() {
|
|
138
|
+
const currentSettings = { ...this._currentSettings };
|
|
139
|
+
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) ?? '{}');
|
|
232
140
|
// Do not save secrets in local storage if using the secrets manager.
|
|
233
|
-
if (this.
|
|
141
|
+
if (this._useSecretsManager) {
|
|
234
142
|
this._secretFields.forEach(field => delete currentSettings[field]);
|
|
235
143
|
}
|
|
236
144
|
settings[this._provider] = currentSettings;
|
|
237
145
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
238
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Save the settings to the setting registry.
|
|
149
|
+
*/
|
|
150
|
+
saveSettingsToRegistry() {
|
|
151
|
+
const sanitizedSettings = { ...this._currentSettings };
|
|
152
|
+
if (this._useSecretsManager) {
|
|
153
|
+
this._secretFields.forEach(field => {
|
|
154
|
+
sanitizedSettings[field] = SECRETS_REPLACEMENT;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
this._settings
|
|
158
|
+
.set('AIprovider', { provider: this._provider, ...sanitizedSettings })
|
|
159
|
+
.catch(console.error);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Triggered when the settings has changed.
|
|
163
|
+
*/
|
|
164
|
+
_settingsChanged = (settings) => {
|
|
165
|
+
this._updateUseSecretsManager(this._settings.get('UseSecretsManager').composite ?? true);
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Triggered when the secret fields visibility has changed.
|
|
169
|
+
*/
|
|
170
|
+
_fieldVisibilityChanged = (_, value) => {
|
|
171
|
+
if (this._useSecretsManager) {
|
|
172
|
+
this._updateSchema();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Update the settings whether the secrets manager is used or not.
|
|
177
|
+
*
|
|
178
|
+
* @param value - whether to use the secrets manager or not.
|
|
179
|
+
*/
|
|
180
|
+
_updateUseSecretsManager = (value) => {
|
|
181
|
+
// No-op if the value did not change or the secrets manager has not been provided.
|
|
182
|
+
if (value === this._useSecretsManager || this._secretsManager === null) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Update the secrets manager.
|
|
186
|
+
this._useSecretsManager = value;
|
|
187
|
+
if (!value) {
|
|
188
|
+
// Detach all the password inputs attached to the secrets manager, and save the
|
|
189
|
+
// current settings to the local storage to save the password.
|
|
190
|
+
this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// Remove all the keys stored locally.
|
|
194
|
+
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
195
|
+
Object.keys(settings).forEach(provider => {
|
|
196
|
+
Object.keys(settings[provider])
|
|
197
|
+
.filter(key => key.toLowerCase().includes('key'))
|
|
198
|
+
.forEach(key => {
|
|
199
|
+
delete settings[provider][key];
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
203
|
+
}
|
|
204
|
+
this._updateSchema();
|
|
205
|
+
this.saveSettingsToLocalStorage();
|
|
206
|
+
this.saveSettingsToRegistry();
|
|
207
|
+
};
|
|
239
208
|
/**
|
|
240
209
|
* Build the schema for a given provider.
|
|
241
210
|
*/
|
|
@@ -244,24 +213,26 @@ export class AiSettings extends React.Component {
|
|
|
244
213
|
this._uiSchema = {};
|
|
245
214
|
const settingsSchema = this._providerRegistry.getSettingsSchema(this._provider);
|
|
246
215
|
this._secretFields = [];
|
|
216
|
+
this._defaultFormData = {};
|
|
247
217
|
if (settingsSchema) {
|
|
248
218
|
Object.entries(settingsSchema).forEach(([key, value]) => {
|
|
249
219
|
if (key.toLowerCase().includes('key')) {
|
|
250
220
|
this._secretFields.push(key);
|
|
251
|
-
|
|
221
|
+
// If the secrets manager is not used, do not show the secrets fields.
|
|
222
|
+
// If the secrets manager is used, check if the fields should be visible.
|
|
223
|
+
const showSecretFields = !this._useSecretsManager ||
|
|
224
|
+
(this._secretsManager?.secretFieldsVisibility ?? true);
|
|
225
|
+
if (!showSecretFields) {
|
|
252
226
|
return;
|
|
253
227
|
}
|
|
254
228
|
this._uiSchema[key] = { 'ui:widget': 'password' };
|
|
255
229
|
}
|
|
256
230
|
schema.properties[key] = value;
|
|
231
|
+
if (value.default !== undefined) {
|
|
232
|
+
this._defaultFormData[key] = value.default;
|
|
233
|
+
}
|
|
257
234
|
});
|
|
258
235
|
}
|
|
259
|
-
// Do not save secrets in settings if using the secrets manager.
|
|
260
|
-
if (this._secretsManager &&
|
|
261
|
-
this._useSecretsManager &&
|
|
262
|
-
this._settingConnector instanceof SettingConnector) {
|
|
263
|
-
this._settingConnector.doNotSave = this._secretFields;
|
|
264
|
-
}
|
|
265
236
|
return schema;
|
|
266
237
|
}
|
|
267
238
|
/**
|
|
@@ -289,14 +260,137 @@ export class AiSettings extends React.Component {
|
|
|
289
260
|
await renderer.renderModel(model);
|
|
290
261
|
this.setState({ instruction: renderer.node });
|
|
291
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Check for compatibility of the provider with the current environment.
|
|
265
|
+
* If the provider is not compatible, display an error message.
|
|
266
|
+
*/
|
|
267
|
+
async _checkProviderCompatibility() {
|
|
268
|
+
const compatibilityCheck = this._providerRegistry.getCompatibilityCheck(this._provider);
|
|
269
|
+
if (!compatibilityCheck) {
|
|
270
|
+
this.setState({ compatibilityError: null });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const error = await compatibilityCheck();
|
|
274
|
+
if (!error) {
|
|
275
|
+
this.setState({ compatibilityError: null });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const errorDiv = document.createElement('div');
|
|
279
|
+
errorDiv.className = ERROR_CLASS;
|
|
280
|
+
errorDiv.innerHTML = error;
|
|
281
|
+
this.setState({ compatibilityError: error });
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Triggered when the provider has changed, to update the schema and values.
|
|
285
|
+
* Update the Jupyterlab settings accordingly.
|
|
286
|
+
*/
|
|
287
|
+
_onProviderChanged = (e) => {
|
|
288
|
+
const provider = e.formData.provider;
|
|
289
|
+
if (provider === this._currentSettings.provider) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this._provider = provider;
|
|
293
|
+
this.saveCurrentProvider();
|
|
294
|
+
this._updateSchema();
|
|
295
|
+
this._renderInstruction();
|
|
296
|
+
this._checkProviderCompatibility();
|
|
297
|
+
// Initialize the current settings.
|
|
298
|
+
const isModified = this._updatedFormData(this.getSettingsFromLocalStorage());
|
|
299
|
+
if (isModified !== this.state.isModified) {
|
|
300
|
+
this.setState({ isModified });
|
|
301
|
+
}
|
|
302
|
+
this.saveSettingsToRegistry();
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Callback function called when the password input has been programmatically updated
|
|
306
|
+
* with the secret manager.
|
|
307
|
+
*/
|
|
308
|
+
_onPasswordUpdated = (fieldName, value) => {
|
|
309
|
+
this._currentSettings[fieldName] = value;
|
|
310
|
+
this.saveSettingsToRegistry();
|
|
311
|
+
};
|
|
312
|
+
/**
|
|
313
|
+
* Update the current settings with the new values from the form.
|
|
314
|
+
*
|
|
315
|
+
* @param data - The form data to update.
|
|
316
|
+
* @returns - Boolean whether the form is not the default one.
|
|
317
|
+
*/
|
|
318
|
+
_updatedFormData(data) {
|
|
319
|
+
let isModified = false;
|
|
320
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
321
|
+
if (this._defaultFormData[key] !== undefined) {
|
|
322
|
+
if (value === undefined) {
|
|
323
|
+
const schemaProperty = this.state.schema.properties?.[key];
|
|
324
|
+
if (schemaProperty.type === 'string') {
|
|
325
|
+
data[key] = '';
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (value !== this._defaultFormData[key]) {
|
|
329
|
+
isModified = true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
this._currentSettings = JSONExt.deepCopy(data);
|
|
334
|
+
return isModified;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Triggered when the form value has changed, to update the current settings and save
|
|
338
|
+
* it in local storage.
|
|
339
|
+
* Update the Jupyterlab settings accordingly.
|
|
340
|
+
*/
|
|
341
|
+
_onFormChanged = (e) => {
|
|
342
|
+
const { formData } = e;
|
|
343
|
+
const isModified = this._updatedFormData(formData);
|
|
344
|
+
this.saveSettingsToLocalStorage();
|
|
345
|
+
this.saveSettingsToRegistry();
|
|
346
|
+
if (isModified !== this.state.isModified) {
|
|
347
|
+
this.setState({ isModified });
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* Handler for the "Restore to defaults" button - clears all
|
|
352
|
+
* modified settings then calls `setFormData` to restore the
|
|
353
|
+
* values.
|
|
354
|
+
*/
|
|
355
|
+
_reset = async (event) => {
|
|
356
|
+
event.stopPropagation();
|
|
357
|
+
this._currentSettings = {
|
|
358
|
+
...this._currentSettings,
|
|
359
|
+
...this._defaultFormData
|
|
360
|
+
};
|
|
361
|
+
this.saveSettingsToLocalStorage();
|
|
362
|
+
this.saveSettingsToRegistry();
|
|
363
|
+
this.setState({ isModified: false });
|
|
364
|
+
};
|
|
292
365
|
render() {
|
|
293
366
|
return (React.createElement("div", { ref: this._formRef },
|
|
294
367
|
React.createElement(WrappedFormComponent, { formData: { provider: this._provider }, schema: this._providerSchema, onChange: this._onProviderChanged }),
|
|
368
|
+
this.state.compatibilityError !== null && (React.createElement("div", { className: ERROR_CLASS },
|
|
369
|
+
React.createElement("i", { className: 'fas fa-exclamation-triangle' }),
|
|
370
|
+
React.createElement("span", null, this.state.compatibilityError))),
|
|
295
371
|
this.state.instruction !== null && (React.createElement("details", null,
|
|
296
372
|
React.createElement("summary", { className: INSTRUCTION_CLASS }, "Instructions"),
|
|
297
373
|
React.createElement("span", { ref: node => node && node.replaceChildren(this.state.instruction) }))),
|
|
298
|
-
React.createElement(
|
|
374
|
+
React.createElement("div", { className: "jp-SettingsHeader" },
|
|
375
|
+
React.createElement("h3", { title: this._provider }, this._provider),
|
|
376
|
+
React.createElement("div", { className: "jp-SettingsHeader-buttonbar" }, this.state.isModified && (React.createElement(Button, { className: "jp-RestoreButton", onClick: this._reset }, "Restore to Defaults")))),
|
|
377
|
+
React.createElement(WrappedFormComponent, { formData: this._currentSettings, schema: this.state.schema, onChange: this._onFormChanged, uiSchema: this._uiSchema, idPrefix: `jp-SettingsEditor-${PLUGIN_IDS.providerRegistry}`, formContext: {
|
|
378
|
+
...this.props.formContext,
|
|
379
|
+
defaultFormData: this._defaultFormData
|
|
380
|
+
} })));
|
|
299
381
|
}
|
|
382
|
+
_providerRegistry;
|
|
383
|
+
_provider;
|
|
384
|
+
_providerSchema;
|
|
385
|
+
_useSecretsManager;
|
|
386
|
+
_rmRegistry;
|
|
387
|
+
_secretsManager;
|
|
388
|
+
_currentSettings = { provider: 'None' };
|
|
389
|
+
_uiSchema = {};
|
|
390
|
+
_settings;
|
|
391
|
+
_formRef = React.createRef();
|
|
392
|
+
_secretFields = [];
|
|
393
|
+
_defaultFormData = {};
|
|
300
394
|
}
|
|
301
395
|
var Private;
|
|
302
396
|
(function (Private) {
|
package/lib/tokens.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { ReadonlyPartialJSONObject, Token } from '@lumino/coreutils';
|
|
|
3
3
|
import { ISignal } from '@lumino/signaling';
|
|
4
4
|
import { JSONSchema7 } from 'json-schema';
|
|
5
5
|
import { IBaseCompleter } from './base-completer';
|
|
6
|
+
import { AIChatModel, AICompleter } from './types/ai-model';
|
|
6
7
|
export declare const PLUGIN_IDS: {
|
|
7
8
|
chat: string;
|
|
8
9
|
chatCommandRegistry: string;
|
|
@@ -46,6 +47,20 @@ export interface IAIProvider {
|
|
|
46
47
|
* Default to `(error) => error.message`.
|
|
47
48
|
*/
|
|
48
49
|
errorMessage?: (error: any) => string;
|
|
50
|
+
/**
|
|
51
|
+
* Compatibility check function, to determine if the provider is compatible with the
|
|
52
|
+
* current environment.
|
|
53
|
+
*/
|
|
54
|
+
compatibilityCheck?: () => Promise<string | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Whether to expose or not the chat model.
|
|
57
|
+
*
|
|
58
|
+
* ### CAUTION
|
|
59
|
+
* This flag will expose the whole chat model API, which may contain private keys.
|
|
60
|
+
* Be sure to use it with a model that does not expose sensitive information in the
|
|
61
|
+
* API.
|
|
62
|
+
*/
|
|
63
|
+
exposeChatModel?: boolean;
|
|
49
64
|
}
|
|
50
65
|
/**
|
|
51
66
|
* The provider registry interface.
|
|
@@ -66,11 +81,11 @@ export interface IAIProviderRegistry {
|
|
|
66
81
|
/**
|
|
67
82
|
* Get the current completer of the completion provider.
|
|
68
83
|
*/
|
|
69
|
-
currentCompleter:
|
|
84
|
+
currentCompleter: AICompleter | null;
|
|
70
85
|
/**
|
|
71
86
|
* Get the current llm chat model.
|
|
72
87
|
*/
|
|
73
|
-
currentChatModel:
|
|
88
|
+
currentChatModel: AIChatModel | null;
|
|
74
89
|
/**
|
|
75
90
|
* Get the settings schema of a given provider.
|
|
76
91
|
*/
|
|
@@ -79,6 +94,10 @@ export interface IAIProviderRegistry {
|
|
|
79
94
|
* Get the instructions of a given provider.
|
|
80
95
|
*/
|
|
81
96
|
getInstructions(provider: string): string | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Get the compatibility check function of a given provider.
|
|
99
|
+
*/
|
|
100
|
+
getCompatibilityCheck(provider: string): (() => Promise<string | null>) | undefined;
|
|
82
101
|
/**
|
|
83
102
|
* Format an error message from the current provider.
|
|
84
103
|
*/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
|
+
import { IterableReadableStream } from '@langchain/core/utils/stream';
|
|
3
|
+
/**
|
|
4
|
+
* The reduced AI chat model interface.
|
|
5
|
+
*/
|
|
6
|
+
export type AIChatModel = {
|
|
7
|
+
/**
|
|
8
|
+
* The stream function of the chat model.
|
|
9
|
+
*/
|
|
10
|
+
stream: (input: any, options?: any) => Promise<IterableReadableStream<any>>;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The reduced AI completer interface.
|
|
14
|
+
*/
|
|
15
|
+
export type AICompleter = {
|
|
16
|
+
/**
|
|
17
|
+
* The fetch function of the completer.
|
|
18
|
+
*/
|
|
19
|
+
fetch: (request: CompletionHandler.IRequest, context: IInlineCompletionContext) => Promise<any>;
|
|
20
|
+
/**
|
|
21
|
+
* The optional request completion function of the completer.
|
|
22
|
+
*/
|
|
23
|
+
requestCompletion?: () => void;
|
|
24
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "AI code completions and chat for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"watch:labextension": "jupyter labextension watch ."
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@jupyter/chat": "^0.
|
|
59
|
+
"@jupyter/chat": "^0.12.0",
|
|
60
60
|
"@jupyterlab/application": "^4.4.0",
|
|
61
61
|
"@jupyterlab/apputils": "^4.5.0",
|
|
62
62
|
"@jupyterlab/completer": "^4.4.0",
|
|
@@ -66,31 +66,37 @@
|
|
|
66
66
|
"@jupyterlab/settingregistry": "^4.4.0",
|
|
67
67
|
"@jupyterlab/ui-components": "^4.4.0",
|
|
68
68
|
"@langchain/anthropic": "^0.3.9",
|
|
69
|
-
"@langchain/community": "^0.3.
|
|
70
|
-
"@langchain/core": "^0.3.
|
|
69
|
+
"@langchain/community": "^0.3.44",
|
|
70
|
+
"@langchain/core": "^0.3.57",
|
|
71
71
|
"@langchain/mistralai": "^0.1.1",
|
|
72
|
+
"@langchain/ollama": "^0.2.0",
|
|
72
73
|
"@langchain/openai": "^0.4.4",
|
|
73
74
|
"@lumino/coreutils": "^2.1.2",
|
|
74
75
|
"@lumino/polling": "^2.1.2",
|
|
75
76
|
"@lumino/signaling": "^2.1.2",
|
|
77
|
+
"@mlc-ai/web-llm": "^0.2.79",
|
|
78
|
+
"@mlc-ai/web-runtime": "^0.18.0-dev2",
|
|
79
|
+
"@mlc-ai/web-tokenizers": "^0.1.6",
|
|
76
80
|
"@mui/icons-material": "^5.11.0",
|
|
77
81
|
"@mui/material": "^5.11.0",
|
|
78
82
|
"@rjsf/core": "^5.18.4",
|
|
79
83
|
"@rjsf/utils": "^5.18.4",
|
|
80
84
|
"@rjsf/validator-ajv8": "^5.18.4",
|
|
81
85
|
"json5": "^2.2.3",
|
|
82
|
-
"jupyter-secrets-manager": "^0.
|
|
86
|
+
"jupyter-secrets-manager": "^0.4.0",
|
|
83
87
|
"react": "^18.2.0",
|
|
84
88
|
"react-dom": "^18.2.0"
|
|
85
89
|
},
|
|
86
90
|
"devDependencies": {
|
|
87
91
|
"@jupyterlab/builder": "^4.4.0",
|
|
88
92
|
"@stylistic/eslint-plugin": "^3.0.1",
|
|
93
|
+
"@types/chrome": "^0.0.304",
|
|
89
94
|
"@types/json-schema": "^7.0.11",
|
|
90
95
|
"@types/react": "^18.0.26",
|
|
91
96
|
"@types/react-addons-linked-state-mixin": "^0.14.22",
|
|
92
97
|
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
|
93
98
|
"@typescript-eslint/parser": "^6.1.0",
|
|
99
|
+
"@webgpu/types": "^0.1.54",
|
|
94
100
|
"css-loader": "^6.7.1",
|
|
95
101
|
"eslint": "^8.36.0",
|
|
96
102
|
"eslint-config-prettier": "^8.8.0",
|
|
@@ -105,8 +111,8 @@
|
|
|
105
111
|
"stylelint-config-standard": "^34.0.0",
|
|
106
112
|
"stylelint-csstree-validator": "^3.0.0",
|
|
107
113
|
"stylelint-prettier": "^4.0.0",
|
|
108
|
-
"ts-json-schema-generator": "^2.
|
|
109
|
-
"typescript": "~5.
|
|
114
|
+
"ts-json-schema-generator": "^2.4.0",
|
|
115
|
+
"typescript": "~5.8.3",
|
|
110
116
|
"yjs": "^13.5.0"
|
|
111
117
|
},
|
|
112
118
|
"sideEffects": [
|
|
@@ -120,9 +126,6 @@
|
|
|
120
126
|
"jupyterlab": {
|
|
121
127
|
"extension": true,
|
|
122
128
|
"outputDir": "jupyterlite_ai/labextension",
|
|
123
|
-
"schemaDir": "schema"
|
|
124
|
-
"disabledExtensions": [
|
|
125
|
-
"@jupyterlab/apputils-extension:settings-connector"
|
|
126
|
-
]
|
|
129
|
+
"schemaDir": "schema"
|
|
127
130
|
}
|
|
128
131
|
}
|
|
@@ -11,12 +11,6 @@
|
|
|
11
11
|
"description": "Whether to use or not the secrets manager. If not, secrets will be stored in the browser (local storage)",
|
|
12
12
|
"default": true
|
|
13
13
|
},
|
|
14
|
-
"HideSecretFields": {
|
|
15
|
-
"type": "boolean",
|
|
16
|
-
"title": "Hide secret fields",
|
|
17
|
-
"description": "Whether to hide the secret fields in the UI or not",
|
|
18
|
-
"default": true
|
|
19
|
-
},
|
|
20
14
|
"AIprovider": {
|
|
21
15
|
"type": "object",
|
|
22
16
|
"title": "AI provider",
|
package/src/base-completer.ts
CHANGED
|
@@ -2,15 +2,9 @@ import {
|
|
|
2
2
|
CompletionHandler,
|
|
3
3
|
IInlineCompletionContext
|
|
4
4
|
} from '@jupyterlab/completer';
|
|
5
|
-
import { BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
6
5
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
7
6
|
|
|
8
7
|
export interface IBaseCompleter {
|
|
9
|
-
/**
|
|
10
|
-
* The LLM completer.
|
|
11
|
-
*/
|
|
12
|
-
completer: BaseLanguageModel;
|
|
13
|
-
|
|
14
8
|
/**
|
|
15
9
|
* The completion prompt.
|
|
16
10
|
*/
|