@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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/lib/base-completer.d.ts +0 -5
  3. package/lib/chat-handler.d.ts +19 -5
  4. package/lib/chat-handler.js +47 -26
  5. package/lib/completion-provider.d.ts +2 -2
  6. package/lib/completion-provider.js +4 -3
  7. package/lib/components/stop-button.d.ts +0 -1
  8. package/lib/default-providers/Anthropic/completer.d.ts +0 -2
  9. package/lib/default-providers/Anthropic/completer.js +2 -4
  10. package/lib/default-providers/ChromeAI/completer.d.ts +0 -2
  11. package/lib/default-providers/ChromeAI/completer.js +2 -4
  12. package/lib/default-providers/ChromeAI/instructions.d.ts +4 -0
  13. package/lib/default-providers/ChromeAI/instructions.js +18 -0
  14. package/lib/default-providers/MistralAI/completer.d.ts +0 -2
  15. package/lib/default-providers/MistralAI/completer.js +3 -4
  16. package/lib/default-providers/Ollama/completer.d.ts +17 -0
  17. package/lib/default-providers/Ollama/completer.js +49 -0
  18. package/lib/default-providers/Ollama/instructions.d.ts +2 -0
  19. package/lib/default-providers/Ollama/instructions.js +70 -0
  20. package/lib/default-providers/Ollama/settings-schema.json +146 -0
  21. package/lib/default-providers/OpenAI/completer.d.ts +0 -2
  22. package/lib/default-providers/OpenAI/completer.js +2 -4
  23. package/lib/default-providers/WebLLM/completer.d.ts +27 -0
  24. package/lib/default-providers/WebLLM/completer.js +136 -0
  25. package/lib/default-providers/WebLLM/instructions.d.ts +6 -0
  26. package/lib/default-providers/WebLLM/instructions.js +32 -0
  27. package/lib/default-providers/WebLLM/settings-schema.json +21 -0
  28. package/lib/default-providers/index.js +119 -4
  29. package/lib/index.d.ts +2 -2
  30. package/lib/index.js +16 -26
  31. package/lib/provider.d.ts +11 -13
  32. package/lib/provider.js +120 -52
  33. package/lib/settings/index.d.ts +0 -1
  34. package/lib/settings/index.js +0 -1
  35. package/lib/settings/panel.d.ts +37 -8
  36. package/lib/settings/panel.js +225 -131
  37. package/lib/tokens.d.ts +21 -2
  38. package/lib/types/ai-model.d.ts +24 -0
  39. package/lib/types/ai-model.js +5 -0
  40. package/package.json +14 -11
  41. package/schema/provider-registry.json +0 -6
  42. package/src/base-completer.ts +0 -6
  43. package/src/chat-handler.ts +40 -7
  44. package/src/completion-provider.ts +2 -2
  45. package/src/default-providers/Anthropic/completer.ts +0 -5
  46. package/src/default-providers/ChromeAI/completer.ts +0 -5
  47. package/src/default-providers/ChromeAI/instructions.ts +21 -0
  48. package/src/default-providers/MistralAI/completer.ts +0 -5
  49. package/src/default-providers/Ollama/completer.ts +62 -0
  50. package/src/default-providers/Ollama/instructions.ts +70 -0
  51. package/src/default-providers/OpenAI/completer.ts +0 -5
  52. package/src/default-providers/WebLLM/completer.ts +162 -0
  53. package/src/default-providers/WebLLM/instructions.ts +33 -0
  54. package/src/default-providers/index.ts +151 -14
  55. package/src/index.ts +17 -29
  56. package/src/provider.ts +132 -45
  57. package/src/settings/index.ts +0 -1
  58. package/src/settings/panel.tsx +207 -73
  59. package/src/tokens.ts +23 -2
  60. package/src/types/ai-model.ts +37 -0
  61. package/src/types/service-worker.d.ts +6 -0
  62. package/style/base.css +5 -0
  63. package/lib/settings/settings-connector.d.ts +0 -31
  64. package/lib/settings/settings-connector.js +0 -61
  65. package/src/settings/settings-connector.ts +0 -88
@@ -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, SettingConnector } from '.';
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 = (_a = props.formContext.rmRegistry) !== null && _a !== void 0 ? _a : null;
120
- this._secretsManager = (_b = props.formContext.secretsManager) !== null && _b !== void 0 ? _b : null;
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
- (_d = this._settings.get('UseSecretsManager').composite) !== null && _d !== void 0 ? _d : true;
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 (led to default if there are no user settings).
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 = (_f = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _f === void 0 ? void 0 : _f[1];
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
- this.state = { schema, instruction: null };
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._settings
162
- .set('AIprovider', this._currentSettings)
163
- .catch(console.error);
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 = ((_a = this._formRef.current) === null || _a === void 0 ? void 0 : _a.getElementsByTagName('input')) || [];
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 (_a = settings['_current']) !== null && _a !== void 0 ? _a : 'None';
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
- getSettings() {
221
- var _a;
130
+ getSettingsFromLocalStorage() {
222
131
  const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
223
- return (_a = settings[this._provider]) !== null && _a !== void 0 ? _a : { provider: this._provider };
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
- saveSettings(value) {
229
- var _a;
230
- const currentSettings = { ...value };
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._secretsManager && this._useSecretsManager) {
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
- if (this._hideSecretFields) {
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(WrappedFormComponent, { formData: this._currentSettings, schema: this.state.schema, onChange: this._onFormChange, uiSchema: this._uiSchema })));
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: IBaseCompleter | null;
84
+ currentCompleter: AICompleter | null;
70
85
  /**
71
86
  * Get the current llm chat model.
72
87
  */
73
- currentChatModel: BaseChatModel | null;
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
+ };
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyterlite/ai",
3
- "version": "0.6.2",
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.9.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.31",
70
- "@langchain/core": "^0.3.40",
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.3.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.3.0",
109
- "typescript": "~5.1.6",
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",
@@ -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
  */