@jupyterlite/ai 0.8.0 → 0.9.0-a0

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 (162) hide show
  1. package/lib/agent.d.ts +233 -0
  2. package/lib/agent.js +604 -0
  3. package/lib/chat-model.d.ts +195 -0
  4. package/lib/chat-model.js +590 -0
  5. package/lib/completion/completion-provider.d.ts +83 -0
  6. package/lib/completion/completion-provider.js +209 -0
  7. package/lib/completion/index.d.ts +1 -0
  8. package/lib/completion/index.js +1 -0
  9. package/lib/components/clear-button.d.ts +18 -0
  10. package/lib/components/clear-button.js +31 -0
  11. package/lib/components/index.d.ts +3 -0
  12. package/lib/components/index.js +3 -0
  13. package/lib/components/model-select.d.ts +19 -0
  14. package/lib/components/model-select.js +154 -0
  15. package/lib/components/stop-button.d.ts +3 -3
  16. package/lib/components/stop-button.js +8 -9
  17. package/lib/components/token-usage-display.d.ts +45 -0
  18. package/lib/components/token-usage-display.js +74 -0
  19. package/lib/components/tool-select.d.ts +27 -0
  20. package/lib/components/tool-select.js +130 -0
  21. package/lib/icons.d.ts +3 -1
  22. package/lib/icons.js +10 -13
  23. package/lib/index.d.ts +4 -5
  24. package/lib/index.js +322 -167
  25. package/lib/mcp/browser.d.ts +68 -0
  26. package/lib/mcp/browser.js +132 -0
  27. package/lib/models/settings-model.d.ts +69 -0
  28. package/lib/models/settings-model.js +295 -0
  29. package/lib/providers/built-in-providers.d.ts +9 -0
  30. package/lib/providers/built-in-providers.js +192 -0
  31. package/lib/providers/models.d.ts +37 -0
  32. package/lib/providers/models.js +28 -0
  33. package/lib/providers/provider-registry.d.ts +94 -0
  34. package/lib/providers/provider-registry.js +155 -0
  35. package/lib/tokens.d.ts +157 -86
  36. package/lib/tokens.js +16 -12
  37. package/lib/tools/commands.d.ts +11 -0
  38. package/lib/tools/commands.js +126 -0
  39. package/lib/tools/file.d.ts +27 -0
  40. package/lib/tools/file.js +262 -0
  41. package/lib/tools/notebook.d.ts +40 -0
  42. package/lib/tools/notebook.js +762 -0
  43. package/lib/tools/tool-registry.d.ts +35 -0
  44. package/lib/tools/tool-registry.js +55 -0
  45. package/lib/widgets/ai-settings.d.ts +39 -0
  46. package/lib/widgets/ai-settings.js +506 -0
  47. package/lib/widgets/chat-wrapper.d.ts +144 -0
  48. package/lib/widgets/chat-wrapper.js +390 -0
  49. package/lib/widgets/provider-config-dialog.d.ts +13 -0
  50. package/lib/widgets/provider-config-dialog.js +104 -0
  51. package/package.json +150 -41
  52. package/schema/settings-model.json +153 -0
  53. package/src/agent.ts +800 -0
  54. package/src/chat-model.ts +770 -0
  55. package/src/completion/completion-provider.ts +308 -0
  56. package/src/completion/index.ts +1 -0
  57. package/src/components/clear-button.tsx +56 -0
  58. package/src/components/index.ts +3 -0
  59. package/src/components/model-select.tsx +245 -0
  60. package/src/components/stop-button.tsx +11 -11
  61. package/src/components/token-usage-display.tsx +130 -0
  62. package/src/components/tool-select.tsx +218 -0
  63. package/src/icons.ts +12 -14
  64. package/src/index.ts +468 -238
  65. package/src/mcp/browser.ts +213 -0
  66. package/src/models/settings-model.ts +409 -0
  67. package/src/providers/built-in-providers.ts +216 -0
  68. package/src/providers/models.ts +79 -0
  69. package/src/providers/provider-registry.ts +189 -0
  70. package/src/tokens.ts +203 -90
  71. package/src/tools/commands.ts +151 -0
  72. package/src/tools/file.ts +307 -0
  73. package/src/tools/notebook.ts +964 -0
  74. package/src/tools/tool-registry.ts +63 -0
  75. package/src/types.d.ts +4 -0
  76. package/src/widgets/ai-settings.tsx +1100 -0
  77. package/src/widgets/chat-wrapper.tsx +543 -0
  78. package/src/widgets/provider-config-dialog.tsx +256 -0
  79. package/style/base.css +335 -14
  80. package/style/icons/jupyternaut-lite.svg +1 -1
  81. package/lib/base-completer.d.ts +0 -49
  82. package/lib/base-completer.js +0 -14
  83. package/lib/chat-handler.d.ts +0 -56
  84. package/lib/chat-handler.js +0 -201
  85. package/lib/completion-provider.d.ts +0 -34
  86. package/lib/completion-provider.js +0 -32
  87. package/lib/default-prompts.d.ts +0 -2
  88. package/lib/default-prompts.js +0 -31
  89. package/lib/default-providers/Anthropic/completer.d.ts +0 -12
  90. package/lib/default-providers/Anthropic/completer.js +0 -46
  91. package/lib/default-providers/Anthropic/settings-schema.json +0 -70
  92. package/lib/default-providers/ChromeAI/completer.d.ts +0 -12
  93. package/lib/default-providers/ChromeAI/completer.js +0 -56
  94. package/lib/default-providers/ChromeAI/instructions.d.ts +0 -6
  95. package/lib/default-providers/ChromeAI/instructions.js +0 -42
  96. package/lib/default-providers/ChromeAI/settings-schema.json +0 -18
  97. package/lib/default-providers/Gemini/completer.d.ts +0 -12
  98. package/lib/default-providers/Gemini/completer.js +0 -48
  99. package/lib/default-providers/Gemini/instructions.d.ts +0 -2
  100. package/lib/default-providers/Gemini/instructions.js +0 -9
  101. package/lib/default-providers/Gemini/settings-schema.json +0 -64
  102. package/lib/default-providers/MistralAI/completer.d.ts +0 -13
  103. package/lib/default-providers/MistralAI/completer.js +0 -52
  104. package/lib/default-providers/MistralAI/instructions.d.ts +0 -2
  105. package/lib/default-providers/MistralAI/instructions.js +0 -18
  106. package/lib/default-providers/MistralAI/settings-schema.json +0 -75
  107. package/lib/default-providers/Ollama/completer.d.ts +0 -12
  108. package/lib/default-providers/Ollama/completer.js +0 -43
  109. package/lib/default-providers/Ollama/instructions.d.ts +0 -2
  110. package/lib/default-providers/Ollama/instructions.js +0 -70
  111. package/lib/default-providers/Ollama/settings-schema.json +0 -143
  112. package/lib/default-providers/OpenAI/completer.d.ts +0 -12
  113. package/lib/default-providers/OpenAI/completer.js +0 -43
  114. package/lib/default-providers/OpenAI/settings-schema.json +0 -628
  115. package/lib/default-providers/WebLLM/completer.d.ts +0 -21
  116. package/lib/default-providers/WebLLM/completer.js +0 -127
  117. package/lib/default-providers/WebLLM/instructions.d.ts +0 -6
  118. package/lib/default-providers/WebLLM/instructions.js +0 -32
  119. package/lib/default-providers/WebLLM/settings-schema.json +0 -19
  120. package/lib/default-providers/index.d.ts +0 -2
  121. package/lib/default-providers/index.js +0 -179
  122. package/lib/provider.d.ts +0 -144
  123. package/lib/provider.js +0 -412
  124. package/lib/settings/base.json +0 -7
  125. package/lib/settings/index.d.ts +0 -3
  126. package/lib/settings/index.js +0 -3
  127. package/lib/settings/panel.d.ts +0 -226
  128. package/lib/settings/panel.js +0 -510
  129. package/lib/settings/textarea.d.ts +0 -2
  130. package/lib/settings/textarea.js +0 -18
  131. package/lib/settings/utils.d.ts +0 -2
  132. package/lib/settings/utils.js +0 -4
  133. package/lib/types/ai-model.d.ts +0 -24
  134. package/lib/types/ai-model.js +0 -5
  135. package/schema/chat.json +0 -28
  136. package/schema/provider-registry.json +0 -29
  137. package/schema/system-prompts.json +0 -22
  138. package/src/base-completer.ts +0 -75
  139. package/src/chat-handler.ts +0 -262
  140. package/src/completion-provider.ts +0 -64
  141. package/src/default-prompts.ts +0 -33
  142. package/src/default-providers/Anthropic/completer.ts +0 -59
  143. package/src/default-providers/ChromeAI/completer.ts +0 -73
  144. package/src/default-providers/ChromeAI/instructions.ts +0 -45
  145. package/src/default-providers/Gemini/completer.ts +0 -61
  146. package/src/default-providers/Gemini/instructions.ts +0 -9
  147. package/src/default-providers/MistralAI/completer.ts +0 -69
  148. package/src/default-providers/MistralAI/instructions.ts +0 -18
  149. package/src/default-providers/Ollama/completer.ts +0 -54
  150. package/src/default-providers/Ollama/instructions.ts +0 -70
  151. package/src/default-providers/OpenAI/completer.ts +0 -54
  152. package/src/default-providers/WebLLM/completer.ts +0 -151
  153. package/src/default-providers/WebLLM/instructions.ts +0 -33
  154. package/src/default-providers/index.ts +0 -211
  155. package/src/global.d.ts +0 -9
  156. package/src/provider.ts +0 -514
  157. package/src/settings/index.ts +0 -3
  158. package/src/settings/panel.tsx +0 -773
  159. package/src/settings/textarea.tsx +0 -33
  160. package/src/settings/utils.ts +0 -5
  161. package/src/types/ai-model.ts +0 -37
  162. package/src/types/service-worker.d.ts +0 -6
@@ -1,773 +0,0 @@
1
- import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
2
- import { ISettingRegistry } from '@jupyterlab/settingregistry';
3
- import {
4
- Button,
5
- FormComponent,
6
- IFormRenderer
7
- } from '@jupyterlab/ui-components';
8
- import { JSONExt, ReadonlyPartialJSONObject } from '@lumino/coreutils';
9
- import { IChangeEvent } from '@rjsf/core';
10
- import type { FieldProps } from '@rjsf/utils';
11
- import validator from '@rjsf/validator-ajv8';
12
- import { JSONSchema7 } from 'json-schema';
13
- import { ISecretsManager } from 'jupyter-secrets-manager';
14
- import React from 'react';
15
-
16
- import { getSecretId, SECRETS_REPLACEMENT } from '.';
17
- import baseSettings from './base.json';
18
- import { IAIProviderRegistry, IDict, ModelRole, PLUGIN_IDS } from '../tokens';
19
-
20
- const MD_MIME_TYPE = 'text/markdown';
21
- const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
22
- const ERROR_CLASS = 'jp-AISettingsError';
23
- const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
24
- const STORAGE_KEYS = {
25
- chat: '@jupyterlite/ai:chat-settings',
26
- completer: '@jupyterlite/ai:completer-settings'
27
- };
28
-
29
- export const aiSettingsRenderer = (options: {
30
- providerRegistry: IAIProviderRegistry;
31
- secretsToken?: symbol;
32
- rmRegistry?: IRenderMimeRegistry;
33
- secretsManager?: ISecretsManager;
34
- }): IFormRenderer => {
35
- const { secretsToken } = options;
36
- delete options.secretsToken;
37
- if (secretsToken) {
38
- Private.setToken(secretsToken);
39
- }
40
- return {
41
- fieldRenderer: (props: FieldProps) => {
42
- props.formContext = { ...props.formContext, ...options };
43
- return <AiSettings {...props} />;
44
- }
45
- };
46
- };
47
-
48
- const WrappedFormComponent = (props: any): JSX.Element => {
49
- return <FormComponent {...props} validator={validator} />;
50
- };
51
-
52
- export interface IAiSettings {
53
- /**
54
- * Get the local storage settings for a specific role (chat or completer).
55
- */
56
- getLocalStorage(role: ModelRole): IDict<any>;
57
- /**
58
- * Set the local storage item for a specific role (chat or completer).
59
- * If the key is not provider (null) we assume the value should replace the whole
60
- * local storage for this role.
61
- */
62
- setLocalStorageItem(role: ModelRole, key: string | null, value: any): void;
63
- /**
64
- * Get the settings from the registry (jupyterlab settings system) for a given role.
65
- */
66
- getSettingsFromRegistry(role: ModelRole): IDict<any>;
67
- /**
68
- * Save the settings to the setting registry.
69
- */
70
- saveSettingsToRegistry(role: ModelRole, settings: IDict<any>): void;
71
- }
72
-
73
- export class AiSettings
74
- extends React.Component<FieldProps, AiSettings.states>
75
- implements IAiSettings
76
- {
77
- constructor(props: FieldProps) {
78
- super(props);
79
- this._settings = props.formContext.settings;
80
- const uniqueProvider =
81
- (this._settings.get('UniqueProvider').composite as boolean) ?? true;
82
-
83
- this.state = { uniqueProvider };
84
-
85
- this._settings.changed.connect(this._settingsChanged);
86
- }
87
-
88
- private _settingsChanged = () => {
89
- const uniqueProvider =
90
- (this._settings.get('UniqueProvider').composite as boolean) ?? true;
91
- if (this.state.uniqueProvider === uniqueProvider) {
92
- return;
93
- }
94
- if (uniqueProvider) {
95
- // Copy chat settings to the completer settings if there should be a unique
96
- // provider for both.
97
- this.setLocalStorageItem('completer', null, this.getLocalStorage('chat'));
98
- this.saveSettingsToRegistry(
99
- 'completer',
100
- this.getSettingsFromRegistry('chat')
101
- );
102
- }
103
- this.setState({ uniqueProvider });
104
- };
105
-
106
- /**
107
- * Get the local storage settings for a specific role (chat or completer).
108
- */
109
- getLocalStorage = (role: ModelRole): IDict<any> => {
110
- const storageKey = STORAGE_KEYS[role];
111
- return JSON.parse(localStorage.getItem(storageKey) ?? '{}');
112
- };
113
-
114
- /**
115
- * Set the local storage item for a specific role (chat or completer).
116
- * If the key is not provider (null) we assume the value should replace the whole
117
- * local storage for this role.
118
- */
119
- setLocalStorageItem = (
120
- role: ModelRole,
121
- key: string | null,
122
- value: any
123
- ): void => {
124
- const storageKey = STORAGE_KEYS[role];
125
- let settings: IDict<any>;
126
-
127
- if (key !== null) {
128
- settings = JSON.parse(localStorage.getItem(storageKey) ?? '{}');
129
- settings[key] = value;
130
- } else {
131
- settings = value;
132
- }
133
-
134
- localStorage.setItem(storageKey, JSON.stringify(settings));
135
-
136
- // If both chat and completer use the same settings, only the chat settings should
137
- // be editable for user, so we should duplicate its values to the completer
138
- // local storage.
139
- if (this.state.uniqueProvider && role === 'chat') {
140
- const storageKeyCompleter = STORAGE_KEYS['completer'];
141
- localStorage.setItem(storageKeyCompleter, JSON.stringify(settings));
142
- }
143
- };
144
-
145
- /**
146
- * Get the settings from the registry (jupyterlab settings system) for a given role.
147
- */
148
- getSettingsFromRegistry = (role: ModelRole): IDict<any> => {
149
- const settings = this._settings.get('AIproviders')
150
- .composite as ReadonlyPartialJSONObject;
151
- return settings && Object.keys(settings).includes(role)
152
- ? (settings[role] as IDict<any>)
153
- : { provider: 'None' };
154
- };
155
-
156
- /**
157
- * Save the settings to the setting registry.
158
- */
159
- saveSettingsToRegistry = (role: ModelRole, settings: IDict<any>): void => {
160
- const fullSettings = this._settings.get('AIproviders')
161
- .composite as IDict<any>;
162
- fullSettings[role] = { ...settings };
163
-
164
- // If both chat and completer use the same settings, only the chat settings should
165
- // be editable for user, so we should duplicate its values to the completer
166
- // settings.
167
- if (this.state.uniqueProvider && role === 'chat') {
168
- fullSettings['completer'] = { ...settings };
169
- }
170
- this._settings.set('AIproviders', { ...fullSettings }).catch(console.error);
171
- };
172
-
173
- render(): JSX.Element {
174
- return (
175
- <div>
176
- <h3>
177
- {this.state.uniqueProvider
178
- ? 'Chat and completer provider'
179
- : 'Chat provider'}
180
- </h3>
181
- <AiProviderSettings {...this.props} role={'chat'} aiSettings={this} />
182
- {!this.state.uniqueProvider && (
183
- <>
184
- <h3>Completer provider</h3>
185
- <AiProviderSettings
186
- {...this.props}
187
- role={'completer'}
188
- aiSettings={this}
189
- />
190
- </>
191
- )}
192
- </div>
193
- );
194
- }
195
-
196
- private _settings: ISettingRegistry.ISettings;
197
- }
198
-
199
- /**
200
- * The AI settings component namespace.
201
- */
202
- namespace AiSettings {
203
- /**
204
- * The AI settings component states.
205
- */
206
- export type states = {
207
- /**
208
- * Whether there is only one provider for chat and completion.
209
- */
210
- uniqueProvider: boolean;
211
- };
212
- /**
213
- * The provider names object.
214
- */
215
- export type providers = {
216
- [key in ModelRole]: string;
217
- };
218
- /**
219
- * The provider schemas object.
220
- */
221
- export type schemas = {
222
- [key in ModelRole]: JSONSchema7;
223
- };
224
- }
225
-
226
- export class AiProviderSettings extends React.Component<
227
- AiProviderSettings.props,
228
- AiProviderSettings.states
229
- > {
230
- constructor(props: AiProviderSettings.props) {
231
- super(props);
232
- if (!props.formContext.providerRegistry) {
233
- throw new Error(
234
- 'The provider registry is needed to enable the jupyterlite-ai settings panel'
235
- );
236
- }
237
- this._role = props.role;
238
- this._providerRegistry = props.formContext.providerRegistry;
239
- this._rmRegistry = props.formContext.rmRegistry ?? null;
240
- this._secretsManager = props.formContext.secretsManager ?? null;
241
- this._settings = props.formContext.settings;
242
-
243
- const useSecretsManagerSetting =
244
- (this._settings.get('UseSecretsManager').composite as boolean) ?? true;
245
- this._useSecretsManager =
246
- useSecretsManagerSetting && this._secretsManager !== null;
247
-
248
- // Initialize the providers schema.
249
- const providerSchema = JSONExt.deepCopy(baseSettings) as any;
250
- providerSchema.properties.provider = {
251
- type: 'string',
252
- title: 'Provider',
253
- description: 'The AI provider to use for chat and completion',
254
- default: 'None',
255
- enum: ['None'].concat(this._providerRegistry.providers)
256
- };
257
- this._providerSchema = providerSchema as JSONSchema7;
258
-
259
- // Check if there is saved values in local storage, otherwise use the settings from
260
- // the setting registry (leads to default if there are no user settings).
261
- const storageKey = STORAGE_KEYS[this._role];
262
- const storageSettings = localStorage.getItem(storageKey);
263
- if (storageSettings === null) {
264
- const labSettings = this.props.aiSettings.getSettingsFromRegistry(
265
- this._role
266
- );
267
- if (Object.keys(labSettings).includes('provider')) {
268
- // Get the provider name.
269
- const provider = Object.entries(labSettings).find(
270
- v => v[0] === 'provider'
271
- )?.[1] as string;
272
- // Save the settings.
273
- const settings: any = {
274
- _current: provider
275
- };
276
- settings[provider] = labSettings;
277
- this.props.aiSettings.setLocalStorageItem(this._role, null, settings);
278
- }
279
- }
280
-
281
- // Initialize the settings from the saved ones.
282
- this._provider = this.getCurrentProvider();
283
-
284
- // Initialize the schema.
285
- const schema = this._buildSchema();
286
-
287
- // Initialize the current settings.
288
- const isModified = this._updatedFormData(
289
- this.getSettingsFromLocalStorage()
290
- );
291
-
292
- this.state = {
293
- schema,
294
- instruction: null,
295
- compatibilityError: null,
296
- isModified: isModified
297
- };
298
- this._renderInstruction();
299
-
300
- this._checkProviderCompatibility();
301
-
302
- // Update the setting registry.
303
- this.saveSettingsToRegistry();
304
-
305
- this._secretsManager?.fieldVisibilityChanged.connect(
306
- this._fieldVisibilityChanged
307
- );
308
-
309
- this._settings.changed.connect(this._settingsChanged);
310
- }
311
-
312
- componentDidMount(): void {
313
- this.componentDidUpdate();
314
- }
315
-
316
- async componentDidUpdate(): Promise<void> {
317
- if (!this._secretsManager || !this._useSecretsManager) {
318
- return;
319
- }
320
-
321
- // Attach the password inputs to the secrets manager.
322
- await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
323
- const inputs = this._formRef.current?.getElementsByTagName('input') || [];
324
- for (let i = 0; i < inputs.length; i++) {
325
- if (inputs[i].type.toLowerCase() === 'password') {
326
- const label = inputs[i].getAttribute('label');
327
- if (label) {
328
- const id = getSecretId(this._provider, label);
329
- this._secretsManager.attach(
330
- Private.getToken(),
331
- SECRETS_NAMESPACE,
332
- id,
333
- inputs[i],
334
- (value: string) => this._onPasswordUpdated(label, value)
335
- );
336
- }
337
- }
338
- }
339
- }
340
-
341
- componentWillUnmount(): void {
342
- this._settings.changed.disconnect(this._settingsChanged);
343
- this._secretsManager?.fieldVisibilityChanged.disconnect(
344
- this._fieldVisibilityChanged
345
- );
346
- if (!this._secretsManager || !this._useSecretsManager) {
347
- return;
348
- }
349
- this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
350
- }
351
-
352
- /**
353
- * Get the current provider from the local storage.
354
- */
355
- getCurrentProvider(): string {
356
- const settings = this.props.aiSettings.getLocalStorage(this._role);
357
- return settings['_current'] ?? 'None';
358
- }
359
-
360
- /**
361
- * Save the current provider to the local storage.
362
- */
363
- saveCurrentProvider(): void {
364
- this.props.aiSettings.setLocalStorageItem(
365
- this._role,
366
- '_current',
367
- this._provider
368
- );
369
- }
370
-
371
- /**
372
- * Get settings from local storage for the current provider provider.
373
- */
374
- getSettingsFromLocalStorage(): IDict<any> {
375
- const settings = this.props.aiSettings.getLocalStorage(this._role);
376
- return settings[this._provider] ?? { provider: this._provider };
377
- }
378
-
379
- /**
380
- * Save settings in local storage for a given provider.
381
- */
382
- saveSettingsToLocalStorage() {
383
- const currentSettings = { ...this._currentSettings };
384
- // Do not save secrets in local storage if using the secrets manager.
385
- if (this._useSecretsManager) {
386
- this._secretFields.forEach(field => delete currentSettings[field]);
387
- }
388
- this.props.aiSettings.setLocalStorageItem(
389
- this._role,
390
- this._provider,
391
- currentSettings
392
- );
393
- }
394
-
395
- /**
396
- * Save the settings to the setting registry.
397
- */
398
- saveSettingsToRegistry(): void {
399
- const sanitizedSettings = { ...this._currentSettings };
400
- if (this._useSecretsManager) {
401
- this._secretFields.forEach(field => {
402
- sanitizedSettings[field] = SECRETS_REPLACEMENT;
403
- });
404
- }
405
-
406
- this.props.aiSettings.saveSettingsToRegistry(this._role, {
407
- provider: this._provider,
408
- ...sanitizedSettings
409
- });
410
- }
411
-
412
- /**
413
- * Triggered when the settings has changed.
414
- */
415
- private _settingsChanged = (settings: ISettingRegistry.ISettings) => {
416
- this._updateUseSecretsManager(
417
- (this._settings.get('UseSecretsManager').composite as boolean) ?? true
418
- );
419
- };
420
-
421
- /**
422
- * Triggered when the secret fields visibility has changed.
423
- */
424
- private _fieldVisibilityChanged = (
425
- _: ISecretsManager,
426
- value: boolean
427
- ): void => {
428
- if (this._useSecretsManager) {
429
- this._updateSchema();
430
- }
431
- };
432
-
433
- /**
434
- * Update the settings whether the secrets manager is used or not.
435
- *
436
- * @param value - whether to use the secrets manager or not.
437
- */
438
- private _updateUseSecretsManager = (value: boolean) => {
439
- // No-op if the value did not change or the secrets manager has not been provided.
440
- if (value === this._useSecretsManager || this._secretsManager === null) {
441
- return;
442
- }
443
-
444
- // Update the secrets manager.
445
- this._useSecretsManager = value;
446
- if (!value) {
447
- // Detach all the password inputs attached to the secrets manager, and save the
448
- // current settings to the local storage to save the password.
449
- this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
450
- } else {
451
- // Remove all the keys stored locally.
452
- const settings = this.props.aiSettings.getLocalStorage(this._role);
453
- Object.keys(settings).forEach(provider => {
454
- Object.keys(settings[provider])
455
- .filter(key => key.toLowerCase().includes('key'))
456
- .forEach(key => {
457
- delete settings[provider][key];
458
- });
459
- });
460
- this.props.aiSettings.setLocalStorageItem(this._role, null, settings);
461
- }
462
- this._updateSchema();
463
- this.saveSettingsToLocalStorage();
464
- this.saveSettingsToRegistry();
465
- };
466
-
467
- /**
468
- * Build the schema for a given provider.
469
- */
470
- private _buildSchema(): JSONSchema7 {
471
- const schema = JSONExt.deepCopy(baseSettings) as any;
472
- this._uiSchema = {};
473
- const settingsSchema = this._providerRegistry.getSettingsSchema(
474
- this._provider
475
- );
476
-
477
- this._secretFields = [];
478
- this._defaultFormData = {};
479
- if (settingsSchema) {
480
- Object.entries(settingsSchema).forEach(([key, value]) => {
481
- if (key.toLowerCase().includes('key')) {
482
- this._secretFields.push(key);
483
-
484
- // If the secrets manager is not used, show the secrets fields.
485
- // If the secrets manager is used, check if the fields should be visible.
486
- const showSecretFields =
487
- !this._useSecretsManager ||
488
- (this._secretsManager?.secretFieldsVisibility ?? true);
489
- if (!showSecretFields) {
490
- return;
491
- }
492
-
493
- this._uiSchema[key] = { 'ui:widget': 'password' };
494
- }
495
- schema.properties[key] = value;
496
- if (value.default !== undefined) {
497
- this._defaultFormData[key] = value.default;
498
- }
499
- });
500
- }
501
-
502
- return schema as JSONSchema7;
503
- }
504
-
505
- /**
506
- * Update the schema state for the given provider, that trigger the re-rendering of
507
- * the component.
508
- */
509
- private _updateSchema() {
510
- const schema = this._buildSchema();
511
- this.setState({ schema });
512
- }
513
-
514
- /**
515
- * Render the markdown instructions for the current provider.
516
- */
517
- private async _renderInstruction(): Promise<void> {
518
- let instructions = this._providerRegistry.getInstructions(this._provider);
519
- if (!this._rmRegistry || !instructions) {
520
- this.setState({ instruction: null });
521
- return;
522
- }
523
- instructions = `---\n\n${instructions}\n\n---`;
524
- const renderer = this._rmRegistry.createRenderer(MD_MIME_TYPE);
525
- const model = this._rmRegistry.createModel({
526
- data: { [MD_MIME_TYPE]: instructions }
527
- });
528
- await renderer.renderModel(model);
529
- this.setState({ instruction: renderer.node });
530
- }
531
-
532
- /**
533
- * Check for compatibility of the provider with the current environment.
534
- * If the provider is not compatible, display an error message.
535
- */
536
- private async _checkProviderCompatibility(): Promise<void> {
537
- const compatibilityCheck = this._providerRegistry.getCompatibilityCheck(
538
- this._provider
539
- );
540
- if (!compatibilityCheck) {
541
- this.setState({ compatibilityError: null });
542
- return;
543
- }
544
- const error = await compatibilityCheck();
545
- if (!error) {
546
- this.setState({ compatibilityError: null });
547
- return;
548
- }
549
- const errorDiv = document.createElement('div');
550
- errorDiv.className = ERROR_CLASS;
551
- errorDiv.innerHTML = error;
552
- this.setState({ compatibilityError: error });
553
- }
554
-
555
- /**
556
- * Triggered when the provider has changed, to update the schema and values.
557
- * Update the Jupyterlab settings accordingly.
558
- */
559
- private _onProviderChanged = (e: IChangeEvent) => {
560
- const provider = e.formData.provider;
561
- if (provider === this._currentSettings.provider) {
562
- return;
563
- }
564
- this._provider = provider;
565
- this.saveCurrentProvider();
566
- this._updateSchema();
567
- this._renderInstruction();
568
- this._checkProviderCompatibility();
569
-
570
- // Initialize the current settings.
571
- const isModified = this._updatedFormData(
572
- this.getSettingsFromLocalStorage()
573
- );
574
- if (isModified !== this.state.isModified) {
575
- this.setState({ isModified });
576
- }
577
- this.saveSettingsToRegistry();
578
- };
579
-
580
- /**
581
- * Callback function called when the password input has been programmatically updated
582
- * with the secret manager.
583
- */
584
- private _onPasswordUpdated = (fieldName: string, value: string) => {
585
- this._currentSettings[fieldName] = value;
586
- this.saveSettingsToRegistry();
587
- };
588
-
589
- /**
590
- * Update the current settings with the new values from the form.
591
- *
592
- * @param data - The form data to update.
593
- * @returns - Boolean whether the form is not the default one.
594
- */
595
- private _updatedFormData(data: IDict): boolean {
596
- let isModified = false;
597
- Object.entries(data).forEach(([key, value]) => {
598
- if (this._defaultFormData[key] !== undefined) {
599
- if (value === undefined) {
600
- const schemaProperty = this.state.schema.properties?.[
601
- key
602
- ] as JSONSchema7;
603
- if (schemaProperty.type === 'string') {
604
- data[key] = '';
605
- }
606
- }
607
- if (value !== this._defaultFormData[key]) {
608
- isModified = true;
609
- }
610
- }
611
- });
612
- this._currentSettings = JSONExt.deepCopy(data);
613
- return isModified;
614
- }
615
-
616
- /**
617
- * Triggered when the form value has changed, to update the current settings and save
618
- * it in local storage.
619
- * Update the Jupyterlab settings accordingly.
620
- */
621
- private _onFormChanged = (e: IChangeEvent): void => {
622
- const { formData } = e;
623
- const isModified = this._updatedFormData(formData);
624
- this.saveSettingsToLocalStorage();
625
- this.saveSettingsToRegistry();
626
- if (isModified !== this.state.isModified) {
627
- this.setState({ isModified });
628
- }
629
- };
630
-
631
- /**
632
- * Handler for the "Restore to defaults" button - clears all
633
- * modified settings then calls `setFormData` to restore the
634
- * values.
635
- */
636
- private _reset = async (event: React.MouseEvent): Promise<void> => {
637
- event.stopPropagation();
638
- this._currentSettings = {
639
- ...this._currentSettings,
640
- ...this._defaultFormData
641
- };
642
- this.saveSettingsToLocalStorage();
643
- this.saveSettingsToRegistry();
644
- this.setState({ isModified: false });
645
- };
646
-
647
- render(): JSX.Element {
648
- return (
649
- <div ref={this._formRef}>
650
- <WrappedFormComponent
651
- formData={{ provider: this._provider }}
652
- schema={this._providerSchema}
653
- onChange={this._onProviderChanged}
654
- idPrefix={`jp-SettingsEditor-${PLUGIN_IDS.providerRegistry}-${this._role}`}
655
- />
656
- {this.state.compatibilityError !== null && (
657
- <div className={ERROR_CLASS}>
658
- <i className={'fas fa-exclamation-triangle'}></i>
659
- <span>{this.state.compatibilityError}</span>
660
- </div>
661
- )}
662
- {this.state.instruction !== null && (
663
- <details>
664
- <summary className={INSTRUCTION_CLASS}>Instructions</summary>
665
- <span
666
- ref={node =>
667
- node && node.replaceChildren(this.state.instruction!)
668
- }
669
- />
670
- </details>
671
- )}
672
- <div className="jp-SettingsHeader">
673
- <h3 title={this._provider}>{this._provider}</h3>
674
- <div className="jp-SettingsHeader-buttonbar">
675
- {this.state.isModified && (
676
- <Button className="jp-RestoreButton" onClick={this._reset}>
677
- Restore to Defaults
678
- </Button>
679
- )}
680
- </div>
681
- </div>
682
- <WrappedFormComponent
683
- formData={this._currentSettings}
684
- schema={this.state.schema}
685
- onChange={this._onFormChanged}
686
- uiSchema={this._uiSchema}
687
- idPrefix={`jp-SettingsEditor-${PLUGIN_IDS.providerRegistry}-${this._role}`}
688
- formContext={{
689
- ...this.props.formContext,
690
- defaultFormData: this._defaultFormData
691
- }}
692
- />
693
- </div>
694
- );
695
- }
696
-
697
- private _role: ModelRole;
698
- private _providerRegistry: IAIProviderRegistry;
699
- private _provider: string;
700
- private _providerSchema: JSONSchema7;
701
- private _useSecretsManager: boolean;
702
- private _rmRegistry: IRenderMimeRegistry | null;
703
- private _secretsManager: ISecretsManager | null;
704
- private _currentSettings: IDict<any> = { provider: 'None' };
705
- private _uiSchema: IDict<any> = {};
706
- private _settings: ISettingRegistry.ISettings;
707
- private _formRef = React.createRef<HTMLDivElement>();
708
- private _secretFields: string[] = [];
709
- private _defaultFormData: IDict<any> = {};
710
- }
711
-
712
- /**
713
- * The AI provider settings component namespace.
714
- */
715
- export namespace AiProviderSettings {
716
- /**
717
- * The AI provider settings component props.
718
- */
719
- export type props = FieldProps & {
720
- /**
721
- * Why this model is used for (chat or completion).
722
- */
723
- role: ModelRole;
724
- /**
725
- * The parent component which should handle:
726
- * - the get/set functions for local storage
727
- * - save settings using jupyter settings system
728
- */
729
- aiSettings: IAiSettings;
730
- };
731
- /**
732
- * The AI provider settings component states.
733
- */
734
- export type states = {
735
- /**
736
- * The schema of the settings.
737
- */
738
- schema: JSONSchema7;
739
- /**
740
- * The instructions for this provider.
741
- */
742
- instruction: HTMLElement | null;
743
- /**
744
- * An error if the model in not compatible with the current environment.
745
- */
746
- compatibilityError: string | null;
747
- /**
748
- * Whether the settings are modified from default or not.
749
- */
750
- isModified?: boolean;
751
- };
752
- }
753
-
754
- namespace Private {
755
- /**
756
- * The token to use with the secrets manager.
757
- */
758
- let secretsToken: symbol;
759
-
760
- /**
761
- * Set of the token.
762
- */
763
- export function setToken(value: symbol): void {
764
- secretsToken = value;
765
- }
766
-
767
- /**
768
- * get the token.
769
- */
770
- export function getToken(): symbol {
771
- return secretsToken;
772
- }
773
- }