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